Project

General

Profile

Download (79.5 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
/*
3
 * util.inc
4
 *
5
 * part of pfSense (https://www.pfsense.org)
6
 * Copyright (c) 2004-2013 BSD Perimeter
7
 * Copyright (c) 2013-2016 Electric Sheep Fencing
8
 * Copyright (c) 2014-2020 Rubicon Communications, LLC (Netgate)
9
 * All rights reserved.
10
 *
11
 * originally part of m0n0wall (http://m0n0.ch/wall)
12
 * Copyright (c) 2003-2004 Manuel Kasper <mk@neon1.net>.
13
 * All rights reserved.
14
 *
15
 * Licensed under the Apache License, Version 2.0 (the "License");
16
 * you may not use this file except in compliance with the License.
17
 * You may obtain a copy of the License at
18
 *
19
 * http://www.apache.org/licenses/LICENSE-2.0
20
 *
21
 * Unless required by applicable law or agreed to in writing, software
22
 * distributed under the License is distributed on an "AS IS" BASIS,
23
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
24
 * See the License for the specific language governing permissions and
25
 * limitations under the License.
26
 */
27

    
28
define('VIP_ALL', 1);
29
define('VIP_CARP', 2);
30
define('VIP_IPALIAS', 3);
31

    
32
/* kill a process by pid file */
33
function killbypid($pidfile, $waitfor = 0) {
34
	return sigkillbypid($pidfile, "TERM", $waitfor);
35
}
36

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

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

    
50
	return (intval($retval) == 0);
51
}
52

    
53
function isvalidproc($proc) {
54
	return is_process_running($proc);
55
}
56

    
57
/* sigkill a process by pid file, and wait for it to terminate or remove the .pid file for $waitfor seconds */
58
/* return 1 for success and 0 for a failure */
59
function sigkillbypid($pidfile, $sig, $waitfor = 0) {
60
	if (isvalidpid($pidfile)) {
61
		$result = mwexec("/bin/pkill " . escapeshellarg("-{$sig}") .
62
		    " -F {$pidfile}", true);
63
		$waitcounter = $waitfor * 10;
64
		while(isvalidpid($pidfile) && $waitcounter > 0) {
65
			$waitcounter = $waitcounter - 1;
66
			usleep(100000);
67
		}
68
		return $result;
69
	}
70

    
71
	return 0;
72
}
73

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

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

    
88
function is_subsystem_dirty($subsystem = "") {
89
	global $g;
90

    
91
	if ($subsystem == "") {
92
		return false;
93
	}
94

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

    
99
	return false;
100
}
101

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

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

    
110
function clear_subsystem_dirty($subsystem = "") {
111
	global $g;
112

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

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

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

    
155
		return $fp;
156
	}
157

    
158
	return NULL;
159
}
160

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
360
	foreach ( $parts as $v ) {
361

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

    
365
	}
366

    
367
	return $binstr;
368
}
369

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

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

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

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

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

    
391
	return $ip;
392
}
393

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

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

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

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

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

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

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

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

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

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

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

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

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

    
499
	return $rangeaddresses;
500
}
501

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

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

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

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

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

    
556
	$rangesubnets = array();
557
	$netsize = 0;
558

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

    
563
		// 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)
564

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

    
573
		// 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)
574

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

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

    
587
		if ($ip2bin < $ip1bin) {
588
			continue;
589
		}
590

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

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

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

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

    
610
	ksort($rangesubnets, SORT_STRING);
611
	$out = array();
612

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

    
622
	return $out;
623
}
624

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

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

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

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

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

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

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

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

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

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

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

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

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

    
766
}
767

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

    
783
function is_v4($ip_or_subnet) {
784
	return is_ipaddrv4($ip_or_subnet) || is_subnetv4($ip_or_subnet);
785
}
786

    
787
function is_v6($ip_or_subnet) {
788
	return is_ipaddrv6($ip_or_subnet) || is_subnetv6($ip_or_subnet);
789
}
790

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
911
	if (!is_ipaddrv6($subnet)) {
912
		return $result;
913
	}
914

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

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

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

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

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

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

    
954
	return $result;
955
}
956

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

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

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

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

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

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

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

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

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

    
1037
	return true;
1038
}
1039

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

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

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

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

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

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

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

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

    
1112
	$values[0] = intval($values[0]);
1113
	$values[1] = intval($values[1]);
1114

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

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

    
1123
	return true;
1124
}
1125

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

    
1137
/* returns true if $port is in use */
1138
function is_port_in_use($port, $proto = "tcp", $ip_version = 4) {
1139
	$port_info = array();
1140
	exec("/usr/bin/netstat --libxo json -an " . escapeshellarg('-' . $ip_version) . " -p " . escapeshellarg($proto), $rawdata, $rc);
1141
	if ($rc == 0) {
1142
		$netstatarr = json_decode(implode(" ", $rawdata), JSON_OBJECT_AS_ARRAY);
1143
		$netstatarr = $netstatarr['statistics']['socket'];
1144

    
1145
		foreach($netstatarr as $index => $portstats){
1146
			array_push($port_info, $portstats['local']['port']);
1147
		}
1148
	}
1149

    
1150
	return in_array($port, $port_info);
1151
}
1152

    
1153
/* returns true if $portrange is a valid TCP/UDP portrange ("<port>:<port>") */
1154
function is_portrange($portrange) {
1155
	$ports = explode(":", $portrange);
1156

    
1157
	return (count($ports) == 2 && is_port($ports[0]) && is_port($ports[1]));
1158
}
1159

    
1160
/* returns true if $port is a valid TCP/UDP port number or range ("<port>:<port>") */
1161
function is_port_or_range($port) {
1162
	return (is_port($port) || is_portrange($port));
1163
}
1164

    
1165
/* returns true if $port is an alias that is a port type */
1166
function is_portalias($port) {
1167
	global $config;
1168

    
1169
	if (is_alias($port)) {
1170
		if (is_array($config['aliases']['alias'])) {
1171
			foreach ($config['aliases']['alias'] as $alias) {
1172
				if ($alias['name'] == $port && preg_match("/port/i", $alias['type'])) {
1173
					return true;
1174
				}
1175
			}
1176
		}
1177
	}
1178
	return false;
1179
}
1180

    
1181
/* returns true if $port is a valid port number or an alias thereof */
1182
function is_port_or_alias($port) {
1183
	return (is_port($port) || is_portalias($port));
1184
}
1185

    
1186
/* returns true if $port is a valid TCP/UDP port number or range ("<port>:<port>") or an alias thereof */
1187
function is_port_or_range_or_alias($port) {
1188
	return (is_port($port) || is_portrange($port) || is_portalias($port));
1189
}
1190

    
1191
/* create ranges of sequential port numbers (200:215) and remove duplicates */
1192
function group_ports($ports, $kflc = false) {
1193
	if (!is_array($ports) || empty($ports)) {
1194
		return;
1195
	}
1196

    
1197
	$uniq = array();
1198
	$comments = array();
1199
	foreach ($ports as $port) {
1200
		if (($kflc) && (strpos($port, '#') === 0)) {	// Keep Full Line Comments (lines beginning with #).
1201
			$comments[] = $port;
1202
		} else if (is_portrange($port)) {
1203
			list($begin, $end) = explode(":", $port);
1204
			if ($begin > $end) {
1205
				$aux = $begin;
1206
				$begin = $end;
1207
				$end = $aux;
1208
			}
1209
			for ($i = $begin; $i <= $end; $i++) {
1210
				if (!in_array($i, $uniq)) {
1211
					$uniq[] = $i;
1212
				}
1213
			}
1214
		} else if (is_port($port)) {
1215
			if (!in_array($port, $uniq)) {
1216
				$uniq[] = $port;
1217
			}
1218
		}
1219
	}
1220
	sort($uniq, SORT_NUMERIC);
1221

    
1222
	$result = array();
1223
	foreach ($uniq as $idx => $port) {
1224
		if ($idx == 0) {
1225
			$result[] = $port;
1226
			continue;
1227
		}
1228

    
1229
		$last = end($result);
1230
		if (is_portrange($last)) {
1231
			list($begin, $end) = explode(":", $last);
1232
		} else {
1233
			$begin = $end = $last;
1234
		}
1235

    
1236
		if ($port == ($end+1)) {
1237
			$end++;
1238
			$result[count($result)-1] = "{$begin}:{$end}";
1239
		} else {
1240
			$result[] = $port;
1241
		}
1242
	}
1243

    
1244
	return array_merge($comments, $result);
1245
}
1246

    
1247
/* returns true if $val is a valid shaper bandwidth value */
1248
function is_valid_shaperbw($val) {
1249
	return (preg_match("/^(\d+(?:\.\d+)?)([MKG]?b|%)$/", $val));
1250
}
1251

    
1252
/* returns true if $test is in the range between $start and $end */
1253
function is_inrange_v4($test, $start, $end) {
1254
	if (!is_ipaddrv4($test) || !is_ipaddrv4($start) || !is_ipaddrv4($end)) {
1255
		return false;
1256
	}
1257

    
1258
	if (ip2ulong($test) <= ip2ulong($end) &&
1259
	    ip2ulong($test) >= ip2ulong($start)) {
1260
		return true;
1261
	}
1262

    
1263
	return false;
1264
}
1265

    
1266
/* returns true if $test is in the range between $start and $end */
1267
function is_inrange_v6($test, $start, $end) {
1268
	if (!is_ipaddrv6($test) || !is_ipaddrv6($start) || !is_ipaddrv6($end)) {
1269
		return false;
1270
	}
1271

    
1272
	if (inet_pton($test) <= inet_pton($end) &&
1273
	    inet_pton($test) >= inet_pton($start)) {
1274
		return true;
1275
	}
1276

    
1277
	return false;
1278
}
1279

    
1280
/* returns true if $test is in the range between $start and $end */
1281
function is_inrange($test, $start, $end) {
1282
	return is_ipaddrv6($test) ? is_inrange_v6($test, $start, $end) : is_inrange_v4($test, $start, $end);
1283
}
1284

    
1285
function get_configured_vip_list($family = 'all', $type = VIP_ALL) {
1286
	global $config;
1287

    
1288
	$list = array();
1289
	if (!is_array($config['virtualip']) ||
1290
	    !is_array($config['virtualip']['vip']) ||
1291
	    empty($config['virtualip']['vip'])) {
1292
		return ($list);
1293
	}
1294

    
1295
	$viparr = &$config['virtualip']['vip'];
1296
	foreach ($viparr as $vip) {
1297

    
1298
		if ($type == VIP_CARP) {
1299
			if ($vip['mode'] != "carp")
1300
				continue;
1301
		} elseif ($type == VIP_IPALIAS) {
1302
			if ($vip['mode'] != "ipalias")
1303
				continue;
1304
		} else {
1305
			if ($vip['mode'] != "carp" && $vip['mode'] != "ipalias")
1306
				continue;
1307
		}
1308

    
1309
		if ($family == 'all' ||
1310
		    ($family == 'inet' && is_ipaddrv4($vip['subnet'])) ||
1311
		    ($family == 'inet6' && is_ipaddrv6($vip['subnet']))) {
1312
			$list["_vip{$vip['uniqid']}"] = $vip['subnet'];
1313
		}
1314
	}
1315
	return ($list);
1316
}
1317

    
1318
function get_configured_vip($vipinterface = '') {
1319

    
1320
	return (get_configured_vip_detail($vipinterface, 'all', 'vip'));
1321
}
1322

    
1323
function get_configured_vip_interface($vipinterface = '') {
1324

    
1325
	return (get_configured_vip_detail($vipinterface, 'all', 'iface'));
1326
}
1327

    
1328
function get_configured_vip_ipv4($vipinterface = '') {
1329

    
1330
	return (get_configured_vip_detail($vipinterface, 'inet', 'ip'));
1331
}
1332

    
1333
function get_configured_vip_ipv6($vipinterface = '') {
1334

    
1335
	return (get_configured_vip_detail($vipinterface, 'inet6', 'ip'));
1336
}
1337

    
1338
function get_configured_vip_subnetv4($vipinterface = '') {
1339

    
1340
	return (get_configured_vip_detail($vipinterface, 'inet', 'subnet'));
1341
}
1342

    
1343
function get_configured_vip_subnetv6($vipinterface = '') {
1344

    
1345
	return (get_configured_vip_detail($vipinterface, 'inet6', 'subnet'));
1346
}
1347

    
1348
function get_configured_vip_detail($vipinterface = '', $family = 'inet', $what = 'ip') {
1349
	global $config;
1350

    
1351
	if (empty($vipinterface) ||
1352
	    !is_array($config['virtualip']) ||
1353
	    !is_array($config['virtualip']['vip']) ||
1354
	    empty($config['virtualip']['vip'])) {
1355
		return (NULL);
1356
	}
1357

    
1358
	$viparr = &$config['virtualip']['vip'];
1359
	foreach ($viparr as $vip) {
1360
		if ($vip['mode'] != "carp" && $vip['mode'] != "ipalias") {
1361
			continue;
1362
		}
1363

    
1364
		if ($vipinterface != "_vip{$vip['uniqid']}") {
1365
			continue;
1366
		}
1367

    
1368
		switch ($what) {
1369
			case 'subnet':
1370
				if ($family == 'inet' && is_ipaddrv4($vip['subnet']))
1371
					return ($vip['subnet_bits']);
1372
				else if ($family == 'inet6' && is_ipaddrv6($vip['subnet']))
1373
					return ($vip['subnet_bits']);
1374
				break;
1375
			case 'iface':
1376
				return ($vip['interface']);
1377
				break;
1378
			case 'vip':
1379
				return ($vip);
1380
				break;
1381
			case 'ip':
1382
			default:
1383
				if ($family == 'inet' && is_ipaddrv4($vip['subnet'])) {
1384
					return ($vip['subnet']);
1385
				} else if ($family == 'inet6' && is_ipaddrv6($vip['subnet'])) {
1386
					return ($vip['subnet']);
1387
				}
1388
				break;
1389
		}
1390
		break;
1391
	}
1392

    
1393
	return (NULL);
1394
}
1395

    
1396
/* comparison function for sorting by the order in which interfaces are normally created */
1397
function compare_interface_friendly_names($a, $b) {
1398
	if ($a == $b) {
1399
		return 0;
1400
	} else if ($a == 'wan') {
1401
		return -1;
1402
	} else if ($b == 'wan') {
1403
		return 1;
1404
	} else if ($a == 'lan') {
1405
		return -1;
1406
	} else if ($b == 'lan') {
1407
		return 1;
1408
	}
1409

    
1410
	return strnatcmp($a, $b);
1411
}
1412

    
1413
/* return the configured interfaces list. */
1414
function get_configured_interface_list($withdisabled = false) {
1415
	global $config;
1416

    
1417
	$iflist = array();
1418

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

    
1426
	return $iflist;
1427
}
1428

    
1429
/* return the configured interfaces list. */
1430
function get_configured_interface_list_by_realif($withdisabled = false) {
1431
	global $config;
1432

    
1433
	$iflist = array();
1434

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

    
1445
	return $iflist;
1446
}
1447

    
1448
/* return the configured interfaces list with their description. */
1449
function get_configured_interface_with_descr($withdisabled = false) {
1450
	global $config, $user_settings;
1451

    
1452
	$iflist = array();
1453

    
1454
	/* if list */
1455
	foreach ($config['interfaces'] as $if => $ifdetail) {
1456
		if (isset($ifdetail['enable']) || $withdisabled == true) {
1457
			if (empty($ifdetail['descr'])) {
1458
				$iflist[$if] = strtoupper($if);
1459
			} else {
1460
				$iflist[$if] = strtoupper($ifdetail['descr']);
1461
			}
1462
		}
1463
	}
1464

    
1465
	if ($user_settings['webgui']['interfacessort']) {
1466
		asort($iflist);
1467
	}
1468

    
1469
	return $iflist;
1470
}
1471

    
1472
/*
1473
 *   get_configured_ip_addresses() - Return a list of all configured
1474
 *   IPv4 addresses.
1475
 *
1476
 */
1477
function get_configured_ip_addresses() {
1478
	global $config;
1479

    
1480
	if (!function_exists('get_interface_ip')) {
1481
		require_once("interfaces.inc");
1482
	}
1483
	$ip_array = array();
1484
	$interfaces = get_configured_interface_list();
1485
	if (is_array($interfaces)) {
1486
		foreach ($interfaces as $int) {
1487
			$ipaddr = get_interface_ip($int);
1488
			$ip_array[$int] = $ipaddr;
1489
		}
1490
	}
1491
	$interfaces = get_configured_vip_list('inet');
1492
	if (is_array($interfaces)) {
1493
		foreach ($interfaces as $int => $ipaddr) {
1494
			$ip_array[$int] = $ipaddr;
1495
		}
1496
	}
1497

    
1498
	/* pppoe server */
1499
	if (is_array($config['pppoes']) && is_array($config['pppoes']['pppoe'])) {
1500
		foreach ($config['pppoes']['pppoe'] as $pppoe) {
1501
			if ($pppoe['mode'] == "server") {
1502
				if (is_ipaddr($pppoe['localip'])) {
1503
					$int = "pppoes". $pppoe['pppoeid'];
1504
					$ip_array[$int] = $pppoe['localip'];
1505
				}
1506
			}
1507
		}
1508
	}
1509

    
1510
	return $ip_array;
1511
}
1512

    
1513
/*
1514
 *   get_configured_ipv6_addresses() - Return a list of all configured
1515
 *   IPv6 addresses.
1516
 *
1517
 */
1518
function get_configured_ipv6_addresses($linklocal_fallback = false) {
1519
	require_once("interfaces.inc");
1520
	$ipv6_array = array();
1521
	$interfaces = get_configured_interface_list();
1522
	if (is_array($interfaces)) {
1523
		foreach ($interfaces as $int) {
1524
			$ipaddrv6 = text_to_compressed_ip6(get_interface_ipv6($int, false, $linklocal_fallback));
1525
			$ipv6_array[$int] = $ipaddrv6;
1526
		}
1527
	}
1528
	$interfaces = get_configured_vip_list('inet6');
1529
	if (is_array($interfaces)) {
1530
		foreach ($interfaces as $int => $ipaddrv6) {
1531
			$ipv6_array[$int] = text_to_compressed_ip6($ipaddrv6);
1532
		}
1533
	}
1534
	return $ipv6_array;
1535
}
1536

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

    
1642
			case "friendly":
1643
				if ($friendly != "") {
1644
					$toput['if'] = $ifname;
1645
					$iflist[$friendly] = $toput;
1646
				}
1647
				break;
1648
			}
1649
		}
1650
	}
1651
	return $iflist;
1652
}
1653

    
1654
function get_lagg_interface_list() {
1655
	global $config;
1656

    
1657
	$plist = array();
1658
	if (isset($config['laggs']['lagg']) && is_array($config['laggs']['lagg'])) {
1659
		foreach ($config['laggs']['lagg'] as $lagg) {
1660
			$lagg['mac'] = get_interface_mac($lagg['laggif']);
1661
			$lagg['islagg'] = true;
1662
			$plist[$lagg['laggif']] = $lagg;
1663
		}
1664
	}
1665

    
1666
	return ($plist);
1667
}
1668

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

    
1691
/****f* util/log_auth
1692
* NAME
1693
*   log_auth   - Sends a string to syslog as LOG_AUTH facility
1694
* INPUTS
1695
*   $error     - string containing the syslog message.
1696
* RESULT
1697
*   null
1698
******/
1699
function log_auth($error) {
1700
	global $g;
1701
	$page = $_SERVER['SCRIPT_NAME'];
1702
	syslog(LOG_AUTH, "$page: $error");
1703
	if ($g['debug']) {
1704
		syslog(LOG_WARNING, var_dump(debug_backtrace()));
1705
	}
1706
	return;
1707
}
1708

    
1709
/****f* util/exec_command
1710
 * NAME
1711
 *   exec_command - Execute a command and return a string of the result.
1712
 * INPUTS
1713
 *   $command   - String of the command to be executed.
1714
 * RESULT
1715
 *   String containing the command's result.
1716
 * NOTES
1717
 *   This function returns the command's stdout and stderr.
1718
 ******/
1719
function exec_command($command) {
1720
	$output = array();
1721
	exec($command . ' 2>&1', $output);
1722
	return(implode("\n", $output));
1723
}
1724

    
1725
/* wrapper for exec()
1726
   Executes in background or foreground.
1727
   For background execution, returns PID of background process to allow calling code control */
1728
function mwexec($command, $nologentry = false, $clearsigmask = false, $background = false) {
1729
	global $g;
1730
	$retval = 0;
1731

    
1732
	if ($g['debug']) {
1733
		if (!$_SERVER['REMOTE_ADDR']) {
1734
			echo "mwexec(): $command" . ($background ? " [BG]":"") . "\n";
1735
		}
1736
	}
1737
	if ($clearsigmask) {
1738
		$oldset = array();
1739
		pcntl_sigprocmask(SIG_SETMASK, array(), $oldset);
1740
	}
1741

    
1742
	if ($background) {
1743
		// start background process and return PID
1744
		$retval = exec("/usr/bin/nohup $command > /dev/null 2>&1 & echo $!");
1745
	} else {
1746
		// run in foreground, and (optionally) log if nonzero return
1747
		$outputarray = array();
1748
		exec("$command 2>&1", $outputarray, $retval);
1749
		if (($retval <> 0) && (!$nologentry || isset($config['system']['developerspew']))) {
1750
			log_error(sprintf(gettext("The command '%1\$s' returned exit code '%2\$d', the output was '%3\$s' "), $command, $retval, implode(" ", $outputarray)));
1751
		}
1752
	}
1753

    
1754
	if ($clearsigmask) {
1755
		pcntl_sigprocmask(SIG_SETMASK, $oldset);
1756
	}
1757

    
1758
	return $retval;
1759
}
1760

    
1761
/* wrapper for exec() in background */
1762
function mwexec_bg($command, $clearsigmask = false) {
1763
	return mwexec($command, false, $clearsigmask, true);
1764
}
1765

    
1766
/*	unlink a file, or pattern-match of a file, if it exists
1767
	if the file/path contains glob() compatible wildcards, all matching files will be unlinked
1768
	any warning/errors are suppressed (e.g. no matching files to delete)
1769
	If there are matching file(s) and they were all unlinked OK, then return true.
1770
	Otherwise return false (the requested file(s) did not exist, or could not be deleted)
1771
	This allows the caller to know if they were the one to successfully delete the file(s).
1772
*/
1773
function unlink_if_exists($fn) {
1774
	$to_do = glob($fn);
1775
	if (is_array($to_do) && count($to_do) > 0) {
1776
		// Returns an array of true/false indicating if each unlink worked
1777
		$results = @array_map("unlink", $to_do);
1778
		// If there is no false in the array, then all went well
1779
		$result = !in_array(false, $results, true);
1780
	} else {
1781
		$result = @unlink($fn);
1782
	}
1783
	return $result;
1784
}
1785
/* make a global alias table (for faster lookups) */
1786
function alias_make_table($config) {
1787
	global $aliastable;
1788

    
1789
	$aliastable = array();
1790

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

    
1800
/* check if an alias exists */
1801
function is_alias($name) {
1802
	global $aliastable;
1803

    
1804
	return isset($aliastable[$name]);
1805
}
1806

    
1807
function alias_get_type($name) {
1808
	global $config;
1809

    
1810
	if (is_array($config['aliases']['alias'])) {
1811
		foreach ($config['aliases']['alias'] as $alias) {
1812
			if ($name == $alias['name']) {
1813
				return $alias['type'];
1814
			}
1815
		}
1816
	}
1817

    
1818
	return "";
1819
}
1820

    
1821
/* expand a host or network alias, if necessary */
1822
function alias_expand($name) {
1823
	global $config, $aliastable;
1824
	$urltable_prefix = "/var/db/aliastables/";
1825
	$urltable_filename = $urltable_prefix . $name . ".txt";
1826

    
1827
	if (isset($aliastable[$name])) {
1828
		// alias names cannot be strictly numeric. redmine #4289
1829
		if (is_numericint($name)) {
1830
			return null;
1831
		}
1832
		// make sure if it's a ports alias, it actually exists. redmine #5845
1833
		foreach ($config['aliases']['alias'] as $alias) {
1834
			if ($alias['name'] == $name) {
1835
				if ($alias['type'] == "urltable_ports") {
1836
					if (is_URL($alias['url']) && file_exists($urltable_filename) && filesize($urltable_filename)) {
1837
						return "\${$name}";
1838
					} else {
1839
						return null;
1840
					}
1841
				}
1842
			}
1843
		}
1844
		return "\${$name}";
1845
	} else if (is_ipaddr($name) || is_subnet($name) || is_port_or_range($name)) {
1846
		return "{$name}";
1847
	} else {
1848
		return null;
1849
	}
1850
}
1851

    
1852
function alias_expand_urltable($name) {
1853
	global $config;
1854
	$urltable_prefix = "/var/db/aliastables/";
1855
	$urltable_filename = $urltable_prefix . $name . ".txt";
1856

    
1857
	if (is_array($config['aliases']['alias'])) {
1858
		foreach ($config['aliases']['alias'] as $alias) {
1859
			if (preg_match("/urltable/i", $alias['type']) && ($alias['name'] == $name)) {
1860
				if (is_URL($alias["url"]) && file_exists($urltable_filename)) {
1861
					if (!filesize($urltable_filename)) {
1862
						// file exists, but is empty, try to sync
1863
						send_event("service sync alias {$name}");
1864
					}
1865
					return $urltable_filename;
1866
				} else {
1867
					send_event("service sync alias {$name}");
1868
					break;
1869
				}
1870
			}
1871
		}
1872
	}
1873
	return null;
1874
}
1875

    
1876
/* obtain MAC address given an IP address by looking at the ARP/NDP table */
1877
function arp_get_mac_by_ip($ip, $do_ping = true) {
1878
	unset($macaddr);
1879
	$retval = 1;
1880
	switch (is_ipaddr($ip)) {
1881
		case 4:
1882
			if ($do_ping === true) {
1883
				mwexec("/sbin/ping -c 1 -t 1 " . escapeshellarg($ip), true);
1884
			}
1885
			$macaddr = exec("/usr/sbin/arp -n " . escapeshellarg($ip) . " | /usr/bin/awk '{print $4}'", $output, $retval);
1886
			break;
1887
		case 6:
1888
			if ($do_ping === true) {
1889
				mwexec("/sbin/ping6 -c 1 -X 1 " . escapeshellarg($ip), true);
1890
			}
1891
			$macaddr = exec("/usr/sbin/ndp -n " . escapeshellarg($ip) . " | /usr/bin/awk '{print $2}'", $output, $retval);
1892
			break;
1893
	}
1894
	if ($retval == 0 && is_macaddr($macaddr)) {
1895
		return $macaddr;
1896
	} else {
1897
		return false;
1898
	}
1899
}
1900

    
1901
/* return a fieldname that is safe for xml usage */
1902
function xml_safe_fieldname($fieldname) {
1903
	$replace = array(
1904
	    '/', '-', ' ', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')',
1905
	    '_', '+', '=', '{', '}', '[', ']', '|', '/', '<', '>', '?',
1906
	    ':', ',', '.', '\'', '\\'
1907
	);
1908
	return strtolower(str_replace($replace, "", $fieldname));
1909
}
1910

    
1911
function mac_format($clientmac) {
1912
	global $config, $cpzone;
1913

    
1914
	$mac = explode(":", $clientmac);
1915
	$mac_format = $cpzone ? $config['captiveportal'][$cpzone]['radmac_format'] : false;
1916

    
1917
	switch ($mac_format) {
1918
		case 'singledash':
1919
			return "$mac[0]$mac[1]$mac[2]-$mac[3]$mac[4]$mac[5]";
1920

    
1921
		case 'ietf':
1922
			return "$mac[0]-$mac[1]-$mac[2]-$mac[3]-$mac[4]-$mac[5]";
1923

    
1924
		case 'cisco':
1925
			return "$mac[0]$mac[1].$mac[2]$mac[3].$mac[4]$mac[5]";
1926

    
1927
		case 'unformatted':
1928
			return "$mac[0]$mac[1]$mac[2]$mac[3]$mac[4]$mac[5]";
1929

    
1930
		default:
1931
			return $clientmac;
1932
	}
1933
}
1934

    
1935
function resolve_retry($hostname, $retries = 5) {
1936

    
1937
	if (is_ipaddr($hostname)) {
1938
		return $hostname;
1939
	}
1940

    
1941
	for ($i = 0; $i < $retries; $i++) {
1942
		// FIXME: gethostbyname does not work for AAAA hostnames, boo, hiss
1943
		$ip = gethostbyname($hostname);
1944

    
1945
		if ($ip && $ip != $hostname) {
1946
			/* success */
1947
			return $ip;
1948
		}
1949

    
1950
		sleep(1);
1951
	}
1952

    
1953
	return false;
1954
}
1955

    
1956
function format_bytes($bytes) {
1957
	if ($bytes >= 1099511627776) {
1958
		return sprintf("%.2f TiB", $bytes/1099511627776);
1959
	} else if ($bytes >= 1073741824) {
1960
		return sprintf("%.2f GiB", $bytes/1073741824);
1961
	} else if ($bytes >= 1048576) {
1962
		return sprintf("%.2f MiB", $bytes/1048576);
1963
	} else if ($bytes >= 1024) {
1964
		return sprintf("%.0f KiB", $bytes/1024);
1965
	} else {
1966
		return sprintf("%d B", $bytes);
1967
	}
1968
}
1969

    
1970
function format_number($num, $precision = 3) {
1971
	$units = array('', 'K', 'M', 'G', 'T');
1972

    
1973
	$i = 0;
1974
	while ($num > 1000 && $i < count($units)) {
1975
		$num /= 1000;
1976
		$i++;
1977
	}
1978
	$num = round($num, $precision);
1979

    
1980
	return ("$num {$units[$i]}");
1981
}
1982

    
1983
function update_filter_reload_status($text, $new=false) {
1984
	global $g;
1985

    
1986
	if ($new) {
1987
		file_put_contents("{$g['varrun_path']}/filter_reload_status", $text  . PHP_EOL);
1988
	} else {
1989
		file_put_contents("{$g['varrun_path']}/filter_reload_status", $text  . PHP_EOL, FILE_APPEND);
1990
	}
1991
}
1992

    
1993
/****** util/return_dir_as_array
1994
 * NAME
1995
 *   return_dir_as_array - Return a directory's contents as an array.
1996
 * INPUTS
1997
 *   $dir          - string containing the path to the desired directory.
1998
 *   $filter_regex - string containing a regular expression to filter file names. Default empty.
1999
 * RESULT
2000
 *   $dir_array - array containing the directory's contents. This array will be empty if the path specified is invalid.
2001
 ******/
2002
function return_dir_as_array($dir, $filter_regex = '') {
2003
	$dir_array = array();
2004
	if (is_dir($dir)) {
2005
		if ($dh = opendir($dir)) {
2006
			while (($file = readdir($dh)) !== false) {
2007
				if (($file == ".") || ($file == "..")) {
2008
					continue;
2009
				}
2010

    
2011
				if (empty($filter_regex) || preg_match($filter_regex, $file)) {
2012
					array_push($dir_array, $file);
2013
				}
2014
			}
2015
			closedir($dh);
2016
		}
2017
	}
2018
	return $dir_array;
2019
}
2020

    
2021
function run_plugins($directory) {
2022
	global $config, $g;
2023

    
2024
	/* process packager manager custom rules */
2025
	$files = return_dir_as_array($directory);
2026
	if (is_array($files)) {
2027
		foreach ($files as $file) {
2028
			if (stristr($file, ".sh") == true) {
2029
				mwexec($directory . $file . " start");
2030
			} else if (!is_dir($directory . "/" . $file) && stristr($file, ".inc")) {
2031
				require_once($directory . "/" . $file);
2032
			}
2033
		}
2034
	}
2035
}
2036

    
2037
/*
2038
 *    safe_mkdir($path, $mode = 0755)
2039
 *    create directory if it doesn't already exist and isn't a file!
2040
 */
2041
function safe_mkdir($path, $mode = 0755) {
2042
	global $g;
2043

    
2044
	if (!is_file($path) && !is_dir($path)) {
2045
		return @mkdir($path, $mode, true);
2046
	} else {
2047
		return false;
2048
	}
2049
}
2050

    
2051
/*
2052
 * get_sysctl($names)
2053
 * Get values of sysctl OID's listed in $names (accepts an array or a single
2054
 * name) and return an array of key/value pairs set for those that exist
2055
 */
2056
function get_sysctl($names) {
2057
	if (empty($names)) {
2058
		return array();
2059
	}
2060

    
2061
	if (is_array($names)) {
2062
		$name_list = array();
2063
		foreach ($names as $name) {
2064
			$name_list[] = escapeshellarg($name);
2065
		}
2066
	} else {
2067
		$name_list = array(escapeshellarg($names));
2068
	}
2069

    
2070
	exec("/sbin/sysctl -iq " . implode(" ", $name_list), $output);
2071
	$values = array();
2072
	foreach ($output as $line) {
2073
		$line = explode(": ", $line, 2);
2074
		if (count($line) == 2) {
2075
			$values[$line[0]] = $line[1];
2076
		}
2077
	}
2078

    
2079
	return $values;
2080
}
2081

    
2082
/*
2083
 * get_single_sysctl($name)
2084
 * Wrapper for get_sysctl() to simplify read of a single sysctl value
2085
 * return the value for sysctl $name or empty string if it doesn't exist
2086
 */
2087
function get_single_sysctl($name) {
2088
	if (empty($name)) {
2089
		return "";
2090
	}
2091

    
2092
	$value = get_sysctl($name);
2093
	if (empty($value) || !isset($value[$name])) {
2094
		return "";
2095
	}
2096

    
2097
	return $value[$name];
2098
}
2099

    
2100
/*
2101
 * set_sysctl($value_list)
2102
 * Set sysctl OID's listed as key/value pairs and return
2103
 * an array with keys set for those that succeeded
2104
 */
2105
function set_sysctl($values) {
2106
	if (empty($values)) {
2107
		return array();
2108
	}
2109

    
2110
	$value_list = array();
2111
	foreach ($values as $key => $value) {
2112
		$value_list[] = escapeshellarg($key) . "=" . escapeshellarg($value);
2113
	}
2114

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

    
2117
	/* Retry individually if failed (one or more read-only) */
2118
	if ($success <> 0 && count($value_list) > 1) {
2119
		foreach ($value_list as $value) {
2120
			exec("/sbin/sysctl -iq " . $value, $output);
2121
		}
2122
	}
2123

    
2124
	$ret = array();
2125
	foreach ($output as $line) {
2126
		$line = explode(": ", $line, 2);
2127
		if (count($line) == 2) {
2128
			$ret[$line[0]] = true;
2129
		}
2130
	}
2131

    
2132
	return $ret;
2133
}
2134

    
2135
/*
2136
 * set_single_sysctl($name, $value)
2137
 * Wrapper to set_sysctl() to make it simple to set only one sysctl
2138
 * returns boolean meaning if it succeeded
2139
 */
2140
function set_single_sysctl($name, $value) {
2141
	if (empty($name)) {
2142
		return false;
2143
	}
2144

    
2145
	$result = set_sysctl(array($name => $value));
2146

    
2147
	if (!isset($result[$name]) || $result[$name] != $value) {
2148
		return false;
2149
	}
2150

    
2151
	return true;
2152
}
2153

    
2154
/*
2155
 *     get_memory()
2156
 *     returns an array listing the amount of
2157
 *     memory installed in the hardware
2158
 *     [0] net memory available for the OS (FreeBSD) after some is taken by BIOS, video or whatever - e.g. 235 MBytes
2159
 *     [1] real (actual) memory of the system, should be the size of the RAM card/s - e.g. 256 MBytes
2160
 */
2161
function get_memory() {
2162
	$physmem = get_single_sysctl("hw.physmem");
2163
	$realmem = get_single_sysctl("hw.realmem");
2164
	/* convert from bytes to megabytes */
2165
	return array(($physmem/1048576), ($realmem/1048576));
2166
}
2167

    
2168
function mute_kernel_msgs() {
2169
	global $g, $config;
2170

    
2171
	if ($config['system']['enableserial']) {
2172
		return;
2173
	}
2174
	exec("/sbin/conscontrol mute on");
2175
}
2176

    
2177
function unmute_kernel_msgs() {
2178
	global $g;
2179

    
2180
	exec("/sbin/conscontrol mute off");
2181
}
2182

    
2183
function start_devd() {
2184
	global $g;
2185

    
2186
	/* Generate hints for the kernel loader. */
2187
	$module_paths = explode(";", get_single_sysctl("kern.module_path"));
2188
	foreach ($module_paths as $id => $path) {
2189
		if (!is_dir($path) || file_exists("{$path}/linker.hints")) {
2190
			continue;
2191
		}
2192
		if (($files = scandir($path)) == false) {
2193
			continue;
2194
		}
2195
		$found = false;
2196
		foreach ($files as $id => $file) {
2197
			if (strlen($file) > 3 &&
2198
			    strcasecmp(substr($file, -3), ".ko") == 0) {
2199
				$found = true;
2200
				break;
2201
			}
2202
		}
2203
		if ($found == false) {
2204
			continue;
2205
		}
2206
		$_gb = exec("/usr/sbin/kldxref $path");
2207
		unset($_gb);
2208
	}
2209

    
2210
	/* Use the undocumented -q options of devd to quiet its log spamming */
2211
	$_gb = exec("/sbin/devd -q -f /etc/{$g['product_name']}-devd.conf");
2212
	sleep(1);
2213
	unset($_gb);
2214
}
2215

    
2216
function is_interface_vlan_mismatch() {
2217
	global $config, $g;
2218

    
2219
	if (is_array($config['vlans']['vlan'])) {
2220
		foreach ($config['vlans']['vlan'] as $vlan) {
2221
			if (substr($vlan['if'], 0, 4) == "lagg") {
2222
				return false;
2223
			}
2224
			if (does_interface_exist($vlan['if']) == false) {
2225
				return true;
2226
			}
2227
		}
2228
	}
2229

    
2230
	return false;
2231
}
2232

    
2233
function is_interface_mismatch() {
2234
	global $config, $g;
2235

    
2236
	$do_assign = false;
2237
	$i = 0;
2238
	$missing_interfaces = array();
2239
	if (is_array($config['interfaces'])) {
2240
		foreach ($config['interfaces'] as $ifname => $ifcfg) {
2241
			if (interface_is_vlan($ifcfg['if']) != NULL ||
2242
			    interface_is_qinq($ifcfg['if']) != NULL ||
2243
			    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'])) {
2244
				// Do not check these interfaces.
2245
				$i++;
2246
				continue;
2247
			} else if (does_interface_exist($ifcfg['if']) == false) {
2248
				$missing_interfaces[] = $ifcfg['if'];
2249
				$do_assign = true;
2250
			} else {
2251
				$i++;
2252
			}
2253
		}
2254
	}
2255

    
2256
	if (file_exists("{$g['tmp_path']}/assign_complete")) {
2257
		$do_assign = false;
2258
	}
2259

    
2260
	if (!empty($missing_interfaces) && $do_assign) {
2261
		file_put_contents("{$g['tmp_path']}/missing_interfaces", implode(' ', $missing_interfaces));
2262
	} else {
2263
		@unlink("{$g['tmp_path']}/missing_interfaces");
2264
	}
2265

    
2266
	return $do_assign;
2267
}
2268

    
2269
/* sync carp entries to other firewalls */
2270
function carp_sync_client() {
2271
	global $g;
2272
	send_event("filter sync");
2273
}
2274

    
2275
/****f* util/isAjax
2276
 * NAME
2277
 *   isAjax - reports if the request is driven from prototype
2278
 * INPUTS
2279
 *   none
2280
 * RESULT
2281
 *   true/false
2282
 ******/
2283
function isAjax() {
2284
	return isset ($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest';
2285
}
2286

    
2287
/****f* util/timeout
2288
 * NAME
2289
 *   timeout - console input with timeout countdown. Note: erases 2 char of screen for timer. Leave space.
2290
 * INPUTS
2291
 *   optional, seconds to wait before timeout. Default 9 seconds.
2292
 * RESULT
2293
 *   returns 1 char of user input or null if no input.
2294
 ******/
2295
function timeout($timer = 9) {
2296
	while (!isset($key)) {
2297
		if ($timer >= 9) {
2298
			echo chr(8) . chr(8) . ($timer == 9 ? chr(32) : null) . "{$timer}";
2299
		} else {
2300
			echo chr(8). "{$timer}";
2301
		}
2302
		`/bin/stty -icanon min 0 time 25`;
2303
		$key = trim(`KEY=\`dd count=1 2>/dev/null\`; echo \$KEY`);
2304
		`/bin/stty icanon`;
2305
		if ($key == '') {
2306
			unset($key);
2307
		}
2308
		$timer--;
2309
		if ($timer == 0) {
2310
			break;
2311
		}
2312
	}
2313
	return $key;
2314
}
2315

    
2316
/****f* util/msort
2317
 * NAME
2318
 *   msort - sort array
2319
 * INPUTS
2320
 *   $array to be sorted, field to sort by, direction of sort
2321
 * RESULT
2322
 *   returns newly sorted array
2323
 ******/
2324
function msort($array, $id = "id", $sort_ascending = true) {
2325
	$temp_array = array();
2326
	if (!is_array($array)) {
2327
		return $temp_array;
2328
	}
2329
	while (count($array)>0) {
2330
		$lowest_id = 0;
2331
		$index = 0;
2332
		foreach ($array as $item) {
2333
			if (isset($item[$id])) {
2334
				if ($array[$lowest_id][$id]) {
2335
					if (strtolower($item[$id]) < strtolower($array[$lowest_id][$id])) {
2336
						$lowest_id = $index;
2337
					}
2338
				}
2339
			}
2340
			$index++;
2341
		}
2342
		$temp_array[] = $array[$lowest_id];
2343
		$array = array_merge(array_slice($array, 0, $lowest_id), array_slice($array, $lowest_id + 1));
2344
	}
2345
	if ($sort_ascending) {
2346
		return $temp_array;
2347
	} else {
2348
		return array_reverse($temp_array);
2349
	}
2350
}
2351

    
2352
/****f* util/is_URL
2353
 * NAME
2354
 *   is_URL
2355
 * INPUTS
2356
 *   string to check
2357
 * RESULT
2358
 *   Returns true if item is a URL
2359
 ******/
2360
function is_URL($url) {
2361
	$match = preg_match("'\b(([\w-]+://?|www[.])[^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|/)))'", $url);
2362
	if ($match) {
2363
		return true;
2364
	}
2365
	return false;
2366
}
2367

    
2368
function is_file_included($file = "") {
2369
	$files = get_included_files();
2370
	if (in_array($file, $files)) {
2371
		return true;
2372
	}
2373

    
2374
	return false;
2375
}
2376

    
2377
/*
2378
 * Replace a value on a deep associative array using regex
2379
 */
2380
function array_replace_values_recursive($data, $match, $replace) {
2381
	if (empty($data)) {
2382
		return $data;
2383
	}
2384

    
2385
	if (is_string($data)) {
2386
		$data = preg_replace("/{$match}/", $replace, $data);
2387
	} else if (is_array($data)) {
2388
		foreach ($data as $k => $v) {
2389
			$data[$k] = array_replace_values_recursive($v, $match, $replace);
2390
		}
2391
	}
2392

    
2393
	return $data;
2394
}
2395

    
2396
/*
2397
	This function was borrowed from a comment on PHP.net at the following URL:
2398
	http://www.php.net/manual/en/function.array-merge-recursive.php#73843
2399
 */
2400
function array_merge_recursive_unique($array0, $array1) {
2401

    
2402
	$arrays = func_get_args();
2403
	$remains = $arrays;
2404

    
2405
	// We walk through each arrays and put value in the results (without
2406
	// considering previous value).
2407
	$result = array();
2408

    
2409
	// loop available array
2410
	foreach ($arrays as $array) {
2411

    
2412
		// The first remaining array is $array. We are processing it. So
2413
		// we remove it from remaining arrays.
2414
		array_shift($remains);
2415

    
2416
		// We don't care non array param, like array_merge since PHP 5.0.
2417
		if (is_array($array)) {
2418
			// Loop values
2419
			foreach ($array as $key => $value) {
2420
				if (is_array($value)) {
2421
					// we gather all remaining arrays that have such key available
2422
					$args = array();
2423
					foreach ($remains as $remain) {
2424
						if (array_key_exists($key, $remain)) {
2425
							array_push($args, $remain[$key]);
2426
						}
2427
					}
2428

    
2429
					if (count($args) > 2) {
2430
						// put the recursion
2431
						$result[$key] = call_user_func_array(__FUNCTION__, $args);
2432
					} else {
2433
						foreach ($value as $vkey => $vval) {
2434
							if (!is_array($result[$key])) {
2435
								$result[$key] = array();
2436
							}
2437
							$result[$key][$vkey] = $vval;
2438
						}
2439
					}
2440
				} else {
2441
					// simply put the value
2442
					$result[$key] = $value;
2443
				}
2444
			}
2445
		}
2446
	}
2447
	return $result;
2448
}
2449

    
2450

    
2451
/*
2452
 * converts a string like "a,b,c,d"
2453
 * into an array like array("a" => "b", "c" => "d")
2454
 */
2455
function explode_assoc($delimiter, $string) {
2456
	$array = explode($delimiter, $string);
2457
	$result = array();
2458
	$numkeys = floor(count($array) / 2);
2459
	for ($i = 0; $i < $numkeys; $i += 1) {
2460
		$result[$array[$i * 2]] = $array[$i * 2 + 1];
2461
	}
2462
	return $result;
2463
}
2464

    
2465
/*
2466
 * Given a string of text with some delimiter, look for occurrences
2467
 * of some string and replace all of those.
2468
 * $text - the text string (e.g. "abc,defg,x123,ipv4,xyz")
2469
 * $delimiter - the delimiter (e.g. ",")
2470
 * $element - the element to match (e.g. "defg")
2471
 * $replacement - the string to replace it with (e.g. "42")
2472
 * Returns the resulting delimited string (e.g. "abc,42,x123,ipv4,xyz")
2473
 */
2474
function replace_element_in_list($text, $delimiter, $element, $replacement) {
2475
	$textArray = explode($delimiter, $text);
2476
	while (($entry = array_search($element, $textArray)) !== false) {
2477
		$textArray[$entry] = $replacement;
2478
	}
2479
	return implode(',', $textArray);
2480
}
2481

    
2482
/* Try to change a static route, if it doesn't exist, add it */
2483
function route_add_or_change($args) {
2484
	global $config;
2485

    
2486
	if (empty($args)) {
2487
		return false;
2488
	}
2489

    
2490
	/* First, try to add it */
2491
	$_gb = exec(escapeshellcmd("/sbin/route add " . $args), $output, $rc);
2492
		
2493
	if (isset($config['system']['route-debug'])) {
2494
		$add_change = 'add';
2495
		$mt = microtime();
2496
		log_error("ROUTING debug: $mt - ADD RC={$rc} - $args");
2497
	}
2498

    
2499
	if ($rc != 0) {
2500
		/* If it fails, try to change it */
2501
		$_gb = exec(escapeshellcmd("/sbin/route change " . $args),
2502
		    $output, $rc);
2503

    
2504
		if (isset($config['system']['route-debug'])) {
2505
			$add_change = 'change';
2506
			$mt = microtime();
2507
			log_error("ROUTING debug: $mt - CHG RC={$rc} - $args");
2508
		}
2509
	}
2510
	if (isset($config['system']['route-debug'])) {
2511
		file_put_contents("/dev/console", "\n[".getmypid()."] ROUTE: {$add_change} {$args} result: {$rc}");
2512
	}
2513

    
2514
	return ($rc == 0);
2515
}
2516

    
2517
function alias_to_subnets_recursive($name, $returnhostnames = false) {
2518
	global $aliastable;
2519
	$result = array();
2520
	if (!isset($aliastable[$name])) {
2521
		return $result;
2522
	}
2523
	$subnets = preg_split('/\s+/', $aliastable[$name]);
2524
	foreach ($subnets as $net) {
2525
		if (is_alias($net)) {
2526
			$sub = alias_to_subnets_recursive($net, $returnhostnames);
2527
			$result = array_merge($result, $sub);
2528
			continue;
2529
		} elseif (!is_subnet($net)) {
2530
			if (is_ipaddrv4($net)) {
2531
				$net .= "/32";
2532
			} else if (is_ipaddrv6($net)) {
2533
				$net .= "/128";
2534
			} else if ($returnhostnames === false || !is_fqdn($net)) {
2535
				continue;
2536
			}
2537
		}
2538
		$result[] = $net;
2539
	}
2540
	return $result;
2541
}
2542

    
2543
function get_staticroutes($returnsubnetsonly = false, $returnhostnames = false, $returnenabledroutesonly = false) {
2544
	global $config, $aliastable;
2545

    
2546
	/* Bail if there are no routes, but return an array always so callers don't have to check. */
2547
	if (!is_array($config['staticroutes']['route'])) {
2548
		return array();
2549
	}
2550

    
2551
	$allstaticroutes = array();
2552
	$allsubnets = array();
2553
	/* Loop through routes and expand aliases as we find them. */
2554
	foreach ($config['staticroutes']['route'] as $route) {
2555
		if ($returnenabledroutesonly && isset($route['disabled'])) {
2556
			continue;
2557
		}
2558

    
2559
		if (is_alias($route['network'])) {
2560
			foreach (alias_to_subnets_recursive($route['network'], $returnhostnames) as $net) {
2561
				$temproute = $route;
2562
				$temproute['network'] = $net;
2563
				$allstaticroutes[] = $temproute;
2564
				$allsubnets[] = $net;
2565
			}
2566
		} elseif (is_subnet($route['network'])) {
2567
			$allstaticroutes[] = $route;
2568
			$allsubnets[] = $route['network'];
2569
		}
2570
	}
2571
	if ($returnsubnetsonly) {
2572
		return $allsubnets;
2573
	} else {
2574
		return $allstaticroutes;
2575
	}
2576
}
2577

    
2578
/****f* util/get_alias_list
2579
 * NAME
2580
 *   get_alias_list - Provide a list of aliases.
2581
 * INPUTS
2582
 *   $type          - Optional, can be a string or array specifying what type(s) of aliases you need.
2583
 * RESULT
2584
 *   Array containing list of aliases.
2585
 *   If $type is unspecified, all aliases are returned.
2586
 *   If $type is a string, all aliases of the type specified in $type are returned.
2587
 *   If $type is an array, all aliases of any type specified in any element of $type are returned.
2588
 */
2589
function get_alias_list($type = null) {
2590
	global $config;
2591
	$result = array();
2592
	if ($config['aliases']['alias'] <> "" && is_array($config['aliases']['alias'])) {
2593
		foreach ($config['aliases']['alias'] as $alias) {
2594
			if ($type === null) {
2595
				$result[] = $alias['name'];
2596
			} else if (is_array($type)) {
2597
				if (in_array($alias['type'], $type)) {
2598
					$result[] = $alias['name'];
2599
				}
2600
			} else if ($type === $alias['type']) {
2601
				$result[] = $alias['name'];
2602
			}
2603
		}
2604
	}
2605
	return $result;
2606
}
2607

    
2608
/* returns an array consisting of every element of $haystack that is not equal to $needle. */
2609
function array_exclude($needle, $haystack) {
2610
	$result = array();
2611
	if (is_array($haystack)) {
2612
		foreach ($haystack as $thing) {
2613
			if ($needle !== $thing) {
2614
				$result[] = $thing;
2615
			}
2616
		}
2617
	}
2618
	return $result;
2619
}
2620

    
2621
/* Define what is preferred, IPv4 or IPv6 */
2622
function prefer_ipv4_or_ipv6() {
2623
	global $config;
2624

    
2625
	if (isset($config['system']['prefer_ipv4'])) {
2626
		mwexec("/etc/rc.d/ip6addrctl prefer_ipv4");
2627
	} else {
2628
		mwexec("/etc/rc.d/ip6addrctl prefer_ipv6");
2629
	}
2630
}
2631

    
2632
/* Redirect to page passing parameters via POST */
2633
function post_redirect($page, $params) {
2634
	if (!is_array($params)) {
2635
		return;
2636
	}
2637

    
2638
	print "<html><body><form action=\"{$page}\" name=\"formredir\" method=\"post\">\n";
2639
	foreach ($params as $key => $value) {
2640
		print "<input type=\"hidden\" name=\"{$key}\" value=\"{$value}\" />\n";
2641
	}
2642
	print "</form>\n";
2643
	print "<script type=\"text/javascript\">\n";
2644
	print "//<![CDATA[\n";
2645
	print "document.formredir.submit();\n";
2646
	print "//]]>\n";
2647
	print "</script>\n";
2648
	print "</body></html>\n";
2649
}
2650

    
2651
/* Locate disks that can be queried for S.M.A.R.T. data. */
2652
function get_smart_drive_list() {
2653
	/* SMART supports some disks directly, and some controllers directly,
2654
	 * See https://redmine.pfsense.org/issues/9042 */
2655
	$supported_disk_types = array("ad", "da", "ada");
2656
	$supported_controller_types = array("nvme");
2657
	$disk_list = explode(" ", get_single_sysctl("kern.disks"));
2658
	foreach ($disk_list as $id => $disk) {
2659
		// We only want certain kinds of disks for S.M.A.R.T.
2660
		// 1 is a match, 0 is no match, False is any problem processing the regex
2661
		if (preg_match("/^(" . implode("|", $supported_disk_types) . ").*[0-9]{1,2}$/", $disk) !== 1) {
2662
			unset($disk_list[$id]);
2663
			continue;
2664
		}
2665
	}
2666
	foreach ($supported_controller_types as $controller) {
2667
		$devices = glob("/dev/{$controller}*");
2668
		if (!is_array($devices)) {
2669
			continue;
2670
		}
2671
		foreach ($devices as $device) {
2672
			$disk_list[] = basename($device);
2673
		}
2674
	}
2675
	sort($disk_list);
2676
	return $disk_list;
2677
}
2678

    
2679
// Validate a network address
2680
//	$addr: the address to validate
2681
//	$type: IPV4|IPV6|IPV4V6
2682
//	$label: the label used by the GUI to display this value. Required to compose an error message
2683
//	$err_msg: pointer to the callers error message array so that error messages can be added to it here
2684
//	$alias: are aliases permitted for this address?
2685
// Returns:
2686
//	IPV4 - if $addr is a valid IPv4 address
2687
//	IPV6 - if $addr is a valid IPv6 address
2688
//	ALIAS - if $alias=true and $addr is an alias
2689
//	false - otherwise
2690

    
2691
function validateipaddr(&$addr, $type, $label, &$err_msg, $alias=false) {
2692
	switch ($type) {
2693
		case IPV4:
2694
			if (is_ipaddrv4($addr)) {
2695
				return IPV4;
2696
			} else if ($alias) {
2697
				if (is_alias($addr)) {
2698
					return ALIAS;
2699
				} else {
2700
					$err_msg[] = sprintf(gettext("%s must be a valid IPv4 address or alias."), $label);
2701
					return false;
2702
				}
2703
			} else {
2704
				$err_msg[] = sprintf(gettext("%s must be a valid IPv4 address."), $label);
2705
				return false;
2706
			}
2707
		break;
2708
		case IPV6:
2709
			if (is_ipaddrv6($addr)) {
2710
				$addr = strtolower($addr);
2711
				return IPV6;
2712
			} else if ($alias) {
2713
				if (is_alias($addr)) {
2714
					return ALIAS;
2715
				} else {
2716
					$err_msg[] = sprintf(gettext("%s must be a valid IPv6 address or alias."), $label);
2717
					return false;
2718
				}
2719
			} else {
2720
				$err_msg[] = sprintf(gettext("%s must be a valid IPv6 address."), $label);
2721
				return false;
2722
			}
2723
		break;
2724
		case IPV4V6:
2725
			if (is_ipaddrv6($addr)) {
2726
				$addr = strtolower($addr);
2727
				return IPV6;
2728
			} else if (is_ipaddrv4($addr)) {
2729
				return IPV4;
2730
			} else if ($alias) {
2731
				if (is_alias($addr)) {
2732
					return ALIAS;
2733
				} else {
2734
					$err_msg[] = sprintf(gettext("%s must be a valid IPv4 or IPv6 address or alias."), $label);
2735
					return false;
2736
				}
2737
			} else {
2738
				$err_msg[] = sprintf(gettext("%s must be a valid IPv4 or IPv6 address."), $label);
2739
				return false;
2740
			}
2741
		break;
2742
	}
2743

    
2744
	return false;
2745
}
2746

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

    
2769
	/* Make hexstrings */
2770
	if ($duidtype) {
2771
		switch ($duidtype) {
2772
		/* Add a hardware type to DUID-LLT and DUID-LL; assume Ethernet */
2773
		case 1:
2774
		case 3:
2775
			$duidpt1 = '00:01:' . $duidpt1;
2776
			break;
2777
		/* Remove '-' from given UUID and insert ':' every 2 characters */
2778
		case 4:
2779
			$duidpt1 = implode(':', str_split(str_replace('-', '', $duidpt1), 2));
2780
			break;
2781
		default:
2782
		}
2783
		$duidpt1 = '00:0' . $duidtype . ':' . $duidpt1;
2784
	}
2785

    
2786
	$values = explode(':', strtolower(str_replace('-', ':', $duidpt1)));
2787

    
2788
	if (hexdec($values[0]) != count($values) - 2)
2789
		array_unshift($values, dechex(count($values)), '00');
2790

    
2791
	array_walk($values, function(&$value) {
2792
		$value = str_pad($value, 2, '0', STR_PAD_LEFT);
2793
	});
2794

    
2795
	return implode(":", $values);
2796
}
2797

    
2798
/* Returns true if $dhcp6duid is a valid DUID entry.
2799
 * Parse the entry to check for valid length according to known DUID types.
2800
 */
2801
function is_duid($dhcp6duid) {
2802
	$values = explode(":", $dhcp6duid);
2803
	if (hexdec($values[0]) == count($values) - 2) {
2804
		switch (hexdec($values[2] . $values[3])) {
2805
		case 0:
2806
			return false;
2807
			break;
2808
		case 1:
2809
			if (count($values) != 16 || strlen($dhcp6duid) != 47)
2810
				return false;
2811
			break;
2812
		case 3:
2813
			if (count($values) != 12 || strlen($dhcp6duid) != 35)
2814
				return false;
2815
			break;
2816
		case 4:
2817
			if (count($values) != 20 || strlen($dhcp6duid) != 59)
2818
				return false;
2819
			break;
2820
		/* DUID is up to 128 octets; allow 2 octets for type code, 2 more for option-len */
2821
		default:
2822
			if (count($values) > 132 || strlen($dhcp6duid) != count($values) * 3 - 1)
2823
				return false;
2824
		}
2825
	} else
2826
		return false;
2827

    
2828
	for ($i = 0; $i < count($values); $i++) {
2829
		if (ctype_xdigit($values[$i]) == false)
2830
			return false;
2831
		if (hexdec($values[$i]) < 0 || hexdec($values[$i]) > 255)
2832
			return false;
2833
	}
2834

    
2835
	return true;
2836
}
2837

    
2838
/* Write the DHCP6 DUID file */
2839
function write_dhcp6_duid($duidstring) {
2840
	// Create the hex array from the dhcp6duid config entry and write to file
2841
	global $g;
2842

    
2843
	if(!is_duid($duidstring)) {
2844
		log_error(gettext("Error: attempting to write DUID file - Invalid DUID detected"));
2845
		return false;
2846
	}
2847
	$temp = str_replace(":","",$duidstring);
2848
	$duid_binstring = pack("H*",$temp);
2849
	if ($fd = fopen("{$g['vardb_path']}/dhcp6c_duid", "wb")) {
2850
		fwrite($fd, $duid_binstring);
2851
		fclose($fd);
2852
		return true;
2853
	}
2854
	log_error(gettext("Error: attempting to write DUID file - File write error"));
2855
	return false;
2856
}
2857

    
2858
/* returns duid string from 'vardb_path']}/dhcp6c_duid' */
2859
function get_duid_from_file() {
2860
	global $g;
2861

    
2862
	$duid_ASCII = "";
2863
	$count = 0;
2864

    
2865
	if (file_exists("{$g['vardb_path']}/dhcp6c_duid") &&
2866
	    ($fd = fopen("{$g['vardb_path']}/dhcp6c_duid", "r"))) {
2867
		$fsize = filesize("{$g['vardb_path']}/dhcp6c_duid");
2868
		if ($fsize <= 132) {
2869
			$buffer = fread($fd, $fsize);
2870
			while($count < $fsize) {
2871
				$duid_ASCII .= bin2hex($buffer[$count]);
2872
				$count++;
2873
				if($count < $fsize) {
2874
					$duid_ASCII .= ":";
2875
				}
2876
			}
2877
		}
2878
		fclose($fd);
2879
	}
2880
	//if no file or error with read then the string returns blanked DUID string
2881
	if(!is_duid($duid_ASCII)) {
2882
		return "--:--:--:--:--:--:--:--:--:--:--:--:--:--:--:--";
2883
	}
2884
	return($duid_ASCII);
2885
}
2886

    
2887
/* Replaces the Mac OS 9 and earlier (\r) and DOS/Windows (\r\n) newlines with the Unix equivalent (\n). */
2888
function unixnewlines($text) {
2889
	return preg_replace('/\r\n?/', "\n", $text);
2890
}
2891

    
2892
function array_remove_duplicate($array, $field) {
2893
	foreach ($array as $sub) {
2894
		if (isset($sub[$field])) {
2895
			$cmp[] = $sub[$field];
2896
		}
2897
	}
2898
	$unique = array_unique(array_reverse($cmp, true));
2899
	foreach ($unique as $k => $rien) {
2900
		$new[] = $array[$k];
2901
	}
2902
	return $new;
2903
}
2904

    
2905
function dhcpd_date_adjust_gmt($dt) {
2906
	global $config;
2907

    
2908
	init_config_arr(array('dhcpd'));
2909

    
2910
	foreach ($config['dhcpd'] as $dhcpditem) {
2911
		if ($dhcpditem['dhcpleaseinlocaltime'] == "yes") {
2912
			$ts = strtotime($dt . " GMT");
2913
			if ($ts !== false) {
2914
				return strftime("%Y/%m/%d %H:%M:%S", $ts);
2915
			}
2916
		}
2917
	}
2918

    
2919
	/*
2920
	 * If we did not need to convert to local time or the conversion
2921
	 * failed, just return the input.
2922
	 */
2923
	return $dt;
2924
}
2925

    
2926
global $supported_image_types;
2927
$supported_image_types = array(
2928
	IMAGETYPE_JPEG,
2929
	IMAGETYPE_PNG,
2930
	IMAGETYPE_GIF,
2931
	IMAGETYPE_WEBP
2932
);
2933

    
2934
function is_supported_image($image_filename) {
2935
	global $supported_image_types;
2936
	$img_info = getimagesize($image_filename);
2937

    
2938
	/* If it's not an image, or it isn't in the supported list, return false */
2939
	if (($img_info === false) ||
2940
	    !in_array($img_info[2], array_keys($supported_image_types))) {
2941
		return false;
2942
	} else {
2943
		return $img_info[2];
2944
	}
2945
}
2946

    
2947
?>
(53-53/60)