Project

General

Profile

Download (104 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

    
28
define('VIP_ALL', 1);
29
define('VIP_CARP', 2);
30
define('VIP_IPALIAS', 3);
31

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

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

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

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

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

    
73
	return 0;
74
}
75

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

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

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

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

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

    
101
	return false;
102
}
103

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

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

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

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

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

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

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

    
164
		return $fp;
165
	}
166

    
167
	return NULL;
168
}
169

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
371
	foreach ( $parts as $v ) {
372

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

    
376
	}
377

    
378
	return $binstr;
379
}
380

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

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

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

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

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

    
402
	return $ip;
403
}
404

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

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

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

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

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

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

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

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

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

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

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

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

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

    
516
	return $rangeaddresses;
517
}
518

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

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

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

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

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

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

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

    
580
		// 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)
581

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

    
590
		// 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)
591

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

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

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

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

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

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

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

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

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

    
639
	return $out;
640
}
641

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
825
}
826

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1013
	return $result;
1014
}
1015

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

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

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

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

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

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

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

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

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

    
1099
	return true;
1100
}
1101

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

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

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

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

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

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

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

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

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

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

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

    
1185
	return true;
1186
}
1187

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1325
	return false;
1326
}
1327

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

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

    
1339
	return false;
1340
}
1341

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

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

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

    
1360
	return($list);
1361
}
1362

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1471
	return (NULL);
1472
}
1473

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

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

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

    
1495
	$iflist = array();
1496

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

    
1504
	return $iflist;
1505
}
1506

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

    
1511
	$iflist = array();
1512

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

    
1523
	return $iflist;
1524
}
1525

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

    
1530
	$iflist = array();
1531

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

    
1543
	if ($user_settings['webgui']['interfacessort']) {
1544
		asort($iflist);
1545
	}
1546

    
1547
	return $iflist;
1548
}
1549

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

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

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

    
1588
	return $ip_array;
1589
}
1590

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

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

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

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

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

    
1767
	return ($plist);
1768
}
1769

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

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

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

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

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

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

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

    
1859
	return $retval;
1860
}
1861

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

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

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

    
1895
	$aliastable = array();
1896

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

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

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

    
1912
function alias_get_type($name) {
1913
	global $config;
1914

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

    
1923
	return "";
1924
}
1925

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

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

    
1963
function alias_expand_urltable($name) {
1964
	global $config;
1965
	$urltable_prefix = "/var/db/aliastables/";
1966
	$urltable_filename = $urltable_prefix . $name . ".txt";
1967

    
1968
	if (!is_array($config['aliases']['alias'])) {
1969
		return null;
1970
	}
1971

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

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

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

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

    
2027
function mac_format($clientmac) {
2028
	global $config, $cpzone;
2029

    
2030
	$mac = explode(":", $clientmac);
2031
	$mac_format = $cpzone ? $config['captiveportal'][$cpzone]['radmac_format'] : false;
2032

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

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

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

    
2043
		case 'unformatted':
2044
			return "$mac[0]$mac[1]$mac[2]$mac[3]$mac[4]$mac[5]";
2045

    
2046
		default:
2047
			return $clientmac;
2048
	}
2049
}
2050

    
2051
function resolve_retry($hostname, $protocol = 'inet') {
2052
	$retries = 10;
2053
	$numrecords = 1;
2054
	$recresult = array();
2055
	$returnres = array();
2056

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

    
2076
	for ($i = 0; $i < $retries; $i++) {
2077
		if ($checkproto($hostname)) {
2078
			return $hostname;
2079
		}
2080

    
2081
		$dnsresult = @dns_get_record($hostname, $dnsproto);
2082

    
2083
		if (!empty($dnsresult)) {
2084
			foreach ($dnsresult as $dnsrec => $ip) {
2085
				if (is_array($ip)) {
2086
					if (in_array($ip['type'], $dnstype)) {
2087
						if ($checkproto($ip['ip'])) { 
2088
							$recresult[] = $ip['ip'];
2089
						}
2090

    
2091
						if ($checkproto($ip['ipv6'])) { 
2092
							$recresult[] = $ip['ipv6'];
2093
						}
2094
					}
2095
				}
2096
			}
2097
		}
2098

    
2099
		// Return on success
2100
		if (!empty($recresult)) {
2101
			if ($numrecords == 1) {
2102
				return $recresult[0];
2103
			} else {
2104
				return array_slice($recresult, 0, $numrecords);
2105
			}
2106
		}
2107

    
2108
		usleep(100000);
2109
	}
2110

    
2111
	return false;
2112
}
2113

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

    
2128
function format_number($num, $precision = 3) {
2129
	$units = array('', 'K', 'M', 'G', 'T');
2130

    
2131
	$i = 0;
2132
	while ($num > 1000 && $i < count($units)) {
2133
		$num /= 1000;
2134
		$i++;
2135
	}
2136
	$num = round($num, $precision);
2137

    
2138
	return ("$num {$units[$i]}");
2139
}
2140

    
2141

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

    
2158
function update_filter_reload_status($text, $new=false) {
2159
	global $g;
2160

    
2161
	if ($new) {
2162
		file_put_contents("{$g['varrun_path']}/filter_reload_status", $text  . PHP_EOL);
2163
	} else {
2164
		file_put_contents("{$g['varrun_path']}/filter_reload_status", $text  . PHP_EOL, FILE_APPEND);
2165
	}
2166
}
2167

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

    
2186
				if (empty($filter_regex) || preg_match($filter_regex, $file)) {
2187
					array_push($dir_array, $file);
2188
				}
2189
			}
2190
			closedir($dh);
2191
		}
2192
	}
2193
	return $dir_array;
2194
}
2195

    
2196
function run_plugins($directory) {
2197
	global $config, $g;
2198

    
2199
	/* process packager manager custom rules */
2200
	$files = return_dir_as_array($directory);
2201
	if (is_array($files)) {
2202
		foreach ($files as $file) {
2203
			if (stristr($file, ".sh") == true) {
2204
				mwexec($directory . $file . " start");
2205
			} else if (!is_dir($directory . "/" . $file) && stristr($file, ".inc")) {
2206
				require_once($directory . "/" . $file);
2207
			}
2208
		}
2209
	}
2210
}
2211

    
2212
/*
2213
 *    safe_mkdir($path, $mode = 0755)
2214
 *    create directory if it doesn't already exist and isn't a file!
2215
 */
2216
function safe_mkdir($path, $mode = 0755) {
2217
	global $g;
2218

    
2219
	if (!is_file($path) && !is_dir($path)) {
2220
		return @mkdir($path, $mode, true);
2221
	} else {
2222
		return false;
2223
	}
2224
}
2225

    
2226
/*
2227
 * get_sysctl($names)
2228
 * Get values of sysctl OID's listed in $names (accepts an array or a single
2229
 * name) and return an array of key/value pairs set for those that exist
2230
 */
2231
function get_sysctl($names) {
2232
	if (empty($names)) {
2233
		return array();
2234
	}
2235

    
2236
	if (is_array($names)) {
2237
		$name_list = array();
2238
		foreach ($names as $name) {
2239
			$name_list[] = escapeshellarg($name);
2240
		}
2241
	} else {
2242
		$name_list = array(escapeshellarg($names));
2243
	}
2244

    
2245
	exec("/sbin/sysctl -iq " . implode(" ", $name_list), $output);
2246
	$values = array();
2247
	foreach ($output as $line) {
2248
		$line = explode(": ", $line, 2);
2249
		if (count($line) == 2) {
2250
			$values[$line[0]] = $line[1];
2251
		}
2252
	}
2253

    
2254
	return $values;
2255
}
2256

    
2257
/*
2258
 * get_single_sysctl($name)
2259
 * Wrapper for get_sysctl() to simplify read of a single sysctl value
2260
 * return the value for sysctl $name or empty string if it doesn't exist
2261
 */
2262
function get_single_sysctl($name) {
2263
	if (empty($name)) {
2264
		return "";
2265
	}
2266

    
2267
	$value = get_sysctl($name);
2268
	if (empty($value) || !isset($value[$name])) {
2269
		return "";
2270
	}
2271

    
2272
	return $value[$name];
2273
}
2274

    
2275
/*
2276
 * set_sysctl($value_list)
2277
 * Set sysctl OID's listed as key/value pairs and return
2278
 * an array with keys set for those that succeeded
2279
 */
2280
function set_sysctl($values) {
2281
	if (empty($values)) {
2282
		return array();
2283
	}
2284

    
2285
	$value_list = array();
2286
	foreach ($values as $key => $value) {
2287
		$value_list[] = escapeshellarg($key) . "=" . escapeshellarg($value);
2288
	}
2289

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

    
2292
	/* Retry individually if failed (one or more read-only) */
2293
	if ($success <> 0 && count($value_list) > 1) {
2294
		foreach ($value_list as $value) {
2295
			exec("/sbin/sysctl -iq " . $value, $output);
2296
		}
2297
	}
2298

    
2299
	$ret = array();
2300
	foreach ($output as $line) {
2301
		$line = explode(": ", $line, 2);
2302
		if (count($line) == 2) {
2303
			$ret[$line[0]] = true;
2304
		}
2305
	}
2306

    
2307
	return $ret;
2308
}
2309

    
2310
/*
2311
 * set_single_sysctl($name, $value)
2312
 * Wrapper to set_sysctl() to make it simple to set only one sysctl
2313
 * returns boolean meaning if it succeeded
2314
 */
2315
function set_single_sysctl($name, $value) {
2316
	if (empty($name)) {
2317
		return false;
2318
	}
2319

    
2320
	$result = set_sysctl(array($name => $value));
2321

    
2322
	if (!isset($result[$name]) || $result[$name] != $value) {
2323
		return false;
2324
	}
2325

    
2326
	return true;
2327
}
2328

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

    
2343
function mute_kernel_msgs() {
2344
	global $g, $config;
2345

    
2346
	if ($config['system']['enableserial']) {
2347
		return;
2348
	}
2349
	exec("/sbin/conscontrol mute on");
2350
}
2351

    
2352
function unmute_kernel_msgs() {
2353
	global $g;
2354

    
2355
	exec("/sbin/conscontrol mute off");
2356
}
2357

    
2358
function start_devd() {
2359
	global $g;
2360

    
2361
	/* Generate hints for the kernel loader. */
2362
	$module_paths = explode(";", get_single_sysctl("kern.module_path"));
2363
	foreach ($module_paths as $id => $path) {
2364
		if (!is_dir($path) || file_exists("{$path}/linker.hints")) {
2365
			continue;
2366
		}
2367
		if (($files = scandir($path)) == false) {
2368
			continue;
2369
		}
2370
		$found = false;
2371
		foreach ($files as $id => $file) {
2372
			if (strlen($file) > 3 &&
2373
			    strcasecmp(substr($file, -3), ".ko") == 0) {
2374
				$found = true;
2375
				break;
2376
			}
2377
		}
2378
		if ($found == false) {
2379
			continue;
2380
		}
2381
		$_gb = exec("/usr/sbin/kldxref $path");
2382
		unset($_gb);
2383
	}
2384

    
2385
	/* Use the undocumented -q options of devd to quiet its log spamming */
2386
	$_gb = exec("/sbin/devd -q -f /etc/{$g['product_name']}-devd.conf");
2387
	sleep(1);
2388
	unset($_gb);
2389
}
2390

    
2391
function is_interface_vlan_mismatch() {
2392
	global $config, $g;
2393

    
2394
	if (is_array($config['vlans']['vlan'])) {
2395
		foreach ($config['vlans']['vlan'] as $vlan) {
2396
			if (substr($vlan['if'], 0, 4) == "lagg") {
2397
				return false;
2398
			}
2399
			if (does_interface_exist($vlan['if']) == false) {
2400
				return true;
2401
			}
2402
		}
2403
	}
2404

    
2405
	return false;
2406
}
2407

    
2408
function is_interface_mismatch() {
2409
	global $config, $g;
2410

    
2411
	$do_assign = false;
2412
	$i = 0;
2413
	$missing_interfaces = array();
2414
	if (is_array($config['interfaces'])) {
2415
		foreach ($config['interfaces'] as $ifname => $ifcfg) {
2416
			if (interface_is_vlan($ifcfg['if']) != NULL ||
2417
			    interface_is_qinq($ifcfg['if']) != NULL ||
2418
			    preg_match("/^enc|^cua|^tun|^tap|^l2tp|^pptp|^ppp|^ovpn|^ipsec|^gif|^gre|^lagg|^bridge|vlan|_wlan|_\d{0,4}_\d{0,4}$/i", $ifcfg['if'])) {
2419
				// Do not check these interfaces.
2420
				$i++;
2421
				continue;
2422
			} else if (does_interface_exist($ifcfg['if']) == false) {
2423
				$missing_interfaces[] = $ifcfg['if'];
2424
				$do_assign = true;
2425
			} else {
2426
				$i++;
2427
			}
2428
		}
2429
	}
2430

    
2431
	/* VLAN/QinQ-only interface mismatch detection
2432
	 * see https://redmine.pfsense.org/issues/12170 */
2433
	init_config_arr(array('vlans', 'vlan'));
2434
	foreach ($config['vlans']['vlan'] as $vlan) {
2435
		if (!does_interface_exist($vlan['if']) && 
2436
		    !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'])) {
2437
			$missing_interfaces[] = $vlan['if'];
2438
			$do_assign = true;
2439
		}
2440
	}
2441
	init_config_arr(array('qinqs', 'qinqentry'));
2442
	foreach ($config['qinqs']['qinqentry'] as $qinq) {
2443
		if (!does_interface_exist($qinq['if']) &&
2444
		    !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'])) {
2445
			$missing_interfaces[] = $qinq['if'];
2446
			$do_assign = true;
2447
		}
2448
	}
2449

    
2450
	if (file_exists("{$g['tmp_path']}/assign_complete")) {
2451
		$do_assign = false;
2452
	}
2453

    
2454
	if (!empty($missing_interfaces) && $do_assign) {
2455
		file_put_contents("{$g['tmp_path']}/missing_interfaces", implode(' ', $missing_interfaces));
2456
	} else {
2457
		@unlink("{$g['tmp_path']}/missing_interfaces");
2458
	}
2459

    
2460
	return $do_assign;
2461
}
2462

    
2463
/* sync carp entries to other firewalls */
2464
function carp_sync_client() {
2465
	global $g;
2466
	send_event("filter sync");
2467
}
2468

    
2469
/****f* util/isAjax
2470
 * NAME
2471
 *   isAjax - reports if the request is driven from prototype
2472
 * INPUTS
2473
 *   none
2474
 * RESULT
2475
 *   true/false
2476
 ******/
2477
function isAjax() {
2478
	return isset ($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest';
2479
}
2480

    
2481
/****f* util/timeout
2482
 * NAME
2483
 *   timeout - console input with timeout countdown. Note: erases 2 char of screen for timer. Leave space.
2484
 * INPUTS
2485
 *   optional, seconds to wait before timeout. Default 9 seconds.
2486
 * RESULT
2487
 *   returns 1 char of user input or null if no input.
2488
 ******/
2489
function timeout($timer = 9) {
2490
	while (!isset($key)) {
2491
		if ($timer >= 9) {
2492
			echo chr(8) . chr(8) . ($timer == 9 ? chr(32) : null) . "{$timer}";
2493
		} else {
2494
			echo chr(8). "{$timer}";
2495
		}
2496
		`/bin/stty -icanon min 0 time 25`;
2497
		$key = trim(`KEY=\`dd count=1 2>/dev/null\`; echo \$KEY`);
2498
		`/bin/stty icanon`;
2499
		if ($key == '') {
2500
			unset($key);
2501
		}
2502
		$timer--;
2503
		if ($timer == 0) {
2504
			break;
2505
		}
2506
	}
2507
	return $key;
2508
}
2509

    
2510
/****f* util/msort
2511
 * NAME
2512
 *   msort - sort array
2513
 * INPUTS
2514
 *   $array to be sorted, field to sort by, direction of sort
2515
 * RESULT
2516
 *   returns newly sorted array
2517
 ******/
2518
function msort($array, $id = "id", $sort_ascending = true) {
2519
	$temp_array = array();
2520
	if (!is_array($array)) {
2521
		return $temp_array;
2522
	}
2523
	while (count($array)>0) {
2524
		$lowest_id = 0;
2525
		$index = 0;
2526
		foreach ($array as $item) {
2527
			if (isset($item[$id])) {
2528
				if ($array[$lowest_id][$id]) {
2529
					if (strtolower($item[$id]) < strtolower($array[$lowest_id][$id])) {
2530
						$lowest_id = $index;
2531
					}
2532
				}
2533
			}
2534
			$index++;
2535
		}
2536
		$temp_array[] = $array[$lowest_id];
2537
		$array = array_merge(array_slice($array, 0, $lowest_id), array_slice($array, $lowest_id + 1));
2538
	}
2539
	if ($sort_ascending) {
2540
		return $temp_array;
2541
	} else {
2542
		return array_reverse($temp_array);
2543
	}
2544
}
2545

    
2546
/****f* util/is_URL
2547
 * NAME
2548
 *   is_URL
2549
 * INPUTS
2550
 *   $url: string to check
2551
 *   $httponly: Only allow HTTP or HTTPS scheme
2552
 * RESULT
2553
 *   Returns true if item is a URL
2554
 ******/
2555
function is_URL($url, $httponly = false) {
2556
	$match = preg_match("'\b(([\w-]+://?|www[.])[^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|/)))'", $url);
2557
	if ($match) {
2558
		if ($httponly) {
2559
			$urlparts = parse_url($url);
2560
			return in_array(strtolower($urlparts['scheme']), array('http', 'https'));
2561
		} else {
2562
			return true;
2563
		}
2564
	}
2565
	return false;
2566
}
2567

    
2568
function is_file_included($file = "") {
2569
	$files = get_included_files();
2570
	if (in_array($file, $files)) {
2571
		return true;
2572
	}
2573

    
2574
	return false;
2575
}
2576

    
2577
/*
2578
 * Replace a value on a deep associative array using regex
2579
 */
2580
function array_replace_values_recursive($data, $match, $replace) {
2581
	if (empty($data)) {
2582
		return $data;
2583
	}
2584

    
2585
	if (is_string($data)) {
2586
		$data = preg_replace("/{$match}/", $replace, $data);
2587
	} else if (is_array($data)) {
2588
		foreach ($data as $k => $v) {
2589
			$data[$k] = array_replace_values_recursive($v, $match, $replace);
2590
		}
2591
	}
2592

    
2593
	return $data;
2594
}
2595

    
2596
/*
2597
	This function was borrowed from a comment on PHP.net at the following URL:
2598
	http://www.php.net/manual/en/function.array-merge-recursive.php#73843
2599
 */
2600
function array_merge_recursive_unique($array0, $array1) {
2601

    
2602
	$arrays = func_get_args();
2603
	$remains = $arrays;
2604

    
2605
	// We walk through each arrays and put value in the results (without
2606
	// considering previous value).
2607
	$result = array();
2608

    
2609
	// loop available array
2610
	foreach ($arrays as $array) {
2611

    
2612
		// The first remaining array is $array. We are processing it. So
2613
		// we remove it from remaining arrays.
2614
		array_shift($remains);
2615

    
2616
		// We don't care non array param, like array_merge since PHP 5.0.
2617
		if (is_array($array)) {
2618
			// Loop values
2619
			foreach ($array as $key => $value) {
2620
				if (is_array($value)) {
2621
					// we gather all remaining arrays that have such key available
2622
					$args = array();
2623
					foreach ($remains as $remain) {
2624
						if (array_key_exists($key, $remain)) {
2625
							array_push($args, $remain[$key]);
2626
						}
2627
					}
2628

    
2629
					if (count($args) > 2) {
2630
						// put the recursion
2631
						$result[$key] = call_user_func_array(__FUNCTION__, $args);
2632
					} else {
2633
						foreach ($value as $vkey => $vval) {
2634
							if (!is_array($result[$key])) {
2635
								$result[$key] = array();
2636
							}
2637
							$result[$key][$vkey] = $vval;
2638
						}
2639
					}
2640
				} else {
2641
					// simply put the value
2642
					$result[$key] = $value;
2643
				}
2644
			}
2645
		}
2646
	}
2647
	return $result;
2648
}
2649

    
2650

    
2651
/*
2652
 * converts a string like "a,b,c,d"
2653
 * into an array like array("a" => "b", "c" => "d")
2654
 */
2655
function explode_assoc($delimiter, $string) {
2656
	$array = explode($delimiter, $string);
2657
	$result = array();
2658
	$numkeys = floor(count($array) / 2);
2659
	for ($i = 0; $i < $numkeys; $i += 1) {
2660
		$result[$array[$i * 2]] = $array[$i * 2 + 1];
2661
	}
2662
	return $result;
2663
}
2664

    
2665
/*
2666
 * Given a string of text with some delimiter, look for occurrences
2667
 * of some string and replace all of those.
2668
 * $text - the text string (e.g. "abc,defg,x123,ipv4,xyz")
2669
 * $delimiter - the delimiter (e.g. ",")
2670
 * $element - the element to match (e.g. "defg")
2671
 * $replacement - the string to replace it with (e.g. "42")
2672
 * Returns the resulting delimited string (e.g. "abc,42,x123,ipv4,xyz")
2673
 */
2674
function replace_element_in_list($text, $delimiter, $element, $replacement) {
2675
	$textArray = explode($delimiter, $text);
2676
	while (($entry = array_search($element, $textArray)) !== false) {
2677
		$textArray[$entry] = $replacement;
2678
	}
2679
	return implode(',', $textArray);
2680
}
2681

    
2682
/* Return system's route table */
2683
function route_table() {
2684
	$_gb = exec("/usr/bin/netstat --libxo json -nWr", $rawdata, $rc);
2685

    
2686
	if ($rc != 0) {
2687
		return array();
2688
	}
2689

    
2690
	$netstatarr = json_decode(implode(" ", $rawdata), JSON_OBJECT_AS_ARRAY);
2691
	$netstatarr = $netstatarr['statistics']['route-information']
2692
	    ['route-table']['rt-family'];
2693

    
2694
	$result = array();
2695
	$result['inet'] = array();
2696
	$result['inet6'] = array();
2697
	foreach ($netstatarr as $item) {
2698
		if ($item['address-family'] == 'Internet') {
2699
			$result['inet'] = $item['rt-entry'];
2700
		} else if ($item['address-family'] == 'Internet6') {
2701
			$result['inet6'] = $item['rt-entry'];
2702
		}
2703
	}
2704
	unset($netstatarr);
2705

    
2706
	return $result;
2707
}
2708

    
2709
/* check if route is static (not BGP/OSPF) */
2710
function is_static_route($target, $ipprotocol = '') {
2711
	if (is_v4($target) || (($target == 'default') && ($ipprotocol == 'inet'))) {
2712
		$inet = '4';
2713
	} elseif (is_v6($target) || (($target == 'default') && ($ipprotocol == 'inet6'))) {
2714
		$inet = '6';
2715
	} else {
2716
		return false;
2717
	}
2718

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

    
2723
	return false;
2724
}
2725

    
2726
/* Get static route for specific destination */
2727
function route_get($target, $ipprotocol = '', $useroute = false) {
2728
	global $config;
2729

    
2730
	if (!empty($ipprotocol)) {
2731
		$family = $ipprotocol;
2732
	} else if (is_v4($target)) {
2733
		$family = 'inet';
2734
	} else if (is_v6($target)) {
2735
		$family = 'inet6';
2736
	}
2737

    
2738
	if (empty($family)) {
2739
		return array();
2740
	}
2741

    
2742
	if ($useroute) {
2743
		if ($family == 'inet') {
2744
			$inet = '4';
2745
		} else {
2746
			$inet = '6';
2747
		}
2748
		$interface = exec("/sbin/route -n{$inet} get {$target} 2>/dev/null | /usr/bin/awk '/interface:/{print $2}'");
2749
		if (empty($interface)) {
2750
			return array();
2751
		} elseif ($interface == 'lo0') {
2752
			// interface assigned IP address
2753
			foreach ($config['interfaces'] as $intf => $infconf) {
2754
				if ((($inet == '4') && (get_interface_ip($intf) == $target)) ||
2755
				    (($inet == '6') && (get_interface_ipv6($intf) == $target))) {
2756
					$interface = convert_friendly_interface_to_real_interface_name($intf);
2757
					$gateway = $interface;
2758
					break;
2759
				}
2760
			}
2761
		} else {
2762
			$gateway = exec("/sbin/route -n{$inet} get {$target} 2>/dev/null | /usr/bin/awk '/gateway:/{print $2}'");
2763
			if (!$gateway) {
2764
				// non-local gateway
2765
				$gateway = get_interface_mac($interface);
2766
			}
2767
		}
2768
		$result[] = array('gateway' => $gateway, 'interface-name' => $interface);
2769
	} else {
2770
		$rtable = route_table();
2771
		if (empty($rtable)) {
2772
			return array();
2773
		}
2774

    
2775
		$result = array();
2776
		foreach ($rtable[$family] as $item) {
2777
			if ($item['destination'] == $target ||
2778
			    ip_in_subnet($target, $item['destination'])) {
2779
				$result[] = $item;
2780
			}
2781
		}
2782
	}
2783

    
2784
	return $result;
2785
}
2786

    
2787
/* Get default route */
2788
function route_get_default($ipprotocol) {
2789
	if (empty($ipprotocol) || ($ipprotocol != 'inet' &&
2790
	    $ipprotocol != 'inet6')) {
2791
		return '';
2792
	}
2793

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

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

    
2800
	if (!isset($route[0]['gateway'])) {
2801
		return '';
2802
	}
2803

    
2804
	return $route[0]['gateway'];
2805
}
2806

    
2807
/* Delete a static route */
2808
function route_del($target, $ipprotocol = '') {
2809
	global $config;
2810

    
2811
	if (empty($target)) {
2812
		return;
2813
	}
2814

    
2815
	if (!empty($ipprotocol) && $ipprotocol != 'inet' &&
2816
	    $ipprotocol != 'inet6') {
2817
		return false;
2818
	}
2819

    
2820
	$route = route_get($target, $ipprotocol, true);
2821

    
2822
	if (empty($route)) {
2823
		return;
2824
	}
2825

    
2826
	$target_prefix = '';
2827
	if (is_subnet($target)) {
2828
		$target_prefix = '-net';
2829
	} else if (is_ipaddr($target)) {
2830
		$target_prefix = '-host';
2831
	}
2832

    
2833
	if (!empty($ipprotocol)) {
2834
		$target_prefix .= " -{$ipprotocol}";
2835
	} else if (is_v6($target)) {
2836
		$target_prefix .= ' -inet6';
2837
	} else if (is_v4($target)) {
2838
		$target_prefix .= ' -inet';
2839
	}
2840

    
2841
	foreach ($route as $item) {
2842
		if (substr($item['gateway'], 0, 5) == 'link#') {
2843
			continue;
2844
		}
2845

    
2846
		if (is_macaddr($item['gateway'])) {
2847
			$gw = '-iface ' . $item['interface-name'];
2848
		} else {
2849
			$gw = $item['gateway'];
2850
		}
2851

    
2852
		$_gb = exec(escapeshellcmd("/sbin/route del {$target_prefix} " .
2853
		    "{$target} {$gw}"), $output, $rc);
2854

    
2855
		if (isset($config['system']['route-debug'])) {
2856
			log_error("ROUTING debug: " . microtime() .
2857
			    " - DEL RC={$rc} - {$target} - gw: " . $gw);
2858
			file_put_contents("/dev/console", "\n[" . getmypid() .
2859
			    "] ROUTE DEL: {$target_prefix} {$target} {$gw} " .
2860
			    "result: {$rc}");
2861
		}
2862
	}
2863
}
2864

    
2865
/*
2866
 * Add static route.  If it already exists, remove it and re-add
2867
 *
2868
 * $target - IP, subnet or 'default'
2869
 * $gw     - gateway address
2870
 * $iface  - Network interface
2871
 * $args   - Extra arguments for /sbin/route
2872
 * $ipprotocol - 'inet' or 'inet6'.  Mandatory when $target == 'default'
2873
 *
2874
 */
2875
function route_add_or_change($target, $gw, $iface = '', $args = '',
2876
    $ipprotocol = '') {
2877
	global $config;
2878

    
2879
	if (empty($target) || (empty($gw) && empty($iface))) {
2880
		return false;
2881
	}
2882

    
2883
	if ($target == 'default' && empty($ipprotocol)) {
2884
		return false;
2885
	}
2886

    
2887
	if (!empty($ipprotocol) && $ipprotocol != 'inet' &&
2888
	    $ipprotocol != 'inet6') {
2889
		return false;
2890
	}
2891

    
2892
	/* use '-host' for IPv6 /128 routes, see https://redmine.pfsense.org/issues/11594 */
2893
	if (is_subnetv4($target) || (is_subnetv6($target) && (subnet_size($target) > 1))) {
2894
		$target_prefix = '-net';
2895
	} else if (is_ipaddr($target)) {
2896
		$target_prefix = '-host';
2897
	}
2898

    
2899
	if (!empty($ipprotocol)) {
2900
		$target_prefix .= " -{$ipprotocol}";
2901
	} else if (is_v6($target)) {
2902
		$target_prefix .= ' -inet6';
2903
	} else if (is_v4($target)) {
2904
		$target_prefix .= ' -inet';
2905
	}
2906

    
2907
	/* If there is another route to the same target, remove it */
2908
	route_del($target, $ipprotocol);
2909

    
2910
	$params = '';
2911
	if (!empty($iface) && does_interface_exist($iface)) {
2912
		$params .= " -iface {$iface}";
2913
	}
2914
	if (is_ipaddr($gw)) {
2915
		/* set correct linklocal gateway address,
2916
		 * see https://redmine.pfsense.org/issues/11713 
2917
		 * and https://redmine.pfsense.org/issues/11806 */
2918
		if (is_ipaddrv6($gw) && is_linklocal($gw) && empty(get_ll_scope($gw))) {
2919
			$routeget = route_get($gw, 'inet6', true);
2920
			$gw .= "%" . $routeget[0]['interface-name'];
2921
		}
2922
		$params .= " " . $gw;
2923
	}
2924

    
2925
	if (empty($params)) {
2926
		log_error("route_add_or_change: Invalid gateway {$gw} and/or " .
2927
		    "network interface {$iface}");
2928
		return false;
2929
	}
2930

    
2931
	$_gb = exec(escapeshellcmd("/sbin/route add {$target_prefix} " .
2932
	    "{$target} {$args} {$params}"), $output, $rc);
2933

    
2934
	if (isset($config['system']['route-debug'])) {
2935
		log_error("ROUTING debug: " . microtime() .
2936
		    " - ADD RC={$rc} - {$target} {$args}");
2937
		file_put_contents("/dev/console", "\n[" . getmypid() .
2938
		    "] ROUTE ADD: {$target_prefix} {$target} {$args} " .
2939
		    "{$params} result: {$rc}");
2940
	}
2941

    
2942
	return ($rc == 0);
2943
}
2944

    
2945
function set_ipv6routes_mtu($interface, $mtu) {
2946
	global $config, $g;
2947

    
2948
	$ipv6mturoutes = array();
2949
	$if = convert_real_interface_to_friendly_interface_name($interface);
2950
	if (!$config['interfaces'][$if]['ipaddrv6']) {
2951
		return;
2952
	}
2953
	$a_gateways = return_gateways_array();
2954
	$a_staticroutes = get_staticroutes(false, false, true);
2955
	foreach ($a_gateways as $gate) {
2956
		foreach ($a_staticroutes as $sroute) {
2957
			if (($gate['interface'] == $interface) &&
2958
			    ($sroute['gateway'] == $gate['name'])) {
2959
				$tgt = $sroute['network'];
2960
				$gateway = $gate['gateway'];
2961
				$ipv6mturoutes[$tgt] = $gateway;
2962
			}
2963
		}
2964
		if (($gate['interface'] == $interface) &&
2965
		    $gate['isdefaultgw'] && is_ipaddrv6($gate['gateway'])) {
2966
			$tgt = "default";
2967
			$gateway = $gate['gateway'];
2968
			$ipv6mturoutes[$tgt] = $gateway;
2969
		}
2970
	}
2971
	foreach ($ipv6mturoutes as $tgt => $gateway) {
2972
		mwexec("/sbin/route change -6 -mtu " . escapeshellarg($mtu) .
2973
		    " " . escapeshellarg($tgt) . " " .
2974
		    escapeshellarg($gateway));
2975
	}
2976
}
2977

    
2978
function alias_to_subnets_recursive($name, $returnhostnames = false) {
2979
	global $aliastable;
2980
	$result = array();
2981
	if (!isset($aliastable[$name])) {
2982
		return $result;
2983
	}
2984
	$subnets = preg_split('/\s+/', $aliastable[$name]);
2985
	foreach ($subnets as $net) {
2986
		if (is_alias($net)) {
2987
			$sub = alias_to_subnets_recursive($net, $returnhostnames);
2988
			$result = array_merge($result, $sub);
2989
			continue;
2990
		} elseif (!is_subnet($net)) {
2991
			if (is_ipaddrv4($net)) {
2992
				$net .= "/32";
2993
			} else if (is_ipaddrv6($net)) {
2994
				$net .= "/128";
2995
			} else if ($returnhostnames === false || !is_fqdn($net)) {
2996
				continue;
2997
			}
2998
		}
2999
		$result[] = $net;
3000
	}
3001
	return $result;
3002
}
3003

    
3004
function get_staticroutes($returnsubnetsonly = false, $returnhostnames = false, $returnenabledroutesonly = false) {
3005
	global $config, $aliastable;
3006

    
3007
	/* Bail if there are no routes, but return an array always so callers don't have to check. */
3008
	init_config_arr(array('staticroutes', 'route'));
3009
	if (empty($config['staticroutes']['route'])) {
3010
		return array();
3011
	}
3012

    
3013
	$allstaticroutes = array();
3014
	$allsubnets = array();
3015
	/* Loop through routes and expand aliases as we find them. */
3016
	foreach ($config['staticroutes']['route'] as $route) {
3017
		if ($returnenabledroutesonly && isset($route['disabled'])) {
3018
			continue;
3019
		}
3020

    
3021
		if (is_alias($route['network'])) {
3022
			foreach (alias_to_subnets_recursive($route['network'], $returnhostnames) as $net) {
3023
				$temproute = $route;
3024
				$temproute['network'] = $net;
3025
				$allstaticroutes[] = $temproute;
3026
				$allsubnets[] = $net;
3027
			}
3028
		} elseif (is_subnet($route['network'])) {
3029
			$allstaticroutes[] = $route;
3030
			$allsubnets[] = $route['network'];
3031
		}
3032
	}
3033
	if ($returnsubnetsonly) {
3034
		return $allsubnets;
3035
	} else {
3036
		return $allstaticroutes;
3037
	}
3038
}
3039

    
3040
/****f* util/get_alias_list
3041
 * NAME
3042
 *   get_alias_list - Provide a list of aliases.
3043
 * INPUTS
3044
 *   $type          - Optional, can be a string or array specifying what type(s) of aliases you need.
3045
 * RESULT
3046
 *   Array containing list of aliases.
3047
 *   If $type is unspecified, all aliases are returned.
3048
 *   If $type is a string, all aliases of the type specified in $type are returned.
3049
 *   If $type is an array, all aliases of any type specified in any element of $type are returned.
3050
 */
3051
function get_alias_list($type = null) {
3052
	global $config;
3053
	$result = array();
3054
	if ($config['aliases']['alias'] <> "" && is_array($config['aliases']['alias'])) {
3055
		foreach ($config['aliases']['alias'] as $alias) {
3056
			if ($type === null) {
3057
				$result[] = $alias['name'];
3058
			} else if (is_array($type)) {
3059
				if (in_array($alias['type'], $type)) {
3060
					$result[] = $alias['name'];
3061
				}
3062
			} else if ($type === $alias['type']) {
3063
				$result[] = $alias['name'];
3064
			}
3065
		}
3066
	}
3067
	return $result;
3068
}
3069

    
3070
/* returns an array consisting of every element of $haystack that is not equal to $needle. */
3071
function array_exclude($needle, $haystack) {
3072
	$result = array();
3073
	if (is_array($haystack)) {
3074
		foreach ($haystack as $thing) {
3075
			if ($needle !== $thing) {
3076
				$result[] = $thing;
3077
			}
3078
		}
3079
	}
3080
	return $result;
3081
}
3082

    
3083
/* Define what is preferred, IPv4 or IPv6 */
3084
function prefer_ipv4_or_ipv6() {
3085
	global $config;
3086

    
3087
	if (isset($config['system']['prefer_ipv4'])) {
3088
		mwexec("/etc/rc.d/ip6addrctl prefer_ipv4");
3089
	} else {
3090
		mwexec("/etc/rc.d/ip6addrctl prefer_ipv6");
3091
	}
3092
}
3093

    
3094
/* Redirect to page passing parameters via POST */
3095
function post_redirect($page, $params) {
3096
	if (!is_array($params)) {
3097
		return;
3098
	}
3099

    
3100
	print "<html><body><form action=\"{$page}\" name=\"formredir\" method=\"post\">\n";
3101
	foreach ($params as $key => $value) {
3102
		print "<input type=\"hidden\" name=\"{$key}\" value=\"{$value}\" />\n";
3103
	}
3104
	print "</form>\n";
3105
	print "<script type=\"text/javascript\">\n";
3106
	print "//<![CDATA[\n";
3107
	print "document.formredir.submit();\n";
3108
	print "//]]>\n";
3109
	print "</script>\n";
3110
	print "</body></html>\n";
3111
}
3112

    
3113
/* Locate disks that can be queried for S.M.A.R.T. data. */
3114
function get_smart_drive_list() {
3115
	/* SMART supports some disks directly, and some controllers directly,
3116
	 * See https://redmine.pfsense.org/issues/9042 */
3117
	$supported_disk_types = array("ad", "da", "ada");
3118
	$supported_controller_types = array("nvme");
3119
	$disk_list = explode(" ", get_single_sysctl("kern.disks"));
3120
	foreach ($disk_list as $id => $disk) {
3121
		// We only want certain kinds of disks for S.M.A.R.T.
3122
		// 1 is a match, 0 is no match, False is any problem processing the regex
3123
		if (preg_match("/^(" . implode("|", $supported_disk_types) . ").*[0-9]{1,2}$/", $disk) !== 1) {
3124
			unset($disk_list[$id]);
3125
			continue;
3126
		}
3127
	}
3128
	foreach ($supported_controller_types as $controller) {
3129
		$devices = glob("/dev/{$controller}*");
3130
		if (!is_array($devices)) {
3131
			continue;
3132
		}
3133
		foreach ($devices as $device) {
3134
			$disk_list[] = basename($device);
3135
		}
3136
	}
3137
	sort($disk_list);
3138
	return $disk_list;
3139
}
3140

    
3141
// Validate a network address
3142
//	$addr: the address to validate
3143
//	$type: IPV4|IPV6|IPV4V6
3144
//	$label: the label used by the GUI to display this value. Required to compose an error message
3145
//	$err_msg: pointer to the callers error message array so that error messages can be added to it here
3146
//	$alias: are aliases permitted for this address?
3147
// Returns:
3148
//	IPV4 - if $addr is a valid IPv4 address
3149
//	IPV6 - if $addr is a valid IPv6 address
3150
//	ALIAS - if $alias=true and $addr is an alias
3151
//	false - otherwise
3152

    
3153
function validateipaddr(&$addr, $type, $label, &$err_msg, $alias=false) {
3154
	switch ($type) {
3155
		case IPV4:
3156
			if (is_ipaddrv4($addr)) {
3157
				return IPV4;
3158
			} else if ($alias) {
3159
				if (is_alias($addr)) {
3160
					return ALIAS;
3161
				} else {
3162
					$err_msg[] = sprintf(gettext("%s must be a valid IPv4 address or alias."), $label);
3163
					return false;
3164
				}
3165
			} else {
3166
				$err_msg[] = sprintf(gettext("%s must be a valid IPv4 address."), $label);
3167
				return false;
3168
			}
3169
		break;
3170
		case IPV6:
3171
			if (is_ipaddrv6($addr)) {
3172
				$addr = strtolower($addr);
3173
				return IPV6;
3174
			} else if ($alias) {
3175
				if (is_alias($addr)) {
3176
					return ALIAS;
3177
				} else {
3178
					$err_msg[] = sprintf(gettext("%s must be a valid IPv6 address or alias."), $label);
3179
					return false;
3180
				}
3181
			} else {
3182
				$err_msg[] = sprintf(gettext("%s must be a valid IPv6 address."), $label);
3183
				return false;
3184
			}
3185
		break;
3186
		case IPV4V6:
3187
			if (is_ipaddrv6($addr)) {
3188
				$addr = strtolower($addr);
3189
				return IPV6;
3190
			} else if (is_ipaddrv4($addr)) {
3191
				return IPV4;
3192
			} else if ($alias) {
3193
				if (is_alias($addr)) {
3194
					return ALIAS;
3195
				} else {
3196
					$err_msg[] = sprintf(gettext("%s must be a valid IPv4 or IPv6 address or alias."), $label);
3197
					return false;
3198
				}
3199
			} else {
3200
				$err_msg[] = sprintf(gettext("%s must be a valid IPv4 or IPv6 address."), $label);
3201
				return false;
3202
			}
3203
		break;
3204
	}
3205

    
3206
	return false;
3207
}
3208

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

    
3231
	/* Make hexstrings */
3232
	if ($duidtype) {
3233
		switch ($duidtype) {
3234
		/* Add a hardware type to DUID-LLT and DUID-LL; assume Ethernet */
3235
		case 1:
3236
		case 3:
3237
			$duidpt1 = '00:01:' . $duidpt1;
3238
			break;
3239
		/* Remove '-' from given UUID and insert ':' every 2 characters */
3240
		case 4:
3241
			$duidpt1 = implode(':', str_split(str_replace('-', '', $duidpt1), 2));
3242
			break;
3243
		default:
3244
		}
3245
		$duidpt1 = '00:0' . $duidtype . ':' . $duidpt1;
3246
	}
3247

    
3248
	$values = explode(':', strtolower(str_replace('-', ':', $duidpt1)));
3249

    
3250
	if (hexdec($values[0]) != count($values) - 2)
3251
		array_unshift($values, dechex(count($values)), '00');
3252

    
3253
	array_walk($values, function(&$value) {
3254
		$value = str_pad($value, 2, '0', STR_PAD_LEFT);
3255
	});
3256

    
3257
	return implode(":", $values);
3258
}
3259

    
3260
/* Returns true if $dhcp6duid is a valid DUID entry.
3261
 * Parse the entry to check for valid length according to known DUID types.
3262
 */
3263
function is_duid($dhcp6duid) {
3264
	$values = explode(":", $dhcp6duid);
3265
	if (hexdec($values[0]) == count($values) - 2) {
3266
		switch (hexdec($values[2] . $values[3])) {
3267
		case 0:
3268
			return false;
3269
			break;
3270
		case 1:
3271
			if (count($values) != 16 || strlen($dhcp6duid) != 47)
3272
				return false;
3273
			break;
3274
		case 3:
3275
			if (count($values) != 12 || strlen($dhcp6duid) != 35)
3276
				return false;
3277
			break;
3278
		case 4:
3279
			if (count($values) != 20 || strlen($dhcp6duid) != 59)
3280
				return false;
3281
			break;
3282
		/* DUID is up to 128 octets; allow 2 octets for type code, 2 more for option-len */
3283
		default:
3284
			if (count($values) > 132 || strlen($dhcp6duid) != count($values) * 3 - 1)
3285
				return false;
3286
		}
3287
	} else
3288
		return false;
3289

    
3290
	for ($i = 0; $i < count($values); $i++) {
3291
		if (ctype_xdigit($values[$i]) == false)
3292
			return false;
3293
		if (hexdec($values[$i]) < 0 || hexdec($values[$i]) > 255)
3294
			return false;
3295
	}
3296

    
3297
	return true;
3298
}
3299

    
3300
/* Write the DHCP6 DUID file */
3301
function write_dhcp6_duid($duidstring) {
3302
	// Create the hex array from the dhcp6duid config entry and write to file
3303
	global $g;
3304

    
3305
	if(!is_duid($duidstring)) {
3306
		log_error(gettext("Error: attempting to write DUID file - Invalid DUID detected"));
3307
		return false;
3308
	}
3309
	$temp = str_replace(":","",$duidstring);
3310
	$duid_binstring = pack("H*",$temp);
3311
	if ($fd = fopen("{$g['vardb_path']}/dhcp6c_duid", "wb")) {
3312
		fwrite($fd, $duid_binstring);
3313
		fclose($fd);
3314
		return true;
3315
	}
3316
	log_error(gettext("Error: attempting to write DUID file - File write error"));
3317
	return false;
3318
}
3319

    
3320
/* returns duid string from 'vardb_path']}/dhcp6c_duid' */
3321
function get_duid_from_file() {
3322
	global $g;
3323

    
3324
	$duid_ASCII = "";
3325
	$count = 0;
3326

    
3327
	if (file_exists("{$g['vardb_path']}/dhcp6c_duid") &&
3328
	    ($fd = fopen("{$g['vardb_path']}/dhcp6c_duid", "r"))) {
3329
		$fsize = filesize("{$g['vardb_path']}/dhcp6c_duid");
3330
		if ($fsize <= 132) {
3331
			$buffer = fread($fd, $fsize);
3332
			while($count < $fsize) {
3333
				$duid_ASCII .= bin2hex($buffer[$count]);
3334
				$count++;
3335
				if($count < $fsize) {
3336
					$duid_ASCII .= ":";
3337
				}
3338
			}
3339
		}
3340
		fclose($fd);
3341
	}
3342
	//if no file or error with read then the string returns blanked DUID string
3343
	if(!is_duid($duid_ASCII)) {
3344
		return "--:--:--:--:--:--:--:--:--:--:--:--:--:--:--:--";
3345
	}
3346
	return($duid_ASCII);
3347
}
3348

    
3349
/* Replaces the Mac OS 9 and earlier (\r) and DOS/Windows (\r\n) newlines with the Unix equivalent (\n). */
3350
function unixnewlines($text) {
3351
	return preg_replace('/\r\n?/', "\n", $text);
3352
}
3353

    
3354
function array_remove_duplicate($array, $field) {
3355
	$cmp = array();
3356
	foreach ($array as $sub) {
3357
		if (isset($sub[$field])) {
3358
			$cmp[] = $sub[$field];
3359
		}
3360
	}
3361
	$unique = array_unique(array_reverse($cmp, true));
3362
	foreach ($unique as $k => $rien) {
3363
		$new[] = $array[$k];
3364
	}
3365
	return $new;
3366
}
3367

    
3368
function dhcpd_date_adjust_gmt($dt) {
3369
	global $config;
3370

    
3371
	init_config_arr(array('dhcpd'));
3372

    
3373
	foreach ($config['dhcpd'] as $dhcpditem) {
3374
		if ($dhcpditem['dhcpleaseinlocaltime'] == "yes") {
3375
			$ts = strtotime($dt . " GMT");
3376
			if ($ts !== false) {
3377
				return strftime("%Y/%m/%d %H:%M:%S", $ts);
3378
			}
3379
		}
3380
	}
3381

    
3382
	/*
3383
	 * If we did not need to convert to local time or the conversion
3384
	 * failed, just return the input.
3385
	 */
3386
	return $dt;
3387
}
3388

    
3389
global $supported_image_types;
3390
$supported_image_types = array(
3391
	IMAGETYPE_JPEG,
3392
	IMAGETYPE_PNG,
3393
	IMAGETYPE_GIF,
3394
	IMAGETYPE_WEBP
3395
);
3396

    
3397
function is_supported_image($image_filename) {
3398
	global $supported_image_types;
3399
	$img_info = getimagesize($image_filename);
3400

    
3401
	/* If it's not an image, or it isn't in the supported list, return false */
3402
	if (($img_info === false) ||
3403
	    !in_array($img_info[2], array_keys($supported_image_types))) {
3404
		return false;
3405
	} else {
3406
		return $img_info[2];
3407
	}
3408
}
3409

    
3410
function get_lagg_ports ($laggport) {
3411
	$laggp = array();
3412
	foreach ($laggport as $lgp) {
3413
		list($lpname, $lpinfo) = explode(" ", $lgp);
3414
		preg_match('~<(.+)>~', $lpinfo, $lgportmode);
3415
		if ($lgportmode[1]) {
3416
			$laggp[] = $lpname . " (" . $lgportmode[1] . ")";
3417
		} else {
3418
			$laggp[] = $lpname;
3419
		}
3420
	}
3421
	if ($laggp) {
3422
		return implode(", ", $laggp);
3423
	} else {
3424
		return false;
3425
	}
3426
}
3427

    
3428
function cisco_to_cidr($addr) {
3429
	if (!is_ipaddr($addr)) {
3430
		throw new Exception('Invalid IP Addr');
3431
	}
3432

    
3433
	$mask = decbin(~ip2long($addr));
3434
	$mask = substr($mask, -32);
3435
	$k = 0;
3436
	for ($i = 0; $i <= 32; $i++) {
3437
		$k += intval($mask[$i]);
3438
	}
3439
	return $k;
3440
}
3441

    
3442
function cisco_extract_index($prule) {
3443
	$index = explode("#", $prule);
3444
	if (is_numeric($index[1])) {
3445
		return intval($index[1]);
3446
	} else {
3447
		syslog(LOG_WARNING, "Error parsing rule {$prule}: Could not extract index");
3448
	}
3449
	return -1;;
3450
}
3451

    
3452
function parse_cisco_acl_rule($rule, $devname, $dir, $proto) {
3453
	$rule_orig = $rule;
3454
	$rule = explode(" ", $rule);
3455
	$tmprule = "";
3456
	$index = 0;
3457

    
3458
	if ($rule[$index] == "permit") {
3459
		$startrule = "pass {$dir} quick on {$devname} ";
3460
	} else if ($rule[$index] == "deny") {
3461
		$startrule = "block {$dir} quick on {$devname} ";
3462
	} else {
3463
		return;
3464
	}
3465

    
3466
	$index++;
3467

    
3468
	switch ($rule[$index]) {
3469
		case "ip":
3470
			break;
3471
		case "icmp":
3472
			$icmp = ($proto == "inet") ? "icmp" : "ipv6-icmp";
3473
			$tmprule .= "proto {$icmp} ";
3474
			break;
3475
		case "tcp":
3476
		case "udp":
3477
			$tmprule .= "proto {$rule[$index]} ";
3478
			break;
3479
		default:
3480
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid protocol.");
3481
			return;
3482
	}
3483
	$index++;
3484

    
3485
	/* Source */
3486
	if (trim($rule[$index]) == "host") {
3487
		$index++;
3488
		if ((($proto == 'inet') && (is_ipaddrv4(trim($rule[$index])) || (trim($rule[$index]) == "{clientip}"))) ||
3489
		    (($proto == 'inet6') && (is_ipaddrv6(trim($rule[$index])) || (trim($rule[$index]) == "{clientipv6}")))) {
3490
			if ($GLOBALS['attributes']['framed_ip']) {
3491
				$tmprule .= "from {$GLOBALS['attributes']['framed_ip']} ";
3492
			} else {
3493
				$tmprule .= "from {$rule[$index]} ";
3494
			}
3495
			$index++;
3496
		} else {
3497
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source host '{$rule[$index]}'.");
3498
			return;
3499
		}
3500
	} elseif (is_subnetv6(trim($rule[$index])) && ($proto == "inet6")) {
3501
		$tmprule .= "from {$rule[$index]} ";
3502
		$index++;
3503
	} elseif (trim($rule[$index]) == "any") {
3504
		$tmprule .= "from any ";
3505
		$index++;
3506
	} else {
3507
		$network = $rule[$index];
3508
		$netmask = $rule[++$index];
3509

    
3510
		if (is_ipaddrv4($network) && ($proto == "inet")) {
3511
			try {
3512
				$netmask = cisco_to_cidr($netmask);
3513
			} catch(Exception $e) {
3514
				syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source netmask '$netmask'.");
3515
				return;
3516
			}
3517
			$tmprule .= "from {$network}/{$netmask} ";
3518
		} else {
3519
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source network '$network'.");
3520
			return;
3521
		}
3522

    
3523
		$index++;
3524
	}
3525

    
3526
	/* Source Operator */
3527
	if (in_array(trim($rule[$index]), array("lt", "gt", "eq", "neq"))) {
3528
		switch(trim($rule[$index])) {
3529
			case "lt":
3530
				$operator = "<";
3531
				break;
3532
			case "gt":
3533
				$operator = ">";
3534
				break;
3535
			case "eq":
3536
				$operator = "=";
3537
				break;
3538
			case "neq":
3539
				$operator = "!=";
3540
				break;
3541
		}
3542

    
3543
		$port = $rule[++$index];
3544
		if (is_port($port)) {
3545
			$tmprule .= "port {$operator} {$port} ";
3546
		} else {
3547
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source port: '$port' not a numeric value between 0 and 65535.");
3548
			return;
3549
		}
3550
		$index++;
3551
	} else if (trim($rule[$index]) == "range") {
3552
		$port = array($rule[++$index], $rule[++$index]);
3553
		if (is_port($port[0]) && is_port($port[1])) {
3554
			$tmprule .= "port {$port[0]}:{$port[1]} ";
3555
		} else {
3556
			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.");
3557
			return;
3558
		}
3559
		$index++;
3560
	}
3561

    
3562
	/* Destination */
3563
	if (trim($rule[$index]) == "host") {
3564
		$index++;
3565
		if ((($proto == 'inet') && (is_ipaddrv4(trim($rule[$index])) || (trim($rule[$index]) == "{clientip}"))) ||
3566
		    (($proto == 'inet6') && (is_ipaddrv6(trim($rule[$index])) || (trim($rule[$index]) == "{clientipv6}")))) {
3567
			$tmprule .= "to {$rule[$index]} ";
3568
			$index++;
3569
		} else {
3570
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination host '{$rule[$index]}'.");
3571
			return;
3572
		}
3573
	} elseif (is_subnetv6(trim($rule[$index])) && ($proto == "inet6")) {
3574
		$tmprule .= "to {$rule[$index]} ";
3575
		$index++;
3576
	} elseif (trim($rule[$index]) == "any") {
3577
		$tmprule .= "to any ";
3578
		$index++;
3579
	} else {
3580
		$network = $rule[$index];
3581
		$netmask = $rule[++$index];
3582

    
3583
		if (is_ipaddrv4($network) && ($proto == "inet")) {
3584
			try {
3585
				$netmask = cisco_to_cidr($netmask);
3586
			} catch(Exception $e) {
3587
				syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination network '$network'.");
3588
				return;
3589
			}
3590
			$tmprule .= "to {$network}/{$netmask} ";
3591
		} else {
3592
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination network '$network'.");
3593
			return;
3594
		}
3595

    
3596
		$index++;
3597
	}
3598

    
3599
	/* Destination Operator */
3600
	if (in_array(trim($rule[$index]), array("lt", "gt", "eq", "neq"))) {
3601
		switch(trim($rule[$index])) {
3602
			case "lt":
3603
				$operator = "<";
3604
				break;
3605
			case "gt":
3606
				$operator = ">";
3607
				break;
3608
			case "eq":
3609
				$operator = "=";
3610
				break;
3611
			case "neq":
3612
				$operator = "!=";
3613
				break;
3614
		}
3615

    
3616
		$port = $rule[++$index];
3617
		if (is_port($port)) {
3618
			$tmprule .= "port {$operator} {$port} ";
3619
		} else {
3620
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination port: '$port' not a numeric value between 0 and 65535.");
3621
			return;
3622
		}
3623
		$index++;
3624
	} else if (trim($rule[$index]) == "range") {
3625
		$port = array($rule[++$index], $rule[++$index]);
3626
		if (is_port($port[0]) && is_port($port[1])) {
3627
			$tmprule .= "port {$port[0]}:{$port[1]} ";
3628
		} else {
3629
			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.");
3630
			return;
3631
		}
3632
		$index++;
3633
	}
3634

    
3635
	$tmprule = $startrule . $proto . " " . $tmprule;
3636
	return $tmprule;
3637
}
3638

    
3639
function parse_cisco_acl($attribs, $dev) {
3640
	global $attributes;
3641

    
3642
	if (!is_array($attribs)) {
3643
		return "";
3644
	}
3645
	$finalrules = "";
3646
	if (is_array($attribs['ciscoavpair'])) {
3647
		$inrules = array('inet' => array(), 'inet6' => array());
3648
		$outrules = array('inet' => array(), 'inet6' => array());
3649
		foreach ($attribs['ciscoavpair'] as $avrules) {
3650
			$rule = explode("=", $avrules);
3651
			$dir = "";
3652
			if (strstr($rule[0], "inacl")) {
3653
				$dir = "in";
3654
			} else if (strstr($rule[0], "outacl")) {
3655
				$dir = "out";
3656
			} else if (strstr($rule[0], "dns-servers")) {
3657
				$attributes['dns-servers'] = explode(" ", $rule[1]);
3658
				continue;
3659
			} else if (strstr($rule[0], "route")) {
3660
				if (!is_array($attributes['routes'])) {
3661
					$attributes['routes'] = array();
3662
				}
3663
				$attributes['routes'][] = $rule[1];
3664
				continue;
3665
			}
3666
			$rindex = cisco_extract_index($rule[0]);
3667
			if ($rindex < 0) {
3668
				continue;
3669
			}
3670

    
3671
			if (strstr($rule[0], "ipv6")) {
3672
				$proto = "inet6";
3673
			} else {
3674
				$proto = "inet";
3675
			}
3676

    
3677
			$tmprule = parse_cisco_acl_rule($rule[1], $dev, $dir, $proto);
3678

    
3679
			if ($dir == "in") {
3680
				$inrules[$proto][$rindex] = $tmprule;
3681
			} else if ($dir == "out") {
3682
				$outrules[$proto][$rindex] = $tmprule;
3683
			}
3684
		}
3685

    
3686

    
3687
		$state = "";
3688
		foreach (array('inet', 'inet6') as $ip) {
3689
			if (!empty($outrules[$ip])) {
3690
				$state = "no state";
3691
			}
3692
			ksort($inrules[$ip], SORT_NUMERIC);
3693
			foreach ($inrules[$ip] as $inrule) {
3694
				$finalrules .= "{$inrule} {$state}\n";
3695
			}
3696
			if (!empty($outrules[$ip])) {
3697
				ksort($outrules[$ip], SORT_NUMERIC);
3698
				foreach ($outrules[$ip] as $outrule) {
3699
					$finalrules .= "{$outrule} {$state}\n";
3700
				}
3701
			}
3702
		}
3703
	}
3704
	return $finalrules;
3705
}
3706

    
3707
function alias_idn_to_utf8($alias) {
3708
	if (is_alias($alias)) {
3709
		return $alias;
3710
	} else {
3711
		return idn_to_utf8($alias);
3712
	}
3713
}
3714

    
3715
function alias_idn_to_ascii($alias) {
3716
	if (is_alias($alias)) {
3717
		return $alias;
3718
	} else {
3719
		return idn_to_ascii($alias);
3720
	}
3721
}
3722

    
3723
// These funtions were in guiconfig.inc but have been moved here so non GUI processes can use them
3724
function address_to_pconfig($adr, &$padr, &$pmask, &$pnot, &$pbeginport, &$pendport) {
3725
	if (isset($adr['any'])) {
3726
		$padr = "any";
3727
	} else if ($adr['network']) {
3728
		$padr = $adr['network'];
3729
	} else if ($adr['address']) {
3730
		list($padr, $pmask) = explode("/", $adr['address']);
3731
		if (!$pmask) {
3732
			if (is_ipaddrv6($padr)) {
3733
				$pmask = 128;
3734
			} else {
3735
				$pmask = 32;
3736
			}
3737
		}
3738
	}
3739

    
3740
	if (isset($adr['not'])) {
3741
		$pnot = 1;
3742
	} else {
3743
		$pnot = 0;
3744
	}
3745

    
3746
	if ($adr['port']) {
3747
		list($pbeginport, $pendport) = explode("-", $adr['port']);
3748
		if (!$pendport) {
3749
			$pendport = $pbeginport;
3750
		}
3751
	} else if (!is_alias($pbeginport) && !is_alias($pendport)) {
3752
		$pbeginport = "any";
3753
		$pendport = "any";
3754
	}
3755
}
3756

    
3757
function pconfig_to_address(&$adr, $padr, $pmask, $pnot = false, $pbeginport = 0, $pendport = 0, $addmask = false) {
3758
	$adr = array();
3759

    
3760
	if ($padr == "any") {
3761
		$adr['any'] = true;
3762
	} else if (is_specialnet($padr)) {
3763
		if ($addmask) {
3764
			$padr .= "/" . $pmask;
3765
		}
3766
		$adr['network'] = $padr;
3767
	} else {
3768
		$adr['address'] = $padr;
3769
		if (is_ipaddrv6($padr)) {
3770
			if ($pmask != 128) {
3771
				$adr['address'] .= "/" . $pmask;
3772
			}
3773
		} else {
3774
			if ($pmask != 32) {
3775
				$adr['address'] .= "/" . $pmask;
3776
			}
3777
		}
3778
	}
3779

    
3780
	if ($pnot) {
3781
		$adr['not'] = true;
3782
	} else {
3783
		unset($adr['not']);
3784
	}
3785

    
3786
	if (($pbeginport != 0) && ($pbeginport != "any")) {
3787
		if ($pbeginport != $pendport) {
3788
			$adr['port'] = $pbeginport . "-" . $pendport;
3789
		} else {
3790
			$adr['port'] = $pbeginport;
3791
		}
3792
	}
3793

    
3794
	/*
3795
	 * If the port is still unset, then it must not be numeric, but could
3796
	 * be an alias or a well-known/registered service.
3797
	 * See https://redmine.pfsense.org/issues/8410
3798
	 */
3799
	if (!isset($adr['port']) && is_port_or_alias($pbeginport)) {
3800
		$adr['port'] = $pbeginport;
3801
	}
3802
}
3803

    
3804
function is_specialnet($net) {
3805
	global $specialsrcdst;
3806

    
3807
	if (!$net) {
3808
		return false;
3809
	}
3810
	if (in_array($net, $specialsrcdst)) {
3811
		return true;
3812
	} else {
3813
		return false;
3814
	}
3815
}
3816

    
3817
function is_interface_ipaddr($interface) {
3818
	global $config;
3819

    
3820
	if (is_array($config['interfaces'][$interface]) &&
3821
	    isset($config['interfaces'][$interface]['ipaddr']) &&
3822
	    !empty($config['interfaces'][$interface]['ipaddr'])) {
3823
		return true;
3824
	}
3825
	return false;
3826
}
3827

    
3828
function is_interface_ipaddrv6($interface) {
3829
	global $config;
3830

    
3831
	if (is_array($config['interfaces'][$interface]) &&
3832
	    isset($config['interfaces'][$interface]['ipaddrv6']) &&
3833
	    !empty($config['interfaces'][$interface]['ipaddrv6'])) {
3834
		return true;
3835
	}
3836
	return false;
3837
}
3838

    
3839
function escape_filter_regex($filtertext) {
3840
	/* If the caller (user) has not already put a backslash before a slash, to escape it in the regex, */
3841
	/* then this will do it. Take out any "\/" already there, then turn all ordinary "/" into "\/".    */
3842
	return str_replace('/', '\/', str_replace('\/', '/', $filtertext));
3843
}
3844

    
3845
/*
3846
 * Check if a given pattern has the same number of two different unescaped
3847
 * characters.
3848
 * For example, it can ensure a pattern has balanced sets of parentheses,
3849
 * braces, and brackets.
3850
 */
3851
function is_pattern_balanced_char($pattern, $open, $close) {
3852
	/* First remove escaped versions */
3853
	$pattern = str_replace('\\' . $open, '', $pattern);
3854
	$pattern = str_replace('\\' . $close, '', $pattern);
3855
	/* Check if the counts of both characters match in the target pattern */
3856
	return (substr_count($pattern, $open) == substr_count($pattern, $close));
3857
}
3858

    
3859
/*
3860
 * Check if a pattern contains balanced sets of parentheses, braces, and
3861
 * brackets.
3862
 */
3863
function is_pattern_balanced($pattern) {
3864
	if (is_pattern_balanced_char($pattern, '(', ')') &&
3865
	    is_pattern_balanced_char($pattern, '{', '}') &&
3866
	    is_pattern_balanced_char($pattern, '[', ']')) {
3867
		/* Balanced if all are true */
3868
		return true;
3869
	}
3870
	return false;
3871
}
3872

    
3873
function cleanup_regex_pattern($filtertext) {
3874
	/* Cleanup filter to prevent backreferences. */
3875
	$filtertext = escape_filter_regex($filtertext);
3876

    
3877
	/* Remove \<digit>+ backreferences
3878
	 * To match \ it must be escaped as \\\\ in PHP for preg_replace() */
3879
	$filtertext = preg_replace('/\\\\\\d+/', '', $filtertext);
3880

    
3881
	/* Check for unbalanced parentheses, braces, and brackets which
3882
	 * may be an error or attempt to circumvent protections.
3883
	 * Also discard any pattern that attempts problematic duplication
3884
	 * methods. */
3885
	if (!is_pattern_balanced($filtertext) ||
3886
	    (substr_count($filtertext, ')*') > 0) ||
3887
	    (substr_count($filtertext, ')+') > 0) ||
3888
	    (substr_count($filtertext, '{') > 0)) {
3889
		return '';
3890
	}
3891

    
3892
	return $filtertext;
3893
}
3894

    
3895
function ip6_to_asn1($addr) {
3896
	/* IPv6 MIB uses an OCTET STRING of length 16 to represent
3897
	 * 128-bit IPv6 address in network byte order.
3898
	 * see https://datatracker.ietf.org/doc/html/rfc2465#section-3 
3899
	 * i.e. fc00:3::4 = 252.0.0.3.0.0.0.0.0.0.0.0.0.0.0.4
3900
	 */
3901

    
3902
	if (!is_ipaddrv6($addr)) {
3903
		return false;
3904
	}
3905

    
3906
	foreach (explode(':', Net_IPv6::uncompress($addr)) as $v) {
3907
		$ipv6str .= str_pad($v, 4, '0', STR_PAD_LEFT);
3908
	}
3909
	foreach (str_split($ipv6str, 2) as $v) {
3910
		$octstr .= base_convert($v, 16, 10) . '.';
3911
	}
3912

    
3913
	return $octstr;
3914
}
3915

    
3916
function interfaces_interrupts() {
3917
	exec("/usr/bin/vmstat -i --libxo json", $rawdata, $rc);
3918
	$interrupts = array();
3919
	if ($rc == 0) {
3920
		$vnstatarr = json_decode(implode(" ", $rawdata), JSON_OBJECT_AS_ARRAY);
3921
		$interruptarr = $vnstatarr['interrupt-statistics']['interrupt'];
3922

    
3923
		foreach ($interruptarr as $int){
3924
			preg_match("/irq\d+: ([a-z0-9]+)/", $int['name'], $matches);
3925
			$name = $matches[1];
3926
			if (array_key_exists($name, $interrupts)) {
3927
				/* interface with multiple queues */
3928
				$interrupts[$name]['total'] += $int['total'];
3929
				$interrupts[$name]['rate'] += $int['rate'];
3930
			} else {
3931
				$interrupts[$name]['total'] = $int['total'];
3932
				$interrupts[$name]['rate'] = $int['rate'];
3933
			}
3934
		}
3935
	}
3936

    
3937
	return $interrupts;
3938
}
3939

    
3940
?>
(53-53/61)