Project

General

Profile

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

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

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

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

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

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

    
74
	return 0;
75
}
76

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

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

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

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

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

    
102
	return false;
103
}
104

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

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

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

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

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

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

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

    
165
		return $fp;
166
	}
167

    
168
	return NULL;
169
}
170

    
171
/* unlock configuration file */
172
function unlock($cfglckkey = 0)
173
{
174
	if (!is_resource($cfglckkey))
175
		return;
176

    
177
	flock($cfglckkey, LOCK_UN);
178
	fclose($cfglckkey);
179
}
180

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

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

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

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

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

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

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

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

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

    
244
/**
245
 * Test if a kernel module exists on disk in well-known locations
246
 *
247
 * Locations:
248
 *   > /boot/kernel
249
 *   > /bood/modules
250
 *
251
 * @param string $module_name		The name of the kernel module
252
 * @param string ...$add_dirs		Additional directories to search
253
 *
254
 * @return bool
255
 */
256
function is_module_available(string $module_name, string ...$add_dirs): bool
257
{
258
	/* bail if module is already loaded */
259
	if (is_module_loaded($module_name)) {
260
		return (true);
261
	}
262

    
263
	/* ensure the $module_name ends with .ko */
264
	if (!str_ends_with($module_name, '.ko')) {
265
		$module_name .= '.ko';
266
	}
267

    
268
	/* build list of well-known locations */
269
	$mod_dirs = [
270
		'/boot/kernel',
271
		'/boot/modules',
272
		...$add_dirs
273
	];
274

    
275
	/* filter list of well-known locations and cast to bool */
276
	return ((bool) array_filter($mod_dirs, function($mod_dir) use ($module_name) {
277
		return (file_exists($mod_dir . '/' . $module_name));
278
	}));
279
}
280

    
281
/**
282
 * Test if a kernel module is loaded
283
 *
284
 * @param string $module_name	The name of the kernel module
285
 *
286
 * @param bool
287
 */
288
function is_module_loaded(string $module_name): bool
289
{
290
	/* sanitize input */
291
	$module_name = escapeshellarg(rtrim($module_name, '.ko'));
292

    
293
	$base_cmd = ['/sbin/kldstat', '-q'];
294

    
295
	/* first pass test by module name, tests for in-kernel modules */
296
	$cmd = implode(' ', [...$base_cmd, '-m', $module_name]);
297
	exec($cmd, $out, $rc);
298

    
299
	/* bailout early if we found it */
300
	if ($rc === 0) {
301
		return (true);
302
	}
303

    
304
	/* last pass test by file name */
305
	$cmd = implode(' ', [...$base_cmd, '-n', $module_name]);
306
	exec($cmd, $out, $rc);
307

    
308
	return ($rc === 0);
309
}
310

    
311
/* validate non-negative numeric string, or equivalent numeric variable */
312
function is_numericint($arg) {
313
	return (((is_int($arg) && $arg >= 0) || (is_string($arg) && strlen($arg) > 0 && ctype_digit($arg))) ? true : false);
314
}
315

    
316
/* Generate the (human readable) ipv4 or ipv6 subnet address (i.e., netmask, or subnet start IP)
317
   given an (human readable) ipv4 or ipv6 host address and subnet bit count */
318
function gen_subnet($ipaddr, $bits) {
319
	if (($sn = gen_subnetv6($ipaddr, $bits)) == '') {
320
		$sn = gen_subnetv4($ipaddr, $bits);  // try to avoid rechecking IPv4/v6
321
	}
322
	return $sn;
323
}
324

    
325
/* same as gen_subnet() but accepts IPv4 only */
326
function gen_subnetv4($ipaddr, $bits) {
327
	if (is_ipaddrv4($ipaddr) && is_numericint($bits) && $bits <= 32) {
328
		if ($bits == 0) {
329
			return '0.0.0.0';  // avoids <<32
330
		}
331
		return long2ip(ip2long($ipaddr) & ((0xFFFFFFFF << (32 - $bits)) & 0xFFFFFFFF));
332
	}
333
	return "";
334
}
335

    
336
/* same as gen_subnet() but accepts IPv6 only */
337
function gen_subnetv6($ipaddr, $bits) {
338
	if (is_ipaddrv6($ipaddr) && is_numericint($bits) && $bits <= 128) {
339
		return text_to_compressed_ip6(Net_IPv6::getNetmask($ipaddr, $bits));
340
	}
341
	return "";
342
}
343

    
344
/* Generate the (human readable) ipv4 or ipv6 subnet end address (i.e., highest address, end IP, or IPv4 broadcast address)
345
   given an (human readable) ipv4 or ipv6 host address and subnet bit count. */
346
function gen_subnet_max($ipaddr, $bits) {
347
	if (($sn = gen_subnetv6_max($ipaddr, $bits)) == '') {
348
		$sn = gen_subnetv4_max($ipaddr, $bits);  // try to avoid rechecking IPv4/v6
349
	}
350
	return $sn;
351
}
352

    
353
/* same as gen_subnet_max() but validates IPv4 only */
354
function gen_subnetv4_max($ipaddr, $bits) {
355
	if (is_ipaddrv4($ipaddr) && is_numericint($bits) && $bits <= 32) {
356
		if ($bits == 32) {
357
			return $ipaddr;
358
		}
359
		return long2ip32(ip2long($ipaddr) | (~gen_subnet_mask_long($bits) & 0xFFFFFFFF));
360
	}
361
	return "";
362
}
363

    
364
/* same as gen_subnet_max() but validates IPv6 only */
365
function gen_subnetv6_max($ipaddr, $bits) {
366
	if (is_ipaddrv6($ipaddr) && is_numericint($bits) && $bits <= 128) {
367
		$endip_bin = substr(ip6_to_bin($ipaddr), 0, $bits) . str_repeat('1', 128 - $bits);
368
		return bin_to_compressed_ip6($endip_bin);
369
	}
370
	return "";
371
}
372

    
373
/* returns a subnet mask (long given a bit count) */
374
function gen_subnet_mask_long($bits) {
375
	$sm = 0;
376
	for ($i = 0; $i < $bits; $i++) {
377
		$sm >>= 1;
378
		$sm |= 0x80000000;
379
	}
380
	return $sm;
381
}
382

    
383
/* same as above but returns a string */
384
function gen_subnet_mask($bits) {
385
	return long2ip(gen_subnet_mask_long($bits));
386
}
387

    
388
/* Convert a prefix length to an IPv6 address-like mask notation. Very rare but at least ntp needs it. See #4463 */
389
function gen_subnet_mask_v6($bits) {
390
	/* Binary representation of the prefix length */
391
	$bin = str_repeat('1', $bits);
392
	/* Pad right with zeroes to reach the full address length */
393
	$bin = str_pad($bin, 128, '0', STR_PAD_RIGHT);
394
	/* Convert back to an IPv6 address style notation */
395
	return bin_to_ip6($bin);
396
}
397

    
398
/* Convert long int to IPv4 address
399
   Returns '' if not valid IPv4 (including if any bits >32 are non-zero) */
400
function long2ip32($ip) {
401
	return long2ip($ip & 0xFFFFFFFF);
402
}
403

    
404
/* Convert IPv4 address to long int, truncated to 32-bits to avoid sign extension on 64-bit platforms.
405
   Returns '' if not valid IPv4. */
406
function ip2long32($ip) {
407
	return (ip2long($ip) & 0xFFFFFFFF);
408
}
409

    
410
/* Convert IPv4 address to unsigned long int.
411
   Returns '' if not valid IPv4. */
412
function ip2ulong($ip) {
413
	return sprintf("%u", ip2long32($ip));
414
}
415

    
416
/*
417
 * Convert IPv6 address to binary
418
 *
419
 * Obtained from: pear-Net_IPv6
420
 */
421
function ip6_to_bin($ip) {
422
	$binstr = '';
423

    
424
	$ip = Net_IPv6::removeNetmaskSpec($ip);
425
	$ip = Net_IPv6::Uncompress($ip);
426

    
427
	$parts = explode(':', $ip);
428

    
429
	foreach ( $parts as $v ) {
430

    
431
		$str     = base_convert($v, 16, 2);
432
		$binstr .= str_pad($str, 16, '0', STR_PAD_LEFT);
433

    
434
	}
435

    
436
	return $binstr;
437
}
438

    
439
/*
440
 * Convert IPv6 binary to uncompressed address
441
 *
442
 * Obtained from: pear-Net_IPv6
443
 */
444
function bin_to_ip6($bin) {
445
	$ip = "";
446

    
447
	if (strlen($bin) < 128) {
448
		$bin = str_pad($bin, 128, '0', STR_PAD_LEFT);
449
	}
450

    
451
	$parts = str_split($bin, "16");
452

    
453
	foreach ( $parts as $v ) {
454
		$str = base_convert($v, 2, 16);
455
		$ip .= $str.":";
456
	}
457

    
458
	$ip = substr($ip, 0, -1);
459

    
460
	return $ip;
461
}
462

    
463
/*
464
 * Convert IPv6 binary to compressed address
465
 */
466
function bin_to_compressed_ip6($bin) {
467
	return text_to_compressed_ip6(bin_to_ip6($bin));
468
}
469

    
470
/*
471
 * Convert textual IPv6 address string to compressed address
472
 */
473
function text_to_compressed_ip6($text) {
474
	// Force re-compression by passing parameter 2 (force) true.
475
	// This ensures that supposedly-compressed formats are uncompressed
476
	// first then re-compressed into strictly correct form.
477
	// e.g. 2001:0:0:4:0:0:0:1
478
	// 2001::4:0:0:0:1 is a strictly-incorrect compression,
479
	// but maybe the user entered it like that.
480
	// The "force" parameter will ensure it is returned as:
481
	// 2001:0:0:4::1
482
	return Net_IPv6::compress($text, true);
483
}
484

    
485
/* Find out how many IPs are contained within a given IP range
486
 *  e.g. 192.168.0.0 to 192.168.0.255 returns 256
487
 */
488
function ip_range_size_v4($startip, $endip) {
489
	if (is_ipaddrv4($startip) && is_ipaddrv4($endip)) {
490
		// Operate as unsigned long because otherwise it wouldn't work
491
		//   when crossing over from 127.255.255.255 / 128.0.0.0 barrier
492
		return abs(ip2ulong($startip) - ip2ulong($endip)) + 1;
493
	}
494
	return -1;
495
}
496

    
497
/* Find the smallest possible subnet mask which can contain a given number of IPs
498
 *  e.g. 512 IPs can fit in a /23, but 513 IPs need a /22
499
 */
500
function find_smallest_cidr_v4($number) {
501
	$smallest = 1;
502
	for ($b=32; $b > 0; $b--) {
503
		$smallest = ($number <= pow(2, $b)) ? $b : $smallest;
504
	}
505
	return (32-$smallest);
506
}
507

    
508
/* Return the previous IP address before the given address */
509
function ip_before($ip, $offset = 1) {
510
	return long2ip32(ip2long($ip) - $offset);
511
}
512

    
513
/* Return the next IP address after the given address */
514
function ip_after($ip, $offset = 1) {
515
	return long2ip32(ip2long($ip) + $offset);
516
}
517

    
518
/* Return true if the first IP is 'before' the second */
519
function ip_less_than($ip1, $ip2) {
520
	// Compare as unsigned long because otherwise it wouldn't work when
521
	//   crossing over from 127.255.255.255 / 128.0.0.0 barrier
522
	return ip2ulong($ip1) < ip2ulong($ip2);
523
}
524

    
525
/* Return true if the first IP is 'after' the second */
526
function ip_greater_than($ip1, $ip2) {
527
	// Compare as unsigned long because otherwise it wouldn't work
528
	//   when crossing over from 127.255.255.255 / 128.0.0.0 barrier
529
	return ip2ulong($ip1) > ip2ulong($ip2);
530
}
531

    
532
/* compare two IP addresses */
533
function ipcmp($a, $b) {
534
	if (is_subnet($a)) {
535
		$a = explode('/', $a)[0];
536
	}
537
	if (is_subnet($b)) {
538
		$b = explode('/', $b)[0];
539
	}
540
	if (ip_less_than($a, $b)) {
541
		return -1;
542
	} else if (ip_greater_than($a, $b)) {
543
		return 1;
544
	} else {
545
		return 0;
546
	}
547
}
548

    
549
/* Convert a range of IPv4 addresses to an array of individual addresses. */
550
/* Note: IPv6 ranges are not yet supported here. */
551
function ip_range_to_address_array($startip, $endip, $max_size = 5000) {
552
	if (!is_ipaddrv4($startip) || !is_ipaddrv4($endip)) {
553
		return false;
554
	}
555

    
556
	if (ip_greater_than($startip, $endip)) {
557
		// Swap start and end so we can process sensibly.
558
		$temp = $startip;
559
		$startip = $endip;
560
		$endip = $temp;
561
	}
562

    
563
	if (ip_range_size_v4($startip, $endip) > $max_size) {
564
		return false;
565
	}
566

    
567
	// Container for IP addresses within this range.
568
	$rangeaddresses = array();
569
	$end_int = ip2ulong($endip);
570
	for ($ip_int = ip2ulong($startip); $ip_int <= $end_int; $ip_int++) {
571
		$rangeaddresses[] = long2ip($ip_int);
572
	}
573

    
574
	return $rangeaddresses;
575
}
576

    
577
/*
578
 * Convert an IPv4 or IPv6 IP range to an array of subnets which can contain the range.
579
 * Algorithm and embodying code PD'ed by Stilez - enjoy as you like :-)
580
 *
581
 * Documented on pfsense dev list 19-20 May 2013. Summary:
582
 *
583
 * The algorithm looks at patterns of 0's and 1's in the least significant bit(s), whether IPv4 or IPv6.
584
 * These are all that needs checking to identify a _guaranteed_ correct, minimal and optimal subnet array.
585
 *
586
 * As a result, string/binary pattern matching of the binary IP is very efficient. It uses just 2 pattern-matching rules
587
 * to chop off increasingly larger subnets at both ends that can't be part of larger subnets, until nothing's left.
588
 *
589
 * (a) If any range has EITHER low bit 1 (in startip) or 0 (in endip), that end-point is _always guaranteed_ to be optimally
590
 * represented by its own 'single IP' CIDR; the remaining range then shrinks by one IP up or down, causing the new end-point's
591
 * low bit to change from 1->0 (startip) or 0->1 (endip). Only one edge case needs checking: if a range contains exactly 2
592
 * adjacent IPs of this format, then the two IPs themselves are required to span it, and we're done.
593
 * 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
594
 * low bits can now be ignored.
595
 *
596
 * (b) If any range has BOTH startip and endip ending in some number of 0's and 1's respectively, these low bits can
597
 * *always* be ignored and "bit-shifted" for subnet spanning. So provided we remember the bits we've place-shifted, we can
598
 * _always_ right-shift and chop off those bits, leaving a smaller range that has EITHER startip ending in 1 or endip ending
599
 * in 0 (ie can now apply (a) again) or the entire range has vanished and we're done.
600
 * We then loop to redo (a) again on the remaining (place shifted) range until after a few loops, the remaining (place shifted)
601
 * range 'vanishes' by meeting the exit criteria of (a) or (b), and we're done.
602
 */
603
function ip_range_to_subnet_array($ip1, $ip2) {
604

    
605
	if (is_ipaddrv4($ip1) && is_ipaddrv4($ip2)) {
606
		$proto = 'ipv4';  // for clarity
607
		$bits = 32;
608
		$ip1bin = decbin(ip2long32($ip1));
609
		$ip2bin = decbin(ip2long32($ip2));
610
	} elseif (is_ipaddrv6($ip1) && is_ipaddrv6($ip2)) {
611
		$proto = 'ipv6';
612
		$bits = 128;
613
		$ip1bin = ip6_to_bin($ip1);
614
		$ip2bin = ip6_to_bin($ip2);
615
	} else {
616
		return array();
617
	}
618

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

    
623
	if ($ip1bin == $ip2bin) {
624
		return array($ip1 . '/' . $bits); // exit if ip1=ip2 (trivial case)
625
	}
626

    
627
	if ($ip1bin > $ip2bin) {
628
		list ($ip1bin, $ip2bin) = array($ip2bin, $ip1bin);  // swap if needed (ensures ip1 < ip2)
629
	}
630

    
631
	$rangesubnets = array();
632
	$netsize = 0;
633

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

    
638
		// 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)
639

    
640
		if (substr($ip1bin, -1, 1) == '1') {
641
			// the start ip must be in a separate one-IP cidr range
642
			$new_subnet_ip = substr($ip1bin, $netsize, $bits - $netsize) . str_repeat('0', $netsize);
643
			$rangesubnets[$new_subnet_ip] = $bits - $netsize;
644
			$n = strrpos($ip1bin, '0');  //can't be all 1's
645
			$ip1bin = ($n == 0 ? '' : substr($ip1bin, 0, $n)) . '1' . str_repeat('0', $bits - $n - 1);  // BINARY VERSION OF $ip1 += 1
646
		}
647

    
648
		// 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)
649

    
650
		if (substr($ip2bin, -1, 1) == '0') {
651
			// the end ip must be in a separate one-IP cidr range
652
			$new_subnet_ip = substr($ip2bin, $netsize, $bits - $netsize) . str_repeat('0', $netsize);
653
			$rangesubnets[$new_subnet_ip] = $bits - $netsize;
654
			$n = strrpos($ip2bin, '1');  //can't be all 0's
655
			$ip2bin = ($n == 0 ? '' : substr($ip2bin, 0, $n)) . '0' . str_repeat('1', $bits - $n - 1);  // BINARY VERSION OF $ip2 -= 1
656
			// already checked for the edge case where end = start+1 and start ends in 0x1, above, so it's safe
657
		}
658

    
659
		// this is the only edge case arising from increment/decrement.
660
		// 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)
661

    
662
		if ($ip2bin < $ip1bin) {
663
			continue;
664
		}
665

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

    
669
		$shift = $bits - max(strrpos($ip1bin, '0'), strrpos($ip2bin, '1'));  // num of low bits which are '0' in ip1 and '1' in ip2
670
		$ip1bin = str_repeat('0', $shift) . substr($ip1bin, 0, $bits - $shift);
671
		$ip2bin = str_repeat('0', $shift) . substr($ip2bin, 0, $bits - $shift);
672
		$netsize += $shift;
673
		if ($ip1bin == $ip2bin) {
674
			// we're done.
675
			$new_subnet_ip = substr($ip1bin, $netsize, $bits - $netsize) . str_repeat('0', $netsize);
676
			$rangesubnets[$new_subnet_ip] = $bits - $netsize;
677
			continue;
678
		}
679

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

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

    
685
	ksort($rangesubnets, SORT_STRING);
686
	$out = array();
687

    
688
	foreach ($rangesubnets as $ip => $netmask) {
689
		if ($proto == 'ipv4') {
690
			$i = str_split($ip, 8);
691
			$out[] = implode('.', array(bindec($i[0]), bindec($i[1]), bindec($i[2]), bindec($i[3]))) . '/' . $netmask;
692
		} else {
693
			$out[] = bin_to_compressed_ip6($ip) . '/' . $netmask;
694
		}
695
	}
696

    
697
	return $out;
698
}
699

    
700
/* returns true if $range is a valid pair of IPv4 or IPv6 addresses separated by a "-"
701
	false - if not a valid pair
702
	true (numeric 4 or 6) - if valid, gives type of addresses */
703
function is_iprange($range) {
704
	if (substr_count($range, '-') != 1) {
705
		return false;
706
	}
707
	list($ip1, $ip2) = explode ('-', $range);
708
	if (is_ipaddrv4($ip1) && is_ipaddrv4($ip2)) {
709
		return 4;
710
	}
711
	if (is_ipaddrv6($ip1) && is_ipaddrv6($ip2)) {
712
		return 6;
713
	}
714
	return false;
715
}
716

    
717
/* returns true if $ipaddr is a valid dotted IPv4 address or a IPv6
718
	false - not valid
719
	true (numeric 4 or 6) - if valid, gives type of address */
720
function is_ipaddr($ipaddr) {
721
	if (is_ipaddrv4($ipaddr)) {
722
		return 4;
723
	}
724
	if (is_ipaddrv6($ipaddr)) {
725
		return 6;
726
	}
727
	return false;
728
}
729

    
730
/* returns true if $ipaddr is a valid IPv6 address */
731
function is_ipaddrv6($ipaddr) {
732
	if (!is_string($ipaddr) || empty($ipaddr)) {
733
		return false;
734
	}
735
	/*
736
	 * While Net_IPv6::checkIPv6() considers IPv6/mask a valid IPv6,
737
	 * is_ipaddrv6() needs to be more strict to keep the compatibility
738
	 * with is_ipaddrv4().
739
	 */
740
	if (strstr($ipaddr, "/")) {
741
		return false;
742
	}
743
	if (strstr($ipaddr, "%") && is_linklocal($ipaddr)) {
744
		$tmpip = explode("%", $ipaddr);
745
		$ipaddr = $tmpip[0];
746
	}
747
	/*
748
	 * Net_IPv6::checkIPv6 does not reject multiple attempts at compression
749
	 * so we must check it beforehand.
750
	 * https://redmine.pfsense.org/issues/13069
751
	 */
752
	if (substr_count($ipaddr, '::') > 1) {
753
		return false;
754
	}
755
	return Net_IPv6::checkIPv6($ipaddr);
756
}
757

    
758
function is_ipaddrv6_v4map($ipaddr) {
759
	/* check RFC4291 par 2.2.2 format, ex: fd00::1.2.3.4
760
	 * see https://redmine.pfsense.org/issues/11446 */
761
	if (is_ipaddrv6($ipaddr) && preg_match('/^[0-9a-f:]{2,30}[0-9.]{7,15}$/i', $ipaddr)) {
762
		return true;
763
	}
764
	return false;
765
}
766

    
767
/* returns true if $ipaddr is a valid dotted IPv4 address */
768
function is_ipaddrv4($ipaddr) {
769
	if (!is_string($ipaddr) || empty($ipaddr) || ip2long($ipaddr) === FALSE) {
770
		return false;
771
	}
772
	return true;
773
}
774

    
775
function is_mcast($ipaddr) {
776
	if (is_mcastv4($ipaddr)) {
777
		return 4;
778
	}
779
	if (is_mcastv6($ipaddr)) {
780
		return 6;
781
	}
782
	return false;
783
}
784

    
785
function is_mcastv4($ipaddr) {
786
	if (!is_ipaddrv4($ipaddr) ||
787
	    !preg_match('/^2(?:2[4-9]|3\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d?|0)){3}$/', $ipaddr)) {
788
		return false;
789
	}
790
	return true;
791
}
792

    
793
function is_mcastv6($ipaddr) {
794
	if (!is_ipaddrv6($ipaddr) || !preg_match('/^ff.+$/', $ipaddr)) {
795
		return false;
796
	}
797
	return true;
798
}
799

    
800
/* returns 4 or 6 respectively (== TRUE) if $ipaddr is a valid IPv4 or IPv6 linklocal address
801
   returns '' if not a valid linklocal address */
802
function is_linklocal($ipaddr) {
803
	if (is_ipaddrv4($ipaddr)) {
804
		// input is IPv4
805
		// test if it's 169.254.x.x per rfc3927 2.1
806
		$ip4 = explode(".", $ipaddr);
807
		if ($ip4[0] == '169' && $ip4[1] == '254') {
808
			return 4;
809
		}
810
	} elseif (Net_IPv6::getAddressType($ipaddr) == NET_IPV6_LOCAL_LINK) {
811
		return 6;
812
	}
813
	return '';
814
}
815

    
816
/* returns scope of a linklocal address */
817
function get_ll_scope($addr) {
818
	if (!is_linklocal($addr) || !strstr($addr, "%")) {
819
		return "";
820
	}
821
	return explode("%", $addr)[1];
822
}
823

    
824
/* returns true if $ipaddr is a valid literal IPv6 address */
825
function is_literalipaddrv6($ipaddr) {
826
	if (substr($ipaddr,0,1) == '[' && substr($ipaddr,-1,1) == ']') {
827
		// if it's data wrapped in "[ ... ]" then test if middle part is valid IPv6
828
		return is_ipaddrv6(substr($ipaddr,1,-1));
829
	}
830
	return false;
831
}
832

    
833
/* returns true if $iport is a valid IPv4:port or [Literal IPv6]:port
834
	false - not valid
835
	true (numeric 4 or 6) - if valid, gives type of address */
836
function is_ipaddrwithport($ipport) {
837
	$c = strrpos($ipport, ":");
838
	if ($c === false) {
839
		return false;  // can't split at final colon if no colon exists
840
	}
841

    
842
	if (!is_port(substr($ipport, $c + 1))) {
843
		return false;  // no valid port after last colon
844
	}
845

    
846
	$ip = substr($ipport, 0, $c);  // else is text before last colon a valid IP
847
	if (is_literalipaddrv6($ip)) {
848
		return 6;
849
	} elseif (is_ipaddrv4($ip)) {
850
		return 4;
851
	} else {
852
		return false;
853
	}
854
}
855

    
856
function is_hostnamewithport($hostport) {
857
	$parts = explode(":", $hostport);
858
	// no need to validate with is_string(); if it's not a string then explode won't return 2 parts anyway
859
	if (count($parts) == 2) {
860
		return is_hostname($parts[0]) && is_port($parts[1]);
861
	}
862
	return false;
863
}
864

    
865
/* returns true if $ipaddr is a valid dotted IPv4 address or an alias thereof */
866
function is_ipaddroralias($ipaddr) {
867
	if (is_alias($ipaddr)) {
868
		foreach (config_get_path('aliases/alias', []) as $alias) {
869
			if ($alias['name'] == $ipaddr && !preg_match("/port/i", $alias['type'])) {
870
				return true;
871
			}
872
		}
873
		return false;
874
	} else {
875
		return is_ipaddr($ipaddr);
876
	}
877

    
878
}
879

    
880
/* returns true if $subnet is a valid IPv4 or IPv6 subnet in CIDR format
881
	false - if not a valid subnet
882
	true (numeric 4 or 6) - if valid, gives type of subnet */
883
function is_subnet($subnet) {
884
	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)) {
885
		if (is_ipaddrv4($parts[1]) && $parts[3] <= 32) {
886
			return 4;
887
		}
888
		if (is_ipaddrv6($parts[2]) && $parts[3] <= 128) {
889
			return 6;
890
		}
891
	}
892
	return false;
893
}
894

    
895
function is_v4($ip_or_subnet) {
896
	return is_ipaddrv4($ip_or_subnet) || is_subnetv4($ip_or_subnet);
897
}
898

    
899
function is_v6($ip_or_subnet) {
900
	return is_ipaddrv6($ip_or_subnet) || is_subnetv6($ip_or_subnet);
901
}
902

    
903
/* same as is_subnet() but accepts IPv4 only */
904
function is_subnetv4($subnet) {
905
	return (is_subnet($subnet) == 4);
906
}
907

    
908
/* same as is_subnet() but accepts IPv6 only */
909
function is_subnetv6($subnet) {
910
	return (is_subnet($subnet) == 6);
911
}
912

    
913
/* returns true if $subnet is a valid subnet in CIDR format or an alias thereof */
914
function is_subnetoralias($subnet) {
915
	global $aliastable;
916

    
917
	if (isset($aliastable[$subnet]) && is_subnet($aliastable[$subnet])) {
918
		return true;
919
	} else {
920
		return is_subnet($subnet);
921
	}
922
}
923

    
924
/* Get number of addresses in an IPv4/IPv6 subnet (represented as a string)
925
   optional $exact=true forces error (0) to be returned if it can't be represented exactly
926
   Exact result not possible above PHP_MAX_INT which is about 2^31 addresses on x32 or 2^63 on x64
927
   Returns 0 for bad data or if cannot represent size as an INT when $exact is set. */
928
function subnet_size($subnet, $exact=false) {
929
	$parts = explode("/", $subnet);
930
	$iptype = is_ipaddr($parts[0]);
931
	if (count($parts) == 2 && $iptype) {
932
		return subnet_size_by_netmask($iptype, $parts[1], $exact);
933
	}
934
	return 0;
935
}
936

    
937
/* Get number of addresses in an IPv4/IPv6 subnet (represented numerically as IP type + bits)
938
   optional $exact=true forces error (0) to be returned if it can't be represented exactly
939
   Hard to think where we might need to count exactly a huge subnet but an overflow detection option is probably sensible
940
   Returns 0 for bad data or if cannot represent size as an INT when $exact is set. */
941
function subnet_size_by_netmask($iptype, $bits, $exact=false) {
942
	if (!is_numericint($bits)) {
943
		return 0;
944
	} elseif ($iptype == 4 && $bits <= 32) {
945
		$snsize = 32 - $bits;
946
	} elseif ($iptype == 6 && $bits <= 128) {
947
		$snsize = 128 - $bits;
948
	} else {
949
		return 0;
950
	}
951

    
952
	// 2**N returns an exact result as an INT if possible, and a float/double if not.
953
	// 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
954
	$result = 2 ** $snsize;
955

    
956
	if ($exact && !is_int($result)) {
957
		//exact required but can't represent result exactly as an INT
958
		return 0;
959
	} else {
960
		// result ok, will be an INT where possible (guaranteed up to 2^31 addresses on x32/x64) and a float for 'huge' subnets
961
		return $result;
962
	}
963
}
964

    
965
/* function used by pfblockerng */
966
function subnetv4_expand($subnet) {
967
	$result = array();
968
	list ($ip, $bits) = explode("/", $subnet);
969
	$net = ip2long($ip);
970
	$mask = (0xffffffff << (32 - $bits));
971
	$net &= $mask;
972
	$size = round(exp(log(2) * (32 - $bits)));
973
	for ($i = 0; $i < $size; $i += 1) {
974
		$result[] = long2ip($net | $i);
975
	}
976
	return $result;
977
}
978

    
979
/* find out whether two IPv4/IPv6 CIDR subnets overlap.
980
   Note: CIDR overlap implies one is identical or included so largest sn will be the same */
981
function check_subnets_overlap($subnet1, $bits1, $subnet2, $bits2) {
982
	if (is_ipaddrv4($subnet1)) {
983
		return check_subnetsv4_overlap($subnet1, $bits1, $subnet2, $bits2);
984
	} else {
985
		return check_subnetsv6_overlap($subnet1, $bits1, $subnet2, $bits2);
986
	}
987
}
988

    
989
/* find out whether two IPv4 CIDR subnets overlap.
990
   Note: CIDR overlap means sn1/sn2 are identical or one is included in other. So sn using largest $bits will be the same  */
991
function check_subnetsv4_overlap($subnet1, $bits1, $subnet2, $bits2) {
992
	$largest_sn = min($bits1, $bits2);
993
	$subnetv4_start1 = gen_subnetv4($subnet1, $largest_sn);
994
	$subnetv4_start2 = gen_subnetv4($subnet2, $largest_sn);
995

    
996
	if ($subnetv4_start1 == '' || $subnetv4_start2 == '') {
997
		// One or both args is not a valid IPv4 subnet
998
		//FIXME: needs to return "bad data" not true/false if bad. For now return false, best we can do until fixed
999
		return false;
1000
	}
1001
	return ($subnetv4_start1 == $subnetv4_start2);
1002
}
1003

    
1004
/* find out whether two IPv6 CIDR subnets overlap.
1005
   Note: CIDR overlap means sn1/sn2 are identical or one is included in other. So sn using largest $bits will be the same  */
1006
function check_subnetsv6_overlap($subnet1, $bits1, $subnet2, $bits2) {
1007
	$largest_sn = min($bits1, $bits2);
1008
	$subnetv6_start1 = gen_subnetv6($subnet1, $largest_sn);
1009
	$subnetv6_start2 = gen_subnetv6($subnet2, $largest_sn);
1010

    
1011
	if ($subnetv6_start1 == '' || $subnetv6_start2 == '') {
1012
		// One or both args is not a valid IPv6 subnet
1013
		//FIXME: needs to return "bad data" not true/false if bad. For now return false, best we can do until fixed
1014
		return false;
1015
	}
1016
	return ($subnetv6_start1 == $subnetv6_start2);
1017
}
1018

    
1019
/* return all PTR zones for a IPv6 network */
1020
function get_v6_ptr_zones($subnet, $bits) {
1021
	$result = array();
1022

    
1023
	if (!is_ipaddrv6($subnet)) {
1024
		return $result;
1025
	}
1026

    
1027
	if (!is_numericint($bits) || $bits > 128) {
1028
		return $result;
1029
	}
1030

    
1031
	/*
1032
	 * Find a small nibble boundary subnet mask
1033
	 * e.g. a /29 will create 8 /32 PTR zones
1034
	 */
1035
	$small_sn = $bits;
1036
	while ($small_sn % 4 != 0) {
1037
		$small_sn++;
1038
	}
1039

    
1040
	/* Get network prefix */
1041
	$small_subnet = Net_IPv6::getNetmask($subnet, $bits);
1042

    
1043
	/*
1044
	 * While small network is part of bigger one, increase 4-bit in last
1045
	 * digit to get next small network
1046
	 */
1047
	while (Net_IPv6::isInNetmask($small_subnet, $subnet, $bits)) {
1048
		/* Get a pure hex value */
1049
		$unpacked = unpack('H*hex', inet_pton($small_subnet));
1050
		/* Create PTR record using $small_sn / 4 chars */
1051
		$result[] = implode('.', array_reverse(str_split(substr(
1052
		    $unpacked['hex'], 0, $small_sn / 4)))).'.ip6.arpa';
1053

    
1054
		/* Detect what part of IP should be increased */
1055
		$change_part = (int) ($small_sn / 16);
1056
		if ($small_sn % 16 == 0) {
1057
			$change_part--;
1058
		}
1059

    
1060
		/* Increase 1 to desired part */
1061
		$parts = explode(":", Net_IPv6::uncompress($small_subnet));
1062
		$parts[$change_part]++;
1063
		$small_subnet = implode(":", $parts);
1064
	}
1065

    
1066
	return $result;
1067
}
1068

    
1069
/* return true if $addr is in $subnet, false if not */
1070
function ip_in_subnet($addr, $subnet) {
1071
	if (is_ipaddrv6($addr) && is_subnetv6($subnet)) {
1072
		/* Normalize IPv6 prefix to its start address to avoid PHP errors
1073
		 * https://redmine.pfsense.org/issues/14256
1074
		 */
1075
		list($prefix, $length) = explode("/", $subnet);
1076
		$prefix = gen_subnetv6($prefix, $length);
1077
		$subnet = "{$prefix}/{$length}";
1078
		return (Net_IPv6::isInNetmask($addr, $subnet));
1079
	} else if (is_ipaddrv4($addr) && is_subnetv4($subnet)) {
1080
		list($ip, $mask) = explode('/', $subnet);
1081
		$mask = (0xffffffff << (32 - $mask)) & 0xffffffff;
1082
		return ((ip2long($addr) & $mask) == (ip2long($ip) & $mask));
1083
	}
1084
	return false;
1085
}
1086

    
1087
/* returns true if $hostname is just a valid hostname (top part without any of the domain part) */
1088
function is_unqualified_hostname($hostname) {
1089
	if (!is_string($hostname)) {
1090
		return false;
1091
	}
1092

    
1093
	if (preg_match('/^(?:[a-z0-9_]|[a-z0-9_][a-z0-9_\-]*[a-z0-9_])$/i', $hostname)) {
1094
		return true;
1095
	} else {
1096
		return false;
1097
	}
1098
}
1099

    
1100
/* returns true if $hostname is a valid hostname, with or without being a fully-qualified domain name. */
1101
function is_hostname($hostname, $allow_wildcard=false) {
1102
	if (!is_string($hostname)) {
1103
		return false;
1104
	}
1105

    
1106
	if (is_domain($hostname, $allow_wildcard)) {
1107
		if ((substr_count($hostname, ".") == 1) && ($hostname[strlen($hostname)-1] == ".")) {
1108
			/* Only a single dot at the end like "test." - hosts cannot be directly in the root domain. */
1109
			return false;
1110
		} else {
1111
			return true;
1112
		}
1113
	} else {
1114
		return false;
1115
	}
1116
}
1117

    
1118
/* returns true if $domain is a valid domain name */
1119
function is_domain($domain, $allow_wildcard=false, $trailing_dot=true) {
1120
	if (!is_string($domain)) {
1121
		return false;
1122
	}
1123
	if (!$trailing_dot && ($domain[strlen($domain)-1] == ".")) {
1124
		return false;
1125
	}
1126
	if ($allow_wildcard) {
1127
		$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';
1128
	} else {
1129
		$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';
1130
	}
1131

    
1132
	if (preg_match($domain_regex, $domain)) {
1133
		return true;
1134
	} else {
1135
		return false;
1136
	}
1137
}
1138

    
1139
/* returns true if $macaddr is a valid MAC address */
1140
function is_macaddr($macaddr, $partial=false) {
1141
	$values = explode(":", $macaddr);
1142

    
1143
	/* Verify if the MAC address has a proper amount of parts for either a partial or full match. */
1144
	if ($partial) {
1145
		if ((count($values) < 1) || (count($values) > 6)) {
1146
			return false;
1147
		}
1148
	} elseif (count($values) != 6) {
1149
		return false;
1150
	}
1151
	for ($i = 0; $i < count($values); $i++) {
1152
		if (ctype_xdigit($values[$i]) == false)
1153
			return false;
1154
		if (hexdec($values[$i]) < 0 || hexdec($values[$i]) > 255)
1155
			return false;
1156
	}
1157

    
1158
	return true;
1159
}
1160

    
1161
/*
1162
	If $return_message is true then
1163
		returns a text message about the reason that the name is invalid.
1164
		the text includes the type of "thing" that is being checked, passed in $object. (e.g. "alias", "gateway group", "schedule")
1165
	else
1166
		returns true if $name is a valid name for an alias
1167
		returns false if $name is not a valid name for an alias
1168

    
1169
	Aliases cannot be:
1170
		bad chars: anything except a-z 0-9 and underscore
1171
		bad names: empty string, pure numeric, pure underscore
1172
		reserved words: pre-defined service/protocol/port names which should not be ambiguous, and the words "port" and  "pass" */
1173

    
1174
function is_validaliasname($name, $return_message = false, $object = "alias") {
1175
	/* Array of reserved words */
1176
	$reserved = array("port", "pass");
1177

    
1178
	if (!is_string($name) || strlen($name) >= 32 || preg_match('/(^_*$|^\d*$|[^a-z0-9_])/i', $name)) {
1179
		if ($return_message) {
1180
			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, _');
1181
		} else {
1182
			return false;
1183
		}
1184
	}
1185
	if (in_array($name, $reserved, true)) {
1186
		if ($return_message) {
1187
			return sprintf(gettext('The %1$s name must not be either of the reserved words %2$s or %3$s.'), $object, "'port'", "'pass'");
1188
		} else {
1189
			return false;
1190
		}
1191
	}
1192
	if (getprotobyname($name)) {
1193
		if ($return_message) {
1194
			return sprintf(gettext('The %1$s name must not be an IP protocol name such as TCP, UDP, ICMP etc.'), $object);
1195
		} else {
1196
			return false;
1197
		}
1198
	}
1199
	if (getservbyname($name, "tcp") || getservbyname($name, "udp")) {
1200
		if ($return_message) {
1201
			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);
1202
		} else {
1203
			return false;
1204
		}
1205
	}
1206
	if ($return_message) {
1207
		return sprintf(gettext('The %1$s name is valid.'), $object);
1208
	} else {
1209
		return true;
1210
	}
1211
}
1212

    
1213
/* returns a text message indicating if the alias name is valid, or the reason it is not valid. */
1214
function invalidaliasnamemsg($name, $object = "alias") {
1215
	return is_validaliasname($name, true, $object);
1216
}
1217

    
1218
/*
1219
 * returns true if $range is a valid integer range between $min and $max
1220
 * range delimiter can be ':' or '-'
1221
 */
1222
function is_intrange($range, $min, $max) {
1223
	$values = preg_split("/[:-]/", $range);
1224

    
1225
	if (!is_array($values) || count($values) != 2) {
1226
		return false;
1227
	}
1228

    
1229
	if (!ctype_digit($values[0]) || !ctype_digit($values[1])) {
1230
		return false;
1231
	}
1232

    
1233
	$values[0] = intval($values[0]);
1234
	$values[1] = intval($values[1]);
1235

    
1236
	if ($values[0] >= $values[1]) {
1237
		return false;
1238
	}
1239

    
1240
	if ($values[0] < $min || $values[1] > $max) {
1241
		return false;
1242
	}
1243

    
1244
	return true;
1245
}
1246

    
1247
/* returns true if $port is a valid TCP/UDP port */
1248
function is_port($port) {
1249
	if (ctype_digit($port) && ((intval($port) >= 1) && (intval($port) <= 65535))) {
1250
		return true;
1251
	}
1252
	if (getservbyname($port, "tcp") || getservbyname($port, "udp")) {
1253
		return true;
1254
	}
1255
	return false;
1256
}
1257

    
1258
/* returns true if $port is in use */
1259
function is_port_in_use($port, $proto = "tcp", $ip_version = 4) {
1260
	$port_info = array();
1261
	exec("/usr/bin/netstat --libxo json -an " . escapeshellarg('-' . $ip_version) . " -p " . escapeshellarg($proto), $rawdata, $rc);
1262
	if ($rc == 0) {
1263
		$netstatarr = json_decode(implode(" ", $rawdata), JSON_OBJECT_AS_ARRAY);
1264
		$netstatarr = $netstatarr['statistics']['socket'];
1265

    
1266
		foreach($netstatarr as $portstats){
1267
			array_push($port_info, $portstats['local']['port']);
1268
		}
1269
	}
1270

    
1271
	return in_array($port, $port_info);
1272
}
1273

    
1274
/* returns true if $portrange is a valid TCP/UDP portrange ("<port>:<port>") */
1275
function is_portrange($portrange) {
1276
	$ports = explode(":", $portrange);
1277

    
1278
	return (count($ports) == 2 && is_port($ports[0]) && is_port($ports[1]));
1279
}
1280

    
1281
/* returns true if $port is a valid TCP/UDP port number or range ("<port>:<port>") */
1282
function is_port_or_range($port) {
1283
	return (is_port($port) || is_portrange($port));
1284
}
1285

    
1286
/* returns true if $port is an alias that is a port type */
1287
function is_portalias($port) {
1288
	if (is_alias($port)) {
1289
		foreach (config_get_path('aliases/alias', []) as $alias) {
1290
			if ($alias['name'] == $port && preg_match("/port/i", $alias['type'])) {
1291
				return true;
1292
			}
1293
		}
1294
	}
1295
	return false;
1296
}
1297

    
1298
/* returns true if $port is a valid port number or an alias thereof */
1299
function is_port_or_alias($port) {
1300
	return (is_port($port) || is_portalias($port));
1301
}
1302

    
1303
/* returns true if $port is a valid TCP/UDP port number or range ("<port>:<port>") or an alias thereof */
1304
function is_port_or_range_or_alias($port) {
1305
	return (is_port($port) || is_portrange($port) || is_portalias($port));
1306
}
1307

    
1308
/* create ranges of sequential port numbers (200:215) and remove duplicates */
1309
function group_ports($ports, $kflc = false) {
1310
	if (!is_array($ports) || empty($ports)) {
1311
		return;
1312
	}
1313

    
1314
	$uniq = array();
1315
	$comments = array();
1316
	foreach ($ports as $port) {
1317
		if (($kflc) && (strpos($port, '#') === 0)) {	// Keep Full Line Comments (lines beginning with #).
1318
			$comments[] = $port;
1319
		} else if (is_portrange($port)) {
1320
			list($begin, $end) = explode(":", $port);
1321
			if ($begin > $end) {
1322
				$aux = $begin;
1323
				$begin = $end;
1324
				$end = $aux;
1325
			}
1326
			for ($i = $begin; $i <= $end; $i++) {
1327
				if (!in_array($i, $uniq)) {
1328
					$uniq[] = $i;
1329
				}
1330
			}
1331
		} else if (is_port($port)) {
1332
			if (!in_array($port, $uniq)) {
1333
				$uniq[] = $port;
1334
			}
1335
		}
1336
	}
1337
	sort($uniq, SORT_NUMERIC);
1338

    
1339
	$result = array();
1340
	foreach ($uniq as $idx => $port) {
1341
		if ($idx == 0) {
1342
			$result[] = $port;
1343
			continue;
1344
		}
1345

    
1346
		$last = end($result);
1347
		if (is_portrange($last)) {
1348
			list($begin, $end) = explode(":", $last);
1349
		} else {
1350
			$begin = $end = $last;
1351
		}
1352

    
1353
		if ($port == ($end+1)) {
1354
			$end++;
1355
			$result[count($result)-1] = "{$begin}:{$end}";
1356
		} else {
1357
			$result[] = $port;
1358
		}
1359
	}
1360

    
1361
	return array_merge($comments, $result);
1362
}
1363

    
1364
/* returns true if $val is a valid shaper bandwidth value */
1365
function is_valid_shaperbw($val) {
1366
	return (preg_match("/^(\d+(?:\.\d+)?)([MKG]?b|%)$/", $val));
1367
}
1368

    
1369
/* returns true if $test is in the range between $start and $end */
1370
function is_inrange_v4($test, $start, $end) {
1371
	if (!is_ipaddrv4($test) || !is_ipaddrv4($start) || !is_ipaddrv4($end)) {
1372
		return false;
1373
	}
1374

    
1375
	if (ip2ulong($test) <= ip2ulong($end) &&
1376
	    ip2ulong($test) >= ip2ulong($start)) {
1377
		return true;
1378
	}
1379

    
1380
	return false;
1381
}
1382

    
1383
/* returns true if $test is in the range between $start and $end */
1384
function is_inrange_v6($test, $start, $end) {
1385
	if (!is_ipaddrv6($test) || !is_ipaddrv6($start) || !is_ipaddrv6($end)) {
1386
		return false;
1387
	}
1388

    
1389
	if (inet_pton($test) <= inet_pton($end) &&
1390
	    inet_pton($test) >= inet_pton($start)) {
1391
		return true;
1392
	}
1393

    
1394
	return false;
1395
}
1396

    
1397
/* returns true if $test is in the range between $start and $end */
1398
function is_inrange($test, $start, $end) {
1399
	return is_ipaddrv6($test) ? is_inrange_v6($test, $start, $end) : is_inrange_v4($test, $start, $end);
1400
}
1401

    
1402
/**
1403
 * Check if an ethertype is valid
1404
 *
1405
 * @param string $ethertype	The ethertype as hex string
1406
 *
1407
 * @return bool
1408
 */
1409
function is_ethertype(string $ethertype): bool {
1410
	$ethertype = strtolower($ethertype);
1411

    
1412
	if (!str_starts_with($ethertype, '0x')) {
1413
		return (false);
1414
	}
1415

    
1416
	$ethertype = substr($ethertype, 2);
1417

    
1418
	if (!ctype_xdigit($ethertype)) {
1419
		return (false);
1420
	}
1421

    
1422
	return (filter_var(hexdec($ethertype), FILTER_VALIDATE_INT, ['options' => ['min_range' => 0x1, 'max_range' => 0xffff]]));
1423
}
1424

    
1425
function build_vip_list($fif, $family = "all") {
1426
	$list = array('address' => gettext('Interface Address'));
1427

    
1428
	$viplist = get_configured_vip_list($family);
1429
	foreach ($viplist as $vip => $address) {
1430
		if ($fif == get_configured_vip_interface($vip)) {
1431
			$list[$vip] = "$address";
1432
			if (get_vip_descr($address)) {
1433
				$list[$vip] .= " (". get_vip_descr($address) .")";
1434
			}
1435
		}
1436
	}
1437

    
1438
	return($list);
1439
}
1440

    
1441
function get_configured_vip_list($family = 'all', $type = VIP_ALL) {
1442
	global $config;
1443

    
1444
	$list = array();
1445
	if (!array_key_exists('virtualip', $config) ||
1446
		!is_array($config['virtualip']) ||
1447
	    !is_array($config['virtualip']['vip']) ||
1448
	    empty($config['virtualip']['vip'])) {
1449
		return ($list);
1450
	}
1451

    
1452
	$viparr = &$config['virtualip']['vip'];
1453
	foreach ($viparr as $vip) {
1454

    
1455
		if ($type == VIP_CARP) {
1456
			if ($vip['mode'] != "carp")
1457
				continue;
1458
		} elseif ($type == VIP_IPALIAS) {
1459
			if ($vip['mode'] != "ipalias")
1460
				continue;
1461
		} else {
1462
			if ($vip['mode'] != "carp" && $vip['mode'] != "ipalias")
1463
				continue;
1464
		}
1465

    
1466
		if ($family == 'all' ||
1467
		    ($family == 'inet' && is_ipaddrv4($vip['subnet'])) ||
1468
		    ($family == 'inet6' && is_ipaddrv6($vip['subnet']))) {
1469
			$list["_vip{$vip['uniqid']}"] = $vip['subnet'];
1470
		}
1471
	}
1472
	return ($list);
1473
}
1474

    
1475
function get_configured_vip($vipinterface = '') {
1476

    
1477
	return (get_configured_vip_detail($vipinterface, 'all', 'vip'));
1478
}
1479

    
1480
function get_configured_vip_interface($vipinterface = '') {
1481

    
1482
	return (get_configured_vip_detail($vipinterface, 'all', 'iface'));
1483
}
1484

    
1485
function get_configured_vip_ipv4($vipinterface = '') {
1486

    
1487
	return (get_configured_vip_detail($vipinterface, 'inet', 'ip'));
1488
}
1489

    
1490
function get_configured_vip_ipv6($vipinterface = '') {
1491

    
1492
	return (get_configured_vip_detail($vipinterface, 'inet6', 'ip'));
1493
}
1494

    
1495
function get_configured_vip_subnetv4($vipinterface = '') {
1496

    
1497
	return (get_configured_vip_detail($vipinterface, 'inet', 'subnet'));
1498
}
1499

    
1500
function get_configured_vip_subnetv6($vipinterface = '') {
1501

    
1502
	return (get_configured_vip_detail($vipinterface, 'inet6', 'subnet'));
1503
}
1504

    
1505
function get_configured_vip_detail($vipinterface = '', $family = 'inet', $what = 'ip') {
1506
	global $config;
1507

    
1508
	if (empty($vipinterface) ||
1509
	    !is_array($config['virtualip']) ||
1510
	    !is_array($config['virtualip']['vip']) ||
1511
	    empty($config['virtualip']['vip'])) {
1512
		return (NULL);
1513
	}
1514

    
1515
	$viparr = &$config['virtualip']['vip'];
1516
	foreach ($viparr as $vip) {
1517
		if ($vip['mode'] != "carp" && $vip['mode'] != "ipalias") {
1518
			continue;
1519
		}
1520

    
1521
		if ($vipinterface != "_vip{$vip['uniqid']}") {
1522
			continue;
1523
		}
1524

    
1525
		switch ($what) {
1526
			case 'subnet':
1527
				if ($family == 'inet' && is_ipaddrv4($vip['subnet']))
1528
					return ($vip['subnet_bits']);
1529
				else if ($family == 'inet6' && is_ipaddrv6($vip['subnet']))
1530
					return ($vip['subnet_bits']);
1531
				break;
1532
			case 'iface':
1533
				return ($vip['interface']);
1534
				break;
1535
			case 'vip':
1536
				return ($vip);
1537
				break;
1538
			case 'ip':
1539
			default:
1540
				if ($family == 'inet' && is_ipaddrv4($vip['subnet'])) {
1541
					return ($vip['subnet']);
1542
				} else if ($family == 'inet6' && is_ipaddrv6($vip['subnet'])) {
1543
					return ($vip['subnet']);
1544
				}
1545
				break;
1546
		}
1547
		break;
1548
	}
1549

    
1550
	return (NULL);
1551
}
1552

    
1553
/* comparison function for sorting by the order in which interfaces are normally created */
1554
function compare_interface_friendly_names($a, $b) {
1555
	if ($a == $b) {
1556
		return 0;
1557
	} else if ($a == 'wan') {
1558
		return -1;
1559
	} else if ($b == 'wan') {
1560
		return 1;
1561
	} else if ($a == 'lan') {
1562
		return -1;
1563
	} else if ($b == 'lan') {
1564
		return 1;
1565
	}
1566

    
1567
	return strnatcmp($a, $b);
1568
}
1569

    
1570
/**
1571
 * Get the configured interfaces list
1572
 *
1573
 * @param bool $with_disabled Include disabled interfaces
1574
 *
1575
 * @return array
1576
 */
1577
function get_configured_interface_list(bool $with_disabled = false) : array
1578
{
1579
	$iflist = [];
1580
	foreach (config_get_path('interfaces', []) as $if => $if_detail) {
1581
		if ($with_disabled || isset($if_detail['enable'])) {
1582
			$iflist[$if] = $if;
1583
		}
1584
	}
1585

    
1586
	return ($iflist);
1587
}
1588

    
1589
/**
1590
 * Return the configured (and real) interfaces list.
1591
 *
1592
 * @param bool $with_disabled Include disabled interfaces
1593
 *
1594
 * @return array
1595
 */
1596
function get_configured_interface_list_by_realif(bool $with_disabled = false) : array
1597
{
1598
	$iflist = [];
1599
	foreach (config_get_path('interfaces', []) as $if => $if_detail) {
1600
		if ($with_disabled || isset($if_detail['enable'])) {
1601
			$tmpif = get_real_interface($if);
1602
			if (empty($tmpif)) {
1603
				continue;
1604
			}
1605
			$iflist[$tmpif] = $if;
1606
		}
1607
	}
1608

    
1609
	return ($iflist);
1610
}
1611

    
1612
/**
1613
 * Return the configured interfaces list with their description.
1614
 *
1615
 * @param bool $with_disabled Include disabled interfaces
1616
 *
1617
 * @return array
1618
 */
1619
function get_configured_interface_with_descr(bool $with_disabled = false) : array
1620
{
1621
	global $user_settings;
1622

    
1623
	$iflist = [];
1624
	foreach (config_get_path('interfaces', []) as $if => $if_detail) {
1625
		if ($with_disabled || isset($if_detail['enable'])) {
1626
			$iflist[$if] = strtoupper(array_get_path($if_detail, 'descr', $if));
1627
		}
1628
	}
1629

    
1630
	if (is_array($user_settings)
1631
	    && array_get_path($user_settings, 'webgui/interfacessort')) {
1632
		asort($iflist);
1633
	}
1634

    
1635
	return ($iflist);
1636
}
1637

    
1638
/*
1639
 *   get_configured_ip_addresses() - Return a list of all configured
1640
 *   IPv4 addresses.
1641
 *
1642
 */
1643
function get_configured_ip_addresses() {
1644
	global $config;
1645

    
1646
	if (!function_exists('get_interface_ip')) {
1647
		require_once("interfaces.inc");
1648
	}
1649
	$ip_array = array();
1650
	$interfaces = get_configured_interface_list();
1651
	if (is_array($interfaces)) {
1652
		foreach ($interfaces as $int) {
1653
			$ipaddr = get_interface_ip($int);
1654
			$ip_array[$int] = $ipaddr;
1655
		}
1656
	}
1657
	$interfaces = get_configured_vip_list('inet');
1658
	if (is_array($interfaces)) {
1659
		foreach ($interfaces as $int => $ipaddr) {
1660
			$ip_array[$int] = $ipaddr;
1661
		}
1662
	}
1663

    
1664
	/* pppoe server */
1665
	if (is_array($config['pppoes']) && is_array($config['pppoes']['pppoe'])) {
1666
		foreach ($config['pppoes']['pppoe'] as $pppoe) {
1667
			if ($pppoe['mode'] == "server") {
1668
				if (is_ipaddr($pppoe['localip'])) {
1669
					$int = "poes". $pppoe['pppoeid'];
1670
					$ip_array[$int] = $pppoe['localip'];
1671
				}
1672
			}
1673
		}
1674
	}
1675

    
1676
	return $ip_array;
1677
}
1678

    
1679
/*
1680
 *   get_configured_ipv6_addresses() - Return a list of all configured
1681
 *   IPv6 addresses.
1682
 *
1683
 */
1684
function get_configured_ipv6_addresses($linklocal_fallback = false) {
1685
	require_once("interfaces.inc");
1686
	$ipv6_array = array();
1687
	$interfaces = get_configured_interface_list();
1688
	if (is_array($interfaces)) {
1689
		foreach ($interfaces as $int) {
1690
			$ipaddrv6 = text_to_compressed_ip6(get_interface_ipv6($int, false, $linklocal_fallback));
1691
			$ipv6_array[$int] = $ipaddrv6;
1692
		}
1693
	}
1694
	$interfaces = get_configured_vip_list('inet6');
1695
	if (is_array($interfaces)) {
1696
		foreach ($interfaces as $int => $ipaddrv6) {
1697
			$ipv6_array[$int] = text_to_compressed_ip6($ipaddrv6);
1698
		}
1699
	}
1700
	return $ipv6_array;
1701
}
1702

    
1703
/*
1704
 *   get_interface_list() - Return a list of all physical interfaces
1705
 *   along with MAC and status.
1706
 *
1707
 *   $mode = "active" - use ifconfig -lu
1708
 *           "media"  - use ifconfig to check physical connection
1709
 *			status (much slower)
1710
 */
1711
function get_interface_list($mode = "active", $keyby = "physical", $vfaces = false) {
1712
	global $config;
1713
	$upints = array();
1714
	/* get a list of virtual interface types */
1715
	if (!$vfaces) {
1716
		$vfaces = array(
1717
				'bridge',
1718
				'ppp',
1719
				'pppoe',
1720
				'poes',
1721
				'pptp',
1722
				'l2tp',
1723
				'sl',
1724
				'gif',
1725
				'gre',
1726
				'faith',
1727
				'lo',
1728
				'ng',
1729
				'_vlan',
1730
				'_wlan',
1731
				'pflog',
1732
				'plip',
1733
				'pfsync',
1734
				'enc',
1735
				'tun',
1736
				'lagg',
1737
				'vip'
1738
		);
1739
	} else {
1740
		$vfaces = array(
1741
				'bridge',
1742
				'poes',
1743
				'sl',
1744
				'faith',
1745
				'lo',
1746
				'ng',
1747
				'_vlan',
1748
				'_wlan',
1749
				'pflog',
1750
				'plip',
1751
				'pfsync',
1752
				'enc',
1753
				'tun',
1754
				'lagg',
1755
				'vip',
1756
				'l2tps'
1757
		);
1758
	}
1759
	switch ($mode) {
1760
		case "active":
1761
			$upints = pfSense_interface_listget(IFF_UP);
1762
			break;
1763
		case "media":
1764
			$intlist = pfSense_interface_listget();
1765
			$ifconfig = [];
1766
			exec("/sbin/ifconfig -a", $ifconfig);
1767
			$ifstatus = preg_grep('/status:/', $ifconfig);
1768
			foreach ($ifstatus as $status) {
1769
				$int = array_shift($intlist);
1770
				if (stristr($status, "active")) {
1771
					$upints[] = $int;
1772
				}
1773
			}
1774
			break;
1775
		default:
1776
			$upints = pfSense_interface_listget();
1777
			break;
1778
	}
1779
	/* build interface list with netstat */
1780
	$linkinfo = [];
1781
	exec("/usr/bin/netstat -inW -f link | awk '{ print $1, $4 }'", $linkinfo);
1782
	array_shift($linkinfo);
1783
	/* build ip address list with netstat */
1784
	$ipinfo = [];
1785
	exec("/usr/bin/netstat -inW -f inet | awk '{ print $1, $4 }'", $ipinfo);
1786
	array_shift($ipinfo);
1787
	foreach ($linkinfo as $link) {
1788
		$friendly = "";
1789
		$alink = explode(" ", $link);
1790
		$ifname = rtrim(trim($alink[0]), '*');
1791
		/* trim out all numbers before checking for vfaces */
1792
		if (!in_array(array_shift(preg_split('/(\d-)*\d$/', $ifname)), $vfaces) &&
1793
		    interface_is_vlan($ifname) == NULL &&
1794
		    interface_is_qinq($ifname) == NULL &&
1795
		    !stristr($ifname, "_wlan") &&
1796
		    !stristr($ifname, "_stf")) {
1797
			$toput = array(
1798
					"mac" => trim($alink[1]),
1799
					"up" => in_array($ifname, $upints)
1800
				);
1801
			foreach ($ipinfo as $ip) {
1802
				$aip = explode(" ", $ip);
1803
				if ($aip[0] == $ifname) {
1804
					$toput['ipaddr'] = $aip[1];
1805
				}
1806
			}
1807
			if (is_array($config['interfaces'])) {
1808
				foreach ($config['interfaces'] as $name => $int) {
1809
					if ($int['if'] == $ifname) {
1810
						$friendly = $name;
1811
					}
1812
				}
1813
			}
1814
			switch ($keyby) {
1815
			case "physical":
1816
				if ($friendly != "") {
1817
					$toput['friendly'] = $friendly;
1818
				}
1819
				$dmesg_arr = array();
1820
				exec("/sbin/dmesg |grep $ifname | head -n1", $dmesg_arr);
1821
				preg_match_all("/<(.*?)>/i", $dmesg_arr[0], $dmesg);
1822
				$toput['dmesg'] = $dmesg[1][0];
1823
				$iflist[$ifname] = $toput;
1824
				break;
1825
			case "ppp":
1826

    
1827
			case "friendly":
1828
				if ($friendly != "") {
1829
					$toput['if'] = $ifname;
1830
					$iflist[$friendly] = $toput;
1831
				}
1832
				break;
1833
			}
1834
		}
1835
	}
1836
	return $iflist;
1837
}
1838

    
1839
function get_lagg_interface_list() {
1840
	global $config;
1841

    
1842
	$plist = array();
1843
	if (isset($config['laggs']['lagg']) && is_array($config['laggs']['lagg'])) {
1844
		foreach ($config['laggs']['lagg'] as $lagg) {
1845
			$lagg['mac'] = get_interface_mac($lagg['laggif']);
1846
			$lagg['islagg'] = true;
1847
			$plist[$lagg['laggif']] = $lagg;
1848
		}
1849
	}
1850

    
1851
	return ($plist);
1852
}
1853

    
1854
/****f* util/log_error
1855
* NAME
1856
*   log_error  - Sends a string to syslog.
1857
* INPUTS
1858
*   $error     - string containing the syslog message.
1859
* RESULT
1860
*   null
1861
******/
1862
function log_error($error) {
1863
	global $g;
1864
	$page = $_SERVER['SCRIPT_NAME'];
1865
	if (empty($page)) {
1866
		$files = get_included_files();
1867
		$page = basename($files[0]);
1868
	}
1869
	syslog(LOG_ERR, "$page: $error");
1870
	if (g_get('debug')) {
1871
		syslog(LOG_WARNING, var_export(debug_backtrace()));
1872
	}
1873
	return;
1874
}
1875

    
1876
/****f* util/log_auth
1877
* NAME
1878
*   log_auth   - Sends a string to syslog as LOG_AUTH facility
1879
* INPUTS
1880
*   $error     - string containing the syslog message.
1881
* RESULT
1882
*   null
1883
******/
1884
function log_auth($error) {
1885
	global $g;
1886
	$page = $_SERVER['SCRIPT_NAME'];
1887
	$level = config_path_enabled('system/webgui', 'quietlogin') ? LOG_NOTICE|LOG_AUTH : LOG_AUTH;
1888
	syslog($level, "{$page}: {$error}");
1889
	if (g_get('debug')) {
1890
		syslog(LOG_WARNING, var_export(debug_backtrace()));
1891
	}
1892
	return;
1893
}
1894

    
1895
/****f* util/exec_command
1896
 * NAME
1897
 *   exec_command - Execute a command and return a string of the result.
1898
 * INPUTS
1899
 *   $command   - String of the command to be executed.
1900
 * RESULT
1901
 *   String containing the command's result.
1902
 * NOTES
1903
 *   This function returns the command's stdout and stderr.
1904
 ******/
1905
function exec_command($command) {
1906
	$output = array();
1907
	exec($command . ' 2>&1', $output);
1908
	return(implode("\n", $output));
1909
}
1910

    
1911
/* wrapper for exec()
1912
   Executes in background or foreground.
1913
   For background execution, returns PID of background process to allow calling code control */
1914
function mwexec($command, $nologentry = false, $clearsigmask = false, $background = false) {
1915
	global $g;
1916
	$retval = 0;
1917

    
1918
	if (g_get('debug')) {
1919
		if (!$_SERVER['REMOTE_ADDR']) {
1920
			echo "mwexec(): $command" . ($background ? " [BG]":"") . "\n";
1921
		}
1922
	}
1923
	if ($clearsigmask) {
1924
		$oldset = array();
1925
		pcntl_sigprocmask(SIG_SETMASK, array(), $oldset);
1926
	}
1927

    
1928
	if ($background) {
1929
		// start background process and return PID
1930
		$retval = exec("/usr/bin/nohup $command > /dev/null 2>&1 & echo $!");
1931
	} else {
1932
		// run in foreground, and (optionally) log if nonzero return
1933
		$outputarray = array();
1934
		exec("$command 2>&1", $outputarray, $retval);
1935
		if (($retval <> 0) && (!$nologentry || isset($config['system']['developerspew']))) {
1936
			log_error(sprintf(gettext("The command '%1\$s' returned exit code '%2\$d', the output was '%3\$s' "), $command, $retval, implode(" ", $outputarray)));
1937
		}
1938
	}
1939

    
1940
	if ($clearsigmask) {
1941
		pcntl_sigprocmask(SIG_SETMASK, $oldset);
1942
	}
1943

    
1944
	return $retval;
1945
}
1946

    
1947
/* wrapper for exec() in background */
1948
function mwexec_bg($command, $clearsigmask = false) {
1949
	return mwexec($command, false, $clearsigmask, true);
1950
}
1951

    
1952
/*
1953
 * Unlink a file, or pattern-match of a file, if it exists
1954
 *
1955
 * If the file/path contains glob() compatible wildcards, all matching files
1956
 * will be unlinked.
1957
 * Any warning/errors are suppressed (e.g. no matching files to delete)
1958
 * If there are matching file(s) and they were all unlinked OK, then return
1959
 * true.  Otherwise return false (the requested file(s) did not exist, or
1960
 * could not be deleted), this allows the caller to know if they were the one
1961
 * to successfully delete the file(s).
1962
 */
1963
function unlink_if_exists($fn) {
1964
	$to_do = glob($fn);
1965
	if (is_array($to_do) && count($to_do) > 0) {
1966
		// Returns an array of boolean indicating if each unlink worked
1967
		$results = @array_map("unlink", $to_do);
1968
		// If there is no false in the array, then all went well
1969
		$result = !in_array(false, $results, true);
1970
	} else {
1971
		$result = @unlink($fn);
1972
	}
1973
	return $result;
1974
}
1975

    
1976
/* make a global alias table (for faster lookups) */
1977
function alias_make_table() {
1978
	global $aliastable;
1979

    
1980
	$aliastable = array();
1981

    
1982
	foreach (config_get_path('aliases/alias', []) as $alias) {
1983
		if (!is_array($alias) || empty($alias)) {
1984
			continue;
1985
		}
1986
		if ($alias['name']) {
1987
			$aliastable[$alias['name']] = $alias['address'];
1988
		}
1989
	}
1990
}
1991

    
1992
/* check if an alias exists */
1993
function is_alias($name) {
1994
	global $aliastable;
1995

    
1996
	return isset($aliastable[$name]);
1997
}
1998

    
1999
function alias_get_type($name) {
2000

    
2001
	foreach (config_get_path('aliases/alias', []) as $alias) {
2002
		if ($name == $alias['name']) {
2003
			return $alias['type'];
2004
		}
2005
	}
2006

    
2007
	return "";
2008
}
2009

    
2010
/* expand a host or network alias, if necessary */
2011
function alias_expand($name) {
2012
	global $aliastable;
2013
	$urltable_prefix = "/var/db/aliastables/";
2014
	$urltable_filename = $urltable_prefix . $name . ".txt";
2015

    
2016
	if (isset($aliastable[$name])) {
2017
		// alias names cannot be strictly numeric. redmine #4289
2018
		if (is_numericint($name)) {
2019
			return null;
2020
		}
2021
		/*
2022
		 * make sure if it's a ports alias, it actually exists.
2023
		 * redmine #5845
2024
		 */
2025
		foreach (config_get_path('aliases/alias', []) as $alias) {
2026
			if ($alias['name'] == $name) {
2027
				if ($alias['type'] == "urltable_ports") {
2028
					if (is_URL($alias['url']) &&
2029
					    file_exists($urltable_filename) &&
2030
					    !empty(trim(file_get_contents($urltable_filename)))) {
2031
						return "\${$name}";
2032
					} else {
2033
						return null;
2034
					}
2035
				}
2036
			}
2037
		}
2038
		return "\${$name}";
2039
	} else if (is_ipaddr($name) || is_subnet($name) ||
2040
	    is_port_or_range($name)) {
2041
		return "{$name}";
2042
	} else {
2043
		return null;
2044
	}
2045
}
2046

    
2047
function alias_expand_urltable($name) {
2048
	$urltable_prefix = "/var/db/aliastables/";
2049
	$urltable_filename = $urltable_prefix . $name . ".txt";
2050

    
2051
	foreach (config_get_path('aliases/alias', []) as $alias) {
2052
		if (!preg_match("/urltable/i", $alias['type']) ||
2053
		    ($alias['name'] != $name)) {
2054
			continue;
2055
		}
2056

    
2057
		if (is_URL($alias["url"]) && file_exists($urltable_filename)) {
2058
			if (!filesize($urltable_filename)) {
2059
				// file exists, but is empty, try to sync
2060
				send_event("service sync alias {$name}");
2061
			}
2062
			return $urltable_filename;
2063
		} else {
2064
			send_event("service sync alias {$name}");
2065
			break;
2066
		}
2067
	}
2068
	return null;
2069
}
2070

    
2071
/* obtain MAC address given an IP address by looking at the ARP/NDP table */
2072
function arp_get_mac_by_ip($ip, $do_ping = true) {
2073
	unset($macaddr);
2074
	$retval = 1;
2075
	switch (is_ipaddr($ip)) {
2076
		case 4:
2077
			if ($do_ping === true) {
2078
				mwexec("/sbin/ping -c 1 -t 1 " . escapeshellarg($ip), true);
2079
			}
2080
			$macaddr = exec("/usr/sbin/arp -n " . escapeshellarg($ip) . " | /usr/bin/awk '{print $4}'", $output, $retval);
2081
			break;
2082
		case 6:
2083
			if ($do_ping === true) {
2084
				mwexec("/sbin/ping6 -c 1 -X 1 " . escapeshellarg($ip), true);
2085
			}
2086
			$macaddr = exec("/usr/sbin/ndp -n " . escapeshellarg($ip) . " | /usr/bin/awk '{print $2}'", $output, $retval);
2087
			break;
2088
	}
2089
	if ($retval == 0 && is_macaddr($macaddr)) {
2090
		return $macaddr;
2091
	} else {
2092
		return false;
2093
	}
2094
}
2095

    
2096
/* return a fieldname that is safe for xml usage */
2097
function xml_safe_fieldname($fieldname) {
2098
	$replace = array(
2099
	    '/', '-', ' ', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')',
2100
	    '_', '+', '=', '{', '}', '[', ']', '|', '/', '<', '>', '?',
2101
	    ':', ',', '.', '\'', '\\'
2102
	);
2103
	return strtolower(str_replace($replace, "", $fieldname));
2104
}
2105

    
2106
function mac_format($clientmac) {
2107
	global $config, $cpzone;
2108

    
2109
	$mac = explode(":", $clientmac);
2110
	$mac_format = $cpzone ? $config['captiveportal'][$cpzone]['radmac_format'] : false;
2111

    
2112
	switch ($mac_format) {
2113
		case 'singledash':
2114
			return "$mac[0]$mac[1]$mac[2]-$mac[3]$mac[4]$mac[5]";
2115

    
2116
		case 'ietf':
2117
			return "$mac[0]-$mac[1]-$mac[2]-$mac[3]-$mac[4]-$mac[5]";
2118

    
2119
		case 'cisco':
2120
			return "$mac[0]$mac[1].$mac[2]$mac[3].$mac[4]$mac[5]";
2121

    
2122
		case 'unformatted':
2123
			return "$mac[0]$mac[1]$mac[2]$mac[3]$mac[4]$mac[5]";
2124

    
2125
		default:
2126
			return $clientmac;
2127
	}
2128
}
2129

    
2130
function resolve_retry($hostname, $protocol = 'inet') {
2131
	$retries = 10;
2132
	$numrecords = 1;
2133
	$recresult = array();
2134

    
2135
	switch ($protocol) {
2136
		case 'any':
2137
			$checkproto = 'is_ipaddr';
2138
			$dnsproto = DNS_ANY;
2139
			$dnstype = array('A', 'AAAA');
2140
			break;
2141
		case 'inet6':
2142
			$checkproto = 'is_ipaddrv6';
2143
			$dnsproto = DNS_AAAA;
2144
			$dnstype = array('AAAA');
2145
			break;
2146
		case 'inet':
2147
		default:
2148
			$checkproto = 'is_ipaddrv4';
2149
			$dnsproto = DNS_A;
2150
			$dnstype = array('A');
2151
			break;
2152
	}
2153

    
2154
	for ($i = 0; $i < $retries; $i++) {
2155
		if ($checkproto($hostname)) {
2156
			return $hostname;
2157
		}
2158

    
2159
		$dnsresult = @dns_get_record($hostname, $dnsproto);
2160

    
2161
		if (!empty($dnsresult)) {
2162
			foreach ($dnsresult as $ip) {
2163
				if (is_array($ip)) {
2164
					if (in_array($ip['type'], $dnstype)) {
2165
						if ($checkproto($ip['ip'])) {
2166
							$recresult[] = $ip['ip'];
2167
						}
2168

    
2169
						if ($checkproto($ip['ipv6'])) {
2170
							$recresult[] = $ip['ipv6'];
2171
						}
2172
					}
2173
				}
2174
			}
2175
		}
2176

    
2177
		// Return on success
2178
		if (!empty($recresult)) {
2179
			if ($numrecords == 1) {
2180
				return $recresult[0];
2181
			} else {
2182
				return array_slice($recresult, 0, $numrecords);
2183
			}
2184
		}
2185

    
2186
		usleep(100000);
2187
	}
2188

    
2189
	return false;
2190
}
2191

    
2192
function format_bytes($bytes) {
2193
	if ($bytes >= 1099511627776) {
2194
		return sprintf("%.2f TiB", $bytes/1099511627776);
2195
	} else if ($bytes >= 1073741824) {
2196
		return sprintf("%.2f GiB", $bytes/1073741824);
2197
	} else if ($bytes >= 1048576) {
2198
		return sprintf("%.2f MiB", $bytes/1048576);
2199
	} else if ($bytes >= 1024) {
2200
		return sprintf("%.0f KiB", $bytes/1024);
2201
	} else {
2202
		return sprintf("%d B", $bytes);
2203
	}
2204
}
2205

    
2206
function format_number(int $num, int $precision = 3): string
2207
{
2208
    $units = ['', 'K', 'M', 'G', 'T'];
2209
    for ($i = 0; $num >= 1000; $i++) {
2210
        $num /= 1000;
2211
    }
2212
    return (round($num, $precision) . $units[$i]);
2213
}
2214

    
2215
function unformat_number($formated_num) {
2216
	$num = strtoupper($formated_num);
2217

    
2218
	if ( strpos($num,"T") !== false ) {
2219
		$num = str_replace("T","",$num) * 1000 * 1000 * 1000 * 1000;
2220
	} else if ( strpos($num,"G") !== false ) {
2221
		$num = str_replace("G","",$num) * 1000 * 1000 * 1000;
2222
	} else if ( strpos($num,"M") !== false ) {
2223
		$num = str_replace("M","",$num) * 1000 * 1000;
2224
	} else if ( strpos($num,"K") !== false ) {
2225
		$num = str_replace("K","",$num) * 1000;
2226
	}
2227

    
2228
	return $num;
2229
}
2230

    
2231
function update_filter_reload_status($text, $new=false) {
2232
	global $g;
2233

    
2234
	if ($new) {
2235
		file_put_contents("{$g['varrun_path']}/filter_reload_status", $text  . PHP_EOL);
2236
	} else {
2237
		file_put_contents("{$g['varrun_path']}/filter_reload_status", $text  . PHP_EOL, FILE_APPEND);
2238
	}
2239
}
2240

    
2241
/****** util/return_dir_as_array
2242
 * NAME
2243
 *   return_dir_as_array - Return a directory's contents as an array.
2244
 * INPUTS
2245
 *   $dir          - string containing the path to the desired directory.
2246
 *   $filter_regex - string containing a regular expression to filter file names. Default empty.
2247
 * RESULT
2248
 *   $dir_array - array containing the directory's contents. This array will be empty if the path specified is invalid.
2249
 ******/
2250
function return_dir_as_array($dir, $filter_regex = '') {
2251
	$dir_array = array();
2252
	if (is_dir($dir)) {
2253
		if ($dh = opendir($dir)) {
2254
			while (($file = readdir($dh)) !== false) {
2255
				if (($file == ".") || ($file == "..")) {
2256
					continue;
2257
				}
2258

    
2259
				if (empty($filter_regex) || preg_match($filter_regex, $file)) {
2260
					array_push($dir_array, $file);
2261
				}
2262
			}
2263
			closedir($dh);
2264
		}
2265
	}
2266
	return $dir_array;
2267
}
2268

    
2269
function run_plugins($directory) {
2270
	/* process packager manager custom rules */
2271
	$files = return_dir_as_array($directory);
2272
	if (is_array($files)) {
2273
		foreach ($files as $file) {
2274
			if (stristr($file, ".sh") == true) {
2275
				mwexec($directory . $file . " start");
2276
			} else if (!is_dir($directory . "/" . $file) && stristr($file, ".inc")) {
2277
				require_once($directory . "/" . $file);
2278
			}
2279
		}
2280
	}
2281
}
2282

    
2283
/*
2284
 *    safe_mkdir($path, $mode = 0755)
2285
 *    create directory if it doesn't already exist and isn't a file!
2286
 */
2287
function safe_mkdir($path, $mode = 0755) {
2288
	if (!is_file($path) && !is_dir($path)) {
2289
		return @mkdir($path, $mode, true);
2290
	} else {
2291
		return false;
2292
	}
2293
}
2294

    
2295
/*
2296
 * get_sysctl($names)
2297
 * Get values of sysctl OID's listed in $names (accepts an array or a single
2298
 * name) and return an array of key/value pairs set for those that exist
2299
 */
2300
function get_sysctl($names) {
2301
	if (empty($names)) {
2302
		return array();
2303
	}
2304

    
2305
	if (is_array($names)) {
2306
		$name_list = array();
2307
		foreach ($names as $name) {
2308
			$name_list[] = escapeshellarg($name);
2309
		}
2310
	} else {
2311
		$name_list = array(escapeshellarg($names));
2312
	}
2313

    
2314
	exec("/sbin/sysctl -iq " . implode(" ", $name_list), $output);
2315
	$values = array();
2316
	foreach ($output as $line) {
2317
		$line = explode(": ", $line, 2);
2318
		if (count($line) == 2) {
2319
			$values[$line[0]] = $line[1];
2320
		}
2321
	}
2322

    
2323
	return $values;
2324
}
2325

    
2326
/*
2327
 * get_single_sysctl($name)
2328
 * Wrapper for get_sysctl() to simplify read of a single sysctl value
2329
 * return the value for sysctl $name or empty string if it doesn't exist
2330
 */
2331
function get_single_sysctl($name) {
2332
	if (empty($name)) {
2333
		return "";
2334
	}
2335

    
2336
	$value = get_sysctl($name);
2337
	if (empty($value) || !isset($value[$name])) {
2338
		return "";
2339
	}
2340

    
2341
	return $value[$name];
2342
}
2343

    
2344
/*
2345
 * set_sysctl($value_list)
2346
 * Set sysctl OID's listed as key/value pairs and return
2347
 * an array with keys set for those that succeeded
2348
 */
2349
function set_sysctl($values) {
2350
	if (empty($values)) {
2351
		return array();
2352
	}
2353

    
2354
	$value_list = array();
2355
	foreach ($values as $key => $value) {
2356
		$value_list[] = escapeshellarg($key) . "=" . escapeshellarg($value);
2357
	}
2358

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

    
2361
	/* Retry individually if failed (one or more read-only) */
2362
	if ($success <> 0 && count($value_list) > 1) {
2363
		foreach ($value_list as $value) {
2364
			exec("/sbin/sysctl -iq " . $value, $output);
2365
		}
2366
	}
2367

    
2368
	$ret = array();
2369
	foreach ($output as $line) {
2370
		$line = explode(": ", $line, 2);
2371
		if (count($line) == 2) {
2372
			$ret[$line[0]] = true;
2373
		}
2374
	}
2375

    
2376
	return $ret;
2377
}
2378

    
2379
/*
2380
 * set_single_sysctl($name, $value)
2381
 * Wrapper to set_sysctl() to make it simple to set only one sysctl
2382
 * returns boolean meaning if it succeeded
2383
 */
2384
function set_single_sysctl($name, $value) {
2385
	if (empty($name)) {
2386
		return false;
2387
	}
2388

    
2389
	$result = set_sysctl(array($name => $value));
2390

    
2391
	if (!isset($result[$name]) || $result[$name] != $value) {
2392
		return false;
2393
	}
2394

    
2395
	return true;
2396
}
2397

    
2398
/*
2399
 *     get_memory()
2400
 *     returns an array listing the amount of
2401
 *     memory installed in the hardware
2402
 *     [0] net memory available for the OS (FreeBSD) after some is taken by BIOS, video or whatever - e.g. 235 MBytes
2403
 *     [1] real (actual) memory of the system, should be the size of the RAM card/s - e.g. 256 MBytes
2404
 */
2405
function get_memory() {
2406
	$physmem = get_single_sysctl("hw.physmem");
2407
	$realmem = get_single_sysctl("hw.realmem");
2408
	/* convert from bytes to megabytes */
2409
	return array(($physmem/1048576), ($realmem/1048576));
2410
}
2411

    
2412
function mute_kernel_msgs() {
2413
	if (config_path_enabled('system','enableserial')) {
2414
		return;
2415
	}
2416
	exec("/sbin/conscontrol mute on");
2417
}
2418

    
2419
function unmute_kernel_msgs() {
2420
	exec("/sbin/conscontrol mute off");
2421
}
2422

    
2423
function start_devd() {
2424
	global $g;
2425

    
2426
	/* Generate hints for the kernel loader. */
2427
	$module_paths = explode(";", get_single_sysctl("kern.module_path"));
2428
	foreach ($module_paths as $path) {
2429
		if (!is_dir($path) ||
2430
		    (($files = scandir($path)) == false)) {
2431
			continue;
2432
		}
2433
		$found = false;
2434
		foreach ($files as $file) {
2435
			if (strlen($file) > 3 &&
2436
			    strcasecmp(substr($file, -3), ".ko") == 0) {
2437
				$found = true;
2438
				break;
2439
			}
2440
		}
2441
		if ($found == false) {
2442
			continue;
2443
		}
2444
		$_gb = exec("/usr/sbin/kldxref $path");
2445
		unset($_gb);
2446
	}
2447

    
2448
	/* Use the undocumented -q options of devd to quiet its log spamming */
2449
	$_gb = exec("/sbin/devd -q -f /etc/{$g['product_name']}-devd.conf");
2450
	sleep(1);
2451
	unset($_gb);
2452
}
2453

    
2454
function is_interface_vlan_mismatch() {
2455
	foreach (config_get_path('vlans/vlan', []) as $vlan) {
2456
		if (substr($vlan['if'], 0, 4) == "lagg") {
2457
			return false;
2458
		}
2459
		if (does_interface_exist($vlan['if']) == false) {
2460
			return true;
2461
		}
2462
	}
2463

    
2464
	return false;
2465
}
2466

    
2467
function is_interface_mismatch() {
2468
	global $config, $g;
2469

    
2470
	$do_assign = false;
2471
	$i = 0;
2472
	$missing_interfaces = array();
2473
	if (is_array($config['interfaces'])) {
2474
		foreach ($config['interfaces'] as $ifcfg) {
2475
			if (interface_is_vlan($ifcfg['if']) != NULL ||
2476
			    interface_is_qinq($ifcfg['if']) != NULL ||
2477
			    preg_match("/^bridge|^cua|^enc|^gif|^gre|^ipsec|^l2tp|^lagg|^ngeth|^ovpn|^ppp|^pptp|^tap|^tun|^ue|vlan|^wg|_wlan|_\d{0,4}_\d{0,4}$/i", $ifcfg['if'])) {
2478
				// Do not check these interfaces.
2479
				$i++;
2480
				continue;
2481
			} else if (does_interface_exist($ifcfg['if']) == false) {
2482
				$missing_interfaces[] = $ifcfg['if'];
2483
				$do_assign = true;
2484
			} else {
2485
				$i++;
2486
			}
2487
		}
2488
	}
2489

    
2490
	/* VLAN/QinQ-only interface mismatch detection
2491
	 * see https://redmine.pfsense.org/issues/12170 */
2492
	init_config_arr(array('vlans', 'vlan'));
2493
	foreach ($config['vlans']['vlan'] as $vlan) {
2494
		if (!does_interface_exist($vlan['if']) &&
2495
		    !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'])) {
2496
			$missing_interfaces[] = $vlan['if'];
2497
			$do_assign = true;
2498
		}
2499
	}
2500
	init_config_arr(array('qinqs', 'qinqentry'));
2501
	foreach ($config['qinqs']['qinqentry'] as $qinq) {
2502
		if (!does_interface_exist($qinq['if']) &&
2503
		    !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'])) {
2504
			$missing_interfaces[] = $qinq['if'];
2505
			$do_assign = true;
2506
		}
2507
	}
2508

    
2509
	if (file_exists("{$g['tmp_path']}/assign_complete")) {
2510
		$do_assign = false;
2511
	}
2512

    
2513
	if (!empty($missing_interfaces) && $do_assign) {
2514
		file_put_contents("{$g['tmp_path']}/missing_interfaces", implode(' ', $missing_interfaces));
2515
	} else {
2516
		@unlink("{$g['tmp_path']}/missing_interfaces");
2517
	}
2518

    
2519
	return $do_assign;
2520
}
2521

    
2522
/* sync carp entries to other firewalls */
2523
function carp_sync_client() {
2524
	send_event("filter sync");
2525
}
2526

    
2527
/****f* util/isAjax
2528
 * NAME
2529
 *   isAjax - reports if the request is driven from prototype
2530
 * INPUTS
2531
 *   none
2532
 * RESULT
2533
 *   true/false
2534
 ******/
2535
function isAjax() {
2536
	return isset ($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest';
2537
}
2538

    
2539
/****f* util/timeout
2540
 * NAME
2541
 *   timeout - console input with timeout countdown. Note: erases 2 char of screen for timer. Leave space.
2542
 * INPUTS
2543
 *   optional, seconds to wait before timeout. Default 9 seconds.
2544
 * RESULT
2545
 *   returns 1 char of user input or null if no input.
2546
 ******/
2547
function timeout($timer = 9) {
2548
	while (!isset($key)) {
2549
		if ($timer >= 9) {
2550
			echo chr(8) . chr(8) . ($timer == 9 ? chr(32) : null) . "{$timer}";
2551
		} else {
2552
			echo chr(8). "{$timer}";
2553
		}
2554
		`/bin/stty -icanon min 0 time 25`;
2555
		$key = trim(`KEY=\`dd count=1 2>/dev/null\`; echo \$KEY`);
2556
		`/bin/stty icanon`;
2557
		if ($key == '') {
2558
			unset($key);
2559
		}
2560
		$timer--;
2561
		if ($timer == 0) {
2562
			break;
2563
		}
2564
	}
2565
	return $key;
2566
}
2567

    
2568
/****f* util/msort
2569
 * NAME
2570
 *   msort - sort array
2571
 * INPUTS
2572
 *   $array to be sorted, field to sort by, direction of sort
2573
 * RESULT
2574
 *   returns newly sorted array
2575
 ******/
2576
function msort($array, $id = "id", $sort_ascending = true) {
2577
	$temp_array = array();
2578
	if (!is_array($array)) {
2579
		return $temp_array;
2580
	}
2581
	while (count($array)>0) {
2582
		$lowest_id = 0;
2583
		$index = 0;
2584
		foreach ($array as $item) {
2585
			if (isset($item[$id])) {
2586
				if ($array[$lowest_id][$id]) {
2587
					if (strtolower($item[$id]) < strtolower($array[$lowest_id][$id])) {
2588
						$lowest_id = $index;
2589
					}
2590
				}
2591
			}
2592
			$index++;
2593
		}
2594
		$temp_array[] = $array[$lowest_id];
2595
		$array = array_merge(array_slice($array, 0, $lowest_id), array_slice($array, $lowest_id + 1));
2596
	}
2597
	if ($sort_ascending) {
2598
		return $temp_array;
2599
	} else {
2600
		return array_reverse($temp_array);
2601
	}
2602
}
2603

    
2604
/****f* util/is_URL
2605
 * NAME
2606
 *   is_URL
2607
 * INPUTS
2608
 *   $url: string to check
2609
 *   $httponly: Only allow HTTP or HTTPS scheme
2610
 * RESULT
2611
 *   Returns true if item is a URL
2612
 ******/
2613
function is_URL($url, $httponly = false) {
2614
	if (filter_var($url, FILTER_VALIDATE_URL)) {
2615
		if ($httponly) {
2616
			return in_array(strtolower(parse_url($url, PHP_URL_SCHEME)), ['http', 'https']);
2617
		}
2618
		return true;
2619
	}
2620
	return false;
2621
}
2622

    
2623
function is_file_included($file = "") {
2624
	$files = get_included_files();
2625
	if (in_array($file, $files)) {
2626
		return true;
2627
	}
2628

    
2629
	return false;
2630
}
2631

    
2632
/*
2633
 * Replace a value on a deep associative array using regex
2634
 */
2635
function array_replace_values_recursive($data, $match, $replace) {
2636
	if (empty($data)) {
2637
		return $data;
2638
	}
2639

    
2640
	if (is_string($data)) {
2641
		$data = preg_replace("/{$match}/", $replace, $data);
2642
	} else if (is_array($data)) {
2643
		foreach ($data as $k => $v) {
2644
			$data[$k] = array_replace_values_recursive($v, $match, $replace);
2645
		}
2646
	}
2647

    
2648
	return $data;
2649
}
2650

    
2651
/*
2652
	This function was borrowed from a comment on PHP.net at the following URL:
2653
	https://www.php.net/manual/en/function.array-merge-recursive.php#73843
2654
 */
2655
function array_merge_recursive_unique($array0, $array1) {
2656

    
2657
	$arrays = func_get_args();
2658
	$remains = $arrays;
2659

    
2660
	// We walk through each arrays and put value in the results (without
2661
	// considering previous value).
2662
	$result = array();
2663

    
2664
	// loop available array
2665
	foreach ($arrays as $array) {
2666

    
2667
		// The first remaining array is $array. We are processing it. So
2668
		// we remove it from remaining arrays.
2669
		array_shift($remains);
2670

    
2671
		// We don't care non array param, like array_merge since PHP 5.0.
2672
		if (is_array($array)) {
2673
			// Loop values
2674
			foreach ($array as $key => $value) {
2675
				if (is_array($value)) {
2676
					// we gather all remaining arrays that have such key available
2677
					$args = array();
2678
					foreach ($remains as $remain) {
2679
						if (array_key_exists($key, $remain)) {
2680
							array_push($args, $remain[$key]);
2681
						}
2682
					}
2683

    
2684
					if (count($args) > 2) {
2685
						// put the recursion
2686
						$result[$key] = call_user_func_array(__FUNCTION__, $args);
2687
					} else {
2688
						foreach ($value as $vkey => $vval) {
2689
							if (!is_array($result[$key])) {
2690
								$result[$key] = array();
2691
							}
2692
							$result[$key][$vkey] = $vval;
2693
						}
2694
					}
2695
				} else {
2696
					// simply put the value
2697
					$result[$key] = $value;
2698
				}
2699
			}
2700
		}
2701
	}
2702
	return $result;
2703
}
2704

    
2705

    
2706
/*
2707
 * converts a string like "a,b,c,d"
2708
 * into an array like array("a" => "b", "c" => "d")
2709
 */
2710
function explode_assoc($delimiter, $string) {
2711
	$array = explode($delimiter, $string);
2712
	$result = array();
2713
	$numkeys = floor(count($array) / 2);
2714
	for ($i = 0; $i < $numkeys; $i += 1) {
2715
		$result[$array[$i * 2]] = $array[$i * 2 + 1];
2716
	}
2717
	return $result;
2718
}
2719

    
2720
/*
2721
 * Given a string of text with some delimiter, look for occurrences
2722
 * of some string and replace all of those.
2723
 * $text - the text string (e.g. "abc,defg,x123,ipv4,xyz")
2724
 * $delimiter - the delimiter (e.g. ",")
2725
 * $element - the element to match (e.g. "defg")
2726
 * $replacement - the string to replace it with (e.g. "42")
2727
 * Returns the resulting delimited string (e.g. "abc,42,x123,ipv4,xyz")
2728
 */
2729
function replace_element_in_list($text, $delimiter, $element, $replacement) {
2730
	$textArray = explode($delimiter, $text);
2731
	while (($entry = array_search($element, $textArray)) !== false) {
2732
		$textArray[$entry] = $replacement;
2733
	}
2734
	return implode(',', $textArray);
2735
}
2736

    
2737
/* Return system's route table */
2738
function route_table() {
2739
	exec("/usr/bin/netstat --libxo json -nWr", $rawdata, $rc);
2740

    
2741
	if ($rc != 0) {
2742
		return array();
2743
	}
2744

    
2745
	$netstatarr = json_decode(implode(" ", $rawdata), JSON_OBJECT_AS_ARRAY);
2746
	$netstatarr = $netstatarr['statistics']['route-information']
2747
	    ['route-table']['rt-family'];
2748

    
2749
	$result = array();
2750
	$result['inet'] = array();
2751
	$result['inet6'] = array();
2752
	foreach ($netstatarr as $item) {
2753
		if ($item['address-family'] == 'Internet') {
2754
			$result['inet'] = $item['rt-entry'];
2755
		} else if ($item['address-family'] == 'Internet6') {
2756
			$result['inet6'] = $item['rt-entry'];
2757
		}
2758
	}
2759
	unset($netstatarr);
2760

    
2761
	return $result;
2762
}
2763

    
2764
/* check if route is static (not BGP/OSPF) */
2765
function is_static_route($target, $ipprotocol = '') {
2766
	if (is_v4($target) || (($target == 'default') && ($ipprotocol == 'inet'))) {
2767
		$inet = '4';
2768
	} elseif (is_v6($target) || (($target == 'default') && ($ipprotocol == 'inet6'))) {
2769
		$inet = '6';
2770
	} else {
2771
		return false;
2772
	}
2773

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

    
2778
	return false;
2779
}
2780

    
2781
/* Get static route for specific destination */
2782
function route_get($target, $ipprotocol = '', $useroute = false) {
2783
	global $config;
2784

    
2785
	if (!empty($ipprotocol)) {
2786
		$family = $ipprotocol;
2787
	} else if (is_v4($target)) {
2788
		$family = 'inet';
2789
	} else if (is_v6($target)) {
2790
		$family = 'inet6';
2791
	}
2792

    
2793
	if (empty($family)) {
2794
		return array();
2795
	}
2796

    
2797
	if ($useroute) {
2798
		if ($family == 'inet') {
2799
			$inet = '4';
2800
		} else {
2801
			$inet = '6';
2802
		}
2803
		$interface = exec("/sbin/route -n{$inet} get {$target} 2>/dev/null | /usr/bin/awk '/interface:/{print $2}'");
2804
		if (empty($interface)) {
2805
			return array();
2806
		} elseif ($interface == 'lo0') {
2807
			// interface assigned IP address
2808
			foreach (array_keys($config['interfaces']) as $intf) {
2809
				if ((($inet == '4') && (get_interface_ip($intf) == $target)) ||
2810
				    (($inet == '6') && (get_interface_ipv6($intf) == $target))) {
2811
					$interface = convert_friendly_interface_to_real_interface_name($intf);
2812
					$gateway = $interface;
2813
					break;
2814
				}
2815
			}
2816
		} else {
2817
			$gateway = exec("/sbin/route -n{$inet} get {$target} 2>/dev/null | /usr/bin/awk '/gateway:/{print $2}'");
2818
			if (!$gateway) {
2819
				// non-local gateway
2820
				$gateway = get_interface_mac($interface);
2821
			}
2822
		}
2823
		$result[] = array('gateway' => $gateway, 'interface-name' => $interface);
2824
	} else {
2825
		$rtable = route_table();
2826
		if (empty($rtable)) {
2827
			return array();
2828
		}
2829

    
2830
		$result = array();
2831
		foreach ($rtable[$family] as $item) {
2832
			if ($item['destination'] == $target ||
2833
			    ip_in_subnet($target, $item['destination'])) {
2834
				$result[] = $item;
2835
			}
2836
		}
2837
	}
2838

    
2839
	return $result;
2840
}
2841

    
2842
/* Get default route */
2843
function route_get_default($ipprotocol) {
2844
	if (empty($ipprotocol) || ($ipprotocol != 'inet' &&
2845
	    $ipprotocol != 'inet6')) {
2846
		return '';
2847
	}
2848

    
2849
	$route = route_get('default', $ipprotocol, true);
2850

    
2851
	if (empty($route)) {
2852
		return '';
2853
	}
2854

    
2855
	if (!isset($route[0]['gateway'])) {
2856
		return '';
2857
	}
2858

    
2859
	return $route[0]['gateway'];
2860
}
2861

    
2862
/* Delete a static route */
2863
function route_del($target, $ipprotocol = '') {
2864
	global $config;
2865

    
2866
	if (empty($target)) {
2867
		return;
2868
	}
2869

    
2870
	if (!empty($ipprotocol) && $ipprotocol != 'inet' &&
2871
	    $ipprotocol != 'inet6') {
2872
		return false;
2873
	}
2874

    
2875
	$route = route_get($target, $ipprotocol, true);
2876

    
2877
	if (empty($route)) {
2878
		return;
2879
	}
2880

    
2881
	$target_prefix = '';
2882
	if (is_subnet($target)) {
2883
		$target_prefix = '-net';
2884
	} else if (is_ipaddr($target)) {
2885
		$target_prefix = '-host';
2886
	}
2887

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

    
2896
	foreach ($route as $item) {
2897
		if (substr($item['gateway'], 0, 5) == 'link#') {
2898
			continue;
2899
		}
2900

    
2901
		if (is_macaddr($item['gateway'])) {
2902
			$gw = '-iface ' . $item['interface-name'];
2903
		} else {
2904
			$gw = $item['gateway'];
2905
		}
2906

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

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

    
2920
/*
2921
 * Add static route.  If it already exists, remove it and re-add
2922
 *
2923
 * $target - IP, subnet or 'default'
2924
 * $gw     - gateway address
2925
 * $iface  - Network interface
2926
 * $args   - Extra arguments for /sbin/route
2927
 * $ipprotocol - 'inet' or 'inet6'.  Mandatory when $target == 'default'
2928
 *
2929
 */
2930
function route_add_or_change($target, $gw, $iface = '', $args = '',
2931
    $ipprotocol = '') {
2932
	global $config;
2933

    
2934
	if (empty($target) || (empty($gw) && empty($iface))) {
2935
		return false;
2936
	}
2937

    
2938
	if ($target == 'default' && empty($ipprotocol)) {
2939
		return false;
2940
	}
2941

    
2942
	if (!empty($ipprotocol) && $ipprotocol != 'inet' &&
2943
	    $ipprotocol != 'inet6') {
2944
		return false;
2945
	}
2946

    
2947
	/* use '-host' for IPv6 /128 routes, see https://redmine.pfsense.org/issues/11594 */
2948
	if (is_subnetv4($target) || (is_subnetv6($target) && (subnet_size($target) > 1))) {
2949
		$target_prefix = '-net';
2950
	} else if (is_ipaddr($target)) {
2951
		$target_prefix = '-host';
2952
	}
2953

    
2954
	if (!empty($ipprotocol)) {
2955
		$target_prefix .= " -{$ipprotocol}";
2956
	} else if (is_v6($target)) {
2957
		$target_prefix .= ' -inet6';
2958
	} else if (is_v4($target)) {
2959
		$target_prefix .= ' -inet';
2960
	}
2961

    
2962
	/* If there is another route to the same target, remove it */
2963
	route_del($target, $ipprotocol);
2964

    
2965
	$params = '';
2966
	if (!empty($iface) && does_interface_exist($iface)) {
2967
		$params .= " -iface {$iface}";
2968
	}
2969
	if (is_ipaddr($gw)) {
2970
		/* set correct linklocal gateway address,
2971
		 * see https://redmine.pfsense.org/issues/11713
2972
		 * and https://redmine.pfsense.org/issues/11806 */
2973
		if (is_ipaddrv6($gw) && is_linklocal($gw) && empty(get_ll_scope($gw))) {
2974
			$routeget = route_get($gw, 'inet6', true);
2975
			$gw .= "%" . $routeget[0]['interface-name'];
2976
		}
2977
		$params .= " " . $gw;
2978
	}
2979

    
2980
	if (empty($params)) {
2981
		log_error("route_add_or_change: Invalid gateway {$gw} and/or " .
2982
		    "network interface {$iface}");
2983
		return false;
2984
	}
2985

    
2986
	exec(escapeshellcmd("/sbin/route add {$target_prefix} " .
2987
	    "{$target} {$args} {$params}"), $output, $rc);
2988

    
2989
	if (isset($config['system']['route-debug'])) {
2990
		log_error("ROUTING debug: " . microtime() .
2991
		    " - ADD RC={$rc} - {$target} {$args}");
2992
		file_put_contents("/dev/console", "\n[" . getmypid() .
2993
		    "] ROUTE ADD: {$target_prefix} {$target} {$args} " .
2994
		    "{$params} result: {$rc}");
2995
	}
2996

    
2997
	return ($rc == 0);
2998
}
2999

    
3000
function set_ipv6routes_mtu($interface, $mtu) {
3001
	global $config;
3002

    
3003
	$ipv6mturoutes = array();
3004
	$if = convert_real_interface_to_friendly_interface_name($interface);
3005
	if (!$config['interfaces'][$if]['ipaddrv6']) {
3006
		return;
3007
	}
3008
	$a_gateways = return_gateways_array();
3009
	$a_staticroutes = get_staticroutes(false, false, true);
3010
	foreach ($a_gateways as $gate) {
3011
		foreach ($a_staticroutes as $sroute) {
3012
			if (($gate['interface'] == $interface) &&
3013
			    ($sroute['gateway'] == $gate['name']) &&
3014
			    (is_ipaddrv6($gate['gateway']))) {
3015
				$tgt = $sroute['network'];
3016
				$gateway = $gate['gateway'];
3017
				$ipv6mturoutes[$tgt] = $gateway;
3018
			}
3019
		}
3020
		if (($gate['interface'] == $interface) &&
3021
		    $gate['isdefaultgw'] && is_ipaddrv6($gate['gateway'])) {
3022
			$tgt = "default";
3023
			$gateway = $gate['gateway'];
3024
			$ipv6mturoutes[$tgt] = $gateway;
3025
		}
3026
	}
3027
	foreach ($ipv6mturoutes as $tgt => $gateway) {
3028
		mwexec("/sbin/route change -6 -mtu " . escapeshellarg($mtu) .
3029
		    " " . escapeshellarg($tgt) . " " .
3030
		    escapeshellarg($gateway));
3031
	}
3032
}
3033

    
3034
function alias_to_subnets_recursive($name, $returnhostnames = false) {
3035
	global $aliastable;
3036
	$result = array();
3037
	if (!isset($aliastable[$name])) {
3038
		return $result;
3039
	}
3040
	$subnets = preg_split('/\s+/', $aliastable[$name]);
3041
	foreach ($subnets as $net) {
3042
		if (is_alias($net)) {
3043
			$sub = alias_to_subnets_recursive($net, $returnhostnames);
3044
			$result = array_merge($result, $sub);
3045
			continue;
3046
		} elseif (!is_subnet($net)) {
3047
			if (is_ipaddrv4($net)) {
3048
				$net .= "/32";
3049
			} else if (is_ipaddrv6($net)) {
3050
				$net .= "/128";
3051
			} else if ($returnhostnames === false || !is_fqdn($net)) {
3052
				continue;
3053
			}
3054
		}
3055
		$result[] = $net;
3056
	}
3057
	return $result;
3058
}
3059

    
3060
function get_staticroutes($returnsubnetsonly = false, $returnhostnames = false, $returnenabledroutesonly = false) {
3061
	global $config;
3062

    
3063
	/* Bail if there are no routes, but return an array always so callers don't have to check. */
3064
	init_config_arr(array('staticroutes', 'route'));
3065
	if (empty($config['staticroutes']['route'])) {
3066
		return array();
3067
	}
3068

    
3069
	$allstaticroutes = array();
3070
	$allsubnets = array();
3071
	/* Loop through routes and expand aliases as we find them. */
3072
	foreach ($config['staticroutes']['route'] as $route) {
3073
		if ($returnenabledroutesonly && isset($route['disabled'])) {
3074
			continue;
3075
		}
3076

    
3077
		if (is_alias($route['network'])) {
3078
			foreach (alias_to_subnets_recursive($route['network'], $returnhostnames) as $net) {
3079
				$temproute = $route;
3080
				$temproute['network'] = $net;
3081
				$allstaticroutes[] = $temproute;
3082
				$allsubnets[] = $net;
3083
			}
3084
		} elseif (is_subnet($route['network'])) {
3085
			$allstaticroutes[] = $route;
3086
			$allsubnets[] = $route['network'];
3087
		}
3088
	}
3089
	if ($returnsubnetsonly) {
3090
		return $allsubnets;
3091
	} else {
3092
		return $allstaticroutes;
3093
	}
3094
}
3095

    
3096
/****f* util/get_alias_list
3097
 * NAME
3098
 *   get_alias_list - Provide a list of aliases.
3099
 * INPUTS
3100
 *   $type          - Optional, can be a string or array specifying what type(s) of aliases you need.
3101
 * RESULT
3102
 *   Array containing list of aliases.
3103
 *   If $type is unspecified, all aliases are returned.
3104
 *   If $type is a string, all aliases of the type specified in $type are returned.
3105
 *   If $type is an array, all aliases of any type specified in any element of $type are returned.
3106
 */
3107
function get_alias_list($type = null) {
3108
	$result = array();
3109
	foreach (config_get_path('aliases/alias', []) as $alias) {
3110
		if ($type === null) {
3111
			$result[] = $alias['name'];
3112
		} else if (is_array($type)) {
3113
			if (in_array($alias['type'], $type)) {
3114
				$result[] = $alias['name'];
3115
			}
3116
		} else if ($type === $alias['type']) {
3117
			$result[] = $alias['name'];
3118
		}
3119
	}
3120
	return $result;
3121
}
3122

    
3123
/* Returns the current alias contents sorted by name in case insensitive and
3124
 * natural order.
3125
 * https://redmine.pfsense.org/issues/14015 */
3126
function get_sorted_aliases() {
3127
	$aliases = config_get_path('aliases/alias', []);
3128
	uasort($aliases, function ($a, $b) { return strnatcasecmp($a['name'], $b['name']); });
3129
	return $aliases;
3130
}
3131

    
3132
/* returns an array consisting of every element of $haystack that is not equal to $needle. */
3133
function array_exclude($needle, $haystack) {
3134
	$result = array();
3135
	if (is_array($haystack)) {
3136
		foreach ($haystack as $thing) {
3137
			if ($needle !== $thing) {
3138
				$result[] = $thing;
3139
			}
3140
		}
3141
	}
3142
	return $result;
3143
}
3144

    
3145
/* Define what is preferred, IPv4 or IPv6 */
3146
function prefer_ipv4_or_ipv6() {
3147
	if (config_path_enabled('system', 'prefer_ipv4')) {
3148
		mwexec("/etc/rc.d/ip6addrctl prefer_ipv4");
3149
	} else {
3150
		mwexec("/etc/rc.d/ip6addrctl prefer_ipv6");
3151
	}
3152
}
3153

    
3154
/* Redirect to page passing parameters via POST */
3155
function post_redirect($page, $params) {
3156
	if (!is_array($params)) {
3157
		return;
3158
	}
3159

    
3160
	print "<html><body><form action=\"{$page}\" name=\"formredir\" method=\"post\">\n";
3161
	foreach ($params as $key => $value) {
3162
		print "<input type=\"hidden\" name=\"{$key}\" value=\"{$value}\" />\n";
3163
	}
3164
	print "</form>\n";
3165
	print "<script type=\"text/javascript\">\n";
3166
	print "//<![CDATA[\n";
3167
	print "document.formredir.submit();\n";
3168
	print "//]]>\n";
3169
	print "</script>\n";
3170
	print "</body></html>\n";
3171
}
3172

    
3173
/* Locate disks that can be queried for S.M.A.R.T. data. */
3174
function get_smart_drive_list() {
3175
	/* SMART supports some disks directly, and some controllers directly,
3176
	 * See https://redmine.pfsense.org/issues/9042 */
3177
	$supported_disk_types = array("ad", "da", "ada");
3178
	$supported_controller_types = array("nvme");
3179
	$disk_list = explode(" ", get_single_sysctl("kern.disks"));
3180
	foreach ($disk_list as $id => $disk) {
3181
		// We only want certain kinds of disks for S.M.A.R.T.
3182
		// 1 is a match, 0 is no match, False is any problem processing the regex
3183
		if (preg_match("/^(" . implode("|", $supported_disk_types) . ").*[0-9]{1,2}$/", $disk) !== 1) {
3184
			unset($disk_list[$id]);
3185
			continue;
3186
		}
3187
	}
3188
	foreach ($supported_controller_types as $controller) {
3189
		$devices = glob("/dev/{$controller}*");
3190
		if (!is_array($devices)) {
3191
			continue;
3192
		}
3193
		foreach ($devices as $device) {
3194
			$disk_list[] = basename($device);
3195
		}
3196
	}
3197
	sort($disk_list);
3198
	return $disk_list;
3199
}
3200

    
3201
// Validate a network address
3202
//	$addr: the address to validate
3203
//	$type: IPV4|IPV6|IPV4V6
3204
//	$label: the label used by the GUI to display this value. Required to compose an error message
3205
//	$err_msg: pointer to the callers error message array so that error messages can be added to it here
3206
//	$alias: are aliases permitted for this address?
3207
// Returns:
3208
//	IPV4 - if $addr is a valid IPv4 address
3209
//	IPV6 - if $addr is a valid IPv6 address
3210
//	ALIAS - if $alias=true and $addr is an alias
3211
//	false - otherwise
3212

    
3213
function validateipaddr(&$addr, $type, $label, &$err_msg, $alias=false) {
3214
	switch ($type) {
3215
		case IPV4:
3216
			if (is_ipaddrv4($addr)) {
3217
				return IPV4;
3218
			} else if ($alias) {
3219
				if (is_alias($addr)) {
3220
					return ALIAS;
3221
				} else {
3222
					$err_msg[] = sprintf(gettext("%s must be a valid IPv4 address or alias."), $label);
3223
					return false;
3224
				}
3225
			} else {
3226
				$err_msg[] = sprintf(gettext("%s must be a valid IPv4 address."), $label);
3227
				return false;
3228
			}
3229
		break;
3230
		case IPV6:
3231
			if (is_ipaddrv6($addr)) {
3232
				$addr = strtolower($addr);
3233
				return IPV6;
3234
			} else if ($alias) {
3235
				if (is_alias($addr)) {
3236
					return ALIAS;
3237
				} else {
3238
					$err_msg[] = sprintf(gettext("%s must be a valid IPv6 address or alias."), $label);
3239
					return false;
3240
				}
3241
			} else {
3242
				$err_msg[] = sprintf(gettext("%s must be a valid IPv6 address."), $label);
3243
				return false;
3244
			}
3245
		break;
3246
		case IPV4V6:
3247
			if (is_ipaddrv6($addr)) {
3248
				$addr = strtolower($addr);
3249
				return IPV6;
3250
			} else if (is_ipaddrv4($addr)) {
3251
				return IPV4;
3252
			} else if ($alias) {
3253
				if (is_alias($addr)) {
3254
					return ALIAS;
3255
				} else {
3256
					$err_msg[] = sprintf(gettext("%s must be a valid IPv4 or IPv6 address or alias."), $label);
3257
					return false;
3258
				}
3259
			} else {
3260
				$err_msg[] = sprintf(gettext("%s must be a valid IPv4 or IPv6 address."), $label);
3261
				return false;
3262
			}
3263
		break;
3264
	}
3265

    
3266
	return false;
3267
}
3268

    
3269
/* From DUID configuration inputs, format a string that looks (more) like the expected raw DUID format:
3270
 * 1) For DUIDs entered as a known DUID type, convert to a hexstring and prepend the DUID number, after having done the following:
3271
 *     a) For DUID-LLT and DUID-EN, convert the time/enterprise ID input to hex and append the link-layer address/identifier input.
3272
 *     b) For DUID-LLT and DUID-LL, prepend a hardware type of 1.
3273
 *     c) For DUID-UUID, remove any "-".
3274
 * 2) Replace any remaining "-" with ":".
3275
 * 3) If any components are input with just a single char (hex digit hopefully), put a "0" in front.
3276
 * 4) The first two components should be a 16-bit integer (little- or big-endian, depending on the current machine type) that
3277
 *    is equal to the number of other components. If not, prepend this as "nn:00" (all pfSense builds are little-endian).
3278
 *    This is convenience, because the DUID reported by dhcp6c in logs does not include this count, which corresponds to the
3279
 *    option-len field of DHCPv6's OPTION_CLIENTID option.
3280
 *
3281
 * The final result should be closer to:
3282
 *
3283
 * "nn:00:00:0n:nn:nn:nn:..."
3284
 *
3285
 * This function does not validate the input. is_duid() will do validation.
3286
 */
3287
function format_duid($duidtype, $duidpt1, $duidpt2=null) {
3288
	if ($duidpt2)
3289
		$duidpt1 = implode(':', str_split(str_pad(dechex($duidpt1), 8, '0', STR_PAD_LEFT), 2)) . ':' . $duidpt2;
3290

    
3291
	/* Make hexstrings */
3292
	if ($duidtype) {
3293
		switch ($duidtype) {
3294
		/* Add a hardware type to DUID-LLT and DUID-LL; assume Ethernet */
3295
		case 1:
3296
		case 3:
3297
			$duidpt1 = '00:01:' . $duidpt1;
3298
			break;
3299
		/* Remove '-' from given UUID and insert ':' every 2 characters */
3300
		case 4:
3301
			$duidpt1 = implode(':', str_split(str_replace('-', '', $duidpt1), 2));
3302
			break;
3303
		default:
3304
		}
3305
		$duidpt1 = '00:0' . $duidtype . ':' . $duidpt1;
3306
	}
3307

    
3308
	$values = explode(':', strtolower(str_replace('-', ':', $duidpt1)));
3309

    
3310
	if (hexdec($values[0]) != count($values) - 2)
3311
		array_unshift($values, dechex(count($values)), '00');
3312

    
3313
	array_walk($values, function(&$value) {
3314
		$value = str_pad($value, 2, '0', STR_PAD_LEFT);
3315
	});
3316

    
3317
	return implode(":", $values);
3318
}
3319

    
3320
/* Returns true if $dhcp6duid is a valid DUID entry.
3321
 * Parse the entry to check for valid length according to known DUID types.
3322
 */
3323
function is_duid($dhcp6duid) {
3324
	$values = explode(":", $dhcp6duid);
3325
	if (hexdec($values[0]) == count($values) - 2) {
3326
		switch (hexdec($values[2] . $values[3])) {
3327
		case 0:
3328
			return false;
3329
			break;
3330
		case 1:
3331
			if (count($values) != 16 || strlen($dhcp6duid) != 47)
3332
				return false;
3333
			break;
3334
		case 3:
3335
			if (count($values) != 12 || strlen($dhcp6duid) != 35)
3336
				return false;
3337
			break;
3338
		case 4:
3339
			if (count($values) != 20 || strlen($dhcp6duid) != 59)
3340
				return false;
3341
			break;
3342
		/* DUID is up to 128 octets; allow 2 octets for type code, 2 more for option-len */
3343
		default:
3344
			if (count($values) > 132 || strlen($dhcp6duid) != count($values) * 3 - 1)
3345
				return false;
3346
		}
3347
	} else
3348
		return false;
3349

    
3350
	for ($i = 0; $i < count($values); $i++) {
3351
		if (ctype_xdigit($values[$i]) == false)
3352
			return false;
3353
		if (hexdec($values[$i]) < 0 || hexdec($values[$i]) > 255)
3354
			return false;
3355
	}
3356

    
3357
	return true;
3358
}
3359

    
3360
/* Write the DHCP6 DUID file */
3361
function write_dhcp6_duid($duidstring) {
3362
	// Create the hex array from the dhcp6duid config entry and write to file
3363
	global $g;
3364

    
3365
	if(!is_duid($duidstring)) {
3366
		log_error(gettext("Error: attempting to write DUID file - Invalid DUID detected"));
3367
		return false;
3368
	}
3369
	$temp = str_replace(":","",$duidstring);
3370
	$duid_binstring = pack("H*",$temp);
3371
	if ($fd = fopen("{$g['vardb_path']}/dhcp6c_duid", "wb")) {
3372
		fwrite($fd, $duid_binstring);
3373
		fclose($fd);
3374
		return true;
3375
	}
3376
	log_error(gettext("Error: attempting to write DUID file - File write error"));
3377
	return false;
3378
}
3379

    
3380
/* returns duid string from 'vardb_path']}/dhcp6c_duid' */
3381
function get_duid_from_file() {
3382
	global $g;
3383

    
3384
	$duid_ASCII = "";
3385
	$count = 0;
3386

    
3387
	if (file_exists("{$g['vardb_path']}/dhcp6c_duid") &&
3388
	    ($fd = fopen("{$g['vardb_path']}/dhcp6c_duid", "r"))) {
3389
		$fsize = filesize("{$g['vardb_path']}/dhcp6c_duid");
3390
		if ($fsize <= 132) {
3391
			$buffer = fread($fd, $fsize);
3392
			while($count < $fsize) {
3393
				$duid_ASCII .= bin2hex($buffer[$count]);
3394
				$count++;
3395
				if($count < $fsize) {
3396
					$duid_ASCII .= ":";
3397
				}
3398
			}
3399
		}
3400
		fclose($fd);
3401
	}
3402
	//if no file or error with read then the string returns blanked DUID string
3403
	if(!is_duid($duid_ASCII)) {
3404
		return "--:--:--:--:--:--:--:--:--:--:--:--:--:--:--:--";
3405
	}
3406
	return($duid_ASCII);
3407
}
3408

    
3409
/* Replaces the Mac OS 9 and earlier (\r) and DOS/Windows (\r\n) newlines with the Unix equivalent (\n). */
3410
function unixnewlines($text) {
3411
	return preg_replace('/\r\n?/', "\n", $text);
3412
}
3413

    
3414
function array_remove_duplicate($array, $field) {
3415
	$cmp = array();
3416
	foreach ($array as $sub) {
3417
		if (isset($sub[$field])) {
3418
			$cmp[] = $sub[$field];
3419
		}
3420
	}
3421
	$unique = array_unique(array_reverse($cmp, true));
3422
	foreach (array_keys($unique) as $k) {
3423
		$new[] = $array[$k];
3424
	}
3425
	return $new;
3426
}
3427

    
3428
/**
3429
 * Return a value specified by a path of keys in a nested array, if it exists.
3430
 * @param $arr array value to search
3431
 * @param $path string path with '/' separators
3432
 * @param $default mixed value to return if the path is not found
3433
 * @returns mixed value at path or $default if the path does not exist or if the
3434
 *          path keys an empty string and $default is non-null
3435
 */
3436
function array_get_path(array &$arr, string $path, $default = null) {
3437
	$vpath = explode('/', $path);
3438
	$el = $arr;
3439
	foreach ($vpath as $key) {
3440
		if (mb_strlen($key) == 0) {
3441
			continue;
3442
		}
3443
		if (is_array($el) && array_key_exists($key, $el)) {
3444
			$el = $el[$key];
3445
		} else {
3446
			return ($default);
3447
		}
3448
	}
3449

    
3450
	if (($default !== null) && ($el === '')) {
3451
		return ($default);
3452
	}
3453

    
3454
	return ($el);
3455
}
3456

    
3457
/*
3458
 * Initialize an arbitrary array multiple levels deep only if unset
3459
 * @param $arr top of array
3460
 * @param $path string path with '/' separators
3461
 */
3462
function array_init_path(mixed &$arr, ?string $path)
3463
{
3464
	if (!is_array($arr)) {
3465
		$arr = [];
3466
	}
3467
	if (is_null($path)) {
3468
		return;
3469
	}
3470
	$tmp = &$arr;
3471
	foreach (explode('/', $path) as $key) {
3472
		if (!is_array($tmp[$key])) {
3473
			$tmp[$key] = [];
3474
		}
3475
		$tmp = &$tmp[$key];
3476
	}
3477
}
3478

    
3479
/**
3480
 * Set a value by path in a nested array, creating arrays for intermediary keys
3481
 * as necessary. If the path cannot be reached because an intermediary exists
3482
 * but is not empty or an array, return $default.
3483
 * @param $arr array value to search
3484
 * @param $path string path with '/' separators
3485
 * @param $value mixed
3486
 * @param $default mixed value to return if the path is not found
3487
 * @returns mixed $val or $default if the path prefix does not exist
3488
 */
3489
function array_set_path(array &$arr, string $path, $value, $default = null) {
3490
	$vpath = explode('/', $path);
3491
	$vkey = null;
3492
	do {
3493
		$vkey = array_pop($vpath);
3494
	} while (mb_strlen($vkey) == 0);
3495
	if ($vkey == null) {
3496
		return ($default);
3497
	}
3498
	$el =& $arr;
3499
	foreach ($vpath as $key) {
3500
		if (mb_strlen($key) == 0) {
3501
			continue;
3502
		}
3503
		if (array_key_exists($key, $el) && !empty($el[$key])) {
3504
			if (!is_array($el[$key])) {
3505
					return ($default);
3506
			}
3507
		} else {
3508
				$el[$key] = [];
3509
		}
3510
		$el =& $el[$key];
3511
	}
3512
	$el[$vkey] = $value;
3513
	return ($value);
3514
}
3515

    
3516
/**
3517
 * Determine whether a path in a nested array has a non-null value keyed by
3518
 * $enable_key.
3519
 * @param $arr array value to search
3520
 * @param $path string path with '/' separators
3521
 * @param $enable_key string an optional alternative key value for the enable key
3522
 * @returns bool true if $enable_key exists in the array at $path, and has a
3523
 * non-null value, otherwise false
3524
 */
3525
function array_path_enabled(array &$arr, string $path, $enable_key = "enable") {
3526
	$el = array_get_path($arr, $path, []);
3527
	if (is_array($el) && isset($el[$enable_key])) {
3528
		return (true);
3529
	}
3530
	return (false);
3531
}
3532

    
3533
/**
3534
 * Remove a key from the nested array by path.
3535
 * @param $arr array value to search
3536
 * @param $path string path with '/' separators
3537
 * @returns array copy of the removed value or null
3538
 */
3539
function array_del_path(array &$arr, string $path) {
3540
	$vpath = explode('/', $path);
3541
	$vkey = array_pop($vpath);
3542
	$el =& $arr;
3543
	foreach($vpath as $key) {
3544
		if (mb_strlen($key) == 0) {
3545
			continue;
3546
		}
3547
		if (is_array($el) && array_key_exists($key, $el)) {
3548
			$el =& $el[$key];
3549
		} else {
3550
			return null;
3551
		}
3552
	}
3553

    
3554
	if (!(is_array($el) && array_key_exists($vkey, $el))) {
3555
		return null;
3556
	}
3557

    
3558
	$ret = $el[$vkey];
3559
	unset($el[$vkey]);
3560
	return ($ret);
3561
}
3562

    
3563

    
3564
function dhcpd_date_adjust_gmt($dt) {
3565
	init_config_arr(array('dhcpd'));
3566

    
3567
	foreach (config_get_path('dhcpd', []) as $dhcpditem) {
3568
		if (empty($dhcpditem)) {
3569
			continue;
3570
		}
3571
		if ($dhcpditem['dhcpleaseinlocaltime'] == "yes") {
3572
			$ts = strtotime($dt . " GMT");
3573
			if ($ts !== false) {
3574
				return strftime("%Y/%m/%d %H:%M:%S", $ts);
3575
			}
3576
		}
3577
	}
3578

    
3579
	/*
3580
	 * If we did not need to convert to local time or the conversion
3581
	 * failed, just return the input.
3582
	 */
3583
	return $dt;
3584
}
3585

    
3586
global $supported_image_types;
3587
$supported_image_types = array(
3588
	IMAGETYPE_JPEG,
3589
	IMAGETYPE_PNG,
3590
	IMAGETYPE_GIF,
3591
	IMAGETYPE_WEBP
3592
);
3593

    
3594
function is_supported_image($image_filename) {
3595
	global $supported_image_types;
3596
	$img_info = getimagesize($image_filename);
3597

    
3598
	/* If it's not an image, or it isn't in the supported list, return false */
3599
	if (($img_info === false) ||
3600
	    !in_array($img_info[2], array_keys($supported_image_types))) {
3601
		return false;
3602
	} else {
3603
		return $img_info[2];
3604
	}
3605
}
3606

    
3607
function get_lagg_ports ($laggport) {
3608
	$laggp = array();
3609
	foreach ($laggport as $lgp) {
3610
		list($lpname, $lpinfo) = explode(" ", $lgp);
3611
		preg_match('~<(.+)>~', $lpinfo, $lgportmode);
3612
		if ($lgportmode[1]) {
3613
			$laggp[] = $lpname . " (" . $lgportmode[1] . ")";
3614
		} else {
3615
			$laggp[] = $lpname;
3616
		}
3617
	}
3618
	if ($laggp) {
3619
		return implode(", ", $laggp);
3620
	} else {
3621
		return false;
3622
	}
3623
}
3624

    
3625
function cisco_to_cidr($addr) {
3626
	if (!is_ipaddr($addr)) {
3627
		throw new Exception('Value is not in dotted quad notation.');
3628
	}
3629

    
3630
	$mask = decbin(~ip2long($addr));
3631
	$mask = substr($mask, -32);
3632
	$k = 0;
3633
	for ($i = 0; $i <= 32; $i++) {
3634
		$k += intval($mask[$i]);
3635
	}
3636
	return $k;
3637
}
3638

    
3639
function cisco_extract_index($prule) {
3640
	$index = explode("#", $prule);
3641
	if (is_numeric($index[1])) {
3642
		return intval($index[1]);
3643
	} else {
3644
		syslog(LOG_WARNING, "Error parsing rule {$prule}: Could not extract index");
3645
	}
3646
	return -1;;
3647
}
3648

    
3649
function parse_cisco_acl_rule($rule, $devname, $dir, $proto) {
3650
	$rule_orig = $rule;
3651
	$rule = explode(" ", $rule);
3652
	$tmprule = "";
3653
	$index = 0;
3654

    
3655
	if ($rule[$index] == "permit") {
3656
		$startrule = "pass {$dir} quick on {$devname} ";
3657
	} else if ($rule[$index] == "deny") {
3658
		$startrule = "block {$dir} quick on {$devname} ";
3659
	} else {
3660
		return;
3661
	}
3662

    
3663
	$index++;
3664

    
3665
	switch ($rule[$index]) {
3666
		case "ip":
3667
			break;
3668
		case "icmp":
3669
			$icmp = ($proto == "inet") ? "icmp" : "ipv6-icmp";
3670
			$tmprule .= "proto {$icmp} ";
3671
			break;
3672
		case "tcp":
3673
		case "udp":
3674
			$tmprule .= "proto {$rule[$index]} ";
3675
			break;
3676
		default:
3677
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid protocol.");
3678
			return;
3679
	}
3680
	$index++;
3681

    
3682
	/* Source */
3683
	if (trim($rule[$index]) == "host") {
3684
		$index++;
3685
		if ((($proto == 'inet') && (is_ipaddrv4(trim($rule[$index])) || (trim($rule[$index]) == "{clientip}"))) ||
3686
		    (($proto == 'inet6') && (is_ipaddrv6(trim($rule[$index])) || (trim($rule[$index]) == "{clientipv6}")))) {
3687
			if ($GLOBALS['attributes']['framed_ip']) {
3688
				$tmprule .= "from {$GLOBALS['attributes']['framed_ip']} ";
3689
			} else {
3690
				$tmprule .= "from {$rule[$index]} ";
3691
			}
3692
			$index++;
3693
		} else {
3694
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source host '{$rule[$index]}'.");
3695
			return;
3696
		}
3697
	} elseif (is_subnetv6(trim($rule[$index])) && ($proto == "inet6")) {
3698
		$tmprule .= "from {$rule[$index]} ";
3699
		$index++;
3700
	} elseif (trim($rule[$index]) == "any") {
3701
		$tmprule .= "from any ";
3702
		$index++;
3703
	} else {
3704
		$network = $rule[$index];
3705
		$netmask = $rule[++$index];
3706

    
3707
		if (is_ipaddrv4($network) && ($proto == "inet")) {
3708
			try {
3709
				$netmask = cisco_to_cidr($netmask);
3710
			} catch(Exception $e) {
3711
				syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source netmask '$netmask' (" . $e->getMessage() . ").");
3712
				return;
3713
			}
3714
			$tmprule .= "from {$network}/{$netmask} ";
3715
		} else {
3716
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source network '$network'.");
3717
			return;
3718
		}
3719

    
3720
		$index++;
3721
	}
3722

    
3723
	/* Source Operator */
3724
	if (in_array(trim($rule[$index]), array("lt", "gt", "eq", "neq"))) {
3725
		switch(trim($rule[$index])) {
3726
			case "lt":
3727
				$operator = "<";
3728
				break;
3729
			case "gt":
3730
				$operator = ">";
3731
				break;
3732
			case "eq":
3733
				$operator = "=";
3734
				break;
3735
			case "neq":
3736
				$operator = "!=";
3737
				break;
3738
		}
3739

    
3740
		$port = $rule[++$index];
3741
		if (is_port($port)) {
3742
			$tmprule .= "port {$operator} {$port} ";
3743
		} else {
3744
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source port: '$port' not a numeric value between 0 and 65535.");
3745
			return;
3746
		}
3747
		$index++;
3748
	} else if (trim($rule[$index]) == "range") {
3749
		$port = array($rule[++$index], $rule[++$index]);
3750
		if (is_port($port[0]) && is_port($port[1])) {
3751
			$tmprule .= "port {$port[0]}:{$port[1]} ";
3752
		} else {
3753
			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.");
3754
			return;
3755
		}
3756
		$index++;
3757
	}
3758

    
3759
	/* Destination */
3760
	if (trim($rule[$index]) == "host") {
3761
		$index++;
3762
		if ((($proto == 'inet') && (is_ipaddrv4(trim($rule[$index])) || (trim($rule[$index]) == "{clientip}"))) ||
3763
		    (($proto == 'inet6') && (is_ipaddrv6(trim($rule[$index])) || (trim($rule[$index]) == "{clientipv6}")))) {
3764
			$tmprule .= "to {$rule[$index]} ";
3765
			$index++;
3766
		} else {
3767
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination host '{$rule[$index]}'.");
3768
			return;
3769
		}
3770
	} elseif (is_subnetv6(trim($rule[$index])) && ($proto == "inet6")) {
3771
		$tmprule .= "to {$rule[$index]} ";
3772
		$index++;
3773
	} elseif (trim($rule[$index]) == "any") {
3774
		$tmprule .= "to any ";
3775
		$index++;
3776
	} else {
3777
		$network = $rule[$index];
3778
		$netmask = $rule[++$index];
3779

    
3780
		if (is_ipaddrv4($network) && ($proto == "inet")) {
3781
			try {
3782
				$netmask = cisco_to_cidr($netmask);
3783
			} catch(Exception $e) {
3784
				syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination netmask '$netmask' (" . $e->getMessage() . ").");
3785
				return;
3786
			}
3787
			$tmprule .= "to {$network}/{$netmask} ";
3788
		} else {
3789
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination network '$network'.");
3790
			return;
3791
		}
3792

    
3793
		$index++;
3794
	}
3795

    
3796
	/* Destination Operator */
3797
	if (in_array(trim($rule[$index]), array("lt", "gt", "eq", "neq"))) {
3798
		switch(trim($rule[$index])) {
3799
			case "lt":
3800
				$operator = "<";
3801
				break;
3802
			case "gt":
3803
				$operator = ">";
3804
				break;
3805
			case "eq":
3806
				$operator = "=";
3807
				break;
3808
			case "neq":
3809
				$operator = "!=";
3810
				break;
3811
		}
3812

    
3813
		$port = $rule[++$index];
3814
		if (is_port($port)) {
3815
			$tmprule .= "port {$operator} {$port} ";
3816
		} else {
3817
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination port: '$port' not a numeric value between 0 and 65535.");
3818
			return;
3819
		}
3820
		$index++;
3821
	} else if (trim($rule[$index]) == "range") {
3822
		$port = array($rule[++$index], $rule[++$index]);
3823
		if (is_port($port[0]) && is_port($port[1])) {
3824
			$tmprule .= "port {$port[0]}:{$port[1]} ";
3825
		} else {
3826
			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.");
3827
			return;
3828
		}
3829
		$index++;
3830
	}
3831

    
3832
	$tmprule = $startrule . $proto . " " . $tmprule;
3833
	return $tmprule;
3834
}
3835

    
3836
function parse_cisco_acl($attribs, $dev) {
3837
	global $attributes;
3838

    
3839
	if (!is_array($attribs)) {
3840
		return "";
3841
	}
3842
	$finalrules = "";
3843
	if (is_array($attribs['ciscoavpair'])) {
3844
		$inrules = array('inet' => array(), 'inet6' => array());
3845
		$outrules = array('inet' => array(), 'inet6' => array());
3846
		foreach ($attribs['ciscoavpair'] as $avrules) {
3847
			$rule = explode("=", $avrules);
3848
			$dir = "";
3849
			if (strstr($rule[0], "inacl")) {
3850
				$dir = "in";
3851
			} else if (strstr($rule[0], "outacl")) {
3852
				$dir = "out";
3853
			} else if (strstr($rule[0], "dns-servers")) {
3854
				$attributes['dns-servers'] = explode(" ", $rule[1]);
3855
				continue;
3856
			} else if (strstr($rule[0], "route")) {
3857
				if (!is_array($attributes['routes'])) {
3858
					$attributes['routes'] = array();
3859
				}
3860
				$attributes['routes'][] = $rule[1];
3861
				continue;
3862
			}
3863
			$rindex = cisco_extract_index($rule[0]);
3864
			if ($rindex < 0) {
3865
				continue;
3866
			}
3867

    
3868
			if (strstr($rule[0], "ipv6")) {
3869
				$proto = "inet6";
3870
			} else {
3871
				$proto = "inet";
3872
			}
3873

    
3874
			$tmprule = parse_cisco_acl_rule($rule[1], $dev, $dir, $proto);
3875
			if (!empty($tmprule)) {
3876
				if ($dir == "in") {
3877
					$inrules[$proto][$rindex] = $tmprule;
3878
				} else if ($dir == "out") {
3879
					$outrules[$proto][$rindex] = $tmprule;
3880
				}
3881
			}
3882
		}
3883

    
3884

    
3885
		$state = "";
3886
		foreach (array('inet', 'inet6') as $ip) {
3887
			if (!empty($outrules[$ip])) {
3888
				$state = "no state";
3889
			}
3890
			ksort($inrules[$ip], SORT_NUMERIC);
3891
			foreach ($inrules[$ip] as $inrule) {
3892
				$finalrules .= "{$inrule} {$state}\n";
3893
			}
3894
			if (!empty($outrules[$ip])) {
3895
				ksort($outrules[$ip], SORT_NUMERIC);
3896
				foreach ($outrules[$ip] as $outrule) {
3897
					$finalrules .= "{$outrule} {$state}\n";
3898
				}
3899
			}
3900
		}
3901
	}
3902
	return $finalrules;
3903
}
3904

    
3905
function alias_idn_to_utf8($alias) {
3906
	if (is_alias($alias)) {
3907
		return $alias;
3908
	} else {
3909
		return idn_to_utf8($alias);
3910
	}
3911
}
3912

    
3913
function alias_idn_to_ascii($alias) {
3914
	if (is_alias($alias)) {
3915
		return $alias;
3916
	} else {
3917
		return idn_to_ascii($alias);
3918
	}
3919
}
3920

    
3921
// These functions were in guiconfig.inc but have been moved here so non GUI processes can use them
3922
function address_to_pconfig($adr, &$padr, &$pmask, &$pnot, &$pbeginport, &$pendport) {
3923
	if (isset($adr['any'])) {
3924
		$padr = "any";
3925
	} else if ($adr['network']) {
3926
		$padr = $adr['network'];
3927
	} else if ($adr['address']) {
3928
		list($padr, $pmask) = explode("/", $adr['address']);
3929
		if (!$pmask) {
3930
			if (is_ipaddrv6($padr)) {
3931
				$pmask = 128;
3932
			} else {
3933
				$pmask = 32;
3934
			}
3935
		}
3936
	}
3937

    
3938
	if (isset($adr['not'])) {
3939
		$pnot = 1;
3940
	} else {
3941
		$pnot = 0;
3942
	}
3943

    
3944
	if ($adr['port']) {
3945
		list($pbeginport, $pendport) = explode("-", $adr['port']);
3946
		if (!$pendport) {
3947
			$pendport = $pbeginport;
3948
		}
3949
	} else if (!is_alias($pbeginport) && !is_alias($pendport)) {
3950
		$pbeginport = "any";
3951
		$pendport = "any";
3952
	}
3953
}
3954

    
3955
function pconfig_to_address(&$adr, $padr, $pmask, $pnot = false, $pbeginport = 0, $pendport = 0, $addmask = false) {
3956
	$adr = array();
3957

    
3958
	if ($padr == "any") {
3959
		$adr['any'] = true;
3960
	} else if (is_specialnet($padr)) {
3961
		if ($addmask) {
3962
			$padr .= "/" . $pmask;
3963
		}
3964
		$adr['network'] = $padr;
3965
	} else {
3966
		$adr['address'] = $padr;
3967
		if (is_ipaddrv6($padr)) {
3968
			if ($pmask != 128) {
3969
				$adr['address'] .= "/" . $pmask;
3970
			}
3971
		} else {
3972
			if ($pmask != 32) {
3973
				$adr['address'] .= "/" . $pmask;
3974
			}
3975
		}
3976
	}
3977

    
3978
	if ($pnot) {
3979
		$adr['not'] = true;
3980
	} else {
3981
		unset($adr['not']);
3982
	}
3983

    
3984
	if (($pbeginport != 0) && ($pbeginport != "any")) {
3985
		if ($pbeginport != $pendport) {
3986
			$adr['port'] = $pbeginport . "-" . $pendport;
3987
		} else {
3988
			$adr['port'] = $pbeginport;
3989
		}
3990
	}
3991

    
3992
	/*
3993
	 * If the port is still unset, then it must not be numeric, but could
3994
	 * be an alias or a well-known/registered service.
3995
	 * See https://redmine.pfsense.org/issues/8410
3996
	 */
3997
	if (!isset($adr['port']) && is_port_or_alias($pbeginport)) {
3998
		$adr['port'] = $pbeginport;
3999
	}
4000
}
4001

    
4002
function is_specialnet($net) {
4003
	global $specialsrcdst;
4004

    
4005
	if (!$net) {
4006
		return false;
4007
	}
4008
	if (in_array($net, $specialsrcdst)) {
4009
		return true;
4010
	} else {
4011
		return false;
4012
	}
4013
}
4014

    
4015
function is_interface_ipaddr($interface) {
4016
	if (!empty(config_get_path("interfaces/{$interface}/ipaddr"))) {
4017
		return true;
4018
	}
4019
	return false;
4020
}
4021

    
4022
function is_interface_ipaddrv6($interface) {
4023
	if (!empty(config_get_path("interfaces/{$interface}/ipaddrv6"))) {
4024
		return true;
4025
	}
4026
	return false;
4027
}
4028

    
4029
function escape_filter_regex($filtertext) {
4030
	/* If the caller (user) has not already put a backslash before a slash, to escape it in the regex, */
4031
	/* then this will do it. Take out any "\/" already there, then turn all ordinary "/" into "\/".    */
4032
	return str_replace('/', '\/', str_replace('\/', '/', $filtertext));
4033
}
4034

    
4035
/*
4036
 * Check if a given pattern has the same number of two different unescaped
4037
 * characters.
4038
 * For example, it can ensure a pattern has balanced sets of parentheses,
4039
 * braces, and brackets.
4040
 */
4041
function is_pattern_balanced_char($pattern, $open, $close) {
4042
	/* First remove escaped versions */
4043
	$pattern = str_replace('\\' . $open, '', $pattern);
4044
	$pattern = str_replace('\\' . $close, '', $pattern);
4045
	/* Check if the counts of both characters match in the target pattern */
4046
	return (substr_count($pattern, $open) == substr_count($pattern, $close));
4047
}
4048

    
4049
/*
4050
 * Check if a pattern contains balanced sets of parentheses, braces, and
4051
 * brackets.
4052
 */
4053
function is_pattern_balanced($pattern) {
4054
	if (is_pattern_balanced_char($pattern, '(', ')') &&
4055
	    is_pattern_balanced_char($pattern, '{', '}') &&
4056
	    is_pattern_balanced_char($pattern, '[', ']')) {
4057
		/* Balanced if all are true */
4058
		return true;
4059
	}
4060
	return false;
4061
}
4062

    
4063
function cleanup_regex_pattern($filtertext) {
4064
	/* Cleanup filter to prevent backreferences. */
4065
	$filtertext = escape_filter_regex($filtertext);
4066

    
4067
	/* Remove \<digit>+ backreferences
4068
	 * To match \ it must be escaped as \\\\ in PHP for preg_replace() */
4069
	$filtertext = preg_replace('/\\\\\\d+/', '', $filtertext);
4070

    
4071
	/* Check for unbalanced parentheses, braces, and brackets which
4072
	 * may be an error or attempt to circumvent protections.
4073
	 * Also discard any pattern that attempts problematic duplication
4074
	 * methods. */
4075
	if (!is_pattern_balanced($filtertext) ||
4076
	    (substr_count($filtertext, ')*') > 0) ||
4077
	    (substr_count($filtertext, ')+') > 0) ||
4078
	    (substr_count($filtertext, '{') > 0)) {
4079
		return '';
4080
	}
4081

    
4082
	return $filtertext;
4083
}
4084

    
4085
function ip6_to_asn1($addr) {
4086
	/* IPv6 MIB uses an OCTET STRING of length 16 to represent
4087
	 * 128-bit IPv6 address in network byte order.
4088
	 * see https://datatracker.ietf.org/doc/html/rfc2465#section-3
4089
	 * i.e. fc00:3::4 = 252.0.0.3.0.0.0.0.0.0.0.0.0.0.0.4
4090
	 */
4091

    
4092
	if (!is_ipaddrv6($addr)) {
4093
		return false;
4094
	}
4095
	$ipv6str = "";
4096
	$octstr = "";
4097
	foreach (explode(':', Net_IPv6::uncompress($addr)) as $v) {
4098
		$ipv6str .= str_pad($v, 4, '0', STR_PAD_LEFT);
4099
	}
4100
	foreach (str_split($ipv6str, 2) as $v) {
4101
		$octstr .= base_convert($v, 16, 10) . '.';
4102
	}
4103

    
4104
	return $octstr;
4105
}
4106

    
4107
function interfaces_interrupts() {
4108
	exec("/usr/bin/vmstat -i --libxo json", $rawdata, $rc);
4109
	$interrupts = array();
4110
	if ($rc == 0) {
4111
		$vnstatarr = json_decode(implode(" ", $rawdata), JSON_OBJECT_AS_ARRAY);
4112
		$interruptarr = $vnstatarr['interrupt-statistics']['interrupt'];
4113

    
4114
		foreach ($interruptarr as $int){
4115
			preg_match("/irq\d+: ([a-z0-9]+)/", $int['name'], $matches);
4116
			$name = $matches[1];
4117
			if (array_key_exists($name, $interrupts)) {
4118
				/* interface with multiple queues */
4119
				$interrupts[$name]['total'] += $int['total'];
4120
				$interrupts[$name]['rate'] += $int['rate'];
4121
			} else {
4122
				$interrupts[$name]['total'] = $int['total'];
4123
				$interrupts[$name]['rate'] = $int['rate'];
4124
			}
4125
		}
4126
	}
4127

    
4128
	return $interrupts;
4129
}
4130

    
4131
function dummynet_load_module($max_qlimit) {
4132
	if (!is_module_loaded("dummynet.ko")) {
4133
		mute_kernel_msgs();
4134
		mwexec("/sbin/kldload dummynet");
4135
		unmute_kernel_msgs();
4136
	}
4137
	$sysctls = (array(
4138
			"net.inet.ip.dummynet.io_fast" => "1",
4139
			"net.inet.ip.dummynet.hash_size" => "256",
4140
			"net.inet.ip.dummynet.pipe_slot_limit" => $max_qlimit
4141
	));
4142
	init_config_arr(array('sysctl', 'item'));
4143
	foreach (config_get_path('sysctl/item', []) as $item) {
4144
		if (preg_match('/net\.inet\.ip\.dummynet\./', $item['tunable'])) {
4145
			$sysctls[$item['tunable']] = $item['value'];
4146
		}
4147
	}
4148
	set_sysctl($sysctls);
4149
}
4150

    
4151
function get_interface_vip_ips($interface) {
4152
	global $config;
4153
	$vipips = '';
4154

    
4155
	init_config_arr(array('virtualip', 'vip'));
4156
	foreach ($config['virtualip']['vip'] as $vip) {
4157
		if (($vip['interface'] == $interface) &&
4158
		    (($vip['mode'] == "carp") || ($vip['mode'] == "ipalias"))) {
4159
			$vipips .= $vip['subnet'] . ' ';
4160
		}
4161
	}
4162
	return $vipips;
4163
}
4164

    
4165
?>
(54-54/61)