Project

General

Profile

Download (93.8 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-2020 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 (ip_less_than($a, $b)) {
468
		return -1;
469
	} else if (ip_greater_than($a, $b)) {
470
		return 1;
471
	} else {
472
		return 0;
473
	}
474
}
475

    
476
/* Convert a range of IPv4 addresses to an array of individual addresses. */
477
/* Note: IPv6 ranges are not yet supported here. */
478
function ip_range_to_address_array($startip, $endip, $max_size = 5000) {
479
	if (!is_ipaddrv4($startip) || !is_ipaddrv4($endip)) {
480
		return false;
481
	}
482

    
483
	if (ip_greater_than($startip, $endip)) {
484
		// Swap start and end so we can process sensibly.
485
		$temp = $startip;
486
		$startip = $endip;
487
		$endip = $temp;
488
	}
489

    
490
	if (ip_range_size_v4($startip, $endip) > $max_size) {
491
		return false;
492
	}
493

    
494
	// Container for IP addresses within this range.
495
	$rangeaddresses = array();
496
	$end_int = ip2ulong($endip);
497
	for ($ip_int = ip2ulong($startip); $ip_int <= $end_int; $ip_int++) {
498
		$rangeaddresses[] = long2ip($ip_int);
499
	}
500

    
501
	return $rangeaddresses;
502
}
503

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

    
532
	if (is_ipaddrv4($ip1) && is_ipaddrv4($ip2)) {
533
		$proto = 'ipv4';  // for clarity
534
		$bits = 32;
535
		$ip1bin = decbin(ip2long32($ip1));
536
		$ip2bin = decbin(ip2long32($ip2));
537
	} elseif (is_ipaddrv6($ip1) && is_ipaddrv6($ip2)) {
538
		$proto = 'ipv6';
539
		$bits = 128;
540
		$ip1bin = ip6_to_bin($ip1);
541
		$ip2bin = ip6_to_bin($ip2);
542
	} else {
543
		return array();
544
	}
545

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

    
550
	if ($ip1bin == $ip2bin) {
551
		return array($ip1 . '/' . $bits); // exit if ip1=ip2 (trivial case)
552
	}
553

    
554
	if ($ip1bin > $ip2bin) {
555
		list ($ip1bin, $ip2bin) = array($ip2bin, $ip1bin);  // swap if needed (ensures ip1 < ip2)
556
	}
557

    
558
	$rangesubnets = array();
559
	$netsize = 0;
560

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

    
565
		// 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)
566

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

    
575
		// 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)
576

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

    
586
		// this is the only edge case arising from increment/decrement.
587
		// 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)
588

    
589
		if ($ip2bin < $ip1bin) {
590
			continue;
591
		}
592

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

    
596
		$shift = $bits - max(strrpos($ip1bin, '0'), strrpos($ip2bin, '1'));  // num of low bits which are '0' in ip1 and '1' in ip2
597
		$ip1bin = str_repeat('0', $shift) . substr($ip1bin, 0, $bits - $shift);
598
		$ip2bin = str_repeat('0', $shift) . substr($ip2bin, 0, $bits - $shift);
599
		$netsize += $shift;
600
		if ($ip1bin == $ip2bin) {
601
			// we're done.
602
			$new_subnet_ip = substr($ip1bin, $netsize, $bits - $netsize) . str_repeat('0', $netsize);
603
			$rangesubnets[$new_subnet_ip] = $bits - $netsize;
604
			continue;
605
		}
606

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

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

    
612
	ksort($rangesubnets, SORT_STRING);
613
	$out = array();
614

    
615
	foreach ($rangesubnets as $ip => $netmask) {
616
		if ($proto == 'ipv4') {
617
			$i = str_split($ip, 8);
618
			$out[] = implode('.', array(bindec($i[0]), bindec($i[1]), bindec($i[2]), bindec($i[3]))) . '/' . $netmask;
619
		} else {
620
			$out[] = bin_to_compressed_ip6($ip) . '/' . $netmask;
621
		}
622
	}
623

    
624
	return $out;
625
}
626

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

    
644
/* returns true if $ipaddr is a valid dotted IPv4 address or a IPv6
645
	false - not valid
646
	true (numeric 4 or 6) - if valid, gives type of address */
647
function is_ipaddr($ipaddr) {
648
	if (is_ipaddrv4($ipaddr)) {
649
		return 4;
650
	}
651
	if (is_ipaddrv6($ipaddr)) {
652
		return 6;
653
	}
654
	return false;
655
}
656

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

    
677
/* returns true if $ipaddr is a valid dotted IPv4 address */
678
function is_ipaddrv4($ipaddr) {
679
	if (!is_string($ipaddr) || empty($ipaddr) || ip2long($ipaddr) === FALSE) {
680
		return false;
681
	}
682
	return true;
683
}
684

    
685
function is_mcast($ipaddr) {
686
	if (is_mcastv4($ipaddr)) {
687
		return 4;
688
	}
689
	if (is_mcastv6($ipaddr)) {
690
		return 6;
691
	}
692
	return false;
693
}
694

    
695
function is_mcastv4($ipaddr) {
696
	if (!is_ipaddrv4($ipaddr) ||
697
	    !preg_match('/^2(?:2[4-9]|3\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d?|0)){3}$/', $ipaddr)) {
698
		return false;
699
	}
700
	return true;
701
}
702

    
703
function is_mcastv6($ipaddr) {
704
	if (!is_ipaddrv6($ipaddr) || !preg_match('/^ff.+$/', $ipaddr)) {
705
		return false;
706
	}
707
	return true;
708
}
709

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

    
726
/* returns scope of a linklocal address */
727
function get_ll_scope($addr) {
728
	if (!is_linklocal($addr) || !strstr($addr, "%")) {
729
		return "";
730
	}
731
	list ($ll, $scope) = explode("%", $addr);
732
	return $scope;
733
}
734

    
735
/* returns true if $ipaddr is a valid literal IPv6 address */
736
function is_literalipaddrv6($ipaddr) {
737
	if (substr($ipaddr,0,1) == '[' && substr($ipaddr,-1,1) == ']') {
738
		// if it's data wrapped in "[ ... ]" then test if middle part is valid IPv6
739
		return is_ipaddrv6(substr($ipaddr,1,-1));
740
	}
741
	return false;
742
}
743

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

    
753
	if (!is_port(substr($ipport, $c + 1))) {
754
		return false;  // no valid port after last colon
755
	}
756

    
757
	$ip = substr($ipport, 0, $c);  // else is text before last colon a valid IP
758
	if (is_literalipaddrv6($ip)) {
759
		return 6;
760
	} elseif (is_ipaddrv4($ip)) {
761
		return 4;
762
	} else {
763
		return false;
764
	}
765
}
766

    
767
function is_hostnamewithport($hostport) {
768
	$parts = explode(":", $hostport);
769
	// no need to validate with is_string(); if it's not a string then explode won't return 2 parts anyway
770
	if (count($parts) == 2) {
771
		return is_hostname($parts[0]) && is_port($parts[1]);
772
	}
773
	return false;
774
}
775

    
776
/* returns true if $ipaddr is a valid dotted IPv4 address or an alias thereof */
777
function is_ipaddroralias($ipaddr) {
778
	global $config;
779

    
780
	if (is_alias($ipaddr)) {
781
		if (is_array($config['aliases']['alias'])) {
782
			foreach ($config['aliases']['alias'] as $alias) {
783
				if ($alias['name'] == $ipaddr && !preg_match("/port/i", $alias['type'])) {
784
					return true;
785
				}
786
			}
787
		}
788
		return false;
789
	} else {
790
		return is_ipaddr($ipaddr);
791
	}
792

    
793
}
794

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

    
810
function is_v4($ip_or_subnet) {
811
	return is_ipaddrv4($ip_or_subnet) || is_subnetv4($ip_or_subnet);
812
}
813

    
814
function is_v6($ip_or_subnet) {
815
	return is_ipaddrv6($ip_or_subnet) || is_subnetv6($ip_or_subnet);
816
}
817

    
818
/* same as is_subnet() but accepts IPv4 only */
819
function is_subnetv4($subnet) {
820
	return (is_subnet($subnet) == 4);
821
}
822

    
823
/* same as is_subnet() but accepts IPv6 only */
824
function is_subnetv6($subnet) {
825
	return (is_subnet($subnet) == 6);
826
}
827

    
828
/* returns true if $subnet is a valid subnet in CIDR format or an alias thereof */
829
function is_subnetoralias($subnet) {
830
	global $aliastable;
831

    
832
	if (isset($aliastable[$subnet]) && is_subnet($aliastable[$subnet])) {
833
		return true;
834
	} else {
835
		return is_subnet($subnet);
836
	}
837
}
838

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

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

    
867
	// 2**N returns an exact result as an INT if possible, and a float/double if not.
868
	// 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
869
	$result = 2 ** $snsize;
870

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

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

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

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

    
911
	if ($subnetv4_start1 == '' || $subnetv4_start2 == '') {
912
		// One or both args is not a valid IPv4 subnet
913
		//FIXME: needs to return "bad data" not true/false if bad. For now return false, best we can do until fixed
914
		return false;
915
	}
916
	return ($subnetv4_start1 == $subnetv4_start2);
917
}
918

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

    
926
	if ($subnetv6_start1 == '' || $subnetv6_start2 == '') {
927
		// One or both args is not a valid IPv6 subnet
928
		//FIXME: needs to return "bad data" not true/false if bad. For now return false, best we can do until fixed
929
		return false;
930
	}
931
	return ($subnetv6_start1 == $subnetv6_start2);
932
}
933

    
934
/* return all PTR zones for a IPv6 network */
935
function get_v6_ptr_zones($subnet, $bits) {
936
	$result = array();
937

    
938
	if (!is_ipaddrv6($subnet)) {
939
		return $result;
940
	}
941

    
942
	if (!is_numericint($bits) || $bits > 128) {
943
		return $result;
944
	}
945

    
946
	/*
947
	 * Find a small nibble boundary subnet mask
948
	 * e.g. a /29 will create 8 /32 PTR zones
949
	 */
950
	$small_sn = $bits;
951
	while ($small_sn % 4 != 0) {
952
		$small_sn++;
953
	}
954

    
955
	/* Get network prefix */
956
	$small_subnet = Net_IPv6::getNetmask($subnet, $bits);
957

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

    
969
		/* Detect what part of IP should be increased */
970
		$change_part = (int) ($small_sn / 16);
971
		if ($small_sn % 16 == 0) {
972
			$change_part--;
973
		}
974

    
975
		/* Increase 1 to desired part */
976
		$parts = explode(":", Net_IPv6::uncompress($small_subnet));
977
		$parts[$change_part]++;
978
		$small_subnet = implode(":", $parts);
979
	}
980

    
981
	return $result;
982
}
983

    
984
/* return true if $addr is in $subnet, false if not */
985
function ip_in_subnet($addr, $subnet) {
986
	if (is_ipaddrv6($addr) && is_subnetv6($subnet)) {
987
		return (Net_IPv6::isInNetmask($addr, $subnet));
988
	} else if (is_ipaddrv4($addr) && is_subnetv4($subnet)) {
989
		list($ip, $mask) = explode('/', $subnet);
990
		$mask = (0xffffffff << (32 - $mask)) & 0xffffffff;
991
		return ((ip2long($addr) & $mask) == (ip2long($ip) & $mask));
992
	}
993
	return false;
994
}
995

    
996
/* returns true if $hostname is just a valid hostname (top part without any of the domain part) */
997
function is_unqualified_hostname($hostname) {
998
	if (!is_string($hostname)) {
999
		return false;
1000
	}
1001

    
1002
	if (preg_match('/^(?:[a-z0-9_]|[a-z0-9_][a-z0-9_\-]*[a-z0-9_])$/i', $hostname)) {
1003
		return true;
1004
	} else {
1005
		return false;
1006
	}
1007
}
1008

    
1009
/* returns true if $hostname is a valid hostname, with or without being a fully-qualified domain name. */
1010
function is_hostname($hostname, $allow_wildcard=false) {
1011
	if (!is_string($hostname)) {
1012
		return false;
1013
	}
1014

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

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

    
1041
	if (preg_match($domain_regex, $domain)) {
1042
		return true;
1043
	} else {
1044
		return false;
1045
	}
1046
}
1047

    
1048
/* returns true if $macaddr is a valid MAC address */
1049
function is_macaddr($macaddr, $partial=false) {
1050
	$values = explode(":", $macaddr);
1051

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

    
1067
	return true;
1068
}
1069

    
1070
/*
1071
	If $return_message is true then
1072
		returns a text message about the reason that the name is invalid.
1073
		the text includes the type of "thing" that is being checked, passed in $object. (e.g. "alias", "gateway group", "schedule")
1074
	else
1075
		returns true if $name is a valid name for an alias
1076
		returns false if $name is not a valid name for an alias
1077

    
1078
	Aliases cannot be:
1079
		bad chars: anything except a-z 0-9 and underscore
1080
		bad names: empty string, pure numeric, pure underscore
1081
		reserved words: pre-defined service/protocol/port names which should not be ambiguous, and the words "port" and  "pass" */
1082

    
1083
function is_validaliasname($name, $return_message = false, $object = "alias") {
1084
	/* Array of reserved words */
1085
	$reserved = array("port", "pass");
1086

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

    
1122
/* returns a text message indicating if the alias name is valid, or the reason it is not valid. */
1123
function invalidaliasnamemsg($name, $object = "alias") {
1124
	return is_validaliasname($name, true, $object);
1125
}
1126

    
1127
/*
1128
 * returns true if $range is a valid integer range between $min and $max
1129
 * range delimiter can be ':' or '-'
1130
 */
1131
function is_intrange($range, $min, $max) {
1132
	$values = preg_split("/[:-]/", $range);
1133

    
1134
	if (!is_array($values) || count($values) != 2) {
1135
		return false;
1136
	}
1137

    
1138
	if (!ctype_digit($values[0]) || !ctype_digit($values[1])) {
1139
		return false;
1140
	}
1141

    
1142
	$values[0] = intval($values[0]);
1143
	$values[1] = intval($values[1]);
1144

    
1145
	if ($values[0] >= $values[1]) {
1146
		return false;
1147
	}
1148

    
1149
	if ($values[0] < $min || $values[1] > $max) {
1150
		return false;
1151
	}
1152

    
1153
	return true;
1154
}
1155

    
1156
/* returns true if $port is a valid TCP/UDP port */
1157
function is_port($port) {
1158
	if (ctype_digit($port) && ((intval($port) >= 1) && (intval($port) <= 65535))) {
1159
		return true;
1160
	}
1161
	if (getservbyname($port, "tcp") || getservbyname($port, "udp")) {
1162
		return true;
1163
	}
1164
	return false;
1165
}
1166

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

    
1175
		foreach($netstatarr as $index => $portstats){
1176
			array_push($port_info, $portstats['local']['port']);
1177
		}
1178
	}
1179

    
1180
	return in_array($port, $port_info);
1181
}
1182

    
1183
/* returns true if $portrange is a valid TCP/UDP portrange ("<port>:<port>") */
1184
function is_portrange($portrange) {
1185
	$ports = explode(":", $portrange);
1186

    
1187
	return (count($ports) == 2 && is_port($ports[0]) && is_port($ports[1]));
1188
}
1189

    
1190
/* returns true if $port is a valid TCP/UDP port number or range ("<port>:<port>") */
1191
function is_port_or_range($port) {
1192
	return (is_port($port) || is_portrange($port));
1193
}
1194

    
1195
/* returns true if $port is an alias that is a port type */
1196
function is_portalias($port) {
1197
	global $config;
1198

    
1199
	if (is_alias($port)) {
1200
		if (is_array($config['aliases']['alias'])) {
1201
			foreach ($config['aliases']['alias'] as $alias) {
1202
				if ($alias['name'] == $port && preg_match("/port/i", $alias['type'])) {
1203
					return true;
1204
				}
1205
			}
1206
		}
1207
	}
1208
	return false;
1209
}
1210

    
1211
/* returns true if $port is a valid port number or an alias thereof */
1212
function is_port_or_alias($port) {
1213
	return (is_port($port) || is_portalias($port));
1214
}
1215

    
1216
/* returns true if $port is a valid TCP/UDP port number or range ("<port>:<port>") or an alias thereof */
1217
function is_port_or_range_or_alias($port) {
1218
	return (is_port($port) || is_portrange($port) || is_portalias($port));
1219
}
1220

    
1221
/* create ranges of sequential port numbers (200:215) and remove duplicates */
1222
function group_ports($ports, $kflc = false) {
1223
	if (!is_array($ports) || empty($ports)) {
1224
		return;
1225
	}
1226

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

    
1252
	$result = array();
1253
	foreach ($uniq as $idx => $port) {
1254
		if ($idx == 0) {
1255
			$result[] = $port;
1256
			continue;
1257
		}
1258

    
1259
		$last = end($result);
1260
		if (is_portrange($last)) {
1261
			list($begin, $end) = explode(":", $last);
1262
		} else {
1263
			$begin = $end = $last;
1264
		}
1265

    
1266
		if ($port == ($end+1)) {
1267
			$end++;
1268
			$result[count($result)-1] = "{$begin}:{$end}";
1269
		} else {
1270
			$result[] = $port;
1271
		}
1272
	}
1273

    
1274
	return array_merge($comments, $result);
1275
}
1276

    
1277
/* returns true if $val is a valid shaper bandwidth value */
1278
function is_valid_shaperbw($val) {
1279
	return (preg_match("/^(\d+(?:\.\d+)?)([MKG]?b|%)$/", $val));
1280
}
1281

    
1282
/* returns true if $test is in the range between $start and $end */
1283
function is_inrange_v4($test, $start, $end) {
1284
	if (!is_ipaddrv4($test) || !is_ipaddrv4($start) || !is_ipaddrv4($end)) {
1285
		return false;
1286
	}
1287

    
1288
	if (ip2ulong($test) <= ip2ulong($end) &&
1289
	    ip2ulong($test) >= ip2ulong($start)) {
1290
		return true;
1291
	}
1292

    
1293
	return false;
1294
}
1295

    
1296
/* returns true if $test is in the range between $start and $end */
1297
function is_inrange_v6($test, $start, $end) {
1298
	if (!is_ipaddrv6($test) || !is_ipaddrv6($start) || !is_ipaddrv6($end)) {
1299
		return false;
1300
	}
1301

    
1302
	if (inet_pton($test) <= inet_pton($end) &&
1303
	    inet_pton($test) >= inet_pton($start)) {
1304
		return true;
1305
	}
1306

    
1307
	return false;
1308
}
1309

    
1310
/* returns true if $test is in the range between $start and $end */
1311
function is_inrange($test, $start, $end) {
1312
	return is_ipaddrv6($test) ? is_inrange_v6($test, $start, $end) : is_inrange_v4($test, $start, $end);
1313
}
1314

    
1315
function build_vip_list($fif, $family = "all") {
1316
	$list = array('address' => gettext('Interface Address'));
1317

    
1318
	$viplist = get_configured_vip_list($family);
1319
	foreach ($viplist as $vip => $address) {
1320
		if ($fif == get_configured_vip_interface($vip)) {
1321
			$list[$vip] = "$address";
1322
			if (get_vip_descr($address)) {
1323
				$list[$vip] .= " (". get_vip_descr($address) .")";
1324
			}
1325
		}
1326
	}
1327

    
1328
	return($list);
1329
}
1330

    
1331
function get_configured_vip_list($family = 'all', $type = VIP_ALL) {
1332
	global $config;
1333

    
1334
	$list = array();
1335
	if (!is_array($config['virtualip']) ||
1336
	    !is_array($config['virtualip']['vip']) ||
1337
	    empty($config['virtualip']['vip'])) {
1338
		return ($list);
1339
	}
1340

    
1341
	$viparr = &$config['virtualip']['vip'];
1342
	foreach ($viparr as $vip) {
1343

    
1344
		if ($type == VIP_CARP) {
1345
			if ($vip['mode'] != "carp")
1346
				continue;
1347
		} elseif ($type == VIP_IPALIAS) {
1348
			if ($vip['mode'] != "ipalias")
1349
				continue;
1350
		} else {
1351
			if ($vip['mode'] != "carp" && $vip['mode'] != "ipalias")
1352
				continue;
1353
		}
1354

    
1355
		if ($family == 'all' ||
1356
		    ($family == 'inet' && is_ipaddrv4($vip['subnet'])) ||
1357
		    ($family == 'inet6' && is_ipaddrv6($vip['subnet']))) {
1358
			$list["_vip{$vip['uniqid']}"] = $vip['subnet'];
1359
		}
1360
	}
1361
	return ($list);
1362
}
1363

    
1364
function get_configured_vip($vipinterface = '') {
1365

    
1366
	return (get_configured_vip_detail($vipinterface, 'all', 'vip'));
1367
}
1368

    
1369
function get_configured_vip_interface($vipinterface = '') {
1370

    
1371
	return (get_configured_vip_detail($vipinterface, 'all', 'iface'));
1372
}
1373

    
1374
function get_configured_vip_ipv4($vipinterface = '') {
1375

    
1376
	return (get_configured_vip_detail($vipinterface, 'inet', 'ip'));
1377
}
1378

    
1379
function get_configured_vip_ipv6($vipinterface = '') {
1380

    
1381
	return (get_configured_vip_detail($vipinterface, 'inet6', 'ip'));
1382
}
1383

    
1384
function get_configured_vip_subnetv4($vipinterface = '') {
1385

    
1386
	return (get_configured_vip_detail($vipinterface, 'inet', 'subnet'));
1387
}
1388

    
1389
function get_configured_vip_subnetv6($vipinterface = '') {
1390

    
1391
	return (get_configured_vip_detail($vipinterface, 'inet6', 'subnet'));
1392
}
1393

    
1394
function get_configured_vip_detail($vipinterface = '', $family = 'inet', $what = 'ip') {
1395
	global $config;
1396

    
1397
	if (empty($vipinterface) ||
1398
	    !is_array($config['virtualip']) ||
1399
	    !is_array($config['virtualip']['vip']) ||
1400
	    empty($config['virtualip']['vip'])) {
1401
		return (NULL);
1402
	}
1403

    
1404
	$viparr = &$config['virtualip']['vip'];
1405
	foreach ($viparr as $vip) {
1406
		if ($vip['mode'] != "carp" && $vip['mode'] != "ipalias") {
1407
			continue;
1408
		}
1409

    
1410
		if ($vipinterface != "_vip{$vip['uniqid']}") {
1411
			continue;
1412
		}
1413

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

    
1439
	return (NULL);
1440
}
1441

    
1442
/* comparison function for sorting by the order in which interfaces are normally created */
1443
function compare_interface_friendly_names($a, $b) {
1444
	if ($a == $b) {
1445
		return 0;
1446
	} else if ($a == 'wan') {
1447
		return -1;
1448
	} else if ($b == 'wan') {
1449
		return 1;
1450
	} else if ($a == 'lan') {
1451
		return -1;
1452
	} else if ($b == 'lan') {
1453
		return 1;
1454
	}
1455

    
1456
	return strnatcmp($a, $b);
1457
}
1458

    
1459
/* return the configured interfaces list. */
1460
function get_configured_interface_list($withdisabled = false) {
1461
	global $config;
1462

    
1463
	$iflist = array();
1464

    
1465
	/* if list */
1466
	foreach ($config['interfaces'] as $if => $ifdetail) {
1467
		if (isset($ifdetail['enable']) || $withdisabled == true) {
1468
			$iflist[$if] = $if;
1469
		}
1470
	}
1471

    
1472
	return $iflist;
1473
}
1474

    
1475
/* return the configured interfaces list. */
1476
function get_configured_interface_list_by_realif($withdisabled = false) {
1477
	global $config;
1478

    
1479
	$iflist = array();
1480

    
1481
	/* if list */
1482
	foreach ($config['interfaces'] as $if => $ifdetail) {
1483
		if (isset($ifdetail['enable']) || $withdisabled == true) {
1484
			$tmpif = get_real_interface($if);
1485
			if (!empty($tmpif)) {
1486
				$iflist[$tmpif] = $if;
1487
			}
1488
		}
1489
	}
1490

    
1491
	return $iflist;
1492
}
1493

    
1494
/* return the configured interfaces list with their description. */
1495
function get_configured_interface_with_descr($withdisabled = false) {
1496
	global $config, $user_settings;
1497

    
1498
	$iflist = array();
1499

    
1500
	/* if list */
1501
	foreach ($config['interfaces'] as $if => $ifdetail) {
1502
		if (isset($ifdetail['enable']) || $withdisabled == true) {
1503
			if (empty($ifdetail['descr'])) {
1504
				$iflist[$if] = strtoupper($if);
1505
			} else {
1506
				$iflist[$if] = strtoupper($ifdetail['descr']);
1507
			}
1508
		}
1509
	}
1510

    
1511
	if ($user_settings['webgui']['interfacessort']) {
1512
		asort($iflist);
1513
	}
1514

    
1515
	return $iflist;
1516
}
1517

    
1518
/*
1519
 *   get_configured_ip_addresses() - Return a list of all configured
1520
 *   IPv4 addresses.
1521
 *
1522
 */
1523
function get_configured_ip_addresses() {
1524
	global $config;
1525

    
1526
	if (!function_exists('get_interface_ip')) {
1527
		require_once("interfaces.inc");
1528
	}
1529
	$ip_array = array();
1530
	$interfaces = get_configured_interface_list();
1531
	if (is_array($interfaces)) {
1532
		foreach ($interfaces as $int) {
1533
			$ipaddr = get_interface_ip($int);
1534
			$ip_array[$int] = $ipaddr;
1535
		}
1536
	}
1537
	$interfaces = get_configured_vip_list('inet');
1538
	if (is_array($interfaces)) {
1539
		foreach ($interfaces as $int => $ipaddr) {
1540
			$ip_array[$int] = $ipaddr;
1541
		}
1542
	}
1543

    
1544
	/* pppoe server */
1545
	if (is_array($config['pppoes']) && is_array($config['pppoes']['pppoe'])) {
1546
		foreach ($config['pppoes']['pppoe'] as $pppoe) {
1547
			if ($pppoe['mode'] == "server") {
1548
				if (is_ipaddr($pppoe['localip'])) {
1549
					$int = "pppoes". $pppoe['pppoeid'];
1550
					$ip_array[$int] = $pppoe['localip'];
1551
				}
1552
			}
1553
		}
1554
	}
1555

    
1556
	return $ip_array;
1557
}
1558

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

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

    
1689
			case "friendly":
1690
				if ($friendly != "") {
1691
					$toput['if'] = $ifname;
1692
					$iflist[$friendly] = $toput;
1693
				}
1694
				break;
1695
			}
1696
		}
1697
	}
1698
	return $iflist;
1699
}
1700

    
1701
function get_lagg_interface_list() {
1702
	global $config;
1703

    
1704
	$plist = array();
1705
	if (isset($config['laggs']['lagg']) && is_array($config['laggs']['lagg'])) {
1706
		foreach ($config['laggs']['lagg'] as $lagg) {
1707
			$lagg['mac'] = get_interface_mac($lagg['laggif']);
1708
			$lagg['islagg'] = true;
1709
			$plist[$lagg['laggif']] = $lagg;
1710
		}
1711
	}
1712

    
1713
	return ($plist);
1714
}
1715

    
1716
/****f* util/log_error
1717
* NAME
1718
*   log_error  - Sends a string to syslog.
1719
* INPUTS
1720
*   $error     - string containing the syslog message.
1721
* RESULT
1722
*   null
1723
******/
1724
function log_error($error) {
1725
	global $g;
1726
	$page = $_SERVER['SCRIPT_NAME'];
1727
	if (empty($page)) {
1728
		$files = get_included_files();
1729
		$page = basename($files[0]);
1730
	}
1731
	syslog(LOG_ERR, "$page: $error");
1732
	if ($g['debug']) {
1733
		syslog(LOG_WARNING, var_dump(debug_backtrace()));
1734
	}
1735
	return;
1736
}
1737

    
1738
/****f* util/log_auth
1739
* NAME
1740
*   log_auth   - Sends a string to syslog as LOG_AUTH facility
1741
* INPUTS
1742
*   $error     - string containing the syslog message.
1743
* RESULT
1744
*   null
1745
******/
1746
function log_auth($error) {
1747
	global $g;
1748
	$page = $_SERVER['SCRIPT_NAME'];
1749
	syslog(LOG_AUTH, "$page: $error");
1750
	if ($g['debug']) {
1751
		syslog(LOG_WARNING, var_dump(debug_backtrace()));
1752
	}
1753
	return;
1754
}
1755

    
1756
/****f* util/exec_command
1757
 * NAME
1758
 *   exec_command - Execute a command and return a string of the result.
1759
 * INPUTS
1760
 *   $command   - String of the command to be executed.
1761
 * RESULT
1762
 *   String containing the command's result.
1763
 * NOTES
1764
 *   This function returns the command's stdout and stderr.
1765
 ******/
1766
function exec_command($command) {
1767
	$output = array();
1768
	exec($command . ' 2>&1', $output);
1769
	return(implode("\n", $output));
1770
}
1771

    
1772
/* wrapper for exec()
1773
   Executes in background or foreground.
1774
   For background execution, returns PID of background process to allow calling code control */
1775
function mwexec($command, $nologentry = false, $clearsigmask = false, $background = false) {
1776
	global $g;
1777
	$retval = 0;
1778

    
1779
	if ($g['debug']) {
1780
		if (!$_SERVER['REMOTE_ADDR']) {
1781
			echo "mwexec(): $command" . ($background ? " [BG]":"") . "\n";
1782
		}
1783
	}
1784
	if ($clearsigmask) {
1785
		$oldset = array();
1786
		pcntl_sigprocmask(SIG_SETMASK, array(), $oldset);
1787
	}
1788

    
1789
	if ($background) {
1790
		// start background process and return PID
1791
		$retval = exec("/usr/bin/nohup $command > /dev/null 2>&1 & echo $!");
1792
	} else {
1793
		// run in foreground, and (optionally) log if nonzero return
1794
		$outputarray = array();
1795
		exec("$command 2>&1", $outputarray, $retval);
1796
		if (($retval <> 0) && (!$nologentry || isset($config['system']['developerspew']))) {
1797
			log_error(sprintf(gettext("The command '%1\$s' returned exit code '%2\$d', the output was '%3\$s' "), $command, $retval, implode(" ", $outputarray)));
1798
		}
1799
	}
1800

    
1801
	if ($clearsigmask) {
1802
		pcntl_sigprocmask(SIG_SETMASK, $oldset);
1803
	}
1804

    
1805
	return $retval;
1806
}
1807

    
1808
/* wrapper for exec() in background */
1809
function mwexec_bg($command, $clearsigmask = false) {
1810
	return mwexec($command, false, $clearsigmask, true);
1811
}
1812

    
1813
/*	unlink a file, or pattern-match of a file, if it exists
1814
	if the file/path contains glob() compatible wildcards, all matching files will be unlinked
1815
	any warning/errors are suppressed (e.g. no matching files to delete)
1816
	If there are matching file(s) and they were all unlinked OK, then return true.
1817
	Otherwise return false (the requested file(s) did not exist, or could not be deleted)
1818
	This allows the caller to know if they were the one to successfully delete the file(s).
1819
*/
1820
function unlink_if_exists($fn) {
1821
	$to_do = glob($fn);
1822
	if (is_array($to_do) && count($to_do) > 0) {
1823
		// Returns an array of true/false indicating if each unlink worked
1824
		$results = @array_map("unlink", $to_do);
1825
		// If there is no false in the array, then all went well
1826
		$result = !in_array(false, $results, true);
1827
	} else {
1828
		$result = @unlink($fn);
1829
	}
1830
	return $result;
1831
}
1832
/* make a global alias table (for faster lookups) */
1833
function alias_make_table() {
1834
	global $aliastable, $config;
1835

    
1836
	$aliastable = array();
1837

    
1838
	init_config_arr(array('aliases', 'alias'));
1839
	foreach ($config['aliases']['alias'] as $alias) {
1840
		if ($alias['name']) {
1841
			$aliastable[$alias['name']] = $alias['address'];
1842
		}
1843
	}
1844
}
1845

    
1846
/* check if an alias exists */
1847
function is_alias($name) {
1848
	global $aliastable;
1849

    
1850
	return isset($aliastable[$name]);
1851
}
1852

    
1853
function alias_get_type($name) {
1854
	global $config;
1855

    
1856
	if (is_array($config['aliases']['alias'])) {
1857
		foreach ($config['aliases']['alias'] as $alias) {
1858
			if ($name == $alias['name']) {
1859
				return $alias['type'];
1860
			}
1861
		}
1862
	}
1863

    
1864
	return "";
1865
}
1866

    
1867
/* expand a host or network alias, if necessary */
1868
function alias_expand($name) {
1869
	global $config, $aliastable;
1870
	$urltable_prefix = "/var/db/aliastables/";
1871
	$urltable_filename = $urltable_prefix . $name . ".txt";
1872

    
1873
	if (isset($aliastable[$name])) {
1874
		// alias names cannot be strictly numeric. redmine #4289
1875
		if (is_numericint($name)) {
1876
			return null;
1877
		}
1878
		// make sure if it's a ports alias, it actually exists. redmine #5845
1879
		foreach ($config['aliases']['alias'] as $alias) {
1880
			if ($alias['name'] == $name) {
1881
				if ($alias['type'] == "urltable_ports") {
1882
					if (is_URL($alias['url']) && file_exists($urltable_filename) && filesize($urltable_filename)) {
1883
						return "\${$name}";
1884
					} else {
1885
						return null;
1886
					}
1887
				}
1888
			}
1889
		}
1890
		return "\${$name}";
1891
	} else if (is_ipaddr($name) || is_subnet($name) || is_port_or_range($name)) {
1892
		return "{$name}";
1893
	} else {
1894
		return null;
1895
	}
1896
}
1897

    
1898
function alias_expand_urltable($name) {
1899
	global $config;
1900
	$urltable_prefix = "/var/db/aliastables/";
1901
	$urltable_filename = $urltable_prefix . $name . ".txt";
1902

    
1903
	if (is_array($config['aliases']['alias'])) {
1904
		foreach ($config['aliases']['alias'] as $alias) {
1905
			if (preg_match("/urltable/i", $alias['type']) && ($alias['name'] == $name)) {
1906
				if (is_URL($alias["url"]) && file_exists($urltable_filename)) {
1907
					if (!filesize($urltable_filename)) {
1908
						// file exists, but is empty, try to sync
1909
						send_event("service sync alias {$name}");
1910
					}
1911
					return $urltable_filename;
1912
				} else {
1913
					send_event("service sync alias {$name}");
1914
					break;
1915
				}
1916
			}
1917
		}
1918
	}
1919
	return null;
1920
}
1921

    
1922
/* obtain MAC address given an IP address by looking at the ARP/NDP table */
1923
function arp_get_mac_by_ip($ip, $do_ping = true) {
1924
	unset($macaddr);
1925
	$retval = 1;
1926
	switch (is_ipaddr($ip)) {
1927
		case 4:
1928
			if ($do_ping === true) {
1929
				mwexec("/sbin/ping -c 1 -t 1 " . escapeshellarg($ip), true);
1930
			}
1931
			$macaddr = exec("/usr/sbin/arp -n " . escapeshellarg($ip) . " | /usr/bin/awk '{print $4}'", $output, $retval);
1932
			break;
1933
		case 6:
1934
			if ($do_ping === true) {
1935
				mwexec("/sbin/ping6 -c 1 -X 1 " . escapeshellarg($ip), true);
1936
			}
1937
			$macaddr = exec("/usr/sbin/ndp -n " . escapeshellarg($ip) . " | /usr/bin/awk '{print $2}'", $output, $retval);
1938
			break;
1939
	}
1940
	if ($retval == 0 && is_macaddr($macaddr)) {
1941
		return $macaddr;
1942
	} else {
1943
		return false;
1944
	}
1945
}
1946

    
1947
/* return a fieldname that is safe for xml usage */
1948
function xml_safe_fieldname($fieldname) {
1949
	$replace = array(
1950
	    '/', '-', ' ', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')',
1951
	    '_', '+', '=', '{', '}', '[', ']', '|', '/', '<', '>', '?',
1952
	    ':', ',', '.', '\'', '\\'
1953
	);
1954
	return strtolower(str_replace($replace, "", $fieldname));
1955
}
1956

    
1957
function mac_format($clientmac) {
1958
	global $config, $cpzone;
1959

    
1960
	$mac = explode(":", $clientmac);
1961
	$mac_format = $cpzone ? $config['captiveportal'][$cpzone]['radmac_format'] : false;
1962

    
1963
	switch ($mac_format) {
1964
		case 'singledash':
1965
			return "$mac[0]$mac[1]$mac[2]-$mac[3]$mac[4]$mac[5]";
1966

    
1967
		case 'ietf':
1968
			return "$mac[0]-$mac[1]-$mac[2]-$mac[3]-$mac[4]-$mac[5]";
1969

    
1970
		case 'cisco':
1971
			return "$mac[0]$mac[1].$mac[2]$mac[3].$mac[4]$mac[5]";
1972

    
1973
		case 'unformatted':
1974
			return "$mac[0]$mac[1]$mac[2]$mac[3]$mac[4]$mac[5]";
1975

    
1976
		default:
1977
			return $clientmac;
1978
	}
1979
}
1980

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

    
1983
	$recresult = array();
1984
	$returnres = array();
1985
	for ($i = 0; $i < $retries; $i++) {
1986
		switch ($protocol) {
1987
			case 'any':
1988
				$checkproto = 'is_ipaddr';
1989
				$dnsproto = DNS_ANY;
1990
				$dnstype = array('A', 'AAAA');
1991
				break;
1992
			case 'inet6':
1993
				$checkproto = 'is_ipaddrv6';
1994
				$dnsproto = DNS_AAAA;
1995
				$dnstype = array('AAAA');
1996
				break;
1997
			case 'inet': 
1998
			default:
1999
				$checkproto = 'is_ipaddrv4';
2000
				$dnsproto = DNS_A;
2001
				$dnstype = array('A');
2002
				break;
2003
		}
2004
		if ($checkproto($hostname)) {
2005
			return $hostname;
2006
		}
2007
		$dnsresult = @dns_get_record($hostname, $dnsproto);
2008
		if (!empty($dnsresult)) {
2009
			foreach ($dnsresult as $dnsrec => $ip) {
2010
				if (is_array($ip)) {
2011
					if (in_array($ip['type'], $dnstype)) {
2012
					    if ($checkproto($ip['ip'])) { 
2013
						    $recresult[] = $ip['ip'];
2014
					    }
2015
					    if ($checkproto($ip['ipv6'])) { 
2016
						    $recresult[] = $ip['ipv6'];
2017
					    }
2018
					}
2019
				}
2020
			}
2021
		}
2022

    
2023
		sleep(1);
2024
	}
2025

    
2026
	if (!empty($recresult)) {
2027
		if ($numrecords == 1) {
2028
			return $recresult[0];
2029
		} else {
2030
			return array_slice($recresult, 0, $numrecords);
2031
		}
2032
	}
2033

    
2034
	return false;
2035
}
2036

    
2037
function format_bytes($bytes) {
2038
	if ($bytes >= 1099511627776) {
2039
		return sprintf("%.2f TiB", $bytes/1099511627776);
2040
	} else if ($bytes >= 1073741824) {
2041
		return sprintf("%.2f GiB", $bytes/1073741824);
2042
	} else if ($bytes >= 1048576) {
2043
		return sprintf("%.2f MiB", $bytes/1048576);
2044
	} else if ($bytes >= 1024) {
2045
		return sprintf("%.0f KiB", $bytes/1024);
2046
	} else {
2047
		return sprintf("%d B", $bytes);
2048
	}
2049
}
2050

    
2051
function format_number($num, $precision = 3) {
2052
	$units = array('', 'K', 'M', 'G', 'T');
2053

    
2054
	$i = 0;
2055
	while ($num > 1000 && $i < count($units)) {
2056
		$num /= 1000;
2057
		$i++;
2058
	}
2059
	$num = round($num, $precision);
2060

    
2061
	return ("$num {$units[$i]}");
2062
}
2063

    
2064

    
2065
function unformat_number($formated_num) {
2066
	$num = strtoupper($formated_num);
2067
    
2068
	if ( strpos($num,"T") !== false ) {
2069
		$num = str_replace("T","",$num) * 1000 * 1000 * 1000 * 1000;
2070
	} else if ( strpos($num,"G") !== false ) {
2071
		$num = str_replace("G","",$num) * 1000 * 1000 * 1000;
2072
	} else if ( strpos($num,"M") !== false ) {
2073
		$num = str_replace("M","",$num) * 1000 * 1000;
2074
	} else if ( strpos($num,"K") !== false ) {
2075
		$num = str_replace("K","",$num) * 1000;
2076
	}
2077
    
2078
	return $num;
2079
}
2080

    
2081
function update_filter_reload_status($text, $new=false) {
2082
	global $g;
2083

    
2084
	if ($new) {
2085
		file_put_contents("{$g['varrun_path']}/filter_reload_status", $text  . PHP_EOL);
2086
	} else {
2087
		file_put_contents("{$g['varrun_path']}/filter_reload_status", $text  . PHP_EOL, FILE_APPEND);
2088
	}
2089
}
2090

    
2091
/****** util/return_dir_as_array
2092
 * NAME
2093
 *   return_dir_as_array - Return a directory's contents as an array.
2094
 * INPUTS
2095
 *   $dir          - string containing the path to the desired directory.
2096
 *   $filter_regex - string containing a regular expression to filter file names. Default empty.
2097
 * RESULT
2098
 *   $dir_array - array containing the directory's contents. This array will be empty if the path specified is invalid.
2099
 ******/
2100
function return_dir_as_array($dir, $filter_regex = '') {
2101
	$dir_array = array();
2102
	if (is_dir($dir)) {
2103
		if ($dh = opendir($dir)) {
2104
			while (($file = readdir($dh)) !== false) {
2105
				if (($file == ".") || ($file == "..")) {
2106
					continue;
2107
				}
2108

    
2109
				if (empty($filter_regex) || preg_match($filter_regex, $file)) {
2110
					array_push($dir_array, $file);
2111
				}
2112
			}
2113
			closedir($dh);
2114
		}
2115
	}
2116
	return $dir_array;
2117
}
2118

    
2119
function run_plugins($directory) {
2120
	global $config, $g;
2121

    
2122
	/* process packager manager custom rules */
2123
	$files = return_dir_as_array($directory);
2124
	if (is_array($files)) {
2125
		foreach ($files as $file) {
2126
			if (stristr($file, ".sh") == true) {
2127
				mwexec($directory . $file . " start");
2128
			} else if (!is_dir($directory . "/" . $file) && stristr($file, ".inc")) {
2129
				require_once($directory . "/" . $file);
2130
			}
2131
		}
2132
	}
2133
}
2134

    
2135
/*
2136
 *    safe_mkdir($path, $mode = 0755)
2137
 *    create directory if it doesn't already exist and isn't a file!
2138
 */
2139
function safe_mkdir($path, $mode = 0755) {
2140
	global $g;
2141

    
2142
	if (!is_file($path) && !is_dir($path)) {
2143
		return @mkdir($path, $mode, true);
2144
	} else {
2145
		return false;
2146
	}
2147
}
2148

    
2149
/*
2150
 * get_sysctl($names)
2151
 * Get values of sysctl OID's listed in $names (accepts an array or a single
2152
 * name) and return an array of key/value pairs set for those that exist
2153
 */
2154
function get_sysctl($names) {
2155
	if (empty($names)) {
2156
		return array();
2157
	}
2158

    
2159
	if (is_array($names)) {
2160
		$name_list = array();
2161
		foreach ($names as $name) {
2162
			$name_list[] = escapeshellarg($name);
2163
		}
2164
	} else {
2165
		$name_list = array(escapeshellarg($names));
2166
	}
2167

    
2168
	exec("/sbin/sysctl -iq " . implode(" ", $name_list), $output);
2169
	$values = array();
2170
	foreach ($output as $line) {
2171
		$line = explode(": ", $line, 2);
2172
		if (count($line) == 2) {
2173
			$values[$line[0]] = $line[1];
2174
		}
2175
	}
2176

    
2177
	return $values;
2178
}
2179

    
2180
/*
2181
 * get_single_sysctl($name)
2182
 * Wrapper for get_sysctl() to simplify read of a single sysctl value
2183
 * return the value for sysctl $name or empty string if it doesn't exist
2184
 */
2185
function get_single_sysctl($name) {
2186
	if (empty($name)) {
2187
		return "";
2188
	}
2189

    
2190
	$value = get_sysctl($name);
2191
	if (empty($value) || !isset($value[$name])) {
2192
		return "";
2193
	}
2194

    
2195
	return $value[$name];
2196
}
2197

    
2198
/*
2199
 * set_sysctl($value_list)
2200
 * Set sysctl OID's listed as key/value pairs and return
2201
 * an array with keys set for those that succeeded
2202
 */
2203
function set_sysctl($values) {
2204
	if (empty($values)) {
2205
		return array();
2206
	}
2207

    
2208
	$value_list = array();
2209
	foreach ($values as $key => $value) {
2210
		$value_list[] = escapeshellarg($key) . "=" . escapeshellarg($value);
2211
	}
2212

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

    
2215
	/* Retry individually if failed (one or more read-only) */
2216
	if ($success <> 0 && count($value_list) > 1) {
2217
		foreach ($value_list as $value) {
2218
			exec("/sbin/sysctl -iq " . $value, $output);
2219
		}
2220
	}
2221

    
2222
	$ret = array();
2223
	foreach ($output as $line) {
2224
		$line = explode(": ", $line, 2);
2225
		if (count($line) == 2) {
2226
			$ret[$line[0]] = true;
2227
		}
2228
	}
2229

    
2230
	return $ret;
2231
}
2232

    
2233
/*
2234
 * set_single_sysctl($name, $value)
2235
 * Wrapper to set_sysctl() to make it simple to set only one sysctl
2236
 * returns boolean meaning if it succeeded
2237
 */
2238
function set_single_sysctl($name, $value) {
2239
	if (empty($name)) {
2240
		return false;
2241
	}
2242

    
2243
	$result = set_sysctl(array($name => $value));
2244

    
2245
	if (!isset($result[$name]) || $result[$name] != $value) {
2246
		return false;
2247
	}
2248

    
2249
	return true;
2250
}
2251

    
2252
/*
2253
 *     get_memory()
2254
 *     returns an array listing the amount of
2255
 *     memory installed in the hardware
2256
 *     [0] net memory available for the OS (FreeBSD) after some is taken by BIOS, video or whatever - e.g. 235 MBytes
2257
 *     [1] real (actual) memory of the system, should be the size of the RAM card/s - e.g. 256 MBytes
2258
 */
2259
function get_memory() {
2260
	$physmem = get_single_sysctl("hw.physmem");
2261
	$realmem = get_single_sysctl("hw.realmem");
2262
	/* convert from bytes to megabytes */
2263
	return array(($physmem/1048576), ($realmem/1048576));
2264
}
2265

    
2266
function mute_kernel_msgs() {
2267
	global $g, $config;
2268

    
2269
	if ($config['system']['enableserial']) {
2270
		return;
2271
	}
2272
	exec("/sbin/conscontrol mute on");
2273
}
2274

    
2275
function unmute_kernel_msgs() {
2276
	global $g;
2277

    
2278
	exec("/sbin/conscontrol mute off");
2279
}
2280

    
2281
function start_devd() {
2282
	global $g;
2283

    
2284
	/* Generate hints for the kernel loader. */
2285
	$module_paths = explode(";", get_single_sysctl("kern.module_path"));
2286
	foreach ($module_paths as $id => $path) {
2287
		if (!is_dir($path) || file_exists("{$path}/linker.hints")) {
2288
			continue;
2289
		}
2290
		if (($files = scandir($path)) == false) {
2291
			continue;
2292
		}
2293
		$found = false;
2294
		foreach ($files as $id => $file) {
2295
			if (strlen($file) > 3 &&
2296
			    strcasecmp(substr($file, -3), ".ko") == 0) {
2297
				$found = true;
2298
				break;
2299
			}
2300
		}
2301
		if ($found == false) {
2302
			continue;
2303
		}
2304
		$_gb = exec("/usr/sbin/kldxref $path");
2305
		unset($_gb);
2306
	}
2307

    
2308
	/* Use the undocumented -q options of devd to quiet its log spamming */
2309
	$_gb = exec("/sbin/devd -q -f /etc/{$g['product_name']}-devd.conf");
2310
	sleep(1);
2311
	unset($_gb);
2312
}
2313

    
2314
function is_interface_vlan_mismatch() {
2315
	global $config, $g;
2316

    
2317
	if (is_array($config['vlans']['vlan'])) {
2318
		foreach ($config['vlans']['vlan'] as $vlan) {
2319
			if (substr($vlan['if'], 0, 4) == "lagg") {
2320
				return false;
2321
			}
2322
			if (does_interface_exist($vlan['if']) == false) {
2323
				return true;
2324
			}
2325
		}
2326
	}
2327

    
2328
	return false;
2329
}
2330

    
2331
function is_interface_mismatch() {
2332
	global $config, $g;
2333

    
2334
	$do_assign = false;
2335
	$i = 0;
2336
	$missing_interfaces = array();
2337
	if (is_array($config['interfaces'])) {
2338
		foreach ($config['interfaces'] as $ifname => $ifcfg) {
2339
			if (interface_is_vlan($ifcfg['if']) != NULL ||
2340
			    interface_is_qinq($ifcfg['if']) != NULL ||
2341
			    preg_match("/^enc|^cua|^tun|^tap|^l2tp|^pptp|^ppp|^ovpn|^ipsec|^gif|^gre|^lagg|^bridge|vlan|_wlan|_\d{0,4}_\d{0,4}$/i", $ifcfg['if'])) {
2342
				// Do not check these interfaces.
2343
				$i++;
2344
				continue;
2345
			} else if (does_interface_exist($ifcfg['if']) == false) {
2346
				$missing_interfaces[] = $ifcfg['if'];
2347
				$do_assign = true;
2348
			} else {
2349
				$i++;
2350
			}
2351
		}
2352
	}
2353

    
2354
	if (file_exists("{$g['tmp_path']}/assign_complete")) {
2355
		$do_assign = false;
2356
	}
2357

    
2358
	if (!empty($missing_interfaces) && $do_assign) {
2359
		file_put_contents("{$g['tmp_path']}/missing_interfaces", implode(' ', $missing_interfaces));
2360
	} else {
2361
		@unlink("{$g['tmp_path']}/missing_interfaces");
2362
	}
2363

    
2364
	return $do_assign;
2365
}
2366

    
2367
/* sync carp entries to other firewalls */
2368
function carp_sync_client() {
2369
	global $g;
2370
	send_event("filter sync");
2371
}
2372

    
2373
/****f* util/isAjax
2374
 * NAME
2375
 *   isAjax - reports if the request is driven from prototype
2376
 * INPUTS
2377
 *   none
2378
 * RESULT
2379
 *   true/false
2380
 ******/
2381
function isAjax() {
2382
	return isset ($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest';
2383
}
2384

    
2385
/****f* util/timeout
2386
 * NAME
2387
 *   timeout - console input with timeout countdown. Note: erases 2 char of screen for timer. Leave space.
2388
 * INPUTS
2389
 *   optional, seconds to wait before timeout. Default 9 seconds.
2390
 * RESULT
2391
 *   returns 1 char of user input or null if no input.
2392
 ******/
2393
function timeout($timer = 9) {
2394
	while (!isset($key)) {
2395
		if ($timer >= 9) {
2396
			echo chr(8) . chr(8) . ($timer == 9 ? chr(32) : null) . "{$timer}";
2397
		} else {
2398
			echo chr(8). "{$timer}";
2399
		}
2400
		`/bin/stty -icanon min 0 time 25`;
2401
		$key = trim(`KEY=\`dd count=1 2>/dev/null\`; echo \$KEY`);
2402
		`/bin/stty icanon`;
2403
		if ($key == '') {
2404
			unset($key);
2405
		}
2406
		$timer--;
2407
		if ($timer == 0) {
2408
			break;
2409
		}
2410
	}
2411
	return $key;
2412
}
2413

    
2414
/****f* util/msort
2415
 * NAME
2416
 *   msort - sort array
2417
 * INPUTS
2418
 *   $array to be sorted, field to sort by, direction of sort
2419
 * RESULT
2420
 *   returns newly sorted array
2421
 ******/
2422
function msort($array, $id = "id", $sort_ascending = true) {
2423
	$temp_array = array();
2424
	if (!is_array($array)) {
2425
		return $temp_array;
2426
	}
2427
	while (count($array)>0) {
2428
		$lowest_id = 0;
2429
		$index = 0;
2430
		foreach ($array as $item) {
2431
			if (isset($item[$id])) {
2432
				if ($array[$lowest_id][$id]) {
2433
					if (strtolower($item[$id]) < strtolower($array[$lowest_id][$id])) {
2434
						$lowest_id = $index;
2435
					}
2436
				}
2437
			}
2438
			$index++;
2439
		}
2440
		$temp_array[] = $array[$lowest_id];
2441
		$array = array_merge(array_slice($array, 0, $lowest_id), array_slice($array, $lowest_id + 1));
2442
	}
2443
	if ($sort_ascending) {
2444
		return $temp_array;
2445
	} else {
2446
		return array_reverse($temp_array);
2447
	}
2448
}
2449

    
2450
/****f* util/is_URL
2451
 * NAME
2452
 *   is_URL
2453
 * INPUTS
2454
 *   string to check
2455
 * RESULT
2456
 *   Returns true if item is a URL
2457
 ******/
2458
function is_URL($url) {
2459
	$match = preg_match("'\b(([\w-]+://?|www[.])[^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|/)))'", $url);
2460
	if ($match) {
2461
		return true;
2462
	}
2463
	return false;
2464
}
2465

    
2466
function is_file_included($file = "") {
2467
	$files = get_included_files();
2468
	if (in_array($file, $files)) {
2469
		return true;
2470
	}
2471

    
2472
	return false;
2473
}
2474

    
2475
/*
2476
 * Replace a value on a deep associative array using regex
2477
 */
2478
function array_replace_values_recursive($data, $match, $replace) {
2479
	if (empty($data)) {
2480
		return $data;
2481
	}
2482

    
2483
	if (is_string($data)) {
2484
		$data = preg_replace("/{$match}/", $replace, $data);
2485
	} else if (is_array($data)) {
2486
		foreach ($data as $k => $v) {
2487
			$data[$k] = array_replace_values_recursive($v, $match, $replace);
2488
		}
2489
	}
2490

    
2491
	return $data;
2492
}
2493

    
2494
/*
2495
	This function was borrowed from a comment on PHP.net at the following URL:
2496
	http://www.php.net/manual/en/function.array-merge-recursive.php#73843
2497
 */
2498
function array_merge_recursive_unique($array0, $array1) {
2499

    
2500
	$arrays = func_get_args();
2501
	$remains = $arrays;
2502

    
2503
	// We walk through each arrays and put value in the results (without
2504
	// considering previous value).
2505
	$result = array();
2506

    
2507
	// loop available array
2508
	foreach ($arrays as $array) {
2509

    
2510
		// The first remaining array is $array. We are processing it. So
2511
		// we remove it from remaining arrays.
2512
		array_shift($remains);
2513

    
2514
		// We don't care non array param, like array_merge since PHP 5.0.
2515
		if (is_array($array)) {
2516
			// Loop values
2517
			foreach ($array as $key => $value) {
2518
				if (is_array($value)) {
2519
					// we gather all remaining arrays that have such key available
2520
					$args = array();
2521
					foreach ($remains as $remain) {
2522
						if (array_key_exists($key, $remain)) {
2523
							array_push($args, $remain[$key]);
2524
						}
2525
					}
2526

    
2527
					if (count($args) > 2) {
2528
						// put the recursion
2529
						$result[$key] = call_user_func_array(__FUNCTION__, $args);
2530
					} else {
2531
						foreach ($value as $vkey => $vval) {
2532
							if (!is_array($result[$key])) {
2533
								$result[$key] = array();
2534
							}
2535
							$result[$key][$vkey] = $vval;
2536
						}
2537
					}
2538
				} else {
2539
					// simply put the value
2540
					$result[$key] = $value;
2541
				}
2542
			}
2543
		}
2544
	}
2545
	return $result;
2546
}
2547

    
2548

    
2549
/*
2550
 * converts a string like "a,b,c,d"
2551
 * into an array like array("a" => "b", "c" => "d")
2552
 */
2553
function explode_assoc($delimiter, $string) {
2554
	$array = explode($delimiter, $string);
2555
	$result = array();
2556
	$numkeys = floor(count($array) / 2);
2557
	for ($i = 0; $i < $numkeys; $i += 1) {
2558
		$result[$array[$i * 2]] = $array[$i * 2 + 1];
2559
	}
2560
	return $result;
2561
}
2562

    
2563
/*
2564
 * Given a string of text with some delimiter, look for occurrences
2565
 * of some string and replace all of those.
2566
 * $text - the text string (e.g. "abc,defg,x123,ipv4,xyz")
2567
 * $delimiter - the delimiter (e.g. ",")
2568
 * $element - the element to match (e.g. "defg")
2569
 * $replacement - the string to replace it with (e.g. "42")
2570
 * Returns the resulting delimited string (e.g. "abc,42,x123,ipv4,xyz")
2571
 */
2572
function replace_element_in_list($text, $delimiter, $element, $replacement) {
2573
	$textArray = explode($delimiter, $text);
2574
	while (($entry = array_search($element, $textArray)) !== false) {
2575
		$textArray[$entry] = $replacement;
2576
	}
2577
	return implode(',', $textArray);
2578
}
2579

    
2580
/* Return system's route table */
2581
function route_table() {
2582
	$_gb = exec("/usr/bin/netstat --libxo json -nWr", $rawdata, $rc);
2583

    
2584
	if ($rc != 0) {
2585
		return array();
2586
	}
2587

    
2588
	$netstatarr = json_decode(implode(" ", $rawdata), JSON_OBJECT_AS_ARRAY);
2589
	$netstatarr = $netstatarr['statistics']['route-information']
2590
	    ['route-table']['rt-family'];
2591

    
2592
	$result = array();
2593
	$result['inet'] = array();
2594
	$result['inet6'] = array();
2595
	foreach ($netstatarr as $item) {
2596
		if ($item['address-family'] == 'Internet') {
2597
			$result['inet'] = $item['rt-entry'];
2598
		} else if ($item['address-family'] == 'Internet6') {
2599
			$result['inet6'] = $item['rt-entry'];
2600
		}
2601
	}
2602
	unset($netstatarr);
2603

    
2604
	return $result;
2605
}
2606

    
2607
/* Get static route for specific destination */
2608
function route_get($target, $ipprotocol = '') {
2609
	if (!empty($ipprotocol)) {
2610
		$family = $ipprotocol;
2611
	} else if (is_v4($target)) {
2612
		$family = 'inet';
2613
	} else if (is_v6($target)) {
2614
		$family = 'inet6';
2615
	}
2616

    
2617
	if (empty($family)) {
2618
		return array();
2619
	}
2620

    
2621
	$rtable = route_table();
2622

    
2623
	if (empty($rtable)) {
2624
		return array();
2625
	}
2626

    
2627
	$result = array();
2628
	foreach ($rtable[$family] as $item) {
2629
		if ($item['destination'] == $target) {
2630
			$result[] = $item;
2631
		}
2632
	}
2633

    
2634
	return $result;
2635
}
2636

    
2637
/* Get default route */
2638
function route_get_default($ipprotocol) {
2639
	if (empty($ipprotocol) || ($ipprotocol != 'inet' &&
2640
	    $ipprotocol != 'inet6')) {
2641
		return '';
2642
	}
2643

    
2644
	$route = route_get('default', $ipprotocol);
2645

    
2646
	if (empty($route)) {
2647
		return '';
2648
	}
2649

    
2650
	if (!isset($route[0]['gateway'])) {
2651
		return '';
2652
	}
2653

    
2654
	return $route[0]['gateway'];
2655
}
2656

    
2657
/* Delete a static route */
2658
function route_del($target, $ipprotocol = '') {
2659
	global $config;
2660

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

    
2665
	if (!empty($ipprotocol) && $ipprotocol != 'inet' &&
2666
	    $ipprotocol != 'inet6') {
2667
		return false;
2668
	}
2669

    
2670
	$route = route_get($target, $ipprotocol);
2671

    
2672
	if (empty($route)) {
2673
		return;
2674
	}
2675

    
2676
	$target_prefix = '';
2677
	if (is_subnet($target)) {
2678
		$target_prefix = '-net';
2679
	} else if (is_ipaddr($target)) {
2680
		$target_prefix = '-host';
2681
	}
2682

    
2683
	if (!empty($ipprotocol)) {
2684
		$target_prefix .= " -{$ipprotocol}";
2685
	} else if (is_v6($target)) {
2686
		$target_prefix .= ' -inet6';
2687
	} else if (is_v4($target)) {
2688
		$target_prefix .= ' -inet';
2689
	}
2690

    
2691
	foreach ($route as $item) {
2692
		if (substr($item['gateway'], 0, 5) == 'link#') {
2693
			continue;
2694
		}
2695

    
2696
		if (is_macaddr($item['gateway'])) {
2697
			$gw = '-iface ' . $item['interface-name'];
2698
		} else {
2699
			$gw = $item['gateway'];
2700
		}
2701

    
2702
		$_gb = exec(escapeshellcmd("/sbin/route del {$target_prefix} " .
2703
		    "{$target} {$gw}"), $output, $rc);
2704

    
2705
		if (isset($config['system']['route-debug'])) {
2706
			log_error("ROUTING debug: " . microtime() .
2707
			    " - DEL RC={$rc} - {$target} - gw: " . $gw);
2708
			file_put_contents("/dev/console", "\n[" . getmypid() .
2709
			    "] ROUTE DEL: {$target_prefix} {$target} {$gw} " .
2710
			    "result: {$rc}");
2711
		}
2712
	}
2713
}
2714

    
2715
/*
2716
 * Add static route.  If it already exists, remove it and re-add
2717
 *
2718
 * $target - IP, subnet or 'default'
2719
 * $gw     - gateway address
2720
 * $iface  - Network interface
2721
 * $args   - Extra arguments for /sbin/route
2722
 * $ipprotocol - 'inet' or 'inet6'.  Mandatory when $target == 'default'
2723
 *
2724
 */
2725
function route_add_or_change($target, $gw, $iface = '', $args = '',
2726
    $ipprotocol = '') {
2727
	global $config;
2728

    
2729
	if (empty($target) || (empty($gw) && empty($iface))) {
2730
		return false;
2731
	}
2732

    
2733
	if ($target == 'default' && empty($ipprotocol)) {
2734
		return false;
2735
	}
2736

    
2737
	if (!empty($ipprotocol) && $ipprotocol != 'inet' &&
2738
	    $ipprotocol != 'inet6') {
2739
		return false;
2740
	}
2741

    
2742
	if (is_subnet($target)) {
2743
		$target_prefix = '-net';
2744
	} else if (is_ipaddr($target)) {
2745
		$target_prefix = '-host';
2746
	}
2747

    
2748
	if (!empty($ipprotocol)) {
2749
		$target_prefix .= " -{$ipprotocol}";
2750
	} else if (is_v6($target)) {
2751
		$target_prefix .= ' -inet6';
2752
	} else if (is_v4($target)) {
2753
		$target_prefix .= ' -inet';
2754
	}
2755

    
2756
	/* If there is another route to the same target, remove it */
2757
	route_del($target, $ipprotocol);
2758

    
2759
	$params = '';
2760
	if (!empty($iface) && does_interface_exist($iface)) {
2761
		$params .= " -iface {$gw}";
2762
	}
2763
	if (is_ipaddr($gw)) {
2764
		$params .= " " . $gw;
2765
	}
2766

    
2767
	if (empty($params)) {
2768
		log_error("route_add_or_change: Invalid gateway {$gw} and/or " .
2769
		    "network interface {$iface}");
2770
		return false;
2771
	}
2772

    
2773
	$_gb = exec(escapeshellcmd("/sbin/route add {$target_prefix} " .
2774
	    "{$target} {$args} {$params}"), $output, $rc);
2775

    
2776
	if (isset($config['system']['route-debug'])) {
2777
		log_error("ROUTING debug: " . microtime() .
2778
		    " - ADD RC={$rc} - {$target} {$args}");
2779
		file_put_contents("/dev/console", "\n[" . getmypid() .
2780
		    "] ROUTE ADD: {$target_prefix} {$target} {$args} " .
2781
		    "{$params} result: {$rc}");
2782
	}
2783

    
2784
	return ($rc == 0);
2785
}
2786

    
2787
function set_ipv6routes_mtu($interface, $mtu) {
2788
	global $config, $g;
2789

    
2790
	$ipv6mturoutes = array();
2791
	$if = convert_real_interface_to_friendly_interface_name($interface);
2792
	if (!$config['interfaces'][$if]['ipaddrv6']) {
2793
		return;
2794
	}
2795
	$a_gateways = return_gateways_array();
2796
	$a_staticroutes = get_staticroutes(false, false, true);
2797
	foreach ($a_gateways as $gate) {
2798
		foreach ($a_staticroutes as $sroute) {
2799
			if ($gate['interface'] == $interface &&
2800
			    $sroute['gateway'] == $gate['name']) {
2801
				$tgt = $sroute['network'];
2802
				$gateway = $gate['gateway'];
2803
				$ipv6mturoutes[$tgt] = $gateway;
2804
			}
2805
		}
2806
		if ($gate['interface'] == $interface &&
2807
		    $gate['isdefaultgw']) {
2808
			$tgt = "default";
2809
			$gateway = $gate['gateway'];
2810
			$ipv6mturoutes[$tgt] = $gateway;
2811
		}
2812
	}
2813
	foreach ($ipv6mturoutes as $tgt => $gateway) {
2814
		mwexec("/sbin/route change -6 -mtu " . escapeshellarg($mtu) .
2815
		    " " . escapeshellarg($tgt) . " " .
2816
		    escapeshellarg($gateway));
2817
	}
2818
}
2819

    
2820
function alias_to_subnets_recursive($name, $returnhostnames = false) {
2821
	global $aliastable;
2822
	$result = array();
2823
	if (!isset($aliastable[$name])) {
2824
		return $result;
2825
	}
2826
	$subnets = preg_split('/\s+/', $aliastable[$name]);
2827
	foreach ($subnets as $net) {
2828
		if (is_alias($net)) {
2829
			$sub = alias_to_subnets_recursive($net, $returnhostnames);
2830
			$result = array_merge($result, $sub);
2831
			continue;
2832
		} elseif (!is_subnet($net)) {
2833
			if (is_ipaddrv4($net)) {
2834
				$net .= "/32";
2835
			} else if (is_ipaddrv6($net)) {
2836
				$net .= "/128";
2837
			} else if ($returnhostnames === false || !is_fqdn($net)) {
2838
				continue;
2839
			}
2840
		}
2841
		$result[] = $net;
2842
	}
2843
	return $result;
2844
}
2845

    
2846
function get_staticroutes($returnsubnetsonly = false, $returnhostnames = false, $returnenabledroutesonly = false) {
2847
	global $config, $aliastable;
2848

    
2849
	/* Bail if there are no routes, but return an array always so callers don't have to check. */
2850
	init_config_arr(array('staticroutes', 'route'));
2851
	if (empty($config['staticroutes']['route'])) {
2852
		return array();
2853
	}
2854

    
2855
	$allstaticroutes = array();
2856
	$allsubnets = array();
2857
	/* Loop through routes and expand aliases as we find them. */
2858
	foreach ($config['staticroutes']['route'] as $route) {
2859
		if ($returnenabledroutesonly && isset($route['disabled'])) {
2860
			continue;
2861
		}
2862

    
2863
		if (is_alias($route['network'])) {
2864
			foreach (alias_to_subnets_recursive($route['network'], $returnhostnames) as $net) {
2865
				$temproute = $route;
2866
				$temproute['network'] = $net;
2867
				$allstaticroutes[] = $temproute;
2868
				$allsubnets[] = $net;
2869
			}
2870
		} elseif (is_subnet($route['network'])) {
2871
			$allstaticroutes[] = $route;
2872
			$allsubnets[] = $route['network'];
2873
		}
2874
	}
2875
	if ($returnsubnetsonly) {
2876
		return $allsubnets;
2877
	} else {
2878
		return $allstaticroutes;
2879
	}
2880
}
2881

    
2882
/****f* util/get_alias_list
2883
 * NAME
2884
 *   get_alias_list - Provide a list of aliases.
2885
 * INPUTS
2886
 *   $type          - Optional, can be a string or array specifying what type(s) of aliases you need.
2887
 * RESULT
2888
 *   Array containing list of aliases.
2889
 *   If $type is unspecified, all aliases are returned.
2890
 *   If $type is a string, all aliases of the type specified in $type are returned.
2891
 *   If $type is an array, all aliases of any type specified in any element of $type are returned.
2892
 */
2893
function get_alias_list($type = null) {
2894
	global $config;
2895
	$result = array();
2896
	if ($config['aliases']['alias'] <> "" && is_array($config['aliases']['alias'])) {
2897
		foreach ($config['aliases']['alias'] as $alias) {
2898
			if ($type === null) {
2899
				$result[] = $alias['name'];
2900
			} else if (is_array($type)) {
2901
				if (in_array($alias['type'], $type)) {
2902
					$result[] = $alias['name'];
2903
				}
2904
			} else if ($type === $alias['type']) {
2905
				$result[] = $alias['name'];
2906
			}
2907
		}
2908
	}
2909
	return $result;
2910
}
2911

    
2912
/* returns an array consisting of every element of $haystack that is not equal to $needle. */
2913
function array_exclude($needle, $haystack) {
2914
	$result = array();
2915
	if (is_array($haystack)) {
2916
		foreach ($haystack as $thing) {
2917
			if ($needle !== $thing) {
2918
				$result[] = $thing;
2919
			}
2920
		}
2921
	}
2922
	return $result;
2923
}
2924

    
2925
/* Define what is preferred, IPv4 or IPv6 */
2926
function prefer_ipv4_or_ipv6() {
2927
	global $config;
2928

    
2929
	if (isset($config['system']['prefer_ipv4'])) {
2930
		mwexec("/etc/rc.d/ip6addrctl prefer_ipv4");
2931
	} else {
2932
		mwexec("/etc/rc.d/ip6addrctl prefer_ipv6");
2933
	}
2934
}
2935

    
2936
/* Redirect to page passing parameters via POST */
2937
function post_redirect($page, $params) {
2938
	if (!is_array($params)) {
2939
		return;
2940
	}
2941

    
2942
	print "<html><body><form action=\"{$page}\" name=\"formredir\" method=\"post\">\n";
2943
	foreach ($params as $key => $value) {
2944
		print "<input type=\"hidden\" name=\"{$key}\" value=\"{$value}\" />\n";
2945
	}
2946
	print "</form>\n";
2947
	print "<script type=\"text/javascript\">\n";
2948
	print "//<![CDATA[\n";
2949
	print "document.formredir.submit();\n";
2950
	print "//]]>\n";
2951
	print "</script>\n";
2952
	print "</body></html>\n";
2953
}
2954

    
2955
/* Locate disks that can be queried for S.M.A.R.T. data. */
2956
function get_smart_drive_list() {
2957
	/* SMART supports some disks directly, and some controllers directly,
2958
	 * See https://redmine.pfsense.org/issues/9042 */
2959
	$supported_disk_types = array("ad", "da", "ada");
2960
	$supported_controller_types = array("nvme");
2961
	$disk_list = explode(" ", get_single_sysctl("kern.disks"));
2962
	foreach ($disk_list as $id => $disk) {
2963
		// We only want certain kinds of disks for S.M.A.R.T.
2964
		// 1 is a match, 0 is no match, False is any problem processing the regex
2965
		if (preg_match("/^(" . implode("|", $supported_disk_types) . ").*[0-9]{1,2}$/", $disk) !== 1) {
2966
			unset($disk_list[$id]);
2967
			continue;
2968
		}
2969
	}
2970
	foreach ($supported_controller_types as $controller) {
2971
		$devices = glob("/dev/{$controller}*");
2972
		if (!is_array($devices)) {
2973
			continue;
2974
		}
2975
		foreach ($devices as $device) {
2976
			$disk_list[] = basename($device);
2977
		}
2978
	}
2979
	sort($disk_list);
2980
	return $disk_list;
2981
}
2982

    
2983
// Validate a network address
2984
//	$addr: the address to validate
2985
//	$type: IPV4|IPV6|IPV4V6
2986
//	$label: the label used by the GUI to display this value. Required to compose an error message
2987
//	$err_msg: pointer to the callers error message array so that error messages can be added to it here
2988
//	$alias: are aliases permitted for this address?
2989
// Returns:
2990
//	IPV4 - if $addr is a valid IPv4 address
2991
//	IPV6 - if $addr is a valid IPv6 address
2992
//	ALIAS - if $alias=true and $addr is an alias
2993
//	false - otherwise
2994

    
2995
function validateipaddr(&$addr, $type, $label, &$err_msg, $alias=false) {
2996
	switch ($type) {
2997
		case IPV4:
2998
			if (is_ipaddrv4($addr)) {
2999
				return IPV4;
3000
			} else if ($alias) {
3001
				if (is_alias($addr)) {
3002
					return ALIAS;
3003
				} else {
3004
					$err_msg[] = sprintf(gettext("%s must be a valid IPv4 address or alias."), $label);
3005
					return false;
3006
				}
3007
			} else {
3008
				$err_msg[] = sprintf(gettext("%s must be a valid IPv4 address."), $label);
3009
				return false;
3010
			}
3011
		break;
3012
		case IPV6:
3013
			if (is_ipaddrv6($addr)) {
3014
				$addr = strtolower($addr);
3015
				return IPV6;
3016
			} else if ($alias) {
3017
				if (is_alias($addr)) {
3018
					return ALIAS;
3019
				} else {
3020
					$err_msg[] = sprintf(gettext("%s must be a valid IPv6 address or alias."), $label);
3021
					return false;
3022
				}
3023
			} else {
3024
				$err_msg[] = sprintf(gettext("%s must be a valid IPv6 address."), $label);
3025
				return false;
3026
			}
3027
		break;
3028
		case IPV4V6:
3029
			if (is_ipaddrv6($addr)) {
3030
				$addr = strtolower($addr);
3031
				return IPV6;
3032
			} else if (is_ipaddrv4($addr)) {
3033
				return IPV4;
3034
			} else if ($alias) {
3035
				if (is_alias($addr)) {
3036
					return ALIAS;
3037
				} else {
3038
					$err_msg[] = sprintf(gettext("%s must be a valid IPv4 or IPv6 address or alias."), $label);
3039
					return false;
3040
				}
3041
			} else {
3042
				$err_msg[] = sprintf(gettext("%s must be a valid IPv4 or IPv6 address."), $label);
3043
				return false;
3044
			}
3045
		break;
3046
	}
3047

    
3048
	return false;
3049
}
3050

    
3051
/* From DUID configuration inputs, format a string that looks (more) like the expected raw DUID format:
3052
 * 1) For DUIDs entered as a known DUID type, convert to a hexstring and prepend the DUID number, after having done the following:
3053
 *     a) For DUID-LLT and DUID-EN, convert the time/enterprise ID input to hex and append the link-layer address/identifier input.
3054
 *     b) For DUID-LLT and DUID-LL, prepend a hardware type of 1.
3055
 *     c) For DUID-UUID, remove any "-".
3056
 * 2) Replace any remaining "-" with ":".
3057
 * 3) If any components are input with just a single char (hex digit hopefully), put a "0" in front.
3058
 * 4) The first two components should be a 16-bit integer (little- or big-endian, depending on the current machine type) that
3059
 *    is equal to the number of other components. If not, prepend this as "nn:00" (all pfSense builds are little-endian).
3060
 *    This is convenience, because the DUID reported by dhcp6c in logs does not include this count, which corresponds to the
3061
 *    option-len field of DHCPv6's OPTION_CLIENTID option.
3062
 *
3063
 * The final result should be closer to:
3064
 *
3065
 * "nn:00:00:0n:nn:nn:nn:..."
3066
 *
3067
 * This function does not validate the input. is_duid() will do validation.
3068
 */
3069
function format_duid($duidtype, $duidpt1, $duidpt2=null) {
3070
	if ($duidpt2)
3071
		$duidpt1 = implode(':', str_split(str_pad(dechex($duidpt1), 8, '0', STR_PAD_LEFT), 2)) . ':' . $duidpt2;
3072

    
3073
	/* Make hexstrings */
3074
	if ($duidtype) {
3075
		switch ($duidtype) {
3076
		/* Add a hardware type to DUID-LLT and DUID-LL; assume Ethernet */
3077
		case 1:
3078
		case 3:
3079
			$duidpt1 = '00:01:' . $duidpt1;
3080
			break;
3081
		/* Remove '-' from given UUID and insert ':' every 2 characters */
3082
		case 4:
3083
			$duidpt1 = implode(':', str_split(str_replace('-', '', $duidpt1), 2));
3084
			break;
3085
		default:
3086
		}
3087
		$duidpt1 = '00:0' . $duidtype . ':' . $duidpt1;
3088
	}
3089

    
3090
	$values = explode(':', strtolower(str_replace('-', ':', $duidpt1)));
3091

    
3092
	if (hexdec($values[0]) != count($values) - 2)
3093
		array_unshift($values, dechex(count($values)), '00');
3094

    
3095
	array_walk($values, function(&$value) {
3096
		$value = str_pad($value, 2, '0', STR_PAD_LEFT);
3097
	});
3098

    
3099
	return implode(":", $values);
3100
}
3101

    
3102
/* Returns true if $dhcp6duid is a valid DUID entry.
3103
 * Parse the entry to check for valid length according to known DUID types.
3104
 */
3105
function is_duid($dhcp6duid) {
3106
	$values = explode(":", $dhcp6duid);
3107
	if (hexdec($values[0]) == count($values) - 2) {
3108
		switch (hexdec($values[2] . $values[3])) {
3109
		case 0:
3110
			return false;
3111
			break;
3112
		case 1:
3113
			if (count($values) != 16 || strlen($dhcp6duid) != 47)
3114
				return false;
3115
			break;
3116
		case 3:
3117
			if (count($values) != 12 || strlen($dhcp6duid) != 35)
3118
				return false;
3119
			break;
3120
		case 4:
3121
			if (count($values) != 20 || strlen($dhcp6duid) != 59)
3122
				return false;
3123
			break;
3124
		/* DUID is up to 128 octets; allow 2 octets for type code, 2 more for option-len */
3125
		default:
3126
			if (count($values) > 132 || strlen($dhcp6duid) != count($values) * 3 - 1)
3127
				return false;
3128
		}
3129
	} else
3130
		return false;
3131

    
3132
	for ($i = 0; $i < count($values); $i++) {
3133
		if (ctype_xdigit($values[$i]) == false)
3134
			return false;
3135
		if (hexdec($values[$i]) < 0 || hexdec($values[$i]) > 255)
3136
			return false;
3137
	}
3138

    
3139
	return true;
3140
}
3141

    
3142
/* Write the DHCP6 DUID file */
3143
function write_dhcp6_duid($duidstring) {
3144
	// Create the hex array from the dhcp6duid config entry and write to file
3145
	global $g;
3146

    
3147
	if(!is_duid($duidstring)) {
3148
		log_error(gettext("Error: attempting to write DUID file - Invalid DUID detected"));
3149
		return false;
3150
	}
3151
	$temp = str_replace(":","",$duidstring);
3152
	$duid_binstring = pack("H*",$temp);
3153
	if ($fd = fopen("{$g['vardb_path']}/dhcp6c_duid", "wb")) {
3154
		fwrite($fd, $duid_binstring);
3155
		fclose($fd);
3156
		return true;
3157
	}
3158
	log_error(gettext("Error: attempting to write DUID file - File write error"));
3159
	return false;
3160
}
3161

    
3162
/* returns duid string from 'vardb_path']}/dhcp6c_duid' */
3163
function get_duid_from_file() {
3164
	global $g;
3165

    
3166
	$duid_ASCII = "";
3167
	$count = 0;
3168

    
3169
	if (file_exists("{$g['vardb_path']}/dhcp6c_duid") &&
3170
	    ($fd = fopen("{$g['vardb_path']}/dhcp6c_duid", "r"))) {
3171
		$fsize = filesize("{$g['vardb_path']}/dhcp6c_duid");
3172
		if ($fsize <= 132) {
3173
			$buffer = fread($fd, $fsize);
3174
			while($count < $fsize) {
3175
				$duid_ASCII .= bin2hex($buffer[$count]);
3176
				$count++;
3177
				if($count < $fsize) {
3178
					$duid_ASCII .= ":";
3179
				}
3180
			}
3181
		}
3182
		fclose($fd);
3183
	}
3184
	//if no file or error with read then the string returns blanked DUID string
3185
	if(!is_duid($duid_ASCII)) {
3186
		return "--:--:--:--:--:--:--:--:--:--:--:--:--:--:--:--";
3187
	}
3188
	return($duid_ASCII);
3189
}
3190

    
3191
/* Replaces the Mac OS 9 and earlier (\r) and DOS/Windows (\r\n) newlines with the Unix equivalent (\n). */
3192
function unixnewlines($text) {
3193
	return preg_replace('/\r\n?/', "\n", $text);
3194
}
3195

    
3196
function array_remove_duplicate($array, $field) {
3197
	foreach ($array as $sub) {
3198
		if (isset($sub[$field])) {
3199
			$cmp[] = $sub[$field];
3200
		}
3201
	}
3202
	$unique = array_unique(array_reverse($cmp, true));
3203
	foreach ($unique as $k => $rien) {
3204
		$new[] = $array[$k];
3205
	}
3206
	return $new;
3207
}
3208

    
3209
function dhcpd_date_adjust_gmt($dt) {
3210
	global $config;
3211

    
3212
	init_config_arr(array('dhcpd'));
3213

    
3214
	foreach ($config['dhcpd'] as $dhcpditem) {
3215
		if ($dhcpditem['dhcpleaseinlocaltime'] == "yes") {
3216
			$ts = strtotime($dt . " GMT");
3217
			if ($ts !== false) {
3218
				return strftime("%Y/%m/%d %H:%M:%S", $ts);
3219
			}
3220
		}
3221
	}
3222

    
3223
	/*
3224
	 * If we did not need to convert to local time or the conversion
3225
	 * failed, just return the input.
3226
	 */
3227
	return $dt;
3228
}
3229

    
3230
global $supported_image_types;
3231
$supported_image_types = array(
3232
	IMAGETYPE_JPEG,
3233
	IMAGETYPE_PNG,
3234
	IMAGETYPE_GIF,
3235
	IMAGETYPE_WEBP
3236
);
3237

    
3238
function is_supported_image($image_filename) {
3239
	global $supported_image_types;
3240
	$img_info = getimagesize($image_filename);
3241

    
3242
	/* If it's not an image, or it isn't in the supported list, return false */
3243
	if (($img_info === false) ||
3244
	    !in_array($img_info[2], array_keys($supported_image_types))) {
3245
		return false;
3246
	} else {
3247
		return $img_info[2];
3248
	}
3249
}
3250

    
3251
function get_lagg_ports ($laggport) {
3252
	$laggp = array();
3253
	foreach ($laggport as $lgp) {
3254
		list($lpname, $lpinfo) = explode(" ", $lgp);
3255
		preg_match('~<(.+)>~', $lpinfo, $lgportmode);
3256
		if ($lgportmode[1]) {
3257
			$laggp[] = $lpname . " (" . $lgportmode[1] . ")";
3258
		} else {
3259
			$laggp[] = $lpname;
3260
		}
3261
	}
3262
	if ($laggp) {
3263
		return implode(", ", $laggp);
3264
	} else {
3265
		return false;
3266
	}
3267
}
3268

    
3269
function cisco_to_cidr($addr) {
3270
	if (!is_ipaddr($addr)) {
3271
		throw new Exception('Invalid IP Addr');
3272
	}
3273

    
3274
	$mask = decbin(~ip2long($addr));
3275
	$mask = substr($mask, -32);
3276
	$k = 0;
3277
	for ($i = 0; $i <= 32; $i++) {
3278
		$k += intval($mask[$i]);
3279
	}
3280
	return $k;
3281
}
3282

    
3283
function cisco_extract_index($prule) {
3284
	$index = explode("#", $prule);
3285
	if (is_numeric($index[1])) {
3286
		return intval($index[1]);
3287
	} else {
3288
		syslog(LOG_WARNING, "Error parsing rule {$prule}: Could not extract index");
3289
	}
3290
	return -1;;
3291
}
3292

    
3293
function parse_cisco_acl_rule($rule, $devname, $dir, $proto) {
3294
	$rule_orig = $rule;
3295
	$rule = explode(" ", $rule);
3296
	$tmprule = "";
3297
	$index = 0;
3298

    
3299
	if ($rule[$index] == "permit") {
3300
		$startrule = "pass {$dir} quick on {$devname} ";
3301
	} else if ($rule[$index] == "deny") {
3302
		$startrule = "block {$dir} quick on {$devname} ";
3303
	} else {
3304
		return;
3305
	}
3306

    
3307
	$index++;
3308

    
3309
	switch ($rule[$index]) {
3310
		case "ip":
3311
			break;
3312
		case "icmp":
3313
			$icmp = ($proto == "inet") ? "icmp" : "ipv6-icmp";
3314
			$tmprule .= "proto {$icmp} ";
3315
			break;
3316
		case "tcp":
3317
		case "udp":
3318
			$tmprule .= "proto {$rule[$index]} ";
3319
			break;
3320
		default:
3321
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid protocol.");
3322
			return;
3323
	}
3324
	$index++;
3325

    
3326
	/* Source */
3327
	if (trim($rule[$index]) == "host") {
3328
		$index++;
3329
		if ((is_ipaddrv4(trim($rule[$index])) && ($proto == "inet")) ||
3330
		    (is_ipaddrv6(trim($rule[$index])) && ($proto == "inet6"))) {
3331
			$tmprule .= "from {$rule[$index]} ";
3332
			$index++;
3333
		} else {
3334
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source host '{$rule[$index]}'.");
3335
			return;
3336
		}
3337
	} elseif (is_subnetv6(trim($rule[$index])) && ($proto == "inet6")) {
3338
		$tmprule .= "from {$rule[$index]} ";
3339
		$index++;
3340
	} elseif (trim($rule[$index]) == "any") {
3341
		$tmprule .= "from any ";
3342
		$index++;
3343
	} else {
3344
		$network = $rule[$index];
3345
		$netmask = $rule[++$index];
3346

    
3347
		if (is_ipaddrv4($network) && ($proto == "inet")) {
3348
			try {
3349
				$netmask = cisco_to_cidr($netmask);
3350
			} catch(Exception $e) {
3351
				syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source netmask '$netmask'.");
3352
				return;
3353
			}
3354
			$tmprule .= "from {$network}/{$netmask}";
3355
		} else {
3356
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source network '$network'.");
3357
			return;
3358
		}
3359

    
3360
		$index++;
3361
	}
3362

    
3363
	/* Source Operator */
3364
	if (in_array(trim($rule[$index]), array("lt", "gt", "eq", "neq"))) {
3365
		switch(trim($rule[$index])) {
3366
			case "lt":
3367
				$operator = "<";
3368
				break;
3369
			case "gt":
3370
				$operator = ">";
3371
				break;
3372
			case "eq":
3373
				$operator = "=";
3374
				break;
3375
			case "neq":
3376
				$operator = "!=";
3377
				break;
3378
		}
3379

    
3380
		$port = $rule[++$index];
3381
		if (is_port($port)) {
3382
			$tmprule .= "port {$operator} {$port} ";
3383
		} else {
3384
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source port: '$port' not a numeric value between 0 and 65535.");
3385
			return;
3386
		}
3387
		$index++;
3388
	} else if (trim($rule[$index]) == "range") {
3389
		$port = array($rule[++$index], $rule[++$index]);
3390
		if (is_port($port[0]) && is_port($port[1])) {
3391
			$port[0]--;
3392
			$port[1]++;
3393
			$tmprule .= "port {$port[0]} >< {$port[1]} ";
3394
		} else {
3395
			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.");
3396
			return;
3397
		}
3398
		$index++;
3399
	}
3400

    
3401
	/* Destination */
3402
	if (trim($rule[$index]) == "host") {
3403
		$index++;
3404
		if ((is_ipaddrv4(trim($rule[$index])) && ($proto == "inet")) ||
3405
		    (is_ipaddrv6(trim($rule[$index])) && ($proto == "inet6"))) {
3406
			$tmprule .= "to {$rule[$index]} ";
3407
			$index++;
3408
		} else {
3409
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination host '{$rule[$index]}'.");
3410
			return;
3411
		}
3412
	} elseif (is_subnetv6(trim($rule[$index])) && ($proto == "inet6")) {
3413
		$tmprule .= "to {$rule[$index]} ";
3414
		$index++;
3415
	} elseif (trim($rule[$index]) == "any") {
3416
		$tmprule .= "to any ";
3417
		$index++;
3418
	} else {
3419
		$network = $rule[$index];
3420
		$netmask = $rule[++$index];
3421

    
3422
		if (is_ipaddrv4($network) && ($proto == "inet")) {
3423
			try {
3424
				$netmask = cisco_to_cidr($netmask);
3425
			} catch(Exception $e) {
3426
				syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination network '$network'.");
3427
				return;
3428
			}
3429
			$tmprule .= "to {$network}/{$netmask}";
3430
		} else {
3431
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination network '$network'.");
3432
			return;
3433
		}
3434

    
3435
		$index++;
3436
	}
3437

    
3438
	/* Destination Operator */
3439
	if (in_array(trim($rule[$index]), array("lt", "gt", "eq", "neq"))) {
3440
		switch(trim($rule[$index])) {
3441
			case "lt":
3442
				$operator = "<";
3443
				break;
3444
			case "gt":
3445
				$operator = ">";
3446
				break;
3447
			case "eq":
3448
				$operator = "=";
3449
				break;
3450
			case "neq":
3451
				$operator = "!=";
3452
				break;
3453
		}
3454

    
3455
		$port = $rule[++$index];
3456
		if (is_port($port)) {
3457
			$tmprule .= "port {$operator} {$port} ";
3458
		} else {
3459
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination port: '$port' not a numeric value between 0 and 65535.");
3460
			return;
3461
		}
3462
		$index++;
3463
	} else if (trim($rule[$index]) == "range") {
3464
		$port = array($rule[++$index], $rule[++$index]);
3465
		if (is_port($port[0]) && is_port($port[1])) {
3466
			$port[0]--;
3467
			$port[1]++;
3468
			$tmprule .= "port {$port[0]} >< {$port[1]} ";
3469
		} else {
3470
			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.");
3471
			return;
3472
		}
3473
		$index++;
3474
	}
3475

    
3476
	$tmprule = $startrule . $proto . " " . $tmprule;
3477
	return $tmprule;
3478
}
3479

    
3480
function parse_cisco_acl($attribs, $dev) {
3481
	global $attributes;
3482

    
3483
	if (!is_array($attribs)) {
3484
		return "";
3485
	}
3486
	$finalrules = "";
3487
	if (is_array($attribs['ciscoavpair'])) {
3488
		$inrules = array('inet' => array(), 'inet6' => array());
3489
		$outrules = array('inet' => array(), 'inet6' => array());
3490
		foreach ($attribs['ciscoavpair'] as $avrules) {
3491
			$rule = explode("=", $avrules);
3492
			$dir = "";
3493
			if (strstr($rule[0], "inacl")) {
3494
				$dir = "in";
3495
			} else if (strstr($rule[0], "outacl")) {
3496
				$dir = "out";
3497
			} else if (strstr($rule[0], "dns-servers")) {
3498
				$attributes['dns-servers'] = explode(" ", $rule[1]);
3499
				continue;
3500
			} else if (strstr($rule[0], "route")) {
3501
				if (!is_array($attributes['routes'])) {
3502
					$attributes['routes'] = array();
3503
				}
3504
				$attributes['routes'][] = $rule[1];
3505
				continue;
3506
			}
3507
			$rindex = cisco_extract_index($rule[0]);
3508
			if ($rindex < 0) {
3509
				continue;
3510
			}
3511

    
3512
			if (strstr($rule[0], "ipv6")) {
3513
				$proto = "inet6";
3514
			} else {
3515
				$proto = "inet";
3516
			}
3517

    
3518
			$tmprule = parse_cisco_acl_rule($rule[1], $dev, $dir, $proto);
3519

    
3520
			if ($dir == "in") {
3521
				$inrules[$proto][$rindex] = $tmprule;
3522
			} else if ($dir == "out") {
3523
				$outrules[$proto][$rindex] = $tmprule;
3524
			}
3525
		}
3526

    
3527

    
3528
		$state = "";
3529
		foreach (array('inet', 'inet6') as $ip) {
3530
			if (!empty($outrules[$ip])) {
3531
				$state = "no state";
3532
			}
3533
			ksort($inrules[$ip], SORT_NUMERIC);
3534
			foreach ($inrules[$ip] as $inrule) {
3535
				$finalrules .= "{$inrule} {$state}\n";
3536
			}
3537
			if (!empty($outrules[$ip])) {
3538
				ksort($outrules[$ip], SORT_NUMERIC);
3539
				foreach ($outrules[$ip] as $outrule) {
3540
					$finalrules .= "{$outrule} {$state}\n";
3541
				}
3542
			}
3543
		}
3544
	}
3545
	return $finalrules;
3546
}
3547

    
3548
?>
(53-53/61)