Project

General

Profile

Download (66.4 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
/*
3
	util.inc
4
*/
5
/* ====================================================================
6
 *	Copyright (c)  2004-2015  Electric Sheep Fencing, LLC. All rights reserved.
7
 *
8
 *  Some or all of this file is based on the m0n0wall project which is
9
 *  Copyright (c)  2004 Manuel Kasper (BSD 2 clause)
10
 *
11
 *	Redistribution and use in source and binary forms, with or without modification,
12
 *	are permitted provided that the following conditions are met:
13
 *
14
 *	1. Redistributions of source code must retain the above copyright notice,
15
 *		this list of conditions and the following disclaimer.
16
 *
17
 *	2. Redistributions in binary form must reproduce the above copyright
18
 *		notice, this list of conditions and the following disclaimer in
19
 *		the documentation and/or other materials provided with the
20
 *		distribution.
21
 *
22
 *	3. All advertising materials mentioning features or use of this software
23
 *		must display the following acknowledgment:
24
 *		"This product includes software developed by the pfSense Project
25
 *		 for use in the pfSense software distribution. (http://www.pfsense.org/).
26
 *
27
 *	4. The names "pfSense" and "pfSense Project" must not be used to
28
 *		 endorse or promote products derived from this software without
29
 *		 prior written permission. For written permission, please contact
30
 *		 coreteam@pfsense.org.
31
 *
32
 *	5. Products derived from this software may not be called "pfSense"
33
 *		nor may "pfSense" appear in their names without prior written
34
 *		permission of the Electric Sheep Fencing, LLC.
35
 *
36
 *	6. Redistributions of any form whatsoever must retain the following
37
 *		acknowledgment:
38
 *
39
 *	"This product includes software developed by the pfSense Project
40
 *	for use in the pfSense software distribution (http://www.pfsense.org/).
41
 *
42
 *	THIS SOFTWARE IS PROVIDED BY THE pfSense PROJECT ``AS IS'' AND ANY
43
 *	EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
44
 *	IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
45
 *	PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE pfSense PROJECT OR
46
 *	ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
47
 *	SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
48
 *	NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
49
 *	LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
50
 *	HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
51
 *	STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
52
 *	ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
53
 *	OF THE POSSIBILITY OF SUCH DAMAGE.
54
 *
55
 *	====================================================================
56
 *
57
 */
58

    
59
define('VIP_ALL', 1);
60
define('VIP_CARP', 2);
61
define('VIP_IPALIAS', 3);
62

    
63
/* kill a process by pid file */
64
function killbypid($pidfile) {
65
	return sigkillbypid($pidfile, "TERM");
66
}
67

    
68
function isvalidpid($pidfile) {
69
	$output = "";
70
	if (file_exists($pidfile)) {
71
		exec("/bin/pgrep -nF {$pidfile}", $output, $retval);
72
		return (intval($retval) == 0);
73
	}
74
	return false;
75
}
76

    
77
function is_process_running($process) {
78
	$output = "";
79
	exec("/bin/pgrep -anx " . escapeshellarg($process), $output, $retval);
80

    
81
	return (intval($retval) == 0);
82
}
83

    
84
function isvalidproc($proc) {
85
	return is_process_running($proc);
86
}
87

    
88
/* sigkill a process by pid file */
89
/* return 1 for success and 0 for a failure */
90
function sigkillbypid($pidfile, $sig) {
91
	if (file_exists($pidfile)) {
92
		return mwexec("/bin/pkill " . escapeshellarg("-{$sig}") . " -F {$pidfile}", true);
93
	}
94

    
95
	return 0;
96
}
97

    
98
/* kill a process by name */
99
function sigkillbyname($procname, $sig) {
100
	if (isvalidproc($procname)) {
101
		return mwexec("/usr/bin/killall " . escapeshellarg("-{$sig}") . " " . escapeshellarg($procname), true);
102
	}
103
}
104

    
105
/* kill a process by name */
106
function killbyname($procname) {
107
	if (isvalidproc($procname)) {
108
		mwexec("/usr/bin/killall " . escapeshellarg($procname));
109
	}
110
}
111

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

    
115
	if ($subsystem == "") {
116
		return false;
117
	}
118

    
119
	if (file_exists("{$g['varrun_path']}/{$subsystem}.dirty")) {
120
		return true;
121
	}
122

    
123
	return false;
124
}
125

    
126
function mark_subsystem_dirty($subsystem = "") {
127
	global $g;
128

    
129
	if (!file_put_contents("{$g['varrun_path']}/{$subsystem}.dirty", "DIRTY")) {
130
		log_error(sprintf(gettext("WARNING: Could not mark subsystem: %s dirty"), $subsystem));
131
	}
132
}
133

    
134
function clear_subsystem_dirty($subsystem = "") {
135
	global $g;
136

    
137
	@unlink("{$g['varrun_path']}/{$subsystem}.dirty");
138
}
139

    
140
function config_lock() {
141
	return;
142
}
143
function config_unlock() {
144
	return;
145
}
146

    
147
/* lock configuration file */
148
function lock($lock, $op = LOCK_SH) {
149
	global $g, $cfglckkeyconsumers;
150
	if (!$lock) {
151
		die(gettext("WARNING: You must give a name as parameter to lock() function."));
152
	}
153
	if (!file_exists("{$g['tmp_path']}/{$lock}.lock")) {
154
		@touch("{$g['tmp_path']}/{$lock}.lock");
155
		@chmod("{$g['tmp_path']}/{$lock}.lock", 0666);
156
	}
157
	$cfglckkeyconsumers++;
158
	if ($fp = fopen("{$g['tmp_path']}/{$lock}.lock", "w")) {
159
		if (flock($fp, $op)) {
160
			return $fp;
161
		} else {
162
			fclose($fp);
163
		}
164
	}
165
}
166

    
167
function try_lock($lock, $timeout = 5) {
168
	global $g, $cfglckkeyconsumers;
169
	if (!$lock) {
170
		die(gettext("WARNING: You must give a name as parameter to try_lock() function."));
171
	}
172
	if (!file_exists("{$g['tmp_path']}/{$lock}.lock")) {
173
		@touch("{$g['tmp_path']}/{$lock}.lock");
174
		@chmod("{$g['tmp_path']}/{$lock}.lock", 0666);
175
	}
176
	$cfglckkeyconsumers++;
177
	if ($fp = fopen("{$g['tmp_path']}/{$lock}.lock", "w")) {
178
		$trycounter = 0;
179
		while (!flock($fp, LOCK_EX | LOCK_NB)) {
180
			if ($trycounter >= $timeout) {
181
				fclose($fp);
182
				return NULL;
183
			}
184
			sleep(1);
185
			$trycounter++;
186
		}
187

    
188
		return $fp;
189
	}
190

    
191
	return NULL;
192
}
193

    
194
/* unlock configuration file */
195
function unlock($cfglckkey = 0) {
196
	global $g, $cfglckkeyconsumers;
197
	flock($cfglckkey, LOCK_UN);
198
	fclose($cfglckkey);
199
	return;
200
}
201

    
202
/* unlock forcefully configuration file */
203
function unlock_force($lock) {
204
	global $g;
205

    
206
	@unlink("{$g['tmp_path']}/{$lock}.lock");
207
}
208

    
209
function send_event($cmd) {
210
	global $g;
211

    
212
	if (!isset($g['event_address'])) {
213
		$g['event_address'] = "unix:///var/run/check_reload_status";
214
	}
215

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

    
234
function send_multiple_events($cmds) {
235
	global $g;
236

    
237
	if (!isset($g['event_address'])) {
238
		$g['event_address'] = "unix:///var/run/check_reload_status";
239
	}
240

    
241
	if (!is_array($cmds)) {
242
		return;
243
	}
244

    
245
	while ($try < 3) {
246
		$fd = @fsockopen($g['event_address']);
247
		if ($fd) {
248
			foreach ($cmds as $cmd) {
249
				fwrite($fd, $cmd);
250
				$resp = fread($fd, 4096);
251
				if ($resp != "OK\n") {
252
					log_error("send_event: sent {$cmd} got {$resp}");
253
				}
254
			}
255
			fclose($fd);
256
			$try = 3;
257
		} else if (!is_process_running("check_reload_status")) {
258
			mwexec_bg("/usr/bin/nice -n20 /usr/local/sbin/check_reload_status");
259
		}
260
		$try++;
261
	}
262
}
263

    
264
function refcount_init($reference) {
265
	$shmid = @shmop_open($reference, "c", 0644, 10);
266
	@shmop_write($shmid, str_pad("0", 10, "\x0", STR_PAD_RIGHT), 0);
267
	@shmop_close($shmid);
268
}
269

    
270
function refcount_reference($reference) {
271
	/* Take out a lock across the shared memory read, increment, write sequence to make it atomic. */
272
	$shm_lck = lock("shm{$reference}", LOCK_EX);
273
	try {
274
		/* NOTE: A warning is generated when shared memory does not exist */
275
		$shmid = @shmop_open($reference, "w", 0, 0);
276
		if (!$shmid) {
277
			refcount_init($reference);
278
			$shmid = @shmop_open($reference, "w", 0, 0);
279
			if (!$shmid) {
280
				log_error(sprintf(gettext("Could not open shared memory %s"), $reference));
281
				unlock($shm_lck);
282
				return;
283
			}
284
		}
285
		$shm_data = @shmop_read($shmid, 0, 10);
286
		$shm_data = intval($shm_data) + 1;
287
		@shmop_write($shmid, str_pad($shm_data, 10, "\x0", STR_PAD_RIGHT), 0);
288
		@shmop_close($shmid);
289
		unlock($shm_lck);
290
	} catch (Exception $e) {
291
		log_error($e->getMessage());
292
		unlock($shm_lck);
293
	}
294

    
295
	return $shm_data;
296
}
297

    
298
function refcount_unreference($reference) {
299
	/* Take out a lock across the shared memory read, decrement, write sequence to make it atomic. */
300
	$shm_lck = lock("shm{$reference}", LOCK_EX);
301
	try {
302
		$shmid = @shmop_open($reference, "w", 0, 0);
303
		if (!$shmid) {
304
			refcount_init($reference);
305
			log_error(sprintf(gettext("Could not open shared memory %s"), $reference));
306
			unlock($shm_lck);
307
			return;
308
		}
309
		$shm_data = @shmop_read($shmid, 0, 10);
310
		$shm_data = intval($shm_data) - 1;
311
		if ($shm_data < 0) {
312
			//debug_backtrace();
313
			log_error(sprintf(gettext("Reference %s is going negative, not doing unreference."), $reference));
314
		} else {
315
			@shmop_write($shmid, str_pad($shm_data, 10, "\x0", STR_PAD_RIGHT), 0);
316
		}
317
		@shmop_close($shmid);
318
		unlock($shm_lck);
319
	} catch (Exception $e) {
320
		log_error($e->getMessage());
321
		unlock($shm_lck);
322
	}
323

    
324
	return $shm_data;
325
}
326

    
327
function refcount_read($reference) {
328
	/* This function just reads the current value of the refcount for information. */
329
	/* There is no need for locking. */
330
	$shmid = @shmop_open($reference, "a", 0, 0);
331
	if (!$shmid) {
332
		log_error(sprintf(gettext("Could not open shared memory for read %s"), $reference));
333
		return -1;
334
	}
335
	$shm_data = @shmop_read($shmid, 0, 10);
336
	@shmop_close($shmid);
337
	return $shm_data;
338
}
339

    
340
function is_module_loaded($module_name) {
341
	$module_name = str_replace(".ko", "", $module_name);
342
	$running = 0;
343
	$_gb = exec("/sbin/kldstat -qn {$module_name} 2>&1", $_gb, $running);
344
	if (intval($running) == 0) {
345
		return true;
346
	} else {
347
		return false;
348
	}
349
}
350

    
351
/* validate non-negative numeric string, or equivalent numeric variable */
352
function is_numericint($arg) {
353
	return (((is_int($arg) && $arg >= 0) || (is_string($arg) && strlen($arg) > 0 && ctype_digit($arg))) ? true : false);
354
}
355

    
356
/* Generate the (human readable) ipv4 or ipv6 subnet address (i.e., netmask, or subnet start IP)
357
   given an (human readable) ipv4 or ipv6 host address and subnet bit count */
358
function gen_subnet($ipaddr, $bits) {
359
	if (($sn = gen_subnetv6($ipaddr, $bits)) == '') {
360
		$sn = gen_subnetv4($ipaddr, $bits);  // try to avoid rechecking IPv4/v6
361
	}
362
	return $sn;
363
}
364

    
365
/* same as gen_subnet() but accepts IPv4 only */
366
function gen_subnetv4($ipaddr, $bits) {
367
	if (is_ipaddrv4($ipaddr) && is_numericint($bits) && $bits <= 32) {
368
		if ($bits == 0) {
369
			return '0.0.0.0';  // avoids <<32
370
		}
371
		return long2ip(ip2long($ipaddr) & ((0xFFFFFFFF << (32 - $bits)) & 0xFFFFFFFF));
372
	}
373
	return "";
374
}
375

    
376
/* same as gen_subnet() but accepts IPv6 only */
377
function gen_subnetv6($ipaddr, $bits) {
378
	if (is_ipaddrv6($ipaddr) && is_numericint($bits) && $bits <= 128) {
379
		return Net_IPv6::compress(Net_IPv6::getNetmask($ipaddr, $bits));
380
	}
381
	return "";
382
}
383

    
384
/* Generate the (human readable) ipv4 or ipv6 subnet end address (i.e., highest address, end IP, or IPv4 broadcast address)
385
   given an (human readable) ipv4 or ipv6 host address and subnet bit count. */
386
function gen_subnet_max($ipaddr, $bits) {
387
	if (($sn = gen_subnetv6_max($ipaddr, $bits)) == '') {
388
		$sn = gen_subnetv4_max($ipaddr, $bits);  // try to avoid rechecking IPv4/v6
389
	}
390
	return $sn;
391
}
392

    
393
/* same as gen_subnet_max() but validates IPv4 only */
394
function gen_subnetv4_max($ipaddr, $bits) {
395
	if (is_ipaddrv4($ipaddr) && is_numericint($bits) && $bits <= 32) {
396
		if ($bits == 32) {
397
			return $ipaddr;
398
		}
399
		return long2ip32(ip2long($ipaddr) | (~gen_subnet_mask_long($bits) & 0xFFFFFFFF));
400
	}
401
	return "";
402
}
403

    
404
/* same as gen_subnet_max() but validates IPv6 only */
405
function gen_subnetv6_max($ipaddr, $bits) {
406
	if (is_ipaddrv6($ipaddr) && is_numericint($bits) && $bits <= 128) {
407
		$endip_bin = substr(Net_IPv6::_ip2Bin($ipaddr), 0, $bits) . str_repeat('1', 128 - $bits);
408
		return Net_IPv6::compress(Net_IPv6::_bin2Ip($endip_bin));
409
	}
410
	return "";
411
}
412

    
413
/* returns a subnet mask (long given a bit count) */
414
function gen_subnet_mask_long($bits) {
415
	$sm = 0;
416
	for ($i = 0; $i < $bits; $i++) {
417
		$sm >>= 1;
418
		$sm |= 0x80000000;
419
	}
420
	return $sm;
421
}
422

    
423
/* same as above but returns a string */
424
function gen_subnet_mask($bits) {
425
	return long2ip(gen_subnet_mask_long($bits));
426
}
427

    
428
/* Convert a prefix length to an IPv6 address-like mask notation. Very rare but at least ntp needs it. See #4463 */
429
function gen_subnet_mask_v6($bits) {
430
	/* Binary representation of the prefix length */
431
	$bin = str_repeat('1', $bits);
432
	/* Pad right with zeroes to reach the full address length */
433
	$bin = str_pad($bin, 128, '0', STR_PAD_RIGHT);
434
	/* Convert back to an IPv6 address style notation */
435
	return Net_IPv6::_bin2Ip($bin);
436
}
437

    
438
/* Convert long int to IPv4 address
439
   Returns '' if not valid IPv4 (including if any bits >32 are non-zero) */
440
function long2ip32($ip) {
441
	return long2ip($ip & 0xFFFFFFFF);
442
}
443

    
444
/* Convert IPv4 address to long int, truncated to 32-bits to avoid sign extension on 64-bit platforms.
445
   Returns '' if not valid IPv4. */
446
function ip2long32($ip) {
447
	return (ip2long($ip) & 0xFFFFFFFF);
448
}
449

    
450
/* Convert IPv4 address to unsigned long int.
451
   Returns '' if not valid IPv4. */
452
function ip2ulong($ip) {
453
	return sprintf("%u", ip2long32($ip));
454
}
455

    
456
/* Find out how many IPs are contained within a given IP range
457
 *  e.g. 192.168.0.0 to 192.168.0.255 returns 256
458
 */
459
function ip_range_size_v4($startip, $endip) {
460
	if (is_ipaddrv4($startip) && is_ipaddrv4($endip)) {
461
		// Operate as unsigned long because otherwise it wouldn't work
462
		//   when crossing over from 127.255.255.255 / 128.0.0.0 barrier
463
		return abs(ip2ulong($startip) - ip2ulong($endip)) + 1;
464
	}
465
	return -1;
466
}
467

    
468
/* Find the smallest possible subnet mask which can contain a given number of IPs
469
 *  e.g. 512 IPs can fit in a /23, but 513 IPs need a /22
470
 */
471
function find_smallest_cidr_v4($number) {
472
	$smallest = 1;
473
	for ($b=32; $b > 0; $b--) {
474
		$smallest = ($number <= pow(2, $b)) ? $b : $smallest;
475
	}
476
	return (32-$smallest);
477
}
478

    
479
/* Return the previous IP address before the given address */
480
function ip_before($ip, $offset = 1) {
481
	return long2ip32(ip2long($ip) - $offset);
482
}
483

    
484
/* Return the next IP address after the given address */
485
function ip_after($ip, $offset = 1) {
486
	return long2ip32(ip2long($ip) + $offset);
487
}
488

    
489
/* Return true if the first IP is 'before' the second */
490
function ip_less_than($ip1, $ip2) {
491
	// Compare as unsigned long because otherwise it wouldn't work when
492
	//   crossing over from 127.255.255.255 / 128.0.0.0 barrier
493
	return ip2ulong($ip1) < ip2ulong($ip2);
494
}
495

    
496
/* Return true if the first IP is 'after' the second */
497
function ip_greater_than($ip1, $ip2) {
498
	// Compare as unsigned long because otherwise it wouldn't work
499
	//   when crossing over from 127.255.255.255 / 128.0.0.0 barrier
500
	return ip2ulong($ip1) > ip2ulong($ip2);
501
}
502

    
503
/* compare two IP addresses */
504
function ipcmp($a, $b) {
505
	if (ip_less_than($a, $b)) {
506
		return -1;
507
	} else if (ip_greater_than($a, $b)) {
508
		return 1;
509
	} else {
510
		return 0;
511
	}
512
}
513

    
514
/* Convert a range of IPv4 addresses to an array of individual addresses. */
515
/* Note: IPv6 ranges are not yet supported here. */
516
function ip_range_to_address_array($startip, $endip, $max_size = 5000) {
517
	if (!is_ipaddrv4($startip) || !is_ipaddrv4($endip)) {
518
		return false;
519
	}
520

    
521
	if (ip_greater_than($startip, $endip)) {
522
		// Swap start and end so we can process sensibly.
523
		$temp = $startip;
524
		$startip = $endip;
525
		$endip = $temp;
526
	}
527

    
528
	if (ip_range_size_v4($startip, $endip) > $max_size) {
529
		return false;
530
	}
531

    
532
	// Container for IP addresses within this range.
533
	$rangeaddresses = array();
534
	$end_int = ip2ulong($endip);
535
	for ($ip_int = ip2ulong($startip); $ip_int <= $end_int; $ip_int++) {
536
		$rangeaddresses[] = long2ip($ip_int);
537
	}
538

    
539
	return $rangeaddresses;
540
}
541

    
542
/* 	Convert an IPv4 or IPv6 IP range to an array of subnets which can contain the range.
543
	Algorithm and embodying code PD'ed by Stilez - enjoy as you like :-)
544

    
545
	Documented on pfsense dev list 19-20 May 2013. Summary:
546

    
547
	The algorithm looks at patterns of 0's and 1's in the least significant bit(s), whether IPv4 or IPv6.
548
	These are all that needs checking to identify a _guaranteed_ correct, minimal and optimal subnet array.
549

    
550
	As a result, string/binary pattern matching of the binary IP is very efficient. It uses just 2 pattern-matching rules
551
	to chop off increasingly larger subnets at both ends that can't be part of larger subnets, until nothing's left.
552

    
553
	(a) If any range has EITHER low bit 1 (in startip) or 0 (in endip), that end-point is _always guaranteed_ to be optimally
554
	represented by its own 'single IP' CIDR; the remaining range then shrinks by one IP up or down, causing the new end-point's
555
	low bit to change from 1->0 (startip) or 0->1 (endip). Only one edge case needs checking: if a range contains exactly 2
556
	adjacent IPs of this format, then the two IPs themselves are required to span it, and we're done.
557
	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
558
	low bits can now be ignored.
559

    
560
	(b) If any range has BOTH startip and endip ending in some number of 0's and 1's respectively, these low bits can
561
	*always* be ignored and "bit-shifted" for subnet spanning. So provided we remember the bits we've place-shifted, we can
562
	_always_ right-shift and chop off those bits, leaving a smaller range that has EITHER startip ending in 1 or endip ending
563
	in 0 (ie can now apply (a) again) or the entire range has vanished and we're done.
564
	We then loop to redo (a) again on the remaining (place shifted) range until after a few loops, the remaining (place shifted)
565
	range 'vanishes' by meeting the exit criteria of (a) or (b), and we're done.
566
*/
567

    
568
function ip_range_to_subnet_array($ip1, $ip2) {
569

    
570
	if (is_ipaddrv4($ip1) && is_ipaddrv4($ip2)) {
571
		$proto = 'ipv4';  // for clarity
572
		$bits = 32;
573
		$ip1bin = decbin(ip2long32($ip1));
574
		$ip2bin = decbin(ip2long32($ip2));
575
	} elseif (is_ipaddrv6($ip1) && is_ipaddrv6($ip2)) {
576
		$proto = 'ipv6';
577
		$bits = 128;
578
		$ip1bin = Net_IPv6::_ip2Bin($ip1);
579
		$ip2bin = Net_IPv6::_ip2Bin($ip2);
580
	} else {
581
		return array();
582
	}
583

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

    
588
	if ($ip1bin == $ip2bin) {
589
		return array($ip1 . '/' . $bits); // exit if ip1=ip2 (trivial case)
590
	}
591

    
592
	if ($ip1bin > $ip2bin) {
593
		list ($ip1bin, $ip2bin) = array($ip2bin, $ip1bin);  // swap if needed (ensures ip1 < ip2)
594
	}
595

    
596
	$rangesubnets = array();
597
	$netsize = 0;
598

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

    
603
		// 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)
604

    
605
		if (substr($ip1bin, -1, 1) == '1') {
606
			// the start ip must be in a separate one-IP cidr range
607
			$new_subnet_ip = substr($ip1bin, $netsize, $bits - $netsize) . str_repeat('0', $netsize);
608
			$rangesubnets[$new_subnet_ip] = $bits - $netsize;
609
			$n = strrpos($ip1bin, '0');  //can't be all 1's
610
			$ip1bin = ($n == 0 ? '' : substr($ip1bin, 0, $n)) . '1' . str_repeat('0', $bits - $n - 1);  // BINARY VERSION OF $ip1 += 1
611
		}
612

    
613
		// 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)
614

    
615
		if (substr($ip2bin, -1, 1) == '0') {
616
			// the end ip must be in a separate one-IP cidr range
617
			$new_subnet_ip = substr($ip2bin, $netsize, $bits - $netsize) . str_repeat('0', $netsize);
618
			$rangesubnets[$new_subnet_ip] = $bits - $netsize;
619
			$n = strrpos($ip2bin, '1');  //can't be all 0's
620
			$ip2bin = ($n == 0 ? '' : substr($ip2bin, 0, $n)) . '0' . str_repeat('1', $bits - $n - 1);  // BINARY VERSION OF $ip2 -= 1
621
			// already checked for the edge case where end = start+1 and start ends in 0x1, above, so it's safe
622
		}
623

    
624
		// this is the only edge case arising from increment/decrement.
625
		// 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)
626

    
627
		if ($ip2bin < $ip1bin) {
628
			continue;
629
		}
630

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

    
634
		$shift = $bits - max(strrpos($ip1bin, '0'), strrpos($ip2bin, '1'));  // num of low bits which are '0' in ip1 and '1' in ip2
635
		$ip1bin = str_repeat('0', $shift) . substr($ip1bin, 0, $bits - $shift);
636
		$ip2bin = str_repeat('0', $shift) . substr($ip2bin, 0, $bits - $shift);
637
		$netsize += $shift;
638
		if ($ip1bin == $ip2bin) {
639
			// we're done.
640
			$new_subnet_ip = substr($ip1bin, $netsize, $bits - $netsize) . str_repeat('0', $netsize);
641
			$rangesubnets[$new_subnet_ip] = $bits - $netsize;
642
			continue;
643
		}
644

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

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

    
650
	ksort($rangesubnets, SORT_STRING);
651
	$out = array();
652

    
653
	foreach ($rangesubnets as $ip => $netmask) {
654
		if ($proto == 'ipv4') {
655
			$i = str_split($ip, 8);
656
			$out[] = implode('.', array(bindec($i[0]), bindec($i[1]), bindec($i[2]), bindec($i[3]))) . '/' . $netmask;
657
		} else {
658
			$out[] = Net_IPv6::compress(Net_IPv6::_bin2Ip($ip)) . '/' . $netmask;
659
		}
660
	}
661

    
662
	return $out;
663
}
664

    
665
/* returns true if $range is a valid pair of IPv4 or IPv6 addresses separated by a "-"
666
	false - if not a valid pair
667
	true (numeric 4 or 6) - if valid, gives type of addresses */
668
function is_iprange($range) {
669
	if (substr_count($range, '-') != 1) {
670
		return false;
671
	}
672
	list($ip1, $ip2) = explode ('-', $range);
673
	if (is_ipaddrv4($ip1) && is_ipaddrv4($ip2)) {
674
		return 4;
675
	}
676
	if (is_ipaddrv6($ip1) && is_ipaddrv6($ip2)) {
677
		return 6;
678
	}
679
	return false;
680
}
681

    
682
/* returns true if $ipaddr is a valid dotted IPv4 address or a IPv6
683
	false - not valid
684
	true (numeric 4 or 6) - if valid, gives type of address */
685
function is_ipaddr($ipaddr) {
686
	if (is_ipaddrv4($ipaddr)) {
687
		return 4;
688
	}
689
	if (is_ipaddrv6($ipaddr)) {
690
		return 6;
691
	}
692
	return false;
693
}
694

    
695
/* returns true if $ipaddr is a valid IPv6 address */
696
function is_ipaddrv6($ipaddr) {
697
	if (!is_string($ipaddr) || empty($ipaddr)) {
698
		return false;
699
	}
700
	if (strstr($ipaddr, "%") && is_linklocal($ipaddr)) {
701
		$tmpip = explode("%", $ipaddr);
702
		$ipaddr = $tmpip[0];
703
	}
704
	return Net_IPv6::checkIPv6($ipaddr);
705
}
706

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

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

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

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

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

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

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

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

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

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

    
798
}
799

    
800
/* returns true if $subnet is a valid IPv4 or IPv6 subnet in CIDR format
801
	false - if not a valid subnet
802
	true (numeric 4 or 6) - if valid, gives type of subnet */
803
function is_subnet($subnet) {
804
	if (is_string($subnet) && preg_match('/^(?:([0-9.]{7,15})|([0-9a-f:]{2,39}))\/(\d{1,3})$/i', $subnet, $parts)) {
805
		if (is_ipaddrv4($parts[1]) && $parts[3] <= 32) {
806
			return 4;
807
		}
808
		if (is_ipaddrv6($parts[2]) && $parts[3] <= 128) {
809
			return 6;
810
		}
811
	}
812
	return false;
813
}
814

    
815
/* same as is_subnet() but accepts IPv4 only */
816
function is_subnetv4($subnet) {
817
	return (is_subnet($subnet) == 4);
818
}
819

    
820
/* same as is_subnet() but accepts IPv6 only */
821
function is_subnetv6($subnet) {
822
	return (is_subnet($subnet) == 6);
823
}
824

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

    
829
	if (isset($aliastable[$subnet]) && is_subnet($aliastable[$subnet])) {
830
		return true;
831
	} else {
832
		return is_subnet($subnet);
833
	}
834
}
835

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

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

    
867
	// 2**N returns an exact result as an INT if possible, and a float/double if not. 
868
	// Detect this switch, rather than comparing $result<=PHP_MAX_INT or $bits >=8*PHP_INT_SIZE as it's (probably) easier to get completely reliable
869
	$result = 2 ** $snsize;
870
	
871
	if ($exact && !is_int($result)) {
872
		//exact required but can't represent result exactly as an INT
873
		return 0;
874
	} else {
875
		// result ok, will be an INT where possible (guaranteed up to 2^31 addresses on x32/x64) and a float for 'huge' subnets
876
		return $result;
877
	}
878
}
879

    
880

    
881
function subnet_expand($subnet) {
882
	if (is_subnetv4($subnet)) {
883
		return subnetv4_expand($subnet);
884
	} else if (is_subnetv6($subnet)) {
885
		return subnetv6_expand($subnet);
886
	} else {
887
		return $subnet;
888
	}
889
}
890

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

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

    
914
/* find out whether two IPv4 CIDR subnets overlap.
915
   Note: CIDR overlap means sn1/sn2 are identical or one is included in other. So sn using largest $bits will be the same  */
916
function check_subnetsv4_overlap($subnet1, $bits1, $subnet2, $bits2) {
917
	$largest_sn = min($bits1, $bits2);
918
	$subnetv4_start1 = gen_subnetv4($subnet1, $largest_sn);
919
	$subnetv4_start2 = gen_subnetv4($subnet2, $largest_sn);
920
	
921
	if($subnetv4_start1 == '' || $subnetv4_start2 == '') {
922
		// One or both args is not a valid IPv4 subnet
923
		//FIXME: needs to return "bad data" not true/false if bad. For now return false, best we can do until fixed
924
		return false;
925
	}
926
	return ($subnetv4_start1 == $subnetv4_start2);
927
}
928

    
929
/* find out whether two IPv6 CIDR subnets overlap.
930
   Note: CIDR overlap means sn1/sn2 are identical or one is included in other. So sn using largest $bits will be the same  */
931
function check_subnetsv6_overlap($subnet1, $bits1, $subnet2, $bits2) {
932
	$largest_sn = min($bits1, $bits2);
933
	$subnetv6_start1 = gen_subnetv6($subnet1, $largest_sn);
934
	$subnetv6_start2 = gen_subnetv6($subnet2, $largest_sn);
935
	
936
	if($subnetv6_start1 == '' || $subnetv6_start2 == '') {
937
		// One or both args is not a valid IPv6 subnet
938
		//FIXME: needs to return "bad data" not true/false if bad. For now return false, best we can do until fixed
939
		return false;
940
	}
941
	return ($subnetv6_start1 == $subnetv6_start2);
942
}
943

    
944
/* return all PTR zones for a IPv6 network */
945
function get_v6_ptr_zones($subnet, $bits) {
946
	$result = array();
947

    
948
	if (!is_ipaddrv6($subnet)) {
949
		return $result;
950
	}
951

    
952
	if (!is_numericint($bits) || $bits > 128) {
953
		return $result;
954
	}
955

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

    
965
	/* Get network prefix */
966
	$small_subnet = Net_IPv6::getNetmask($subnet, $bits);
967

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

    
979
		/* Detect what part of IP should be increased */
980
		$change_part = (int) ($small_sn / 16);
981
		if ($small_sn % 16 == 0) {
982
			$change_part--;
983
		}
984

    
985
		/* Increase 1 to desired part */
986
		$parts = explode(":", Net_IPv6::uncompress($small_subnet));
987
		$parts[$change_part]++;
988
		$small_subnet = implode(":", $parts);
989
	}
990

    
991
	return $result;
992
}
993

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

    
1006
/* returns true if $hostname is just a valid hostname (top part without any of the domain part) */
1007
function is_unqualified_hostname($hostname) {
1008
	if (!is_string($hostname)) {
1009
		return false;
1010
	}
1011

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

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

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

    
1037
/* returns true if $domain is a valid domain name */
1038
function is_domain($domain, $allow_wildcard=false) {
1039
	if (!is_string($domain)) {
1040
		return false;
1041
	}
1042
	if ($allow_wildcard) {
1043
		$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';
1044
	} else {
1045
		$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';
1046
	}
1047

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

    
1055
/* returns true if $macaddr is a valid MAC address */
1056
function is_macaddr($macaddr, $partial=false) {
1057
	$repeat = ($partial) ? '1,5' : '5';
1058
	return preg_match('/^[0-9A-F]{2}(?:[:][0-9A-F]{2}){'.$repeat.'}$/i', $macaddr) == 1 ? true : false;
1059
}
1060

    
1061
/* returns true if $name is a valid name for an alias
1062
   returns NULL if a reserved word is used
1063
   returns FALSE for bad chars in the name - this allows calling code to determine what the problem was.
1064
   aliases cannot be:
1065
	bad chars: anything except a-z 0-9 and underscore
1066
	bad names: empty string, pure numeric, pure underscore
1067
	reserved words: pre-defined service/protocol/port names which should not be ambiguous, and the words "port" and  "pass" */
1068

    
1069
function is_validaliasname($name) {
1070
	/* Array of reserved words */
1071
	$reserved = array("port", "pass");
1072

    
1073
	if (!is_string($name) || strlen($name) >= 32 || preg_match('/(^_*$|^\d*$|[^a-z0-9_])/i', $name)) {
1074
		return false;
1075
	}
1076
	if (in_array($name, $reserved, true) || getservbyname($name, "tcp") || getservbyname($name, "udp") || getprotobyname($name)) {
1077
		return; /* return NULL */
1078
	}
1079
	return true;
1080
}
1081

    
1082
/* returns true if $port is a valid TCP/UDP port */
1083
function is_port($port) {
1084
	if (ctype_digit($port) && ((intval($port) >= 1) && (intval($port) <= 65535))) {
1085
		return true;
1086
	}
1087
	if (getservbyname($port, "tcp") || getservbyname($port, "udp")) {
1088
		return true;
1089
	}
1090
	return false;
1091
}
1092

    
1093
/* returns true if $portrange is a valid TCP/UDP portrange ("<port>:<port>") */
1094
function is_portrange($portrange) {
1095
	$ports = explode(":", $portrange);
1096

    
1097
	return (count($ports) == 2 && is_port($ports[0]) && is_port($ports[1]));
1098
}
1099

    
1100
/* returns true if $port is a valid port number or an alias thereof */
1101
function is_portoralias($port) {
1102
	global $config;
1103

    
1104
	if (is_alias($port)) {
1105
		if (is_array($config['aliases']['alias'])) {
1106
			foreach ($config['aliases']['alias'] as $alias) {
1107
				if ($alias['name'] == $port && preg_match("/port/i", $alias['type'])) {
1108
					return true;
1109
				}
1110
			}
1111
		}
1112
		return false;
1113
	} else {
1114
		return is_port($port);
1115
	}
1116
}
1117

    
1118
/* create ranges of sequential port numbers (200:215) and remove duplicates */
1119
function group_ports($ports, $kflc = false) {
1120
	if (!is_array($ports) || empty($ports)) {
1121
		return;
1122
	}
1123

    
1124
	$uniq = array();
1125
	$comments = array();
1126
	foreach ($ports as $port) {
1127
		if (($kflc) && (strpos($port, '#') === 0)) {	// Keep Full Line Comments (lines beginning with #).
1128
			$comments[] = $port;
1129
		} else if (is_portrange($port)) {
1130
			list($begin, $end) = explode(":", $port);
1131
			if ($begin > $end) {
1132
				$aux = $begin;
1133
				$begin = $end;
1134
				$end = $aux;
1135
			}
1136
			for ($i = $begin; $i <= $end; $i++) {
1137
				if (!in_array($i, $uniq)) {
1138
					$uniq[] = $i;
1139
				}
1140
			}
1141
		} else if (is_port($port)) {
1142
			if (!in_array($port, $uniq)) {
1143
				$uniq[] = $port;
1144
			}
1145
		}
1146
	}
1147
	sort($uniq, SORT_NUMERIC);
1148

    
1149
	$result = array();
1150
	foreach ($uniq as $idx => $port) {
1151
		if ($idx == 0) {
1152
			$result[] = $port;
1153
			continue;
1154
		}
1155

    
1156
		$last = end($result);
1157
		if (is_portrange($last)) {
1158
			list($begin, $end) = explode(":", $last);
1159
		} else {
1160
			$begin = $end = $last;
1161
		}
1162

    
1163
		if ($port == ($end+1)) {
1164
			$end++;
1165
			$result[count($result)-1] = "{$begin}:{$end}";
1166
		} else {
1167
			$result[] = $port;
1168
		}
1169
	}
1170

    
1171
	return array_merge($comments, $result);
1172
}
1173

    
1174
/* returns true if $val is a valid shaper bandwidth value */
1175
function is_valid_shaperbw($val) {
1176
	return (preg_match("/^(\d+(?:\.\d+)?)([MKG]?b|%)$/", $val));
1177
}
1178

    
1179
/* returns true if $test is in the range between $start and $end */
1180
function is_inrange_v4($test, $start, $end) {
1181
	if ((ip2ulong($test) <= ip2ulong($end)) && (ip2ulong($test) >= ip2ulong($start))) {
1182
		return true;
1183
	} else {
1184
		return false;
1185
	}
1186
}
1187

    
1188
/* returns true if $test is in the range between $start and $end */
1189
function is_inrange_v6($test, $start, $end) {
1190
	if ((inet_pton($test) <= inet_pton($end)) && (inet_pton($test) >= inet_pton($start))) {
1191
		return true;
1192
	} else {
1193
		return false;
1194
	}
1195
}
1196

    
1197
/* returns true if $test is in the range between $start and $end */
1198
function is_inrange($test, $start, $end) {
1199
	return is_ipaddrv6($test) ? is_inrange_v6($test, $start, $end) : is_inrange_v4($test, $start, $end);
1200
}
1201

    
1202
function get_configured_vip_list($family = 'all', $type = VIP_ALL) {
1203
	global $config;
1204

    
1205
	$list = array();
1206
	if (!is_array($config['virtualip']['vip']) || empty($config['virtualip']['vip'])) {
1207
		return ($list);
1208
	}
1209

    
1210
	$viparr = &$config['virtualip']['vip'];
1211
	foreach ($viparr as $vip) {
1212

    
1213
		if ($type == VIP_CARP) {
1214
			if ($vip['mode'] != "carp")
1215
				continue;
1216
		} elseif ($type == VIP_IPALIAS) {
1217
			if ($vip['mode'] != "ipalias")
1218
				continue;
1219
		} else {
1220
			if ($vip['mode'] != "carp" && $vip['mode'] != "ipalias")
1221
				continue;
1222
		}
1223

    
1224
		if ($family == 'all' ||
1225
		    ($family == 'inet' && is_ipaddrv4($vip['subnet'])) ||
1226
		    ($family == 'inet6' && is_ipaddrv6($vip['subnet']))) {
1227
			$list["_vip{$vip['uniqid']}"] = $vip['subnet'];
1228
		}
1229
	}
1230
	return ($list);
1231
}
1232

    
1233
function get_configured_vip($vipinterface = '') {
1234

    
1235
	return (get_configured_vip_detail($vipinterface, 'all', 'vip'));
1236
}
1237

    
1238
function get_configured_vip_interface($vipinterface = '') {
1239

    
1240
	return (get_configured_vip_detail($vipinterface, 'all', 'iface'));
1241
}
1242

    
1243
function get_configured_vip_ipv4($vipinterface = '') {
1244

    
1245
	return (get_configured_vip_detail($vipinterface, 'inet', 'ip'));
1246
}
1247

    
1248
function get_configured_vip_ipv6($vipinterface = '') {
1249

    
1250
	return (get_configured_vip_detail($vipinterface, 'inet6', 'ip'));
1251
}
1252

    
1253
function get_configured_vip_subnetv4($vipinterface = '') {
1254

    
1255
	return (get_configured_vip_detail($vipinterface, 'inet', 'subnet'));
1256
}
1257

    
1258
function get_configured_vip_subnetv6($vipinterface = '') {
1259

    
1260
	return (get_configured_vip_detail($vipinterface, 'inet6', 'subnet'));
1261
}
1262

    
1263
function get_configured_vip_detail($vipinterface = '', $family = 'inet', $what = 'ip') {
1264
	global $config;
1265

    
1266
	if (empty($vipinterface) || !is_array($config['virtualip']['vip']) ||
1267
	    empty($config['virtualip']['vip'])) {
1268
		return (NULL);
1269
	}
1270

    
1271
	$viparr = &$config['virtualip']['vip'];
1272
	foreach ($viparr as $vip) {
1273
		if ($vip['mode'] != "carp" && $vip['mode'] != "ipalias") {
1274
			continue;
1275
		}
1276

    
1277
		if ($vipinterface != "_vip{$vip['uniqid']}") {
1278
			continue;
1279
		}
1280

    
1281
		switch ($what) {
1282
			case 'subnet':
1283
				if ($family == 'inet' && is_ipaddrv4($vip['subnet']))
1284
					return ($vip['subnet_bits']);
1285
				else if ($family == 'inet6' && is_ipaddrv6($vip['subnet']))
1286
					return ($vip['subnet_bits']);
1287
				break;
1288
			case 'iface':
1289
				return ($vip['interface']);
1290
				break;
1291
			case 'vip':
1292
				return ($vip);
1293
				break;
1294
			case 'ip':
1295
			default:
1296
				if ($family == 'inet' && is_ipaddrv4($vip['subnet'])) {
1297
					return ($vip['subnet']);
1298
				} else if ($family == 'inet6' && is_ipaddrv6($vip['subnet'])) {
1299
					return ($vip['subnet']);
1300
				}
1301
				break;
1302
		}
1303
		break;
1304
	}
1305

    
1306
	return (NULL);
1307
}
1308

    
1309
/* comparison function for sorting by the order in which interfaces are normally created */
1310
function compare_interface_friendly_names($a, $b) {
1311
	if ($a == $b) {
1312
		return 0;
1313
	} else if ($a == 'wan') {
1314
		return -1;
1315
	} else if ($b == 'wan') {
1316
		return 1;
1317
	} else if ($a == 'lan') {
1318
		return -1;
1319
	} else if ($b == 'lan') {
1320
		return 1;
1321
	}
1322

    
1323
	return strnatcmp($a, $b);
1324
}
1325

    
1326
/* return the configured interfaces list. */
1327
function get_configured_interface_list($only_opt = false, $withdisabled = false) {
1328
	global $config;
1329

    
1330
	$iflist = array();
1331

    
1332
	/* if list */
1333
	foreach ($config['interfaces'] as $if => $ifdetail) {
1334
		if ($only_opt && ($if == "wan" || $if == "lan")) {
1335
			continue;
1336
		}
1337
		if (isset($ifdetail['enable']) || $withdisabled == true) {
1338
			$iflist[$if] = $if;
1339
		}
1340
	}
1341

    
1342
	return $iflist;
1343
}
1344

    
1345
/* return the configured interfaces list. */
1346
function get_configured_interface_list_by_realif($only_opt = false, $withdisabled = false) {
1347
	global $config;
1348

    
1349
	$iflist = array();
1350

    
1351
	/* if list */
1352
	foreach ($config['interfaces'] as $if => $ifdetail) {
1353
		if ($only_opt && ($if == "wan" || $if == "lan")) {
1354
			continue;
1355
		}
1356
		if (isset($ifdetail['enable']) || $withdisabled == true) {
1357
			$tmpif = get_real_interface($if);
1358
			if (!empty($tmpif)) {
1359
				$iflist[$tmpif] = $if;
1360
			}
1361
		}
1362
	}
1363

    
1364
	return $iflist;
1365
}
1366

    
1367
/* return the configured interfaces list with their description. */
1368
function get_configured_interface_with_descr($only_opt = false, $withdisabled = false) {
1369
	global $config;
1370

    
1371
	$iflist = array();
1372

    
1373
	/* if list */
1374
	foreach ($config['interfaces'] as $if => $ifdetail) {
1375
		if ($only_opt && ($if == "wan" || $if == "lan")) {
1376
			continue;
1377
		}
1378
		if (isset($ifdetail['enable']) || $withdisabled == true) {
1379
			if (empty($ifdetail['descr'])) {
1380
				$iflist[$if] = strtoupper($if);
1381
			} else {
1382
				$iflist[$if] = strtoupper($ifdetail['descr']);
1383
			}
1384
		}
1385
	}
1386

    
1387
	return $iflist;
1388
}
1389

    
1390
/*
1391
 *   get_configured_ip_addresses() - Return a list of all configured
1392
 *   IPv4 addresses.
1393
 *
1394
 */
1395
function get_configured_ip_addresses() {
1396
	global $config;
1397

    
1398
	if (!function_exists('get_interface_ip')) {
1399
		require_once("interfaces.inc");
1400
	}
1401
	$ip_array = array();
1402
	$interfaces = get_configured_interface_list();
1403
	if (is_array($interfaces)) {
1404
		foreach ($interfaces as $int) {
1405
			$ipaddr = get_interface_ip($int);
1406
			$ip_array[$int] = $ipaddr;
1407
		}
1408
	}
1409
	$interfaces = get_configured_vip_list('inet');
1410
	if (is_array($interfaces)) {
1411
		foreach ($interfaces as $int => $ipaddr) {
1412
			$ip_array[$int] = $ipaddr;
1413
		}
1414
	}
1415

    
1416
	/* pppoe server */
1417
	if (is_array($config['pppoes']) && is_array($config['pppoes']['pppoe'])) {
1418
		foreach ($config['pppoes']['pppoe'] as $pppoe) {
1419
			if ($pppoe['mode'] == "server") {
1420
				if (is_ipaddr($pppoe['localip'])) {
1421
					$int = "pppoes". $pppoe['pppoeid'];
1422
					$ip_array[$int] = $pppoe['localip'];
1423
				}
1424
			}
1425
		}
1426
	}
1427

    
1428
	return $ip_array;
1429
}
1430

    
1431
/*
1432
 *   get_configured_ipv6_addresses() - Return a list of all configured
1433
 *   IPv6 addresses.
1434
 *
1435
 */
1436
function get_configured_ipv6_addresses() {
1437
	require_once("interfaces.inc");
1438
	$ipv6_array = array();
1439
	$interfaces = get_configured_interface_list();
1440
	if (is_array($interfaces)) {
1441
		foreach ($interfaces as $int) {
1442
			$ipaddrv6 = get_interface_ipv6($int);
1443
			$ipv6_array[$int] = $ipaddrv6;
1444
		}
1445
	}
1446
	$interfaces = get_configured_vip_list('inet6');
1447
	if (is_array($interfaces)) {
1448
		foreach ($interfaces as $int => $ipaddrv6) {
1449
			$ipv6_array[$int] = $ipaddrv6;
1450
		}
1451
	}
1452
	return $ipv6_array;
1453
}
1454

    
1455
/*
1456
 *   get_interface_list() - Return a list of all physical interfaces
1457
 *   along with MAC and status.
1458
 *
1459
 *   $mode = "active" - use ifconfig -lu
1460
 *           "media"  - use ifconfig to check physical connection
1461
 *			status (much slower)
1462
 */
1463
function get_interface_list($mode = "active", $keyby = "physical", $vfaces = "") {
1464
	global $config;
1465
	$upints = array();
1466
	/* get a list of virtual interface types */
1467
	if (!$vfaces) {
1468
		$vfaces = array(
1469
				'bridge',
1470
				'ppp',
1471
				'pppoe',
1472
				'pptp',
1473
				'l2tp',
1474
				'sl',
1475
				'gif',
1476
				'gre',
1477
				'faith',
1478
				'lo',
1479
				'ng',
1480
				'_vlan',
1481
				'_wlan',
1482
				'pflog',
1483
				'plip',
1484
				'pfsync',
1485
				'enc',
1486
				'tun',
1487
				'lagg',
1488
				'vip',
1489
				'ipfw'
1490
		);
1491
	}
1492
	switch ($mode) {
1493
		case "active":
1494
			$upints = pfSense_interface_listget(IFF_UP);
1495
			break;
1496
		case "media":
1497
			$intlist = pfSense_interface_listget();
1498
			$ifconfig = "";
1499
			exec("/sbin/ifconfig -a", $ifconfig);
1500
			$regexp = '/(' . implode('|', $intlist) . '):\s/';
1501
			$ifstatus = preg_grep('/status:/', $ifconfig);
1502
			foreach ($ifstatus as $status) {
1503
				$int = array_shift($intlist);
1504
				if (stristr($status, "active")) {
1505
					$upints[] = $int;
1506
				}
1507
			}
1508
			break;
1509
		default:
1510
			$upints = pfSense_interface_listget();
1511
			break;
1512
	}
1513
	/* build interface list with netstat */
1514
	$linkinfo = "";
1515
	exec("/usr/bin/netstat -inW -f link | awk '{ print $1, $4 }'", $linkinfo);
1516
	array_shift($linkinfo);
1517
	/* build ip address list with netstat */
1518
	$ipinfo = "";
1519
	exec("/usr/bin/netstat -inW -f inet | awk '{ print $1, $4 }'", $ipinfo);
1520
	array_shift($ipinfo);
1521
	foreach ($linkinfo as $link) {
1522
		$friendly = "";
1523
		$alink = explode(" ", $link);
1524
		$ifname = rtrim(trim($alink[0]), '*');
1525
		/* trim out all numbers before checking for vfaces */
1526
		if (!in_array(array_shift(preg_split('/\d/', $ifname)), $vfaces) &&
1527
		    !stristr($ifname, "_vlan") && !stristr($ifname, "_wlan")) {
1528
			$toput = array(
1529
					"mac" => trim($alink[1]),
1530
					"up" => in_array($ifname, $upints)
1531
				);
1532
			foreach ($ipinfo as $ip) {
1533
				$aip = explode(" ", $ip);
1534
				if ($aip[0] == $ifname) {
1535
					$toput['ipaddr'] = $aip[1];
1536
				}
1537
			}
1538
			if (is_array($config['interfaces'])) {
1539
				foreach ($config['interfaces'] as $name => $int) {
1540
					if ($int['if'] == $ifname) {
1541
						$friendly = $name;
1542
					}
1543
				}
1544
			}
1545
			switch ($keyby) {
1546
			case "physical":
1547
				if ($friendly != "") {
1548
					$toput['friendly'] = $friendly;
1549
				}
1550
				$dmesg_arr = array();
1551
				exec("/sbin/dmesg |grep $ifname | head -n1", $dmesg_arr);
1552
				preg_match_all("/<(.*?)>/i", $dmesg_arr[0], $dmesg);
1553
				$toput['dmesg'] = $dmesg[1][0];
1554
				$iflist[$ifname] = $toput;
1555
				break;
1556
			case "ppp":
1557

    
1558
			case "friendly":
1559
				if ($friendly != "") {
1560
					$toput['if'] = $ifname;
1561
					$iflist[$friendly] = $toput;
1562
				}
1563
				break;
1564
			}
1565
		}
1566
	}
1567
	return $iflist;
1568
}
1569

    
1570
/****f* util/log_error
1571
* NAME
1572
*   log_error  - Sends a string to syslog.
1573
* INPUTS
1574
*   $error     - string containing the syslog message.
1575
* RESULT
1576
*   null
1577
******/
1578
function log_error($error) {
1579
	global $g;
1580
	$page = $_SERVER['SCRIPT_NAME'];
1581
	if (empty($page)) {
1582
		$files = get_included_files();
1583
		$page = basename($files[0]);
1584
	}
1585
	syslog(LOG_ERR, "$page: $error");
1586
	if ($g['debug']) {
1587
		syslog(LOG_WARNING, var_dump(debug_backtrace()));
1588
	}
1589
	return;
1590
}
1591

    
1592
/****f* util/log_auth
1593
* NAME
1594
*   log_auth   - Sends a string to syslog as LOG_AUTH facility
1595
* INPUTS
1596
*   $error     - string containing the syslog message.
1597
* RESULT
1598
*   null
1599
******/
1600
function log_auth($error) {
1601
	global $g;
1602
	$page = $_SERVER['SCRIPT_NAME'];
1603
	syslog(LOG_AUTH, "$page: $error");
1604
	if ($g['debug']) {
1605
		syslog(LOG_WARNING, var_dump(debug_backtrace()));
1606
	}
1607
	return;
1608
}
1609

    
1610
/****f* util/exec_command
1611
 * NAME
1612
 *   exec_command - Execute a command and return a string of the result.
1613
 * INPUTS
1614
 *   $command   - String of the command to be executed.
1615
 * RESULT
1616
 *   String containing the command's result.
1617
 * NOTES
1618
 *   This function returns the command's stdout and stderr.
1619
 ******/
1620
function exec_command($command) {
1621
	$output = array();
1622
	exec($command . ' 2>&1', $output);
1623
	return(implode("\n", $output));
1624
}
1625

    
1626
/* wrapper for exec()
1627
   Executes in background or foreground.
1628
   For background execution, returns PID of background process to allow calling code control */
1629
function mwexec($command, $nologentry = false, $clearsigmask = false, $background = false) {
1630
	global $g;
1631
	$retval = 0;
1632

    
1633
	if ($g['debug']) {
1634
		if (!$_SERVER['REMOTE_ADDR']) {
1635
			echo "mwexec(): $command" . ($background ? " [BG]":"") . "\n";
1636
		}
1637
	}
1638
	if ($clearsigmask) {
1639
		$oldset = array();
1640
		pcntl_sigprocmask(SIG_SETMASK, array(), $oldset);
1641
	}
1642

    
1643
	if ($background) {
1644
		// start background process and return PID
1645
		$retval = exec("/usr/bin/nohup $command > /dev/null 2>&1 & echo $!");
1646
	} else {
1647
		// run in foreground, and (optionally) log if nonzero return
1648
		$outputarray = array();
1649
		exec("$command 2>&1", $outputarray, $retval);
1650
		if (($retval <> 0) && (!$nologentry || isset($config['system']['developerspew']))) {
1651
			log_error(sprintf(gettext("The command '%1\$s' returned exit code '%2\$d', the output was '%3\$s' "), $command, $retval, implode(" ", $outputarray)));
1652
		}
1653
	}
1654

    
1655
	if ($clearsigmask) {
1656
		pcntl_sigprocmask(SIG_SETMASK, $oldset);
1657
	}
1658

    
1659
	return $retval;
1660
}
1661

    
1662
/* wrapper for exec() in background */
1663
function mwexec_bg($command, $clearsigmask = false) {
1664
	return mwexec($command, false, $clearsigmask, true);
1665
}
1666

    
1667
/* unlink a file, or pattern-match of a file, if it exists
1668
   if the file/path contains glob() compatible wildcards, all matching files will be unlinked
1669
   if no matches, no error occurs */
1670
function unlink_if_exists($fn) {
1671
	$to_do = glob($fn);
1672
	if (is_array($to_do) && count($to_do) > 0) {
1673
		@array_map("unlink", $to_do);
1674
	} else {
1675
		@unlink($fn);
1676
	}
1677
}
1678
/* make a global alias table (for faster lookups) */
1679
function alias_make_table($config) {
1680
	global $aliastable;
1681

    
1682
	$aliastable = array();
1683

    
1684
	if (is_array($config['aliases']['alias'])) {
1685
		foreach ($config['aliases']['alias'] as $alias) {
1686
			if ($alias['name']) {
1687
				$aliastable[$alias['name']] = $alias['address'];
1688
			}
1689
		}
1690
	}
1691
}
1692

    
1693
/* check if an alias exists */
1694
function is_alias($name) {
1695
	global $aliastable;
1696

    
1697
	return isset($aliastable[$name]);
1698
}
1699

    
1700
function alias_get_type($name) {
1701
	global $config;
1702

    
1703
	if (is_array($config['aliases']['alias'])) {
1704
		foreach ($config['aliases']['alias'] as $alias) {
1705
			if ($name == $alias['name']) {
1706
				return $alias['type'];
1707
			}
1708
		}
1709
	}
1710

    
1711
	return "";
1712
}
1713

    
1714
/* expand a host or network alias, if necessary */
1715
function alias_expand($name) {
1716
	global $config, $aliastable;
1717
	$urltable_prefix = "/var/db/aliastables/";
1718
	$urltable_filename = $urltable_prefix . $name . ".txt";
1719

    
1720
	if (isset($aliastable[$name])) {
1721
		// alias names cannot be strictly numeric. redmine #4289
1722
		if (is_numericint($name)) {
1723
			return null;
1724
		}
1725
		// make sure if it's a ports alias, it actually exists. redmine #5845
1726
		foreach ($config['aliases']['alias'] as $alias) {
1727
			if ($alias['name'] == $name) {
1728
				if ($alias['type'] == "urltable_ports") {
1729
					if (is_URL($alias['url']) && file_exists($urltable_filename) && filesize($urltable_filename)) {
1730
						return "\${$name}";
1731
					} else {
1732
						return null;
1733
					}
1734
				}
1735
			}
1736
		}
1737
		return "\${$name}";
1738
	} else if (is_ipaddr($name) || is_subnet($name) || is_port($name) || is_portrange($name)) {
1739
		return "{$name}";
1740
	} else {
1741
		return null;
1742
	}
1743
}
1744

    
1745
function alias_expand_urltable($name) {
1746
	global $config;
1747
	$urltable_prefix = "/var/db/aliastables/";
1748
	$urltable_filename = $urltable_prefix . $name . ".txt";
1749

    
1750
	if (is_array($config['aliases']['alias'])) {
1751
		foreach ($config['aliases']['alias'] as $alias) {
1752
			if (preg_match("/urltable/i", $alias['type']) && ($alias['name'] == $name)) {
1753
				if (is_URL($alias["url"]) && file_exists($urltable_filename) && filesize($urltable_filename)) {
1754
					return $urltable_filename;
1755
				} else {
1756
					send_event("service sync alias {$name}");
1757
					break;
1758
				}
1759
			}
1760
		}
1761
	}
1762
	return null;
1763
}
1764

    
1765
/* obtain MAC address given an IP address by looking at the ARP table */
1766
function arp_get_mac_by_ip($ip) {
1767
	mwexec("/sbin/ping -c 1 -t 1 " . escapeshellarg($ip), true);
1768
	$arpoutput = "";
1769
	exec("/usr/sbin/arp -n " . escapeshellarg($ip), $arpoutput);
1770

    
1771
	if ($arpoutput[0]) {
1772
		$arpi = explode(" ", $arpoutput[0]);
1773
		$macaddr = $arpi[3];
1774
		if (is_macaddr($macaddr)) {
1775
			return $macaddr;
1776
		} else {
1777
			return false;
1778
		}
1779
	}
1780

    
1781
	return false;
1782
}
1783

    
1784
/* return a fieldname that is safe for xml usage */
1785
function xml_safe_fieldname($fieldname) {
1786
	$replace = array('/', '-', ' ', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')',
1787
			 '_', '+', '=', '{', '}', '[', ']', '|', '/', '<', '>', '?',
1788
			 ':', ',', '.', '\'', '\\'
1789
		);
1790
	return strtolower(str_replace($replace, "", $fieldname));
1791
}
1792

    
1793
function mac_format($clientmac) {
1794
	global $config, $cpzone;
1795

    
1796
	$mac = explode(":", $clientmac);
1797
	$mac_format = $cpzone ? $config['captiveportal'][$cpzone]['radmac_format'] : false;
1798

    
1799
	switch ($mac_format) {
1800
		case 'singledash':
1801
			return "$mac[0]$mac[1]$mac[2]-$mac[3]$mac[4]$mac[5]";
1802

    
1803
		case 'ietf':
1804
			return "$mac[0]-$mac[1]-$mac[2]-$mac[3]-$mac[4]-$mac[5]";
1805

    
1806
		case 'cisco':
1807
			return "$mac[0]$mac[1].$mac[2]$mac[3].$mac[4]$mac[5]";
1808

    
1809
		case 'unformatted':
1810
			return "$mac[0]$mac[1]$mac[2]$mac[3]$mac[4]$mac[5]";
1811

    
1812
		default:
1813
			return $clientmac;
1814
	}
1815
}
1816

    
1817
function resolve_retry($hostname, $retries = 5) {
1818

    
1819
	if (is_ipaddr($hostname)) {
1820
		return $hostname;
1821
	}
1822

    
1823
	for ($i = 0; $i < $retries; $i++) {
1824
		// FIXME: gethostbyname does not work for AAAA hostnames, boo, hiss
1825
		$ip = gethostbyname($hostname);
1826

    
1827
		if ($ip && $ip != $hostname) {
1828
			/* success */
1829
			return $ip;
1830
		}
1831

    
1832
		sleep(1);
1833
	}
1834

    
1835
	return false;
1836
}
1837

    
1838
function format_bytes($bytes) {
1839
	if ($bytes >= 1099511627776) {
1840
		return sprintf("%.2f TiB", $bytes/1099511627776);
1841
	} else if ($bytes >= 1073741824) {
1842
		return sprintf("%.2f GiB", $bytes/1073741824);
1843
	} else if ($bytes >= 1048576) {
1844
		return sprintf("%.2f MiB", $bytes/1048576);
1845
	} else if ($bytes >= 1024) {
1846
		return sprintf("%.0f KiB", $bytes/1024);
1847
	} else {
1848
		return sprintf("%d B", $bytes);
1849
	}
1850
}
1851

    
1852
function format_number($num, $precision = 3) {
1853
	$units = array('', 'K', 'M', 'G', 'T');
1854

    
1855
	$i = 0;
1856
	while ($num > 1000 && $i < count($units)) {
1857
		$num /= 1000;
1858
		$i++;
1859
	}
1860
	round($num, $precision);
1861

    
1862
	return ("$num {$units[$i]}");
1863
}
1864

    
1865
function update_filter_reload_status($text) {
1866
	global $g;
1867

    
1868
	file_put_contents("{$g['varrun_path']}/filter_reload_status", $text);
1869
}
1870

    
1871
/****** util/return_dir_as_array
1872
 * NAME
1873
 *   return_dir_as_array - Return a directory's contents as an array.
1874
 * INPUTS
1875
 *   $dir          - string containing the path to the desired directory.
1876
 *   $filter_regex - string containing a regular expression to filter file names. Default empty.
1877
 * RESULT
1878
 *   $dir_array - array containing the directory's contents. This array will be empty if the path specified is invalid.
1879
 ******/
1880
function return_dir_as_array($dir, $filter_regex = '') {
1881
	$dir_array = array();
1882
	if (is_dir($dir)) {
1883
		if ($dh = opendir($dir)) {
1884
			while (($file = readdir($dh)) !== false) {
1885
				if (($file == ".") || ($file == "..")) {
1886
					continue;
1887
				}
1888

    
1889
				if (empty($filter_regex) || preg_match($filter_regex, $file)) {
1890
					array_push($dir_array, $file);
1891
				}
1892
			}
1893
			closedir($dh);
1894
		}
1895
	}
1896
	return $dir_array;
1897
}
1898

    
1899
function run_plugins($directory) {
1900
	global $config, $g;
1901

    
1902
	/* process packager manager custom rules */
1903
	$files = return_dir_as_array($directory);
1904
	if (is_array($files)) {
1905
		foreach ($files as $file) {
1906
			if (stristr($file, ".sh") == true) {
1907
				mwexec($directory . $file . " start");
1908
			} else if (!is_dir($directory . "/" . $file) && stristr($file, ".inc")) {
1909
				require_once($directory . "/" . $file);
1910
			}
1911
		}
1912
	}
1913
}
1914

    
1915
/*
1916
 *    safe_mkdir($path, $mode = 0755)
1917
 *    create directory if it doesn't already exist and isn't a file!
1918
 */
1919
function safe_mkdir($path, $mode = 0755) {
1920
	global $g;
1921

    
1922
	if (!is_file($path) && !is_dir($path)) {
1923
		return @mkdir($path, $mode, true);
1924
	} else {
1925
		return false;
1926
	}
1927
}
1928

    
1929
/*
1930
 * get_sysctl($names)
1931
 * Get values of sysctl OID's listed in $names (accepts an array or a single
1932
 * name) and return an array of key/value pairs set for those that exist
1933
 */
1934
function get_sysctl($names) {
1935
	if (empty($names)) {
1936
		return array();
1937
	}
1938

    
1939
	if (is_array($names)) {
1940
		$name_list = array();
1941
		foreach ($names as $name) {
1942
			$name_list[] = escapeshellarg($name);
1943
		}
1944
	} else {
1945
		$name_list = array(escapeshellarg($names));
1946
	}
1947

    
1948
	exec("/sbin/sysctl -i " . implode(" ", $name_list), $output);
1949
	$values = array();
1950
	foreach ($output as $line) {
1951
		$line = explode(": ", $line, 2);
1952
		if (count($line) == 2) {
1953
			$values[$line[0]] = $line[1];
1954
		}
1955
	}
1956

    
1957
	return $values;
1958
}
1959

    
1960
/*
1961
 * get_single_sysctl($name)
1962
 * Wrapper for get_sysctl() to simplify read of a single sysctl value
1963
 * return the value for sysctl $name or empty string if it doesn't exist
1964
 */
1965
function get_single_sysctl($name) {
1966
	if (empty($name)) {
1967
		return "";
1968
	}
1969

    
1970
	$value = get_sysctl($name);
1971
	if (empty($value) || !isset($value[$name])) {
1972
		return "";
1973
	}
1974

    
1975
	return $value[$name];
1976
}
1977

    
1978
/*
1979
 * set_sysctl($value_list)
1980
 * Set sysctl OID's listed as key/value pairs and return
1981
 * an array with keys set for those that succeeded
1982
 */
1983
function set_sysctl($values) {
1984
	if (empty($values)) {
1985
		return array();
1986
	}
1987

    
1988
	$value_list = array();
1989
	foreach ($values as $key => $value) {
1990
		$value_list[] = escapeshellarg($key) . "=" . escapeshellarg($value);
1991
	}
1992

    
1993
	exec("/sbin/sysctl -i " . implode(" ", $value_list), $output, $success);
1994

    
1995
	/* Retry individually if failed (one or more read-only) */
1996
	if ($success <> 0 && count($value_list) > 1) {
1997
		foreach ($value_list as $value) {
1998
			exec("/sbin/sysctl -i " . $value, $output);
1999
		}
2000
	}
2001

    
2002
	$ret = array();
2003
	foreach ($output as $line) {
2004
		$line = explode(": ", $line, 2);
2005
		if (count($line) == 2) {
2006
			$ret[$line[0]] = true;
2007
		}
2008
	}
2009

    
2010
	return $ret;
2011
}
2012

    
2013
/*
2014
 * set_single_sysctl($name, $value)
2015
 * Wrapper to set_sysctl() to make it simple to set only one sysctl
2016
 * returns boolean meaning if it succeeded
2017
 */
2018
function set_single_sysctl($name, $value) {
2019
	if (empty($name)) {
2020
		return false;
2021
	}
2022

    
2023
	$result = set_sysctl(array($name => $value));
2024

    
2025
	if (!isset($result[$name]) || $result[$name] != $value) {
2026
		return false;
2027
	}
2028

    
2029
	return true;
2030
}
2031

    
2032
/*
2033
 *     get_memory()
2034
 *     returns an array listing the amount of
2035
 *     memory installed in the hardware
2036
 *     [0] net memory available for the OS (FreeBSD) after some is taken by BIOS, video or whatever - e.g. 235 MBytes
2037
 *     [1] real (actual) memory of the system, should be the size of the RAM card/s - e.g. 256 MBytes
2038
 */
2039
function get_memory() {
2040
	$physmem = get_single_sysctl("hw.physmem");
2041
	$realmem = get_single_sysctl("hw.realmem");
2042
	/* convert from bytes to megabytes */
2043
	return array(($physmem/1048576), ($realmem/1048576));
2044
}
2045

    
2046
function mute_kernel_msgs() {
2047
	global $g, $config;
2048
	// Do not mute serial console.  The kernel gets very very cranky
2049
	// and will start dishing you cannot control tty errors.
2050
	if ($g['platform'] == 'nanobsd') {
2051
		return;
2052
	}
2053
	if ($config['system']['enableserial']) {
2054
		return;
2055
	}
2056
	exec("/sbin/conscontrol mute on");
2057
}
2058

    
2059
function unmute_kernel_msgs() {
2060
	global $g;
2061
	// Do not mute serial console.  The kernel gets very very cranky
2062
	// and will start dishing you cannot control tty errors.
2063
	if ($g['platform'] == 'nanobsd') {
2064
		return;
2065
	}
2066
	exec("/sbin/conscontrol mute off");
2067
}
2068

    
2069
function start_devd() {
2070
	/* Use the undocumented -q options of devd to quiet its log spamming */
2071
	$_gb = exec("/sbin/devd -q");
2072
	sleep(1);
2073
	unset($_gb);
2074
}
2075

    
2076
function is_interface_vlan_mismatch() {
2077
	global $config, $g;
2078

    
2079
	if (is_array($config['vlans']['vlan'])) {
2080
		foreach ($config['vlans']['vlan'] as $vlan) {
2081
			if (substr($vlan['if'], 0, 4) == "lagg") {
2082
				return false;
2083
			}
2084
			if (does_interface_exist($vlan['if']) == false) {
2085
				return true;
2086
			}
2087
		}
2088
	}
2089

    
2090
	return false;
2091
}
2092

    
2093
function is_interface_mismatch() {
2094
	global $config, $g;
2095

    
2096
	$do_assign = false;
2097
	$i = 0;
2098
	$missing_interfaces = array();
2099
	if (is_array($config['interfaces'])) {
2100
		foreach ($config['interfaces'] as $ifname => $ifcfg) {
2101
			if (preg_match("/^enc|^cua|^tun|^tap|^l2tp|^pptp|^ppp|^ovpn|^gif|^gre|^lagg|^bridge|vlan|_wlan/i", $ifcfg['if'])) {
2102
				// Do not check these interfaces.
2103
				$i++;
2104
				continue;
2105
			} else if (does_interface_exist($ifcfg['if']) == false) {
2106
				$missing_interfaces[] = $ifcfg['if'];
2107
				$do_assign = true;
2108
			} else {
2109
				$i++;
2110
			}
2111
		}
2112
	}
2113

    
2114
	if (file_exists("{$g['tmp_path']}/assign_complete")) {
2115
		$do_assign = false;
2116
	}
2117

    
2118
	if (!empty($missing_interfaces) && $do_assign) {
2119
		file_put_contents("{$g['tmp_path']}/missing_interfaces", implode(' ', $missing_interfaces));
2120
	} else {
2121
		@unlink("{$g['tmp_path']}/missing_interfaces");
2122
	}
2123

    
2124
	return $do_assign;
2125
}
2126

    
2127
/* sync carp entries to other firewalls */
2128
function carp_sync_client() {
2129
	global $g;
2130
	send_event("filter sync");
2131
}
2132

    
2133
/****f* util/isAjax
2134
 * NAME
2135
 *   isAjax - reports if the request is driven from prototype
2136
 * INPUTS
2137
 *   none
2138
 * RESULT
2139
 *   true/false
2140
 ******/
2141
function isAjax() {
2142
	return isset ($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest';
2143
}
2144

    
2145
/****f* util/timeout
2146
 * NAME
2147
 *   timeout - console input with timeout countdown. Note: erases 2 char of screen for timer. Leave space.
2148
 * INPUTS
2149
 *   optional, seconds to wait before timeout. Default 9 seconds.
2150
 * RESULT
2151
 *   returns 1 char of user input or null if no input.
2152
 ******/
2153
function timeout($timer = 9) {
2154
	while (!isset($key)) {
2155
		if ($timer >= 9) {
2156
			echo chr(8) . chr(8) . ($timer == 9 ? chr(32) : null) . "{$timer}";
2157
		} else {
2158
			echo chr(8). "{$timer}";
2159
		}
2160
		`/bin/stty -icanon min 0 time 25`;
2161
		$key = trim(`KEY=\`dd count=1 2>/dev/null\`; echo \$KEY`);
2162
		`/bin/stty icanon`;
2163
		if ($key == '') {
2164
			unset($key);
2165
		}
2166
		$timer--;
2167
		if ($timer == 0) {
2168
			break;
2169
		}
2170
	}
2171
	return $key;
2172
}
2173

    
2174
/****f* util/msort
2175
 * NAME
2176
 *   msort - sort array
2177
 * INPUTS
2178
 *   $array to be sorted, field to sort by, direction of sort
2179
 * RESULT
2180
 *   returns newly sorted array
2181
 ******/
2182
function msort($array, $id = "id", $sort_ascending = true) {
2183
	$temp_array = array();
2184
	while (count($array)>0) {
2185
		$lowest_id = 0;
2186
		$index = 0;
2187
		foreach ($array as $item) {
2188
			if (isset($item[$id])) {
2189
				if ($array[$lowest_id][$id]) {
2190
					if (strtolower($item[$id]) < strtolower($array[$lowest_id][$id])) {
2191
						$lowest_id = $index;
2192
					}
2193
				}
2194
			}
2195
			$index++;
2196
		}
2197
		$temp_array[] = $array[$lowest_id];
2198
		$array = array_merge(array_slice($array, 0, $lowest_id), array_slice($array, $lowest_id + 1));
2199
	}
2200
	if ($sort_ascending) {
2201
		return $temp_array;
2202
	} else {
2203
		return array_reverse($temp_array);
2204
	}
2205
}
2206

    
2207
/****f* util/is_URL
2208
 * NAME
2209
 *   is_URL
2210
 * INPUTS
2211
 *   string to check
2212
 * RESULT
2213
 *   Returns true if item is a URL
2214
 ******/
2215
function is_URL($url) {
2216
	$match = preg_match("'\b(([\w-]+://?|www[.])[^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|/)))'", $url);
2217
	if ($match) {
2218
		return true;
2219
	}
2220
	return false;
2221
}
2222

    
2223
function is_file_included($file = "") {
2224
	$files = get_included_files();
2225
	if (in_array($file, $files)) {
2226
		return true;
2227
	}
2228

    
2229
	return false;
2230
}
2231

    
2232
/*
2233
 * Replace a value on a deep associative array using regex
2234
 */
2235
function array_replace_values_recursive($data, $match, $replace) {
2236
	if (empty($data)) {
2237
		return $data;
2238
	}
2239

    
2240
	if (is_string($data)) {
2241
		$data = preg_replace("/{$match}/", $replace, $data);
2242
	} else if (is_array($data)) {
2243
		foreach ($data as $k => $v) {
2244
			$data[$k] = array_replace_values_recursive($v, $match, $replace);
2245
		}
2246
	}
2247

    
2248
	return $data;
2249
}
2250

    
2251
/*
2252
	This function was borrowed from a comment on PHP.net at the following URL:
2253
	http://www.php.net/manual/en/function.array-merge-recursive.php#73843
2254
 */
2255
function array_merge_recursive_unique($array0, $array1) {
2256

    
2257
	$arrays = func_get_args();
2258
	$remains = $arrays;
2259

    
2260
	// We walk through each arrays and put value in the results (without
2261
	// considering previous value).
2262
	$result = array();
2263

    
2264
	// loop available array
2265
	foreach ($arrays as $array) {
2266

    
2267
		// The first remaining array is $array. We are processing it. So
2268
		// we remove it from remaining arrays.
2269
		array_shift($remains);
2270

    
2271
		// We don't care non array param, like array_merge since PHP 5.0.
2272
		if (is_array($array)) {
2273
			// Loop values
2274
			foreach ($array as $key => $value) {
2275
				if (is_array($value)) {
2276
					// we gather all remaining arrays that have such key available
2277
					$args = array();
2278
					foreach ($remains as $remain) {
2279
						if (array_key_exists($key, $remain)) {
2280
							array_push($args, $remain[$key]);
2281
						}
2282
					}
2283

    
2284
					if (count($args) > 2) {
2285
						// put the recursion
2286
						$result[$key] = call_user_func_array(__FUNCTION__, $args);
2287
					} else {
2288
						foreach ($value as $vkey => $vval) {
2289
							$result[$key][$vkey] = $vval;
2290
						}
2291
					}
2292
				} else {
2293
					// simply put the value
2294
					$result[$key] = $value;
2295
				}
2296
			}
2297
		}
2298
	}
2299
	return $result;
2300
}
2301

    
2302

    
2303
/*
2304
 * converts a string like "a,b,c,d"
2305
 * into an array like array("a" => "b", "c" => "d")
2306
 */
2307
function explode_assoc($delimiter, $string) {
2308
	$array = explode($delimiter, $string);
2309
	$result = array();
2310
	$numkeys = floor(count($array) / 2);
2311
	for ($i = 0; $i < $numkeys; $i += 1) {
2312
		$result[$array[$i * 2]] = $array[$i * 2 + 1];
2313
	}
2314
	return $result;
2315
}
2316

    
2317
function get_staticroutes($returnsubnetsonly = false, $returnhostnames = false) {
2318
	global $config, $aliastable;
2319

    
2320
	/* Bail if there are no routes, but return an array always so callers don't have to check. */
2321
	if (!is_array($config['staticroutes']['route'])) {
2322
		return array();
2323
	}
2324

    
2325
	$allstaticroutes = array();
2326
	$allsubnets = array();
2327
	/* Loop through routes and expand aliases as we find them. */
2328
	foreach ($config['staticroutes']['route'] as $route) {
2329
		if (is_alias($route['network'])) {
2330
			if (!isset($aliastable[$route['network']])) {
2331
				continue;
2332
			}
2333

    
2334
			$subnets = preg_split('/\s+/', $aliastable[$route['network']]);
2335
			foreach ($subnets as $net) {
2336
				if (!is_subnet($net)) {
2337
					if (is_ipaddrv4($net)) {
2338
						$net .= "/32";
2339
					} else if (is_ipaddrv6($net)) {
2340
						$net .= "/128";
2341
					} else if ($returnhostnames === false || !is_fqdn($net)) {
2342
						continue;
2343
					}
2344
				}
2345
				$temproute = $route;
2346
				$temproute['network'] = $net;
2347
				$allstaticroutes[] = $temproute;
2348
				$allsubnets[] = $net;
2349
			}
2350
		} elseif (is_subnet($route['network'])) {
2351
			$allstaticroutes[] = $route;
2352
			$allsubnets[] = $route['network'];
2353
		}
2354
	}
2355
	if ($returnsubnetsonly) {
2356
		return $allsubnets;
2357
	} else {
2358
		return $allstaticroutes;
2359
	}
2360
}
2361

    
2362
/****f* util/get_alias_list
2363
 * NAME
2364
 *   get_alias_list - Provide a list of aliases.
2365
 * INPUTS
2366
 *   $type          - Optional, can be a string or array specifying what type(s) of aliases you need.
2367
 * RESULT
2368
 *   Array containing list of aliases.
2369
 *   If $type is unspecified, all aliases are returned.
2370
 *   If $type is a string, all aliases of the type specified in $type are returned.
2371
 *   If $type is an array, all aliases of any type specified in any element of $type are returned.
2372
 */
2373
function get_alias_list($type = null) {
2374
	global $config;
2375
	$result = array();
2376
	if ($config['aliases']['alias'] <> "" && is_array($config['aliases']['alias'])) {
2377
		foreach ($config['aliases']['alias'] as $alias) {
2378
			if ($type === null) {
2379
				$result[] = $alias['name'];
2380
			} else if (is_array($type)) {
2381
				if (in_array($alias['type'], $type)) {
2382
					$result[] = $alias['name'];
2383
				}
2384
			} else if ($type === $alias['type']) {
2385
				$result[] = $alias['name'];
2386
			}
2387
		}
2388
	}
2389
	return $result;
2390
}
2391

    
2392
/* returns an array consisting of every element of $haystack that is not equal to $needle. */
2393
function array_exclude($needle, $haystack) {
2394
	$result = array();
2395
	if (is_array($haystack)) {
2396
		foreach ($haystack as $thing) {
2397
			if ($needle !== $thing) {
2398
				$result[] = $thing;
2399
			}
2400
		}
2401
	}
2402
	return $result;
2403
}
2404

    
2405
/* Define what is preferred, IPv4 or IPv6 */
2406
function prefer_ipv4_or_ipv6() {
2407
	global $config;
2408

    
2409
	if (isset($config['system']['prefer_ipv4'])) {
2410
		mwexec("/etc/rc.d/ip6addrctl prefer_ipv4");
2411
	} else {
2412
		mwexec("/etc/rc.d/ip6addrctl prefer_ipv6");
2413
	}
2414
}
2415

    
2416
/* Redirect to page passing parameters via POST */
2417
function post_redirect($page, $params) {
2418
	if (!is_array($params)) {
2419
		return;
2420
	}
2421

    
2422
	print "<html><body><form action=\"{$page}\" name=\"formredir\" method=\"post\">\n";
2423
	foreach ($params as $key => $value) {
2424
		print "<input type=\"hidden\" name=\"{$key}\" value=\"{$value}\" />\n";
2425
	}
2426
	print "</form>\n";
2427
	print "<script type=\"text/javascript\">\n";
2428
	print "//<![CDATA[\n";
2429
	print "document.formredir.submit();\n";
2430
	print "//]]>\n";
2431
	print "</script>\n";
2432
	print "</body></html>\n";
2433
}
2434

    
2435
/* Locate disks that can be queried for S.M.A.R.T. data. */
2436
function get_smart_drive_list() {
2437
	$disk_list = explode(" ", get_single_sysctl("kern.disks"));
2438
	foreach ($disk_list as $id => $disk) {
2439
		// We only want certain kinds of disks for S.M.A.R.T.
2440
		// 1 is a match, 0 is no match, False is any problem processing the regex
2441
		if (preg_match("/^(ad|da|ada).*[0-9]{1,2}$/", $disk) !== 1) {
2442
			unset($disk_list[$id]);
2443
		}
2444
	}
2445
	sort($disk_list);
2446
	return $disk_list;
2447
}
2448

    
2449
?>
(55-55/65)