Project

General

Profile

Download (82.3 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
/* returns 4 or 6 respectively (== TRUE) if $ipaddr is a valid IPv4 or IPv6 linklocal address
686
   returns '' if not a valid linklocal address */
687
function is_linklocal($ipaddr) {
688
	if (is_ipaddrv4($ipaddr)) {
689
		// input is IPv4
690
		// test if it's 169.254.x.x per rfc3927 2.1
691
		$ip4 = explode(".", $ipaddr);
692
		if ($ip4[0] == '169' && $ip4[1] == '254') {
693
			return 4;
694
		}
695
	} elseif (Net_IPv6::getAddressType($ipaddr) == NET_IPV6_LOCAL_LINK) {
696
		return 6;
697
	}
698
	return '';
699
}
700

    
701
/* returns scope of a linklocal address */
702
function get_ll_scope($addr) {
703
	if (!is_linklocal($addr) || !strstr($addr, "%")) {
704
		return "";
705
	}
706
	list ($ll, $scope) = explode("%", $addr);
707
	return $scope;
708
}
709

    
710
/* returns true if $ipaddr is a valid literal IPv6 address */
711
function is_literalipaddrv6($ipaddr) {
712
	if (substr($ipaddr,0,1) == '[' && substr($ipaddr,-1,1) == ']') {
713
		// if it's data wrapped in "[ ... ]" then test if middle part is valid IPv6
714
		return is_ipaddrv6(substr($ipaddr,1,-1));
715
	}
716
	return false;
717
}
718

    
719
/* returns true if $iport is a valid IPv4:port or [Literal IPv6]:port
720
	false - not valid
721
	true (numeric 4 or 6) - if valid, gives type of address */
722
function is_ipaddrwithport($ipport) {
723
	$c = strrpos($ipport, ":");
724
	if ($c === false) {
725
		return false;  // can't split at final colon if no colon exists
726
	}
727

    
728
	if (!is_port(substr($ipport, $c + 1))) {
729
		return false;  // no valid port after last colon
730
	}
731

    
732
	$ip = substr($ipport, 0, $c);  // else is text before last colon a valid IP
733
	if (is_literalipaddrv6($ip)) {
734
		return 6;
735
	} elseif (is_ipaddrv4($ip)) {
736
		return 4;
737
	} else {
738
		return false;
739
	}
740
}
741

    
742
function is_hostnamewithport($hostport) {
743
	$parts = explode(":", $hostport);
744
	// no need to validate with is_string(); if it's not a string then explode won't return 2 parts anyway
745
	if (count($parts) == 2) {
746
		return is_hostname($parts[0]) && is_port($parts[1]);
747
	}
748
	return false;
749
}
750

    
751
/* returns true if $ipaddr is a valid dotted IPv4 address or an alias thereof */
752
function is_ipaddroralias($ipaddr) {
753
	global $config;
754

    
755
	if (is_alias($ipaddr)) {
756
		if (is_array($config['aliases']['alias'])) {
757
			foreach ($config['aliases']['alias'] as $alias) {
758
				if ($alias['name'] == $ipaddr && !preg_match("/port/i", $alias['type'])) {
759
					return true;
760
				}
761
			}
762
		}
763
		return false;
764
	} else {
765
		return is_ipaddr($ipaddr);
766
	}
767

    
768
}
769

    
770
/* returns true if $subnet is a valid IPv4 or IPv6 subnet in CIDR format
771
	false - if not a valid subnet
772
	true (numeric 4 or 6) - if valid, gives type of subnet */
773
function is_subnet($subnet) {
774
	if (is_string($subnet) && preg_match('/^(?:([0-9.]{7,15})|([0-9a-f:]{2,39}))\/(\d{1,3})$/i', $subnet, $parts)) {
775
		if (is_ipaddrv4($parts[1]) && $parts[3] <= 32) {
776
			return 4;
777
		}
778
		if (is_ipaddrv6($parts[2]) && $parts[3] <= 128) {
779
			return 6;
780
		}
781
	}
782
	return false;
783
}
784

    
785
function is_v4($ip_or_subnet) {
786
	return is_ipaddrv4($ip_or_subnet) || is_subnetv4($ip_or_subnet);
787
}
788

    
789
function is_v6($ip_or_subnet) {
790
	return is_ipaddrv6($ip_or_subnet) || is_subnetv6($ip_or_subnet);
791
}
792

    
793
/* same as is_subnet() but accepts IPv4 only */
794
function is_subnetv4($subnet) {
795
	return (is_subnet($subnet) == 4);
796
}
797

    
798
/* same as is_subnet() but accepts IPv6 only */
799
function is_subnetv6($subnet) {
800
	return (is_subnet($subnet) == 6);
801
}
802

    
803
/* returns true if $subnet is a valid subnet in CIDR format or an alias thereof */
804
function is_subnetoralias($subnet) {
805
	global $aliastable;
806

    
807
	if (isset($aliastable[$subnet]) && is_subnet($aliastable[$subnet])) {
808
		return true;
809
	} else {
810
		return is_subnet($subnet);
811
	}
812
}
813

    
814
/* Get number of addresses in an IPv4/IPv6 subnet (represented as a string)
815
   optional $exact=true forces error (0) to be returned if it can't be represented exactly
816
   Exact result not possible above PHP_MAX_INT which is about 2^31 addresses on x32 or 2^63 on x64
817
   Returns 0 for bad data or if cannot represent size as an INT when $exact is set. */
818
function subnet_size($subnet, $exact=false) {
819
	$parts = explode("/", $subnet);
820
	$iptype = is_ipaddr($parts[0]);
821
	if (count($parts) == 2 && $iptype) {
822
		return subnet_size_by_netmask($iptype, $parts[1], $exact);
823
	}
824
	return 0;
825
}
826

    
827
/* Get number of addresses in an IPv4/IPv6 subnet (represented numerically as IP type + bits)
828
   optional $exact=true forces error (0) to be returned if it can't be represented exactly
829
   Hard to think where we might need to count exactly a huge subnet but an overflow detection option is probably sensible
830
   Returns 0 for bad data or if cannot represent size as an INT when $exact is set. */
831
function subnet_size_by_netmask($iptype, $bits, $exact=false) {
832
	if (!is_numericint($bits)) {
833
		return 0;
834
	} elseif ($iptype == 4 && $bits <= 32) {
835
		$snsize = 32 - $bits;
836
	} elseif ($iptype == 6 && $bits <= 128) {
837
		$snsize = 128 - $bits;
838
	} else {
839
		return 0;
840
	}
841

    
842
	// 2**N returns an exact result as an INT if possible, and a float/double if not.
843
	// 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
844
	$result = 2 ** $snsize;
845

    
846
	if ($exact && !is_int($result)) {
847
		//exact required but can't represent result exactly as an INT
848
		return 0;
849
	} else {
850
		// result ok, will be an INT where possible (guaranteed up to 2^31 addresses on x32/x64) and a float for 'huge' subnets
851
		return $result;
852
	}
853
}
854

    
855
/* function used by pfblockerng */
856
function subnetv4_expand($subnet) {
857
	$result = array();
858
	list ($ip, $bits) = explode("/", $subnet);
859
	$net = ip2long($ip);
860
	$mask = (0xffffffff << (32 - $bits));
861
	$net &= $mask;
862
	$size = round(exp(log(2) * (32 - $bits)));
863
	for ($i = 0; $i < $size; $i += 1) {
864
		$result[] = long2ip($net | $i);
865
	}
866
	return $result;
867
}
868

    
869
/* find out whether two IPv4/IPv6 CIDR subnets overlap.
870
   Note: CIDR overlap implies one is identical or included so largest sn will be the same */
871
function check_subnets_overlap($subnet1, $bits1, $subnet2, $bits2) {
872
	if (is_ipaddrv4($subnet1)) {
873
		return check_subnetsv4_overlap($subnet1, $bits1, $subnet2, $bits2);
874
	} else {
875
		return check_subnetsv6_overlap($subnet1, $bits1, $subnet2, $bits2);
876
	}
877
}
878

    
879
/* find out whether two IPv4 CIDR subnets overlap.
880
   Note: CIDR overlap means sn1/sn2 are identical or one is included in other. So sn using largest $bits will be the same  */
881
function check_subnetsv4_overlap($subnet1, $bits1, $subnet2, $bits2) {
882
	$largest_sn = min($bits1, $bits2);
883
	$subnetv4_start1 = gen_subnetv4($subnet1, $largest_sn);
884
	$subnetv4_start2 = gen_subnetv4($subnet2, $largest_sn);
885

    
886
	if ($subnetv4_start1 == '' || $subnetv4_start2 == '') {
887
		// One or both args is not a valid IPv4 subnet
888
		//FIXME: needs to return "bad data" not true/false if bad. For now return false, best we can do until fixed
889
		return false;
890
	}
891
	return ($subnetv4_start1 == $subnetv4_start2);
892
}
893

    
894
/* find out whether two IPv6 CIDR subnets overlap.
895
   Note: CIDR overlap means sn1/sn2 are identical or one is included in other. So sn using largest $bits will be the same  */
896
function check_subnetsv6_overlap($subnet1, $bits1, $subnet2, $bits2) {
897
	$largest_sn = min($bits1, $bits2);
898
	$subnetv6_start1 = gen_subnetv6($subnet1, $largest_sn);
899
	$subnetv6_start2 = gen_subnetv6($subnet2, $largest_sn);
900

    
901
	if ($subnetv6_start1 == '' || $subnetv6_start2 == '') {
902
		// One or both args is not a valid IPv6 subnet
903
		//FIXME: needs to return "bad data" not true/false if bad. For now return false, best we can do until fixed
904
		return false;
905
	}
906
	return ($subnetv6_start1 == $subnetv6_start2);
907
}
908

    
909
/* return all PTR zones for a IPv6 network */
910
function get_v6_ptr_zones($subnet, $bits) {
911
	$result = array();
912

    
913
	if (!is_ipaddrv6($subnet)) {
914
		return $result;
915
	}
916

    
917
	if (!is_numericint($bits) || $bits > 128) {
918
		return $result;
919
	}
920

    
921
	/*
922
	 * Find a small nibble boundary subnet mask
923
	 * e.g. a /29 will create 8 /32 PTR zones
924
	 */
925
	$small_sn = $bits;
926
	while ($small_sn % 4 != 0) {
927
		$small_sn++;
928
	}
929

    
930
	/* Get network prefix */
931
	$small_subnet = Net_IPv6::getNetmask($subnet, $bits);
932

    
933
	/*
934
	 * While small network is part of bigger one, increase 4-bit in last
935
	 * digit to get next small network
936
	 */
937
	while (Net_IPv6::isInNetmask($small_subnet, $subnet, $bits)) {
938
		/* Get a pure hex value */
939
		$unpacked = unpack('H*hex', inet_pton($small_subnet));
940
		/* Create PTR record using $small_sn / 4 chars */
941
		$result[] = implode('.', array_reverse(str_split(substr(
942
		    $unpacked['hex'], 0, $small_sn / 4)))).'.ip6.arpa';
943

    
944
		/* Detect what part of IP should be increased */
945
		$change_part = (int) ($small_sn / 16);
946
		if ($small_sn % 16 == 0) {
947
			$change_part--;
948
		}
949

    
950
		/* Increase 1 to desired part */
951
		$parts = explode(":", Net_IPv6::uncompress($small_subnet));
952
		$parts[$change_part]++;
953
		$small_subnet = implode(":", $parts);
954
	}
955

    
956
	return $result;
957
}
958

    
959
/* return true if $addr is in $subnet, false if not */
960
function ip_in_subnet($addr, $subnet) {
961
	if (is_ipaddrv6($addr) && is_subnetv6($subnet)) {
962
		return (Net_IPv6::isInNetmask($addr, $subnet));
963
	} else if (is_ipaddrv4($addr) && is_subnetv4($subnet)) {
964
		list($ip, $mask) = explode('/', $subnet);
965
		$mask = (0xffffffff << (32 - $mask)) & 0xffffffff;
966
		return ((ip2long($addr) & $mask) == (ip2long($ip) & $mask));
967
	}
968
	return false;
969
}
970

    
971
/* returns true if $hostname is just a valid hostname (top part without any of the domain part) */
972
function is_unqualified_hostname($hostname) {
973
	if (!is_string($hostname)) {
974
		return false;
975
	}
976

    
977
	if (preg_match('/^(?:[a-z0-9_]|[a-z0-9_][a-z0-9_\-]*[a-z0-9_])$/i', $hostname)) {
978
		return true;
979
	} else {
980
		return false;
981
	}
982
}
983

    
984
/* returns true if $hostname is a valid hostname, with or without being a fully-qualified domain name. */
985
function is_hostname($hostname, $allow_wildcard=false) {
986
	if (!is_string($hostname)) {
987
		return false;
988
	}
989

    
990
	if (is_domain($hostname, $allow_wildcard)) {
991
		if ((substr_count($hostname, ".") == 1) && ($hostname[strlen($hostname)-1] == ".")) {
992
			/* Only a single dot at the end like "test." - hosts cannot be directly in the root domain. */
993
			return false;
994
		} else {
995
			return true;
996
		}
997
	} else {
998
		return false;
999
	}
1000
}
1001

    
1002
/* returns true if $domain is a valid domain name */
1003
function is_domain($domain, $allow_wildcard=false, $trailing_dot=true) {
1004
	if (!is_string($domain)) {
1005
		return false;
1006
	}
1007
	if (!$trailing_dot && ($domain[strlen($domain)-1] == ".")) {
1008
		return false;
1009
	}
1010
	if ($allow_wildcard) {
1011
		$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';
1012
	} else {
1013
		$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';
1014
	}
1015

    
1016
	if (preg_match($domain_regex, $domain)) {
1017
		return true;
1018
	} else {
1019
		return false;
1020
	}
1021
}
1022

    
1023
/* returns true if $macaddr is a valid MAC address */
1024
function is_macaddr($macaddr, $partial=false) {
1025
	$values = explode(":", $macaddr);
1026

    
1027
	/* Verify if the MAC address has a proper amount of parts for either a partial or full match. */
1028
	if ($partial) {
1029
		if ((count($values) < 1) || (count($values) > 6)) {
1030
			return false;
1031
		}
1032
	} elseif (count($values) != 6) {
1033
		return false;
1034
	}
1035
	for ($i = 0; $i < count($values); $i++) {
1036
		if (ctype_xdigit($values[$i]) == false)
1037
			return false;
1038
		if (hexdec($values[$i]) < 0 || hexdec($values[$i]) > 255)
1039
			return false;
1040
	}
1041

    
1042
	return true;
1043
}
1044

    
1045
/*
1046
	If $return_message is true then
1047
		returns a text message about the reason that the name is invalid.
1048
		the text includes the type of "thing" that is being checked, passed in $object. (e.g. "alias", "gateway group", "schedule")
1049
	else
1050
		returns true if $name is a valid name for an alias
1051
		returns false if $name is not a valid name for an alias
1052

    
1053
	Aliases cannot be:
1054
		bad chars: anything except a-z 0-9 and underscore
1055
		bad names: empty string, pure numeric, pure underscore
1056
		reserved words: pre-defined service/protocol/port names which should not be ambiguous, and the words "port" and  "pass" */
1057

    
1058
function is_validaliasname($name, $return_message = false, $object = "alias") {
1059
	/* Array of reserved words */
1060
	$reserved = array("port", "pass");
1061

    
1062
	if (!is_string($name) || strlen($name) >= 32 || preg_match('/(^_*$|^\d*$|[^a-z0-9_])/i', $name)) {
1063
		if ($return_message) {
1064
			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, _');
1065
		} else {
1066
			return false;
1067
		}
1068
	}
1069
	if (in_array($name, $reserved, true)) {
1070
		if ($return_message) {
1071
			return sprintf(gettext('The %1$s name must not be either of the reserved words %2$s or %3$s.'), $object, "'port'", "'pass'");
1072
		} else {
1073
			return false;
1074
		}
1075
	}
1076
	if (getprotobyname($name)) {
1077
		if ($return_message) {
1078
			return sprintf(gettext('The %1$s name must not be an IP protocol name such as TCP, UDP, ICMP etc.'), $object);
1079
		} else {
1080
			return false;
1081
		}
1082
	}
1083
	if (getservbyname($name, "tcp") || getservbyname($name, "udp")) {
1084
		if ($return_message) {
1085
			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);
1086
		} else {
1087
			return false;
1088
		}
1089
	}
1090
	if ($return_message) {
1091
		return sprintf(gettext("The %1$s name is valid."), $object);
1092
	} else {
1093
		return true;
1094
	}
1095
}
1096

    
1097
/* returns a text message indicating if the alias name is valid, or the reason it is not valid. */
1098
function invalidaliasnamemsg($name, $object = "alias") {
1099
	return is_validaliasname($name, true, $object);
1100
}
1101

    
1102
/*
1103
 * returns true if $range is a valid integer range between $min and $max
1104
 * range delimiter can be ':' or '-'
1105
 */
1106
function is_intrange($range, $min, $max) {
1107
	$values = preg_split("/[:-]/", $range);
1108

    
1109
	if (!is_array($values) || count($values) != 2) {
1110
		return false;
1111
	}
1112

    
1113
	if (!ctype_digit($values[0]) || !ctype_digit($values[1])) {
1114
		return false;
1115
	}
1116

    
1117
	$values[0] = intval($values[0]);
1118
	$values[1] = intval($values[1]);
1119

    
1120
	if ($values[0] >= $values[1]) {
1121
		return false;
1122
	}
1123

    
1124
	if ($values[0] < $min || $values[1] > $max) {
1125
		return false;
1126
	}
1127

    
1128
	return true;
1129
}
1130

    
1131
/* returns true if $port is a valid TCP/UDP port */
1132
function is_port($port) {
1133
	if (ctype_digit($port) && ((intval($port) >= 1) && (intval($port) <= 65535))) {
1134
		return true;
1135
	}
1136
	if (getservbyname($port, "tcp") || getservbyname($port, "udp")) {
1137
		return true;
1138
	}
1139
	return false;
1140
}
1141

    
1142
/* returns true if $port is in use */
1143
function is_port_in_use($port, $proto = "tcp", $ip_version = 4) {
1144
	$port_info = array();
1145
	exec("/usr/bin/netstat --libxo json -an " . escapeshellarg('-' . $ip_version) . " -p " . escapeshellarg($proto), $rawdata, $rc);
1146
	if ($rc == 0) {
1147
		$netstatarr = json_decode(implode(" ", $rawdata), JSON_OBJECT_AS_ARRAY);
1148
		$netstatarr = $netstatarr['statistics']['socket'];
1149

    
1150
		foreach($netstatarr as $index => $portstats){
1151
			array_push($port_info, $portstats['local']['port']);
1152
		}
1153
	}
1154

    
1155
	return in_array($port, $port_info);
1156
}
1157

    
1158
/* returns true if $portrange is a valid TCP/UDP portrange ("<port>:<port>") */
1159
function is_portrange($portrange) {
1160
	$ports = explode(":", $portrange);
1161

    
1162
	return (count($ports) == 2 && is_port($ports[0]) && is_port($ports[1]));
1163
}
1164

    
1165
/* returns true if $port is a valid TCP/UDP port number or range ("<port>:<port>") */
1166
function is_port_or_range($port) {
1167
	return (is_port($port) || is_portrange($port));
1168
}
1169

    
1170
/* returns true if $port is an alias that is a port type */
1171
function is_portalias($port) {
1172
	global $config;
1173

    
1174
	if (is_alias($port)) {
1175
		if (is_array($config['aliases']['alias'])) {
1176
			foreach ($config['aliases']['alias'] as $alias) {
1177
				if ($alias['name'] == $port && preg_match("/port/i", $alias['type'])) {
1178
					return true;
1179
				}
1180
			}
1181
		}
1182
	}
1183
	return false;
1184
}
1185

    
1186
/* returns true if $port is a valid port number or an alias thereof */
1187
function is_port_or_alias($port) {
1188
	return (is_port($port) || is_portalias($port));
1189
}
1190

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

    
1196
/* create ranges of sequential port numbers (200:215) and remove duplicates */
1197
function group_ports($ports, $kflc = false) {
1198
	if (!is_array($ports) || empty($ports)) {
1199
		return;
1200
	}
1201

    
1202
	$uniq = array();
1203
	$comments = array();
1204
	foreach ($ports as $port) {
1205
		if (($kflc) && (strpos($port, '#') === 0)) {	// Keep Full Line Comments (lines beginning with #).
1206
			$comments[] = $port;
1207
		} else if (is_portrange($port)) {
1208
			list($begin, $end) = explode(":", $port);
1209
			if ($begin > $end) {
1210
				$aux = $begin;
1211
				$begin = $end;
1212
				$end = $aux;
1213
			}
1214
			for ($i = $begin; $i <= $end; $i++) {
1215
				if (!in_array($i, $uniq)) {
1216
					$uniq[] = $i;
1217
				}
1218
			}
1219
		} else if (is_port($port)) {
1220
			if (!in_array($port, $uniq)) {
1221
				$uniq[] = $port;
1222
			}
1223
		}
1224
	}
1225
	sort($uniq, SORT_NUMERIC);
1226

    
1227
	$result = array();
1228
	foreach ($uniq as $idx => $port) {
1229
		if ($idx == 0) {
1230
			$result[] = $port;
1231
			continue;
1232
		}
1233

    
1234
		$last = end($result);
1235
		if (is_portrange($last)) {
1236
			list($begin, $end) = explode(":", $last);
1237
		} else {
1238
			$begin = $end = $last;
1239
		}
1240

    
1241
		if ($port == ($end+1)) {
1242
			$end++;
1243
			$result[count($result)-1] = "{$begin}:{$end}";
1244
		} else {
1245
			$result[] = $port;
1246
		}
1247
	}
1248

    
1249
	return array_merge($comments, $result);
1250
}
1251

    
1252
/* returns true if $val is a valid shaper bandwidth value */
1253
function is_valid_shaperbw($val) {
1254
	return (preg_match("/^(\d+(?:\.\d+)?)([MKG]?b|%)$/", $val));
1255
}
1256

    
1257
/* returns true if $test is in the range between $start and $end */
1258
function is_inrange_v4($test, $start, $end) {
1259
	if (!is_ipaddrv4($test) || !is_ipaddrv4($start) || !is_ipaddrv4($end)) {
1260
		return false;
1261
	}
1262

    
1263
	if (ip2ulong($test) <= ip2ulong($end) &&
1264
	    ip2ulong($test) >= ip2ulong($start)) {
1265
		return true;
1266
	}
1267

    
1268
	return false;
1269
}
1270

    
1271
/* returns true if $test is in the range between $start and $end */
1272
function is_inrange_v6($test, $start, $end) {
1273
	if (!is_ipaddrv6($test) || !is_ipaddrv6($start) || !is_ipaddrv6($end)) {
1274
		return false;
1275
	}
1276

    
1277
	if (inet_pton($test) <= inet_pton($end) &&
1278
	    inet_pton($test) >= inet_pton($start)) {
1279
		return true;
1280
	}
1281

    
1282
	return false;
1283
}
1284

    
1285
/* returns true if $test is in the range between $start and $end */
1286
function is_inrange($test, $start, $end) {
1287
	return is_ipaddrv6($test) ? is_inrange_v6($test, $start, $end) : is_inrange_v4($test, $start, $end);
1288
}
1289

    
1290
function get_configured_vip_list($family = 'all', $type = VIP_ALL) {
1291
	global $config;
1292

    
1293
	$list = array();
1294
	if (!is_array($config['virtualip']) ||
1295
	    !is_array($config['virtualip']['vip']) ||
1296
	    empty($config['virtualip']['vip'])) {
1297
		return ($list);
1298
	}
1299

    
1300
	$viparr = &$config['virtualip']['vip'];
1301
	foreach ($viparr as $vip) {
1302

    
1303
		if ($type == VIP_CARP) {
1304
			if ($vip['mode'] != "carp")
1305
				continue;
1306
		} elseif ($type == VIP_IPALIAS) {
1307
			if ($vip['mode'] != "ipalias")
1308
				continue;
1309
		} else {
1310
			if ($vip['mode'] != "carp" && $vip['mode'] != "ipalias")
1311
				continue;
1312
		}
1313

    
1314
		if ($family == 'all' ||
1315
		    ($family == 'inet' && is_ipaddrv4($vip['subnet'])) ||
1316
		    ($family == 'inet6' && is_ipaddrv6($vip['subnet']))) {
1317
			$list["_vip{$vip['uniqid']}"] = $vip['subnet'];
1318
		}
1319
	}
1320
	return ($list);
1321
}
1322

    
1323
function get_configured_vip($vipinterface = '') {
1324

    
1325
	return (get_configured_vip_detail($vipinterface, 'all', 'vip'));
1326
}
1327

    
1328
function get_configured_vip_interface($vipinterface = '') {
1329

    
1330
	return (get_configured_vip_detail($vipinterface, 'all', 'iface'));
1331
}
1332

    
1333
function get_configured_vip_ipv4($vipinterface = '') {
1334

    
1335
	return (get_configured_vip_detail($vipinterface, 'inet', 'ip'));
1336
}
1337

    
1338
function get_configured_vip_ipv6($vipinterface = '') {
1339

    
1340
	return (get_configured_vip_detail($vipinterface, 'inet6', 'ip'));
1341
}
1342

    
1343
function get_configured_vip_subnetv4($vipinterface = '') {
1344

    
1345
	return (get_configured_vip_detail($vipinterface, 'inet', 'subnet'));
1346
}
1347

    
1348
function get_configured_vip_subnetv6($vipinterface = '') {
1349

    
1350
	return (get_configured_vip_detail($vipinterface, 'inet6', 'subnet'));
1351
}
1352

    
1353
function get_configured_vip_detail($vipinterface = '', $family = 'inet', $what = 'ip') {
1354
	global $config;
1355

    
1356
	if (empty($vipinterface) ||
1357
	    !is_array($config['virtualip']) ||
1358
	    !is_array($config['virtualip']['vip']) ||
1359
	    empty($config['virtualip']['vip'])) {
1360
		return (NULL);
1361
	}
1362

    
1363
	$viparr = &$config['virtualip']['vip'];
1364
	foreach ($viparr as $vip) {
1365
		if ($vip['mode'] != "carp" && $vip['mode'] != "ipalias") {
1366
			continue;
1367
		}
1368

    
1369
		if ($vipinterface != "_vip{$vip['uniqid']}") {
1370
			continue;
1371
		}
1372

    
1373
		switch ($what) {
1374
			case 'subnet':
1375
				if ($family == 'inet' && is_ipaddrv4($vip['subnet']))
1376
					return ($vip['subnet_bits']);
1377
				else if ($family == 'inet6' && is_ipaddrv6($vip['subnet']))
1378
					return ($vip['subnet_bits']);
1379
				break;
1380
			case 'iface':
1381
				return ($vip['interface']);
1382
				break;
1383
			case 'vip':
1384
				return ($vip);
1385
				break;
1386
			case 'ip':
1387
			default:
1388
				if ($family == 'inet' && is_ipaddrv4($vip['subnet'])) {
1389
					return ($vip['subnet']);
1390
				} else if ($family == 'inet6' && is_ipaddrv6($vip['subnet'])) {
1391
					return ($vip['subnet']);
1392
				}
1393
				break;
1394
		}
1395
		break;
1396
	}
1397

    
1398
	return (NULL);
1399
}
1400

    
1401
/* comparison function for sorting by the order in which interfaces are normally created */
1402
function compare_interface_friendly_names($a, $b) {
1403
	if ($a == $b) {
1404
		return 0;
1405
	} else if ($a == 'wan') {
1406
		return -1;
1407
	} else if ($b == 'wan') {
1408
		return 1;
1409
	} else if ($a == 'lan') {
1410
		return -1;
1411
	} else if ($b == 'lan') {
1412
		return 1;
1413
	}
1414

    
1415
	return strnatcmp($a, $b);
1416
}
1417

    
1418
/* return the configured interfaces list. */
1419
function get_configured_interface_list($withdisabled = false) {
1420
	global $config;
1421

    
1422
	$iflist = array();
1423

    
1424
	/* if list */
1425
	foreach ($config['interfaces'] as $if => $ifdetail) {
1426
		if (isset($ifdetail['enable']) || $withdisabled == true) {
1427
			$iflist[$if] = $if;
1428
		}
1429
	}
1430

    
1431
	return $iflist;
1432
}
1433

    
1434
/* return the configured interfaces list. */
1435
function get_configured_interface_list_by_realif($withdisabled = false) {
1436
	global $config;
1437

    
1438
	$iflist = array();
1439

    
1440
	/* if list */
1441
	foreach ($config['interfaces'] as $if => $ifdetail) {
1442
		if (isset($ifdetail['enable']) || $withdisabled == true) {
1443
			$tmpif = get_real_interface($if);
1444
			if (!empty($tmpif)) {
1445
				$iflist[$tmpif] = $if;
1446
			}
1447
		}
1448
	}
1449

    
1450
	return $iflist;
1451
}
1452

    
1453
/* return the configured interfaces list with their description. */
1454
function get_configured_interface_with_descr($withdisabled = false) {
1455
	global $config, $user_settings;
1456

    
1457
	$iflist = array();
1458

    
1459
	/* if list */
1460
	foreach ($config['interfaces'] as $if => $ifdetail) {
1461
		if (isset($ifdetail['enable']) || $withdisabled == true) {
1462
			if (empty($ifdetail['descr'])) {
1463
				$iflist[$if] = strtoupper($if);
1464
			} else {
1465
				$iflist[$if] = strtoupper($ifdetail['descr']);
1466
			}
1467
		}
1468
	}
1469

    
1470
	if ($user_settings['webgui']['interfacessort']) {
1471
		asort($iflist);
1472
	}
1473

    
1474
	return $iflist;
1475
}
1476

    
1477
/*
1478
 *   get_configured_ip_addresses() - Return a list of all configured
1479
 *   IPv4 addresses.
1480
 *
1481
 */
1482
function get_configured_ip_addresses() {
1483
	global $config;
1484

    
1485
	if (!function_exists('get_interface_ip')) {
1486
		require_once("interfaces.inc");
1487
	}
1488
	$ip_array = array();
1489
	$interfaces = get_configured_interface_list();
1490
	if (is_array($interfaces)) {
1491
		foreach ($interfaces as $int) {
1492
			$ipaddr = get_interface_ip($int);
1493
			$ip_array[$int] = $ipaddr;
1494
		}
1495
	}
1496
	$interfaces = get_configured_vip_list('inet');
1497
	if (is_array($interfaces)) {
1498
		foreach ($interfaces as $int => $ipaddr) {
1499
			$ip_array[$int] = $ipaddr;
1500
		}
1501
	}
1502

    
1503
	/* pppoe server */
1504
	if (is_array($config['pppoes']) && is_array($config['pppoes']['pppoe'])) {
1505
		foreach ($config['pppoes']['pppoe'] as $pppoe) {
1506
			if ($pppoe['mode'] == "server") {
1507
				if (is_ipaddr($pppoe['localip'])) {
1508
					$int = "pppoes". $pppoe['pppoeid'];
1509
					$ip_array[$int] = $pppoe['localip'];
1510
				}
1511
			}
1512
		}
1513
	}
1514

    
1515
	return $ip_array;
1516
}
1517

    
1518
/*
1519
 *   get_configured_ipv6_addresses() - Return a list of all configured
1520
 *   IPv6 addresses.
1521
 *
1522
 */
1523
function get_configured_ipv6_addresses($linklocal_fallback = false) {
1524
	require_once("interfaces.inc");
1525
	$ipv6_array = array();
1526
	$interfaces = get_configured_interface_list();
1527
	if (is_array($interfaces)) {
1528
		foreach ($interfaces as $int) {
1529
			$ipaddrv6 = text_to_compressed_ip6(get_interface_ipv6($int, false, $linklocal_fallback));
1530
			$ipv6_array[$int] = $ipaddrv6;
1531
		}
1532
	}
1533
	$interfaces = get_configured_vip_list('inet6');
1534
	if (is_array($interfaces)) {
1535
		foreach ($interfaces as $int => $ipaddrv6) {
1536
			$ipv6_array[$int] = text_to_compressed_ip6($ipaddrv6);
1537
		}
1538
	}
1539
	return $ipv6_array;
1540
}
1541

    
1542
/*
1543
 *   get_interface_list() - Return a list of all physical interfaces
1544
 *   along with MAC and status.
1545
 *
1546
 *   $mode = "active" - use ifconfig -lu
1547
 *           "media"  - use ifconfig to check physical connection
1548
 *			status (much slower)
1549
 */
1550
function get_interface_list($mode = "active", $keyby = "physical", $vfaces = "") {
1551
	global $config;
1552
	$upints = array();
1553
	/* get a list of virtual interface types */
1554
	if (!$vfaces) {
1555
		$vfaces = array(
1556
				'bridge',
1557
				'ppp',
1558
				'pppoe',
1559
				'pptp',
1560
				'l2tp',
1561
				'sl',
1562
				'gif',
1563
				'gre',
1564
				'faith',
1565
				'lo',
1566
				'ng',
1567
				'_vlan',
1568
				'_wlan',
1569
				'pflog',
1570
				'plip',
1571
				'pfsync',
1572
				'enc',
1573
				'tun',
1574
				'lagg',
1575
				'vip',
1576
				'ipfw'
1577
		);
1578
	}
1579
	switch ($mode) {
1580
		case "active":
1581
			$upints = pfSense_interface_listget(IFF_UP);
1582
			break;
1583
		case "media":
1584
			$intlist = pfSense_interface_listget();
1585
			$ifconfig = "";
1586
			exec("/sbin/ifconfig -a", $ifconfig);
1587
			$regexp = '/(' . implode('|', $intlist) . '):\s/';
1588
			$ifstatus = preg_grep('/status:/', $ifconfig);
1589
			foreach ($ifstatus as $status) {
1590
				$int = array_shift($intlist);
1591
				if (stristr($status, "active")) {
1592
					$upints[] = $int;
1593
				}
1594
			}
1595
			break;
1596
		default:
1597
			$upints = pfSense_interface_listget();
1598
			break;
1599
	}
1600
	/* build interface list with netstat */
1601
	$linkinfo = "";
1602
	exec("/usr/bin/netstat -inW -f link | awk '{ print $1, $4 }'", $linkinfo);
1603
	array_shift($linkinfo);
1604
	/* build ip address list with netstat */
1605
	$ipinfo = "";
1606
	exec("/usr/bin/netstat -inW -f inet | awk '{ print $1, $4 }'", $ipinfo);
1607
	array_shift($ipinfo);
1608
	foreach ($linkinfo as $link) {
1609
		$friendly = "";
1610
		$alink = explode(" ", $link);
1611
		$ifname = rtrim(trim($alink[0]), '*');
1612
		/* trim out all numbers before checking for vfaces */
1613
		if (!in_array(array_shift(preg_split('/\d/', $ifname)), $vfaces) &&
1614
		    interface_is_vlan($ifname) == NULL &&
1615
		    interface_is_qinq($ifname) == NULL &&
1616
		    !stristr($ifname, "_wlan")) {
1617
			$toput = array(
1618
					"mac" => trim($alink[1]),
1619
					"up" => in_array($ifname, $upints)
1620
				);
1621
			foreach ($ipinfo as $ip) {
1622
				$aip = explode(" ", $ip);
1623
				if ($aip[0] == $ifname) {
1624
					$toput['ipaddr'] = $aip[1];
1625
				}
1626
			}
1627
			if (is_array($config['interfaces'])) {
1628
				foreach ($config['interfaces'] as $name => $int) {
1629
					if ($int['if'] == $ifname) {
1630
						$friendly = $name;
1631
					}
1632
				}
1633
			}
1634
			switch ($keyby) {
1635
			case "physical":
1636
				if ($friendly != "") {
1637
					$toput['friendly'] = $friendly;
1638
				}
1639
				$dmesg_arr = array();
1640
				exec("/sbin/dmesg |grep $ifname | head -n1", $dmesg_arr);
1641
				preg_match_all("/<(.*?)>/i", $dmesg_arr[0], $dmesg);
1642
				$toput['dmesg'] = $dmesg[1][0];
1643
				$iflist[$ifname] = $toput;
1644
				break;
1645
			case "ppp":
1646

    
1647
			case "friendly":
1648
				if ($friendly != "") {
1649
					$toput['if'] = $ifname;
1650
					$iflist[$friendly] = $toput;
1651
				}
1652
				break;
1653
			}
1654
		}
1655
	}
1656
	return $iflist;
1657
}
1658

    
1659
function get_lagg_interface_list() {
1660
	global $config;
1661

    
1662
	$plist = array();
1663
	if (isset($config['laggs']['lagg']) && is_array($config['laggs']['lagg'])) {
1664
		foreach ($config['laggs']['lagg'] as $lagg) {
1665
			$lagg['mac'] = get_interface_mac($lagg['laggif']);
1666
			$lagg['islagg'] = true;
1667
			$plist[$lagg['laggif']] = $lagg;
1668
		}
1669
	}
1670

    
1671
	return ($plist);
1672
}
1673

    
1674
/****f* util/log_error
1675
* NAME
1676
*   log_error  - Sends a string to syslog.
1677
* INPUTS
1678
*   $error     - string containing the syslog message.
1679
* RESULT
1680
*   null
1681
******/
1682
function log_error($error) {
1683
	global $g;
1684
	$page = $_SERVER['SCRIPT_NAME'];
1685
	if (empty($page)) {
1686
		$files = get_included_files();
1687
		$page = basename($files[0]);
1688
	}
1689
	syslog(LOG_ERR, "$page: $error");
1690
	if ($g['debug']) {
1691
		syslog(LOG_WARNING, var_dump(debug_backtrace()));
1692
	}
1693
	return;
1694
}
1695

    
1696
/****f* util/log_auth
1697
* NAME
1698
*   log_auth   - Sends a string to syslog as LOG_AUTH facility
1699
* INPUTS
1700
*   $error     - string containing the syslog message.
1701
* RESULT
1702
*   null
1703
******/
1704
function log_auth($error) {
1705
	global $g;
1706
	$page = $_SERVER['SCRIPT_NAME'];
1707
	syslog(LOG_AUTH, "$page: $error");
1708
	if ($g['debug']) {
1709
		syslog(LOG_WARNING, var_dump(debug_backtrace()));
1710
	}
1711
	return;
1712
}
1713

    
1714
/****f* util/exec_command
1715
 * NAME
1716
 *   exec_command - Execute a command and return a string of the result.
1717
 * INPUTS
1718
 *   $command   - String of the command to be executed.
1719
 * RESULT
1720
 *   String containing the command's result.
1721
 * NOTES
1722
 *   This function returns the command's stdout and stderr.
1723
 ******/
1724
function exec_command($command) {
1725
	$output = array();
1726
	exec($command . ' 2>&1', $output);
1727
	return(implode("\n", $output));
1728
}
1729

    
1730
/* wrapper for exec()
1731
   Executes in background or foreground.
1732
   For background execution, returns PID of background process to allow calling code control */
1733
function mwexec($command, $nologentry = false, $clearsigmask = false, $background = false) {
1734
	global $g;
1735
	$retval = 0;
1736

    
1737
	if ($g['debug']) {
1738
		if (!$_SERVER['REMOTE_ADDR']) {
1739
			echo "mwexec(): $command" . ($background ? " [BG]":"") . "\n";
1740
		}
1741
	}
1742
	if ($clearsigmask) {
1743
		$oldset = array();
1744
		pcntl_sigprocmask(SIG_SETMASK, array(), $oldset);
1745
	}
1746

    
1747
	if ($background) {
1748
		// start background process and return PID
1749
		$retval = exec("/usr/bin/nohup $command > /dev/null 2>&1 & echo $!");
1750
	} else {
1751
		// run in foreground, and (optionally) log if nonzero return
1752
		$outputarray = array();
1753
		exec("$command 2>&1", $outputarray, $retval);
1754
		if (($retval <> 0) && (!$nologentry || isset($config['system']['developerspew']))) {
1755
			log_error(sprintf(gettext("The command '%1\$s' returned exit code '%2\$d', the output was '%3\$s' "), $command, $retval, implode(" ", $outputarray)));
1756
		}
1757
	}
1758

    
1759
	if ($clearsigmask) {
1760
		pcntl_sigprocmask(SIG_SETMASK, $oldset);
1761
	}
1762

    
1763
	return $retval;
1764
}
1765

    
1766
/* wrapper for exec() in background */
1767
function mwexec_bg($command, $clearsigmask = false) {
1768
	return mwexec($command, false, $clearsigmask, true);
1769
}
1770

    
1771
/*	unlink a file, or pattern-match of a file, if it exists
1772
	if the file/path contains glob() compatible wildcards, all matching files will be unlinked
1773
	any warning/errors are suppressed (e.g. no matching files to delete)
1774
	If there are matching file(s) and they were all unlinked OK, then return true.
1775
	Otherwise return false (the requested file(s) did not exist, or could not be deleted)
1776
	This allows the caller to know if they were the one to successfully delete the file(s).
1777
*/
1778
function unlink_if_exists($fn) {
1779
	$to_do = glob($fn);
1780
	if (is_array($to_do) && count($to_do) > 0) {
1781
		// Returns an array of true/false indicating if each unlink worked
1782
		$results = @array_map("unlink", $to_do);
1783
		// If there is no false in the array, then all went well
1784
		$result = !in_array(false, $results, true);
1785
	} else {
1786
		$result = @unlink($fn);
1787
	}
1788
	return $result;
1789
}
1790
/* make a global alias table (for faster lookups) */
1791
function alias_make_table($config) {
1792
	global $aliastable;
1793

    
1794
	$aliastable = array();
1795

    
1796
	if (is_array($config['aliases']['alias'])) {
1797
		foreach ($config['aliases']['alias'] as $alias) {
1798
			if ($alias['name']) {
1799
				$aliastable[$alias['name']] = $alias['address'];
1800
			}
1801
		}
1802
	}
1803
}
1804

    
1805
/* check if an alias exists */
1806
function is_alias($name) {
1807
	global $aliastable;
1808

    
1809
	return isset($aliastable[$name]);
1810
}
1811

    
1812
function alias_get_type($name) {
1813
	global $config;
1814

    
1815
	if (is_array($config['aliases']['alias'])) {
1816
		foreach ($config['aliases']['alias'] as $alias) {
1817
			if ($name == $alias['name']) {
1818
				return $alias['type'];
1819
			}
1820
		}
1821
	}
1822

    
1823
	return "";
1824
}
1825

    
1826
/* expand a host or network alias, if necessary */
1827
function alias_expand($name) {
1828
	global $config, $aliastable;
1829
	$urltable_prefix = "/var/db/aliastables/";
1830
	$urltable_filename = $urltable_prefix . $name . ".txt";
1831

    
1832
	if (isset($aliastable[$name])) {
1833
		// alias names cannot be strictly numeric. redmine #4289
1834
		if (is_numericint($name)) {
1835
			return null;
1836
		}
1837
		// make sure if it's a ports alias, it actually exists. redmine #5845
1838
		foreach ($config['aliases']['alias'] as $alias) {
1839
			if ($alias['name'] == $name) {
1840
				if ($alias['type'] == "urltable_ports") {
1841
					if (is_URL($alias['url']) && file_exists($urltable_filename) && filesize($urltable_filename)) {
1842
						return "\${$name}";
1843
					} else {
1844
						return null;
1845
					}
1846
				}
1847
			}
1848
		}
1849
		return "\${$name}";
1850
	} else if (is_ipaddr($name) || is_subnet($name) || is_port_or_range($name)) {
1851
		return "{$name}";
1852
	} else {
1853
		return null;
1854
	}
1855
}
1856

    
1857
function alias_expand_urltable($name) {
1858
	global $config;
1859
	$urltable_prefix = "/var/db/aliastables/";
1860
	$urltable_filename = $urltable_prefix . $name . ".txt";
1861

    
1862
	if (is_array($config['aliases']['alias'])) {
1863
		foreach ($config['aliases']['alias'] as $alias) {
1864
			if (preg_match("/urltable/i", $alias['type']) && ($alias['name'] == $name)) {
1865
				if (is_URL($alias["url"]) && file_exists($urltable_filename)) {
1866
					if (!filesize($urltable_filename)) {
1867
						// file exists, but is empty, try to sync
1868
						send_event("service sync alias {$name}");
1869
					}
1870
					return $urltable_filename;
1871
				} else {
1872
					send_event("service sync alias {$name}");
1873
					break;
1874
				}
1875
			}
1876
		}
1877
	}
1878
	return null;
1879
}
1880

    
1881
/* obtain MAC address given an IP address by looking at the ARP/NDP table */
1882
function arp_get_mac_by_ip($ip, $do_ping = true) {
1883
	unset($macaddr);
1884
	$retval = 1;
1885
	switch (is_ipaddr($ip)) {
1886
		case 4:
1887
			if ($do_ping === true) {
1888
				mwexec("/sbin/ping -c 1 -t 1 " . escapeshellarg($ip), true);
1889
			}
1890
			$macaddr = exec("/usr/sbin/arp -n " . escapeshellarg($ip) . " | /usr/bin/awk '{print $4}'", $output, $retval);
1891
			break;
1892
		case 6:
1893
			if ($do_ping === true) {
1894
				mwexec("/sbin/ping6 -c 1 -X 1 " . escapeshellarg($ip), true);
1895
			}
1896
			$macaddr = exec("/usr/sbin/ndp -n " . escapeshellarg($ip) . " | /usr/bin/awk '{print $2}'", $output, $retval);
1897
			break;
1898
	}
1899
	if ($retval == 0 && is_macaddr($macaddr)) {
1900
		return $macaddr;
1901
	} else {
1902
		return false;
1903
	}
1904
}
1905

    
1906
/* return a fieldname that is safe for xml usage */
1907
function xml_safe_fieldname($fieldname) {
1908
	$replace = array(
1909
	    '/', '-', ' ', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')',
1910
	    '_', '+', '=', '{', '}', '[', ']', '|', '/', '<', '>', '?',
1911
	    ':', ',', '.', '\'', '\\'
1912
	);
1913
	return strtolower(str_replace($replace, "", $fieldname));
1914
}
1915

    
1916
function mac_format($clientmac) {
1917
	global $config, $cpzone;
1918

    
1919
	$mac = explode(":", $clientmac);
1920
	$mac_format = $cpzone ? $config['captiveportal'][$cpzone]['radmac_format'] : false;
1921

    
1922
	switch ($mac_format) {
1923
		case 'singledash':
1924
			return "$mac[0]$mac[1]$mac[2]-$mac[3]$mac[4]$mac[5]";
1925

    
1926
		case 'ietf':
1927
			return "$mac[0]-$mac[1]-$mac[2]-$mac[3]-$mac[4]-$mac[5]";
1928

    
1929
		case 'cisco':
1930
			return "$mac[0]$mac[1].$mac[2]$mac[3].$mac[4]$mac[5]";
1931

    
1932
		case 'unformatted':
1933
			return "$mac[0]$mac[1]$mac[2]$mac[3]$mac[4]$mac[5]";
1934

    
1935
		default:
1936
			return $clientmac;
1937
	}
1938
}
1939

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

    
1942
	$recresult = array();
1943
	$returnres = array();
1944
	for ($i = 0; $i < $retries; $i++) {
1945
		switch ($protocol) {
1946
			case 'any':
1947
				$checkproto = 'is_ipaddr';
1948
				$dnsproto = DNS_ANY;
1949
				$dnstype = array('A', 'AAAA');
1950
				break;
1951
			case 'inet6':
1952
				$checkproto = 'is_ipaddrv6';
1953
				$dnsproto = DNS_AAAA;
1954
				$dnstype = array('AAAA');
1955
				break;
1956
			case 'inet': 
1957
			default:
1958
				$checkproto = 'is_ipaddrv4';
1959
				$dnsproto = DNS_A;
1960
				$dnstype = array('A');
1961
				break;
1962
		}
1963
		if ($checkproto($hostname)) {
1964
			return $hostname;
1965
		}
1966
		$dnsresult = @dns_get_record($hostname, $dnsproto);
1967
		if (!empty($dnsresult)) {
1968
			foreach ($dnsresult as $dnsrec => $ip) {
1969
				if (is_array($ip)) {
1970
					if (in_array($ip['type'], $dnstype)) {
1971
					    if ($checkproto($ip['ip'])) { 
1972
						    $recresult[] = $ip['ip'];
1973
					    }
1974
					    if ($checkproto($ip['ipv6'])) { 
1975
						    $recresult[] = $ip['ipv6'];
1976
					    }
1977
					}
1978
				}
1979
			}
1980
		}
1981

    
1982
		sleep(1);
1983
	}
1984

    
1985
	if (!empty($recresult)) {
1986
		if ($numrecords == 1) {
1987
			return $recresult[0];
1988
		} else {
1989
			return array_slice($recresult, 0, $numrecords);
1990
		}
1991
	}
1992

    
1993
	return false;
1994
}
1995

    
1996
function format_bytes($bytes) {
1997
	if ($bytes >= 1099511627776) {
1998
		return sprintf("%.2f TiB", $bytes/1099511627776);
1999
	} else if ($bytes >= 1073741824) {
2000
		return sprintf("%.2f GiB", $bytes/1073741824);
2001
	} else if ($bytes >= 1048576) {
2002
		return sprintf("%.2f MiB", $bytes/1048576);
2003
	} else if ($bytes >= 1024) {
2004
		return sprintf("%.0f KiB", $bytes/1024);
2005
	} else {
2006
		return sprintf("%d B", $bytes);
2007
	}
2008
}
2009

    
2010
function format_number($num, $precision = 3) {
2011
	$units = array('', 'K', 'M', 'G', 'T');
2012

    
2013
	$i = 0;
2014
	while ($num > 1000 && $i < count($units)) {
2015
		$num /= 1000;
2016
		$i++;
2017
	}
2018
	$num = round($num, $precision);
2019

    
2020
	return ("$num {$units[$i]}");
2021
}
2022

    
2023

    
2024
function unformat_number($formated_num) {
2025
	$num = strtoupper($formated_num);
2026
    
2027
	if ( strpos($num,"T") !== false ) {
2028
		$num = str_replace("T","",$num) * 1000 * 1000 * 1000 * 1000;
2029
	} else if ( strpos($num,"G") !== false ) {
2030
		$num = str_replace("G","",$num) * 1000 * 1000 * 1000;
2031
	} else if ( strpos($num,"M") !== false ) {
2032
		$num = str_replace("M","",$num) * 1000 * 1000;
2033
	} else if ( strpos($num,"K") !== false ) {
2034
		$num = str_replace("K","",$num) * 1000;
2035
	}
2036
    
2037
	return $num;
2038
}
2039

    
2040
function update_filter_reload_status($text, $new=false) {
2041
	global $g;
2042

    
2043
	if ($new) {
2044
		file_put_contents("{$g['varrun_path']}/filter_reload_status", $text  . PHP_EOL);
2045
	} else {
2046
		file_put_contents("{$g['varrun_path']}/filter_reload_status", $text  . PHP_EOL, FILE_APPEND);
2047
	}
2048
}
2049

    
2050
/****** util/return_dir_as_array
2051
 * NAME
2052
 *   return_dir_as_array - Return a directory's contents as an array.
2053
 * INPUTS
2054
 *   $dir          - string containing the path to the desired directory.
2055
 *   $filter_regex - string containing a regular expression to filter file names. Default empty.
2056
 * RESULT
2057
 *   $dir_array - array containing the directory's contents. This array will be empty if the path specified is invalid.
2058
 ******/
2059
function return_dir_as_array($dir, $filter_regex = '') {
2060
	$dir_array = array();
2061
	if (is_dir($dir)) {
2062
		if ($dh = opendir($dir)) {
2063
			while (($file = readdir($dh)) !== false) {
2064
				if (($file == ".") || ($file == "..")) {
2065
					continue;
2066
				}
2067

    
2068
				if (empty($filter_regex) || preg_match($filter_regex, $file)) {
2069
					array_push($dir_array, $file);
2070
				}
2071
			}
2072
			closedir($dh);
2073
		}
2074
	}
2075
	return $dir_array;
2076
}
2077

    
2078
function run_plugins($directory) {
2079
	global $config, $g;
2080

    
2081
	/* process packager manager custom rules */
2082
	$files = return_dir_as_array($directory);
2083
	if (is_array($files)) {
2084
		foreach ($files as $file) {
2085
			if (stristr($file, ".sh") == true) {
2086
				mwexec($directory . $file . " start");
2087
			} else if (!is_dir($directory . "/" . $file) && stristr($file, ".inc")) {
2088
				require_once($directory . "/" . $file);
2089
			}
2090
		}
2091
	}
2092
}
2093

    
2094
/*
2095
 *    safe_mkdir($path, $mode = 0755)
2096
 *    create directory if it doesn't already exist and isn't a file!
2097
 */
2098
function safe_mkdir($path, $mode = 0755) {
2099
	global $g;
2100

    
2101
	if (!is_file($path) && !is_dir($path)) {
2102
		return @mkdir($path, $mode, true);
2103
	} else {
2104
		return false;
2105
	}
2106
}
2107

    
2108
/*
2109
 * get_sysctl($names)
2110
 * Get values of sysctl OID's listed in $names (accepts an array or a single
2111
 * name) and return an array of key/value pairs set for those that exist
2112
 */
2113
function get_sysctl($names) {
2114
	if (empty($names)) {
2115
		return array();
2116
	}
2117

    
2118
	if (is_array($names)) {
2119
		$name_list = array();
2120
		foreach ($names as $name) {
2121
			$name_list[] = escapeshellarg($name);
2122
		}
2123
	} else {
2124
		$name_list = array(escapeshellarg($names));
2125
	}
2126

    
2127
	exec("/sbin/sysctl -iq " . implode(" ", $name_list), $output);
2128
	$values = array();
2129
	foreach ($output as $line) {
2130
		$line = explode(": ", $line, 2);
2131
		if (count($line) == 2) {
2132
			$values[$line[0]] = $line[1];
2133
		}
2134
	}
2135

    
2136
	return $values;
2137
}
2138

    
2139
/*
2140
 * get_single_sysctl($name)
2141
 * Wrapper for get_sysctl() to simplify read of a single sysctl value
2142
 * return the value for sysctl $name or empty string if it doesn't exist
2143
 */
2144
function get_single_sysctl($name) {
2145
	if (empty($name)) {
2146
		return "";
2147
	}
2148

    
2149
	$value = get_sysctl($name);
2150
	if (empty($value) || !isset($value[$name])) {
2151
		return "";
2152
	}
2153

    
2154
	return $value[$name];
2155
}
2156

    
2157
/*
2158
 * set_sysctl($value_list)
2159
 * Set sysctl OID's listed as key/value pairs and return
2160
 * an array with keys set for those that succeeded
2161
 */
2162
function set_sysctl($values) {
2163
	if (empty($values)) {
2164
		return array();
2165
	}
2166

    
2167
	$value_list = array();
2168
	foreach ($values as $key => $value) {
2169
		$value_list[] = escapeshellarg($key) . "=" . escapeshellarg($value);
2170
	}
2171

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

    
2174
	/* Retry individually if failed (one or more read-only) */
2175
	if ($success <> 0 && count($value_list) > 1) {
2176
		foreach ($value_list as $value) {
2177
			exec("/sbin/sysctl -iq " . $value, $output);
2178
		}
2179
	}
2180

    
2181
	$ret = array();
2182
	foreach ($output as $line) {
2183
		$line = explode(": ", $line, 2);
2184
		if (count($line) == 2) {
2185
			$ret[$line[0]] = true;
2186
		}
2187
	}
2188

    
2189
	return $ret;
2190
}
2191

    
2192
/*
2193
 * set_single_sysctl($name, $value)
2194
 * Wrapper to set_sysctl() to make it simple to set only one sysctl
2195
 * returns boolean meaning if it succeeded
2196
 */
2197
function set_single_sysctl($name, $value) {
2198
	if (empty($name)) {
2199
		return false;
2200
	}
2201

    
2202
	$result = set_sysctl(array($name => $value));
2203

    
2204
	if (!isset($result[$name]) || $result[$name] != $value) {
2205
		return false;
2206
	}
2207

    
2208
	return true;
2209
}
2210

    
2211
/*
2212
 *     get_memory()
2213
 *     returns an array listing the amount of
2214
 *     memory installed in the hardware
2215
 *     [0] net memory available for the OS (FreeBSD) after some is taken by BIOS, video or whatever - e.g. 235 MBytes
2216
 *     [1] real (actual) memory of the system, should be the size of the RAM card/s - e.g. 256 MBytes
2217
 */
2218
function get_memory() {
2219
	$physmem = get_single_sysctl("hw.physmem");
2220
	$realmem = get_single_sysctl("hw.realmem");
2221
	/* convert from bytes to megabytes */
2222
	return array(($physmem/1048576), ($realmem/1048576));
2223
}
2224

    
2225
function mute_kernel_msgs() {
2226
	global $g, $config;
2227

    
2228
	if ($config['system']['enableserial']) {
2229
		return;
2230
	}
2231
	exec("/sbin/conscontrol mute on");
2232
}
2233

    
2234
function unmute_kernel_msgs() {
2235
	global $g;
2236

    
2237
	exec("/sbin/conscontrol mute off");
2238
}
2239

    
2240
function start_devd() {
2241
	global $g;
2242

    
2243
	/* Generate hints for the kernel loader. */
2244
	$module_paths = explode(";", get_single_sysctl("kern.module_path"));
2245
	foreach ($module_paths as $id => $path) {
2246
		if (!is_dir($path) || file_exists("{$path}/linker.hints")) {
2247
			continue;
2248
		}
2249
		if (($files = scandir($path)) == false) {
2250
			continue;
2251
		}
2252
		$found = false;
2253
		foreach ($files as $id => $file) {
2254
			if (strlen($file) > 3 &&
2255
			    strcasecmp(substr($file, -3), ".ko") == 0) {
2256
				$found = true;
2257
				break;
2258
			}
2259
		}
2260
		if ($found == false) {
2261
			continue;
2262
		}
2263
		$_gb = exec("/usr/sbin/kldxref $path");
2264
		unset($_gb);
2265
	}
2266

    
2267
	/* Use the undocumented -q options of devd to quiet its log spamming */
2268
	$_gb = exec("/sbin/devd -q -f /etc/{$g['product_name']}-devd.conf");
2269
	sleep(1);
2270
	unset($_gb);
2271
}
2272

    
2273
function is_interface_vlan_mismatch() {
2274
	global $config, $g;
2275

    
2276
	if (is_array($config['vlans']['vlan'])) {
2277
		foreach ($config['vlans']['vlan'] as $vlan) {
2278
			if (substr($vlan['if'], 0, 4) == "lagg") {
2279
				return false;
2280
			}
2281
			if (does_interface_exist($vlan['if']) == false) {
2282
				return true;
2283
			}
2284
		}
2285
	}
2286

    
2287
	return false;
2288
}
2289

    
2290
function is_interface_mismatch() {
2291
	global $config, $g;
2292

    
2293
	$do_assign = false;
2294
	$i = 0;
2295
	$missing_interfaces = array();
2296
	if (is_array($config['interfaces'])) {
2297
		foreach ($config['interfaces'] as $ifname => $ifcfg) {
2298
			if (interface_is_vlan($ifcfg['if']) != NULL ||
2299
			    interface_is_qinq($ifcfg['if']) != NULL ||
2300
			    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'])) {
2301
				// Do not check these interfaces.
2302
				$i++;
2303
				continue;
2304
			} else if (does_interface_exist($ifcfg['if']) == false) {
2305
				$missing_interfaces[] = $ifcfg['if'];
2306
				$do_assign = true;
2307
			} else {
2308
				$i++;
2309
			}
2310
		}
2311
	}
2312

    
2313
	if (file_exists("{$g['tmp_path']}/assign_complete")) {
2314
		$do_assign = false;
2315
	}
2316

    
2317
	if (!empty($missing_interfaces) && $do_assign) {
2318
		file_put_contents("{$g['tmp_path']}/missing_interfaces", implode(' ', $missing_interfaces));
2319
	} else {
2320
		@unlink("{$g['tmp_path']}/missing_interfaces");
2321
	}
2322

    
2323
	return $do_assign;
2324
}
2325

    
2326
/* sync carp entries to other firewalls */
2327
function carp_sync_client() {
2328
	global $g;
2329
	send_event("filter sync");
2330
}
2331

    
2332
/****f* util/isAjax
2333
 * NAME
2334
 *   isAjax - reports if the request is driven from prototype
2335
 * INPUTS
2336
 *   none
2337
 * RESULT
2338
 *   true/false
2339
 ******/
2340
function isAjax() {
2341
	return isset ($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest';
2342
}
2343

    
2344
/****f* util/timeout
2345
 * NAME
2346
 *   timeout - console input with timeout countdown. Note: erases 2 char of screen for timer. Leave space.
2347
 * INPUTS
2348
 *   optional, seconds to wait before timeout. Default 9 seconds.
2349
 * RESULT
2350
 *   returns 1 char of user input or null if no input.
2351
 ******/
2352
function timeout($timer = 9) {
2353
	while (!isset($key)) {
2354
		if ($timer >= 9) {
2355
			echo chr(8) . chr(8) . ($timer == 9 ? chr(32) : null) . "{$timer}";
2356
		} else {
2357
			echo chr(8). "{$timer}";
2358
		}
2359
		`/bin/stty -icanon min 0 time 25`;
2360
		$key = trim(`KEY=\`dd count=1 2>/dev/null\`; echo \$KEY`);
2361
		`/bin/stty icanon`;
2362
		if ($key == '') {
2363
			unset($key);
2364
		}
2365
		$timer--;
2366
		if ($timer == 0) {
2367
			break;
2368
		}
2369
	}
2370
	return $key;
2371
}
2372

    
2373
/****f* util/msort
2374
 * NAME
2375
 *   msort - sort array
2376
 * INPUTS
2377
 *   $array to be sorted, field to sort by, direction of sort
2378
 * RESULT
2379
 *   returns newly sorted array
2380
 ******/
2381
function msort($array, $id = "id", $sort_ascending = true) {
2382
	$temp_array = array();
2383
	if (!is_array($array)) {
2384
		return $temp_array;
2385
	}
2386
	while (count($array)>0) {
2387
		$lowest_id = 0;
2388
		$index = 0;
2389
		foreach ($array as $item) {
2390
			if (isset($item[$id])) {
2391
				if ($array[$lowest_id][$id]) {
2392
					if (strtolower($item[$id]) < strtolower($array[$lowest_id][$id])) {
2393
						$lowest_id = $index;
2394
					}
2395
				}
2396
			}
2397
			$index++;
2398
		}
2399
		$temp_array[] = $array[$lowest_id];
2400
		$array = array_merge(array_slice($array, 0, $lowest_id), array_slice($array, $lowest_id + 1));
2401
	}
2402
	if ($sort_ascending) {
2403
		return $temp_array;
2404
	} else {
2405
		return array_reverse($temp_array);
2406
	}
2407
}
2408

    
2409
/****f* util/is_URL
2410
 * NAME
2411
 *   is_URL
2412
 * INPUTS
2413
 *   string to check
2414
 * RESULT
2415
 *   Returns true if item is a URL
2416
 ******/
2417
function is_URL($url) {
2418
	$match = preg_match("'\b(([\w-]+://?|www[.])[^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|/)))'", $url);
2419
	if ($match) {
2420
		return true;
2421
	}
2422
	return false;
2423
}
2424

    
2425
function is_file_included($file = "") {
2426
	$files = get_included_files();
2427
	if (in_array($file, $files)) {
2428
		return true;
2429
	}
2430

    
2431
	return false;
2432
}
2433

    
2434
/*
2435
 * Replace a value on a deep associative array using regex
2436
 */
2437
function array_replace_values_recursive($data, $match, $replace) {
2438
	if (empty($data)) {
2439
		return $data;
2440
	}
2441

    
2442
	if (is_string($data)) {
2443
		$data = preg_replace("/{$match}/", $replace, $data);
2444
	} else if (is_array($data)) {
2445
		foreach ($data as $k => $v) {
2446
			$data[$k] = array_replace_values_recursive($v, $match, $replace);
2447
		}
2448
	}
2449

    
2450
	return $data;
2451
}
2452

    
2453
/*
2454
	This function was borrowed from a comment on PHP.net at the following URL:
2455
	http://www.php.net/manual/en/function.array-merge-recursive.php#73843
2456
 */
2457
function array_merge_recursive_unique($array0, $array1) {
2458

    
2459
	$arrays = func_get_args();
2460
	$remains = $arrays;
2461

    
2462
	// We walk through each arrays and put value in the results (without
2463
	// considering previous value).
2464
	$result = array();
2465

    
2466
	// loop available array
2467
	foreach ($arrays as $array) {
2468

    
2469
		// The first remaining array is $array. We are processing it. So
2470
		// we remove it from remaining arrays.
2471
		array_shift($remains);
2472

    
2473
		// We don't care non array param, like array_merge since PHP 5.0.
2474
		if (is_array($array)) {
2475
			// Loop values
2476
			foreach ($array as $key => $value) {
2477
				if (is_array($value)) {
2478
					// we gather all remaining arrays that have such key available
2479
					$args = array();
2480
					foreach ($remains as $remain) {
2481
						if (array_key_exists($key, $remain)) {
2482
							array_push($args, $remain[$key]);
2483
						}
2484
					}
2485

    
2486
					if (count($args) > 2) {
2487
						// put the recursion
2488
						$result[$key] = call_user_func_array(__FUNCTION__, $args);
2489
					} else {
2490
						foreach ($value as $vkey => $vval) {
2491
							if (!is_array($result[$key])) {
2492
								$result[$key] = array();
2493
							}
2494
							$result[$key][$vkey] = $vval;
2495
						}
2496
					}
2497
				} else {
2498
					// simply put the value
2499
					$result[$key] = $value;
2500
				}
2501
			}
2502
		}
2503
	}
2504
	return $result;
2505
}
2506

    
2507

    
2508
/*
2509
 * converts a string like "a,b,c,d"
2510
 * into an array like array("a" => "b", "c" => "d")
2511
 */
2512
function explode_assoc($delimiter, $string) {
2513
	$array = explode($delimiter, $string);
2514
	$result = array();
2515
	$numkeys = floor(count($array) / 2);
2516
	for ($i = 0; $i < $numkeys; $i += 1) {
2517
		$result[$array[$i * 2]] = $array[$i * 2 + 1];
2518
	}
2519
	return $result;
2520
}
2521

    
2522
/*
2523
 * Given a string of text with some delimiter, look for occurrences
2524
 * of some string and replace all of those.
2525
 * $text - the text string (e.g. "abc,defg,x123,ipv4,xyz")
2526
 * $delimiter - the delimiter (e.g. ",")
2527
 * $element - the element to match (e.g. "defg")
2528
 * $replacement - the string to replace it with (e.g. "42")
2529
 * Returns the resulting delimited string (e.g. "abc,42,x123,ipv4,xyz")
2530
 */
2531
function replace_element_in_list($text, $delimiter, $element, $replacement) {
2532
	$textArray = explode($delimiter, $text);
2533
	while (($entry = array_search($element, $textArray)) !== false) {
2534
		$textArray[$entry] = $replacement;
2535
	}
2536
	return implode(',', $textArray);
2537
}
2538

    
2539
/* Try to change a static route, if it doesn't exist, add it */
2540
function route_add_or_change($args) {
2541
	global $config;
2542

    
2543
	if (empty($args)) {
2544
		return false;
2545
	}
2546

    
2547
	/* First, try to add it */
2548
	$_gb = exec(escapeshellcmd("/sbin/route add " . $args), $output, $rc);
2549
		
2550
	if (isset($config['system']['route-debug'])) {
2551
		$add_change = 'add';
2552
		$mt = microtime();
2553
		log_error("ROUTING debug: $mt - ADD RC={$rc} - $args");
2554
	}
2555

    
2556
	if ($rc != 0) {
2557
		/* If it fails, try to change it */
2558
		$_gb = exec(escapeshellcmd("/sbin/route change " . $args),
2559
		    $output, $rc);
2560

    
2561
		if (isset($config['system']['route-debug'])) {
2562
			$add_change = 'change';
2563
			$mt = microtime();
2564
			log_error("ROUTING debug: $mt - CHG RC={$rc} - $args");
2565
		}
2566
	}
2567
	if (isset($config['system']['route-debug'])) {
2568
		file_put_contents("/dev/console", "\n[".getmypid()."] ROUTE: {$add_change} {$args} result: {$rc}");
2569
	}
2570

    
2571
	return ($rc == 0);
2572
}
2573

    
2574
function set_ipv6routes_mtu($interface, $mtu) {
2575
	global $config, $g;
2576

    
2577
	$ipv6mturoutes = array();
2578
	$if = convert_real_interface_to_friendly_interface_name($interface);
2579
	if (!$config['interfaces'][$if]['ipaddrv6']) {
2580
		return;
2581
	}
2582
	$a_gateways = return_gateways_array();
2583
	$a_staticroutes = get_staticroutes(false, false, true);
2584
	foreach ($a_gateways as $gate) {
2585
		foreach ($a_staticroutes as $sroute) {
2586
			if ($gate['interface'] == $interface &&
2587
			    $sroute['gateway'] == $gate['name']) {
2588
				$tgt = $sroute['network'];
2589
				$gateway = $gate['gateway'];
2590
				$ipv6mturoutes[$tgt] = $gateway;
2591
			}
2592
		}
2593
		if ($gate['interface'] == $interface &&
2594
		    $gate['isdefaultgw']) {
2595
			$tgt = "default";
2596
			$gateway = $gate['gateway'];
2597
			$ipv6mturoutes[$tgt] = $gateway;
2598
		}
2599
	}
2600
	foreach ($ipv6mturoutes as $tgt => $gateway) {
2601
		mwexec("/sbin/route change -6 -mtu " . escapeshellarg($mtu) .
2602
		    " " . escapeshellarg($tgt) . " " .
2603
		    escapeshellarg($gateway));
2604
	}
2605
}
2606

    
2607
function alias_to_subnets_recursive($name, $returnhostnames = false) {
2608
	global $aliastable;
2609
	$result = array();
2610
	if (!isset($aliastable[$name])) {
2611
		return $result;
2612
	}
2613
	$subnets = preg_split('/\s+/', $aliastable[$name]);
2614
	foreach ($subnets as $net) {
2615
		if (is_alias($net)) {
2616
			$sub = alias_to_subnets_recursive($net, $returnhostnames);
2617
			$result = array_merge($result, $sub);
2618
			continue;
2619
		} elseif (!is_subnet($net)) {
2620
			if (is_ipaddrv4($net)) {
2621
				$net .= "/32";
2622
			} else if (is_ipaddrv6($net)) {
2623
				$net .= "/128";
2624
			} else if ($returnhostnames === false || !is_fqdn($net)) {
2625
				continue;
2626
			}
2627
		}
2628
		$result[] = $net;
2629
	}
2630
	return $result;
2631
}
2632

    
2633
function get_staticroutes($returnsubnetsonly = false, $returnhostnames = false, $returnenabledroutesonly = false) {
2634
	global $config, $aliastable;
2635

    
2636
	/* Bail if there are no routes, but return an array always so callers don't have to check. */
2637
	if (!is_array($config['staticroutes']['route'])) {
2638
		return array();
2639
	}
2640

    
2641
	$allstaticroutes = array();
2642
	$allsubnets = array();
2643
	/* Loop through routes and expand aliases as we find them. */
2644
	foreach ($config['staticroutes']['route'] as $route) {
2645
		if ($returnenabledroutesonly && isset($route['disabled'])) {
2646
			continue;
2647
		}
2648

    
2649
		if (is_alias($route['network'])) {
2650
			foreach (alias_to_subnets_recursive($route['network'], $returnhostnames) as $net) {
2651
				$temproute = $route;
2652
				$temproute['network'] = $net;
2653
				$allstaticroutes[] = $temproute;
2654
				$allsubnets[] = $net;
2655
			}
2656
		} elseif (is_subnet($route['network'])) {
2657
			$allstaticroutes[] = $route;
2658
			$allsubnets[] = $route['network'];
2659
		}
2660
	}
2661
	if ($returnsubnetsonly) {
2662
		return $allsubnets;
2663
	} else {
2664
		return $allstaticroutes;
2665
	}
2666
}
2667

    
2668
/****f* util/get_alias_list
2669
 * NAME
2670
 *   get_alias_list - Provide a list of aliases.
2671
 * INPUTS
2672
 *   $type          - Optional, can be a string or array specifying what type(s) of aliases you need.
2673
 * RESULT
2674
 *   Array containing list of aliases.
2675
 *   If $type is unspecified, all aliases are returned.
2676
 *   If $type is a string, all aliases of the type specified in $type are returned.
2677
 *   If $type is an array, all aliases of any type specified in any element of $type are returned.
2678
 */
2679
function get_alias_list($type = null) {
2680
	global $config;
2681
	$result = array();
2682
	if ($config['aliases']['alias'] <> "" && is_array($config['aliases']['alias'])) {
2683
		foreach ($config['aliases']['alias'] as $alias) {
2684
			if ($type === null) {
2685
				$result[] = $alias['name'];
2686
			} else if (is_array($type)) {
2687
				if (in_array($alias['type'], $type)) {
2688
					$result[] = $alias['name'];
2689
				}
2690
			} else if ($type === $alias['type']) {
2691
				$result[] = $alias['name'];
2692
			}
2693
		}
2694
	}
2695
	return $result;
2696
}
2697

    
2698
/* returns an array consisting of every element of $haystack that is not equal to $needle. */
2699
function array_exclude($needle, $haystack) {
2700
	$result = array();
2701
	if (is_array($haystack)) {
2702
		foreach ($haystack as $thing) {
2703
			if ($needle !== $thing) {
2704
				$result[] = $thing;
2705
			}
2706
		}
2707
	}
2708
	return $result;
2709
}
2710

    
2711
/* Define what is preferred, IPv4 or IPv6 */
2712
function prefer_ipv4_or_ipv6() {
2713
	global $config;
2714

    
2715
	if (isset($config['system']['prefer_ipv4'])) {
2716
		mwexec("/etc/rc.d/ip6addrctl prefer_ipv4");
2717
	} else {
2718
		mwexec("/etc/rc.d/ip6addrctl prefer_ipv6");
2719
	}
2720
}
2721

    
2722
/* Redirect to page passing parameters via POST */
2723
function post_redirect($page, $params) {
2724
	if (!is_array($params)) {
2725
		return;
2726
	}
2727

    
2728
	print "<html><body><form action=\"{$page}\" name=\"formredir\" method=\"post\">\n";
2729
	foreach ($params as $key => $value) {
2730
		print "<input type=\"hidden\" name=\"{$key}\" value=\"{$value}\" />\n";
2731
	}
2732
	print "</form>\n";
2733
	print "<script type=\"text/javascript\">\n";
2734
	print "//<![CDATA[\n";
2735
	print "document.formredir.submit();\n";
2736
	print "//]]>\n";
2737
	print "</script>\n";
2738
	print "</body></html>\n";
2739
}
2740

    
2741
/* Locate disks that can be queried for S.M.A.R.T. data. */
2742
function get_smart_drive_list() {
2743
	/* SMART supports some disks directly, and some controllers directly,
2744
	 * See https://redmine.pfsense.org/issues/9042 */
2745
	$supported_disk_types = array("ad", "da", "ada");
2746
	$supported_controller_types = array("nvme");
2747
	$disk_list = explode(" ", get_single_sysctl("kern.disks"));
2748
	foreach ($disk_list as $id => $disk) {
2749
		// We only want certain kinds of disks for S.M.A.R.T.
2750
		// 1 is a match, 0 is no match, False is any problem processing the regex
2751
		if (preg_match("/^(" . implode("|", $supported_disk_types) . ").*[0-9]{1,2}$/", $disk) !== 1) {
2752
			unset($disk_list[$id]);
2753
			continue;
2754
		}
2755
	}
2756
	foreach ($supported_controller_types as $controller) {
2757
		$devices = glob("/dev/{$controller}*");
2758
		if (!is_array($devices)) {
2759
			continue;
2760
		}
2761
		foreach ($devices as $device) {
2762
			$disk_list[] = basename($device);
2763
		}
2764
	}
2765
	sort($disk_list);
2766
	return $disk_list;
2767
}
2768

    
2769
// Validate a network address
2770
//	$addr: the address to validate
2771
//	$type: IPV4|IPV6|IPV4V6
2772
//	$label: the label used by the GUI to display this value. Required to compose an error message
2773
//	$err_msg: pointer to the callers error message array so that error messages can be added to it here
2774
//	$alias: are aliases permitted for this address?
2775
// Returns:
2776
//	IPV4 - if $addr is a valid IPv4 address
2777
//	IPV6 - if $addr is a valid IPv6 address
2778
//	ALIAS - if $alias=true and $addr is an alias
2779
//	false - otherwise
2780

    
2781
function validateipaddr(&$addr, $type, $label, &$err_msg, $alias=false) {
2782
	switch ($type) {
2783
		case IPV4:
2784
			if (is_ipaddrv4($addr)) {
2785
				return IPV4;
2786
			} else if ($alias) {
2787
				if (is_alias($addr)) {
2788
					return ALIAS;
2789
				} else {
2790
					$err_msg[] = sprintf(gettext("%s must be a valid IPv4 address or alias."), $label);
2791
					return false;
2792
				}
2793
			} else {
2794
				$err_msg[] = sprintf(gettext("%s must be a valid IPv4 address."), $label);
2795
				return false;
2796
			}
2797
		break;
2798
		case IPV6:
2799
			if (is_ipaddrv6($addr)) {
2800
				$addr = strtolower($addr);
2801
				return IPV6;
2802
			} else if ($alias) {
2803
				if (is_alias($addr)) {
2804
					return ALIAS;
2805
				} else {
2806
					$err_msg[] = sprintf(gettext("%s must be a valid IPv6 address or alias."), $label);
2807
					return false;
2808
				}
2809
			} else {
2810
				$err_msg[] = sprintf(gettext("%s must be a valid IPv6 address."), $label);
2811
				return false;
2812
			}
2813
		break;
2814
		case IPV4V6:
2815
			if (is_ipaddrv6($addr)) {
2816
				$addr = strtolower($addr);
2817
				return IPV6;
2818
			} else if (is_ipaddrv4($addr)) {
2819
				return IPV4;
2820
			} else if ($alias) {
2821
				if (is_alias($addr)) {
2822
					return ALIAS;
2823
				} else {
2824
					$err_msg[] = sprintf(gettext("%s must be a valid IPv4 or IPv6 address or alias."), $label);
2825
					return false;
2826
				}
2827
			} else {
2828
				$err_msg[] = sprintf(gettext("%s must be a valid IPv4 or IPv6 address."), $label);
2829
				return false;
2830
			}
2831
		break;
2832
	}
2833

    
2834
	return false;
2835
}
2836

    
2837
/* From DUID configuration inputs, format a string that looks (more) like the expected raw DUID format:
2838
 * 1) For DUIDs entered as a known DUID type, convert to a hexstring and prepend the DUID number, after having done the following:
2839
 *     a) For DUID-LLT and DUID-EN, convert the time/enterprise ID input to hex and append the link-layer address/identifier input.
2840
 *     b) For DUID-LLT and DUID-LL, prepend a hardware type of 1.
2841
 *     c) For DUID-UUID, remove any "-".
2842
 * 2) Replace any remaining "-" with ":".
2843
 * 3) If any components are input with just a single char (hex digit hopefully), put a "0" in front.
2844
 * 4) The first two components should be a 16-bit integer (little- or big-endian, depending on the current machine type) that
2845
 *    is equal to the number of other components. If not, prepend this as "nn:00" (all pfSense builds are little-endian).
2846
 *    This is convenience, because the DUID reported by dhcp6c in logs does not include this count, which corresponds to the
2847
 *    option-len field of DHCPv6's OPTION_CLIENTID option.
2848
 *
2849
 * The final result should be closer to:
2850
 *
2851
 * "nn:00:00:0n:nn:nn:nn:..."
2852
 *
2853
 * This function does not validate the input. is_duid() will do validation.
2854
 */
2855
function format_duid($duidtype, $duidpt1, $duidpt2=null) {
2856
	if ($duidpt2)
2857
		$duidpt1 = implode(':', str_split(str_pad(dechex($duidpt1), 8, '0', STR_PAD_LEFT), 2)) . ':' . $duidpt2;
2858

    
2859
	/* Make hexstrings */
2860
	if ($duidtype) {
2861
		switch ($duidtype) {
2862
		/* Add a hardware type to DUID-LLT and DUID-LL; assume Ethernet */
2863
		case 1:
2864
		case 3:
2865
			$duidpt1 = '00:01:' . $duidpt1;
2866
			break;
2867
		/* Remove '-' from given UUID and insert ':' every 2 characters */
2868
		case 4:
2869
			$duidpt1 = implode(':', str_split(str_replace('-', '', $duidpt1), 2));
2870
			break;
2871
		default:
2872
		}
2873
		$duidpt1 = '00:0' . $duidtype . ':' . $duidpt1;
2874
	}
2875

    
2876
	$values = explode(':', strtolower(str_replace('-', ':', $duidpt1)));
2877

    
2878
	if (hexdec($values[0]) != count($values) - 2)
2879
		array_unshift($values, dechex(count($values)), '00');
2880

    
2881
	array_walk($values, function(&$value) {
2882
		$value = str_pad($value, 2, '0', STR_PAD_LEFT);
2883
	});
2884

    
2885
	return implode(":", $values);
2886
}
2887

    
2888
/* Returns true if $dhcp6duid is a valid DUID entry.
2889
 * Parse the entry to check for valid length according to known DUID types.
2890
 */
2891
function is_duid($dhcp6duid) {
2892
	$values = explode(":", $dhcp6duid);
2893
	if (hexdec($values[0]) == count($values) - 2) {
2894
		switch (hexdec($values[2] . $values[3])) {
2895
		case 0:
2896
			return false;
2897
			break;
2898
		case 1:
2899
			if (count($values) != 16 || strlen($dhcp6duid) != 47)
2900
				return false;
2901
			break;
2902
		case 3:
2903
			if (count($values) != 12 || strlen($dhcp6duid) != 35)
2904
				return false;
2905
			break;
2906
		case 4:
2907
			if (count($values) != 20 || strlen($dhcp6duid) != 59)
2908
				return false;
2909
			break;
2910
		/* DUID is up to 128 octets; allow 2 octets for type code, 2 more for option-len */
2911
		default:
2912
			if (count($values) > 132 || strlen($dhcp6duid) != count($values) * 3 - 1)
2913
				return false;
2914
		}
2915
	} else
2916
		return false;
2917

    
2918
	for ($i = 0; $i < count($values); $i++) {
2919
		if (ctype_xdigit($values[$i]) == false)
2920
			return false;
2921
		if (hexdec($values[$i]) < 0 || hexdec($values[$i]) > 255)
2922
			return false;
2923
	}
2924

    
2925
	return true;
2926
}
2927

    
2928
/* Write the DHCP6 DUID file */
2929
function write_dhcp6_duid($duidstring) {
2930
	// Create the hex array from the dhcp6duid config entry and write to file
2931
	global $g;
2932

    
2933
	if(!is_duid($duidstring)) {
2934
		log_error(gettext("Error: attempting to write DUID file - Invalid DUID detected"));
2935
		return false;
2936
	}
2937
	$temp = str_replace(":","",$duidstring);
2938
	$duid_binstring = pack("H*",$temp);
2939
	if ($fd = fopen("{$g['vardb_path']}/dhcp6c_duid", "wb")) {
2940
		fwrite($fd, $duid_binstring);
2941
		fclose($fd);
2942
		return true;
2943
	}
2944
	log_error(gettext("Error: attempting to write DUID file - File write error"));
2945
	return false;
2946
}
2947

    
2948
/* returns duid string from 'vardb_path']}/dhcp6c_duid' */
2949
function get_duid_from_file() {
2950
	global $g;
2951

    
2952
	$duid_ASCII = "";
2953
	$count = 0;
2954

    
2955
	if (file_exists("{$g['vardb_path']}/dhcp6c_duid") &&
2956
	    ($fd = fopen("{$g['vardb_path']}/dhcp6c_duid", "r"))) {
2957
		$fsize = filesize("{$g['vardb_path']}/dhcp6c_duid");
2958
		if ($fsize <= 132) {
2959
			$buffer = fread($fd, $fsize);
2960
			while($count < $fsize) {
2961
				$duid_ASCII .= bin2hex($buffer[$count]);
2962
				$count++;
2963
				if($count < $fsize) {
2964
					$duid_ASCII .= ":";
2965
				}
2966
			}
2967
		}
2968
		fclose($fd);
2969
	}
2970
	//if no file or error with read then the string returns blanked DUID string
2971
	if(!is_duid($duid_ASCII)) {
2972
		return "--:--:--:--:--:--:--:--:--:--:--:--:--:--:--:--";
2973
	}
2974
	return($duid_ASCII);
2975
}
2976

    
2977
/* Replaces the Mac OS 9 and earlier (\r) and DOS/Windows (\r\n) newlines with the Unix equivalent (\n). */
2978
function unixnewlines($text) {
2979
	return preg_replace('/\r\n?/', "\n", $text);
2980
}
2981

    
2982
function array_remove_duplicate($array, $field) {
2983
	foreach ($array as $sub) {
2984
		if (isset($sub[$field])) {
2985
			$cmp[] = $sub[$field];
2986
		}
2987
	}
2988
	$unique = array_unique(array_reverse($cmp, true));
2989
	foreach ($unique as $k => $rien) {
2990
		$new[] = $array[$k];
2991
	}
2992
	return $new;
2993
}
2994

    
2995
function dhcpd_date_adjust_gmt($dt) {
2996
	global $config;
2997

    
2998
	init_config_arr(array('dhcpd'));
2999

    
3000
	foreach ($config['dhcpd'] as $dhcpditem) {
3001
		if ($dhcpditem['dhcpleaseinlocaltime'] == "yes") {
3002
			$ts = strtotime($dt . " GMT");
3003
			if ($ts !== false) {
3004
				return strftime("%Y/%m/%d %H:%M:%S", $ts);
3005
			}
3006
		}
3007
	}
3008

    
3009
	/*
3010
	 * If we did not need to convert to local time or the conversion
3011
	 * failed, just return the input.
3012
	 */
3013
	return $dt;
3014
}
3015

    
3016
global $supported_image_types;
3017
$supported_image_types = array(
3018
	IMAGETYPE_JPEG,
3019
	IMAGETYPE_PNG,
3020
	IMAGETYPE_GIF,
3021
	IMAGETYPE_WEBP
3022
);
3023

    
3024
function is_supported_image($image_filename) {
3025
	global $supported_image_types;
3026
	$img_info = getimagesize($image_filename);
3027

    
3028
	/* If it's not an image, or it isn't in the supported list, return false */
3029
	if (($img_info === false) ||
3030
	    !in_array($img_info[2], array_keys($supported_image_types))) {
3031
		return false;
3032
	} else {
3033
		return $img_info[2];
3034
	}
3035
}
3036

    
3037
function get_lagg_ports ($laggport) {
3038
	$laggp = array();
3039
	foreach ($laggport as $lgp) {
3040
		list($lpname, $lpinfo) = explode(" ", $lgp);
3041
		preg_match('~<(.+)>~', $lpinfo, $lgportmode);
3042
		if ($lgportmode[1]) {
3043
			$laggp[] = $lpname . " (" . $lgportmode[1] . ")";
3044
		} else {
3045
			$laggp[] = $lpname;
3046
		}
3047
	}
3048
	if ($laggp) {
3049
		return implode(", ", $laggp);
3050
	} else {
3051
		return false;
3052
	}
3053
}
3054

    
3055
?>
(53-53/60)