Project

General

Profile

Download (68.3 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 Rubicon Communications, LLC (Netgate)
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 (!is_ipaddrv4($test) || !is_ipaddrv4($start) || !is_ipaddrv4($end)) {
1204
		return false;
1205
	}
1206

    
1207
	if (ip2ulong($test) <= ip2ulong($end) &&
1208
	    ip2ulong($test) >= ip2ulong($start)) {
1209
		return true;
1210
	}
1211

    
1212
	return false;
1213
}
1214

    
1215
/* returns true if $test is in the range between $start and $end */
1216
function is_inrange_v6($test, $start, $end) {
1217
	if (!is_ipaddrv6($test) || !is_ipaddrv6($start) || !is_ipaddrv6($end)) {
1218
		return false;
1219
	}
1220

    
1221
	if (inet_pton($test) <= inet_pton($end) &&
1222
	    inet_pton($test) >= inet_pton($start)) {
1223
		return true;
1224
	}
1225

    
1226
	return false;
1227
}
1228

    
1229
/* returns true if $test is in the range between $start and $end */
1230
function is_inrange($test, $start, $end) {
1231
	return is_ipaddrv6($test) ? is_inrange_v6($test, $start, $end) : is_inrange_v4($test, $start, $end);
1232
}
1233

    
1234
function get_configured_vip_list($family = 'all', $type = VIP_ALL) {
1235
	global $config;
1236

    
1237
	$list = array();
1238
	if (!is_array($config['virtualip']['vip']) || empty($config['virtualip']['vip'])) {
1239
		return ($list);
1240
	}
1241

    
1242
	$viparr = &$config['virtualip']['vip'];
1243
	foreach ($viparr as $vip) {
1244

    
1245
		if ($type == VIP_CARP) {
1246
			if ($vip['mode'] != "carp")
1247
				continue;
1248
		} elseif ($type == VIP_IPALIAS) {
1249
			if ($vip['mode'] != "ipalias")
1250
				continue;
1251
		} else {
1252
			if ($vip['mode'] != "carp" && $vip['mode'] != "ipalias")
1253
				continue;
1254
		}
1255

    
1256
		if ($family == 'all' ||
1257
		    ($family == 'inet' && is_ipaddrv4($vip['subnet'])) ||
1258
		    ($family == 'inet6' && is_ipaddrv6($vip['subnet']))) {
1259
			$list["_vip{$vip['uniqid']}"] = $vip['subnet'];
1260
		}
1261
	}
1262
	return ($list);
1263
}
1264

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

    
1267
	return (get_configured_vip_detail($vipinterface, 'all', 'vip'));
1268
}
1269

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

    
1272
	return (get_configured_vip_detail($vipinterface, 'all', 'iface'));
1273
}
1274

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

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

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

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

    
1285
function get_configured_vip_subnetv4($vipinterface = '') {
1286

    
1287
	return (get_configured_vip_detail($vipinterface, 'inet', 'subnet'));
1288
}
1289

    
1290
function get_configured_vip_subnetv6($vipinterface = '') {
1291

    
1292
	return (get_configured_vip_detail($vipinterface, 'inet6', 'subnet'));
1293
}
1294

    
1295
function get_configured_vip_detail($vipinterface = '', $family = 'inet', $what = 'ip') {
1296
	global $config;
1297

    
1298
	if (empty($vipinterface) || !is_array($config['virtualip']['vip']) ||
1299
	    empty($config['virtualip']['vip'])) {
1300
		return (NULL);
1301
	}
1302

    
1303
	$viparr = &$config['virtualip']['vip'];
1304
	foreach ($viparr as $vip) {
1305
		if ($vip['mode'] != "carp" && $vip['mode'] != "ipalias") {
1306
			continue;
1307
		}
1308

    
1309
		if ($vipinterface != "_vip{$vip['uniqid']}") {
1310
			continue;
1311
		}
1312

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

    
1338
	return (NULL);
1339
}
1340

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

    
1355
	return strnatcmp($a, $b);
1356
}
1357

    
1358
/* return the configured interfaces list. */
1359
function get_configured_interface_list($only_opt = false, $withdisabled = false) {
1360
	global $config;
1361

    
1362
	$iflist = array();
1363

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

    
1374
	return $iflist;
1375
}
1376

    
1377
/* return the configured interfaces list. */
1378
function get_configured_interface_list_by_realif($only_opt = false, $withdisabled = false) {
1379
	global $config;
1380

    
1381
	$iflist = array();
1382

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

    
1396
	return $iflist;
1397
}
1398

    
1399
/* return the configured interfaces list with their description. */
1400
function get_configured_interface_with_descr($only_opt = false, $withdisabled = false) {
1401
	global $config;
1402

    
1403
	$iflist = array();
1404

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

    
1419
	return $iflist;
1420
}
1421

    
1422
/*
1423
 *   get_configured_ip_addresses() - Return a list of all configured
1424
 *   IPv4 addresses.
1425
 *
1426
 */
1427
function get_configured_ip_addresses() {
1428
	global $config;
1429

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

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

    
1460
	return $ip_array;
1461
}
1462

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

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

    
1590
			case "friendly":
1591
				if ($friendly != "") {
1592
					$toput['if'] = $ifname;
1593
					$iflist[$friendly] = $toput;
1594
				}
1595
				break;
1596
			}
1597
		}
1598
	}
1599
	return $iflist;
1600
}
1601

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

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

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

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

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

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

    
1687
	if ($clearsigmask) {
1688
		pcntl_sigprocmask(SIG_SETMASK, $oldset);
1689
	}
1690

    
1691
	return $retval;
1692
}
1693

    
1694
/* wrapper for exec() in background */
1695
function mwexec_bg($command, $clearsigmask = false) {
1696
	return mwexec($command, false, $clearsigmask, true);
1697
}
1698

    
1699
/*	unlink a file, or pattern-match of a file, if it exists
1700
	if the file/path contains glob() compatible wildcards, all matching files will be unlinked
1701
	any warning/errors are suppressed (e.g. no matching files to delete)
1702
	If there are matching file(s) and they were all unlinked OK, then return true.
1703
	Otherwise return false (the requested file(s) did not exist, or could not be deleted)
1704
	This allows the caller to know if they were the one to successfully delete the file(s).
1705
*/
1706
function unlink_if_exists($fn) {
1707
	$to_do = glob($fn);
1708
	if (is_array($to_do) && count($to_do) > 0) {
1709
		// Returns an array of true/false indicating if each unlink worked
1710
		$results = @array_map("unlink", $to_do);
1711
		// If there is no false in the array, then all went well
1712
		$result = !in_array(false, $results, true);
1713
	} else {
1714
		$result = @unlink($fn);
1715
	}
1716
	return $result;
1717
}
1718
/* make a global alias table (for faster lookups) */
1719
function alias_make_table($config) {
1720
	global $aliastable;
1721

    
1722
	$aliastable = array();
1723

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

    
1733
/* check if an alias exists */
1734
function is_alias($name) {
1735
	global $aliastable;
1736

    
1737
	return isset($aliastable[$name]);
1738
}
1739

    
1740
function alias_get_type($name) {
1741
	global $config;
1742

    
1743
	if (is_array($config['aliases']['alias'])) {
1744
		foreach ($config['aliases']['alias'] as $alias) {
1745
			if ($name == $alias['name']) {
1746
				return $alias['type'];
1747
			}
1748
		}
1749
	}
1750

    
1751
	return "";
1752
}
1753

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

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

    
1785
function alias_expand_urltable($name) {
1786
	global $config;
1787
	$urltable_prefix = "/var/db/aliastables/";
1788
	$urltable_filename = $urltable_prefix . $name . ".txt";
1789

    
1790
	if (is_array($config['aliases']['alias'])) {
1791
		foreach ($config['aliases']['alias'] as $alias) {
1792
			if (preg_match("/urltable/i", $alias['type']) && ($alias['name'] == $name)) {
1793
				if (is_URL($alias["url"]) && file_exists($urltable_filename)) {
1794
					if (!filesize($urltable_filename)) {
1795
						// file exists, but is empty, try to sync
1796
						send_event("service sync alias {$name}");
1797
					}
1798
					return $urltable_filename;
1799
				} else {
1800
					send_event("service sync alias {$name}");
1801
					break;
1802
				}
1803
			}
1804
		}
1805
	}
1806
	return null;
1807
}
1808

    
1809
/* obtain MAC address given an IP address by looking at the ARP table */
1810
function arp_get_mac_by_ip($ip) {
1811
	mwexec("/sbin/ping -c 1 -t 1 " . escapeshellarg($ip), true);
1812
	$arpoutput = "";
1813
	exec("/usr/sbin/arp -n " . escapeshellarg($ip), $arpoutput);
1814

    
1815
	if ($arpoutput[0]) {
1816
		$arpi = explode(" ", $arpoutput[0]);
1817
		$macaddr = $arpi[3];
1818
		if (is_macaddr($macaddr)) {
1819
			return $macaddr;
1820
		} else {
1821
			return false;
1822
		}
1823
	}
1824

    
1825
	return false;
1826
}
1827

    
1828
/* return a fieldname that is safe for xml usage */
1829
function xml_safe_fieldname($fieldname) {
1830
	$replace = array('/', '-', ' ', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')',
1831
			 '_', '+', '=', '{', '}', '[', ']', '|', '/', '<', '>', '?',
1832
			 ':', ',', '.', '\'', '\\'
1833
		);
1834
	return strtolower(str_replace($replace, "", $fieldname));
1835
}
1836

    
1837
function mac_format($clientmac) {
1838
	global $config, $cpzone;
1839

    
1840
	$mac = explode(":", $clientmac);
1841
	$mac_format = $cpzone ? $config['captiveportal'][$cpzone]['radmac_format'] : false;
1842

    
1843
	switch ($mac_format) {
1844
		case 'singledash':
1845
			return "$mac[0]$mac[1]$mac[2]-$mac[3]$mac[4]$mac[5]";
1846

    
1847
		case 'ietf':
1848
			return "$mac[0]-$mac[1]-$mac[2]-$mac[3]-$mac[4]-$mac[5]";
1849

    
1850
		case 'cisco':
1851
			return "$mac[0]$mac[1].$mac[2]$mac[3].$mac[4]$mac[5]";
1852

    
1853
		case 'unformatted':
1854
			return "$mac[0]$mac[1]$mac[2]$mac[3]$mac[4]$mac[5]";
1855

    
1856
		default:
1857
			return $clientmac;
1858
	}
1859
}
1860

    
1861
function resolve_retry($hostname, $retries = 5) {
1862

    
1863
	if (is_ipaddr($hostname)) {
1864
		return $hostname;
1865
	}
1866

    
1867
	for ($i = 0; $i < $retries; $i++) {
1868
		// FIXME: gethostbyname does not work for AAAA hostnames, boo, hiss
1869
		$ip = gethostbyname($hostname);
1870

    
1871
		if ($ip && $ip != $hostname) {
1872
			/* success */
1873
			return $ip;
1874
		}
1875

    
1876
		sleep(1);
1877
	}
1878

    
1879
	return false;
1880
}
1881

    
1882
function format_bytes($bytes) {
1883
	if ($bytes >= 1099511627776) {
1884
		return sprintf("%.2f TiB", $bytes/1099511627776);
1885
	} else if ($bytes >= 1073741824) {
1886
		return sprintf("%.2f GiB", $bytes/1073741824);
1887
	} else if ($bytes >= 1048576) {
1888
		return sprintf("%.2f MiB", $bytes/1048576);
1889
	} else if ($bytes >= 1024) {
1890
		return sprintf("%.0f KiB", $bytes/1024);
1891
	} else {
1892
		return sprintf("%d B", $bytes);
1893
	}
1894
}
1895

    
1896
function format_number($num, $precision = 3) {
1897
	$units = array('', 'K', 'M', 'G', 'T');
1898

    
1899
	$i = 0;
1900
	while ($num > 1000 && $i < count($units)) {
1901
		$num /= 1000;
1902
		$i++;
1903
	}
1904
	$num = round($num, $precision);
1905

    
1906
	return ("$num {$units[$i]}");
1907
}
1908

    
1909
function update_filter_reload_status($text, $new=false) {
1910
	global $g;
1911

    
1912
	if ($new) {
1913
		file_put_contents("{$g['varrun_path']}/filter_reload_status", $text  . PHP_EOL);
1914
	} else {
1915
		file_put_contents("{$g['varrun_path']}/filter_reload_status", $text  . PHP_EOL, FILE_APPEND);
1916
	}
1917
}
1918

    
1919
/****** util/return_dir_as_array
1920
 * NAME
1921
 *   return_dir_as_array - Return a directory's contents as an array.
1922
 * INPUTS
1923
 *   $dir          - string containing the path to the desired directory.
1924
 *   $filter_regex - string containing a regular expression to filter file names. Default empty.
1925
 * RESULT
1926
 *   $dir_array - array containing the directory's contents. This array will be empty if the path specified is invalid.
1927
 ******/
1928
function return_dir_as_array($dir, $filter_regex = '') {
1929
	$dir_array = array();
1930
	if (is_dir($dir)) {
1931
		if ($dh = opendir($dir)) {
1932
			while (($file = readdir($dh)) !== false) {
1933
				if (($file == ".") || ($file == "..")) {
1934
					continue;
1935
				}
1936

    
1937
				if (empty($filter_regex) || preg_match($filter_regex, $file)) {
1938
					array_push($dir_array, $file);
1939
				}
1940
			}
1941
			closedir($dh);
1942
		}
1943
	}
1944
	return $dir_array;
1945
}
1946

    
1947
function run_plugins($directory) {
1948
	global $config, $g;
1949

    
1950
	/* process packager manager custom rules */
1951
	$files = return_dir_as_array($directory);
1952
	if (is_array($files)) {
1953
		foreach ($files as $file) {
1954
			if (stristr($file, ".sh") == true) {
1955
				mwexec($directory . $file . " start");
1956
			} else if (!is_dir($directory . "/" . $file) && stristr($file, ".inc")) {
1957
				require_once($directory . "/" . $file);
1958
			}
1959
		}
1960
	}
1961
}
1962

    
1963
/*
1964
 *    safe_mkdir($path, $mode = 0755)
1965
 *    create directory if it doesn't already exist and isn't a file!
1966
 */
1967
function safe_mkdir($path, $mode = 0755) {
1968
	global $g;
1969

    
1970
	if (!is_file($path) && !is_dir($path)) {
1971
		return @mkdir($path, $mode, true);
1972
	} else {
1973
		return false;
1974
	}
1975
}
1976

    
1977
/*
1978
 * get_sysctl($names)
1979
 * Get values of sysctl OID's listed in $names (accepts an array or a single
1980
 * name) and return an array of key/value pairs set for those that exist
1981
 */
1982
function get_sysctl($names) {
1983
	if (empty($names)) {
1984
		return array();
1985
	}
1986

    
1987
	if (is_array($names)) {
1988
		$name_list = array();
1989
		foreach ($names as $name) {
1990
			$name_list[] = escapeshellarg($name);
1991
		}
1992
	} else {
1993
		$name_list = array(escapeshellarg($names));
1994
	}
1995

    
1996
	exec("/sbin/sysctl -i " . implode(" ", $name_list), $output);
1997
	$values = array();
1998
	foreach ($output as $line) {
1999
		$line = explode(": ", $line, 2);
2000
		if (count($line) == 2) {
2001
			$values[$line[0]] = $line[1];
2002
		}
2003
	}
2004

    
2005
	return $values;
2006
}
2007

    
2008
/*
2009
 * get_single_sysctl($name)
2010
 * Wrapper for get_sysctl() to simplify read of a single sysctl value
2011
 * return the value for sysctl $name or empty string if it doesn't exist
2012
 */
2013
function get_single_sysctl($name) {
2014
	if (empty($name)) {
2015
		return "";
2016
	}
2017

    
2018
	$value = get_sysctl($name);
2019
	if (empty($value) || !isset($value[$name])) {
2020
		return "";
2021
	}
2022

    
2023
	return $value[$name];
2024
}
2025

    
2026
/*
2027
 * set_sysctl($value_list)
2028
 * Set sysctl OID's listed as key/value pairs and return
2029
 * an array with keys set for those that succeeded
2030
 */
2031
function set_sysctl($values) {
2032
	if (empty($values)) {
2033
		return array();
2034
	}
2035

    
2036
	$value_list = array();
2037
	foreach ($values as $key => $value) {
2038
		$value_list[] = escapeshellarg($key) . "=" . escapeshellarg($value);
2039
	}
2040

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

    
2043
	/* Retry individually if failed (one or more read-only) */
2044
	if ($success <> 0 && count($value_list) > 1) {
2045
		foreach ($value_list as $value) {
2046
			exec("/sbin/sysctl -i " . $value, $output);
2047
		}
2048
	}
2049

    
2050
	$ret = array();
2051
	foreach ($output as $line) {
2052
		$line = explode(": ", $line, 2);
2053
		if (count($line) == 2) {
2054
			$ret[$line[0]] = true;
2055
		}
2056
	}
2057

    
2058
	return $ret;
2059
}
2060

    
2061
/*
2062
 * set_single_sysctl($name, $value)
2063
 * Wrapper to set_sysctl() to make it simple to set only one sysctl
2064
 * returns boolean meaning if it succeeded
2065
 */
2066
function set_single_sysctl($name, $value) {
2067
	if (empty($name)) {
2068
		return false;
2069
	}
2070

    
2071
	$result = set_sysctl(array($name => $value));
2072

    
2073
	if (!isset($result[$name]) || $result[$name] != $value) {
2074
		return false;
2075
	}
2076

    
2077
	return true;
2078
}
2079

    
2080
/*
2081
 *     get_memory()
2082
 *     returns an array listing the amount of
2083
 *     memory installed in the hardware
2084
 *     [0] net memory available for the OS (FreeBSD) after some is taken by BIOS, video or whatever - e.g. 235 MBytes
2085
 *     [1] real (actual) memory of the system, should be the size of the RAM card/s - e.g. 256 MBytes
2086
 */
2087
function get_memory() {
2088
	$physmem = get_single_sysctl("hw.physmem");
2089
	$realmem = get_single_sysctl("hw.realmem");
2090
	/* convert from bytes to megabytes */
2091
	return array(($physmem/1048576), ($realmem/1048576));
2092
}
2093

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

    
2107
function unmute_kernel_msgs() {
2108
	global $g;
2109
	// Do not mute serial console.  The kernel gets very very cranky
2110
	// and will start dishing you cannot control tty errors.
2111
	if ($g['platform'] == 'nanobsd') {
2112
		return;
2113
	}
2114
	exec("/sbin/conscontrol mute off");
2115
}
2116

    
2117
function start_devd() {
2118
	/* Use the undocumented -q options of devd to quiet its log spamming */
2119
	$_gb = exec("/sbin/devd -q");
2120
	sleep(1);
2121
	unset($_gb);
2122
}
2123

    
2124
function is_interface_vlan_mismatch() {
2125
	global $config, $g;
2126

    
2127
	if (is_array($config['vlans']['vlan'])) {
2128
		foreach ($config['vlans']['vlan'] as $vlan) {
2129
			if (substr($vlan['if'], 0, 4) == "lagg") {
2130
				return false;
2131
			}
2132
			if (does_interface_exist($vlan['if']) == false) {
2133
				return true;
2134
			}
2135
		}
2136
	}
2137

    
2138
	return false;
2139
}
2140

    
2141
function is_interface_mismatch() {
2142
	global $config, $g;
2143

    
2144
	$do_assign = false;
2145
	$i = 0;
2146
	$missing_interfaces = array();
2147
	if (is_array($config['interfaces'])) {
2148
		foreach ($config['interfaces'] as $ifname => $ifcfg) {
2149
			if (preg_match("/^enc|^cua|^tun|^tap|^l2tp|^pptp|^ppp|^ovpn|^gif|^gre|^lagg|^bridge|vlan|_wlan/i", $ifcfg['if'])) {
2150
				// Do not check these interfaces.
2151
				$i++;
2152
				continue;
2153
			} else if (does_interface_exist($ifcfg['if']) == false) {
2154
				$missing_interfaces[] = $ifcfg['if'];
2155
				$do_assign = true;
2156
			} else {
2157
				$i++;
2158
			}
2159
		}
2160
	}
2161

    
2162
	if (file_exists("{$g['tmp_path']}/assign_complete")) {
2163
		$do_assign = false;
2164
	}
2165

    
2166
	if (!empty($missing_interfaces) && $do_assign) {
2167
		file_put_contents("{$g['tmp_path']}/missing_interfaces", implode(' ', $missing_interfaces));
2168
	} else {
2169
		@unlink("{$g['tmp_path']}/missing_interfaces");
2170
	}
2171

    
2172
	return $do_assign;
2173
}
2174

    
2175
/* sync carp entries to other firewalls */
2176
function carp_sync_client() {
2177
	global $g;
2178
	send_event("filter sync");
2179
}
2180

    
2181
/****f* util/isAjax
2182
 * NAME
2183
 *   isAjax - reports if the request is driven from prototype
2184
 * INPUTS
2185
 *   none
2186
 * RESULT
2187
 *   true/false
2188
 ******/
2189
function isAjax() {
2190
	return isset ($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest';
2191
}
2192

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

    
2222
/****f* util/msort
2223
 * NAME
2224
 *   msort - sort array
2225
 * INPUTS
2226
 *   $array to be sorted, field to sort by, direction of sort
2227
 * RESULT
2228
 *   returns newly sorted array
2229
 ******/
2230
function msort($array, $id = "id", $sort_ascending = true) {
2231
	$temp_array = array();
2232
	while (count($array)>0) {
2233
		$lowest_id = 0;
2234
		$index = 0;
2235
		foreach ($array as $item) {
2236
			if (isset($item[$id])) {
2237
				if ($array[$lowest_id][$id]) {
2238
					if (strtolower($item[$id]) < strtolower($array[$lowest_id][$id])) {
2239
						$lowest_id = $index;
2240
					}
2241
				}
2242
			}
2243
			$index++;
2244
		}
2245
		$temp_array[] = $array[$lowest_id];
2246
		$array = array_merge(array_slice($array, 0, $lowest_id), array_slice($array, $lowest_id + 1));
2247
	}
2248
	if ($sort_ascending) {
2249
		return $temp_array;
2250
	} else {
2251
		return array_reverse($temp_array);
2252
	}
2253
}
2254

    
2255
/****f* util/is_URL
2256
 * NAME
2257
 *   is_URL
2258
 * INPUTS
2259
 *   string to check
2260
 * RESULT
2261
 *   Returns true if item is a URL
2262
 ******/
2263
function is_URL($url) {
2264
	$match = preg_match("'\b(([\w-]+://?|www[.])[^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|/)))'", $url);
2265
	if ($match) {
2266
		return true;
2267
	}
2268
	return false;
2269
}
2270

    
2271
function is_file_included($file = "") {
2272
	$files = get_included_files();
2273
	if (in_array($file, $files)) {
2274
		return true;
2275
	}
2276

    
2277
	return false;
2278
}
2279

    
2280
/*
2281
 * Replace a value on a deep associative array using regex
2282
 */
2283
function array_replace_values_recursive($data, $match, $replace) {
2284
	if (empty($data)) {
2285
		return $data;
2286
	}
2287

    
2288
	if (is_string($data)) {
2289
		$data = preg_replace("/{$match}/", $replace, $data);
2290
	} else if (is_array($data)) {
2291
		foreach ($data as $k => $v) {
2292
			$data[$k] = array_replace_values_recursive($v, $match, $replace);
2293
		}
2294
	}
2295

    
2296
	return $data;
2297
}
2298

    
2299
/*
2300
	This function was borrowed from a comment on PHP.net at the following URL:
2301
	http://www.php.net/manual/en/function.array-merge-recursive.php#73843
2302
 */
2303
function array_merge_recursive_unique($array0, $array1) {
2304

    
2305
	$arrays = func_get_args();
2306
	$remains = $arrays;
2307

    
2308
	// We walk through each arrays and put value in the results (without
2309
	// considering previous value).
2310
	$result = array();
2311

    
2312
	// loop available array
2313
	foreach ($arrays as $array) {
2314

    
2315
		// The first remaining array is $array. We are processing it. So
2316
		// we remove it from remaining arrays.
2317
		array_shift($remains);
2318

    
2319
		// We don't care non array param, like array_merge since PHP 5.0.
2320
		if (is_array($array)) {
2321
			// Loop values
2322
			foreach ($array as $key => $value) {
2323
				if (is_array($value)) {
2324
					// we gather all remaining arrays that have such key available
2325
					$args = array();
2326
					foreach ($remains as $remain) {
2327
						if (array_key_exists($key, $remain)) {
2328
							array_push($args, $remain[$key]);
2329
						}
2330
					}
2331

    
2332
					if (count($args) > 2) {
2333
						// put the recursion
2334
						$result[$key] = call_user_func_array(__FUNCTION__, $args);
2335
					} else {
2336
						foreach ($value as $vkey => $vval) {
2337
							$result[$key][$vkey] = $vval;
2338
						}
2339
					}
2340
				} else {
2341
					// simply put the value
2342
					$result[$key] = $value;
2343
				}
2344
			}
2345
		}
2346
	}
2347
	return $result;
2348
}
2349

    
2350

    
2351
/*
2352
 * converts a string like "a,b,c,d"
2353
 * into an array like array("a" => "b", "c" => "d")
2354
 */
2355
function explode_assoc($delimiter, $string) {
2356
	$array = explode($delimiter, $string);
2357
	$result = array();
2358
	$numkeys = floor(count($array) / 2);
2359
	for ($i = 0; $i < $numkeys; $i += 1) {
2360
		$result[$array[$i * 2]] = $array[$i * 2 + 1];
2361
	}
2362
	return $result;
2363
}
2364

    
2365
function get_staticroutes($returnsubnetsonly = false, $returnhostnames = false) {
2366
	global $config, $aliastable;
2367

    
2368
	/* Bail if there are no routes, but return an array always so callers don't have to check. */
2369
	if (!is_array($config['staticroutes']['route'])) {
2370
		return array();
2371
	}
2372

    
2373
	$allstaticroutes = array();
2374
	$allsubnets = array();
2375
	/* Loop through routes and expand aliases as we find them. */
2376
	foreach ($config['staticroutes']['route'] as $route) {
2377
		if (is_alias($route['network'])) {
2378
			if (!isset($aliastable[$route['network']])) {
2379
				continue;
2380
			}
2381

    
2382
			$subnets = preg_split('/\s+/', $aliastable[$route['network']]);
2383
			foreach ($subnets as $net) {
2384
				if (!is_subnet($net)) {
2385
					if (is_ipaddrv4($net)) {
2386
						$net .= "/32";
2387
					} else if (is_ipaddrv6($net)) {
2388
						$net .= "/128";
2389
					} else if ($returnhostnames === false || !is_fqdn($net)) {
2390
						continue;
2391
					}
2392
				}
2393
				$temproute = $route;
2394
				$temproute['network'] = $net;
2395
				$allstaticroutes[] = $temproute;
2396
				$allsubnets[] = $net;
2397
			}
2398
		} elseif (is_subnet($route['network'])) {
2399
			$allstaticroutes[] = $route;
2400
			$allsubnets[] = $route['network'];
2401
		}
2402
	}
2403
	if ($returnsubnetsonly) {
2404
		return $allsubnets;
2405
	} else {
2406
		return $allstaticroutes;
2407
	}
2408
}
2409

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

    
2440
/* returns an array consisting of every element of $haystack that is not equal to $needle. */
2441
function array_exclude($needle, $haystack) {
2442
	$result = array();
2443
	if (is_array($haystack)) {
2444
		foreach ($haystack as $thing) {
2445
			if ($needle !== $thing) {
2446
				$result[] = $thing;
2447
			}
2448
		}
2449
	}
2450
	return $result;
2451
}
2452

    
2453
/* Define what is preferred, IPv4 or IPv6 */
2454
function prefer_ipv4_or_ipv6() {
2455
	global $config;
2456

    
2457
	if (isset($config['system']['prefer_ipv4'])) {
2458
		mwexec("/etc/rc.d/ip6addrctl prefer_ipv4");
2459
	} else {
2460
		mwexec("/etc/rc.d/ip6addrctl prefer_ipv6");
2461
	}
2462
}
2463

    
2464
/* Redirect to page passing parameters via POST */
2465
function post_redirect($page, $params) {
2466
	if (!is_array($params)) {
2467
		return;
2468
	}
2469

    
2470
	print "<html><body><form action=\"{$page}\" name=\"formredir\" method=\"post\">\n";
2471
	foreach ($params as $key => $value) {
2472
		print "<input type=\"hidden\" name=\"{$key}\" value=\"{$value}\" />\n";
2473
	}
2474
	print "</form>\n";
2475
	print "<script type=\"text/javascript\">\n";
2476
	print "//<![CDATA[\n";
2477
	print "document.formredir.submit();\n";
2478
	print "//]]>\n";
2479
	print "</script>\n";
2480
	print "</body></html>\n";
2481
}
2482

    
2483
/* Locate disks that can be queried for S.M.A.R.T. data. */
2484
function get_smart_drive_list() {
2485
	$disk_list = explode(" ", get_single_sysctl("kern.disks"));
2486
	foreach ($disk_list as $id => $disk) {
2487
		// We only want certain kinds of disks for S.M.A.R.T.
2488
		// 1 is a match, 0 is no match, False is any problem processing the regex
2489
		if (preg_match("/^(ad|da|ada).*[0-9]{1,2}$/", $disk) !== 1) {
2490
			unset($disk_list[$id]);
2491
		}
2492
	}
2493
	sort($disk_list);
2494
	return $disk_list;
2495
}
2496

    
2497
?>
(55-55/65)