Project

General

Profile

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

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

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

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

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

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

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

    
73
	return 0;
74
}
75

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

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

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

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

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

    
101
	return false;
102
}
103

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

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

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

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

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

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

    
157
		return $fp;
158
	}
159

    
160
	return NULL;
161
}
162

    
163
/* unlock configuration file */
164
function unlock($cfglckkey = 0) {
165
	global $g;
166
	flock($cfglckkey, LOCK_UN);
167
	fclose($cfglckkey);
168
	return;
169
}
170

    
171
/* unlock forcefully configuration file */
172
function unlock_force($lock) {
173
	global $g;
174

    
175
	@unlink("{$g['tmp_path']}/{$lock}.lock");
176
}
177

    
178
function send_event($cmd) {
179
	global $g;
180

    
181
	if (!isset($g['event_address'])) {
182
		$g['event_address'] = "unix:///var/run/check_reload_status";
183
	}
184

    
185
	$try = 0;
186
	while ($try < 3) {
187
		$fd = @fsockopen($g['event_address']);
188
		if ($fd) {
189
			fwrite($fd, $cmd);
190
			$resp = fread($fd, 4096);
191
			if ($resp != "OK\n") {
192
				log_error("send_event: sent {$cmd} got {$resp}");
193
			}
194
			fclose($fd);
195
			$try = 3;
196
		} else if (!is_process_running("check_reload_status")) {
197
			mwexec_bg("/usr/bin/nice -n20 /usr/local/sbin/check_reload_status");
198
		}
199
		$try++;
200
	}
201
}
202

    
203
function send_multiple_events($cmds) {
204
	global $g;
205

    
206
	if (!isset($g['event_address'])) {
207
		$g['event_address'] = "unix:///var/run/check_reload_status";
208
	}
209

    
210
	if (!is_array($cmds)) {
211
		return;
212
	}
213

    
214
	while ($try < 3) {
215
		$fd = @fsockopen($g['event_address']);
216
		if ($fd) {
217
			foreach ($cmds as $cmd) {
218
				fwrite($fd, $cmd);
219
				$resp = fread($fd, 4096);
220
				if ($resp != "OK\n") {
221
					log_error("send_event: sent {$cmd} got {$resp}");
222
				}
223
			}
224
			fclose($fd);
225
			$try = 3;
226
		} else if (!is_process_running("check_reload_status")) {
227
			mwexec_bg("/usr/bin/nice -n20 /usr/local/sbin/check_reload_status");
228
		}
229
		$try++;
230
	}
231
}
232

    
233
function is_module_loaded($module_name) {
234
	$module_name = str_replace(".ko", "", $module_name);
235
	$running = 0;
236
	$_gb = exec("/sbin/kldstat -qn {$module_name} 2>&1", $_gb, $running);
237
	if (intval($running) == 0) {
238
		return true;
239
	} else {
240
		return false;
241
	}
242
}
243

    
244
/* validate non-negative numeric string, or equivalent numeric variable */
245
function is_numericint($arg) {
246
	return (((is_int($arg) && $arg >= 0) || (is_string($arg) && strlen($arg) > 0 && ctype_digit($arg))) ? true : false);
247
}
248

    
249
/* Generate the (human readable) ipv4 or ipv6 subnet address (i.e., netmask, or subnet start IP)
250
   given an (human readable) ipv4 or ipv6 host address and subnet bit count */
251
function gen_subnet($ipaddr, $bits) {
252
	if (($sn = gen_subnetv6($ipaddr, $bits)) == '') {
253
		$sn = gen_subnetv4($ipaddr, $bits);  // try to avoid rechecking IPv4/v6
254
	}
255
	return $sn;
256
}
257

    
258
/* same as gen_subnet() but accepts IPv4 only */
259
function gen_subnetv4($ipaddr, $bits) {
260
	if (is_ipaddrv4($ipaddr) && is_numericint($bits) && $bits <= 32) {
261
		if ($bits == 0) {
262
			return '0.0.0.0';  // avoids <<32
263
		}
264
		return long2ip(ip2long($ipaddr) & ((0xFFFFFFFF << (32 - $bits)) & 0xFFFFFFFF));
265
	}
266
	return "";
267
}
268

    
269
/* same as gen_subnet() but accepts IPv6 only */
270
function gen_subnetv6($ipaddr, $bits) {
271
	if (is_ipaddrv6($ipaddr) && is_numericint($bits) && $bits <= 128) {
272
		return text_to_compressed_ip6(Net_IPv6::getNetmask($ipaddr, $bits));
273
	}
274
	return "";
275
}
276

    
277
/* Generate the (human readable) ipv4 or ipv6 subnet end address (i.e., highest address, end IP, or IPv4 broadcast address)
278
   given an (human readable) ipv4 or ipv6 host address and subnet bit count. */
279
function gen_subnet_max($ipaddr, $bits) {
280
	if (($sn = gen_subnetv6_max($ipaddr, $bits)) == '') {
281
		$sn = gen_subnetv4_max($ipaddr, $bits);  // try to avoid rechecking IPv4/v6
282
	}
283
	return $sn;
284
}
285

    
286
/* same as gen_subnet_max() but validates IPv4 only */
287
function gen_subnetv4_max($ipaddr, $bits) {
288
	if (is_ipaddrv4($ipaddr) && is_numericint($bits) && $bits <= 32) {
289
		if ($bits == 32) {
290
			return $ipaddr;
291
		}
292
		return long2ip32(ip2long($ipaddr) | (~gen_subnet_mask_long($bits) & 0xFFFFFFFF));
293
	}
294
	return "";
295
}
296

    
297
/* same as gen_subnet_max() but validates IPv6 only */
298
function gen_subnetv6_max($ipaddr, $bits) {
299
	if (is_ipaddrv6($ipaddr) && is_numericint($bits) && $bits <= 128) {
300
		$endip_bin = substr(ip6_to_bin($ipaddr), 0, $bits) . str_repeat('1', 128 - $bits);
301
		return bin_to_compressed_ip6($endip_bin);
302
	}
303
	return "";
304
}
305

    
306
/* returns a subnet mask (long given a bit count) */
307
function gen_subnet_mask_long($bits) {
308
	$sm = 0;
309
	for ($i = 0; $i < $bits; $i++) {
310
		$sm >>= 1;
311
		$sm |= 0x80000000;
312
	}
313
	return $sm;
314
}
315

    
316
/* same as above but returns a string */
317
function gen_subnet_mask($bits) {
318
	return long2ip(gen_subnet_mask_long($bits));
319
}
320

    
321
/* Convert a prefix length to an IPv6 address-like mask notation. Very rare but at least ntp needs it. See #4463 */
322
function gen_subnet_mask_v6($bits) {
323
	/* Binary representation of the prefix length */
324
	$bin = str_repeat('1', $bits);
325
	/* Pad right with zeroes to reach the full address length */
326
	$bin = str_pad($bin, 128, '0', STR_PAD_RIGHT);
327
	/* Convert back to an IPv6 address style notation */
328
	return bin_to_ip6($bin);
329
}
330

    
331
/* Convert long int to IPv4 address
332
   Returns '' if not valid IPv4 (including if any bits >32 are non-zero) */
333
function long2ip32($ip) {
334
	return long2ip($ip & 0xFFFFFFFF);
335
}
336

    
337
/* Convert IPv4 address to long int, truncated to 32-bits to avoid sign extension on 64-bit platforms.
338
   Returns '' if not valid IPv4. */
339
function ip2long32($ip) {
340
	return (ip2long($ip) & 0xFFFFFFFF);
341
}
342

    
343
/* Convert IPv4 address to unsigned long int.
344
   Returns '' if not valid IPv4. */
345
function ip2ulong($ip) {
346
	return sprintf("%u", ip2long32($ip));
347
}
348

    
349
/*
350
 * Convert IPv6 address to binary
351
 *
352
 * Obtained from: pear-Net_IPv6
353
 */
354
function ip6_to_bin($ip) {
355
	$binstr = '';
356

    
357
	$ip = Net_IPv6::removeNetmaskSpec($ip);
358
	$ip = Net_IPv6::Uncompress($ip);
359

    
360
	$parts = explode(':', $ip);
361

    
362
	foreach ( $parts as $v ) {
363

    
364
		$str     = base_convert($v, 16, 2);
365
		$binstr .= str_pad($str, 16, '0', STR_PAD_LEFT);
366

    
367
	}
368

    
369
	return $binstr;
370
}
371

    
372
/*
373
 * Convert IPv6 binary to uncompressed address
374
 *
375
 * Obtained from: pear-Net_IPv6
376
 */
377
function bin_to_ip6($bin) {
378
	$ip = "";
379

    
380
	if (strlen($bin) < 128) {
381
		$bin = str_pad($bin, 128, '0', STR_PAD_LEFT);
382
	}
383

    
384
	$parts = str_split($bin, "16");
385

    
386
	foreach ( $parts as $v ) {
387
		$str = base_convert($v, 2, 16);
388
		$ip .= $str.":";
389
	}
390

    
391
	$ip = substr($ip, 0, -1);
392

    
393
	return $ip;
394
}
395

    
396
/*
397
 * Convert IPv6 binary to compressed address
398
 */
399
function bin_to_compressed_ip6($bin) {
400
	return text_to_compressed_ip6(bin_to_ip6($bin));
401
}
402

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

    
418
/* Find out how many IPs are contained within a given IP range
419
 *  e.g. 192.168.0.0 to 192.168.0.255 returns 256
420
 */
421
function ip_range_size_v4($startip, $endip) {
422
	if (is_ipaddrv4($startip) && is_ipaddrv4($endip)) {
423
		// Operate as unsigned long because otherwise it wouldn't work
424
		//   when crossing over from 127.255.255.255 / 128.0.0.0 barrier
425
		return abs(ip2ulong($startip) - ip2ulong($endip)) + 1;
426
	}
427
	return -1;
428
}
429

    
430
/* Find the smallest possible subnet mask which can contain a given number of IPs
431
 *  e.g. 512 IPs can fit in a /23, but 513 IPs need a /22
432
 */
433
function find_smallest_cidr_v4($number) {
434
	$smallest = 1;
435
	for ($b=32; $b > 0; $b--) {
436
		$smallest = ($number <= pow(2, $b)) ? $b : $smallest;
437
	}
438
	return (32-$smallest);
439
}
440

    
441
/* Return the previous IP address before the given address */
442
function ip_before($ip, $offset = 1) {
443
	return long2ip32(ip2long($ip) - $offset);
444
}
445

    
446
/* Return the next IP address after the given address */
447
function ip_after($ip, $offset = 1) {
448
	return long2ip32(ip2long($ip) + $offset);
449
}
450

    
451
/* Return true if the first IP is 'before' the second */
452
function ip_less_than($ip1, $ip2) {
453
	// Compare as unsigned long because otherwise it wouldn't work when
454
	//   crossing over from 127.255.255.255 / 128.0.0.0 barrier
455
	return ip2ulong($ip1) < ip2ulong($ip2);
456
}
457

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

    
465
/* compare two IP addresses */
466
function ipcmp($a, $b) {
467
	if (is_subnet($a)) {
468
		list($a, $amask) = explode('/', $a);
469
	}
470
	if (is_subnet($b)) {
471
		list($b, $bmask) = explode('/', $b);
472
	}
473
	if (ip_less_than($a, $b)) {
474
		return -1;
475
	} else if (ip_greater_than($a, $b)) {
476
		return 1;
477
	} else {
478
		return 0;
479
	}
480
}
481

    
482
/* Convert a range of IPv4 addresses to an array of individual addresses. */
483
/* Note: IPv6 ranges are not yet supported here. */
484
function ip_range_to_address_array($startip, $endip, $max_size = 5000) {
485
	if (!is_ipaddrv4($startip) || !is_ipaddrv4($endip)) {
486
		return false;
487
	}
488

    
489
	if (ip_greater_than($startip, $endip)) {
490
		// Swap start and end so we can process sensibly.
491
		$temp = $startip;
492
		$startip = $endip;
493
		$endip = $temp;
494
	}
495

    
496
	if (ip_range_size_v4($startip, $endip) > $max_size) {
497
		return false;
498
	}
499

    
500
	// Container for IP addresses within this range.
501
	$rangeaddresses = array();
502
	$end_int = ip2ulong($endip);
503
	for ($ip_int = ip2ulong($startip); $ip_int <= $end_int; $ip_int++) {
504
		$rangeaddresses[] = long2ip($ip_int);
505
	}
506

    
507
	return $rangeaddresses;
508
}
509

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

    
538
	if (is_ipaddrv4($ip1) && is_ipaddrv4($ip2)) {
539
		$proto = 'ipv4';  // for clarity
540
		$bits = 32;
541
		$ip1bin = decbin(ip2long32($ip1));
542
		$ip2bin = decbin(ip2long32($ip2));
543
	} elseif (is_ipaddrv6($ip1) && is_ipaddrv6($ip2)) {
544
		$proto = 'ipv6';
545
		$bits = 128;
546
		$ip1bin = ip6_to_bin($ip1);
547
		$ip2bin = ip6_to_bin($ip2);
548
	} else {
549
		return array();
550
	}
551

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

    
556
	if ($ip1bin == $ip2bin) {
557
		return array($ip1 . '/' . $bits); // exit if ip1=ip2 (trivial case)
558
	}
559

    
560
	if ($ip1bin > $ip2bin) {
561
		list ($ip1bin, $ip2bin) = array($ip2bin, $ip1bin);  // swap if needed (ensures ip1 < ip2)
562
	}
563

    
564
	$rangesubnets = array();
565
	$netsize = 0;
566

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

    
571
		// 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)
572

    
573
		if (substr($ip1bin, -1, 1) == '1') {
574
			// the start ip must be in a separate one-IP cidr range
575
			$new_subnet_ip = substr($ip1bin, $netsize, $bits - $netsize) . str_repeat('0', $netsize);
576
			$rangesubnets[$new_subnet_ip] = $bits - $netsize;
577
			$n = strrpos($ip1bin, '0');  //can't be all 1's
578
			$ip1bin = ($n == 0 ? '' : substr($ip1bin, 0, $n)) . '1' . str_repeat('0', $bits - $n - 1);  // BINARY VERSION OF $ip1 += 1
579
		}
580

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

    
583
		if (substr($ip2bin, -1, 1) == '0') {
584
			// the end ip must be in a separate one-IP cidr range
585
			$new_subnet_ip = substr($ip2bin, $netsize, $bits - $netsize) . str_repeat('0', $netsize);
586
			$rangesubnets[$new_subnet_ip] = $bits - $netsize;
587
			$n = strrpos($ip2bin, '1');  //can't be all 0's
588
			$ip2bin = ($n == 0 ? '' : substr($ip2bin, 0, $n)) . '0' . str_repeat('1', $bits - $n - 1);  // BINARY VERSION OF $ip2 -= 1
589
			// already checked for the edge case where end = start+1 and start ends in 0x1, above, so it's safe
590
		}
591

    
592
		// this is the only edge case arising from increment/decrement.
593
		// 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)
594

    
595
		if ($ip2bin < $ip1bin) {
596
			continue;
597
		}
598

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

    
602
		$shift = $bits - max(strrpos($ip1bin, '0'), strrpos($ip2bin, '1'));  // num of low bits which are '0' in ip1 and '1' in ip2
603
		$ip1bin = str_repeat('0', $shift) . substr($ip1bin, 0, $bits - $shift);
604
		$ip2bin = str_repeat('0', $shift) . substr($ip2bin, 0, $bits - $shift);
605
		$netsize += $shift;
606
		if ($ip1bin == $ip2bin) {
607
			// we're done.
608
			$new_subnet_ip = substr($ip1bin, $netsize, $bits - $netsize) . str_repeat('0', $netsize);
609
			$rangesubnets[$new_subnet_ip] = $bits - $netsize;
610
			continue;
611
		}
612

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

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

    
618
	ksort($rangesubnets, SORT_STRING);
619
	$out = array();
620

    
621
	foreach ($rangesubnets as $ip => $netmask) {
622
		if ($proto == 'ipv4') {
623
			$i = str_split($ip, 8);
624
			$out[] = implode('.', array(bindec($i[0]), bindec($i[1]), bindec($i[2]), bindec($i[3]))) . '/' . $netmask;
625
		} else {
626
			$out[] = bin_to_compressed_ip6($ip) . '/' . $netmask;
627
		}
628
	}
629

    
630
	return $out;
631
}
632

    
633
/* returns true if $range is a valid pair of IPv4 or IPv6 addresses separated by a "-"
634
	false - if not a valid pair
635
	true (numeric 4 or 6) - if valid, gives type of addresses */
636
function is_iprange($range) {
637
	if (substr_count($range, '-') != 1) {
638
		return false;
639
	}
640
	list($ip1, $ip2) = explode ('-', $range);
641
	if (is_ipaddrv4($ip1) && is_ipaddrv4($ip2)) {
642
		return 4;
643
	}
644
	if (is_ipaddrv6($ip1) && is_ipaddrv6($ip2)) {
645
		return 6;
646
	}
647
	return false;
648
}
649

    
650
/* returns true if $ipaddr is a valid dotted IPv4 address or a IPv6
651
	false - not valid
652
	true (numeric 4 or 6) - if valid, gives type of address */
653
function is_ipaddr($ipaddr) {
654
	if (is_ipaddrv4($ipaddr)) {
655
		return 4;
656
	}
657
	if (is_ipaddrv6($ipaddr)) {
658
		return 6;
659
	}
660
	return false;
661
}
662

    
663
/* returns true if $ipaddr is a valid IPv6 address */
664
function is_ipaddrv6($ipaddr) {
665
	if (!is_string($ipaddr) || empty($ipaddr)) {
666
		return false;
667
	}
668
	/*
669
	 * While Net_IPv6::checkIPv6() considers IPv6/mask a valid IPv6,
670
	 * is_ipaddrv6() needs to be more strict to keep the compatibility
671
	 * with is_ipaddrv4().
672
	 */
673
	if (strstr($ipaddr, "/")) {
674
		return false;
675
	}
676
	if (strstr($ipaddr, "%") && is_linklocal($ipaddr)) {
677
		$tmpip = explode("%", $ipaddr);
678
		$ipaddr = $tmpip[0];
679
	}
680
	return Net_IPv6::checkIPv6($ipaddr);
681
}
682

    
683
/* returns true if $ipaddr is a valid dotted IPv4 address */
684
function is_ipaddrv4($ipaddr) {
685
	if (!is_string($ipaddr) || empty($ipaddr) || ip2long($ipaddr) === FALSE) {
686
		return false;
687
	}
688
	return true;
689
}
690

    
691
function is_mcast($ipaddr) {
692
	if (is_mcastv4($ipaddr)) {
693
		return 4;
694
	}
695
	if (is_mcastv6($ipaddr)) {
696
		return 6;
697
	}
698
	return false;
699
}
700

    
701
function is_mcastv4($ipaddr) {
702
	if (!is_ipaddrv4($ipaddr) ||
703
	    !preg_match('/^2(?:2[4-9]|3\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d?|0)){3}$/', $ipaddr)) {
704
		return false;
705
	}
706
	return true;
707
}
708

    
709
function is_mcastv6($ipaddr) {
710
	if (!is_ipaddrv6($ipaddr) || !preg_match('/^ff.+$/', $ipaddr)) {
711
		return false;
712
	}
713
	return true;
714
}
715

    
716
/* returns 4 or 6 respectively (== TRUE) if $ipaddr is a valid IPv4 or IPv6 linklocal address
717
   returns '' if not a valid linklocal address */
718
function is_linklocal($ipaddr) {
719
	if (is_ipaddrv4($ipaddr)) {
720
		// input is IPv4
721
		// test if it's 169.254.x.x per rfc3927 2.1
722
		$ip4 = explode(".", $ipaddr);
723
		if ($ip4[0] == '169' && $ip4[1] == '254') {
724
			return 4;
725
		}
726
	} elseif (Net_IPv6::getAddressType($ipaddr) == NET_IPV6_LOCAL_LINK) {
727
		return 6;
728
	}
729
	return '';
730
}
731

    
732
/* returns scope of a linklocal address */
733
function get_ll_scope($addr) {
734
	if (!is_linklocal($addr) || !strstr($addr, "%")) {
735
		return "";
736
	}
737
	list ($ll, $scope) = explode("%", $addr);
738
	return $scope;
739
}
740

    
741
/* returns true if $ipaddr is a valid literal IPv6 address */
742
function is_literalipaddrv6($ipaddr) {
743
	if (substr($ipaddr,0,1) == '[' && substr($ipaddr,-1,1) == ']') {
744
		// if it's data wrapped in "[ ... ]" then test if middle part is valid IPv6
745
		return is_ipaddrv6(substr($ipaddr,1,-1));
746
	}
747
	return false;
748
}
749

    
750
/* returns true if $iport is a valid IPv4:port or [Literal IPv6]:port
751
	false - not valid
752
	true (numeric 4 or 6) - if valid, gives type of address */
753
function is_ipaddrwithport($ipport) {
754
	$c = strrpos($ipport, ":");
755
	if ($c === false) {
756
		return false;  // can't split at final colon if no colon exists
757
	}
758

    
759
	if (!is_port(substr($ipport, $c + 1))) {
760
		return false;  // no valid port after last colon
761
	}
762

    
763
	$ip = substr($ipport, 0, $c);  // else is text before last colon a valid IP
764
	if (is_literalipaddrv6($ip)) {
765
		return 6;
766
	} elseif (is_ipaddrv4($ip)) {
767
		return 4;
768
	} else {
769
		return false;
770
	}
771
}
772

    
773
function is_hostnamewithport($hostport) {
774
	$parts = explode(":", $hostport);
775
	// no need to validate with is_string(); if it's not a string then explode won't return 2 parts anyway
776
	if (count($parts) == 2) {
777
		return is_hostname($parts[0]) && is_port($parts[1]);
778
	}
779
	return false;
780
}
781

    
782
/* returns true if $ipaddr is a valid dotted IPv4 address or an alias thereof */
783
function is_ipaddroralias($ipaddr) {
784
	global $config;
785

    
786
	if (is_alias($ipaddr)) {
787
		if (is_array($config['aliases']['alias'])) {
788
			foreach ($config['aliases']['alias'] as $alias) {
789
				if ($alias['name'] == $ipaddr && !preg_match("/port/i", $alias['type'])) {
790
					return true;
791
				}
792
			}
793
		}
794
		return false;
795
	} else {
796
		return is_ipaddr($ipaddr);
797
	}
798

    
799
}
800

    
801
/* returns true if $subnet is a valid IPv4 or IPv6 subnet in CIDR format
802
	false - if not a valid subnet
803
	true (numeric 4 or 6) - if valid, gives type of subnet */
804
function is_subnet($subnet) {
805
	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)) {
806
		if (is_ipaddrv4($parts[1]) && $parts[3] <= 32) {
807
			return 4;
808
		}
809
		if (is_ipaddrv6($parts[2]) && $parts[3] <= 128) {
810
			return 6;
811
		}
812
	}
813
	return false;
814
}
815

    
816
function is_v4($ip_or_subnet) {
817
	return is_ipaddrv4($ip_or_subnet) || is_subnetv4($ip_or_subnet);
818
}
819

    
820
function is_v6($ip_or_subnet) {
821
	return is_ipaddrv6($ip_or_subnet) || is_subnetv6($ip_or_subnet);
822
}
823

    
824
/* same as is_subnet() but accepts IPv4 only */
825
function is_subnetv4($subnet) {
826
	return (is_subnet($subnet) == 4);
827
}
828

    
829
/* same as is_subnet() but accepts IPv6 only */
830
function is_subnetv6($subnet) {
831
	return (is_subnet($subnet) == 6);
832
}
833

    
834
/* returns true if $subnet is a valid subnet in CIDR format or an alias thereof */
835
function is_subnetoralias($subnet) {
836
	global $aliastable;
837

    
838
	if (isset($aliastable[$subnet]) && is_subnet($aliastable[$subnet])) {
839
		return true;
840
	} else {
841
		return is_subnet($subnet);
842
	}
843
}
844

    
845
/* Get number of addresses in an IPv4/IPv6 subnet (represented as a string)
846
   optional $exact=true forces error (0) to be returned if it can't be represented exactly
847
   Exact result not possible above PHP_MAX_INT which is about 2^31 addresses on x32 or 2^63 on x64
848
   Returns 0 for bad data or if cannot represent size as an INT when $exact is set. */
849
function subnet_size($subnet, $exact=false) {
850
	$parts = explode("/", $subnet);
851
	$iptype = is_ipaddr($parts[0]);
852
	if (count($parts) == 2 && $iptype) {
853
		return subnet_size_by_netmask($iptype, $parts[1], $exact);
854
	}
855
	return 0;
856
}
857

    
858
/* Get number of addresses in an IPv4/IPv6 subnet (represented numerically as IP type + bits)
859
   optional $exact=true forces error (0) to be returned if it can't be represented exactly
860
   Hard to think where we might need to count exactly a huge subnet but an overflow detection option is probably sensible
861
   Returns 0 for bad data or if cannot represent size as an INT when $exact is set. */
862
function subnet_size_by_netmask($iptype, $bits, $exact=false) {
863
	if (!is_numericint($bits)) {
864
		return 0;
865
	} elseif ($iptype == 4 && $bits <= 32) {
866
		$snsize = 32 - $bits;
867
	} elseif ($iptype == 6 && $bits <= 128) {
868
		$snsize = 128 - $bits;
869
	} else {
870
		return 0;
871
	}
872

    
873
	// 2**N returns an exact result as an INT if possible, and a float/double if not.
874
	// 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
875
	$result = 2 ** $snsize;
876

    
877
	if ($exact && !is_int($result)) {
878
		//exact required but can't represent result exactly as an INT
879
		return 0;
880
	} else {
881
		// result ok, will be an INT where possible (guaranteed up to 2^31 addresses on x32/x64) and a float for 'huge' subnets
882
		return $result;
883
	}
884
}
885

    
886
/* function used by pfblockerng */
887
function subnetv4_expand($subnet) {
888
	$result = array();
889
	list ($ip, $bits) = explode("/", $subnet);
890
	$net = ip2long($ip);
891
	$mask = (0xffffffff << (32 - $bits));
892
	$net &= $mask;
893
	$size = round(exp(log(2) * (32 - $bits)));
894
	for ($i = 0; $i < $size; $i += 1) {
895
		$result[] = long2ip($net | $i);
896
	}
897
	return $result;
898
}
899

    
900
/* find out whether two IPv4/IPv6 CIDR subnets overlap.
901
   Note: CIDR overlap implies one is identical or included so largest sn will be the same */
902
function check_subnets_overlap($subnet1, $bits1, $subnet2, $bits2) {
903
	if (is_ipaddrv4($subnet1)) {
904
		return check_subnetsv4_overlap($subnet1, $bits1, $subnet2, $bits2);
905
	} else {
906
		return check_subnetsv6_overlap($subnet1, $bits1, $subnet2, $bits2);
907
	}
908
}
909

    
910
/* find out whether two IPv4 CIDR subnets overlap.
911
   Note: CIDR overlap means sn1/sn2 are identical or one is included in other. So sn using largest $bits will be the same  */
912
function check_subnetsv4_overlap($subnet1, $bits1, $subnet2, $bits2) {
913
	$largest_sn = min($bits1, $bits2);
914
	$subnetv4_start1 = gen_subnetv4($subnet1, $largest_sn);
915
	$subnetv4_start2 = gen_subnetv4($subnet2, $largest_sn);
916

    
917
	if ($subnetv4_start1 == '' || $subnetv4_start2 == '') {
918
		// One or both args is not a valid IPv4 subnet
919
		//FIXME: needs to return "bad data" not true/false if bad. For now return false, best we can do until fixed
920
		return false;
921
	}
922
	return ($subnetv4_start1 == $subnetv4_start2);
923
}
924

    
925
/* find out whether two IPv6 CIDR subnets overlap.
926
   Note: CIDR overlap means sn1/sn2 are identical or one is included in other. So sn using largest $bits will be the same  */
927
function check_subnetsv6_overlap($subnet1, $bits1, $subnet2, $bits2) {
928
	$largest_sn = min($bits1, $bits2);
929
	$subnetv6_start1 = gen_subnetv6($subnet1, $largest_sn);
930
	$subnetv6_start2 = gen_subnetv6($subnet2, $largest_sn);
931

    
932
	if ($subnetv6_start1 == '' || $subnetv6_start2 == '') {
933
		// One or both args is not a valid IPv6 subnet
934
		//FIXME: needs to return "bad data" not true/false if bad. For now return false, best we can do until fixed
935
		return false;
936
	}
937
	return ($subnetv6_start1 == $subnetv6_start2);
938
}
939

    
940
/* return all PTR zones for a IPv6 network */
941
function get_v6_ptr_zones($subnet, $bits) {
942
	$result = array();
943

    
944
	if (!is_ipaddrv6($subnet)) {
945
		return $result;
946
	}
947

    
948
	if (!is_numericint($bits) || $bits > 128) {
949
		return $result;
950
	}
951

    
952
	/*
953
	 * Find a small nibble boundary subnet mask
954
	 * e.g. a /29 will create 8 /32 PTR zones
955
	 */
956
	$small_sn = $bits;
957
	while ($small_sn % 4 != 0) {
958
		$small_sn++;
959
	}
960

    
961
	/* Get network prefix */
962
	$small_subnet = Net_IPv6::getNetmask($subnet, $bits);
963

    
964
	/*
965
	 * While small network is part of bigger one, increase 4-bit in last
966
	 * digit to get next small network
967
	 */
968
	while (Net_IPv6::isInNetmask($small_subnet, $subnet, $bits)) {
969
		/* Get a pure hex value */
970
		$unpacked = unpack('H*hex', inet_pton($small_subnet));
971
		/* Create PTR record using $small_sn / 4 chars */
972
		$result[] = implode('.', array_reverse(str_split(substr(
973
		    $unpacked['hex'], 0, $small_sn / 4)))).'.ip6.arpa';
974

    
975
		/* Detect what part of IP should be increased */
976
		$change_part = (int) ($small_sn / 16);
977
		if ($small_sn % 16 == 0) {
978
			$change_part--;
979
		}
980

    
981
		/* Increase 1 to desired part */
982
		$parts = explode(":", Net_IPv6::uncompress($small_subnet));
983
		$parts[$change_part]++;
984
		$small_subnet = implode(":", $parts);
985
	}
986

    
987
	return $result;
988
}
989

    
990
/* return true if $addr is in $subnet, false if not */
991
function ip_in_subnet($addr, $subnet) {
992
	if (is_ipaddrv6($addr) && is_subnetv6($subnet)) {
993
		return (Net_IPv6::isInNetmask($addr, $subnet));
994
	} else if (is_ipaddrv4($addr) && is_subnetv4($subnet)) {
995
		list($ip, $mask) = explode('/', $subnet);
996
		$mask = (0xffffffff << (32 - $mask)) & 0xffffffff;
997
		return ((ip2long($addr) & $mask) == (ip2long($ip) & $mask));
998
	}
999
	return false;
1000
}
1001

    
1002
/* returns true if $hostname is just a valid hostname (top part without any of the domain part) */
1003
function is_unqualified_hostname($hostname) {
1004
	if (!is_string($hostname)) {
1005
		return false;
1006
	}
1007

    
1008
	if (preg_match('/^(?:[a-z0-9_]|[a-z0-9_][a-z0-9_\-]*[a-z0-9_])$/i', $hostname)) {
1009
		return true;
1010
	} else {
1011
		return false;
1012
	}
1013
}
1014

    
1015
/* returns true if $hostname is a valid hostname, with or without being a fully-qualified domain name. */
1016
function is_hostname($hostname, $allow_wildcard=false) {
1017
	if (!is_string($hostname)) {
1018
		return false;
1019
	}
1020

    
1021
	if (is_domain($hostname, $allow_wildcard)) {
1022
		if ((substr_count($hostname, ".") == 1) && ($hostname[strlen($hostname)-1] == ".")) {
1023
			/* Only a single dot at the end like "test." - hosts cannot be directly in the root domain. */
1024
			return false;
1025
		} else {
1026
			return true;
1027
		}
1028
	} else {
1029
		return false;
1030
	}
1031
}
1032

    
1033
/* returns true if $domain is a valid domain name */
1034
function is_domain($domain, $allow_wildcard=false, $trailing_dot=true) {
1035
	if (!is_string($domain)) {
1036
		return false;
1037
	}
1038
	if (!$trailing_dot && ($domain[strlen($domain)-1] == ".")) {
1039
		return false;
1040
	}
1041
	if ($allow_wildcard) {
1042
		$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';
1043
	} else {
1044
		$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';
1045
	}
1046

    
1047
	if (preg_match($domain_regex, $domain)) {
1048
		return true;
1049
	} else {
1050
		return false;
1051
	}
1052
}
1053

    
1054
/* returns true if $macaddr is a valid MAC address */
1055
function is_macaddr($macaddr, $partial=false) {
1056
	$values = explode(":", $macaddr);
1057

    
1058
	/* Verify if the MAC address has a proper amount of parts for either a partial or full match. */
1059
	if ($partial) {
1060
		if ((count($values) < 1) || (count($values) > 6)) {
1061
			return false;
1062
		}
1063
	} elseif (count($values) != 6) {
1064
		return false;
1065
	}
1066
	for ($i = 0; $i < count($values); $i++) {
1067
		if (ctype_xdigit($values[$i]) == false)
1068
			return false;
1069
		if (hexdec($values[$i]) < 0 || hexdec($values[$i]) > 255)
1070
			return false;
1071
	}
1072

    
1073
	return true;
1074
}
1075

    
1076
/*
1077
	If $return_message is true then
1078
		returns a text message about the reason that the name is invalid.
1079
		the text includes the type of "thing" that is being checked, passed in $object. (e.g. "alias", "gateway group", "schedule")
1080
	else
1081
		returns true if $name is a valid name for an alias
1082
		returns false if $name is not a valid name for an alias
1083

    
1084
	Aliases cannot be:
1085
		bad chars: anything except a-z 0-9 and underscore
1086
		bad names: empty string, pure numeric, pure underscore
1087
		reserved words: pre-defined service/protocol/port names which should not be ambiguous, and the words "port" and  "pass" */
1088

    
1089
function is_validaliasname($name, $return_message = false, $object = "alias") {
1090
	/* Array of reserved words */
1091
	$reserved = array("port", "pass");
1092

    
1093
	if (!is_string($name) || strlen($name) >= 32 || preg_match('/(^_*$|^\d*$|[^a-z0-9_])/i', $name)) {
1094
		if ($return_message) {
1095
			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, _');
1096
		} else {
1097
			return false;
1098
		}
1099
	}
1100
	if (in_array($name, $reserved, true)) {
1101
		if ($return_message) {
1102
			return sprintf(gettext('The %1$s name must not be either of the reserved words %2$s or %3$s.'), $object, "'port'", "'pass'");
1103
		} else {
1104
			return false;
1105
		}
1106
	}
1107
	if (getprotobyname($name)) {
1108
		if ($return_message) {
1109
			return sprintf(gettext('The %1$s name must not be an IP protocol name such as TCP, UDP, ICMP etc.'), $object);
1110
		} else {
1111
			return false;
1112
		}
1113
	}
1114
	if (getservbyname($name, "tcp") || getservbyname($name, "udp")) {
1115
		if ($return_message) {
1116
			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);
1117
		} else {
1118
			return false;
1119
		}
1120
	}
1121
	if ($return_message) {
1122
		return sprintf(gettext("The %1$s name is valid."), $object);
1123
	} else {
1124
		return true;
1125
	}
1126
}
1127

    
1128
/* returns a text message indicating if the alias name is valid, or the reason it is not valid. */
1129
function invalidaliasnamemsg($name, $object = "alias") {
1130
	return is_validaliasname($name, true, $object);
1131
}
1132

    
1133
/*
1134
 * returns true if $range is a valid integer range between $min and $max
1135
 * range delimiter can be ':' or '-'
1136
 */
1137
function is_intrange($range, $min, $max) {
1138
	$values = preg_split("/[:-]/", $range);
1139

    
1140
	if (!is_array($values) || count($values) != 2) {
1141
		return false;
1142
	}
1143

    
1144
	if (!ctype_digit($values[0]) || !ctype_digit($values[1])) {
1145
		return false;
1146
	}
1147

    
1148
	$values[0] = intval($values[0]);
1149
	$values[1] = intval($values[1]);
1150

    
1151
	if ($values[0] >= $values[1]) {
1152
		return false;
1153
	}
1154

    
1155
	if ($values[0] < $min || $values[1] > $max) {
1156
		return false;
1157
	}
1158

    
1159
	return true;
1160
}
1161

    
1162
/* returns true if $port is a valid TCP/UDP port */
1163
function is_port($port) {
1164
	if (ctype_digit($port) && ((intval($port) >= 1) && (intval($port) <= 65535))) {
1165
		return true;
1166
	}
1167
	if (getservbyname($port, "tcp") || getservbyname($port, "udp")) {
1168
		return true;
1169
	}
1170
	return false;
1171
}
1172

    
1173
/* returns true if $port is in use */
1174
function is_port_in_use($port, $proto = "tcp", $ip_version = 4) {
1175
	$port_info = array();
1176
	exec("/usr/bin/netstat --libxo json -an " . escapeshellarg('-' . $ip_version) . " -p " . escapeshellarg($proto), $rawdata, $rc);
1177
	if ($rc == 0) {
1178
		$netstatarr = json_decode(implode(" ", $rawdata), JSON_OBJECT_AS_ARRAY);
1179
		$netstatarr = $netstatarr['statistics']['socket'];
1180

    
1181
		foreach($netstatarr as $index => $portstats){
1182
			array_push($port_info, $portstats['local']['port']);
1183
		}
1184
	}
1185

    
1186
	return in_array($port, $port_info);
1187
}
1188

    
1189
/* returns true if $portrange is a valid TCP/UDP portrange ("<port>:<port>") */
1190
function is_portrange($portrange) {
1191
	$ports = explode(":", $portrange);
1192

    
1193
	return (count($ports) == 2 && is_port($ports[0]) && is_port($ports[1]));
1194
}
1195

    
1196
/* returns true if $port is a valid TCP/UDP port number or range ("<port>:<port>") */
1197
function is_port_or_range($port) {
1198
	return (is_port($port) || is_portrange($port));
1199
}
1200

    
1201
/* returns true if $port is an alias that is a port type */
1202
function is_portalias($port) {
1203
	global $config;
1204

    
1205
	if (is_alias($port)) {
1206
		if (is_array($config['aliases']['alias'])) {
1207
			foreach ($config['aliases']['alias'] as $alias) {
1208
				if ($alias['name'] == $port && preg_match("/port/i", $alias['type'])) {
1209
					return true;
1210
				}
1211
			}
1212
		}
1213
	}
1214
	return false;
1215
}
1216

    
1217
/* returns true if $port is a valid port number or an alias thereof */
1218
function is_port_or_alias($port) {
1219
	return (is_port($port) || is_portalias($port));
1220
}
1221

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

    
1227
/* create ranges of sequential port numbers (200:215) and remove duplicates */
1228
function group_ports($ports, $kflc = false) {
1229
	if (!is_array($ports) || empty($ports)) {
1230
		return;
1231
	}
1232

    
1233
	$uniq = array();
1234
	$comments = array();
1235
	foreach ($ports as $port) {
1236
		if (($kflc) && (strpos($port, '#') === 0)) {	// Keep Full Line Comments (lines beginning with #).
1237
			$comments[] = $port;
1238
		} else if (is_portrange($port)) {
1239
			list($begin, $end) = explode(":", $port);
1240
			if ($begin > $end) {
1241
				$aux = $begin;
1242
				$begin = $end;
1243
				$end = $aux;
1244
			}
1245
			for ($i = $begin; $i <= $end; $i++) {
1246
				if (!in_array($i, $uniq)) {
1247
					$uniq[] = $i;
1248
				}
1249
			}
1250
		} else if (is_port($port)) {
1251
			if (!in_array($port, $uniq)) {
1252
				$uniq[] = $port;
1253
			}
1254
		}
1255
	}
1256
	sort($uniq, SORT_NUMERIC);
1257

    
1258
	$result = array();
1259
	foreach ($uniq as $idx => $port) {
1260
		if ($idx == 0) {
1261
			$result[] = $port;
1262
			continue;
1263
		}
1264

    
1265
		$last = end($result);
1266
		if (is_portrange($last)) {
1267
			list($begin, $end) = explode(":", $last);
1268
		} else {
1269
			$begin = $end = $last;
1270
		}
1271

    
1272
		if ($port == ($end+1)) {
1273
			$end++;
1274
			$result[count($result)-1] = "{$begin}:{$end}";
1275
		} else {
1276
			$result[] = $port;
1277
		}
1278
	}
1279

    
1280
	return array_merge($comments, $result);
1281
}
1282

    
1283
/* returns true if $val is a valid shaper bandwidth value */
1284
function is_valid_shaperbw($val) {
1285
	return (preg_match("/^(\d+(?:\.\d+)?)([MKG]?b|%)$/", $val));
1286
}
1287

    
1288
/* returns true if $test is in the range between $start and $end */
1289
function is_inrange_v4($test, $start, $end) {
1290
	if (!is_ipaddrv4($test) || !is_ipaddrv4($start) || !is_ipaddrv4($end)) {
1291
		return false;
1292
	}
1293

    
1294
	if (ip2ulong($test) <= ip2ulong($end) &&
1295
	    ip2ulong($test) >= ip2ulong($start)) {
1296
		return true;
1297
	}
1298

    
1299
	return false;
1300
}
1301

    
1302
/* returns true if $test is in the range between $start and $end */
1303
function is_inrange_v6($test, $start, $end) {
1304
	if (!is_ipaddrv6($test) || !is_ipaddrv6($start) || !is_ipaddrv6($end)) {
1305
		return false;
1306
	}
1307

    
1308
	if (inet_pton($test) <= inet_pton($end) &&
1309
	    inet_pton($test) >= inet_pton($start)) {
1310
		return true;
1311
	}
1312

    
1313
	return false;
1314
}
1315

    
1316
/* returns true if $test is in the range between $start and $end */
1317
function is_inrange($test, $start, $end) {
1318
	return is_ipaddrv6($test) ? is_inrange_v6($test, $start, $end) : is_inrange_v4($test, $start, $end);
1319
}
1320

    
1321
function build_vip_list($fif, $family = "all") {
1322
	$list = array('address' => gettext('Interface Address'));
1323

    
1324
	$viplist = get_configured_vip_list($family);
1325
	foreach ($viplist as $vip => $address) {
1326
		if ($fif == get_configured_vip_interface($vip)) {
1327
			$list[$vip] = "$address";
1328
			if (get_vip_descr($address)) {
1329
				$list[$vip] .= " (". get_vip_descr($address) .")";
1330
			}
1331
		}
1332
	}
1333

    
1334
	return($list);
1335
}
1336

    
1337
function get_configured_vip_list($family = 'all', $type = VIP_ALL) {
1338
	global $config;
1339

    
1340
	$list = array();
1341
	if (!is_array($config['virtualip']) ||
1342
	    !is_array($config['virtualip']['vip']) ||
1343
	    empty($config['virtualip']['vip'])) {
1344
		return ($list);
1345
	}
1346

    
1347
	$viparr = &$config['virtualip']['vip'];
1348
	foreach ($viparr as $vip) {
1349

    
1350
		if ($type == VIP_CARP) {
1351
			if ($vip['mode'] != "carp")
1352
				continue;
1353
		} elseif ($type == VIP_IPALIAS) {
1354
			if ($vip['mode'] != "ipalias")
1355
				continue;
1356
		} else {
1357
			if ($vip['mode'] != "carp" && $vip['mode'] != "ipalias")
1358
				continue;
1359
		}
1360

    
1361
		if ($family == 'all' ||
1362
		    ($family == 'inet' && is_ipaddrv4($vip['subnet'])) ||
1363
		    ($family == 'inet6' && is_ipaddrv6($vip['subnet']))) {
1364
			$list["_vip{$vip['uniqid']}"] = $vip['subnet'];
1365
		}
1366
	}
1367
	return ($list);
1368
}
1369

    
1370
function get_configured_vip($vipinterface = '') {
1371

    
1372
	return (get_configured_vip_detail($vipinterface, 'all', 'vip'));
1373
}
1374

    
1375
function get_configured_vip_interface($vipinterface = '') {
1376

    
1377
	return (get_configured_vip_detail($vipinterface, 'all', 'iface'));
1378
}
1379

    
1380
function get_configured_vip_ipv4($vipinterface = '') {
1381

    
1382
	return (get_configured_vip_detail($vipinterface, 'inet', 'ip'));
1383
}
1384

    
1385
function get_configured_vip_ipv6($vipinterface = '') {
1386

    
1387
	return (get_configured_vip_detail($vipinterface, 'inet6', 'ip'));
1388
}
1389

    
1390
function get_configured_vip_subnetv4($vipinterface = '') {
1391

    
1392
	return (get_configured_vip_detail($vipinterface, 'inet', 'subnet'));
1393
}
1394

    
1395
function get_configured_vip_subnetv6($vipinterface = '') {
1396

    
1397
	return (get_configured_vip_detail($vipinterface, 'inet6', 'subnet'));
1398
}
1399

    
1400
function get_configured_vip_detail($vipinterface = '', $family = 'inet', $what = 'ip') {
1401
	global $config;
1402

    
1403
	if (empty($vipinterface) ||
1404
	    !is_array($config['virtualip']) ||
1405
	    !is_array($config['virtualip']['vip']) ||
1406
	    empty($config['virtualip']['vip'])) {
1407
		return (NULL);
1408
	}
1409

    
1410
	$viparr = &$config['virtualip']['vip'];
1411
	foreach ($viparr as $vip) {
1412
		if ($vip['mode'] != "carp" && $vip['mode'] != "ipalias") {
1413
			continue;
1414
		}
1415

    
1416
		if ($vipinterface != "_vip{$vip['uniqid']}") {
1417
			continue;
1418
		}
1419

    
1420
		switch ($what) {
1421
			case 'subnet':
1422
				if ($family == 'inet' && is_ipaddrv4($vip['subnet']))
1423
					return ($vip['subnet_bits']);
1424
				else if ($family == 'inet6' && is_ipaddrv6($vip['subnet']))
1425
					return ($vip['subnet_bits']);
1426
				break;
1427
			case 'iface':
1428
				return ($vip['interface']);
1429
				break;
1430
			case 'vip':
1431
				return ($vip);
1432
				break;
1433
			case 'ip':
1434
			default:
1435
				if ($family == 'inet' && is_ipaddrv4($vip['subnet'])) {
1436
					return ($vip['subnet']);
1437
				} else if ($family == 'inet6' && is_ipaddrv6($vip['subnet'])) {
1438
					return ($vip['subnet']);
1439
				}
1440
				break;
1441
		}
1442
		break;
1443
	}
1444

    
1445
	return (NULL);
1446
}
1447

    
1448
/* comparison function for sorting by the order in which interfaces are normally created */
1449
function compare_interface_friendly_names($a, $b) {
1450
	if ($a == $b) {
1451
		return 0;
1452
	} else if ($a == 'wan') {
1453
		return -1;
1454
	} else if ($b == 'wan') {
1455
		return 1;
1456
	} else if ($a == 'lan') {
1457
		return -1;
1458
	} else if ($b == 'lan') {
1459
		return 1;
1460
	}
1461

    
1462
	return strnatcmp($a, $b);
1463
}
1464

    
1465
/* return the configured interfaces list. */
1466
function get_configured_interface_list($withdisabled = false) {
1467
	global $config;
1468

    
1469
	$iflist = array();
1470

    
1471
	/* if list */
1472
	foreach ($config['interfaces'] as $if => $ifdetail) {
1473
		if (isset($ifdetail['enable']) || $withdisabled == true) {
1474
			$iflist[$if] = $if;
1475
		}
1476
	}
1477

    
1478
	return $iflist;
1479
}
1480

    
1481
/* return the configured interfaces list. */
1482
function get_configured_interface_list_by_realif($withdisabled = false) {
1483
	global $config;
1484

    
1485
	$iflist = array();
1486

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

    
1497
	return $iflist;
1498
}
1499

    
1500
/* return the configured interfaces list with their description. */
1501
function get_configured_interface_with_descr($withdisabled = false) {
1502
	global $config, $user_settings;
1503

    
1504
	$iflist = array();
1505

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

    
1517
	if ($user_settings['webgui']['interfacessort']) {
1518
		asort($iflist);
1519
	}
1520

    
1521
	return $iflist;
1522
}
1523

    
1524
/*
1525
 *   get_configured_ip_addresses() - Return a list of all configured
1526
 *   IPv4 addresses.
1527
 *
1528
 */
1529
function get_configured_ip_addresses() {
1530
	global $config;
1531

    
1532
	if (!function_exists('get_interface_ip')) {
1533
		require_once("interfaces.inc");
1534
	}
1535
	$ip_array = array();
1536
	$interfaces = get_configured_interface_list();
1537
	if (is_array($interfaces)) {
1538
		foreach ($interfaces as $int) {
1539
			$ipaddr = get_interface_ip($int);
1540
			$ip_array[$int] = $ipaddr;
1541
		}
1542
	}
1543
	$interfaces = get_configured_vip_list('inet');
1544
	if (is_array($interfaces)) {
1545
		foreach ($interfaces as $int => $ipaddr) {
1546
			$ip_array[$int] = $ipaddr;
1547
		}
1548
	}
1549

    
1550
	/* pppoe server */
1551
	if (is_array($config['pppoes']) && is_array($config['pppoes']['pppoe'])) {
1552
		foreach ($config['pppoes']['pppoe'] as $pppoe) {
1553
			if ($pppoe['mode'] == "server") {
1554
				if (is_ipaddr($pppoe['localip'])) {
1555
					$int = "poes". $pppoe['pppoeid'];
1556
					$ip_array[$int] = $pppoe['localip'];
1557
				}
1558
			}
1559
		}
1560
	}
1561

    
1562
	return $ip_array;
1563
}
1564

    
1565
/*
1566
 *   get_configured_ipv6_addresses() - Return a list of all configured
1567
 *   IPv6 addresses.
1568
 *
1569
 */
1570
function get_configured_ipv6_addresses($linklocal_fallback = false) {
1571
	require_once("interfaces.inc");
1572
	$ipv6_array = array();
1573
	$interfaces = get_configured_interface_list();
1574
	if (is_array($interfaces)) {
1575
		foreach ($interfaces as $int) {
1576
			$ipaddrv6 = text_to_compressed_ip6(get_interface_ipv6($int, false, $linklocal_fallback));
1577
			$ipv6_array[$int] = $ipaddrv6;
1578
		}
1579
	}
1580
	$interfaces = get_configured_vip_list('inet6');
1581
	if (is_array($interfaces)) {
1582
		foreach ($interfaces as $int => $ipaddrv6) {
1583
			$ipv6_array[$int] = text_to_compressed_ip6($ipaddrv6);
1584
		}
1585
	}
1586
	return $ipv6_array;
1587
}
1588

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

    
1717
			case "friendly":
1718
				if ($friendly != "") {
1719
					$toput['if'] = $ifname;
1720
					$iflist[$friendly] = $toput;
1721
				}
1722
				break;
1723
			}
1724
		}
1725
	}
1726
	return $iflist;
1727
}
1728

    
1729
function get_lagg_interface_list() {
1730
	global $config;
1731

    
1732
	$plist = array();
1733
	if (isset($config['laggs']['lagg']) && is_array($config['laggs']['lagg'])) {
1734
		foreach ($config['laggs']['lagg'] as $lagg) {
1735
			$lagg['mac'] = get_interface_mac($lagg['laggif']);
1736
			$lagg['islagg'] = true;
1737
			$plist[$lagg['laggif']] = $lagg;
1738
		}
1739
	}
1740

    
1741
	return ($plist);
1742
}
1743

    
1744
/****f* util/log_error
1745
* NAME
1746
*   log_error  - Sends a string to syslog.
1747
* INPUTS
1748
*   $error     - string containing the syslog message.
1749
* RESULT
1750
*   null
1751
******/
1752
function log_error($error) {
1753
	global $g;
1754
	$page = $_SERVER['SCRIPT_NAME'];
1755
	if (empty($page)) {
1756
		$files = get_included_files();
1757
		$page = basename($files[0]);
1758
	}
1759
	syslog(LOG_ERR, "$page: $error");
1760
	if ($g['debug']) {
1761
		syslog(LOG_WARNING, var_dump(debug_backtrace()));
1762
	}
1763
	return;
1764
}
1765

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

    
1784
/****f* util/exec_command
1785
 * NAME
1786
 *   exec_command - Execute a command and return a string of the result.
1787
 * INPUTS
1788
 *   $command   - String of the command to be executed.
1789
 * RESULT
1790
 *   String containing the command's result.
1791
 * NOTES
1792
 *   This function returns the command's stdout and stderr.
1793
 ******/
1794
function exec_command($command) {
1795
	$output = array();
1796
	exec($command . ' 2>&1', $output);
1797
	return(implode("\n", $output));
1798
}
1799

    
1800
/* wrapper for exec()
1801
   Executes in background or foreground.
1802
   For background execution, returns PID of background process to allow calling code control */
1803
function mwexec($command, $nologentry = false, $clearsigmask = false, $background = false) {
1804
	global $g;
1805
	$retval = 0;
1806

    
1807
	if ($g['debug']) {
1808
		if (!$_SERVER['REMOTE_ADDR']) {
1809
			echo "mwexec(): $command" . ($background ? " [BG]":"") . "\n";
1810
		}
1811
	}
1812
	if ($clearsigmask) {
1813
		$oldset = array();
1814
		pcntl_sigprocmask(SIG_SETMASK, array(), $oldset);
1815
	}
1816

    
1817
	if ($background) {
1818
		// start background process and return PID
1819
		$retval = exec("/usr/bin/nohup $command > /dev/null 2>&1 & echo $!");
1820
	} else {
1821
		// run in foreground, and (optionally) log if nonzero return
1822
		$outputarray = array();
1823
		exec("$command 2>&1", $outputarray, $retval);
1824
		if (($retval <> 0) && (!$nologentry || isset($config['system']['developerspew']))) {
1825
			log_error(sprintf(gettext("The command '%1\$s' returned exit code '%2\$d', the output was '%3\$s' "), $command, $retval, implode(" ", $outputarray)));
1826
		}
1827
	}
1828

    
1829
	if ($clearsigmask) {
1830
		pcntl_sigprocmask(SIG_SETMASK, $oldset);
1831
	}
1832

    
1833
	return $retval;
1834
}
1835

    
1836
/* wrapper for exec() in background */
1837
function mwexec_bg($command, $clearsigmask = false) {
1838
	return mwexec($command, false, $clearsigmask, true);
1839
}
1840

    
1841
/*
1842
 * Unlink a file, or pattern-match of a file, if it exists
1843
 *
1844
 * If the file/path contains glob() compatible wildcards, all matching files
1845
 * will be unlinked.
1846
 * Any warning/errors are suppressed (e.g. no matching files to delete)
1847
 * If there are matching file(s) and they were all unlinked OK, then return
1848
 * true.  Otherwise return false (the requested file(s) did not exist, or
1849
 * could not be deleted), this allows the caller to know if they were the one
1850
 * to successfully delete the file(s).
1851
 */
1852
function unlink_if_exists($fn) {
1853
	$to_do = glob($fn);
1854
	if (is_array($to_do) && count($to_do) > 0) {
1855
		// Returns an array of boolean indicating if each unlink worked
1856
		$results = @array_map("unlink", $to_do);
1857
		// If there is no false in the array, then all went well
1858
		$result = !in_array(false, $results, true);
1859
	} else {
1860
		$result = @unlink($fn);
1861
	}
1862
	return $result;
1863
}
1864

    
1865
/* make a global alias table (for faster lookups) */
1866
function alias_make_table() {
1867
	global $aliastable, $config;
1868

    
1869
	$aliastable = array();
1870

    
1871
	init_config_arr(array('aliases', 'alias'));
1872
	foreach ($config['aliases']['alias'] as $alias) {
1873
		if ($alias['name']) {
1874
			$aliastable[$alias['name']] = $alias['address'];
1875
		}
1876
	}
1877
}
1878

    
1879
/* check if an alias exists */
1880
function is_alias($name) {
1881
	global $aliastable;
1882

    
1883
	return isset($aliastable[$name]);
1884
}
1885

    
1886
function alias_get_type($name) {
1887
	global $config;
1888

    
1889
	if (is_array($config['aliases']['alias'])) {
1890
		foreach ($config['aliases']['alias'] as $alias) {
1891
			if ($name == $alias['name']) {
1892
				return $alias['type'];
1893
			}
1894
		}
1895
	}
1896

    
1897
	return "";
1898
}
1899

    
1900
/* expand a host or network alias, if necessary */
1901
function alias_expand($name) {
1902
	global $config, $aliastable;
1903
	$urltable_prefix = "/var/db/aliastables/";
1904
	$urltable_filename = $urltable_prefix . $name . ".txt";
1905

    
1906
	if (isset($aliastable[$name])) {
1907
		// alias names cannot be strictly numeric. redmine #4289
1908
		if (is_numericint($name)) {
1909
			return null;
1910
		}
1911
		/*
1912
		 * make sure if it's a ports alias, it actually exists.
1913
		 * redmine #5845
1914
		 */
1915
		foreach ($config['aliases']['alias'] as $alias) {
1916
			if ($alias['name'] == $name) {
1917
				if ($alias['type'] == "urltable_ports") {
1918
					if (is_URL($alias['url']) &&
1919
					    file_exists($urltable_filename) &&
1920
					    filesize($urltable_filename)) {
1921
						return "\${$name}";
1922
					} else {
1923
						return null;
1924
					}
1925
				}
1926
			}
1927
		}
1928
		return "\${$name}";
1929
	} else if (is_ipaddr($name) || is_subnet($name) ||
1930
	    is_port_or_range($name)) {
1931
		return "{$name}";
1932
	} else {
1933
		return null;
1934
	}
1935
}
1936

    
1937
function alias_expand_urltable($name) {
1938
	global $config;
1939
	$urltable_prefix = "/var/db/aliastables/";
1940
	$urltable_filename = $urltable_prefix . $name . ".txt";
1941

    
1942
	if (!is_array($config['aliases']['alias'])) {
1943
		return null;
1944
	}
1945

    
1946
	foreach ($config['aliases']['alias'] as $alias) {
1947
		if (!preg_match("/urltable/i", $alias['type']) ||
1948
		    ($alias['name'] != $name)) {
1949
			continue;
1950
		}
1951

    
1952
		if (is_URL($alias["url"]) && file_exists($urltable_filename)) {
1953
			if (!filesize($urltable_filename)) {
1954
				// file exists, but is empty, try to sync
1955
				send_event("service sync alias {$name}");
1956
			}
1957
			return $urltable_filename;
1958
		} else {
1959
			send_event("service sync alias {$name}");
1960
			break;
1961
		}
1962
	}
1963
	return null;
1964
}
1965

    
1966
/* obtain MAC address given an IP address by looking at the ARP/NDP table */
1967
function arp_get_mac_by_ip($ip, $do_ping = true) {
1968
	unset($macaddr);
1969
	$retval = 1;
1970
	switch (is_ipaddr($ip)) {
1971
		case 4:
1972
			if ($do_ping === true) {
1973
				mwexec("/sbin/ping -c 1 -t 1 " . escapeshellarg($ip), true);
1974
			}
1975
			$macaddr = exec("/usr/sbin/arp -n " . escapeshellarg($ip) . " | /usr/bin/awk '{print $4}'", $output, $retval);
1976
			break;
1977
		case 6:
1978
			if ($do_ping === true) {
1979
				mwexec("/sbin/ping6 -c 1 -X 1 " . escapeshellarg($ip), true);
1980
			}
1981
			$macaddr = exec("/usr/sbin/ndp -n " . escapeshellarg($ip) . " | /usr/bin/awk '{print $2}'", $output, $retval);
1982
			break;
1983
	}
1984
	if ($retval == 0 && is_macaddr($macaddr)) {
1985
		return $macaddr;
1986
	} else {
1987
		return false;
1988
	}
1989
}
1990

    
1991
/* return a fieldname that is safe for xml usage */
1992
function xml_safe_fieldname($fieldname) {
1993
	$replace = array(
1994
	    '/', '-', ' ', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')',
1995
	    '_', '+', '=', '{', '}', '[', ']', '|', '/', '<', '>', '?',
1996
	    ':', ',', '.', '\'', '\\'
1997
	);
1998
	return strtolower(str_replace($replace, "", $fieldname));
1999
}
2000

    
2001
function mac_format($clientmac) {
2002
	global $config, $cpzone;
2003

    
2004
	$mac = explode(":", $clientmac);
2005
	$mac_format = $cpzone ? $config['captiveportal'][$cpzone]['radmac_format'] : false;
2006

    
2007
	switch ($mac_format) {
2008
		case 'singledash':
2009
			return "$mac[0]$mac[1]$mac[2]-$mac[3]$mac[4]$mac[5]";
2010

    
2011
		case 'ietf':
2012
			return "$mac[0]-$mac[1]-$mac[2]-$mac[3]-$mac[4]-$mac[5]";
2013

    
2014
		case 'cisco':
2015
			return "$mac[0]$mac[1].$mac[2]$mac[3].$mac[4]$mac[5]";
2016

    
2017
		case 'unformatted':
2018
			return "$mac[0]$mac[1]$mac[2]$mac[3]$mac[4]$mac[5]";
2019

    
2020
		default:
2021
			return $clientmac;
2022
	}
2023
}
2024

    
2025
function resolve_retry($hostname, $protocol = 'inet', $retries = 5, $numrecords = 1) {
2026

    
2027
	$recresult = array();
2028
	$returnres = array();
2029
	for ($i = 0; $i < $retries; $i++) {
2030
		switch ($protocol) {
2031
			case 'any':
2032
				$checkproto = 'is_ipaddr';
2033
				$dnsproto = DNS_ANY;
2034
				$dnstype = array('A', 'AAAA');
2035
				break;
2036
			case 'inet6':
2037
				$checkproto = 'is_ipaddrv6';
2038
				$dnsproto = DNS_AAAA;
2039
				$dnstype = array('AAAA');
2040
				break;
2041
			case 'inet': 
2042
			default:
2043
				$checkproto = 'is_ipaddrv4';
2044
				$dnsproto = DNS_A;
2045
				$dnstype = array('A');
2046
				break;
2047
		}
2048
		if ($checkproto($hostname)) {
2049
			return $hostname;
2050
		}
2051
		$dnsresult = @dns_get_record($hostname, $dnsproto);
2052
		if (!empty($dnsresult)) {
2053
			foreach ($dnsresult as $dnsrec => $ip) {
2054
				if (is_array($ip)) {
2055
					if (in_array($ip['type'], $dnstype)) {
2056
					    if ($checkproto($ip['ip'])) { 
2057
						    $recresult[] = $ip['ip'];
2058
					    }
2059
					    if ($checkproto($ip['ipv6'])) { 
2060
						    $recresult[] = $ip['ipv6'];
2061
					    }
2062
					}
2063
				}
2064
			}
2065
		}
2066

    
2067
		sleep(1);
2068
	}
2069

    
2070
	if (!empty($recresult)) {
2071
		if ($numrecords == 1) {
2072
			return $recresult[0];
2073
		} else {
2074
			return array_slice($recresult, 0, $numrecords);
2075
		}
2076
	}
2077

    
2078
	return false;
2079
}
2080

    
2081
function format_bytes($bytes) {
2082
	if ($bytes >= 1099511627776) {
2083
		return sprintf("%.2f TiB", $bytes/1099511627776);
2084
	} else if ($bytes >= 1073741824) {
2085
		return sprintf("%.2f GiB", $bytes/1073741824);
2086
	} else if ($bytes >= 1048576) {
2087
		return sprintf("%.2f MiB", $bytes/1048576);
2088
	} else if ($bytes >= 1024) {
2089
		return sprintf("%.0f KiB", $bytes/1024);
2090
	} else {
2091
		return sprintf("%d B", $bytes);
2092
	}
2093
}
2094

    
2095
function format_number($num, $precision = 3) {
2096
	$units = array('', 'K', 'M', 'G', 'T');
2097

    
2098
	$i = 0;
2099
	while ($num > 1000 && $i < count($units)) {
2100
		$num /= 1000;
2101
		$i++;
2102
	}
2103
	$num = round($num, $precision);
2104

    
2105
	return ("$num {$units[$i]}");
2106
}
2107

    
2108

    
2109
function unformat_number($formated_num) {
2110
	$num = strtoupper($formated_num);
2111
    
2112
	if ( strpos($num,"T") !== false ) {
2113
		$num = str_replace("T","",$num) * 1000 * 1000 * 1000 * 1000;
2114
	} else if ( strpos($num,"G") !== false ) {
2115
		$num = str_replace("G","",$num) * 1000 * 1000 * 1000;
2116
	} else if ( strpos($num,"M") !== false ) {
2117
		$num = str_replace("M","",$num) * 1000 * 1000;
2118
	} else if ( strpos($num,"K") !== false ) {
2119
		$num = str_replace("K","",$num) * 1000;
2120
	}
2121
    
2122
	return $num;
2123
}
2124

    
2125
function update_filter_reload_status($text, $new=false) {
2126
	global $g;
2127

    
2128
	if ($new) {
2129
		file_put_contents("{$g['varrun_path']}/filter_reload_status", $text  . PHP_EOL);
2130
	} else {
2131
		file_put_contents("{$g['varrun_path']}/filter_reload_status", $text  . PHP_EOL, FILE_APPEND);
2132
	}
2133
}
2134

    
2135
/****** util/return_dir_as_array
2136
 * NAME
2137
 *   return_dir_as_array - Return a directory's contents as an array.
2138
 * INPUTS
2139
 *   $dir          - string containing the path to the desired directory.
2140
 *   $filter_regex - string containing a regular expression to filter file names. Default empty.
2141
 * RESULT
2142
 *   $dir_array - array containing the directory's contents. This array will be empty if the path specified is invalid.
2143
 ******/
2144
function return_dir_as_array($dir, $filter_regex = '') {
2145
	$dir_array = array();
2146
	if (is_dir($dir)) {
2147
		if ($dh = opendir($dir)) {
2148
			while (($file = readdir($dh)) !== false) {
2149
				if (($file == ".") || ($file == "..")) {
2150
					continue;
2151
				}
2152

    
2153
				if (empty($filter_regex) || preg_match($filter_regex, $file)) {
2154
					array_push($dir_array, $file);
2155
				}
2156
			}
2157
			closedir($dh);
2158
		}
2159
	}
2160
	return $dir_array;
2161
}
2162

    
2163
function run_plugins($directory) {
2164
	global $config, $g;
2165

    
2166
	/* process packager manager custom rules */
2167
	$files = return_dir_as_array($directory);
2168
	if (is_array($files)) {
2169
		foreach ($files as $file) {
2170
			if (stristr($file, ".sh") == true) {
2171
				mwexec($directory . $file . " start");
2172
			} else if (!is_dir($directory . "/" . $file) && stristr($file, ".inc")) {
2173
				require_once($directory . "/" . $file);
2174
			}
2175
		}
2176
	}
2177
}
2178

    
2179
/*
2180
 *    safe_mkdir($path, $mode = 0755)
2181
 *    create directory if it doesn't already exist and isn't a file!
2182
 */
2183
function safe_mkdir($path, $mode = 0755) {
2184
	global $g;
2185

    
2186
	if (!is_file($path) && !is_dir($path)) {
2187
		return @mkdir($path, $mode, true);
2188
	} else {
2189
		return false;
2190
	}
2191
}
2192

    
2193
/*
2194
 * get_sysctl($names)
2195
 * Get values of sysctl OID's listed in $names (accepts an array or a single
2196
 * name) and return an array of key/value pairs set for those that exist
2197
 */
2198
function get_sysctl($names) {
2199
	if (empty($names)) {
2200
		return array();
2201
	}
2202

    
2203
	if (is_array($names)) {
2204
		$name_list = array();
2205
		foreach ($names as $name) {
2206
			$name_list[] = escapeshellarg($name);
2207
		}
2208
	} else {
2209
		$name_list = array(escapeshellarg($names));
2210
	}
2211

    
2212
	exec("/sbin/sysctl -iq " . implode(" ", $name_list), $output);
2213
	$values = array();
2214
	foreach ($output as $line) {
2215
		$line = explode(": ", $line, 2);
2216
		if (count($line) == 2) {
2217
			$values[$line[0]] = $line[1];
2218
		}
2219
	}
2220

    
2221
	return $values;
2222
}
2223

    
2224
/*
2225
 * get_single_sysctl($name)
2226
 * Wrapper for get_sysctl() to simplify read of a single sysctl value
2227
 * return the value for sysctl $name or empty string if it doesn't exist
2228
 */
2229
function get_single_sysctl($name) {
2230
	if (empty($name)) {
2231
		return "";
2232
	}
2233

    
2234
	$value = get_sysctl($name);
2235
	if (empty($value) || !isset($value[$name])) {
2236
		return "";
2237
	}
2238

    
2239
	return $value[$name];
2240
}
2241

    
2242
/*
2243
 * set_sysctl($value_list)
2244
 * Set sysctl OID's listed as key/value pairs and return
2245
 * an array with keys set for those that succeeded
2246
 */
2247
function set_sysctl($values) {
2248
	if (empty($values)) {
2249
		return array();
2250
	}
2251

    
2252
	$value_list = array();
2253
	foreach ($values as $key => $value) {
2254
		$value_list[] = escapeshellarg($key) . "=" . escapeshellarg($value);
2255
	}
2256

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

    
2259
	/* Retry individually if failed (one or more read-only) */
2260
	if ($success <> 0 && count($value_list) > 1) {
2261
		foreach ($value_list as $value) {
2262
			exec("/sbin/sysctl -iq " . $value, $output);
2263
		}
2264
	}
2265

    
2266
	$ret = array();
2267
	foreach ($output as $line) {
2268
		$line = explode(": ", $line, 2);
2269
		if (count($line) == 2) {
2270
			$ret[$line[0]] = true;
2271
		}
2272
	}
2273

    
2274
	return $ret;
2275
}
2276

    
2277
/*
2278
 * set_single_sysctl($name, $value)
2279
 * Wrapper to set_sysctl() to make it simple to set only one sysctl
2280
 * returns boolean meaning if it succeeded
2281
 */
2282
function set_single_sysctl($name, $value) {
2283
	if (empty($name)) {
2284
		return false;
2285
	}
2286

    
2287
	$result = set_sysctl(array($name => $value));
2288

    
2289
	if (!isset($result[$name]) || $result[$name] != $value) {
2290
		return false;
2291
	}
2292

    
2293
	return true;
2294
}
2295

    
2296
/*
2297
 *     get_memory()
2298
 *     returns an array listing the amount of
2299
 *     memory installed in the hardware
2300
 *     [0] net memory available for the OS (FreeBSD) after some is taken by BIOS, video or whatever - e.g. 235 MBytes
2301
 *     [1] real (actual) memory of the system, should be the size of the RAM card/s - e.g. 256 MBytes
2302
 */
2303
function get_memory() {
2304
	$physmem = get_single_sysctl("hw.physmem");
2305
	$realmem = get_single_sysctl("hw.realmem");
2306
	/* convert from bytes to megabytes */
2307
	return array(($physmem/1048576), ($realmem/1048576));
2308
}
2309

    
2310
function mute_kernel_msgs() {
2311
	global $g, $config;
2312

    
2313
	if ($config['system']['enableserial']) {
2314
		return;
2315
	}
2316
	exec("/sbin/conscontrol mute on");
2317
}
2318

    
2319
function unmute_kernel_msgs() {
2320
	global $g;
2321

    
2322
	exec("/sbin/conscontrol mute off");
2323
}
2324

    
2325
function start_devd() {
2326
	global $g;
2327

    
2328
	/* Generate hints for the kernel loader. */
2329
	$module_paths = explode(";", get_single_sysctl("kern.module_path"));
2330
	foreach ($module_paths as $id => $path) {
2331
		if (!is_dir($path) || file_exists("{$path}/linker.hints")) {
2332
			continue;
2333
		}
2334
		if (($files = scandir($path)) == false) {
2335
			continue;
2336
		}
2337
		$found = false;
2338
		foreach ($files as $id => $file) {
2339
			if (strlen($file) > 3 &&
2340
			    strcasecmp(substr($file, -3), ".ko") == 0) {
2341
				$found = true;
2342
				break;
2343
			}
2344
		}
2345
		if ($found == false) {
2346
			continue;
2347
		}
2348
		$_gb = exec("/usr/sbin/kldxref $path");
2349
		unset($_gb);
2350
	}
2351

    
2352
	/* Use the undocumented -q options of devd to quiet its log spamming */
2353
	$_gb = exec("/sbin/devd -q -f /etc/{$g['product_name']}-devd.conf");
2354
	sleep(1);
2355
	unset($_gb);
2356
}
2357

    
2358
function is_interface_vlan_mismatch() {
2359
	global $config, $g;
2360

    
2361
	if (is_array($config['vlans']['vlan'])) {
2362
		foreach ($config['vlans']['vlan'] as $vlan) {
2363
			if (substr($vlan['if'], 0, 4) == "lagg") {
2364
				return false;
2365
			}
2366
			if (does_interface_exist($vlan['if']) == false) {
2367
				return true;
2368
			}
2369
		}
2370
	}
2371

    
2372
	return false;
2373
}
2374

    
2375
function is_interface_mismatch() {
2376
	global $config, $g;
2377

    
2378
	$do_assign = false;
2379
	$i = 0;
2380
	$missing_interfaces = array();
2381
	if (is_array($config['interfaces'])) {
2382
		foreach ($config['interfaces'] as $ifname => $ifcfg) {
2383
			if (interface_is_vlan($ifcfg['if']) != NULL ||
2384
			    interface_is_qinq($ifcfg['if']) != NULL ||
2385
			    preg_match("/^enc|^cua|^tun|^tap|^l2tp|^pptp|^ppp|^ovpn|^ipsec|^gif|^gre|^lagg|^bridge|vlan|^wg|_wlan|_\d{0,4}_\d{0,4}$/i", $ifcfg['if'])) {
2386
				// Do not check these interfaces.
2387
				$i++;
2388
				continue;
2389
			} else if (does_interface_exist($ifcfg['if']) == false) {
2390
				$missing_interfaces[] = $ifcfg['if'];
2391
				$do_assign = true;
2392
			} else {
2393
				$i++;
2394
			}
2395
		}
2396
	}
2397

    
2398
	if (file_exists("{$g['tmp_path']}/assign_complete")) {
2399
		$do_assign = false;
2400
	}
2401

    
2402
	if (!empty($missing_interfaces) && $do_assign) {
2403
		file_put_contents("{$g['tmp_path']}/missing_interfaces", implode(' ', $missing_interfaces));
2404
	} else {
2405
		@unlink("{$g['tmp_path']}/missing_interfaces");
2406
	}
2407

    
2408
	return $do_assign;
2409
}
2410

    
2411
/* sync carp entries to other firewalls */
2412
function carp_sync_client() {
2413
	global $g;
2414
	send_event("filter sync");
2415
}
2416

    
2417
/****f* util/isAjax
2418
 * NAME
2419
 *   isAjax - reports if the request is driven from prototype
2420
 * INPUTS
2421
 *   none
2422
 * RESULT
2423
 *   true/false
2424
 ******/
2425
function isAjax() {
2426
	return isset ($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest';
2427
}
2428

    
2429
/****f* util/timeout
2430
 * NAME
2431
 *   timeout - console input with timeout countdown. Note: erases 2 char of screen for timer. Leave space.
2432
 * INPUTS
2433
 *   optional, seconds to wait before timeout. Default 9 seconds.
2434
 * RESULT
2435
 *   returns 1 char of user input or null if no input.
2436
 ******/
2437
function timeout($timer = 9) {
2438
	while (!isset($key)) {
2439
		if ($timer >= 9) {
2440
			echo chr(8) . chr(8) . ($timer == 9 ? chr(32) : null) . "{$timer}";
2441
		} else {
2442
			echo chr(8). "{$timer}";
2443
		}
2444
		`/bin/stty -icanon min 0 time 25`;
2445
		$key = trim(`KEY=\`dd count=1 2>/dev/null\`; echo \$KEY`);
2446
		`/bin/stty icanon`;
2447
		if ($key == '') {
2448
			unset($key);
2449
		}
2450
		$timer--;
2451
		if ($timer == 0) {
2452
			break;
2453
		}
2454
	}
2455
	return $key;
2456
}
2457

    
2458
/****f* util/msort
2459
 * NAME
2460
 *   msort - sort array
2461
 * INPUTS
2462
 *   $array to be sorted, field to sort by, direction of sort
2463
 * RESULT
2464
 *   returns newly sorted array
2465
 ******/
2466
function msort($array, $id = "id", $sort_ascending = true) {
2467
	$temp_array = array();
2468
	if (!is_array($array)) {
2469
		return $temp_array;
2470
	}
2471
	while (count($array)>0) {
2472
		$lowest_id = 0;
2473
		$index = 0;
2474
		foreach ($array as $item) {
2475
			if (isset($item[$id])) {
2476
				if ($array[$lowest_id][$id]) {
2477
					if (strtolower($item[$id]) < strtolower($array[$lowest_id][$id])) {
2478
						$lowest_id = $index;
2479
					}
2480
				}
2481
			}
2482
			$index++;
2483
		}
2484
		$temp_array[] = $array[$lowest_id];
2485
		$array = array_merge(array_slice($array, 0, $lowest_id), array_slice($array, $lowest_id + 1));
2486
	}
2487
	if ($sort_ascending) {
2488
		return $temp_array;
2489
	} else {
2490
		return array_reverse($temp_array);
2491
	}
2492
}
2493

    
2494
/****f* util/is_URL
2495
 * NAME
2496
 *   is_URL
2497
 * INPUTS
2498
 *   string to check
2499
 * RESULT
2500
 *   Returns true if item is a URL
2501
 ******/
2502
function is_URL($url) {
2503
	$match = preg_match("'\b(([\w-]+://?|www[.])[^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|/)))'", $url);
2504
	if ($match) {
2505
		return true;
2506
	}
2507
	return false;
2508
}
2509

    
2510
function is_file_included($file = "") {
2511
	$files = get_included_files();
2512
	if (in_array($file, $files)) {
2513
		return true;
2514
	}
2515

    
2516
	return false;
2517
}
2518

    
2519
/*
2520
 * Replace a value on a deep associative array using regex
2521
 */
2522
function array_replace_values_recursive($data, $match, $replace) {
2523
	if (empty($data)) {
2524
		return $data;
2525
	}
2526

    
2527
	if (is_string($data)) {
2528
		$data = preg_replace("/{$match}/", $replace, $data);
2529
	} else if (is_array($data)) {
2530
		foreach ($data as $k => $v) {
2531
			$data[$k] = array_replace_values_recursive($v, $match, $replace);
2532
		}
2533
	}
2534

    
2535
	return $data;
2536
}
2537

    
2538
/*
2539
	This function was borrowed from a comment on PHP.net at the following URL:
2540
	http://www.php.net/manual/en/function.array-merge-recursive.php#73843
2541
 */
2542
function array_merge_recursive_unique($array0, $array1) {
2543

    
2544
	$arrays = func_get_args();
2545
	$remains = $arrays;
2546

    
2547
	// We walk through each arrays and put value in the results (without
2548
	// considering previous value).
2549
	$result = array();
2550

    
2551
	// loop available array
2552
	foreach ($arrays as $array) {
2553

    
2554
		// The first remaining array is $array. We are processing it. So
2555
		// we remove it from remaining arrays.
2556
		array_shift($remains);
2557

    
2558
		// We don't care non array param, like array_merge since PHP 5.0.
2559
		if (is_array($array)) {
2560
			// Loop values
2561
			foreach ($array as $key => $value) {
2562
				if (is_array($value)) {
2563
					// we gather all remaining arrays that have such key available
2564
					$args = array();
2565
					foreach ($remains as $remain) {
2566
						if (array_key_exists($key, $remain)) {
2567
							array_push($args, $remain[$key]);
2568
						}
2569
					}
2570

    
2571
					if (count($args) > 2) {
2572
						// put the recursion
2573
						$result[$key] = call_user_func_array(__FUNCTION__, $args);
2574
					} else {
2575
						foreach ($value as $vkey => $vval) {
2576
							if (!is_array($result[$key])) {
2577
								$result[$key] = array();
2578
							}
2579
							$result[$key][$vkey] = $vval;
2580
						}
2581
					}
2582
				} else {
2583
					// simply put the value
2584
					$result[$key] = $value;
2585
				}
2586
			}
2587
		}
2588
	}
2589
	return $result;
2590
}
2591

    
2592

    
2593
/*
2594
 * converts a string like "a,b,c,d"
2595
 * into an array like array("a" => "b", "c" => "d")
2596
 */
2597
function explode_assoc($delimiter, $string) {
2598
	$array = explode($delimiter, $string);
2599
	$result = array();
2600
	$numkeys = floor(count($array) / 2);
2601
	for ($i = 0; $i < $numkeys; $i += 1) {
2602
		$result[$array[$i * 2]] = $array[$i * 2 + 1];
2603
	}
2604
	return $result;
2605
}
2606

    
2607
/*
2608
 * Given a string of text with some delimiter, look for occurrences
2609
 * of some string and replace all of those.
2610
 * $text - the text string (e.g. "abc,defg,x123,ipv4,xyz")
2611
 * $delimiter - the delimiter (e.g. ",")
2612
 * $element - the element to match (e.g. "defg")
2613
 * $replacement - the string to replace it with (e.g. "42")
2614
 * Returns the resulting delimited string (e.g. "abc,42,x123,ipv4,xyz")
2615
 */
2616
function replace_element_in_list($text, $delimiter, $element, $replacement) {
2617
	$textArray = explode($delimiter, $text);
2618
	while (($entry = array_search($element, $textArray)) !== false) {
2619
		$textArray[$entry] = $replacement;
2620
	}
2621
	return implode(',', $textArray);
2622
}
2623

    
2624
/* Return system's route table */
2625
function route_table() {
2626
	$_gb = exec("/usr/bin/netstat --libxo json -nWr", $rawdata, $rc);
2627

    
2628
	if ($rc != 0) {
2629
		return array();
2630
	}
2631

    
2632
	$netstatarr = json_decode(implode(" ", $rawdata), JSON_OBJECT_AS_ARRAY);
2633
	$netstatarr = $netstatarr['statistics']['route-information']
2634
	    ['route-table']['rt-family'];
2635

    
2636
	$result = array();
2637
	$result['inet'] = array();
2638
	$result['inet6'] = array();
2639
	foreach ($netstatarr as $item) {
2640
		if ($item['address-family'] == 'Internet') {
2641
			$result['inet'] = $item['rt-entry'];
2642
		} else if ($item['address-family'] == 'Internet6') {
2643
			$result['inet6'] = $item['rt-entry'];
2644
		}
2645
	}
2646
	unset($netstatarr);
2647

    
2648
	return $result;
2649
}
2650

    
2651
/* Get static route for specific destination */
2652
function route_get($target, $ipprotocol = '') {
2653
	if (!empty($ipprotocol)) {
2654
		$family = $ipprotocol;
2655
	} else if (is_v4($target)) {
2656
		$family = 'inet';
2657
	} else if (is_v6($target)) {
2658
		$family = 'inet6';
2659
	}
2660

    
2661
	if (empty($family)) {
2662
		return array();
2663
	}
2664

    
2665
	$rtable = route_table();
2666

    
2667
	if (empty($rtable)) {
2668
		return array();
2669
	}
2670

    
2671
	$result = array();
2672
	foreach ($rtable[$family] as $item) {
2673
		if ($item['destination'] == $target ||
2674
		    ip_in_subnet($target, $item['destination'])) {
2675
			$result[] = $item;
2676
		}
2677
	}
2678

    
2679
	return $result;
2680
}
2681

    
2682
/* Get default route */
2683
function route_get_default($ipprotocol) {
2684
	if (empty($ipprotocol) || ($ipprotocol != 'inet' &&
2685
	    $ipprotocol != 'inet6')) {
2686
		return '';
2687
	}
2688

    
2689
	$route = route_get('default', $ipprotocol);
2690

    
2691
	if (empty($route)) {
2692
		return '';
2693
	}
2694

    
2695
	if (!isset($route[0]['gateway'])) {
2696
		return '';
2697
	}
2698

    
2699
	return $route[0]['gateway'];
2700
}
2701

    
2702
/* Delete a static route */
2703
function route_del($target, $ipprotocol = '') {
2704
	global $config;
2705

    
2706
	if (empty($target)) {
2707
		return;
2708
	}
2709

    
2710
	if (!empty($ipprotocol) && $ipprotocol != 'inet' &&
2711
	    $ipprotocol != 'inet6') {
2712
		return false;
2713
	}
2714

    
2715
	$route = route_get($target, $ipprotocol);
2716

    
2717
	if (empty($route)) {
2718
		return;
2719
	}
2720

    
2721
	$target_prefix = '';
2722
	if (is_subnet($target)) {
2723
		$target_prefix = '-net';
2724
	} else if (is_ipaddr($target)) {
2725
		$target_prefix = '-host';
2726
	}
2727

    
2728
	if (!empty($ipprotocol)) {
2729
		$target_prefix .= " -{$ipprotocol}";
2730
	} else if (is_v6($target)) {
2731
		$target_prefix .= ' -inet6';
2732
	} else if (is_v4($target)) {
2733
		$target_prefix .= ' -inet';
2734
	}
2735

    
2736
	foreach ($route as $item) {
2737
		if (substr($item['gateway'], 0, 5) == 'link#') {
2738
			continue;
2739
		}
2740

    
2741
		if (is_macaddr($item['gateway'])) {
2742
			$gw = '-iface ' . $item['interface-name'];
2743
		} else {
2744
			$gw = $item['gateway'];
2745
		}
2746

    
2747
		$_gb = exec(escapeshellcmd("/sbin/route del {$target_prefix} " .
2748
		    "{$target} {$gw}"), $output, $rc);
2749

    
2750
		if (isset($config['system']['route-debug'])) {
2751
			log_error("ROUTING debug: " . microtime() .
2752
			    " - DEL RC={$rc} - {$target} - gw: " . $gw);
2753
			file_put_contents("/dev/console", "\n[" . getmypid() .
2754
			    "] ROUTE DEL: {$target_prefix} {$target} {$gw} " .
2755
			    "result: {$rc}");
2756
		}
2757
	}
2758
}
2759

    
2760
/*
2761
 * Add static route.  If it already exists, remove it and re-add
2762
 *
2763
 * $target - IP, subnet or 'default'
2764
 * $gw     - gateway address
2765
 * $iface  - Network interface
2766
 * $args   - Extra arguments for /sbin/route
2767
 * $ipprotocol - 'inet' or 'inet6'.  Mandatory when $target == 'default'
2768
 *
2769
 */
2770
function route_add_or_change($target, $gw, $iface = '', $args = '',
2771
    $ipprotocol = '') {
2772
	global $config;
2773

    
2774
	if (empty($target) || (empty($gw) && empty($iface))) {
2775
		return false;
2776
	}
2777

    
2778
	if ($target == 'default' && empty($ipprotocol)) {
2779
		return false;
2780
	}
2781

    
2782
	if (!empty($ipprotocol) && $ipprotocol != 'inet' &&
2783
	    $ipprotocol != 'inet6') {
2784
		return false;
2785
	}
2786

    
2787
	if (is_subnet($target)) {
2788
		$target_prefix = '-net';
2789
	} else if (is_ipaddr($target)) {
2790
		$target_prefix = '-host';
2791
	}
2792

    
2793
	if (!empty($ipprotocol)) {
2794
		$target_prefix .= " -{$ipprotocol}";
2795
	} else if (is_v6($target)) {
2796
		$target_prefix .= ' -inet6';
2797
	} else if (is_v4($target)) {
2798
		$target_prefix .= ' -inet';
2799
	}
2800

    
2801
	/* If there is another route to the same target, remove it */
2802
	route_del($target, $ipprotocol);
2803

    
2804
	$params = '';
2805
	if (!empty($iface) && does_interface_exist($iface)) {
2806
		$params .= " -iface {$gw}";
2807
	}
2808
	if (is_ipaddr($gw)) {
2809
		$params .= " " . $gw;
2810
	}
2811

    
2812
	if (empty($params)) {
2813
		log_error("route_add_or_change: Invalid gateway {$gw} and/or " .
2814
		    "network interface {$iface}");
2815
		return false;
2816
	}
2817

    
2818
	$_gb = exec(escapeshellcmd("/sbin/route add {$target_prefix} " .
2819
	    "{$target} {$args} {$params}"), $output, $rc);
2820

    
2821
	if (isset($config['system']['route-debug'])) {
2822
		log_error("ROUTING debug: " . microtime() .
2823
		    " - ADD RC={$rc} - {$target} {$args}");
2824
		file_put_contents("/dev/console", "\n[" . getmypid() .
2825
		    "] ROUTE ADD: {$target_prefix} {$target} {$args} " .
2826
		    "{$params} result: {$rc}");
2827
	}
2828

    
2829
	return ($rc == 0);
2830
}
2831

    
2832
function set_ipv6routes_mtu($interface, $mtu) {
2833
	global $config, $g;
2834

    
2835
	$ipv6mturoutes = array();
2836
	$if = convert_real_interface_to_friendly_interface_name($interface);
2837
	if (!$config['interfaces'][$if]['ipaddrv6']) {
2838
		return;
2839
	}
2840
	$a_gateways = return_gateways_array();
2841
	$a_staticroutes = get_staticroutes(false, false, true);
2842
	foreach ($a_gateways as $gate) {
2843
		foreach ($a_staticroutes as $sroute) {
2844
			if ($gate['interface'] == $interface &&
2845
			    $sroute['gateway'] == $gate['name']) {
2846
				$tgt = $sroute['network'];
2847
				$gateway = $gate['gateway'];
2848
				$ipv6mturoutes[$tgt] = $gateway;
2849
			}
2850
		}
2851
		if ($gate['interface'] == $interface &&
2852
		    $gate['isdefaultgw']) {
2853
			$tgt = "default";
2854
			$gateway = $gate['gateway'];
2855
			$ipv6mturoutes[$tgt] = $gateway;
2856
		}
2857
	}
2858
	foreach ($ipv6mturoutes as $tgt => $gateway) {
2859
		mwexec("/sbin/route change -6 -mtu " . escapeshellarg($mtu) .
2860
		    " " . escapeshellarg($tgt) . " " .
2861
		    escapeshellarg($gateway));
2862
	}
2863
}
2864

    
2865
function alias_to_subnets_recursive($name, $returnhostnames = false) {
2866
	global $aliastable;
2867
	$result = array();
2868
	if (!isset($aliastable[$name])) {
2869
		return $result;
2870
	}
2871
	$subnets = preg_split('/\s+/', $aliastable[$name]);
2872
	foreach ($subnets as $net) {
2873
		if (is_alias($net)) {
2874
			$sub = alias_to_subnets_recursive($net, $returnhostnames);
2875
			$result = array_merge($result, $sub);
2876
			continue;
2877
		} elseif (!is_subnet($net)) {
2878
			if (is_ipaddrv4($net)) {
2879
				$net .= "/32";
2880
			} else if (is_ipaddrv6($net)) {
2881
				$net .= "/128";
2882
			} else if ($returnhostnames === false || !is_fqdn($net)) {
2883
				continue;
2884
			}
2885
		}
2886
		$result[] = $net;
2887
	}
2888
	return $result;
2889
}
2890

    
2891
function get_staticroutes($returnsubnetsonly = false, $returnhostnames = false, $returnenabledroutesonly = false) {
2892
	global $config, $aliastable;
2893

    
2894
	/* Bail if there are no routes, but return an array always so callers don't have to check. */
2895
	init_config_arr(array('staticroutes', 'route'));
2896
	if (empty($config['staticroutes']['route'])) {
2897
		return array();
2898
	}
2899

    
2900
	$allstaticroutes = array();
2901
	$allsubnets = array();
2902
	/* Loop through routes and expand aliases as we find them. */
2903
	foreach ($config['staticroutes']['route'] as $route) {
2904
		if ($returnenabledroutesonly && isset($route['disabled'])) {
2905
			continue;
2906
		}
2907

    
2908
		if (is_alias($route['network'])) {
2909
			foreach (alias_to_subnets_recursive($route['network'], $returnhostnames) as $net) {
2910
				$temproute = $route;
2911
				$temproute['network'] = $net;
2912
				$allstaticroutes[] = $temproute;
2913
				$allsubnets[] = $net;
2914
			}
2915
		} elseif (is_subnet($route['network'])) {
2916
			$allstaticroutes[] = $route;
2917
			$allsubnets[] = $route['network'];
2918
		}
2919
	}
2920
	if ($returnsubnetsonly) {
2921
		return $allsubnets;
2922
	} else {
2923
		return $allstaticroutes;
2924
	}
2925
}
2926

    
2927
/****f* util/get_alias_list
2928
 * NAME
2929
 *   get_alias_list - Provide a list of aliases.
2930
 * INPUTS
2931
 *   $type          - Optional, can be a string or array specifying what type(s) of aliases you need.
2932
 * RESULT
2933
 *   Array containing list of aliases.
2934
 *   If $type is unspecified, all aliases are returned.
2935
 *   If $type is a string, all aliases of the type specified in $type are returned.
2936
 *   If $type is an array, all aliases of any type specified in any element of $type are returned.
2937
 */
2938
function get_alias_list($type = null) {
2939
	global $config;
2940
	$result = array();
2941
	if ($config['aliases']['alias'] <> "" && is_array($config['aliases']['alias'])) {
2942
		foreach ($config['aliases']['alias'] as $alias) {
2943
			if ($type === null) {
2944
				$result[] = $alias['name'];
2945
			} else if (is_array($type)) {
2946
				if (in_array($alias['type'], $type)) {
2947
					$result[] = $alias['name'];
2948
				}
2949
			} else if ($type === $alias['type']) {
2950
				$result[] = $alias['name'];
2951
			}
2952
		}
2953
	}
2954
	return $result;
2955
}
2956

    
2957
/* returns an array consisting of every element of $haystack that is not equal to $needle. */
2958
function array_exclude($needle, $haystack) {
2959
	$result = array();
2960
	if (is_array($haystack)) {
2961
		foreach ($haystack as $thing) {
2962
			if ($needle !== $thing) {
2963
				$result[] = $thing;
2964
			}
2965
		}
2966
	}
2967
	return $result;
2968
}
2969

    
2970
/* Define what is preferred, IPv4 or IPv6 */
2971
function prefer_ipv4_or_ipv6() {
2972
	global $config;
2973

    
2974
	if (isset($config['system']['prefer_ipv4'])) {
2975
		mwexec("/etc/rc.d/ip6addrctl prefer_ipv4");
2976
	} else {
2977
		mwexec("/etc/rc.d/ip6addrctl prefer_ipv6");
2978
	}
2979
}
2980

    
2981
/* Redirect to page passing parameters via POST */
2982
function post_redirect($page, $params) {
2983
	if (!is_array($params)) {
2984
		return;
2985
	}
2986

    
2987
	print "<html><body><form action=\"{$page}\" name=\"formredir\" method=\"post\">\n";
2988
	foreach ($params as $key => $value) {
2989
		print "<input type=\"hidden\" name=\"{$key}\" value=\"{$value}\" />\n";
2990
	}
2991
	print "</form>\n";
2992
	print "<script type=\"text/javascript\">\n";
2993
	print "//<![CDATA[\n";
2994
	print "document.formredir.submit();\n";
2995
	print "//]]>\n";
2996
	print "</script>\n";
2997
	print "</body></html>\n";
2998
}
2999

    
3000
/* Locate disks that can be queried for S.M.A.R.T. data. */
3001
function get_smart_drive_list() {
3002
	/* SMART supports some disks directly, and some controllers directly,
3003
	 * See https://redmine.pfsense.org/issues/9042 */
3004
	$supported_disk_types = array("ad", "da", "ada");
3005
	$supported_controller_types = array("nvme");
3006
	$disk_list = explode(" ", get_single_sysctl("kern.disks"));
3007
	foreach ($disk_list as $id => $disk) {
3008
		// We only want certain kinds of disks for S.M.A.R.T.
3009
		// 1 is a match, 0 is no match, False is any problem processing the regex
3010
		if (preg_match("/^(" . implode("|", $supported_disk_types) . ").*[0-9]{1,2}$/", $disk) !== 1) {
3011
			unset($disk_list[$id]);
3012
			continue;
3013
		}
3014
	}
3015
	foreach ($supported_controller_types as $controller) {
3016
		$devices = glob("/dev/{$controller}*");
3017
		if (!is_array($devices)) {
3018
			continue;
3019
		}
3020
		foreach ($devices as $device) {
3021
			$disk_list[] = basename($device);
3022
		}
3023
	}
3024
	sort($disk_list);
3025
	return $disk_list;
3026
}
3027

    
3028
// Validate a network address
3029
//	$addr: the address to validate
3030
//	$type: IPV4|IPV6|IPV4V6
3031
//	$label: the label used by the GUI to display this value. Required to compose an error message
3032
//	$err_msg: pointer to the callers error message array so that error messages can be added to it here
3033
//	$alias: are aliases permitted for this address?
3034
// Returns:
3035
//	IPV4 - if $addr is a valid IPv4 address
3036
//	IPV6 - if $addr is a valid IPv6 address
3037
//	ALIAS - if $alias=true and $addr is an alias
3038
//	false - otherwise
3039

    
3040
function validateipaddr(&$addr, $type, $label, &$err_msg, $alias=false) {
3041
	switch ($type) {
3042
		case IPV4:
3043
			if (is_ipaddrv4($addr)) {
3044
				return IPV4;
3045
			} else if ($alias) {
3046
				if (is_alias($addr)) {
3047
					return ALIAS;
3048
				} else {
3049
					$err_msg[] = sprintf(gettext("%s must be a valid IPv4 address or alias."), $label);
3050
					return false;
3051
				}
3052
			} else {
3053
				$err_msg[] = sprintf(gettext("%s must be a valid IPv4 address."), $label);
3054
				return false;
3055
			}
3056
		break;
3057
		case IPV6:
3058
			if (is_ipaddrv6($addr)) {
3059
				$addr = strtolower($addr);
3060
				return IPV6;
3061
			} else if ($alias) {
3062
				if (is_alias($addr)) {
3063
					return ALIAS;
3064
				} else {
3065
					$err_msg[] = sprintf(gettext("%s must be a valid IPv6 address or alias."), $label);
3066
					return false;
3067
				}
3068
			} else {
3069
				$err_msg[] = sprintf(gettext("%s must be a valid IPv6 address."), $label);
3070
				return false;
3071
			}
3072
		break;
3073
		case IPV4V6:
3074
			if (is_ipaddrv6($addr)) {
3075
				$addr = strtolower($addr);
3076
				return IPV6;
3077
			} else if (is_ipaddrv4($addr)) {
3078
				return IPV4;
3079
			} else if ($alias) {
3080
				if (is_alias($addr)) {
3081
					return ALIAS;
3082
				} else {
3083
					$err_msg[] = sprintf(gettext("%s must be a valid IPv4 or IPv6 address or alias."), $label);
3084
					return false;
3085
				}
3086
			} else {
3087
				$err_msg[] = sprintf(gettext("%s must be a valid IPv4 or IPv6 address."), $label);
3088
				return false;
3089
			}
3090
		break;
3091
	}
3092

    
3093
	return false;
3094
}
3095

    
3096
/* From DUID configuration inputs, format a string that looks (more) like the expected raw DUID format:
3097
 * 1) For DUIDs entered as a known DUID type, convert to a hexstring and prepend the DUID number, after having done the following:
3098
 *     a) For DUID-LLT and DUID-EN, convert the time/enterprise ID input to hex and append the link-layer address/identifier input.
3099
 *     b) For DUID-LLT and DUID-LL, prepend a hardware type of 1.
3100
 *     c) For DUID-UUID, remove any "-".
3101
 * 2) Replace any remaining "-" with ":".
3102
 * 3) If any components are input with just a single char (hex digit hopefully), put a "0" in front.
3103
 * 4) The first two components should be a 16-bit integer (little- or big-endian, depending on the current machine type) that
3104
 *    is equal to the number of other components. If not, prepend this as "nn:00" (all pfSense builds are little-endian).
3105
 *    This is convenience, because the DUID reported by dhcp6c in logs does not include this count, which corresponds to the
3106
 *    option-len field of DHCPv6's OPTION_CLIENTID option.
3107
 *
3108
 * The final result should be closer to:
3109
 *
3110
 * "nn:00:00:0n:nn:nn:nn:..."
3111
 *
3112
 * This function does not validate the input. is_duid() will do validation.
3113
 */
3114
function format_duid($duidtype, $duidpt1, $duidpt2=null) {
3115
	if ($duidpt2)
3116
		$duidpt1 = implode(':', str_split(str_pad(dechex($duidpt1), 8, '0', STR_PAD_LEFT), 2)) . ':' . $duidpt2;
3117

    
3118
	/* Make hexstrings */
3119
	if ($duidtype) {
3120
		switch ($duidtype) {
3121
		/* Add a hardware type to DUID-LLT and DUID-LL; assume Ethernet */
3122
		case 1:
3123
		case 3:
3124
			$duidpt1 = '00:01:' . $duidpt1;
3125
			break;
3126
		/* Remove '-' from given UUID and insert ':' every 2 characters */
3127
		case 4:
3128
			$duidpt1 = implode(':', str_split(str_replace('-', '', $duidpt1), 2));
3129
			break;
3130
		default:
3131
		}
3132
		$duidpt1 = '00:0' . $duidtype . ':' . $duidpt1;
3133
	}
3134

    
3135
	$values = explode(':', strtolower(str_replace('-', ':', $duidpt1)));
3136

    
3137
	if (hexdec($values[0]) != count($values) - 2)
3138
		array_unshift($values, dechex(count($values)), '00');
3139

    
3140
	array_walk($values, function(&$value) {
3141
		$value = str_pad($value, 2, '0', STR_PAD_LEFT);
3142
	});
3143

    
3144
	return implode(":", $values);
3145
}
3146

    
3147
/* Returns true if $dhcp6duid is a valid DUID entry.
3148
 * Parse the entry to check for valid length according to known DUID types.
3149
 */
3150
function is_duid($dhcp6duid) {
3151
	$values = explode(":", $dhcp6duid);
3152
	if (hexdec($values[0]) == count($values) - 2) {
3153
		switch (hexdec($values[2] . $values[3])) {
3154
		case 0:
3155
			return false;
3156
			break;
3157
		case 1:
3158
			if (count($values) != 16 || strlen($dhcp6duid) != 47)
3159
				return false;
3160
			break;
3161
		case 3:
3162
			if (count($values) != 12 || strlen($dhcp6duid) != 35)
3163
				return false;
3164
			break;
3165
		case 4:
3166
			if (count($values) != 20 || strlen($dhcp6duid) != 59)
3167
				return false;
3168
			break;
3169
		/* DUID is up to 128 octets; allow 2 octets for type code, 2 more for option-len */
3170
		default:
3171
			if (count($values) > 132 || strlen($dhcp6duid) != count($values) * 3 - 1)
3172
				return false;
3173
		}
3174
	} else
3175
		return false;
3176

    
3177
	for ($i = 0; $i < count($values); $i++) {
3178
		if (ctype_xdigit($values[$i]) == false)
3179
			return false;
3180
		if (hexdec($values[$i]) < 0 || hexdec($values[$i]) > 255)
3181
			return false;
3182
	}
3183

    
3184
	return true;
3185
}
3186

    
3187
/* Write the DHCP6 DUID file */
3188
function write_dhcp6_duid($duidstring) {
3189
	// Create the hex array from the dhcp6duid config entry and write to file
3190
	global $g;
3191

    
3192
	if(!is_duid($duidstring)) {
3193
		log_error(gettext("Error: attempting to write DUID file - Invalid DUID detected"));
3194
		return false;
3195
	}
3196
	$temp = str_replace(":","",$duidstring);
3197
	$duid_binstring = pack("H*",$temp);
3198
	if ($fd = fopen("{$g['vardb_path']}/dhcp6c_duid", "wb")) {
3199
		fwrite($fd, $duid_binstring);
3200
		fclose($fd);
3201
		return true;
3202
	}
3203
	log_error(gettext("Error: attempting to write DUID file - File write error"));
3204
	return false;
3205
}
3206

    
3207
/* returns duid string from 'vardb_path']}/dhcp6c_duid' */
3208
function get_duid_from_file() {
3209
	global $g;
3210

    
3211
	$duid_ASCII = "";
3212
	$count = 0;
3213

    
3214
	if (file_exists("{$g['vardb_path']}/dhcp6c_duid") &&
3215
	    ($fd = fopen("{$g['vardb_path']}/dhcp6c_duid", "r"))) {
3216
		$fsize = filesize("{$g['vardb_path']}/dhcp6c_duid");
3217
		if ($fsize <= 132) {
3218
			$buffer = fread($fd, $fsize);
3219
			while($count < $fsize) {
3220
				$duid_ASCII .= bin2hex($buffer[$count]);
3221
				$count++;
3222
				if($count < $fsize) {
3223
					$duid_ASCII .= ":";
3224
				}
3225
			}
3226
		}
3227
		fclose($fd);
3228
	}
3229
	//if no file or error with read then the string returns blanked DUID string
3230
	if(!is_duid($duid_ASCII)) {
3231
		return "--:--:--:--:--:--:--:--:--:--:--:--:--:--:--:--";
3232
	}
3233
	return($duid_ASCII);
3234
}
3235

    
3236
/* Replaces the Mac OS 9 and earlier (\r) and DOS/Windows (\r\n) newlines with the Unix equivalent (\n). */
3237
function unixnewlines($text) {
3238
	return preg_replace('/\r\n?/', "\n", $text);
3239
}
3240

    
3241
function array_remove_duplicate($array, $field) {
3242
	foreach ($array as $sub) {
3243
		if (isset($sub[$field])) {
3244
			$cmp[] = $sub[$field];
3245
		}
3246
	}
3247
	$unique = array_unique(array_reverse($cmp, true));
3248
	foreach ($unique as $k => $rien) {
3249
		$new[] = $array[$k];
3250
	}
3251
	return $new;
3252
}
3253

    
3254
function dhcpd_date_adjust_gmt($dt) {
3255
	global $config;
3256

    
3257
	init_config_arr(array('dhcpd'));
3258

    
3259
	foreach ($config['dhcpd'] as $dhcpditem) {
3260
		if ($dhcpditem['dhcpleaseinlocaltime'] == "yes") {
3261
			$ts = strtotime($dt . " GMT");
3262
			if ($ts !== false) {
3263
				return strftime("%Y/%m/%d %H:%M:%S", $ts);
3264
			}
3265
		}
3266
	}
3267

    
3268
	/*
3269
	 * If we did not need to convert to local time or the conversion
3270
	 * failed, just return the input.
3271
	 */
3272
	return $dt;
3273
}
3274

    
3275
global $supported_image_types;
3276
$supported_image_types = array(
3277
	IMAGETYPE_JPEG,
3278
	IMAGETYPE_PNG,
3279
	IMAGETYPE_GIF,
3280
	IMAGETYPE_WEBP
3281
);
3282

    
3283
function is_supported_image($image_filename) {
3284
	global $supported_image_types;
3285
	$img_info = getimagesize($image_filename);
3286

    
3287
	/* If it's not an image, or it isn't in the supported list, return false */
3288
	if (($img_info === false) ||
3289
	    !in_array($img_info[2], array_keys($supported_image_types))) {
3290
		return false;
3291
	} else {
3292
		return $img_info[2];
3293
	}
3294
}
3295

    
3296
function get_lagg_ports ($laggport) {
3297
	$laggp = array();
3298
	foreach ($laggport as $lgp) {
3299
		list($lpname, $lpinfo) = explode(" ", $lgp);
3300
		preg_match('~<(.+)>~', $lpinfo, $lgportmode);
3301
		if ($lgportmode[1]) {
3302
			$laggp[] = $lpname . " (" . $lgportmode[1] . ")";
3303
		} else {
3304
			$laggp[] = $lpname;
3305
		}
3306
	}
3307
	if ($laggp) {
3308
		return implode(", ", $laggp);
3309
	} else {
3310
		return false;
3311
	}
3312
}
3313

    
3314
function cisco_to_cidr($addr) {
3315
	if (!is_ipaddr($addr)) {
3316
		throw new Exception('Invalid IP Addr');
3317
	}
3318

    
3319
	$mask = decbin(~ip2long($addr));
3320
	$mask = substr($mask, -32);
3321
	$k = 0;
3322
	for ($i = 0; $i <= 32; $i++) {
3323
		$k += intval($mask[$i]);
3324
	}
3325
	return $k;
3326
}
3327

    
3328
function cisco_extract_index($prule) {
3329
	$index = explode("#", $prule);
3330
	if (is_numeric($index[1])) {
3331
		return intval($index[1]);
3332
	} else {
3333
		syslog(LOG_WARNING, "Error parsing rule {$prule}: Could not extract index");
3334
	}
3335
	return -1;;
3336
}
3337

    
3338
function parse_cisco_acl_rule($rule, $devname, $dir, $proto) {
3339
	$rule_orig = $rule;
3340
	$rule = explode(" ", $rule);
3341
	$tmprule = "";
3342
	$index = 0;
3343

    
3344
	if ($rule[$index] == "permit") {
3345
		$startrule = "pass {$dir} quick on {$devname} ";
3346
	} else if ($rule[$index] == "deny") {
3347
		$startrule = "block {$dir} quick on {$devname} ";
3348
	} else {
3349
		return;
3350
	}
3351

    
3352
	$index++;
3353

    
3354
	switch ($rule[$index]) {
3355
		case "ip":
3356
			break;
3357
		case "icmp":
3358
			$icmp = ($proto == "inet") ? "icmp" : "ipv6-icmp";
3359
			$tmprule .= "proto {$icmp} ";
3360
			break;
3361
		case "tcp":
3362
		case "udp":
3363
			$tmprule .= "proto {$rule[$index]} ";
3364
			break;
3365
		default:
3366
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid protocol.");
3367
			return;
3368
	}
3369
	$index++;
3370

    
3371
	/* Source */
3372
	if (trim($rule[$index]) == "host") {
3373
		$index++;
3374
		if ((is_ipaddrv4(trim($rule[$index])) && ($proto == "inet")) ||
3375
		    (is_ipaddrv6(trim($rule[$index])) && ($proto == "inet6"))) {
3376
			$tmprule .= "from {$rule[$index]} ";
3377
			$index++;
3378
		} else {
3379
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source host '{$rule[$index]}'.");
3380
			return;
3381
		}
3382
	} elseif (is_subnetv6(trim($rule[$index])) && ($proto == "inet6")) {
3383
		$tmprule .= "from {$rule[$index]} ";
3384
		$index++;
3385
	} elseif (trim($rule[$index]) == "any") {
3386
		$tmprule .= "from any ";
3387
		$index++;
3388
	} else {
3389
		$network = $rule[$index];
3390
		$netmask = $rule[++$index];
3391

    
3392
		if (is_ipaddrv4($network) && ($proto == "inet")) {
3393
			try {
3394
				$netmask = cisco_to_cidr($netmask);
3395
			} catch(Exception $e) {
3396
				syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source netmask '$netmask'.");
3397
				return;
3398
			}
3399
			$tmprule .= "from {$network}/{$netmask}";
3400
		} else {
3401
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source network '$network'.");
3402
			return;
3403
		}
3404

    
3405
		$index++;
3406
	}
3407

    
3408
	/* Source Operator */
3409
	if (in_array(trim($rule[$index]), array("lt", "gt", "eq", "neq"))) {
3410
		switch(trim($rule[$index])) {
3411
			case "lt":
3412
				$operator = "<";
3413
				break;
3414
			case "gt":
3415
				$operator = ">";
3416
				break;
3417
			case "eq":
3418
				$operator = "=";
3419
				break;
3420
			case "neq":
3421
				$operator = "!=";
3422
				break;
3423
		}
3424

    
3425
		$port = $rule[++$index];
3426
		if (is_port($port)) {
3427
			$tmprule .= "port {$operator} {$port} ";
3428
		} else {
3429
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source port: '$port' not a numeric value between 0 and 65535.");
3430
			return;
3431
		}
3432
		$index++;
3433
	} else if (trim($rule[$index]) == "range") {
3434
		$port = array($rule[++$index], $rule[++$index]);
3435
		if (is_port($port[0]) && is_port($port[1])) {
3436
			$port[0]--;
3437
			$port[1]++;
3438
			$tmprule .= "port {$port[0]} >< {$port[1]} ";
3439
		} else {
3440
			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.");
3441
			return;
3442
		}
3443
		$index++;
3444
	}
3445

    
3446
	/* Destination */
3447
	if (trim($rule[$index]) == "host") {
3448
		$index++;
3449
		if ((is_ipaddrv4(trim($rule[$index])) && ($proto == "inet")) ||
3450
		    (is_ipaddrv6(trim($rule[$index])) && ($proto == "inet6"))) {
3451
			$tmprule .= "to {$rule[$index]} ";
3452
			$index++;
3453
		} else {
3454
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination host '{$rule[$index]}'.");
3455
			return;
3456
		}
3457
	} elseif (is_subnetv6(trim($rule[$index])) && ($proto == "inet6")) {
3458
		$tmprule .= "to {$rule[$index]} ";
3459
		$index++;
3460
	} elseif (trim($rule[$index]) == "any") {
3461
		$tmprule .= "to any ";
3462
		$index++;
3463
	} else {
3464
		$network = $rule[$index];
3465
		$netmask = $rule[++$index];
3466

    
3467
		if (is_ipaddrv4($network) && ($proto == "inet")) {
3468
			try {
3469
				$netmask = cisco_to_cidr($netmask);
3470
			} catch(Exception $e) {
3471
				syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination network '$network'.");
3472
				return;
3473
			}
3474
			$tmprule .= "to {$network}/{$netmask}";
3475
		} else {
3476
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination network '$network'.");
3477
			return;
3478
		}
3479

    
3480
		$index++;
3481
	}
3482

    
3483
	/* Destination Operator */
3484
	if (in_array(trim($rule[$index]), array("lt", "gt", "eq", "neq"))) {
3485
		switch(trim($rule[$index])) {
3486
			case "lt":
3487
				$operator = "<";
3488
				break;
3489
			case "gt":
3490
				$operator = ">";
3491
				break;
3492
			case "eq":
3493
				$operator = "=";
3494
				break;
3495
			case "neq":
3496
				$operator = "!=";
3497
				break;
3498
		}
3499

    
3500
		$port = $rule[++$index];
3501
		if (is_port($port)) {
3502
			$tmprule .= "port {$operator} {$port} ";
3503
		} else {
3504
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination port: '$port' not a numeric value between 0 and 65535.");
3505
			return;
3506
		}
3507
		$index++;
3508
	} else if (trim($rule[$index]) == "range") {
3509
		$port = array($rule[++$index], $rule[++$index]);
3510
		if (is_port($port[0]) && is_port($port[1])) {
3511
			$port[0]--;
3512
			$port[1]++;
3513
			$tmprule .= "port {$port[0]} >< {$port[1]} ";
3514
		} else {
3515
			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.");
3516
			return;
3517
		}
3518
		$index++;
3519
	}
3520

    
3521
	$tmprule = $startrule . $proto . " " . $tmprule;
3522
	return $tmprule;
3523
}
3524

    
3525
function parse_cisco_acl($attribs, $dev) {
3526
	global $attributes;
3527

    
3528
	if (!is_array($attribs)) {
3529
		return "";
3530
	}
3531
	$finalrules = "";
3532
	if (is_array($attribs['ciscoavpair'])) {
3533
		$inrules = array('inet' => array(), 'inet6' => array());
3534
		$outrules = array('inet' => array(), 'inet6' => array());
3535
		foreach ($attribs['ciscoavpair'] as $avrules) {
3536
			$rule = explode("=", $avrules);
3537
			$dir = "";
3538
			if (strstr($rule[0], "inacl")) {
3539
				$dir = "in";
3540
			} else if (strstr($rule[0], "outacl")) {
3541
				$dir = "out";
3542
			} else if (strstr($rule[0], "dns-servers")) {
3543
				$attributes['dns-servers'] = explode(" ", $rule[1]);
3544
				continue;
3545
			} else if (strstr($rule[0], "route")) {
3546
				if (!is_array($attributes['routes'])) {
3547
					$attributes['routes'] = array();
3548
				}
3549
				$attributes['routes'][] = $rule[1];
3550
				continue;
3551
			}
3552
			$rindex = cisco_extract_index($rule[0]);
3553
			if ($rindex < 0) {
3554
				continue;
3555
			}
3556

    
3557
			if (strstr($rule[0], "ipv6")) {
3558
				$proto = "inet6";
3559
			} else {
3560
				$proto = "inet";
3561
			}
3562

    
3563
			$tmprule = parse_cisco_acl_rule($rule[1], $dev, $dir, $proto);
3564

    
3565
			if ($dir == "in") {
3566
				$inrules[$proto][$rindex] = $tmprule;
3567
			} else if ($dir == "out") {
3568
				$outrules[$proto][$rindex] = $tmprule;
3569
			}
3570
		}
3571

    
3572

    
3573
		$state = "";
3574
		foreach (array('inet', 'inet6') as $ip) {
3575
			if (!empty($outrules[$ip])) {
3576
				$state = "no state";
3577
			}
3578
			ksort($inrules[$ip], SORT_NUMERIC);
3579
			foreach ($inrules[$ip] as $inrule) {
3580
				$finalrules .= "{$inrule} {$state}\n";
3581
			}
3582
			if (!empty($outrules[$ip])) {
3583
				ksort($outrules[$ip], SORT_NUMERIC);
3584
				foreach ($outrules[$ip] as $outrule) {
3585
					$finalrules .= "{$outrule} {$state}\n";
3586
				}
3587
			}
3588
		}
3589
	}
3590
	return $finalrules;
3591
}
3592

    
3593
function alias_idn_to_utf8($alias) {
3594
	if (is_alias($alias)) {
3595
		return $alias;
3596
	} else {
3597
		return idn_to_utf8($alias);
3598
	}
3599
}
3600

    
3601
function alias_idn_to_ascii($alias) {
3602
	if (is_alias($alias)) {
3603
		return $alias;
3604
	} else {
3605
		return idn_to_ascii($alias);
3606
	}
3607
}
3608

    
3609
?>
(53-53/61)