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
	return Net_IPv6::checkIPv6($ipaddr);
690
}
691

    
692
function is_ipaddrv6_v4map($ipaddr) {
693
	/* check RFC4291 par 2.2.2 format, ex: fd00::1.2.3.4
694
	 * see https://redmine.pfsense.org/issues/11446 */
695
	if (is_ipaddrv6($ipaddr) && preg_match('/^[0-9a-f:]{2,30}[0-9.]{7,15}$/i', $ipaddr)) {
696
		return true;
697
	}
698
	return false;
699
}
700

    
701
/* returns true if $ipaddr is a valid dotted IPv4 address */
702
function is_ipaddrv4($ipaddr) {
703
	if (!is_string($ipaddr) || empty($ipaddr) || ip2long($ipaddr) === FALSE) {
704
		return false;
705
	}
706
	return true;
707
}
708

    
709
function is_mcast($ipaddr) {
710
	if (is_mcastv4($ipaddr)) {
711
		return 4;
712
	}
713
	if (is_mcastv6($ipaddr)) {
714
		return 6;
715
	}
716
	return false;
717
}
718

    
719
function is_mcastv4($ipaddr) {
720
	if (!is_ipaddrv4($ipaddr) ||
721
	    !preg_match('/^2(?:2[4-9]|3\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d?|0)){3}$/', $ipaddr)) {
722
		return false;
723
	}
724
	return true;
725
}
726

    
727
function is_mcastv6($ipaddr) {
728
	if (!is_ipaddrv6($ipaddr) || !preg_match('/^ff.+$/', $ipaddr)) {
729
		return false;
730
	}
731
	return true;
732
}
733

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

    
750
/* returns scope of a linklocal address */
751
function get_ll_scope($addr) {
752
	if (!is_linklocal($addr) || !strstr($addr, "%")) {
753
		return "";
754
	}
755
	list ($ll, $scope) = explode("%", $addr);
756
	return $scope;
757
}
758

    
759
/* returns true if $ipaddr is a valid literal IPv6 address */
760
function is_literalipaddrv6($ipaddr) {
761
	if (substr($ipaddr,0,1) == '[' && substr($ipaddr,-1,1) == ']') {
762
		// if it's data wrapped in "[ ... ]" then test if middle part is valid IPv6
763
		return is_ipaddrv6(substr($ipaddr,1,-1));
764
	}
765
	return false;
766
}
767

    
768
/* returns true if $iport is a valid IPv4:port or [Literal IPv6]:port
769
	false - not valid
770
	true (numeric 4 or 6) - if valid, gives type of address */
771
function is_ipaddrwithport($ipport) {
772
	$c = strrpos($ipport, ":");
773
	if ($c === false) {
774
		return false;  // can't split at final colon if no colon exists
775
	}
776

    
777
	if (!is_port(substr($ipport, $c + 1))) {
778
		return false;  // no valid port after last colon
779
	}
780

    
781
	$ip = substr($ipport, 0, $c);  // else is text before last colon a valid IP
782
	if (is_literalipaddrv6($ip)) {
783
		return 6;
784
	} elseif (is_ipaddrv4($ip)) {
785
		return 4;
786
	} else {
787
		return false;
788
	}
789
}
790

    
791
function is_hostnamewithport($hostport) {
792
	$parts = explode(":", $hostport);
793
	// no need to validate with is_string(); if it's not a string then explode won't return 2 parts anyway
794
	if (count($parts) == 2) {
795
		return is_hostname($parts[0]) && is_port($parts[1]);
796
	}
797
	return false;
798
}
799

    
800
/* returns true if $ipaddr is a valid dotted IPv4 address or an alias thereof */
801
function is_ipaddroralias($ipaddr) {
802
	global $config;
803

    
804
	if (is_alias($ipaddr)) {
805
		if (is_array($config['aliases']['alias'])) {
806
			foreach ($config['aliases']['alias'] as $alias) {
807
				if ($alias['name'] == $ipaddr && !preg_match("/port/i", $alias['type'])) {
808
					return true;
809
				}
810
			}
811
		}
812
		return false;
813
	} else {
814
		return is_ipaddr($ipaddr);
815
	}
816

    
817
}
818

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

    
834
function is_v4($ip_or_subnet) {
835
	return is_ipaddrv4($ip_or_subnet) || is_subnetv4($ip_or_subnet);
836
}
837

    
838
function is_v6($ip_or_subnet) {
839
	return is_ipaddrv6($ip_or_subnet) || is_subnetv6($ip_or_subnet);
840
}
841

    
842
/* same as is_subnet() but accepts IPv4 only */
843
function is_subnetv4($subnet) {
844
	return (is_subnet($subnet) == 4);
845
}
846

    
847
/* same as is_subnet() but accepts IPv6 only */
848
function is_subnetv6($subnet) {
849
	return (is_subnet($subnet) == 6);
850
}
851

    
852
/* returns true if $subnet is a valid subnet in CIDR format or an alias thereof */
853
function is_subnetoralias($subnet) {
854
	global $aliastable;
855

    
856
	if (isset($aliastable[$subnet]) && is_subnet($aliastable[$subnet])) {
857
		return true;
858
	} else {
859
		return is_subnet($subnet);
860
	}
861
}
862

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

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

    
891
	// 2**N returns an exact result as an INT if possible, and a float/double if not.
892
	// 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
893
	$result = 2 ** $snsize;
894

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

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

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

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

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

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

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

    
958
/* return all PTR zones for a IPv6 network */
959
function get_v6_ptr_zones($subnet, $bits) {
960
	$result = array();
961

    
962
	if (!is_ipaddrv6($subnet)) {
963
		return $result;
964
	}
965

    
966
	if (!is_numericint($bits) || $bits > 128) {
967
		return $result;
968
	}
969

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

    
979
	/* Get network prefix */
980
	$small_subnet = Net_IPv6::getNetmask($subnet, $bits);
981

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

    
993
		/* Detect what part of IP should be increased */
994
		$change_part = (int) ($small_sn / 16);
995
		if ($small_sn % 16 == 0) {
996
			$change_part--;
997
		}
998

    
999
		/* Increase 1 to desired part */
1000
		$parts = explode(":", Net_IPv6::uncompress($small_subnet));
1001
		$parts[$change_part]++;
1002
		$small_subnet = implode(":", $parts);
1003
	}
1004

    
1005
	return $result;
1006
}
1007

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

    
1020
/* returns true if $hostname is just a valid hostname (top part without any of the domain part) */
1021
function is_unqualified_hostname($hostname) {
1022
	if (!is_string($hostname)) {
1023
		return false;
1024
	}
1025

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

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

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

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

    
1065
	if (preg_match($domain_regex, $domain)) {
1066
		return true;
1067
	} else {
1068
		return false;
1069
	}
1070
}
1071

    
1072
/* returns true if $macaddr is a valid MAC address */
1073
function is_macaddr($macaddr, $partial=false) {
1074
	$values = explode(":", $macaddr);
1075

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

    
1091
	return true;
1092
}
1093

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

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

    
1107
function is_validaliasname($name, $return_message = false, $object = "alias") {
1108
	/* Array of reserved words */
1109
	$reserved = array("port", "pass");
1110

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

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

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

    
1158
	if (!is_array($values) || count($values) != 2) {
1159
		return false;
1160
	}
1161

    
1162
	if (!ctype_digit($values[0]) || !ctype_digit($values[1])) {
1163
		return false;
1164
	}
1165

    
1166
	$values[0] = intval($values[0]);
1167
	$values[1] = intval($values[1]);
1168

    
1169
	if ($values[0] >= $values[1]) {
1170
		return false;
1171
	}
1172

    
1173
	if ($values[0] < $min || $values[1] > $max) {
1174
		return false;
1175
	}
1176

    
1177
	return true;
1178
}
1179

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

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

    
1199
		foreach($netstatarr as $index => $portstats){
1200
			array_push($port_info, $portstats['local']['port']);
1201
		}
1202
	}
1203

    
1204
	return in_array($port, $port_info);
1205
}
1206

    
1207
/* returns true if $portrange is a valid TCP/UDP portrange ("<port>:<port>") */
1208
function is_portrange($portrange) {
1209
	$ports = explode(":", $portrange);
1210

    
1211
	return (count($ports) == 2 && is_port($ports[0]) && is_port($ports[1]));
1212
}
1213

    
1214
/* returns true if $port is a valid TCP/UDP port number or range ("<port>:<port>") */
1215
function is_port_or_range($port) {
1216
	return (is_port($port) || is_portrange($port));
1217
}
1218

    
1219
/* returns true if $port is an alias that is a port type */
1220
function is_portalias($port) {
1221
	global $config;
1222

    
1223
	if (is_alias($port)) {
1224
		if (is_array($config['aliases']['alias'])) {
1225
			foreach ($config['aliases']['alias'] as $alias) {
1226
				if ($alias['name'] == $port && preg_match("/port/i", $alias['type'])) {
1227
					return true;
1228
				}
1229
			}
1230
		}
1231
	}
1232
	return false;
1233
}
1234

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

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

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

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

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

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

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

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

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

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

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

    
1317
	return false;
1318
}
1319

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

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

    
1331
	return false;
1332
}
1333

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

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

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

    
1352
	return($list);
1353
}
1354

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1463
	return (NULL);
1464
}
1465

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

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

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

    
1487
	$iflist = array();
1488

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

    
1496
	return $iflist;
1497
}
1498

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

    
1503
	$iflist = array();
1504

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

    
1515
	return $iflist;
1516
}
1517

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

    
1522
	$iflist = array();
1523

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

    
1535
	if ($user_settings['webgui']['interfacessort']) {
1536
		asort($iflist);
1537
	}
1538

    
1539
	return $iflist;
1540
}
1541

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

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

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

    
1580
	return $ip_array;
1581
}
1582

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

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

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

    
1747
function get_lagg_interface_list() {
1748
	global $config;
1749

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

    
1759
	return ($plist);
1760
}
1761

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

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

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

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

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

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

    
1847
	if ($clearsigmask) {
1848
		pcntl_sigprocmask(SIG_SETMASK, $oldset);
1849
	}
1850

    
1851
	return $retval;
1852
}
1853

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

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

    
1883
/* make a global alias table (for faster lookups) */
1884
function alias_make_table() {
1885
	global $aliastable, $config;
1886

    
1887
	$aliastable = array();
1888

    
1889
	init_config_arr(array('aliases', 'alias'));
1890
	foreach ($config['aliases']['alias'] as $alias) {
1891
		if ($alias['name']) {
1892
			$aliastable[$alias['name']] = $alias['address'];
1893
		}
1894
	}
1895
}
1896

    
1897
/* check if an alias exists */
1898
function is_alias($name) {
1899
	global $aliastable;
1900

    
1901
	return isset($aliastable[$name]);
1902
}
1903

    
1904
function alias_get_type($name) {
1905
	global $config;
1906

    
1907
	if (is_array($config['aliases']['alias'])) {
1908
		foreach ($config['aliases']['alias'] as $alias) {
1909
			if ($name == $alias['name']) {
1910
				return $alias['type'];
1911
			}
1912
		}
1913
	}
1914

    
1915
	return "";
1916
}
1917

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

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

    
1955
function alias_expand_urltable($name) {
1956
	global $config;
1957
	$urltable_prefix = "/var/db/aliastables/";
1958
	$urltable_filename = $urltable_prefix . $name . ".txt";
1959

    
1960
	if (!is_array($config['aliases']['alias'])) {
1961
		return null;
1962
	}
1963

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
2073
		$dnsresult = @dns_get_record($hostname, $dnsproto);
2074

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

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

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

    
2100
		usleep(100000);
2101
	}
2102

    
2103
	return false;
2104
}
2105

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

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

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

    
2130
	return ("$num {$units[$i]}");
2131
}
2132

    
2133

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

    
2150
function update_filter_reload_status($text, $new=false) {
2151
	global $g;
2152

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

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

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

    
2188
function run_plugins($directory) {
2189
	global $config, $g;
2190

    
2191
	/* process packager manager custom rules */
2192
	$files = return_dir_as_array($directory);
2193
	if (is_array($files)) {
2194
		foreach ($files as $file) {
2195
			if (stristr($file, ".sh") == true) {
2196
				mwexec($directory . $file . " start");
2197
			} else if (!is_dir($directory . "/" . $file) && stristr($file, ".inc")) {
2198
				require_once($directory . "/" . $file);
2199
			}
2200
		}
2201
	}
2202
}
2203

    
2204
/*
2205
 *    safe_mkdir($path, $mode = 0755)
2206
 *    create directory if it doesn't already exist and isn't a file!
2207
 */
2208
function safe_mkdir($path, $mode = 0755) {
2209
	global $g;
2210

    
2211
	if (!is_file($path) && !is_dir($path)) {
2212
		return @mkdir($path, $mode, true);
2213
	} else {
2214
		return false;
2215
	}
2216
}
2217

    
2218
/*
2219
 * get_sysctl($names)
2220
 * Get values of sysctl OID's listed in $names (accepts an array or a single
2221
 * name) and return an array of key/value pairs set for those that exist
2222
 */
2223
function get_sysctl($names) {
2224
	if (empty($names)) {
2225
		return array();
2226
	}
2227

    
2228
	if (is_array($names)) {
2229
		$name_list = array();
2230
		foreach ($names as $name) {
2231
			$name_list[] = escapeshellarg($name);
2232
		}
2233
	} else {
2234
		$name_list = array(escapeshellarg($names));
2235
	}
2236

    
2237
	exec("/sbin/sysctl -iq " . implode(" ", $name_list), $output);
2238
	$values = array();
2239
	foreach ($output as $line) {
2240
		$line = explode(": ", $line, 2);
2241
		if (count($line) == 2) {
2242
			$values[$line[0]] = $line[1];
2243
		}
2244
	}
2245

    
2246
	return $values;
2247
}
2248

    
2249
/*
2250
 * get_single_sysctl($name)
2251
 * Wrapper for get_sysctl() to simplify read of a single sysctl value
2252
 * return the value for sysctl $name or empty string if it doesn't exist
2253
 */
2254
function get_single_sysctl($name) {
2255
	if (empty($name)) {
2256
		return "";
2257
	}
2258

    
2259
	$value = get_sysctl($name);
2260
	if (empty($value) || !isset($value[$name])) {
2261
		return "";
2262
	}
2263

    
2264
	return $value[$name];
2265
}
2266

    
2267
/*
2268
 * set_sysctl($value_list)
2269
 * Set sysctl OID's listed as key/value pairs and return
2270
 * an array with keys set for those that succeeded
2271
 */
2272
function set_sysctl($values) {
2273
	if (empty($values)) {
2274
		return array();
2275
	}
2276

    
2277
	$value_list = array();
2278
	foreach ($values as $key => $value) {
2279
		$value_list[] = escapeshellarg($key) . "=" . escapeshellarg($value);
2280
	}
2281

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

    
2284
	/* Retry individually if failed (one or more read-only) */
2285
	if ($success <> 0 && count($value_list) > 1) {
2286
		foreach ($value_list as $value) {
2287
			exec("/sbin/sysctl -iq " . $value, $output);
2288
		}
2289
	}
2290

    
2291
	$ret = array();
2292
	foreach ($output as $line) {
2293
		$line = explode(": ", $line, 2);
2294
		if (count($line) == 2) {
2295
			$ret[$line[0]] = true;
2296
		}
2297
	}
2298

    
2299
	return $ret;
2300
}
2301

    
2302
/*
2303
 * set_single_sysctl($name, $value)
2304
 * Wrapper to set_sysctl() to make it simple to set only one sysctl
2305
 * returns boolean meaning if it succeeded
2306
 */
2307
function set_single_sysctl($name, $value) {
2308
	if (empty($name)) {
2309
		return false;
2310
	}
2311

    
2312
	$result = set_sysctl(array($name => $value));
2313

    
2314
	if (!isset($result[$name]) || $result[$name] != $value) {
2315
		return false;
2316
	}
2317

    
2318
	return true;
2319
}
2320

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

    
2335
function mute_kernel_msgs() {
2336
	global $g, $config;
2337

    
2338
	if ($config['system']['enableserial']) {
2339
		return;
2340
	}
2341
	exec("/sbin/conscontrol mute on");
2342
}
2343

    
2344
function unmute_kernel_msgs() {
2345
	global $g;
2346

    
2347
	exec("/sbin/conscontrol mute off");
2348
}
2349

    
2350
function start_devd() {
2351
	global $g;
2352

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

    
2377
	/* Use the undocumented -q options of devd to quiet its log spamming */
2378
	$_gb = exec("/sbin/devd -q -f /etc/{$g['product_name']}-devd.conf");
2379
	sleep(1);
2380
	unset($_gb);
2381
}
2382

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

    
2386
	if (is_array($config['vlans']['vlan'])) {
2387
		foreach ($config['vlans']['vlan'] as $vlan) {
2388
			if (substr($vlan['if'], 0, 4) == "lagg") {
2389
				return false;
2390
			}
2391
			if (does_interface_exist($vlan['if']) == false) {
2392
				return true;
2393
			}
2394
		}
2395
	}
2396

    
2397
	return false;
2398
}
2399

    
2400
function is_interface_mismatch() {
2401
	global $config, $g;
2402

    
2403
	$do_assign = false;
2404
	$i = 0;
2405
	$missing_interfaces = array();
2406
	if (is_array($config['interfaces'])) {
2407
		foreach ($config['interfaces'] as $ifname => $ifcfg) {
2408
			if (interface_is_vlan($ifcfg['if']) != NULL ||
2409
			    interface_is_qinq($ifcfg['if']) != NULL ||
2410
			    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'])) {
2411
				// Do not check these interfaces.
2412
				$i++;
2413
				continue;
2414
			} else if (does_interface_exist($ifcfg['if']) == false) {
2415
				$missing_interfaces[] = $ifcfg['if'];
2416
				$do_assign = true;
2417
			} else {
2418
				$i++;
2419
			}
2420
		}
2421
	}
2422

    
2423
	/* VLAN/QinQ-only interface mismatch detection
2424
	 * see https://redmine.pfsense.org/issues/12170 */
2425
	init_config_arr(array('vlans', 'vlan'));
2426
	foreach ($config['vlans']['vlan'] as $vlan) {
2427
		if (!does_interface_exist($vlan['if']) && 
2428
		    !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'])) {
2429
			$missing_interfaces[] = $vlan['if'];
2430
			$do_assign = true;
2431
		}
2432
	}
2433
	init_config_arr(array('qinqs', 'qinqentry'));
2434
	foreach ($config['qinqs']['qinqentry'] as $qinq) {
2435
		if (!does_interface_exist($qinq['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", $qinq['if'])) {
2437
			$missing_interfaces[] = $qinq['if'];
2438
			$do_assign = true;
2439
		}
2440
	}
2441

    
2442
	if (file_exists("{$g['tmp_path']}/assign_complete")) {
2443
		$do_assign = false;
2444
	}
2445

    
2446
	if (!empty($missing_interfaces) && $do_assign) {
2447
		file_put_contents("{$g['tmp_path']}/missing_interfaces", implode(' ', $missing_interfaces));
2448
	} else {
2449
		@unlink("{$g['tmp_path']}/missing_interfaces");
2450
	}
2451

    
2452
	return $do_assign;
2453
}
2454

    
2455
/* sync carp entries to other firewalls */
2456
function carp_sync_client() {
2457
	global $g;
2458
	send_event("filter sync");
2459
}
2460

    
2461
/****f* util/isAjax
2462
 * NAME
2463
 *   isAjax - reports if the request is driven from prototype
2464
 * INPUTS
2465
 *   none
2466
 * RESULT
2467
 *   true/false
2468
 ******/
2469
function isAjax() {
2470
	return isset ($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest';
2471
}
2472

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

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

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

    
2560
function is_file_included($file = "") {
2561
	$files = get_included_files();
2562
	if (in_array($file, $files)) {
2563
		return true;
2564
	}
2565

    
2566
	return false;
2567
}
2568

    
2569
/*
2570
 * Replace a value on a deep associative array using regex
2571
 */
2572
function array_replace_values_recursive($data, $match, $replace) {
2573
	if (empty($data)) {
2574
		return $data;
2575
	}
2576

    
2577
	if (is_string($data)) {
2578
		$data = preg_replace("/{$match}/", $replace, $data);
2579
	} else if (is_array($data)) {
2580
		foreach ($data as $k => $v) {
2581
			$data[$k] = array_replace_values_recursive($v, $match, $replace);
2582
		}
2583
	}
2584

    
2585
	return $data;
2586
}
2587

    
2588
/*
2589
	This function was borrowed from a comment on PHP.net at the following URL:
2590
	http://www.php.net/manual/en/function.array-merge-recursive.php#73843
2591
 */
2592
function array_merge_recursive_unique($array0, $array1) {
2593

    
2594
	$arrays = func_get_args();
2595
	$remains = $arrays;
2596

    
2597
	// We walk through each arrays and put value in the results (without
2598
	// considering previous value).
2599
	$result = array();
2600

    
2601
	// loop available array
2602
	foreach ($arrays as $array) {
2603

    
2604
		// The first remaining array is $array. We are processing it. So
2605
		// we remove it from remaining arrays.
2606
		array_shift($remains);
2607

    
2608
		// We don't care non array param, like array_merge since PHP 5.0.
2609
		if (is_array($array)) {
2610
			// Loop values
2611
			foreach ($array as $key => $value) {
2612
				if (is_array($value)) {
2613
					// we gather all remaining arrays that have such key available
2614
					$args = array();
2615
					foreach ($remains as $remain) {
2616
						if (array_key_exists($key, $remain)) {
2617
							array_push($args, $remain[$key]);
2618
						}
2619
					}
2620

    
2621
					if (count($args) > 2) {
2622
						// put the recursion
2623
						$result[$key] = call_user_func_array(__FUNCTION__, $args);
2624
					} else {
2625
						foreach ($value as $vkey => $vval) {
2626
							if (!is_array($result[$key])) {
2627
								$result[$key] = array();
2628
							}
2629
							$result[$key][$vkey] = $vval;
2630
						}
2631
					}
2632
				} else {
2633
					// simply put the value
2634
					$result[$key] = $value;
2635
				}
2636
			}
2637
		}
2638
	}
2639
	return $result;
2640
}
2641

    
2642

    
2643
/*
2644
 * converts a string like "a,b,c,d"
2645
 * into an array like array("a" => "b", "c" => "d")
2646
 */
2647
function explode_assoc($delimiter, $string) {
2648
	$array = explode($delimiter, $string);
2649
	$result = array();
2650
	$numkeys = floor(count($array) / 2);
2651
	for ($i = 0; $i < $numkeys; $i += 1) {
2652
		$result[$array[$i * 2]] = $array[$i * 2 + 1];
2653
	}
2654
	return $result;
2655
}
2656

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

    
2674
/* Return system's route table */
2675
function route_table() {
2676
	$_gb = exec("/usr/bin/netstat --libxo json -nWr", $rawdata, $rc);
2677

    
2678
	if ($rc != 0) {
2679
		return array();
2680
	}
2681

    
2682
	$netstatarr = json_decode(implode(" ", $rawdata), JSON_OBJECT_AS_ARRAY);
2683
	$netstatarr = $netstatarr['statistics']['route-information']
2684
	    ['route-table']['rt-family'];
2685

    
2686
	$result = array();
2687
	$result['inet'] = array();
2688
	$result['inet6'] = array();
2689
	foreach ($netstatarr as $item) {
2690
		if ($item['address-family'] == 'Internet') {
2691
			$result['inet'] = $item['rt-entry'];
2692
		} else if ($item['address-family'] == 'Internet6') {
2693
			$result['inet6'] = $item['rt-entry'];
2694
		}
2695
	}
2696
	unset($netstatarr);
2697

    
2698
	return $result;
2699
}
2700

    
2701
/* check if route is static (not BGP/OSPF) */
2702
function is_static_route($target, $ipprotocol = '') {
2703
	if (is_v4($target) || (($target == 'default') && ($ipprotocol == 'inet'))) {
2704
		$inet = '4';
2705
	} elseif (is_v6($target) || (($target == 'default') && ($ipprotocol == 'inet6'))) {
2706
		$inet = '6';
2707
	} else {
2708
		return false;
2709
	}
2710

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

    
2715
	return false;
2716
}
2717

    
2718
/* Get static route for specific destination */
2719
function route_get($target, $ipprotocol = '', $useroute = false) {
2720
	global $config;
2721

    
2722
	if (!empty($ipprotocol)) {
2723
		$family = $ipprotocol;
2724
	} else if (is_v4($target)) {
2725
		$family = 'inet';
2726
	} else if (is_v6($target)) {
2727
		$family = 'inet6';
2728
	}
2729

    
2730
	if (empty($family)) {
2731
		return array();
2732
	}
2733

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

    
2767
		$result = array();
2768
		foreach ($rtable[$family] as $item) {
2769
			if ($item['destination'] == $target ||
2770
			    ip_in_subnet($target, $item['destination'])) {
2771
				$result[] = $item;
2772
			}
2773
		}
2774
	}
2775

    
2776
	return $result;
2777
}
2778

    
2779
/* Get default route */
2780
function route_get_default($ipprotocol) {
2781
	if (empty($ipprotocol) || ($ipprotocol != 'inet' &&
2782
	    $ipprotocol != 'inet6')) {
2783
		return '';
2784
	}
2785

    
2786
	$route = route_get('default', $ipprotocol, true);
2787

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

    
2792
	if (!isset($route[0]['gateway'])) {
2793
		return '';
2794
	}
2795

    
2796
	return $route[0]['gateway'];
2797
}
2798

    
2799
/* Delete a static route */
2800
function route_del($target, $ipprotocol = '') {
2801
	global $config;
2802

    
2803
	if (empty($target)) {
2804
		return;
2805
	}
2806

    
2807
	if (!empty($ipprotocol) && $ipprotocol != 'inet' &&
2808
	    $ipprotocol != 'inet6') {
2809
		return false;
2810
	}
2811

    
2812
	$route = route_get($target, $ipprotocol, true);
2813

    
2814
	if (empty($route)) {
2815
		return;
2816
	}
2817

    
2818
	$target_prefix = '';
2819
	if (is_subnet($target)) {
2820
		$target_prefix = '-net';
2821
	} else if (is_ipaddr($target)) {
2822
		$target_prefix = '-host';
2823
	}
2824

    
2825
	if (!empty($ipprotocol)) {
2826
		$target_prefix .= " -{$ipprotocol}";
2827
	} else if (is_v6($target)) {
2828
		$target_prefix .= ' -inet6';
2829
	} else if (is_v4($target)) {
2830
		$target_prefix .= ' -inet';
2831
	}
2832

    
2833
	foreach ($route as $item) {
2834
		if (substr($item['gateway'], 0, 5) == 'link#') {
2835
			continue;
2836
		}
2837

    
2838
		if (is_macaddr($item['gateway'])) {
2839
			$gw = '-iface ' . $item['interface-name'];
2840
		} else {
2841
			$gw = $item['gateway'];
2842
		}
2843

    
2844
		$_gb = exec(escapeshellcmd("/sbin/route del {$target_prefix} " .
2845
		    "{$target} {$gw}"), $output, $rc);
2846

    
2847
		if (isset($config['system']['route-debug'])) {
2848
			log_error("ROUTING debug: " . microtime() .
2849
			    " - DEL RC={$rc} - {$target} - gw: " . $gw);
2850
			file_put_contents("/dev/console", "\n[" . getmypid() .
2851
			    "] ROUTE DEL: {$target_prefix} {$target} {$gw} " .
2852
			    "result: {$rc}");
2853
		}
2854
	}
2855
}
2856

    
2857
/*
2858
 * Add static route.  If it already exists, remove it and re-add
2859
 *
2860
 * $target - IP, subnet or 'default'
2861
 * $gw     - gateway address
2862
 * $iface  - Network interface
2863
 * $args   - Extra arguments for /sbin/route
2864
 * $ipprotocol - 'inet' or 'inet6'.  Mandatory when $target == 'default'
2865
 *
2866
 */
2867
function route_add_or_change($target, $gw, $iface = '', $args = '',
2868
    $ipprotocol = '') {
2869
	global $config;
2870

    
2871
	if (empty($target) || (empty($gw) && empty($iface))) {
2872
		return false;
2873
	}
2874

    
2875
	if ($target == 'default' && empty($ipprotocol)) {
2876
		return false;
2877
	}
2878

    
2879
	if (!empty($ipprotocol) && $ipprotocol != 'inet' &&
2880
	    $ipprotocol != 'inet6') {
2881
		return false;
2882
	}
2883

    
2884
	/* use '-host' for IPv6 /128 routes, see https://redmine.pfsense.org/issues/11594 */
2885
	if (is_subnetv4($target) || (is_subnetv6($target) && (subnet_size($target) > 1))) {
2886
		$target_prefix = '-net';
2887
	} else if (is_ipaddr($target)) {
2888
		$target_prefix = '-host';
2889
	}
2890

    
2891
	if (!empty($ipprotocol)) {
2892
		$target_prefix .= " -{$ipprotocol}";
2893
	} else if (is_v6($target)) {
2894
		$target_prefix .= ' -inet6';
2895
	} else if (is_v4($target)) {
2896
		$target_prefix .= ' -inet';
2897
	}
2898

    
2899
	/* If there is another route to the same target, remove it */
2900
	route_del($target, $ipprotocol);
2901

    
2902
	$params = '';
2903
	if (!empty($iface) && does_interface_exist($iface)) {
2904
		$params .= " -iface {$iface}";
2905
	}
2906
	if (is_ipaddr($gw)) {
2907
		/* set correct linklocal gateway address,
2908
		 * see https://redmine.pfsense.org/issues/11713 
2909
		 * and https://redmine.pfsense.org/issues/11806 */
2910
		if (is_ipaddrv6($gw) && is_linklocal($gw) && empty(get_ll_scope($gw))) {
2911
			$routeget = route_get($gw, 'inet6', true);
2912
			$gw .= "%" . $routeget[0]['interface-name'];
2913
		}
2914
		$params .= " " . $gw;
2915
	}
2916

    
2917
	if (empty($params)) {
2918
		log_error("route_add_or_change: Invalid gateway {$gw} and/or " .
2919
		    "network interface {$iface}");
2920
		return false;
2921
	}
2922

    
2923
	$_gb = exec(escapeshellcmd("/sbin/route add {$target_prefix} " .
2924
	    "{$target} {$args} {$params}"), $output, $rc);
2925

    
2926
	if (isset($config['system']['route-debug'])) {
2927
		log_error("ROUTING debug: " . microtime() .
2928
		    " - ADD RC={$rc} - {$target} {$args}");
2929
		file_put_contents("/dev/console", "\n[" . getmypid() .
2930
		    "] ROUTE ADD: {$target_prefix} {$target} {$args} " .
2931
		    "{$params} result: {$rc}");
2932
	}
2933

    
2934
	return ($rc == 0);
2935
}
2936

    
2937
function set_ipv6routes_mtu($interface, $mtu) {
2938
	global $config, $g;
2939

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

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

    
2996
function get_staticroutes($returnsubnetsonly = false, $returnhostnames = false, $returnenabledroutesonly = false) {
2997
	global $config, $aliastable;
2998

    
2999
	/* Bail if there are no routes, but return an array always so callers don't have to check. */
3000
	init_config_arr(array('staticroutes', 'route'));
3001
	if (empty($config['staticroutes']['route'])) {
3002
		return array();
3003
	}
3004

    
3005
	$allstaticroutes = array();
3006
	$allsubnets = array();
3007
	/* Loop through routes and expand aliases as we find them. */
3008
	foreach ($config['staticroutes']['route'] as $route) {
3009
		if ($returnenabledroutesonly && isset($route['disabled'])) {
3010
			continue;
3011
		}
3012

    
3013
		if (is_alias($route['network'])) {
3014
			foreach (alias_to_subnets_recursive($route['network'], $returnhostnames) as $net) {
3015
				$temproute = $route;
3016
				$temproute['network'] = $net;
3017
				$allstaticroutes[] = $temproute;
3018
				$allsubnets[] = $net;
3019
			}
3020
		} elseif (is_subnet($route['network'])) {
3021
			$allstaticroutes[] = $route;
3022
			$allsubnets[] = $route['network'];
3023
		}
3024
	}
3025
	if ($returnsubnetsonly) {
3026
		return $allsubnets;
3027
	} else {
3028
		return $allstaticroutes;
3029
	}
3030
}
3031

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

    
3062
/* returns an array consisting of every element of $haystack that is not equal to $needle. */
3063
function array_exclude($needle, $haystack) {
3064
	$result = array();
3065
	if (is_array($haystack)) {
3066
		foreach ($haystack as $thing) {
3067
			if ($needle !== $thing) {
3068
				$result[] = $thing;
3069
			}
3070
		}
3071
	}
3072
	return $result;
3073
}
3074

    
3075
/* Define what is preferred, IPv4 or IPv6 */
3076
function prefer_ipv4_or_ipv6() {
3077
	global $config;
3078

    
3079
	if (isset($config['system']['prefer_ipv4'])) {
3080
		mwexec("/etc/rc.d/ip6addrctl prefer_ipv4");
3081
	} else {
3082
		mwexec("/etc/rc.d/ip6addrctl prefer_ipv6");
3083
	}
3084
}
3085

    
3086
/* Redirect to page passing parameters via POST */
3087
function post_redirect($page, $params) {
3088
	if (!is_array($params)) {
3089
		return;
3090
	}
3091

    
3092
	print "<html><body><form action=\"{$page}\" name=\"formredir\" method=\"post\">\n";
3093
	foreach ($params as $key => $value) {
3094
		print "<input type=\"hidden\" name=\"{$key}\" value=\"{$value}\" />\n";
3095
	}
3096
	print "</form>\n";
3097
	print "<script type=\"text/javascript\">\n";
3098
	print "//<![CDATA[\n";
3099
	print "document.formredir.submit();\n";
3100
	print "//]]>\n";
3101
	print "</script>\n";
3102
	print "</body></html>\n";
3103
}
3104

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

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

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

    
3198
	return false;
3199
}
3200

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

    
3223
	/* Make hexstrings */
3224
	if ($duidtype) {
3225
		switch ($duidtype) {
3226
		/* Add a hardware type to DUID-LLT and DUID-LL; assume Ethernet */
3227
		case 1:
3228
		case 3:
3229
			$duidpt1 = '00:01:' . $duidpt1;
3230
			break;
3231
		/* Remove '-' from given UUID and insert ':' every 2 characters */
3232
		case 4:
3233
			$duidpt1 = implode(':', str_split(str_replace('-', '', $duidpt1), 2));
3234
			break;
3235
		default:
3236
		}
3237
		$duidpt1 = '00:0' . $duidtype . ':' . $duidpt1;
3238
	}
3239

    
3240
	$values = explode(':', strtolower(str_replace('-', ':', $duidpt1)));
3241

    
3242
	if (hexdec($values[0]) != count($values) - 2)
3243
		array_unshift($values, dechex(count($values)), '00');
3244

    
3245
	array_walk($values, function(&$value) {
3246
		$value = str_pad($value, 2, '0', STR_PAD_LEFT);
3247
	});
3248

    
3249
	return implode(":", $values);
3250
}
3251

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

    
3282
	for ($i = 0; $i < count($values); $i++) {
3283
		if (ctype_xdigit($values[$i]) == false)
3284
			return false;
3285
		if (hexdec($values[$i]) < 0 || hexdec($values[$i]) > 255)
3286
			return false;
3287
	}
3288

    
3289
	return true;
3290
}
3291

    
3292
/* Write the DHCP6 DUID file */
3293
function write_dhcp6_duid($duidstring) {
3294
	// Create the hex array from the dhcp6duid config entry and write to file
3295
	global $g;
3296

    
3297
	if(!is_duid($duidstring)) {
3298
		log_error(gettext("Error: attempting to write DUID file - Invalid DUID detected"));
3299
		return false;
3300
	}
3301
	$temp = str_replace(":","",$duidstring);
3302
	$duid_binstring = pack("H*",$temp);
3303
	if ($fd = fopen("{$g['vardb_path']}/dhcp6c_duid", "wb")) {
3304
		fwrite($fd, $duid_binstring);
3305
		fclose($fd);
3306
		return true;
3307
	}
3308
	log_error(gettext("Error: attempting to write DUID file - File write error"));
3309
	return false;
3310
}
3311

    
3312
/* returns duid string from 'vardb_path']}/dhcp6c_duid' */
3313
function get_duid_from_file() {
3314
	global $g;
3315

    
3316
	$duid_ASCII = "";
3317
	$count = 0;
3318

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

    
3341
/* Replaces the Mac OS 9 and earlier (\r) and DOS/Windows (\r\n) newlines with the Unix equivalent (\n). */
3342
function unixnewlines($text) {
3343
	return preg_replace('/\r\n?/', "\n", $text);
3344
}
3345

    
3346
function array_remove_duplicate($array, $field) {
3347
	$cmp = array();
3348
	foreach ($array as $sub) {
3349
		if (isset($sub[$field])) {
3350
			$cmp[] = $sub[$field];
3351
		}
3352
	}
3353
	$unique = array_unique(array_reverse($cmp, true));
3354
	foreach ($unique as $k => $rien) {
3355
		$new[] = $array[$k];
3356
	}
3357
	return $new;
3358
}
3359

    
3360
function dhcpd_date_adjust_gmt($dt) {
3361
	global $config;
3362

    
3363
	init_config_arr(array('dhcpd'));
3364

    
3365
	foreach ($config['dhcpd'] as $dhcpditem) {
3366
		if ($dhcpditem['dhcpleaseinlocaltime'] == "yes") {
3367
			$ts = strtotime($dt . " GMT");
3368
			if ($ts !== false) {
3369
				return strftime("%Y/%m/%d %H:%M:%S", $ts);
3370
			}
3371
		}
3372
	}
3373

    
3374
	/*
3375
	 * If we did not need to convert to local time or the conversion
3376
	 * failed, just return the input.
3377
	 */
3378
	return $dt;
3379
}
3380

    
3381
global $supported_image_types;
3382
$supported_image_types = array(
3383
	IMAGETYPE_JPEG,
3384
	IMAGETYPE_PNG,
3385
	IMAGETYPE_GIF,
3386
	IMAGETYPE_WEBP
3387
);
3388

    
3389
function is_supported_image($image_filename) {
3390
	global $supported_image_types;
3391
	$img_info = getimagesize($image_filename);
3392

    
3393
	/* If it's not an image, or it isn't in the supported list, return false */
3394
	if (($img_info === false) ||
3395
	    !in_array($img_info[2], array_keys($supported_image_types))) {
3396
		return false;
3397
	} else {
3398
		return $img_info[2];
3399
	}
3400
}
3401

    
3402
function get_lagg_ports ($laggport) {
3403
	$laggp = array();
3404
	foreach ($laggport as $lgp) {
3405
		list($lpname, $lpinfo) = explode(" ", $lgp);
3406
		preg_match('~<(.+)>~', $lpinfo, $lgportmode);
3407
		if ($lgportmode[1]) {
3408
			$laggp[] = $lpname . " (" . $lgportmode[1] . ")";
3409
		} else {
3410
			$laggp[] = $lpname;
3411
		}
3412
	}
3413
	if ($laggp) {
3414
		return implode(", ", $laggp);
3415
	} else {
3416
		return false;
3417
	}
3418
}
3419

    
3420
function cisco_to_cidr($addr) {
3421
	if (!is_ipaddr($addr)) {
3422
		throw new Exception('Invalid IP Addr');
3423
	}
3424

    
3425
	$mask = decbin(~ip2long($addr));
3426
	$mask = substr($mask, -32);
3427
	$k = 0;
3428
	for ($i = 0; $i <= 32; $i++) {
3429
		$k += intval($mask[$i]);
3430
	}
3431
	return $k;
3432
}
3433

    
3434
function cisco_extract_index($prule) {
3435
	$index = explode("#", $prule);
3436
	if (is_numeric($index[1])) {
3437
		return intval($index[1]);
3438
	} else {
3439
		syslog(LOG_WARNING, "Error parsing rule {$prule}: Could not extract index");
3440
	}
3441
	return -1;;
3442
}
3443

    
3444
function parse_cisco_acl_rule($rule, $devname, $dir, $proto) {
3445
	$rule_orig = $rule;
3446
	$rule = explode(" ", $rule);
3447
	$tmprule = "";
3448
	$index = 0;
3449

    
3450
	if ($rule[$index] == "permit") {
3451
		$startrule = "pass {$dir} quick on {$devname} ";
3452
	} else if ($rule[$index] == "deny") {
3453
		$startrule = "block {$dir} quick on {$devname} ";
3454
	} else {
3455
		return;
3456
	}
3457

    
3458
	$index++;
3459

    
3460
	switch ($rule[$index]) {
3461
		case "ip":
3462
			break;
3463
		case "icmp":
3464
			$icmp = ($proto == "inet") ? "icmp" : "ipv6-icmp";
3465
			$tmprule .= "proto {$icmp} ";
3466
			break;
3467
		case "tcp":
3468
		case "udp":
3469
			$tmprule .= "proto {$rule[$index]} ";
3470
			break;
3471
		default:
3472
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid protocol.");
3473
			return;
3474
	}
3475
	$index++;
3476

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

    
3502
		if (is_ipaddrv4($network) && ($proto == "inet")) {
3503
			try {
3504
				$netmask = cisco_to_cidr($netmask);
3505
			} catch(Exception $e) {
3506
				syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source netmask '$netmask'.");
3507
				return;
3508
			}
3509
			$tmprule .= "from {$network}/{$netmask} ";
3510
		} else {
3511
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source network '$network'.");
3512
			return;
3513
		}
3514

    
3515
		$index++;
3516
	}
3517

    
3518
	/* Source Operator */
3519
	if (in_array(trim($rule[$index]), array("lt", "gt", "eq", "neq"))) {
3520
		switch(trim($rule[$index])) {
3521
			case "lt":
3522
				$operator = "<";
3523
				break;
3524
			case "gt":
3525
				$operator = ">";
3526
				break;
3527
			case "eq":
3528
				$operator = "=";
3529
				break;
3530
			case "neq":
3531
				$operator = "!=";
3532
				break;
3533
		}
3534

    
3535
		$port = $rule[++$index];
3536
		if (is_port($port)) {
3537
			$tmprule .= "port {$operator} {$port} ";
3538
		} else {
3539
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source port: '$port' not a numeric value between 0 and 65535.");
3540
			return;
3541
		}
3542
		$index++;
3543
	} else if (trim($rule[$index]) == "range") {
3544
		$port = array($rule[++$index], $rule[++$index]);
3545
		if (is_port($port[0]) && is_port($port[1])) {
3546
			$tmprule .= "port {$port[0]}:{$port[1]} ";
3547
		} else {
3548
			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.");
3549
			return;
3550
		}
3551
		$index++;
3552
	}
3553

    
3554
	/* Destination */
3555
	if (trim($rule[$index]) == "host") {
3556
		$index++;
3557
		if ((($proto == 'inet') && (is_ipaddrv4(trim($rule[$index])) || (trim($rule[$index]) == "{clientip}"))) ||
3558
		    (($proto == 'inet6') && (is_ipaddrv6(trim($rule[$index])) || (trim($rule[$index]) == "{clientipv6}")))) {
3559
			$tmprule .= "to {$rule[$index]} ";
3560
			$index++;
3561
		} else {
3562
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination host '{$rule[$index]}'.");
3563
			return;
3564
		}
3565
	} elseif (is_subnetv6(trim($rule[$index])) && ($proto == "inet6")) {
3566
		$tmprule .= "to {$rule[$index]} ";
3567
		$index++;
3568
	} elseif (trim($rule[$index]) == "any") {
3569
		$tmprule .= "to any ";
3570
		$index++;
3571
	} else {
3572
		$network = $rule[$index];
3573
		$netmask = $rule[++$index];
3574

    
3575
		if (is_ipaddrv4($network) && ($proto == "inet")) {
3576
			try {
3577
				$netmask = cisco_to_cidr($netmask);
3578
			} catch(Exception $e) {
3579
				syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination network '$network'.");
3580
				return;
3581
			}
3582
			$tmprule .= "to {$network}/{$netmask} ";
3583
		} else {
3584
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination network '$network'.");
3585
			return;
3586
		}
3587

    
3588
		$index++;
3589
	}
3590

    
3591
	/* Destination Operator */
3592
	if (in_array(trim($rule[$index]), array("lt", "gt", "eq", "neq"))) {
3593
		switch(trim($rule[$index])) {
3594
			case "lt":
3595
				$operator = "<";
3596
				break;
3597
			case "gt":
3598
				$operator = ">";
3599
				break;
3600
			case "eq":
3601
				$operator = "=";
3602
				break;
3603
			case "neq":
3604
				$operator = "!=";
3605
				break;
3606
		}
3607

    
3608
		$port = $rule[++$index];
3609
		if (is_port($port)) {
3610
			$tmprule .= "port {$operator} {$port} ";
3611
		} else {
3612
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination port: '$port' not a numeric value between 0 and 65535.");
3613
			return;
3614
		}
3615
		$index++;
3616
	} else if (trim($rule[$index]) == "range") {
3617
		$port = array($rule[++$index], $rule[++$index]);
3618
		if (is_port($port[0]) && is_port($port[1])) {
3619
			$tmprule .= "port {$port[0]}:{$port[1]} ";
3620
		} else {
3621
			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.");
3622
			return;
3623
		}
3624
		$index++;
3625
	}
3626

    
3627
	$tmprule = $startrule . $proto . " " . $tmprule;
3628
	return $tmprule;
3629
}
3630

    
3631
function parse_cisco_acl($attribs, $dev) {
3632
	global $attributes;
3633

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

    
3663
			if (strstr($rule[0], "ipv6")) {
3664
				$proto = "inet6";
3665
			} else {
3666
				$proto = "inet";
3667
			}
3668

    
3669
			$tmprule = parse_cisco_acl_rule($rule[1], $dev, $dir, $proto);
3670

    
3671
			if ($dir == "in") {
3672
				$inrules[$proto][$rindex] = $tmprule;
3673
			} else if ($dir == "out") {
3674
				$outrules[$proto][$rindex] = $tmprule;
3675
			}
3676
		}
3677

    
3678

    
3679
		$state = "";
3680
		foreach (array('inet', 'inet6') as $ip) {
3681
			if (!empty($outrules[$ip])) {
3682
				$state = "no state";
3683
			}
3684
			ksort($inrules[$ip], SORT_NUMERIC);
3685
			foreach ($inrules[$ip] as $inrule) {
3686
				$finalrules .= "{$inrule} {$state}\n";
3687
			}
3688
			if (!empty($outrules[$ip])) {
3689
				ksort($outrules[$ip], SORT_NUMERIC);
3690
				foreach ($outrules[$ip] as $outrule) {
3691
					$finalrules .= "{$outrule} {$state}\n";
3692
				}
3693
			}
3694
		}
3695
	}
3696
	return $finalrules;
3697
}
3698

    
3699
function alias_idn_to_utf8($alias) {
3700
	if (is_alias($alias)) {
3701
		return $alias;
3702
	} else {
3703
		return idn_to_utf8($alias);
3704
	}
3705
}
3706

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

    
3715
// These funtions were in guiconfig.inc but have been moved here so non GUI processes can use them
3716
function address_to_pconfig($adr, &$padr, &$pmask, &$pnot, &$pbeginport, &$pendport) {
3717
	if (isset($adr['any'])) {
3718
		$padr = "any";
3719
	} else if ($adr['network']) {
3720
		$padr = $adr['network'];
3721
	} else if ($adr['address']) {
3722
		list($padr, $pmask) = explode("/", $adr['address']);
3723
		if (!$pmask) {
3724
			if (is_ipaddrv6($padr)) {
3725
				$pmask = 128;
3726
			} else {
3727
				$pmask = 32;
3728
			}
3729
		}
3730
	}
3731

    
3732
	if (isset($adr['not'])) {
3733
		$pnot = 1;
3734
	} else {
3735
		$pnot = 0;
3736
	}
3737

    
3738
	if ($adr['port']) {
3739
		list($pbeginport, $pendport) = explode("-", $adr['port']);
3740
		if (!$pendport) {
3741
			$pendport = $pbeginport;
3742
		}
3743
	} else if (!is_alias($pbeginport) && !is_alias($pendport)) {
3744
		$pbeginport = "any";
3745
		$pendport = "any";
3746
	}
3747
}
3748

    
3749
function pconfig_to_address(&$adr, $padr, $pmask, $pnot = false, $pbeginport = 0, $pendport = 0) {
3750
	$adr = array();
3751

    
3752
	if ($padr == "any") {
3753
		$adr['any'] = true;
3754
	} else if (is_specialnet($padr)) {
3755
		$adr['network'] = $padr;
3756
	} else {
3757
		$adr['address'] = $padr;
3758
		if (is_ipaddrv6($padr)) {
3759
			if ($pmask != 128) {
3760
				$adr['address'] .= "/" . $pmask;
3761
			}
3762
		} else {
3763
			if ($pmask != 32) {
3764
				$adr['address'] .= "/" . $pmask;
3765
			}
3766
		}
3767
	}
3768

    
3769
	if ($pnot) {
3770
		$adr['not'] = true;
3771
	} else {
3772
		unset($adr['not']);
3773
	}
3774

    
3775
	if (($pbeginport != 0) && ($pbeginport != "any")) {
3776
		if ($pbeginport != $pendport) {
3777
			$adr['port'] = $pbeginport . "-" . $pendport;
3778
		} else {
3779
			$adr['port'] = $pbeginport;
3780
		}
3781
	}
3782

    
3783
	/*
3784
	 * If the port is still unset, then it must not be numeric, but could
3785
	 * be an alias or a well-known/registered service.
3786
	 * See https://redmine.pfsense.org/issues/8410
3787
	 */
3788
	if (!isset($adr['port']) && is_port_or_alias($pbeginport)) {
3789
		$adr['port'] = $pbeginport;
3790
	}
3791
}
3792

    
3793
function is_specialnet($net) {
3794
	global $specialsrcdst;
3795

    
3796
	if (!$net) {
3797
		return false;
3798
	}
3799
	if (in_array($net, $specialsrcdst)) {
3800
		return true;
3801
	} else {
3802
		return false;
3803
	}
3804
}
3805

    
3806
function is_interface_ipaddr($interface) {
3807
	global $config;
3808

    
3809
	if (is_array($config['interfaces'][$interface]) &&
3810
	    isset($config['interfaces'][$interface]['ipaddr']) &&
3811
	    !empty($config['interfaces'][$interface]['ipaddr'])) {
3812
		return true;
3813
	}
3814
	return false;
3815
}
3816

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

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

    
3828
function escape_filter_regex($filtertext) {
3829
	/* If the caller (user) has not already put a backslash before a slash, to escape it in the regex, */
3830
	/* then this will do it. Take out any "\/" already there, then turn all ordinary "/" into "\/".    */
3831
	return str_replace('/', '\/', str_replace('\/', '/', $filtertext));
3832
}
3833

    
3834
/*
3835
 * Check if a given pattern has the same number of two different unescaped
3836
 * characters.
3837
 * For example, it can ensure a pattern has balanced sets of parentheses,
3838
 * braces, and brackets.
3839
 */
3840
function is_pattern_balanced_char($pattern, $open, $close) {
3841
	/* First remove escaped versions */
3842
	$pattern = str_replace('\\' . $open, '', $pattern);
3843
	$pattern = str_replace('\\' . $close, '', $pattern);
3844
	/* Check if the counts of both characters match in the target pattern */
3845
	return (substr_count($pattern, $open) == substr_count($pattern, $close));
3846
}
3847

    
3848
/*
3849
 * Check if a pattern contains balanced sets of parentheses, braces, and
3850
 * brackets.
3851
 */
3852
function is_pattern_balanced($pattern) {
3853
	if (is_pattern_balanced_char($pattern, '(', ')') &&
3854
	    is_pattern_balanced_char($pattern, '{', '}') &&
3855
	    is_pattern_balanced_char($pattern, '[', ']')) {
3856
		/* Balanced if all are true */
3857
		return true;
3858
	}
3859
	return false;
3860
}
3861

    
3862
function cleanup_regex_pattern($filtertext) {
3863
	/* Cleanup filter to prevent backreferences. */
3864
	$filtertext = escape_filter_regex($filtertext);
3865

    
3866
	/* Remove \<digit>+ backreferences
3867
	 * To match \ it must be escaped as \\\\ in PHP for preg_replace() */
3868
	$filtertext = preg_replace('/\\\\\\d+/', '', $filtertext);
3869

    
3870
	/* Check for unbalanced parentheses, braces, and brackets which
3871
	 * may be an error or attempt to circumvent protections.
3872
	 * Also discard any pattern that attempts problematic duplication
3873
	 * methods. */
3874
	if (!is_pattern_balanced($filtertext) ||
3875
	    (substr_count($filtertext, ')*') > 0) ||
3876
	    (substr_count($filtertext, ')+') > 0) ||
3877
	    (substr_count($filtertext, '{') > 0)) {
3878
		return '';
3879
	}
3880

    
3881
	return $filtertext;
3882
}
3883

    
3884
function ip6_to_asn1($addr) {
3885
	/* IPv6 MIB uses an OCTET STRING of length 16 to represent
3886
	 * 128-bit IPv6 address in network byte order.
3887
	 * see https://datatracker.ietf.org/doc/html/rfc2465#section-3 
3888
	 * i.e. fc00:3::4 = 252.0.0.3.0.0.0.0.0.0.0.0.0.0.0.4
3889
	 */
3890

    
3891
	if (!is_ipaddrv6($addr)) {
3892
		return false;
3893
	}
3894

    
3895
	foreach (explode(':', Net_IPv6::uncompress($addr)) as $v) {
3896
		$ipv6str .= str_pad($v, 4, '0', STR_PAD_LEFT);
3897
	}
3898
	foreach (str_split($ipv6str, 2) as $v) {
3899
		$octstr .= base_convert($v, 16, 10) . '.';
3900
	}
3901

    
3902
	return $octstr;
3903
}
3904

    
3905
function interfaces_interrupts() {
3906
	exec("/usr/bin/vmstat -i --libxo json", $rawdata, $rc);
3907
	$interrupts = array();
3908
	if ($rc == 0) {
3909
		$vnstatarr = json_decode(implode(" ", $rawdata), JSON_OBJECT_AS_ARRAY);
3910
		$interruptarr = $vnstatarr['interrupt-statistics']['interrupt'];
3911

    
3912
		foreach ($interruptarr as $int){
3913
			preg_match("/irq\d+: ([a-z0-9]+)/", $int['name'], $matches);
3914
			$name = $matches[1];
3915
			if (array_key_exists($name, $interrupts)) {
3916
				/* interface with multiple queues */
3917
				$interrupts[$name]['total'] += $int['total'];
3918
				$interrupts[$name]['rate'] += $int['rate'];
3919
			} else {
3920
				$interrupts[$name]['total'] = $int['total'];
3921
				$interrupts[$name]['rate'] = $int['rate'];
3922
			}
3923
		}
3924
	}
3925

    
3926
	return $interrupts;
3927
}
3928

    
3929
?>
(53-53/61)