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
	if (is_alias($ipaddr)) {
811
		foreach (config_get_path('aliases/alias', []) as $alias) {
812
			if ($alias['name'] == $ipaddr && !preg_match("/port/i", $alias['type'])) {
813
				return true;
814
			}
815
		}
816
		return false;
817
	} else {
818
		return is_ipaddr($ipaddr);
819
	}
820

    
821
}
822

    
823
/* returns true if $subnet is a valid IPv4 or IPv6 subnet in CIDR format
824
	false - if not a valid subnet
825
	true (numeric 4 or 6) - if valid, gives type of subnet */
826
function is_subnet($subnet) {
827
	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)) {
828
		if (is_ipaddrv4($parts[1]) && $parts[3] <= 32) {
829
			return 4;
830
		}
831
		if (is_ipaddrv6($parts[2]) && $parts[3] <= 128) {
832
			return 6;
833
		}
834
	}
835
	return false;
836
}
837

    
838
function is_v4($ip_or_subnet) {
839
	return is_ipaddrv4($ip_or_subnet) || is_subnetv4($ip_or_subnet);
840
}
841

    
842
function is_v6($ip_or_subnet) {
843
	return is_ipaddrv6($ip_or_subnet) || is_subnetv6($ip_or_subnet);
844
}
845

    
846
/* same as is_subnet() but accepts IPv4 only */
847
function is_subnetv4($subnet) {
848
	return (is_subnet($subnet) == 4);
849
}
850

    
851
/* same as is_subnet() but accepts IPv6 only */
852
function is_subnetv6($subnet) {
853
	return (is_subnet($subnet) == 6);
854
}
855

    
856
/* returns true if $subnet is a valid subnet in CIDR format or an alias thereof */
857
function is_subnetoralias($subnet) {
858
	global $aliastable;
859

    
860
	if (isset($aliastable[$subnet]) && is_subnet($aliastable[$subnet])) {
861
		return true;
862
	} else {
863
		return is_subnet($subnet);
864
	}
865
}
866

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

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

    
895
	// 2**N returns an exact result as an INT if possible, and a float/double if not.
896
	// 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
897
	$result = 2 ** $snsize;
898

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

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

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

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

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

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

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

    
962
/* return all PTR zones for a IPv6 network */
963
function get_v6_ptr_zones($subnet, $bits) {
964
	$result = array();
965

    
966
	if (!is_ipaddrv6($subnet)) {
967
		return $result;
968
	}
969

    
970
	if (!is_numericint($bits) || $bits > 128) {
971
		return $result;
972
	}
973

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

    
983
	/* Get network prefix */
984
	$small_subnet = Net_IPv6::getNetmask($subnet, $bits);
985

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

    
997
		/* Detect what part of IP should be increased */
998
		$change_part = (int) ($small_sn / 16);
999
		if ($small_sn % 16 == 0) {
1000
			$change_part--;
1001
		}
1002

    
1003
		/* Increase 1 to desired part */
1004
		$parts = explode(":", Net_IPv6::uncompress($small_subnet));
1005
		$parts[$change_part]++;
1006
		$small_subnet = implode(":", $parts);
1007
	}
1008

    
1009
	return $result;
1010
}
1011

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

    
1024
/* returns true if $hostname is just a valid hostname (top part without any of the domain part) */
1025
function is_unqualified_hostname($hostname) {
1026
	if (!is_string($hostname)) {
1027
		return false;
1028
	}
1029

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

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

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

    
1055
/* returns true if $domain is a valid domain name */
1056
function is_domain($domain, $allow_wildcard=false, $trailing_dot=true) {
1057
	if (!is_string($domain)) {
1058
		return false;
1059
	}
1060
	if (!$trailing_dot && ($domain[strlen($domain)-1] == ".")) {
1061
		return false;
1062
	}
1063
	if ($allow_wildcard) {
1064
		$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';
1065
	} else {
1066
		$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';
1067
	}
1068

    
1069
	if (preg_match($domain_regex, $domain)) {
1070
		return true;
1071
	} else {
1072
		return false;
1073
	}
1074
}
1075

    
1076
/* returns true if $macaddr is a valid MAC address */
1077
function is_macaddr($macaddr, $partial=false) {
1078
	$values = explode(":", $macaddr);
1079

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

    
1095
	return true;
1096
}
1097

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

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

    
1111
function is_validaliasname($name, $return_message = false, $object = "alias") {
1112
	/* Array of reserved words */
1113
	$reserved = array("port", "pass");
1114

    
1115
	if (!is_string($name) || strlen($name) >= 32 || preg_match('/(^_*$|^\d*$|[^a-z0-9_])/i', $name)) {
1116
		if ($return_message) {
1117
			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, _');
1118
		} else {
1119
			return false;
1120
		}
1121
	}
1122
	if (in_array($name, $reserved, true)) {
1123
		if ($return_message) {
1124
			return sprintf(gettext('The %1$s name must not be either of the reserved words %2$s or %3$s.'), $object, "'port'", "'pass'");
1125
		} else {
1126
			return false;
1127
		}
1128
	}
1129
	if (getprotobyname($name)) {
1130
		if ($return_message) {
1131
			return sprintf(gettext('The %1$s name must not be an IP protocol name such as TCP, UDP, ICMP etc.'), $object);
1132
		} else {
1133
			return false;
1134
		}
1135
	}
1136
	if (getservbyname($name, "tcp") || getservbyname($name, "udp")) {
1137
		if ($return_message) {
1138
			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);
1139
		} else {
1140
			return false;
1141
		}
1142
	}
1143
	if ($return_message) {
1144
		return sprintf(gettext('The %1$s name is valid.'), $object);
1145
	} else {
1146
		return true;
1147
	}
1148
}
1149

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

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

    
1162
	if (!is_array($values) || count($values) != 2) {
1163
		return false;
1164
	}
1165

    
1166
	if (!ctype_digit($values[0]) || !ctype_digit($values[1])) {
1167
		return false;
1168
	}
1169

    
1170
	$values[0] = intval($values[0]);
1171
	$values[1] = intval($values[1]);
1172

    
1173
	if ($values[0] >= $values[1]) {
1174
		return false;
1175
	}
1176

    
1177
	if ($values[0] < $min || $values[1] > $max) {
1178
		return false;
1179
	}
1180

    
1181
	return true;
1182
}
1183

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

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

    
1203
		foreach($netstatarr as $portstats){
1204
			array_push($port_info, $portstats['local']['port']);
1205
		}
1206
	}
1207

    
1208
	return in_array($port, $port_info);
1209
}
1210

    
1211
/* returns true if $portrange is a valid TCP/UDP portrange ("<port>:<port>") */
1212
function is_portrange($portrange) {
1213
	$ports = explode(":", $portrange);
1214

    
1215
	return (count($ports) == 2 && is_port($ports[0]) && is_port($ports[1]));
1216
}
1217

    
1218
/* returns true if $port is a valid TCP/UDP port number or range ("<port>:<port>") */
1219
function is_port_or_range($port) {
1220
	return (is_port($port) || is_portrange($port));
1221
}
1222

    
1223
/* returns true if $port is an alias that is a port type */
1224
function is_portalias($port) {
1225
	if (is_alias($port)) {
1226
		foreach (config_get_path('aliases/alias', []) as $alias) {
1227
			if ($alias['name'] == $port && preg_match("/port/i", $alias['type'])) {
1228
				return true;
1229
			}
1230
		}
1231
	}
1232
	return false;
1233
}
1234

    
1235
/* returns true if $port is a valid port number or an alias thereof */
1236
function is_port_or_alias($port) {
1237
	return (is_port($port) || is_portalias($port));
1238
}
1239

    
1240
/* returns true if $port is a valid TCP/UDP port number or range ("<port>:<port>") or an alias thereof */
1241
function is_port_or_range_or_alias($port) {
1242
	return (is_port($port) || is_portrange($port) || is_portalias($port));
1243
}
1244

    
1245
/* create ranges of sequential port numbers (200:215) and remove duplicates */
1246
function group_ports($ports, $kflc = false) {
1247
	if (!is_array($ports) || empty($ports)) {
1248
		return;
1249
	}
1250

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

    
1276
	$result = array();
1277
	foreach ($uniq as $idx => $port) {
1278
		if ($idx == 0) {
1279
			$result[] = $port;
1280
			continue;
1281
		}
1282

    
1283
		$last = end($result);
1284
		if (is_portrange($last)) {
1285
			list($begin, $end) = explode(":", $last);
1286
		} else {
1287
			$begin = $end = $last;
1288
		}
1289

    
1290
		if ($port == ($end+1)) {
1291
			$end++;
1292
			$result[count($result)-1] = "{$begin}:{$end}";
1293
		} else {
1294
			$result[] = $port;
1295
		}
1296
	}
1297

    
1298
	return array_merge($comments, $result);
1299
}
1300

    
1301
/* returns true if $val is a valid shaper bandwidth value */
1302
function is_valid_shaperbw($val) {
1303
	return (preg_match("/^(\d+(?:\.\d+)?)([MKG]?b|%)$/", $val));
1304
}
1305

    
1306
/* returns true if $test is in the range between $start and $end */
1307
function is_inrange_v4($test, $start, $end) {
1308
	if (!is_ipaddrv4($test) || !is_ipaddrv4($start) || !is_ipaddrv4($end)) {
1309
		return false;
1310
	}
1311

    
1312
	if (ip2ulong($test) <= ip2ulong($end) &&
1313
	    ip2ulong($test) >= ip2ulong($start)) {
1314
		return true;
1315
	}
1316

    
1317
	return false;
1318
}
1319

    
1320
/* returns true if $test is in the range between $start and $end */
1321
function is_inrange_v6($test, $start, $end) {
1322
	if (!is_ipaddrv6($test) || !is_ipaddrv6($start) || !is_ipaddrv6($end)) {
1323
		return false;
1324
	}
1325

    
1326
	if (inet_pton($test) <= inet_pton($end) &&
1327
	    inet_pton($test) >= inet_pton($start)) {
1328
		return true;
1329
	}
1330

    
1331
	return false;
1332
}
1333

    
1334
/* returns true if $test is in the range between $start and $end */
1335
function is_inrange($test, $start, $end) {
1336
	return is_ipaddrv6($test) ? is_inrange_v6($test, $start, $end) : is_inrange_v4($test, $start, $end);
1337
}
1338

    
1339
function build_vip_list($fif, $family = "all") {
1340
	$list = array('address' => gettext('Interface Address'));
1341

    
1342
	$viplist = get_configured_vip_list($family);
1343
	foreach ($viplist as $vip => $address) {
1344
		if ($fif == get_configured_vip_interface($vip)) {
1345
			$list[$vip] = "$address";
1346
			if (get_vip_descr($address)) {
1347
				$list[$vip] .= " (". get_vip_descr($address) .")";
1348
			}
1349
		}
1350
	}
1351

    
1352
	return($list);
1353
}
1354

    
1355
function get_configured_vip_list($family = 'all', $type = VIP_ALL) {
1356
	global $config;
1357

    
1358
	$list = array();
1359
	if (!array_key_exists('virtualip', $config) ||
1360
		!is_array($config['virtualip']) ||
1361
	    !is_array($config['virtualip']['vip']) ||
1362
	    empty($config['virtualip']['vip'])) {
1363
		return ($list);
1364
	}
1365

    
1366
	$viparr = &$config['virtualip']['vip'];
1367
	foreach ($viparr as $vip) {
1368

    
1369
		if ($type == VIP_CARP) {
1370
			if ($vip['mode'] != "carp")
1371
				continue;
1372
		} elseif ($type == VIP_IPALIAS) {
1373
			if ($vip['mode'] != "ipalias")
1374
				continue;
1375
		} else {
1376
			if ($vip['mode'] != "carp" && $vip['mode'] != "ipalias")
1377
				continue;
1378
		}
1379

    
1380
		if ($family == 'all' ||
1381
		    ($family == 'inet' && is_ipaddrv4($vip['subnet'])) ||
1382
		    ($family == 'inet6' && is_ipaddrv6($vip['subnet']))) {
1383
			$list["_vip{$vip['uniqid']}"] = $vip['subnet'];
1384
		}
1385
	}
1386
	return ($list);
1387
}
1388

    
1389
function get_configured_vip($vipinterface = '') {
1390

    
1391
	return (get_configured_vip_detail($vipinterface, 'all', 'vip'));
1392
}
1393

    
1394
function get_configured_vip_interface($vipinterface = '') {
1395

    
1396
	return (get_configured_vip_detail($vipinterface, 'all', 'iface'));
1397
}
1398

    
1399
function get_configured_vip_ipv4($vipinterface = '') {
1400

    
1401
	return (get_configured_vip_detail($vipinterface, 'inet', 'ip'));
1402
}
1403

    
1404
function get_configured_vip_ipv6($vipinterface = '') {
1405

    
1406
	return (get_configured_vip_detail($vipinterface, 'inet6', 'ip'));
1407
}
1408

    
1409
function get_configured_vip_subnetv4($vipinterface = '') {
1410

    
1411
	return (get_configured_vip_detail($vipinterface, 'inet', 'subnet'));
1412
}
1413

    
1414
function get_configured_vip_subnetv6($vipinterface = '') {
1415

    
1416
	return (get_configured_vip_detail($vipinterface, 'inet6', 'subnet'));
1417
}
1418

    
1419
function get_configured_vip_detail($vipinterface = '', $family = 'inet', $what = 'ip') {
1420
	global $config;
1421

    
1422
	if (empty($vipinterface) ||
1423
	    !is_array($config['virtualip']) ||
1424
	    !is_array($config['virtualip']['vip']) ||
1425
	    empty($config['virtualip']['vip'])) {
1426
		return (NULL);
1427
	}
1428

    
1429
	$viparr = &$config['virtualip']['vip'];
1430
	foreach ($viparr as $vip) {
1431
		if ($vip['mode'] != "carp" && $vip['mode'] != "ipalias") {
1432
			continue;
1433
		}
1434

    
1435
		if ($vipinterface != "_vip{$vip['uniqid']}") {
1436
			continue;
1437
		}
1438

    
1439
		switch ($what) {
1440
			case 'subnet':
1441
				if ($family == 'inet' && is_ipaddrv4($vip['subnet']))
1442
					return ($vip['subnet_bits']);
1443
				else if ($family == 'inet6' && is_ipaddrv6($vip['subnet']))
1444
					return ($vip['subnet_bits']);
1445
				break;
1446
			case 'iface':
1447
				return ($vip['interface']);
1448
				break;
1449
			case 'vip':
1450
				return ($vip);
1451
				break;
1452
			case 'ip':
1453
			default:
1454
				if ($family == 'inet' && is_ipaddrv4($vip['subnet'])) {
1455
					return ($vip['subnet']);
1456
				} else if ($family == 'inet6' && is_ipaddrv6($vip['subnet'])) {
1457
					return ($vip['subnet']);
1458
				}
1459
				break;
1460
		}
1461
		break;
1462
	}
1463

    
1464
	return (NULL);
1465
}
1466

    
1467
/* comparison function for sorting by the order in which interfaces are normally created */
1468
function compare_interface_friendly_names($a, $b) {
1469
	if ($a == $b) {
1470
		return 0;
1471
	} else if ($a == 'wan') {
1472
		return -1;
1473
	} else if ($b == 'wan') {
1474
		return 1;
1475
	} else if ($a == 'lan') {
1476
		return -1;
1477
	} else if ($b == 'lan') {
1478
		return 1;
1479
	}
1480

    
1481
	return strnatcmp($a, $b);
1482
}
1483

    
1484
/* return the configured interfaces list. */
1485
function get_configured_interface_list($withdisabled = false) {
1486
	global $config;
1487

    
1488
	$iflist = array();
1489

    
1490
	/* if list */
1491
	foreach ($config['interfaces'] as $if => $ifdetail) {
1492
		if (isset($ifdetail['enable']) || $withdisabled == true) {
1493
			$iflist[$if] = $if;
1494
		}
1495
	}
1496

    
1497
	return $iflist;
1498
}
1499

    
1500
/* return the configured interfaces list. */
1501
function get_configured_interface_list_by_realif($withdisabled = false) {
1502
	global $config;
1503

    
1504
	$iflist = array();
1505

    
1506
	/* if list */
1507
	foreach ($config['interfaces'] as $if => $ifdetail) {
1508
		if (isset($ifdetail['enable']) || $withdisabled == true) {
1509
			$tmpif = get_real_interface($if);
1510
			if (!empty($tmpif)) {
1511
				$iflist[$tmpif] = $if;
1512
			}
1513
		}
1514
	}
1515

    
1516
	return $iflist;
1517
}
1518

    
1519
/* return the configured interfaces list with their description. */
1520
function get_configured_interface_with_descr($withdisabled = false) {
1521
	global $config, $user_settings;
1522

    
1523
	$iflist = array();
1524

    
1525
	/* if list */
1526
	foreach ($config['interfaces'] as $if => $ifdetail) {
1527
		if (isset($ifdetail['enable']) || $withdisabled == true) {
1528
			if (empty($ifdetail['descr'])) {
1529
				$iflist[$if] = strtoupper($if);
1530
			} else {
1531
				$iflist[$if] = strtoupper($ifdetail['descr']);
1532
			}
1533
		}
1534
	}
1535

    
1536
	if (is_array($user_settings) && array_get_path($user_settings, 'webgui/interfacessort')) {
1537
		asort($iflist);
1538
	}
1539

    
1540
	return $iflist;
1541
}
1542

    
1543
/*
1544
 *   get_configured_ip_addresses() - Return a list of all configured
1545
 *   IPv4 addresses.
1546
 *
1547
 */
1548
function get_configured_ip_addresses() {
1549
	global $config;
1550

    
1551
	if (!function_exists('get_interface_ip')) {
1552
		require_once("interfaces.inc");
1553
	}
1554
	$ip_array = array();
1555
	$interfaces = get_configured_interface_list();
1556
	if (is_array($interfaces)) {
1557
		foreach ($interfaces as $int) {
1558
			$ipaddr = get_interface_ip($int);
1559
			$ip_array[$int] = $ipaddr;
1560
		}
1561
	}
1562
	$interfaces = get_configured_vip_list('inet');
1563
	if (is_array($interfaces)) {
1564
		foreach ($interfaces as $int => $ipaddr) {
1565
			$ip_array[$int] = $ipaddr;
1566
		}
1567
	}
1568

    
1569
	/* pppoe server */
1570
	if (is_array($config['pppoes']) && is_array($config['pppoes']['pppoe'])) {
1571
		foreach ($config['pppoes']['pppoe'] as $pppoe) {
1572
			if ($pppoe['mode'] == "server") {
1573
				if (is_ipaddr($pppoe['localip'])) {
1574
					$int = "poes". $pppoe['pppoeid'];
1575
					$ip_array[$int] = $pppoe['localip'];
1576
				}
1577
			}
1578
		}
1579
	}
1580

    
1581
	return $ip_array;
1582
}
1583

    
1584
/*
1585
 *   get_configured_ipv6_addresses() - Return a list of all configured
1586
 *   IPv6 addresses.
1587
 *
1588
 */
1589
function get_configured_ipv6_addresses($linklocal_fallback = false) {
1590
	require_once("interfaces.inc");
1591
	$ipv6_array = array();
1592
	$interfaces = get_configured_interface_list();
1593
	if (is_array($interfaces)) {
1594
		foreach ($interfaces as $int) {
1595
			$ipaddrv6 = text_to_compressed_ip6(get_interface_ipv6($int, false, $linklocal_fallback));
1596
			$ipv6_array[$int] = $ipaddrv6;
1597
		}
1598
	}
1599
	$interfaces = get_configured_vip_list('inet6');
1600
	if (is_array($interfaces)) {
1601
		foreach ($interfaces as $int => $ipaddrv6) {
1602
			$ipv6_array[$int] = text_to_compressed_ip6($ipaddrv6);
1603
		}
1604
	}
1605
	return $ipv6_array;
1606
}
1607

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

    
1732
			case "friendly":
1733
				if ($friendly != "") {
1734
					$toput['if'] = $ifname;
1735
					$iflist[$friendly] = $toput;
1736
				}
1737
				break;
1738
			}
1739
		}
1740
	}
1741
	return $iflist;
1742
}
1743

    
1744
function get_lagg_interface_list() {
1745
	global $config;
1746

    
1747
	$plist = array();
1748
	if (isset($config['laggs']['lagg']) && is_array($config['laggs']['lagg'])) {
1749
		foreach ($config['laggs']['lagg'] as $lagg) {
1750
			$lagg['mac'] = get_interface_mac($lagg['laggif']);
1751
			$lagg['islagg'] = true;
1752
			$plist[$lagg['laggif']] = $lagg;
1753
		}
1754
	}
1755

    
1756
	return ($plist);
1757
}
1758

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

    
1781
/****f* util/log_auth
1782
* NAME
1783
*   log_auth   - Sends a string to syslog as LOG_AUTH facility
1784
* INPUTS
1785
*   $error     - string containing the syslog message.
1786
* RESULT
1787
*   null
1788
******/
1789
function log_auth($error) {
1790
	global $g;
1791
	$page = $_SERVER['SCRIPT_NAME'];
1792
	syslog(LOG_AUTH, "$page: $error");
1793
	if ($g['debug']) {
1794
		syslog(LOG_WARNING, var_export(debug_backtrace()));
1795
	}
1796
	return;
1797
}
1798

    
1799
/****f* util/exec_command
1800
 * NAME
1801
 *   exec_command - Execute a command and return a string of the result.
1802
 * INPUTS
1803
 *   $command   - String of the command to be executed.
1804
 * RESULT
1805
 *   String containing the command's result.
1806
 * NOTES
1807
 *   This function returns the command's stdout and stderr.
1808
 ******/
1809
function exec_command($command) {
1810
	$output = array();
1811
	exec($command . ' 2>&1', $output);
1812
	return(implode("\n", $output));
1813
}
1814

    
1815
/* wrapper for exec()
1816
   Executes in background or foreground.
1817
   For background execution, returns PID of background process to allow calling code control */
1818
function mwexec($command, $nologentry = false, $clearsigmask = false, $background = false) {
1819
	global $g;
1820
	$retval = 0;
1821

    
1822
	if ($g['debug']) {
1823
		if (!$_SERVER['REMOTE_ADDR']) {
1824
			echo "mwexec(): $command" . ($background ? " [BG]":"") . "\n";
1825
		}
1826
	}
1827
	if ($clearsigmask) {
1828
		$oldset = array();
1829
		pcntl_sigprocmask(SIG_SETMASK, array(), $oldset);
1830
	}
1831

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

    
1844
	if ($clearsigmask) {
1845
		pcntl_sigprocmask(SIG_SETMASK, $oldset);
1846
	}
1847

    
1848
	return $retval;
1849
}
1850

    
1851
/* wrapper for exec() in background */
1852
function mwexec_bg($command, $clearsigmask = false) {
1853
	return mwexec($command, false, $clearsigmask, true);
1854
}
1855

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

    
1880
/* make a global alias table (for faster lookups) */
1881
function alias_make_table() {
1882
	global $aliastable;
1883

    
1884
	$aliastable = array();
1885

    
1886
	init_config_arr(array('aliases', 'alias'));
1887
	foreach (config_get_path('aliases/alias', []) as $alias) {
1888
		if ($alias['name']) {
1889
			$aliastable[$alias['name']] = $alias['address'];
1890
		}
1891
	}
1892
}
1893

    
1894
/* check if an alias exists */
1895
function is_alias($name) {
1896
	global $aliastable;
1897

    
1898
	return isset($aliastable[$name]);
1899
}
1900

    
1901
function alias_get_type($name) {
1902

    
1903
	foreach (config_get_path('aliases/alias', []) as $alias) {
1904
		if ($name == $alias['name']) {
1905
			return $alias['type'];
1906
		}
1907
	}
1908

    
1909
	return "";
1910
}
1911

    
1912
/* expand a host or network alias, if necessary */
1913
function alias_expand($name) {
1914
	global $aliastable;
1915
	$urltable_prefix = "/var/db/aliastables/";
1916
	$urltable_filename = $urltable_prefix . $name . ".txt";
1917

    
1918
	if (isset($aliastable[$name])) {
1919
		// alias names cannot be strictly numeric. redmine #4289
1920
		if (is_numericint($name)) {
1921
			return null;
1922
		}
1923
		/*
1924
		 * make sure if it's a ports alias, it actually exists.
1925
		 * redmine #5845
1926
		 */
1927
		foreach (config_get_path('aliases/alias', []) as $alias) {
1928
			if ($alias['name'] == $name) {
1929
				if ($alias['type'] == "urltable_ports") {
1930
					if (is_URL($alias['url']) &&
1931
					    file_exists($urltable_filename) &&
1932
					    !empty(trim(file_get_contents($urltable_filename)))) {
1933
						return "\${$name}";
1934
					} else {
1935
						return null;
1936
					}
1937
				}
1938
			}
1939
		}
1940
		return "\${$name}";
1941
	} else if (is_ipaddr($name) || is_subnet($name) ||
1942
	    is_port_or_range($name)) {
1943
		return "{$name}";
1944
	} else {
1945
		return null;
1946
	}
1947
}
1948

    
1949
function alias_expand_urltable($name) {
1950
	$urltable_prefix = "/var/db/aliastables/";
1951
	$urltable_filename = $urltable_prefix . $name . ".txt";
1952

    
1953
	foreach (config_get_path('aliases/alias', []) as $alias) {
1954
		if (!preg_match("/urltable/i", $alias['type']) ||
1955
		    ($alias['name'] != $name)) {
1956
			continue;
1957
		}
1958

    
1959
		if (is_URL($alias["url"]) && file_exists($urltable_filename)) {
1960
			if (!filesize($urltable_filename)) {
1961
				// file exists, but is empty, try to sync
1962
				send_event("service sync alias {$name}");
1963
			}
1964
			return $urltable_filename;
1965
		} else {
1966
			send_event("service sync alias {$name}");
1967
			break;
1968
		}
1969
	}
1970
	return null;
1971
}
1972

    
1973
/* obtain MAC address given an IP address by looking at the ARP/NDP table */
1974
function arp_get_mac_by_ip($ip, $do_ping = true) {
1975
	unset($macaddr);
1976
	$retval = 1;
1977
	switch (is_ipaddr($ip)) {
1978
		case 4:
1979
			if ($do_ping === true) {
1980
				mwexec("/sbin/ping -c 1 -t 1 " . escapeshellarg($ip), true);
1981
			}
1982
			$macaddr = exec("/usr/sbin/arp -n " . escapeshellarg($ip) . " | /usr/bin/awk '{print $4}'", $output, $retval);
1983
			break;
1984
		case 6:
1985
			if ($do_ping === true) {
1986
				mwexec("/sbin/ping6 -c 1 -X 1 " . escapeshellarg($ip), true);
1987
			}
1988
			$macaddr = exec("/usr/sbin/ndp -n " . escapeshellarg($ip) . " | /usr/bin/awk '{print $2}'", $output, $retval);
1989
			break;
1990
	}
1991
	if ($retval == 0 && is_macaddr($macaddr)) {
1992
		return $macaddr;
1993
	} else {
1994
		return false;
1995
	}
1996
}
1997

    
1998
/* return a fieldname that is safe for xml usage */
1999
function xml_safe_fieldname($fieldname) {
2000
	$replace = array(
2001
	    '/', '-', ' ', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')',
2002
	    '_', '+', '=', '{', '}', '[', ']', '|', '/', '<', '>', '?',
2003
	    ':', ',', '.', '\'', '\\'
2004
	);
2005
	return strtolower(str_replace($replace, "", $fieldname));
2006
}
2007

    
2008
function mac_format($clientmac) {
2009
	global $config, $cpzone;
2010

    
2011
	$mac = explode(":", $clientmac);
2012
	$mac_format = $cpzone ? $config['captiveportal'][$cpzone]['radmac_format'] : false;
2013

    
2014
	switch ($mac_format) {
2015
		case 'singledash':
2016
			return "$mac[0]$mac[1]$mac[2]-$mac[3]$mac[4]$mac[5]";
2017

    
2018
		case 'ietf':
2019
			return "$mac[0]-$mac[1]-$mac[2]-$mac[3]-$mac[4]-$mac[5]";
2020

    
2021
		case 'cisco':
2022
			return "$mac[0]$mac[1].$mac[2]$mac[3].$mac[4]$mac[5]";
2023

    
2024
		case 'unformatted':
2025
			return "$mac[0]$mac[1]$mac[2]$mac[3]$mac[4]$mac[5]";
2026

    
2027
		default:
2028
			return $clientmac;
2029
	}
2030
}
2031

    
2032
function resolve_retry($hostname, $protocol = 'inet') {
2033
	$retries = 10;
2034
	$numrecords = 1;
2035
	$recresult = array();
2036

    
2037
	switch ($protocol) {
2038
		case 'any':
2039
			$checkproto = 'is_ipaddr';
2040
			$dnsproto = DNS_ANY;
2041
			$dnstype = array('A', 'AAAA');
2042
			break;
2043
		case 'inet6':
2044
			$checkproto = 'is_ipaddrv6';
2045
			$dnsproto = DNS_AAAA;
2046
			$dnstype = array('AAAA');
2047
			break;
2048
		case 'inet': 
2049
		default:
2050
			$checkproto = 'is_ipaddrv4';
2051
			$dnsproto = DNS_A;
2052
			$dnstype = array('A');
2053
			break;
2054
	}
2055

    
2056
	for ($i = 0; $i < $retries; $i++) {
2057
		if ($checkproto($hostname)) {
2058
			return $hostname;
2059
		}
2060

    
2061
		$dnsresult = @dns_get_record($hostname, $dnsproto);
2062

    
2063
		if (!empty($dnsresult)) {
2064
			foreach ($dnsresult as $ip) {
2065
				if (is_array($ip)) {
2066
					if (in_array($ip['type'], $dnstype)) {
2067
						if ($checkproto($ip['ip'])) { 
2068
							$recresult[] = $ip['ip'];
2069
						}
2070

    
2071
						if ($checkproto($ip['ipv6'])) { 
2072
							$recresult[] = $ip['ipv6'];
2073
						}
2074
					}
2075
				}
2076
			}
2077
		}
2078

    
2079
		// Return on success
2080
		if (!empty($recresult)) {
2081
			if ($numrecords == 1) {
2082
				return $recresult[0];
2083
			} else {
2084
				return array_slice($recresult, 0, $numrecords);
2085
			}
2086
		}
2087

    
2088
		usleep(100000);
2089
	}
2090

    
2091
	return false;
2092
}
2093

    
2094
function format_bytes($bytes) {
2095
	if ($bytes >= 1099511627776) {
2096
		return sprintf("%.2f TiB", $bytes/1099511627776);
2097
	} else if ($bytes >= 1073741824) {
2098
		return sprintf("%.2f GiB", $bytes/1073741824);
2099
	} else if ($bytes >= 1048576) {
2100
		return sprintf("%.2f MiB", $bytes/1048576);
2101
	} else if ($bytes >= 1024) {
2102
		return sprintf("%.0f KiB", $bytes/1024);
2103
	} else {
2104
		return sprintf("%d B", $bytes);
2105
	}
2106
}
2107

    
2108
function format_number($num, $precision = 3) {
2109
	$units = array('', 'K', 'M', 'G', 'T');
2110

    
2111
	$i = 0;
2112
	while ($num > 1000 && $i < count($units)) {
2113
		$num /= 1000;
2114
		$i++;
2115
	}
2116
	$num = round($num, $precision);
2117

    
2118
	return ("$num {$units[$i]}");
2119
}
2120

    
2121

    
2122
function unformat_number($formated_num) {
2123
	$num = strtoupper($formated_num);
2124
    
2125
	if ( strpos($num,"T") !== false ) {
2126
		$num = str_replace("T","",$num) * 1000 * 1000 * 1000 * 1000;
2127
	} else if ( strpos($num,"G") !== false ) {
2128
		$num = str_replace("G","",$num) * 1000 * 1000 * 1000;
2129
	} else if ( strpos($num,"M") !== false ) {
2130
		$num = str_replace("M","",$num) * 1000 * 1000;
2131
	} else if ( strpos($num,"K") !== false ) {
2132
		$num = str_replace("K","",$num) * 1000;
2133
	}
2134
    
2135
	return $num;
2136
}
2137

    
2138
function update_filter_reload_status($text, $new=false) {
2139
	global $g;
2140

    
2141
	if ($new) {
2142
		file_put_contents("{$g['varrun_path']}/filter_reload_status", $text  . PHP_EOL);
2143
	} else {
2144
		file_put_contents("{$g['varrun_path']}/filter_reload_status", $text  . PHP_EOL, FILE_APPEND);
2145
	}
2146
}
2147

    
2148
/****** util/return_dir_as_array
2149
 * NAME
2150
 *   return_dir_as_array - Return a directory's contents as an array.
2151
 * INPUTS
2152
 *   $dir          - string containing the path to the desired directory.
2153
 *   $filter_regex - string containing a regular expression to filter file names. Default empty.
2154
 * RESULT
2155
 *   $dir_array - array containing the directory's contents. This array will be empty if the path specified is invalid.
2156
 ******/
2157
function return_dir_as_array($dir, $filter_regex = '') {
2158
	$dir_array = array();
2159
	if (is_dir($dir)) {
2160
		if ($dh = opendir($dir)) {
2161
			while (($file = readdir($dh)) !== false) {
2162
				if (($file == ".") || ($file == "..")) {
2163
					continue;
2164
				}
2165

    
2166
				if (empty($filter_regex) || preg_match($filter_regex, $file)) {
2167
					array_push($dir_array, $file);
2168
				}
2169
			}
2170
			closedir($dh);
2171
		}
2172
	}
2173
	return $dir_array;
2174
}
2175

    
2176
function run_plugins($directory) {
2177
	/* process packager manager custom rules */
2178
	$files = return_dir_as_array($directory);
2179
	if (is_array($files)) {
2180
		foreach ($files as $file) {
2181
			if (stristr($file, ".sh") == true) {
2182
				mwexec($directory . $file . " start");
2183
			} else if (!is_dir($directory . "/" . $file) && stristr($file, ".inc")) {
2184
				require_once($directory . "/" . $file);
2185
			}
2186
		}
2187
	}
2188
}
2189

    
2190
/*
2191
 *    safe_mkdir($path, $mode = 0755)
2192
 *    create directory if it doesn't already exist and isn't a file!
2193
 */
2194
function safe_mkdir($path, $mode = 0755) {
2195
	if (!is_file($path) && !is_dir($path)) {
2196
		return @mkdir($path, $mode, true);
2197
	} else {
2198
		return false;
2199
	}
2200
}
2201

    
2202
/*
2203
 * get_sysctl($names)
2204
 * Get values of sysctl OID's listed in $names (accepts an array or a single
2205
 * name) and return an array of key/value pairs set for those that exist
2206
 */
2207
function get_sysctl($names) {
2208
	if (empty($names)) {
2209
		return array();
2210
	}
2211

    
2212
	if (is_array($names)) {
2213
		$name_list = array();
2214
		foreach ($names as $name) {
2215
			$name_list[] = escapeshellarg($name);
2216
		}
2217
	} else {
2218
		$name_list = array(escapeshellarg($names));
2219
	}
2220

    
2221
	exec("/sbin/sysctl -iq " . implode(" ", $name_list), $output);
2222
	$values = array();
2223
	foreach ($output as $line) {
2224
		$line = explode(": ", $line, 2);
2225
		if (count($line) == 2) {
2226
			$values[$line[0]] = $line[1];
2227
		}
2228
	}
2229

    
2230
	return $values;
2231
}
2232

    
2233
/*
2234
 * get_single_sysctl($name)
2235
 * Wrapper for get_sysctl() to simplify read of a single sysctl value
2236
 * return the value for sysctl $name or empty string if it doesn't exist
2237
 */
2238
function get_single_sysctl($name) {
2239
	if (empty($name)) {
2240
		return "";
2241
	}
2242

    
2243
	$value = get_sysctl($name);
2244
	if (empty($value) || !isset($value[$name])) {
2245
		return "";
2246
	}
2247

    
2248
	return $value[$name];
2249
}
2250

    
2251
/*
2252
 * set_sysctl($value_list)
2253
 * Set sysctl OID's listed as key/value pairs and return
2254
 * an array with keys set for those that succeeded
2255
 */
2256
function set_sysctl($values) {
2257
	if (empty($values)) {
2258
		return array();
2259
	}
2260

    
2261
	$value_list = array();
2262
	foreach ($values as $key => $value) {
2263
		$value_list[] = escapeshellarg($key) . "=" . escapeshellarg($value);
2264
	}
2265

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

    
2268
	/* Retry individually if failed (one or more read-only) */
2269
	if ($success <> 0 && count($value_list) > 1) {
2270
		foreach ($value_list as $value) {
2271
			exec("/sbin/sysctl -iq " . $value, $output);
2272
		}
2273
	}
2274

    
2275
	$ret = array();
2276
	foreach ($output as $line) {
2277
		$line = explode(": ", $line, 2);
2278
		if (count($line) == 2) {
2279
			$ret[$line[0]] = true;
2280
		}
2281
	}
2282

    
2283
	return $ret;
2284
}
2285

    
2286
/*
2287
 * set_single_sysctl($name, $value)
2288
 * Wrapper to set_sysctl() to make it simple to set only one sysctl
2289
 * returns boolean meaning if it succeeded
2290
 */
2291
function set_single_sysctl($name, $value) {
2292
	if (empty($name)) {
2293
		return false;
2294
	}
2295

    
2296
	$result = set_sysctl(array($name => $value));
2297

    
2298
	if (!isset($result[$name]) || $result[$name] != $value) {
2299
		return false;
2300
	}
2301

    
2302
	return true;
2303
}
2304

    
2305
/*
2306
 *     get_memory()
2307
 *     returns an array listing the amount of
2308
 *     memory installed in the hardware
2309
 *     [0] net memory available for the OS (FreeBSD) after some is taken by BIOS, video or whatever - e.g. 235 MBytes
2310
 *     [1] real (actual) memory of the system, should be the size of the RAM card/s - e.g. 256 MBytes
2311
 */
2312
function get_memory() {
2313
	$physmem = get_single_sysctl("hw.physmem");
2314
	$realmem = get_single_sysctl("hw.realmem");
2315
	/* convert from bytes to megabytes */
2316
	return array(($physmem/1048576), ($realmem/1048576));
2317
}
2318

    
2319
function mute_kernel_msgs() {
2320
	if (config_path_enabled('system','enableserial')) {
2321
		return;
2322
	}
2323
	exec("/sbin/conscontrol mute on");
2324
}
2325

    
2326
function unmute_kernel_msgs() {
2327
	exec("/sbin/conscontrol mute off");
2328
}
2329

    
2330
function start_devd() {
2331
	global $g;
2332

    
2333
	/* Generate hints for the kernel loader. */
2334
	$module_paths = explode(";", get_single_sysctl("kern.module_path"));
2335
	foreach ($module_paths as $path) {
2336
		if (!is_dir($path) || file_exists("{$path}/linker.hints")) {
2337
			continue;
2338
		}
2339
		if (($files = scandir($path)) == false) {
2340
			continue;
2341
		}
2342
		$found = false;
2343
		foreach ($files as $file) {
2344
			if (strlen($file) > 3 &&
2345
			    strcasecmp(substr($file, -3), ".ko") == 0) {
2346
				$found = true;
2347
				break;
2348
			}
2349
		}
2350
		if ($found == false) {
2351
			continue;
2352
		}
2353
		$_gb = exec("/usr/sbin/kldxref $path");
2354
		unset($_gb);
2355
	}
2356

    
2357
	/* Use the undocumented -q options of devd to quiet its log spamming */
2358
	$_gb = exec("/sbin/devd -q -f /etc/{$g['product_name']}-devd.conf");
2359
	sleep(1);
2360
	unset($_gb);
2361
}
2362

    
2363
function is_interface_vlan_mismatch() {
2364
	foreach (config_get_path('vlans/vlan', []) as $vlan) {
2365
		if (substr($vlan['if'], 0, 4) == "lagg") {
2366
			return false;
2367
		}
2368
		if (does_interface_exist($vlan['if']) == false) {
2369
			return true;
2370
		}
2371
	}
2372

    
2373
	return false;
2374
}
2375

    
2376
function is_interface_mismatch() {
2377
	global $config, $g;
2378

    
2379
	$do_assign = false;
2380
	$i = 0;
2381
	$missing_interfaces = array();
2382
	if (is_array($config['interfaces'])) {
2383
		foreach ($config['interfaces'] as $ifcfg) {
2384
			if (interface_is_vlan($ifcfg['if']) != NULL ||
2385
			    interface_is_qinq($ifcfg['if']) != NULL ||
2386
			    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'])) {
2387
				// Do not check these interfaces.
2388
				$i++;
2389
				continue;
2390
			} else if (does_interface_exist($ifcfg['if']) == false) {
2391
				$missing_interfaces[] = $ifcfg['if'];
2392
				$do_assign = true;
2393
			} else {
2394
				$i++;
2395
			}
2396
		}
2397
	}
2398

    
2399
	/* VLAN/QinQ-only interface mismatch detection
2400
	 * see https://redmine.pfsense.org/issues/12170 */
2401
	init_config_arr(array('vlans', 'vlan'));
2402
	foreach ($config['vlans']['vlan'] as $vlan) {
2403
		if (!does_interface_exist($vlan['if']) && 
2404
		    !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'])) {
2405
			$missing_interfaces[] = $vlan['if'];
2406
			$do_assign = true;
2407
		}
2408
	}
2409
	init_config_arr(array('qinqs', 'qinqentry'));
2410
	foreach ($config['qinqs']['qinqentry'] as $qinq) {
2411
		if (!does_interface_exist($qinq['if']) &&
2412
		    !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'])) {
2413
			$missing_interfaces[] = $qinq['if'];
2414
			$do_assign = true;
2415
		}
2416
	}
2417

    
2418
	if (file_exists("{$g['tmp_path']}/assign_complete")) {
2419
		$do_assign = false;
2420
	}
2421

    
2422
	if (!empty($missing_interfaces) && $do_assign) {
2423
		file_put_contents("{$g['tmp_path']}/missing_interfaces", implode(' ', $missing_interfaces));
2424
	} else {
2425
		@unlink("{$g['tmp_path']}/missing_interfaces");
2426
	}
2427

    
2428
	return $do_assign;
2429
}
2430

    
2431
/* sync carp entries to other firewalls */
2432
function carp_sync_client() {
2433
	send_event("filter sync");
2434
}
2435

    
2436
/****f* util/isAjax
2437
 * NAME
2438
 *   isAjax - reports if the request is driven from prototype
2439
 * INPUTS
2440
 *   none
2441
 * RESULT
2442
 *   true/false
2443
 ******/
2444
function isAjax() {
2445
	return isset ($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest';
2446
}
2447

    
2448
/****f* util/timeout
2449
 * NAME
2450
 *   timeout - console input with timeout countdown. Note: erases 2 char of screen for timer. Leave space.
2451
 * INPUTS
2452
 *   optional, seconds to wait before timeout. Default 9 seconds.
2453
 * RESULT
2454
 *   returns 1 char of user input or null if no input.
2455
 ******/
2456
function timeout($timer = 9) {
2457
	while (!isset($key)) {
2458
		if ($timer >= 9) {
2459
			echo chr(8) . chr(8) . ($timer == 9 ? chr(32) : null) . "{$timer}";
2460
		} else {
2461
			echo chr(8). "{$timer}";
2462
		}
2463
		`/bin/stty -icanon min 0 time 25`;
2464
		$key = trim(`KEY=\`dd count=1 2>/dev/null\`; echo \$KEY`);
2465
		`/bin/stty icanon`;
2466
		if ($key == '') {
2467
			unset($key);
2468
		}
2469
		$timer--;
2470
		if ($timer == 0) {
2471
			break;
2472
		}
2473
	}
2474
	return $key;
2475
}
2476

    
2477
/****f* util/msort
2478
 * NAME
2479
 *   msort - sort array
2480
 * INPUTS
2481
 *   $array to be sorted, field to sort by, direction of sort
2482
 * RESULT
2483
 *   returns newly sorted array
2484
 ******/
2485
function msort($array, $id = "id", $sort_ascending = true) {
2486
	$temp_array = array();
2487
	if (!is_array($array)) {
2488
		return $temp_array;
2489
	}
2490
	while (count($array)>0) {
2491
		$lowest_id = 0;
2492
		$index = 0;
2493
		foreach ($array as $item) {
2494
			if (isset($item[$id])) {
2495
				if ($array[$lowest_id][$id]) {
2496
					if (strtolower($item[$id]) < strtolower($array[$lowest_id][$id])) {
2497
						$lowest_id = $index;
2498
					}
2499
				}
2500
			}
2501
			$index++;
2502
		}
2503
		$temp_array[] = $array[$lowest_id];
2504
		$array = array_merge(array_slice($array, 0, $lowest_id), array_slice($array, $lowest_id + 1));
2505
	}
2506
	if ($sort_ascending) {
2507
		return $temp_array;
2508
	} else {
2509
		return array_reverse($temp_array);
2510
	}
2511
}
2512

    
2513
/****f* util/is_URL
2514
 * NAME
2515
 *   is_URL
2516
 * INPUTS
2517
 *   $url: string to check
2518
 *   $httponly: Only allow HTTP or HTTPS scheme
2519
 * RESULT
2520
 *   Returns true if item is a URL
2521
 ******/
2522
function is_URL($url, $httponly = false) {
2523
	$match = preg_match("'\b(([\w-]+://?|www[.])[^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|/)))'", $url);
2524
	if ($match) {
2525
		if ($httponly) {
2526
			$urlparts = parse_url($url);
2527
			return in_array(strtolower($urlparts['scheme']), array('http', 'https'));
2528
		} else {
2529
			return true;
2530
		}
2531
	}
2532
	return false;
2533
}
2534

    
2535
function is_file_included($file = "") {
2536
	$files = get_included_files();
2537
	if (in_array($file, $files)) {
2538
		return true;
2539
	}
2540

    
2541
	return false;
2542
}
2543

    
2544
/*
2545
 * Replace a value on a deep associative array using regex
2546
 */
2547
function array_replace_values_recursive($data, $match, $replace) {
2548
	if (empty($data)) {
2549
		return $data;
2550
	}
2551

    
2552
	if (is_string($data)) {
2553
		$data = preg_replace("/{$match}/", $replace, $data);
2554
	} else if (is_array($data)) {
2555
		foreach ($data as $k => $v) {
2556
			$data[$k] = array_replace_values_recursive($v, $match, $replace);
2557
		}
2558
	}
2559

    
2560
	return $data;
2561
}
2562

    
2563
/*
2564
	This function was borrowed from a comment on PHP.net at the following URL:
2565
	https://www.php.net/manual/en/function.array-merge-recursive.php#73843
2566
 */
2567
function array_merge_recursive_unique($array0, $array1) {
2568

    
2569
	$arrays = func_get_args();
2570
	$remains = $arrays;
2571

    
2572
	// We walk through each arrays and put value in the results (without
2573
	// considering previous value).
2574
	$result = array();
2575

    
2576
	// loop available array
2577
	foreach ($arrays as $array) {
2578

    
2579
		// The first remaining array is $array. We are processing it. So
2580
		// we remove it from remaining arrays.
2581
		array_shift($remains);
2582

    
2583
		// We don't care non array param, like array_merge since PHP 5.0.
2584
		if (is_array($array)) {
2585
			// Loop values
2586
			foreach ($array as $key => $value) {
2587
				if (is_array($value)) {
2588
					// we gather all remaining arrays that have such key available
2589
					$args = array();
2590
					foreach ($remains as $remain) {
2591
						if (array_key_exists($key, $remain)) {
2592
							array_push($args, $remain[$key]);
2593
						}
2594
					}
2595

    
2596
					if (count($args) > 2) {
2597
						// put the recursion
2598
						$result[$key] = call_user_func_array(__FUNCTION__, $args);
2599
					} else {
2600
						foreach ($value as $vkey => $vval) {
2601
							if (!is_array($result[$key])) {
2602
								$result[$key] = array();
2603
							}
2604
							$result[$key][$vkey] = $vval;
2605
						}
2606
					}
2607
				} else {
2608
					// simply put the value
2609
					$result[$key] = $value;
2610
				}
2611
			}
2612
		}
2613
	}
2614
	return $result;
2615
}
2616

    
2617

    
2618
/*
2619
 * converts a string like "a,b,c,d"
2620
 * into an array like array("a" => "b", "c" => "d")
2621
 */
2622
function explode_assoc($delimiter, $string) {
2623
	$array = explode($delimiter, $string);
2624
	$result = array();
2625
	$numkeys = floor(count($array) / 2);
2626
	for ($i = 0; $i < $numkeys; $i += 1) {
2627
		$result[$array[$i * 2]] = $array[$i * 2 + 1];
2628
	}
2629
	return $result;
2630
}
2631

    
2632
/*
2633
 * Given a string of text with some delimiter, look for occurrences
2634
 * of some string and replace all of those.
2635
 * $text - the text string (e.g. "abc,defg,x123,ipv4,xyz")
2636
 * $delimiter - the delimiter (e.g. ",")
2637
 * $element - the element to match (e.g. "defg")
2638
 * $replacement - the string to replace it with (e.g. "42")
2639
 * Returns the resulting delimited string (e.g. "abc,42,x123,ipv4,xyz")
2640
 */
2641
function replace_element_in_list($text, $delimiter, $element, $replacement) {
2642
	$textArray = explode($delimiter, $text);
2643
	while (($entry = array_search($element, $textArray)) !== false) {
2644
		$textArray[$entry] = $replacement;
2645
	}
2646
	return implode(',', $textArray);
2647
}
2648

    
2649
/* Return system's route table */
2650
function route_table() {
2651
	exec("/usr/bin/netstat --libxo json -nWr", $rawdata, $rc);
2652

    
2653
	if ($rc != 0) {
2654
		return array();
2655
	}
2656

    
2657
	$netstatarr = json_decode(implode(" ", $rawdata), JSON_OBJECT_AS_ARRAY);
2658
	$netstatarr = $netstatarr['statistics']['route-information']
2659
	    ['route-table']['rt-family'];
2660

    
2661
	$result = array();
2662
	$result['inet'] = array();
2663
	$result['inet6'] = array();
2664
	foreach ($netstatarr as $item) {
2665
		if ($item['address-family'] == 'Internet') {
2666
			$result['inet'] = $item['rt-entry'];
2667
		} else if ($item['address-family'] == 'Internet6') {
2668
			$result['inet6'] = $item['rt-entry'];
2669
		}
2670
	}
2671
	unset($netstatarr);
2672

    
2673
	return $result;
2674
}
2675

    
2676
/* check if route is static (not BGP/OSPF) */
2677
function is_static_route($target, $ipprotocol = '') {
2678
	if (is_v4($target) || (($target == 'default') && ($ipprotocol == 'inet'))) {
2679
		$inet = '4';
2680
	} elseif (is_v6($target) || (($target == 'default') && ($ipprotocol == 'inet6'))) {
2681
		$inet = '6';
2682
	} else {
2683
		return false;
2684
	}
2685

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

    
2690
	return false;
2691
}
2692

    
2693
/* Get static route for specific destination */
2694
function route_get($target, $ipprotocol = '', $useroute = false) {
2695
	global $config;
2696

    
2697
	if (!empty($ipprotocol)) {
2698
		$family = $ipprotocol;
2699
	} else if (is_v4($target)) {
2700
		$family = 'inet';
2701
	} else if (is_v6($target)) {
2702
		$family = 'inet6';
2703
	}
2704

    
2705
	if (empty($family)) {
2706
		return array();
2707
	}
2708

    
2709
	if ($useroute) {
2710
		if ($family == 'inet') {
2711
			$inet = '4';
2712
		} else {
2713
			$inet = '6';
2714
		}
2715
		$interface = exec("/sbin/route -n{$inet} get {$target} 2>/dev/null | /usr/bin/awk '/interface:/{print $2}'");
2716
		if (empty($interface)) {
2717
			return array();
2718
		} elseif ($interface == 'lo0') {
2719
			// interface assigned IP address
2720
			foreach (array_keys($config['interfaces']) as $intf) {
2721
				if ((($inet == '4') && (get_interface_ip($intf) == $target)) ||
2722
				    (($inet == '6') && (get_interface_ipv6($intf) == $target))) {
2723
					$interface = convert_friendly_interface_to_real_interface_name($intf);
2724
					$gateway = $interface;
2725
					break;
2726
				}
2727
			}
2728
		} else {
2729
			$gateway = exec("/sbin/route -n{$inet} get {$target} 2>/dev/null | /usr/bin/awk '/gateway:/{print $2}'");
2730
			if (!$gateway) {
2731
				// non-local gateway
2732
				$gateway = get_interface_mac($interface);
2733
			}
2734
		}
2735
		$result[] = array('gateway' => $gateway, 'interface-name' => $interface);
2736
	} else {
2737
		$rtable = route_table();
2738
		if (empty($rtable)) {
2739
			return array();
2740
		}
2741

    
2742
		$result = array();
2743
		foreach ($rtable[$family] as $item) {
2744
			if ($item['destination'] == $target ||
2745
			    ip_in_subnet($target, $item['destination'])) {
2746
				$result[] = $item;
2747
			}
2748
		}
2749
	}
2750

    
2751
	return $result;
2752
}
2753

    
2754
/* Get default route */
2755
function route_get_default($ipprotocol) {
2756
	if (empty($ipprotocol) || ($ipprotocol != 'inet' &&
2757
	    $ipprotocol != 'inet6')) {
2758
		return '';
2759
	}
2760

    
2761
	$route = route_get('default', $ipprotocol, true);
2762

    
2763
	if (empty($route)) {
2764
		return '';
2765
	}
2766

    
2767
	if (!isset($route[0]['gateway'])) {
2768
		return '';
2769
	}
2770

    
2771
	return $route[0]['gateway'];
2772
}
2773

    
2774
/* Delete a static route */
2775
function route_del($target, $ipprotocol = '') {
2776
	global $config;
2777

    
2778
	if (empty($target)) {
2779
		return;
2780
	}
2781

    
2782
	if (!empty($ipprotocol) && $ipprotocol != 'inet' &&
2783
	    $ipprotocol != 'inet6') {
2784
		return false;
2785
	}
2786

    
2787
	$route = route_get($target, $ipprotocol, true);
2788

    
2789
	if (empty($route)) {
2790
		return;
2791
	}
2792

    
2793
	$target_prefix = '';
2794
	if (is_subnet($target)) {
2795
		$target_prefix = '-net';
2796
	} else if (is_ipaddr($target)) {
2797
		$target_prefix = '-host';
2798
	}
2799

    
2800
	if (!empty($ipprotocol)) {
2801
		$target_prefix .= " -{$ipprotocol}";
2802
	} else if (is_v6($target)) {
2803
		$target_prefix .= ' -inet6';
2804
	} else if (is_v4($target)) {
2805
		$target_prefix .= ' -inet';
2806
	}
2807

    
2808
	foreach ($route as $item) {
2809
		if (substr($item['gateway'], 0, 5) == 'link#') {
2810
			continue;
2811
		}
2812

    
2813
		if (is_macaddr($item['gateway'])) {
2814
			$gw = '-iface ' . $item['interface-name'];
2815
		} else {
2816
			$gw = $item['gateway'];
2817
		}
2818

    
2819
		exec(escapeshellcmd("/sbin/route del {$target_prefix} " .
2820
		    "{$target} {$gw}"), $output, $rc);
2821

    
2822
		if (isset($config['system']['route-debug'])) {
2823
			log_error("ROUTING debug: " . microtime() .
2824
			    " - DEL RC={$rc} - {$target} - gw: " . $gw);
2825
			file_put_contents("/dev/console", "\n[" . getmypid() .
2826
			    "] ROUTE DEL: {$target_prefix} {$target} {$gw} " .
2827
			    "result: {$rc}");
2828
		}
2829
	}
2830
}
2831

    
2832
/*
2833
 * Add static route.  If it already exists, remove it and re-add
2834
 *
2835
 * $target - IP, subnet or 'default'
2836
 * $gw     - gateway address
2837
 * $iface  - Network interface
2838
 * $args   - Extra arguments for /sbin/route
2839
 * $ipprotocol - 'inet' or 'inet6'.  Mandatory when $target == 'default'
2840
 *
2841
 */
2842
function route_add_or_change($target, $gw, $iface = '', $args = '',
2843
    $ipprotocol = '') {
2844
	global $config;
2845

    
2846
	if (empty($target) || (empty($gw) && empty($iface))) {
2847
		return false;
2848
	}
2849

    
2850
	if ($target == 'default' && empty($ipprotocol)) {
2851
		return false;
2852
	}
2853

    
2854
	if (!empty($ipprotocol) && $ipprotocol != 'inet' &&
2855
	    $ipprotocol != 'inet6') {
2856
		return false;
2857
	}
2858

    
2859
	/* use '-host' for IPv6 /128 routes, see https://redmine.pfsense.org/issues/11594 */
2860
	if (is_subnetv4($target) || (is_subnetv6($target) && (subnet_size($target) > 1))) {
2861
		$target_prefix = '-net';
2862
	} else if (is_ipaddr($target)) {
2863
		$target_prefix = '-host';
2864
	}
2865

    
2866
	if (!empty($ipprotocol)) {
2867
		$target_prefix .= " -{$ipprotocol}";
2868
	} else if (is_v6($target)) {
2869
		$target_prefix .= ' -inet6';
2870
	} else if (is_v4($target)) {
2871
		$target_prefix .= ' -inet';
2872
	}
2873

    
2874
	/* If there is another route to the same target, remove it */
2875
	route_del($target, $ipprotocol);
2876

    
2877
	$params = '';
2878
	if (!empty($iface) && does_interface_exist($iface)) {
2879
		$params .= " -iface {$iface}";
2880
	}
2881
	if (is_ipaddr($gw)) {
2882
		/* set correct linklocal gateway address,
2883
		 * see https://redmine.pfsense.org/issues/11713 
2884
		 * and https://redmine.pfsense.org/issues/11806 */
2885
		if (is_ipaddrv6($gw) && is_linklocal($gw) && empty(get_ll_scope($gw))) {
2886
			$routeget = route_get($gw, 'inet6', true);
2887
			$gw .= "%" . $routeget[0]['interface-name'];
2888
		}
2889
		$params .= " " . $gw;
2890
	}
2891

    
2892
	if (empty($params)) {
2893
		log_error("route_add_or_change: Invalid gateway {$gw} and/or " .
2894
		    "network interface {$iface}");
2895
		return false;
2896
	}
2897

    
2898
	exec(escapeshellcmd("/sbin/route add {$target_prefix} " .
2899
	    "{$target} {$args} {$params}"), $output, $rc);
2900

    
2901
	if (isset($config['system']['route-debug'])) {
2902
		log_error("ROUTING debug: " . microtime() .
2903
		    " - ADD RC={$rc} - {$target} {$args}");
2904
		file_put_contents("/dev/console", "\n[" . getmypid() .
2905
		    "] ROUTE ADD: {$target_prefix} {$target} {$args} " .
2906
		    "{$params} result: {$rc}");
2907
	}
2908

    
2909
	return ($rc == 0);
2910
}
2911

    
2912
function set_ipv6routes_mtu($interface, $mtu) {
2913
	global $config;
2914

    
2915
	$ipv6mturoutes = array();
2916
	$if = convert_real_interface_to_friendly_interface_name($interface);
2917
	if (!$config['interfaces'][$if]['ipaddrv6']) {
2918
		return;
2919
	}
2920
	$a_gateways = return_gateways_array();
2921
	$a_staticroutes = get_staticroutes(false, false, true);
2922
	foreach ($a_gateways as $gate) {
2923
		foreach ($a_staticroutes as $sroute) {
2924
			if (($gate['interface'] == $interface) &&
2925
			    ($sroute['gateway'] == $gate['name'])) {
2926
				$tgt = $sroute['network'];
2927
				$gateway = $gate['gateway'];
2928
				$ipv6mturoutes[$tgt] = $gateway;
2929
			}
2930
		}
2931
		if (($gate['interface'] == $interface) &&
2932
		    $gate['isdefaultgw'] && is_ipaddrv6($gate['gateway'])) {
2933
			$tgt = "default";
2934
			$gateway = $gate['gateway'];
2935
			$ipv6mturoutes[$tgt] = $gateway;
2936
		}
2937
	}
2938
	foreach ($ipv6mturoutes as $tgt => $gateway) {
2939
		mwexec("/sbin/route change -6 -mtu " . escapeshellarg($mtu) .
2940
		    " " . escapeshellarg($tgt) . " " .
2941
		    escapeshellarg($gateway));
2942
	}
2943
}
2944

    
2945
function alias_to_subnets_recursive($name, $returnhostnames = false) {
2946
	global $aliastable;
2947
	$result = array();
2948
	if (!isset($aliastable[$name])) {
2949
		return $result;
2950
	}
2951
	$subnets = preg_split('/\s+/', $aliastable[$name]);
2952
	foreach ($subnets as $net) {
2953
		if (is_alias($net)) {
2954
			$sub = alias_to_subnets_recursive($net, $returnhostnames);
2955
			$result = array_merge($result, $sub);
2956
			continue;
2957
		} elseif (!is_subnet($net)) {
2958
			if (is_ipaddrv4($net)) {
2959
				$net .= "/32";
2960
			} else if (is_ipaddrv6($net)) {
2961
				$net .= "/128";
2962
			} else if ($returnhostnames === false || !is_fqdn($net)) {
2963
				continue;
2964
			}
2965
		}
2966
		$result[] = $net;
2967
	}
2968
	return $result;
2969
}
2970

    
2971
function get_staticroutes($returnsubnetsonly = false, $returnhostnames = false, $returnenabledroutesonly = false) {
2972
	global $config;
2973

    
2974
	/* Bail if there are no routes, but return an array always so callers don't have to check. */
2975
	init_config_arr(array('staticroutes', 'route'));
2976
	if (empty($config['staticroutes']['route'])) {
2977
		return array();
2978
	}
2979

    
2980
	$allstaticroutes = array();
2981
	$allsubnets = array();
2982
	/* Loop through routes and expand aliases as we find them. */
2983
	foreach ($config['staticroutes']['route'] as $route) {
2984
		if ($returnenabledroutesonly && isset($route['disabled'])) {
2985
			continue;
2986
		}
2987

    
2988
		if (is_alias($route['network'])) {
2989
			foreach (alias_to_subnets_recursive($route['network'], $returnhostnames) as $net) {
2990
				$temproute = $route;
2991
				$temproute['network'] = $net;
2992
				$allstaticroutes[] = $temproute;
2993
				$allsubnets[] = $net;
2994
			}
2995
		} elseif (is_subnet($route['network'])) {
2996
			$allstaticroutes[] = $route;
2997
			$allsubnets[] = $route['network'];
2998
		}
2999
	}
3000
	if ($returnsubnetsonly) {
3001
		return $allsubnets;
3002
	} else {
3003
		return $allstaticroutes;
3004
	}
3005
}
3006

    
3007
/****f* util/get_alias_list
3008
 * NAME
3009
 *   get_alias_list - Provide a list of aliases.
3010
 * INPUTS
3011
 *   $type          - Optional, can be a string or array specifying what type(s) of aliases you need.
3012
 * RESULT
3013
 *   Array containing list of aliases.
3014
 *   If $type is unspecified, all aliases are returned.
3015
 *   If $type is a string, all aliases of the type specified in $type are returned.
3016
 *   If $type is an array, all aliases of any type specified in any element of $type are returned.
3017
 */
3018
function get_alias_list($type = null) {
3019
	$result = array();
3020
	foreach (config_get_path('aliases/alias', []) as $alias) {
3021
		if ($type === null) {
3022
			$result[] = $alias['name'];
3023
		} else if (is_array($type)) {
3024
			if (in_array($alias['type'], $type)) {
3025
				$result[] = $alias['name'];
3026
			}
3027
		} else if ($type === $alias['type']) {
3028
			$result[] = $alias['name'];
3029
		}
3030
	}
3031
	return $result;
3032
}
3033

    
3034
/* returns an array consisting of every element of $haystack that is not equal to $needle. */
3035
function array_exclude($needle, $haystack) {
3036
	$result = array();
3037
	if (is_array($haystack)) {
3038
		foreach ($haystack as $thing) {
3039
			if ($needle !== $thing) {
3040
				$result[] = $thing;
3041
			}
3042
		}
3043
	}
3044
	return $result;
3045
}
3046

    
3047
/* Define what is preferred, IPv4 or IPv6 */
3048
function prefer_ipv4_or_ipv6() {
3049
	if (config_path_enabled('system', 'prefer_ipv4')) {
3050
		mwexec("/etc/rc.d/ip6addrctl prefer_ipv4");
3051
	} else {
3052
		mwexec("/etc/rc.d/ip6addrctl prefer_ipv6");
3053
	}
3054
}
3055

    
3056
/* Redirect to page passing parameters via POST */
3057
function post_redirect($page, $params) {
3058
	if (!is_array($params)) {
3059
		return;
3060
	}
3061

    
3062
	print "<html><body><form action=\"{$page}\" name=\"formredir\" method=\"post\">\n";
3063
	foreach ($params as $key => $value) {
3064
		print "<input type=\"hidden\" name=\"{$key}\" value=\"{$value}\" />\n";
3065
	}
3066
	print "</form>\n";
3067
	print "<script type=\"text/javascript\">\n";
3068
	print "//<![CDATA[\n";
3069
	print "document.formredir.submit();\n";
3070
	print "//]]>\n";
3071
	print "</script>\n";
3072
	print "</body></html>\n";
3073
}
3074

    
3075
/* Locate disks that can be queried for S.M.A.R.T. data. */
3076
function get_smart_drive_list() {
3077
	/* SMART supports some disks directly, and some controllers directly,
3078
	 * See https://redmine.pfsense.org/issues/9042 */
3079
	$supported_disk_types = array("ad", "da", "ada");
3080
	$supported_controller_types = array("nvme");
3081
	$disk_list = explode(" ", get_single_sysctl("kern.disks"));
3082
	foreach ($disk_list as $id => $disk) {
3083
		// We only want certain kinds of disks for S.M.A.R.T.
3084
		// 1 is a match, 0 is no match, False is any problem processing the regex
3085
		if (preg_match("/^(" . implode("|", $supported_disk_types) . ").*[0-9]{1,2}$/", $disk) !== 1) {
3086
			unset($disk_list[$id]);
3087
			continue;
3088
		}
3089
	}
3090
	foreach ($supported_controller_types as $controller) {
3091
		$devices = glob("/dev/{$controller}*");
3092
		if (!is_array($devices)) {
3093
			continue;
3094
		}
3095
		foreach ($devices as $device) {
3096
			$disk_list[] = basename($device);
3097
		}
3098
	}
3099
	sort($disk_list);
3100
	return $disk_list;
3101
}
3102

    
3103
// Validate a network address
3104
//	$addr: the address to validate
3105
//	$type: IPV4|IPV6|IPV4V6
3106
//	$label: the label used by the GUI to display this value. Required to compose an error message
3107
//	$err_msg: pointer to the callers error message array so that error messages can be added to it here
3108
//	$alias: are aliases permitted for this address?
3109
// Returns:
3110
//	IPV4 - if $addr is a valid IPv4 address
3111
//	IPV6 - if $addr is a valid IPv6 address
3112
//	ALIAS - if $alias=true and $addr is an alias
3113
//	false - otherwise
3114

    
3115
function validateipaddr(&$addr, $type, $label, &$err_msg, $alias=false) {
3116
	switch ($type) {
3117
		case IPV4:
3118
			if (is_ipaddrv4($addr)) {
3119
				return IPV4;
3120
			} else if ($alias) {
3121
				if (is_alias($addr)) {
3122
					return ALIAS;
3123
				} else {
3124
					$err_msg[] = sprintf(gettext("%s must be a valid IPv4 address or alias."), $label);
3125
					return false;
3126
				}
3127
			} else {
3128
				$err_msg[] = sprintf(gettext("%s must be a valid IPv4 address."), $label);
3129
				return false;
3130
			}
3131
		break;
3132
		case IPV6:
3133
			if (is_ipaddrv6($addr)) {
3134
				$addr = strtolower($addr);
3135
				return IPV6;
3136
			} else if ($alias) {
3137
				if (is_alias($addr)) {
3138
					return ALIAS;
3139
				} else {
3140
					$err_msg[] = sprintf(gettext("%s must be a valid IPv6 address or alias."), $label);
3141
					return false;
3142
				}
3143
			} else {
3144
				$err_msg[] = sprintf(gettext("%s must be a valid IPv6 address."), $label);
3145
				return false;
3146
			}
3147
		break;
3148
		case IPV4V6:
3149
			if (is_ipaddrv6($addr)) {
3150
				$addr = strtolower($addr);
3151
				return IPV6;
3152
			} else if (is_ipaddrv4($addr)) {
3153
				return IPV4;
3154
			} else if ($alias) {
3155
				if (is_alias($addr)) {
3156
					return ALIAS;
3157
				} else {
3158
					$err_msg[] = sprintf(gettext("%s must be a valid IPv4 or IPv6 address or alias."), $label);
3159
					return false;
3160
				}
3161
			} else {
3162
				$err_msg[] = sprintf(gettext("%s must be a valid IPv4 or IPv6 address."), $label);
3163
				return false;
3164
			}
3165
		break;
3166
	}
3167

    
3168
	return false;
3169
}
3170

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

    
3193
	/* Make hexstrings */
3194
	if ($duidtype) {
3195
		switch ($duidtype) {
3196
		/* Add a hardware type to DUID-LLT and DUID-LL; assume Ethernet */
3197
		case 1:
3198
		case 3:
3199
			$duidpt1 = '00:01:' . $duidpt1;
3200
			break;
3201
		/* Remove '-' from given UUID and insert ':' every 2 characters */
3202
		case 4:
3203
			$duidpt1 = implode(':', str_split(str_replace('-', '', $duidpt1), 2));
3204
			break;
3205
		default:
3206
		}
3207
		$duidpt1 = '00:0' . $duidtype . ':' . $duidpt1;
3208
	}
3209

    
3210
	$values = explode(':', strtolower(str_replace('-', ':', $duidpt1)));
3211

    
3212
	if (hexdec($values[0]) != count($values) - 2)
3213
		array_unshift($values, dechex(count($values)), '00');
3214

    
3215
	array_walk($values, function(&$value) {
3216
		$value = str_pad($value, 2, '0', STR_PAD_LEFT);
3217
	});
3218

    
3219
	return implode(":", $values);
3220
}
3221

    
3222
/* Returns true if $dhcp6duid is a valid DUID entry.
3223
 * Parse the entry to check for valid length according to known DUID types.
3224
 */
3225
function is_duid($dhcp6duid) {
3226
	$values = explode(":", $dhcp6duid);
3227
	if (hexdec($values[0]) == count($values) - 2) {
3228
		switch (hexdec($values[2] . $values[3])) {
3229
		case 0:
3230
			return false;
3231
			break;
3232
		case 1:
3233
			if (count($values) != 16 || strlen($dhcp6duid) != 47)
3234
				return false;
3235
			break;
3236
		case 3:
3237
			if (count($values) != 12 || strlen($dhcp6duid) != 35)
3238
				return false;
3239
			break;
3240
		case 4:
3241
			if (count($values) != 20 || strlen($dhcp6duid) != 59)
3242
				return false;
3243
			break;
3244
		/* DUID is up to 128 octets; allow 2 octets for type code, 2 more for option-len */
3245
		default:
3246
			if (count($values) > 132 || strlen($dhcp6duid) != count($values) * 3 - 1)
3247
				return false;
3248
		}
3249
	} else
3250
		return false;
3251

    
3252
	for ($i = 0; $i < count($values); $i++) {
3253
		if (ctype_xdigit($values[$i]) == false)
3254
			return false;
3255
		if (hexdec($values[$i]) < 0 || hexdec($values[$i]) > 255)
3256
			return false;
3257
	}
3258

    
3259
	return true;
3260
}
3261

    
3262
/* Write the DHCP6 DUID file */
3263
function write_dhcp6_duid($duidstring) {
3264
	// Create the hex array from the dhcp6duid config entry and write to file
3265
	global $g;
3266

    
3267
	if(!is_duid($duidstring)) {
3268
		log_error(gettext("Error: attempting to write DUID file - Invalid DUID detected"));
3269
		return false;
3270
	}
3271
	$temp = str_replace(":","",$duidstring);
3272
	$duid_binstring = pack("H*",$temp);
3273
	if ($fd = fopen("{$g['vardb_path']}/dhcp6c_duid", "wb")) {
3274
		fwrite($fd, $duid_binstring);
3275
		fclose($fd);
3276
		return true;
3277
	}
3278
	log_error(gettext("Error: attempting to write DUID file - File write error"));
3279
	return false;
3280
}
3281

    
3282
/* returns duid string from 'vardb_path']}/dhcp6c_duid' */
3283
function get_duid_from_file() {
3284
	global $g;
3285

    
3286
	$duid_ASCII = "";
3287
	$count = 0;
3288

    
3289
	if (file_exists("{$g['vardb_path']}/dhcp6c_duid") &&
3290
	    ($fd = fopen("{$g['vardb_path']}/dhcp6c_duid", "r"))) {
3291
		$fsize = filesize("{$g['vardb_path']}/dhcp6c_duid");
3292
		if ($fsize <= 132) {
3293
			$buffer = fread($fd, $fsize);
3294
			while($count < $fsize) {
3295
				$duid_ASCII .= bin2hex($buffer[$count]);
3296
				$count++;
3297
				if($count < $fsize) {
3298
					$duid_ASCII .= ":";
3299
				}
3300
			}
3301
		}
3302
		fclose($fd);
3303
	}
3304
	//if no file or error with read then the string returns blanked DUID string
3305
	if(!is_duid($duid_ASCII)) {
3306
		return "--:--:--:--:--:--:--:--:--:--:--:--:--:--:--:--";
3307
	}
3308
	return($duid_ASCII);
3309
}
3310

    
3311
/* Replaces the Mac OS 9 and earlier (\r) and DOS/Windows (\r\n) newlines with the Unix equivalent (\n). */
3312
function unixnewlines($text) {
3313
	return preg_replace('/\r\n?/', "\n", $text);
3314
}
3315

    
3316
function array_remove_duplicate($array, $field) {
3317
	$cmp = array();
3318
	foreach ($array as $sub) {
3319
		if (isset($sub[$field])) {
3320
			$cmp[] = $sub[$field];
3321
		}
3322
	}
3323
	$unique = array_unique(array_reverse($cmp, true));
3324
	foreach (array_keys($unique) as $k) {
3325
		$new[] = $array[$k];
3326
	}
3327
	return $new;
3328
}
3329

    
3330
/**
3331
 * Return a value specified by a path of keys in a nested array, if it exists.
3332
 * @param $arr array value to search
3333
 * @param $path string path with '/' separators
3334
 * @param $default mixed value to return if the path is not found
3335
 * @returns mixed value at path or $default if the path does not exist or if the
3336
 *          path keys an empty string and $default is non-null
3337
 */
3338
function array_get_path(array &$arr, string $path, $default = null) {
3339
	$vpath = explode('/', $path);
3340
	$el = $arr;
3341
	foreach ($vpath as $key) {
3342
		if (mb_strlen($key) == 0) {
3343
			continue;
3344
		}
3345
		if (is_array($el) && array_key_exists($key, $el)) {
3346
			$el = $el[$key];
3347
		} else {
3348
			return ($default);
3349
		}
3350
	}
3351

    
3352
	if (($default !== null) && ($el === '')) {
3353
		return ($default);
3354
	}
3355
	
3356
	return ($el);
3357
}
3358

    
3359
/*
3360
 * Initialize an arbitrary array multiple levels deep only if unset
3361
 * @param $arr top of array
3362
 * @param $path string path with '/' separators
3363
 */
3364
function array_init_path(mixed &$arr, ?string $path)
3365
{
3366
	if (!is_array($arr)) {
3367
		$arr = [];
3368
	}
3369
	if (is_null($path)) {
3370
		return;
3371
	}
3372
	$tmp = &$arr;
3373
	foreach (explode('/', $path) as $key) {
3374
		if (!is_array($tmp[$key])) {
3375
			$tmp[$key] = [];
3376
		}
3377
		$tmp = &$tmp[$key];
3378
	}
3379
}
3380

    
3381
/**
3382
 * Set a value by path in a nested array, creating arrays for intermediary keys
3383
 * as necessary. If the path cannot be reached because an intermediary exists
3384
 * but is not empty or an array, return $default.
3385
 * @param $arr array value to search
3386
 * @param $path string path with '/' separators
3387
 * @param $value mixed 
3388
 * @param $default mixed value to return if the path is not found
3389
 * @returns mixed $val or $default if the path prefix does not exist
3390
 */
3391
function array_set_path(array &$arr, string $path, $value, $default = null) {
3392
	$vpath = explode('/', $path);
3393
	$vkey = null;
3394
	do {
3395
		$vkey = array_pop($vpath);
3396
	} while (mb_strlen($vkey) == 0);
3397
	if ($vkey == null) {
3398
		return ($default);
3399
	}
3400
	$el =& $arr;
3401
	foreach ($vpath as $key) {
3402
		if (mb_strlen($key) == 0) {
3403
			continue;
3404
		}
3405
		if (array_key_exists($key, $el) && !empty($el[$key])) {
3406
			if (!is_array($el[$key])) {
3407
					return ($default);
3408
			}
3409
		} else {
3410
				$el[$key] = [];
3411
		}
3412
		$el =& $el[$key];
3413
	}
3414
	$el[$vkey] = $value;
3415
	return ($value);
3416
}
3417

    
3418
/**
3419
 * Determine whether a path in a nested array has a non-null value keyed by
3420
 * $enable_key. 
3421
 * @param $arr array value to search
3422
 * @param $path string path with '/' separators
3423
 * @param $enable_key string an optional alternative key value for the enable key
3424
 * @returns bool true if $enable_key exists in the array at $path, and has a
3425
 * non-null value, otherwise false
3426
 */
3427
function array_path_enabled(array &$arr, string $path, $enable_key = "enable") {
3428
	$el = array_get_path($arr, $path, []);
3429
	if (is_array($el) && isset($el[$enable_key])) {
3430
		return (true);
3431
	}
3432
	return (false);
3433
}
3434

    
3435
/**
3436
 * Remove a key from the nested array by path.
3437
 * @param $arr array value to search
3438
 * @param $path string path with '/' separators
3439
 * @returns array copy of the removed value or null
3440
 */
3441
function array_del_path(array &$arr, string $path) {
3442
	$vpath = explode('/', $path);
3443
	$vkey = array_pop($vpath);
3444
	$el =& $arr;
3445
	foreach($vpath as $key) {
3446
		if (mb_strlen($key) == 0) {
3447
			continue;
3448
		}
3449
		if (is_array($el) && array_key_exists($key, $el)) {
3450
			$el =& $el[$key];
3451
		} else {
3452
			return null;
3453
		}
3454
	}
3455
	$ret = $el[$vkey];
3456
	unset($el[$vkey]);
3457
	return ($ret);
3458
}
3459

    
3460

    
3461
function dhcpd_date_adjust_gmt($dt) {
3462
	init_config_arr(array('dhcpd'));
3463

    
3464
	foreach (config_get_path('dhcpd', []) as $dhcpditem) {
3465
		if ($dhcpditem['dhcpleaseinlocaltime'] == "yes") {
3466
			$ts = strtotime($dt . " GMT");
3467
			if ($ts !== false) {
3468
				return strftime("%Y/%m/%d %H:%M:%S", $ts);
3469
			}
3470
		}
3471
	}
3472

    
3473
	/*
3474
	 * If we did not need to convert to local time or the conversion
3475
	 * failed, just return the input.
3476
	 */
3477
	return $dt;
3478
}
3479

    
3480
global $supported_image_types;
3481
$supported_image_types = array(
3482
	IMAGETYPE_JPEG,
3483
	IMAGETYPE_PNG,
3484
	IMAGETYPE_GIF,
3485
	IMAGETYPE_WEBP
3486
);
3487

    
3488
function is_supported_image($image_filename) {
3489
	global $supported_image_types;
3490
	$img_info = getimagesize($image_filename);
3491

    
3492
	/* If it's not an image, or it isn't in the supported list, return false */
3493
	if (($img_info === false) ||
3494
	    !in_array($img_info[2], array_keys($supported_image_types))) {
3495
		return false;
3496
	} else {
3497
		return $img_info[2];
3498
	}
3499
}
3500

    
3501
function get_lagg_ports ($laggport) {
3502
	$laggp = array();
3503
	foreach ($laggport as $lgp) {
3504
		list($lpname, $lpinfo) = explode(" ", $lgp);
3505
		preg_match('~<(.+)>~', $lpinfo, $lgportmode);
3506
		if ($lgportmode[1]) {
3507
			$laggp[] = $lpname . " (" . $lgportmode[1] . ")";
3508
		} else {
3509
			$laggp[] = $lpname;
3510
		}
3511
	}
3512
	if ($laggp) {
3513
		return implode(", ", $laggp);
3514
	} else {
3515
		return false;
3516
	}
3517
}
3518

    
3519
function cisco_to_cidr($addr) {
3520
	if (!is_ipaddr($addr)) {
3521
		throw new Exception('Invalid IP Addr');
3522
	}
3523

    
3524
	$mask = decbin(~ip2long($addr));
3525
	$mask = substr($mask, -32);
3526
	$k = 0;
3527
	for ($i = 0; $i <= 32; $i++) {
3528
		$k += intval($mask[$i]);
3529
	}
3530
	return $k;
3531
}
3532

    
3533
function cisco_extract_index($prule) {
3534
	$index = explode("#", $prule);
3535
	if (is_numeric($index[1])) {
3536
		return intval($index[1]);
3537
	} else {
3538
		syslog(LOG_WARNING, "Error parsing rule {$prule}: Could not extract index");
3539
	}
3540
	return -1;;
3541
}
3542

    
3543
function parse_cisco_acl_rule($rule, $devname, $dir, $proto) {
3544
	$rule_orig = $rule;
3545
	$rule = explode(" ", $rule);
3546
	$tmprule = "";
3547
	$index = 0;
3548

    
3549
	if ($rule[$index] == "permit") {
3550
		$startrule = "pass {$dir} quick on {$devname} ";
3551
	} else if ($rule[$index] == "deny") {
3552
		$startrule = "block {$dir} quick on {$devname} ";
3553
	} else {
3554
		return;
3555
	}
3556

    
3557
	$index++;
3558

    
3559
	switch ($rule[$index]) {
3560
		case "ip":
3561
			break;
3562
		case "icmp":
3563
			$icmp = ($proto == "inet") ? "icmp" : "ipv6-icmp";
3564
			$tmprule .= "proto {$icmp} ";
3565
			break;
3566
		case "tcp":
3567
		case "udp":
3568
			$tmprule .= "proto {$rule[$index]} ";
3569
			break;
3570
		default:
3571
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid protocol.");
3572
			return;
3573
	}
3574
	$index++;
3575

    
3576
	/* Source */
3577
	if (trim($rule[$index]) == "host") {
3578
		$index++;
3579
		if ((($proto == 'inet') && (is_ipaddrv4(trim($rule[$index])) || (trim($rule[$index]) == "{clientip}"))) ||
3580
		    (($proto == 'inet6') && (is_ipaddrv6(trim($rule[$index])) || (trim($rule[$index]) == "{clientipv6}")))) {
3581
			if ($GLOBALS['attributes']['framed_ip']) {
3582
				$tmprule .= "from {$GLOBALS['attributes']['framed_ip']} ";
3583
			} else {
3584
				$tmprule .= "from {$rule[$index]} ";
3585
			}
3586
			$index++;
3587
		} else {
3588
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source host '{$rule[$index]}'.");
3589
			return;
3590
		}
3591
	} elseif (is_subnetv6(trim($rule[$index])) && ($proto == "inet6")) {
3592
		$tmprule .= "from {$rule[$index]} ";
3593
		$index++;
3594
	} elseif (trim($rule[$index]) == "any") {
3595
		$tmprule .= "from any ";
3596
		$index++;
3597
	} else {
3598
		$network = $rule[$index];
3599
		$netmask = $rule[++$index];
3600

    
3601
		if (is_ipaddrv4($network) && ($proto == "inet")) {
3602
			try {
3603
				$netmask = cisco_to_cidr($netmask);
3604
			} catch(Exception) {
3605
				syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source netmask '$netmask'.");
3606
				return;
3607
			}
3608
			$tmprule .= "from {$network}/{$netmask} ";
3609
		} else {
3610
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source network '$network'.");
3611
			return;
3612
		}
3613

    
3614
		$index++;
3615
	}
3616

    
3617
	/* Source Operator */
3618
	if (in_array(trim($rule[$index]), array("lt", "gt", "eq", "neq"))) {
3619
		switch(trim($rule[$index])) {
3620
			case "lt":
3621
				$operator = "<";
3622
				break;
3623
			case "gt":
3624
				$operator = ">";
3625
				break;
3626
			case "eq":
3627
				$operator = "=";
3628
				break;
3629
			case "neq":
3630
				$operator = "!=";
3631
				break;
3632
		}
3633

    
3634
		$port = $rule[++$index];
3635
		if (is_port($port)) {
3636
			$tmprule .= "port {$operator} {$port} ";
3637
		} else {
3638
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source port: '$port' not a numeric value between 0 and 65535.");
3639
			return;
3640
		}
3641
		$index++;
3642
	} else if (trim($rule[$index]) == "range") {
3643
		$port = array($rule[++$index], $rule[++$index]);
3644
		if (is_port($port[0]) && is_port($port[1])) {
3645
			$tmprule .= "port {$port[0]}:{$port[1]} ";
3646
		} else {
3647
			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.");
3648
			return;
3649
		}
3650
		$index++;
3651
	}
3652

    
3653
	/* Destination */
3654
	if (trim($rule[$index]) == "host") {
3655
		$index++;
3656
		if ((($proto == 'inet') && (is_ipaddrv4(trim($rule[$index])) || (trim($rule[$index]) == "{clientip}"))) ||
3657
		    (($proto == 'inet6') && (is_ipaddrv6(trim($rule[$index])) || (trim($rule[$index]) == "{clientipv6}")))) {
3658
			$tmprule .= "to {$rule[$index]} ";
3659
			$index++;
3660
		} else {
3661
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination host '{$rule[$index]}'.");
3662
			return;
3663
		}
3664
	} elseif (is_subnetv6(trim($rule[$index])) && ($proto == "inet6")) {
3665
		$tmprule .= "to {$rule[$index]} ";
3666
		$index++;
3667
	} elseif (trim($rule[$index]) == "any") {
3668
		$tmprule .= "to any ";
3669
		$index++;
3670
	} else {
3671
		$network = $rule[$index];
3672
		$netmask = $rule[++$index];
3673

    
3674
		if (is_ipaddrv4($network) && ($proto == "inet")) {
3675
			try {
3676
				$netmask = cisco_to_cidr($netmask);
3677
			} catch(Exception) {
3678
				syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination network '$network'.");
3679
				return;
3680
			}
3681
			$tmprule .= "to {$network}/{$netmask} ";
3682
		} else {
3683
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination network '$network'.");
3684
			return;
3685
		}
3686

    
3687
		$index++;
3688
	}
3689

    
3690
	/* Destination Operator */
3691
	if (in_array(trim($rule[$index]), array("lt", "gt", "eq", "neq"))) {
3692
		switch(trim($rule[$index])) {
3693
			case "lt":
3694
				$operator = "<";
3695
				break;
3696
			case "gt":
3697
				$operator = ">";
3698
				break;
3699
			case "eq":
3700
				$operator = "=";
3701
				break;
3702
			case "neq":
3703
				$operator = "!=";
3704
				break;
3705
		}
3706

    
3707
		$port = $rule[++$index];
3708
		if (is_port($port)) {
3709
			$tmprule .= "port {$operator} {$port} ";
3710
		} else {
3711
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination port: '$port' not a numeric value between 0 and 65535.");
3712
			return;
3713
		}
3714
		$index++;
3715
	} else if (trim($rule[$index]) == "range") {
3716
		$port = array($rule[++$index], $rule[++$index]);
3717
		if (is_port($port[0]) && is_port($port[1])) {
3718
			$tmprule .= "port {$port[0]}:{$port[1]} ";
3719
		} else {
3720
			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.");
3721
			return;
3722
		}
3723
		$index++;
3724
	}
3725

    
3726
	$tmprule = $startrule . $proto . " " . $tmprule;
3727
	return $tmprule;
3728
}
3729

    
3730
function parse_cisco_acl($attribs, $dev) {
3731
	global $attributes;
3732

    
3733
	if (!is_array($attribs)) {
3734
		return "";
3735
	}
3736
	$finalrules = "";
3737
	if (is_array($attribs['ciscoavpair'])) {
3738
		$inrules = array('inet' => array(), 'inet6' => array());
3739
		$outrules = array('inet' => array(), 'inet6' => array());
3740
		foreach ($attribs['ciscoavpair'] as $avrules) {
3741
			$rule = explode("=", $avrules);
3742
			$dir = "";
3743
			if (strstr($rule[0], "inacl")) {
3744
				$dir = "in";
3745
			} else if (strstr($rule[0], "outacl")) {
3746
				$dir = "out";
3747
			} else if (strstr($rule[0], "dns-servers")) {
3748
				$attributes['dns-servers'] = explode(" ", $rule[1]);
3749
				continue;
3750
			} else if (strstr($rule[0], "route")) {
3751
				if (!is_array($attributes['routes'])) {
3752
					$attributes['routes'] = array();
3753
				}
3754
				$attributes['routes'][] = $rule[1];
3755
				continue;
3756
			}
3757
			$rindex = cisco_extract_index($rule[0]);
3758
			if ($rindex < 0) {
3759
				continue;
3760
			}
3761

    
3762
			if (strstr($rule[0], "ipv6")) {
3763
				$proto = "inet6";
3764
			} else {
3765
				$proto = "inet";
3766
			}
3767

    
3768
			$tmprule = parse_cisco_acl_rule($rule[1], $dev, $dir, $proto);
3769
			if (!empty($tmprule)) {
3770
				if ($dir == "in") {
3771
					$inrules[$proto][$rindex] = $tmprule;
3772
				} else if ($dir == "out") {
3773
					$outrules[$proto][$rindex] = $tmprule;
3774
				}
3775
			}
3776
		}
3777

    
3778

    
3779
		$state = "";
3780
		foreach (array('inet', 'inet6') as $ip) {
3781
			if (!empty($outrules[$ip])) {
3782
				$state = "no state";
3783
			}
3784
			ksort($inrules[$ip], SORT_NUMERIC);
3785
			foreach ($inrules[$ip] as $inrule) {
3786
				$finalrules .= "{$inrule} {$state}\n";
3787
			}
3788
			if (!empty($outrules[$ip])) {
3789
				ksort($outrules[$ip], SORT_NUMERIC);
3790
				foreach ($outrules[$ip] as $outrule) {
3791
					$finalrules .= "{$outrule} {$state}\n";
3792
				}
3793
			}
3794
		}
3795
	}
3796
	return $finalrules;
3797
}
3798

    
3799
function alias_idn_to_utf8($alias) {
3800
	if (is_alias($alias)) {
3801
		return $alias;
3802
	} else {
3803
		return idn_to_utf8($alias);
3804
	}
3805
}
3806

    
3807
function alias_idn_to_ascii($alias) {
3808
	if (is_alias($alias)) {
3809
		return $alias;
3810
	} else {
3811
		return idn_to_ascii($alias);
3812
	}
3813
}
3814

    
3815
// These funtions were in guiconfig.inc but have been moved here so non GUI processes can use them
3816
function address_to_pconfig($adr, &$padr, &$pmask, &$pnot, &$pbeginport, &$pendport) {
3817
	if (isset($adr['any'])) {
3818
		$padr = "any";
3819
	} else if ($adr['network']) {
3820
		$padr = $adr['network'];
3821
	} else if ($adr['address']) {
3822
		list($padr, $pmask) = explode("/", $adr['address']);
3823
		if (!$pmask) {
3824
			if (is_ipaddrv6($padr)) {
3825
				$pmask = 128;
3826
			} else {
3827
				$pmask = 32;
3828
			}
3829
		}
3830
	}
3831

    
3832
	if (isset($adr['not'])) {
3833
		$pnot = 1;
3834
	} else {
3835
		$pnot = 0;
3836
	}
3837

    
3838
	if ($adr['port']) {
3839
		list($pbeginport, $pendport) = explode("-", $adr['port']);
3840
		if (!$pendport) {
3841
			$pendport = $pbeginport;
3842
		}
3843
	} else if (!is_alias($pbeginport) && !is_alias($pendport)) {
3844
		$pbeginport = "any";
3845
		$pendport = "any";
3846
	}
3847
}
3848

    
3849
function pconfig_to_address(&$adr, $padr, $pmask, $pnot = false, $pbeginport = 0, $pendport = 0, $addmask = false) {
3850
	$adr = array();
3851

    
3852
	if ($padr == "any") {
3853
		$adr['any'] = true;
3854
	} else if (is_specialnet($padr)) {
3855
		if ($addmask) {
3856
			$padr .= "/" . $pmask;
3857
		}
3858
		$adr['network'] = $padr;
3859
	} else {
3860
		$adr['address'] = $padr;
3861
		if (is_ipaddrv6($padr)) {
3862
			if ($pmask != 128) {
3863
				$adr['address'] .= "/" . $pmask;
3864
			}
3865
		} else {
3866
			if ($pmask != 32) {
3867
				$adr['address'] .= "/" . $pmask;
3868
			}
3869
		}
3870
	}
3871

    
3872
	if ($pnot) {
3873
		$adr['not'] = true;
3874
	} else {
3875
		unset($adr['not']);
3876
	}
3877

    
3878
	if (($pbeginport != 0) && ($pbeginport != "any")) {
3879
		if ($pbeginport != $pendport) {
3880
			$adr['port'] = $pbeginport . "-" . $pendport;
3881
		} else {
3882
			$adr['port'] = $pbeginport;
3883
		}
3884
	}
3885

    
3886
	/*
3887
	 * If the port is still unset, then it must not be numeric, but could
3888
	 * be an alias or a well-known/registered service.
3889
	 * See https://redmine.pfsense.org/issues/8410
3890
	 */
3891
	if (!isset($adr['port']) && is_port_or_alias($pbeginport)) {
3892
		$adr['port'] = $pbeginport;
3893
	}
3894
}
3895

    
3896
function is_specialnet($net) {
3897
	global $specialsrcdst;
3898

    
3899
	if (!$net) {
3900
		return false;
3901
	}
3902
	if (in_array($net, $specialsrcdst)) {
3903
		return true;
3904
	} else {
3905
		return false;
3906
	}
3907
}
3908

    
3909
function is_interface_ipaddr($interface) {
3910
	if (!empty(config_get_path("interfaces/{$interface}/ipaddr"))) {
3911
		return true;
3912
	}
3913
	return false;
3914
}
3915

    
3916
function is_interface_ipaddrv6($interface) {
3917
	if (!empty(config_get_path("interfaces/{$interface}/ipaddrv6"))) {
3918
		return true;
3919
	}
3920
	return false;
3921
}
3922

    
3923
function escape_filter_regex($filtertext) {
3924
	/* If the caller (user) has not already put a backslash before a slash, to escape it in the regex, */
3925
	/* then this will do it. Take out any "\/" already there, then turn all ordinary "/" into "\/".    */
3926
	return str_replace('/', '\/', str_replace('\/', '/', $filtertext));
3927
}
3928

    
3929
/*
3930
 * Check if a given pattern has the same number of two different unescaped
3931
 * characters.
3932
 * For example, it can ensure a pattern has balanced sets of parentheses,
3933
 * braces, and brackets.
3934
 */
3935
function is_pattern_balanced_char($pattern, $open, $close) {
3936
	/* First remove escaped versions */
3937
	$pattern = str_replace('\\' . $open, '', $pattern);
3938
	$pattern = str_replace('\\' . $close, '', $pattern);
3939
	/* Check if the counts of both characters match in the target pattern */
3940
	return (substr_count($pattern, $open) == substr_count($pattern, $close));
3941
}
3942

    
3943
/*
3944
 * Check if a pattern contains balanced sets of parentheses, braces, and
3945
 * brackets.
3946
 */
3947
function is_pattern_balanced($pattern) {
3948
	if (is_pattern_balanced_char($pattern, '(', ')') &&
3949
	    is_pattern_balanced_char($pattern, '{', '}') &&
3950
	    is_pattern_balanced_char($pattern, '[', ']')) {
3951
		/* Balanced if all are true */
3952
		return true;
3953
	}
3954
	return false;
3955
}
3956

    
3957
function cleanup_regex_pattern($filtertext) {
3958
	/* Cleanup filter to prevent backreferences. */
3959
	$filtertext = escape_filter_regex($filtertext);
3960

    
3961
	/* Remove \<digit>+ backreferences
3962
	 * To match \ it must be escaped as \\\\ in PHP for preg_replace() */
3963
	$filtertext = preg_replace('/\\\\\\d+/', '', $filtertext);
3964

    
3965
	/* Check for unbalanced parentheses, braces, and brackets which
3966
	 * may be an error or attempt to circumvent protections.
3967
	 * Also discard any pattern that attempts problematic duplication
3968
	 * methods. */
3969
	if (!is_pattern_balanced($filtertext) ||
3970
	    (substr_count($filtertext, ')*') > 0) ||
3971
	    (substr_count($filtertext, ')+') > 0) ||
3972
	    (substr_count($filtertext, '{') > 0)) {
3973
		return '';
3974
	}
3975

    
3976
	return $filtertext;
3977
}
3978

    
3979
function ip6_to_asn1($addr) {
3980
	/* IPv6 MIB uses an OCTET STRING of length 16 to represent
3981
	 * 128-bit IPv6 address in network byte order.
3982
	 * see https://datatracker.ietf.org/doc/html/rfc2465#section-3 
3983
	 * i.e. fc00:3::4 = 252.0.0.3.0.0.0.0.0.0.0.0.0.0.0.4
3984
	 */
3985

    
3986
	if (!is_ipaddrv6($addr)) {
3987
		return false;
3988
	}
3989
	$ipv6str = "";
3990
	$octstr = "";
3991
	foreach (explode(':', Net_IPv6::uncompress($addr)) as $v) {
3992
		$ipv6str .= str_pad($v, 4, '0', STR_PAD_LEFT);
3993
	}
3994
	foreach (str_split($ipv6str, 2) as $v) {
3995
		$octstr .= base_convert($v, 16, 10) . '.';
3996
	}
3997

    
3998
	return $octstr;
3999
}
4000

    
4001
function interfaces_interrupts() {
4002
	exec("/usr/bin/vmstat -i --libxo json", $rawdata, $rc);
4003
	$interrupts = array();
4004
	if ($rc == 0) {
4005
		$vnstatarr = json_decode(implode(" ", $rawdata), JSON_OBJECT_AS_ARRAY);
4006
		$interruptarr = $vnstatarr['interrupt-statistics']['interrupt'];
4007

    
4008
		foreach ($interruptarr as $int){
4009
			preg_match("/irq\d+: ([a-z0-9]+)/", $int['name'], $matches);
4010
			$name = $matches[1];
4011
			if (array_key_exists($name, $interrupts)) {
4012
				/* interface with multiple queues */
4013
				$interrupts[$name]['total'] += $int['total'];
4014
				$interrupts[$name]['rate'] += $int['rate'];
4015
			} else {
4016
				$interrupts[$name]['total'] = $int['total'];
4017
				$interrupts[$name]['rate'] = $int['rate'];
4018
			}
4019
		}
4020
	}
4021

    
4022
	return $interrupts;
4023
}
4024

    
4025
function dummynet_load_module($max_qlimit) {
4026
	if (!is_module_loaded("dummynet.ko")) {
4027
		mute_kernel_msgs();
4028
		mwexec("/sbin/kldload dummynet");
4029
		unmute_kernel_msgs();
4030
	}
4031
	$sysctls = (array(
4032
			"net.inet.ip.dummynet.io_fast" => "1",
4033
			"net.inet.ip.dummynet.hash_size" => "256",
4034
			"net.inet.ip.dummynet.pipe_slot_limit" => $max_qlimit
4035
	));
4036
	init_config_arr(array('sysctl', 'item'));
4037
	foreach (config_get_path('sysctl/item', []) as $item) {
4038
		if (preg_match('/net\.inet\.ip\.dummynet\./', $item['tunable'])) {
4039
			$sysctls[$item['tunable']] = $item['value'];
4040
		}
4041
	}
4042
	set_sysctl($sysctls);
4043
}
4044

    
4045
function get_interface_vip_ips($interface) {
4046
	global $config;
4047
	$vipips = '';
4048

    
4049
	init_config_arr(array('virtualip', 'vip'));
4050
	foreach ($config['virtualip']['vip'] as $vip) {
4051
		if (($vip['interface'] == $interface) &&
4052
		    (($vip['mode'] == "carp") || ($vip['mode'] == "ipalias"))) {
4053
			$vipips .= $vip['subnet'] . ' ';
4054
		}
4055
	}
4056
	return $vipips;
4057
}
4058

    
4059
?>
(54-54/62)