Project

General

Profile

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

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

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

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

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

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

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

    
73
	return 0;
74
}
75

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

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

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

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

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

    
101
	return false;
102
}
103

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

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

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

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

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

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

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

    
164
		return $fp;
165
	}
166

    
167
	return NULL;
168
}
169

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
371
	foreach ( $parts as $v ) {
372

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

    
376
	}
377

    
378
	return $binstr;
379
}
380

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

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

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

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

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

    
402
	return $ip;
403
}
404

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

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

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

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

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

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

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

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

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

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

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

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

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

    
516
	return $rangeaddresses;
517
}
518

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
639
	return $out;
640
}
641

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
825
}
826

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1013
	return $result;
1014
}
1015

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

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

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

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

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

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

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

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

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

    
1099
	return true;
1100
}
1101

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

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

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

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

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

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

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

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

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

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

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

    
1185
	return true;
1186
}
1187

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1325
	return false;
1326
}
1327

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

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

    
1339
	return false;
1340
}
1341

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

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

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

    
1360
	return($list);
1361
}
1362

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1471
	return (NULL);
1472
}
1473

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

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

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

    
1495
	$iflist = array();
1496

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

    
1504
	return $iflist;
1505
}
1506

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

    
1511
	$iflist = array();
1512

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

    
1523
	return $iflist;
1524
}
1525

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

    
1530
	$iflist = array();
1531

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

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

    
1547
	return $iflist;
1548
}
1549

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

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

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

    
1588
	return $ip_array;
1589
}
1590

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

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

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

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

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

    
1764
	return ($plist);
1765
}
1766

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

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

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

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

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

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

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

    
1856
	return $retval;
1857
}
1858

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

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

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

    
1892
	$aliastable = array();
1893

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

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

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

    
1909
function alias_get_type($name) {
1910
	global $config;
1911

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

    
1920
	return "";
1921
}
1922

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

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

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

    
1965
	if (!is_array($config['aliases']['alias'])) {
1966
		return null;
1967
	}
1968

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

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

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

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

    
2024
function mac_format($clientmac) {
2025
	global $config, $cpzone;
2026

    
2027
	$mac = explode(":", $clientmac);
2028
	$mac_format = $cpzone ? $config['captiveportal'][$cpzone]['radmac_format'] : false;
2029

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

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

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

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

    
2043
		default:
2044
			return $clientmac;
2045
	}
2046
}
2047

    
2048
function resolve_retry($hostname, $protocol = 'inet') {
2049
	$retries = 10;
2050
	$numrecords = 1;
2051
	$recresult = array();
2052
	$returnres = array();
2053

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

    
2073
	for ($i = 0; $i < $retries; $i++) {
2074
		if ($checkproto($hostname)) {
2075
			return $hostname;
2076
		}
2077

    
2078
		$dnsresult = @dns_get_record($hostname, $dnsproto);
2079

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

    
2088
						if ($checkproto($ip['ipv6'])) { 
2089
							$recresult[] = $ip['ipv6'];
2090
						}
2091
					}
2092
				}
2093
			}
2094
		}
2095

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

    
2105
		usleep(100000);
2106
	}
2107

    
2108
	return false;
2109
}
2110

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

    
2125
function format_number($num, $precision = 3) {
2126
	$units = array('', 'K', 'M', 'G', 'T');
2127

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

    
2135
	return ("$num {$units[$i]}");
2136
}
2137

    
2138

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

    
2155
function update_filter_reload_status($text, $new=false) {
2156
	global $g;
2157

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

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

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

    
2193
function run_plugins($directory) {
2194
	global $config, $g;
2195

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

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

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

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

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

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

    
2251
	return $values;
2252
}
2253

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

    
2264
	$value = get_sysctl($name);
2265
	if (empty($value) || !isset($value[$name])) {
2266
		return "";
2267
	}
2268

    
2269
	return $value[$name];
2270
}
2271

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

    
2282
	$value_list = array();
2283
	foreach ($values as $key => $value) {
2284
		$value_list[] = escapeshellarg($key) . "=" . escapeshellarg($value);
2285
	}
2286

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

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

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

    
2304
	return $ret;
2305
}
2306

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

    
2317
	$result = set_sysctl(array($name => $value));
2318

    
2319
	if (!isset($result[$name]) || $result[$name] != $value) {
2320
		return false;
2321
	}
2322

    
2323
	return true;
2324
}
2325

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

    
2340
function mute_kernel_msgs() {
2341
	global $g, $config;
2342

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

    
2349
function unmute_kernel_msgs() {
2350
	global $g;
2351

    
2352
	exec("/sbin/conscontrol mute off");
2353
}
2354

    
2355
function start_devd() {
2356
	global $g;
2357

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

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

    
2388
function is_interface_vlan_mismatch() {
2389
	global $config, $g;
2390

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

    
2402
	return false;
2403
}
2404

    
2405
function is_interface_mismatch() {
2406
	global $config, $g;
2407

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

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

    
2447
	if (file_exists("{$g['tmp_path']}/assign_complete")) {
2448
		$do_assign = false;
2449
	}
2450

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

    
2457
	return $do_assign;
2458
}
2459

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

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

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

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

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

    
2565
function is_file_included($file = "") {
2566
	$files = get_included_files();
2567
	if (in_array($file, $files)) {
2568
		return true;
2569
	}
2570

    
2571
	return false;
2572
}
2573

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

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

    
2590
	return $data;
2591
}
2592

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

    
2599
	$arrays = func_get_args();
2600
	$remains = $arrays;
2601

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

    
2606
	// loop available array
2607
	foreach ($arrays as $array) {
2608

    
2609
		// The first remaining array is $array. We are processing it. So
2610
		// we remove it from remaining arrays.
2611
		array_shift($remains);
2612

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

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

    
2647

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

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

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

    
2683
	if ($rc != 0) {
2684
		return array();
2685
	}
2686

    
2687
	$netstatarr = json_decode(implode(" ", $rawdata), JSON_OBJECT_AS_ARRAY);
2688
	$netstatarr = $netstatarr['statistics']['route-information']
2689
	    ['route-table']['rt-family'];
2690

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

    
2703
	return $result;
2704
}
2705

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

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

    
2720
	return false;
2721
}
2722

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

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

    
2735
	if (empty($family)) {
2736
		return array();
2737
	}
2738

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

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

    
2781
	return $result;
2782
}
2783

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

    
2791
	$route = route_get('default', $ipprotocol, true);
2792

    
2793
	if (empty($route)) {
2794
		return '';
2795
	}
2796

    
2797
	if (!isset($route[0]['gateway'])) {
2798
		return '';
2799
	}
2800

    
2801
	return $route[0]['gateway'];
2802
}
2803

    
2804
/* Delete a static route */
2805
function route_del($target, $ipprotocol = '') {
2806
	global $config;
2807

    
2808
	if (empty($target)) {
2809
		return;
2810
	}
2811

    
2812
	if (!empty($ipprotocol) && $ipprotocol != 'inet' &&
2813
	    $ipprotocol != 'inet6') {
2814
		return false;
2815
	}
2816

    
2817
	$route = route_get($target, $ipprotocol, true);
2818

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

    
2823
	$target_prefix = '';
2824
	if (is_subnet($target)) {
2825
		$target_prefix = '-net';
2826
	} else if (is_ipaddr($target)) {
2827
		$target_prefix = '-host';
2828
	}
2829

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

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

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

    
2849
		$_gb = exec(escapeshellcmd("/sbin/route del {$target_prefix} " .
2850
		    "{$target} {$gw}"), $output, $rc);
2851

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

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

    
2876
	if (empty($target) || (empty($gw) && empty($iface))) {
2877
		return false;
2878
	}
2879

    
2880
	if ($target == 'default' && empty($ipprotocol)) {
2881
		return false;
2882
	}
2883

    
2884
	if (!empty($ipprotocol) && $ipprotocol != 'inet' &&
2885
	    $ipprotocol != 'inet6') {
2886
		return false;
2887
	}
2888

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

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

    
2904
	/* If there is another route to the same target, remove it */
2905
	route_del($target, $ipprotocol);
2906

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

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

    
2928
	$_gb = exec(escapeshellcmd("/sbin/route add {$target_prefix} " .
2929
	    "{$target} {$args} {$params}"), $output, $rc);
2930

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

    
2939
	return ($rc == 0);
2940
}
2941

    
2942
function set_ipv6routes_mtu($interface, $mtu) {
2943
	global $config, $g;
2944

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

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

    
3001
function get_staticroutes($returnsubnetsonly = false, $returnhostnames = false, $returnenabledroutesonly = false) {
3002
	global $config, $aliastable;
3003

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

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

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

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

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

    
3080
/* Define what is preferred, IPv4 or IPv6 */
3081
function prefer_ipv4_or_ipv6() {
3082
	global $config;
3083

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

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

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

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

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

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

    
3203
	return false;
3204
}
3205

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

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

    
3245
	$values = explode(':', strtolower(str_replace('-', ':', $duidpt1)));
3246

    
3247
	if (hexdec($values[0]) != count($values) - 2)
3248
		array_unshift($values, dechex(count($values)), '00');
3249

    
3250
	array_walk($values, function(&$value) {
3251
		$value = str_pad($value, 2, '0', STR_PAD_LEFT);
3252
	});
3253

    
3254
	return implode(":", $values);
3255
}
3256

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

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

    
3294
	return true;
3295
}
3296

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

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

    
3317
/* returns duid string from 'vardb_path']}/dhcp6c_duid' */
3318
function get_duid_from_file() {
3319
	global $g;
3320

    
3321
	$duid_ASCII = "";
3322
	$count = 0;
3323

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

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

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

    
3365
function dhcpd_date_adjust_gmt($dt) {
3366
	global $config;
3367

    
3368
	init_config_arr(array('dhcpd'));
3369

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

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

    
3386
global $supported_image_types;
3387
$supported_image_types = array(
3388
	IMAGETYPE_JPEG,
3389
	IMAGETYPE_PNG,
3390
	IMAGETYPE_GIF,
3391
	IMAGETYPE_WEBP
3392
);
3393

    
3394
function is_supported_image($image_filename) {
3395
	global $supported_image_types;
3396
	$img_info = getimagesize($image_filename);
3397

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

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

    
3425
function cisco_to_cidr($addr) {
3426
	if (!is_ipaddr($addr)) {
3427
		throw new Exception('Invalid IP Addr');
3428
	}
3429

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

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

    
3449
function parse_cisco_acl_rule($rule, $devname, $dir, $proto) {
3450
	$rule_orig = $rule;
3451
	$rule = explode(" ", $rule);
3452
	$tmprule = "";
3453
	$index = 0;
3454

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

    
3463
	$index++;
3464

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

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

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

    
3520
		$index++;
3521
	}
3522

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

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

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

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

    
3593
		$index++;
3594
	}
3595

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

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

    
3632
	$tmprule = $startrule . $proto . " " . $tmprule;
3633
	return $tmprule;
3634
}
3635

    
3636
function parse_cisco_acl($attribs, $dev) {
3637
	global $attributes;
3638

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

    
3668
			if (strstr($rule[0], "ipv6")) {
3669
				$proto = "inet6";
3670
			} else {
3671
				$proto = "inet";
3672
			}
3673

    
3674
			$tmprule = parse_cisco_acl_rule($rule[1], $dev, $dir, $proto);
3675

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

    
3683

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

    
3704
function alias_idn_to_utf8($alias) {
3705
	if (is_alias($alias)) {
3706
		return $alias;
3707
	} else {
3708
		return idn_to_utf8($alias);
3709
	}
3710
}
3711

    
3712
function alias_idn_to_ascii($alias) {
3713
	if (is_alias($alias)) {
3714
		return $alias;
3715
	} else {
3716
		return idn_to_ascii($alias);
3717
	}
3718
}
3719

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

    
3737
	if (isset($adr['not'])) {
3738
		$pnot = 1;
3739
	} else {
3740
		$pnot = 0;
3741
	}
3742

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

    
3754
function pconfig_to_address(&$adr, $padr, $pmask, $pnot = false, $pbeginport = 0, $pendport = 0, $addmask = false) {
3755
	$adr = array();
3756

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

    
3777
	if ($pnot) {
3778
		$adr['not'] = true;
3779
	} else {
3780
		unset($adr['not']);
3781
	}
3782

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

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

    
3801
function is_specialnet($net) {
3802
	global $specialsrcdst;
3803

    
3804
	if (!$net) {
3805
		return false;
3806
	}
3807
	if (in_array($net, $specialsrcdst)) {
3808
		return true;
3809
	} else {
3810
		return false;
3811
	}
3812
}
3813

    
3814
function is_interface_ipaddr($interface) {
3815
	global $config;
3816

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

    
3825
function is_interface_ipaddrv6($interface) {
3826
	global $config;
3827

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

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

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

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

    
3870
function cleanup_regex_pattern($filtertext) {
3871
	/* Cleanup filter to prevent backreferences. */
3872
	$filtertext = escape_filter_regex($filtertext);
3873

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

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

    
3889
	return $filtertext;
3890
}
3891

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

    
3899
	if (!is_ipaddrv6($addr)) {
3900
		return false;
3901
	}
3902

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

    
3910
	return $octstr;
3911
}
3912

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

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

    
3934
	return $interrupts;
3935
}
3936

    
3937
function dummynet_load_module($max_qlimit) {
3938
	global $config;
3939

    
3940
	if (!is_module_loaded("dummynet.ko")) {
3941
		mute_kernel_msgs();
3942
		mwexec("/sbin/kldload dummynet");
3943
		unmute_kernel_msgs();
3944
	}
3945
	$sysctls = (array(
3946
			"net.inet.ip.dummynet.io_fast" => "1",
3947
			"net.inet.ip.dummynet.hash_size" => "256",
3948
			"net.inet.ip.dummynet.pipe_slot_limit" => $max_qlimit
3949
	));
3950
	init_config_arr(array('sysctl', 'item'));
3951
	if (!empty($config['sysctl']['item'])) {
3952
		foreach ($config['sysctl']['item'] as $item) {
3953
			if (preg_match('/net\.inet\.ip\.dummynet\./', $item['tunable'])) {
3954
				$sysctls[$item['tunable']] = $item['value'];
3955
			}
3956
		}
3957
	}
3958
	set_sysctl($sysctls);
3959
}
3960

    
3961
function get_interface_vip_ips($interface) {
3962
	global $config;
3963
	$vipips = '';
3964

    
3965
	init_config_arr(array('virtualip', 'vip'));
3966
	foreach ($config['virtualip']['vip'] as $vip) {
3967
		if (($vip['interface'] == $interface) &&
3968
		    (($vip['mode'] == "carp") || ($vip['mode'] == "ipalias"))) {
3969
			$vipips .= $vip['subnet'] . ' ';
3970
		}
3971
	}
3972
	return $vipips;
3973
}
3974

    
3975
?>
(53-53/61)