Project

General

Profile

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

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

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

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

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

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

    
74
	return 0;
75
}
76

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

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

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

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

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

    
102
	return false;
103
}
104

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

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

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

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

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

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

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

    
165
		return $fp;
166
	}
167

    
168
	return NULL;
169
}
170

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
372
	foreach ( $parts as $v ) {
373

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

    
377
	}
378

    
379
	return $binstr;
380
}
381

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

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

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

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

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

    
403
	return $ip;
404
}
405

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

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

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

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

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

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

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

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

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

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

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

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

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

    
517
	return $rangeaddresses;
518
}
519

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
640
	return $out;
641
}
642

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
821
}
822

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1009
	return $result;
1010
}
1011

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

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

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

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

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

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

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

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

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

    
1095
	return true;
1096
}
1097

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

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

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

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

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

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

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

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

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

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

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

    
1181
	return true;
1182
}
1183

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1317
	return false;
1318
}
1319

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

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

    
1331
	return false;
1332
}
1333

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

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

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

    
1352
	return($list);
1353
}
1354

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1464
	return (NULL);
1465
}
1466

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

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

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

    
1500
	return ($iflist);
1501
}
1502

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

    
1523
	return ($iflist);
1524
}
1525

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

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

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

    
1549
	return ($iflist);
1550
}
1551

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

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

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

    
1590
	return $ip_array;
1591
}
1592

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

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

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

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

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

    
1765
	return ($plist);
1766
}
1767

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

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

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

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

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

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

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

    
1857
	return $retval;
1858
}
1859

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

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

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

    
1893
	$aliastable = array();
1894

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

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

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

    
1910
function alias_get_type($name) {
1911

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

    
1918
	return "";
1919
}
1920

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
2097
		usleep(100000);
2098
	}
2099

    
2100
	return false;
2101
}
2102

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

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

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

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

    
2130

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

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

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

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

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

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

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

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

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

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

    
2239
	return $values;
2240
}
2241

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

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

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

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

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

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

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

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

    
2292
	return $ret;
2293
}
2294

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

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

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

    
2311
	return true;
2312
}
2313

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

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

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

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

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

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

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

    
2382
	return false;
2383
}
2384

    
2385
function is_interface_mismatch() {
2386
	global $config, $g;
2387

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

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

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

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

    
2437
	return $do_assign;
2438
}
2439

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

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

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

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

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

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

    
2550
	return false;
2551
}
2552

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

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

    
2569
	return $data;
2570
}
2571

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

    
2578
	$arrays = func_get_args();
2579
	$remains = $arrays;
2580

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

    
2585
	// loop available array
2586
	foreach ($arrays as $array) {
2587

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

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

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

    
2626

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

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

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

    
2662
	if ($rc != 0) {
2663
		return array();
2664
	}
2665

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

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

    
2682
	return $result;
2683
}
2684

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

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

    
2699
	return false;
2700
}
2701

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

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

    
2714
	if (empty($family)) {
2715
		return array();
2716
	}
2717

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

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

    
2760
	return $result;
2761
}
2762

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

    
2770
	$route = route_get('default', $ipprotocol, true);
2771

    
2772
	if (empty($route)) {
2773
		return '';
2774
	}
2775

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

    
2780
	return $route[0]['gateway'];
2781
}
2782

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

    
2787
	if (empty($target)) {
2788
		return;
2789
	}
2790

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

    
2796
	$route = route_get($target, $ipprotocol, true);
2797

    
2798
	if (empty($route)) {
2799
		return;
2800
	}
2801

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
2918
	return ($rc == 0);
2919
}
2920

    
2921
function set_ipv6routes_mtu($interface, $mtu) {
2922
	global $config;
2923

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

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

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

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

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

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

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

    
3044
/* returns an array consisting of every element of $haystack that is not equal to $needle. */
3045
function array_exclude($needle, $haystack) {
3046
	$result = array();
3047
	if (is_array($haystack)) {
3048
		foreach ($haystack as $thing) {
3049
			if ($needle !== $thing) {
3050
				$result[] = $thing;
3051
			}
3052
		}
3053
	}
3054
	return $result;
3055
}
3056

    
3057
/* Define what is preferred, IPv4 or IPv6 */
3058
function prefer_ipv4_or_ipv6() {
3059
	if (config_path_enabled('system', 'prefer_ipv4')) {
3060
		mwexec("/etc/rc.d/ip6addrctl prefer_ipv4");
3061
	} else {
3062
		mwexec("/etc/rc.d/ip6addrctl prefer_ipv6");
3063
	}
3064
}
3065

    
3066
/* Redirect to page passing parameters via POST */
3067
function post_redirect($page, $params) {
3068
	if (!is_array($params)) {
3069
		return;
3070
	}
3071

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

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

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

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

    
3178
	return false;
3179
}
3180

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

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

    
3220
	$values = explode(':', strtolower(str_replace('-', ':', $duidpt1)));
3221

    
3222
	if (hexdec($values[0]) != count($values) - 2)
3223
		array_unshift($values, dechex(count($values)), '00');
3224

    
3225
	array_walk($values, function(&$value) {
3226
		$value = str_pad($value, 2, '0', STR_PAD_LEFT);
3227
	});
3228

    
3229
	return implode(":", $values);
3230
}
3231

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

    
3262
	for ($i = 0; $i < count($values); $i++) {
3263
		if (ctype_xdigit($values[$i]) == false)
3264
			return false;
3265
		if (hexdec($values[$i]) < 0 || hexdec($values[$i]) > 255)
3266
			return false;
3267
	}
3268

    
3269
	return true;
3270
}
3271

    
3272
/* Write the DHCP6 DUID file */
3273
function write_dhcp6_duid($duidstring) {
3274
	// Create the hex array from the dhcp6duid config entry and write to file
3275
	global $g;
3276

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

    
3292
/* returns duid string from 'vardb_path']}/dhcp6c_duid' */
3293
function get_duid_from_file() {
3294
	global $g;
3295

    
3296
	$duid_ASCII = "";
3297
	$count = 0;
3298

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

    
3321
/* Replaces the Mac OS 9 and earlier (\r) and DOS/Windows (\r\n) newlines with the Unix equivalent (\n). */
3322
function unixnewlines($text) {
3323
	return preg_replace('/\r\n?/', "\n", $text);
3324
}
3325

    
3326
function array_remove_duplicate($array, $field) {
3327
	$cmp = array();
3328
	foreach ($array as $sub) {
3329
		if (isset($sub[$field])) {
3330
			$cmp[] = $sub[$field];
3331
		}
3332
	}
3333
	$unique = array_unique(array_reverse($cmp, true));
3334
	foreach (array_keys($unique) as $k) {
3335
		$new[] = $array[$k];
3336
	}
3337
	return $new;
3338
}
3339

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

    
3362
	if (($default !== null) && ($el === '')) {
3363
		return ($default);
3364
	}
3365
	
3366
	return ($el);
3367
}
3368

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

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

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

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

    
3466
	if (!(is_array($el) && array_key_exists($vkey, $el))) {
3467
		return null;
3468
	}
3469

    
3470
	$ret = $el[$vkey];
3471
	unset($el[$vkey]);
3472
	return ($ret);
3473
}
3474

    
3475

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

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

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

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

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

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

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

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

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

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

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

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

    
3572
	$index++;
3573

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

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

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

    
3629
		$index++;
3630
	}
3631

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

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

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

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

    
3702
		$index++;
3703
	}
3704

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

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

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

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

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

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

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

    
3793

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
3991
	return $filtertext;
3992
}
3993

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

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

    
4013
	return $octstr;
4014
}
4015

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

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

    
4037
	return $interrupts;
4038
}
4039

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

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

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

    
4074
?>
(54-54/61)