Project

General

Profile

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

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

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

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

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

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

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

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

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

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

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

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

    
517
	return $rangeaddresses;
518
}
519

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
640
	return $out;
641
}
642

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
826
}
827

    
828
/* returns true if $subnet is a valid IPv4 or IPv6 subnet in CIDR format
829
	false - if not a valid subnet
830
	true (numeric 4 or 6) - if valid, gives type of subnet */
831
function is_subnet($subnet) {
832
	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)) {
833
		if (is_ipaddrv4($parts[1]) && $parts[3] <= 32) {
834
			return 4;
835
		}
836
		if (is_ipaddrv6($parts[2]) && $parts[3] <= 128) {
837
			return 6;
838
		}
839
	}
840
	return false;
841
}
842

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

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

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

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

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

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

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

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

    
900
	// 2**N returns an exact result as an INT if possible, and a float/double if not.
901
	// 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
902
	$result = 2 ** $snsize;
903

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1014
	return $result;
1015
}
1016

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

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

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

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

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

    
1060
/* returns true if $domain is a valid domain name */
1061
function is_domain($domain, $allow_wildcard=false, $trailing_dot=true) {
1062
	if (!is_string($domain)) {
1063
		return false;
1064
	}
1065
	if (!$trailing_dot && ($domain[strlen($domain)-1] == ".")) {
1066
		return false;
1067
	}
1068
	if ($allow_wildcard) {
1069
		$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';
1070
	} else {
1071
		$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';
1072
	}
1073

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

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

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

    
1100
	return true;
1101
}
1102

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

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

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

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

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

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

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

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

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

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

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

    
1186
	return true;
1187
}
1188

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1326
	return false;
1327
}
1328

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

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

    
1340
	return false;
1341
}
1342

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

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

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

    
1361
	return($list);
1362
}
1363

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

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

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

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

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

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

    
1400
	return (get_configured_vip_detail($vipinterface, 'all', 'vip'));
1401
}
1402

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

    
1405
	return (get_configured_vip_detail($vipinterface, 'all', 'iface'));
1406
}
1407

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

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

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

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

    
1418
function get_configured_vip_subnetv4($vipinterface = '') {
1419

    
1420
	return (get_configured_vip_detail($vipinterface, 'inet', 'subnet'));
1421
}
1422

    
1423
function get_configured_vip_subnetv6($vipinterface = '') {
1424

    
1425
	return (get_configured_vip_detail($vipinterface, 'inet6', 'subnet'));
1426
}
1427

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

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

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

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

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

    
1473
	return (NULL);
1474
}
1475

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

    
1490
	return strnatcmp($a, $b);
1491
}
1492

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

    
1497
	$iflist = array();
1498

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

    
1506
	return $iflist;
1507
}
1508

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

    
1513
	$iflist = array();
1514

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

    
1525
	return $iflist;
1526
}
1527

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

    
1532
	$iflist = array();
1533

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

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

    
1549
	return $iflist;
1550
}
1551

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

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

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

    
1590
	return $ip_array;
1591
}
1592

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

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

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

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

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

    
1766
	return ($plist);
1767
}
1768

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

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

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

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

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

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

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

    
1858
	return $retval;
1859
}
1860

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

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

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

    
1894
	$aliastable = array();
1895

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

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

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

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

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

    
1922
	return "";
1923
}
1924

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
2107
		usleep(100000);
2108
	}
2109

    
2110
	return false;
2111
}
2112

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

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

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

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

    
2140

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

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

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

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

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

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

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

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

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

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

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

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

    
2253
	return $values;
2254
}
2255

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

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

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

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

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

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

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

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

    
2306
	return $ret;
2307
}
2308

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

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

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

    
2325
	return true;
2326
}
2327

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

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

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

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

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

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

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

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

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

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

    
2404
	return false;
2405
}
2406

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

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

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

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

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

    
2459
	return $do_assign;
2460
}
2461

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

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

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

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

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

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

    
2573
	return false;
2574
}
2575

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

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

    
2592
	return $data;
2593
}
2594

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

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

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

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

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

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

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

    
2649

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

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

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

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

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

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

    
2705
	return $result;
2706
}
2707

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

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

    
2722
	return false;
2723
}
2724

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

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

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

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

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

    
2783
	return $result;
2784
}
2785

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
3205
	return false;
3206
}
3207

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

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

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

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

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

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

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

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

    
3296
	return true;
3297
}
3298

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

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

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

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

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

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

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

    
3367
/**
3368
 * Return a value specified by a path of keys in a nested array, if it exists
3369
 * @param $arr array value to search
3370
 * @param $path string path with '/' separators
3371
 * @param $default mixed value to return if the path is not found
3372
 * @returns mixed value at path or $default if the path does not exist
3373
 */
3374
function array_get_path(array &$arr, string $path, $default = null) {
3375
	$vpath = mb_split('/', $path);
3376
	$el = $arr;
3377
	foreach ($vpath as $key) {
3378
		if (is_array($el) && array_key_exists($key, $el)) {
3379
			$el = $el[$key];
3380
		} else {
3381
			return ($default);
3382
		}
3383
	}
3384
	return ($el);
3385
}
3386

    
3387
/**
3388
 * Set a value by path in the config. If the path cannot be reached because an
3389
 * intermediary does not exist, return null
3390
 * @param $arr array value to search
3391
 * @param $path string path with '/' separators
3392
 * @param $val mixed 
3393
 * @param $default mixed value to return if the path is not found
3394
 * @returns mixed $val or $default if the path prefix does not exist
3395
 */
3396
function array_set_path(array &$arr, string $path, $value, $default = null) {
3397
	$vpath = mb_split('/', $path);
3398
	$vkey = array_pop($vpath);
3399
	$el =& $arr;
3400
	foreach ($vpath as $key) {
3401
		if (is_array($el) && array_key_exists($key, $el)) {
3402
			$el =& $el[$key];
3403
		} else {
3404
			return ($default);
3405
		}
3406
	}
3407
	$el[$vkey] =& $value;
3408
	return ($value);
3409
}
3410

    
3411
/**
3412
 * Determine whether a path in a nested array has a non-null value keyed
3413
 * 'enabled'. Some parts of the config historically identify services as enabled
3414
 * by having a key to a non-null value named 'enable', and checking it with
3415
 * isset(). This can be counter-intuitive as isset() will return true if the
3416
 * array element is any non-null value that evaluates to false.
3417
 * @param $arr array value to search
3418
 * @param $path string path with '/' separators
3419
 * @param $enable_key string an optional alternative key value for the enable key
3420
 * @returns bool true if $enable_key exists in the array at $path, and has a
3421
 * non-null value, otherwise false
3422
 */
3423
function array_path_enabled(array &$arr, string $path, $enable_key = "enable") {
3424
	$el = config_get_path($path, []);
3425
	if (is_array($el) && isset($el[$enable_key])) {
3426
		return (true);
3427
	}
3428
	return (false);
3429
}
3430

    
3431
function dhcpd_date_adjust_gmt($dt) {
3432
	global $config;
3433

    
3434
	init_config_arr(array('dhcpd'));
3435

    
3436
	foreach ($config['dhcpd'] as $dhcpditem) {
3437
		if ($dhcpditem['dhcpleaseinlocaltime'] == "yes") {
3438
			$ts = strtotime($dt . " GMT");
3439
			if ($ts !== false) {
3440
				return strftime("%Y/%m/%d %H:%M:%S", $ts);
3441
			}
3442
		}
3443
	}
3444

    
3445
	/*
3446
	 * If we did not need to convert to local time or the conversion
3447
	 * failed, just return the input.
3448
	 */
3449
	return $dt;
3450
}
3451

    
3452
global $supported_image_types;
3453
$supported_image_types = array(
3454
	IMAGETYPE_JPEG,
3455
	IMAGETYPE_PNG,
3456
	IMAGETYPE_GIF,
3457
	IMAGETYPE_WEBP
3458
);
3459

    
3460
function is_supported_image($image_filename) {
3461
	global $supported_image_types;
3462
	$img_info = getimagesize($image_filename);
3463

    
3464
	/* If it's not an image, or it isn't in the supported list, return false */
3465
	if (($img_info === false) ||
3466
	    !in_array($img_info[2], array_keys($supported_image_types))) {
3467
		return false;
3468
	} else {
3469
		return $img_info[2];
3470
	}
3471
}
3472

    
3473
function get_lagg_ports ($laggport) {
3474
	$laggp = array();
3475
	foreach ($laggport as $lgp) {
3476
		list($lpname, $lpinfo) = explode(" ", $lgp);
3477
		preg_match('~<(.+)>~', $lpinfo, $lgportmode);
3478
		if ($lgportmode[1]) {
3479
			$laggp[] = $lpname . " (" . $lgportmode[1] . ")";
3480
		} else {
3481
			$laggp[] = $lpname;
3482
		}
3483
	}
3484
	if ($laggp) {
3485
		return implode(", ", $laggp);
3486
	} else {
3487
		return false;
3488
	}
3489
}
3490

    
3491
function cisco_to_cidr($addr) {
3492
	if (!is_ipaddr($addr)) {
3493
		throw new Exception('Invalid IP Addr');
3494
	}
3495

    
3496
	$mask = decbin(~ip2long($addr));
3497
	$mask = substr($mask, -32);
3498
	$k = 0;
3499
	for ($i = 0; $i <= 32; $i++) {
3500
		$k += intval($mask[$i]);
3501
	}
3502
	return $k;
3503
}
3504

    
3505
function cisco_extract_index($prule) {
3506
	$index = explode("#", $prule);
3507
	if (is_numeric($index[1])) {
3508
		return intval($index[1]);
3509
	} else {
3510
		syslog(LOG_WARNING, "Error parsing rule {$prule}: Could not extract index");
3511
	}
3512
	return -1;;
3513
}
3514

    
3515
function parse_cisco_acl_rule($rule, $devname, $dir, $proto) {
3516
	$rule_orig = $rule;
3517
	$rule = explode(" ", $rule);
3518
	$tmprule = "";
3519
	$index = 0;
3520

    
3521
	if ($rule[$index] == "permit") {
3522
		$startrule = "pass {$dir} quick on {$devname} ";
3523
	} else if ($rule[$index] == "deny") {
3524
		$startrule = "block {$dir} quick on {$devname} ";
3525
	} else {
3526
		return;
3527
	}
3528

    
3529
	$index++;
3530

    
3531
	switch ($rule[$index]) {
3532
		case "ip":
3533
			break;
3534
		case "icmp":
3535
			$icmp = ($proto == "inet") ? "icmp" : "ipv6-icmp";
3536
			$tmprule .= "proto {$icmp} ";
3537
			break;
3538
		case "tcp":
3539
		case "udp":
3540
			$tmprule .= "proto {$rule[$index]} ";
3541
			break;
3542
		default:
3543
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid protocol.");
3544
			return;
3545
	}
3546
	$index++;
3547

    
3548
	/* Source */
3549
	if (trim($rule[$index]) == "host") {
3550
		$index++;
3551
		if ((($proto == 'inet') && (is_ipaddrv4(trim($rule[$index])) || (trim($rule[$index]) == "{clientip}"))) ||
3552
		    (($proto == 'inet6') && (is_ipaddrv6(trim($rule[$index])) || (trim($rule[$index]) == "{clientipv6}")))) {
3553
			if ($GLOBALS['attributes']['framed_ip']) {
3554
				$tmprule .= "from {$GLOBALS['attributes']['framed_ip']} ";
3555
			} else {
3556
				$tmprule .= "from {$rule[$index]} ";
3557
			}
3558
			$index++;
3559
		} else {
3560
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source host '{$rule[$index]}'.");
3561
			return;
3562
		}
3563
	} elseif (is_subnetv6(trim($rule[$index])) && ($proto == "inet6")) {
3564
		$tmprule .= "from {$rule[$index]} ";
3565
		$index++;
3566
	} elseif (trim($rule[$index]) == "any") {
3567
		$tmprule .= "from any ";
3568
		$index++;
3569
	} else {
3570
		$network = $rule[$index];
3571
		$netmask = $rule[++$index];
3572

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

    
3586
		$index++;
3587
	}
3588

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

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

    
3625
	/* Destination */
3626
	if (trim($rule[$index]) == "host") {
3627
		$index++;
3628
		if ((($proto == 'inet') && (is_ipaddrv4(trim($rule[$index])) || (trim($rule[$index]) == "{clientip}"))) ||
3629
		    (($proto == 'inet6') && (is_ipaddrv6(trim($rule[$index])) || (trim($rule[$index]) == "{clientipv6}")))) {
3630
			$tmprule .= "to {$rule[$index]} ";
3631
			$index++;
3632
		} else {
3633
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination host '{$rule[$index]}'.");
3634
			return;
3635
		}
3636
	} elseif (is_subnetv6(trim($rule[$index])) && ($proto == "inet6")) {
3637
		$tmprule .= "to {$rule[$index]} ";
3638
		$index++;
3639
	} elseif (trim($rule[$index]) == "any") {
3640
		$tmprule .= "to any ";
3641
		$index++;
3642
	} else {
3643
		$network = $rule[$index];
3644
		$netmask = $rule[++$index];
3645

    
3646
		if (is_ipaddrv4($network) && ($proto == "inet")) {
3647
			try {
3648
				$netmask = cisco_to_cidr($netmask);
3649
			} catch(Exception $e) {
3650
				syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination network '$network'.");
3651
				return;
3652
			}
3653
			$tmprule .= "to {$network}/{$netmask} ";
3654
		} else {
3655
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination network '$network'.");
3656
			return;
3657
		}
3658

    
3659
		$index++;
3660
	}
3661

    
3662
	/* Destination Operator */
3663
	if (in_array(trim($rule[$index]), array("lt", "gt", "eq", "neq"))) {
3664
		switch(trim($rule[$index])) {
3665
			case "lt":
3666
				$operator = "<";
3667
				break;
3668
			case "gt":
3669
				$operator = ">";
3670
				break;
3671
			case "eq":
3672
				$operator = "=";
3673
				break;
3674
			case "neq":
3675
				$operator = "!=";
3676
				break;
3677
		}
3678

    
3679
		$port = $rule[++$index];
3680
		if (is_port($port)) {
3681
			$tmprule .= "port {$operator} {$port} ";
3682
		} else {
3683
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination port: '$port' not a numeric value between 0 and 65535.");
3684
			return;
3685
		}
3686
		$index++;
3687
	} else if (trim($rule[$index]) == "range") {
3688
		$port = array($rule[++$index], $rule[++$index]);
3689
		if (is_port($port[0]) && is_port($port[1])) {
3690
			$tmprule .= "port {$port[0]}:{$port[1]} ";
3691
		} else {
3692
			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.");
3693
			return;
3694
		}
3695
		$index++;
3696
	}
3697

    
3698
	$tmprule = $startrule . $proto . " " . $tmprule;
3699
	return $tmprule;
3700
}
3701

    
3702
function parse_cisco_acl($attribs, $dev) {
3703
	global $attributes;
3704

    
3705
	if (!is_array($attribs)) {
3706
		return "";
3707
	}
3708
	$finalrules = "";
3709
	if (is_array($attribs['ciscoavpair'])) {
3710
		$inrules = array('inet' => array(), 'inet6' => array());
3711
		$outrules = array('inet' => array(), 'inet6' => array());
3712
		foreach ($attribs['ciscoavpair'] as $avrules) {
3713
			$rule = explode("=", $avrules);
3714
			$dir = "";
3715
			if (strstr($rule[0], "inacl")) {
3716
				$dir = "in";
3717
			} else if (strstr($rule[0], "outacl")) {
3718
				$dir = "out";
3719
			} else if (strstr($rule[0], "dns-servers")) {
3720
				$attributes['dns-servers'] = explode(" ", $rule[1]);
3721
				continue;
3722
			} else if (strstr($rule[0], "route")) {
3723
				if (!is_array($attributes['routes'])) {
3724
					$attributes['routes'] = array();
3725
				}
3726
				$attributes['routes'][] = $rule[1];
3727
				continue;
3728
			}
3729
			$rindex = cisco_extract_index($rule[0]);
3730
			if ($rindex < 0) {
3731
				continue;
3732
			}
3733

    
3734
			if (strstr($rule[0], "ipv6")) {
3735
				$proto = "inet6";
3736
			} else {
3737
				$proto = "inet";
3738
			}
3739

    
3740
			$tmprule = parse_cisco_acl_rule($rule[1], $dev, $dir, $proto);
3741

    
3742
			if ($dir == "in") {
3743
				$inrules[$proto][$rindex] = $tmprule;
3744
			} else if ($dir == "out") {
3745
				$outrules[$proto][$rindex] = $tmprule;
3746
			}
3747
		}
3748

    
3749

    
3750
		$state = "";
3751
		foreach (array('inet', 'inet6') as $ip) {
3752
			if (!empty($outrules[$ip])) {
3753
				$state = "no state";
3754
			}
3755
			ksort($inrules[$ip], SORT_NUMERIC);
3756
			foreach ($inrules[$ip] as $inrule) {
3757
				$finalrules .= "{$inrule} {$state}\n";
3758
			}
3759
			if (!empty($outrules[$ip])) {
3760
				ksort($outrules[$ip], SORT_NUMERIC);
3761
				foreach ($outrules[$ip] as $outrule) {
3762
					$finalrules .= "{$outrule} {$state}\n";
3763
				}
3764
			}
3765
		}
3766
	}
3767
	return $finalrules;
3768
}
3769

    
3770
function alias_idn_to_utf8($alias) {
3771
	if (is_alias($alias)) {
3772
		return $alias;
3773
	} else {
3774
		return idn_to_utf8($alias);
3775
	}
3776
}
3777

    
3778
function alias_idn_to_ascii($alias) {
3779
	if (is_alias($alias)) {
3780
		return $alias;
3781
	} else {
3782
		return idn_to_ascii($alias);
3783
	}
3784
}
3785

    
3786
// These funtions were in guiconfig.inc but have been moved here so non GUI processes can use them
3787
function address_to_pconfig($adr, &$padr, &$pmask, &$pnot, &$pbeginport, &$pendport) {
3788
	if (isset($adr['any'])) {
3789
		$padr = "any";
3790
	} else if ($adr['network']) {
3791
		$padr = $adr['network'];
3792
	} else if ($adr['address']) {
3793
		list($padr, $pmask) = explode("/", $adr['address']);
3794
		if (!$pmask) {
3795
			if (is_ipaddrv6($padr)) {
3796
				$pmask = 128;
3797
			} else {
3798
				$pmask = 32;
3799
			}
3800
		}
3801
	}
3802

    
3803
	if (isset($adr['not'])) {
3804
		$pnot = 1;
3805
	} else {
3806
		$pnot = 0;
3807
	}
3808

    
3809
	if ($adr['port']) {
3810
		list($pbeginport, $pendport) = explode("-", $adr['port']);
3811
		if (!$pendport) {
3812
			$pendport = $pbeginport;
3813
		}
3814
	} else if (!is_alias($pbeginport) && !is_alias($pendport)) {
3815
		$pbeginport = "any";
3816
		$pendport = "any";
3817
	}
3818
}
3819

    
3820
function pconfig_to_address(&$adr, $padr, $pmask, $pnot = false, $pbeginport = 0, $pendport = 0, $addmask = false) {
3821
	$adr = array();
3822

    
3823
	if ($padr == "any") {
3824
		$adr['any'] = true;
3825
	} else if (is_specialnet($padr)) {
3826
		if ($addmask) {
3827
			$padr .= "/" . $pmask;
3828
		}
3829
		$adr['network'] = $padr;
3830
	} else {
3831
		$adr['address'] = $padr;
3832
		if (is_ipaddrv6($padr)) {
3833
			if ($pmask != 128) {
3834
				$adr['address'] .= "/" . $pmask;
3835
			}
3836
		} else {
3837
			if ($pmask != 32) {
3838
				$adr['address'] .= "/" . $pmask;
3839
			}
3840
		}
3841
	}
3842

    
3843
	if ($pnot) {
3844
		$adr['not'] = true;
3845
	} else {
3846
		unset($adr['not']);
3847
	}
3848

    
3849
	if (($pbeginport != 0) && ($pbeginport != "any")) {
3850
		if ($pbeginport != $pendport) {
3851
			$adr['port'] = $pbeginport . "-" . $pendport;
3852
		} else {
3853
			$adr['port'] = $pbeginport;
3854
		}
3855
	}
3856

    
3857
	/*
3858
	 * If the port is still unset, then it must not be numeric, but could
3859
	 * be an alias or a well-known/registered service.
3860
	 * See https://redmine.pfsense.org/issues/8410
3861
	 */
3862
	if (!isset($adr['port']) && is_port_or_alias($pbeginport)) {
3863
		$adr['port'] = $pbeginport;
3864
	}
3865
}
3866

    
3867
function is_specialnet($net) {
3868
	global $specialsrcdst;
3869

    
3870
	if (!$net) {
3871
		return false;
3872
	}
3873
	if (in_array($net, $specialsrcdst)) {
3874
		return true;
3875
	} else {
3876
		return false;
3877
	}
3878
}
3879

    
3880
function is_interface_ipaddr($interface) {
3881
	global $config;
3882

    
3883
	if (is_array($config['interfaces'][$interface]) &&
3884
	    isset($config['interfaces'][$interface]['ipaddr']) &&
3885
	    !empty($config['interfaces'][$interface]['ipaddr'])) {
3886
		return true;
3887
	}
3888
	return false;
3889
}
3890

    
3891
function is_interface_ipaddrv6($interface) {
3892
	global $config;
3893

    
3894
	if (is_array($config['interfaces'][$interface]) &&
3895
	    isset($config['interfaces'][$interface]['ipaddrv6']) &&
3896
	    !empty($config['interfaces'][$interface]['ipaddrv6'])) {
3897
		return true;
3898
	}
3899
	return false;
3900
}
3901

    
3902
function escape_filter_regex($filtertext) {
3903
	/* If the caller (user) has not already put a backslash before a slash, to escape it in the regex, */
3904
	/* then this will do it. Take out any "\/" already there, then turn all ordinary "/" into "\/".    */
3905
	return str_replace('/', '\/', str_replace('\/', '/', $filtertext));
3906
}
3907

    
3908
/*
3909
 * Check if a given pattern has the same number of two different unescaped
3910
 * characters.
3911
 * For example, it can ensure a pattern has balanced sets of parentheses,
3912
 * braces, and brackets.
3913
 */
3914
function is_pattern_balanced_char($pattern, $open, $close) {
3915
	/* First remove escaped versions */
3916
	$pattern = str_replace('\\' . $open, '', $pattern);
3917
	$pattern = str_replace('\\' . $close, '', $pattern);
3918
	/* Check if the counts of both characters match in the target pattern */
3919
	return (substr_count($pattern, $open) == substr_count($pattern, $close));
3920
}
3921

    
3922
/*
3923
 * Check if a pattern contains balanced sets of parentheses, braces, and
3924
 * brackets.
3925
 */
3926
function is_pattern_balanced($pattern) {
3927
	if (is_pattern_balanced_char($pattern, '(', ')') &&
3928
	    is_pattern_balanced_char($pattern, '{', '}') &&
3929
	    is_pattern_balanced_char($pattern, '[', ']')) {
3930
		/* Balanced if all are true */
3931
		return true;
3932
	}
3933
	return false;
3934
}
3935

    
3936
function cleanup_regex_pattern($filtertext) {
3937
	/* Cleanup filter to prevent backreferences. */
3938
	$filtertext = escape_filter_regex($filtertext);
3939

    
3940
	/* Remove \<digit>+ backreferences
3941
	 * To match \ it must be escaped as \\\\ in PHP for preg_replace() */
3942
	$filtertext = preg_replace('/\\\\\\d+/', '', $filtertext);
3943

    
3944
	/* Check for unbalanced parentheses, braces, and brackets which
3945
	 * may be an error or attempt to circumvent protections.
3946
	 * Also discard any pattern that attempts problematic duplication
3947
	 * methods. */
3948
	if (!is_pattern_balanced($filtertext) ||
3949
	    (substr_count($filtertext, ')*') > 0) ||
3950
	    (substr_count($filtertext, ')+') > 0) ||
3951
	    (substr_count($filtertext, '{') > 0)) {
3952
		return '';
3953
	}
3954

    
3955
	return $filtertext;
3956
}
3957

    
3958
function ip6_to_asn1($addr) {
3959
	/* IPv6 MIB uses an OCTET STRING of length 16 to represent
3960
	 * 128-bit IPv6 address in network byte order.
3961
	 * see https://datatracker.ietf.org/doc/html/rfc2465#section-3 
3962
	 * i.e. fc00:3::4 = 252.0.0.3.0.0.0.0.0.0.0.0.0.0.0.4
3963
	 */
3964

    
3965
	if (!is_ipaddrv6($addr)) {
3966
		return false;
3967
	}
3968

    
3969
	foreach (explode(':', Net_IPv6::uncompress($addr)) as $v) {
3970
		$ipv6str .= str_pad($v, 4, '0', STR_PAD_LEFT);
3971
	}
3972
	foreach (str_split($ipv6str, 2) as $v) {
3973
		$octstr .= base_convert($v, 16, 10) . '.';
3974
	}
3975

    
3976
	return $octstr;
3977
}
3978

    
3979
function interfaces_interrupts() {
3980
	exec("/usr/bin/vmstat -i --libxo json", $rawdata, $rc);
3981
	$interrupts = array();
3982
	if ($rc == 0) {
3983
		$vnstatarr = json_decode(implode(" ", $rawdata), JSON_OBJECT_AS_ARRAY);
3984
		$interruptarr = $vnstatarr['interrupt-statistics']['interrupt'];
3985

    
3986
		foreach ($interruptarr as $int){
3987
			preg_match("/irq\d+: ([a-z0-9]+)/", $int['name'], $matches);
3988
			$name = $matches[1];
3989
			if (array_key_exists($name, $interrupts)) {
3990
				/* interface with multiple queues */
3991
				$interrupts[$name]['total'] += $int['total'];
3992
				$interrupts[$name]['rate'] += $int['rate'];
3993
			} else {
3994
				$interrupts[$name]['total'] = $int['total'];
3995
				$interrupts[$name]['rate'] = $int['rate'];
3996
			}
3997
		}
3998
	}
3999

    
4000
	return $interrupts;
4001
}
4002

    
4003
function dummynet_load_module($max_qlimit) {
4004
	global $config;
4005

    
4006
	if (!is_module_loaded("dummynet.ko")) {
4007
		mute_kernel_msgs();
4008
		mwexec("/sbin/kldload dummynet");
4009
		unmute_kernel_msgs();
4010
	}
4011
	$sysctls = (array(
4012
			"net.inet.ip.dummynet.io_fast" => "1",
4013
			"net.inet.ip.dummynet.hash_size" => "256",
4014
			"net.inet.ip.dummynet.pipe_slot_limit" => $max_qlimit
4015
	));
4016
	init_config_arr(array('sysctl', 'item'));
4017
	if (!empty($config['sysctl']['item'])) {
4018
		foreach ($config['sysctl']['item'] as $item) {
4019
			if (preg_match('/net\.inet\.ip\.dummynet\./', $item['tunable'])) {
4020
				$sysctls[$item['tunable']] = $item['value'];
4021
			}
4022
		}
4023
	}
4024
	set_sysctl($sysctls);
4025
}
4026

    
4027
function get_interface_vip_ips($interface) {
4028
	global $config;
4029
	$vipips = '';
4030

    
4031
	init_config_arr(array('virtualip', 'vip'));
4032
	foreach ($config['virtualip']['vip'] as $vip) {
4033
		if (($vip['interface'] == $interface) &&
4034
		    (($vip['mode'] == "carp") || ($vip['mode'] == "ipalias"))) {
4035
			$vipips .= $vip['subnet'] . ' ';
4036
		}
4037
	}
4038
	return $vipips;
4039
}
4040

    
4041
?>
(54-54/62)