Project

General

Profile

Download (77.8 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
/*
3
 * util.inc
4
 *
5
 * part of pfSense (https://www.pfsense.org)
6
 * Copyright (c) 2004-2019 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, $waitfor = 0) {
32
	return sigkillbypid($pidfile, "TERM", $waitfor);
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, and wait for it to terminate or remove the .pid file for $waitfor seconds */
56
/* return 1 for success and 0 for a failure */
57
function sigkillbypid($pidfile, $sig, $waitfor = 0) {
58
	if (isvalidpid($pidfile)) {
59
		$result = mwexec("/bin/pkill " . escapeshellarg("-{$sig}") .
60
		    " -F {$pidfile}", true);
61
		$waitcounter = $waitfor * 10;
62
		while(isvalidpid($pidfile) && $waitcounter > 0) {
63
			$waitcounter = $waitcounter - 1;
64
			usleep(100000);
65
		}
66
		return $result;
67
	}
68

    
69
	return 0;
70
}
71

    
72
/* kill a process by name */
73
function sigkillbyname($procname, $sig) {
74
	if (isvalidproc($procname)) {
75
		return mwexec("/usr/bin/killall " . escapeshellarg("-{$sig}") . " " . escapeshellarg($procname), true);
76
	}
77
}
78

    
79
/* kill a process by name */
80
function killbyname($procname) {
81
	if (isvalidproc($procname)) {
82
		mwexec("/usr/bin/killall " . escapeshellarg($procname));
83
	}
84
}
85

    
86
function is_subsystem_dirty($subsystem = "") {
87
	global $g;
88

    
89
	if ($subsystem == "") {
90
		return false;
91
	}
92

    
93
	if (file_exists("{$g['varrun_path']}/{$subsystem}.dirty")) {
94
		return true;
95
	}
96

    
97
	return false;
98
}
99

    
100
function mark_subsystem_dirty($subsystem = "") {
101
	global $g;
102

    
103
	if (!file_put_contents("{$g['varrun_path']}/{$subsystem}.dirty", "DIRTY")) {
104
		log_error(sprintf(gettext("WARNING: Could not mark subsystem: %s dirty"), $subsystem));
105
	}
106
}
107

    
108
function clear_subsystem_dirty($subsystem = "") {
109
	global $g;
110

    
111
	@unlink("{$g['varrun_path']}/{$subsystem}.dirty");
112
}
113

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

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

    
153
		return $fp;
154
	}
155

    
156
	return NULL;
157
}
158

    
159
/* unlock configuration file */
160
function unlock($cfglckkey = 0) {
161
	global $g;
162
	flock($cfglckkey, LOCK_UN);
163
	fclose($cfglckkey);
164
	return;
165
}
166

    
167
/* unlock forcefully configuration file */
168
function unlock_force($lock) {
169
	global $g;
170

    
171
	@unlink("{$g['tmp_path']}/{$lock}.lock");
172
}
173

    
174
function send_event($cmd) {
175
	global $g;
176

    
177
	if (!isset($g['event_address'])) {
178
		$g['event_address'] = "unix:///var/run/check_reload_status";
179
	}
180

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

    
199
function send_multiple_events($cmds) {
200
	global $g;
201

    
202
	if (!isset($g['event_address'])) {
203
		$g['event_address'] = "unix:///var/run/check_reload_status";
204
	}
205

    
206
	if (!is_array($cmds)) {
207
		return;
208
	}
209

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

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

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

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

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

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

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

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

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

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

    
312
/* same as above but returns a string */
313
function gen_subnet_mask($bits) {
314
	return long2ip(gen_subnet_mask_long($bits));
315
}
316

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

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

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

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

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

    
353
	$ip = Net_IPv6::removeNetmaskSpec($ip);
354
	$ip = Net_IPv6::Uncompress($ip);
355

    
356
	$parts = explode(':', $ip);
357

    
358
	foreach ( $parts as $v ) {
359

    
360
		$str     = base_convert($v, 16, 2);
361
		$binstr .= str_pad($str, 16, '0', STR_PAD_LEFT);
362

    
363
	}
364

    
365
	return $binstr;
366
}
367

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

    
376
	if (strlen($bin) < 128) {
377
		$bin = str_pad($bin, 128, '0', STR_PAD_LEFT);
378
	}
379

    
380
	$parts = str_split($bin, "16");
381

    
382
	foreach ( $parts as $v ) {
383
		$str = base_convert($v, 2, 16);
384
		$ip .= $str.":";
385
	}
386

    
387
	$ip = substr($ip, 0, -1);
388

    
389
	return $ip;
390
}
391

    
392
/*
393
 * Convert IPv6 binary to compressed address
394
 */
395
function bin_to_compressed_ip6($bin) {
396
	return text_to_compressed_ip6(bin_to_ip6($bin));
397
}
398

    
399
/*
400
 * Convert textual IPv6 address string to compressed address
401
 */
402
function text_to_compressed_ip6($text) {
403
	// Force re-compression by passing parameter 2 (force) true.
404
	// This ensures that supposedly-compressed formats are uncompressed
405
	// first then re-compressed into strictly correct form.
406
	// e.g. 2001:0:0:4:0:0:0:1
407
	// 2001::4:0:0:0:1 is a strictly-incorrect compression,
408
	// but maybe the user entered it like that.
409
	// The "force" parameter will ensure it is returned as:
410
	// 2001:0:0:4::1
411
	return Net_IPv6::compress($text, true);
412
}
413

    
414
/* Find out how many IPs are contained within a given IP range
415
 *  e.g. 192.168.0.0 to 192.168.0.255 returns 256
416
 */
417
function ip_range_size_v4($startip, $endip) {
418
	if (is_ipaddrv4($startip) && is_ipaddrv4($endip)) {
419
		// Operate as unsigned long because otherwise it wouldn't work
420
		//   when crossing over from 127.255.255.255 / 128.0.0.0 barrier
421
		return abs(ip2ulong($startip) - ip2ulong($endip)) + 1;
422
	}
423
	return -1;
424
}
425

    
426
/* Find the smallest possible subnet mask which can contain a given number of IPs
427
 *  e.g. 512 IPs can fit in a /23, but 513 IPs need a /22
428
 */
429
function find_smallest_cidr_v4($number) {
430
	$smallest = 1;
431
	for ($b=32; $b > 0; $b--) {
432
		$smallest = ($number <= pow(2, $b)) ? $b : $smallest;
433
	}
434
	return (32-$smallest);
435
}
436

    
437
/* Return the previous IP address before the given address */
438
function ip_before($ip, $offset = 1) {
439
	return long2ip32(ip2long($ip) - $offset);
440
}
441

    
442
/* Return the next IP address after the given address */
443
function ip_after($ip, $offset = 1) {
444
	return long2ip32(ip2long($ip) + $offset);
445
}
446

    
447
/* Return true if the first IP is 'before' the second */
448
function ip_less_than($ip1, $ip2) {
449
	// Compare as unsigned long because otherwise it wouldn't work when
450
	//   crossing over from 127.255.255.255 / 128.0.0.0 barrier
451
	return ip2ulong($ip1) < ip2ulong($ip2);
452
}
453

    
454
/* Return true if the first IP is 'after' the second */
455
function ip_greater_than($ip1, $ip2) {
456
	// Compare as unsigned long because otherwise it wouldn't work
457
	//   when crossing over from 127.255.255.255 / 128.0.0.0 barrier
458
	return ip2ulong($ip1) > ip2ulong($ip2);
459
}
460

    
461
/* compare two IP addresses */
462
function ipcmp($a, $b) {
463
	if (ip_less_than($a, $b)) {
464
		return -1;
465
	} else if (ip_greater_than($a, $b)) {
466
		return 1;
467
	} else {
468
		return 0;
469
	}
470
}
471

    
472
/* Convert a range of IPv4 addresses to an array of individual addresses. */
473
/* Note: IPv6 ranges are not yet supported here. */
474
function ip_range_to_address_array($startip, $endip, $max_size = 5000) {
475
	if (!is_ipaddrv4($startip) || !is_ipaddrv4($endip)) {
476
		return false;
477
	}
478

    
479
	if (ip_greater_than($startip, $endip)) {
480
		// Swap start and end so we can process sensibly.
481
		$temp = $startip;
482
		$startip = $endip;
483
		$endip = $temp;
484
	}
485

    
486
	if (ip_range_size_v4($startip, $endip) > $max_size) {
487
		return false;
488
	}
489

    
490
	// Container for IP addresses within this range.
491
	$rangeaddresses = array();
492
	$end_int = ip2ulong($endip);
493
	for ($ip_int = ip2ulong($startip); $ip_int <= $end_int; $ip_int++) {
494
		$rangeaddresses[] = long2ip($ip_int);
495
	}
496

    
497
	return $rangeaddresses;
498
}
499

    
500
/*
501
 * Convert an IPv4 or IPv6 IP range to an array of subnets which can contain the range.
502
 * Algorithm and embodying code PD'ed by Stilez - enjoy as you like :-)
503
 *
504
 * Documented on pfsense dev list 19-20 May 2013. Summary:
505
 *
506
 * The algorithm looks at patterns of 0's and 1's in the least significant bit(s), whether IPv4 or IPv6.
507
 * These are all that needs checking to identify a _guaranteed_ correct, minimal and optimal subnet array.
508
 *
509
 * As a result, string/binary pattern matching of the binary IP is very efficient. It uses just 2 pattern-matching rules
510
 * to chop off increasingly larger subnets at both ends that can't be part of larger subnets, until nothing's left.
511
 *
512
 * (a) If any range has EITHER low bit 1 (in startip) or 0 (in endip), that end-point is _always guaranteed_ to be optimally
513
 * represented by its own 'single IP' CIDR; the remaining range then shrinks by one IP up or down, causing the new end-point's
514
 * low bit to change from 1->0 (startip) or 0->1 (endip). Only one edge case needs checking: if a range contains exactly 2
515
 * adjacent IPs of this format, then the two IPs themselves are required to span it, and we're done.
516
 * 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
517
 * low bits can now be ignored.
518
 *
519
 * (b) If any range has BOTH startip and endip ending in some number of 0's and 1's respectively, these low bits can
520
 * *always* be ignored and "bit-shifted" for subnet spanning. So provided we remember the bits we've place-shifted, we can
521
 * _always_ right-shift and chop off those bits, leaving a smaller range that has EITHER startip ending in 1 or endip ending
522
 * in 0 (ie can now apply (a) again) or the entire range has vanished and we're done.
523
 * We then loop to redo (a) again on the remaining (place shifted) range until after a few loops, the remaining (place shifted)
524
 * range 'vanishes' by meeting the exit criteria of (a) or (b), and we're done.
525
 */
526
function ip_range_to_subnet_array($ip1, $ip2) {
527

    
528
	if (is_ipaddrv4($ip1) && is_ipaddrv4($ip2)) {
529
		$proto = 'ipv4';  // for clarity
530
		$bits = 32;
531
		$ip1bin = decbin(ip2long32($ip1));
532
		$ip2bin = decbin(ip2long32($ip2));
533
	} elseif (is_ipaddrv6($ip1) && is_ipaddrv6($ip2)) {
534
		$proto = 'ipv6';
535
		$bits = 128;
536
		$ip1bin = ip6_to_bin($ip1);
537
		$ip2bin = ip6_to_bin($ip2);
538
	} else {
539
		return array();
540
	}
541

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

    
546
	if ($ip1bin == $ip2bin) {
547
		return array($ip1 . '/' . $bits); // exit if ip1=ip2 (trivial case)
548
	}
549

    
550
	if ($ip1bin > $ip2bin) {
551
		list ($ip1bin, $ip2bin) = array($ip2bin, $ip1bin);  // swap if needed (ensures ip1 < ip2)
552
	}
553

    
554
	$rangesubnets = array();
555
	$netsize = 0;
556

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

    
561
		// 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)
562

    
563
		if (substr($ip1bin, -1, 1) == '1') {
564
			// the start ip must be in a separate one-IP cidr range
565
			$new_subnet_ip = substr($ip1bin, $netsize, $bits - $netsize) . str_repeat('0', $netsize);
566
			$rangesubnets[$new_subnet_ip] = $bits - $netsize;
567
			$n = strrpos($ip1bin, '0');  //can't be all 1's
568
			$ip1bin = ($n == 0 ? '' : substr($ip1bin, 0, $n)) . '1' . str_repeat('0', $bits - $n - 1);  // BINARY VERSION OF $ip1 += 1
569
		}
570

    
571
		// 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)
572

    
573
		if (substr($ip2bin, -1, 1) == '0') {
574
			// the end ip must be in a separate one-IP cidr range
575
			$new_subnet_ip = substr($ip2bin, $netsize, $bits - $netsize) . str_repeat('0', $netsize);
576
			$rangesubnets[$new_subnet_ip] = $bits - $netsize;
577
			$n = strrpos($ip2bin, '1');  //can't be all 0's
578
			$ip2bin = ($n == 0 ? '' : substr($ip2bin, 0, $n)) . '0' . str_repeat('1', $bits - $n - 1);  // BINARY VERSION OF $ip2 -= 1
579
			// already checked for the edge case where end = start+1 and start ends in 0x1, above, so it's safe
580
		}
581

    
582
		// this is the only edge case arising from increment/decrement.
583
		// 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)
584

    
585
		if ($ip2bin < $ip1bin) {
586
			continue;
587
		}
588

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

    
592
		$shift = $bits - max(strrpos($ip1bin, '0'), strrpos($ip2bin, '1'));  // num of low bits which are '0' in ip1 and '1' in ip2
593
		$ip1bin = str_repeat('0', $shift) . substr($ip1bin, 0, $bits - $shift);
594
		$ip2bin = str_repeat('0', $shift) . substr($ip2bin, 0, $bits - $shift);
595
		$netsize += $shift;
596
		if ($ip1bin == $ip2bin) {
597
			// we're done.
598
			$new_subnet_ip = substr($ip1bin, $netsize, $bits - $netsize) . str_repeat('0', $netsize);
599
			$rangesubnets[$new_subnet_ip] = $bits - $netsize;
600
			continue;
601
		}
602

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

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

    
608
	ksort($rangesubnets, SORT_STRING);
609
	$out = array();
610

    
611
	foreach ($rangesubnets as $ip => $netmask) {
612
		if ($proto == 'ipv4') {
613
			$i = str_split($ip, 8);
614
			$out[] = implode('.', array(bindec($i[0]), bindec($i[1]), bindec($i[2]), bindec($i[3]))) . '/' . $netmask;
615
		} else {
616
			$out[] = bin_to_compressed_ip6($ip) . '/' . $netmask;
617
		}
618
	}
619

    
620
	return $out;
621
}
622

    
623
/* returns true if $range is a valid pair of IPv4 or IPv6 addresses separated by a "-"
624
	false - if not a valid pair
625
	true (numeric 4 or 6) - if valid, gives type of addresses */
626
function is_iprange($range) {
627
	if (substr_count($range, '-') != 1) {
628
		return false;
629
	}
630
	list($ip1, $ip2) = explode ('-', $range);
631
	if (is_ipaddrv4($ip1) && is_ipaddrv4($ip2)) {
632
		return 4;
633
	}
634
	if (is_ipaddrv6($ip1) && is_ipaddrv6($ip2)) {
635
		return 6;
636
	}
637
	return false;
638
}
639

    
640
/* returns true if $ipaddr is a valid dotted IPv4 address or a IPv6
641
	false - not valid
642
	true (numeric 4 or 6) - if valid, gives type of address */
643
function is_ipaddr($ipaddr) {
644
	if (is_ipaddrv4($ipaddr)) {
645
		return 4;
646
	}
647
	if (is_ipaddrv6($ipaddr)) {
648
		return 6;
649
	}
650
	return false;
651
}
652

    
653
/* returns true if $ipaddr is a valid IPv6 address */
654
function is_ipaddrv6($ipaddr) {
655
	if (!is_string($ipaddr) || empty($ipaddr)) {
656
		return false;
657
	}
658
	/*
659
	 * While Net_IPv6::checkIPv6() considers IPv6/mask a valid IPv6,
660
	 * is_ipaddrv6() needs to be more strict to keep the compatibility
661
	 * with is_ipaddrv4().
662
	 */
663
	if (strstr($ipaddr, "/")) {
664
		return false;
665
	}
666
	if (strstr($ipaddr, "%") && is_linklocal($ipaddr)) {
667
		$tmpip = explode("%", $ipaddr);
668
		$ipaddr = $tmpip[0];
669
	}
670
	return Net_IPv6::checkIPv6($ipaddr);
671
}
672

    
673
/* returns true if $ipaddr is a valid dotted IPv4 address */
674
function is_ipaddrv4($ipaddr) {
675
	if (!is_string($ipaddr) || empty($ipaddr) || ip2long($ipaddr) === FALSE) {
676
		return false;
677
	}
678
	return true;
679
}
680

    
681
/* returns 4 or 6 respectively (== TRUE) if $ipaddr is a valid IPv4 or IPv6 linklocal address
682
   returns '' if not a valid linklocal address */
683
function is_linklocal($ipaddr) {
684
	if (is_ipaddrv4($ipaddr)) {
685
		// input is IPv4
686
		// test if it's 169.254.x.x per rfc3927 2.1
687
		$ip4 = explode(".", $ipaddr);
688
		if ($ip4[0] == '169' && $ip4[1] == '254') {
689
			return 4;
690
		}
691
	} elseif (Net_IPv6::getAddressType($ipaddr) == NET_IPV6_LOCAL_LINK) {
692
		return 6;
693
	}
694
	return '';
695
}
696

    
697
/* returns scope of a linklocal address */
698
function get_ll_scope($addr) {
699
	if (!is_linklocal($addr) || !strstr($addr, "%")) {
700
		return "";
701
	}
702
	list ($ll, $scope) = explode("%", $addr);
703
	return $scope;
704
}
705

    
706
/* returns true if $ipaddr is a valid literal IPv6 address */
707
function is_literalipaddrv6($ipaddr) {
708
	if (substr($ipaddr,0,1) == '[' && substr($ipaddr,-1,1) == ']') {
709
		// if it's data wrapped in "[ ... ]" then test if middle part is valid IPv6
710
		return is_ipaddrv6(substr($ipaddr,1,-1));
711
	}
712
	return false;
713
}
714

    
715
/* returns true if $iport is a valid IPv4:port or [Literal IPv6]:port
716
	false - not valid
717
	true (numeric 4 or 6) - if valid, gives type of address */
718
function is_ipaddrwithport($ipport) {
719
	$c = strrpos($ipport, ":");
720
	if ($c === false) {
721
		return false;  // can't split at final colon if no colon exists
722
	}
723

    
724
	if (!is_port(substr($ipport, $c + 1))) {
725
		return false;  // no valid port after last colon
726
	}
727

    
728
	$ip = substr($ipport, 0, $c);  // else is text before last colon a valid IP
729
	if (is_literalipaddrv6($ip)) {
730
		return 6;
731
	} elseif (is_ipaddrv4($ip)) {
732
		return 4;
733
	} else {
734
		return false;
735
	}
736
}
737

    
738
function is_hostnamewithport($hostport) {
739
	$parts = explode(":", $hostport);
740
	// no need to validate with is_string(); if it's not a string then explode won't return 2 parts anyway
741
	if (count($parts) == 2) {
742
		return is_hostname($parts[0]) && is_port($parts[1]);
743
	}
744
	return false;
745
}
746

    
747
/* returns true if $ipaddr is a valid dotted IPv4 address or an alias thereof */
748
function is_ipaddroralias($ipaddr) {
749
	global $config;
750

    
751
	if (is_alias($ipaddr)) {
752
		if (is_array($config['aliases']['alias'])) {
753
			foreach ($config['aliases']['alias'] as $alias) {
754
				if ($alias['name'] == $ipaddr && !preg_match("/port/i", $alias['type'])) {
755
					return true;
756
				}
757
			}
758
		}
759
		return false;
760
	} else {
761
		return is_ipaddr($ipaddr);
762
	}
763

    
764
}
765

    
766
/* returns true if $subnet is a valid IPv4 or IPv6 subnet in CIDR format
767
	false - if not a valid subnet
768
	true (numeric 4 or 6) - if valid, gives type of subnet */
769
function is_subnet($subnet) {
770
	if (is_string($subnet) && preg_match('/^(?:([0-9.]{7,15})|([0-9a-f:]{2,39}))\/(\d{1,3})$/i', $subnet, $parts)) {
771
		if (is_ipaddrv4($parts[1]) && $parts[3] <= 32) {
772
			return 4;
773
		}
774
		if (is_ipaddrv6($parts[2]) && $parts[3] <= 128) {
775
			return 6;
776
		}
777
	}
778
	return false;
779
}
780

    
781
function is_v4($ip_or_subnet) {
782
	return is_ipaddrv4($ip_or_subnet) || is_subnetv4($ip_or_subnet);
783
}
784

    
785
function is_v6($ip_or_subnet) {
786
	return is_ipaddrv6($ip_or_subnet) || is_subnetv6($ip_or_subnet);
787
}
788

    
789
/* same as is_subnet() but accepts IPv4 only */
790
function is_subnetv4($subnet) {
791
	return (is_subnet($subnet) == 4);
792
}
793

    
794
/* same as is_subnet() but accepts IPv6 only */
795
function is_subnetv6($subnet) {
796
	return (is_subnet($subnet) == 6);
797
}
798

    
799
/* returns true if $subnet is a valid subnet in CIDR format or an alias thereof */
800
function is_subnetoralias($subnet) {
801
	global $aliastable;
802

    
803
	if (isset($aliastable[$subnet]) && is_subnet($aliastable[$subnet])) {
804
		return true;
805
	} else {
806
		return is_subnet($subnet);
807
	}
808
}
809

    
810
/* Get number of addresses in an IPv4/IPv6 subnet (represented as a string)
811
   optional $exact=true forces error (0) to be returned if it can't be represented exactly
812
   Exact result not possible above PHP_MAX_INT which is about 2^31 addresses on x32 or 2^63 on x64
813
   Returns 0 for bad data or if cannot represent size as an INT when $exact is set. */
814
function subnet_size($subnet, $exact=false) {
815
	$parts = explode("/", $subnet);
816
	$iptype = is_ipaddr($parts[0]);
817
	if (count($parts) == 2 && $iptype) {
818
		return subnet_size_by_netmask($iptype, $parts[1], $exact);
819
	}
820
	return 0;
821
}
822

    
823
/* Get number of addresses in an IPv4/IPv6 subnet (represented numerically as IP type + bits)
824
   optional $exact=true forces error (0) to be returned if it can't be represented exactly
825
   Hard to think where we might need to count exactly a huge subnet but an overflow detection option is probably sensible
826
   Returns 0 for bad data or if cannot represent size as an INT when $exact is set. */
827
function subnet_size_by_netmask($iptype, $bits, $exact=false) {
828
	if (!is_numericint($bits)) {
829
		return 0;
830
	} elseif ($iptype == 4 && $bits <= 32) {
831
		$snsize = 32 - $bits;
832
	} elseif ($iptype == 6 && $bits <= 128) {
833
		$snsize = 128 - $bits;
834
	} else {
835
		return 0;
836
	}
837

    
838
	// 2**N returns an exact result as an INT if possible, and a float/double if not.
839
	// 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
840
	$result = 2 ** $snsize;
841

    
842
	if ($exact && !is_int($result)) {
843
		//exact required but can't represent result exactly as an INT
844
		return 0;
845
	} else {
846
		// result ok, will be an INT where possible (guaranteed up to 2^31 addresses on x32/x64) and a float for 'huge' subnets
847
		return $result;
848
	}
849
}
850

    
851
/* function used by pfblockerng */
852
function subnetv4_expand($subnet) {
853
	$result = array();
854
	list ($ip, $bits) = explode("/", $subnet);
855
	$net = ip2long($ip);
856
	$mask = (0xffffffff << (32 - $bits));
857
	$net &= $mask;
858
	$size = round(exp(log(2) * (32 - $bits)));
859
	for ($i = 0; $i < $size; $i += 1) {
860
		$result[] = long2ip($net | $i);
861
	}
862
	return $result;
863
}
864

    
865
/* find out whether two IPv4/IPv6 CIDR subnets overlap.
866
   Note: CIDR overlap implies one is identical or included so largest sn will be the same */
867
function check_subnets_overlap($subnet1, $bits1, $subnet2, $bits2) {
868
	if (is_ipaddrv4($subnet1)) {
869
		return check_subnetsv4_overlap($subnet1, $bits1, $subnet2, $bits2);
870
	} else {
871
		return check_subnetsv6_overlap($subnet1, $bits1, $subnet2, $bits2);
872
	}
873
}
874

    
875
/* find out whether two IPv4 CIDR subnets overlap.
876
   Note: CIDR overlap means sn1/sn2 are identical or one is included in other. So sn using largest $bits will be the same  */
877
function check_subnetsv4_overlap($subnet1, $bits1, $subnet2, $bits2) {
878
	$largest_sn = min($bits1, $bits2);
879
	$subnetv4_start1 = gen_subnetv4($subnet1, $largest_sn);
880
	$subnetv4_start2 = gen_subnetv4($subnet2, $largest_sn);
881

    
882
	if ($subnetv4_start1 == '' || $subnetv4_start2 == '') {
883
		// One or both args is not a valid IPv4 subnet
884
		//FIXME: needs to return "bad data" not true/false if bad. For now return false, best we can do until fixed
885
		return false;
886
	}
887
	return ($subnetv4_start1 == $subnetv4_start2);
888
}
889

    
890
/* find out whether two IPv6 CIDR subnets overlap.
891
   Note: CIDR overlap means sn1/sn2 are identical or one is included in other. So sn using largest $bits will be the same  */
892
function check_subnetsv6_overlap($subnet1, $bits1, $subnet2, $bits2) {
893
	$largest_sn = min($bits1, $bits2);
894
	$subnetv6_start1 = gen_subnetv6($subnet1, $largest_sn);
895
	$subnetv6_start2 = gen_subnetv6($subnet2, $largest_sn);
896

    
897
	if ($subnetv6_start1 == '' || $subnetv6_start2 == '') {
898
		// One or both args is not a valid IPv6 subnet
899
		//FIXME: needs to return "bad data" not true/false if bad. For now return false, best we can do until fixed
900
		return false;
901
	}
902
	return ($subnetv6_start1 == $subnetv6_start2);
903
}
904

    
905
/* return all PTR zones for a IPv6 network */
906
function get_v6_ptr_zones($subnet, $bits) {
907
	$result = array();
908

    
909
	if (!is_ipaddrv6($subnet)) {
910
		return $result;
911
	}
912

    
913
	if (!is_numericint($bits) || $bits > 128) {
914
		return $result;
915
	}
916

    
917
	/*
918
	 * Find a small nibble boundary subnet mask
919
	 * e.g. a /29 will create 8 /32 PTR zones
920
	 */
921
	$small_sn = $bits;
922
	while ($small_sn % 4 != 0) {
923
		$small_sn++;
924
	}
925

    
926
	/* Get network prefix */
927
	$small_subnet = Net_IPv6::getNetmask($subnet, $bits);
928

    
929
	/*
930
	 * While small network is part of bigger one, increase 4-bit in last
931
	 * digit to get next small network
932
	 */
933
	while (Net_IPv6::isInNetmask($small_subnet, $subnet, $bits)) {
934
		/* Get a pure hex value */
935
		$unpacked = unpack('H*hex', inet_pton($small_subnet));
936
		/* Create PTR record using $small_sn / 4 chars */
937
		$result[] = implode('.', array_reverse(str_split(substr(
938
		    $unpacked['hex'], 0, $small_sn / 4)))).'.ip6.arpa';
939

    
940
		/* Detect what part of IP should be increased */
941
		$change_part = (int) ($small_sn / 16);
942
		if ($small_sn % 16 == 0) {
943
			$change_part--;
944
		}
945

    
946
		/* Increase 1 to desired part */
947
		$parts = explode(":", Net_IPv6::uncompress($small_subnet));
948
		$parts[$change_part]++;
949
		$small_subnet = implode(":", $parts);
950
	}
951

    
952
	return $result;
953
}
954

    
955
/* return true if $addr is in $subnet, false if not */
956
function ip_in_subnet($addr, $subnet) {
957
	if (is_ipaddrv6($addr) && is_subnetv6($subnet)) {
958
		return (Net_IPv6::isInNetmask($addr, $subnet));
959
	} else if (is_ipaddrv4($addr) && is_subnetv4($subnet)) {
960
		list($ip, $mask) = explode('/', $subnet);
961
		$mask = (0xffffffff << (32 - $mask)) & 0xffffffff;
962
		return ((ip2long($addr) & $mask) == (ip2long($ip) & $mask));
963
	}
964
	return false;
965
}
966

    
967
/* returns true if $hostname is just a valid hostname (top part without any of the domain part) */
968
function is_unqualified_hostname($hostname) {
969
	if (!is_string($hostname)) {
970
		return false;
971
	}
972

    
973
	if (preg_match('/^(?:[a-z0-9_]|[a-z0-9_][a-z0-9_\-]*[a-z0-9_])$/i', $hostname)) {
974
		return true;
975
	} else {
976
		return false;
977
	}
978
}
979

    
980
/* returns true if $hostname is a valid hostname, with or without being a fully-qualified domain name. */
981
function is_hostname($hostname, $allow_wildcard=false) {
982
	if (!is_string($hostname)) {
983
		return false;
984
	}
985

    
986
	if (is_domain($hostname, $allow_wildcard)) {
987
		if ((substr_count($hostname, ".") == 1) && ($hostname[strlen($hostname)-1] == ".")) {
988
			/* Only a single dot at the end like "test." - hosts cannot be directly in the root domain. */
989
			return false;
990
		} else {
991
			return true;
992
		}
993
	} else {
994
		return false;
995
	}
996
}
997

    
998
/* returns true if $domain is a valid domain name */
999
function is_domain($domain, $allow_wildcard=false) {
1000
	if (!is_string($domain)) {
1001
		return false;
1002
	}
1003
	if ($allow_wildcard) {
1004
		$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';
1005
	} else {
1006
		$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';
1007
	}
1008

    
1009
	if (preg_match($domain_regex, $domain)) {
1010
		return true;
1011
	} else {
1012
		return false;
1013
	}
1014
}
1015

    
1016
/* returns true if $macaddr is a valid MAC address */
1017
function is_macaddr($macaddr, $partial=false) {
1018
	$values = explode(":", $macaddr);
1019

    
1020
	/* Verify if the MAC address has a proper amount of parts for either a partial or full match. */
1021
	if ($partial) {
1022
		if ((count($values) < 1) || (count($values) > 6)) {
1023
			return false;
1024
		}
1025
	} elseif (count($values) != 6) {
1026
		return false;
1027
	}
1028
	for ($i = 0; $i < count($values); $i++) {
1029
		if (ctype_xdigit($values[$i]) == false)
1030
			return false;
1031
		if (hexdec($values[$i]) < 0 || hexdec($values[$i]) > 255)
1032
			return false;
1033
	}
1034

    
1035
	return true;
1036
}
1037

    
1038
/*
1039
	If $return_message is true then
1040
		returns a text message about the reason that the name is invalid.
1041
		the text includes the type of "thing" that is being checked, passed in $object. (e.g. "alias", "gateway group", "schedule")
1042
	else
1043
		returns true if $name is a valid name for an alias
1044
		returns false if $name is not a valid name for an alias
1045

    
1046
	Aliases cannot be:
1047
		bad chars: anything except a-z 0-9 and underscore
1048
		bad names: empty string, pure numeric, pure underscore
1049
		reserved words: pre-defined service/protocol/port names which should not be ambiguous, and the words "port" and  "pass" */
1050

    
1051
function is_validaliasname($name, $return_message = false, $object = "alias") {
1052
	/* Array of reserved words */
1053
	$reserved = array("port", "pass");
1054

    
1055
	if (!is_string($name) || strlen($name) >= 32 || preg_match('/(^_*$|^\d*$|[^a-z0-9_])/i', $name)) {
1056
		if ($return_message) {
1057
			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, _');
1058
		} else {
1059
			return false;
1060
		}
1061
	}
1062
	if (in_array($name, $reserved, true)) {
1063
		if ($return_message) {
1064
			return sprintf(gettext('The %1$s name must not be either of the reserved words %2$s or %3$s.'), $object, "'port'", "'pass'");
1065
		} else {
1066
			return false;
1067
		}
1068
	}
1069
	if (getprotobyname($name)) {
1070
		if ($return_message) {
1071
			return sprintf(gettext('The %1$s name must not be an IP protocol name such as TCP, UDP, ICMP etc.'), $object);
1072
		} else {
1073
			return false;
1074
		}
1075
	}
1076
	if (getservbyname($name, "tcp") || getservbyname($name, "udp")) {
1077
		if ($return_message) {
1078
			return sprintf(gettext('The %1$s name must not be a well-known or registered TCP or UDP port name such as ssh, smtp, pop3, tftp, http, openvpn etc.'), $object);
1079
		} else {
1080
			return false;
1081
		}
1082
	}
1083
	if ($return_message) {
1084
		return sprintf(gettext("The %1$s name is valid."), $object);
1085
	} else {
1086
		return true;
1087
	}
1088
}
1089

    
1090
/* returns a text message indicating if the alias name is valid, or the reason it is not valid. */
1091
function invalidaliasnamemsg($name, $object = "alias") {
1092
	return is_validaliasname($name, true, $object);
1093
}
1094

    
1095
/*
1096
 * returns true if $range is a valid integer range between $min and $max
1097
 * range delimiter can be ':' or '-'
1098
 */
1099
function is_intrange($range, $min, $max) {
1100
	$values = preg_split("/[:-]/", $range);
1101

    
1102
	if (!is_array($values) || count($values) != 2) {
1103
		return false;
1104
	}
1105

    
1106
	if (!ctype_digit($values[0]) || !ctype_digit($values[1])) {
1107
		return false;
1108
	}
1109

    
1110
	$values[0] = intval($values[0]);
1111
	$values[1] = intval($values[1]);
1112

    
1113
	if ($values[0] >= $values[1]) {
1114
		return false;
1115
	}
1116

    
1117
	if ($values[0] < $min || $values[1] > $max) {
1118
		return false;
1119
	}
1120

    
1121
	return true;
1122
}
1123

    
1124
/* returns true if $port is a valid TCP/UDP port */
1125
function is_port($port) {
1126
	if (ctype_digit($port) && ((intval($port) >= 1) && (intval($port) <= 65535))) {
1127
		return true;
1128
	}
1129
	if (getservbyname($port, "tcp") || getservbyname($port, "udp")) {
1130
		return true;
1131
	}
1132
	return false;
1133
}
1134

    
1135
/* returns true if $portrange is a valid TCP/UDP portrange ("<port>:<port>") */
1136
function is_portrange($portrange) {
1137
	$ports = explode(":", $portrange);
1138

    
1139
	return (count($ports) == 2 && is_port($ports[0]) && is_port($ports[1]));
1140
}
1141

    
1142
/* returns true if $port is a valid TCP/UDP port number or range ("<port>:<port>") */
1143
function is_port_or_range($port) {
1144
	return (is_port($port) || is_portrange($port));
1145
}
1146

    
1147
/* returns true if $port is an alias that is a port type */
1148
function is_portalias($port) {
1149
	global $config;
1150

    
1151
	if (is_alias($port)) {
1152
		if (is_array($config['aliases']['alias'])) {
1153
			foreach ($config['aliases']['alias'] as $alias) {
1154
				if ($alias['name'] == $port && preg_match("/port/i", $alias['type'])) {
1155
					return true;
1156
				}
1157
			}
1158
		}
1159
	}
1160
	return false;
1161
}
1162

    
1163
/* returns true if $port is a valid port number or an alias thereof */
1164
function is_port_or_alias($port) {
1165
	return (is_port($port) || is_portalias($port));
1166
}
1167

    
1168
/* returns true if $port is a valid TCP/UDP port number or range ("<port>:<port>") or an alias thereof */
1169
function is_port_or_range_or_alias($port) {
1170
	return (is_port($port) || is_portrange($port) || is_portalias($port));
1171
}
1172

    
1173
/* create ranges of sequential port numbers (200:215) and remove duplicates */
1174
function group_ports($ports, $kflc = false) {
1175
	if (!is_array($ports) || empty($ports)) {
1176
		return;
1177
	}
1178

    
1179
	$uniq = array();
1180
	$comments = array();
1181
	foreach ($ports as $port) {
1182
		if (($kflc) && (strpos($port, '#') === 0)) {	// Keep Full Line Comments (lines beginning with #).
1183
			$comments[] = $port;
1184
		} else if (is_portrange($port)) {
1185
			list($begin, $end) = explode(":", $port);
1186
			if ($begin > $end) {
1187
				$aux = $begin;
1188
				$begin = $end;
1189
				$end = $aux;
1190
			}
1191
			for ($i = $begin; $i <= $end; $i++) {
1192
				if (!in_array($i, $uniq)) {
1193
					$uniq[] = $i;
1194
				}
1195
			}
1196
		} else if (is_port($port)) {
1197
			if (!in_array($port, $uniq)) {
1198
				$uniq[] = $port;
1199
			}
1200
		}
1201
	}
1202
	sort($uniq, SORT_NUMERIC);
1203

    
1204
	$result = array();
1205
	foreach ($uniq as $idx => $port) {
1206
		if ($idx == 0) {
1207
			$result[] = $port;
1208
			continue;
1209
		}
1210

    
1211
		$last = end($result);
1212
		if (is_portrange($last)) {
1213
			list($begin, $end) = explode(":", $last);
1214
		} else {
1215
			$begin = $end = $last;
1216
		}
1217

    
1218
		if ($port == ($end+1)) {
1219
			$end++;
1220
			$result[count($result)-1] = "{$begin}:{$end}";
1221
		} else {
1222
			$result[] = $port;
1223
		}
1224
	}
1225

    
1226
	return array_merge($comments, $result);
1227
}
1228

    
1229
/* returns true if $val is a valid shaper bandwidth value */
1230
function is_valid_shaperbw($val) {
1231
	return (preg_match("/^(\d+(?:\.\d+)?)([MKG]?b|%)$/", $val));
1232
}
1233

    
1234
/* returns true if $test is in the range between $start and $end */
1235
function is_inrange_v4($test, $start, $end) {
1236
	if (!is_ipaddrv4($test) || !is_ipaddrv4($start) || !is_ipaddrv4($end)) {
1237
		return false;
1238
	}
1239

    
1240
	if (ip2ulong($test) <= ip2ulong($end) &&
1241
	    ip2ulong($test) >= ip2ulong($start)) {
1242
		return true;
1243
	}
1244

    
1245
	return false;
1246
}
1247

    
1248
/* returns true if $test is in the range between $start and $end */
1249
function is_inrange_v6($test, $start, $end) {
1250
	if (!is_ipaddrv6($test) || !is_ipaddrv6($start) || !is_ipaddrv6($end)) {
1251
		return false;
1252
	}
1253

    
1254
	if (inet_pton($test) <= inet_pton($end) &&
1255
	    inet_pton($test) >= inet_pton($start)) {
1256
		return true;
1257
	}
1258

    
1259
	return false;
1260
}
1261

    
1262
/* returns true if $test is in the range between $start and $end */
1263
function is_inrange($test, $start, $end) {
1264
	return is_ipaddrv6($test) ? is_inrange_v6($test, $start, $end) : is_inrange_v4($test, $start, $end);
1265
}
1266

    
1267
function get_configured_vip_list($family = 'all', $type = VIP_ALL) {
1268
	global $config;
1269

    
1270
	$list = array();
1271
	if (!is_array($config['virtualip']) ||
1272
	    !is_array($config['virtualip']['vip']) ||
1273
	    empty($config['virtualip']['vip'])) {
1274
		return ($list);
1275
	}
1276

    
1277
	$viparr = &$config['virtualip']['vip'];
1278
	foreach ($viparr as $vip) {
1279

    
1280
		if ($type == VIP_CARP) {
1281
			if ($vip['mode'] != "carp")
1282
				continue;
1283
		} elseif ($type == VIP_IPALIAS) {
1284
			if ($vip['mode'] != "ipalias")
1285
				continue;
1286
		} else {
1287
			if ($vip['mode'] != "carp" && $vip['mode'] != "ipalias")
1288
				continue;
1289
		}
1290

    
1291
		if ($family == 'all' ||
1292
		    ($family == 'inet' && is_ipaddrv4($vip['subnet'])) ||
1293
		    ($family == 'inet6' && is_ipaddrv6($vip['subnet']))) {
1294
			$list["_vip{$vip['uniqid']}"] = $vip['subnet'];
1295
		}
1296
	}
1297
	return ($list);
1298
}
1299

    
1300
function get_configured_vip($vipinterface = '') {
1301

    
1302
	return (get_configured_vip_detail($vipinterface, 'all', 'vip'));
1303
}
1304

    
1305
function get_configured_vip_interface($vipinterface = '') {
1306

    
1307
	return (get_configured_vip_detail($vipinterface, 'all', 'iface'));
1308
}
1309

    
1310
function get_configured_vip_ipv4($vipinterface = '') {
1311

    
1312
	return (get_configured_vip_detail($vipinterface, 'inet', 'ip'));
1313
}
1314

    
1315
function get_configured_vip_ipv6($vipinterface = '') {
1316

    
1317
	return (get_configured_vip_detail($vipinterface, 'inet6', 'ip'));
1318
}
1319

    
1320
function get_configured_vip_subnetv4($vipinterface = '') {
1321

    
1322
	return (get_configured_vip_detail($vipinterface, 'inet', 'subnet'));
1323
}
1324

    
1325
function get_configured_vip_subnetv6($vipinterface = '') {
1326

    
1327
	return (get_configured_vip_detail($vipinterface, 'inet6', 'subnet'));
1328
}
1329

    
1330
function get_configured_vip_detail($vipinterface = '', $family = 'inet', $what = 'ip') {
1331
	global $config;
1332

    
1333
	if (empty($vipinterface) ||
1334
	    !is_array($config['virtualip']) ||
1335
	    !is_array($config['virtualip']['vip']) ||
1336
	    empty($config['virtualip']['vip'])) {
1337
		return (NULL);
1338
	}
1339

    
1340
	$viparr = &$config['virtualip']['vip'];
1341
	foreach ($viparr as $vip) {
1342
		if ($vip['mode'] != "carp" && $vip['mode'] != "ipalias") {
1343
			continue;
1344
		}
1345

    
1346
		if ($vipinterface != "_vip{$vip['uniqid']}") {
1347
			continue;
1348
		}
1349

    
1350
		switch ($what) {
1351
			case 'subnet':
1352
				if ($family == 'inet' && is_ipaddrv4($vip['subnet']))
1353
					return ($vip['subnet_bits']);
1354
				else if ($family == 'inet6' && is_ipaddrv6($vip['subnet']))
1355
					return ($vip['subnet_bits']);
1356
				break;
1357
			case 'iface':
1358
				return ($vip['interface']);
1359
				break;
1360
			case 'vip':
1361
				return ($vip);
1362
				break;
1363
			case 'ip':
1364
			default:
1365
				if ($family == 'inet' && is_ipaddrv4($vip['subnet'])) {
1366
					return ($vip['subnet']);
1367
				} else if ($family == 'inet6' && is_ipaddrv6($vip['subnet'])) {
1368
					return ($vip['subnet']);
1369
				}
1370
				break;
1371
		}
1372
		break;
1373
	}
1374

    
1375
	return (NULL);
1376
}
1377

    
1378
/* comparison function for sorting by the order in which interfaces are normally created */
1379
function compare_interface_friendly_names($a, $b) {
1380
	if ($a == $b) {
1381
		return 0;
1382
	} else if ($a == 'wan') {
1383
		return -1;
1384
	} else if ($b == 'wan') {
1385
		return 1;
1386
	} else if ($a == 'lan') {
1387
		return -1;
1388
	} else if ($b == 'lan') {
1389
		return 1;
1390
	}
1391

    
1392
	return strnatcmp($a, $b);
1393
}
1394

    
1395
/* return the configured interfaces list. */
1396
function get_configured_interface_list($withdisabled = false) {
1397
	global $config;
1398

    
1399
	$iflist = array();
1400

    
1401
	/* if list */
1402
	foreach ($config['interfaces'] as $if => $ifdetail) {
1403
		if (isset($ifdetail['enable']) || $withdisabled == true) {
1404
			$iflist[$if] = $if;
1405
		}
1406
	}
1407

    
1408
	return $iflist;
1409
}
1410

    
1411
/* return the configured interfaces list. */
1412
function get_configured_interface_list_by_realif($withdisabled = false) {
1413
	global $config;
1414

    
1415
	$iflist = array();
1416

    
1417
	/* if list */
1418
	foreach ($config['interfaces'] as $if => $ifdetail) {
1419
		if (isset($ifdetail['enable']) || $withdisabled == true) {
1420
			$tmpif = get_real_interface($if);
1421
			if (!empty($tmpif)) {
1422
				$iflist[$tmpif] = $if;
1423
			}
1424
		}
1425
	}
1426

    
1427
	return $iflist;
1428
}
1429

    
1430
/* return the configured interfaces list with their description. */
1431
function get_configured_interface_with_descr($withdisabled = false) {
1432
	global $config, $user_settings;
1433

    
1434
	$iflist = array();
1435

    
1436
	/* if list */
1437
	foreach ($config['interfaces'] as $if => $ifdetail) {
1438
		if (isset($ifdetail['enable']) || $withdisabled == true) {
1439
			if (empty($ifdetail['descr'])) {
1440
				$iflist[$if] = strtoupper($if);
1441
			} else {
1442
				$iflist[$if] = strtoupper($ifdetail['descr']);
1443
			}
1444
		}
1445
	}
1446

    
1447
	if ($user_settings['webgui']['interfacessort']) {
1448
		asort($iflist);
1449
	}
1450

    
1451
	return $iflist;
1452
}
1453

    
1454
/*
1455
 *   get_configured_ip_addresses() - Return a list of all configured
1456
 *   IPv4 addresses.
1457
 *
1458
 */
1459
function get_configured_ip_addresses() {
1460
	global $config;
1461

    
1462
	if (!function_exists('get_interface_ip')) {
1463
		require_once("interfaces.inc");
1464
	}
1465
	$ip_array = array();
1466
	$interfaces = get_configured_interface_list();
1467
	if (is_array($interfaces)) {
1468
		foreach ($interfaces as $int) {
1469
			$ipaddr = get_interface_ip($int);
1470
			$ip_array[$int] = $ipaddr;
1471
		}
1472
	}
1473
	$interfaces = get_configured_vip_list('inet');
1474
	if (is_array($interfaces)) {
1475
		foreach ($interfaces as $int => $ipaddr) {
1476
			$ip_array[$int] = $ipaddr;
1477
		}
1478
	}
1479

    
1480
	/* pppoe server */
1481
	if (is_array($config['pppoes']) && is_array($config['pppoes']['pppoe'])) {
1482
		foreach ($config['pppoes']['pppoe'] as $pppoe) {
1483
			if ($pppoe['mode'] == "server") {
1484
				if (is_ipaddr($pppoe['localip'])) {
1485
					$int = "pppoes". $pppoe['pppoeid'];
1486
					$ip_array[$int] = $pppoe['localip'];
1487
				}
1488
			}
1489
		}
1490
	}
1491

    
1492
	return $ip_array;
1493
}
1494

    
1495
/*
1496
 *   get_configured_ipv6_addresses() - Return a list of all configured
1497
 *   IPv6 addresses.
1498
 *
1499
 */
1500
function get_configured_ipv6_addresses($linklocal_fallback = false) {
1501
	require_once("interfaces.inc");
1502
	$ipv6_array = array();
1503
	$interfaces = get_configured_interface_list();
1504
	if (is_array($interfaces)) {
1505
		foreach ($interfaces as $int) {
1506
			$ipaddrv6 = text_to_compressed_ip6(get_interface_ipv6($int, false, $linklocal_fallback));
1507
			$ipv6_array[$int] = $ipaddrv6;
1508
		}
1509
	}
1510
	$interfaces = get_configured_vip_list('inet6');
1511
	if (is_array($interfaces)) {
1512
		foreach ($interfaces as $int => $ipaddrv6) {
1513
			$ipv6_array[$int] = text_to_compressed_ip6($ipaddrv6);
1514
		}
1515
	}
1516
	return $ipv6_array;
1517
}
1518

    
1519
/*
1520
 *   get_interface_list() - Return a list of all physical interfaces
1521
 *   along with MAC and status.
1522
 *
1523
 *   $mode = "active" - use ifconfig -lu
1524
 *           "media"  - use ifconfig to check physical connection
1525
 *			status (much slower)
1526
 */
1527
function get_interface_list($mode = "active", $keyby = "physical", $vfaces = "") {
1528
	global $config;
1529
	$upints = array();
1530
	/* get a list of virtual interface types */
1531
	if (!$vfaces) {
1532
		$vfaces = array(
1533
				'bridge',
1534
				'ppp',
1535
				'pppoe',
1536
				'pptp',
1537
				'l2tp',
1538
				'sl',
1539
				'gif',
1540
				'gre',
1541
				'faith',
1542
				'lo',
1543
				'ng',
1544
				'_vlan',
1545
				'_wlan',
1546
				'pflog',
1547
				'plip',
1548
				'pfsync',
1549
				'enc',
1550
				'tun',
1551
				'lagg',
1552
				'vip',
1553
				'ipfw'
1554
		);
1555
	}
1556
	switch ($mode) {
1557
		case "active":
1558
			$upints = pfSense_interface_listget(IFF_UP);
1559
			break;
1560
		case "media":
1561
			$intlist = pfSense_interface_listget();
1562
			$ifconfig = "";
1563
			exec("/sbin/ifconfig -a", $ifconfig);
1564
			$regexp = '/(' . implode('|', $intlist) . '):\s/';
1565
			$ifstatus = preg_grep('/status:/', $ifconfig);
1566
			foreach ($ifstatus as $status) {
1567
				$int = array_shift($intlist);
1568
				if (stristr($status, "active")) {
1569
					$upints[] = $int;
1570
				}
1571
			}
1572
			break;
1573
		default:
1574
			$upints = pfSense_interface_listget();
1575
			break;
1576
	}
1577
	/* build interface list with netstat */
1578
	$linkinfo = "";
1579
	exec("/usr/bin/netstat -inW -f link | awk '{ print $1, $4 }'", $linkinfo);
1580
	array_shift($linkinfo);
1581
	/* build ip address list with netstat */
1582
	$ipinfo = "";
1583
	exec("/usr/bin/netstat -inW -f inet | awk '{ print $1, $4 }'", $ipinfo);
1584
	array_shift($ipinfo);
1585
	foreach ($linkinfo as $link) {
1586
		$friendly = "";
1587
		$alink = explode(" ", $link);
1588
		$ifname = rtrim(trim($alink[0]), '*');
1589
		/* trim out all numbers before checking for vfaces */
1590
		if (!in_array(array_shift(preg_split('/\d/', $ifname)), $vfaces) &&
1591
		    interface_is_vlan($ifname) == NULL &&
1592
		    interface_is_qinq($ifname) == NULL &&
1593
		    !stristr($ifname, "_wlan")) {
1594
			$toput = array(
1595
					"mac" => trim($alink[1]),
1596
					"up" => in_array($ifname, $upints)
1597
				);
1598
			foreach ($ipinfo as $ip) {
1599
				$aip = explode(" ", $ip);
1600
				if ($aip[0] == $ifname) {
1601
					$toput['ipaddr'] = $aip[1];
1602
				}
1603
			}
1604
			if (is_array($config['interfaces'])) {
1605
				foreach ($config['interfaces'] as $name => $int) {
1606
					if ($int['if'] == $ifname) {
1607
						$friendly = $name;
1608
					}
1609
				}
1610
			}
1611
			switch ($keyby) {
1612
			case "physical":
1613
				if ($friendly != "") {
1614
					$toput['friendly'] = $friendly;
1615
				}
1616
				$dmesg_arr = array();
1617
				exec("/sbin/dmesg |grep $ifname | head -n1", $dmesg_arr);
1618
				preg_match_all("/<(.*?)>/i", $dmesg_arr[0], $dmesg);
1619
				$toput['dmesg'] = $dmesg[1][0];
1620
				$iflist[$ifname] = $toput;
1621
				break;
1622
			case "ppp":
1623

    
1624
			case "friendly":
1625
				if ($friendly != "") {
1626
					$toput['if'] = $ifname;
1627
					$iflist[$friendly] = $toput;
1628
				}
1629
				break;
1630
			}
1631
		}
1632
	}
1633
	return $iflist;
1634
}
1635

    
1636
function get_lagg_interface_list() {
1637
	global $config;
1638

    
1639
	$plist = array();
1640
	if (isset($config['laggs']['lagg']) && is_array($config['laggs']['lagg'])) {
1641
		foreach ($config['laggs']['lagg'] as $lagg) {
1642
			$lagg['mac'] = get_interface_mac($lagg['laggif']);
1643
			$lagg['islagg'] = true;
1644
			$plist[$lagg['laggif']] = $lagg;
1645
		}
1646
	}
1647

    
1648
	return ($plist);
1649
}
1650

    
1651
/****f* util/log_error
1652
* NAME
1653
*   log_error  - Sends a string to syslog.
1654
* INPUTS
1655
*   $error     - string containing the syslog message.
1656
* RESULT
1657
*   null
1658
******/
1659
function log_error($error) {
1660
	global $g;
1661
	$page = $_SERVER['SCRIPT_NAME'];
1662
	if (empty($page)) {
1663
		$files = get_included_files();
1664
		$page = basename($files[0]);
1665
	}
1666
	syslog(LOG_ERR, "$page: $error");
1667
	if ($g['debug']) {
1668
		syslog(LOG_WARNING, var_dump(debug_backtrace()));
1669
	}
1670
	return;
1671
}
1672

    
1673
/****f* util/log_auth
1674
* NAME
1675
*   log_auth   - Sends a string to syslog as LOG_AUTH facility
1676
* INPUTS
1677
*   $error     - string containing the syslog message.
1678
* RESULT
1679
*   null
1680
******/
1681
function log_auth($error) {
1682
	global $g;
1683
	$page = $_SERVER['SCRIPT_NAME'];
1684
	syslog(LOG_AUTH, "$page: $error");
1685
	if ($g['debug']) {
1686
		syslog(LOG_WARNING, var_dump(debug_backtrace()));
1687
	}
1688
	return;
1689
}
1690

    
1691
/****f* util/exec_command
1692
 * NAME
1693
 *   exec_command - Execute a command and return a string of the result.
1694
 * INPUTS
1695
 *   $command   - String of the command to be executed.
1696
 * RESULT
1697
 *   String containing the command's result.
1698
 * NOTES
1699
 *   This function returns the command's stdout and stderr.
1700
 ******/
1701
function exec_command($command) {
1702
	$output = array();
1703
	exec($command . ' 2>&1', $output);
1704
	return(implode("\n", $output));
1705
}
1706

    
1707
/* wrapper for exec()
1708
   Executes in background or foreground.
1709
   For background execution, returns PID of background process to allow calling code control */
1710
function mwexec($command, $nologentry = false, $clearsigmask = false, $background = false) {
1711
	global $g;
1712
	$retval = 0;
1713

    
1714
	if ($g['debug']) {
1715
		if (!$_SERVER['REMOTE_ADDR']) {
1716
			echo "mwexec(): $command" . ($background ? " [BG]":"") . "\n";
1717
		}
1718
	}
1719
	if ($clearsigmask) {
1720
		$oldset = array();
1721
		pcntl_sigprocmask(SIG_SETMASK, array(), $oldset);
1722
	}
1723

    
1724
	if ($background) {
1725
		// start background process and return PID
1726
		$retval = exec("/usr/bin/nohup $command > /dev/null 2>&1 & echo $!");
1727
	} else {
1728
		// run in foreground, and (optionally) log if nonzero return
1729
		$outputarray = array();
1730
		exec("$command 2>&1", $outputarray, $retval);
1731
		if (($retval <> 0) && (!$nologentry || isset($config['system']['developerspew']))) {
1732
			log_error(sprintf(gettext("The command '%1\$s' returned exit code '%2\$d', the output was '%3\$s' "), $command, $retval, implode(" ", $outputarray)));
1733
		}
1734
	}
1735

    
1736
	if ($clearsigmask) {
1737
		pcntl_sigprocmask(SIG_SETMASK, $oldset);
1738
	}
1739

    
1740
	return $retval;
1741
}
1742

    
1743
/* wrapper for exec() in background */
1744
function mwexec_bg($command, $clearsigmask = false) {
1745
	return mwexec($command, false, $clearsigmask, true);
1746
}
1747

    
1748
/*	unlink a file, or pattern-match of a file, if it exists
1749
	if the file/path contains glob() compatible wildcards, all matching files will be unlinked
1750
	any warning/errors are suppressed (e.g. no matching files to delete)
1751
	If there are matching file(s) and they were all unlinked OK, then return true.
1752
	Otherwise return false (the requested file(s) did not exist, or could not be deleted)
1753
	This allows the caller to know if they were the one to successfully delete the file(s).
1754
*/
1755
function unlink_if_exists($fn) {
1756
	$to_do = glob($fn);
1757
	if (is_array($to_do) && count($to_do) > 0) {
1758
		// Returns an array of true/false indicating if each unlink worked
1759
		$results = @array_map("unlink", $to_do);
1760
		// If there is no false in the array, then all went well
1761
		$result = !in_array(false, $results, true);
1762
	} else {
1763
		$result = @unlink($fn);
1764
	}
1765
	return $result;
1766
}
1767
/* make a global alias table (for faster lookups) */
1768
function alias_make_table($config) {
1769
	global $aliastable;
1770

    
1771
	$aliastable = array();
1772

    
1773
	if (is_array($config['aliases']['alias'])) {
1774
		foreach ($config['aliases']['alias'] as $alias) {
1775
			if ($alias['name']) {
1776
				$aliastable[$alias['name']] = $alias['address'];
1777
			}
1778
		}
1779
	}
1780
}
1781

    
1782
/* check if an alias exists */
1783
function is_alias($name) {
1784
	global $aliastable;
1785

    
1786
	return isset($aliastable[$name]);
1787
}
1788

    
1789
function alias_get_type($name) {
1790
	global $config;
1791

    
1792
	if (is_array($config['aliases']['alias'])) {
1793
		foreach ($config['aliases']['alias'] as $alias) {
1794
			if ($name == $alias['name']) {
1795
				return $alias['type'];
1796
			}
1797
		}
1798
	}
1799

    
1800
	return "";
1801
}
1802

    
1803
/* expand a host or network alias, if necessary */
1804
function alias_expand($name) {
1805
	global $config, $aliastable;
1806
	$urltable_prefix = "/var/db/aliastables/";
1807
	$urltable_filename = $urltable_prefix . $name . ".txt";
1808

    
1809
	if (isset($aliastable[$name])) {
1810
		// alias names cannot be strictly numeric. redmine #4289
1811
		if (is_numericint($name)) {
1812
			return null;
1813
		}
1814
		// make sure if it's a ports alias, it actually exists. redmine #5845
1815
		foreach ($config['aliases']['alias'] as $alias) {
1816
			if ($alias['name'] == $name) {
1817
				if ($alias['type'] == "urltable_ports") {
1818
					if (is_URL($alias['url']) && file_exists($urltable_filename) && filesize($urltable_filename)) {
1819
						return "\${$name}";
1820
					} else {
1821
						return null;
1822
					}
1823
				}
1824
			}
1825
		}
1826
		return "\${$name}";
1827
	} else if (is_ipaddr($name) || is_subnet($name) || is_port_or_range($name)) {
1828
		return "{$name}";
1829
	} else {
1830
		return null;
1831
	}
1832
}
1833

    
1834
function alias_expand_urltable($name) {
1835
	global $config;
1836
	$urltable_prefix = "/var/db/aliastables/";
1837
	$urltable_filename = $urltable_prefix . $name . ".txt";
1838

    
1839
	if (is_array($config['aliases']['alias'])) {
1840
		foreach ($config['aliases']['alias'] as $alias) {
1841
			if (preg_match("/urltable/i", $alias['type']) && ($alias['name'] == $name)) {
1842
				if (is_URL($alias["url"]) && file_exists($urltable_filename)) {
1843
					if (!filesize($urltable_filename)) {
1844
						// file exists, but is empty, try to sync
1845
						send_event("service sync alias {$name}");
1846
					}
1847
					return $urltable_filename;
1848
				} else {
1849
					send_event("service sync alias {$name}");
1850
					break;
1851
				}
1852
			}
1853
		}
1854
	}
1855
	return null;
1856
}
1857

    
1858
/* obtain MAC address given an IP address by looking at the ARP/NDP table */
1859
function arp_get_mac_by_ip($ip, $do_ping = true) {
1860
	unset($macaddr);
1861
	$retval = 1;
1862
	switch (is_ipaddr($ip)) {
1863
		case 4:
1864
			if ($do_ping === true) {
1865
				mwexec("/sbin/ping -c 1 -t 1 " . escapeshellarg($ip), true);
1866
			}
1867
			$macaddr = exec("/usr/sbin/arp -n " . escapeshellarg($ip) . " | /usr/bin/awk '{print $4}'", $output, $retval);
1868
			break;
1869
		case 6:
1870
			if ($do_ping === true) {
1871
				mwexec("/sbin/ping6 -c 1 -X 1 " . escapeshellarg($ip), true);
1872
			}
1873
			$macaddr = exec("/usr/sbin/ndp -n " . escapeshellarg($ip) . " | /usr/bin/awk '{print $2}'", $output, $retval);
1874
			break;
1875
	}
1876
	if ($retval == 0 && is_macaddr($macaddr)) {
1877
		return $macaddr;
1878
	} else {
1879
		return false;
1880
	}
1881
}
1882

    
1883
/* return a fieldname that is safe for xml usage */
1884
function xml_safe_fieldname($fieldname) {
1885
	$replace = array(
1886
	    '/', '-', ' ', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')',
1887
	    '_', '+', '=', '{', '}', '[', ']', '|', '/', '<', '>', '?',
1888
	    ':', ',', '.', '\'', '\\'
1889
	);
1890
	return strtolower(str_replace($replace, "", $fieldname));
1891
}
1892

    
1893
function mac_format($clientmac) {
1894
	global $config, $cpzone;
1895

    
1896
	$mac = explode(":", $clientmac);
1897
	$mac_format = $cpzone ? $config['captiveportal'][$cpzone]['radmac_format'] : false;
1898

    
1899
	switch ($mac_format) {
1900
		case 'singledash':
1901
			return "$mac[0]$mac[1]$mac[2]-$mac[3]$mac[4]$mac[5]";
1902

    
1903
		case 'ietf':
1904
			return "$mac[0]-$mac[1]-$mac[2]-$mac[3]-$mac[4]-$mac[5]";
1905

    
1906
		case 'cisco':
1907
			return "$mac[0]$mac[1].$mac[2]$mac[3].$mac[4]$mac[5]";
1908

    
1909
		case 'unformatted':
1910
			return "$mac[0]$mac[1]$mac[2]$mac[3]$mac[4]$mac[5]";
1911

    
1912
		default:
1913
			return $clientmac;
1914
	}
1915
}
1916

    
1917
function resolve_retry($hostname, $retries = 5) {
1918

    
1919
	if (is_ipaddr($hostname)) {
1920
		return $hostname;
1921
	}
1922

    
1923
	for ($i = 0; $i < $retries; $i++) {
1924
		// FIXME: gethostbyname does not work for AAAA hostnames, boo, hiss
1925
		$ip = gethostbyname($hostname);
1926

    
1927
		if ($ip && $ip != $hostname) {
1928
			/* success */
1929
			return $ip;
1930
		}
1931

    
1932
		sleep(1);
1933
	}
1934

    
1935
	return false;
1936
}
1937

    
1938
function format_bytes($bytes) {
1939
	if ($bytes >= 1099511627776) {
1940
		return sprintf("%.2f TiB", $bytes/1099511627776);
1941
	} else if ($bytes >= 1073741824) {
1942
		return sprintf("%.2f GiB", $bytes/1073741824);
1943
	} else if ($bytes >= 1048576) {
1944
		return sprintf("%.2f MiB", $bytes/1048576);
1945
	} else if ($bytes >= 1024) {
1946
		return sprintf("%.0f KiB", $bytes/1024);
1947
	} else {
1948
		return sprintf("%d B", $bytes);
1949
	}
1950
}
1951

    
1952
function format_number($num, $precision = 3) {
1953
	$units = array('', 'K', 'M', 'G', 'T');
1954

    
1955
	$i = 0;
1956
	while ($num > 1000 && $i < count($units)) {
1957
		$num /= 1000;
1958
		$i++;
1959
	}
1960
	$num = round($num, $precision);
1961

    
1962
	return ("$num {$units[$i]}");
1963
}
1964

    
1965
function update_filter_reload_status($text, $new=false) {
1966
	global $g;
1967

    
1968
	if ($new) {
1969
		file_put_contents("{$g['varrun_path']}/filter_reload_status", $text  . PHP_EOL);
1970
	} else {
1971
		file_put_contents("{$g['varrun_path']}/filter_reload_status", $text  . PHP_EOL, FILE_APPEND);
1972
	}
1973
}
1974

    
1975
/****** util/return_dir_as_array
1976
 * NAME
1977
 *   return_dir_as_array - Return a directory's contents as an array.
1978
 * INPUTS
1979
 *   $dir          - string containing the path to the desired directory.
1980
 *   $filter_regex - string containing a regular expression to filter file names. Default empty.
1981
 * RESULT
1982
 *   $dir_array - array containing the directory's contents. This array will be empty if the path specified is invalid.
1983
 ******/
1984
function return_dir_as_array($dir, $filter_regex = '') {
1985
	$dir_array = array();
1986
	if (is_dir($dir)) {
1987
		if ($dh = opendir($dir)) {
1988
			while (($file = readdir($dh)) !== false) {
1989
				if (($file == ".") || ($file == "..")) {
1990
					continue;
1991
				}
1992

    
1993
				if (empty($filter_regex) || preg_match($filter_regex, $file)) {
1994
					array_push($dir_array, $file);
1995
				}
1996
			}
1997
			closedir($dh);
1998
		}
1999
	}
2000
	return $dir_array;
2001
}
2002

    
2003
function run_plugins($directory) {
2004
	global $config, $g;
2005

    
2006
	/* process packager manager custom rules */
2007
	$files = return_dir_as_array($directory);
2008
	if (is_array($files)) {
2009
		foreach ($files as $file) {
2010
			if (stristr($file, ".sh") == true) {
2011
				mwexec($directory . $file . " start");
2012
			} else if (!is_dir($directory . "/" . $file) && stristr($file, ".inc")) {
2013
				require_once($directory . "/" . $file);
2014
			}
2015
		}
2016
	}
2017
}
2018

    
2019
/*
2020
 *    safe_mkdir($path, $mode = 0755)
2021
 *    create directory if it doesn't already exist and isn't a file!
2022
 */
2023
function safe_mkdir($path, $mode = 0755) {
2024
	global $g;
2025

    
2026
	if (!is_file($path) && !is_dir($path)) {
2027
		return @mkdir($path, $mode, true);
2028
	} else {
2029
		return false;
2030
	}
2031
}
2032

    
2033
/*
2034
 * get_sysctl($names)
2035
 * Get values of sysctl OID's listed in $names (accepts an array or a single
2036
 * name) and return an array of key/value pairs set for those that exist
2037
 */
2038
function get_sysctl($names) {
2039
	if (empty($names)) {
2040
		return array();
2041
	}
2042

    
2043
	if (is_array($names)) {
2044
		$name_list = array();
2045
		foreach ($names as $name) {
2046
			$name_list[] = escapeshellarg($name);
2047
		}
2048
	} else {
2049
		$name_list = array(escapeshellarg($names));
2050
	}
2051

    
2052
	exec("/sbin/sysctl -iq " . implode(" ", $name_list), $output);
2053
	$values = array();
2054
	foreach ($output as $line) {
2055
		$line = explode(": ", $line, 2);
2056
		if (count($line) == 2) {
2057
			$values[$line[0]] = $line[1];
2058
		}
2059
	}
2060

    
2061
	return $values;
2062
}
2063

    
2064
/*
2065
 * get_single_sysctl($name)
2066
 * Wrapper for get_sysctl() to simplify read of a single sysctl value
2067
 * return the value for sysctl $name or empty string if it doesn't exist
2068
 */
2069
function get_single_sysctl($name) {
2070
	if (empty($name)) {
2071
		return "";
2072
	}
2073

    
2074
	$value = get_sysctl($name);
2075
	if (empty($value) || !isset($value[$name])) {
2076
		return "";
2077
	}
2078

    
2079
	return $value[$name];
2080
}
2081

    
2082
/*
2083
 * set_sysctl($value_list)
2084
 * Set sysctl OID's listed as key/value pairs and return
2085
 * an array with keys set for those that succeeded
2086
 */
2087
function set_sysctl($values) {
2088
	if (empty($values)) {
2089
		return array();
2090
	}
2091

    
2092
	$value_list = array();
2093
	foreach ($values as $key => $value) {
2094
		$value_list[] = escapeshellarg($key) . "=" . escapeshellarg($value);
2095
	}
2096

    
2097
	exec("/sbin/sysctl -iq " . implode(" ", $value_list), $output, $success);
2098

    
2099
	/* Retry individually if failed (one or more read-only) */
2100
	if ($success <> 0 && count($value_list) > 1) {
2101
		foreach ($value_list as $value) {
2102
			exec("/sbin/sysctl -iq " . $value, $output);
2103
		}
2104
	}
2105

    
2106
	$ret = array();
2107
	foreach ($output as $line) {
2108
		$line = explode(": ", $line, 2);
2109
		if (count($line) == 2) {
2110
			$ret[$line[0]] = true;
2111
		}
2112
	}
2113

    
2114
	return $ret;
2115
}
2116

    
2117
/*
2118
 * set_single_sysctl($name, $value)
2119
 * Wrapper to set_sysctl() to make it simple to set only one sysctl
2120
 * returns boolean meaning if it succeeded
2121
 */
2122
function set_single_sysctl($name, $value) {
2123
	if (empty($name)) {
2124
		return false;
2125
	}
2126

    
2127
	$result = set_sysctl(array($name => $value));
2128

    
2129
	if (!isset($result[$name]) || $result[$name] != $value) {
2130
		return false;
2131
	}
2132

    
2133
	return true;
2134
}
2135

    
2136
/*
2137
 *     get_memory()
2138
 *     returns an array listing the amount of
2139
 *     memory installed in the hardware
2140
 *     [0] net memory available for the OS (FreeBSD) after some is taken by BIOS, video or whatever - e.g. 235 MBytes
2141
 *     [1] real (actual) memory of the system, should be the size of the RAM card/s - e.g. 256 MBytes
2142
 */
2143
function get_memory() {
2144
	$physmem = get_single_sysctl("hw.physmem");
2145
	$realmem = get_single_sysctl("hw.realmem");
2146
	/* convert from bytes to megabytes */
2147
	return array(($physmem/1048576), ($realmem/1048576));
2148
}
2149

    
2150
function mute_kernel_msgs() {
2151
	global $g, $config;
2152

    
2153
	if ($config['system']['enableserial']) {
2154
		return;
2155
	}
2156
	exec("/sbin/conscontrol mute on");
2157
}
2158

    
2159
function unmute_kernel_msgs() {
2160
	global $g;
2161

    
2162
	exec("/sbin/conscontrol mute off");
2163
}
2164

    
2165
function start_devd() {
2166
	global $g;
2167

    
2168
	/* Generate hints for the kernel loader. */
2169
	$module_paths = explode(";", get_single_sysctl("kern.module_path"));
2170
	foreach ($module_paths as $id => $path) {
2171
		if (!is_dir($path) || file_exists("{$path}/linker.hints")) {
2172
			continue;
2173
		}
2174
		if (($files = scandir($path)) == false) {
2175
			continue;
2176
		}
2177
		$found = false;
2178
		foreach ($files as $id => $file) {
2179
			if (strlen($file) > 3 &&
2180
			    strcasecmp(substr($file, -3), ".ko") == 0) {
2181
				$found = true;
2182
				break;
2183
			}
2184
		}
2185
		if ($found == false) {
2186
			continue;
2187
		}
2188
		$_gb = exec("/usr/sbin/kldxref $path");
2189
		unset($_gb);
2190
	}
2191

    
2192
	/* Use the undocumented -q options of devd to quiet its log spamming */
2193
	$_gb = exec("/sbin/devd -q -f /etc/{$g['product_name']}-devd.conf");
2194
	sleep(1);
2195
	unset($_gb);
2196
}
2197

    
2198
function is_interface_vlan_mismatch() {
2199
	global $config, $g;
2200

    
2201
	if (is_array($config['vlans']['vlan'])) {
2202
		foreach ($config['vlans']['vlan'] as $vlan) {
2203
			if (substr($vlan['if'], 0, 4) == "lagg") {
2204
				return false;
2205
			}
2206
			if (does_interface_exist($vlan['if']) == false) {
2207
				return true;
2208
			}
2209
		}
2210
	}
2211

    
2212
	return false;
2213
}
2214

    
2215
function is_interface_mismatch() {
2216
	global $config, $g;
2217

    
2218
	$do_assign = false;
2219
	$i = 0;
2220
	$missing_interfaces = array();
2221
	if (is_array($config['interfaces'])) {
2222
		foreach ($config['interfaces'] as $ifname => $ifcfg) {
2223
			if (interface_is_vlan($ifcfg['if']) != NULL ||
2224
			    interface_is_qinq($ifcfg['if']) != NULL ||
2225
			    preg_match("/^enc|^cua|^tun|^tap|^l2tp|^pptp|^ppp|^ovpn|^ipsec|^gif|^gre|^lagg|^bridge|vlan|_wlan|_\d{0,4}_\d{0,4}$/i", $ifcfg['if'])) {
2226
				// Do not check these interfaces.
2227
				$i++;
2228
				continue;
2229
			} else if (does_interface_exist($ifcfg['if']) == false) {
2230
				$missing_interfaces[] = $ifcfg['if'];
2231
				$do_assign = true;
2232
			} else {
2233
				$i++;
2234
			}
2235
		}
2236
	}
2237

    
2238
	if (file_exists("{$g['tmp_path']}/assign_complete")) {
2239
		$do_assign = false;
2240
	}
2241

    
2242
	if (!empty($missing_interfaces) && $do_assign) {
2243
		file_put_contents("{$g['tmp_path']}/missing_interfaces", implode(' ', $missing_interfaces));
2244
	} else {
2245
		@unlink("{$g['tmp_path']}/missing_interfaces");
2246
	}
2247

    
2248
	return $do_assign;
2249
}
2250

    
2251
/* sync carp entries to other firewalls */
2252
function carp_sync_client() {
2253
	global $g;
2254
	send_event("filter sync");
2255
}
2256

    
2257
/****f* util/isAjax
2258
 * NAME
2259
 *   isAjax - reports if the request is driven from prototype
2260
 * INPUTS
2261
 *   none
2262
 * RESULT
2263
 *   true/false
2264
 ******/
2265
function isAjax() {
2266
	return isset ($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest';
2267
}
2268

    
2269
/****f* util/timeout
2270
 * NAME
2271
 *   timeout - console input with timeout countdown. Note: erases 2 char of screen for timer. Leave space.
2272
 * INPUTS
2273
 *   optional, seconds to wait before timeout. Default 9 seconds.
2274
 * RESULT
2275
 *   returns 1 char of user input or null if no input.
2276
 ******/
2277
function timeout($timer = 9) {
2278
	while (!isset($key)) {
2279
		if ($timer >= 9) {
2280
			echo chr(8) . chr(8) . ($timer == 9 ? chr(32) : null) . "{$timer}";
2281
		} else {
2282
			echo chr(8). "{$timer}";
2283
		}
2284
		`/bin/stty -icanon min 0 time 25`;
2285
		$key = trim(`KEY=\`dd count=1 2>/dev/null\`; echo \$KEY`);
2286
		`/bin/stty icanon`;
2287
		if ($key == '') {
2288
			unset($key);
2289
		}
2290
		$timer--;
2291
		if ($timer == 0) {
2292
			break;
2293
		}
2294
	}
2295
	return $key;
2296
}
2297

    
2298
/****f* util/msort
2299
 * NAME
2300
 *   msort - sort array
2301
 * INPUTS
2302
 *   $array to be sorted, field to sort by, direction of sort
2303
 * RESULT
2304
 *   returns newly sorted array
2305
 ******/
2306
function msort($array, $id = "id", $sort_ascending = true) {
2307
	$temp_array = array();
2308
	if (!is_array($array)) {
2309
		return $temp_array;
2310
	}
2311
	while (count($array)>0) {
2312
		$lowest_id = 0;
2313
		$index = 0;
2314
		foreach ($array as $item) {
2315
			if (isset($item[$id])) {
2316
				if ($array[$lowest_id][$id]) {
2317
					if (strtolower($item[$id]) < strtolower($array[$lowest_id][$id])) {
2318
						$lowest_id = $index;
2319
					}
2320
				}
2321
			}
2322
			$index++;
2323
		}
2324
		$temp_array[] = $array[$lowest_id];
2325
		$array = array_merge(array_slice($array, 0, $lowest_id), array_slice($array, $lowest_id + 1));
2326
	}
2327
	if ($sort_ascending) {
2328
		return $temp_array;
2329
	} else {
2330
		return array_reverse($temp_array);
2331
	}
2332
}
2333

    
2334
/****f* util/is_URL
2335
 * NAME
2336
 *   is_URL
2337
 * INPUTS
2338
 *   string to check
2339
 * RESULT
2340
 *   Returns true if item is a URL
2341
 ******/
2342
function is_URL($url) {
2343
	$match = preg_match("'\b(([\w-]+://?|www[.])[^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|/)))'", $url);
2344
	if ($match) {
2345
		return true;
2346
	}
2347
	return false;
2348
}
2349

    
2350
function is_file_included($file = "") {
2351
	$files = get_included_files();
2352
	if (in_array($file, $files)) {
2353
		return true;
2354
	}
2355

    
2356
	return false;
2357
}
2358

    
2359
/*
2360
 * Replace a value on a deep associative array using regex
2361
 */
2362
function array_replace_values_recursive($data, $match, $replace) {
2363
	if (empty($data)) {
2364
		return $data;
2365
	}
2366

    
2367
	if (is_string($data)) {
2368
		$data = preg_replace("/{$match}/", $replace, $data);
2369
	} else if (is_array($data)) {
2370
		foreach ($data as $k => $v) {
2371
			$data[$k] = array_replace_values_recursive($v, $match, $replace);
2372
		}
2373
	}
2374

    
2375
	return $data;
2376
}
2377

    
2378
/*
2379
	This function was borrowed from a comment on PHP.net at the following URL:
2380
	http://www.php.net/manual/en/function.array-merge-recursive.php#73843
2381
 */
2382
function array_merge_recursive_unique($array0, $array1) {
2383

    
2384
	$arrays = func_get_args();
2385
	$remains = $arrays;
2386

    
2387
	// We walk through each arrays and put value in the results (without
2388
	// considering previous value).
2389
	$result = array();
2390

    
2391
	// loop available array
2392
	foreach ($arrays as $array) {
2393

    
2394
		// The first remaining array is $array. We are processing it. So
2395
		// we remove it from remaining arrays.
2396
		array_shift($remains);
2397

    
2398
		// We don't care non array param, like array_merge since PHP 5.0.
2399
		if (is_array($array)) {
2400
			// Loop values
2401
			foreach ($array as $key => $value) {
2402
				if (is_array($value)) {
2403
					// we gather all remaining arrays that have such key available
2404
					$args = array();
2405
					foreach ($remains as $remain) {
2406
						if (array_key_exists($key, $remain)) {
2407
							array_push($args, $remain[$key]);
2408
						}
2409
					}
2410

    
2411
					if (count($args) > 2) {
2412
						// put the recursion
2413
						$result[$key] = call_user_func_array(__FUNCTION__, $args);
2414
					} else {
2415
						foreach ($value as $vkey => $vval) {
2416
							if (!is_array($result[$key])) {
2417
								$result[$key] = array();
2418
							}
2419
							$result[$key][$vkey] = $vval;
2420
						}
2421
					}
2422
				} else {
2423
					// simply put the value
2424
					$result[$key] = $value;
2425
				}
2426
			}
2427
		}
2428
	}
2429
	return $result;
2430
}
2431

    
2432

    
2433
/*
2434
 * converts a string like "a,b,c,d"
2435
 * into an array like array("a" => "b", "c" => "d")
2436
 */
2437
function explode_assoc($delimiter, $string) {
2438
	$array = explode($delimiter, $string);
2439
	$result = array();
2440
	$numkeys = floor(count($array) / 2);
2441
	for ($i = 0; $i < $numkeys; $i += 1) {
2442
		$result[$array[$i * 2]] = $array[$i * 2 + 1];
2443
	}
2444
	return $result;
2445
}
2446

    
2447
/*
2448
 * Given a string of text with some delimiter, look for occurrences
2449
 * of some string and replace all of those.
2450
 * $text - the text string (e.g. "abc,defg,x123,ipv4,xyz")
2451
 * $delimiter - the delimiter (e.g. ",")
2452
 * $element - the element to match (e.g. "defg")
2453
 * $replacement - the string to replace it with (e.g. "42")
2454
 * Returns the resulting delimited string (e.g. "abc,42,x123,ipv4,xyz")
2455
 */
2456
function replace_element_in_list($text, $delimiter, $element, $replacement) {
2457
	$textArray = explode($delimiter, $text);
2458
	while (($entry = array_search($element, $textArray)) !== false) {
2459
		$textArray[$entry] = $replacement;
2460
	}
2461
	return implode(',', $textArray);
2462
}
2463

    
2464
/* Try to change a static route, if it doesn't exist, add it */
2465
function route_add_or_change($args) {
2466
	global $config;
2467

    
2468
	if (empty($args)) {
2469
		return false;
2470
	}
2471

    
2472
	/* First, try to add it */
2473
	$_gb = exec(escapeshellcmd("/sbin/route add " . $args), $output, $rc);
2474
		
2475
	if (isset($config['system']['route-debug'])) {
2476
		$add_change = 'add';
2477
		$mt = microtime();
2478
		log_error("ROUTING debug: $mt - ADD RC={$rc} - $args");
2479
	}
2480

    
2481
	if ($rc != 0) {
2482
		/* If it fails, try to change it */
2483
		$_gb = exec(escapeshellcmd("/sbin/route change " . $args),
2484
		    $output, $rc);
2485

    
2486
		if (isset($config['system']['route-debug'])) {
2487
			$add_change = 'change';
2488
			$mt = microtime();
2489
			log_error("ROUTING debug: $mt - CHG RC={$rc} - $args");
2490
		}
2491
	}
2492
	if (isset($config['system']['route-debug'])) {
2493
		file_put_contents("/dev/console", "\n[".getmypid()."] ROUTE: {$add_change} {$args} result: {$rc}");
2494
	}
2495

    
2496
	return ($rc == 0);
2497
}
2498

    
2499
function alias_to_subnets_recursive($name, $returnhostnames = false) {
2500
	global $aliastable;
2501
	$result = array();
2502
	if (!isset($aliastable[$name])) {
2503
		return $result;
2504
	}
2505
	$subnets = preg_split('/\s+/', $aliastable[$name]);
2506
	foreach ($subnets as $net) {
2507
		if (is_alias($net)) {
2508
			$sub = alias_to_subnets_recursive($net, $returnhostnames);
2509
			$result = array_merge($result, $sub);
2510
			continue;
2511
		} elseif (!is_subnet($net)) {
2512
			if (is_ipaddrv4($net)) {
2513
				$net .= "/32";
2514
			} else if (is_ipaddrv6($net)) {
2515
				$net .= "/128";
2516
			} else if ($returnhostnames === false || !is_fqdn($net)) {
2517
				continue;
2518
			}
2519
		}
2520
		$result[] = $net;
2521
	}
2522
	return $result;
2523
}
2524

    
2525
function get_staticroutes($returnsubnetsonly = false, $returnhostnames = false, $returnenabledroutesonly = false) {
2526
	global $config, $aliastable;
2527

    
2528
	/* Bail if there are no routes, but return an array always so callers don't have to check. */
2529
	if (!is_array($config['staticroutes']['route'])) {
2530
		return array();
2531
	}
2532

    
2533
	$allstaticroutes = array();
2534
	$allsubnets = array();
2535
	/* Loop through routes and expand aliases as we find them. */
2536
	foreach ($config['staticroutes']['route'] as $route) {
2537
		if ($returnenabledroutesonly && isset($route['disabled'])) {
2538
			continue;
2539
		}
2540

    
2541
		if (is_alias($route['network'])) {
2542
			foreach (alias_to_subnets_recursive($route['network'], $returnhostnames) as $net) {
2543
				$temproute = $route;
2544
				$temproute['network'] = $net;
2545
				$allstaticroutes[] = $temproute;
2546
				$allsubnets[] = $net;
2547
			}
2548
		} elseif (is_subnet($route['network'])) {
2549
			$allstaticroutes[] = $route;
2550
			$allsubnets[] = $route['network'];
2551
		}
2552
	}
2553
	if ($returnsubnetsonly) {
2554
		return $allsubnets;
2555
	} else {
2556
		return $allstaticroutes;
2557
	}
2558
}
2559

    
2560
/****f* util/get_alias_list
2561
 * NAME
2562
 *   get_alias_list - Provide a list of aliases.
2563
 * INPUTS
2564
 *   $type          - Optional, can be a string or array specifying what type(s) of aliases you need.
2565
 * RESULT
2566
 *   Array containing list of aliases.
2567
 *   If $type is unspecified, all aliases are returned.
2568
 *   If $type is a string, all aliases of the type specified in $type are returned.
2569
 *   If $type is an array, all aliases of any type specified in any element of $type are returned.
2570
 */
2571
function get_alias_list($type = null) {
2572
	global $config;
2573
	$result = array();
2574
	if ($config['aliases']['alias'] <> "" && is_array($config['aliases']['alias'])) {
2575
		foreach ($config['aliases']['alias'] as $alias) {
2576
			if ($type === null) {
2577
				$result[] = $alias['name'];
2578
			} else if (is_array($type)) {
2579
				if (in_array($alias['type'], $type)) {
2580
					$result[] = $alias['name'];
2581
				}
2582
			} else if ($type === $alias['type']) {
2583
				$result[] = $alias['name'];
2584
			}
2585
		}
2586
	}
2587
	return $result;
2588
}
2589

    
2590
/* returns an array consisting of every element of $haystack that is not equal to $needle. */
2591
function array_exclude($needle, $haystack) {
2592
	$result = array();
2593
	if (is_array($haystack)) {
2594
		foreach ($haystack as $thing) {
2595
			if ($needle !== $thing) {
2596
				$result[] = $thing;
2597
			}
2598
		}
2599
	}
2600
	return $result;
2601
}
2602

    
2603
/* Define what is preferred, IPv4 or IPv6 */
2604
function prefer_ipv4_or_ipv6() {
2605
	global $config;
2606

    
2607
	if (isset($config['system']['prefer_ipv4'])) {
2608
		mwexec("/etc/rc.d/ip6addrctl prefer_ipv4");
2609
	} else {
2610
		mwexec("/etc/rc.d/ip6addrctl prefer_ipv6");
2611
	}
2612
}
2613

    
2614
/* Redirect to page passing parameters via POST */
2615
function post_redirect($page, $params) {
2616
	if (!is_array($params)) {
2617
		return;
2618
	}
2619

    
2620
	print "<html><body><form action=\"{$page}\" name=\"formredir\" method=\"post\">\n";
2621
	foreach ($params as $key => $value) {
2622
		print "<input type=\"hidden\" name=\"{$key}\" value=\"{$value}\" />\n";
2623
	}
2624
	print "</form>\n";
2625
	print "<script type=\"text/javascript\">\n";
2626
	print "//<![CDATA[\n";
2627
	print "document.formredir.submit();\n";
2628
	print "//]]>\n";
2629
	print "</script>\n";
2630
	print "</body></html>\n";
2631
}
2632

    
2633
/* Locate disks that can be queried for S.M.A.R.T. data. */
2634
function get_smart_drive_list() {
2635
	/* SMART supports some disks directly, and some controllers directly,
2636
	 * See https://redmine.pfsense.org/issues/9042 */
2637
	$supported_disk_types = array("ad", "da", "ada");
2638
	$supported_controller_types = array("nvme");
2639
	$disk_list = explode(" ", get_single_sysctl("kern.disks"));
2640
	foreach ($disk_list as $id => $disk) {
2641
		// We only want certain kinds of disks for S.M.A.R.T.
2642
		// 1 is a match, 0 is no match, False is any problem processing the regex
2643
		if (preg_match("/^(" . implode("|", $supported_disk_types) . ").*[0-9]{1,2}$/", $disk) !== 1) {
2644
			unset($disk_list[$id]);
2645
			continue;
2646
		}
2647
	}
2648
	foreach ($supported_controller_types as $controller) {
2649
		$devices = glob("/dev/{$controller}*");
2650
		if (!is_array($devices)) {
2651
			continue;
2652
		}
2653
		foreach ($devices as $device) {
2654
			$disk_list[] = basename($device);
2655
		}
2656
	}
2657
	sort($disk_list);
2658
	return $disk_list;
2659
}
2660

    
2661
// Validate a network address
2662
//	$addr: the address to validate
2663
//	$type: IPV4|IPV6|IPV4V6
2664
//	$label: the label used by the GUI to display this value. Required to compose an error message
2665
//	$err_msg: pointer to the callers error message array so that error messages can be added to it here
2666
//	$alias: are aliases permitted for this address?
2667
// Returns:
2668
//	IPV4 - if $addr is a valid IPv4 address
2669
//	IPV6 - if $addr is a valid IPv6 address
2670
//	ALIAS - if $alias=true and $addr is an alias
2671
//	false - otherwise
2672

    
2673
function validateipaddr(&$addr, $type, $label, &$err_msg, $alias=false) {
2674
	switch ($type) {
2675
		case IPV4:
2676
			if (is_ipaddrv4($addr)) {
2677
				return IPV4;
2678
			} else if ($alias) {
2679
				if (is_alias($addr)) {
2680
					return ALIAS;
2681
				} else {
2682
					$err_msg[] = sprintf(gettext("%s must be a valid IPv4 address or alias."), $label);
2683
					return false;
2684
				}
2685
			} else {
2686
				$err_msg[] = sprintf(gettext("%s must be a valid IPv4 address."), $label);
2687
				return false;
2688
			}
2689
		break;
2690
		case IPV6:
2691
			if (is_ipaddrv6($addr)) {
2692
				$addr = strtolower($addr);
2693
				return IPV6;
2694
			} else if ($alias) {
2695
				if (is_alias($addr)) {
2696
					return ALIAS;
2697
				} else {
2698
					$err_msg[] = sprintf(gettext("%s must be a valid IPv6 address or alias."), $label);
2699
					return false;
2700
				}
2701
			} else {
2702
				$err_msg[] = sprintf(gettext("%s must be a valid IPv6 address."), $label);
2703
				return false;
2704
			}
2705
		break;
2706
		case IPV4V6:
2707
			if (is_ipaddrv6($addr)) {
2708
				$addr = strtolower($addr);
2709
				return IPV6;
2710
			} else if (is_ipaddrv4($addr)) {
2711
				return IPV4;
2712
			} else if ($alias) {
2713
				if (is_alias($addr)) {
2714
					return ALIAS;
2715
				} else {
2716
					$err_msg[] = sprintf(gettext("%s must be a valid IPv4 or IPv6 address or alias."), $label);
2717
					return false;
2718
				}
2719
			} else {
2720
				$err_msg[] = sprintf(gettext("%s must be a valid IPv4 or IPv6 address."), $label);
2721
				return false;
2722
			}
2723
		break;
2724
	}
2725

    
2726
	return false;
2727
}
2728

    
2729
/* From DUID configuration inputs, format a string that looks (more) like the expected raw DUID format:
2730
 * 1) For DUIDs entered as a known DUID type, convert to a hexstring and prepend the DUID number, after having done the following:
2731
 *     a) For DUID-LLT and DUID-EN, convert the time/enterprise ID input to hex and append the link-layer address/identifier input.
2732
 *     b) For DUID-LLT and DUID-LL, prepend a hardware type of 1.
2733
 *     c) For DUID-UUID, remove any "-".
2734
 * 2) Replace any remaining "-" with ":".
2735
 * 3) If any components are input with just a single char (hex digit hopefully), put a "0" in front.
2736
 * 4) The first two components should be a 16-bit integer (little- or big-endian, depending on the current machine type) that
2737
 *    is equal to the number of other components. If not, prepend this as "nn:00" (all pfSense builds are little-endian).
2738
 *    This is convenience, because the DUID reported by dhcp6c in logs does not include this count, which corresponds to the
2739
 *    option-len field of DHCPv6's OPTION_CLIENTID option.
2740
 *
2741
 * The final result should be closer to:
2742
 *
2743
 * "nn:00:00:0n:nn:nn:nn:..."
2744
 *
2745
 * This function does not validate the input. is_duid() will do validation.
2746
 */
2747
function format_duid($duidtype, $duidpt1, $duidpt2=null) {
2748
	if ($duidpt2)
2749
		$duidpt1 = implode(':', str_split(str_pad(dechex($duidpt1), 8, '0', STR_PAD_LEFT), 2)) . ':' . $duidpt2;
2750

    
2751
	/* Make hexstrings */
2752
	if ($duidtype) {
2753
		switch ($duidtype) {
2754
		/* Add a hardware type to DUID-LLT and DUID-LL; assume Ethernet */
2755
		case 1:
2756
		case 3:
2757
			$duidpt1 = '00:01:' . $duidpt1;
2758
			break;
2759
		/* Remove '-' from given UUID and insert ':' every 2 characters */
2760
		case 4:
2761
			$duidpt1 = implode(':', str_split(str_replace('-', '', $duidpt1), 2));
2762
			break;
2763
		default:
2764
		}
2765
		$duidpt1 = '00:0' . $duidtype . ':' . $duidpt1;
2766
	}
2767

    
2768
	$values = explode(':', strtolower(str_replace('-', ':', $duidpt1)));
2769

    
2770
	if (hexdec($values[0]) != count($values) - 2)
2771
		array_unshift($values, dechex(count($values)), '00');
2772

    
2773
	array_walk($values, function(&$value) {
2774
		$value = str_pad($value, 2, '0', STR_PAD_LEFT);
2775
	});
2776

    
2777
	return implode(":", $values);
2778
}
2779

    
2780
/* Returns true if $dhcp6duid is a valid DUID entry.
2781
 * Parse the entry to check for valid length according to known DUID types.
2782
 */
2783
function is_duid($dhcp6duid) {
2784
	$values = explode(":", $dhcp6duid);
2785
	if (hexdec($values[0]) == count($values) - 2) {
2786
		switch (hexdec($values[2] . $values[3])) {
2787
		case 0:
2788
			return false;
2789
			break;
2790
		case 1:
2791
			if (count($values) != 16 || strlen($dhcp6duid) != 47)
2792
				return false;
2793
			break;
2794
		case 3:
2795
			if (count($values) != 12 || strlen($dhcp6duid) != 35)
2796
				return false;
2797
			break;
2798
		case 4:
2799
			if (count($values) != 20 || strlen($dhcp6duid) != 59)
2800
				return false;
2801
			break;
2802
		/* DUID is up to 128 octets; allow 2 octets for type code, 2 more for option-len */
2803
		default:
2804
			if (count($values) > 132 || strlen($dhcp6duid) != count($values) * 3 - 1)
2805
				return false;
2806
		}
2807
	} else
2808
		return false;
2809

    
2810
	for ($i = 0; $i < count($values); $i++) {
2811
		if (ctype_xdigit($values[$i]) == false)
2812
			return false;
2813
		if (hexdec($values[$i]) < 0 || hexdec($values[$i]) > 255)
2814
			return false;
2815
	}
2816

    
2817
	return true;
2818
}
2819

    
2820
/* Write the DHCP6 DUID file */
2821
function write_dhcp6_duid($duidstring) {
2822
	// Create the hex array from the dhcp6duid config entry and write to file
2823
	global $g;
2824

    
2825
	if(!is_duid($duidstring)) {
2826
		log_error(gettext("Error: attempting to write DUID file - Invalid DUID detected"));
2827
		return false;
2828
	}
2829
	$temp = str_replace(":","",$duidstring);
2830
	$duid_binstring = pack("H*",$temp);
2831
	if ($fd = fopen("{$g['vardb_path']}/dhcp6c_duid", "wb")) {
2832
		fwrite($fd, $duid_binstring);
2833
		fclose($fd);
2834
		return true;
2835
	}
2836
	log_error(gettext("Error: attempting to write DUID file - File write error"));
2837
	return false;
2838
}
2839

    
2840
/* returns duid string from 'vardb_path']}/dhcp6c_duid' */
2841
function get_duid_from_file() {
2842
	global $g;
2843

    
2844
	$duid_ASCII = "";
2845
	$count = 0;
2846

    
2847
	if (file_exists("{$g['vardb_path']}/dhcp6c_duid") &&
2848
	    ($fd = fopen("{$g['vardb_path']}/dhcp6c_duid", "r"))) {
2849
		$fsize = filesize("{$g['vardb_path']}/dhcp6c_duid");
2850
		if ($fsize <= 132) {
2851
			$buffer = fread($fd, $fsize);
2852
			while($count < $fsize) {
2853
				$duid_ASCII .= bin2hex($buffer[$count]);
2854
				$count++;
2855
				if($count < $fsize) {
2856
					$duid_ASCII .= ":";
2857
				}
2858
			}
2859
		}
2860
		fclose($fd);
2861
	}
2862
	//if no file or error with read then the string returns blanked DUID string
2863
	if(!is_duid($duid_ASCII)) {
2864
		return "--:--:--:--:--:--:--:--:--:--:--:--:--:--:--:--";
2865
	}
2866
	return($duid_ASCII);
2867
}
2868

    
2869
/* Replaces the Mac OS 9 and earlier (\r) and DOS/Windows (\r\n) newlines with the Unix equivalent (\n). */
2870
function unixnewlines($text) {
2871
	return preg_replace('/\r\n?/', "\n", $text);
2872
}
2873

    
2874
?>
(52-52/59)