Project

General

Profile

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

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

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

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

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

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

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

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

    
94
	return 0;
95
}
96

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

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

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

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

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

    
122
	return false;
123
}
124

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

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

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

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

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

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

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

    
187
		return $fp;
188
	}
189

    
190
	return NULL;
191
}
192

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

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

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

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

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

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

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

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

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

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

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

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

    
294
	return $shm_data;
295
}
296

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

    
323
	return $shm_data;
324
}
325

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
538
	return $rangeaddresses;
539
}
540

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
661
	return $out;
662
}
663

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

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

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

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

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

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

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

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

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

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

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

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

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

    
797
}
798

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

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

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

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

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

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

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

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

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

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

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

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

    
930
/* return all PTR zones for a IPv6 network */
931
function get_v6_ptr_zones($subnet, $bits) {
932
	$result = array();
933

    
934
	if (!is_ipaddrv6($subnet)) {
935
		return $result;
936
	}
937

    
938
	if (!is_numericint($bits) || $bits > 128) {
939
		return $result;
940
	}
941

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

    
951
	/* Get network prefix */
952
	$small_subnet = Net_IPv6::getNetmask($subnet, $bits);
953

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

    
965
		/* Detect what part of IP should be increased */
966
		$change_part = (int) ($small_sn / 16);
967
		if ($small_sn % 16 == 0) {
968
			$change_part--;
969
		}
970

    
971
		/* Increase 1 to desired part */
972
		$parts = explode(":", Net_IPv6::uncompress($small_subnet));
973
		$parts[$change_part]++;
974
		$small_subnet = implode(":", $parts);
975
	}
976

    
977
	return $result;
978
}
979

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

    
992
/* returns true if $hostname is just a valid hostname (top part without any of the domain part) */
993
function is_unqualified_hostname($hostname) {
994
	if (!is_string($hostname)) {
995
		return false;
996
	}
997

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

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

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

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

    
1034
	if (preg_match($domain_regex, $domain)) {
1035
		return true;
1036
	} else {
1037
		return false;
1038
	}
1039
}
1040

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

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

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

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

    
1064
	if (!is_string($name) || strlen($name) >= 32 || preg_match('/(^_*$|^\d*$|[^a-z0-9_])/i', $name)) {
1065
		if ($return_message) {
1066
			return sprintf(gettext('The %1$s name must be less than 32 characters long, may not consist of only numbers, may not consist of only underscores, and may only contain the following characters: %2$s'), $object, 'a-z, A-Z, 0-9, _');
1067
		} else {
1068
			return false;
1069
		}
1070
	}
1071
	if (in_array($name, $reserved, true)) {
1072
		if ($return_message) {
1073
			return sprintf(gettext('The %1$s name must not be either of the reserved words %2$s or %3$s.'), $object, "'port'", "'pass'");
1074
		} else {
1075
			return false;
1076
		}
1077
	}
1078
	if (getprotobyname($name)) {
1079
		if ($return_message) {
1080
			return sprintf(gettext('The %1$s name must not be a well-known IP protocol name such as TCP, UDP, ICMP etc.'), $object);
1081
		} else {
1082
			return false;
1083
		}
1084
	}
1085
	if (getservbyname($name, "tcp") || getservbyname($name, "udp")) {
1086
		if ($return_message) {
1087
			return sprintf(gettext('The %1$s name must not be a well-known TCP or UDP port name such as ssh, smtp, pop3, tftp, http, openvpn etc.'), $object);
1088
		} else {
1089
			return false;
1090
		}
1091
	}
1092
	if ($return_message) {
1093
		return sprintf(gettext("The %1$s name is valid."), $object);
1094
	} else {
1095
		return true;
1096
	}
1097
}
1098

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

    
1104
/* returns true if $port is a valid TCP/UDP port */
1105
function is_port($port) {
1106
	if (ctype_digit($port) && ((intval($port) >= 1) && (intval($port) <= 65535))) {
1107
		return true;
1108
	}
1109
	if (getservbyname($port, "tcp") || getservbyname($port, "udp")) {
1110
		return true;
1111
	}
1112
	return false;
1113
}
1114

    
1115
/* returns true if $portrange is a valid TCP/UDP portrange ("<port>:<port>") */
1116
function is_portrange($portrange) {
1117
	$ports = explode(":", $portrange);
1118

    
1119
	return (count($ports) == 2 && is_port($ports[0]) && is_port($ports[1]));
1120
}
1121

    
1122
/* returns true if $port is a valid port number or an alias thereof */
1123
function is_portoralias($port) {
1124
	global $config;
1125

    
1126
	if (is_alias($port)) {
1127
		if (is_array($config['aliases']['alias'])) {
1128
			foreach ($config['aliases']['alias'] as $alias) {
1129
				if ($alias['name'] == $port && preg_match("/port/i", $alias['type'])) {
1130
					return true;
1131
				}
1132
			}
1133
		}
1134
		return false;
1135
	} else {
1136
		return is_port($port);
1137
	}
1138
}
1139

    
1140
/* create ranges of sequential port numbers (200:215) and remove duplicates */
1141
function group_ports($ports, $kflc = false) {
1142
	if (!is_array($ports) || empty($ports)) {
1143
		return;
1144
	}
1145

    
1146
	$uniq = array();
1147
	$comments = array();
1148
	foreach ($ports as $port) {
1149
		if (($kflc) && (strpos($port, '#') === 0)) {	// Keep Full Line Comments (lines beginning with #).
1150
			$comments[] = $port;
1151
		} else if (is_portrange($port)) {
1152
			list($begin, $end) = explode(":", $port);
1153
			if ($begin > $end) {
1154
				$aux = $begin;
1155
				$begin = $end;
1156
				$end = $aux;
1157
			}
1158
			for ($i = $begin; $i <= $end; $i++) {
1159
				if (!in_array($i, $uniq)) {
1160
					$uniq[] = $i;
1161
				}
1162
			}
1163
		} else if (is_port($port)) {
1164
			if (!in_array($port, $uniq)) {
1165
				$uniq[] = $port;
1166
			}
1167
		}
1168
	}
1169
	sort($uniq, SORT_NUMERIC);
1170

    
1171
	$result = array();
1172
	foreach ($uniq as $idx => $port) {
1173
		if ($idx == 0) {
1174
			$result[] = $port;
1175
			continue;
1176
		}
1177

    
1178
		$last = end($result);
1179
		if (is_portrange($last)) {
1180
			list($begin, $end) = explode(":", $last);
1181
		} else {
1182
			$begin = $end = $last;
1183
		}
1184

    
1185
		if ($port == ($end+1)) {
1186
			$end++;
1187
			$result[count($result)-1] = "{$begin}:{$end}";
1188
		} else {
1189
			$result[] = $port;
1190
		}
1191
	}
1192

    
1193
	return array_merge($comments, $result);
1194
}
1195

    
1196
/* returns true if $val is a valid shaper bandwidth value */
1197
function is_valid_shaperbw($val) {
1198
	return (preg_match("/^(\d+(?:\.\d+)?)([MKG]?b|%)$/", $val));
1199
}
1200

    
1201
/* returns true if $test is in the range between $start and $end */
1202
function is_inrange_v4($test, $start, $end) {
1203
	if ((ip2ulong($test) <= ip2ulong($end)) && (ip2ulong($test) >= ip2ulong($start))) {
1204
		return true;
1205
	} else {
1206
		return false;
1207
	}
1208
}
1209

    
1210
/* returns true if $test is in the range between $start and $end */
1211
function is_inrange_v6($test, $start, $end) {
1212
	if ((inet_pton($test) <= inet_pton($end)) && (inet_pton($test) >= inet_pton($start))) {
1213
		return true;
1214
	} else {
1215
		return false;
1216
	}
1217
}
1218

    
1219
/* returns true if $test is in the range between $start and $end */
1220
function is_inrange($test, $start, $end) {
1221
	return is_ipaddrv6($test) ? is_inrange_v6($test, $start, $end) : is_inrange_v4($test, $start, $end);
1222
}
1223

    
1224
function get_configured_vip_list($family = 'all', $type = VIP_ALL) {
1225
	global $config;
1226

    
1227
	$list = array();
1228
	if (!is_array($config['virtualip']['vip']) || empty($config['virtualip']['vip'])) {
1229
		return ($list);
1230
	}
1231

    
1232
	$viparr = &$config['virtualip']['vip'];
1233
	foreach ($viparr as $vip) {
1234

    
1235
		if ($type == VIP_CARP) {
1236
			if ($vip['mode'] != "carp")
1237
				continue;
1238
		} elseif ($type == VIP_IPALIAS) {
1239
			if ($vip['mode'] != "ipalias")
1240
				continue;
1241
		} else {
1242
			if ($vip['mode'] != "carp" && $vip['mode'] != "ipalias")
1243
				continue;
1244
		}
1245

    
1246
		if ($family == 'all' ||
1247
		    ($family == 'inet' && is_ipaddrv4($vip['subnet'])) ||
1248
		    ($family == 'inet6' && is_ipaddrv6($vip['subnet']))) {
1249
			$list["_vip{$vip['uniqid']}"] = $vip['subnet'];
1250
		}
1251
	}
1252
	return ($list);
1253
}
1254

    
1255
function get_configured_vip($vipinterface = '') {
1256

    
1257
	return (get_configured_vip_detail($vipinterface, 'all', 'vip'));
1258
}
1259

    
1260
function get_configured_vip_interface($vipinterface = '') {
1261

    
1262
	return (get_configured_vip_detail($vipinterface, 'all', 'iface'));
1263
}
1264

    
1265
function get_configured_vip_ipv4($vipinterface = '') {
1266

    
1267
	return (get_configured_vip_detail($vipinterface, 'inet', 'ip'));
1268
}
1269

    
1270
function get_configured_vip_ipv6($vipinterface = '') {
1271

    
1272
	return (get_configured_vip_detail($vipinterface, 'inet6', 'ip'));
1273
}
1274

    
1275
function get_configured_vip_subnetv4($vipinterface = '') {
1276

    
1277
	return (get_configured_vip_detail($vipinterface, 'inet', 'subnet'));
1278
}
1279

    
1280
function get_configured_vip_subnetv6($vipinterface = '') {
1281

    
1282
	return (get_configured_vip_detail($vipinterface, 'inet6', 'subnet'));
1283
}
1284

    
1285
function get_configured_vip_detail($vipinterface = '', $family = 'inet', $what = 'ip') {
1286
	global $config;
1287

    
1288
	if (empty($vipinterface) || !is_array($config['virtualip']['vip']) ||
1289
	    empty($config['virtualip']['vip'])) {
1290
		return (NULL);
1291
	}
1292

    
1293
	$viparr = &$config['virtualip']['vip'];
1294
	foreach ($viparr as $vip) {
1295
		if ($vip['mode'] != "carp" && $vip['mode'] != "ipalias") {
1296
			continue;
1297
		}
1298

    
1299
		if ($vipinterface != "_vip{$vip['uniqid']}") {
1300
			continue;
1301
		}
1302

    
1303
		switch ($what) {
1304
			case 'subnet':
1305
				if ($family == 'inet' && is_ipaddrv4($vip['subnet']))
1306
					return ($vip['subnet_bits']);
1307
				else if ($family == 'inet6' && is_ipaddrv6($vip['subnet']))
1308
					return ($vip['subnet_bits']);
1309
				break;
1310
			case 'iface':
1311
				return ($vip['interface']);
1312
				break;
1313
			case 'vip':
1314
				return ($vip);
1315
				break;
1316
			case 'ip':
1317
			default:
1318
				if ($family == 'inet' && is_ipaddrv4($vip['subnet'])) {
1319
					return ($vip['subnet']);
1320
				} else if ($family == 'inet6' && is_ipaddrv6($vip['subnet'])) {
1321
					return ($vip['subnet']);
1322
				}
1323
				break;
1324
		}
1325
		break;
1326
	}
1327

    
1328
	return (NULL);
1329
}
1330

    
1331
/* comparison function for sorting by the order in which interfaces are normally created */
1332
function compare_interface_friendly_names($a, $b) {
1333
	if ($a == $b) {
1334
		return 0;
1335
	} else if ($a == 'wan') {
1336
		return -1;
1337
	} else if ($b == 'wan') {
1338
		return 1;
1339
	} else if ($a == 'lan') {
1340
		return -1;
1341
	} else if ($b == 'lan') {
1342
		return 1;
1343
	}
1344

    
1345
	return strnatcmp($a, $b);
1346
}
1347

    
1348
/* return the configured interfaces list. */
1349
function get_configured_interface_list($only_opt = false, $withdisabled = false) {
1350
	global $config;
1351

    
1352
	$iflist = array();
1353

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

    
1364
	return $iflist;
1365
}
1366

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

    
1371
	$iflist = array();
1372

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

    
1386
	return $iflist;
1387
}
1388

    
1389
/* return the configured interfaces list with their description. */
1390
function get_configured_interface_with_descr($only_opt = false, $withdisabled = false) {
1391
	global $config;
1392

    
1393
	$iflist = array();
1394

    
1395
	/* if list */
1396
	foreach ($config['interfaces'] as $if => $ifdetail) {
1397
		if ($only_opt && ($if == "wan" || $if == "lan")) {
1398
			continue;
1399
		}
1400
		if (isset($ifdetail['enable']) || $withdisabled == true) {
1401
			if (empty($ifdetail['descr'])) {
1402
				$iflist[$if] = strtoupper($if);
1403
			} else {
1404
				$iflist[$if] = strtoupper($ifdetail['descr']);
1405
			}
1406
		}
1407
	}
1408

    
1409
	return $iflist;
1410
}
1411

    
1412
/*
1413
 *   get_configured_ip_addresses() - Return a list of all configured
1414
 *   IPv4 addresses.
1415
 *
1416
 */
1417
function get_configured_ip_addresses() {
1418
	global $config;
1419

    
1420
	if (!function_exists('get_interface_ip')) {
1421
		require_once("interfaces.inc");
1422
	}
1423
	$ip_array = array();
1424
	$interfaces = get_configured_interface_list();
1425
	if (is_array($interfaces)) {
1426
		foreach ($interfaces as $int) {
1427
			$ipaddr = get_interface_ip($int);
1428
			$ip_array[$int] = $ipaddr;
1429
		}
1430
	}
1431
	$interfaces = get_configured_vip_list('inet');
1432
	if (is_array($interfaces)) {
1433
		foreach ($interfaces as $int => $ipaddr) {
1434
			$ip_array[$int] = $ipaddr;
1435
		}
1436
	}
1437

    
1438
	/* pppoe server */
1439
	if (is_array($config['pppoes']) && is_array($config['pppoes']['pppoe'])) {
1440
		foreach ($config['pppoes']['pppoe'] as $pppoe) {
1441
			if ($pppoe['mode'] == "server") {
1442
				if (is_ipaddr($pppoe['localip'])) {
1443
					$int = "pppoes". $pppoe['pppoeid'];
1444
					$ip_array[$int] = $pppoe['localip'];
1445
				}
1446
			}
1447
		}
1448
	}
1449

    
1450
	return $ip_array;
1451
}
1452

    
1453
/*
1454
 *   get_configured_ipv6_addresses() - Return a list of all configured
1455
 *   IPv6 addresses.
1456
 *
1457
 */
1458
function get_configured_ipv6_addresses($linklocal_fallback = false) {
1459
	require_once("interfaces.inc");
1460
	$ipv6_array = array();
1461
	$interfaces = get_configured_interface_list();
1462
	if (is_array($interfaces)) {
1463
		foreach ($interfaces as $int) {
1464
			$ipaddrv6 = get_interface_ipv6($int, false, $linklocal_fallback);
1465
			$ipv6_array[$int] = $ipaddrv6;
1466
		}
1467
	}
1468
	$interfaces = get_configured_vip_list('inet6');
1469
	if (is_array($interfaces)) {
1470
		foreach ($interfaces as $int => $ipaddrv6) {
1471
			$ipv6_array[$int] = $ipaddrv6;
1472
		}
1473
	}
1474
	return $ipv6_array;
1475
}
1476

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

    
1580
			case "friendly":
1581
				if ($friendly != "") {
1582
					$toput['if'] = $ifname;
1583
					$iflist[$friendly] = $toput;
1584
				}
1585
				break;
1586
			}
1587
		}
1588
	}
1589
	return $iflist;
1590
}
1591

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

    
1614
/****f* util/log_auth
1615
* NAME
1616
*   log_auth   - Sends a string to syslog as LOG_AUTH facility
1617
* INPUTS
1618
*   $error     - string containing the syslog message.
1619
* RESULT
1620
*   null
1621
******/
1622
function log_auth($error) {
1623
	global $g;
1624
	$page = $_SERVER['SCRIPT_NAME'];
1625
	syslog(LOG_AUTH, "$page: $error");
1626
	if ($g['debug']) {
1627
		syslog(LOG_WARNING, var_dump(debug_backtrace()));
1628
	}
1629
	return;
1630
}
1631

    
1632
/****f* util/exec_command
1633
 * NAME
1634
 *   exec_command - Execute a command and return a string of the result.
1635
 * INPUTS
1636
 *   $command   - String of the command to be executed.
1637
 * RESULT
1638
 *   String containing the command's result.
1639
 * NOTES
1640
 *   This function returns the command's stdout and stderr.
1641
 ******/
1642
function exec_command($command) {
1643
	$output = array();
1644
	exec($command . ' 2>&1', $output);
1645
	return(implode("\n", $output));
1646
}
1647

    
1648
/* wrapper for exec()
1649
   Executes in background or foreground.
1650
   For background execution, returns PID of background process to allow calling code control */
1651
function mwexec($command, $nologentry = false, $clearsigmask = false, $background = false) {
1652
	global $g;
1653
	$retval = 0;
1654

    
1655
	if ($g['debug']) {
1656
		if (!$_SERVER['REMOTE_ADDR']) {
1657
			echo "mwexec(): $command" . ($background ? " [BG]":"") . "\n";
1658
		}
1659
	}
1660
	if ($clearsigmask) {
1661
		$oldset = array();
1662
		pcntl_sigprocmask(SIG_SETMASK, array(), $oldset);
1663
	}
1664

    
1665
	if ($background) {
1666
		// start background process and return PID
1667
		$retval = exec("/usr/bin/nohup $command > /dev/null 2>&1 & echo $!");
1668
	} else {
1669
		// run in foreground, and (optionally) log if nonzero return
1670
		$outputarray = array();
1671
		exec("$command 2>&1", $outputarray, $retval);
1672
		if (($retval <> 0) && (!$nologentry || isset($config['system']['developerspew']))) {
1673
			log_error(sprintf(gettext("The command '%1\$s' returned exit code '%2\$d', the output was '%3\$s' "), $command, $retval, implode(" ", $outputarray)));
1674
		}
1675
	}
1676

    
1677
	if ($clearsigmask) {
1678
		pcntl_sigprocmask(SIG_SETMASK, $oldset);
1679
	}
1680

    
1681
	return $retval;
1682
}
1683

    
1684
/* wrapper for exec() in background */
1685
function mwexec_bg($command, $clearsigmask = false) {
1686
	return mwexec($command, false, $clearsigmask, true);
1687
}
1688

    
1689
/* unlink a file, or pattern-match of a file, if it exists
1690
   if the file/path contains glob() compatible wildcards, all matching files will be unlinked
1691
   if no matches, no error occurs */
1692
function unlink_if_exists($fn) {
1693
	$to_do = glob($fn);
1694
	if (is_array($to_do) && count($to_do) > 0) {
1695
		@array_map("unlink", $to_do);
1696
	} else {
1697
		@unlink($fn);
1698
	}
1699
}
1700
/* make a global alias table (for faster lookups) */
1701
function alias_make_table($config) {
1702
	global $aliastable;
1703

    
1704
	$aliastable = array();
1705

    
1706
	if (is_array($config['aliases']['alias'])) {
1707
		foreach ($config['aliases']['alias'] as $alias) {
1708
			if ($alias['name']) {
1709
				$aliastable[$alias['name']] = $alias['address'];
1710
			}
1711
		}
1712
	}
1713
}
1714

    
1715
/* check if an alias exists */
1716
function is_alias($name) {
1717
	global $aliastable;
1718

    
1719
	return isset($aliastable[$name]);
1720
}
1721

    
1722
function alias_get_type($name) {
1723
	global $config;
1724

    
1725
	if (is_array($config['aliases']['alias'])) {
1726
		foreach ($config['aliases']['alias'] as $alias) {
1727
			if ($name == $alias['name']) {
1728
				return $alias['type'];
1729
			}
1730
		}
1731
	}
1732

    
1733
	return "";
1734
}
1735

    
1736
/* expand a host or network alias, if necessary */
1737
function alias_expand($name) {
1738
	global $config, $aliastable;
1739
	$urltable_prefix = "/var/db/aliastables/";
1740
	$urltable_filename = $urltable_prefix . $name . ".txt";
1741

    
1742
	if (isset($aliastable[$name])) {
1743
		// alias names cannot be strictly numeric. redmine #4289
1744
		if (is_numericint($name)) {
1745
			return null;
1746
		}
1747
		// make sure if it's a ports alias, it actually exists. redmine #5845
1748
		foreach ($config['aliases']['alias'] as $alias) {
1749
			if ($alias['name'] == $name) {
1750
				if ($alias['type'] == "urltable_ports") {
1751
					if (is_URL($alias['url']) && file_exists($urltable_filename) && filesize($urltable_filename)) {
1752
						return "\${$name}";
1753
					} else {
1754
						return null;
1755
					}
1756
				}
1757
			}
1758
		}
1759
		return "\${$name}";
1760
	} else if (is_ipaddr($name) || is_subnet($name) || is_port($name) || is_portrange($name)) {
1761
		return "{$name}";
1762
	} else {
1763
		return null;
1764
	}
1765
}
1766

    
1767
function alias_expand_urltable($name) {
1768
	global $config;
1769
	$urltable_prefix = "/var/db/aliastables/";
1770
	$urltable_filename = $urltable_prefix . $name . ".txt";
1771

    
1772
	if (is_array($config['aliases']['alias'])) {
1773
		foreach ($config['aliases']['alias'] as $alias) {
1774
			if (preg_match("/urltable/i", $alias['type']) && ($alias['name'] == $name)) {
1775
				if (is_URL($alias["url"]) && file_exists($urltable_filename)) {
1776
					if (!filesize($urltable_filename)) {
1777
						// file exists, but is empty, try to sync
1778
						send_event("service sync alias {$name}");
1779
					}
1780
					return $urltable_filename;
1781
				} else {
1782
					send_event("service sync alias {$name}");
1783
					break;
1784
				}
1785
			}
1786
		}
1787
	}
1788
	return null;
1789
}
1790

    
1791
/* obtain MAC address given an IP address by looking at the ARP table */
1792
function arp_get_mac_by_ip($ip) {
1793
	mwexec("/sbin/ping -c 1 -t 1 " . escapeshellarg($ip), true);
1794
	$arpoutput = "";
1795
	exec("/usr/sbin/arp -n " . escapeshellarg($ip), $arpoutput);
1796

    
1797
	if ($arpoutput[0]) {
1798
		$arpi = explode(" ", $arpoutput[0]);
1799
		$macaddr = $arpi[3];
1800
		if (is_macaddr($macaddr)) {
1801
			return $macaddr;
1802
		} else {
1803
			return false;
1804
		}
1805
	}
1806

    
1807
	return false;
1808
}
1809

    
1810
/* return a fieldname that is safe for xml usage */
1811
function xml_safe_fieldname($fieldname) {
1812
	$replace = array('/', '-', ' ', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')',
1813
			 '_', '+', '=', '{', '}', '[', ']', '|', '/', '<', '>', '?',
1814
			 ':', ',', '.', '\'', '\\'
1815
		);
1816
	return strtolower(str_replace($replace, "", $fieldname));
1817
}
1818

    
1819
function mac_format($clientmac) {
1820
	global $config, $cpzone;
1821

    
1822
	$mac = explode(":", $clientmac);
1823
	$mac_format = $cpzone ? $config['captiveportal'][$cpzone]['radmac_format'] : false;
1824

    
1825
	switch ($mac_format) {
1826
		case 'singledash':
1827
			return "$mac[0]$mac[1]$mac[2]-$mac[3]$mac[4]$mac[5]";
1828

    
1829
		case 'ietf':
1830
			return "$mac[0]-$mac[1]-$mac[2]-$mac[3]-$mac[4]-$mac[5]";
1831

    
1832
		case 'cisco':
1833
			return "$mac[0]$mac[1].$mac[2]$mac[3].$mac[4]$mac[5]";
1834

    
1835
		case 'unformatted':
1836
			return "$mac[0]$mac[1]$mac[2]$mac[3]$mac[4]$mac[5]";
1837

    
1838
		default:
1839
			return $clientmac;
1840
	}
1841
}
1842

    
1843
function resolve_retry($hostname, $retries = 5) {
1844

    
1845
	if (is_ipaddr($hostname)) {
1846
		return $hostname;
1847
	}
1848

    
1849
	for ($i = 0; $i < $retries; $i++) {
1850
		// FIXME: gethostbyname does not work for AAAA hostnames, boo, hiss
1851
		$ip = gethostbyname($hostname);
1852

    
1853
		if ($ip && $ip != $hostname) {
1854
			/* success */
1855
			return $ip;
1856
		}
1857

    
1858
		sleep(1);
1859
	}
1860

    
1861
	return false;
1862
}
1863

    
1864
function format_bytes($bytes) {
1865
	if ($bytes >= 1099511627776) {
1866
		return sprintf("%.2f TiB", $bytes/1099511627776);
1867
	} else if ($bytes >= 1073741824) {
1868
		return sprintf("%.2f GiB", $bytes/1073741824);
1869
	} else if ($bytes >= 1048576) {
1870
		return sprintf("%.2f MiB", $bytes/1048576);
1871
	} else if ($bytes >= 1024) {
1872
		return sprintf("%.0f KiB", $bytes/1024);
1873
	} else {
1874
		return sprintf("%d B", $bytes);
1875
	}
1876
}
1877

    
1878
function format_number($num, $precision = 3) {
1879
	$units = array('', 'K', 'M', 'G', 'T');
1880

    
1881
	$i = 0;
1882
	while ($num > 1000 && $i < count($units)) {
1883
		$num /= 1000;
1884
		$i++;
1885
	}
1886
	round($num, $precision);
1887

    
1888
	return ("$num {$units[$i]}");
1889
}
1890

    
1891
function update_filter_reload_status($text) {
1892
	global $g;
1893

    
1894
	file_put_contents("{$g['varrun_path']}/filter_reload_status", $text);
1895
}
1896

    
1897
/****** util/return_dir_as_array
1898
 * NAME
1899
 *   return_dir_as_array - Return a directory's contents as an array.
1900
 * INPUTS
1901
 *   $dir          - string containing the path to the desired directory.
1902
 *   $filter_regex - string containing a regular expression to filter file names. Default empty.
1903
 * RESULT
1904
 *   $dir_array - array containing the directory's contents. This array will be empty if the path specified is invalid.
1905
 ******/
1906
function return_dir_as_array($dir, $filter_regex = '') {
1907
	$dir_array = array();
1908
	if (is_dir($dir)) {
1909
		if ($dh = opendir($dir)) {
1910
			while (($file = readdir($dh)) !== false) {
1911
				if (($file == ".") || ($file == "..")) {
1912
					continue;
1913
				}
1914

    
1915
				if (empty($filter_regex) || preg_match($filter_regex, $file)) {
1916
					array_push($dir_array, $file);
1917
				}
1918
			}
1919
			closedir($dh);
1920
		}
1921
	}
1922
	return $dir_array;
1923
}
1924

    
1925
function run_plugins($directory) {
1926
	global $config, $g;
1927

    
1928
	/* process packager manager custom rules */
1929
	$files = return_dir_as_array($directory);
1930
	if (is_array($files)) {
1931
		foreach ($files as $file) {
1932
			if (stristr($file, ".sh") == true) {
1933
				mwexec($directory . $file . " start");
1934
			} else if (!is_dir($directory . "/" . $file) && stristr($file, ".inc")) {
1935
				require_once($directory . "/" . $file);
1936
			}
1937
		}
1938
	}
1939
}
1940

    
1941
/*
1942
 *    safe_mkdir($path, $mode = 0755)
1943
 *    create directory if it doesn't already exist and isn't a file!
1944
 */
1945
function safe_mkdir($path, $mode = 0755) {
1946
	global $g;
1947

    
1948
	if (!is_file($path) && !is_dir($path)) {
1949
		return @mkdir($path, $mode, true);
1950
	} else {
1951
		return false;
1952
	}
1953
}
1954

    
1955
/*
1956
 * get_sysctl($names)
1957
 * Get values of sysctl OID's listed in $names (accepts an array or a single
1958
 * name) and return an array of key/value pairs set for those that exist
1959
 */
1960
function get_sysctl($names) {
1961
	if (empty($names)) {
1962
		return array();
1963
	}
1964

    
1965
	if (is_array($names)) {
1966
		$name_list = array();
1967
		foreach ($names as $name) {
1968
			$name_list[] = escapeshellarg($name);
1969
		}
1970
	} else {
1971
		$name_list = array(escapeshellarg($names));
1972
	}
1973

    
1974
	exec("/sbin/sysctl -i " . implode(" ", $name_list), $output);
1975
	$values = array();
1976
	foreach ($output as $line) {
1977
		$line = explode(": ", $line, 2);
1978
		if (count($line) == 2) {
1979
			$values[$line[0]] = $line[1];
1980
		}
1981
	}
1982

    
1983
	return $values;
1984
}
1985

    
1986
/*
1987
 * get_single_sysctl($name)
1988
 * Wrapper for get_sysctl() to simplify read of a single sysctl value
1989
 * return the value for sysctl $name or empty string if it doesn't exist
1990
 */
1991
function get_single_sysctl($name) {
1992
	if (empty($name)) {
1993
		return "";
1994
	}
1995

    
1996
	$value = get_sysctl($name);
1997
	if (empty($value) || !isset($value[$name])) {
1998
		return "";
1999
	}
2000

    
2001
	return $value[$name];
2002
}
2003

    
2004
/*
2005
 * set_sysctl($value_list)
2006
 * Set sysctl OID's listed as key/value pairs and return
2007
 * an array with keys set for those that succeeded
2008
 */
2009
function set_sysctl($values) {
2010
	if (empty($values)) {
2011
		return array();
2012
	}
2013

    
2014
	$value_list = array();
2015
	foreach ($values as $key => $value) {
2016
		$value_list[] = escapeshellarg($key) . "=" . escapeshellarg($value);
2017
	}
2018

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

    
2021
	/* Retry individually if failed (one or more read-only) */
2022
	if ($success <> 0 && count($value_list) > 1) {
2023
		foreach ($value_list as $value) {
2024
			exec("/sbin/sysctl -i " . $value, $output);
2025
		}
2026
	}
2027

    
2028
	$ret = array();
2029
	foreach ($output as $line) {
2030
		$line = explode(": ", $line, 2);
2031
		if (count($line) == 2) {
2032
			$ret[$line[0]] = true;
2033
		}
2034
	}
2035

    
2036
	return $ret;
2037
}
2038

    
2039
/*
2040
 * set_single_sysctl($name, $value)
2041
 * Wrapper to set_sysctl() to make it simple to set only one sysctl
2042
 * returns boolean meaning if it succeeded
2043
 */
2044
function set_single_sysctl($name, $value) {
2045
	if (empty($name)) {
2046
		return false;
2047
	}
2048

    
2049
	$result = set_sysctl(array($name => $value));
2050

    
2051
	if (!isset($result[$name]) || $result[$name] != $value) {
2052
		return false;
2053
	}
2054

    
2055
	return true;
2056
}
2057

    
2058
/*
2059
 *     get_memory()
2060
 *     returns an array listing the amount of
2061
 *     memory installed in the hardware
2062
 *     [0] net memory available for the OS (FreeBSD) after some is taken by BIOS, video or whatever - e.g. 235 MBytes
2063
 *     [1] real (actual) memory of the system, should be the size of the RAM card/s - e.g. 256 MBytes
2064
 */
2065
function get_memory() {
2066
	$physmem = get_single_sysctl("hw.physmem");
2067
	$realmem = get_single_sysctl("hw.realmem");
2068
	/* convert from bytes to megabytes */
2069
	return array(($physmem/1048576), ($realmem/1048576));
2070
}
2071

    
2072
function mute_kernel_msgs() {
2073
	global $g, $config;
2074
	// Do not mute serial console.  The kernel gets very very cranky
2075
	// and will start dishing you cannot control tty errors.
2076
	if ($g['platform'] == 'nanobsd') {
2077
		return;
2078
	}
2079
	if ($config['system']['enableserial']) {
2080
		return;
2081
	}
2082
	exec("/sbin/conscontrol mute on");
2083
}
2084

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

    
2095
function start_devd() {
2096
	/* Use the undocumented -q options of devd to quiet its log spamming */
2097
	$_gb = exec("/sbin/devd -q");
2098
	sleep(1);
2099
	unset($_gb);
2100
}
2101

    
2102
function is_interface_vlan_mismatch() {
2103
	global $config, $g;
2104

    
2105
	if (is_array($config['vlans']['vlan'])) {
2106
		foreach ($config['vlans']['vlan'] as $vlan) {
2107
			if (substr($vlan['if'], 0, 4) == "lagg") {
2108
				return false;
2109
			}
2110
			if (does_interface_exist($vlan['if']) == false) {
2111
				return true;
2112
			}
2113
		}
2114
	}
2115

    
2116
	return false;
2117
}
2118

    
2119
function is_interface_mismatch() {
2120
	global $config, $g;
2121

    
2122
	$do_assign = false;
2123
	$i = 0;
2124
	$missing_interfaces = array();
2125
	if (is_array($config['interfaces'])) {
2126
		foreach ($config['interfaces'] as $ifname => $ifcfg) {
2127
			if (preg_match("/^enc|^cua|^tun|^tap|^l2tp|^pptp|^ppp|^ovpn|^gif|^gre|^lagg|^bridge|vlan|_wlan/i", $ifcfg['if'])) {
2128
				// Do not check these interfaces.
2129
				$i++;
2130
				continue;
2131
			} else if (does_interface_exist($ifcfg['if']) == false) {
2132
				$missing_interfaces[] = $ifcfg['if'];
2133
				$do_assign = true;
2134
			} else {
2135
				$i++;
2136
			}
2137
		}
2138
	}
2139

    
2140
	if (file_exists("{$g['tmp_path']}/assign_complete")) {
2141
		$do_assign = false;
2142
	}
2143

    
2144
	if (!empty($missing_interfaces) && $do_assign) {
2145
		file_put_contents("{$g['tmp_path']}/missing_interfaces", implode(' ', $missing_interfaces));
2146
	} else {
2147
		@unlink("{$g['tmp_path']}/missing_interfaces");
2148
	}
2149

    
2150
	return $do_assign;
2151
}
2152

    
2153
/* sync carp entries to other firewalls */
2154
function carp_sync_client() {
2155
	global $g;
2156
	send_event("filter sync");
2157
}
2158

    
2159
/****f* util/isAjax
2160
 * NAME
2161
 *   isAjax - reports if the request is driven from prototype
2162
 * INPUTS
2163
 *   none
2164
 * RESULT
2165
 *   true/false
2166
 ******/
2167
function isAjax() {
2168
	return isset ($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest';
2169
}
2170

    
2171
/****f* util/timeout
2172
 * NAME
2173
 *   timeout - console input with timeout countdown. Note: erases 2 char of screen for timer. Leave space.
2174
 * INPUTS
2175
 *   optional, seconds to wait before timeout. Default 9 seconds.
2176
 * RESULT
2177
 *   returns 1 char of user input or null if no input.
2178
 ******/
2179
function timeout($timer = 9) {
2180
	while (!isset($key)) {
2181
		if ($timer >= 9) {
2182
			echo chr(8) . chr(8) . ($timer == 9 ? chr(32) : null) . "{$timer}";
2183
		} else {
2184
			echo chr(8). "{$timer}";
2185
		}
2186
		`/bin/stty -icanon min 0 time 25`;
2187
		$key = trim(`KEY=\`dd count=1 2>/dev/null\`; echo \$KEY`);
2188
		`/bin/stty icanon`;
2189
		if ($key == '') {
2190
			unset($key);
2191
		}
2192
		$timer--;
2193
		if ($timer == 0) {
2194
			break;
2195
		}
2196
	}
2197
	return $key;
2198
}
2199

    
2200
/****f* util/msort
2201
 * NAME
2202
 *   msort - sort array
2203
 * INPUTS
2204
 *   $array to be sorted, field to sort by, direction of sort
2205
 * RESULT
2206
 *   returns newly sorted array
2207
 ******/
2208
function msort($array, $id = "id", $sort_ascending = true) {
2209
	$temp_array = array();
2210
	while (count($array)>0) {
2211
		$lowest_id = 0;
2212
		$index = 0;
2213
		foreach ($array as $item) {
2214
			if (isset($item[$id])) {
2215
				if ($array[$lowest_id][$id]) {
2216
					if (strtolower($item[$id]) < strtolower($array[$lowest_id][$id])) {
2217
						$lowest_id = $index;
2218
					}
2219
				}
2220
			}
2221
			$index++;
2222
		}
2223
		$temp_array[] = $array[$lowest_id];
2224
		$array = array_merge(array_slice($array, 0, $lowest_id), array_slice($array, $lowest_id + 1));
2225
	}
2226
	if ($sort_ascending) {
2227
		return $temp_array;
2228
	} else {
2229
		return array_reverse($temp_array);
2230
	}
2231
}
2232

    
2233
/****f* util/is_URL
2234
 * NAME
2235
 *   is_URL
2236
 * INPUTS
2237
 *   string to check
2238
 * RESULT
2239
 *   Returns true if item is a URL
2240
 ******/
2241
function is_URL($url) {
2242
	$match = preg_match("'\b(([\w-]+://?|www[.])[^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|/)))'", $url);
2243
	if ($match) {
2244
		return true;
2245
	}
2246
	return false;
2247
}
2248

    
2249
function is_file_included($file = "") {
2250
	$files = get_included_files();
2251
	if (in_array($file, $files)) {
2252
		return true;
2253
	}
2254

    
2255
	return false;
2256
}
2257

    
2258
/*
2259
 * Replace a value on a deep associative array using regex
2260
 */
2261
function array_replace_values_recursive($data, $match, $replace) {
2262
	if (empty($data)) {
2263
		return $data;
2264
	}
2265

    
2266
	if (is_string($data)) {
2267
		$data = preg_replace("/{$match}/", $replace, $data);
2268
	} else if (is_array($data)) {
2269
		foreach ($data as $k => $v) {
2270
			$data[$k] = array_replace_values_recursive($v, $match, $replace);
2271
		}
2272
	}
2273

    
2274
	return $data;
2275
}
2276

    
2277
/*
2278
	This function was borrowed from a comment on PHP.net at the following URL:
2279
	http://www.php.net/manual/en/function.array-merge-recursive.php#73843
2280
 */
2281
function array_merge_recursive_unique($array0, $array1) {
2282

    
2283
	$arrays = func_get_args();
2284
	$remains = $arrays;
2285

    
2286
	// We walk through each arrays and put value in the results (without
2287
	// considering previous value).
2288
	$result = array();
2289

    
2290
	// loop available array
2291
	foreach ($arrays as $array) {
2292

    
2293
		// The first remaining array is $array. We are processing it. So
2294
		// we remove it from remaining arrays.
2295
		array_shift($remains);
2296

    
2297
		// We don't care non array param, like array_merge since PHP 5.0.
2298
		if (is_array($array)) {
2299
			// Loop values
2300
			foreach ($array as $key => $value) {
2301
				if (is_array($value)) {
2302
					// we gather all remaining arrays that have such key available
2303
					$args = array();
2304
					foreach ($remains as $remain) {
2305
						if (array_key_exists($key, $remain)) {
2306
							array_push($args, $remain[$key]);
2307
						}
2308
					}
2309

    
2310
					if (count($args) > 2) {
2311
						// put the recursion
2312
						$result[$key] = call_user_func_array(__FUNCTION__, $args);
2313
					} else {
2314
						foreach ($value as $vkey => $vval) {
2315
							$result[$key][$vkey] = $vval;
2316
						}
2317
					}
2318
				} else {
2319
					// simply put the value
2320
					$result[$key] = $value;
2321
				}
2322
			}
2323
		}
2324
	}
2325
	return $result;
2326
}
2327

    
2328

    
2329
/*
2330
 * converts a string like "a,b,c,d"
2331
 * into an array like array("a" => "b", "c" => "d")
2332
 */
2333
function explode_assoc($delimiter, $string) {
2334
	$array = explode($delimiter, $string);
2335
	$result = array();
2336
	$numkeys = floor(count($array) / 2);
2337
	for ($i = 0; $i < $numkeys; $i += 1) {
2338
		$result[$array[$i * 2]] = $array[$i * 2 + 1];
2339
	}
2340
	return $result;
2341
}
2342

    
2343
function get_staticroutes($returnsubnetsonly = false, $returnhostnames = false) {
2344
	global $config, $aliastable;
2345

    
2346
	/* Bail if there are no routes, but return an array always so callers don't have to check. */
2347
	if (!is_array($config['staticroutes']['route'])) {
2348
		return array();
2349
	}
2350

    
2351
	$allstaticroutes = array();
2352
	$allsubnets = array();
2353
	/* Loop through routes and expand aliases as we find them. */
2354
	foreach ($config['staticroutes']['route'] as $route) {
2355
		if (is_alias($route['network'])) {
2356
			if (!isset($aliastable[$route['network']])) {
2357
				continue;
2358
			}
2359

    
2360
			$subnets = preg_split('/\s+/', $aliastable[$route['network']]);
2361
			foreach ($subnets as $net) {
2362
				if (!is_subnet($net)) {
2363
					if (is_ipaddrv4($net)) {
2364
						$net .= "/32";
2365
					} else if (is_ipaddrv6($net)) {
2366
						$net .= "/128";
2367
					} else if ($returnhostnames === false || !is_fqdn($net)) {
2368
						continue;
2369
					}
2370
				}
2371
				$temproute = $route;
2372
				$temproute['network'] = $net;
2373
				$allstaticroutes[] = $temproute;
2374
				$allsubnets[] = $net;
2375
			}
2376
		} elseif (is_subnet($route['network'])) {
2377
			$allstaticroutes[] = $route;
2378
			$allsubnets[] = $route['network'];
2379
		}
2380
	}
2381
	if ($returnsubnetsonly) {
2382
		return $allsubnets;
2383
	} else {
2384
		return $allstaticroutes;
2385
	}
2386
}
2387

    
2388
/****f* util/get_alias_list
2389
 * NAME
2390
 *   get_alias_list - Provide a list of aliases.
2391
 * INPUTS
2392
 *   $type          - Optional, can be a string or array specifying what type(s) of aliases you need.
2393
 * RESULT
2394
 *   Array containing list of aliases.
2395
 *   If $type is unspecified, all aliases are returned.
2396
 *   If $type is a string, all aliases of the type specified in $type are returned.
2397
 *   If $type is an array, all aliases of any type specified in any element of $type are returned.
2398
 */
2399
function get_alias_list($type = null) {
2400
	global $config;
2401
	$result = array();
2402
	if ($config['aliases']['alias'] <> "" && is_array($config['aliases']['alias'])) {
2403
		foreach ($config['aliases']['alias'] as $alias) {
2404
			if ($type === null) {
2405
				$result[] = $alias['name'];
2406
			} else if (is_array($type)) {
2407
				if (in_array($alias['type'], $type)) {
2408
					$result[] = $alias['name'];
2409
				}
2410
			} else if ($type === $alias['type']) {
2411
				$result[] = $alias['name'];
2412
			}
2413
		}
2414
	}
2415
	return $result;
2416
}
2417

    
2418
/* returns an array consisting of every element of $haystack that is not equal to $needle. */
2419
function array_exclude($needle, $haystack) {
2420
	$result = array();
2421
	if (is_array($haystack)) {
2422
		foreach ($haystack as $thing) {
2423
			if ($needle !== $thing) {
2424
				$result[] = $thing;
2425
			}
2426
		}
2427
	}
2428
	return $result;
2429
}
2430

    
2431
/* Define what is preferred, IPv4 or IPv6 */
2432
function prefer_ipv4_or_ipv6() {
2433
	global $config;
2434

    
2435
	if (isset($config['system']['prefer_ipv4'])) {
2436
		mwexec("/etc/rc.d/ip6addrctl prefer_ipv4");
2437
	} else {
2438
		mwexec("/etc/rc.d/ip6addrctl prefer_ipv6");
2439
	}
2440
}
2441

    
2442
/* Redirect to page passing parameters via POST */
2443
function post_redirect($page, $params) {
2444
	if (!is_array($params)) {
2445
		return;
2446
	}
2447

    
2448
	print "<html><body><form action=\"{$page}\" name=\"formredir\" method=\"post\">\n";
2449
	foreach ($params as $key => $value) {
2450
		print "<input type=\"hidden\" name=\"{$key}\" value=\"{$value}\" />\n";
2451
	}
2452
	print "</form>\n";
2453
	print "<script type=\"text/javascript\">\n";
2454
	print "//<![CDATA[\n";
2455
	print "document.formredir.submit();\n";
2456
	print "//]]>\n";
2457
	print "</script>\n";
2458
	print "</body></html>\n";
2459
}
2460

    
2461
/* Locate disks that can be queried for S.M.A.R.T. data. */
2462
function get_smart_drive_list() {
2463
	$disk_list = explode(" ", get_single_sysctl("kern.disks"));
2464
	foreach ($disk_list as $id => $disk) {
2465
		// We only want certain kinds of disks for S.M.A.R.T.
2466
		// 1 is a match, 0 is no match, False is any problem processing the regex
2467
		if (preg_match("/^(ad|da|ada).*[0-9]{1,2}$/", $disk) !== 1) {
2468
			unset($disk_list[$id]);
2469
		}
2470
	}
2471
	sort($disk_list);
2472
	return $disk_list;
2473
}
2474

    
2475
?>
(55-55/65)