Project

General

Profile

Download (58.1 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
	pfSense_BUILDER_BINARIES:	/bin/ps	/bin/kill	/usr/bin/killall	/sbin/ifconfig	/usr/bin/netstat
60
	pfSense_BUILDER_BINARIES:	/usr/bin/awk	/sbin/dmesg		/sbin/ping /usr/local/sbin/gzsig	/usr/sbin/arp
61
	pfSense_BUILDER_BINARIES:	/sbin/conscontrol	/sbin/devd	/bin/ps
62
	pfSense_MODULE:	utils
63
*/
64

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

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

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

    
83
	return (intval($retval) == 0);
84
}
85

    
86
function isvalidproc($proc) {
87
	return is_process_running($proc);
88
}
89

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

    
97
	return 0;
98
}
99

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

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

    
114
function is_subsystem_dirty($subsystem = "") {
115
	global $g;
116

    
117
	if ($subsystem == "") {
118
		return false;
119
	}
120

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

    
125
	return false;
126
}
127

    
128
function mark_subsystem_dirty($subsystem = "") {
129
	global $g;
130

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

    
136
function clear_subsystem_dirty($subsystem = "") {
137
	global $g;
138

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

    
142
function config_lock() {
143
	return;
144
}
145
function config_unlock() {
146
	return;
147
}
148

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

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

    
190
		return $fp;
191
	}
192

    
193
	return NULL;
194
}
195

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

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

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

    
211
function send_event($cmd) {
212
	global $g;
213

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

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

    
236
function send_multiple_events($cmds) {
237
	global $g;
238

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

    
243
	if (!is_array($cmds)) {
244
		return;
245
	}
246

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

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

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

    
297
	return $shm_data;
298
}
299

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

    
326
	return $shm_data;
327
}
328

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

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

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

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

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

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

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

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

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

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

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

    
430
/* Convert long int to IP address, truncating to 32-bits. */
431
function long2ip32($ip) {
432
	return long2ip($ip & 0xFFFFFFFF);
433
}
434

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

    
440
/* Convert IP address to unsigned long int. */
441
function ip2ulong($ip) {
442
	return sprintf("%u", ip2long32($ip));
443
}
444

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

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

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

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

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

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

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

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

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

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

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

    
528
	return $rangeaddresses;
529
}
530

    
531
/* Convert a range of IPv4 addresses to an array of subnets which can contain the range. */
532
/* Note: IPv6 ranges are not yet supported here. */
533
function ip_range_to_subnet_array($startip, $endip) {
534
	if (!is_ipaddrv4($startip) || !is_ipaddrv4($endip)) {
535
		return array();
536
	}
537

    
538
	if (ip_greater_than($startip, $endip)) {
539
		// Swap start and end so we can process sensibly.
540
		$temp = $startip;
541
		$startip = $endip;
542
		$endip = $temp;
543
	}
544

    
545
	// Container for subnets within this range.
546
	$rangesubnets = array();
547

    
548
	// Figure out what the smallest subnet is that holds the number of IPs in the given range.
549
	$cidr = find_smallest_cidr_v4(ip_range_size_v4($startip, $endip));
550

    
551
	// Loop here to reduce subnet size and retest as needed. We need to make sure
552
	//   that the target subnet is wholly contained between $startip and $endip.
553
	for ($cidr; $cidr <= 32; $cidr++) {
554
		// Find the network and broadcast addresses for the subnet being tested.
555
		$targetsub_min = gen_subnet($startip, $cidr);
556
		$targetsub_max = gen_subnet_max($startip, $cidr);
557

    
558
		// Check best case where the range is exactly one subnet.
559
		if (($targetsub_min == $startip) && ($targetsub_max == $endip)) {
560
			// Hooray, the range is exactly this subnet!
561
			return array("{$startip}/{$cidr}");
562
		}
563

    
564
		// These remaining scenarios will find a subnet that uses the largest
565
		//  chunk possible of the range being tested, and leave the rest to be
566
		//  tested recursively after the loop.
567

    
568
		// Check if the subnet begins with $startip and ends before $endip
569
		if (($targetsub_min == $startip) && ip_less_than($targetsub_max, $endip)) {
570
			break;
571
		}
572

    
573
		// Check if the subnet ends at $endip and starts after $startip
574
		if (ip_greater_than($targetsub_min, $startip) && ($targetsub_max == $endip)) {
575
			break;
576
		}
577

    
578
		// Check if the subnet is between $startip and $endip
579
		if (ip_greater_than($targetsub_min, $startip) && ip_less_than($targetsub_max, $endip)) {
580
			break;
581
		}
582
	}
583

    
584
	// Some logic that will recursively search from $startip to the first IP before the start of the subnet we just found.
585
	// NOTE: This may never be hit, the way the above algo turned out, but is left for completeness.
586
	if ($startip != $targetsub_min) {
587
		$rangesubnets = array_merge($rangesubnets, ip_range_to_subnet_array($startip, ip_before($targetsub_min)));
588
	}
589

    
590
	// Add in the subnet we found before, to preserve ordering
591
	$rangesubnets[] = "{$targetsub_min}/{$cidr}";
592

    
593
	// And some more logic that will search after the subnet we found to fill in to the end of the range.
594
	if ($endip != $targetsub_max) {
595
		$rangesubnets = array_merge($rangesubnets, ip_range_to_subnet_array(ip_after($targetsub_max), $endip));
596
	}
597
	return $rangesubnets;
598
}
599

    
600
/* returns true if $range is a valid pair of IPv4 or IPv6 addresses separated by a "-"
601
	false - if not a valid pair
602
	true (numeric 4 or 6) - if valid, gives type of addresses */
603
function is_iprange($range) {
604
	if (substr_count($range, '-') != 1) {
605
		return false;
606
	}
607
	list($ip1, $ip2) = explode ('-', $range);
608
	if (is_ipaddrv4($ip1) && is_ipaddrv4($ip2)) {
609
		return 4;
610
	}
611
	if (is_ipaddrv6($ip1) && is_ipaddrv6($ip2)) {
612
		return 6;
613
	}
614
	return false;
615
}
616

    
617
/* returns true if $ipaddr is a valid dotted IPv4 address or a IPv6
618
	false - not valid
619
	true (numeric 4 or 6) - if valid, gives type of address */
620
function is_ipaddr($ipaddr) {
621
	if (is_ipaddrv4($ipaddr)) {
622
		return 4;
623
	}
624
	if (is_ipaddrv6($ipaddr)) {
625
		return 6;
626
	}
627
	return false;
628
}
629

    
630
/* returns true if $ipaddr is a valid IPv6 address */
631
function is_ipaddrv6($ipaddr) {
632
	if (!is_string($ipaddr) || empty($ipaddr)) {
633
		return false;
634
	}
635
	if (strstr($ipaddr, "%") && is_linklocal($ipaddr)) {
636
		$tmpip = explode("%", $ipaddr);
637
		$ipaddr = $tmpip[0];
638
	}
639
	return Net_IPv6::checkIPv6($ipaddr);
640
}
641

    
642
/* returns true if $ipaddr is a valid dotted IPv4 address */
643
function is_ipaddrv4($ipaddr) {
644
	if (!is_string($ipaddr) || empty($ipaddr) || ip2long($ipaddr) === FALSE) {
645
		return false;
646
	}
647
	return true;
648
}
649

    
650
/* returns true if $ipaddr is a valid IPv6 linklocal address */
651
function is_linklocal($ipaddr) {
652
	return (strtolower(substr($ipaddr, 0, 5)) == "fe80:");
653
}
654

    
655
/* returns scope of a linklocal address */
656
function get_ll_scope($addr) {
657
	if (!is_linklocal($addr) || !strstr($addr, "%")) {
658
		return "";
659
	}
660
	list ($ll, $scope) = explode("%", $addr);
661
	return $scope;
662
}
663

    
664
/* returns true if $ipaddr is a valid literal IPv6 address */
665
function is_literalipaddrv6($ipaddr) {
666
	if (preg_match("/\[([0-9a-f:]+)\]/i", $ipaddr, $match)) {
667
		$ipaddr = $match[1];
668
	} else {
669
		return false;
670
	}
671

    
672
	return is_ipaddrv6($ipaddr);
673
}
674

    
675
/* returns true if $iport is a valid IPv4/IPv6 address + port
676
	false - not valid
677
	true (numeric 4 or 6) - if valid, gives type of address */
678
function is_ipaddrwithport($ipport) {
679
	$c = strrpos($ipport, ":");
680
	if ($c === false) {
681
		return false;  // can't split at final colon if no colon exists
682
	}
683

    
684
	if (!is_port(substr($ipport, $c + 1))) {
685
		return false;  // no valid port after last colon
686
	}
687

    
688
	$ip = substr($ipport, 0, $c);  // else is text before last colon a valid IP
689
	if (is_literalipaddrv6($ip)) {
690
		return 6;
691
	} elseif (is_ipaddrv4($ip)) {
692
		return 4;
693
	} else {
694
		return false;
695
	}
696
}
697

    
698
function is_hostnamewithport($hostport) {
699
	$parts = explode(":", $hostport);
700
	$port = array_pop($parts);
701
	if (count($parts) == 1) {
702
		return is_hostname($parts[0]) && is_port($port);
703
	} else {
704
		return false;
705
	}
706
}
707

    
708
/* returns true if $ipaddr is a valid dotted IPv4 address or an alias thereof */
709
function is_ipaddroralias($ipaddr) {
710
	global $config;
711

    
712
	if (is_alias($ipaddr)) {
713
		if (is_array($config['aliases']['alias'])) {
714
			foreach ($config['aliases']['alias'] as $alias) {
715
				if ($alias['name'] == $ipaddr && !preg_match("/port/i", $alias['type'])) {
716
					return true;
717
				}
718
			}
719
		}
720
		return false;
721
	} else {
722
		return is_ipaddr($ipaddr);
723
	}
724

    
725
}
726

    
727
/* returns true if $subnet is a valid IPv4 or IPv6 subnet in CIDR format
728
	false - if not a valid subnet
729
	true (numeric 4 or 6) - if valid, gives type of subnet */
730
function is_subnet($subnet) {
731
	if (is_string($subnet) && preg_match('/^(?:([0-9.]{7,15})|([0-9a-f:]{2,39}))\/(\d{1,3})$/i', $subnet, $parts)) {
732
		if (is_ipaddrv4($parts[1]) && $parts[3] <= 32) {
733
			return 4;
734
		}
735
		if (is_ipaddrv6($parts[2]) && $parts[3] <= 128) {
736
			return 6;
737
		}
738
	}
739
	return false;
740
}
741

    
742
/* same as is_subnet() but accepts IPv4 only */
743
function is_subnetv4($subnet) {
744
	return (is_subnet($subnet) == 4);
745
}
746

    
747
/* same as is_subnet() but accepts IPv6 only */
748
function is_subnetv6($subnet) {
749
	return (is_subnet($subnet) == 6);
750
}
751

    
752
/* returns true if $subnet is a valid subnet in CIDR format or an alias thereof */
753
function is_subnetoralias($subnet) {
754
	global $aliastable;
755

    
756
	if (isset($aliastable[$subnet]) && is_subnet($aliastable[$subnet])) {
757
		return true;
758
	} else {
759
		return is_subnet($subnet);
760
	}
761
}
762

    
763
function subnet_size($subnet) {
764
	if (is_subnetv4($subnet)) {
765
		list ($ip, $bits) = explode("/", $subnet);
766
		return round(exp(log(2) * (32 - $bits)));
767
	}
768
	else if (is_subnetv6($subnet)) {
769
		list ($ip, $bits) = explode("/", $subnet);
770
		return round(exp(log(2) * (128 - $bits)));
771
	}
772
	else {
773
		return 0;
774
	}
775
}
776

    
777

    
778
function subnet_expand($subnet) {
779
	if (is_subnetv4($subnet)) {
780
		return subnetv4_expand($subnet);
781
	} else if (is_subnetv6($subnet)) {
782
		return subnetv6_expand($subnet);
783
	} else {
784
		return $subnet;
785
	}
786
}
787

    
788
function subnetv4_expand($subnet) {
789
	$result = array();
790
	list ($ip, $bits) = explode("/", $subnet);
791
	$net = ip2long($ip);
792
	$mask = (0xffffffff << (32 - $bits));
793
	$net &= $mask;
794
	$size = round(exp(log(2) * (32 - $bits)));
795
	for ($i = 0; $i < $size; $i += 1) {
796
		$result[] = long2ip($net | $i);
797
	}
798
	return $result;
799
}
800

    
801
/* find out whether two subnets overlap */
802
function check_subnets_overlap($subnet1, $bits1, $subnet2, $bits2) {
803

    
804
	if (!is_numeric($bits1)) {
805
		$bits1 = 32;
806
	}
807
	if (!is_numeric($bits2)) {
808
		$bits2 = 32;
809
	}
810

    
811
	if ($bits1 < $bits2) {
812
		$relbits = $bits1;
813
	} else {
814
		$relbits = $bits2;
815
	}
816

    
817
	$sn1 = gen_subnet_mask_long($relbits) & ip2long($subnet1);
818
	$sn2 = gen_subnet_mask_long($relbits) & ip2long($subnet2);
819

    
820
	return ($sn1 == $sn2);
821
}
822

    
823
/* find out whether two IPv6 subnets overlap */
824
function check_subnetsv6_overlap($subnet1, $bits1, $subnet2, $bits2) {
825
	$sub1_min = gen_subnetv6($subnet1, $bits1);
826
	$sub1_max = gen_subnetv6_max($subnet1, $bits1);
827
	$sub2_min = gen_subnetv6($subnet2, $bits2);
828
	$sub2_max = gen_subnetv6_max($subnet2, $bits2);
829

    
830
	return (is_inrange_v6($sub1_min, $sub2_min, $sub2_max) || is_inrange_v6($sub1_max, $sub2_min, $sub2_max) || is_inrange_v6($sub2_min, $sub1_min, $sub1_max));
831
}
832

    
833
/* return true if $addr is in $subnet, false if not */
834
function ip_in_subnet($addr, $subnet) {
835
	if (is_ipaddrv6($addr) && is_subnetv6($subnet)) {
836
		return (Net_IPv6::isInNetmask($addr, $subnet));
837
	} else if (is_ipaddrv4($addr) && is_subnetv4($subnet)) {
838
		list($ip, $mask) = explode('/', $subnet);
839
		$mask = (0xffffffff << (32 - $mask)) & 0xffffffff;
840
		return ((ip2long($addr) & $mask) == (ip2long($ip) & $mask));
841
	}
842
	return false;
843
}
844

    
845
/* returns true if $hostname is just a valid hostname (top part without any of the domain part) */
846
function is_unqualified_hostname($hostname) {
847
	if (!is_string($hostname)) {
848
		return false;
849
	}
850

    
851
	if (preg_match('/^(?:[a-z0-9_]|[a-z0-9_][a-z0-9_\-]*[a-z0-9_])$/i', $hostname)) {
852
		return true;
853
	} else {
854
		return false;
855
	}
856
}
857

    
858
/* returns true if $hostname is a valid hostname, with or without being a fully-qualified domain name. */
859
function is_hostname($hostname, $allow_wildcard=false) {
860
	if (!is_string($hostname)) {
861
		return false;
862
	}
863

    
864
	if (is_domain($hostname, $allow_wildcard)) {
865
		if ((substr_count($hostname, ".") == 1) && ($hostname[strlen($hostname)-1] == ".")) {
866
			/* Only a single dot at the end like "test." - hosts cannot be directly in the root domain. */
867
			return false;
868
		} else {
869
			return true;
870
		}
871
	} else {
872
		return false;
873
	}
874
}
875

    
876
/* returns true if $domain is a valid domain name */
877
function is_domain($domain, $allow_wildcard=false) {
878
	if (!is_string($domain)) {
879
		return false;
880
	}
881
	if ($allow_wildcard) {
882
		$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';
883
	} else {
884
		$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';
885
	}
886

    
887
	if (preg_match($domain_regex, $domain)) {
888
		return true;
889
	} else {
890
		return false;
891
	}
892
}
893

    
894
/* returns true if $macaddr is a valid MAC address */
895
function is_macaddr($macaddr, $partial=false) {
896
	$repeat = ($partial) ? '1,5' : '5';
897
	return preg_match('/^[0-9A-F]{2}(?:[:][0-9A-F]{2}){'.$repeat.'}$/i', $macaddr) == 1 ? true : false;
898
}
899

    
900
/* returns true if $name is a valid name for an alias
901
   returns NULL if a reserved word is used
902
   returns FALSE for bad chars in the name - this allows calling code to determine what the problem was.
903
   aliases cannot be:
904
	bad chars: anything except a-z 0-9 and underscore
905
	bad names: empty string, pure numeric, pure underscore
906
	reserved words: pre-defined service/protocol/port names which should not be ambiguous, and the words "port" and  "pass" */
907

    
908
function is_validaliasname($name) {
909
	/* Array of reserved words */
910
	$reserved = array("port", "pass");
911

    
912
	if (!is_string($name) || strlen($name) >= 32 || preg_match('/(^_*$|^\d*$|[^a-z0-9_])/i', $name)) {
913
		return false;
914
	}
915
	if (in_array($name, $reserved, true) || getservbyname($name, "tcp") || getservbyname($name, "udp") || getprotobyname($name)) {
916
		return; /* return NULL */
917
	}
918
	return true;
919
}
920

    
921
/* returns true if $port is a valid TCP/UDP port */
922
function is_port($port) {
923
	if (ctype_digit($port) && ((intval($port) >= 1) && (intval($port) <= 65535))) {
924
		return true;
925
	}
926
	if (getservbyname($port, "tcp") || getservbyname($port, "udp")) {
927
		return true;
928
	}
929
	return false;
930
}
931

    
932
/* returns true if $portrange is a valid TCP/UDP portrange ("<port>:<port>") */
933
function is_portrange($portrange) {
934
	$ports = explode(":", $portrange);
935

    
936
	return (count($ports) == 2 && is_port($ports[0]) && is_port($ports[1]));
937
}
938

    
939
/* returns true if $port is a valid port number or an alias thereof */
940
function is_portoralias($port) {
941
	global $config;
942

    
943
	if (is_alias($port)) {
944
		if (is_array($config['aliases']['alias'])) {
945
			foreach ($config['aliases']['alias'] as $alias) {
946
				if ($alias['name'] == $port && preg_match("/port/i", $alias['type'])) {
947
					return true;
948
				}
949
			}
950
		}
951
		return false;
952
	} else {
953
		return is_port($port);
954
	}
955
}
956

    
957
/* create ranges of sequential port numbers (200:215) and remove duplicates */
958
function group_ports($ports) {
959
	if (!is_array($ports) || empty($ports)) {
960
		return;
961
	}
962

    
963
	$uniq = array();
964
	foreach ($ports as $port) {
965
		if (is_portrange($port)) {
966
			list($begin, $end) = explode(":", $port);
967
			if ($begin > $end) {
968
				$aux = $begin;
969
				$begin = $end;
970
				$end = $aux;
971
			}
972
			for ($i = $begin; $i <= $end; $i++) {
973
				if (!in_array($i, $uniq)) {
974
					$uniq[] = $i;
975
				}
976
			}
977
		} else if (is_port($port)) {
978
			if (!in_array($port, $uniq)) {
979
				$uniq[] = $port;
980
			}
981
		}
982
	}
983
	sort($uniq, SORT_NUMERIC);
984

    
985
	$result = array();
986
	foreach ($uniq as $idx => $port) {
987
		if ($idx == 0) {
988
			$result[] = $port;
989
			continue;
990
		}
991

    
992
		$last = end($result);
993
		if (is_portrange($last)) {
994
			list($begin, $end) = explode(":", $last);
995
		} else {
996
			$begin = $end = $last;
997
		}
998

    
999
		if ($port == ($end+1)) {
1000
			$end++;
1001
			$result[count($result)-1] = "{$begin}:{$end}";
1002
		} else {
1003
			$result[] = $port;
1004
		}
1005
	}
1006

    
1007
	return $result;
1008
}
1009

    
1010
/* returns true if $val is a valid shaper bandwidth value */
1011
function is_valid_shaperbw($val) {
1012
	return (preg_match("/^(\d+(?:\.\d+)?)([MKG]?b|%)$/", $val));
1013
}
1014

    
1015
/* returns true if $test is in the range between $start and $end */
1016
function is_inrange_v4($test, $start, $end) {
1017
	if ((ip2ulong($test) <= ip2ulong($end)) && (ip2ulong($test) >= ip2ulong($start))) {
1018
		return true;
1019
	} else {
1020
		return false;
1021
	}
1022
}
1023

    
1024
/* returns true if $test is in the range between $start and $end */
1025
function is_inrange_v6($test, $start, $end) {
1026
	if ((inet_pton($test) <= inet_pton($end)) && (inet_pton($test) >= inet_pton($start))) {
1027
		return true;
1028
	} else {
1029
		return false;
1030
	}
1031
}
1032

    
1033
/* returns true if $test is in the range between $start and $end */
1034
function is_inrange($test, $start, $end) {
1035
	return is_ipaddrv6($test) ? is_inrange_v6($test, $start, $end) : is_inrange_v4($test, $start, $end);
1036
}
1037

    
1038
/* XXX: return the configured carp interface list */
1039
function get_configured_carp_interface_list($carpinterface = '', $family = 'inet', $what = 'ip') {
1040
	global $config;
1041

    
1042
	$iflist = array();
1043

    
1044
	if (!is_array($config['virtualip']['vip']) || empty($config['virtualip']['vip'])) {
1045
		return $iflist;
1046
	}
1047

    
1048
	$viparr = &$config['virtualip']['vip'];
1049
	foreach ($viparr as $vip) {
1050
		if ($vip['mode'] != "carp") {
1051
			continue;
1052
		}
1053

    
1054
		if (empty($carpinterface)) {
1055
			$iflist["_vip{$vip['uniqid']}"] = $vip['subnet'];
1056
			continue;
1057
		}
1058

    
1059
		if ($carpinterface != "_vip{$vip['uniqid']}") {
1060
			continue;
1061
		}
1062

    
1063
		switch ($what) {
1064
			case 'subnet':
1065
				if ($family == 'inet' && is_ipaddrv4($vip['subnet'])) {
1066
					return $vip['subnet_bits'];
1067
				} else if ($family == 'inet6' && is_ipaddrv6($vip['subnet'])) {
1068
					return $vip['subnet_bits'];
1069
				}
1070
				break;
1071
			case 'iface':
1072
				if ($family == 'inet' && is_ipaddrv4($vip['subnet'])) {
1073
					return $vip['interface'];
1074
				} else if ($family == 'inet6' && is_ipaddrv6($vip['subnet'])) {
1075
					return $vip['interface'];
1076
				}
1077
				break;
1078
			case 'vip':
1079
				if ($family == 'inet' && is_ipaddrv4($vip['subnet'])) {
1080
					return $vip;
1081
				} else if ($family == 'inet6' && is_ipaddrv6($vip['subnet'])) {
1082
					return $vip;
1083
				}
1084
				break;
1085
			case 'ip':
1086
			default:
1087
				if ($family == 'inet' && is_ipaddrv4($vip['subnet'])) {
1088
					return $vip['subnet'];
1089
				} else if ($family == 'inet6' && is_ipaddrv6($vip['subnet'])) {
1090
					return $vip['subnet'];
1091
				}
1092
				break;
1093
		}
1094
		break;
1095
	}
1096

    
1097
	return $iflist;
1098
}
1099

    
1100
/* return the configured IP aliases list */
1101
function get_configured_ip_aliases_list($returnfullentry = false) {
1102
	global $config;
1103

    
1104
	$alias_list = array();
1105

    
1106
	if (is_array($config['virtualip']['vip'])) {
1107
		$viparr = &$config['virtualip']['vip'];
1108
		foreach ($viparr as $vip) {
1109
			if ($vip['mode'] == "ipalias") {
1110
				if ($returnfullentry) {
1111
					$alias_list[$vip['subnet']] = $vip;
1112
				} else {
1113
					$alias_list[$vip['subnet']] = $vip['interface'];
1114
				}
1115
			}
1116
		}
1117
	}
1118

    
1119
	return $alias_list;
1120
}
1121

    
1122
/* return all configured aliases list (IP, carp, proxyarp and other) */
1123
function get_configured_vips_list() {
1124
	global $config;
1125

    
1126
	$alias_list = array();
1127

    
1128
	if (is_array($config['virtualip']['vip'])) {
1129
		$viparr = &$config['virtualip']['vip'];
1130
		foreach ($viparr as $vip) {
1131
			if ($vip['mode'] == "carp") {
1132
				$alias_list[] = array("ipaddr" => $vip['subnet'], "if" => "_vip{$vip['uniqid']}");
1133
			} else {
1134
				$alias_list[] = array("ipaddr" => $vip['subnet'], "if" => $vip['interface']);
1135
			}
1136
		}
1137
	}
1138

    
1139
	return $alias_list;
1140
}
1141

    
1142
/* comparison function for sorting by the order in which interfaces are normally created */
1143
function compare_interface_friendly_names($a, $b) {
1144
	if ($a == $b) {
1145
		return 0;
1146
	} else if ($a == 'wan') {
1147
		return -1;
1148
	} else if ($b == 'wan') {
1149
		return 1;
1150
	} else if ($a == 'lan') {
1151
		return -1;
1152
	} else if ($b == 'lan') {
1153
		return 1;
1154
	}
1155

    
1156
	return strnatcmp($a, $b);
1157
}
1158

    
1159
/* return the configured interfaces list. */
1160
function get_configured_interface_list($only_opt = false, $withdisabled = false) {
1161
	global $config;
1162

    
1163
	$iflist = array();
1164

    
1165
	/* if list */
1166
	foreach ($config['interfaces'] as $if => $ifdetail) {
1167
		if ($only_opt && ($if == "wan" || $if == "lan")) {
1168
			continue;
1169
		}
1170
		if (isset($ifdetail['enable']) || $withdisabled == true) {
1171
			$iflist[$if] = $if;
1172
		}
1173
	}
1174

    
1175
	return $iflist;
1176
}
1177

    
1178
/* return the configured interfaces list. */
1179
function get_configured_interface_list_by_realif($only_opt = false, $withdisabled = false) {
1180
	global $config;
1181

    
1182
	$iflist = array();
1183

    
1184
	/* if list */
1185
	foreach ($config['interfaces'] as $if => $ifdetail) {
1186
		if ($only_opt && ($if == "wan" || $if == "lan")) {
1187
			continue;
1188
		}
1189
		if (isset($ifdetail['enable']) || $withdisabled == true) {
1190
			$tmpif = get_real_interface($if);
1191
			if (!empty($tmpif)) {
1192
				$iflist[$tmpif] = $if;
1193
			}
1194
		}
1195
	}
1196

    
1197
	return $iflist;
1198
}
1199

    
1200
/* return the configured interfaces list with their description. */
1201
function get_configured_interface_with_descr($only_opt = false, $withdisabled = false) {
1202
	global $config;
1203

    
1204
	$iflist = array();
1205

    
1206
	/* if list */
1207
	foreach ($config['interfaces'] as $if => $ifdetail) {
1208
		if ($only_opt && ($if == "wan" || $if == "lan")) {
1209
			continue;
1210
		}
1211
		if (isset($ifdetail['enable']) || $withdisabled == true) {
1212
			if (empty($ifdetail['descr'])) {
1213
				$iflist[$if] = strtoupper($if);
1214
			} else {
1215
				$iflist[$if] = strtoupper($ifdetail['descr']);
1216
			}
1217
		}
1218
	}
1219

    
1220
	return $iflist;
1221
}
1222

    
1223
/*
1224
 *   get_configured_ip_addresses() - Return a list of all configured
1225
 *   interfaces IP Addresses
1226
 *
1227
 */
1228
function get_configured_ip_addresses() {
1229
	global $config;
1230

    
1231
	if (!function_exists('get_interface_ip')) {
1232
		require_once("interfaces.inc");
1233
	}
1234
	$ip_array = array();
1235
	$interfaces = get_configured_interface_list();
1236
	if (is_array($interfaces)) {
1237
		foreach ($interfaces as $int) {
1238
			$ipaddr = get_interface_ip($int);
1239
			$ip_array[$int] = $ipaddr;
1240
		}
1241
	}
1242
	$interfaces = get_configured_carp_interface_list();
1243
	if (is_array($interfaces)) {
1244
		foreach ($interfaces as $int => $ipaddr) {
1245
			$ip_array[$int] = $ipaddr;
1246
		}
1247
	}
1248

    
1249
	/* pppoe server */
1250
	if (is_array($config['pppoes']) && is_array($config['pppoes']['pppoe'])) {
1251
		foreach ($config['pppoes']['pppoe'] as $pppoe) {
1252
			if ($pppoe['mode'] == "server") {
1253
				if (is_ipaddr($pppoe['localip'])) {
1254
					$int = "pppoes". $pppoe['pppoeid'];
1255
					$ip_array[$int] = $pppoe['localip'];
1256
				}
1257
			}
1258
		}
1259
	}
1260

    
1261
	return $ip_array;
1262
}
1263

    
1264
/*
1265
 *   get_configured_ipv6_addresses() - Return a list of all configured
1266
 *   interfaces IPv6 Addresses
1267
 *
1268
 */
1269
function get_configured_ipv6_addresses() {
1270
	require_once("interfaces.inc");
1271
	$ipv6_array = array();
1272
	$interfaces = get_configured_interface_list();
1273
	if (is_array($interfaces)) {
1274
		foreach ($interfaces as $int) {
1275
			$ipaddrv6 = get_interface_ipv6($int);
1276
			$ipv6_array[$int] = $ipaddrv6;
1277
		}
1278
	}
1279
	$interfaces = get_configured_carp_interface_list();
1280
	if (is_array($interfaces)) {
1281
		foreach ($interfaces as $int => $ipaddrv6) {
1282
			$ipv6_array[$int] = $ipaddrv6;
1283
		}
1284
	}
1285
	return $ipv6_array;
1286
}
1287

    
1288
/*
1289
 *   get_interface_list() - Return a list of all physical interfaces
1290
 *   along with MAC and status.
1291
 *
1292
 *   $mode = "active" - use ifconfig -lu
1293
 *           "media"  - use ifconfig to check physical connection
1294
 *			status (much slower)
1295
 */
1296
function get_interface_list($mode = "active", $keyby = "physical", $vfaces = "") {
1297
	global $config;
1298
	$upints = array();
1299
	/* get a list of virtual interface types */
1300
	if (!$vfaces) {
1301
		$vfaces = array(
1302
				'bridge',
1303
				'ppp',
1304
				'pppoe',
1305
				'pptp',
1306
				'l2tp',
1307
				'sl',
1308
				'gif',
1309
				'gre',
1310
				'faith',
1311
				'lo',
1312
				'ng',
1313
				'_vlan',
1314
				'_wlan',
1315
				'pflog',
1316
				'plip',
1317
				'pfsync',
1318
				'enc',
1319
				'tun',
1320
				'carp',
1321
				'lagg',
1322
				'vip',
1323
				'ipfw'
1324
		);
1325
	}
1326
	switch ($mode) {
1327
		case "active":
1328
			$upints = pfSense_interface_listget(IFF_UP);
1329
			break;
1330
		case "media":
1331
			$intlist = pfSense_interface_listget();
1332
			$ifconfig = "";
1333
			exec("/sbin/ifconfig -a", $ifconfig);
1334
			$regexp = '/(' . implode('|', $intlist) . '):\s/';
1335
			$ifstatus = preg_grep('/status:/', $ifconfig);
1336
			foreach ($ifstatus as $status) {
1337
				$int = array_shift($intlist);
1338
				if (stristr($status, "active")) {
1339
					$upints[] = $int;
1340
				}
1341
			}
1342
			break;
1343
		default:
1344
			$upints = pfSense_interface_listget();
1345
			break;
1346
	}
1347
	/* build interface list with netstat */
1348
	$linkinfo = "";
1349
	exec("/usr/bin/netstat -inW -f link | awk '{ print $1, $4 }'", $linkinfo);
1350
	array_shift($linkinfo);
1351
	/* build ip address list with netstat */
1352
	$ipinfo = "";
1353
	exec("/usr/bin/netstat -inW -f inet | awk '{ print $1, $4 }'", $ipinfo);
1354
	array_shift($ipinfo);
1355
	foreach ($linkinfo as $link) {
1356
		$friendly = "";
1357
		$alink = explode(" ", $link);
1358
		$ifname = rtrim(trim($alink[0]), '*');
1359
		/* trim out all numbers before checking for vfaces */
1360
		if (!in_array(array_shift(preg_split('/\d/', $ifname)), $vfaces) &&
1361
		    !stristr($ifname, "_vlan") && !stristr($ifname, "_wlan")) {
1362
			$toput = array(
1363
					"mac" => trim($alink[1]),
1364
					"up" => in_array($ifname, $upints)
1365
				);
1366
			foreach ($ipinfo as $ip) {
1367
				$aip = explode(" ", $ip);
1368
				if ($aip[0] == $ifname) {
1369
					$toput['ipaddr'] = $aip[1];
1370
				}
1371
			}
1372
			if (is_array($config['interfaces'])) {
1373
				foreach ($config['interfaces'] as $name => $int) {
1374
					if ($int['if'] == $ifname) {
1375
						$friendly = $name;
1376
					}
1377
				}
1378
			}
1379
			switch ($keyby) {
1380
			case "physical":
1381
				if ($friendly != "") {
1382
					$toput['friendly'] = $friendly;
1383
				}
1384
				$dmesg_arr = array();
1385
				exec("/sbin/dmesg |grep $ifname | head -n1", $dmesg_arr);
1386
				preg_match_all("/<(.*?)>/i", $dmesg_arr[0], $dmesg);
1387
				$toput['dmesg'] = $dmesg[1][0];
1388
				$iflist[$ifname] = $toput;
1389
				break;
1390
			case "ppp":
1391

    
1392
			case "friendly":
1393
				if ($friendly != "") {
1394
					$toput['if'] = $ifname;
1395
					$iflist[$friendly] = $toput;
1396
				}
1397
				break;
1398
			}
1399
		}
1400
	}
1401
	return $iflist;
1402
}
1403

    
1404
/****f* util/log_error
1405
* NAME
1406
*   log_error  - Sends a string to syslog.
1407
* INPUTS
1408
*   $error     - string containing the syslog message.
1409
* RESULT
1410
*   null
1411
******/
1412
function log_error($error) {
1413
	global $g;
1414
	$page = $_SERVER['SCRIPT_NAME'];
1415
	if (empty($page)) {
1416
		$files = get_included_files();
1417
		$page = basename($files[0]);
1418
	}
1419
	syslog(LOG_ERR, "$page: $error");
1420
	if ($g['debug']) {
1421
		syslog(LOG_WARNING, var_dump(debug_backtrace()));
1422
	}
1423
	return;
1424
}
1425

    
1426
/****f* util/log_auth
1427
* NAME
1428
*   log_auth   - Sends a string to syslog as LOG_AUTH facility
1429
* INPUTS
1430
*   $error     - string containing the syslog message.
1431
* RESULT
1432
*   null
1433
******/
1434
function log_auth($error) {
1435
	global $g;
1436
	$page = $_SERVER['SCRIPT_NAME'];
1437
	syslog(LOG_AUTH, "$page: $error");
1438
	if ($g['debug']) {
1439
		syslog(LOG_WARNING, var_dump(debug_backtrace()));
1440
	}
1441
	return;
1442
}
1443

    
1444
/****f* util/exec_command
1445
 * NAME
1446
 *   exec_command - Execute a command and return a string of the result.
1447
 * INPUTS
1448
 *   $command   - String of the command to be executed.
1449
 * RESULT
1450
 *   String containing the command's result.
1451
 * NOTES
1452
 *   This function returns the command's stdout and stderr.
1453
 ******/
1454
function exec_command($command) {
1455
	$output = array();
1456
	exec($command . ' 2>&1', $output);
1457
	return(implode("\n", $output));
1458
}
1459

    
1460
/* wrapper for exec()
1461
   Executes in background or foreground.
1462
   For background execution, returns PID of background process to allow calling code control */
1463
function mwexec($command, $nologentry = false, $clearsigmask = false, $background = false) {
1464
	global $g;
1465
	$retval = 0;
1466

    
1467
	if ($g['debug']) {
1468
		if (!$_SERVER['REMOTE_ADDR']) {
1469
			echo "mwexec(): $command" . ($background ? " [BG]":"") . "\n";
1470
		}
1471
	}
1472
	if ($clearsigmask) {
1473
		$oldset = array();
1474
		pcntl_sigprocmask(SIG_SETMASK, array(), $oldset);
1475
	}
1476

    
1477
	if ($background) {
1478
		// start background process and return PID
1479
		$retval = exec("/usr/bin/nohup $command > /dev/null 2>&1 & echo $!");
1480
	} else {
1481
		// run in foreground, and (optionally) log if nonzero return
1482
		$outputarray = array();
1483
		exec("$command 2>&1", $outputarray, $retval);
1484
		if (($retval <> 0) && (!$nologentry || isset($config['system']['developerspew']))) {
1485
			log_error(sprintf(gettext("The command '%1\$s' returned exit code '%2\$d', the output was '%3\$s' "), $command, $retval, implode(" ", $outputarray)));
1486
		}
1487
	}
1488

    
1489
	if ($clearsigmask) {
1490
		pcntl_sigprocmask(SIG_SETMASK, $oldset);
1491
	}
1492

    
1493
	return $retval;
1494
}
1495

    
1496
/* wrapper for exec() in background */
1497
function mwexec_bg($command, $clearsigmask = false) {
1498
	return mwexec($command, false, $clearsigmask, true);
1499
}
1500

    
1501
/* unlink a file, or pattern-match of a file, if it exists
1502
   if the file/path contains glob() compatible wildcards, all matching files will be unlinked
1503
   if no matches, no error occurs */
1504
function unlink_if_exists($fn) {
1505
	$to_do = glob($fn);
1506
	if (is_array($to_do) && count($to_do) > 0) {
1507
		array_map("unlink", $to_do);
1508
	} else {
1509
		@unlink($fn);
1510
	}
1511
}
1512
/* make a global alias table (for faster lookups) */
1513
function alias_make_table($config) {
1514
	global $aliastable;
1515

    
1516
	$aliastable = array();
1517

    
1518
	if (is_array($config['aliases']['alias'])) {
1519
		foreach ($config['aliases']['alias'] as $alias) {
1520
			if ($alias['name']) {
1521
				$aliastable[$alias['name']] = $alias['address'];
1522
			}
1523
		}
1524
	}
1525
}
1526

    
1527
/* check if an alias exists */
1528
function is_alias($name) {
1529
	global $aliastable;
1530

    
1531
	return isset($aliastable[$name]);
1532
}
1533

    
1534
function alias_get_type($name) {
1535
	global $config;
1536

    
1537
	if (is_array($config['aliases']['alias'])) {
1538
		foreach ($config['aliases']['alias'] as $alias) {
1539
			if ($name == $alias['name']) {
1540
				return $alias['type'];
1541
			}
1542
		}
1543
	}
1544

    
1545
	return "";
1546
}
1547

    
1548
/* expand a host or network alias, if necessary */
1549
function alias_expand($name) {
1550
	global $aliastable;
1551

    
1552
	if (isset($aliastable[$name])) {
1553
		// alias names cannot be strictly numeric. redmine #4289
1554
		if (is_numericint($name)) {
1555
			return null;
1556
		}
1557
		return "\${$name}";
1558
	} else if (is_ipaddr($name) || is_subnet($name) || is_port($name) || is_portrange($name)) {
1559
		return "{$name}";
1560
	} else {
1561
		return null;
1562
	}
1563
}
1564

    
1565
function alias_expand_urltable($name) {
1566
	global $config;
1567
	$urltable_prefix = "/var/db/aliastables/";
1568
	$urltable_filename = $urltable_prefix . $name . ".txt";
1569

    
1570
	if (is_array($config['aliases']['alias'])) {
1571
		foreach ($config['aliases']['alias'] as $alias) {
1572
			if (preg_match("/urltable/i", $alias['type']) && ($alias['name'] == $name)) {
1573
				if (is_URL($alias["url"]) && file_exists($urltable_filename) && filesize($urltable_filename)) {
1574
					return $urltable_filename;
1575
				} else {
1576
					send_event("service sync alias {$name}");
1577
					break;
1578
				}
1579
			}
1580
		}
1581
	}
1582
	return null;
1583
}
1584

    
1585
/* verify (and remove) the digital signature on a file - returns 0 if OK */
1586
function verify_digital_signature($fname) {
1587
	global $g;
1588

    
1589
	if (!file_exists("/usr/local/sbin/gzsig")) {
1590
		return 4;
1591
	}
1592

    
1593
	return mwexec("/usr/local/sbin/gzsig verify {$g['etc_path']}/pubkey.pem < " . escapeshellarg($fname));
1594
}
1595

    
1596
/* obtain MAC address given an IP address by looking at the ARP table */
1597
function arp_get_mac_by_ip($ip) {
1598
	mwexec("/sbin/ping -c 1 -t 1 " . escapeshellarg($ip), true);
1599
	$arpoutput = "";
1600
	exec("/usr/sbin/arp -n " . escapeshellarg($ip), $arpoutput);
1601

    
1602
	if ($arpoutput[0]) {
1603
		$arpi = explode(" ", $arpoutput[0]);
1604
		$macaddr = $arpi[3];
1605
		if (is_macaddr($macaddr)) {
1606
			return $macaddr;
1607
		} else {
1608
			return false;
1609
		}
1610
	}
1611

    
1612
	return false;
1613
}
1614

    
1615
/* return a fieldname that is safe for xml usage */
1616
function xml_safe_fieldname($fieldname) {
1617
	$replace = array('/', '-', ' ', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')',
1618
			 '_', '+', '=', '{', '}', '[', ']', '|', '/', '<', '>', '?',
1619
			 ':', ',', '.', '\'', '\\'
1620
		);
1621
	return strtolower(str_replace($replace, "", $fieldname));
1622
}
1623

    
1624
function mac_format($clientmac) {
1625
	global $config, $cpzone;
1626

    
1627
	$mac = explode(":", $clientmac);
1628
	$mac_format = $cpzone ? $config['captiveportal'][$cpzone]['radmac_format'] : false;
1629

    
1630
	switch ($mac_format) {
1631
		case 'singledash':
1632
			return "$mac[0]$mac[1]$mac[2]-$mac[3]$mac[4]$mac[5]";
1633

    
1634
		case 'ietf':
1635
			return "$mac[0]-$mac[1]-$mac[2]-$mac[3]-$mac[4]-$mac[5]";
1636

    
1637
		case 'cisco':
1638
			return "$mac[0]$mac[1].$mac[2]$mac[3].$mac[4]$mac[5]";
1639

    
1640
		case 'unformatted':
1641
			return "$mac[0]$mac[1]$mac[2]$mac[3]$mac[4]$mac[5]";
1642

    
1643
		default:
1644
			return $clientmac;
1645
	}
1646
}
1647

    
1648
function resolve_retry($hostname, $retries = 5) {
1649

    
1650
	if (is_ipaddr($hostname)) {
1651
		return $hostname;
1652
	}
1653

    
1654
	for ($i = 0; $i < $retries; $i++) {
1655
		// FIXME: gethostbyname does not work for AAAA hostnames, boo, hiss
1656
		$ip = gethostbyname($hostname);
1657

    
1658
		if ($ip && $ip != $hostname) {
1659
			/* success */
1660
			return $ip;
1661
		}
1662

    
1663
		sleep(1);
1664
	}
1665

    
1666
	return false;
1667
}
1668

    
1669
function format_bytes($bytes) {
1670
	if ($bytes >= 1073741824) {
1671
		return sprintf("%.2f GB", $bytes/1073741824);
1672
	} else if ($bytes >= 1048576) {
1673
		return sprintf("%.2f MB", $bytes/1048576);
1674
	} else if ($bytes >= 1024) {
1675
		return sprintf("%.0f KB", $bytes/1024);
1676
	} else {
1677
		return sprintf("%d bytes", $bytes);
1678
	}
1679
}
1680

    
1681
function update_filter_reload_status($text) {
1682
	global $g;
1683

    
1684
	file_put_contents("{$g['varrun_path']}/filter_reload_status", $text);
1685
}
1686

    
1687
/****** util/return_dir_as_array
1688
 * NAME
1689
 *   return_dir_as_array - Return a directory's contents as an array.
1690
 * INPUTS
1691
 *   $dir          - string containing the path to the desired directory.
1692
 *   $filter_regex - string containing a regular expression to filter file names. Default empty.
1693
 * RESULT
1694
 *   $dir_array - array containing the directory's contents. This array will be empty if the path specified is invalid.
1695
 ******/
1696
function return_dir_as_array($dir, $filter_regex = '') {
1697
	$dir_array = array();
1698
	if (is_dir($dir)) {
1699
		if ($dh = opendir($dir)) {
1700
			while (($file = readdir($dh)) !== false) {
1701
				if (($file == ".") || ($file == "..")) {
1702
					continue;
1703
				}
1704

    
1705
				if (empty($filter_regex) || preg_match($filter_regex, $file)) {
1706
					array_push($dir_array, $file);
1707
				}
1708
			}
1709
			closedir($dh);
1710
		}
1711
	}
1712
	return $dir_array;
1713
}
1714

    
1715
function run_plugins($directory) {
1716
	global $config, $g;
1717

    
1718
	/* process packager manager custom rules */
1719
	$files = return_dir_as_array($directory);
1720
	if (is_array($files)) {
1721
		foreach ($files as $file) {
1722
			if (stristr($file, ".sh") == true) {
1723
				mwexec($directory . $file . " start");
1724
			} else if (!is_dir($directory . "/" . $file) && stristr($file, ".inc")) {
1725
				require_once($directory . "/" . $file);
1726
			}
1727
		}
1728
	}
1729
}
1730

    
1731
/*
1732
 *    safe_mkdir($path, $mode = 0755)
1733
 *    create directory if it doesn't already exist and isn't a file!
1734
 */
1735
function safe_mkdir($path, $mode = 0755) {
1736
	global $g;
1737

    
1738
	if (!is_file($path) && !is_dir($path)) {
1739
		return @mkdir($path, $mode, true);
1740
	} else {
1741
		return false;
1742
	}
1743
}
1744

    
1745
/*
1746
 * get_sysctl($names)
1747
 * Get values of sysctl OID's listed in $names (accepts an array or a single
1748
 * name) and return an array of key/value pairs set for those that exist
1749
 */
1750
function get_sysctl($names) {
1751
	if (empty($names)) {
1752
		return array();
1753
	}
1754

    
1755
	if (is_array($names)) {
1756
		$name_list = array();
1757
		foreach ($names as $name) {
1758
			$name_list[] = escapeshellarg($name);
1759
		}
1760
	} else {
1761
		$name_list = array(escapeshellarg($names));
1762
	}
1763

    
1764
	exec("/sbin/sysctl -i " . implode(" ", $name_list), $output);
1765
	$values = array();
1766
	foreach ($output as $line) {
1767
		$line = explode(": ", $line, 2);
1768
		if (count($line) == 2) {
1769
			$values[$line[0]] = $line[1];
1770
		}
1771
	}
1772

    
1773
	return $values;
1774
}
1775

    
1776
/*
1777
 * get_single_sysctl($name)
1778
 * Wrapper for get_sysctl() to simplify read of a single sysctl value
1779
 * return the value for sysctl $name or empty string if it doesn't exist
1780
 */
1781
function get_single_sysctl($name) {
1782
	if (empty($name)) {
1783
		return "";
1784
	}
1785

    
1786
	$value = get_sysctl($name);
1787
	if (empty($value) || !isset($value[$name])) {
1788
		return "";
1789
	}
1790

    
1791
	return $value[$name];
1792
}
1793

    
1794
/*
1795
 * set_sysctl($value_list)
1796
 * Set sysctl OID's listed as key/value pairs and return
1797
 * an array with keys set for those that succeeded
1798
 */
1799
function set_sysctl($values) {
1800
	if (empty($values)) {
1801
		return array();
1802
	}
1803

    
1804
	$value_list = array();
1805
	foreach ($values as $key => $value) {
1806
		$value_list[] = escapeshellarg($key) . "=" . escapeshellarg($value);
1807
	}
1808

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

    
1811
	/* Retry individually if failed (one or more read-only) */
1812
	if ($success <> 0 && count($value_list) > 1) {
1813
		foreach ($value_list as $value) {
1814
			exec("/sbin/sysctl -i " . $value, $output);
1815
		}
1816
	}
1817

    
1818
	$ret = array();
1819
	foreach ($output as $line) {
1820
		$line = explode(": ", $line, 2);
1821
		if (count($line) == 2) {
1822
			$ret[$line[0]] = true;
1823
		}
1824
	}
1825

    
1826
	return $ret;
1827
}
1828

    
1829
/*
1830
 * set_single_sysctl($name, $value)
1831
 * Wrapper to set_sysctl() to make it simple to set only one sysctl
1832
 * returns boolean meaning if it succeeded
1833
 */
1834
function set_single_sysctl($name, $value) {
1835
	if (empty($name)) {
1836
		return false;
1837
	}
1838

    
1839
	$result = set_sysctl(array($name => $value));
1840

    
1841
	if (!isset($result[$name]) || $result[$name] != $value) {
1842
		return false;
1843
	}
1844

    
1845
	return true;
1846
}
1847

    
1848
/*
1849
 *     get_memory()
1850
 *     returns an array listing the amount of
1851
 *     memory installed in the hardware
1852
 *     [0] net memory available for the OS (FreeBSD) after some is taken by BIOS, video or whatever - e.g. 235 MBytes
1853
 *     [1] real (actual) memory of the system, should be the size of the RAM card/s - e.g. 256 MBytes
1854
 */
1855
function get_memory() {
1856
	$physmem = get_single_sysctl("hw.physmem");
1857
	$realmem = get_single_sysctl("hw.realmem");
1858
	/* convert from bytes to megabytes */
1859
	return array(($physmem/1048576), ($realmem/1048576));
1860
}
1861

    
1862
function mute_kernel_msgs() {
1863
	global $g, $config;
1864
	// Do not mute serial console.  The kernel gets very very cranky
1865
	// and will start dishing you cannot control tty errors.
1866
	if ($g['platform'] == 'nanobsd') {
1867
		return;
1868
	}
1869
	if ($config['system']['enableserial']) {
1870
		return;
1871
	}
1872
	exec("/sbin/conscontrol mute on");
1873
}
1874

    
1875
function unmute_kernel_msgs() {
1876
	global $g;
1877
	// Do not mute serial console.  The kernel gets very very cranky
1878
	// and will start dishing you cannot control tty errors.
1879
	if ($g['platform'] == 'nanobsd') {
1880
		return;
1881
	}
1882
	exec("/sbin/conscontrol mute off");
1883
}
1884

    
1885
function start_devd() {
1886
	/* Use the undocumented -q options of devd to quiet its log spamming */
1887
	$_gb = exec("/sbin/devd -q");
1888
	sleep(1);
1889
	unset($_gb);
1890
}
1891

    
1892
function is_interface_vlan_mismatch() {
1893
	global $config, $g;
1894

    
1895
	if (is_array($config['vlans']['vlan'])) {
1896
		foreach ($config['vlans']['vlan'] as $vlan) {
1897
			if (does_interface_exist($vlan['if']) == false) {
1898
				return true;
1899
			}
1900
		}
1901
	}
1902

    
1903
	return false;
1904
}
1905

    
1906
function is_interface_mismatch() {
1907
	global $config, $g;
1908

    
1909
	$do_assign = false;
1910
	$i = 0;
1911
	$missing_interfaces = array();
1912
	if (is_array($config['interfaces'])) {
1913
		foreach ($config['interfaces'] as $ifname => $ifcfg) {
1914
			if (preg_match("/^enc|^cua|^tun|^tap|^l2tp|^pptp|^ppp|^ovpn|^gif|^gre|^lagg|^bridge|vlan|_wlan/i", $ifcfg['if'])) {
1915
				// Do not check these interfaces.
1916
				$i++;
1917
				continue;
1918
			} else if (does_interface_exist($ifcfg['if']) == false) {
1919
				$missing_interfaces[] = $ifcfg['if'];
1920
				$do_assign = true;
1921
			} else {
1922
				$i++;
1923
			}
1924
		}
1925
	}
1926

    
1927
	if (file_exists("{$g['tmp_path']}/assign_complete")) {
1928
		$do_assign = false;
1929
	}
1930

    
1931
	if (!empty($missing_interfaces) && $do_assign) {
1932
		file_put_contents("{$g['tmp_path']}/missing_interfaces", implode(' ', $missing_interfaces));
1933
	} else {
1934
		@unlink("{$g['tmp_path']}/missing_interfaces");
1935
	}
1936

    
1937
	return $do_assign;
1938
}
1939

    
1940
/* sync carp entries to other firewalls */
1941
function carp_sync_client() {
1942
	global $g;
1943
	send_event("filter sync");
1944
}
1945

    
1946
/****f* util/isAjax
1947
 * NAME
1948
 *   isAjax - reports if the request is driven from prototype
1949
 * INPUTS
1950
 *   none
1951
 * RESULT
1952
 *   true/false
1953
 ******/
1954
function isAjax() {
1955
	return isset ($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest';
1956
}
1957

    
1958
/****f* util/timeout
1959
 * NAME
1960
 *   timeout - console input with timeout countdown. Note: erases 2 char of screen for timer. Leave space.
1961
 * INPUTS
1962
 *   optional, seconds to wait before timeout. Default 9 seconds.
1963
 * RESULT
1964
 *   returns 1 char of user input or null if no input.
1965
 ******/
1966
function timeout($timer = 9) {
1967
	while (!isset($key)) {
1968
		if ($timer >= 9) {
1969
			echo chr(8) . chr(8) . ($timer == 9 ? chr(32) : null) . "{$timer}";
1970
		} else {
1971
			echo chr(8). "{$timer}";
1972
		}
1973
		`/bin/stty -icanon min 0 time 25`;
1974
		$key = trim(`KEY=\`dd count=1 2>/dev/null\`; echo \$KEY`);
1975
		`/bin/stty icanon`;
1976
		if ($key == '') {
1977
			unset($key);
1978
		}
1979
		$timer--;
1980
		if ($timer == 0) {
1981
			break;
1982
		}
1983
	}
1984
	return $key;
1985
}
1986

    
1987
/****f* util/msort
1988
 * NAME
1989
 *   msort - sort array
1990
 * INPUTS
1991
 *   $array to be sorted, field to sort by, direction of sort
1992
 * RESULT
1993
 *   returns newly sorted array
1994
 ******/
1995
function msort($array, $id = "id", $sort_ascending = true) {
1996
	$temp_array = array();
1997
	while (count($array)>0) {
1998
		$lowest_id = 0;
1999
		$index = 0;
2000
		foreach ($array as $item) {
2001
			if (isset($item[$id])) {
2002
				if ($array[$lowest_id][$id]) {
2003
					if (strtolower($item[$id]) < strtolower($array[$lowest_id][$id])) {
2004
						$lowest_id = $index;
2005
					}
2006
				}
2007
			}
2008
			$index++;
2009
		}
2010
		$temp_array[] = $array[$lowest_id];
2011
		$array = array_merge(array_slice($array, 0, $lowest_id), array_slice($array, $lowest_id + 1));
2012
	}
2013
	if ($sort_ascending) {
2014
		return $temp_array;
2015
	} else {
2016
		return array_reverse($temp_array);
2017
	}
2018
}
2019

    
2020
/****f* util/is_URL
2021
 * NAME
2022
 *   is_URL
2023
 * INPUTS
2024
 *   string to check
2025
 * RESULT
2026
 *   Returns true if item is a URL
2027
 ******/
2028
function is_URL($url) {
2029
	$match = preg_match("'\b(([\w-]+://?|www[.])[^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|/)))'", $url);
2030
	if ($match) {
2031
		return true;
2032
	}
2033
	return false;
2034
}
2035

    
2036
function is_file_included($file = "") {
2037
	$files = get_included_files();
2038
	if (in_array($file, $files)) {
2039
		return true;
2040
	}
2041

    
2042
	return false;
2043
}
2044

    
2045
/*
2046
 * Replace a value on a deep associative array using regex
2047
 */
2048
function array_replace_values_recursive($data, $match, $replace) {
2049
	if (empty($data)) {
2050
		return $data;
2051
	}
2052

    
2053
	if (is_string($data)) {
2054
		$data = preg_replace("/{$match}/", $replace, $data);
2055
	} else if (is_array($data)) {
2056
		foreach ($data as $k => $v) {
2057
			$data[$k] = array_replace_values_recursive($v, $match, $replace);
2058
		}
2059
	}
2060

    
2061
	return $data;
2062
}
2063

    
2064
/*
2065
	This function was borrowed from a comment on PHP.net at the following URL:
2066
	http://www.php.net/manual/en/function.array-merge-recursive.php#73843
2067
 */
2068
function array_merge_recursive_unique($array0, $array1) {
2069

    
2070
	$arrays = func_get_args();
2071
	$remains = $arrays;
2072

    
2073
	// We walk through each arrays and put value in the results (without
2074
	// considering previous value).
2075
	$result = array();
2076

    
2077
	// loop available array
2078
	foreach ($arrays as $array) {
2079

    
2080
		// The first remaining array is $array. We are processing it. So
2081
		// we remove it from remaining arrays.
2082
		array_shift($remains);
2083

    
2084
		// We don't care non array param, like array_merge since PHP 5.0.
2085
		if (is_array($array)) {
2086
			// Loop values
2087
			foreach ($array as $key => $value) {
2088
				if (is_array($value)) {
2089
					// we gather all remaining arrays that have such key available
2090
					$args = array();
2091
					foreach ($remains as $remain) {
2092
						if (array_key_exists($key, $remain)) {
2093
							array_push($args, $remain[$key]);
2094
						}
2095
					}
2096

    
2097
					if (count($args) > 2) {
2098
						// put the recursion
2099
						$result[$key] = call_user_func_array(__FUNCTION__, $args);
2100
					} else {
2101
						foreach ($value as $vkey => $vval) {
2102
							$result[$key][$vkey] = $vval;
2103
						}
2104
					}
2105
				} else {
2106
					// simply put the value
2107
					$result[$key] = $value;
2108
				}
2109
			}
2110
		}
2111
	}
2112
	return $result;
2113
}
2114

    
2115

    
2116
/*
2117
 * converts a string like "a,b,c,d"
2118
 * into an array like array("a" => "b", "c" => "d")
2119
 */
2120
function explode_assoc($delimiter, $string) {
2121
	$array = explode($delimiter, $string);
2122
	$result = array();
2123
	$numkeys = floor(count($array) / 2);
2124
	for ($i = 0; $i < $numkeys; $i += 1) {
2125
		$result[$array[$i * 2]] = $array[$i * 2 + 1];
2126
	}
2127
	return $result;
2128
}
2129

    
2130
function get_staticroutes($returnsubnetsonly = false, $returnhostnames = false) {
2131
	global $config, $aliastable;
2132

    
2133
	/* Bail if there are no routes, but return an array always so callers don't have to check. */
2134
	if (!is_array($config['staticroutes']['route'])) {
2135
		return array();
2136
	}
2137

    
2138
	$allstaticroutes = array();
2139
	$allsubnets = array();
2140
	/* Loop through routes and expand aliases as we find them. */
2141
	foreach ($config['staticroutes']['route'] as $route) {
2142
		if (is_alias($route['network'])) {
2143
			if (!isset($aliastable[$route['network']])) {
2144
				continue;
2145
			}
2146

    
2147
			$subnets = preg_split('/\s+/', $aliastable[$route['network']]);
2148
			foreach ($subnets as $net) {
2149
				if (!is_subnet($net)) {
2150
					if (is_ipaddrv4($net)) {
2151
						$net .= "/32";
2152
					} else if (is_ipaddrv6($net)) {
2153
						$net .= "/128";
2154
					} else if ($returnhostnames === false || !is_fqdn($net)) {
2155
						continue;
2156
					}
2157
				}
2158
				$temproute = $route;
2159
				$temproute['network'] = $net;
2160
				$allstaticroutes[] = $temproute;
2161
				$allsubnets[] = $net;
2162
			}
2163
		} elseif (is_subnet($route['network'])) {
2164
			$allstaticroutes[] = $route;
2165
			$allsubnets[] = $route['network'];
2166
		}
2167
	}
2168
	if ($returnsubnetsonly) {
2169
		return $allsubnets;
2170
	} else {
2171
		return $allstaticroutes;
2172
	}
2173
}
2174

    
2175
/****f* util/get_alias_list
2176
 * NAME
2177
 *   get_alias_list - Provide a list of aliases.
2178
 * INPUTS
2179
 *   $type          - Optional, can be a string or array specifying what type(s) of aliases you need.
2180
 * RESULT
2181
 *   Array containing list of aliases.
2182
 *   If $type is unspecified, all aliases are returned.
2183
 *   If $type is a string, all aliases of the type specified in $type are returned.
2184
 *   If $type is an array, all aliases of any type specified in any element of $type are returned.
2185
 */
2186
function get_alias_list($type = null) {
2187
	global $config;
2188
	$result = array();
2189
	if ($config['aliases']['alias'] <> "" && is_array($config['aliases']['alias'])) {
2190
		foreach ($config['aliases']['alias'] as $alias) {
2191
			if ($type === null) {
2192
				$result[] = $alias['name'];
2193
			} else if (is_array($type)) {
2194
				if (in_array($alias['type'], $type)) {
2195
					$result[] = $alias['name'];
2196
				}
2197
			} else if ($type === $alias['type']) {
2198
				$result[] = $alias['name'];
2199
			}
2200
		}
2201
	}
2202
	return $result;
2203
}
2204

    
2205
/* returns an array consisting of every element of $haystack that is not equal to $needle. */
2206
function array_exclude($needle, $haystack) {
2207
	$result = array();
2208
	if (is_array($haystack)) {
2209
		foreach ($haystack as $thing) {
2210
			if ($needle !== $thing) {
2211
				$result[] = $thing;
2212
			}
2213
		}
2214
	}
2215
	return $result;
2216
}
2217

    
2218
/* Define what is preferred, IPv4 or IPv6 */
2219
function prefer_ipv4_or_ipv6() {
2220
	global $config;
2221

    
2222
	if (isset($config['system']['prefer_ipv4'])) {
2223
		mwexec("/etc/rc.d/ip6addrctl prefer_ipv4");
2224
	} else {
2225
		mwexec("/etc/rc.d/ip6addrctl prefer_ipv6");
2226
	}
2227
}
2228

    
2229
/* Redirect to page passing parameters via POST */
2230
function post_redirect($page, $params) {
2231
	if (!is_array($params)) {
2232
		return;
2233
	}
2234

    
2235
	print "<html><body><form action=\"{$page}\" name=\"formredir\" method=\"post\">\n";
2236
	foreach ($params as $key => $value) {
2237
		print "<input type=\"hidden\" name=\"{$key}\" value=\"{$value}\" />\n";
2238
	}
2239
	print "</form>\n";
2240
	print "<script type=\"text/javascript\">\n";
2241
	print "//<![CDATA[\n";
2242
	print "document.formredir.submit();\n";
2243
	print "//]]>\n";
2244
	print "</script>\n";
2245
	print "</body></html>\n";
2246
}
2247

    
2248
/* Locate disks that can be queried for S.M.A.R.T. data. */
2249
function get_smart_drive_list() {
2250
	$disk_list = explode(" ", get_single_sysctl("kern.disks"));
2251
	foreach ($disk_list as $id => $disk) {
2252
		// We only want certain kinds of disks for S.M.A.R.T.
2253
		// 1 is a match, 0 is no match, False is any problem processing the regex
2254
		if (preg_match("/^(ad|da|ada).*[0-9]{1,2}$/", $disk) !== 1) {
2255
			unset($disk_list[$id]);
2256
		}
2257
	}
2258
	sort($disk_list);
2259
	return $disk_list;
2260
}
2261

    
2262
?>
(55-55/65)