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-2023 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
{
174
	if (!is_resource($cfglckkey))
175
		return;
176

    
177
	flock($cfglckkey, LOCK_UN);
178
	fclose($cfglckkey);
179
}
180

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
373
	foreach ( $parts as $v ) {
374

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

    
378
	}
379

    
380
	return $binstr;
381
}
382

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

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

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

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

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

    
404
	return $ip;
405
}
406

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

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

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

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

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

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

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

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

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

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

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

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

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

    
518
	return $rangeaddresses;
519
}
520

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

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

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

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

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

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

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

    
582
		// 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)
583

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

    
592
		// 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)
593

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

    
603
		// this is the only edge case arising from increment/decrement.
604
		// 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)
605

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

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

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

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

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

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

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

    
641
	return $out;
642
}
643

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
809
/* returns true if $ipaddr is a valid dotted IPv4 address or an alias thereof */
810
function is_ipaddroralias($ipaddr) {
811
	if (is_alias($ipaddr)) {
812
		foreach (config_get_path('aliases/alias', []) as $alias) {
813
			if ($alias['name'] == $ipaddr && !preg_match("/port/i", $alias['type'])) {
814
				return true;
815
			}
816
		}
817
		return false;
818
	} else {
819
		return is_ipaddr($ipaddr);
820
	}
821

    
822
}
823

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1010
	return $result;
1011
}
1012

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

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

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

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

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

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

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

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

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

    
1096
	return true;
1097
}
1098

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

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

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

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

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

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

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

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

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

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

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

    
1182
	return true;
1183
}
1184

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1318
	return false;
1319
}
1320

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

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

    
1332
	return false;
1333
}
1334

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

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

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

    
1353
	return($list);
1354
}
1355

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1465
	return (NULL);
1466
}
1467

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

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

    
1485
/**
1486
 * Get the configured interfaces list
1487
 *
1488
 * @param bool $with_disabled Include disabled interfaces
1489
 *
1490
 * @return array
1491
 */
1492
function get_configured_interface_list(bool $with_disabled = false) : array
1493
{
1494
	$iflist = [];
1495
	foreach (config_get_path('interfaces', []) as $if => $if_detail) {
1496
		if ($with_disabled || isset($if_detail['enable'])) {
1497
			$iflist[$if] = $if;
1498
		}
1499
	}
1500

    
1501
	return ($iflist);
1502
}
1503

    
1504
/**
1505
 * Return the configured (and real) interfaces list.
1506
 *
1507
 * @param bool $with_disabled Include disabled interfaces
1508
 *
1509
 * @return array
1510
 */
1511
function get_configured_interface_list_by_realif(bool $with_disabled = false) : array
1512
{
1513
	$iflist = [];
1514
	foreach (config_get_path('interfaces', []) as $if => $if_detail) {
1515
		if ($with_disabled || isset($if_detail['enable'])) {
1516
			$tmpif = get_real_interface($if);
1517
			if (empty($tmpif)) {
1518
				continue;
1519
			}
1520
			$iflist[$tmpif] = $if;
1521
		}
1522
	}
1523

    
1524
	return ($iflist);
1525
}
1526

    
1527
/**
1528
 * Return the configured interfaces list with their description.
1529
 *
1530
 * @param bool $with_disabled Include disabled interfaces
1531
 *
1532
 * @return array
1533
 */
1534
function get_configured_interface_with_descr(bool $with_disabled = false) : array
1535
{
1536
	global $user_settings;
1537

    
1538
	$iflist = [];
1539
	foreach (config_get_path('interfaces', []) as $if => $if_detail) {
1540
		if ($with_disabled || isset($if_detail['enable'])) {
1541
			$iflist[$if] = strtoupper(array_get_path($if_detail, 'descr', $if));
1542
		}
1543
	}
1544

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

    
1550
	return ($iflist);
1551
}
1552

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

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

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

    
1591
	return $ip_array;
1592
}
1593

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

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

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

    
1754
function get_lagg_interface_list() {
1755
	global $config;
1756

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

    
1766
	return ($plist);
1767
}
1768

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

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

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

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

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

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

    
1855
	if ($clearsigmask) {
1856
		pcntl_sigprocmask(SIG_SETMASK, $oldset);
1857
	}
1858

    
1859
	return $retval;
1860
}
1861

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

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

    
1891
/* make a global alias table (for faster lookups) */
1892
function alias_make_table() {
1893
	global $aliastable;
1894

    
1895
	$aliastable = array();
1896

    
1897
	foreach (config_get_path('aliases/alias', []) as $alias) {
1898
		if ($alias['name']) {
1899
			$aliastable[$alias['name']] = $alias['address'];
1900
		}
1901
	}
1902
}
1903

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

    
1908
	return isset($aliastable[$name]);
1909
}
1910

    
1911
function alias_get_type($name) {
1912

    
1913
	foreach (config_get_path('aliases/alias', []) as $alias) {
1914
		if ($name == $alias['name']) {
1915
			return $alias['type'];
1916
		}
1917
	}
1918

    
1919
	return "";
1920
}
1921

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

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

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

    
1963
	foreach (config_get_path('aliases/alias', []) as $alias) {
1964
		if (!preg_match("/urltable/i", $alias['type']) ||
1965
		    ($alias['name'] != $name)) {
1966
			continue;
1967
		}
1968

    
1969
		if (is_URL($alias["url"]) && file_exists($urltable_filename)) {
1970
			if (!filesize($urltable_filename)) {
1971
				// file exists, but is empty, try to sync
1972
				send_event("service sync alias {$name}");
1973
			}
1974
			return $urltable_filename;
1975
		} else {
1976
			send_event("service sync alias {$name}");
1977
			break;
1978
		}
1979
	}
1980
	return null;
1981
}
1982

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

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

    
2018
function mac_format($clientmac) {
2019
	global $config, $cpzone;
2020

    
2021
	$mac = explode(":", $clientmac);
2022
	$mac_format = $cpzone ? $config['captiveportal'][$cpzone]['radmac_format'] : false;
2023

    
2024
	switch ($mac_format) {
2025
		case 'singledash':
2026
			return "$mac[0]$mac[1]$mac[2]-$mac[3]$mac[4]$mac[5]";
2027

    
2028
		case 'ietf':
2029
			return "$mac[0]-$mac[1]-$mac[2]-$mac[3]-$mac[4]-$mac[5]";
2030

    
2031
		case 'cisco':
2032
			return "$mac[0]$mac[1].$mac[2]$mac[3].$mac[4]$mac[5]";
2033

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

    
2037
		default:
2038
			return $clientmac;
2039
	}
2040
}
2041

    
2042
function resolve_retry($hostname, $protocol = 'inet') {
2043
	$retries = 10;
2044
	$numrecords = 1;
2045
	$recresult = array();
2046

    
2047
	switch ($protocol) {
2048
		case 'any':
2049
			$checkproto = 'is_ipaddr';
2050
			$dnsproto = DNS_ANY;
2051
			$dnstype = array('A', 'AAAA');
2052
			break;
2053
		case 'inet6':
2054
			$checkproto = 'is_ipaddrv6';
2055
			$dnsproto = DNS_AAAA;
2056
			$dnstype = array('AAAA');
2057
			break;
2058
		case 'inet': 
2059
		default:
2060
			$checkproto = 'is_ipaddrv4';
2061
			$dnsproto = DNS_A;
2062
			$dnstype = array('A');
2063
			break;
2064
	}
2065

    
2066
	for ($i = 0; $i < $retries; $i++) {
2067
		if ($checkproto($hostname)) {
2068
			return $hostname;
2069
		}
2070

    
2071
		$dnsresult = @dns_get_record($hostname, $dnsproto);
2072

    
2073
		if (!empty($dnsresult)) {
2074
			foreach ($dnsresult as $ip) {
2075
				if (is_array($ip)) {
2076
					if (in_array($ip['type'], $dnstype)) {
2077
						if ($checkproto($ip['ip'])) { 
2078
							$recresult[] = $ip['ip'];
2079
						}
2080

    
2081
						if ($checkproto($ip['ipv6'])) { 
2082
							$recresult[] = $ip['ipv6'];
2083
						}
2084
					}
2085
				}
2086
			}
2087
		}
2088

    
2089
		// Return on success
2090
		if (!empty($recresult)) {
2091
			if ($numrecords == 1) {
2092
				return $recresult[0];
2093
			} else {
2094
				return array_slice($recresult, 0, $numrecords);
2095
			}
2096
		}
2097

    
2098
		usleep(100000);
2099
	}
2100

    
2101
	return false;
2102
}
2103

    
2104
function format_bytes($bytes) {
2105
	if ($bytes >= 1099511627776) {
2106
		return sprintf("%.2f TiB", $bytes/1099511627776);
2107
	} else if ($bytes >= 1073741824) {
2108
		return sprintf("%.2f GiB", $bytes/1073741824);
2109
	} else if ($bytes >= 1048576) {
2110
		return sprintf("%.2f MiB", $bytes/1048576);
2111
	} else if ($bytes >= 1024) {
2112
		return sprintf("%.0f KiB", $bytes/1024);
2113
	} else {
2114
		return sprintf("%d B", $bytes);
2115
	}
2116
}
2117

    
2118
function format_number($num, $precision = 3) {
2119
	$units = array('', 'K', 'M', 'G', 'T');
2120

    
2121
	$i = 0;
2122
	while ((float) $num > 1000 && $i < count($units)) {
2123
		(float) $num /= 1000;
2124
		$i++;
2125
	}
2126
	$num = round((float) $num, $precision);
2127

    
2128
	return ("{$num} {$units[$i]}");
2129
}
2130

    
2131
function unformat_number($formated_num) {
2132
	$num = strtoupper($formated_num);
2133
    
2134
	if ( strpos($num,"T") !== false ) {
2135
		$num = str_replace("T","",$num) * 1000 * 1000 * 1000 * 1000;
2136
	} else if ( strpos($num,"G") !== false ) {
2137
		$num = str_replace("G","",$num) * 1000 * 1000 * 1000;
2138
	} else if ( strpos($num,"M") !== false ) {
2139
		$num = str_replace("M","",$num) * 1000 * 1000;
2140
	} else if ( strpos($num,"K") !== false ) {
2141
		$num = str_replace("K","",$num) * 1000;
2142
	}
2143
    
2144
	return $num;
2145
}
2146

    
2147
function update_filter_reload_status($text, $new=false) {
2148
	global $g;
2149

    
2150
	if ($new) {
2151
		file_put_contents("{$g['varrun_path']}/filter_reload_status", $text  . PHP_EOL);
2152
	} else {
2153
		file_put_contents("{$g['varrun_path']}/filter_reload_status", $text  . PHP_EOL, FILE_APPEND);
2154
	}
2155
}
2156

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

    
2175
				if (empty($filter_regex) || preg_match($filter_regex, $file)) {
2176
					array_push($dir_array, $file);
2177
				}
2178
			}
2179
			closedir($dh);
2180
		}
2181
	}
2182
	return $dir_array;
2183
}
2184

    
2185
function run_plugins($directory) {
2186
	/* process packager manager custom rules */
2187
	$files = return_dir_as_array($directory);
2188
	if (is_array($files)) {
2189
		foreach ($files as $file) {
2190
			if (stristr($file, ".sh") == true) {
2191
				mwexec($directory . $file . " start");
2192
			} else if (!is_dir($directory . "/" . $file) && stristr($file, ".inc")) {
2193
				require_once($directory . "/" . $file);
2194
			}
2195
		}
2196
	}
2197
}
2198

    
2199
/*
2200
 *    safe_mkdir($path, $mode = 0755)
2201
 *    create directory if it doesn't already exist and isn't a file!
2202
 */
2203
function safe_mkdir($path, $mode = 0755) {
2204
	if (!is_file($path) && !is_dir($path)) {
2205
		return @mkdir($path, $mode, true);
2206
	} else {
2207
		return false;
2208
	}
2209
}
2210

    
2211
/*
2212
 * get_sysctl($names)
2213
 * Get values of sysctl OID's listed in $names (accepts an array or a single
2214
 * name) and return an array of key/value pairs set for those that exist
2215
 */
2216
function get_sysctl($names) {
2217
	if (empty($names)) {
2218
		return array();
2219
	}
2220

    
2221
	if (is_array($names)) {
2222
		$name_list = array();
2223
		foreach ($names as $name) {
2224
			$name_list[] = escapeshellarg($name);
2225
		}
2226
	} else {
2227
		$name_list = array(escapeshellarg($names));
2228
	}
2229

    
2230
	exec("/sbin/sysctl -iq " . implode(" ", $name_list), $output);
2231
	$values = array();
2232
	foreach ($output as $line) {
2233
		$line = explode(": ", $line, 2);
2234
		if (count($line) == 2) {
2235
			$values[$line[0]] = $line[1];
2236
		}
2237
	}
2238

    
2239
	return $values;
2240
}
2241

    
2242
/*
2243
 * get_single_sysctl($name)
2244
 * Wrapper for get_sysctl() to simplify read of a single sysctl value
2245
 * return the value for sysctl $name or empty string if it doesn't exist
2246
 */
2247
function get_single_sysctl($name) {
2248
	if (empty($name)) {
2249
		return "";
2250
	}
2251

    
2252
	$value = get_sysctl($name);
2253
	if (empty($value) || !isset($value[$name])) {
2254
		return "";
2255
	}
2256

    
2257
	return $value[$name];
2258
}
2259

    
2260
/*
2261
 * set_sysctl($value_list)
2262
 * Set sysctl OID's listed as key/value pairs and return
2263
 * an array with keys set for those that succeeded
2264
 */
2265
function set_sysctl($values) {
2266
	if (empty($values)) {
2267
		return array();
2268
	}
2269

    
2270
	$value_list = array();
2271
	foreach ($values as $key => $value) {
2272
		$value_list[] = escapeshellarg($key) . "=" . escapeshellarg($value);
2273
	}
2274

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

    
2277
	/* Retry individually if failed (one or more read-only) */
2278
	if ($success <> 0 && count($value_list) > 1) {
2279
		foreach ($value_list as $value) {
2280
			exec("/sbin/sysctl -iq " . $value, $output);
2281
		}
2282
	}
2283

    
2284
	$ret = array();
2285
	foreach ($output as $line) {
2286
		$line = explode(": ", $line, 2);
2287
		if (count($line) == 2) {
2288
			$ret[$line[0]] = true;
2289
		}
2290
	}
2291

    
2292
	return $ret;
2293
}
2294

    
2295
/*
2296
 * set_single_sysctl($name, $value)
2297
 * Wrapper to set_sysctl() to make it simple to set only one sysctl
2298
 * returns boolean meaning if it succeeded
2299
 */
2300
function set_single_sysctl($name, $value) {
2301
	if (empty($name)) {
2302
		return false;
2303
	}
2304

    
2305
	$result = set_sysctl(array($name => $value));
2306

    
2307
	if (!isset($result[$name]) || $result[$name] != $value) {
2308
		return false;
2309
	}
2310

    
2311
	return true;
2312
}
2313

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

    
2328
function mute_kernel_msgs() {
2329
	if (config_path_enabled('system','enableserial')) {
2330
		return;
2331
	}
2332
	exec("/sbin/conscontrol mute on");
2333
}
2334

    
2335
function unmute_kernel_msgs() {
2336
	exec("/sbin/conscontrol mute off");
2337
}
2338

    
2339
function start_devd() {
2340
	global $g;
2341

    
2342
	/* Generate hints for the kernel loader. */
2343
	$module_paths = explode(";", get_single_sysctl("kern.module_path"));
2344
	foreach ($module_paths as $path) {
2345
		if (!is_dir($path) ||
2346
		    (($files = scandir($path)) == false)) {
2347
			continue;
2348
		}
2349
		$found = false;
2350
		foreach ($files as $file) {
2351
			if (strlen($file) > 3 &&
2352
			    strcasecmp(substr($file, -3), ".ko") == 0) {
2353
				$found = true;
2354
				break;
2355
			}
2356
		}
2357
		if ($found == false) {
2358
			continue;
2359
		}
2360
		$_gb = exec("/usr/sbin/kldxref $path");
2361
		unset($_gb);
2362
	}
2363

    
2364
	/* Use the undocumented -q options of devd to quiet its log spamming */
2365
	$_gb = exec("/sbin/devd -q -f /etc/{$g['product_name']}-devd.conf");
2366
	sleep(1);
2367
	unset($_gb);
2368
}
2369

    
2370
function is_interface_vlan_mismatch() {
2371
	foreach (config_get_path('vlans/vlan', []) as $vlan) {
2372
		if (substr($vlan['if'], 0, 4) == "lagg") {
2373
			return false;
2374
		}
2375
		if (does_interface_exist($vlan['if']) == false) {
2376
			return true;
2377
		}
2378
	}
2379

    
2380
	return false;
2381
}
2382

    
2383
function is_interface_mismatch() {
2384
	global $config, $g;
2385

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

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

    
2425
	if (file_exists("{$g['tmp_path']}/assign_complete")) {
2426
		$do_assign = false;
2427
	}
2428

    
2429
	if (!empty($missing_interfaces) && $do_assign) {
2430
		file_put_contents("{$g['tmp_path']}/missing_interfaces", implode(' ', $missing_interfaces));
2431
	} else {
2432
		@unlink("{$g['tmp_path']}/missing_interfaces");
2433
	}
2434

    
2435
	return $do_assign;
2436
}
2437

    
2438
/* sync carp entries to other firewalls */
2439
function carp_sync_client() {
2440
	send_event("filter sync");
2441
}
2442

    
2443
/****f* util/isAjax
2444
 * NAME
2445
 *   isAjax - reports if the request is driven from prototype
2446
 * INPUTS
2447
 *   none
2448
 * RESULT
2449
 *   true/false
2450
 ******/
2451
function isAjax() {
2452
	return isset ($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest';
2453
}
2454

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

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

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

    
2542
function is_file_included($file = "") {
2543
	$files = get_included_files();
2544
	if (in_array($file, $files)) {
2545
		return true;
2546
	}
2547

    
2548
	return false;
2549
}
2550

    
2551
/*
2552
 * Replace a value on a deep associative array using regex
2553
 */
2554
function array_replace_values_recursive($data, $match, $replace) {
2555
	if (empty($data)) {
2556
		return $data;
2557
	}
2558

    
2559
	if (is_string($data)) {
2560
		$data = preg_replace("/{$match}/", $replace, $data);
2561
	} else if (is_array($data)) {
2562
		foreach ($data as $k => $v) {
2563
			$data[$k] = array_replace_values_recursive($v, $match, $replace);
2564
		}
2565
	}
2566

    
2567
	return $data;
2568
}
2569

    
2570
/*
2571
	This function was borrowed from a comment on PHP.net at the following URL:
2572
	https://www.php.net/manual/en/function.array-merge-recursive.php#73843
2573
 */
2574
function array_merge_recursive_unique($array0, $array1) {
2575

    
2576
	$arrays = func_get_args();
2577
	$remains = $arrays;
2578

    
2579
	// We walk through each arrays and put value in the results (without
2580
	// considering previous value).
2581
	$result = array();
2582

    
2583
	// loop available array
2584
	foreach ($arrays as $array) {
2585

    
2586
		// The first remaining array is $array. We are processing it. So
2587
		// we remove it from remaining arrays.
2588
		array_shift($remains);
2589

    
2590
		// We don't care non array param, like array_merge since PHP 5.0.
2591
		if (is_array($array)) {
2592
			// Loop values
2593
			foreach ($array as $key => $value) {
2594
				if (is_array($value)) {
2595
					// we gather all remaining arrays that have such key available
2596
					$args = array();
2597
					foreach ($remains as $remain) {
2598
						if (array_key_exists($key, $remain)) {
2599
							array_push($args, $remain[$key]);
2600
						}
2601
					}
2602

    
2603
					if (count($args) > 2) {
2604
						// put the recursion
2605
						$result[$key] = call_user_func_array(__FUNCTION__, $args);
2606
					} else {
2607
						foreach ($value as $vkey => $vval) {
2608
							if (!is_array($result[$key])) {
2609
								$result[$key] = array();
2610
							}
2611
							$result[$key][$vkey] = $vval;
2612
						}
2613
					}
2614
				} else {
2615
					// simply put the value
2616
					$result[$key] = $value;
2617
				}
2618
			}
2619
		}
2620
	}
2621
	return $result;
2622
}
2623

    
2624

    
2625
/*
2626
 * converts a string like "a,b,c,d"
2627
 * into an array like array("a" => "b", "c" => "d")
2628
 */
2629
function explode_assoc($delimiter, $string) {
2630
	$array = explode($delimiter, $string);
2631
	$result = array();
2632
	$numkeys = floor(count($array) / 2);
2633
	for ($i = 0; $i < $numkeys; $i += 1) {
2634
		$result[$array[$i * 2]] = $array[$i * 2 + 1];
2635
	}
2636
	return $result;
2637
}
2638

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

    
2656
/* Return system's route table */
2657
function route_table() {
2658
	exec("/usr/bin/netstat --libxo json -nWr", $rawdata, $rc);
2659

    
2660
	if ($rc != 0) {
2661
		return array();
2662
	}
2663

    
2664
	$netstatarr = json_decode(implode(" ", $rawdata), JSON_OBJECT_AS_ARRAY);
2665
	$netstatarr = $netstatarr['statistics']['route-information']
2666
	    ['route-table']['rt-family'];
2667

    
2668
	$result = array();
2669
	$result['inet'] = array();
2670
	$result['inet6'] = array();
2671
	foreach ($netstatarr as $item) {
2672
		if ($item['address-family'] == 'Internet') {
2673
			$result['inet'] = $item['rt-entry'];
2674
		} else if ($item['address-family'] == 'Internet6') {
2675
			$result['inet6'] = $item['rt-entry'];
2676
		}
2677
	}
2678
	unset($netstatarr);
2679

    
2680
	return $result;
2681
}
2682

    
2683
/* check if route is static (not BGP/OSPF) */
2684
function is_static_route($target, $ipprotocol = '') {
2685
	if (is_v4($target) || (($target == 'default') && ($ipprotocol == 'inet'))) {
2686
		$inet = '4';
2687
	} elseif (is_v6($target) || (($target == 'default') && ($ipprotocol == 'inet6'))) {
2688
		$inet = '6';
2689
	} else {
2690
		return false;
2691
	}
2692

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

    
2697
	return false;
2698
}
2699

    
2700
/* Get static route for specific destination */
2701
function route_get($target, $ipprotocol = '', $useroute = false) {
2702
	global $config;
2703

    
2704
	if (!empty($ipprotocol)) {
2705
		$family = $ipprotocol;
2706
	} else if (is_v4($target)) {
2707
		$family = 'inet';
2708
	} else if (is_v6($target)) {
2709
		$family = 'inet6';
2710
	}
2711

    
2712
	if (empty($family)) {
2713
		return array();
2714
	}
2715

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

    
2749
		$result = array();
2750
		foreach ($rtable[$family] as $item) {
2751
			if ($item['destination'] == $target ||
2752
			    ip_in_subnet($target, $item['destination'])) {
2753
				$result[] = $item;
2754
			}
2755
		}
2756
	}
2757

    
2758
	return $result;
2759
}
2760

    
2761
/* Get default route */
2762
function route_get_default($ipprotocol) {
2763
	if (empty($ipprotocol) || ($ipprotocol != 'inet' &&
2764
	    $ipprotocol != 'inet6')) {
2765
		return '';
2766
	}
2767

    
2768
	$route = route_get('default', $ipprotocol, true);
2769

    
2770
	if (empty($route)) {
2771
		return '';
2772
	}
2773

    
2774
	if (!isset($route[0]['gateway'])) {
2775
		return '';
2776
	}
2777

    
2778
	return $route[0]['gateway'];
2779
}
2780

    
2781
/* Delete a static route */
2782
function route_del($target, $ipprotocol = '') {
2783
	global $config;
2784

    
2785
	if (empty($target)) {
2786
		return;
2787
	}
2788

    
2789
	if (!empty($ipprotocol) && $ipprotocol != 'inet' &&
2790
	    $ipprotocol != 'inet6') {
2791
		return false;
2792
	}
2793

    
2794
	$route = route_get($target, $ipprotocol, true);
2795

    
2796
	if (empty($route)) {
2797
		return;
2798
	}
2799

    
2800
	$target_prefix = '';
2801
	if (is_subnet($target)) {
2802
		$target_prefix = '-net';
2803
	} else if (is_ipaddr($target)) {
2804
		$target_prefix = '-host';
2805
	}
2806

    
2807
	if (!empty($ipprotocol)) {
2808
		$target_prefix .= " -{$ipprotocol}";
2809
	} else if (is_v6($target)) {
2810
		$target_prefix .= ' -inet6';
2811
	} else if (is_v4($target)) {
2812
		$target_prefix .= ' -inet';
2813
	}
2814

    
2815
	foreach ($route as $item) {
2816
		if (substr($item['gateway'], 0, 5) == 'link#') {
2817
			continue;
2818
		}
2819

    
2820
		if (is_macaddr($item['gateway'])) {
2821
			$gw = '-iface ' . $item['interface-name'];
2822
		} else {
2823
			$gw = $item['gateway'];
2824
		}
2825

    
2826
		exec(escapeshellcmd("/sbin/route del {$target_prefix} " .
2827
		    "{$target} {$gw}"), $output, $rc);
2828

    
2829
		if (isset($config['system']['route-debug'])) {
2830
			log_error("ROUTING debug: " . microtime() .
2831
			    " - DEL RC={$rc} - {$target} - gw: " . $gw);
2832
			file_put_contents("/dev/console", "\n[" . getmypid() .
2833
			    "] ROUTE DEL: {$target_prefix} {$target} {$gw} " .
2834
			    "result: {$rc}");
2835
		}
2836
	}
2837
}
2838

    
2839
/*
2840
 * Add static route.  If it already exists, remove it and re-add
2841
 *
2842
 * $target - IP, subnet or 'default'
2843
 * $gw     - gateway address
2844
 * $iface  - Network interface
2845
 * $args   - Extra arguments for /sbin/route
2846
 * $ipprotocol - 'inet' or 'inet6'.  Mandatory when $target == 'default'
2847
 *
2848
 */
2849
function route_add_or_change($target, $gw, $iface = '', $args = '',
2850
    $ipprotocol = '') {
2851
	global $config;
2852

    
2853
	if (empty($target) || (empty($gw) && empty($iface))) {
2854
		return false;
2855
	}
2856

    
2857
	if ($target == 'default' && empty($ipprotocol)) {
2858
		return false;
2859
	}
2860

    
2861
	if (!empty($ipprotocol) && $ipprotocol != 'inet' &&
2862
	    $ipprotocol != 'inet6') {
2863
		return false;
2864
	}
2865

    
2866
	/* use '-host' for IPv6 /128 routes, see https://redmine.pfsense.org/issues/11594 */
2867
	if (is_subnetv4($target) || (is_subnetv6($target) && (subnet_size($target) > 1))) {
2868
		$target_prefix = '-net';
2869
	} else if (is_ipaddr($target)) {
2870
		$target_prefix = '-host';
2871
	}
2872

    
2873
	if (!empty($ipprotocol)) {
2874
		$target_prefix .= " -{$ipprotocol}";
2875
	} else if (is_v6($target)) {
2876
		$target_prefix .= ' -inet6';
2877
	} else if (is_v4($target)) {
2878
		$target_prefix .= ' -inet';
2879
	}
2880

    
2881
	/* If there is another route to the same target, remove it */
2882
	route_del($target, $ipprotocol);
2883

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

    
2899
	if (empty($params)) {
2900
		log_error("route_add_or_change: Invalid gateway {$gw} and/or " .
2901
		    "network interface {$iface}");
2902
		return false;
2903
	}
2904

    
2905
	exec(escapeshellcmd("/sbin/route add {$target_prefix} " .
2906
	    "{$target} {$args} {$params}"), $output, $rc);
2907

    
2908
	if (isset($config['system']['route-debug'])) {
2909
		log_error("ROUTING debug: " . microtime() .
2910
		    " - ADD RC={$rc} - {$target} {$args}");
2911
		file_put_contents("/dev/console", "\n[" . getmypid() .
2912
		    "] ROUTE ADD: {$target_prefix} {$target} {$args} " .
2913
		    "{$params} result: {$rc}");
2914
	}
2915

    
2916
	return ($rc == 0);
2917
}
2918

    
2919
function set_ipv6routes_mtu($interface, $mtu) {
2920
	global $config;
2921

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

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

    
2979
function get_staticroutes($returnsubnetsonly = false, $returnhostnames = false, $returnenabledroutesonly = false) {
2980
	global $config;
2981

    
2982
	/* Bail if there are no routes, but return an array always so callers don't have to check. */
2983
	init_config_arr(array('staticroutes', 'route'));
2984
	if (empty($config['staticroutes']['route'])) {
2985
		return array();
2986
	}
2987

    
2988
	$allstaticroutes = array();
2989
	$allsubnets = array();
2990
	/* Loop through routes and expand aliases as we find them. */
2991
	foreach ($config['staticroutes']['route'] as $route) {
2992
		if ($returnenabledroutesonly && isset($route['disabled'])) {
2993
			continue;
2994
		}
2995

    
2996
		if (is_alias($route['network'])) {
2997
			foreach (alias_to_subnets_recursive($route['network'], $returnhostnames) as $net) {
2998
				$temproute = $route;
2999
				$temproute['network'] = $net;
3000
				$allstaticroutes[] = $temproute;
3001
				$allsubnets[] = $net;
3002
			}
3003
		} elseif (is_subnet($route['network'])) {
3004
			$allstaticroutes[] = $route;
3005
			$allsubnets[] = $route['network'];
3006
		}
3007
	}
3008
	if ($returnsubnetsonly) {
3009
		return $allsubnets;
3010
	} else {
3011
		return $allstaticroutes;
3012
	}
3013
}
3014

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

    
3042
/* Returns the current alias contents sorted by name in case insensitive and
3043
 * natural order.
3044
 * https://redmine.pfsense.org/issues/14015 */
3045
function get_sorted_aliases() {
3046
	$aliases = config_get_path('aliases/alias', []);
3047
	uasort($aliases, function ($a, $b) { return strnatcasecmp($a['name'], $b['name']); });
3048
	return $aliases;
3049
}
3050

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

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

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

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

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

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

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

    
3185
	return false;
3186
}
3187

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

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

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

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

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

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

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

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

    
3276
	return true;
3277
}
3278

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

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

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

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

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

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

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

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

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

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

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

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

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

    
3473
	if (!(is_array($el) && array_key_exists($vkey, $el))) {
3474
		return null;
3475
	}
3476

    
3477
	$ret = $el[$vkey];
3478
	unset($el[$vkey]);
3479
	return ($ret);
3480
}
3481

    
3482

    
3483
function dhcpd_date_adjust_gmt($dt) {
3484
	init_config_arr(array('dhcpd'));
3485

    
3486
	foreach (config_get_path('dhcpd', []) as $dhcpditem) {
3487
		if (empty($dhcpditem)) {
3488
			continue;
3489
		}
3490
		if ($dhcpditem['dhcpleaseinlocaltime'] == "yes") {
3491
			$ts = strtotime($dt . " GMT");
3492
			if ($ts !== false) {
3493
				return strftime("%Y/%m/%d %H:%M:%S", $ts);
3494
			}
3495
		}
3496
	}
3497

    
3498
	/*
3499
	 * If we did not need to convert to local time or the conversion
3500
	 * failed, just return the input.
3501
	 */
3502
	return $dt;
3503
}
3504

    
3505
global $supported_image_types;
3506
$supported_image_types = array(
3507
	IMAGETYPE_JPEG,
3508
	IMAGETYPE_PNG,
3509
	IMAGETYPE_GIF,
3510
	IMAGETYPE_WEBP
3511
);
3512

    
3513
function is_supported_image($image_filename) {
3514
	global $supported_image_types;
3515
	$img_info = getimagesize($image_filename);
3516

    
3517
	/* If it's not an image, or it isn't in the supported list, return false */
3518
	if (($img_info === false) ||
3519
	    !in_array($img_info[2], array_keys($supported_image_types))) {
3520
		return false;
3521
	} else {
3522
		return $img_info[2];
3523
	}
3524
}
3525

    
3526
function get_lagg_ports ($laggport) {
3527
	$laggp = array();
3528
	foreach ($laggport as $lgp) {
3529
		list($lpname, $lpinfo) = explode(" ", $lgp);
3530
		preg_match('~<(.+)>~', $lpinfo, $lgportmode);
3531
		if ($lgportmode[1]) {
3532
			$laggp[] = $lpname . " (" . $lgportmode[1] . ")";
3533
		} else {
3534
			$laggp[] = $lpname;
3535
		}
3536
	}
3537
	if ($laggp) {
3538
		return implode(", ", $laggp);
3539
	} else {
3540
		return false;
3541
	}
3542
}
3543

    
3544
function cisco_to_cidr($addr) {
3545
	if (!is_ipaddr($addr)) {
3546
		throw new Exception('Value is not in dotted quad notation.');
3547
	}
3548

    
3549
	$mask = decbin(~ip2long($addr));
3550
	$mask = substr($mask, -32);
3551
	$k = 0;
3552
	for ($i = 0; $i <= 32; $i++) {
3553
		$k += intval($mask[$i]);
3554
	}
3555
	return $k;
3556
}
3557

    
3558
function cisco_extract_index($prule) {
3559
	$index = explode("#", $prule);
3560
	if (is_numeric($index[1])) {
3561
		return intval($index[1]);
3562
	} else {
3563
		syslog(LOG_WARNING, "Error parsing rule {$prule}: Could not extract index");
3564
	}
3565
	return -1;;
3566
}
3567

    
3568
function parse_cisco_acl_rule($rule, $devname, $dir, $proto) {
3569
	$rule_orig = $rule;
3570
	$rule = explode(" ", $rule);
3571
	$tmprule = "";
3572
	$index = 0;
3573

    
3574
	if ($rule[$index] == "permit") {
3575
		$startrule = "pass {$dir} quick on {$devname} ";
3576
	} else if ($rule[$index] == "deny") {
3577
		$startrule = "block {$dir} quick on {$devname} ";
3578
	} else {
3579
		return;
3580
	}
3581

    
3582
	$index++;
3583

    
3584
	switch ($rule[$index]) {
3585
		case "ip":
3586
			break;
3587
		case "icmp":
3588
			$icmp = ($proto == "inet") ? "icmp" : "ipv6-icmp";
3589
			$tmprule .= "proto {$icmp} ";
3590
			break;
3591
		case "tcp":
3592
		case "udp":
3593
			$tmprule .= "proto {$rule[$index]} ";
3594
			break;
3595
		default:
3596
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid protocol.");
3597
			return;
3598
	}
3599
	$index++;
3600

    
3601
	/* Source */
3602
	if (trim($rule[$index]) == "host") {
3603
		$index++;
3604
		if ((($proto == 'inet') && (is_ipaddrv4(trim($rule[$index])) || (trim($rule[$index]) == "{clientip}"))) ||
3605
		    (($proto == 'inet6') && (is_ipaddrv6(trim($rule[$index])) || (trim($rule[$index]) == "{clientipv6}")))) {
3606
			if ($GLOBALS['attributes']['framed_ip']) {
3607
				$tmprule .= "from {$GLOBALS['attributes']['framed_ip']} ";
3608
			} else {
3609
				$tmprule .= "from {$rule[$index]} ";
3610
			}
3611
			$index++;
3612
		} else {
3613
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source host '{$rule[$index]}'.");
3614
			return;
3615
		}
3616
	} elseif (is_subnetv6(trim($rule[$index])) && ($proto == "inet6")) {
3617
		$tmprule .= "from {$rule[$index]} ";
3618
		$index++;
3619
	} elseif (trim($rule[$index]) == "any") {
3620
		$tmprule .= "from any ";
3621
		$index++;
3622
	} else {
3623
		$network = $rule[$index];
3624
		$netmask = $rule[++$index];
3625

    
3626
		if (is_ipaddrv4($network) && ($proto == "inet")) {
3627
			try {
3628
				$netmask = cisco_to_cidr($netmask);
3629
			} catch(Exception $e) {
3630
				syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source netmask '$netmask' (" . $e->getMessage() . ").");
3631
				return;
3632
			}
3633
			$tmprule .= "from {$network}/{$netmask} ";
3634
		} else {
3635
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source network '$network'.");
3636
			return;
3637
		}
3638

    
3639
		$index++;
3640
	}
3641

    
3642
	/* Source Operator */
3643
	if (in_array(trim($rule[$index]), array("lt", "gt", "eq", "neq"))) {
3644
		switch(trim($rule[$index])) {
3645
			case "lt":
3646
				$operator = "<";
3647
				break;
3648
			case "gt":
3649
				$operator = ">";
3650
				break;
3651
			case "eq":
3652
				$operator = "=";
3653
				break;
3654
			case "neq":
3655
				$operator = "!=";
3656
				break;
3657
		}
3658

    
3659
		$port = $rule[++$index];
3660
		if (is_port($port)) {
3661
			$tmprule .= "port {$operator} {$port} ";
3662
		} else {
3663
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source port: '$port' not a numeric value between 0 and 65535.");
3664
			return;
3665
		}
3666
		$index++;
3667
	} else if (trim($rule[$index]) == "range") {
3668
		$port = array($rule[++$index], $rule[++$index]);
3669
		if (is_port($port[0]) && is_port($port[1])) {
3670
			$tmprule .= "port {$port[0]}:{$port[1]} ";
3671
		} else {
3672
			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.");
3673
			return;
3674
		}
3675
		$index++;
3676
	}
3677

    
3678
	/* Destination */
3679
	if (trim($rule[$index]) == "host") {
3680
		$index++;
3681
		if ((($proto == 'inet') && (is_ipaddrv4(trim($rule[$index])) || (trim($rule[$index]) == "{clientip}"))) ||
3682
		    (($proto == 'inet6') && (is_ipaddrv6(trim($rule[$index])) || (trim($rule[$index]) == "{clientipv6}")))) {
3683
			$tmprule .= "to {$rule[$index]} ";
3684
			$index++;
3685
		} else {
3686
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination host '{$rule[$index]}'.");
3687
			return;
3688
		}
3689
	} elseif (is_subnetv6(trim($rule[$index])) && ($proto == "inet6")) {
3690
		$tmprule .= "to {$rule[$index]} ";
3691
		$index++;
3692
	} elseif (trim($rule[$index]) == "any") {
3693
		$tmprule .= "to any ";
3694
		$index++;
3695
	} else {
3696
		$network = $rule[$index];
3697
		$netmask = $rule[++$index];
3698

    
3699
		if (is_ipaddrv4($network) && ($proto == "inet")) {
3700
			try {
3701
				$netmask = cisco_to_cidr($netmask);
3702
			} catch(Exception $e) {
3703
				syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination netmask '$netmask' (" . $e->getMessage() . ").");
3704
				return;
3705
			}
3706
			$tmprule .= "to {$network}/{$netmask} ";
3707
		} else {
3708
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination network '$network'.");
3709
			return;
3710
		}
3711

    
3712
		$index++;
3713
	}
3714

    
3715
	/* Destination Operator */
3716
	if (in_array(trim($rule[$index]), array("lt", "gt", "eq", "neq"))) {
3717
		switch(trim($rule[$index])) {
3718
			case "lt":
3719
				$operator = "<";
3720
				break;
3721
			case "gt":
3722
				$operator = ">";
3723
				break;
3724
			case "eq":
3725
				$operator = "=";
3726
				break;
3727
			case "neq":
3728
				$operator = "!=";
3729
				break;
3730
		}
3731

    
3732
		$port = $rule[++$index];
3733
		if (is_port($port)) {
3734
			$tmprule .= "port {$operator} {$port} ";
3735
		} else {
3736
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination port: '$port' not a numeric value between 0 and 65535.");
3737
			return;
3738
		}
3739
		$index++;
3740
	} else if (trim($rule[$index]) == "range") {
3741
		$port = array($rule[++$index], $rule[++$index]);
3742
		if (is_port($port[0]) && is_port($port[1])) {
3743
			$tmprule .= "port {$port[0]}:{$port[1]} ";
3744
		} else {
3745
			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.");
3746
			return;
3747
		}
3748
		$index++;
3749
	}
3750

    
3751
	$tmprule = $startrule . $proto . " " . $tmprule;
3752
	return $tmprule;
3753
}
3754

    
3755
function parse_cisco_acl($attribs, $dev) {
3756
	global $attributes;
3757

    
3758
	if (!is_array($attribs)) {
3759
		return "";
3760
	}
3761
	$finalrules = "";
3762
	if (is_array($attribs['ciscoavpair'])) {
3763
		$inrules = array('inet' => array(), 'inet6' => array());
3764
		$outrules = array('inet' => array(), 'inet6' => array());
3765
		foreach ($attribs['ciscoavpair'] as $avrules) {
3766
			$rule = explode("=", $avrules);
3767
			$dir = "";
3768
			if (strstr($rule[0], "inacl")) {
3769
				$dir = "in";
3770
			} else if (strstr($rule[0], "outacl")) {
3771
				$dir = "out";
3772
			} else if (strstr($rule[0], "dns-servers")) {
3773
				$attributes['dns-servers'] = explode(" ", $rule[1]);
3774
				continue;
3775
			} else if (strstr($rule[0], "route")) {
3776
				if (!is_array($attributes['routes'])) {
3777
					$attributes['routes'] = array();
3778
				}
3779
				$attributes['routes'][] = $rule[1];
3780
				continue;
3781
			}
3782
			$rindex = cisco_extract_index($rule[0]);
3783
			if ($rindex < 0) {
3784
				continue;
3785
			}
3786

    
3787
			if (strstr($rule[0], "ipv6")) {
3788
				$proto = "inet6";
3789
			} else {
3790
				$proto = "inet";
3791
			}
3792

    
3793
			$tmprule = parse_cisco_acl_rule($rule[1], $dev, $dir, $proto);
3794
			if (!empty($tmprule)) {
3795
				if ($dir == "in") {
3796
					$inrules[$proto][$rindex] = $tmprule;
3797
				} else if ($dir == "out") {
3798
					$outrules[$proto][$rindex] = $tmprule;
3799
				}
3800
			}
3801
		}
3802

    
3803

    
3804
		$state = "";
3805
		foreach (array('inet', 'inet6') as $ip) {
3806
			if (!empty($outrules[$ip])) {
3807
				$state = "no state";
3808
			}
3809
			ksort($inrules[$ip], SORT_NUMERIC);
3810
			foreach ($inrules[$ip] as $inrule) {
3811
				$finalrules .= "{$inrule} {$state}\n";
3812
			}
3813
			if (!empty($outrules[$ip])) {
3814
				ksort($outrules[$ip], SORT_NUMERIC);
3815
				foreach ($outrules[$ip] as $outrule) {
3816
					$finalrules .= "{$outrule} {$state}\n";
3817
				}
3818
			}
3819
		}
3820
	}
3821
	return $finalrules;
3822
}
3823

    
3824
function alias_idn_to_utf8($alias) {
3825
	if (is_alias($alias)) {
3826
		return $alias;
3827
	} else {
3828
		return idn_to_utf8($alias);
3829
	}
3830
}
3831

    
3832
function alias_idn_to_ascii($alias) {
3833
	if (is_alias($alias)) {
3834
		return $alias;
3835
	} else {
3836
		return idn_to_ascii($alias);
3837
	}
3838
}
3839

    
3840
// These functions were in guiconfig.inc but have been moved here so non GUI processes can use them
3841
function address_to_pconfig($adr, &$padr, &$pmask, &$pnot, &$pbeginport, &$pendport) {
3842
	if (isset($adr['any'])) {
3843
		$padr = "any";
3844
	} else if ($adr['network']) {
3845
		$padr = $adr['network'];
3846
	} else if ($adr['address']) {
3847
		list($padr, $pmask) = explode("/", $adr['address']);
3848
		if (!$pmask) {
3849
			if (is_ipaddrv6($padr)) {
3850
				$pmask = 128;
3851
			} else {
3852
				$pmask = 32;
3853
			}
3854
		}
3855
	}
3856

    
3857
	if (isset($adr['not'])) {
3858
		$pnot = 1;
3859
	} else {
3860
		$pnot = 0;
3861
	}
3862

    
3863
	if ($adr['port']) {
3864
		list($pbeginport, $pendport) = explode("-", $adr['port']);
3865
		if (!$pendport) {
3866
			$pendport = $pbeginport;
3867
		}
3868
	} else if (!is_alias($pbeginport) && !is_alias($pendport)) {
3869
		$pbeginport = "any";
3870
		$pendport = "any";
3871
	}
3872
}
3873

    
3874
function pconfig_to_address(&$adr, $padr, $pmask, $pnot = false, $pbeginport = 0, $pendport = 0, $addmask = false) {
3875
	$adr = array();
3876

    
3877
	if ($padr == "any") {
3878
		$adr['any'] = true;
3879
	} else if (is_specialnet($padr)) {
3880
		if ($addmask) {
3881
			$padr .= "/" . $pmask;
3882
		}
3883
		$adr['network'] = $padr;
3884
	} else {
3885
		$adr['address'] = $padr;
3886
		if (is_ipaddrv6($padr)) {
3887
			if ($pmask != 128) {
3888
				$adr['address'] .= "/" . $pmask;
3889
			}
3890
		} else {
3891
			if ($pmask != 32) {
3892
				$adr['address'] .= "/" . $pmask;
3893
			}
3894
		}
3895
	}
3896

    
3897
	if ($pnot) {
3898
		$adr['not'] = true;
3899
	} else {
3900
		unset($adr['not']);
3901
	}
3902

    
3903
	if (($pbeginport != 0) && ($pbeginport != "any")) {
3904
		if ($pbeginport != $pendport) {
3905
			$adr['port'] = $pbeginport . "-" . $pendport;
3906
		} else {
3907
			$adr['port'] = $pbeginport;
3908
		}
3909
	}
3910

    
3911
	/*
3912
	 * If the port is still unset, then it must not be numeric, but could
3913
	 * be an alias or a well-known/registered service.
3914
	 * See https://redmine.pfsense.org/issues/8410
3915
	 */
3916
	if (!isset($adr['port']) && is_port_or_alias($pbeginport)) {
3917
		$adr['port'] = $pbeginport;
3918
	}
3919
}
3920

    
3921
function is_specialnet($net) {
3922
	global $specialsrcdst;
3923

    
3924
	if (!$net) {
3925
		return false;
3926
	}
3927
	if (in_array($net, $specialsrcdst)) {
3928
		return true;
3929
	} else {
3930
		return false;
3931
	}
3932
}
3933

    
3934
function is_interface_ipaddr($interface) {
3935
	if (!empty(config_get_path("interfaces/{$interface}/ipaddr"))) {
3936
		return true;
3937
	}
3938
	return false;
3939
}
3940

    
3941
function is_interface_ipaddrv6($interface) {
3942
	if (!empty(config_get_path("interfaces/{$interface}/ipaddrv6"))) {
3943
		return true;
3944
	}
3945
	return false;
3946
}
3947

    
3948
function escape_filter_regex($filtertext) {
3949
	/* If the caller (user) has not already put a backslash before a slash, to escape it in the regex, */
3950
	/* then this will do it. Take out any "\/" already there, then turn all ordinary "/" into "\/".    */
3951
	return str_replace('/', '\/', str_replace('\/', '/', $filtertext));
3952
}
3953

    
3954
/*
3955
 * Check if a given pattern has the same number of two different unescaped
3956
 * characters.
3957
 * For example, it can ensure a pattern has balanced sets of parentheses,
3958
 * braces, and brackets.
3959
 */
3960
function is_pattern_balanced_char($pattern, $open, $close) {
3961
	/* First remove escaped versions */
3962
	$pattern = str_replace('\\' . $open, '', $pattern);
3963
	$pattern = str_replace('\\' . $close, '', $pattern);
3964
	/* Check if the counts of both characters match in the target pattern */
3965
	return (substr_count($pattern, $open) == substr_count($pattern, $close));
3966
}
3967

    
3968
/*
3969
 * Check if a pattern contains balanced sets of parentheses, braces, and
3970
 * brackets.
3971
 */
3972
function is_pattern_balanced($pattern) {
3973
	if (is_pattern_balanced_char($pattern, '(', ')') &&
3974
	    is_pattern_balanced_char($pattern, '{', '}') &&
3975
	    is_pattern_balanced_char($pattern, '[', ']')) {
3976
		/* Balanced if all are true */
3977
		return true;
3978
	}
3979
	return false;
3980
}
3981

    
3982
function cleanup_regex_pattern($filtertext) {
3983
	/* Cleanup filter to prevent backreferences. */
3984
	$filtertext = escape_filter_regex($filtertext);
3985

    
3986
	/* Remove \<digit>+ backreferences
3987
	 * To match \ it must be escaped as \\\\ in PHP for preg_replace() */
3988
	$filtertext = preg_replace('/\\\\\\d+/', '', $filtertext);
3989

    
3990
	/* Check for unbalanced parentheses, braces, and brackets which
3991
	 * may be an error or attempt to circumvent protections.
3992
	 * Also discard any pattern that attempts problematic duplication
3993
	 * methods. */
3994
	if (!is_pattern_balanced($filtertext) ||
3995
	    (substr_count($filtertext, ')*') > 0) ||
3996
	    (substr_count($filtertext, ')+') > 0) ||
3997
	    (substr_count($filtertext, '{') > 0)) {
3998
		return '';
3999
	}
4000

    
4001
	return $filtertext;
4002
}
4003

    
4004
function ip6_to_asn1($addr) {
4005
	/* IPv6 MIB uses an OCTET STRING of length 16 to represent
4006
	 * 128-bit IPv6 address in network byte order.
4007
	 * see https://datatracker.ietf.org/doc/html/rfc2465#section-3 
4008
	 * i.e. fc00:3::4 = 252.0.0.3.0.0.0.0.0.0.0.0.0.0.0.4
4009
	 */
4010

    
4011
	if (!is_ipaddrv6($addr)) {
4012
		return false;
4013
	}
4014
	$ipv6str = "";
4015
	$octstr = "";
4016
	foreach (explode(':', Net_IPv6::uncompress($addr)) as $v) {
4017
		$ipv6str .= str_pad($v, 4, '0', STR_PAD_LEFT);
4018
	}
4019
	foreach (str_split($ipv6str, 2) as $v) {
4020
		$octstr .= base_convert($v, 16, 10) . '.';
4021
	}
4022

    
4023
	return $octstr;
4024
}
4025

    
4026
function interfaces_interrupts() {
4027
	exec("/usr/bin/vmstat -i --libxo json", $rawdata, $rc);
4028
	$interrupts = array();
4029
	if ($rc == 0) {
4030
		$vnstatarr = json_decode(implode(" ", $rawdata), JSON_OBJECT_AS_ARRAY);
4031
		$interruptarr = $vnstatarr['interrupt-statistics']['interrupt'];
4032

    
4033
		foreach ($interruptarr as $int){
4034
			preg_match("/irq\d+: ([a-z0-9]+)/", $int['name'], $matches);
4035
			$name = $matches[1];
4036
			if (array_key_exists($name, $interrupts)) {
4037
				/* interface with multiple queues */
4038
				$interrupts[$name]['total'] += $int['total'];
4039
				$interrupts[$name]['rate'] += $int['rate'];
4040
			} else {
4041
				$interrupts[$name]['total'] = $int['total'];
4042
				$interrupts[$name]['rate'] = $int['rate'];
4043
			}
4044
		}
4045
	}
4046

    
4047
	return $interrupts;
4048
}
4049

    
4050
function dummynet_load_module($max_qlimit) {
4051
	if (!is_module_loaded("dummynet.ko")) {
4052
		mute_kernel_msgs();
4053
		mwexec("/sbin/kldload dummynet");
4054
		unmute_kernel_msgs();
4055
	}
4056
	$sysctls = (array(
4057
			"net.inet.ip.dummynet.io_fast" => "1",
4058
			"net.inet.ip.dummynet.hash_size" => "256",
4059
			"net.inet.ip.dummynet.pipe_slot_limit" => $max_qlimit
4060
	));
4061
	init_config_arr(array('sysctl', 'item'));
4062
	foreach (config_get_path('sysctl/item', []) as $item) {
4063
		if (preg_match('/net\.inet\.ip\.dummynet\./', $item['tunable'])) {
4064
			$sysctls[$item['tunable']] = $item['value'];
4065
		}
4066
	}
4067
	set_sysctl($sysctls);
4068
}
4069

    
4070
function get_interface_vip_ips($interface) {
4071
	global $config;
4072
	$vipips = '';
4073

    
4074
	init_config_arr(array('virtualip', 'vip'));
4075
	foreach ($config['virtualip']['vip'] as $vip) {
4076
		if (($vip['interface'] == $interface) &&
4077
		    (($vip['mode'] == "carp") || ($vip['mode'] == "ipalias"))) {
4078
			$vipips .= $vip['subnet'] . ' ';
4079
		}
4080
	}
4081
	return $vipips;
4082
}
4083

    
4084
?>
(55-55/62)