Project

General

Profile

Download (67.4 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
 * Licensed under the Apache License, Version 2.0 (the "License");
14
 * you may not use this file except in compliance with the License.
15
 * You may obtain a copy of the License at
16
 *
17
 * http://www.apache.org/licenses/LICENSE-2.0
18
 *
19
 * Unless required by applicable law or agreed to in writing, software
20
 * distributed under the License is distributed on an "AS IS" BASIS,
21
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22
 * See the License for the specific language governing permissions and
23
 * limitations under the License.
24
 */
25

    
26
define('VIP_ALL', 1);
27
define('VIP_CARP', 2);
28
define('VIP_IPALIAS', 3);
29

    
30
/* kill a process by pid file */
31
function killbypid($pidfile) {
32
	return sigkillbypid($pidfile, "TERM");
33
}
34

    
35
function isvalidpid($pidfile) {
36
	$output = "";
37
	if (file_exists($pidfile)) {
38
		exec("/bin/pgrep -qnF {$pidfile} 2>/dev/null", $output, $retval);
39
		return (intval($retval) == 0);
40
	}
41
	return false;
42
}
43

    
44
function is_process_running($process) {
45
	$output = "";
46
	exec("/bin/pgrep -anx " . escapeshellarg($process), $output, $retval);
47

    
48
	return (intval($retval) == 0);
49
}
50

    
51
function isvalidproc($proc) {
52
	return is_process_running($proc);
53
}
54

    
55
/* sigkill a process by pid file */
56
/* return 1 for success and 0 for a failure */
57
function sigkillbypid($pidfile, $sig) {
58
	if (isvalidpid($pidfile)) {
59
		return mwexec("/bin/pkill " . escapeshellarg("-{$sig}") .
60
		    " -F {$pidfile}", true);
61
	}
62

    
63
	return 0;
64
}
65

    
66
/* kill a process by name */
67
function sigkillbyname($procname, $sig) {
68
	if (isvalidproc($procname)) {
69
		return mwexec("/usr/bin/killall " . escapeshellarg("-{$sig}") . " " . escapeshellarg($procname), true);
70
	}
71
}
72

    
73
/* kill a process by name */
74
function killbyname($procname) {
75
	if (isvalidproc($procname)) {
76
		mwexec("/usr/bin/killall " . escapeshellarg($procname));
77
	}
78
}
79

    
80
function is_subsystem_dirty($subsystem = "") {
81
	global $g;
82

    
83
	if ($subsystem == "") {
84
		return false;
85
	}
86

    
87
	if (file_exists("{$g['varrun_path']}/{$subsystem}.dirty")) {
88
		return true;
89
	}
90

    
91
	return false;
92
}
93

    
94
function mark_subsystem_dirty($subsystem = "") {
95
	global $g;
96

    
97
	if (!file_put_contents("{$g['varrun_path']}/{$subsystem}.dirty", "DIRTY")) {
98
		log_error(sprintf(gettext("WARNING: Could not mark subsystem: %s dirty"), $subsystem));
99
	}
100
}
101

    
102
function clear_subsystem_dirty($subsystem = "") {
103
	global $g;
104

    
105
	@unlink("{$g['varrun_path']}/{$subsystem}.dirty");
106
}
107

    
108
function config_lock() {
109
	return;
110
}
111
function config_unlock() {
112
	return;
113
}
114

    
115
/* lock configuration file */
116
function lock($lock, $op = LOCK_SH) {
117
	global $g, $cfglckkeyconsumers;
118
	if (!$lock) {
119
		die(gettext("WARNING: A name must be given as parameter to lock() function."));
120
	}
121
	if (!file_exists("{$g['tmp_path']}/{$lock}.lock")) {
122
		@touch("{$g['tmp_path']}/{$lock}.lock");
123
		@chmod("{$g['tmp_path']}/{$lock}.lock", 0666);
124
	}
125
	$cfglckkeyconsumers++;
126
	if ($fp = fopen("{$g['tmp_path']}/{$lock}.lock", "w")) {
127
		if (flock($fp, $op)) {
128
			return $fp;
129
		} else {
130
			fclose($fp);
131
		}
132
	}
133
}
134

    
135
function try_lock($lock, $timeout = 5) {
136
	global $g, $cfglckkeyconsumers;
137
	if (!$lock) {
138
		die(gettext("WARNING: A name must be given as parameter to try_lock() function."));
139
	}
140
	if (!file_exists("{$g['tmp_path']}/{$lock}.lock")) {
141
		@touch("{$g['tmp_path']}/{$lock}.lock");
142
		@chmod("{$g['tmp_path']}/{$lock}.lock", 0666);
143
	}
144
	$cfglckkeyconsumers++;
145
	if ($fp = fopen("{$g['tmp_path']}/{$lock}.lock", "w")) {
146
		$trycounter = 0;
147
		while (!flock($fp, LOCK_EX | LOCK_NB)) {
148
			if ($trycounter >= $timeout) {
149
				fclose($fp);
150
				return NULL;
151
			}
152
			sleep(1);
153
			$trycounter++;
154
		}
155

    
156
		return $fp;
157
	}
158

    
159
	return NULL;
160
}
161

    
162
/* unlock configuration file */
163
function unlock($cfglckkey = 0) {
164
	global $g, $cfglckkeyconsumers;
165
	flock($cfglckkey, LOCK_UN);
166
	fclose($cfglckkey);
167
	return;
168
}
169

    
170
/* unlock forcefully configuration file */
171
function unlock_force($lock) {
172
	global $g;
173

    
174
	@unlink("{$g['tmp_path']}/{$lock}.lock");
175
}
176

    
177
function send_event($cmd) {
178
	global $g;
179

    
180
	if (!isset($g['event_address'])) {
181
		$g['event_address'] = "unix:///var/run/check_reload_status";
182
	}
183

    
184
	$try = 0;
185
	while ($try < 3) {
186
		$fd = @fsockopen($g['event_address']);
187
		if ($fd) {
188
			fwrite($fd, $cmd);
189
			$resp = fread($fd, 4096);
190
			if ($resp != "OK\n") {
191
				log_error("send_event: sent {$cmd} got {$resp}");
192
			}
193
			fclose($fd);
194
			$try = 3;
195
		} else if (!is_process_running("check_reload_status")) {
196
			mwexec_bg("/usr/bin/nice -n20 /usr/local/sbin/check_reload_status");
197
		}
198
		$try++;
199
	}
200
}
201

    
202
function send_multiple_events($cmds) {
203
	global $g;
204

    
205
	if (!isset($g['event_address'])) {
206
		$g['event_address'] = "unix:///var/run/check_reload_status";
207
	}
208

    
209
	if (!is_array($cmds)) {
210
		return;
211
	}
212

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

    
232
function is_module_loaded($module_name) {
233
	$module_name = str_replace(".ko", "", $module_name);
234
	$running = 0;
235
	$_gb = exec("/sbin/kldstat -qn {$module_name} 2>&1", $_gb, $running);
236
	if (intval($running) == 0) {
237
		return true;
238
	} else {
239
		return false;
240
	}
241
}
242

    
243
/* validate non-negative numeric string, or equivalent numeric variable */
244
function is_numericint($arg) {
245
	return (((is_int($arg) && $arg >= 0) || (is_string($arg) && strlen($arg) > 0 && ctype_digit($arg))) ? true : false);
246
}
247

    
248
/* Generate the (human readable) ipv4 or ipv6 subnet address (i.e., netmask, or subnet start IP)
249
   given an (human readable) ipv4 or ipv6 host address and subnet bit count */
250
function gen_subnet($ipaddr, $bits) {
251
	if (($sn = gen_subnetv6($ipaddr, $bits)) == '') {
252
		$sn = gen_subnetv4($ipaddr, $bits);  // try to avoid rechecking IPv4/v6
253
	}
254
	return $sn;
255
}
256

    
257
/* same as gen_subnet() but accepts IPv4 only */
258
function gen_subnetv4($ipaddr, $bits) {
259
	if (is_ipaddrv4($ipaddr) && is_numericint($bits) && $bits <= 32) {
260
		if ($bits == 0) {
261
			return '0.0.0.0';  // avoids <<32
262
		}
263
		return long2ip(ip2long($ipaddr) & ((0xFFFFFFFF << (32 - $bits)) & 0xFFFFFFFF));
264
	}
265
	return "";
266
}
267

    
268
/* same as gen_subnet() but accepts IPv6 only */
269
function gen_subnetv6($ipaddr, $bits) {
270
	if (is_ipaddrv6($ipaddr) && is_numericint($bits) && $bits <= 128) {
271
		return Net_IPv6::compress(Net_IPv6::getNetmask($ipaddr, $bits));
272
	}
273
	return "";
274
}
275

    
276
/* Generate the (human readable) ipv4 or ipv6 subnet end address (i.e., highest address, end IP, or IPv4 broadcast address)
277
   given an (human readable) ipv4 or ipv6 host address and subnet bit count. */
278
function gen_subnet_max($ipaddr, $bits) {
279
	if (($sn = gen_subnetv6_max($ipaddr, $bits)) == '') {
280
		$sn = gen_subnetv4_max($ipaddr, $bits);  // try to avoid rechecking IPv4/v6
281
	}
282
	return $sn;
283
}
284

    
285
/* same as gen_subnet_max() but validates IPv4 only */
286
function gen_subnetv4_max($ipaddr, $bits) {
287
	if (is_ipaddrv4($ipaddr) && is_numericint($bits) && $bits <= 32) {
288
		if ($bits == 32) {
289
			return $ipaddr;
290
		}
291
		return long2ip32(ip2long($ipaddr) | (~gen_subnet_mask_long($bits) & 0xFFFFFFFF));
292
	}
293
	return "";
294
}
295

    
296
/* same as gen_subnet_max() but validates IPv6 only */
297
function gen_subnetv6_max($ipaddr, $bits) {
298
	if (is_ipaddrv6($ipaddr) && is_numericint($bits) && $bits <= 128) {
299
		$endip_bin = substr(ip6_to_bin($ipaddr), 0, $bits) . str_repeat('1', 128 - $bits);
300
		return bin_to_compressed_ip6($endip_bin);
301
	}
302
	return "";
303
}
304

    
305
/* returns a subnet mask (long given a bit count) */
306
function gen_subnet_mask_long($bits) {
307
	$sm = 0;
308
	for ($i = 0; $i < $bits; $i++) {
309
		$sm >>= 1;
310
		$sm |= 0x80000000;
311
	}
312
	return $sm;
313
}
314

    
315
/* same as above but returns a string */
316
function gen_subnet_mask($bits) {
317
	return long2ip(gen_subnet_mask_long($bits));
318
}
319

    
320
/* Convert a prefix length to an IPv6 address-like mask notation. Very rare but at least ntp needs it. See #4463 */
321
function gen_subnet_mask_v6($bits) {
322
	/* Binary representation of the prefix length */
323
	$bin = str_repeat('1', $bits);
324
	/* Pad right with zeroes to reach the full address length */
325
	$bin = str_pad($bin, 128, '0', STR_PAD_RIGHT);
326
	/* Convert back to an IPv6 address style notation */
327
	return bin_to_ip6($bin);
328
}
329

    
330
/* Convert long int to IPv4 address
331
   Returns '' if not valid IPv4 (including if any bits >32 are non-zero) */
332
function long2ip32($ip) {
333
	return long2ip($ip & 0xFFFFFFFF);
334
}
335

    
336
/* Convert IPv4 address to long int, truncated to 32-bits to avoid sign extension on 64-bit platforms.
337
   Returns '' if not valid IPv4. */
338
function ip2long32($ip) {
339
	return (ip2long($ip) & 0xFFFFFFFF);
340
}
341

    
342
/* Convert IPv4 address to unsigned long int.
343
   Returns '' if not valid IPv4. */
344
function ip2ulong($ip) {
345
	return sprintf("%u", ip2long32($ip));
346
}
347

    
348
/*
349
 * Convert IPv6 address to binary
350
 *
351
 * Obtained from: pear-Net_IPv6
352
 */
353
function ip6_to_bin($ip) {
354
	$binstr = '';
355

    
356
	$ip = Net_IPv6::removeNetmaskSpec($ip);
357
	$ip = Net_IPv6::Uncompress($ip);
358

    
359
	$parts = explode(':', $ip);
360

    
361
	foreach ( $parts as $v ) {
362

    
363
		$str     = base_convert($v, 16, 2);
364
		$binstr .= str_pad($str, 16, '0', STR_PAD_LEFT);
365

    
366
	}
367

    
368
	return $binstr;
369
}
370

    
371
/*
372
 * Convert IPv6 binary to uncompressed address
373
 *
374
 * Obtained from: pear-Net_IPv6
375
 */
376
function bin_to_ip6($bin) {
377
	$ip = "";
378

    
379
	if (strlen($bin) < 128) {
380
		$bin = str_pad($bin, 128, '0', STR_PAD_LEFT);
381
	}
382

    
383
	$parts = str_split($bin, "16");
384

    
385
	foreach ( $parts as $v ) {
386
		$str = base_convert($v, 2, 16);
387
		$ip .= $str.":";
388
	}
389

    
390
	$ip = substr($ip, 0, -1);
391

    
392
	return $ip;
393
}
394

    
395
/*
396
 * Convert IPv6 binary to compressed address
397
 */
398
function bin_to_compressed_ip6($bin) {
399
	return Net_IPv6::compress(bin_to_ip6($bin));
400
}
401

    
402
/* Find out how many IPs are contained within a given IP range
403
 *  e.g. 192.168.0.0 to 192.168.0.255 returns 256
404
 */
405
function ip_range_size_v4($startip, $endip) {
406
	if (is_ipaddrv4($startip) && is_ipaddrv4($endip)) {
407
		// Operate as unsigned long because otherwise it wouldn't work
408
		//   when crossing over from 127.255.255.255 / 128.0.0.0 barrier
409
		return abs(ip2ulong($startip) - ip2ulong($endip)) + 1;
410
	}
411
	return -1;
412
}
413

    
414
/* Find the smallest possible subnet mask which can contain a given number of IPs
415
 *  e.g. 512 IPs can fit in a /23, but 513 IPs need a /22
416
 */
417
function find_smallest_cidr_v4($number) {
418
	$smallest = 1;
419
	for ($b=32; $b > 0; $b--) {
420
		$smallest = ($number <= pow(2, $b)) ? $b : $smallest;
421
	}
422
	return (32-$smallest);
423
}
424

    
425
/* Return the previous IP address before the given address */
426
function ip_before($ip, $offset = 1) {
427
	return long2ip32(ip2long($ip) - $offset);
428
}
429

    
430
/* Return the next IP address after the given address */
431
function ip_after($ip, $offset = 1) {
432
	return long2ip32(ip2long($ip) + $offset);
433
}
434

    
435
/* Return true if the first IP is 'before' the second */
436
function ip_less_than($ip1, $ip2) {
437
	// Compare as unsigned long because otherwise it wouldn't work when
438
	//   crossing over from 127.255.255.255 / 128.0.0.0 barrier
439
	return ip2ulong($ip1) < ip2ulong($ip2);
440
}
441

    
442
/* Return true if the first IP is 'after' the second */
443
function ip_greater_than($ip1, $ip2) {
444
	// Compare as unsigned long because otherwise it wouldn't work
445
	//   when crossing over from 127.255.255.255 / 128.0.0.0 barrier
446
	return ip2ulong($ip1) > ip2ulong($ip2);
447
}
448

    
449
/* compare two IP addresses */
450
function ipcmp($a, $b) {
451
	if (ip_less_than($a, $b)) {
452
		return -1;
453
	} else if (ip_greater_than($a, $b)) {
454
		return 1;
455
	} else {
456
		return 0;
457
	}
458
}
459

    
460
/* Convert a range of IPv4 addresses to an array of individual addresses. */
461
/* Note: IPv6 ranges are not yet supported here. */
462
function ip_range_to_address_array($startip, $endip, $max_size = 5000) {
463
	if (!is_ipaddrv4($startip) || !is_ipaddrv4($endip)) {
464
		return false;
465
	}
466

    
467
	if (ip_greater_than($startip, $endip)) {
468
		// Swap start and end so we can process sensibly.
469
		$temp = $startip;
470
		$startip = $endip;
471
		$endip = $temp;
472
	}
473

    
474
	if (ip_range_size_v4($startip, $endip) > $max_size) {
475
		return false;
476
	}
477

    
478
	// Container for IP addresses within this range.
479
	$rangeaddresses = array();
480
	$end_int = ip2ulong($endip);
481
	for ($ip_int = ip2ulong($startip); $ip_int <= $end_int; $ip_int++) {
482
		$rangeaddresses[] = long2ip($ip_int);
483
	}
484

    
485
	return $rangeaddresses;
486
}
487

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

    
491
	Documented on pfsense dev list 19-20 May 2013. Summary:
492

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

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

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

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

    
514
function ip_range_to_subnet_array($ip1, $ip2) {
515

    
516
	if (is_ipaddrv4($ip1) && is_ipaddrv4($ip2)) {
517
		$proto = 'ipv4';  // for clarity
518
		$bits = 32;
519
		$ip1bin = decbin(ip2long32($ip1));
520
		$ip2bin = decbin(ip2long32($ip2));
521
	} elseif (is_ipaddrv6($ip1) && is_ipaddrv6($ip2)) {
522
		$proto = 'ipv6';
523
		$bits = 128;
524
		$ip1bin = ip6_to_bin($ip1);
525
		$ip2bin = ip6_to_bin($ip2);
526
	} else {
527
		return array();
528
	}
529

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

    
534
	if ($ip1bin == $ip2bin) {
535
		return array($ip1 . '/' . $bits); // exit if ip1=ip2 (trivial case)
536
	}
537

    
538
	if ($ip1bin > $ip2bin) {
539
		list ($ip1bin, $ip2bin) = array($ip2bin, $ip1bin);  // swap if needed (ensures ip1 < ip2)
540
	}
541

    
542
	$rangesubnets = array();
543
	$netsize = 0;
544

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

    
549
		// 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)
550

    
551
		if (substr($ip1bin, -1, 1) == '1') {
552
			// the start ip must be in a separate one-IP cidr range
553
			$new_subnet_ip = substr($ip1bin, $netsize, $bits - $netsize) . str_repeat('0', $netsize);
554
			$rangesubnets[$new_subnet_ip] = $bits - $netsize;
555
			$n = strrpos($ip1bin, '0');  //can't be all 1's
556
			$ip1bin = ($n == 0 ? '' : substr($ip1bin, 0, $n)) . '1' . str_repeat('0', $bits - $n - 1);  // BINARY VERSION OF $ip1 += 1
557
		}
558

    
559
		// 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)
560

    
561
		if (substr($ip2bin, -1, 1) == '0') {
562
			// the end ip must be in a separate one-IP cidr range
563
			$new_subnet_ip = substr($ip2bin, $netsize, $bits - $netsize) . str_repeat('0', $netsize);
564
			$rangesubnets[$new_subnet_ip] = $bits - $netsize;
565
			$n = strrpos($ip2bin, '1');  //can't be all 0's
566
			$ip2bin = ($n == 0 ? '' : substr($ip2bin, 0, $n)) . '0' . str_repeat('1', $bits - $n - 1);  // BINARY VERSION OF $ip2 -= 1
567
			// already checked for the edge case where end = start+1 and start ends in 0x1, above, so it's safe
568
		}
569

    
570
		// this is the only edge case arising from increment/decrement.
571
		// 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)
572

    
573
		if ($ip2bin < $ip1bin) {
574
			continue;
575
		}
576

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

    
580
		$shift = $bits - max(strrpos($ip1bin, '0'), strrpos($ip2bin, '1'));  // num of low bits which are '0' in ip1 and '1' in ip2
581
		$ip1bin = str_repeat('0', $shift) . substr($ip1bin, 0, $bits - $shift);
582
		$ip2bin = str_repeat('0', $shift) . substr($ip2bin, 0, $bits - $shift);
583
		$netsize += $shift;
584
		if ($ip1bin == $ip2bin) {
585
			// we're done.
586
			$new_subnet_ip = substr($ip1bin, $netsize, $bits - $netsize) . str_repeat('0', $netsize);
587
			$rangesubnets[$new_subnet_ip] = $bits - $netsize;
588
			continue;
589
		}
590

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

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

    
596
	ksort($rangesubnets, SORT_STRING);
597
	$out = array();
598

    
599
	foreach ($rangesubnets as $ip => $netmask) {
600
		if ($proto == 'ipv4') {
601
			$i = str_split($ip, 8);
602
			$out[] = implode('.', array(bindec($i[0]), bindec($i[1]), bindec($i[2]), bindec($i[3]))) . '/' . $netmask;
603
		} else {
604
			$out[] = bin_to_compressed_ip6($ip) . '/' . $netmask;
605
		}
606
	}
607

    
608
	return $out;
609
}
610

    
611
/* returns true if $range is a valid pair of IPv4 or IPv6 addresses separated by a "-"
612
	false - if not a valid pair
613
	true (numeric 4 or 6) - if valid, gives type of addresses */
614
function is_iprange($range) {
615
	if (substr_count($range, '-') != 1) {
616
		return false;
617
	}
618
	list($ip1, $ip2) = explode ('-', $range);
619
	if (is_ipaddrv4($ip1) && is_ipaddrv4($ip2)) {
620
		return 4;
621
	}
622
	if (is_ipaddrv6($ip1) && is_ipaddrv6($ip2)) {
623
		return 6;
624
	}
625
	return false;
626
}
627

    
628
/* returns true if $ipaddr is a valid dotted IPv4 address or a IPv6
629
	false - not valid
630
	true (numeric 4 or 6) - if valid, gives type of address */
631
function is_ipaddr($ipaddr) {
632
	if (is_ipaddrv4($ipaddr)) {
633
		return 4;
634
	}
635
	if (is_ipaddrv6($ipaddr)) {
636
		return 6;
637
	}
638
	return false;
639
}
640

    
641
/* returns true if $ipaddr is a valid IPv6 address */
642
function is_ipaddrv6($ipaddr) {
643
	if (!is_string($ipaddr) || empty($ipaddr)) {
644
		return false;
645
	}
646
	if (strstr($ipaddr, "%") && is_linklocal($ipaddr)) {
647
		$tmpip = explode("%", $ipaddr);
648
		$ipaddr = $tmpip[0];
649
	}
650
	return Net_IPv6::checkIPv6($ipaddr);
651
}
652

    
653
/* returns true if $ipaddr is a valid dotted IPv4 address */
654
function is_ipaddrv4($ipaddr) {
655
	if (!is_string($ipaddr) || empty($ipaddr) || ip2long($ipaddr) === FALSE) {
656
		return false;
657
	}
658
	return true;
659
}
660

    
661
/* returns 4 or 6 respectively (== TRUE) if $ipaddr is a valid IPv4 or IPv6 linklocal address
662
   returns '' if not a valid linklocal address */
663
function is_linklocal($ipaddr) {
664
	if (is_ipaddrv4($ipaddr)) {
665
		// input is IPv4
666
		// test if it's 169.254.x.x per rfc3927 2.1
667
		$ip4 = explode(".", $ipaddr);
668
		if ($ip4[0] == '169' && $ip4[1] == '254') {
669
			return 4;
670
		}
671
	} elseif (Net_IPv6::getAddressType($ipaddr) == NET_IPV6_LOCAL_LINK) {
672
		return 6;
673
	}
674
	return '';
675
}
676

    
677
/* returns scope of a linklocal address */
678
function get_ll_scope($addr) {
679
	if (!is_linklocal($addr) || !strstr($addr, "%")) {
680
		return "";
681
	}
682
	list ($ll, $scope) = explode("%", $addr);
683
	return $scope;
684
}
685

    
686
/* returns true if $ipaddr is a valid literal IPv6 address */
687
function is_literalipaddrv6($ipaddr) {
688
	if (substr($ipaddr,0,1) == '[' && substr($ipaddr,-1,1) == ']') {
689
		// if it's data wrapped in "[ ... ]" then test if middle part is valid IPv6
690
		return is_ipaddrv6(substr($ipaddr,1,-1));
691
	}
692
	return false;
693
}
694

    
695
/* returns true if $iport is a valid IPv4:port or [Literal IPv6]:port
696
	false - not valid
697
	true (numeric 4 or 6) - if valid, gives type of address */
698
function is_ipaddrwithport($ipport) {
699
	$c = strrpos($ipport, ":");
700
	if ($c === false) {
701
		return false;  // can't split at final colon if no colon exists
702
	}
703

    
704
	if (!is_port(substr($ipport, $c + 1))) {
705
		return false;  // no valid port after last colon
706
	}
707

    
708
	$ip = substr($ipport, 0, $c);  // else is text before last colon a valid IP
709
	if (is_literalipaddrv6($ip)) {
710
		return 6;
711
	} elseif (is_ipaddrv4($ip)) {
712
		return 4;
713
	} else {
714
		return false;
715
	}
716
}
717

    
718
function is_hostnamewithport($hostport) {
719
	$parts = explode(":", $hostport);
720
	// no need to validate with is_string(); if it's not a string then explode won't return 2 parts anyway
721
	if (count($parts) == 2) {
722
		return is_hostname($parts[0]) && is_port($parts[1]);
723
	}
724
	return false;
725
}
726

    
727
/* returns true if $ipaddr is a valid dotted IPv4 address or an alias thereof */
728
function is_ipaddroralias($ipaddr) {
729
	global $config;
730

    
731
	if (is_alias($ipaddr)) {
732
		if (is_array($config['aliases']['alias'])) {
733
			foreach ($config['aliases']['alias'] as $alias) {
734
				if ($alias['name'] == $ipaddr && !preg_match("/port/i", $alias['type'])) {
735
					return true;
736
				}
737
			}
738
		}
739
		return false;
740
	} else {
741
		return is_ipaddr($ipaddr);
742
	}
743

    
744
}
745

    
746
/* returns true if $subnet is a valid IPv4 or IPv6 subnet in CIDR format
747
	false - if not a valid subnet
748
	true (numeric 4 or 6) - if valid, gives type of subnet */
749
function is_subnet($subnet) {
750
	if (is_string($subnet) && preg_match('/^(?:([0-9.]{7,15})|([0-9a-f:]{2,39}))\/(\d{1,3})$/i', $subnet, $parts)) {
751
		if (is_ipaddrv4($parts[1]) && $parts[3] <= 32) {
752
			return 4;
753
		}
754
		if (is_ipaddrv6($parts[2]) && $parts[3] <= 128) {
755
			return 6;
756
		}
757
	}
758
	return false;
759
}
760

    
761
/* same as is_subnet() but accepts IPv4 only */
762
function is_subnetv4($subnet) {
763
	return (is_subnet($subnet) == 4);
764
}
765

    
766
/* same as is_subnet() but accepts IPv6 only */
767
function is_subnetv6($subnet) {
768
	return (is_subnet($subnet) == 6);
769
}
770

    
771
/* returns true if $subnet is a valid subnet in CIDR format or an alias thereof */
772
function is_subnetoralias($subnet) {
773
	global $aliastable;
774

    
775
	if (isset($aliastable[$subnet]) && is_subnet($aliastable[$subnet])) {
776
		return true;
777
	} else {
778
		return is_subnet($subnet);
779
	}
780
}
781

    
782
/* Get number of addresses in an IPv4/IPv6 subnet (represented as a string)
783
   optional $exact=true forces error (0) to be returned if it can't be represented exactly
784
   Exact result not possible above PHP_MAX_INT which is about 2^31 addresses on x32 or 2^63 on x64
785
   Returns 0 for bad data or if cannot represent size as an INT when $exact is set. */
786
function subnet_size($subnet, $exact=false) {
787
	$parts = explode("/", $subnet);
788
	$iptype = is_ipaddr($parts[0]);
789
	if (count($parts) == 2 && $iptype) {
790
		return subnet_size_by_netmask($iptype, $parts[1], $exact);
791
	}
792
	return 0;
793
}
794

    
795
/* Get number of addresses in an IPv4/IPv6 subnet (represented numerically as IP type + bits)
796
   optional $exact=true forces error (0) to be returned if it can't be represented exactly
797
   Hard to think where we might need to count exactly a huge subnet but an overflow detection option is probably sensible
798
   Returns 0 for bad data or if cannot represent size as an INT when $exact is set. */
799
function subnet_size_by_netmask($iptype, $bits, $exact=false) {
800
	if (!is_numericint($bits)) {
801
		return 0;
802
	} elseif ($iptype == 4 && $bits <= 32) {
803
		$snsize = 32 - $bits;
804
	} elseif ($iptype == 6 && $bits <= 128) {
805
		$snsize = 128 - $bits;
806
	} else {
807
		return 0;
808
	}
809

    
810
	// 2**N returns an exact result as an INT if possible, and a float/double if not.
811
	// 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
812
	$result = 2 ** $snsize;
813

    
814
	if ($exact && !is_int($result)) {
815
		//exact required but can't represent result exactly as an INT
816
		return 0;
817
	} else {
818
		// result ok, will be an INT where possible (guaranteed up to 2^31 addresses on x32/x64) and a float for 'huge' subnets
819
		return $result;
820
	}
821
}
822

    
823
/* function used by pfblockerng */
824
function subnetv4_expand($subnet) {
825
	$result = array();
826
	list ($ip, $bits) = explode("/", $subnet);
827
	$net = ip2long($ip);
828
	$mask = (0xffffffff << (32 - $bits));
829
	$net &= $mask;
830
	$size = round(exp(log(2) * (32 - $bits)));
831
	for ($i = 0; $i < $size; $i += 1) {
832
		$result[] = long2ip($net | $i);
833
	}
834
	return $result;
835
}
836

    
837
/* find out whether two IPv4/IPv6 CIDR subnets overlap.
838
   Note: CIDR overlap implies one is identical or included so largest sn will be the same */
839
function check_subnets_overlap($subnet1, $bits1, $subnet2, $bits2) {
840
	if (is_ipaddrv4($subnet1)) {
841
		return check_subnetsv4_overlap($subnet1, $bits1, $subnet2, $bits2);
842
	} else {
843
		return check_subnetsv6_overlap($subnet1, $bits1, $subnet2, $bits2);
844
	}
845
}
846

    
847
/* find out whether two IPv4 CIDR subnets overlap.
848
   Note: CIDR overlap means sn1/sn2 are identical or one is included in other. So sn using largest $bits will be the same  */
849
function check_subnetsv4_overlap($subnet1, $bits1, $subnet2, $bits2) {
850
	$largest_sn = min($bits1, $bits2);
851
	$subnetv4_start1 = gen_subnetv4($subnet1, $largest_sn);
852
	$subnetv4_start2 = gen_subnetv4($subnet2, $largest_sn);
853

    
854
	if ($subnetv4_start1 == '' || $subnetv4_start2 == '') {
855
		// One or both args is not a valid IPv4 subnet
856
		//FIXME: needs to return "bad data" not true/false if bad. For now return false, best we can do until fixed
857
		return false;
858
	}
859
	return ($subnetv4_start1 == $subnetv4_start2);
860
}
861

    
862
/* find out whether two IPv6 CIDR subnets overlap.
863
   Note: CIDR overlap means sn1/sn2 are identical or one is included in other. So sn using largest $bits will be the same  */
864
function check_subnetsv6_overlap($subnet1, $bits1, $subnet2, $bits2) {
865
	$largest_sn = min($bits1, $bits2);
866
	$subnetv6_start1 = gen_subnetv6($subnet1, $largest_sn);
867
	$subnetv6_start2 = gen_subnetv6($subnet2, $largest_sn);
868

    
869
	if ($subnetv6_start1 == '' || $subnetv6_start2 == '') {
870
		// One or both args is not a valid IPv6 subnet
871
		//FIXME: needs to return "bad data" not true/false if bad. For now return false, best we can do until fixed
872
		return false;
873
	}
874
	return ($subnetv6_start1 == $subnetv6_start2);
875
}
876

    
877
/* return all PTR zones for a IPv6 network */
878
function get_v6_ptr_zones($subnet, $bits) {
879
	$result = array();
880

    
881
	if (!is_ipaddrv6($subnet)) {
882
		return $result;
883
	}
884

    
885
	if (!is_numericint($bits) || $bits > 128) {
886
		return $result;
887
	}
888

    
889
	/*
890
	 * Find a small nibble boundary subnet mask
891
	 * e.g. a /29 will create 8 /32 PTR zones
892
	 */
893
	$small_sn = $bits;
894
	while ($small_sn % 4 != 0) {
895
		$small_sn++;
896
	}
897

    
898
	/* Get network prefix */
899
	$small_subnet = Net_IPv6::getNetmask($subnet, $bits);
900

    
901
	/*
902
	 * While small network is part of bigger one, increase 4-bit in last
903
	 * digit to get next small network
904
	 */
905
	while (Net_IPv6::isInNetmask($small_subnet, $subnet, $bits)) {
906
		/* Get a pure hex value */
907
		$unpacked = unpack('H*hex', inet_pton($small_subnet));
908
		/* Create PTR record using $small_sn / 4 chars */
909
		$result[] = implode('.', array_reverse(str_split(substr(
910
		    $unpacked['hex'], 0, $small_sn / 4)))).'.ip6.arpa';
911

    
912
		/* Detect what part of IP should be increased */
913
		$change_part = (int) ($small_sn / 16);
914
		if ($small_sn % 16 == 0) {
915
			$change_part--;
916
		}
917

    
918
		/* Increase 1 to desired part */
919
		$parts = explode(":", Net_IPv6::uncompress($small_subnet));
920
		$parts[$change_part]++;
921
		$small_subnet = implode(":", $parts);
922
	}
923

    
924
	return $result;
925
}
926

    
927
/* return true if $addr is in $subnet, false if not */
928
function ip_in_subnet($addr, $subnet) {
929
	if (is_ipaddrv6($addr) && is_subnetv6($subnet)) {
930
		return (Net_IPv6::isInNetmask($addr, $subnet));
931
	} else if (is_ipaddrv4($addr) && is_subnetv4($subnet)) {
932
		list($ip, $mask) = explode('/', $subnet);
933
		$mask = (0xffffffff << (32 - $mask)) & 0xffffffff;
934
		return ((ip2long($addr) & $mask) == (ip2long($ip) & $mask));
935
	}
936
	return false;
937
}
938

    
939
/* returns true if $hostname is just a valid hostname (top part without any of the domain part) */
940
function is_unqualified_hostname($hostname) {
941
	if (!is_string($hostname)) {
942
		return false;
943
	}
944

    
945
	if (preg_match('/^(?:[a-z0-9_]|[a-z0-9_][a-z0-9_\-]*[a-z0-9_])$/i', $hostname)) {
946
		return true;
947
	} else {
948
		return false;
949
	}
950
}
951

    
952
/* returns true if $hostname is a valid hostname, with or without being a fully-qualified domain name. */
953
function is_hostname($hostname, $allow_wildcard=false) {
954
	if (!is_string($hostname)) {
955
		return false;
956
	}
957

    
958
	if (is_domain($hostname, $allow_wildcard)) {
959
		if ((substr_count($hostname, ".") == 1) && ($hostname[strlen($hostname)-1] == ".")) {
960
			/* Only a single dot at the end like "test." - hosts cannot be directly in the root domain. */
961
			return false;
962
		} else {
963
			return true;
964
		}
965
	} else {
966
		return false;
967
	}
968
}
969

    
970
/* returns true if $domain is a valid domain name */
971
function is_domain($domain, $allow_wildcard=false) {
972
	if (!is_string($domain)) {
973
		return false;
974
	}
975
	if ($allow_wildcard) {
976
		$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';
977
	} else {
978
		$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';
979
	}
980

    
981
	if (preg_match($domain_regex, $domain)) {
982
		return true;
983
	} else {
984
		return false;
985
	}
986
}
987

    
988
/* returns true if $macaddr is a valid MAC address */
989
function is_macaddr($macaddr) {
990
	$values = explode(":", $macaddr);
991
	if (count($values) != 6) {
992
		return false;
993
	}
994
	for ($i = 0; $i < 6; $i++) {
995
		if (ctype_xdigit($values[$i]) == false)
996
			return false;
997
		if (hexdec($values[$i]) < 0 || hexdec($values[$i]) > 255)
998
			return false;
999
	}
1000

    
1001
	return true;
1002
}
1003

    
1004
/*
1005
	If $return_message is true then
1006
		returns a text message about the reason that the name is invalid.
1007
		the text includes the type of "thing" that is being checked, passed in $object. (e.g. "alias", "gateway group", "schedule")
1008
	else
1009
		returns true if $name is a valid name for an alias
1010
		returns false if $name is not a valid name for an alias
1011

    
1012
	Aliases cannot be:
1013
		bad chars: anything except a-z 0-9 and underscore
1014
		bad names: empty string, pure numeric, pure underscore
1015
		reserved words: pre-defined service/protocol/port names which should not be ambiguous, and the words "port" and  "pass" */
1016

    
1017
function is_validaliasname($name, $return_message = false, $object = "alias") {
1018
	/* Array of reserved words */
1019
	$reserved = array("port", "pass");
1020

    
1021
	if (!is_string($name) || strlen($name) >= 32 || preg_match('/(^_*$|^\d*$|[^a-z0-9_])/i', $name)) {
1022
		if ($return_message) {
1023
			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, _');
1024
		} else {
1025
			return false;
1026
		}
1027
	}
1028
	if (in_array($name, $reserved, true)) {
1029
		if ($return_message) {
1030
			return sprintf(gettext('The %1$s name must not be either of the reserved words %2$s or %3$s.'), $object, "'port'", "'pass'");
1031
		} else {
1032
			return false;
1033
		}
1034
	}
1035
	if (getprotobyname($name)) {
1036
		if ($return_message) {
1037
			return sprintf(gettext('The %1$s name must not be a well-known IP protocol name such as TCP, UDP, ICMP etc.'), $object);
1038
		} else {
1039
			return false;
1040
		}
1041
	}
1042
	if (getservbyname($name, "tcp") || getservbyname($name, "udp")) {
1043
		if ($return_message) {
1044
			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);
1045
		} else {
1046
			return false;
1047
		}
1048
	}
1049
	if ($return_message) {
1050
		return sprintf(gettext("The %1$s name is valid."), $object);
1051
	} else {
1052
		return true;
1053
	}
1054
}
1055

    
1056
/* returns a text message indicating if the alias name is valid, or the reason it is not valid. */
1057
function invalidaliasnamemsg($name, $object = "alias") {
1058
	return is_validaliasname($name, true, $object);
1059
}
1060

    
1061
/* returns true if $port is a valid TCP/UDP port */
1062
function is_port($port) {
1063
	if (ctype_digit($port) && ((intval($port) >= 1) && (intval($port) <= 65535))) {
1064
		return true;
1065
	}
1066
	if (getservbyname($port, "tcp") || getservbyname($port, "udp")) {
1067
		return true;
1068
	}
1069
	return false;
1070
}
1071

    
1072
/* returns true if $portrange is a valid TCP/UDP portrange ("<port>:<port>") */
1073
function is_portrange($portrange) {
1074
	$ports = explode(":", $portrange);
1075

    
1076
	return (count($ports) == 2 && is_port($ports[0]) && is_port($ports[1]));
1077
}
1078

    
1079
/* returns true if $port is a valid port number or an alias thereof */
1080
function is_portoralias($port) {
1081
	global $config;
1082

    
1083
	if (is_alias($port)) {
1084
		if (is_array($config['aliases']['alias'])) {
1085
			foreach ($config['aliases']['alias'] as $alias) {
1086
				if ($alias['name'] == $port && preg_match("/port/i", $alias['type'])) {
1087
					return true;
1088
				}
1089
			}
1090
		}
1091
		return false;
1092
	} else {
1093
		return is_port($port);
1094
	}
1095
}
1096

    
1097
/* create ranges of sequential port numbers (200:215) and remove duplicates */
1098
function group_ports($ports, $kflc = false) {
1099
	if (!is_array($ports) || empty($ports)) {
1100
		return;
1101
	}
1102

    
1103
	$uniq = array();
1104
	$comments = array();
1105
	foreach ($ports as $port) {
1106
		if (($kflc) && (strpos($port, '#') === 0)) {	// Keep Full Line Comments (lines beginning with #).
1107
			$comments[] = $port;
1108
		} else if (is_portrange($port)) {
1109
			list($begin, $end) = explode(":", $port);
1110
			if ($begin > $end) {
1111
				$aux = $begin;
1112
				$begin = $end;
1113
				$end = $aux;
1114
			}
1115
			for ($i = $begin; $i <= $end; $i++) {
1116
				if (!in_array($i, $uniq)) {
1117
					$uniq[] = $i;
1118
				}
1119
			}
1120
		} else if (is_port($port)) {
1121
			if (!in_array($port, $uniq)) {
1122
				$uniq[] = $port;
1123
			}
1124
		}
1125
	}
1126
	sort($uniq, SORT_NUMERIC);
1127

    
1128
	$result = array();
1129
	foreach ($uniq as $idx => $port) {
1130
		if ($idx == 0) {
1131
			$result[] = $port;
1132
			continue;
1133
		}
1134

    
1135
		$last = end($result);
1136
		if (is_portrange($last)) {
1137
			list($begin, $end) = explode(":", $last);
1138
		} else {
1139
			$begin = $end = $last;
1140
		}
1141

    
1142
		if ($port == ($end+1)) {
1143
			$end++;
1144
			$result[count($result)-1] = "{$begin}:{$end}";
1145
		} else {
1146
			$result[] = $port;
1147
		}
1148
	}
1149

    
1150
	return array_merge($comments, $result);
1151
}
1152

    
1153
/* returns true if $val is a valid shaper bandwidth value */
1154
function is_valid_shaperbw($val) {
1155
	return (preg_match("/^(\d+(?:\.\d+)?)([MKG]?b|%)$/", $val));
1156
}
1157

    
1158
/* returns true if $test is in the range between $start and $end */
1159
function is_inrange_v4($test, $start, $end) {
1160
	if (!is_ipaddrv4($test) || !is_ipaddrv4($start) || !is_ipaddrv4($end)) {
1161
		return false;
1162
	}
1163

    
1164
	if (ip2ulong($test) <= ip2ulong($end) &&
1165
	    ip2ulong($test) >= ip2ulong($start)) {
1166
		return true;
1167
	}
1168

    
1169
	return false;
1170
}
1171

    
1172
/* returns true if $test is in the range between $start and $end */
1173
function is_inrange_v6($test, $start, $end) {
1174
	if (!is_ipaddrv6($test) || !is_ipaddrv6($start) || !is_ipaddrv6($end)) {
1175
		return false;
1176
	}
1177

    
1178
	if (inet_pton($test) <= inet_pton($end) &&
1179
	    inet_pton($test) >= inet_pton($start)) {
1180
		return true;
1181
	}
1182

    
1183
	return false;
1184
}
1185

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

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

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

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

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

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

    
1222
function get_configured_vip($vipinterface = '') {
1223

    
1224
	return (get_configured_vip_detail($vipinterface, 'all', 'vip'));
1225
}
1226

    
1227
function get_configured_vip_interface($vipinterface = '') {
1228

    
1229
	return (get_configured_vip_detail($vipinterface, 'all', 'iface'));
1230
}
1231

    
1232
function get_configured_vip_ipv4($vipinterface = '') {
1233

    
1234
	return (get_configured_vip_detail($vipinterface, 'inet', 'ip'));
1235
}
1236

    
1237
function get_configured_vip_ipv6($vipinterface = '') {
1238

    
1239
	return (get_configured_vip_detail($vipinterface, 'inet6', 'ip'));
1240
}
1241

    
1242
function get_configured_vip_subnetv4($vipinterface = '') {
1243

    
1244
	return (get_configured_vip_detail($vipinterface, 'inet', 'subnet'));
1245
}
1246

    
1247
function get_configured_vip_subnetv6($vipinterface = '') {
1248

    
1249
	return (get_configured_vip_detail($vipinterface, 'inet6', 'subnet'));
1250
}
1251

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

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

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

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

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

    
1295
	return (NULL);
1296
}
1297

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

    
1312
	return strnatcmp($a, $b);
1313
}
1314

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

    
1319
	$iflist = array();
1320

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

    
1331
	return $iflist;
1332
}
1333

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

    
1338
	$iflist = array();
1339

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

    
1353
	return $iflist;
1354
}
1355

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

    
1360
	$iflist = array();
1361

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

    
1376
	return $iflist;
1377
}
1378

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

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

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

    
1417
	return $ip_array;
1418
}
1419

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

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

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

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

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

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

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

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

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

    
1644
	if ($clearsigmask) {
1645
		pcntl_sigprocmask(SIG_SETMASK, $oldset);
1646
	}
1647

    
1648
	return $retval;
1649
}
1650

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

    
1656
/*	unlink a file, or pattern-match of a file, if it exists
1657
	if the file/path contains glob() compatible wildcards, all matching files will be unlinked
1658
	any warning/errors are suppressed (e.g. no matching files to delete)
1659
	If there are matching file(s) and they were all unlinked OK, then return true.
1660
	Otherwise return false (the requested file(s) did not exist, or could not be deleted)
1661
	This allows the caller to know if they were the one to successfully delete the file(s).
1662
*/
1663
function unlink_if_exists($fn) {
1664
	$to_do = glob($fn);
1665
	if (is_array($to_do) && count($to_do) > 0) {
1666
		// Returns an array of true/false indicating if each unlink worked
1667
		$results = @array_map("unlink", $to_do);
1668
		// If there is no false in the array, then all went well
1669
		$result = !in_array(false, $results, true);
1670
	} else {
1671
		$result = @unlink($fn);
1672
	}
1673
	return $result;
1674
}
1675
/* make a global alias table (for faster lookups) */
1676
function alias_make_table($config) {
1677
	global $aliastable;
1678

    
1679
	$aliastable = array();
1680

    
1681
	if (is_array($config['aliases']['alias'])) {
1682
		foreach ($config['aliases']['alias'] as $alias) {
1683
			if ($alias['name']) {
1684
				$aliastable[$alias['name']] = $alias['address'];
1685
			}
1686
		}
1687
	}
1688
}
1689

    
1690
/* check if an alias exists */
1691
function is_alias($name) {
1692
	global $aliastable;
1693

    
1694
	return isset($aliastable[$name]);
1695
}
1696

    
1697
function alias_get_type($name) {
1698
	global $config;
1699

    
1700
	if (is_array($config['aliases']['alias'])) {
1701
		foreach ($config['aliases']['alias'] as $alias) {
1702
			if ($name == $alias['name']) {
1703
				return $alias['type'];
1704
			}
1705
		}
1706
	}
1707

    
1708
	return "";
1709
}
1710

    
1711
/* expand a host or network alias, if necessary */
1712
function alias_expand($name) {
1713
	global $config, $aliastable;
1714
	$urltable_prefix = "/var/db/aliastables/";
1715
	$urltable_filename = $urltable_prefix . $name . ".txt";
1716

    
1717
	if (isset($aliastable[$name])) {
1718
		// alias names cannot be strictly numeric. redmine #4289
1719
		if (is_numericint($name)) {
1720
			return null;
1721
		}
1722
		// make sure if it's a ports alias, it actually exists. redmine #5845
1723
		foreach ($config['aliases']['alias'] as $alias) {
1724
			if ($alias['name'] == $name) {
1725
				if ($alias['type'] == "urltable_ports") {
1726
					if (is_URL($alias['url']) && file_exists($urltable_filename) && filesize($urltable_filename)) {
1727
						return "\${$name}";
1728
					} else {
1729
						return null;
1730
					}
1731
				}
1732
			}
1733
		}
1734
		return "\${$name}";
1735
	} else if (is_ipaddr($name) || is_subnet($name) || is_port($name) || is_portrange($name)) {
1736
		return "{$name}";
1737
	} else {
1738
		return null;
1739
	}
1740
}
1741

    
1742
function alias_expand_urltable($name) {
1743
	global $config;
1744
	$urltable_prefix = "/var/db/aliastables/";
1745
	$urltable_filename = $urltable_prefix . $name . ".txt";
1746

    
1747
	if (is_array($config['aliases']['alias'])) {
1748
		foreach ($config['aliases']['alias'] as $alias) {
1749
			if (preg_match("/urltable/i", $alias['type']) && ($alias['name'] == $name)) {
1750
				if (is_URL($alias["url"]) && file_exists($urltable_filename)) {
1751
					if (!filesize($urltable_filename)) {
1752
						// file exists, but is empty, try to sync
1753
						send_event("service sync alias {$name}");
1754
					}
1755
					return $urltable_filename;
1756
				} else {
1757
					send_event("service sync alias {$name}");
1758
					break;
1759
				}
1760
			}
1761
		}
1762
	}
1763
	return null;
1764
}
1765

    
1766
/* obtain MAC address given an IP address by looking at the ARP table */
1767
function arp_get_mac_by_ip($ip) {
1768
	mwexec("/sbin/ping -c 1 -t 1 " . escapeshellarg($ip), true);
1769
	$arpoutput = "";
1770
	exec("/usr/sbin/arp -n " . escapeshellarg($ip), $arpoutput);
1771

    
1772
	if ($arpoutput[0]) {
1773
		$arpi = explode(" ", $arpoutput[0]);
1774
		$macaddr = $arpi[3];
1775
		if (is_macaddr($macaddr)) {
1776
			return $macaddr;
1777
		} else {
1778
			return false;
1779
		}
1780
	}
1781

    
1782
	return false;
1783
}
1784

    
1785
/* return a fieldname that is safe for xml usage */
1786
function xml_safe_fieldname($fieldname) {
1787
	$replace = array('/', '-', ' ', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')',
1788
			 '_', '+', '=', '{', '}', '[', ']', '|', '/', '<', '>', '?',
1789
			 ':', ',', '.', '\'', '\\'
1790
		);
1791
	return strtolower(str_replace($replace, "", $fieldname));
1792
}
1793

    
1794
function mac_format($clientmac) {
1795
	global $config, $cpzone;
1796

    
1797
	$mac = explode(":", $clientmac);
1798
	$mac_format = $cpzone ? $config['captiveportal'][$cpzone]['radmac_format'] : false;
1799

    
1800
	switch ($mac_format) {
1801
		case 'singledash':
1802
			return "$mac[0]$mac[1]$mac[2]-$mac[3]$mac[4]$mac[5]";
1803

    
1804
		case 'ietf':
1805
			return "$mac[0]-$mac[1]-$mac[2]-$mac[3]-$mac[4]-$mac[5]";
1806

    
1807
		case 'cisco':
1808
			return "$mac[0]$mac[1].$mac[2]$mac[3].$mac[4]$mac[5]";
1809

    
1810
		case 'unformatted':
1811
			return "$mac[0]$mac[1]$mac[2]$mac[3]$mac[4]$mac[5]";
1812

    
1813
		default:
1814
			return $clientmac;
1815
	}
1816
}
1817

    
1818
function resolve_retry($hostname, $retries = 5) {
1819

    
1820
	if (is_ipaddr($hostname)) {
1821
		return $hostname;
1822
	}
1823

    
1824
	for ($i = 0; $i < $retries; $i++) {
1825
		// FIXME: gethostbyname does not work for AAAA hostnames, boo, hiss
1826
		$ip = gethostbyname($hostname);
1827

    
1828
		if ($ip && $ip != $hostname) {
1829
			/* success */
1830
			return $ip;
1831
		}
1832

    
1833
		sleep(1);
1834
	}
1835

    
1836
	return false;
1837
}
1838

    
1839
function format_bytes($bytes) {
1840
	if ($bytes >= 1099511627776) {
1841
		return sprintf("%.2f TiB", $bytes/1099511627776);
1842
	} else if ($bytes >= 1073741824) {
1843
		return sprintf("%.2f GiB", $bytes/1073741824);
1844
	} else if ($bytes >= 1048576) {
1845
		return sprintf("%.2f MiB", $bytes/1048576);
1846
	} else if ($bytes >= 1024) {
1847
		return sprintf("%.0f KiB", $bytes/1024);
1848
	} else {
1849
		return sprintf("%d B", $bytes);
1850
	}
1851
}
1852

    
1853
function format_number($num, $precision = 3) {
1854
	$units = array('', 'K', 'M', 'G', 'T');
1855

    
1856
	$i = 0;
1857
	while ($num > 1000 && $i < count($units)) {
1858
		$num /= 1000;
1859
		$i++;
1860
	}
1861
	$num = round($num, $precision);
1862

    
1863
	return ("$num {$units[$i]}");
1864
}
1865

    
1866
function update_filter_reload_status($text, $new=false) {
1867
	global $g;
1868

    
1869
	if ($new) {
1870
		file_put_contents("{$g['varrun_path']}/filter_reload_status", $text  . PHP_EOL);
1871
	} else {
1872
		file_put_contents("{$g['varrun_path']}/filter_reload_status", $text  . PHP_EOL, FILE_APPEND);
1873
	}
1874
}
1875

    
1876
/****** util/return_dir_as_array
1877
 * NAME
1878
 *   return_dir_as_array - Return a directory's contents as an array.
1879
 * INPUTS
1880
 *   $dir          - string containing the path to the desired directory.
1881
 *   $filter_regex - string containing a regular expression to filter file names. Default empty.
1882
 * RESULT
1883
 *   $dir_array - array containing the directory's contents. This array will be empty if the path specified is invalid.
1884
 ******/
1885
function return_dir_as_array($dir, $filter_regex = '') {
1886
	$dir_array = array();
1887
	if (is_dir($dir)) {
1888
		if ($dh = opendir($dir)) {
1889
			while (($file = readdir($dh)) !== false) {
1890
				if (($file == ".") || ($file == "..")) {
1891
					continue;
1892
				}
1893

    
1894
				if (empty($filter_regex) || preg_match($filter_regex, $file)) {
1895
					array_push($dir_array, $file);
1896
				}
1897
			}
1898
			closedir($dh);
1899
		}
1900
	}
1901
	return $dir_array;
1902
}
1903

    
1904
function run_plugins($directory) {
1905
	global $config, $g;
1906

    
1907
	/* process packager manager custom rules */
1908
	$files = return_dir_as_array($directory);
1909
	if (is_array($files)) {
1910
		foreach ($files as $file) {
1911
			if (stristr($file, ".sh") == true) {
1912
				mwexec($directory . $file . " start");
1913
			} else if (!is_dir($directory . "/" . $file) && stristr($file, ".inc")) {
1914
				require_once($directory . "/" . $file);
1915
			}
1916
		}
1917
	}
1918
}
1919

    
1920
/*
1921
 *    safe_mkdir($path, $mode = 0755)
1922
 *    create directory if it doesn't already exist and isn't a file!
1923
 */
1924
function safe_mkdir($path, $mode = 0755) {
1925
	global $g;
1926

    
1927
	if (!is_file($path) && !is_dir($path)) {
1928
		return @mkdir($path, $mode, true);
1929
	} else {
1930
		return false;
1931
	}
1932
}
1933

    
1934
/*
1935
 * get_sysctl($names)
1936
 * Get values of sysctl OID's listed in $names (accepts an array or a single
1937
 * name) and return an array of key/value pairs set for those that exist
1938
 */
1939
function get_sysctl($names) {
1940
	if (empty($names)) {
1941
		return array();
1942
	}
1943

    
1944
	if (is_array($names)) {
1945
		$name_list = array();
1946
		foreach ($names as $name) {
1947
			$name_list[] = escapeshellarg($name);
1948
		}
1949
	} else {
1950
		$name_list = array(escapeshellarg($names));
1951
	}
1952

    
1953
	exec("/sbin/sysctl -i " . implode(" ", $name_list), $output);
1954
	$values = array();
1955
	foreach ($output as $line) {
1956
		$line = explode(": ", $line, 2);
1957
		if (count($line) == 2) {
1958
			$values[$line[0]] = $line[1];
1959
		}
1960
	}
1961

    
1962
	return $values;
1963
}
1964

    
1965
/*
1966
 * get_single_sysctl($name)
1967
 * Wrapper for get_sysctl() to simplify read of a single sysctl value
1968
 * return the value for sysctl $name or empty string if it doesn't exist
1969
 */
1970
function get_single_sysctl($name) {
1971
	if (empty($name)) {
1972
		return "";
1973
	}
1974

    
1975
	$value = get_sysctl($name);
1976
	if (empty($value) || !isset($value[$name])) {
1977
		return "";
1978
	}
1979

    
1980
	return $value[$name];
1981
}
1982

    
1983
/*
1984
 * set_sysctl($value_list)
1985
 * Set sysctl OID's listed as key/value pairs and return
1986
 * an array with keys set for those that succeeded
1987
 */
1988
function set_sysctl($values) {
1989
	if (empty($values)) {
1990
		return array();
1991
	}
1992

    
1993
	$value_list = array();
1994
	foreach ($values as $key => $value) {
1995
		$value_list[] = escapeshellarg($key) . "=" . escapeshellarg($value);
1996
	}
1997

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

    
2000
	/* Retry individually if failed (one or more read-only) */
2001
	if ($success <> 0 && count($value_list) > 1) {
2002
		foreach ($value_list as $value) {
2003
			exec("/sbin/sysctl -i " . $value, $output);
2004
		}
2005
	}
2006

    
2007
	$ret = array();
2008
	foreach ($output as $line) {
2009
		$line = explode(": ", $line, 2);
2010
		if (count($line) == 2) {
2011
			$ret[$line[0]] = true;
2012
		}
2013
	}
2014

    
2015
	return $ret;
2016
}
2017

    
2018
/*
2019
 * set_single_sysctl($name, $value)
2020
 * Wrapper to set_sysctl() to make it simple to set only one sysctl
2021
 * returns boolean meaning if it succeeded
2022
 */
2023
function set_single_sysctl($name, $value) {
2024
	if (empty($name)) {
2025
		return false;
2026
	}
2027

    
2028
	$result = set_sysctl(array($name => $value));
2029

    
2030
	if (!isset($result[$name]) || $result[$name] != $value) {
2031
		return false;
2032
	}
2033

    
2034
	return true;
2035
}
2036

    
2037
/*
2038
 *     get_memory()
2039
 *     returns an array listing the amount of
2040
 *     memory installed in the hardware
2041
 *     [0] net memory available for the OS (FreeBSD) after some is taken by BIOS, video or whatever - e.g. 235 MBytes
2042
 *     [1] real (actual) memory of the system, should be the size of the RAM card/s - e.g. 256 MBytes
2043
 */
2044
function get_memory() {
2045
	$physmem = get_single_sysctl("hw.physmem");
2046
	$realmem = get_single_sysctl("hw.realmem");
2047
	/* convert from bytes to megabytes */
2048
	return array(($physmem/1048576), ($realmem/1048576));
2049
}
2050

    
2051
function mute_kernel_msgs() {
2052
	global $g, $config;
2053

    
2054
	if ($config['system']['enableserial']) {
2055
		return;
2056
	}
2057
	exec("/sbin/conscontrol mute on");
2058
}
2059

    
2060
function unmute_kernel_msgs() {
2061
	global $g;
2062

    
2063
	exec("/sbin/conscontrol mute off");
2064
}
2065

    
2066
function start_devd() {
2067
	global $g;
2068

    
2069
	/* Use the undocumented -q options of devd to quiet its log spamming */
2070
	$_gb = exec("/sbin/devd -q -f /etc/{$g['product_name']}-devd.conf");
2071
	sleep(1);
2072
	unset($_gb);
2073
}
2074

    
2075
function is_interface_vlan_mismatch() {
2076
	global $config, $g;
2077

    
2078
	if (is_array($config['vlans']['vlan'])) {
2079
		foreach ($config['vlans']['vlan'] as $vlan) {
2080
			if (substr($vlan['if'], 0, 4) == "lagg") {
2081
				return false;
2082
			}
2083
			if (does_interface_exist($vlan['if']) == false) {
2084
				return true;
2085
			}
2086
		}
2087
	}
2088

    
2089
	return false;
2090
}
2091

    
2092
function is_interface_mismatch() {
2093
	global $config, $g;
2094

    
2095
	$do_assign = false;
2096
	$i = 0;
2097
	$missing_interfaces = array();
2098
	if (is_array($config['interfaces'])) {
2099
		foreach ($config['interfaces'] as $ifname => $ifcfg) {
2100
			if (preg_match("/^enc|^cua|^tun|^tap|^l2tp|^pptp|^ppp|^ovpn|^gif|^gre|^lagg|^bridge|vlan|_wlan/i", $ifcfg['if'])) {
2101
				// Do not check these interfaces.
2102
				$i++;
2103
				continue;
2104
			} else if (does_interface_exist($ifcfg['if']) == false) {
2105
				$missing_interfaces[] = $ifcfg['if'];
2106
				$do_assign = true;
2107
			} else {
2108
				$i++;
2109
			}
2110
		}
2111
	}
2112

    
2113
	if (file_exists("{$g['tmp_path']}/assign_complete")) {
2114
		$do_assign = false;
2115
	}
2116

    
2117
	if (!empty($missing_interfaces) && $do_assign) {
2118
		file_put_contents("{$g['tmp_path']}/missing_interfaces", implode(' ', $missing_interfaces));
2119
	} else {
2120
		@unlink("{$g['tmp_path']}/missing_interfaces");
2121
	}
2122

    
2123
	return $do_assign;
2124
}
2125

    
2126
/* sync carp entries to other firewalls */
2127
function carp_sync_client() {
2128
	global $g;
2129
	send_event("filter sync");
2130
}
2131

    
2132
/****f* util/isAjax
2133
 * NAME
2134
 *   isAjax - reports if the request is driven from prototype
2135
 * INPUTS
2136
 *   none
2137
 * RESULT
2138
 *   true/false
2139
 ******/
2140
function isAjax() {
2141
	return isset ($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest';
2142
}
2143

    
2144
/****f* util/timeout
2145
 * NAME
2146
 *   timeout - console input with timeout countdown. Note: erases 2 char of screen for timer. Leave space.
2147
 * INPUTS
2148
 *   optional, seconds to wait before timeout. Default 9 seconds.
2149
 * RESULT
2150
 *   returns 1 char of user input or null if no input.
2151
 ******/
2152
function timeout($timer = 9) {
2153
	while (!isset($key)) {
2154
		if ($timer >= 9) {
2155
			echo chr(8) . chr(8) . ($timer == 9 ? chr(32) : null) . "{$timer}";
2156
		} else {
2157
			echo chr(8). "{$timer}";
2158
		}
2159
		`/bin/stty -icanon min 0 time 25`;
2160
		$key = trim(`KEY=\`dd count=1 2>/dev/null\`; echo \$KEY`);
2161
		`/bin/stty icanon`;
2162
		if ($key == '') {
2163
			unset($key);
2164
		}
2165
		$timer--;
2166
		if ($timer == 0) {
2167
			break;
2168
		}
2169
	}
2170
	return $key;
2171
}
2172

    
2173
/****f* util/msort
2174
 * NAME
2175
 *   msort - sort array
2176
 * INPUTS
2177
 *   $array to be sorted, field to sort by, direction of sort
2178
 * RESULT
2179
 *   returns newly sorted array
2180
 ******/
2181
function msort($array, $id = "id", $sort_ascending = true) {
2182
	$temp_array = array();
2183
	while (count($array)>0) {
2184
		$lowest_id = 0;
2185
		$index = 0;
2186
		foreach ($array as $item) {
2187
			if (isset($item[$id])) {
2188
				if ($array[$lowest_id][$id]) {
2189
					if (strtolower($item[$id]) < strtolower($array[$lowest_id][$id])) {
2190
						$lowest_id = $index;
2191
					}
2192
				}
2193
			}
2194
			$index++;
2195
		}
2196
		$temp_array[] = $array[$lowest_id];
2197
		$array = array_merge(array_slice($array, 0, $lowest_id), array_slice($array, $lowest_id + 1));
2198
	}
2199
	if ($sort_ascending) {
2200
		return $temp_array;
2201
	} else {
2202
		return array_reverse($temp_array);
2203
	}
2204
}
2205

    
2206
/****f* util/is_URL
2207
 * NAME
2208
 *   is_URL
2209
 * INPUTS
2210
 *   string to check
2211
 * RESULT
2212
 *   Returns true if item is a URL
2213
 ******/
2214
function is_URL($url) {
2215
	$match = preg_match("'\b(([\w-]+://?|www[.])[^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|/)))'", $url);
2216
	if ($match) {
2217
		return true;
2218
	}
2219
	return false;
2220
}
2221

    
2222
function is_file_included($file = "") {
2223
	$files = get_included_files();
2224
	if (in_array($file, $files)) {
2225
		return true;
2226
	}
2227

    
2228
	return false;
2229
}
2230

    
2231
/*
2232
 * Replace a value on a deep associative array using regex
2233
 */
2234
function array_replace_values_recursive($data, $match, $replace) {
2235
	if (empty($data)) {
2236
		return $data;
2237
	}
2238

    
2239
	if (is_string($data)) {
2240
		$data = preg_replace("/{$match}/", $replace, $data);
2241
	} else if (is_array($data)) {
2242
		foreach ($data as $k => $v) {
2243
			$data[$k] = array_replace_values_recursive($v, $match, $replace);
2244
		}
2245
	}
2246

    
2247
	return $data;
2248
}
2249

    
2250
/*
2251
	This function was borrowed from a comment on PHP.net at the following URL:
2252
	http://www.php.net/manual/en/function.array-merge-recursive.php#73843
2253
 */
2254
function array_merge_recursive_unique($array0, $array1) {
2255

    
2256
	$arrays = func_get_args();
2257
	$remains = $arrays;
2258

    
2259
	// We walk through each arrays and put value in the results (without
2260
	// considering previous value).
2261
	$result = array();
2262

    
2263
	// loop available array
2264
	foreach ($arrays as $array) {
2265

    
2266
		// The first remaining array is $array. We are processing it. So
2267
		// we remove it from remaining arrays.
2268
		array_shift($remains);
2269

    
2270
		// We don't care non array param, like array_merge since PHP 5.0.
2271
		if (is_array($array)) {
2272
			// Loop values
2273
			foreach ($array as $key => $value) {
2274
				if (is_array($value)) {
2275
					// we gather all remaining arrays that have such key available
2276
					$args = array();
2277
					foreach ($remains as $remain) {
2278
						if (array_key_exists($key, $remain)) {
2279
							array_push($args, $remain[$key]);
2280
						}
2281
					}
2282

    
2283
					if (count($args) > 2) {
2284
						// put the recursion
2285
						$result[$key] = call_user_func_array(__FUNCTION__, $args);
2286
					} else {
2287
						foreach ($value as $vkey => $vval) {
2288
							$result[$key][$vkey] = $vval;
2289
						}
2290
					}
2291
				} else {
2292
					// simply put the value
2293
					$result[$key] = $value;
2294
				}
2295
			}
2296
		}
2297
	}
2298
	return $result;
2299
}
2300

    
2301

    
2302
/*
2303
 * converts a string like "a,b,c,d"
2304
 * into an array like array("a" => "b", "c" => "d")
2305
 */
2306
function explode_assoc($delimiter, $string) {
2307
	$array = explode($delimiter, $string);
2308
	$result = array();
2309
	$numkeys = floor(count($array) / 2);
2310
	for ($i = 0; $i < $numkeys; $i += 1) {
2311
		$result[$array[$i * 2]] = $array[$i * 2 + 1];
2312
	}
2313
	return $result;
2314
}
2315

    
2316
/* Try to change a static route, if it doesn't exist, add it */
2317
function route_add_or_change($args) {
2318
	global $config;
2319

    
2320
	if (empty($args)) {
2321
		return false;
2322
	}
2323

    
2324
	/* First, try to add it */
2325
	$_gb = exec(escapeshellcmd("/sbin/route add " . $args), $output, $rc);
2326

    
2327
	if (isset($config['system']['route-debug'])) {
2328
		$mt = microtime();
2329
		log_error("ROUTING debug: $mt - ADD RC={$rc} - $args");
2330
	}
2331

    
2332
	if ($rc != 0) {
2333
		/* If it fails, try to change it */
2334
		$_gb = exec(escapeshellcmd("/sbin/route change " . $args),
2335
		    $output, $rc);
2336

    
2337
		if (isset($config['system']['route-debug'])) {
2338
			$mt = microtime();
2339
			log_error("ROUTING debug: $mt - CHG RC={$rc} - $args");
2340
		}
2341
	}
2342

    
2343
	return ($rc == 0);
2344
}
2345

    
2346
function get_staticroutes($returnsubnetsonly = false, $returnhostnames = false) {
2347
	global $config, $aliastable;
2348

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

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

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

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

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

    
2434
/* Define what is preferred, IPv4 or IPv6 */
2435
function prefer_ipv4_or_ipv6() {
2436
	global $config;
2437

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

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

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

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

    
2478
// Validate a network address
2479
//	$addr: the address to validate
2480
//	$type: IPV4|IPV6|IPV4V6
2481
//	$label: the label used by the GUI to display this value. Required to compose an error message
2482
//	$err_msg: pointer to the callers error message array so that error messages can be added to it here
2483
//	$alias: are aliases permitted for this address?
2484
function validateipaddr(&$addr, $type, $label, &$err_msg, $alias=false) {
2485
	switch ($type) {
2486
		case IPV4:
2487
			if (is_ipaddrv4($addr)) {
2488
				return true;
2489
			} else if ($alias) {
2490
				if (is_alias($addr)) {
2491
					return true;
2492
				} else {
2493
					$err_msg[] = sprintf(gettext("%s must be a valid IPv4 address or alias."), $label);
2494
					return false;
2495
				}
2496
			} else {
2497
				$err_msg[] = sprintf(gettext("%s must be a valid IPv4 address."), $label);
2498
				return false;
2499
			}
2500
		break;
2501
		case IPV6:
2502
			if (is_ipaddrv6($addr)) {
2503
				$addr = strtolower($addr);
2504
				return true;
2505
			} else if ($alias) {
2506
				if (is_alias($addr)) {
2507
					return true;
2508
				} else {
2509
					$err_msg[] = sprintf(gettext("%s must be a valid IPv6 address or alias."), $label);
2510
					return false;
2511
				}
2512
			} else {
2513
				$err_msg[] = sprintf(gettext("%s must be a valid IPv6 address."), $label);
2514
				return false;
2515
			}
2516
		break;
2517
		case IPV4V6:
2518
			if (is_ipaddrv6($addr)) {
2519
				$addr = strtolower($addr);
2520
				return true;
2521
			} else if (is_ipaddrv4($addr)) {
2522
				return true;
2523
			} else if ($alias) {
2524
				if (is_alias($addr)) {
2525
					return true;
2526
				} else {
2527
					$err_msg[] = sprintf(gettext("%s must be a valid IPv4 or IPv6 address or alias."), $label);
2528
					return false;
2529
				}
2530
			} else {
2531
				$err_msg[] = sprintf(gettext("%s must be a valid IPv4 or IPv6 address."), $label);
2532
				return false;
2533
			}
2534
		break;
2535
	}
2536

    
2537
	return false;
2538
}
2539
?>
(43-43/51)