Project

General

Profile

Download (67.8 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: A name must be given 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: A name must be given 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
/*
1062
	If $return_message is true then
1063
		returns a text message about the reason that the name is invalid.
1064
		the text includes the type of "thing" that is being checked, passed in $object. (e.g. "alias", "gateway group", "schedule")
1065
	else
1066
		returns true if $name is a valid name for an alias
1067
		returns false if $name is not a valid name for an alias
1068

    
1069
	Aliases cannot be:
1070
		bad chars: anything except a-z 0-9 and underscore
1071
		bad names: empty string, pure numeric, pure underscore
1072
		reserved words: pre-defined service/protocol/port names which should not be ambiguous, and the words "port" and  "pass" */
1073

    
1074
function is_validaliasname($name, $return_message = false, $object = "alias") {
1075
	/* Array of reserved words */
1076
	$reserved = array("port", "pass");
1077

    
1078
	if (!is_string($name) || strlen($name) >= 32 || preg_match('/(^_*$|^\d*$|[^a-z0-9_])/i', $name)) {
1079
		if ($return_message) {
1080
			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, _');
1081
		} else {
1082
			return false;
1083
		}
1084
	}
1085
	if (in_array($name, $reserved, true)) {
1086
		if ($return_message) {
1087
			return sprintf(gettext('The %1$s name must not be either of the reserved words %2$s or %3$s.'), $object, "'port'", "'pass'");
1088
		} else {
1089
			return false;
1090
		}
1091
	}
1092
	if (getprotobyname($name)) {
1093
		if ($return_message) {
1094
			return sprintf(gettext('The %1$s name must not be a well-known IP protocol name such as TCP, UDP, ICMP etc.'), $object);
1095
		} else {
1096
			return false;
1097
		}
1098
	}
1099
	if (getservbyname($name, "tcp") || getservbyname($name, "udp")) {
1100
		if ($return_message) {
1101
			return sprintf(gettext('The %1$s name must not be a well-known TCP or UDP port name such as ssh, smtp, pop3, tftp, http, openvpn etc.'), $object);
1102
		} else {
1103
			return false;
1104
		}
1105
	}
1106
	if ($return_message) {
1107
		return sprintf(gettext("The %1$s name is valid."), $object);
1108
	} else {
1109
		return true;
1110
	}
1111
}
1112

    
1113
/* returns a text message indicating if the alias name is valid, or the reason it is not valid. */
1114
function invalidaliasnamemsg($name, $object = "alias") {
1115
	return is_validaliasname($name, true, $object);
1116
}
1117

    
1118
/* returns true if $port is a valid TCP/UDP port */
1119
function is_port($port) {
1120
	if (ctype_digit($port) && ((intval($port) >= 1) && (intval($port) <= 65535))) {
1121
		return true;
1122
	}
1123
	if (getservbyname($port, "tcp") || getservbyname($port, "udp")) {
1124
		return true;
1125
	}
1126
	return false;
1127
}
1128

    
1129
/* returns true if $portrange is a valid TCP/UDP portrange ("<port>:<port>") */
1130
function is_portrange($portrange) {
1131
	$ports = explode(":", $portrange);
1132

    
1133
	return (count($ports) == 2 && is_port($ports[0]) && is_port($ports[1]));
1134
}
1135

    
1136
/* returns true if $port is a valid port number or an alias thereof */
1137
function is_portoralias($port) {
1138
	global $config;
1139

    
1140
	if (is_alias($port)) {
1141
		if (is_array($config['aliases']['alias'])) {
1142
			foreach ($config['aliases']['alias'] as $alias) {
1143
				if ($alias['name'] == $port && preg_match("/port/i", $alias['type'])) {
1144
					return true;
1145
				}
1146
			}
1147
		}
1148
		return false;
1149
	} else {
1150
		return is_port($port);
1151
	}
1152
}
1153

    
1154
/* create ranges of sequential port numbers (200:215) and remove duplicates */
1155
function group_ports($ports, $kflc = false) {
1156
	if (!is_array($ports) || empty($ports)) {
1157
		return;
1158
	}
1159

    
1160
	$uniq = array();
1161
	$comments = array();
1162
	foreach ($ports as $port) {
1163
		if (($kflc) && (strpos($port, '#') === 0)) {	// Keep Full Line Comments (lines beginning with #).
1164
			$comments[] = $port;
1165
		} else if (is_portrange($port)) {
1166
			list($begin, $end) = explode(":", $port);
1167
			if ($begin > $end) {
1168
				$aux = $begin;
1169
				$begin = $end;
1170
				$end = $aux;
1171
			}
1172
			for ($i = $begin; $i <= $end; $i++) {
1173
				if (!in_array($i, $uniq)) {
1174
					$uniq[] = $i;
1175
				}
1176
			}
1177
		} else if (is_port($port)) {
1178
			if (!in_array($port, $uniq)) {
1179
				$uniq[] = $port;
1180
			}
1181
		}
1182
	}
1183
	sort($uniq, SORT_NUMERIC);
1184

    
1185
	$result = array();
1186
	foreach ($uniq as $idx => $port) {
1187
		if ($idx == 0) {
1188
			$result[] = $port;
1189
			continue;
1190
		}
1191

    
1192
		$last = end($result);
1193
		if (is_portrange($last)) {
1194
			list($begin, $end) = explode(":", $last);
1195
		} else {
1196
			$begin = $end = $last;
1197
		}
1198

    
1199
		if ($port == ($end+1)) {
1200
			$end++;
1201
			$result[count($result)-1] = "{$begin}:{$end}";
1202
		} else {
1203
			$result[] = $port;
1204
		}
1205
	}
1206

    
1207
	return array_merge($comments, $result);
1208
}
1209

    
1210
/* returns true if $val is a valid shaper bandwidth value */
1211
function is_valid_shaperbw($val) {
1212
	return (preg_match("/^(\d+(?:\.\d+)?)([MKG]?b|%)$/", $val));
1213
}
1214

    
1215
/* returns true if $test is in the range between $start and $end */
1216
function is_inrange_v4($test, $start, $end) {
1217
	if ((ip2ulong($test) <= ip2ulong($end)) && (ip2ulong($test) >= ip2ulong($start))) {
1218
		return true;
1219
	} else {
1220
		return false;
1221
	}
1222
}
1223

    
1224
/* returns true if $test is in the range between $start and $end */
1225
function is_inrange_v6($test, $start, $end) {
1226
	if ((inet_pton($test) <= inet_pton($end)) && (inet_pton($test) >= inet_pton($start))) {
1227
		return true;
1228
	} else {
1229
		return false;
1230
	}
1231
}
1232

    
1233
/* returns true if $test is in the range between $start and $end */
1234
function is_inrange($test, $start, $end) {
1235
	return is_ipaddrv6($test) ? is_inrange_v6($test, $start, $end) : is_inrange_v4($test, $start, $end);
1236
}
1237

    
1238
function get_configured_vip_list($family = 'all', $type = VIP_ALL) {
1239
	global $config;
1240

    
1241
	$list = array();
1242
	if (!is_array($config['virtualip']['vip']) || empty($config['virtualip']['vip'])) {
1243
		return ($list);
1244
	}
1245

    
1246
	$viparr = &$config['virtualip']['vip'];
1247
	foreach ($viparr as $vip) {
1248

    
1249
		if ($type == VIP_CARP) {
1250
			if ($vip['mode'] != "carp")
1251
				continue;
1252
		} elseif ($type == VIP_IPALIAS) {
1253
			if ($vip['mode'] != "ipalias")
1254
				continue;
1255
		} else {
1256
			if ($vip['mode'] != "carp" && $vip['mode'] != "ipalias")
1257
				continue;
1258
		}
1259

    
1260
		if ($family == 'all' ||
1261
		    ($family == 'inet' && is_ipaddrv4($vip['subnet'])) ||
1262
		    ($family == 'inet6' && is_ipaddrv6($vip['subnet']))) {
1263
			$list["_vip{$vip['uniqid']}"] = $vip['subnet'];
1264
		}
1265
	}
1266
	return ($list);
1267
}
1268

    
1269
function get_configured_vip($vipinterface = '') {
1270

    
1271
	return (get_configured_vip_detail($vipinterface, 'all', 'vip'));
1272
}
1273

    
1274
function get_configured_vip_interface($vipinterface = '') {
1275

    
1276
	return (get_configured_vip_detail($vipinterface, 'all', 'iface'));
1277
}
1278

    
1279
function get_configured_vip_ipv4($vipinterface = '') {
1280

    
1281
	return (get_configured_vip_detail($vipinterface, 'inet', 'ip'));
1282
}
1283

    
1284
function get_configured_vip_ipv6($vipinterface = '') {
1285

    
1286
	return (get_configured_vip_detail($vipinterface, 'inet6', 'ip'));
1287
}
1288

    
1289
function get_configured_vip_subnetv4($vipinterface = '') {
1290

    
1291
	return (get_configured_vip_detail($vipinterface, 'inet', 'subnet'));
1292
}
1293

    
1294
function get_configured_vip_subnetv6($vipinterface = '') {
1295

    
1296
	return (get_configured_vip_detail($vipinterface, 'inet6', 'subnet'));
1297
}
1298

    
1299
function get_configured_vip_detail($vipinterface = '', $family = 'inet', $what = 'ip') {
1300
	global $config;
1301

    
1302
	if (empty($vipinterface) || !is_array($config['virtualip']['vip']) ||
1303
	    empty($config['virtualip']['vip'])) {
1304
		return (NULL);
1305
	}
1306

    
1307
	$viparr = &$config['virtualip']['vip'];
1308
	foreach ($viparr as $vip) {
1309
		if ($vip['mode'] != "carp" && $vip['mode'] != "ipalias") {
1310
			continue;
1311
		}
1312

    
1313
		if ($vipinterface != "_vip{$vip['uniqid']}") {
1314
			continue;
1315
		}
1316

    
1317
		switch ($what) {
1318
			case 'subnet':
1319
				if ($family == 'inet' && is_ipaddrv4($vip['subnet']))
1320
					return ($vip['subnet_bits']);
1321
				else if ($family == 'inet6' && is_ipaddrv6($vip['subnet']))
1322
					return ($vip['subnet_bits']);
1323
				break;
1324
			case 'iface':
1325
				return ($vip['interface']);
1326
				break;
1327
			case 'vip':
1328
				return ($vip);
1329
				break;
1330
			case 'ip':
1331
			default:
1332
				if ($family == 'inet' && is_ipaddrv4($vip['subnet'])) {
1333
					return ($vip['subnet']);
1334
				} else if ($family == 'inet6' && is_ipaddrv6($vip['subnet'])) {
1335
					return ($vip['subnet']);
1336
				}
1337
				break;
1338
		}
1339
		break;
1340
	}
1341

    
1342
	return (NULL);
1343
}
1344

    
1345
/* comparison function for sorting by the order in which interfaces are normally created */
1346
function compare_interface_friendly_names($a, $b) {
1347
	if ($a == $b) {
1348
		return 0;
1349
	} else if ($a == 'wan') {
1350
		return -1;
1351
	} else if ($b == 'wan') {
1352
		return 1;
1353
	} else if ($a == 'lan') {
1354
		return -1;
1355
	} else if ($b == 'lan') {
1356
		return 1;
1357
	}
1358

    
1359
	return strnatcmp($a, $b);
1360
}
1361

    
1362
/* return the configured interfaces list. */
1363
function get_configured_interface_list($only_opt = false, $withdisabled = false) {
1364
	global $config;
1365

    
1366
	$iflist = array();
1367

    
1368
	/* if list */
1369
	foreach ($config['interfaces'] as $if => $ifdetail) {
1370
		if ($only_opt && ($if == "wan" || $if == "lan")) {
1371
			continue;
1372
		}
1373
		if (isset($ifdetail['enable']) || $withdisabled == true) {
1374
			$iflist[$if] = $if;
1375
		}
1376
	}
1377

    
1378
	return $iflist;
1379
}
1380

    
1381
/* return the configured interfaces list. */
1382
function get_configured_interface_list_by_realif($only_opt = false, $withdisabled = false) {
1383
	global $config;
1384

    
1385
	$iflist = array();
1386

    
1387
	/* if list */
1388
	foreach ($config['interfaces'] as $if => $ifdetail) {
1389
		if ($only_opt && ($if == "wan" || $if == "lan")) {
1390
			continue;
1391
		}
1392
		if (isset($ifdetail['enable']) || $withdisabled == true) {
1393
			$tmpif = get_real_interface($if);
1394
			if (!empty($tmpif)) {
1395
				$iflist[$tmpif] = $if;
1396
			}
1397
		}
1398
	}
1399

    
1400
	return $iflist;
1401
}
1402

    
1403
/* return the configured interfaces list with their description. */
1404
function get_configured_interface_with_descr($only_opt = false, $withdisabled = false) {
1405
	global $config;
1406

    
1407
	$iflist = array();
1408

    
1409
	/* if list */
1410
	foreach ($config['interfaces'] as $if => $ifdetail) {
1411
		if ($only_opt && ($if == "wan" || $if == "lan")) {
1412
			continue;
1413
		}
1414
		if (isset($ifdetail['enable']) || $withdisabled == true) {
1415
			if (empty($ifdetail['descr'])) {
1416
				$iflist[$if] = strtoupper($if);
1417
			} else {
1418
				$iflist[$if] = strtoupper($ifdetail['descr']);
1419
			}
1420
		}
1421
	}
1422

    
1423
	return $iflist;
1424
}
1425

    
1426
/*
1427
 *   get_configured_ip_addresses() - Return a list of all configured
1428
 *   IPv4 addresses.
1429
 *
1430
 */
1431
function get_configured_ip_addresses() {
1432
	global $config;
1433

    
1434
	if (!function_exists('get_interface_ip')) {
1435
		require_once("interfaces.inc");
1436
	}
1437
	$ip_array = array();
1438
	$interfaces = get_configured_interface_list();
1439
	if (is_array($interfaces)) {
1440
		foreach ($interfaces as $int) {
1441
			$ipaddr = get_interface_ip($int);
1442
			$ip_array[$int] = $ipaddr;
1443
		}
1444
	}
1445
	$interfaces = get_configured_vip_list('inet');
1446
	if (is_array($interfaces)) {
1447
		foreach ($interfaces as $int => $ipaddr) {
1448
			$ip_array[$int] = $ipaddr;
1449
		}
1450
	}
1451

    
1452
	/* pppoe server */
1453
	if (is_array($config['pppoes']) && is_array($config['pppoes']['pppoe'])) {
1454
		foreach ($config['pppoes']['pppoe'] as $pppoe) {
1455
			if ($pppoe['mode'] == "server") {
1456
				if (is_ipaddr($pppoe['localip'])) {
1457
					$int = "pppoes". $pppoe['pppoeid'];
1458
					$ip_array[$int] = $pppoe['localip'];
1459
				}
1460
			}
1461
		}
1462
	}
1463

    
1464
	return $ip_array;
1465
}
1466

    
1467
/*
1468
 *   get_configured_ipv6_addresses() - Return a list of all configured
1469
 *   IPv6 addresses.
1470
 *
1471
 */
1472
function get_configured_ipv6_addresses($linklocal_fallback = false) {
1473
	require_once("interfaces.inc");
1474
	$ipv6_array = array();
1475
	$interfaces = get_configured_interface_list();
1476
	if (is_array($interfaces)) {
1477
		foreach ($interfaces as $int) {
1478
			$ipaddrv6 = get_interface_ipv6($int, false, $linklocal_fallback);
1479
			$ipv6_array[$int] = $ipaddrv6;
1480
		}
1481
	}
1482
	$interfaces = get_configured_vip_list('inet6');
1483
	if (is_array($interfaces)) {
1484
		foreach ($interfaces as $int => $ipaddrv6) {
1485
			$ipv6_array[$int] = $ipaddrv6;
1486
		}
1487
	}
1488
	return $ipv6_array;
1489
}
1490

    
1491
/*
1492
 *   get_interface_list() - Return a list of all physical interfaces
1493
 *   along with MAC and status.
1494
 *
1495
 *   $mode = "active" - use ifconfig -lu
1496
 *           "media"  - use ifconfig to check physical connection
1497
 *			status (much slower)
1498
 */
1499
function get_interface_list($mode = "active", $keyby = "physical", $vfaces = "") {
1500
	global $config;
1501
	$upints = array();
1502
	/* get a list of virtual interface types */
1503
	if (!$vfaces) {
1504
		$vfaces = array(
1505
				'bridge',
1506
				'ppp',
1507
				'pppoe',
1508
				'pptp',
1509
				'l2tp',
1510
				'sl',
1511
				'gif',
1512
				'gre',
1513
				'faith',
1514
				'lo',
1515
				'ng',
1516
				'_vlan',
1517
				'_wlan',
1518
				'pflog',
1519
				'plip',
1520
				'pfsync',
1521
				'enc',
1522
				'tun',
1523
				'lagg',
1524
				'vip',
1525
				'ipfw'
1526
		);
1527
	}
1528
	switch ($mode) {
1529
		case "active":
1530
			$upints = pfSense_interface_listget(IFF_UP);
1531
			break;
1532
		case "media":
1533
			$intlist = pfSense_interface_listget();
1534
			$ifconfig = "";
1535
			exec("/sbin/ifconfig -a", $ifconfig);
1536
			$regexp = '/(' . implode('|', $intlist) . '):\s/';
1537
			$ifstatus = preg_grep('/status:/', $ifconfig);
1538
			foreach ($ifstatus as $status) {
1539
				$int = array_shift($intlist);
1540
				if (stristr($status, "active")) {
1541
					$upints[] = $int;
1542
				}
1543
			}
1544
			break;
1545
		default:
1546
			$upints = pfSense_interface_listget();
1547
			break;
1548
	}
1549
	/* build interface list with netstat */
1550
	$linkinfo = "";
1551
	exec("/usr/bin/netstat -inW -f link | awk '{ print $1, $4 }'", $linkinfo);
1552
	array_shift($linkinfo);
1553
	/* build ip address list with netstat */
1554
	$ipinfo = "";
1555
	exec("/usr/bin/netstat -inW -f inet | awk '{ print $1, $4 }'", $ipinfo);
1556
	array_shift($ipinfo);
1557
	foreach ($linkinfo as $link) {
1558
		$friendly = "";
1559
		$alink = explode(" ", $link);
1560
		$ifname = rtrim(trim($alink[0]), '*');
1561
		/* trim out all numbers before checking for vfaces */
1562
		if (!in_array(array_shift(preg_split('/\d/', $ifname)), $vfaces) &&
1563
		    !stristr($ifname, "_vlan") && !stristr($ifname, "_wlan")) {
1564
			$toput = array(
1565
					"mac" => trim($alink[1]),
1566
					"up" => in_array($ifname, $upints)
1567
				);
1568
			foreach ($ipinfo as $ip) {
1569
				$aip = explode(" ", $ip);
1570
				if ($aip[0] == $ifname) {
1571
					$toput['ipaddr'] = $aip[1];
1572
				}
1573
			}
1574
			if (is_array($config['interfaces'])) {
1575
				foreach ($config['interfaces'] as $name => $int) {
1576
					if ($int['if'] == $ifname) {
1577
						$friendly = $name;
1578
					}
1579
				}
1580
			}
1581
			switch ($keyby) {
1582
			case "physical":
1583
				if ($friendly != "") {
1584
					$toput['friendly'] = $friendly;
1585
				}
1586
				$dmesg_arr = array();
1587
				exec("/sbin/dmesg |grep $ifname | head -n1", $dmesg_arr);
1588
				preg_match_all("/<(.*?)>/i", $dmesg_arr[0], $dmesg);
1589
				$toput['dmesg'] = $dmesg[1][0];
1590
				$iflist[$ifname] = $toput;
1591
				break;
1592
			case "ppp":
1593

    
1594
			case "friendly":
1595
				if ($friendly != "") {
1596
					$toput['if'] = $ifname;
1597
					$iflist[$friendly] = $toput;
1598
				}
1599
				break;
1600
			}
1601
		}
1602
	}
1603
	return $iflist;
1604
}
1605

    
1606
/****f* util/log_error
1607
* NAME
1608
*   log_error  - Sends a string to syslog.
1609
* INPUTS
1610
*   $error     - string containing the syslog message.
1611
* RESULT
1612
*   null
1613
******/
1614
function log_error($error) {
1615
	global $g;
1616
	$page = $_SERVER['SCRIPT_NAME'];
1617
	if (empty($page)) {
1618
		$files = get_included_files();
1619
		$page = basename($files[0]);
1620
	}
1621
	syslog(LOG_ERR, "$page: $error");
1622
	if ($g['debug']) {
1623
		syslog(LOG_WARNING, var_dump(debug_backtrace()));
1624
	}
1625
	return;
1626
}
1627

    
1628
/****f* util/log_auth
1629
* NAME
1630
*   log_auth   - Sends a string to syslog as LOG_AUTH facility
1631
* INPUTS
1632
*   $error     - string containing the syslog message.
1633
* RESULT
1634
*   null
1635
******/
1636
function log_auth($error) {
1637
	global $g;
1638
	$page = $_SERVER['SCRIPT_NAME'];
1639
	syslog(LOG_AUTH, "$page: $error");
1640
	if ($g['debug']) {
1641
		syslog(LOG_WARNING, var_dump(debug_backtrace()));
1642
	}
1643
	return;
1644
}
1645

    
1646
/****f* util/exec_command
1647
 * NAME
1648
 *   exec_command - Execute a command and return a string of the result.
1649
 * INPUTS
1650
 *   $command   - String of the command to be executed.
1651
 * RESULT
1652
 *   String containing the command's result.
1653
 * NOTES
1654
 *   This function returns the command's stdout and stderr.
1655
 ******/
1656
function exec_command($command) {
1657
	$output = array();
1658
	exec($command . ' 2>&1', $output);
1659
	return(implode("\n", $output));
1660
}
1661

    
1662
/* wrapper for exec()
1663
   Executes in background or foreground.
1664
   For background execution, returns PID of background process to allow calling code control */
1665
function mwexec($command, $nologentry = false, $clearsigmask = false, $background = false) {
1666
	global $g;
1667
	$retval = 0;
1668

    
1669
	if ($g['debug']) {
1670
		if (!$_SERVER['REMOTE_ADDR']) {
1671
			echo "mwexec(): $command" . ($background ? " [BG]":"") . "\n";
1672
		}
1673
	}
1674
	if ($clearsigmask) {
1675
		$oldset = array();
1676
		pcntl_sigprocmask(SIG_SETMASK, array(), $oldset);
1677
	}
1678

    
1679
	if ($background) {
1680
		// start background process and return PID
1681
		$retval = exec("/usr/bin/nohup $command > /dev/null 2>&1 & echo $!");
1682
	} else {
1683
		// run in foreground, and (optionally) log if nonzero return
1684
		$outputarray = array();
1685
		exec("$command 2>&1", $outputarray, $retval);
1686
		if (($retval <> 0) && (!$nologentry || isset($config['system']['developerspew']))) {
1687
			log_error(sprintf(gettext("The command '%1\$s' returned exit code '%2\$d', the output was '%3\$s' "), $command, $retval, implode(" ", $outputarray)));
1688
		}
1689
	}
1690

    
1691
	if ($clearsigmask) {
1692
		pcntl_sigprocmask(SIG_SETMASK, $oldset);
1693
	}
1694

    
1695
	return $retval;
1696
}
1697

    
1698
/* wrapper for exec() in background */
1699
function mwexec_bg($command, $clearsigmask = false) {
1700
	return mwexec($command, false, $clearsigmask, true);
1701
}
1702

    
1703
/* unlink a file, or pattern-match of a file, if it exists
1704
   if the file/path contains glob() compatible wildcards, all matching files will be unlinked
1705
   if no matches, no error occurs */
1706
function unlink_if_exists($fn) {
1707
	$to_do = glob($fn);
1708
	if (is_array($to_do) && count($to_do) > 0) {
1709
		@array_map("unlink", $to_do);
1710
	} else {
1711
		@unlink($fn);
1712
	}
1713
}
1714
/* make a global alias table (for faster lookups) */
1715
function alias_make_table($config) {
1716
	global $aliastable;
1717

    
1718
	$aliastable = array();
1719

    
1720
	if (is_array($config['aliases']['alias'])) {
1721
		foreach ($config['aliases']['alias'] as $alias) {
1722
			if ($alias['name']) {
1723
				$aliastable[$alias['name']] = $alias['address'];
1724
			}
1725
		}
1726
	}
1727
}
1728

    
1729
/* check if an alias exists */
1730
function is_alias($name) {
1731
	global $aliastable;
1732

    
1733
	return isset($aliastable[$name]);
1734
}
1735

    
1736
function alias_get_type($name) {
1737
	global $config;
1738

    
1739
	if (is_array($config['aliases']['alias'])) {
1740
		foreach ($config['aliases']['alias'] as $alias) {
1741
			if ($name == $alias['name']) {
1742
				return $alias['type'];
1743
			}
1744
		}
1745
	}
1746

    
1747
	return "";
1748
}
1749

    
1750
/* expand a host or network alias, if necessary */
1751
function alias_expand($name) {
1752
	global $config, $aliastable;
1753
	$urltable_prefix = "/var/db/aliastables/";
1754
	$urltable_filename = $urltable_prefix . $name . ".txt";
1755

    
1756
	if (isset($aliastable[$name])) {
1757
		// alias names cannot be strictly numeric. redmine #4289
1758
		if (is_numericint($name)) {
1759
			return null;
1760
		}
1761
		// make sure if it's a ports alias, it actually exists. redmine #5845
1762
		foreach ($config['aliases']['alias'] as $alias) {
1763
			if ($alias['name'] == $name) {
1764
				if ($alias['type'] == "urltable_ports") {
1765
					if (is_URL($alias['url']) && file_exists($urltable_filename) && filesize($urltable_filename)) {
1766
						return "\${$name}";
1767
					} else {
1768
						return null;
1769
					}
1770
				}
1771
			}
1772
		}
1773
		return "\${$name}";
1774
	} else if (is_ipaddr($name) || is_subnet($name) || is_port($name) || is_portrange($name)) {
1775
		return "{$name}";
1776
	} else {
1777
		return null;
1778
	}
1779
}
1780

    
1781
function alias_expand_urltable($name) {
1782
	global $config;
1783
	$urltable_prefix = "/var/db/aliastables/";
1784
	$urltable_filename = $urltable_prefix . $name . ".txt";
1785

    
1786
	if (is_array($config['aliases']['alias'])) {
1787
		foreach ($config['aliases']['alias'] as $alias) {
1788
			if (preg_match("/urltable/i", $alias['type']) && ($alias['name'] == $name)) {
1789
				if (is_URL($alias["url"]) && file_exists($urltable_filename) && filesize($urltable_filename)) {
1790
					return $urltable_filename;
1791
				} else {
1792
					send_event("service sync alias {$name}");
1793
					break;
1794
				}
1795
			}
1796
		}
1797
	}
1798
	return null;
1799
}
1800

    
1801
/* obtain MAC address given an IP address by looking at the ARP table */
1802
function arp_get_mac_by_ip($ip) {
1803
	mwexec("/sbin/ping -c 1 -t 1 " . escapeshellarg($ip), true);
1804
	$arpoutput = "";
1805
	exec("/usr/sbin/arp -n " . escapeshellarg($ip), $arpoutput);
1806

    
1807
	if ($arpoutput[0]) {
1808
		$arpi = explode(" ", $arpoutput[0]);
1809
		$macaddr = $arpi[3];
1810
		if (is_macaddr($macaddr)) {
1811
			return $macaddr;
1812
		} else {
1813
			return false;
1814
		}
1815
	}
1816

    
1817
	return false;
1818
}
1819

    
1820
/* return a fieldname that is safe for xml usage */
1821
function xml_safe_fieldname($fieldname) {
1822
	$replace = array('/', '-', ' ', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')',
1823
			 '_', '+', '=', '{', '}', '[', ']', '|', '/', '<', '>', '?',
1824
			 ':', ',', '.', '\'', '\\'
1825
		);
1826
	return strtolower(str_replace($replace, "", $fieldname));
1827
}
1828

    
1829
function mac_format($clientmac) {
1830
	global $config, $cpzone;
1831

    
1832
	$mac = explode(":", $clientmac);
1833
	$mac_format = $cpzone ? $config['captiveportal'][$cpzone]['radmac_format'] : false;
1834

    
1835
	switch ($mac_format) {
1836
		case 'singledash':
1837
			return "$mac[0]$mac[1]$mac[2]-$mac[3]$mac[4]$mac[5]";
1838

    
1839
		case 'ietf':
1840
			return "$mac[0]-$mac[1]-$mac[2]-$mac[3]-$mac[4]-$mac[5]";
1841

    
1842
		case 'cisco':
1843
			return "$mac[0]$mac[1].$mac[2]$mac[3].$mac[4]$mac[5]";
1844

    
1845
		case 'unformatted':
1846
			return "$mac[0]$mac[1]$mac[2]$mac[3]$mac[4]$mac[5]";
1847

    
1848
		default:
1849
			return $clientmac;
1850
	}
1851
}
1852

    
1853
function resolve_retry($hostname, $retries = 5) {
1854

    
1855
	if (is_ipaddr($hostname)) {
1856
		return $hostname;
1857
	}
1858

    
1859
	for ($i = 0; $i < $retries; $i++) {
1860
		// FIXME: gethostbyname does not work for AAAA hostnames, boo, hiss
1861
		$ip = gethostbyname($hostname);
1862

    
1863
		if ($ip && $ip != $hostname) {
1864
			/* success */
1865
			return $ip;
1866
		}
1867

    
1868
		sleep(1);
1869
	}
1870

    
1871
	return false;
1872
}
1873

    
1874
function format_bytes($bytes) {
1875
	if ($bytes >= 1099511627776) {
1876
		return sprintf("%.2f TiB", $bytes/1099511627776);
1877
	} else if ($bytes >= 1073741824) {
1878
		return sprintf("%.2f GiB", $bytes/1073741824);
1879
	} else if ($bytes >= 1048576) {
1880
		return sprintf("%.2f MiB", $bytes/1048576);
1881
	} else if ($bytes >= 1024) {
1882
		return sprintf("%.0f KiB", $bytes/1024);
1883
	} else {
1884
		return sprintf("%d B", $bytes);
1885
	}
1886
}
1887

    
1888
function format_number($num, $precision = 3) {
1889
	$units = array('', 'K', 'M', 'G', 'T');
1890

    
1891
	$i = 0;
1892
	while ($num > 1000 && $i < count($units)) {
1893
		$num /= 1000;
1894
		$i++;
1895
	}
1896
	round($num, $precision);
1897

    
1898
	return ("$num {$units[$i]}");
1899
}
1900

    
1901
function update_filter_reload_status($text) {
1902
	global $g;
1903

    
1904
	file_put_contents("{$g['varrun_path']}/filter_reload_status", $text);
1905
}
1906

    
1907
/****** util/return_dir_as_array
1908
 * NAME
1909
 *   return_dir_as_array - Return a directory's contents as an array.
1910
 * INPUTS
1911
 *   $dir          - string containing the path to the desired directory.
1912
 *   $filter_regex - string containing a regular expression to filter file names. Default empty.
1913
 * RESULT
1914
 *   $dir_array - array containing the directory's contents. This array will be empty if the path specified is invalid.
1915
 ******/
1916
function return_dir_as_array($dir, $filter_regex = '') {
1917
	$dir_array = array();
1918
	if (is_dir($dir)) {
1919
		if ($dh = opendir($dir)) {
1920
			while (($file = readdir($dh)) !== false) {
1921
				if (($file == ".") || ($file == "..")) {
1922
					continue;
1923
				}
1924

    
1925
				if (empty($filter_regex) || preg_match($filter_regex, $file)) {
1926
					array_push($dir_array, $file);
1927
				}
1928
			}
1929
			closedir($dh);
1930
		}
1931
	}
1932
	return $dir_array;
1933
}
1934

    
1935
function run_plugins($directory) {
1936
	global $config, $g;
1937

    
1938
	/* process packager manager custom rules */
1939
	$files = return_dir_as_array($directory);
1940
	if (is_array($files)) {
1941
		foreach ($files as $file) {
1942
			if (stristr($file, ".sh") == true) {
1943
				mwexec($directory . $file . " start");
1944
			} else if (!is_dir($directory . "/" . $file) && stristr($file, ".inc")) {
1945
				require_once($directory . "/" . $file);
1946
			}
1947
		}
1948
	}
1949
}
1950

    
1951
/*
1952
 *    safe_mkdir($path, $mode = 0755)
1953
 *    create directory if it doesn't already exist and isn't a file!
1954
 */
1955
function safe_mkdir($path, $mode = 0755) {
1956
	global $g;
1957

    
1958
	if (!is_file($path) && !is_dir($path)) {
1959
		return @mkdir($path, $mode, true);
1960
	} else {
1961
		return false;
1962
	}
1963
}
1964

    
1965
/*
1966
 * get_sysctl($names)
1967
 * Get values of sysctl OID's listed in $names (accepts an array or a single
1968
 * name) and return an array of key/value pairs set for those that exist
1969
 */
1970
function get_sysctl($names) {
1971
	if (empty($names)) {
1972
		return array();
1973
	}
1974

    
1975
	if (is_array($names)) {
1976
		$name_list = array();
1977
		foreach ($names as $name) {
1978
			$name_list[] = escapeshellarg($name);
1979
		}
1980
	} else {
1981
		$name_list = array(escapeshellarg($names));
1982
	}
1983

    
1984
	exec("/sbin/sysctl -i " . implode(" ", $name_list), $output);
1985
	$values = array();
1986
	foreach ($output as $line) {
1987
		$line = explode(": ", $line, 2);
1988
		if (count($line) == 2) {
1989
			$values[$line[0]] = $line[1];
1990
		}
1991
	}
1992

    
1993
	return $values;
1994
}
1995

    
1996
/*
1997
 * get_single_sysctl($name)
1998
 * Wrapper for get_sysctl() to simplify read of a single sysctl value
1999
 * return the value for sysctl $name or empty string if it doesn't exist
2000
 */
2001
function get_single_sysctl($name) {
2002
	if (empty($name)) {
2003
		return "";
2004
	}
2005

    
2006
	$value = get_sysctl($name);
2007
	if (empty($value) || !isset($value[$name])) {
2008
		return "";
2009
	}
2010

    
2011
	return $value[$name];
2012
}
2013

    
2014
/*
2015
 * set_sysctl($value_list)
2016
 * Set sysctl OID's listed as key/value pairs and return
2017
 * an array with keys set for those that succeeded
2018
 */
2019
function set_sysctl($values) {
2020
	if (empty($values)) {
2021
		return array();
2022
	}
2023

    
2024
	$value_list = array();
2025
	foreach ($values as $key => $value) {
2026
		$value_list[] = escapeshellarg($key) . "=" . escapeshellarg($value);
2027
	}
2028

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

    
2031
	/* Retry individually if failed (one or more read-only) */
2032
	if ($success <> 0 && count($value_list) > 1) {
2033
		foreach ($value_list as $value) {
2034
			exec("/sbin/sysctl -i " . $value, $output);
2035
		}
2036
	}
2037

    
2038
	$ret = array();
2039
	foreach ($output as $line) {
2040
		$line = explode(": ", $line, 2);
2041
		if (count($line) == 2) {
2042
			$ret[$line[0]] = true;
2043
		}
2044
	}
2045

    
2046
	return $ret;
2047
}
2048

    
2049
/*
2050
 * set_single_sysctl($name, $value)
2051
 * Wrapper to set_sysctl() to make it simple to set only one sysctl
2052
 * returns boolean meaning if it succeeded
2053
 */
2054
function set_single_sysctl($name, $value) {
2055
	if (empty($name)) {
2056
		return false;
2057
	}
2058

    
2059
	$result = set_sysctl(array($name => $value));
2060

    
2061
	if (!isset($result[$name]) || $result[$name] != $value) {
2062
		return false;
2063
	}
2064

    
2065
	return true;
2066
}
2067

    
2068
/*
2069
 *     get_memory()
2070
 *     returns an array listing the amount of
2071
 *     memory installed in the hardware
2072
 *     [0] net memory available for the OS (FreeBSD) after some is taken by BIOS, video or whatever - e.g. 235 MBytes
2073
 *     [1] real (actual) memory of the system, should be the size of the RAM card/s - e.g. 256 MBytes
2074
 */
2075
function get_memory() {
2076
	$physmem = get_single_sysctl("hw.physmem");
2077
	$realmem = get_single_sysctl("hw.realmem");
2078
	/* convert from bytes to megabytes */
2079
	return array(($physmem/1048576), ($realmem/1048576));
2080
}
2081

    
2082
function mute_kernel_msgs() {
2083
	global $g, $config;
2084
	// Do not mute serial console.  The kernel gets very very cranky
2085
	// and will start dishing you cannot control tty errors.
2086
	if ($g['platform'] == 'nanobsd') {
2087
		return;
2088
	}
2089
	if ($config['system']['enableserial']) {
2090
		return;
2091
	}
2092
	exec("/sbin/conscontrol mute on");
2093
}
2094

    
2095
function unmute_kernel_msgs() {
2096
	global $g;
2097
	// Do not mute serial console.  The kernel gets very very cranky
2098
	// and will start dishing you cannot control tty errors.
2099
	if ($g['platform'] == 'nanobsd') {
2100
		return;
2101
	}
2102
	exec("/sbin/conscontrol mute off");
2103
}
2104

    
2105
function start_devd() {
2106
	global $g;
2107

    
2108
	/* Use the undocumented -q options of devd to quiet its log spamming */
2109
	$_gb = exec("/sbin/devd -q -f /etc/{$g['product_name']}-devd.conf");
2110
	sleep(1);
2111
	unset($_gb);
2112
}
2113

    
2114
function is_interface_vlan_mismatch() {
2115
	global $config, $g;
2116

    
2117
	if (is_array($config['vlans']['vlan'])) {
2118
		foreach ($config['vlans']['vlan'] as $vlan) {
2119
			if (substr($vlan['if'], 0, 4) == "lagg") {
2120
				return false;
2121
			}
2122
			if (does_interface_exist($vlan['if']) == false) {
2123
				return true;
2124
			}
2125
		}
2126
	}
2127

    
2128
	return false;
2129
}
2130

    
2131
function is_interface_mismatch() {
2132
	global $config, $g;
2133

    
2134
	$do_assign = false;
2135
	$i = 0;
2136
	$missing_interfaces = array();
2137
	if (is_array($config['interfaces'])) {
2138
		foreach ($config['interfaces'] as $ifname => $ifcfg) {
2139
			if (preg_match("/^enc|^cua|^tun|^tap|^l2tp|^pptp|^ppp|^ovpn|^gif|^gre|^lagg|^bridge|vlan|_wlan/i", $ifcfg['if'])) {
2140
				// Do not check these interfaces.
2141
				$i++;
2142
				continue;
2143
			} else if (does_interface_exist($ifcfg['if']) == false) {
2144
				$missing_interfaces[] = $ifcfg['if'];
2145
				$do_assign = true;
2146
			} else {
2147
				$i++;
2148
			}
2149
		}
2150
	}
2151

    
2152
	if (file_exists("{$g['tmp_path']}/assign_complete")) {
2153
		$do_assign = false;
2154
	}
2155

    
2156
	if (!empty($missing_interfaces) && $do_assign) {
2157
		file_put_contents("{$g['tmp_path']}/missing_interfaces", implode(' ', $missing_interfaces));
2158
	} else {
2159
		@unlink("{$g['tmp_path']}/missing_interfaces");
2160
	}
2161

    
2162
	return $do_assign;
2163
}
2164

    
2165
/* sync carp entries to other firewalls */
2166
function carp_sync_client() {
2167
	global $g;
2168
	send_event("filter sync");
2169
}
2170

    
2171
/****f* util/isAjax
2172
 * NAME
2173
 *   isAjax - reports if the request is driven from prototype
2174
 * INPUTS
2175
 *   none
2176
 * RESULT
2177
 *   true/false
2178
 ******/
2179
function isAjax() {
2180
	return isset ($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest';
2181
}
2182

    
2183
/****f* util/timeout
2184
 * NAME
2185
 *   timeout - console input with timeout countdown. Note: erases 2 char of screen for timer. Leave space.
2186
 * INPUTS
2187
 *   optional, seconds to wait before timeout. Default 9 seconds.
2188
 * RESULT
2189
 *   returns 1 char of user input or null if no input.
2190
 ******/
2191
function timeout($timer = 9) {
2192
	while (!isset($key)) {
2193
		if ($timer >= 9) {
2194
			echo chr(8) . chr(8) . ($timer == 9 ? chr(32) : null) . "{$timer}";
2195
		} else {
2196
			echo chr(8). "{$timer}";
2197
		}
2198
		`/bin/stty -icanon min 0 time 25`;
2199
		$key = trim(`KEY=\`dd count=1 2>/dev/null\`; echo \$KEY`);
2200
		`/bin/stty icanon`;
2201
		if ($key == '') {
2202
			unset($key);
2203
		}
2204
		$timer--;
2205
		if ($timer == 0) {
2206
			break;
2207
		}
2208
	}
2209
	return $key;
2210
}
2211

    
2212
/****f* util/msort
2213
 * NAME
2214
 *   msort - sort array
2215
 * INPUTS
2216
 *   $array to be sorted, field to sort by, direction of sort
2217
 * RESULT
2218
 *   returns newly sorted array
2219
 ******/
2220
function msort($array, $id = "id", $sort_ascending = true) {
2221
	$temp_array = array();
2222
	while (count($array)>0) {
2223
		$lowest_id = 0;
2224
		$index = 0;
2225
		foreach ($array as $item) {
2226
			if (isset($item[$id])) {
2227
				if ($array[$lowest_id][$id]) {
2228
					if (strtolower($item[$id]) < strtolower($array[$lowest_id][$id])) {
2229
						$lowest_id = $index;
2230
					}
2231
				}
2232
			}
2233
			$index++;
2234
		}
2235
		$temp_array[] = $array[$lowest_id];
2236
		$array = array_merge(array_slice($array, 0, $lowest_id), array_slice($array, $lowest_id + 1));
2237
	}
2238
	if ($sort_ascending) {
2239
		return $temp_array;
2240
	} else {
2241
		return array_reverse($temp_array);
2242
	}
2243
}
2244

    
2245
/****f* util/is_URL
2246
 * NAME
2247
 *   is_URL
2248
 * INPUTS
2249
 *   string to check
2250
 * RESULT
2251
 *   Returns true if item is a URL
2252
 ******/
2253
function is_URL($url) {
2254
	$match = preg_match("'\b(([\w-]+://?|www[.])[^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|/)))'", $url);
2255
	if ($match) {
2256
		return true;
2257
	}
2258
	return false;
2259
}
2260

    
2261
function is_file_included($file = "") {
2262
	$files = get_included_files();
2263
	if (in_array($file, $files)) {
2264
		return true;
2265
	}
2266

    
2267
	return false;
2268
}
2269

    
2270
/*
2271
 * Replace a value on a deep associative array using regex
2272
 */
2273
function array_replace_values_recursive($data, $match, $replace) {
2274
	if (empty($data)) {
2275
		return $data;
2276
	}
2277

    
2278
	if (is_string($data)) {
2279
		$data = preg_replace("/{$match}/", $replace, $data);
2280
	} else if (is_array($data)) {
2281
		foreach ($data as $k => $v) {
2282
			$data[$k] = array_replace_values_recursive($v, $match, $replace);
2283
		}
2284
	}
2285

    
2286
	return $data;
2287
}
2288

    
2289
/*
2290
	This function was borrowed from a comment on PHP.net at the following URL:
2291
	http://www.php.net/manual/en/function.array-merge-recursive.php#73843
2292
 */
2293
function array_merge_recursive_unique($array0, $array1) {
2294

    
2295
	$arrays = func_get_args();
2296
	$remains = $arrays;
2297

    
2298
	// We walk through each arrays and put value in the results (without
2299
	// considering previous value).
2300
	$result = array();
2301

    
2302
	// loop available array
2303
	foreach ($arrays as $array) {
2304

    
2305
		// The first remaining array is $array. We are processing it. So
2306
		// we remove it from remaining arrays.
2307
		array_shift($remains);
2308

    
2309
		// We don't care non array param, like array_merge since PHP 5.0.
2310
		if (is_array($array)) {
2311
			// Loop values
2312
			foreach ($array as $key => $value) {
2313
				if (is_array($value)) {
2314
					// we gather all remaining arrays that have such key available
2315
					$args = array();
2316
					foreach ($remains as $remain) {
2317
						if (array_key_exists($key, $remain)) {
2318
							array_push($args, $remain[$key]);
2319
						}
2320
					}
2321

    
2322
					if (count($args) > 2) {
2323
						// put the recursion
2324
						$result[$key] = call_user_func_array(__FUNCTION__, $args);
2325
					} else {
2326
						foreach ($value as $vkey => $vval) {
2327
							$result[$key][$vkey] = $vval;
2328
						}
2329
					}
2330
				} else {
2331
					// simply put the value
2332
					$result[$key] = $value;
2333
				}
2334
			}
2335
		}
2336
	}
2337
	return $result;
2338
}
2339

    
2340

    
2341
/*
2342
 * converts a string like "a,b,c,d"
2343
 * into an array like array("a" => "b", "c" => "d")
2344
 */
2345
function explode_assoc($delimiter, $string) {
2346
	$array = explode($delimiter, $string);
2347
	$result = array();
2348
	$numkeys = floor(count($array) / 2);
2349
	for ($i = 0; $i < $numkeys; $i += 1) {
2350
		$result[$array[$i * 2]] = $array[$i * 2 + 1];
2351
	}
2352
	return $result;
2353
}
2354

    
2355
function get_staticroutes($returnsubnetsonly = false, $returnhostnames = false) {
2356
	global $config, $aliastable;
2357

    
2358
	/* Bail if there are no routes, but return an array always so callers don't have to check. */
2359
	if (!is_array($config['staticroutes']['route'])) {
2360
		return array();
2361
	}
2362

    
2363
	$allstaticroutes = array();
2364
	$allsubnets = array();
2365
	/* Loop through routes and expand aliases as we find them. */
2366
	foreach ($config['staticroutes']['route'] as $route) {
2367
		if (is_alias($route['network'])) {
2368
			if (!isset($aliastable[$route['network']])) {
2369
				continue;
2370
			}
2371

    
2372
			$subnets = preg_split('/\s+/', $aliastable[$route['network']]);
2373
			foreach ($subnets as $net) {
2374
				if (!is_subnet($net)) {
2375
					if (is_ipaddrv4($net)) {
2376
						$net .= "/32";
2377
					} else if (is_ipaddrv6($net)) {
2378
						$net .= "/128";
2379
					} else if ($returnhostnames === false || !is_fqdn($net)) {
2380
						continue;
2381
					}
2382
				}
2383
				$temproute = $route;
2384
				$temproute['network'] = $net;
2385
				$allstaticroutes[] = $temproute;
2386
				$allsubnets[] = $net;
2387
			}
2388
		} elseif (is_subnet($route['network'])) {
2389
			$allstaticroutes[] = $route;
2390
			$allsubnets[] = $route['network'];
2391
		}
2392
	}
2393
	if ($returnsubnetsonly) {
2394
		return $allsubnets;
2395
	} else {
2396
		return $allstaticroutes;
2397
	}
2398
}
2399

    
2400
/****f* util/get_alias_list
2401
 * NAME
2402
 *   get_alias_list - Provide a list of aliases.
2403
 * INPUTS
2404
 *   $type          - Optional, can be a string or array specifying what type(s) of aliases you need.
2405
 * RESULT
2406
 *   Array containing list of aliases.
2407
 *   If $type is unspecified, all aliases are returned.
2408
 *   If $type is a string, all aliases of the type specified in $type are returned.
2409
 *   If $type is an array, all aliases of any type specified in any element of $type are returned.
2410
 */
2411
function get_alias_list($type = null) {
2412
	global $config;
2413
	$result = array();
2414
	if ($config['aliases']['alias'] <> "" && is_array($config['aliases']['alias'])) {
2415
		foreach ($config['aliases']['alias'] as $alias) {
2416
			if ($type === null) {
2417
				$result[] = $alias['name'];
2418
			} else if (is_array($type)) {
2419
				if (in_array($alias['type'], $type)) {
2420
					$result[] = $alias['name'];
2421
				}
2422
			} else if ($type === $alias['type']) {
2423
				$result[] = $alias['name'];
2424
			}
2425
		}
2426
	}
2427
	return $result;
2428
}
2429

    
2430
/* returns an array consisting of every element of $haystack that is not equal to $needle. */
2431
function array_exclude($needle, $haystack) {
2432
	$result = array();
2433
	if (is_array($haystack)) {
2434
		foreach ($haystack as $thing) {
2435
			if ($needle !== $thing) {
2436
				$result[] = $thing;
2437
			}
2438
		}
2439
	}
2440
	return $result;
2441
}
2442

    
2443
/* Define what is preferred, IPv4 or IPv6 */
2444
function prefer_ipv4_or_ipv6() {
2445
	global $config;
2446

    
2447
	if (isset($config['system']['prefer_ipv4'])) {
2448
		mwexec("/etc/rc.d/ip6addrctl prefer_ipv4");
2449
	} else {
2450
		mwexec("/etc/rc.d/ip6addrctl prefer_ipv6");
2451
	}
2452
}
2453

    
2454
/* Redirect to page passing parameters via POST */
2455
function post_redirect($page, $params) {
2456
	if (!is_array($params)) {
2457
		return;
2458
	}
2459

    
2460
	print "<html><body><form action=\"{$page}\" name=\"formredir\" method=\"post\">\n";
2461
	foreach ($params as $key => $value) {
2462
		print "<input type=\"hidden\" name=\"{$key}\" value=\"{$value}\" />\n";
2463
	}
2464
	print "</form>\n";
2465
	print "<script type=\"text/javascript\">\n";
2466
	print "//<![CDATA[\n";
2467
	print "document.formredir.submit();\n";
2468
	print "//]]>\n";
2469
	print "</script>\n";
2470
	print "</body></html>\n";
2471
}
2472

    
2473
/* Locate disks that can be queried for S.M.A.R.T. data. */
2474
function get_smart_drive_list() {
2475
	$disk_list = explode(" ", get_single_sysctl("kern.disks"));
2476
	foreach ($disk_list as $id => $disk) {
2477
		// We only want certain kinds of disks for S.M.A.R.T.
2478
		// 1 is a match, 0 is no match, False is any problem processing the regex
2479
		if (preg_match("/^(ad|da|ada).*[0-9]{1,2}$/", $disk) !== 1) {
2480
			unset($disk_list[$id]);
2481
		}
2482
	}
2483
	sort($disk_list);
2484
	return $disk_list;
2485
}
2486

    
2487
?>
(55-55/65)