Project

General

Profile

Download (109 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
/*
3
 * util.inc
4
 *
5
 * part of pfSense (https://www.pfsense.org)
6
 * Copyright (c) 2004-2013 BSD Perimeter
7
 * Copyright (c) 2013-2016 Electric Sheep Fencing
8
 * Copyright (c) 2014-2022 Rubicon Communications, LLC (Netgate)
9
 * All rights reserved.
10
 *
11
 * originally part of m0n0wall (http://m0n0.ch/wall)
12
 * Copyright (c) 2003-2004 Manuel Kasper <mk@neon1.net>.
13
 * All rights reserved.
14
 *
15
 * Licensed under the Apache License, Version 2.0 (the "License");
16
 * you may not use this file except in compliance with the License.
17
 * You may obtain a copy of the License at
18
 *
19
 * http://www.apache.org/licenses/LICENSE-2.0
20
 *
21
 * Unless required by applicable law or agreed to in writing, software
22
 * distributed under the License is distributed on an "AS IS" BASIS,
23
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
24
 * See the License for the specific language governing permissions and
25
 * limitations under the License.
26
 */
27
//require_once('interfaces.inc');
28
require_once('Net/IPv6.php');
29
define('VIP_ALL', 1);
30
define('VIP_CARP', 2);
31
define('VIP_IPALIAS', 3);
32

    
33
/**
34
 * Check if the global $g variable contains a $key
35
 *
36
 * @param string $key The key
37
 * @param bool $isset Also perform isset check
38
 *
39
 * @return bool
40
 */
41
function g_has(string $key, bool $isset = false) : bool
42
{
43
	global $g;
44
	return (array_key_exists($key, $g) && (!$isset || isset($g[$key])));
45
}
46

    
47
/**
48
 * Get the global $g variable value by $key
49
 *
50
 * @param string $key The key
51
 * @param mixed $default The value to return on a key miss
52
 *
53
 * @return mixed
54
 */
55
function g_get(string $key, mixed $default = null) : mixed
56
{
57
	global $g;
58
	return (g_has($key, true) ? $g[$key] : $default); 
59
}
60

    
61
/**
62
 * Set the global $g variable value by $key
63
 *
64
 * @param string $key The key
65
 * @param mixed $value The value
66
 * @param bool $force Force set (can replace) the value
67
 *
68
 * @return mixed
69
 */
70
function g_set(string $key, mixed $value, bool $force = false) : mixed
71
{
72
	global $g;
73
	if ($force || !g_has($key, true)) {
74
		$g[$key] = $value;
75
	}
76
	return (g_get($key));
77
}
78

    
79
/* kill a process by pid file */
80
function killbypid($pidfile, $waitfor = 0) {
81
	return sigkillbypid($pidfile, "TERM", $waitfor);
82
}
83

    
84
function isvalidpid($pidfile) {
85
	$output = "";
86
	if (file_exists($pidfile)) {
87
		exec("/bin/pgrep -qnF {$pidfile} 2>/dev/null", $output, $retval);
88
		return (intval($retval) == 0);
89
	}
90
	return false;
91
}
92

    
93
function is_process_running($process) {
94
	$output = "";
95
	if (!empty($process)) {
96
		exec("/bin/pgrep -anx " . escapeshellarg($process), $output, $retval);
97
		return (intval($retval) == 0);
98
	}
99
	return false;
100
}
101

    
102
function isvalidproc($proc) {
103
	return is_process_running($proc);
104
}
105

    
106
/* sigkill a process by pid file, and wait for it to terminate or remove the .pid file for $waitfor seconds */
107
/* return 1 for success and 0 for a failure */
108
function sigkillbypid($pidfile, $sig, $waitfor = 0) {
109
	if (isvalidpid($pidfile)) {
110
		$result = mwexec("/bin/pkill " . escapeshellarg("-{$sig}") .
111
		    " -F {$pidfile}", true);
112
		$waitcounter = $waitfor * 10;
113
		while(isvalidpid($pidfile) && $waitcounter > 0) {
114
			$waitcounter = $waitcounter - 1;
115
			usleep(100000);
116
		}
117
		return $result;
118
	}
119

    
120
	return 0;
121
}
122

    
123
/* kill a process by name */
124
function sigkillbyname($procname, $sig) {
125
	if (isvalidproc($procname)) {
126
		return mwexec("/usr/bin/killall " . escapeshellarg("-{$sig}") . " " . escapeshellarg($procname), true);
127
	}
128
}
129

    
130
/* kill a process by name */
131
function killbyname($procname) {
132
	if (isvalidproc($procname)) {
133
		mwexec("/usr/bin/killall " . escapeshellarg($procname));
134
	}
135
}
136

    
137
function is_subsystem_dirty($subsystem = "") {
138
	global $g;
139

    
140
	if ($subsystem == "") {
141
		return false;
142
	}
143

    
144
	if (file_exists("{$g['varrun_path']}/{$subsystem}.dirty")) {
145
		return true;
146
	}
147

    
148
	return false;
149
}
150

    
151
function mark_subsystem_dirty($subsystem = "") {
152
	global $g;
153

    
154
	if (!file_put_contents("{$g['varrun_path']}/{$subsystem}.dirty", "DIRTY")) {
155
		log_error(sprintf(gettext("WARNING: Could not mark subsystem: %s dirty"), $subsystem));
156
	}
157
}
158

    
159
function clear_subsystem_dirty($subsystem = "") {
160
	global $g;
161

    
162
	@unlink("{$g['varrun_path']}/{$subsystem}.dirty");
163
}
164

    
165
function clear_filter_subsystems_dirty() {
166
	clear_subsystem_dirty('aliases');
167
	clear_subsystem_dirty('filter');
168
	clear_subsystem_dirty('natconf');
169
	clear_subsystem_dirty('shaper');
170
}
171

    
172
/* lock configuration file */
173
function lock($lock, $op = LOCK_SH) {
174
	global $g;
175
	if (!$lock) {
176
		die(gettext("WARNING: A name must be given as parameter to lock() function."));
177
	}
178
	if (!file_exists("{$g['tmp_path']}/{$lock}.lock")) {
179
		@touch("{$g['tmp_path']}/{$lock}.lock");
180
		@chmod("{$g['tmp_path']}/{$lock}.lock", 0666);
181
	}
182
	if ($fp = fopen("{$g['tmp_path']}/{$lock}.lock", "w")) {
183
		if (flock($fp, $op)) {
184
			return $fp;
185
		} else {
186
			fclose($fp);
187
		}
188
	}
189
}
190

    
191
function try_lock($lock, $timeout = 5) {
192
	global $g;
193
	if (!$lock) {
194
		die(gettext("WARNING: A name must be given as parameter to try_lock() function."));
195
	}
196
	if (!file_exists("{$g['tmp_path']}/{$lock}.lock")) {
197
		@touch("{$g['tmp_path']}/{$lock}.lock");
198
		@chmod("{$g['tmp_path']}/{$lock}.lock", 0666);
199
	}
200
	if ($fp = fopen("{$g['tmp_path']}/{$lock}.lock", "w")) {
201
		$trycounter = 0;
202
		while (!flock($fp, LOCK_EX | LOCK_NB)) {
203
			if ($trycounter >= $timeout) {
204
				fclose($fp);
205
				return NULL;
206
			}
207
			sleep(1);
208
			$trycounter++;
209
		}
210

    
211
		return $fp;
212
	}
213

    
214
	return NULL;
215
}
216

    
217
/* unlock configuration file */
218
function unlock($cfglckkey = 0) {
219
	if (!is_null($cfglckkey)) {
220
		@flock($cfglckkey, LOCK_UN);
221
		@fclose($cfglckkey);
222
	}
223
	return;
224
}
225

    
226
/* unlock forcefully configuration file */
227
function unlock_force($lock) {
228
	global $g;
229

    
230
	@unlink("{$g['tmp_path']}/{$lock}.lock");
231
}
232

    
233
function send_event($cmd) {
234
	global $g;
235

    
236
	if (!isset($g['event_address'])) {
237
		$g['event_address'] = "unix:///var/run/check_reload_status";
238
	}
239

    
240
	$try = 0;
241
	while ($try < 3) {
242
		$fd = @fsockopen($g['event_address']);
243
		if ($fd) {
244
			fwrite($fd, $cmd);
245
			$resp = fread($fd, 4096);
246
			if ($resp != "OK\n") {
247
				log_error("send_event: sent {$cmd} got {$resp}");
248
			}
249
			fclose($fd);
250
			$try = 3;
251
		} else if (!is_process_running("check_reload_status")) {
252
			mwexec_bg("/usr/bin/nice -n20 /usr/local/sbin/check_reload_status");
253
		}
254
		$try++;
255
	}
256
}
257

    
258
function send_multiple_events($cmds) {
259
	global $g;
260

    
261
	if (!isset($g['event_address'])) {
262
		$g['event_address'] = "unix:///var/run/check_reload_status";
263
	}
264

    
265
	if (!is_array($cmds)) {
266
		return;
267
	}
268

    
269
	$try = 0;
270
	while ($try < 3) {
271
		$fd = @fsockopen($g['event_address']);
272
		if ($fd) {
273
			foreach ($cmds as $cmd) {
274
				fwrite($fd, $cmd);
275
				$resp = fread($fd, 4096);
276
				if ($resp != "OK\n") {
277
					log_error("send_event: sent {$cmd} got {$resp}");
278
				}
279
			}
280
			fclose($fd);
281
			$try = 3;
282
		} else if (!is_process_running("check_reload_status")) {
283
			mwexec_bg("/usr/bin/nice -n20 /usr/local/sbin/check_reload_status");
284
		}
285
		$try++;
286
	}
287
}
288

    
289
function is_module_loaded($module_name) {
290
	$module_name = str_replace(".ko", "", $module_name);
291
	$running = 0;
292
	$_gb = exec("/sbin/kldstat -qn {$module_name} 2>&1", $_gb, $running);
293
	if (intval($running) == 0) {
294
		return true;
295
	} else {
296
		return false;
297
	}
298
}
299

    
300
/* validate non-negative numeric string, or equivalent numeric variable */
301
function is_numericint($arg) {
302
	return (((is_int($arg) && $arg >= 0) || (is_string($arg) && strlen($arg) > 0 && ctype_digit($arg))) ? true : false);
303
}
304

    
305
/* Generate the (human readable) ipv4 or ipv6 subnet address (i.e., netmask, or subnet start IP)
306
   given an (human readable) ipv4 or ipv6 host address and subnet bit count */
307
function gen_subnet($ipaddr, $bits) {
308
	if (($sn = gen_subnetv6($ipaddr, $bits)) == '') {
309
		$sn = gen_subnetv4($ipaddr, $bits);  // try to avoid rechecking IPv4/v6
310
	}
311
	return $sn;
312
}
313

    
314
/* same as gen_subnet() but accepts IPv4 only */
315
function gen_subnetv4($ipaddr, $bits) {
316
	if (is_ipaddrv4($ipaddr) && is_numericint($bits) && $bits <= 32) {
317
		if ($bits == 0) {
318
			return '0.0.0.0';  // avoids <<32
319
		}
320
		return long2ip(ip2long($ipaddr) & ((0xFFFFFFFF << (32 - $bits)) & 0xFFFFFFFF));
321
	}
322
	return "";
323
}
324

    
325
/* same as gen_subnet() but accepts IPv6 only */
326
function gen_subnetv6($ipaddr, $bits) {
327
	if (is_ipaddrv6($ipaddr) && is_numericint($bits) && $bits <= 128) {
328
		return text_to_compressed_ip6(Net_IPv6::getNetmask($ipaddr, $bits));
329
	}
330
	return "";
331
}
332

    
333
/* Generate the (human readable) ipv4 or ipv6 subnet end address (i.e., highest address, end IP, or IPv4 broadcast address)
334
   given an (human readable) ipv4 or ipv6 host address and subnet bit count. */
335
function gen_subnet_max($ipaddr, $bits) {
336
	if (($sn = gen_subnetv6_max($ipaddr, $bits)) == '') {
337
		$sn = gen_subnetv4_max($ipaddr, $bits);  // try to avoid rechecking IPv4/v6
338
	}
339
	return $sn;
340
}
341

    
342
/* same as gen_subnet_max() but validates IPv4 only */
343
function gen_subnetv4_max($ipaddr, $bits) {
344
	if (is_ipaddrv4($ipaddr) && is_numericint($bits) && $bits <= 32) {
345
		if ($bits == 32) {
346
			return $ipaddr;
347
		}
348
		return long2ip32(ip2long($ipaddr) | (~gen_subnet_mask_long($bits) & 0xFFFFFFFF));
349
	}
350
	return "";
351
}
352

    
353
/* same as gen_subnet_max() but validates IPv6 only */
354
function gen_subnetv6_max($ipaddr, $bits) {
355
	if (is_ipaddrv6($ipaddr) && is_numericint($bits) && $bits <= 128) {
356
		$endip_bin = substr(ip6_to_bin($ipaddr), 0, $bits) . str_repeat('1', 128 - $bits);
357
		return bin_to_compressed_ip6($endip_bin);
358
	}
359
	return "";
360
}
361

    
362
/* returns a subnet mask (long given a bit count) */
363
function gen_subnet_mask_long($bits) {
364
	$sm = 0;
365
	for ($i = 0; $i < $bits; $i++) {
366
		$sm >>= 1;
367
		$sm |= 0x80000000;
368
	}
369
	return $sm;
370
}
371

    
372
/* same as above but returns a string */
373
function gen_subnet_mask($bits) {
374
	return long2ip(gen_subnet_mask_long($bits));
375
}
376

    
377
/* Convert a prefix length to an IPv6 address-like mask notation. Very rare but at least ntp needs it. See #4463 */
378
function gen_subnet_mask_v6($bits) {
379
	/* Binary representation of the prefix length */
380
	$bin = str_repeat('1', $bits);
381
	/* Pad right with zeroes to reach the full address length */
382
	$bin = str_pad($bin, 128, '0', STR_PAD_RIGHT);
383
	/* Convert back to an IPv6 address style notation */
384
	return bin_to_ip6($bin);
385
}
386

    
387
/* Convert long int to IPv4 address
388
   Returns '' if not valid IPv4 (including if any bits >32 are non-zero) */
389
function long2ip32($ip) {
390
	return long2ip($ip & 0xFFFFFFFF);
391
}
392

    
393
/* Convert IPv4 address to long int, truncated to 32-bits to avoid sign extension on 64-bit platforms.
394
   Returns '' if not valid IPv4. */
395
function ip2long32($ip) {
396
	return (ip2long($ip) & 0xFFFFFFFF);
397
}
398

    
399
/* Convert IPv4 address to unsigned long int.
400
   Returns '' if not valid IPv4. */
401
function ip2ulong($ip) {
402
	return sprintf("%u", ip2long32($ip));
403
}
404

    
405
/*
406
 * Convert IPv6 address to binary
407
 *
408
 * Obtained from: pear-Net_IPv6
409
 */
410
function ip6_to_bin($ip) {
411
	$binstr = '';
412

    
413
	$ip = Net_IPv6::removeNetmaskSpec($ip);
414
	$ip = Net_IPv6::Uncompress($ip);
415

    
416
	$parts = explode(':', $ip);
417

    
418
	foreach ( $parts as $v ) {
419

    
420
		$str     = base_convert($v, 16, 2);
421
		$binstr .= str_pad($str, 16, '0', STR_PAD_LEFT);
422

    
423
	}
424

    
425
	return $binstr;
426
}
427

    
428
/*
429
 * Convert IPv6 binary to uncompressed address
430
 *
431
 * Obtained from: pear-Net_IPv6
432
 */
433
function bin_to_ip6($bin) {
434
	$ip = "";
435

    
436
	if (strlen($bin) < 128) {
437
		$bin = str_pad($bin, 128, '0', STR_PAD_LEFT);
438
	}
439

    
440
	$parts = str_split($bin, "16");
441

    
442
	foreach ( $parts as $v ) {
443
		$str = base_convert($v, 2, 16);
444
		$ip .= $str.":";
445
	}
446

    
447
	$ip = substr($ip, 0, -1);
448

    
449
	return $ip;
450
}
451

    
452
/*
453
 * Convert IPv6 binary to compressed address
454
 */
455
function bin_to_compressed_ip6($bin) {
456
	return text_to_compressed_ip6(bin_to_ip6($bin));
457
}
458

    
459
/*
460
 * Convert textual IPv6 address string to compressed address
461
 */
462
function text_to_compressed_ip6($text) {
463
	// Force re-compression by passing parameter 2 (force) true.
464
	// This ensures that supposedly-compressed formats are uncompressed
465
	// first then re-compressed into strictly correct form.
466
	// e.g. 2001:0:0:4:0:0:0:1
467
	// 2001::4:0:0:0:1 is a strictly-incorrect compression,
468
	// but maybe the user entered it like that.
469
	// The "force" parameter will ensure it is returned as:
470
	// 2001:0:0:4::1
471
	return Net_IPv6::compress($text, true);
472
}
473

    
474
/* Find out how many IPs are contained within a given IP range
475
 *  e.g. 192.168.0.0 to 192.168.0.255 returns 256
476
 */
477
function ip_range_size_v4($startip, $endip) {
478
	if (is_ipaddrv4($startip) && is_ipaddrv4($endip)) {
479
		// Operate as unsigned long because otherwise it wouldn't work
480
		//   when crossing over from 127.255.255.255 / 128.0.0.0 barrier
481
		return abs(ip2ulong($startip) - ip2ulong($endip)) + 1;
482
	}
483
	return -1;
484
}
485

    
486
/* Find the smallest possible subnet mask which can contain a given number of IPs
487
 *  e.g. 512 IPs can fit in a /23, but 513 IPs need a /22
488
 */
489
function find_smallest_cidr_v4($number) {
490
	$smallest = 1;
491
	for ($b=32; $b > 0; $b--) {
492
		$smallest = ($number <= pow(2, $b)) ? $b : $smallest;
493
	}
494
	return (32-$smallest);
495
}
496

    
497
/* Return the previous IP address before the given address */
498
function ip_before($ip, $offset = 1) {
499
	return long2ip32(ip2long($ip) - $offset);
500
}
501

    
502
/* Return the next IP address after the given address */
503
function ip_after($ip, $offset = 1) {
504
	return long2ip32(ip2long($ip) + $offset);
505
}
506

    
507
/* Return true if the first IP is 'before' the second */
508
function ip_less_than($ip1, $ip2) {
509
	// Compare as unsigned long because otherwise it wouldn't work when
510
	//   crossing over from 127.255.255.255 / 128.0.0.0 barrier
511
	return ip2ulong($ip1) < ip2ulong($ip2);
512
}
513

    
514
/* Return true if the first IP is 'after' the second */
515
function ip_greater_than($ip1, $ip2) {
516
	// Compare as unsigned long because otherwise it wouldn't work
517
	//   when crossing over from 127.255.255.255 / 128.0.0.0 barrier
518
	return ip2ulong($ip1) > ip2ulong($ip2);
519
}
520

    
521
/* compare two IP addresses */
522
function ipcmp($a, $b) {
523
	if (is_subnet($a)) {
524
		$a = explode('/', $a)[0];
525
	}
526
	if (is_subnet($b)) {
527
		$b = explode('/', $b)[0];
528
	}
529
	if (ip_less_than($a, $b)) {
530
		return -1;
531
	} else if (ip_greater_than($a, $b)) {
532
		return 1;
533
	} else {
534
		return 0;
535
	}
536
}
537

    
538
/* Convert a range of IPv4 addresses to an array of individual addresses. */
539
/* Note: IPv6 ranges are not yet supported here. */
540
function ip_range_to_address_array($startip, $endip, $max_size = 5000) {
541
	if (!is_ipaddrv4($startip) || !is_ipaddrv4($endip)) {
542
		return false;
543
	}
544

    
545
	if (ip_greater_than($startip, $endip)) {
546
		// Swap start and end so we can process sensibly.
547
		$temp = $startip;
548
		$startip = $endip;
549
		$endip = $temp;
550
	}
551

    
552
	if (ip_range_size_v4($startip, $endip) > $max_size) {
553
		return false;
554
	}
555

    
556
	// Container for IP addresses within this range.
557
	$rangeaddresses = array();
558
	$end_int = ip2ulong($endip);
559
	for ($ip_int = ip2ulong($startip); $ip_int <= $end_int; $ip_int++) {
560
		$rangeaddresses[] = long2ip($ip_int);
561
	}
562

    
563
	return $rangeaddresses;
564
}
565

    
566
/*
567
 * Convert an IPv4 or IPv6 IP range to an array of subnets which can contain the range.
568
 * Algorithm and embodying code PD'ed by Stilez - enjoy as you like :-)
569
 *
570
 * Documented on pfsense dev list 19-20 May 2013. Summary:
571
 *
572
 * The algorithm looks at patterns of 0's and 1's in the least significant bit(s), whether IPv4 or IPv6.
573
 * These are all that needs checking to identify a _guaranteed_ correct, minimal and optimal subnet array.
574
 *
575
 * As a result, string/binary pattern matching of the binary IP is very efficient. It uses just 2 pattern-matching rules
576
 * to chop off increasingly larger subnets at both ends that can't be part of larger subnets, until nothing's left.
577
 *
578
 * (a) If any range has EITHER low bit 1 (in startip) or 0 (in endip), that end-point is _always guaranteed_ to be optimally
579
 * represented by its own 'single IP' CIDR; the remaining range then shrinks by one IP up or down, causing the new end-point's
580
 * low bit to change from 1->0 (startip) or 0->1 (endip). Only one edge case needs checking: if a range contains exactly 2
581
 * adjacent IPs of this format, then the two IPs themselves are required to span it, and we're done.
582
 * 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
583
 * low bits can now be ignored.
584
 *
585
 * (b) If any range has BOTH startip and endip ending in some number of 0's and 1's respectively, these low bits can
586
 * *always* be ignored and "bit-shifted" for subnet spanning. So provided we remember the bits we've place-shifted, we can
587
 * _always_ right-shift and chop off those bits, leaving a smaller range that has EITHER startip ending in 1 or endip ending
588
 * in 0 (ie can now apply (a) again) or the entire range has vanished and we're done.
589
 * We then loop to redo (a) again on the remaining (place shifted) range until after a few loops, the remaining (place shifted)
590
 * range 'vanishes' by meeting the exit criteria of (a) or (b), and we're done.
591
 */
592
function ip_range_to_subnet_array($ip1, $ip2) {
593

    
594
	if (is_ipaddrv4($ip1) && is_ipaddrv4($ip2)) {
595
		$proto = 'ipv4';  // for clarity
596
		$bits = 32;
597
		$ip1bin = decbin(ip2long32($ip1));
598
		$ip2bin = decbin(ip2long32($ip2));
599
	} elseif (is_ipaddrv6($ip1) && is_ipaddrv6($ip2)) {
600
		$proto = 'ipv6';
601
		$bits = 128;
602
		$ip1bin = ip6_to_bin($ip1);
603
		$ip2bin = ip6_to_bin($ip2);
604
	} else {
605
		return array();
606
	}
607

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

    
612
	if ($ip1bin == $ip2bin) {
613
		return array($ip1 . '/' . $bits); // exit if ip1=ip2 (trivial case)
614
	}
615

    
616
	if ($ip1bin > $ip2bin) {
617
		list ($ip1bin, $ip2bin) = array($ip2bin, $ip1bin);  // swap if needed (ensures ip1 < ip2)
618
	}
619

    
620
	$rangesubnets = array();
621
	$netsize = 0;
622

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

    
627
		// 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)
628

    
629
		if (substr($ip1bin, -1, 1) == '1') {
630
			// the start ip must be in a separate one-IP cidr range
631
			$new_subnet_ip = substr($ip1bin, $netsize, $bits - $netsize) . str_repeat('0', $netsize);
632
			$rangesubnets[$new_subnet_ip] = $bits - $netsize;
633
			$n = strrpos($ip1bin, '0');  //can't be all 1's
634
			$ip1bin = ($n == 0 ? '' : substr($ip1bin, 0, $n)) . '1' . str_repeat('0', $bits - $n - 1);  // BINARY VERSION OF $ip1 += 1
635
		}
636

    
637
		// 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)
638

    
639
		if (substr($ip2bin, -1, 1) == '0') {
640
			// the end ip must be in a separate one-IP cidr range
641
			$new_subnet_ip = substr($ip2bin, $netsize, $bits - $netsize) . str_repeat('0', $netsize);
642
			$rangesubnets[$new_subnet_ip] = $bits - $netsize;
643
			$n = strrpos($ip2bin, '1');  //can't be all 0's
644
			$ip2bin = ($n == 0 ? '' : substr($ip2bin, 0, $n)) . '0' . str_repeat('1', $bits - $n - 1);  // BINARY VERSION OF $ip2 -= 1
645
			// already checked for the edge case where end = start+1 and start ends in 0x1, above, so it's safe
646
		}
647

    
648
		// this is the only edge case arising from increment/decrement.
649
		// 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)
650

    
651
		if ($ip2bin < $ip1bin) {
652
			continue;
653
		}
654

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

    
658
		$shift = $bits - max(strrpos($ip1bin, '0'), strrpos($ip2bin, '1'));  // num of low bits which are '0' in ip1 and '1' in ip2
659
		$ip1bin = str_repeat('0', $shift) . substr($ip1bin, 0, $bits - $shift);
660
		$ip2bin = str_repeat('0', $shift) . substr($ip2bin, 0, $bits - $shift);
661
		$netsize += $shift;
662
		if ($ip1bin == $ip2bin) {
663
			// we're done.
664
			$new_subnet_ip = substr($ip1bin, $netsize, $bits - $netsize) . str_repeat('0', $netsize);
665
			$rangesubnets[$new_subnet_ip] = $bits - $netsize;
666
			continue;
667
		}
668

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

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

    
674
	ksort($rangesubnets, SORT_STRING);
675
	$out = array();
676

    
677
	foreach ($rangesubnets as $ip => $netmask) {
678
		if ($proto == 'ipv4') {
679
			$i = str_split($ip, 8);
680
			$out[] = implode('.', array(bindec($i[0]), bindec($i[1]), bindec($i[2]), bindec($i[3]))) . '/' . $netmask;
681
		} else {
682
			$out[] = bin_to_compressed_ip6($ip) . '/' . $netmask;
683
		}
684
	}
685

    
686
	return $out;
687
}
688

    
689
/* returns true if $range is a valid pair of IPv4 or IPv6 addresses separated by a "-"
690
	false - if not a valid pair
691
	true (numeric 4 or 6) - if valid, gives type of addresses */
692
function is_iprange($range) {
693
	if (substr_count($range, '-') != 1) {
694
		return false;
695
	}
696
	list($ip1, $ip2) = explode ('-', $range);
697
	if (is_ipaddrv4($ip1) && is_ipaddrv4($ip2)) {
698
		return 4;
699
	}
700
	if (is_ipaddrv6($ip1) && is_ipaddrv6($ip2)) {
701
		return 6;
702
	}
703
	return false;
704
}
705

    
706
/* returns true if $ipaddr is a valid dotted IPv4 address or a IPv6
707
	false - not valid
708
	true (numeric 4 or 6) - if valid, gives type of address */
709
function is_ipaddr($ipaddr) {
710
	if (is_ipaddrv4($ipaddr)) {
711
		return 4;
712
	}
713
	if (is_ipaddrv6($ipaddr)) {
714
		return 6;
715
	}
716
	return false;
717
}
718

    
719
/* returns true if $ipaddr is a valid IPv6 address */
720
function is_ipaddrv6($ipaddr) {
721
	if (!is_string($ipaddr) || empty($ipaddr)) {
722
		return false;
723
	}
724
	/*
725
	 * While Net_IPv6::checkIPv6() considers IPv6/mask a valid IPv6,
726
	 * is_ipaddrv6() needs to be more strict to keep the compatibility
727
	 * with is_ipaddrv4().
728
	 */
729
	if (strstr($ipaddr, "/")) {
730
		return false;
731
	}
732
	if (strstr($ipaddr, "%") && is_linklocal($ipaddr)) {
733
		$tmpip = explode("%", $ipaddr);
734
		$ipaddr = $tmpip[0];
735
	}
736
	/*
737
	 * Net_IPv6::checkIPv6 does not reject multiple attempts at compression
738
	 * so we must check it beforehand.
739
	 * https://redmine.pfsense.org/issues/13069
740
	 */
741
	if (substr_count($ipaddr, '::') > 1) {
742
		return false;
743
	}
744
	return Net_IPv6::checkIPv6($ipaddr);
745
}
746

    
747
function is_ipaddrv6_v4map($ipaddr) {
748
	/* check RFC4291 par 2.2.2 format, ex: fd00::1.2.3.4
749
	 * see https://redmine.pfsense.org/issues/11446 */
750
	if (is_ipaddrv6($ipaddr) && preg_match('/^[0-9a-f:]{2,30}[0-9.]{7,15}$/i', $ipaddr)) {
751
		return true;
752
	}
753
	return false;
754
}
755

    
756
/* returns true if $ipaddr is a valid dotted IPv4 address */
757
function is_ipaddrv4($ipaddr) {
758
	if (!is_string($ipaddr) || empty($ipaddr) || ip2long($ipaddr) === FALSE) {
759
		return false;
760
	}
761
	return true;
762
}
763

    
764
function is_mcast($ipaddr) {
765
	if (is_mcastv4($ipaddr)) {
766
		return 4;
767
	}
768
	if (is_mcastv6($ipaddr)) {
769
		return 6;
770
	}
771
	return false;
772
}
773

    
774
function is_mcastv4($ipaddr) {
775
	if (!is_ipaddrv4($ipaddr) ||
776
	    !preg_match('/^2(?:2[4-9]|3\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d?|0)){3}$/', $ipaddr)) {
777
		return false;
778
	}
779
	return true;
780
}
781

    
782
function is_mcastv6($ipaddr) {
783
	if (!is_ipaddrv6($ipaddr) || !preg_match('/^ff.+$/', $ipaddr)) {
784
		return false;
785
	}
786
	return true;
787
}
788

    
789
/* returns 4 or 6 respectively (== TRUE) if $ipaddr is a valid IPv4 or IPv6 linklocal address
790
   returns '' if not a valid linklocal address */
791
function is_linklocal($ipaddr) {
792
	if (is_ipaddrv4($ipaddr)) {
793
		// input is IPv4
794
		// test if it's 169.254.x.x per rfc3927 2.1
795
		$ip4 = explode(".", $ipaddr);
796
		if ($ip4[0] == '169' && $ip4[1] == '254') {
797
			return 4;
798
		}
799
	} elseif (Net_IPv6::getAddressType($ipaddr) == NET_IPV6_LOCAL_LINK) {
800
		return 6;
801
	}
802
	return '';
803
}
804

    
805
/* returns scope of a linklocal address */
806
function get_ll_scope($addr) {
807
	if (!is_linklocal($addr) || !strstr($addr, "%")) {
808
		return "";
809
	}
810
	return explode("%", $addr)[1];
811
}
812

    
813
/* returns true if $ipaddr is a valid literal IPv6 address */
814
function is_literalipaddrv6($ipaddr) {
815
	if (substr($ipaddr,0,1) == '[' && substr($ipaddr,-1,1) == ']') {
816
		// if it's data wrapped in "[ ... ]" then test if middle part is valid IPv6
817
		return is_ipaddrv6(substr($ipaddr,1,-1));
818
	}
819
	return false;
820
}
821

    
822
/* returns true if $iport is a valid IPv4:port or [Literal IPv6]:port
823
	false - not valid
824
	true (numeric 4 or 6) - if valid, gives type of address */
825
function is_ipaddrwithport($ipport) {
826
	$c = strrpos($ipport, ":");
827
	if ($c === false) {
828
		return false;  // can't split at final colon if no colon exists
829
	}
830

    
831
	if (!is_port(substr($ipport, $c + 1))) {
832
		return false;  // no valid port after last colon
833
	}
834

    
835
	$ip = substr($ipport, 0, $c);  // else is text before last colon a valid IP
836
	if (is_literalipaddrv6($ip)) {
837
		return 6;
838
	} elseif (is_ipaddrv4($ip)) {
839
		return 4;
840
	} else {
841
		return false;
842
	}
843
}
844

    
845
function is_hostnamewithport($hostport) {
846
	$parts = explode(":", $hostport);
847
	// no need to validate with is_string(); if it's not a string then explode won't return 2 parts anyway
848
	if (count($parts) == 2) {
849
		return is_hostname($parts[0]) && is_port($parts[1]);
850
	}
851
	return false;
852
}
853

    
854
/* returns true if $ipaddr is a valid dotted IPv4 address or an alias thereof */
855
function is_ipaddroralias($ipaddr) {
856
	if (is_alias($ipaddr)) {
857
		foreach (config_get_path('aliases/alias', []) as $alias) {
858
			if ($alias['name'] == $ipaddr && !preg_match("/port/i", $alias['type'])) {
859
				return true;
860
			}
861
		}
862
		return false;
863
	} else {
864
		return is_ipaddr($ipaddr);
865
	}
866

    
867
}
868

    
869
/* returns true if $subnet is a valid IPv4 or IPv6 subnet in CIDR format
870
	false - if not a valid subnet
871
	true (numeric 4 or 6) - if valid, gives type of subnet */
872
function is_subnet($subnet) {
873
	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)) {
874
		if (is_ipaddrv4($parts[1]) && $parts[3] <= 32) {
875
			return 4;
876
		}
877
		if (is_ipaddrv6($parts[2]) && $parts[3] <= 128) {
878
			return 6;
879
		}
880
	}
881
	return false;
882
}
883

    
884
function is_v4($ip_or_subnet) {
885
	return is_ipaddrv4($ip_or_subnet) || is_subnetv4($ip_or_subnet);
886
}
887

    
888
function is_v6($ip_or_subnet) {
889
	return is_ipaddrv6($ip_or_subnet) || is_subnetv6($ip_or_subnet);
890
}
891

    
892
/* same as is_subnet() but accepts IPv4 only */
893
function is_subnetv4($subnet) {
894
	return (is_subnet($subnet) == 4);
895
}
896

    
897
/* same as is_subnet() but accepts IPv6 only */
898
function is_subnetv6($subnet) {
899
	return (is_subnet($subnet) == 6);
900
}
901

    
902
/* returns true if $subnet is a valid subnet in CIDR format or an alias thereof */
903
function is_subnetoralias($subnet) {
904
	global $aliastable;
905

    
906
	if (isset($aliastable[$subnet]) && is_subnet($aliastable[$subnet])) {
907
		return true;
908
	} else {
909
		return is_subnet($subnet);
910
	}
911
}
912

    
913
/* Get number of addresses in an IPv4/IPv6 subnet (represented as a string)
914
   optional $exact=true forces error (0) to be returned if it can't be represented exactly
915
   Exact result not possible above PHP_MAX_INT which is about 2^31 addresses on x32 or 2^63 on x64
916
   Returns 0 for bad data or if cannot represent size as an INT when $exact is set. */
917
function subnet_size($subnet, $exact=false) {
918
	$parts = explode("/", $subnet);
919
	$iptype = is_ipaddr($parts[0]);
920
	if (count($parts) == 2 && $iptype) {
921
		return subnet_size_by_netmask($iptype, $parts[1], $exact);
922
	}
923
	return 0;
924
}
925

    
926
/* Get number of addresses in an IPv4/IPv6 subnet (represented numerically as IP type + bits)
927
   optional $exact=true forces error (0) to be returned if it can't be represented exactly
928
   Hard to think where we might need to count exactly a huge subnet but an overflow detection option is probably sensible
929
   Returns 0 for bad data or if cannot represent size as an INT when $exact is set. */
930
function subnet_size_by_netmask($iptype, $bits, $exact=false) {
931
	if (!is_numericint($bits)) {
932
		return 0;
933
	} elseif ($iptype == 4 && $bits <= 32) {
934
		$snsize = 32 - $bits;
935
	} elseif ($iptype == 6 && $bits <= 128) {
936
		$snsize = 128 - $bits;
937
	} else {
938
		return 0;
939
	}
940

    
941
	// 2**N returns an exact result as an INT if possible, and a float/double if not.
942
	// 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
943
	$result = 2 ** $snsize;
944

    
945
	if ($exact && !is_int($result)) {
946
		//exact required but can't represent result exactly as an INT
947
		return 0;
948
	} else {
949
		// result ok, will be an INT where possible (guaranteed up to 2^31 addresses on x32/x64) and a float for 'huge' subnets
950
		return $result;
951
	}
952
}
953

    
954
/* function used by pfblockerng */
955
function subnetv4_expand($subnet) {
956
	$result = array();
957
	list ($ip, $bits) = explode("/", $subnet);
958
	$net = ip2long($ip);
959
	$mask = (0xffffffff << (32 - $bits));
960
	$net &= $mask;
961
	$size = round(exp(log(2) * (32 - $bits)));
962
	for ($i = 0; $i < $size; $i += 1) {
963
		$result[] = long2ip($net | $i);
964
	}
965
	return $result;
966
}
967

    
968
/* find out whether two IPv4/IPv6 CIDR subnets overlap.
969
   Note: CIDR overlap implies one is identical or included so largest sn will be the same */
970
function check_subnets_overlap($subnet1, $bits1, $subnet2, $bits2) {
971
	if (is_ipaddrv4($subnet1)) {
972
		return check_subnetsv4_overlap($subnet1, $bits1, $subnet2, $bits2);
973
	} else {
974
		return check_subnetsv6_overlap($subnet1, $bits1, $subnet2, $bits2);
975
	}
976
}
977

    
978
/* find out whether two IPv4 CIDR subnets overlap.
979
   Note: CIDR overlap means sn1/sn2 are identical or one is included in other. So sn using largest $bits will be the same  */
980
function check_subnetsv4_overlap($subnet1, $bits1, $subnet2, $bits2) {
981
	$largest_sn = min($bits1, $bits2);
982
	$subnetv4_start1 = gen_subnetv4($subnet1, $largest_sn);
983
	$subnetv4_start2 = gen_subnetv4($subnet2, $largest_sn);
984

    
985
	if ($subnetv4_start1 == '' || $subnetv4_start2 == '') {
986
		// One or both args is not a valid IPv4 subnet
987
		//FIXME: needs to return "bad data" not true/false if bad. For now return false, best we can do until fixed
988
		return false;
989
	}
990
	return ($subnetv4_start1 == $subnetv4_start2);
991
}
992

    
993
/* find out whether two IPv6 CIDR subnets overlap.
994
   Note: CIDR overlap means sn1/sn2 are identical or one is included in other. So sn using largest $bits will be the same  */
995
function check_subnetsv6_overlap($subnet1, $bits1, $subnet2, $bits2) {
996
	$largest_sn = min($bits1, $bits2);
997
	$subnetv6_start1 = gen_subnetv6($subnet1, $largest_sn);
998
	$subnetv6_start2 = gen_subnetv6($subnet2, $largest_sn);
999

    
1000
	if ($subnetv6_start1 == '' || $subnetv6_start2 == '') {
1001
		// One or both args is not a valid IPv6 subnet
1002
		//FIXME: needs to return "bad data" not true/false if bad. For now return false, best we can do until fixed
1003
		return false;
1004
	}
1005
	return ($subnetv6_start1 == $subnetv6_start2);
1006
}
1007

    
1008
/* return all PTR zones for a IPv6 network */
1009
function get_v6_ptr_zones($subnet, $bits) {
1010
	$result = array();
1011

    
1012
	if (!is_ipaddrv6($subnet)) {
1013
		return $result;
1014
	}
1015

    
1016
	if (!is_numericint($bits) || $bits > 128) {
1017
		return $result;
1018
	}
1019

    
1020
	/*
1021
	 * Find a small nibble boundary subnet mask
1022
	 * e.g. a /29 will create 8 /32 PTR zones
1023
	 */
1024
	$small_sn = $bits;
1025
	while ($small_sn % 4 != 0) {
1026
		$small_sn++;
1027
	}
1028

    
1029
	/* Get network prefix */
1030
	$small_subnet = Net_IPv6::getNetmask($subnet, $bits);
1031

    
1032
	/*
1033
	 * While small network is part of bigger one, increase 4-bit in last
1034
	 * digit to get next small network
1035
	 */
1036
	while (Net_IPv6::isInNetmask($small_subnet, $subnet, $bits)) {
1037
		/* Get a pure hex value */
1038
		$unpacked = unpack('H*hex', inet_pton($small_subnet));
1039
		/* Create PTR record using $small_sn / 4 chars */
1040
		$result[] = implode('.', array_reverse(str_split(substr(
1041
		    $unpacked['hex'], 0, $small_sn / 4)))).'.ip6.arpa';
1042

    
1043
		/* Detect what part of IP should be increased */
1044
		$change_part = (int) ($small_sn / 16);
1045
		if ($small_sn % 16 == 0) {
1046
			$change_part--;
1047
		}
1048

    
1049
		/* Increase 1 to desired part */
1050
		$parts = explode(":", Net_IPv6::uncompress($small_subnet));
1051
		$parts[$change_part]++;
1052
		$small_subnet = implode(":", $parts);
1053
	}
1054

    
1055
	return $result;
1056
}
1057

    
1058
/* return true if $addr is in $subnet, false if not */
1059
function ip_in_subnet($addr, $subnet) {
1060
	if (is_ipaddrv6($addr) && is_subnetv6($subnet)) {
1061
		return (Net_IPv6::isInNetmask($addr, $subnet));
1062
	} else if (is_ipaddrv4($addr) && is_subnetv4($subnet)) {
1063
		list($ip, $mask) = explode('/', $subnet);
1064
		$mask = (0xffffffff << (32 - $mask)) & 0xffffffff;
1065
		return ((ip2long($addr) & $mask) == (ip2long($ip) & $mask));
1066
	}
1067
	return false;
1068
}
1069

    
1070
/* returns true if $hostname is just a valid hostname (top part without any of the domain part) */
1071
function is_unqualified_hostname($hostname) {
1072
	if (!is_string($hostname)) {
1073
		return false;
1074
	}
1075

    
1076
	if (preg_match('/^(?:[a-z0-9_]|[a-z0-9_][a-z0-9_\-]*[a-z0-9_])$/i', $hostname)) {
1077
		return true;
1078
	} else {
1079
		return false;
1080
	}
1081
}
1082

    
1083
/* returns true if $hostname is a valid hostname, with or without being a fully-qualified domain name. */
1084
function is_hostname($hostname, $allow_wildcard=false) {
1085
	if (!is_string($hostname)) {
1086
		return false;
1087
	}
1088

    
1089
	if (is_domain($hostname, $allow_wildcard)) {
1090
		if ((substr_count($hostname, ".") == 1) && ($hostname[strlen($hostname)-1] == ".")) {
1091
			/* Only a single dot at the end like "test." - hosts cannot be directly in the root domain. */
1092
			return false;
1093
		} else {
1094
			return true;
1095
		}
1096
	} else {
1097
		return false;
1098
	}
1099
}
1100

    
1101
/* returns true if $domain is a valid domain name */
1102
function is_domain($domain, $allow_wildcard=false, $trailing_dot=true) {
1103
	if (!is_string($domain)) {
1104
		return false;
1105
	}
1106
	if (!$trailing_dot && ($domain[strlen($domain)-1] == ".")) {
1107
		return false;
1108
	}
1109
	if ($allow_wildcard) {
1110
		$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';
1111
	} else {
1112
		$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';
1113
	}
1114

    
1115
	if (preg_match($domain_regex, $domain)) {
1116
		return true;
1117
	} else {
1118
		return false;
1119
	}
1120
}
1121

    
1122
/* returns true if $macaddr is a valid MAC address */
1123
function is_macaddr($macaddr, $partial=false) {
1124
	$values = explode(":", $macaddr);
1125

    
1126
	/* Verify if the MAC address has a proper amount of parts for either a partial or full match. */
1127
	if ($partial) {
1128
		if ((count($values) < 1) || (count($values) > 6)) {
1129
			return false;
1130
		}
1131
	} elseif (count($values) != 6) {
1132
		return false;
1133
	}
1134
	for ($i = 0; $i < count($values); $i++) {
1135
		if (ctype_xdigit($values[$i]) == false)
1136
			return false;
1137
		if (hexdec($values[$i]) < 0 || hexdec($values[$i]) > 255)
1138
			return false;
1139
	}
1140

    
1141
	return true;
1142
}
1143

    
1144
/*
1145
	If $return_message is true then
1146
		returns a text message about the reason that the name is invalid.
1147
		the text includes the type of "thing" that is being checked, passed in $object. (e.g. "alias", "gateway group", "schedule")
1148
	else
1149
		returns true if $name is a valid name for an alias
1150
		returns false if $name is not a valid name for an alias
1151

    
1152
	Aliases cannot be:
1153
		bad chars: anything except a-z 0-9 and underscore
1154
		bad names: empty string, pure numeric, pure underscore
1155
		reserved words: pre-defined service/protocol/port names which should not be ambiguous, and the words "port" and  "pass" */
1156

    
1157
function is_validaliasname($name, $return_message = false, $object = "alias") {
1158
	/* Array of reserved words */
1159
	$reserved = array("port", "pass");
1160

    
1161
	if (!is_string($name) || strlen($name) >= 32 || preg_match('/(^_*$|^\d*$|[^a-z0-9_])/i', $name)) {
1162
		if ($return_message) {
1163
			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, _');
1164
		} else {
1165
			return false;
1166
		}
1167
	}
1168
	if (in_array($name, $reserved, true)) {
1169
		if ($return_message) {
1170
			return sprintf(gettext('The %1$s name must not be either of the reserved words %2$s or %3$s.'), $object, "'port'", "'pass'");
1171
		} else {
1172
			return false;
1173
		}
1174
	}
1175
	if (getprotobyname($name)) {
1176
		if ($return_message) {
1177
			return sprintf(gettext('The %1$s name must not be an IP protocol name such as TCP, UDP, ICMP etc.'), $object);
1178
		} else {
1179
			return false;
1180
		}
1181
	}
1182
	if (getservbyname($name, "tcp") || getservbyname($name, "udp")) {
1183
		if ($return_message) {
1184
			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);
1185
		} else {
1186
			return false;
1187
		}
1188
	}
1189
	if ($return_message) {
1190
		return sprintf(gettext('The %1$s name is valid.'), $object);
1191
	} else {
1192
		return true;
1193
	}
1194
}
1195

    
1196
/* returns a text message indicating if the alias name is valid, or the reason it is not valid. */
1197
function invalidaliasnamemsg($name, $object = "alias") {
1198
	return is_validaliasname($name, true, $object);
1199
}
1200

    
1201
/*
1202
 * returns true if $range is a valid integer range between $min and $max
1203
 * range delimiter can be ':' or '-'
1204
 */
1205
function is_intrange($range, $min, $max) {
1206
	$values = preg_split("/[:-]/", $range);
1207

    
1208
	if (!is_array($values) || count($values) != 2) {
1209
		return false;
1210
	}
1211

    
1212
	if (!ctype_digit($values[0]) || !ctype_digit($values[1])) {
1213
		return false;
1214
	}
1215

    
1216
	$values[0] = intval($values[0]);
1217
	$values[1] = intval($values[1]);
1218

    
1219
	if ($values[0] >= $values[1]) {
1220
		return false;
1221
	}
1222

    
1223
	if ($values[0] < $min || $values[1] > $max) {
1224
		return false;
1225
	}
1226

    
1227
	return true;
1228
}
1229

    
1230
/* returns true if $port is a valid TCP/UDP port */
1231
function is_port($port) {
1232
	if (ctype_digit($port) && ((intval($port) >= 1) && (intval($port) <= 65535))) {
1233
		return true;
1234
	}
1235
	if (getservbyname($port, "tcp") || getservbyname($port, "udp")) {
1236
		return true;
1237
	}
1238
	return false;
1239
}
1240

    
1241
/* returns true if $port is in use */
1242
function is_port_in_use($port, $proto = "tcp", $ip_version = 4) {
1243
	$port_info = array();
1244
	exec("/usr/bin/netstat --libxo json -an " . escapeshellarg('-' . $ip_version) . " -p " . escapeshellarg($proto), $rawdata, $rc);
1245
	if ($rc == 0) {
1246
		$netstatarr = json_decode(implode(" ", $rawdata), JSON_OBJECT_AS_ARRAY);
1247
		$netstatarr = $netstatarr['statistics']['socket'];
1248

    
1249
		foreach($netstatarr as $portstats){
1250
			array_push($port_info, $portstats['local']['port']);
1251
		}
1252
	}
1253

    
1254
	return in_array($port, $port_info);
1255
}
1256

    
1257
/* returns true if $portrange is a valid TCP/UDP portrange ("<port>:<port>") */
1258
function is_portrange($portrange) {
1259
	$ports = explode(":", $portrange);
1260

    
1261
	return (count($ports) == 2 && is_port($ports[0]) && is_port($ports[1]));
1262
}
1263

    
1264
/* returns true if $port is a valid TCP/UDP port number or range ("<port>:<port>") */
1265
function is_port_or_range($port) {
1266
	return (is_port($port) || is_portrange($port));
1267
}
1268

    
1269
/* returns true if $port is an alias that is a port type */
1270
function is_portalias($port) {
1271
	if (is_alias($port)) {
1272
		foreach (config_get_path('aliases/alias', []) as $alias) {
1273
			if ($alias['name'] == $port && preg_match("/port/i", $alias['type'])) {
1274
				return true;
1275
			}
1276
		}
1277
	}
1278
	return false;
1279
}
1280

    
1281
/* returns true if $port is a valid port number or an alias thereof */
1282
function is_port_or_alias($port) {
1283
	return (is_port($port) || is_portalias($port));
1284
}
1285

    
1286
/* returns true if $port is a valid TCP/UDP port number or range ("<port>:<port>") or an alias thereof */
1287
function is_port_or_range_or_alias($port) {
1288
	return (is_port($port) || is_portrange($port) || is_portalias($port));
1289
}
1290

    
1291
/* create ranges of sequential port numbers (200:215) and remove duplicates */
1292
function group_ports($ports, $kflc = false) {
1293
	if (!is_array($ports) || empty($ports)) {
1294
		return;
1295
	}
1296

    
1297
	$uniq = array();
1298
	$comments = array();
1299
	foreach ($ports as $port) {
1300
		if (($kflc) && (strpos($port, '#') === 0)) {	// Keep Full Line Comments (lines beginning with #).
1301
			$comments[] = $port;
1302
		} else if (is_portrange($port)) {
1303
			list($begin, $end) = explode(":", $port);
1304
			if ($begin > $end) {
1305
				$aux = $begin;
1306
				$begin = $end;
1307
				$end = $aux;
1308
			}
1309
			for ($i = $begin; $i <= $end; $i++) {
1310
				if (!in_array($i, $uniq)) {
1311
					$uniq[] = $i;
1312
				}
1313
			}
1314
		} else if (is_port($port)) {
1315
			if (!in_array($port, $uniq)) {
1316
				$uniq[] = $port;
1317
			}
1318
		}
1319
	}
1320
	sort($uniq, SORT_NUMERIC);
1321

    
1322
	$result = array();
1323
	foreach ($uniq as $idx => $port) {
1324
		if ($idx == 0) {
1325
			$result[] = $port;
1326
			continue;
1327
		}
1328

    
1329
		$last = end($result);
1330
		if (is_portrange($last)) {
1331
			list($begin, $end) = explode(":", $last);
1332
		} else {
1333
			$begin = $end = $last;
1334
		}
1335

    
1336
		if ($port == ($end+1)) {
1337
			$end++;
1338
			$result[count($result)-1] = "{$begin}:{$end}";
1339
		} else {
1340
			$result[] = $port;
1341
		}
1342
	}
1343

    
1344
	return array_merge($comments, $result);
1345
}
1346

    
1347
/* returns true if $val is a valid shaper bandwidth value */
1348
function is_valid_shaperbw($val) {
1349
	return (preg_match("/^(\d+(?:\.\d+)?)([MKG]?b|%)$/", $val));
1350
}
1351

    
1352
/* returns true if $test is in the range between $start and $end */
1353
function is_inrange_v4($test, $start, $end) {
1354
	if (!is_ipaddrv4($test) || !is_ipaddrv4($start) || !is_ipaddrv4($end)) {
1355
		return false;
1356
	}
1357

    
1358
	if (ip2ulong($test) <= ip2ulong($end) &&
1359
	    ip2ulong($test) >= ip2ulong($start)) {
1360
		return true;
1361
	}
1362

    
1363
	return false;
1364
}
1365

    
1366
/* returns true if $test is in the range between $start and $end */
1367
function is_inrange_v6($test, $start, $end) {
1368
	if (!is_ipaddrv6($test) || !is_ipaddrv6($start) || !is_ipaddrv6($end)) {
1369
		return false;
1370
	}
1371

    
1372
	if (inet_pton($test) <= inet_pton($end) &&
1373
	    inet_pton($test) >= inet_pton($start)) {
1374
		return true;
1375
	}
1376

    
1377
	return false;
1378
}
1379

    
1380
/* returns true if $test is in the range between $start and $end */
1381
function is_inrange($test, $start, $end) {
1382
	return is_ipaddrv6($test) ? is_inrange_v6($test, $start, $end) : is_inrange_v4($test, $start, $end);
1383
}
1384

    
1385
function build_vip_list($fif, $family = "all") {
1386
	$list = array('address' => gettext('Interface Address'));
1387

    
1388
	$viplist = get_configured_vip_list($family);
1389
	foreach ($viplist as $vip => $address) {
1390
		if ($fif == get_configured_vip_interface($vip)) {
1391
			$list[$vip] = "$address";
1392
			if (get_vip_descr($address)) {
1393
				$list[$vip] .= " (". get_vip_descr($address) .")";
1394
			}
1395
		}
1396
	}
1397

    
1398
	return($list);
1399
}
1400

    
1401
function get_configured_vip_list($family = 'all', $type = VIP_ALL) {
1402
	global $config;
1403

    
1404
	$list = array();
1405
	if (!array_key_exists('virtualip', $config) ||
1406
		!is_array($config['virtualip']) ||
1407
	    !is_array($config['virtualip']['vip']) ||
1408
	    empty($config['virtualip']['vip'])) {
1409
		return ($list);
1410
	}
1411

    
1412
	$viparr = &$config['virtualip']['vip'];
1413
	foreach ($viparr as $vip) {
1414

    
1415
		if ($type == VIP_CARP) {
1416
			if ($vip['mode'] != "carp")
1417
				continue;
1418
		} elseif ($type == VIP_IPALIAS) {
1419
			if ($vip['mode'] != "ipalias")
1420
				continue;
1421
		} else {
1422
			if ($vip['mode'] != "carp" && $vip['mode'] != "ipalias")
1423
				continue;
1424
		}
1425

    
1426
		if ($family == 'all' ||
1427
		    ($family == 'inet' && is_ipaddrv4($vip['subnet'])) ||
1428
		    ($family == 'inet6' && is_ipaddrv6($vip['subnet']))) {
1429
			$list["_vip{$vip['uniqid']}"] = $vip['subnet'];
1430
		}
1431
	}
1432
	return ($list);
1433
}
1434

    
1435
function get_configured_vip($vipinterface = '') {
1436

    
1437
	return (get_configured_vip_detail($vipinterface, 'all', 'vip'));
1438
}
1439

    
1440
function get_configured_vip_interface($vipinterface = '') {
1441

    
1442
	return (get_configured_vip_detail($vipinterface, 'all', 'iface'));
1443
}
1444

    
1445
function get_configured_vip_ipv4($vipinterface = '') {
1446

    
1447
	return (get_configured_vip_detail($vipinterface, 'inet', 'ip'));
1448
}
1449

    
1450
function get_configured_vip_ipv6($vipinterface = '') {
1451

    
1452
	return (get_configured_vip_detail($vipinterface, 'inet6', 'ip'));
1453
}
1454

    
1455
function get_configured_vip_subnetv4($vipinterface = '') {
1456

    
1457
	return (get_configured_vip_detail($vipinterface, 'inet', 'subnet'));
1458
}
1459

    
1460
function get_configured_vip_subnetv6($vipinterface = '') {
1461

    
1462
	return (get_configured_vip_detail($vipinterface, 'inet6', 'subnet'));
1463
}
1464

    
1465
function get_configured_vip_detail($vipinterface = '', $family = 'inet', $what = 'ip') {
1466
	global $config;
1467

    
1468
	if (empty($vipinterface) ||
1469
	    !is_array($config['virtualip']) ||
1470
	    !is_array($config['virtualip']['vip']) ||
1471
	    empty($config['virtualip']['vip'])) {
1472
		return (NULL);
1473
	}
1474

    
1475
	$viparr = &$config['virtualip']['vip'];
1476
	foreach ($viparr as $vip) {
1477
		if ($vip['mode'] != "carp" && $vip['mode'] != "ipalias") {
1478
			continue;
1479
		}
1480

    
1481
		if ($vipinterface != "_vip{$vip['uniqid']}") {
1482
			continue;
1483
		}
1484

    
1485
		switch ($what) {
1486
			case 'subnet':
1487
				if ($family == 'inet' && is_ipaddrv4($vip['subnet']))
1488
					return ($vip['subnet_bits']);
1489
				else if ($family == 'inet6' && is_ipaddrv6($vip['subnet']))
1490
					return ($vip['subnet_bits']);
1491
				break;
1492
			case 'iface':
1493
				return ($vip['interface']);
1494
				break;
1495
			case 'vip':
1496
				return ($vip);
1497
				break;
1498
			case 'ip':
1499
			default:
1500
				if ($family == 'inet' && is_ipaddrv4($vip['subnet'])) {
1501
					return ($vip['subnet']);
1502
				} else if ($family == 'inet6' && is_ipaddrv6($vip['subnet'])) {
1503
					return ($vip['subnet']);
1504
				}
1505
				break;
1506
		}
1507
		break;
1508
	}
1509

    
1510
	return (NULL);
1511
}
1512

    
1513
/* comparison function for sorting by the order in which interfaces are normally created */
1514
function compare_interface_friendly_names($a, $b) {
1515
	if ($a == $b) {
1516
		return 0;
1517
	} else if ($a == 'wan') {
1518
		return -1;
1519
	} else if ($b == 'wan') {
1520
		return 1;
1521
	} else if ($a == 'lan') {
1522
		return -1;
1523
	} else if ($b == 'lan') {
1524
		return 1;
1525
	}
1526

    
1527
	return strnatcmp($a, $b);
1528
}
1529

    
1530
/**
1531
 * Get the configured interfaces list
1532
 *
1533
 * @param bool $with_disabled Include disabled interfaces
1534
 *
1535
 * @return array
1536
 */
1537
function get_configured_interface_list(bool $with_disabled = false) : array
1538
{
1539
	$iflist = [];
1540
	foreach (config_get_path('interfaces', []) as $if => $if_detail) {
1541
		if ($with_disabled || isset($if_detail['enable'])) {
1542
			$iflist[$if] = $if;
1543
		}
1544
	}
1545

    
1546
	return ($iflist);
1547
}
1548

    
1549
/**
1550
 * Return the configured (and real) interfaces list.
1551
 *
1552
 * @param bool $with_disabled Include disabled interfaces
1553
 *
1554
 * @return array
1555
 */
1556
function get_configured_interface_list_by_realif(bool $with_disabled = false) : array
1557
{
1558
	$iflist = [];
1559
	foreach (config_get_path('interfaces', []) as $if => $if_detail) {
1560
		if ($with_disabled || isset($if_detail['enable'])) {
1561
			$tmpif = get_real_interface($if);
1562
			if (empty($tmpif)) {
1563
				continue;
1564
			}
1565
			$iflist[$tmpif] = $if;
1566
		}
1567
	}
1568

    
1569
	return ($iflist);
1570
}
1571

    
1572
/**
1573
 * Return the configured interfaces list with their description.
1574
 *
1575
 * @param bool $with_disabled Include disabled interfaces
1576
 *
1577
 * @return array
1578
 */
1579
function get_configured_interface_with_descr(bool $with_disabled = false) : array
1580
{
1581
	global $user_settings;
1582

    
1583
	$iflist = [];
1584
	foreach (config_get_path('interfaces', []) as $if => $if_detail) {
1585
		if ($with_disabled || isset($if_detail['enable'])) {
1586
			$iflist[$if] = array_get_path($if_detail, 'descr', $if);
1587
		}
1588
	}
1589

    
1590
	if (is_array($user_settings)
1591
	    && array_get_path($user_settings, 'webgui/interfacessort')) {
1592
		asort($iflist);
1593
	}
1594

    
1595
	return ($iflist);
1596
}
1597

    
1598
/*
1599
 *   get_configured_ip_addresses() - Return a list of all configured
1600
 *   IPv4 addresses.
1601
 *
1602
 */
1603
function get_configured_ip_addresses() {
1604
	global $config;
1605

    
1606
	if (!function_exists('get_interface_ip')) {
1607
		require_once("interfaces.inc");
1608
	}
1609
	$ip_array = array();
1610
	$interfaces = get_configured_interface_list();
1611
	if (is_array($interfaces)) {
1612
		foreach ($interfaces as $int) {
1613
			$ipaddr = get_interface_ip($int);
1614
			$ip_array[$int] = $ipaddr;
1615
		}
1616
	}
1617
	$interfaces = get_configured_vip_list('inet');
1618
	if (is_array($interfaces)) {
1619
		foreach ($interfaces as $int => $ipaddr) {
1620
			$ip_array[$int] = $ipaddr;
1621
		}
1622
	}
1623

    
1624
	/* pppoe server */
1625
	if (is_array($config['pppoes']) && is_array($config['pppoes']['pppoe'])) {
1626
		foreach ($config['pppoes']['pppoe'] as $pppoe) {
1627
			if ($pppoe['mode'] == "server") {
1628
				if (is_ipaddr($pppoe['localip'])) {
1629
					$int = "poes". $pppoe['pppoeid'];
1630
					$ip_array[$int] = $pppoe['localip'];
1631
				}
1632
			}
1633
		}
1634
	}
1635

    
1636
	return $ip_array;
1637
}
1638

    
1639
/*
1640
 *   get_configured_ipv6_addresses() - Return a list of all configured
1641
 *   IPv6 addresses.
1642
 *
1643
 */
1644
function get_configured_ipv6_addresses($linklocal_fallback = false) {
1645
	require_once("interfaces.inc");
1646
	$ipv6_array = array();
1647
	$interfaces = get_configured_interface_list();
1648
	if (is_array($interfaces)) {
1649
		foreach ($interfaces as $int) {
1650
			$ipaddrv6 = text_to_compressed_ip6(get_interface_ipv6($int, false, $linklocal_fallback));
1651
			$ipv6_array[$int] = $ipaddrv6;
1652
		}
1653
	}
1654
	$interfaces = get_configured_vip_list('inet6');
1655
	if (is_array($interfaces)) {
1656
		foreach ($interfaces as $int => $ipaddrv6) {
1657
			$ipv6_array[$int] = text_to_compressed_ip6($ipaddrv6);
1658
		}
1659
	}
1660
	return $ipv6_array;
1661
}
1662

    
1663
/*
1664
 *   get_interface_list() - Return a list of all physical interfaces
1665
 *   along with MAC and status.
1666
 *
1667
 *   $mode = "active" - use ifconfig -lu
1668
 *           "media"  - use ifconfig to check physical connection
1669
 *			status (much slower)
1670
 */
1671
function get_interface_list($mode = "active", $keyby = "physical", $vfaces = false) {
1672
	global $config;
1673
	$upints = array();
1674
	/* get a list of virtual interface types */
1675
	if (!$vfaces) {
1676
		$vfaces = array(
1677
				'bridge',
1678
				'ppp',
1679
				'pppoe',
1680
				'poes',
1681
				'pptp',
1682
				'l2tp',
1683
				'sl',
1684
				'gif',
1685
				'gre',
1686
				'faith',
1687
				'lo',
1688
				'ng',
1689
				'_vlan',
1690
				'_wlan',
1691
				'pflog',
1692
				'plip',
1693
				'pfsync',
1694
				'enc',
1695
				'tun',
1696
				'lagg',
1697
				'vip'
1698
		);
1699
	} else {
1700
		$vfaces = array(
1701
				'bridge',
1702
				'poes',
1703
				'sl',
1704
				'faith',
1705
				'lo',
1706
				'ng',
1707
				'_vlan',
1708
				'_wlan',
1709
				'pflog',
1710
				'plip',
1711
				'pfsync',
1712
				'enc',
1713
				'tun',
1714
				'lagg',
1715
				'vip',
1716
				'l2tps'
1717
		);
1718
	}
1719
	switch ($mode) {
1720
		case "active":
1721
			$upints = pfSense_interface_listget(IFF_UP);
1722
			break;
1723
		case "media":
1724
			$intlist = pfSense_interface_listget();
1725
			$ifconfig = [];
1726
			exec("/sbin/ifconfig -a", $ifconfig);
1727
			$ifstatus = preg_grep('/status:/', $ifconfig);
1728
			foreach ($ifstatus as $status) {
1729
				$int = array_shift($intlist);
1730
				if (stristr($status, "active")) {
1731
					$upints[] = $int;
1732
				}
1733
			}
1734
			break;
1735
		default:
1736
			$upints = pfSense_interface_listget();
1737
			break;
1738
	}
1739
	/* build interface list with netstat */
1740
	$linkinfo = [];
1741
	exec("/usr/bin/netstat -inW -f link | awk '{ print $1, $4 }'", $linkinfo);
1742
	array_shift($linkinfo);
1743
	/* build ip address list with netstat */
1744
	$ipinfo = [];
1745
	exec("/usr/bin/netstat -inW -f inet | awk '{ print $1, $4 }'", $ipinfo);
1746
	array_shift($ipinfo);
1747
	foreach ($linkinfo as $link) {
1748
		$friendly = "";
1749
		$alink = explode(" ", $link);
1750
		$ifname = rtrim(trim($alink[0]), '*');
1751
		/* trim out all numbers before checking for vfaces */
1752
		if (!in_array(array_shift(preg_split('/(\d-)*\d$/', $ifname)), $vfaces) &&
1753
		    interface_is_vlan($ifname) == NULL &&
1754
		    interface_is_qinq($ifname) == NULL &&
1755
		    !stristr($ifname, "_wlan") &&
1756
		    !stristr($ifname, "_stf")) {
1757
			$toput = array(
1758
					"mac" => trim($alink[1]),
1759
					"up" => in_array($ifname, $upints)
1760
				);
1761
			foreach ($ipinfo as $ip) {
1762
				$aip = explode(" ", $ip);
1763
				if ($aip[0] == $ifname) {
1764
					$toput['ipaddr'] = $aip[1];
1765
				}
1766
			}
1767
			if (is_array($config['interfaces'])) {
1768
				foreach ($config['interfaces'] as $name => $int) {
1769
					if ($int['if'] == $ifname) {
1770
						$friendly = $name;
1771
					}
1772
				}
1773
			}
1774
			switch ($keyby) {
1775
			case "physical":
1776
				if ($friendly != "") {
1777
					$toput['friendly'] = $friendly;
1778
				}
1779
				$dmesg_arr = array();
1780
				exec("/sbin/dmesg |grep $ifname | head -n1", $dmesg_arr);
1781
				preg_match_all("/<(.*?)>/i", $dmesg_arr[0], $dmesg);
1782
				$toput['dmesg'] = $dmesg[1][0];
1783
				$iflist[$ifname] = $toput;
1784
				break;
1785
			case "ppp":
1786

    
1787
			case "friendly":
1788
				if ($friendly != "") {
1789
					$toput['if'] = $ifname;
1790
					$iflist[$friendly] = $toput;
1791
				}
1792
				break;
1793
			}
1794
		}
1795
	}
1796
	return $iflist;
1797
}
1798

    
1799
function get_lagg_interface_list() {
1800
	global $config;
1801

    
1802
	$plist = array();
1803
	if (isset($config['laggs']['lagg']) && is_array($config['laggs']['lagg'])) {
1804
		foreach ($config['laggs']['lagg'] as $lagg) {
1805
			$lagg['mac'] = get_interface_mac($lagg['laggif']);
1806
			$lagg['islagg'] = true;
1807
			$plist[$lagg['laggif']] = $lagg;
1808
		}
1809
	}
1810

    
1811
	return ($plist);
1812
}
1813

    
1814
/****f* util/log_error
1815
* NAME
1816
*   log_error  - Sends a string to syslog.
1817
* INPUTS
1818
*   $error     - string containing the syslog message.
1819
* RESULT
1820
*   null
1821
******/
1822
function log_error($error) {
1823
	global $g;
1824
	$page = $_SERVER['SCRIPT_NAME'];
1825
	if (empty($page)) {
1826
		$files = get_included_files();
1827
		$page = basename($files[0]);
1828
	}
1829
	syslog(LOG_ERR, "$page: $error");
1830
	if ($g['debug']) {
1831
		syslog(LOG_WARNING, var_export(debug_backtrace()));
1832
	}
1833
	return;
1834
}
1835

    
1836
/****f* util/log_auth
1837
* NAME
1838
*   log_auth   - Sends a string to syslog as LOG_AUTH facility
1839
* INPUTS
1840
*   $error     - string containing the syslog message.
1841
* RESULT
1842
*   null
1843
******/
1844
function log_auth($error) {
1845
	global $g;
1846
	$page = $_SERVER['SCRIPT_NAME'];
1847
	syslog(LOG_AUTH, "$page: $error");
1848
	if ($g['debug']) {
1849
		syslog(LOG_WARNING, var_export(debug_backtrace()));
1850
	}
1851
	return;
1852
}
1853

    
1854
/****f* util/exec_command
1855
 * NAME
1856
 *   exec_command - Execute a command and return a string of the result.
1857
 * INPUTS
1858
 *   $command   - String of the command to be executed.
1859
 * RESULT
1860
 *   String containing the command's result.
1861
 * NOTES
1862
 *   This function returns the command's stdout and stderr.
1863
 ******/
1864
function exec_command($command) {
1865
	$output = array();
1866
	exec($command . ' 2>&1', $output);
1867
	return(implode("\n", $output));
1868
}
1869

    
1870
/* wrapper for exec()
1871
   Executes in background or foreground.
1872
   For background execution, returns PID of background process to allow calling code control */
1873
function mwexec($command, $nologentry = false, $clearsigmask = false, $background = false) {
1874
	global $g;
1875
	$retval = 0;
1876

    
1877
	if ($g['debug']) {
1878
		if (!$_SERVER['REMOTE_ADDR']) {
1879
			echo "mwexec(): $command" . ($background ? " [BG]":"") . "\n";
1880
		}
1881
	}
1882
	if ($clearsigmask) {
1883
		$oldset = array();
1884
		pcntl_sigprocmask(SIG_SETMASK, array(), $oldset);
1885
	}
1886

    
1887
	if ($background) {
1888
		// start background process and return PID
1889
		$retval = exec("/usr/bin/nohup $command > /dev/null 2>&1 & echo $!");
1890
	} else {
1891
		// run in foreground, and (optionally) log if nonzero return
1892
		$outputarray = array();
1893
		exec("$command 2>&1", $outputarray, $retval);
1894
		if (($retval <> 0) && (!$nologentry || isset($config['system']['developerspew']))) {
1895
			log_error(sprintf(gettext("The command '%1\$s' returned exit code '%2\$d', the output was '%3\$s' "), $command, $retval, implode(" ", $outputarray)));
1896
		}
1897
	}
1898

    
1899
	if ($clearsigmask) {
1900
		pcntl_sigprocmask(SIG_SETMASK, $oldset);
1901
	}
1902

    
1903
	return $retval;
1904
}
1905

    
1906
/* wrapper for exec() in background */
1907
function mwexec_bg($command, $clearsigmask = false) {
1908
	return mwexec($command, false, $clearsigmask, true);
1909
}
1910

    
1911
/*
1912
 * Unlink a file, or pattern-match of a file, if it exists
1913
 *
1914
 * If the file/path contains glob() compatible wildcards, all matching files
1915
 * will be unlinked.
1916
 * Any warning/errors are suppressed (e.g. no matching files to delete)
1917
 * If there are matching file(s) and they were all unlinked OK, then return
1918
 * true.  Otherwise return false (the requested file(s) did not exist, or
1919
 * could not be deleted), this allows the caller to know if they were the one
1920
 * to successfully delete the file(s).
1921
 */
1922
function unlink_if_exists($fn) {
1923
	$to_do = glob($fn);
1924
	if (is_array($to_do) && count($to_do) > 0) {
1925
		// Returns an array of boolean indicating if each unlink worked
1926
		$results = @array_map("unlink", $to_do);
1927
		// If there is no false in the array, then all went well
1928
		$result = !in_array(false, $results, true);
1929
	} else {
1930
		$result = @unlink($fn);
1931
	}
1932
	return $result;
1933
}
1934

    
1935
/* make a global alias table (for faster lookups) */
1936
function alias_make_table() {
1937
	global $aliastable;
1938

    
1939
	$aliastable = array();
1940

    
1941
	init_config_arr(array('aliases', 'alias'));
1942
	foreach (config_get_path('aliases/alias', []) as $alias) {
1943
		if ($alias['name']) {
1944
			$aliastable[$alias['name']] = $alias['address'];
1945
		}
1946
	}
1947
}
1948

    
1949
/* check if an alias exists */
1950
function is_alias($name) {
1951
	global $aliastable;
1952

    
1953
	return isset($aliastable[$name]);
1954
}
1955

    
1956
function alias_get_type($name) {
1957

    
1958
	foreach (config_get_path('aliases/alias', []) as $alias) {
1959
		if ($name == $alias['name']) {
1960
			return $alias['type'];
1961
		}
1962
	}
1963

    
1964
	return "";
1965
}
1966

    
1967
/* expand a host or network alias, if necessary */
1968
function alias_expand($name) {
1969
	global $aliastable;
1970
	$urltable_prefix = "/var/db/aliastables/";
1971
	$urltable_filename = $urltable_prefix . $name . ".txt";
1972

    
1973
	if (isset($aliastable[$name])) {
1974
		// alias names cannot be strictly numeric. redmine #4289
1975
		if (is_numericint($name)) {
1976
			return null;
1977
		}
1978
		/*
1979
		 * make sure if it's a ports alias, it actually exists.
1980
		 * redmine #5845
1981
		 */
1982
		foreach (config_get_path('aliases/alias', []) as $alias) {
1983
			if ($alias['name'] == $name) {
1984
				if ($alias['type'] == "urltable_ports") {
1985
					if (is_URL($alias['url']) &&
1986
					    file_exists($urltable_filename) &&
1987
					    !empty(trim(file_get_contents($urltable_filename)))) {
1988
						return "\${$name}";
1989
					} else {
1990
						return null;
1991
					}
1992
				}
1993
			}
1994
		}
1995
		return "\${$name}";
1996
	} else if (is_ipaddr($name) || is_subnet($name) ||
1997
	    is_port_or_range($name)) {
1998
		return "{$name}";
1999
	} else {
2000
		return null;
2001
	}
2002
}
2003

    
2004
function alias_expand_urltable($name) {
2005
	$urltable_prefix = "/var/db/aliastables/";
2006
	$urltable_filename = $urltable_prefix . $name . ".txt";
2007

    
2008
	foreach (config_get_path('aliases/alias', []) as $alias) {
2009
		if (!preg_match("/urltable/i", $alias['type']) ||
2010
		    ($alias['name'] != $name)) {
2011
			continue;
2012
		}
2013

    
2014
		if (is_URL($alias["url"]) && file_exists($urltable_filename)) {
2015
			if (!filesize($urltable_filename)) {
2016
				// file exists, but is empty, try to sync
2017
				send_event("service sync alias {$name}");
2018
			}
2019
			return $urltable_filename;
2020
		} else {
2021
			send_event("service sync alias {$name}");
2022
			break;
2023
		}
2024
	}
2025
	return null;
2026
}
2027

    
2028
/* obtain MAC address given an IP address by looking at the ARP/NDP table */
2029
function arp_get_mac_by_ip($ip, $do_ping = true) {
2030
	unset($macaddr);
2031
	$retval = 1;
2032
	switch (is_ipaddr($ip)) {
2033
		case 4:
2034
			if ($do_ping === true) {
2035
				mwexec("/sbin/ping -c 1 -t 1 " . escapeshellarg($ip), true);
2036
			}
2037
			$macaddr = exec("/usr/sbin/arp -n " . escapeshellarg($ip) . " | /usr/bin/awk '{print $4}'", $output, $retval);
2038
			break;
2039
		case 6:
2040
			if ($do_ping === true) {
2041
				mwexec("/sbin/ping6 -c 1 -X 1 " . escapeshellarg($ip), true);
2042
			}
2043
			$macaddr = exec("/usr/sbin/ndp -n " . escapeshellarg($ip) . " | /usr/bin/awk '{print $2}'", $output, $retval);
2044
			break;
2045
	}
2046
	if ($retval == 0 && is_macaddr($macaddr)) {
2047
		return $macaddr;
2048
	} else {
2049
		return false;
2050
	}
2051
}
2052

    
2053
/* return a fieldname that is safe for xml usage */
2054
function xml_safe_fieldname($fieldname) {
2055
	$replace = array(
2056
	    '/', '-', ' ', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')',
2057
	    '_', '+', '=', '{', '}', '[', ']', '|', '/', '<', '>', '?',
2058
	    ':', ',', '.', '\'', '\\'
2059
	);
2060
	return strtolower(str_replace($replace, "", $fieldname));
2061
}
2062

    
2063
function mac_format($clientmac) {
2064
	global $config, $cpzone;
2065

    
2066
	$mac = explode(":", $clientmac);
2067
	$mac_format = $cpzone ? $config['captiveportal'][$cpzone]['radmac_format'] : false;
2068

    
2069
	switch ($mac_format) {
2070
		case 'singledash':
2071
			return "$mac[0]$mac[1]$mac[2]-$mac[3]$mac[4]$mac[5]";
2072

    
2073
		case 'ietf':
2074
			return "$mac[0]-$mac[1]-$mac[2]-$mac[3]-$mac[4]-$mac[5]";
2075

    
2076
		case 'cisco':
2077
			return "$mac[0]$mac[1].$mac[2]$mac[3].$mac[4]$mac[5]";
2078

    
2079
		case 'unformatted':
2080
			return "$mac[0]$mac[1]$mac[2]$mac[3]$mac[4]$mac[5]";
2081

    
2082
		default:
2083
			return $clientmac;
2084
	}
2085
}
2086

    
2087
function resolve_retry($hostname, $protocol = 'inet') {
2088
	$retries = 10;
2089
	$numrecords = 1;
2090
	$recresult = array();
2091

    
2092
	switch ($protocol) {
2093
		case 'any':
2094
			$checkproto = 'is_ipaddr';
2095
			$dnsproto = DNS_ANY;
2096
			$dnstype = array('A', 'AAAA');
2097
			break;
2098
		case 'inet6':
2099
			$checkproto = 'is_ipaddrv6';
2100
			$dnsproto = DNS_AAAA;
2101
			$dnstype = array('AAAA');
2102
			break;
2103
		case 'inet': 
2104
		default:
2105
			$checkproto = 'is_ipaddrv4';
2106
			$dnsproto = DNS_A;
2107
			$dnstype = array('A');
2108
			break;
2109
	}
2110

    
2111
	for ($i = 0; $i < $retries; $i++) {
2112
		if ($checkproto($hostname)) {
2113
			return $hostname;
2114
		}
2115

    
2116
		$dnsresult = @dns_get_record($hostname, $dnsproto);
2117

    
2118
		if (!empty($dnsresult)) {
2119
			foreach ($dnsresult as $ip) {
2120
				if (is_array($ip)) {
2121
					if (in_array($ip['type'], $dnstype)) {
2122
						if ($checkproto($ip['ip'])) { 
2123
							$recresult[] = $ip['ip'];
2124
						}
2125

    
2126
						if ($checkproto($ip['ipv6'])) { 
2127
							$recresult[] = $ip['ipv6'];
2128
						}
2129
					}
2130
				}
2131
			}
2132
		}
2133

    
2134
		// Return on success
2135
		if (!empty($recresult)) {
2136
			if ($numrecords == 1) {
2137
				return $recresult[0];
2138
			} else {
2139
				return array_slice($recresult, 0, $numrecords);
2140
			}
2141
		}
2142

    
2143
		usleep(100000);
2144
	}
2145

    
2146
	return false;
2147
}
2148

    
2149
function format_bytes($bytes) {
2150
	if ($bytes >= 1099511627776) {
2151
		return sprintf("%.2f TiB", $bytes/1099511627776);
2152
	} else if ($bytes >= 1073741824) {
2153
		return sprintf("%.2f GiB", $bytes/1073741824);
2154
	} else if ($bytes >= 1048576) {
2155
		return sprintf("%.2f MiB", $bytes/1048576);
2156
	} else if ($bytes >= 1024) {
2157
		return sprintf("%.0f KiB", $bytes/1024);
2158
	} else {
2159
		return sprintf("%d B", $bytes);
2160
	}
2161
}
2162

    
2163
function format_number($num, $precision = 3) {
2164
	$units = array('', 'K', 'M', 'G', 'T');
2165

    
2166
	$i = 0;
2167
	while ($num > 1000 && $i < count($units)) {
2168
		$num /= 1000;
2169
		$i++;
2170
	}
2171
	$num = round($num, $precision);
2172

    
2173
	return ("$num {$units[$i]}");
2174
}
2175

    
2176

    
2177
function unformat_number($formated_num) {
2178
	$num = strtoupper($formated_num);
2179
    
2180
	if ( strpos($num,"T") !== false ) {
2181
		$num = str_replace("T","",$num) * 1000 * 1000 * 1000 * 1000;
2182
	} else if ( strpos($num,"G") !== false ) {
2183
		$num = str_replace("G","",$num) * 1000 * 1000 * 1000;
2184
	} else if ( strpos($num,"M") !== false ) {
2185
		$num = str_replace("M","",$num) * 1000 * 1000;
2186
	} else if ( strpos($num,"K") !== false ) {
2187
		$num = str_replace("K","",$num) * 1000;
2188
	}
2189
    
2190
	return $num;
2191
}
2192

    
2193
function update_filter_reload_status($text, $new=false) {
2194
	global $g;
2195

    
2196
	if ($new) {
2197
		file_put_contents("{$g['varrun_path']}/filter_reload_status", $text  . PHP_EOL);
2198
	} else {
2199
		file_put_contents("{$g['varrun_path']}/filter_reload_status", $text  . PHP_EOL, FILE_APPEND);
2200
	}
2201
}
2202

    
2203
/****** util/return_dir_as_array
2204
 * NAME
2205
 *   return_dir_as_array - Return a directory's contents as an array.
2206
 * INPUTS
2207
 *   $dir          - string containing the path to the desired directory.
2208
 *   $filter_regex - string containing a regular expression to filter file names. Default empty.
2209
 * RESULT
2210
 *   $dir_array - array containing the directory's contents. This array will be empty if the path specified is invalid.
2211
 ******/
2212
function return_dir_as_array($dir, $filter_regex = '') {
2213
	$dir_array = array();
2214
	if (is_dir($dir)) {
2215
		if ($dh = opendir($dir)) {
2216
			while (($file = readdir($dh)) !== false) {
2217
				if (($file == ".") || ($file == "..")) {
2218
					continue;
2219
				}
2220

    
2221
				if (empty($filter_regex) || preg_match($filter_regex, $file)) {
2222
					array_push($dir_array, $file);
2223
				}
2224
			}
2225
			closedir($dh);
2226
		}
2227
	}
2228
	return $dir_array;
2229
}
2230

    
2231
function run_plugins($directory) {
2232
	/* process packager manager custom rules */
2233
	$files = return_dir_as_array($directory);
2234
	if (is_array($files)) {
2235
		foreach ($files as $file) {
2236
			if (stristr($file, ".sh") == true) {
2237
				mwexec($directory . $file . " start");
2238
			} else if (!is_dir($directory . "/" . $file) && stristr($file, ".inc")) {
2239
				require_once($directory . "/" . $file);
2240
			}
2241
		}
2242
	}
2243
}
2244

    
2245
/*
2246
 *    safe_mkdir($path, $mode = 0755)
2247
 *    create directory if it doesn't already exist and isn't a file!
2248
 */
2249
function safe_mkdir($path, $mode = 0755) {
2250
	if (!is_file($path) && !is_dir($path)) {
2251
		return @mkdir($path, $mode, true);
2252
	} else {
2253
		return false;
2254
	}
2255
}
2256

    
2257
/*
2258
 * get_sysctl($names)
2259
 * Get values of sysctl OID's listed in $names (accepts an array or a single
2260
 * name) and return an array of key/value pairs set for those that exist
2261
 */
2262
function get_sysctl($names) {
2263
	if (empty($names)) {
2264
		return array();
2265
	}
2266

    
2267
	if (is_array($names)) {
2268
		$name_list = array();
2269
		foreach ($names as $name) {
2270
			$name_list[] = escapeshellarg($name);
2271
		}
2272
	} else {
2273
		$name_list = array(escapeshellarg($names));
2274
	}
2275

    
2276
	exec("/sbin/sysctl -iq " . implode(" ", $name_list), $output);
2277
	$values = array();
2278
	foreach ($output as $line) {
2279
		$line = explode(": ", $line, 2);
2280
		if (count($line) == 2) {
2281
			$values[$line[0]] = $line[1];
2282
		}
2283
	}
2284

    
2285
	return $values;
2286
}
2287

    
2288
/*
2289
 * get_single_sysctl($name)
2290
 * Wrapper for get_sysctl() to simplify read of a single sysctl value
2291
 * return the value for sysctl $name or empty string if it doesn't exist
2292
 */
2293
function get_single_sysctl($name) {
2294
	if (empty($name)) {
2295
		return "";
2296
	}
2297

    
2298
	$value = get_sysctl($name);
2299
	if (empty($value) || !isset($value[$name])) {
2300
		return "";
2301
	}
2302

    
2303
	return $value[$name];
2304
}
2305

    
2306
/*
2307
 * set_sysctl($value_list)
2308
 * Set sysctl OID's listed as key/value pairs and return
2309
 * an array with keys set for those that succeeded
2310
 */
2311
function set_sysctl($values) {
2312
	if (empty($values)) {
2313
		return array();
2314
	}
2315

    
2316
	$value_list = array();
2317
	foreach ($values as $key => $value) {
2318
		$value_list[] = escapeshellarg($key) . "=" . escapeshellarg($value);
2319
	}
2320

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

    
2323
	/* Retry individually if failed (one or more read-only) */
2324
	if ($success <> 0 && count($value_list) > 1) {
2325
		foreach ($value_list as $value) {
2326
			exec("/sbin/sysctl -iq " . $value, $output);
2327
		}
2328
	}
2329

    
2330
	$ret = array();
2331
	foreach ($output as $line) {
2332
		$line = explode(": ", $line, 2);
2333
		if (count($line) == 2) {
2334
			$ret[$line[0]] = true;
2335
		}
2336
	}
2337

    
2338
	return $ret;
2339
}
2340

    
2341
/*
2342
 * set_single_sysctl($name, $value)
2343
 * Wrapper to set_sysctl() to make it simple to set only one sysctl
2344
 * returns boolean meaning if it succeeded
2345
 */
2346
function set_single_sysctl($name, $value) {
2347
	if (empty($name)) {
2348
		return false;
2349
	}
2350

    
2351
	$result = set_sysctl(array($name => $value));
2352

    
2353
	if (!isset($result[$name]) || $result[$name] != $value) {
2354
		return false;
2355
	}
2356

    
2357
	return true;
2358
}
2359

    
2360
/*
2361
 *     get_memory()
2362
 *     returns an array listing the amount of
2363
 *     memory installed in the hardware
2364
 *     [0] net memory available for the OS (FreeBSD) after some is taken by BIOS, video or whatever - e.g. 235 MBytes
2365
 *     [1] real (actual) memory of the system, should be the size of the RAM card/s - e.g. 256 MBytes
2366
 */
2367
function get_memory() {
2368
	$physmem = get_single_sysctl("hw.physmem");
2369
	$realmem = get_single_sysctl("hw.realmem");
2370
	/* convert from bytes to megabytes */
2371
	return array(($physmem/1048576), ($realmem/1048576));
2372
}
2373

    
2374
function mute_kernel_msgs() {
2375
	if (config_path_enabled('system','enableserial')) {
2376
		return;
2377
	}
2378
	exec("/sbin/conscontrol mute on");
2379
}
2380

    
2381
function unmute_kernel_msgs() {
2382
	exec("/sbin/conscontrol mute off");
2383
}
2384

    
2385
function start_devd() {
2386
	global $g;
2387

    
2388
	/* Generate hints for the kernel loader. */
2389
	$module_paths = explode(";", get_single_sysctl("kern.module_path"));
2390
	foreach ($module_paths as $path) {
2391
		if (!is_dir($path) || file_exists("{$path}/linker.hints")) {
2392
			continue;
2393
		}
2394
		if (($files = scandir($path)) == false) {
2395
			continue;
2396
		}
2397
		$found = false;
2398
		foreach ($files as $file) {
2399
			if (strlen($file) > 3 &&
2400
			    strcasecmp(substr($file, -3), ".ko") == 0) {
2401
				$found = true;
2402
				break;
2403
			}
2404
		}
2405
		if ($found == false) {
2406
			continue;
2407
		}
2408
		$_gb = exec("/usr/sbin/kldxref $path");
2409
		unset($_gb);
2410
	}
2411

    
2412
	/* Use the undocumented -q options of devd to quiet its log spamming */
2413
	$_gb = exec("/sbin/devd -q -f /etc/{$g['product_name']}-devd.conf");
2414
	sleep(1);
2415
	unset($_gb);
2416
}
2417

    
2418
function is_interface_vlan_mismatch() {
2419
	foreach (config_get_path('vlans/vlan', []) as $vlan) {
2420
		if (substr($vlan['if'], 0, 4) == "lagg") {
2421
			return false;
2422
		}
2423
		if (does_interface_exist($vlan['if']) == false) {
2424
			return true;
2425
		}
2426
	}
2427

    
2428
	return false;
2429
}
2430

    
2431
function is_interface_mismatch() {
2432
	global $config, $g;
2433

    
2434
	$do_assign = false;
2435
	$i = 0;
2436
	$missing_interfaces = array();
2437
	if (is_array($config['interfaces'])) {
2438
		foreach ($config['interfaces'] as $ifcfg) {
2439
			if (interface_is_vlan($ifcfg['if']) != NULL ||
2440
			    interface_is_qinq($ifcfg['if']) != NULL ||
2441
			    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'])) {
2442
				// Do not check these interfaces.
2443
				$i++;
2444
				continue;
2445
			} else if (does_interface_exist($ifcfg['if']) == false) {
2446
				$missing_interfaces[] = $ifcfg['if'];
2447
				$do_assign = true;
2448
			} else {
2449
				$i++;
2450
			}
2451
		}
2452
	}
2453

    
2454
	/* VLAN/QinQ-only interface mismatch detection
2455
	 * see https://redmine.pfsense.org/issues/12170 */
2456
	init_config_arr(array('vlans', 'vlan'));
2457
	foreach ($config['vlans']['vlan'] as $vlan) {
2458
		if (!does_interface_exist($vlan['if']) && 
2459
		    !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'])) {
2460
			$missing_interfaces[] = $vlan['if'];
2461
			$do_assign = true;
2462
		}
2463
	}
2464
	init_config_arr(array('qinqs', 'qinqentry'));
2465
	foreach ($config['qinqs']['qinqentry'] as $qinq) {
2466
		if (!does_interface_exist($qinq['if']) &&
2467
		    !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'])) {
2468
			$missing_interfaces[] = $qinq['if'];
2469
			$do_assign = true;
2470
		}
2471
	}
2472

    
2473
	if (file_exists("{$g['tmp_path']}/assign_complete")) {
2474
		$do_assign = false;
2475
	}
2476

    
2477
	if (!empty($missing_interfaces) && $do_assign) {
2478
		file_put_contents("{$g['tmp_path']}/missing_interfaces", implode(' ', $missing_interfaces));
2479
	} else {
2480
		@unlink("{$g['tmp_path']}/missing_interfaces");
2481
	}
2482

    
2483
	return $do_assign;
2484
}
2485

    
2486
/* sync carp entries to other firewalls */
2487
function carp_sync_client() {
2488
	send_event("filter sync");
2489
}
2490

    
2491
/****f* util/isAjax
2492
 * NAME
2493
 *   isAjax - reports if the request is driven from prototype
2494
 * INPUTS
2495
 *   none
2496
 * RESULT
2497
 *   true/false
2498
 ******/
2499
function isAjax() {
2500
	return isset ($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest';
2501
}
2502

    
2503
/****f* util/timeout
2504
 * NAME
2505
 *   timeout - console input with timeout countdown. Note: erases 2 char of screen for timer. Leave space.
2506
 * INPUTS
2507
 *   optional, seconds to wait before timeout. Default 9 seconds.
2508
 * RESULT
2509
 *   returns 1 char of user input or null if no input.
2510
 ******/
2511
function timeout($timer = 9) {
2512
	while (!isset($key)) {
2513
		if ($timer >= 9) {
2514
			echo chr(8) . chr(8) . ($timer == 9 ? chr(32) : null) . "{$timer}";
2515
		} else {
2516
			echo chr(8). "{$timer}";
2517
		}
2518
		`/bin/stty -icanon min 0 time 25`;
2519
		$key = trim(`KEY=\`dd count=1 2>/dev/null\`; echo \$KEY`);
2520
		`/bin/stty icanon`;
2521
		if ($key == '') {
2522
			unset($key);
2523
		}
2524
		$timer--;
2525
		if ($timer == 0) {
2526
			break;
2527
		}
2528
	}
2529
	return $key;
2530
}
2531

    
2532
/****f* util/msort
2533
 * NAME
2534
 *   msort - sort array
2535
 * INPUTS
2536
 *   $array to be sorted, field to sort by, direction of sort
2537
 * RESULT
2538
 *   returns newly sorted array
2539
 ******/
2540
function msort($array, $id = "id", $sort_ascending = true) {
2541
	$temp_array = array();
2542
	if (!is_array($array)) {
2543
		return $temp_array;
2544
	}
2545
	while (count($array)>0) {
2546
		$lowest_id = 0;
2547
		$index = 0;
2548
		foreach ($array as $item) {
2549
			if (isset($item[$id])) {
2550
				if ($array[$lowest_id][$id]) {
2551
					if (strtolower($item[$id]) < strtolower($array[$lowest_id][$id])) {
2552
						$lowest_id = $index;
2553
					}
2554
				}
2555
			}
2556
			$index++;
2557
		}
2558
		$temp_array[] = $array[$lowest_id];
2559
		$array = array_merge(array_slice($array, 0, $lowest_id), array_slice($array, $lowest_id + 1));
2560
	}
2561
	if ($sort_ascending) {
2562
		return $temp_array;
2563
	} else {
2564
		return array_reverse($temp_array);
2565
	}
2566
}
2567

    
2568
/****f* util/is_URL
2569
 * NAME
2570
 *   is_URL
2571
 * INPUTS
2572
 *   $url: string to check
2573
 *   $httponly: Only allow HTTP or HTTPS scheme
2574
 * RESULT
2575
 *   Returns true if item is a URL
2576
 ******/
2577
function is_URL($url, $httponly = false) {
2578
	$match = preg_match("'\b(([\w-]+://?|www[.])[^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|/)))'", $url);
2579
	if ($match) {
2580
		if ($httponly) {
2581
			$urlparts = parse_url($url);
2582
			return in_array(strtolower($urlparts['scheme']), array('http', 'https'));
2583
		} else {
2584
			return true;
2585
		}
2586
	}
2587
	return false;
2588
}
2589

    
2590
function is_file_included($file = "") {
2591
	$files = get_included_files();
2592
	if (in_array($file, $files)) {
2593
		return true;
2594
	}
2595

    
2596
	return false;
2597
}
2598

    
2599
/*
2600
 * Replace a value on a deep associative array using regex
2601
 */
2602
function array_replace_values_recursive($data, $match, $replace) {
2603
	if (empty($data)) {
2604
		return $data;
2605
	}
2606

    
2607
	if (is_string($data)) {
2608
		$data = preg_replace("/{$match}/", $replace, $data);
2609
	} else if (is_array($data)) {
2610
		foreach ($data as $k => $v) {
2611
			$data[$k] = array_replace_values_recursive($v, $match, $replace);
2612
		}
2613
	}
2614

    
2615
	return $data;
2616
}
2617

    
2618
/*
2619
	This function was borrowed from a comment on PHP.net at the following URL:
2620
	https://www.php.net/manual/en/function.array-merge-recursive.php#73843
2621
 */
2622
function array_merge_recursive_unique($array0, $array1) {
2623

    
2624
	$arrays = func_get_args();
2625
	$remains = $arrays;
2626

    
2627
	// We walk through each arrays and put value in the results (without
2628
	// considering previous value).
2629
	$result = array();
2630

    
2631
	// loop available array
2632
	foreach ($arrays as $array) {
2633

    
2634
		// The first remaining array is $array. We are processing it. So
2635
		// we remove it from remaining arrays.
2636
		array_shift($remains);
2637

    
2638
		// We don't care non array param, like array_merge since PHP 5.0.
2639
		if (is_array($array)) {
2640
			// Loop values
2641
			foreach ($array as $key => $value) {
2642
				if (is_array($value)) {
2643
					// we gather all remaining arrays that have such key available
2644
					$args = array();
2645
					foreach ($remains as $remain) {
2646
						if (array_key_exists($key, $remain)) {
2647
							array_push($args, $remain[$key]);
2648
						}
2649
					}
2650

    
2651
					if (count($args) > 2) {
2652
						// put the recursion
2653
						$result[$key] = call_user_func_array(__FUNCTION__, $args);
2654
					} else {
2655
						foreach ($value as $vkey => $vval) {
2656
							if (!is_array($result[$key])) {
2657
								$result[$key] = array();
2658
							}
2659
							$result[$key][$vkey] = $vval;
2660
						}
2661
					}
2662
				} else {
2663
					// simply put the value
2664
					$result[$key] = $value;
2665
				}
2666
			}
2667
		}
2668
	}
2669
	return $result;
2670
}
2671

    
2672

    
2673
/*
2674
 * converts a string like "a,b,c,d"
2675
 * into an array like array("a" => "b", "c" => "d")
2676
 */
2677
function explode_assoc($delimiter, $string) {
2678
	$array = explode($delimiter, $string);
2679
	$result = array();
2680
	$numkeys = floor(count($array) / 2);
2681
	for ($i = 0; $i < $numkeys; $i += 1) {
2682
		$result[$array[$i * 2]] = $array[$i * 2 + 1];
2683
	}
2684
	return $result;
2685
}
2686

    
2687
/*
2688
 * Given a string of text with some delimiter, look for occurrences
2689
 * of some string and replace all of those.
2690
 * $text - the text string (e.g. "abc,defg,x123,ipv4,xyz")
2691
 * $delimiter - the delimiter (e.g. ",")
2692
 * $element - the element to match (e.g. "defg")
2693
 * $replacement - the string to replace it with (e.g. "42")
2694
 * Returns the resulting delimited string (e.g. "abc,42,x123,ipv4,xyz")
2695
 */
2696
function replace_element_in_list($text, $delimiter, $element, $replacement) {
2697
	$textArray = explode($delimiter, $text);
2698
	while (($entry = array_search($element, $textArray)) !== false) {
2699
		$textArray[$entry] = $replacement;
2700
	}
2701
	return implode(',', $textArray);
2702
}
2703

    
2704
/* Return system's route table */
2705
function route_table() {
2706
	exec("/usr/bin/netstat --libxo json -nWr", $rawdata, $rc);
2707

    
2708
	if ($rc != 0) {
2709
		return array();
2710
	}
2711

    
2712
	$netstatarr = json_decode(implode(" ", $rawdata), JSON_OBJECT_AS_ARRAY);
2713
	$netstatarr = $netstatarr['statistics']['route-information']
2714
	    ['route-table']['rt-family'];
2715

    
2716
	$result = array();
2717
	$result['inet'] = array();
2718
	$result['inet6'] = array();
2719
	foreach ($netstatarr as $item) {
2720
		if ($item['address-family'] == 'Internet') {
2721
			$result['inet'] = $item['rt-entry'];
2722
		} else if ($item['address-family'] == 'Internet6') {
2723
			$result['inet6'] = $item['rt-entry'];
2724
		}
2725
	}
2726
	unset($netstatarr);
2727

    
2728
	return $result;
2729
}
2730

    
2731
/* check if route is static (not BGP/OSPF) */
2732
function is_static_route($target, $ipprotocol = '') {
2733
	if (is_v4($target) || (($target == 'default') && ($ipprotocol == 'inet'))) {
2734
		$inet = '4';
2735
	} elseif (is_v6($target) || (($target == 'default') && ($ipprotocol == 'inet6'))) {
2736
		$inet = '6';
2737
	} else {
2738
		return false;
2739
	}
2740

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

    
2745
	return false;
2746
}
2747

    
2748
/* Get static route for specific destination */
2749
function route_get($target, $ipprotocol = '', $useroute = false) {
2750
	global $config;
2751

    
2752
	if (!empty($ipprotocol)) {
2753
		$family = $ipprotocol;
2754
	} else if (is_v4($target)) {
2755
		$family = 'inet';
2756
	} else if (is_v6($target)) {
2757
		$family = 'inet6';
2758
	}
2759

    
2760
	if (empty($family)) {
2761
		return array();
2762
	}
2763

    
2764
	if ($useroute) {
2765
		if ($family == 'inet') {
2766
			$inet = '4';
2767
		} else {
2768
			$inet = '6';
2769
		}
2770
		$interface = exec("/sbin/route -n{$inet} get {$target} 2>/dev/null | /usr/bin/awk '/interface:/{print $2}'");
2771
		if (empty($interface)) {
2772
			return array();
2773
		} elseif ($interface == 'lo0') {
2774
			// interface assigned IP address
2775
			foreach (array_keys($config['interfaces']) as $intf) {
2776
				if ((($inet == '4') && (get_interface_ip($intf) == $target)) ||
2777
				    (($inet == '6') && (get_interface_ipv6($intf) == $target))) {
2778
					$interface = convert_friendly_interface_to_real_interface_name($intf);
2779
					$gateway = $interface;
2780
					break;
2781
				}
2782
			}
2783
		} else {
2784
			$gateway = exec("/sbin/route -n{$inet} get {$target} 2>/dev/null | /usr/bin/awk '/gateway:/{print $2}'");
2785
			if (!$gateway) {
2786
				// non-local gateway
2787
				$gateway = get_interface_mac($interface);
2788
			}
2789
		}
2790
		$result[] = array('gateway' => $gateway, 'interface-name' => $interface);
2791
	} else {
2792
		$rtable = route_table();
2793
		if (empty($rtable)) {
2794
			return array();
2795
		}
2796

    
2797
		$result = array();
2798
		foreach ($rtable[$family] as $item) {
2799
			if ($item['destination'] == $target ||
2800
			    ip_in_subnet($target, $item['destination'])) {
2801
				$result[] = $item;
2802
			}
2803
		}
2804
	}
2805

    
2806
	return $result;
2807
}
2808

    
2809
/* Get default route */
2810
function route_get_default($ipprotocol) {
2811
	if (empty($ipprotocol) || ($ipprotocol != 'inet' &&
2812
	    $ipprotocol != 'inet6')) {
2813
		return '';
2814
	}
2815

    
2816
	$route = route_get('default', $ipprotocol, true);
2817

    
2818
	if (empty($route)) {
2819
		return '';
2820
	}
2821

    
2822
	if (!isset($route[0]['gateway'])) {
2823
		return '';
2824
	}
2825

    
2826
	return $route[0]['gateway'];
2827
}
2828

    
2829
/* Delete a static route */
2830
function route_del($target, $ipprotocol = '') {
2831
	global $config;
2832

    
2833
	if (empty($target)) {
2834
		return;
2835
	}
2836

    
2837
	if (!empty($ipprotocol) && $ipprotocol != 'inet' &&
2838
	    $ipprotocol != 'inet6') {
2839
		return false;
2840
	}
2841

    
2842
	$route = route_get($target, $ipprotocol, true);
2843

    
2844
	if (empty($route)) {
2845
		return;
2846
	}
2847

    
2848
	$target_prefix = '';
2849
	if (is_subnet($target)) {
2850
		$target_prefix = '-net';
2851
	} else if (is_ipaddr($target)) {
2852
		$target_prefix = '-host';
2853
	}
2854

    
2855
	if (!empty($ipprotocol)) {
2856
		$target_prefix .= " -{$ipprotocol}";
2857
	} else if (is_v6($target)) {
2858
		$target_prefix .= ' -inet6';
2859
	} else if (is_v4($target)) {
2860
		$target_prefix .= ' -inet';
2861
	}
2862

    
2863
	foreach ($route as $item) {
2864
		if (substr($item['gateway'], 0, 5) == 'link#') {
2865
			continue;
2866
		}
2867

    
2868
		if (is_macaddr($item['gateway'])) {
2869
			$gw = '-iface ' . $item['interface-name'];
2870
		} else {
2871
			$gw = $item['gateway'];
2872
		}
2873

    
2874
		exec(escapeshellcmd("/sbin/route del {$target_prefix} " .
2875
		    "{$target} {$gw}"), $output, $rc);
2876

    
2877
		if (isset($config['system']['route-debug'])) {
2878
			log_error("ROUTING debug: " . microtime() .
2879
			    " - DEL RC={$rc} - {$target} - gw: " . $gw);
2880
			file_put_contents("/dev/console", "\n[" . getmypid() .
2881
			    "] ROUTE DEL: {$target_prefix} {$target} {$gw} " .
2882
			    "result: {$rc}");
2883
		}
2884
	}
2885
}
2886

    
2887
/*
2888
 * Add static route.  If it already exists, remove it and re-add
2889
 *
2890
 * $target - IP, subnet or 'default'
2891
 * $gw     - gateway address
2892
 * $iface  - Network interface
2893
 * $args   - Extra arguments for /sbin/route
2894
 * $ipprotocol - 'inet' or 'inet6'.  Mandatory when $target == 'default'
2895
 *
2896
 */
2897
function route_add_or_change($target, $gw, $iface = '', $args = '',
2898
    $ipprotocol = '') {
2899
	global $config;
2900

    
2901
	if (empty($target) || (empty($gw) && empty($iface))) {
2902
		return false;
2903
	}
2904

    
2905
	if ($target == 'default' && empty($ipprotocol)) {
2906
		return false;
2907
	}
2908

    
2909
	if (!empty($ipprotocol) && $ipprotocol != 'inet' &&
2910
	    $ipprotocol != 'inet6') {
2911
		return false;
2912
	}
2913

    
2914
	/* use '-host' for IPv6 /128 routes, see https://redmine.pfsense.org/issues/11594 */
2915
	if (is_subnetv4($target) || (is_subnetv6($target) && (subnet_size($target) > 1))) {
2916
		$target_prefix = '-net';
2917
	} else if (is_ipaddr($target)) {
2918
		$target_prefix = '-host';
2919
	}
2920

    
2921
	if (!empty($ipprotocol)) {
2922
		$target_prefix .= " -{$ipprotocol}";
2923
	} else if (is_v6($target)) {
2924
		$target_prefix .= ' -inet6';
2925
	} else if (is_v4($target)) {
2926
		$target_prefix .= ' -inet';
2927
	}
2928

    
2929
	/* If there is another route to the same target, remove it */
2930
	route_del($target, $ipprotocol);
2931

    
2932
	$params = '';
2933
	if (!empty($iface) && does_interface_exist($iface)) {
2934
		$params .= " -iface {$iface}";
2935
	}
2936
	if (is_ipaddr($gw)) {
2937
		/* set correct linklocal gateway address,
2938
		 * see https://redmine.pfsense.org/issues/11713 
2939
		 * and https://redmine.pfsense.org/issues/11806 */
2940
		if (is_ipaddrv6($gw) && is_linklocal($gw) && empty(get_ll_scope($gw))) {
2941
			$routeget = route_get($gw, 'inet6', true);
2942
			$gw .= "%" . $routeget[0]['interface-name'];
2943
		}
2944
		$params .= " " . $gw;
2945
	}
2946

    
2947
	if (empty($params)) {
2948
		log_error("route_add_or_change: Invalid gateway {$gw} and/or " .
2949
		    "network interface {$iface}");
2950
		return false;
2951
	}
2952

    
2953
	exec(escapeshellcmd("/sbin/route add {$target_prefix} " .
2954
	    "{$target} {$args} {$params}"), $output, $rc);
2955

    
2956
	if (isset($config['system']['route-debug'])) {
2957
		log_error("ROUTING debug: " . microtime() .
2958
		    " - ADD RC={$rc} - {$target} {$args}");
2959
		file_put_contents("/dev/console", "\n[" . getmypid() .
2960
		    "] ROUTE ADD: {$target_prefix} {$target} {$args} " .
2961
		    "{$params} result: {$rc}");
2962
	}
2963

    
2964
	return ($rc == 0);
2965
}
2966

    
2967
function set_ipv6routes_mtu($interface, $mtu) {
2968
	global $config;
2969

    
2970
	$ipv6mturoutes = array();
2971
	$if = convert_real_interface_to_friendly_interface_name($interface);
2972
	if (!$config['interfaces'][$if]['ipaddrv6']) {
2973
		return;
2974
	}
2975
	$a_gateways = return_gateways_array();
2976
	$a_staticroutes = get_staticroutes(false, false, true);
2977
	foreach ($a_gateways as $gate) {
2978
		foreach ($a_staticroutes as $sroute) {
2979
			if (($gate['interface'] == $interface) &&
2980
			    ($sroute['gateway'] == $gate['name']) &&
2981
			    (is_ipaddrv6($gate['gateway']))) {
2982
				$tgt = $sroute['network'];
2983
				$gateway = $gate['gateway'];
2984
				$ipv6mturoutes[$tgt] = $gateway;
2985
			}
2986
		}
2987
		if (($gate['interface'] == $interface) &&
2988
		    $gate['isdefaultgw'] && is_ipaddrv6($gate['gateway'])) {
2989
			$tgt = "default";
2990
			$gateway = $gate['gateway'];
2991
			$ipv6mturoutes[$tgt] = $gateway;
2992
		}
2993
	}
2994
	foreach ($ipv6mturoutes as $tgt => $gateway) {
2995
		mwexec("/sbin/route change -6 -mtu " . escapeshellarg($mtu) .
2996
		    " " . escapeshellarg($tgt) . " " .
2997
		    escapeshellarg($gateway));
2998
	}
2999
}
3000

    
3001
function alias_to_subnets_recursive($name, $returnhostnames = false) {
3002
	global $aliastable;
3003
	$result = array();
3004
	if (!isset($aliastable[$name])) {
3005
		return $result;
3006
	}
3007
	$subnets = preg_split('/\s+/', $aliastable[$name]);
3008
	foreach ($subnets as $net) {
3009
		if (is_alias($net)) {
3010
			$sub = alias_to_subnets_recursive($net, $returnhostnames);
3011
			$result = array_merge($result, $sub);
3012
			continue;
3013
		} elseif (!is_subnet($net)) {
3014
			if (is_ipaddrv4($net)) {
3015
				$net .= "/32";
3016
			} else if (is_ipaddrv6($net)) {
3017
				$net .= "/128";
3018
			} else if ($returnhostnames === false || !is_fqdn($net)) {
3019
				continue;
3020
			}
3021
		}
3022
		$result[] = $net;
3023
	}
3024
	return $result;
3025
}
3026

    
3027
function get_staticroutes($returnsubnetsonly = false, $returnhostnames = false, $returnenabledroutesonly = false) {
3028
	global $config;
3029

    
3030
	/* Bail if there are no routes, but return an array always so callers don't have to check. */
3031
	init_config_arr(array('staticroutes', 'route'));
3032
	if (empty($config['staticroutes']['route'])) {
3033
		return array();
3034
	}
3035

    
3036
	$allstaticroutes = array();
3037
	$allsubnets = array();
3038
	/* Loop through routes and expand aliases as we find them. */
3039
	foreach ($config['staticroutes']['route'] as $route) {
3040
		if ($returnenabledroutesonly && isset($route['disabled'])) {
3041
			continue;
3042
		}
3043

    
3044
		if (is_alias($route['network'])) {
3045
			foreach (alias_to_subnets_recursive($route['network'], $returnhostnames) as $net) {
3046
				$temproute = $route;
3047
				$temproute['network'] = $net;
3048
				$allstaticroutes[] = $temproute;
3049
				$allsubnets[] = $net;
3050
			}
3051
		} elseif (is_subnet($route['network'])) {
3052
			$allstaticroutes[] = $route;
3053
			$allsubnets[] = $route['network'];
3054
		}
3055
	}
3056
	if ($returnsubnetsonly) {
3057
		return $allsubnets;
3058
	} else {
3059
		return $allstaticroutes;
3060
	}
3061
}
3062

    
3063
/****f* util/get_alias_list
3064
 * NAME
3065
 *   get_alias_list - Provide a list of aliases.
3066
 * INPUTS
3067
 *   $type          - Optional, can be a string or array specifying what type(s) of aliases you need.
3068
 * RESULT
3069
 *   Array containing list of aliases.
3070
 *   If $type is unspecified, all aliases are returned.
3071
 *   If $type is a string, all aliases of the type specified in $type are returned.
3072
 *   If $type is an array, all aliases of any type specified in any element of $type are returned.
3073
 */
3074
function get_alias_list($type = null) {
3075
	$result = array();
3076
	foreach (config_get_path('aliases/alias', []) as $alias) {
3077
		if ($type === null) {
3078
			$result[] = $alias['name'];
3079
		} else if (is_array($type)) {
3080
			if (in_array($alias['type'], $type)) {
3081
				$result[] = $alias['name'];
3082
			}
3083
		} else if ($type === $alias['type']) {
3084
			$result[] = $alias['name'];
3085
		}
3086
	}
3087
	return $result;
3088
}
3089

    
3090
/* returns an array consisting of every element of $haystack that is not equal to $needle. */
3091
function array_exclude($needle, $haystack) {
3092
	$result = array();
3093
	if (is_array($haystack)) {
3094
		foreach ($haystack as $thing) {
3095
			if ($needle !== $thing) {
3096
				$result[] = $thing;
3097
			}
3098
		}
3099
	}
3100
	return $result;
3101
}
3102

    
3103
/* Define what is preferred, IPv4 or IPv6 */
3104
function prefer_ipv4_or_ipv6() {
3105
	if (config_path_enabled('system', 'prefer_ipv4')) {
3106
		mwexec("/etc/rc.d/ip6addrctl prefer_ipv4");
3107
	} else {
3108
		mwexec("/etc/rc.d/ip6addrctl prefer_ipv6");
3109
	}
3110
}
3111

    
3112
/* Redirect to page passing parameters via POST */
3113
function post_redirect($page, $params) {
3114
	if (!is_array($params)) {
3115
		return;
3116
	}
3117

    
3118
	print "<html><body><form action=\"{$page}\" name=\"formredir\" method=\"post\">\n";
3119
	foreach ($params as $key => $value) {
3120
		print "<input type=\"hidden\" name=\"{$key}\" value=\"{$value}\" />\n";
3121
	}
3122
	print "</form>\n";
3123
	print "<script type=\"text/javascript\">\n";
3124
	print "//<![CDATA[\n";
3125
	print "document.formredir.submit();\n";
3126
	print "//]]>\n";
3127
	print "</script>\n";
3128
	print "</body></html>\n";
3129
}
3130

    
3131
/* Locate disks that can be queried for S.M.A.R.T. data. */
3132
function get_smart_drive_list() {
3133
	/* SMART supports some disks directly, and some controllers directly,
3134
	 * See https://redmine.pfsense.org/issues/9042 */
3135
	$supported_disk_types = array("ad", "da", "ada");
3136
	$supported_controller_types = array("nvme");
3137
	$disk_list = explode(" ", get_single_sysctl("kern.disks"));
3138
	foreach ($disk_list as $id => $disk) {
3139
		// We only want certain kinds of disks for S.M.A.R.T.
3140
		// 1 is a match, 0 is no match, False is any problem processing the regex
3141
		if (preg_match("/^(" . implode("|", $supported_disk_types) . ").*[0-9]{1,2}$/", $disk) !== 1) {
3142
			unset($disk_list[$id]);
3143
			continue;
3144
		}
3145
	}
3146
	foreach ($supported_controller_types as $controller) {
3147
		$devices = glob("/dev/{$controller}*");
3148
		if (!is_array($devices)) {
3149
			continue;
3150
		}
3151
		foreach ($devices as $device) {
3152
			$disk_list[] = basename($device);
3153
		}
3154
	}
3155
	sort($disk_list);
3156
	return $disk_list;
3157
}
3158

    
3159
// Validate a network address
3160
//	$addr: the address to validate
3161
//	$type: IPV4|IPV6|IPV4V6
3162
//	$label: the label used by the GUI to display this value. Required to compose an error message
3163
//	$err_msg: pointer to the callers error message array so that error messages can be added to it here
3164
//	$alias: are aliases permitted for this address?
3165
// Returns:
3166
//	IPV4 - if $addr is a valid IPv4 address
3167
//	IPV6 - if $addr is a valid IPv6 address
3168
//	ALIAS - if $alias=true and $addr is an alias
3169
//	false - otherwise
3170

    
3171
function validateipaddr(&$addr, $type, $label, &$err_msg, $alias=false) {
3172
	switch ($type) {
3173
		case IPV4:
3174
			if (is_ipaddrv4($addr)) {
3175
				return IPV4;
3176
			} else if ($alias) {
3177
				if (is_alias($addr)) {
3178
					return ALIAS;
3179
				} else {
3180
					$err_msg[] = sprintf(gettext("%s must be a valid IPv4 address or alias."), $label);
3181
					return false;
3182
				}
3183
			} else {
3184
				$err_msg[] = sprintf(gettext("%s must be a valid IPv4 address."), $label);
3185
				return false;
3186
			}
3187
		break;
3188
		case IPV6:
3189
			if (is_ipaddrv6($addr)) {
3190
				$addr = strtolower($addr);
3191
				return IPV6;
3192
			} else if ($alias) {
3193
				if (is_alias($addr)) {
3194
					return ALIAS;
3195
				} else {
3196
					$err_msg[] = sprintf(gettext("%s must be a valid IPv6 address or alias."), $label);
3197
					return false;
3198
				}
3199
			} else {
3200
				$err_msg[] = sprintf(gettext("%s must be a valid IPv6 address."), $label);
3201
				return false;
3202
			}
3203
		break;
3204
		case IPV4V6:
3205
			if (is_ipaddrv6($addr)) {
3206
				$addr = strtolower($addr);
3207
				return IPV6;
3208
			} else if (is_ipaddrv4($addr)) {
3209
				return IPV4;
3210
			} else if ($alias) {
3211
				if (is_alias($addr)) {
3212
					return ALIAS;
3213
				} else {
3214
					$err_msg[] = sprintf(gettext("%s must be a valid IPv4 or IPv6 address or alias."), $label);
3215
					return false;
3216
				}
3217
			} else {
3218
				$err_msg[] = sprintf(gettext("%s must be a valid IPv4 or IPv6 address."), $label);
3219
				return false;
3220
			}
3221
		break;
3222
	}
3223

    
3224
	return false;
3225
}
3226

    
3227
/* From DUID configuration inputs, format a string that looks (more) like the expected raw DUID format:
3228
 * 1) For DUIDs entered as a known DUID type, convert to a hexstring and prepend the DUID number, after having done the following:
3229
 *     a) For DUID-LLT and DUID-EN, convert the time/enterprise ID input to hex and append the link-layer address/identifier input.
3230
 *     b) For DUID-LLT and DUID-LL, prepend a hardware type of 1.
3231
 *     c) For DUID-UUID, remove any "-".
3232
 * 2) Replace any remaining "-" with ":".
3233
 * 3) If any components are input with just a single char (hex digit hopefully), put a "0" in front.
3234
 * 4) The first two components should be a 16-bit integer (little- or big-endian, depending on the current machine type) that
3235
 *    is equal to the number of other components. If not, prepend this as "nn:00" (all pfSense builds are little-endian).
3236
 *    This is convenience, because the DUID reported by dhcp6c in logs does not include this count, which corresponds to the
3237
 *    option-len field of DHCPv6's OPTION_CLIENTID option.
3238
 *
3239
 * The final result should be closer to:
3240
 *
3241
 * "nn:00:00:0n:nn:nn:nn:..."
3242
 *
3243
 * This function does not validate the input. is_duid() will do validation.
3244
 */
3245
function format_duid($duidtype, $duidpt1, $duidpt2=null) {
3246
	if ($duidpt2)
3247
		$duidpt1 = implode(':', str_split(str_pad(dechex($duidpt1), 8, '0', STR_PAD_LEFT), 2)) . ':' . $duidpt2;
3248

    
3249
	/* Make hexstrings */
3250
	if ($duidtype) {
3251
		switch ($duidtype) {
3252
		/* Add a hardware type to DUID-LLT and DUID-LL; assume Ethernet */
3253
		case 1:
3254
		case 3:
3255
			$duidpt1 = '00:01:' . $duidpt1;
3256
			break;
3257
		/* Remove '-' from given UUID and insert ':' every 2 characters */
3258
		case 4:
3259
			$duidpt1 = implode(':', str_split(str_replace('-', '', $duidpt1), 2));
3260
			break;
3261
		default:
3262
		}
3263
		$duidpt1 = '00:0' . $duidtype . ':' . $duidpt1;
3264
	}
3265

    
3266
	$values = explode(':', strtolower(str_replace('-', ':', $duidpt1)));
3267

    
3268
	if (hexdec($values[0]) != count($values) - 2)
3269
		array_unshift($values, dechex(count($values)), '00');
3270

    
3271
	array_walk($values, function(&$value) {
3272
		$value = str_pad($value, 2, '0', STR_PAD_LEFT);
3273
	});
3274

    
3275
	return implode(":", $values);
3276
}
3277

    
3278
/* Returns true if $dhcp6duid is a valid DUID entry.
3279
 * Parse the entry to check for valid length according to known DUID types.
3280
 */
3281
function is_duid($dhcp6duid) {
3282
	$values = explode(":", $dhcp6duid);
3283
	if (hexdec($values[0]) == count($values) - 2) {
3284
		switch (hexdec($values[2] . $values[3])) {
3285
		case 0:
3286
			return false;
3287
			break;
3288
		case 1:
3289
			if (count($values) != 16 || strlen($dhcp6duid) != 47)
3290
				return false;
3291
			break;
3292
		case 3:
3293
			if (count($values) != 12 || strlen($dhcp6duid) != 35)
3294
				return false;
3295
			break;
3296
		case 4:
3297
			if (count($values) != 20 || strlen($dhcp6duid) != 59)
3298
				return false;
3299
			break;
3300
		/* DUID is up to 128 octets; allow 2 octets for type code, 2 more for option-len */
3301
		default:
3302
			if (count($values) > 132 || strlen($dhcp6duid) != count($values) * 3 - 1)
3303
				return false;
3304
		}
3305
	} else
3306
		return false;
3307

    
3308
	for ($i = 0; $i < count($values); $i++) {
3309
		if (ctype_xdigit($values[$i]) == false)
3310
			return false;
3311
		if (hexdec($values[$i]) < 0 || hexdec($values[$i]) > 255)
3312
			return false;
3313
	}
3314

    
3315
	return true;
3316
}
3317

    
3318
/* Write the DHCP6 DUID file */
3319
function write_dhcp6_duid($duidstring) {
3320
	// Create the hex array from the dhcp6duid config entry and write to file
3321
	global $g;
3322

    
3323
	if(!is_duid($duidstring)) {
3324
		log_error(gettext("Error: attempting to write DUID file - Invalid DUID detected"));
3325
		return false;
3326
	}
3327
	$temp = str_replace(":","",$duidstring);
3328
	$duid_binstring = pack("H*",$temp);
3329
	if ($fd = fopen("{$g['vardb_path']}/dhcp6c_duid", "wb")) {
3330
		fwrite($fd, $duid_binstring);
3331
		fclose($fd);
3332
		return true;
3333
	}
3334
	log_error(gettext("Error: attempting to write DUID file - File write error"));
3335
	return false;
3336
}
3337

    
3338
/* returns duid string from 'vardb_path']}/dhcp6c_duid' */
3339
function get_duid_from_file() {
3340
	global $g;
3341

    
3342
	$duid_ASCII = "";
3343
	$count = 0;
3344

    
3345
	if (file_exists("{$g['vardb_path']}/dhcp6c_duid") &&
3346
	    ($fd = fopen("{$g['vardb_path']}/dhcp6c_duid", "r"))) {
3347
		$fsize = filesize("{$g['vardb_path']}/dhcp6c_duid");
3348
		if ($fsize <= 132) {
3349
			$buffer = fread($fd, $fsize);
3350
			while($count < $fsize) {
3351
				$duid_ASCII .= bin2hex($buffer[$count]);
3352
				$count++;
3353
				if($count < $fsize) {
3354
					$duid_ASCII .= ":";
3355
				}
3356
			}
3357
		}
3358
		fclose($fd);
3359
	}
3360
	//if no file or error with read then the string returns blanked DUID string
3361
	if(!is_duid($duid_ASCII)) {
3362
		return "--:--:--:--:--:--:--:--:--:--:--:--:--:--:--:--";
3363
	}
3364
	return($duid_ASCII);
3365
}
3366

    
3367
/* Replaces the Mac OS 9 and earlier (\r) and DOS/Windows (\r\n) newlines with the Unix equivalent (\n). */
3368
function unixnewlines($text) {
3369
	return preg_replace('/\r\n?/', "\n", $text);
3370
}
3371

    
3372
function array_remove_duplicate($array, $field) {
3373
	$cmp = array();
3374
	foreach ($array as $sub) {
3375
		if (isset($sub[$field])) {
3376
			$cmp[] = $sub[$field];
3377
		}
3378
	}
3379
	$unique = array_unique(array_reverse($cmp, true));
3380
	foreach (array_keys($unique) as $k) {
3381
		$new[] = $array[$k];
3382
	}
3383
	return $new;
3384
}
3385

    
3386
/**
3387
 * Return a value specified by a path of keys in a nested array, if it exists.
3388
 * @param $arr array value to search
3389
 * @param $path string path with '/' separators
3390
 * @param $default mixed value to return if the path is not found
3391
 * @returns mixed value at path or $default if the path does not exist or if the
3392
 *          path keys an empty string and $default is non-null
3393
 */
3394
function array_get_path(array &$arr, string $path, $default = null) {
3395
	$vpath = explode('/', $path);
3396
	$el = $arr;
3397
	foreach ($vpath as $key) {
3398
		if (mb_strlen($key) == 0) {
3399
			continue;
3400
		}
3401
		if (is_array($el) && array_key_exists($key, $el)) {
3402
			$el = $el[$key];
3403
		} else {
3404
			return ($default);
3405
		}
3406
	}
3407

    
3408
	if (($default !== null) && ($el === '')) {
3409
		return ($default);
3410
	}
3411
	
3412
	return ($el);
3413
}
3414

    
3415
/*
3416
 * Initialize an arbitrary array multiple levels deep only if unset
3417
 * @param $arr top of array
3418
 * @param $path string path with '/' separators
3419
 */
3420
function array_init_path(mixed &$arr, ?string $path)
3421
{
3422
	if (!is_array($arr)) {
3423
		$arr = [];
3424
	}
3425
	if (is_null($path)) {
3426
		return;
3427
	}
3428
	$tmp = &$arr;
3429
	foreach (explode('/', $path) as $key) {
3430
		if (!is_array($tmp[$key])) {
3431
			$tmp[$key] = [];
3432
		}
3433
		$tmp = &$tmp[$key];
3434
	}
3435
}
3436

    
3437
/**
3438
 * Set a value by path in a nested array, creating arrays for intermediary keys
3439
 * as necessary. If the path cannot be reached because an intermediary exists
3440
 * but is not empty or an array, return $default.
3441
 * @param $arr array value to search
3442
 * @param $path string path with '/' separators
3443
 * @param $value mixed 
3444
 * @param $default mixed value to return if the path is not found
3445
 * @returns mixed $val or $default if the path prefix does not exist
3446
 */
3447
function array_set_path(array &$arr, string $path, $value, $default = null) {
3448
	$vpath = explode('/', $path);
3449
	$vkey = null;
3450
	do {
3451
		$vkey = array_pop($vpath);
3452
	} while (mb_strlen($vkey) == 0);
3453
	if ($vkey == null) {
3454
		return ($default);
3455
	}
3456
	$el =& $arr;
3457
	foreach ($vpath as $key) {
3458
		if (mb_strlen($key) == 0) {
3459
			continue;
3460
		}
3461
		if (array_key_exists($key, $el) && !empty($el[$key])) {
3462
			if (!is_array($el[$key])) {
3463
					return ($default);
3464
			}
3465
		} else {
3466
				$el[$key] = [];
3467
		}
3468
		$el =& $el[$key];
3469
	}
3470
	$el[$vkey] = $value;
3471
	return ($value);
3472
}
3473

    
3474
/**
3475
 * Determine whether a path in a nested array has a non-null value keyed by
3476
 * $enable_key. 
3477
 * @param $arr array value to search
3478
 * @param $path string path with '/' separators
3479
 * @param $enable_key string an optional alternative key value for the enable key
3480
 * @returns bool true if $enable_key exists in the array at $path, and has a
3481
 * non-null value, otherwise false
3482
 */
3483
function array_path_enabled(array &$arr, string $path, $enable_key = "enable") {
3484
	$el = array_get_path($arr, $path, []);
3485
	if (is_array($el) && isset($el[$enable_key])) {
3486
		return (true);
3487
	}
3488
	return (false);
3489
}
3490

    
3491
/**
3492
 * Remove a key from the nested array by path.
3493
 * @param $arr array value to search
3494
 * @param $path string path with '/' separators
3495
 * @returns array copy of the removed value or null
3496
 */
3497
function array_del_path(array &$arr, string $path) {
3498
	$vpath = explode('/', $path);
3499
	$vkey = array_pop($vpath);
3500
	$el =& $arr;
3501
	foreach($vpath as $key) {
3502
		if (mb_strlen($key) == 0) {
3503
			continue;
3504
		}
3505
		if (is_array($el) && array_key_exists($key, $el)) {
3506
			$el =& $el[$key];
3507
		} else {
3508
			return null;
3509
		}
3510
	}
3511

    
3512
	if (!(is_array($el) && array_key_exists($vkey, $el))) {
3513
		return null;
3514
	}
3515

    
3516
	$ret = $el[$vkey];
3517
	unset($el[$vkey]);
3518
	return ($ret);
3519
}
3520

    
3521

    
3522
function dhcpd_date_adjust_gmt($dt) {
3523
	init_config_arr(array('dhcpd'));
3524

    
3525
	foreach (config_get_path('dhcpd', []) as $dhcpditem) {
3526
		if ($dhcpditem['dhcpleaseinlocaltime'] == "yes") {
3527
			$ts = strtotime($dt . " GMT");
3528
			if ($ts !== false) {
3529
				return strftime("%Y/%m/%d %H:%M:%S", $ts);
3530
			}
3531
		}
3532
	}
3533

    
3534
	/*
3535
	 * If we did not need to convert to local time or the conversion
3536
	 * failed, just return the input.
3537
	 */
3538
	return $dt;
3539
}
3540

    
3541
global $supported_image_types;
3542
$supported_image_types = array(
3543
	IMAGETYPE_JPEG,
3544
	IMAGETYPE_PNG,
3545
	IMAGETYPE_GIF,
3546
	IMAGETYPE_WEBP
3547
);
3548

    
3549
function is_supported_image($image_filename) {
3550
	global $supported_image_types;
3551
	$img_info = getimagesize($image_filename);
3552

    
3553
	/* If it's not an image, or it isn't in the supported list, return false */
3554
	if (($img_info === false) ||
3555
	    !in_array($img_info[2], array_keys($supported_image_types))) {
3556
		return false;
3557
	} else {
3558
		return $img_info[2];
3559
	}
3560
}
3561

    
3562
function get_lagg_ports ($laggport) {
3563
	$laggp = array();
3564
	foreach ($laggport as $lgp) {
3565
		list($lpname, $lpinfo) = explode(" ", $lgp);
3566
		preg_match('~<(.+)>~', $lpinfo, $lgportmode);
3567
		if ($lgportmode[1]) {
3568
			$laggp[] = $lpname . " (" . $lgportmode[1] . ")";
3569
		} else {
3570
			$laggp[] = $lpname;
3571
		}
3572
	}
3573
	if ($laggp) {
3574
		return implode(", ", $laggp);
3575
	} else {
3576
		return false;
3577
	}
3578
}
3579

    
3580
function cisco_to_cidr($addr) {
3581
	if (!is_ipaddr($addr)) {
3582
		throw new Exception('Invalid IP Addr');
3583
	}
3584

    
3585
	$mask = decbin(~ip2long($addr));
3586
	$mask = substr($mask, -32);
3587
	$k = 0;
3588
	for ($i = 0; $i <= 32; $i++) {
3589
		$k += intval($mask[$i]);
3590
	}
3591
	return $k;
3592
}
3593

    
3594
function cisco_extract_index($prule) {
3595
	$index = explode("#", $prule);
3596
	if (is_numeric($index[1])) {
3597
		return intval($index[1]);
3598
	} else {
3599
		syslog(LOG_WARNING, "Error parsing rule {$prule}: Could not extract index");
3600
	}
3601
	return -1;;
3602
}
3603

    
3604
function parse_cisco_acl_rule($rule, $devname, $dir, $proto) {
3605
	$rule_orig = $rule;
3606
	$rule = explode(" ", $rule);
3607
	$tmprule = "";
3608
	$index = 0;
3609

    
3610
	if ($rule[$index] == "permit") {
3611
		$startrule = "pass {$dir} quick on {$devname} ";
3612
	} else if ($rule[$index] == "deny") {
3613
		$startrule = "block {$dir} quick on {$devname} ";
3614
	} else {
3615
		return;
3616
	}
3617

    
3618
	$index++;
3619

    
3620
	switch ($rule[$index]) {
3621
		case "ip":
3622
			break;
3623
		case "icmp":
3624
			$icmp = ($proto == "inet") ? "icmp" : "ipv6-icmp";
3625
			$tmprule .= "proto {$icmp} ";
3626
			break;
3627
		case "tcp":
3628
		case "udp":
3629
			$tmprule .= "proto {$rule[$index]} ";
3630
			break;
3631
		default:
3632
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid protocol.");
3633
			return;
3634
	}
3635
	$index++;
3636

    
3637
	/* Source */
3638
	if (trim($rule[$index]) == "host") {
3639
		$index++;
3640
		if ((($proto == 'inet') && (is_ipaddrv4(trim($rule[$index])) || (trim($rule[$index]) == "{clientip}"))) ||
3641
		    (($proto == 'inet6') && (is_ipaddrv6(trim($rule[$index])) || (trim($rule[$index]) == "{clientipv6}")))) {
3642
			if ($GLOBALS['attributes']['framed_ip']) {
3643
				$tmprule .= "from {$GLOBALS['attributes']['framed_ip']} ";
3644
			} else {
3645
				$tmprule .= "from {$rule[$index]} ";
3646
			}
3647
			$index++;
3648
		} else {
3649
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source host '{$rule[$index]}'.");
3650
			return;
3651
		}
3652
	} elseif (is_subnetv6(trim($rule[$index])) && ($proto == "inet6")) {
3653
		$tmprule .= "from {$rule[$index]} ";
3654
		$index++;
3655
	} elseif (trim($rule[$index]) == "any") {
3656
		$tmprule .= "from any ";
3657
		$index++;
3658
	} else {
3659
		$network = $rule[$index];
3660
		$netmask = $rule[++$index];
3661

    
3662
		if (is_ipaddrv4($network) && ($proto == "inet")) {
3663
			try {
3664
				$netmask = cisco_to_cidr($netmask);
3665
			} catch(Exception) {
3666
				syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source netmask '$netmask'.");
3667
				return;
3668
			}
3669
			$tmprule .= "from {$network}/{$netmask} ";
3670
		} else {
3671
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source network '$network'.");
3672
			return;
3673
		}
3674

    
3675
		$index++;
3676
	}
3677

    
3678
	/* Source Operator */
3679
	if (in_array(trim($rule[$index]), array("lt", "gt", "eq", "neq"))) {
3680
		switch(trim($rule[$index])) {
3681
			case "lt":
3682
				$operator = "<";
3683
				break;
3684
			case "gt":
3685
				$operator = ">";
3686
				break;
3687
			case "eq":
3688
				$operator = "=";
3689
				break;
3690
			case "neq":
3691
				$operator = "!=";
3692
				break;
3693
		}
3694

    
3695
		$port = $rule[++$index];
3696
		if (is_port($port)) {
3697
			$tmprule .= "port {$operator} {$port} ";
3698
		} else {
3699
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source port: '$port' not a numeric value between 0 and 65535.");
3700
			return;
3701
		}
3702
		$index++;
3703
	} else if (trim($rule[$index]) == "range") {
3704
		$port = array($rule[++$index], $rule[++$index]);
3705
		if (is_port($port[0]) && is_port($port[1])) {
3706
			$tmprule .= "port {$port[0]}:{$port[1]} ";
3707
		} else {
3708
			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.");
3709
			return;
3710
		}
3711
		$index++;
3712
	}
3713

    
3714
	/* Destination */
3715
	if (trim($rule[$index]) == "host") {
3716
		$index++;
3717
		if ((($proto == 'inet') && (is_ipaddrv4(trim($rule[$index])) || (trim($rule[$index]) == "{clientip}"))) ||
3718
		    (($proto == 'inet6') && (is_ipaddrv6(trim($rule[$index])) || (trim($rule[$index]) == "{clientipv6}")))) {
3719
			$tmprule .= "to {$rule[$index]} ";
3720
			$index++;
3721
		} else {
3722
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination host '{$rule[$index]}'.");
3723
			return;
3724
		}
3725
	} elseif (is_subnetv6(trim($rule[$index])) && ($proto == "inet6")) {
3726
		$tmprule .= "to {$rule[$index]} ";
3727
		$index++;
3728
	} elseif (trim($rule[$index]) == "any") {
3729
		$tmprule .= "to any ";
3730
		$index++;
3731
	} else {
3732
		$network = $rule[$index];
3733
		$netmask = $rule[++$index];
3734

    
3735
		if (is_ipaddrv4($network) && ($proto == "inet")) {
3736
			try {
3737
				$netmask = cisco_to_cidr($netmask);
3738
			} catch(Exception) {
3739
				syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination network '$network'.");
3740
				return;
3741
			}
3742
			$tmprule .= "to {$network}/{$netmask} ";
3743
		} else {
3744
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination network '$network'.");
3745
			return;
3746
		}
3747

    
3748
		$index++;
3749
	}
3750

    
3751
	/* Destination Operator */
3752
	if (in_array(trim($rule[$index]), array("lt", "gt", "eq", "neq"))) {
3753
		switch(trim($rule[$index])) {
3754
			case "lt":
3755
				$operator = "<";
3756
				break;
3757
			case "gt":
3758
				$operator = ">";
3759
				break;
3760
			case "eq":
3761
				$operator = "=";
3762
				break;
3763
			case "neq":
3764
				$operator = "!=";
3765
				break;
3766
		}
3767

    
3768
		$port = $rule[++$index];
3769
		if (is_port($port)) {
3770
			$tmprule .= "port {$operator} {$port} ";
3771
		} else {
3772
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination port: '$port' not a numeric value between 0 and 65535.");
3773
			return;
3774
		}
3775
		$index++;
3776
	} else if (trim($rule[$index]) == "range") {
3777
		$port = array($rule[++$index], $rule[++$index]);
3778
		if (is_port($port[0]) && is_port($port[1])) {
3779
			$tmprule .= "port {$port[0]}:{$port[1]} ";
3780
		} else {
3781
			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.");
3782
			return;
3783
		}
3784
		$index++;
3785
	}
3786

    
3787
	$tmprule = $startrule . $proto . " " . $tmprule;
3788
	return $tmprule;
3789
}
3790

    
3791
function parse_cisco_acl($attribs, $dev) {
3792
	global $attributes;
3793

    
3794
	if (!is_array($attribs)) {
3795
		return "";
3796
	}
3797
	$finalrules = "";
3798
	if (is_array($attribs['ciscoavpair'])) {
3799
		$inrules = array('inet' => array(), 'inet6' => array());
3800
		$outrules = array('inet' => array(), 'inet6' => array());
3801
		foreach ($attribs['ciscoavpair'] as $avrules) {
3802
			$rule = explode("=", $avrules);
3803
			$dir = "";
3804
			if (strstr($rule[0], "inacl")) {
3805
				$dir = "in";
3806
			} else if (strstr($rule[0], "outacl")) {
3807
				$dir = "out";
3808
			} else if (strstr($rule[0], "dns-servers")) {
3809
				$attributes['dns-servers'] = explode(" ", $rule[1]);
3810
				continue;
3811
			} else if (strstr($rule[0], "route")) {
3812
				if (!is_array($attributes['routes'])) {
3813
					$attributes['routes'] = array();
3814
				}
3815
				$attributes['routes'][] = $rule[1];
3816
				continue;
3817
			}
3818
			$rindex = cisco_extract_index($rule[0]);
3819
			if ($rindex < 0) {
3820
				continue;
3821
			}
3822

    
3823
			if (strstr($rule[0], "ipv6")) {
3824
				$proto = "inet6";
3825
			} else {
3826
				$proto = "inet";
3827
			}
3828

    
3829
			$tmprule = parse_cisco_acl_rule($rule[1], $dev, $dir, $proto);
3830
			if (!empty($tmprule)) {
3831
				if ($dir == "in") {
3832
					$inrules[$proto][$rindex] = $tmprule;
3833
				} else if ($dir == "out") {
3834
					$outrules[$proto][$rindex] = $tmprule;
3835
				}
3836
			}
3837
		}
3838

    
3839

    
3840
		$state = "";
3841
		foreach (array('inet', 'inet6') as $ip) {
3842
			if (!empty($outrules[$ip])) {
3843
				$state = "no state";
3844
			}
3845
			ksort($inrules[$ip], SORT_NUMERIC);
3846
			foreach ($inrules[$ip] as $inrule) {
3847
				$finalrules .= "{$inrule} {$state}\n";
3848
			}
3849
			if (!empty($outrules[$ip])) {
3850
				ksort($outrules[$ip], SORT_NUMERIC);
3851
				foreach ($outrules[$ip] as $outrule) {
3852
					$finalrules .= "{$outrule} {$state}\n";
3853
				}
3854
			}
3855
		}
3856
	}
3857
	return $finalrules;
3858
}
3859

    
3860
function alias_idn_to_utf8($alias) {
3861
	if (is_alias($alias)) {
3862
		return $alias;
3863
	} else {
3864
		return idn_to_utf8($alias);
3865
	}
3866
}
3867

    
3868
function alias_idn_to_ascii($alias) {
3869
	if (is_alias($alias)) {
3870
		return $alias;
3871
	} else {
3872
		return idn_to_ascii($alias);
3873
	}
3874
}
3875

    
3876
// These functions were in guiconfig.inc but have been moved here so non GUI processes can use them
3877
function address_to_pconfig($adr, &$padr, &$pmask, &$pnot, &$pbeginport, &$pendport) {
3878
	if (isset($adr['any'])) {
3879
		$padr = "any";
3880
	} else if ($adr['network']) {
3881
		$padr = $adr['network'];
3882
	} else if ($adr['address']) {
3883
		list($padr, $pmask) = explode("/", $adr['address']);
3884
		if (!$pmask) {
3885
			if (is_ipaddrv6($padr)) {
3886
				$pmask = 128;
3887
			} else {
3888
				$pmask = 32;
3889
			}
3890
		}
3891
	}
3892

    
3893
	if (isset($adr['not'])) {
3894
		$pnot = 1;
3895
	} else {
3896
		$pnot = 0;
3897
	}
3898

    
3899
	if ($adr['port']) {
3900
		list($pbeginport, $pendport) = explode("-", $adr['port']);
3901
		if (!$pendport) {
3902
			$pendport = $pbeginport;
3903
		}
3904
	} else if (!is_alias($pbeginport) && !is_alias($pendport)) {
3905
		$pbeginport = "any";
3906
		$pendport = "any";
3907
	}
3908
}
3909

    
3910
function pconfig_to_address(&$adr, $padr, $pmask, $pnot = false, $pbeginport = 0, $pendport = 0, $addmask = false) {
3911
	$adr = array();
3912

    
3913
	if ($padr == "any") {
3914
		$adr['any'] = true;
3915
	} else if (is_specialnet($padr)) {
3916
		if ($addmask) {
3917
			$padr .= "/" . $pmask;
3918
		}
3919
		$adr['network'] = $padr;
3920
	} else {
3921
		$adr['address'] = $padr;
3922
		if (is_ipaddrv6($padr)) {
3923
			if ($pmask != 128) {
3924
				$adr['address'] .= "/" . $pmask;
3925
			}
3926
		} else {
3927
			if ($pmask != 32) {
3928
				$adr['address'] .= "/" . $pmask;
3929
			}
3930
		}
3931
	}
3932

    
3933
	if ($pnot) {
3934
		$adr['not'] = true;
3935
	} else {
3936
		unset($adr['not']);
3937
	}
3938

    
3939
	if (($pbeginport != 0) && ($pbeginport != "any")) {
3940
		if ($pbeginport != $pendport) {
3941
			$adr['port'] = $pbeginport . "-" . $pendport;
3942
		} else {
3943
			$adr['port'] = $pbeginport;
3944
		}
3945
	}
3946

    
3947
	/*
3948
	 * If the port is still unset, then it must not be numeric, but could
3949
	 * be an alias or a well-known/registered service.
3950
	 * See https://redmine.pfsense.org/issues/8410
3951
	 */
3952
	if (!isset($adr['port']) && is_port_or_alias($pbeginport)) {
3953
		$adr['port'] = $pbeginport;
3954
	}
3955
}
3956

    
3957
function is_specialnet($net) {
3958
	global $specialsrcdst;
3959

    
3960
	if (!$net) {
3961
		return false;
3962
	}
3963
	if (in_array($net, $specialsrcdst)) {
3964
		return true;
3965
	} else {
3966
		return false;
3967
	}
3968
}
3969

    
3970
function is_interface_ipaddr($interface) {
3971
	if (!empty(config_get_path("interfaces/{$interface}/ipaddr"))) {
3972
		return true;
3973
	}
3974
	return false;
3975
}
3976

    
3977
function is_interface_ipaddrv6($interface) {
3978
	if (!empty(config_get_path("interfaces/{$interface}/ipaddrv6"))) {
3979
		return true;
3980
	}
3981
	return false;
3982
}
3983

    
3984
function escape_filter_regex($filtertext) {
3985
	/* If the caller (user) has not already put a backslash before a slash, to escape it in the regex, */
3986
	/* then this will do it. Take out any "\/" already there, then turn all ordinary "/" into "\/".    */
3987
	return str_replace('/', '\/', str_replace('\/', '/', $filtertext));
3988
}
3989

    
3990
/*
3991
 * Check if a given pattern has the same number of two different unescaped
3992
 * characters.
3993
 * For example, it can ensure a pattern has balanced sets of parentheses,
3994
 * braces, and brackets.
3995
 */
3996
function is_pattern_balanced_char($pattern, $open, $close) {
3997
	/* First remove escaped versions */
3998
	$pattern = str_replace('\\' . $open, '', $pattern);
3999
	$pattern = str_replace('\\' . $close, '', $pattern);
4000
	/* Check if the counts of both characters match in the target pattern */
4001
	return (substr_count($pattern, $open) == substr_count($pattern, $close));
4002
}
4003

    
4004
/*
4005
 * Check if a pattern contains balanced sets of parentheses, braces, and
4006
 * brackets.
4007
 */
4008
function is_pattern_balanced($pattern) {
4009
	if (is_pattern_balanced_char($pattern, '(', ')') &&
4010
	    is_pattern_balanced_char($pattern, '{', '}') &&
4011
	    is_pattern_balanced_char($pattern, '[', ']')) {
4012
		/* Balanced if all are true */
4013
		return true;
4014
	}
4015
	return false;
4016
}
4017

    
4018
function cleanup_regex_pattern($filtertext) {
4019
	/* Cleanup filter to prevent backreferences. */
4020
	$filtertext = escape_filter_regex($filtertext);
4021

    
4022
	/* Remove \<digit>+ backreferences
4023
	 * To match \ it must be escaped as \\\\ in PHP for preg_replace() */
4024
	$filtertext = preg_replace('/\\\\\\d+/', '', $filtertext);
4025

    
4026
	/* Check for unbalanced parentheses, braces, and brackets which
4027
	 * may be an error or attempt to circumvent protections.
4028
	 * Also discard any pattern that attempts problematic duplication
4029
	 * methods. */
4030
	if (!is_pattern_balanced($filtertext) ||
4031
	    (substr_count($filtertext, ')*') > 0) ||
4032
	    (substr_count($filtertext, ')+') > 0) ||
4033
	    (substr_count($filtertext, '{') > 0)) {
4034
		return '';
4035
	}
4036

    
4037
	return $filtertext;
4038
}
4039

    
4040
function ip6_to_asn1($addr) {
4041
	/* IPv6 MIB uses an OCTET STRING of length 16 to represent
4042
	 * 128-bit IPv6 address in network byte order.
4043
	 * see https://datatracker.ietf.org/doc/html/rfc2465#section-3 
4044
	 * i.e. fc00:3::4 = 252.0.0.3.0.0.0.0.0.0.0.0.0.0.0.4
4045
	 */
4046

    
4047
	if (!is_ipaddrv6($addr)) {
4048
		return false;
4049
	}
4050
	$ipv6str = "";
4051
	$octstr = "";
4052
	foreach (explode(':', Net_IPv6::uncompress($addr)) as $v) {
4053
		$ipv6str .= str_pad($v, 4, '0', STR_PAD_LEFT);
4054
	}
4055
	foreach (str_split($ipv6str, 2) as $v) {
4056
		$octstr .= base_convert($v, 16, 10) . '.';
4057
	}
4058

    
4059
	return $octstr;
4060
}
4061

    
4062
function interfaces_interrupts() {
4063
	exec("/usr/bin/vmstat -i --libxo json", $rawdata, $rc);
4064
	$interrupts = array();
4065
	if ($rc == 0) {
4066
		$vnstatarr = json_decode(implode(" ", $rawdata), JSON_OBJECT_AS_ARRAY);
4067
		$interruptarr = $vnstatarr['interrupt-statistics']['interrupt'];
4068

    
4069
		foreach ($interruptarr as $int){
4070
			preg_match("/irq\d+: ([a-z0-9]+)/", $int['name'], $matches);
4071
			$name = $matches[1];
4072
			if (array_key_exists($name, $interrupts)) {
4073
				/* interface with multiple queues */
4074
				$interrupts[$name]['total'] += $int['total'];
4075
				$interrupts[$name]['rate'] += $int['rate'];
4076
			} else {
4077
				$interrupts[$name]['total'] = $int['total'];
4078
				$interrupts[$name]['rate'] = $int['rate'];
4079
			}
4080
		}
4081
	}
4082

    
4083
	return $interrupts;
4084
}
4085

    
4086
function dummynet_load_module($max_qlimit) {
4087
	if (!is_module_loaded("dummynet.ko")) {
4088
		mute_kernel_msgs();
4089
		mwexec("/sbin/kldload dummynet");
4090
		unmute_kernel_msgs();
4091
	}
4092
	$sysctls = (array(
4093
			"net.inet.ip.dummynet.io_fast" => "1",
4094
			"net.inet.ip.dummynet.hash_size" => "256",
4095
			"net.inet.ip.dummynet.pipe_slot_limit" => $max_qlimit
4096
	));
4097
	init_config_arr(array('sysctl', 'item'));
4098
	foreach (config_get_path('sysctl/item', []) as $item) {
4099
		if (preg_match('/net\.inet\.ip\.dummynet\./', $item['tunable'])) {
4100
			$sysctls[$item['tunable']] = $item['value'];
4101
		}
4102
	}
4103
	set_sysctl($sysctls);
4104
}
4105

    
4106
function get_interface_vip_ips($interface) {
4107
	global $config;
4108
	$vipips = '';
4109

    
4110
	init_config_arr(array('virtualip', 'vip'));
4111
	foreach ($config['virtualip']['vip'] as $vip) {
4112
		if (($vip['interface'] == $interface) &&
4113
		    (($vip['mode'] == "carp") || ($vip['mode'] == "ipalias"))) {
4114
			$vipips .= $vip['subnet'] . ' ';
4115
		}
4116
	}
4117
	return $vipips;
4118
}
4119

    
4120
?>
(54-54/61)