Project

General

Profile

Download (65.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: 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 long int to IPv4 address
429
   Returns '' if not valid IPv4 (including if any bits >32 are non-zero) */
430
function long2ip32($ip) {
431
	return long2ip($ip & 0xFFFFFFFF);
432
}
433

    
434
/* Convert IPv4 address to long int, truncated to 32-bits to avoid sign extension on 64-bit platforms.
435
   Returns '' if not valid IPv4. */
436
function ip2long32($ip) {
437
	return (ip2long($ip) & 0xFFFFFFFF);
438
}
439

    
440
/* Convert IPv4 address to unsigned long int.
441
   Returns '' if not valid IPv4. */
442
function ip2ulong($ip) {
443
	return sprintf("%u", ip2long32($ip));
444
}
445

    
446
/* Find out how many IPs are contained within a given IP range
447
 *  e.g. 192.168.0.0 to 192.168.0.255 returns 256
448
 */
449
function ip_range_size_v4($startip, $endip) {
450
	if (is_ipaddrv4($startip) && is_ipaddrv4($endip)) {
451
		// Operate as unsigned long because otherwise it wouldn't work
452
		//   when crossing over from 127.255.255.255 / 128.0.0.0 barrier
453
		return abs(ip2ulong($startip) - ip2ulong($endip)) + 1;
454
	}
455
	return -1;
456
}
457

    
458
/* Find the smallest possible subnet mask which can contain a given number of IPs
459
 *  e.g. 512 IPs can fit in a /23, but 513 IPs need a /22
460
 */
461
function find_smallest_cidr_v4($number) {
462
	$smallest = 1;
463
	for ($b=32; $b > 0; $b--) {
464
		$smallest = ($number <= pow(2, $b)) ? $b : $smallest;
465
	}
466
	return (32-$smallest);
467
}
468

    
469
/* Return the previous IP address before the given address */
470
function ip_before($ip, $offset = 1) {
471
	return long2ip32(ip2long($ip) - $offset);
472
}
473

    
474
/* Return the next IP address after the given address */
475
function ip_after($ip, $offset = 1) {
476
	return long2ip32(ip2long($ip) + $offset);
477
}
478

    
479
/* Return true if the first IP is 'before' the second */
480
function ip_less_than($ip1, $ip2) {
481
	// Compare as unsigned long because otherwise it wouldn't work when
482
	//   crossing over from 127.255.255.255 / 128.0.0.0 barrier
483
	return ip2ulong($ip1) < ip2ulong($ip2);
484
}
485

    
486
/* Return true if the first IP is 'after' the second */
487
function ip_greater_than($ip1, $ip2) {
488
	// Compare as unsigned long because otherwise it wouldn't work
489
	//   when crossing over from 127.255.255.255 / 128.0.0.0 barrier
490
	return ip2ulong($ip1) > ip2ulong($ip2);
491
}
492

    
493
/* compare two IP addresses */
494
function ipcmp($a, $b) {
495
	if (ip_less_than($a, $b)) {
496
		return -1;
497
	} else if (ip_greater_than($a, $b)) {
498
		return 1;
499
	} else {
500
		return 0;
501
	}
502
}
503

    
504
/* Convert a range of IPv4 addresses to an array of individual addresses. */
505
/* Note: IPv6 ranges are not yet supported here. */
506
function ip_range_to_address_array($startip, $endip, $max_size = 5000) {
507
	if (!is_ipaddrv4($startip) || !is_ipaddrv4($endip)) {
508
		return false;
509
	}
510

    
511
	if (ip_greater_than($startip, $endip)) {
512
		// Swap start and end so we can process sensibly.
513
		$temp = $startip;
514
		$startip = $endip;
515
		$endip = $temp;
516
	}
517

    
518
	if (ip_range_size_v4($startip, $endip) > $max_size) {
519
		return false;
520
	}
521

    
522
	// Container for IP addresses within this range.
523
	$rangeaddresses = array();
524
	$end_int = ip2ulong($endip);
525
	for ($ip_int = ip2ulong($startip); $ip_int <= $end_int; $ip_int++) {
526
		$rangeaddresses[] = long2ip($ip_int);
527
	}
528

    
529
	return $rangeaddresses;
530
}
531

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

    
535
	Documented on pfsense dev list 19-20 May 2013. Summary:
536

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

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

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

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

    
558
function ip_range_to_subnet_array($ip1, $ip2) {
559

    
560
	if (is_ipaddrv4($ip1) && is_ipaddrv4($ip2)) {
561
		$proto = 'ipv4';  // for clarity
562
		$bits = 32;
563
		$ip1bin = decbin(ip2long32($ip1));
564
		$ip2bin = decbin(ip2long32($ip2));
565
	} elseif (is_ipaddrv6($ip1) && is_ipaddrv6($ip2)) {
566
		$proto = 'ipv6';
567
		$bits = 128;
568
		$ip1bin = Net_IPv6::_ip2Bin($ip1);
569
		$ip2bin = Net_IPv6::_ip2Bin($ip2);
570
	} else {
571
		return array();
572
	}
573

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

    
578
	if ($ip1bin == $ip2bin) {
579
		return array($ip1 . '/' . $bits); // exit if ip1=ip2 (trivial case)
580
	}
581

    
582
	if ($ip1bin > $ip2bin) {
583
		list ($ip1bin, $ip2bin) = array($ip2bin, $ip1bin);  // swap if needed (ensures ip1 < ip2)
584
	}
585

    
586
	$rangesubnets = array();
587
	$netsize = 0;
588

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

    
593
		// 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)
594

    
595
		if (substr($ip1bin, -1, 1) == '1') {
596
			// the start ip must be in a separate one-IP cidr range
597
			$new_subnet_ip = substr($ip1bin, $netsize, $bits - $netsize) . str_repeat('0', $netsize);
598
			$rangesubnets[$new_subnet_ip] = $bits - $netsize;
599
			$n = strrpos($ip1bin, '0');  //can't be all 1's
600
			$ip1bin = ($n == 0 ? '' : substr($ip1bin, 0, $n)) . '1' . str_repeat('0', $bits - $n - 1);  // BINARY VERSION OF $ip1 += 1
601
		}
602

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

    
605
		if (substr($ip2bin, -1, 1) == '0') {
606
			// the end ip must be in a separate one-IP cidr range
607
			$new_subnet_ip = substr($ip2bin, $netsize, $bits - $netsize) . str_repeat('0', $netsize);
608
			$rangesubnets[$new_subnet_ip] = $bits - $netsize;
609
			$n = strrpos($ip2bin, '1');  //can't be all 0's
610
			$ip2bin = ($n == 0 ? '' : substr($ip2bin, 0, $n)) . '0' . str_repeat('1', $bits - $n - 1);  // BINARY VERSION OF $ip2 -= 1
611
			// already checked for the edge case where end = start+1 and start ends in 0x1, above, so it's safe
612
		}
613

    
614
		// this is the only edge case arising from increment/decrement.
615
		// 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)
616

    
617
		if ($ip2bin < $ip1bin) {
618
			continue;
619
		}
620

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

    
624
		$shift = $bits - max(strrpos($ip1bin, '0'), strrpos($ip2bin, '1'));  // num of low bits which are '0' in ip1 and '1' in ip2
625
		$ip1bin = str_repeat('0', $shift) . substr($ip1bin, 0, $bits - $shift);
626
		$ip2bin = str_repeat('0', $shift) . substr($ip2bin, 0, $bits - $shift);
627
		$netsize += $shift;
628
		if ($ip1bin == $ip2bin) {
629
			// we're done.
630
			$new_subnet_ip = substr($ip1bin, $netsize, $bits - $netsize) . str_repeat('0', $netsize);
631
			$rangesubnets[$new_subnet_ip] = $bits - $netsize;
632
			continue;
633
		}
634

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

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

    
640
	ksort($rangesubnets, SORT_STRING);
641
	$out = array();
642

    
643
	foreach ($rangesubnets as $ip => $netmask) {
644
		if ($proto == 'ipv4') {
645
			$i = str_split($ip, 8);
646
			$out[] = implode('.', array(bindec($i[0]), bindec($i[1]), bindec($i[2]), bindec($i[3]))) . '/' . $netmask;
647
		} else {
648
			$out[] = Net_IPv6::compress(Net_IPv6::_bin2Ip($ip)) . '/' . $netmask;
649
		}
650
	}
651

    
652
	return $out;
653
}
654

    
655
/* returns true if $range is a valid pair of IPv4 or IPv6 addresses separated by a "-"
656
	false - if not a valid pair
657
	true (numeric 4 or 6) - if valid, gives type of addresses */
658
function is_iprange($range) {
659
	if (substr_count($range, '-') != 1) {
660
		return false;
661
	}
662
	list($ip1, $ip2) = explode ('-', $range);
663
	if (is_ipaddrv4($ip1) && is_ipaddrv4($ip2)) {
664
		return 4;
665
	}
666
	if (is_ipaddrv6($ip1) && is_ipaddrv6($ip2)) {
667
		return 6;
668
	}
669
	return false;
670
}
671

    
672
/* returns true if $ipaddr is a valid dotted IPv4 address or a IPv6
673
	false - not valid
674
	true (numeric 4 or 6) - if valid, gives type of address */
675
function is_ipaddr($ipaddr) {
676
	if (is_ipaddrv4($ipaddr)) {
677
		return 4;
678
	}
679
	if (is_ipaddrv6($ipaddr)) {
680
		return 6;
681
	}
682
	return false;
683
}
684

    
685
/* returns true if $ipaddr is a valid IPv6 address */
686
function is_ipaddrv6($ipaddr) {
687
	if (!is_string($ipaddr) || empty($ipaddr)) {
688
		return false;
689
	}
690
	if (strstr($ipaddr, "%") && is_linklocal($ipaddr)) {
691
		$tmpip = explode("%", $ipaddr);
692
		$ipaddr = $tmpip[0];
693
	}
694
	return Net_IPv6::checkIPv6($ipaddr);
695
}
696

    
697
/* returns true if $ipaddr is a valid dotted IPv4 address */
698
function is_ipaddrv4($ipaddr) {
699
	if (!is_string($ipaddr) || empty($ipaddr) || ip2long($ipaddr) === FALSE) {
700
		return false;
701
	}
702
	return true;
703
}
704

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

    
721
/* returns scope of a linklocal address */
722
function get_ll_scope($addr) {
723
	if (!is_linklocal($addr) || !strstr($addr, "%")) {
724
		return "";
725
	}
726
	list ($ll, $scope) = explode("%", $addr);
727
	return $scope;
728
}
729

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

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

    
748
	if (!is_port(substr($ipport, $c + 1))) {
749
		return false;  // no valid port after last colon
750
	}
751

    
752
	$ip = substr($ipport, 0, $c);  // else is text before last colon a valid IP
753
	if (is_literalipaddrv6($ip)) {
754
		return 6;
755
	} elseif (is_ipaddrv4($ip)) {
756
		return 4;
757
	} else {
758
		return false;
759
	}
760
}
761

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

    
771
/* returns true if $ipaddr is a valid dotted IPv4 address or an alias thereof */
772
function is_ipaddroralias($ipaddr) {
773
	global $config;
774

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

    
788
}
789

    
790
/* returns true if $subnet is a valid IPv4 or IPv6 subnet in CIDR format
791
	false - if not a valid subnet
792
	true (numeric 4 or 6) - if valid, gives type of subnet */
793
function is_subnet($subnet) {
794
	if (is_string($subnet) && preg_match('/^(?:([0-9.]{7,15})|([0-9a-f:]{2,39}))\/(\d{1,3})$/i', $subnet, $parts)) {
795
		if (is_ipaddrv4($parts[1]) && $parts[3] <= 32) {
796
			return 4;
797
		}
798
		if (is_ipaddrv6($parts[2]) && $parts[3] <= 128) {
799
			return 6;
800
		}
801
	}
802
	return false;
803
}
804

    
805
/* same as is_subnet() but accepts IPv4 only */
806
function is_subnetv4($subnet) {
807
	return (is_subnet($subnet) == 4);
808
}
809

    
810
/* same as is_subnet() but accepts IPv6 only */
811
function is_subnetv6($subnet) {
812
	return (is_subnet($subnet) == 6);
813
}
814

    
815
/* returns true if $subnet is a valid subnet in CIDR format or an alias thereof */
816
function is_subnetoralias($subnet) {
817
	global $aliastable;
818

    
819
	if (isset($aliastable[$subnet]) && is_subnet($aliastable[$subnet])) {
820
		return true;
821
	} else {
822
		return is_subnet($subnet);
823
	}
824
}
825

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

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

    
857
	// 2**N returns an exact result as an INT if possible, and a float/double if not. 
858
	// 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
859
	$result = 2 ** $snsize;
860
	
861
	if ($exact && !is_int($result)) {
862
		//exact required but can't represent result exactly as an INT
863
		return 0;
864
	} else {
865
		// result ok, will be an INT where possible (guaranteed up to 2^31 addresses on x32/x64) and a float for 'huge' subnets
866
		return $result;
867
	}
868
}
869

    
870

    
871
function subnet_expand($subnet) {
872
	if (is_subnetv4($subnet)) {
873
		return subnetv4_expand($subnet);
874
	} else if (is_subnetv6($subnet)) {
875
		return subnetv6_expand($subnet);
876
	} else {
877
		return $subnet;
878
	}
879
}
880

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

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

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

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

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

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

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

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

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

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

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

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

    
981
	return $result;
982
}
983

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

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

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

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

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

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

    
1038
	if (preg_match($domain_regex, $domain)) {
1039
		return true;
1040
	} else {
1041
		return false;
1042
	}
1043
}
1044

    
1045
/* returns true if $macaddr is a valid MAC address */
1046
function is_macaddr($macaddr, $partial=false) {
1047
	$repeat = ($partial) ? '1,5' : '5';
1048
	return preg_match('/^[0-9A-F]{2}(?:[:][0-9A-F]{2}){'.$repeat.'}$/i', $macaddr) == 1 ? true : false;
1049
}
1050

    
1051
/* returns true if $name is a valid name for an alias
1052
   returns NULL if a reserved word is used
1053
   returns FALSE for bad chars in the name - this allows calling code to determine what the problem was.
1054
   aliases cannot be:
1055
	bad chars: anything except a-z 0-9 and underscore
1056
	bad names: empty string, pure numeric, pure underscore
1057
	reserved words: pre-defined service/protocol/port names which should not be ambiguous, and the words "port" and  "pass" */
1058

    
1059
function is_validaliasname($name) {
1060
	/* Array of reserved words */
1061
	$reserved = array("port", "pass");
1062

    
1063
	if (!is_string($name) || strlen($name) >= 32 || preg_match('/(^_*$|^\d*$|[^a-z0-9_])/i', $name)) {
1064
		return false;
1065
	}
1066
	if (in_array($name, $reserved, true) || getservbyname($name, "tcp") || getservbyname($name, "udp") || getprotobyname($name)) {
1067
		return; /* return NULL */
1068
	}
1069
	return true;
1070
}
1071

    
1072
/* returns true if $port is a valid TCP/UDP port */
1073
function is_port($port) {
1074
	if (ctype_digit($port) && ((intval($port) >= 1) && (intval($port) <= 65535))) {
1075
		return true;
1076
	}
1077
	if (getservbyname($port, "tcp") || getservbyname($port, "udp")) {
1078
		return true;
1079
	}
1080
	return false;
1081
}
1082

    
1083
/* returns true if $portrange is a valid TCP/UDP portrange ("<port>:<port>") */
1084
function is_portrange($portrange) {
1085
	$ports = explode(":", $portrange);
1086

    
1087
	return (count($ports) == 2 && is_port($ports[0]) && is_port($ports[1]));
1088
}
1089

    
1090
/* returns true if $port is a valid port number or an alias thereof */
1091
function is_portoralias($port) {
1092
	global $config;
1093

    
1094
	if (is_alias($port)) {
1095
		if (is_array($config['aliases']['alias'])) {
1096
			foreach ($config['aliases']['alias'] as $alias) {
1097
				if ($alias['name'] == $port && preg_match("/port/i", $alias['type'])) {
1098
					return true;
1099
				}
1100
			}
1101
		}
1102
		return false;
1103
	} else {
1104
		return is_port($port);
1105
	}
1106
}
1107

    
1108
/* create ranges of sequential port numbers (200:215) and remove duplicates */
1109
function group_ports($ports) {
1110
	if (!is_array($ports) || empty($ports)) {
1111
		return;
1112
	}
1113

    
1114
	$uniq = array();
1115
	foreach ($ports as $port) {
1116
		if (is_portrange($port)) {
1117
			list($begin, $end) = explode(":", $port);
1118
			if ($begin > $end) {
1119
				$aux = $begin;
1120
				$begin = $end;
1121
				$end = $aux;
1122
			}
1123
			for ($i = $begin; $i <= $end; $i++) {
1124
				if (!in_array($i, $uniq)) {
1125
					$uniq[] = $i;
1126
				}
1127
			}
1128
		} else if (is_port($port)) {
1129
			if (!in_array($port, $uniq)) {
1130
				$uniq[] = $port;
1131
			}
1132
		}
1133
	}
1134
	sort($uniq, SORT_NUMERIC);
1135

    
1136
	$result = array();
1137
	foreach ($uniq as $idx => $port) {
1138
		if ($idx == 0) {
1139
			$result[] = $port;
1140
			continue;
1141
		}
1142

    
1143
		$last = end($result);
1144
		if (is_portrange($last)) {
1145
			list($begin, $end) = explode(":", $last);
1146
		} else {
1147
			$begin = $end = $last;
1148
		}
1149

    
1150
		if ($port == ($end+1)) {
1151
			$end++;
1152
			$result[count($result)-1] = "{$begin}:{$end}";
1153
		} else {
1154
			$result[] = $port;
1155
		}
1156
	}
1157

    
1158
	return $result;
1159
}
1160

    
1161
/* returns true if $val is a valid shaper bandwidth value */
1162
function is_valid_shaperbw($val) {
1163
	return (preg_match("/^(\d+(?:\.\d+)?)([MKG]?b|%)$/", $val));
1164
}
1165

    
1166
/* returns true if $test is in the range between $start and $end */
1167
function is_inrange_v4($test, $start, $end) {
1168
	if ((ip2ulong($test) <= ip2ulong($end)) && (ip2ulong($test) >= ip2ulong($start))) {
1169
		return true;
1170
	} else {
1171
		return false;
1172
	}
1173
}
1174

    
1175
/* returns true if $test is in the range between $start and $end */
1176
function is_inrange_v6($test, $start, $end) {
1177
	if ((inet_pton($test) <= inet_pton($end)) && (inet_pton($test) >= inet_pton($start))) {
1178
		return true;
1179
	} else {
1180
		return false;
1181
	}
1182
}
1183

    
1184
/* returns true if $test is in the range between $start and $end */
1185
function is_inrange($test, $start, $end) {
1186
	return is_ipaddrv6($test) ? is_inrange_v6($test, $start, $end) : is_inrange_v4($test, $start, $end);
1187
}
1188

    
1189
function get_configured_vip_list($family = 'all', $type = VIP_ALL) {
1190
	global $config;
1191

    
1192
	$list = array();
1193
	if (!is_array($config['virtualip']['vip']) || empty($config['virtualip']['vip'])) {
1194
		return ($list);
1195
	}
1196

    
1197
	$viparr = &$config['virtualip']['vip'];
1198
	foreach ($viparr as $vip) {
1199

    
1200
		if ($type == VIP_CARP) {
1201
			if ($vip['mode'] != "carp")
1202
				continue;
1203
		} elseif ($type == VIP_IPALIAS) {
1204
			if ($vip['mode'] != "ipalias")
1205
				continue;
1206
		} else {
1207
			if ($vip['mode'] != "carp" && $vip['mode'] != "ipalias")
1208
				continue;
1209
		}
1210

    
1211
		if ($family == 'all' ||
1212
		    ($family == 'inet' && is_ipaddrv4($vip['subnet'])) ||
1213
		    ($family == 'inet6' && is_ipaddrv6($vip['subnet']))) {
1214
			$list["_vip{$vip['uniqid']}"] = $vip['subnet'];
1215
		}
1216
	}
1217
	return ($list);
1218
}
1219

    
1220
function get_configured_vip($vipinterface = '') {
1221

    
1222
	return (get_configured_vip_detail($vipinterface, 'all', 'vip'));
1223
}
1224

    
1225
function get_configured_vip_interface($vipinterface = '') {
1226

    
1227
	return (get_configured_vip_detail($vipinterface, 'all', 'iface'));
1228
}
1229

    
1230
function get_configured_vip_ipv4($vipinterface = '') {
1231

    
1232
	return (get_configured_vip_detail($vipinterface, 'inet', 'ip'));
1233
}
1234

    
1235
function get_configured_vip_ipv6($vipinterface = '') {
1236

    
1237
	return (get_configured_vip_detail($vipinterface, 'inet6', 'ip'));
1238
}
1239

    
1240
function get_configured_vip_subnetv4($vipinterface = '') {
1241

    
1242
	return (get_configured_vip_detail($vipinterface, 'inet', 'subnet'));
1243
}
1244

    
1245
function get_configured_vip_subnetv6($vipinterface = '') {
1246

    
1247
	return (get_configured_vip_detail($vipinterface, 'inet6', 'subnet'));
1248
}
1249

    
1250
function get_configured_vip_detail($vipinterface = '', $family = 'inet', $what = 'ip') {
1251
	global $config;
1252

    
1253
	if (empty($vipinterface) || !is_array($config['virtualip']['vip']) ||
1254
	    empty($config['virtualip']['vip'])) {
1255
		return (NULL);
1256
	}
1257

    
1258
	$viparr = &$config['virtualip']['vip'];
1259
	foreach ($viparr as $vip) {
1260
		if ($vip['mode'] != "carp" && $vip['mode'] != "ipalias") {
1261
			continue;
1262
		}
1263

    
1264
		if ($vipinterface != "_vip{$vip['uniqid']}") {
1265
			continue;
1266
		}
1267

    
1268
		switch ($what) {
1269
			case 'subnet':
1270
				if ($family == 'inet' && is_ipaddrv4($vip['subnet']))
1271
					return ($vip['subnet_bits']);
1272
				else if ($family == 'inet6' && is_ipaddrv6($vip['subnet']))
1273
					return ($vip['subnet_bits']);
1274
				break;
1275
			case 'iface':
1276
				return ($vip['interface']);
1277
				break;
1278
			case 'vip':
1279
				return ($vip);
1280
				break;
1281
			case 'ip':
1282
			default:
1283
				if ($family == 'inet' && is_ipaddrv4($vip['subnet'])) {
1284
					return ($vip['subnet']);
1285
				} else if ($family == 'inet6' && is_ipaddrv6($vip['subnet'])) {
1286
					return ($vip['subnet']);
1287
				}
1288
				break;
1289
		}
1290
		break;
1291
	}
1292

    
1293
	return (NULL);
1294
}
1295

    
1296
/* comparison function for sorting by the order in which interfaces are normally created */
1297
function compare_interface_friendly_names($a, $b) {
1298
	if ($a == $b) {
1299
		return 0;
1300
	} else if ($a == 'wan') {
1301
		return -1;
1302
	} else if ($b == 'wan') {
1303
		return 1;
1304
	} else if ($a == 'lan') {
1305
		return -1;
1306
	} else if ($b == 'lan') {
1307
		return 1;
1308
	}
1309

    
1310
	return strnatcmp($a, $b);
1311
}
1312

    
1313
/* return the configured interfaces list. */
1314
function get_configured_interface_list($only_opt = false, $withdisabled = false) {
1315
	global $config;
1316

    
1317
	$iflist = array();
1318

    
1319
	/* if list */
1320
	foreach ($config['interfaces'] as $if => $ifdetail) {
1321
		if ($only_opt && ($if == "wan" || $if == "lan")) {
1322
			continue;
1323
		}
1324
		if (isset($ifdetail['enable']) || $withdisabled == true) {
1325
			$iflist[$if] = $if;
1326
		}
1327
	}
1328

    
1329
	return $iflist;
1330
}
1331

    
1332
/* return the configured interfaces list. */
1333
function get_configured_interface_list_by_realif($only_opt = false, $withdisabled = false) {
1334
	global $config;
1335

    
1336
	$iflist = array();
1337

    
1338
	/* if list */
1339
	foreach ($config['interfaces'] as $if => $ifdetail) {
1340
		if ($only_opt && ($if == "wan" || $if == "lan")) {
1341
			continue;
1342
		}
1343
		if (isset($ifdetail['enable']) || $withdisabled == true) {
1344
			$tmpif = get_real_interface($if);
1345
			if (!empty($tmpif)) {
1346
				$iflist[$tmpif] = $if;
1347
			}
1348
		}
1349
	}
1350

    
1351
	return $iflist;
1352
}
1353

    
1354
/* return the configured interfaces list with their description. */
1355
function get_configured_interface_with_descr($only_opt = false, $withdisabled = false) {
1356
	global $config;
1357

    
1358
	$iflist = array();
1359

    
1360
	/* if list */
1361
	foreach ($config['interfaces'] as $if => $ifdetail) {
1362
		if ($only_opt && ($if == "wan" || $if == "lan")) {
1363
			continue;
1364
		}
1365
		if (isset($ifdetail['enable']) || $withdisabled == true) {
1366
			if (empty($ifdetail['descr'])) {
1367
				$iflist[$if] = strtoupper($if);
1368
			} else {
1369
				$iflist[$if] = strtoupper($ifdetail['descr']);
1370
			}
1371
		}
1372
	}
1373

    
1374
	return $iflist;
1375
}
1376

    
1377
/*
1378
 *   get_configured_ip_addresses() - Return a list of all configured
1379
 *   IPv4 addresses.
1380
 *
1381
 */
1382
function get_configured_ip_addresses() {
1383
	global $config;
1384

    
1385
	if (!function_exists('get_interface_ip')) {
1386
		require_once("interfaces.inc");
1387
	}
1388
	$ip_array = array();
1389
	$interfaces = get_configured_interface_list();
1390
	if (is_array($interfaces)) {
1391
		foreach ($interfaces as $int) {
1392
			$ipaddr = get_interface_ip($int);
1393
			$ip_array[$int] = $ipaddr;
1394
		}
1395
	}
1396
	$interfaces = get_configured_vip_list('inet');
1397
	if (is_array($interfaces)) {
1398
		foreach ($interfaces as $int => $ipaddr) {
1399
			$ip_array[$int] = $ipaddr;
1400
		}
1401
	}
1402

    
1403
	/* pppoe server */
1404
	if (is_array($config['pppoes']) && is_array($config['pppoes']['pppoe'])) {
1405
		foreach ($config['pppoes']['pppoe'] as $pppoe) {
1406
			if ($pppoe['mode'] == "server") {
1407
				if (is_ipaddr($pppoe['localip'])) {
1408
					$int = "pppoes". $pppoe['pppoeid'];
1409
					$ip_array[$int] = $pppoe['localip'];
1410
				}
1411
			}
1412
		}
1413
	}
1414

    
1415
	return $ip_array;
1416
}
1417

    
1418
/*
1419
 *   get_configured_ipv6_addresses() - Return a list of all configured
1420
 *   IPv6 addresses.
1421
 *
1422
 */
1423
function get_configured_ipv6_addresses() {
1424
	require_once("interfaces.inc");
1425
	$ipv6_array = array();
1426
	$interfaces = get_configured_interface_list();
1427
	if (is_array($interfaces)) {
1428
		foreach ($interfaces as $int) {
1429
			$ipaddrv6 = get_interface_ipv6($int);
1430
			$ipv6_array[$int] = $ipaddrv6;
1431
		}
1432
	}
1433
	$interfaces = get_configured_vip_list('inet6');
1434
	if (is_array($interfaces)) {
1435
		foreach ($interfaces as $int => $ipaddrv6) {
1436
			$ipv6_array[$int] = $ipaddrv6;
1437
		}
1438
	}
1439
	return $ipv6_array;
1440
}
1441

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

    
1545
			case "friendly":
1546
				if ($friendly != "") {
1547
					$toput['if'] = $ifname;
1548
					$iflist[$friendly] = $toput;
1549
				}
1550
				break;
1551
			}
1552
		}
1553
	}
1554
	return $iflist;
1555
}
1556

    
1557
/****f* util/log_error
1558
* NAME
1559
*   log_error  - Sends a string to syslog.
1560
* INPUTS
1561
*   $error     - string containing the syslog message.
1562
* RESULT
1563
*   null
1564
******/
1565
function log_error($error) {
1566
	global $g;
1567
	$page = $_SERVER['SCRIPT_NAME'];
1568
	if (empty($page)) {
1569
		$files = get_included_files();
1570
		$page = basename($files[0]);
1571
	}
1572
	syslog(LOG_ERR, "$page: $error");
1573
	if ($g['debug']) {
1574
		syslog(LOG_WARNING, var_dump(debug_backtrace()));
1575
	}
1576
	return;
1577
}
1578

    
1579
/****f* util/log_auth
1580
* NAME
1581
*   log_auth   - Sends a string to syslog as LOG_AUTH facility
1582
* INPUTS
1583
*   $error     - string containing the syslog message.
1584
* RESULT
1585
*   null
1586
******/
1587
function log_auth($error) {
1588
	global $g;
1589
	$page = $_SERVER['SCRIPT_NAME'];
1590
	syslog(LOG_AUTH, "$page: $error");
1591
	if ($g['debug']) {
1592
		syslog(LOG_WARNING, var_dump(debug_backtrace()));
1593
	}
1594
	return;
1595
}
1596

    
1597
/****f* util/exec_command
1598
 * NAME
1599
 *   exec_command - Execute a command and return a string of the result.
1600
 * INPUTS
1601
 *   $command   - String of the command to be executed.
1602
 * RESULT
1603
 *   String containing the command's result.
1604
 * NOTES
1605
 *   This function returns the command's stdout and stderr.
1606
 ******/
1607
function exec_command($command) {
1608
	$output = array();
1609
	exec($command . ' 2>&1', $output);
1610
	return(implode("\n", $output));
1611
}
1612

    
1613
/* wrapper for exec()
1614
   Executes in background or foreground.
1615
   For background execution, returns PID of background process to allow calling code control */
1616
function mwexec($command, $nologentry = false, $clearsigmask = false, $background = false) {
1617
	global $g;
1618
	$retval = 0;
1619

    
1620
	if ($g['debug']) {
1621
		if (!$_SERVER['REMOTE_ADDR']) {
1622
			echo "mwexec(): $command" . ($background ? " [BG]":"") . "\n";
1623
		}
1624
	}
1625
	if ($clearsigmask) {
1626
		$oldset = array();
1627
		pcntl_sigprocmask(SIG_SETMASK, array(), $oldset);
1628
	}
1629

    
1630
	if ($background) {
1631
		// start background process and return PID
1632
		$retval = exec("/usr/bin/nohup $command > /dev/null 2>&1 & echo $!");
1633
	} else {
1634
		// run in foreground, and (optionally) log if nonzero return
1635
		$outputarray = array();
1636
		exec("$command 2>&1", $outputarray, $retval);
1637
		if (($retval <> 0) && (!$nologentry || isset($config['system']['developerspew']))) {
1638
			log_error(sprintf(gettext("The command '%1\$s' returned exit code '%2\$d', the output was '%3\$s' "), $command, $retval, implode(" ", $outputarray)));
1639
		}
1640
	}
1641

    
1642
	if ($clearsigmask) {
1643
		pcntl_sigprocmask(SIG_SETMASK, $oldset);
1644
	}
1645

    
1646
	return $retval;
1647
}
1648

    
1649
/* wrapper for exec() in background */
1650
function mwexec_bg($command, $clearsigmask = false) {
1651
	return mwexec($command, false, $clearsigmask, true);
1652
}
1653

    
1654
/* unlink a file, or pattern-match of a file, if it exists
1655
   if the file/path contains glob() compatible wildcards, all matching files will be unlinked
1656
   if no matches, no error occurs */
1657
function unlink_if_exists($fn) {
1658
	$to_do = glob($fn);
1659
	if (is_array($to_do) && count($to_do) > 0) {
1660
		@array_map("unlink", $to_do);
1661
	} else {
1662
		@unlink($fn);
1663
	}
1664
}
1665
/* make a global alias table (for faster lookups) */
1666
function alias_make_table($config) {
1667
	global $aliastable;
1668

    
1669
	$aliastable = array();
1670

    
1671
	if (is_array($config['aliases']['alias'])) {
1672
		foreach ($config['aliases']['alias'] as $alias) {
1673
			if ($alias['name']) {
1674
				$aliastable[$alias['name']] = $alias['address'];
1675
			}
1676
		}
1677
	}
1678
}
1679

    
1680
/* check if an alias exists */
1681
function is_alias($name) {
1682
	global $aliastable;
1683

    
1684
	return isset($aliastable[$name]);
1685
}
1686

    
1687
function alias_get_type($name) {
1688
	global $config;
1689

    
1690
	if (is_array($config['aliases']['alias'])) {
1691
		foreach ($config['aliases']['alias'] as $alias) {
1692
			if ($name == $alias['name']) {
1693
				return $alias['type'];
1694
			}
1695
		}
1696
	}
1697

    
1698
	return "";
1699
}
1700

    
1701
/* expand a host or network alias, if necessary */
1702
function alias_expand($name) {
1703
	global $config, $aliastable;
1704
	$urltable_prefix = "/var/db/aliastables/";
1705
	$urltable_filename = $urltable_prefix . $name . ".txt";
1706

    
1707
	if (isset($aliastable[$name])) {
1708
		// alias names cannot be strictly numeric. redmine #4289
1709
		if (is_numericint($name)) {
1710
			return null;
1711
		}
1712
		// make sure if it's a ports alias, it actually exists. redmine #5845
1713
		foreach ($config['aliases']['alias'] as $alias) {
1714
			if ($alias['name'] == $name) {
1715
				if ($alias['type'] == "urltable_ports") {
1716
					if (is_URL($alias['url']) && file_exists($urltable_filename) && filesize($urltable_filename)) {
1717
						return "\${$name}";
1718
					} else {
1719
						return null;
1720
					}
1721
				}
1722
			}
1723
		}
1724
		return "\${$name}";
1725
	} else if (is_ipaddr($name) || is_subnet($name) || is_port($name) || is_portrange($name)) {
1726
		return "{$name}";
1727
	} else {
1728
		return null;
1729
	}
1730
}
1731

    
1732
function alias_expand_urltable($name) {
1733
	global $config;
1734
	$urltable_prefix = "/var/db/aliastables/";
1735
	$urltable_filename = $urltable_prefix . $name . ".txt";
1736

    
1737
	if (is_array($config['aliases']['alias'])) {
1738
		foreach ($config['aliases']['alias'] as $alias) {
1739
			if (preg_match("/urltable/i", $alias['type']) && ($alias['name'] == $name)) {
1740
				if (is_URL($alias["url"]) && file_exists($urltable_filename) && filesize($urltable_filename)) {
1741
					return $urltable_filename;
1742
				} else {
1743
					send_event("service sync alias {$name}");
1744
					break;
1745
				}
1746
			}
1747
		}
1748
	}
1749
	return null;
1750
}
1751

    
1752
/* obtain MAC address given an IP address by looking at the ARP table */
1753
function arp_get_mac_by_ip($ip) {
1754
	mwexec("/sbin/ping -c 1 -t 1 " . escapeshellarg($ip), true);
1755
	$arpoutput = "";
1756
	exec("/usr/sbin/arp -n " . escapeshellarg($ip), $arpoutput);
1757

    
1758
	if ($arpoutput[0]) {
1759
		$arpi = explode(" ", $arpoutput[0]);
1760
		$macaddr = $arpi[3];
1761
		if (is_macaddr($macaddr)) {
1762
			return $macaddr;
1763
		} else {
1764
			return false;
1765
		}
1766
	}
1767

    
1768
	return false;
1769
}
1770

    
1771
/* return a fieldname that is safe for xml usage */
1772
function xml_safe_fieldname($fieldname) {
1773
	$replace = array('/', '-', ' ', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')',
1774
			 '_', '+', '=', '{', '}', '[', ']', '|', '/', '<', '>', '?',
1775
			 ':', ',', '.', '\'', '\\'
1776
		);
1777
	return strtolower(str_replace($replace, "", $fieldname));
1778
}
1779

    
1780
function mac_format($clientmac) {
1781
	global $config, $cpzone;
1782

    
1783
	$mac = explode(":", $clientmac);
1784
	$mac_format = $cpzone ? $config['captiveportal'][$cpzone]['radmac_format'] : false;
1785

    
1786
	switch ($mac_format) {
1787
		case 'singledash':
1788
			return "$mac[0]$mac[1]$mac[2]-$mac[3]$mac[4]$mac[5]";
1789

    
1790
		case 'ietf':
1791
			return "$mac[0]-$mac[1]-$mac[2]-$mac[3]-$mac[4]-$mac[5]";
1792

    
1793
		case 'cisco':
1794
			return "$mac[0]$mac[1].$mac[2]$mac[3].$mac[4]$mac[5]";
1795

    
1796
		case 'unformatted':
1797
			return "$mac[0]$mac[1]$mac[2]$mac[3]$mac[4]$mac[5]";
1798

    
1799
		default:
1800
			return $clientmac;
1801
	}
1802
}
1803

    
1804
function resolve_retry($hostname, $retries = 5) {
1805

    
1806
	if (is_ipaddr($hostname)) {
1807
		return $hostname;
1808
	}
1809

    
1810
	for ($i = 0; $i < $retries; $i++) {
1811
		// FIXME: gethostbyname does not work for AAAA hostnames, boo, hiss
1812
		$ip = gethostbyname($hostname);
1813

    
1814
		if ($ip && $ip != $hostname) {
1815
			/* success */
1816
			return $ip;
1817
		}
1818

    
1819
		sleep(1);
1820
	}
1821

    
1822
	return false;
1823
}
1824

    
1825
function format_bytes($bytes) {
1826
	if ($bytes >= 1099511627776) {
1827
		return sprintf("%.2f TiB", $bytes/1099511627776);
1828
	} else if ($bytes >= 1073741824) {
1829
		return sprintf("%.2f GiB", $bytes/1073741824);
1830
	} else if ($bytes >= 1048576) {
1831
		return sprintf("%.2f MiB", $bytes/1048576);
1832
	} else if ($bytes >= 1024) {
1833
		return sprintf("%.0f KiB", $bytes/1024);
1834
	} else {
1835
		return sprintf("%d B", $bytes);
1836
	}
1837
}
1838

    
1839
function format_number($num, $precision = 3) {
1840
	$units = array('', 'K', 'M', 'G', 'T');
1841

    
1842
	$i = 0;
1843
	while ($num > 1000 && $i < count($units)) {
1844
		$num /= 1000;
1845
		$i++;
1846
	}
1847
	round($num, $precision);
1848

    
1849
	return ("$num {$units[$i]}");
1850
}
1851

    
1852
function update_filter_reload_status($text) {
1853
	global $g;
1854

    
1855
	file_put_contents("{$g['varrun_path']}/filter_reload_status", $text);
1856
}
1857

    
1858
/****** util/return_dir_as_array
1859
 * NAME
1860
 *   return_dir_as_array - Return a directory's contents as an array.
1861
 * INPUTS
1862
 *   $dir          - string containing the path to the desired directory.
1863
 *   $filter_regex - string containing a regular expression to filter file names. Default empty.
1864
 * RESULT
1865
 *   $dir_array - array containing the directory's contents. This array will be empty if the path specified is invalid.
1866
 ******/
1867
function return_dir_as_array($dir, $filter_regex = '') {
1868
	$dir_array = array();
1869
	if (is_dir($dir)) {
1870
		if ($dh = opendir($dir)) {
1871
			while (($file = readdir($dh)) !== false) {
1872
				if (($file == ".") || ($file == "..")) {
1873
					continue;
1874
				}
1875

    
1876
				if (empty($filter_regex) || preg_match($filter_regex, $file)) {
1877
					array_push($dir_array, $file);
1878
				}
1879
			}
1880
			closedir($dh);
1881
		}
1882
	}
1883
	return $dir_array;
1884
}
1885

    
1886
function run_plugins($directory) {
1887
	global $config, $g;
1888

    
1889
	/* process packager manager custom rules */
1890
	$files = return_dir_as_array($directory);
1891
	if (is_array($files)) {
1892
		foreach ($files as $file) {
1893
			if (stristr($file, ".sh") == true) {
1894
				mwexec($directory . $file . " start");
1895
			} else if (!is_dir($directory . "/" . $file) && stristr($file, ".inc")) {
1896
				require_once($directory . "/" . $file);
1897
			}
1898
		}
1899
	}
1900
}
1901

    
1902
/*
1903
 *    safe_mkdir($path, $mode = 0755)
1904
 *    create directory if it doesn't already exist and isn't a file!
1905
 */
1906
function safe_mkdir($path, $mode = 0755) {
1907
	global $g;
1908

    
1909
	if (!is_file($path) && !is_dir($path)) {
1910
		return @mkdir($path, $mode, true);
1911
	} else {
1912
		return false;
1913
	}
1914
}
1915

    
1916
/*
1917
 * get_sysctl($names)
1918
 * Get values of sysctl OID's listed in $names (accepts an array or a single
1919
 * name) and return an array of key/value pairs set for those that exist
1920
 */
1921
function get_sysctl($names) {
1922
	if (empty($names)) {
1923
		return array();
1924
	}
1925

    
1926
	if (is_array($names)) {
1927
		$name_list = array();
1928
		foreach ($names as $name) {
1929
			$name_list[] = escapeshellarg($name);
1930
		}
1931
	} else {
1932
		$name_list = array(escapeshellarg($names));
1933
	}
1934

    
1935
	exec("/sbin/sysctl -i " . implode(" ", $name_list), $output);
1936
	$values = array();
1937
	foreach ($output as $line) {
1938
		$line = explode(": ", $line, 2);
1939
		if (count($line) == 2) {
1940
			$values[$line[0]] = $line[1];
1941
		}
1942
	}
1943

    
1944
	return $values;
1945
}
1946

    
1947
/*
1948
 * get_single_sysctl($name)
1949
 * Wrapper for get_sysctl() to simplify read of a single sysctl value
1950
 * return the value for sysctl $name or empty string if it doesn't exist
1951
 */
1952
function get_single_sysctl($name) {
1953
	if (empty($name)) {
1954
		return "";
1955
	}
1956

    
1957
	$value = get_sysctl($name);
1958
	if (empty($value) || !isset($value[$name])) {
1959
		return "";
1960
	}
1961

    
1962
	return $value[$name];
1963
}
1964

    
1965
/*
1966
 * set_sysctl($value_list)
1967
 * Set sysctl OID's listed as key/value pairs and return
1968
 * an array with keys set for those that succeeded
1969
 */
1970
function set_sysctl($values) {
1971
	if (empty($values)) {
1972
		return array();
1973
	}
1974

    
1975
	$value_list = array();
1976
	foreach ($values as $key => $value) {
1977
		$value_list[] = escapeshellarg($key) . "=" . escapeshellarg($value);
1978
	}
1979

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

    
1982
	/* Retry individually if failed (one or more read-only) */
1983
	if ($success <> 0 && count($value_list) > 1) {
1984
		foreach ($value_list as $value) {
1985
			exec("/sbin/sysctl -i " . $value, $output);
1986
		}
1987
	}
1988

    
1989
	$ret = array();
1990
	foreach ($output as $line) {
1991
		$line = explode(": ", $line, 2);
1992
		if (count($line) == 2) {
1993
			$ret[$line[0]] = true;
1994
		}
1995
	}
1996

    
1997
	return $ret;
1998
}
1999

    
2000
/*
2001
 * set_single_sysctl($name, $value)
2002
 * Wrapper to set_sysctl() to make it simple to set only one sysctl
2003
 * returns boolean meaning if it succeeded
2004
 */
2005
function set_single_sysctl($name, $value) {
2006
	if (empty($name)) {
2007
		return false;
2008
	}
2009

    
2010
	$result = set_sysctl(array($name => $value));
2011

    
2012
	if (!isset($result[$name]) || $result[$name] != $value) {
2013
		return false;
2014
	}
2015

    
2016
	return true;
2017
}
2018

    
2019
/*
2020
 *     get_memory()
2021
 *     returns an array listing the amount of
2022
 *     memory installed in the hardware
2023
 *     [0] net memory available for the OS (FreeBSD) after some is taken by BIOS, video or whatever - e.g. 235 MBytes
2024
 *     [1] real (actual) memory of the system, should be the size of the RAM card/s - e.g. 256 MBytes
2025
 */
2026
function get_memory() {
2027
	$physmem = get_single_sysctl("hw.physmem");
2028
	$realmem = get_single_sysctl("hw.realmem");
2029
	/* convert from bytes to megabytes */
2030
	return array(($physmem/1048576), ($realmem/1048576));
2031
}
2032

    
2033
function mute_kernel_msgs() {
2034
	global $g, $config;
2035
	// Do not mute serial console.  The kernel gets very very cranky
2036
	// and will start dishing you cannot control tty errors.
2037
	if ($g['platform'] == 'nanobsd') {
2038
		return;
2039
	}
2040
	if ($config['system']['enableserial']) {
2041
		return;
2042
	}
2043
	exec("/sbin/conscontrol mute on");
2044
}
2045

    
2046
function unmute_kernel_msgs() {
2047
	global $g;
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
	exec("/sbin/conscontrol mute off");
2054
}
2055

    
2056
function start_devd() {
2057
	/* Use the undocumented -q options of devd to quiet its log spamming */
2058
	$_gb = exec("/sbin/devd -q");
2059
	sleep(1);
2060
	unset($_gb);
2061
}
2062

    
2063
function is_interface_vlan_mismatch() {
2064
	global $config, $g;
2065

    
2066
	if (is_array($config['vlans']['vlan'])) {
2067
		foreach ($config['vlans']['vlan'] as $vlan) {
2068
			if (substr($vlan['if'], 0, 4) == "lagg") {
2069
				return false;
2070
			}
2071
			if (does_interface_exist($vlan['if']) == false) {
2072
				return true;
2073
			}
2074
		}
2075
	}
2076

    
2077
	return false;
2078
}
2079

    
2080
function is_interface_mismatch() {
2081
	global $config, $g;
2082

    
2083
	$do_assign = false;
2084
	$i = 0;
2085
	$missing_interfaces = array();
2086
	if (is_array($config['interfaces'])) {
2087
		foreach ($config['interfaces'] as $ifname => $ifcfg) {
2088
			if (preg_match("/^enc|^cua|^tun|^tap|^l2tp|^pptp|^ppp|^ovpn|^gif|^gre|^lagg|^bridge|vlan|_wlan/i", $ifcfg['if'])) {
2089
				// Do not check these interfaces.
2090
				$i++;
2091
				continue;
2092
			} else if (does_interface_exist($ifcfg['if']) == false) {
2093
				$missing_interfaces[] = $ifcfg['if'];
2094
				$do_assign = true;
2095
			} else {
2096
				$i++;
2097
			}
2098
		}
2099
	}
2100

    
2101
	if (file_exists("{$g['tmp_path']}/assign_complete")) {
2102
		$do_assign = false;
2103
	}
2104

    
2105
	if (!empty($missing_interfaces) && $do_assign) {
2106
		file_put_contents("{$g['tmp_path']}/missing_interfaces", implode(' ', $missing_interfaces));
2107
	} else {
2108
		@unlink("{$g['tmp_path']}/missing_interfaces");
2109
	}
2110

    
2111
	return $do_assign;
2112
}
2113

    
2114
/* sync carp entries to other firewalls */
2115
function carp_sync_client() {
2116
	global $g;
2117
	send_event("filter sync");
2118
}
2119

    
2120
/****f* util/isAjax
2121
 * NAME
2122
 *   isAjax - reports if the request is driven from prototype
2123
 * INPUTS
2124
 *   none
2125
 * RESULT
2126
 *   true/false
2127
 ******/
2128
function isAjax() {
2129
	return isset ($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest';
2130
}
2131

    
2132
/****f* util/timeout
2133
 * NAME
2134
 *   timeout - console input with timeout countdown. Note: erases 2 char of screen for timer. Leave space.
2135
 * INPUTS
2136
 *   optional, seconds to wait before timeout. Default 9 seconds.
2137
 * RESULT
2138
 *   returns 1 char of user input or null if no input.
2139
 ******/
2140
function timeout($timer = 9) {
2141
	while (!isset($key)) {
2142
		if ($timer >= 9) {
2143
			echo chr(8) . chr(8) . ($timer == 9 ? chr(32) : null) . "{$timer}";
2144
		} else {
2145
			echo chr(8). "{$timer}";
2146
		}
2147
		`/bin/stty -icanon min 0 time 25`;
2148
		$key = trim(`KEY=\`dd count=1 2>/dev/null\`; echo \$KEY`);
2149
		`/bin/stty icanon`;
2150
		if ($key == '') {
2151
			unset($key);
2152
		}
2153
		$timer--;
2154
		if ($timer == 0) {
2155
			break;
2156
		}
2157
	}
2158
	return $key;
2159
}
2160

    
2161
/****f* util/msort
2162
 * NAME
2163
 *   msort - sort array
2164
 * INPUTS
2165
 *   $array to be sorted, field to sort by, direction of sort
2166
 * RESULT
2167
 *   returns newly sorted array
2168
 ******/
2169
function msort($array, $id = "id", $sort_ascending = true) {
2170
	$temp_array = array();
2171
	while (count($array)>0) {
2172
		$lowest_id = 0;
2173
		$index = 0;
2174
		foreach ($array as $item) {
2175
			if (isset($item[$id])) {
2176
				if ($array[$lowest_id][$id]) {
2177
					if (strtolower($item[$id]) < strtolower($array[$lowest_id][$id])) {
2178
						$lowest_id = $index;
2179
					}
2180
				}
2181
			}
2182
			$index++;
2183
		}
2184
		$temp_array[] = $array[$lowest_id];
2185
		$array = array_merge(array_slice($array, 0, $lowest_id), array_slice($array, $lowest_id + 1));
2186
	}
2187
	if ($sort_ascending) {
2188
		return $temp_array;
2189
	} else {
2190
		return array_reverse($temp_array);
2191
	}
2192
}
2193

    
2194
/****f* util/is_URL
2195
 * NAME
2196
 *   is_URL
2197
 * INPUTS
2198
 *   string to check
2199
 * RESULT
2200
 *   Returns true if item is a URL
2201
 ******/
2202
function is_URL($url) {
2203
	$match = preg_match("'\b(([\w-]+://?|www[.])[^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|/)))'", $url);
2204
	if ($match) {
2205
		return true;
2206
	}
2207
	return false;
2208
}
2209

    
2210
function is_file_included($file = "") {
2211
	$files = get_included_files();
2212
	if (in_array($file, $files)) {
2213
		return true;
2214
	}
2215

    
2216
	return false;
2217
}
2218

    
2219
/*
2220
 * Replace a value on a deep associative array using regex
2221
 */
2222
function array_replace_values_recursive($data, $match, $replace) {
2223
	if (empty($data)) {
2224
		return $data;
2225
	}
2226

    
2227
	if (is_string($data)) {
2228
		$data = preg_replace("/{$match}/", $replace, $data);
2229
	} else if (is_array($data)) {
2230
		foreach ($data as $k => $v) {
2231
			$data[$k] = array_replace_values_recursive($v, $match, $replace);
2232
		}
2233
	}
2234

    
2235
	return $data;
2236
}
2237

    
2238
/*
2239
	This function was borrowed from a comment on PHP.net at the following URL:
2240
	http://www.php.net/manual/en/function.array-merge-recursive.php#73843
2241
 */
2242
function array_merge_recursive_unique($array0, $array1) {
2243

    
2244
	$arrays = func_get_args();
2245
	$remains = $arrays;
2246

    
2247
	// We walk through each arrays and put value in the results (without
2248
	// considering previous value).
2249
	$result = array();
2250

    
2251
	// loop available array
2252
	foreach ($arrays as $array) {
2253

    
2254
		// The first remaining array is $array. We are processing it. So
2255
		// we remove it from remaining arrays.
2256
		array_shift($remains);
2257

    
2258
		// We don't care non array param, like array_merge since PHP 5.0.
2259
		if (is_array($array)) {
2260
			// Loop values
2261
			foreach ($array as $key => $value) {
2262
				if (is_array($value)) {
2263
					// we gather all remaining arrays that have such key available
2264
					$args = array();
2265
					foreach ($remains as $remain) {
2266
						if (array_key_exists($key, $remain)) {
2267
							array_push($args, $remain[$key]);
2268
						}
2269
					}
2270

    
2271
					if (count($args) > 2) {
2272
						// put the recursion
2273
						$result[$key] = call_user_func_array(__FUNCTION__, $args);
2274
					} else {
2275
						foreach ($value as $vkey => $vval) {
2276
							$result[$key][$vkey] = $vval;
2277
						}
2278
					}
2279
				} else {
2280
					// simply put the value
2281
					$result[$key] = $value;
2282
				}
2283
			}
2284
		}
2285
	}
2286
	return $result;
2287
}
2288

    
2289

    
2290
/*
2291
 * converts a string like "a,b,c,d"
2292
 * into an array like array("a" => "b", "c" => "d")
2293
 */
2294
function explode_assoc($delimiter, $string) {
2295
	$array = explode($delimiter, $string);
2296
	$result = array();
2297
	$numkeys = floor(count($array) / 2);
2298
	for ($i = 0; $i < $numkeys; $i += 1) {
2299
		$result[$array[$i * 2]] = $array[$i * 2 + 1];
2300
	}
2301
	return $result;
2302
}
2303

    
2304
function get_staticroutes($returnsubnetsonly = false, $returnhostnames = false) {
2305
	global $config, $aliastable;
2306

    
2307
	/* Bail if there are no routes, but return an array always so callers don't have to check. */
2308
	if (!is_array($config['staticroutes']['route'])) {
2309
		return array();
2310
	}
2311

    
2312
	$allstaticroutes = array();
2313
	$allsubnets = array();
2314
	/* Loop through routes and expand aliases as we find them. */
2315
	foreach ($config['staticroutes']['route'] as $route) {
2316
		if (is_alias($route['network'])) {
2317
			if (!isset($aliastable[$route['network']])) {
2318
				continue;
2319
			}
2320

    
2321
			$subnets = preg_split('/\s+/', $aliastable[$route['network']]);
2322
			foreach ($subnets as $net) {
2323
				if (!is_subnet($net)) {
2324
					if (is_ipaddrv4($net)) {
2325
						$net .= "/32";
2326
					} else if (is_ipaddrv6($net)) {
2327
						$net .= "/128";
2328
					} else if ($returnhostnames === false || !is_fqdn($net)) {
2329
						continue;
2330
					}
2331
				}
2332
				$temproute = $route;
2333
				$temproute['network'] = $net;
2334
				$allstaticroutes[] = $temproute;
2335
				$allsubnets[] = $net;
2336
			}
2337
		} elseif (is_subnet($route['network'])) {
2338
			$allstaticroutes[] = $route;
2339
			$allsubnets[] = $route['network'];
2340
		}
2341
	}
2342
	if ($returnsubnetsonly) {
2343
		return $allsubnets;
2344
	} else {
2345
		return $allstaticroutes;
2346
	}
2347
}
2348

    
2349
/****f* util/get_alias_list
2350
 * NAME
2351
 *   get_alias_list - Provide a list of aliases.
2352
 * INPUTS
2353
 *   $type          - Optional, can be a string or array specifying what type(s) of aliases you need.
2354
 * RESULT
2355
 *   Array containing list of aliases.
2356
 *   If $type is unspecified, all aliases are returned.
2357
 *   If $type is a string, all aliases of the type specified in $type are returned.
2358
 *   If $type is an array, all aliases of any type specified in any element of $type are returned.
2359
 */
2360
function get_alias_list($type = null) {
2361
	global $config;
2362
	$result = array();
2363
	if ($config['aliases']['alias'] <> "" && is_array($config['aliases']['alias'])) {
2364
		foreach ($config['aliases']['alias'] as $alias) {
2365
			if ($type === null) {
2366
				$result[] = $alias['name'];
2367
			} else if (is_array($type)) {
2368
				if (in_array($alias['type'], $type)) {
2369
					$result[] = $alias['name'];
2370
				}
2371
			} else if ($type === $alias['type']) {
2372
				$result[] = $alias['name'];
2373
			}
2374
		}
2375
	}
2376
	return $result;
2377
}
2378

    
2379
/* returns an array consisting of every element of $haystack that is not equal to $needle. */
2380
function array_exclude($needle, $haystack) {
2381
	$result = array();
2382
	if (is_array($haystack)) {
2383
		foreach ($haystack as $thing) {
2384
			if ($needle !== $thing) {
2385
				$result[] = $thing;
2386
			}
2387
		}
2388
	}
2389
	return $result;
2390
}
2391

    
2392
/* Define what is preferred, IPv4 or IPv6 */
2393
function prefer_ipv4_or_ipv6() {
2394
	global $config;
2395

    
2396
	if (isset($config['system']['prefer_ipv4'])) {
2397
		mwexec("/etc/rc.d/ip6addrctl prefer_ipv4");
2398
	} else {
2399
		mwexec("/etc/rc.d/ip6addrctl prefer_ipv6");
2400
	}
2401
}
2402

    
2403
/* Redirect to page passing parameters via POST */
2404
function post_redirect($page, $params) {
2405
	if (!is_array($params)) {
2406
		return;
2407
	}
2408

    
2409
	print "<html><body><form action=\"{$page}\" name=\"formredir\" method=\"post\">\n";
2410
	foreach ($params as $key => $value) {
2411
		print "<input type=\"hidden\" name=\"{$key}\" value=\"{$value}\" />\n";
2412
	}
2413
	print "</form>\n";
2414
	print "<script type=\"text/javascript\">\n";
2415
	print "//<![CDATA[\n";
2416
	print "document.formredir.submit();\n";
2417
	print "//]]>\n";
2418
	print "</script>\n";
2419
	print "</body></html>\n";
2420
}
2421

    
2422
/* Locate disks that can be queried for S.M.A.R.T. data. */
2423
function get_smart_drive_list() {
2424
	$disk_list = explode(" ", get_single_sysctl("kern.disks"));
2425
	foreach ($disk_list as $id => $disk) {
2426
		// We only want certain kinds of disks for S.M.A.R.T.
2427
		// 1 is a match, 0 is no match, False is any problem processing the regex
2428
		if (preg_match("/^(ad|da|ada).*[0-9]{1,2}$/", $disk) !== 1) {
2429
			unset($disk_list[$id]);
2430
		}
2431
	}
2432
	sort($disk_list);
2433
	return $disk_list;
2434
}
2435

    
2436
?>
(55-55/65)