Project

General

Profile

Download (82.8 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
/*
3
 * util.inc
4
 *
5
 * part of pfSense (https://www.pfsense.org)
6
 * Copyright (c) 2004-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
	if (!empty($process)) {
49
		exec("/bin/pgrep -anx " . escapeshellarg($process), $output, $retval);
50
		return (intval($retval) == 0);
51
	}
52
	return false;
53
}
54

    
55
function isvalidproc($proc) {
56
	return is_process_running($proc);
57
}
58

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

    
73
	return 0;
74
}
75

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

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

    
90
function is_subsystem_dirty($subsystem = "") {
91
	global $g;
92

    
93
	if ($subsystem == "") {
94
		return false;
95
	}
96

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

    
101
	return false;
102
}
103

    
104
function mark_subsystem_dirty($subsystem = "") {
105
	global $g;
106

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

    
112
function clear_subsystem_dirty($subsystem = "") {
113
	global $g;
114

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

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

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

    
157
		return $fp;
158
	}
159

    
160
	return NULL;
161
}
162

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
362
	foreach ( $parts as $v ) {
363

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

    
367
	}
368

    
369
	return $binstr;
370
}
371

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

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

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

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

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

    
393
	return $ip;
394
}
395

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

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

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

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

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

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

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

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

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

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

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

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

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

    
501
	return $rangeaddresses;
502
}
503

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

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

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

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

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

    
558
	$rangesubnets = array();
559
	$netsize = 0;
560

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

    
565
		// 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)
566

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

    
575
		// 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)
576

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

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

    
589
		if ($ip2bin < $ip1bin) {
590
			continue;
591
		}
592

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

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

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

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

    
612
	ksort($rangesubnets, SORT_STRING);
613
	$out = array();
614

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

    
624
	return $out;
625
}
626

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

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

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

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

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

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

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

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

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

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

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

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

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

    
768
}
769

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

    
785
function is_v4($ip_or_subnet) {
786
	return is_ipaddrv4($ip_or_subnet) || is_subnetv4($ip_or_subnet);
787
}
788

    
789
function is_v6($ip_or_subnet) {
790
	return is_ipaddrv6($ip_or_subnet) || is_subnetv6($ip_or_subnet);
791
}
792

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
913
	if (!is_ipaddrv6($subnet)) {
914
		return $result;
915
	}
916

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

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

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

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

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

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

    
956
	return $result;
957
}
958

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

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

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

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

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

    
1002
/* returns true if $domain is a valid domain name */
1003
function is_domain($domain, $allow_wildcard=false, $trailing_dot=true) {
1004
	if (!is_string($domain)) {
1005
		return false;
1006
	}
1007
	if (!$trailing_dot && ($domain[strlen($domain)-1] == ".")) {
1008
		return false;
1009
	}
1010
	if ($allow_wildcard) {
1011
		$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';
1012
	} else {
1013
		$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';
1014
	}
1015

    
1016
	if (preg_match($domain_regex, $domain)) {
1017
		return true;
1018
	} else {
1019
		return false;
1020
	}
1021
}
1022

    
1023
/* returns true if $macaddr is a valid MAC address */
1024
function is_macaddr($macaddr, $partial=false) {
1025
	$values = explode(":", $macaddr);
1026

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

    
1042
	return true;
1043
}
1044

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

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

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

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

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

    
1102
/*
1103
 * returns true if $range is a valid integer range between $min and $max
1104
 * range delimiter can be ':' or '-'
1105
 */
1106
function is_intrange($range, $min, $max) {
1107
	$values = preg_split("/[:-]/", $range);
1108

    
1109
	if (!is_array($values) || count($values) != 2) {
1110
		return false;
1111
	}
1112

    
1113
	if (!ctype_digit($values[0]) || !ctype_digit($values[1])) {
1114
		return false;
1115
	}
1116

    
1117
	$values[0] = intval($values[0]);
1118
	$values[1] = intval($values[1]);
1119

    
1120
	if ($values[0] >= $values[1]) {
1121
		return false;
1122
	}
1123

    
1124
	if ($values[0] < $min || $values[1] > $max) {
1125
		return false;
1126
	}
1127

    
1128
	return true;
1129
}
1130

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

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

    
1150
		foreach($netstatarr as $index => $portstats){
1151
			array_push($port_info, $portstats['local']['port']);
1152
		}
1153
	}
1154

    
1155
	return in_array($port, $port_info);
1156
}
1157

    
1158
/* returns true if $portrange is a valid TCP/UDP portrange ("<port>:<port>") */
1159
function is_portrange($portrange) {
1160
	$ports = explode(":", $portrange);
1161

    
1162
	return (count($ports) == 2 && is_port($ports[0]) && is_port($ports[1]));
1163
}
1164

    
1165
/* returns true if $port is a valid TCP/UDP port number or range ("<port>:<port>") */
1166
function is_port_or_range($port) {
1167
	return (is_port($port) || is_portrange($port));
1168
}
1169

    
1170
/* returns true if $port is an alias that is a port type */
1171
function is_portalias($port) {
1172
	global $config;
1173

    
1174
	if (is_alias($port)) {
1175
		if (is_array($config['aliases']['alias'])) {
1176
			foreach ($config['aliases']['alias'] as $alias) {
1177
				if ($alias['name'] == $port && preg_match("/port/i", $alias['type'])) {
1178
					return true;
1179
				}
1180
			}
1181
		}
1182
	}
1183
	return false;
1184
}
1185

    
1186
/* returns true if $port is a valid port number or an alias thereof */
1187
function is_port_or_alias($port) {
1188
	return (is_port($port) || is_portalias($port));
1189
}
1190

    
1191
/* returns true if $port is a valid TCP/UDP port number or range ("<port>:<port>") or an alias thereof */
1192
function is_port_or_range_or_alias($port) {
1193
	return (is_port($port) || is_portrange($port) || is_portalias($port));
1194
}
1195

    
1196
/* create ranges of sequential port numbers (200:215) and remove duplicates */
1197
function group_ports($ports, $kflc = false) {
1198
	if (!is_array($ports) || empty($ports)) {
1199
		return;
1200
	}
1201

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

    
1227
	$result = array();
1228
	foreach ($uniq as $idx => $port) {
1229
		if ($idx == 0) {
1230
			$result[] = $port;
1231
			continue;
1232
		}
1233

    
1234
		$last = end($result);
1235
		if (is_portrange($last)) {
1236
			list($begin, $end) = explode(":", $last);
1237
		} else {
1238
			$begin = $end = $last;
1239
		}
1240

    
1241
		if ($port == ($end+1)) {
1242
			$end++;
1243
			$result[count($result)-1] = "{$begin}:{$end}";
1244
		} else {
1245
			$result[] = $port;
1246
		}
1247
	}
1248

    
1249
	return array_merge($comments, $result);
1250
}
1251

    
1252
/* returns true if $val is a valid shaper bandwidth value */
1253
function is_valid_shaperbw($val) {
1254
	return (preg_match("/^(\d+(?:\.\d+)?)([MKG]?b|%)$/", $val));
1255
}
1256

    
1257
/* returns true if $test is in the range between $start and $end */
1258
function is_inrange_v4($test, $start, $end) {
1259
	if (!is_ipaddrv4($test) || !is_ipaddrv4($start) || !is_ipaddrv4($end)) {
1260
		return false;
1261
	}
1262

    
1263
	if (ip2ulong($test) <= ip2ulong($end) &&
1264
	    ip2ulong($test) >= ip2ulong($start)) {
1265
		return true;
1266
	}
1267

    
1268
	return false;
1269
}
1270

    
1271
/* returns true if $test is in the range between $start and $end */
1272
function is_inrange_v6($test, $start, $end) {
1273
	if (!is_ipaddrv6($test) || !is_ipaddrv6($start) || !is_ipaddrv6($end)) {
1274
		return false;
1275
	}
1276

    
1277
	if (inet_pton($test) <= inet_pton($end) &&
1278
	    inet_pton($test) >= inet_pton($start)) {
1279
		return true;
1280
	}
1281

    
1282
	return false;
1283
}
1284

    
1285
/* returns true if $test is in the range between $start and $end */
1286
function is_inrange($test, $start, $end) {
1287
	return is_ipaddrv6($test) ? is_inrange_v6($test, $start, $end) : is_inrange_v4($test, $start, $end);
1288
}
1289

    
1290
function build_vip_list($fif, $family = "all") {
1291
	$list = array('address' => gettext('Interface Address'));
1292

    
1293
	$viplist = get_configured_vip_list($family);
1294
	foreach ($viplist as $vip => $address) {
1295
		if ($fif == get_configured_vip_interface($vip)) {
1296
			$list[$vip] = "$address";
1297
			if (get_vip_descr($address)) {
1298
				$list[$vip] .= " (". get_vip_descr($address) .")";
1299
			}
1300
		}
1301
	}
1302

    
1303
	return($list);
1304
}
1305

    
1306
function get_configured_vip_list($family = 'all', $type = VIP_ALL) {
1307
	global $config;
1308

    
1309
	$list = array();
1310
	if (!is_array($config['virtualip']) ||
1311
	    !is_array($config['virtualip']['vip']) ||
1312
	    empty($config['virtualip']['vip'])) {
1313
		return ($list);
1314
	}
1315

    
1316
	$viparr = &$config['virtualip']['vip'];
1317
	foreach ($viparr as $vip) {
1318

    
1319
		if ($type == VIP_CARP) {
1320
			if ($vip['mode'] != "carp")
1321
				continue;
1322
		} elseif ($type == VIP_IPALIAS) {
1323
			if ($vip['mode'] != "ipalias")
1324
				continue;
1325
		} else {
1326
			if ($vip['mode'] != "carp" && $vip['mode'] != "ipalias")
1327
				continue;
1328
		}
1329

    
1330
		if ($family == 'all' ||
1331
		    ($family == 'inet' && is_ipaddrv4($vip['subnet'])) ||
1332
		    ($family == 'inet6' && is_ipaddrv6($vip['subnet']))) {
1333
			$list["_vip{$vip['uniqid']}"] = $vip['subnet'];
1334
		}
1335
	}
1336
	return ($list);
1337
}
1338

    
1339
function get_configured_vip($vipinterface = '') {
1340

    
1341
	return (get_configured_vip_detail($vipinterface, 'all', 'vip'));
1342
}
1343

    
1344
function get_configured_vip_interface($vipinterface = '') {
1345

    
1346
	return (get_configured_vip_detail($vipinterface, 'all', 'iface'));
1347
}
1348

    
1349
function get_configured_vip_ipv4($vipinterface = '') {
1350

    
1351
	return (get_configured_vip_detail($vipinterface, 'inet', 'ip'));
1352
}
1353

    
1354
function get_configured_vip_ipv6($vipinterface = '') {
1355

    
1356
	return (get_configured_vip_detail($vipinterface, 'inet6', 'ip'));
1357
}
1358

    
1359
function get_configured_vip_subnetv4($vipinterface = '') {
1360

    
1361
	return (get_configured_vip_detail($vipinterface, 'inet', 'subnet'));
1362
}
1363

    
1364
function get_configured_vip_subnetv6($vipinterface = '') {
1365

    
1366
	return (get_configured_vip_detail($vipinterface, 'inet6', 'subnet'));
1367
}
1368

    
1369
function get_configured_vip_detail($vipinterface = '', $family = 'inet', $what = 'ip') {
1370
	global $config;
1371

    
1372
	if (empty($vipinterface) ||
1373
	    !is_array($config['virtualip']) ||
1374
	    !is_array($config['virtualip']['vip']) ||
1375
	    empty($config['virtualip']['vip'])) {
1376
		return (NULL);
1377
	}
1378

    
1379
	$viparr = &$config['virtualip']['vip'];
1380
	foreach ($viparr as $vip) {
1381
		if ($vip['mode'] != "carp" && $vip['mode'] != "ipalias") {
1382
			continue;
1383
		}
1384

    
1385
		if ($vipinterface != "_vip{$vip['uniqid']}") {
1386
			continue;
1387
		}
1388

    
1389
		switch ($what) {
1390
			case 'subnet':
1391
				if ($family == 'inet' && is_ipaddrv4($vip['subnet']))
1392
					return ($vip['subnet_bits']);
1393
				else if ($family == 'inet6' && is_ipaddrv6($vip['subnet']))
1394
					return ($vip['subnet_bits']);
1395
				break;
1396
			case 'iface':
1397
				return ($vip['interface']);
1398
				break;
1399
			case 'vip':
1400
				return ($vip);
1401
				break;
1402
			case 'ip':
1403
			default:
1404
				if ($family == 'inet' && is_ipaddrv4($vip['subnet'])) {
1405
					return ($vip['subnet']);
1406
				} else if ($family == 'inet6' && is_ipaddrv6($vip['subnet'])) {
1407
					return ($vip['subnet']);
1408
				}
1409
				break;
1410
		}
1411
		break;
1412
	}
1413

    
1414
	return (NULL);
1415
}
1416

    
1417
/* comparison function for sorting by the order in which interfaces are normally created */
1418
function compare_interface_friendly_names($a, $b) {
1419
	if ($a == $b) {
1420
		return 0;
1421
	} else if ($a == 'wan') {
1422
		return -1;
1423
	} else if ($b == 'wan') {
1424
		return 1;
1425
	} else if ($a == 'lan') {
1426
		return -1;
1427
	} else if ($b == 'lan') {
1428
		return 1;
1429
	}
1430

    
1431
	return strnatcmp($a, $b);
1432
}
1433

    
1434
/* return the configured interfaces list. */
1435
function get_configured_interface_list($withdisabled = false) {
1436
	global $config;
1437

    
1438
	$iflist = array();
1439

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

    
1447
	return $iflist;
1448
}
1449

    
1450
/* return the configured interfaces list. */
1451
function get_configured_interface_list_by_realif($withdisabled = false) {
1452
	global $config;
1453

    
1454
	$iflist = array();
1455

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

    
1466
	return $iflist;
1467
}
1468

    
1469
/* return the configured interfaces list with their description. */
1470
function get_configured_interface_with_descr($withdisabled = false) {
1471
	global $config, $user_settings;
1472

    
1473
	$iflist = array();
1474

    
1475
	/* if list */
1476
	foreach ($config['interfaces'] as $if => $ifdetail) {
1477
		if (isset($ifdetail['enable']) || $withdisabled == true) {
1478
			if (empty($ifdetail['descr'])) {
1479
				$iflist[$if] = strtoupper($if);
1480
			} else {
1481
				$iflist[$if] = strtoupper($ifdetail['descr']);
1482
			}
1483
		}
1484
	}
1485

    
1486
	if ($user_settings['webgui']['interfacessort']) {
1487
		asort($iflist);
1488
	}
1489

    
1490
	return $iflist;
1491
}
1492

    
1493
/*
1494
 *   get_configured_ip_addresses() - Return a list of all configured
1495
 *   IPv4 addresses.
1496
 *
1497
 */
1498
function get_configured_ip_addresses() {
1499
	global $config;
1500

    
1501
	if (!function_exists('get_interface_ip')) {
1502
		require_once("interfaces.inc");
1503
	}
1504
	$ip_array = array();
1505
	$interfaces = get_configured_interface_list();
1506
	if (is_array($interfaces)) {
1507
		foreach ($interfaces as $int) {
1508
			$ipaddr = get_interface_ip($int);
1509
			$ip_array[$int] = $ipaddr;
1510
		}
1511
	}
1512
	$interfaces = get_configured_vip_list('inet');
1513
	if (is_array($interfaces)) {
1514
		foreach ($interfaces as $int => $ipaddr) {
1515
			$ip_array[$int] = $ipaddr;
1516
		}
1517
	}
1518

    
1519
	/* pppoe server */
1520
	if (is_array($config['pppoes']) && is_array($config['pppoes']['pppoe'])) {
1521
		foreach ($config['pppoes']['pppoe'] as $pppoe) {
1522
			if ($pppoe['mode'] == "server") {
1523
				if (is_ipaddr($pppoe['localip'])) {
1524
					$int = "pppoes". $pppoe['pppoeid'];
1525
					$ip_array[$int] = $pppoe['localip'];
1526
				}
1527
			}
1528
		}
1529
	}
1530

    
1531
	return $ip_array;
1532
}
1533

    
1534
/*
1535
 *   get_configured_ipv6_addresses() - Return a list of all configured
1536
 *   IPv6 addresses.
1537
 *
1538
 */
1539
function get_configured_ipv6_addresses($linklocal_fallback = false) {
1540
	require_once("interfaces.inc");
1541
	$ipv6_array = array();
1542
	$interfaces = get_configured_interface_list();
1543
	if (is_array($interfaces)) {
1544
		foreach ($interfaces as $int) {
1545
			$ipaddrv6 = text_to_compressed_ip6(get_interface_ipv6($int, false, $linklocal_fallback));
1546
			$ipv6_array[$int] = $ipaddrv6;
1547
		}
1548
	}
1549
	$interfaces = get_configured_vip_list('inet6');
1550
	if (is_array($interfaces)) {
1551
		foreach ($interfaces as $int => $ipaddrv6) {
1552
			$ipv6_array[$int] = text_to_compressed_ip6($ipaddrv6);
1553
		}
1554
	}
1555
	return $ipv6_array;
1556
}
1557

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

    
1664
			case "friendly":
1665
				if ($friendly != "") {
1666
					$toput['if'] = $ifname;
1667
					$iflist[$friendly] = $toput;
1668
				}
1669
				break;
1670
			}
1671
		}
1672
	}
1673
	return $iflist;
1674
}
1675

    
1676
function get_lagg_interface_list() {
1677
	global $config;
1678

    
1679
	$plist = array();
1680
	if (isset($config['laggs']['lagg']) && is_array($config['laggs']['lagg'])) {
1681
		foreach ($config['laggs']['lagg'] as $lagg) {
1682
			$lagg['mac'] = get_interface_mac($lagg['laggif']);
1683
			$lagg['islagg'] = true;
1684
			$plist[$lagg['laggif']] = $lagg;
1685
		}
1686
	}
1687

    
1688
	return ($plist);
1689
}
1690

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

    
1713
/****f* util/log_auth
1714
* NAME
1715
*   log_auth   - Sends a string to syslog as LOG_AUTH facility
1716
* INPUTS
1717
*   $error     - string containing the syslog message.
1718
* RESULT
1719
*   null
1720
******/
1721
function log_auth($error) {
1722
	global $g;
1723
	$page = $_SERVER['SCRIPT_NAME'];
1724
	syslog(LOG_AUTH, "$page: $error");
1725
	if ($g['debug']) {
1726
		syslog(LOG_WARNING, var_dump(debug_backtrace()));
1727
	}
1728
	return;
1729
}
1730

    
1731
/****f* util/exec_command
1732
 * NAME
1733
 *   exec_command - Execute a command and return a string of the result.
1734
 * INPUTS
1735
 *   $command   - String of the command to be executed.
1736
 * RESULT
1737
 *   String containing the command's result.
1738
 * NOTES
1739
 *   This function returns the command's stdout and stderr.
1740
 ******/
1741
function exec_command($command) {
1742
	$output = array();
1743
	exec($command . ' 2>&1', $output);
1744
	return(implode("\n", $output));
1745
}
1746

    
1747
/* wrapper for exec()
1748
   Executes in background or foreground.
1749
   For background execution, returns PID of background process to allow calling code control */
1750
function mwexec($command, $nologentry = false, $clearsigmask = false, $background = false) {
1751
	global $g;
1752
	$retval = 0;
1753

    
1754
	if ($g['debug']) {
1755
		if (!$_SERVER['REMOTE_ADDR']) {
1756
			echo "mwexec(): $command" . ($background ? " [BG]":"") . "\n";
1757
		}
1758
	}
1759
	if ($clearsigmask) {
1760
		$oldset = array();
1761
		pcntl_sigprocmask(SIG_SETMASK, array(), $oldset);
1762
	}
1763

    
1764
	if ($background) {
1765
		// start background process and return PID
1766
		$retval = exec("/usr/bin/nohup $command > /dev/null 2>&1 & echo $!");
1767
	} else {
1768
		// run in foreground, and (optionally) log if nonzero return
1769
		$outputarray = array();
1770
		exec("$command 2>&1", $outputarray, $retval);
1771
		if (($retval <> 0) && (!$nologentry || isset($config['system']['developerspew']))) {
1772
			log_error(sprintf(gettext("The command '%1\$s' returned exit code '%2\$d', the output was '%3\$s' "), $command, $retval, implode(" ", $outputarray)));
1773
		}
1774
	}
1775

    
1776
	if ($clearsigmask) {
1777
		pcntl_sigprocmask(SIG_SETMASK, $oldset);
1778
	}
1779

    
1780
	return $retval;
1781
}
1782

    
1783
/* wrapper for exec() in background */
1784
function mwexec_bg($command, $clearsigmask = false) {
1785
	return mwexec($command, false, $clearsigmask, true);
1786
}
1787

    
1788
/*	unlink a file, or pattern-match of a file, if it exists
1789
	if the file/path contains glob() compatible wildcards, all matching files will be unlinked
1790
	any warning/errors are suppressed (e.g. no matching files to delete)
1791
	If there are matching file(s) and they were all unlinked OK, then return true.
1792
	Otherwise return false (the requested file(s) did not exist, or could not be deleted)
1793
	This allows the caller to know if they were the one to successfully delete the file(s).
1794
*/
1795
function unlink_if_exists($fn) {
1796
	$to_do = glob($fn);
1797
	if (is_array($to_do) && count($to_do) > 0) {
1798
		// Returns an array of true/false indicating if each unlink worked
1799
		$results = @array_map("unlink", $to_do);
1800
		// If there is no false in the array, then all went well
1801
		$result = !in_array(false, $results, true);
1802
	} else {
1803
		$result = @unlink($fn);
1804
	}
1805
	return $result;
1806
}
1807
/* make a global alias table (for faster lookups) */
1808
function alias_make_table() {
1809
	global $aliastable, $config;
1810

    
1811
	$aliastable = array();
1812

    
1813
	init_config_arr(array('aliases', 'alias'));
1814
	foreach ($config['aliases']['alias'] as $alias) {
1815
		if ($alias['name']) {
1816
			$aliastable[$alias['name']] = $alias['address'];
1817
		}
1818
	}
1819
}
1820

    
1821
/* check if an alias exists */
1822
function is_alias($name) {
1823
	global $aliastable;
1824

    
1825
	return isset($aliastable[$name]);
1826
}
1827

    
1828
function alias_get_type($name) {
1829
	global $config;
1830

    
1831
	if (is_array($config['aliases']['alias'])) {
1832
		foreach ($config['aliases']['alias'] as $alias) {
1833
			if ($name == $alias['name']) {
1834
				return $alias['type'];
1835
			}
1836
		}
1837
	}
1838

    
1839
	return "";
1840
}
1841

    
1842
/* expand a host or network alias, if necessary */
1843
function alias_expand($name) {
1844
	global $config, $aliastable;
1845
	$urltable_prefix = "/var/db/aliastables/";
1846
	$urltable_filename = $urltable_prefix . $name . ".txt";
1847

    
1848
	if (isset($aliastable[$name])) {
1849
		// alias names cannot be strictly numeric. redmine #4289
1850
		if (is_numericint($name)) {
1851
			return null;
1852
		}
1853
		// make sure if it's a ports alias, it actually exists. redmine #5845
1854
		foreach ($config['aliases']['alias'] as $alias) {
1855
			if ($alias['name'] == $name) {
1856
				if ($alias['type'] == "urltable_ports") {
1857
					if (is_URL($alias['url']) && file_exists($urltable_filename) && filesize($urltable_filename)) {
1858
						return "\${$name}";
1859
					} else {
1860
						return null;
1861
					}
1862
				}
1863
			}
1864
		}
1865
		return "\${$name}";
1866
	} else if (is_ipaddr($name) || is_subnet($name) || is_port_or_range($name)) {
1867
		return "{$name}";
1868
	} else {
1869
		return null;
1870
	}
1871
}
1872

    
1873
function alias_expand_urltable($name) {
1874
	global $config;
1875
	$urltable_prefix = "/var/db/aliastables/";
1876
	$urltable_filename = $urltable_prefix . $name . ".txt";
1877

    
1878
	if (is_array($config['aliases']['alias'])) {
1879
		foreach ($config['aliases']['alias'] as $alias) {
1880
			if (preg_match("/urltable/i", $alias['type']) && ($alias['name'] == $name)) {
1881
				if (is_URL($alias["url"]) && file_exists($urltable_filename)) {
1882
					if (!filesize($urltable_filename)) {
1883
						// file exists, but is empty, try to sync
1884
						send_event("service sync alias {$name}");
1885
					}
1886
					return $urltable_filename;
1887
				} else {
1888
					send_event("service sync alias {$name}");
1889
					break;
1890
				}
1891
			}
1892
		}
1893
	}
1894
	return null;
1895
}
1896

    
1897
/* obtain MAC address given an IP address by looking at the ARP/NDP table */
1898
function arp_get_mac_by_ip($ip, $do_ping = true) {
1899
	unset($macaddr);
1900
	$retval = 1;
1901
	switch (is_ipaddr($ip)) {
1902
		case 4:
1903
			if ($do_ping === true) {
1904
				mwexec("/sbin/ping -c 1 -t 1 " . escapeshellarg($ip), true);
1905
			}
1906
			$macaddr = exec("/usr/sbin/arp -n " . escapeshellarg($ip) . " | /usr/bin/awk '{print $4}'", $output, $retval);
1907
			break;
1908
		case 6:
1909
			if ($do_ping === true) {
1910
				mwexec("/sbin/ping6 -c 1 -X 1 " . escapeshellarg($ip), true);
1911
			}
1912
			$macaddr = exec("/usr/sbin/ndp -n " . escapeshellarg($ip) . " | /usr/bin/awk '{print $2}'", $output, $retval);
1913
			break;
1914
	}
1915
	if ($retval == 0 && is_macaddr($macaddr)) {
1916
		return $macaddr;
1917
	} else {
1918
		return false;
1919
	}
1920
}
1921

    
1922
/* return a fieldname that is safe for xml usage */
1923
function xml_safe_fieldname($fieldname) {
1924
	$replace = array(
1925
	    '/', '-', ' ', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')',
1926
	    '_', '+', '=', '{', '}', '[', ']', '|', '/', '<', '>', '?',
1927
	    ':', ',', '.', '\'', '\\'
1928
	);
1929
	return strtolower(str_replace($replace, "", $fieldname));
1930
}
1931

    
1932
function mac_format($clientmac) {
1933
	global $config, $cpzone;
1934

    
1935
	$mac = explode(":", $clientmac);
1936
	$mac_format = $cpzone ? $config['captiveportal'][$cpzone]['radmac_format'] : false;
1937

    
1938
	switch ($mac_format) {
1939
		case 'singledash':
1940
			return "$mac[0]$mac[1]$mac[2]-$mac[3]$mac[4]$mac[5]";
1941

    
1942
		case 'ietf':
1943
			return "$mac[0]-$mac[1]-$mac[2]-$mac[3]-$mac[4]-$mac[5]";
1944

    
1945
		case 'cisco':
1946
			return "$mac[0]$mac[1].$mac[2]$mac[3].$mac[4]$mac[5]";
1947

    
1948
		case 'unformatted':
1949
			return "$mac[0]$mac[1]$mac[2]$mac[3]$mac[4]$mac[5]";
1950

    
1951
		default:
1952
			return $clientmac;
1953
	}
1954
}
1955

    
1956
function resolve_retry($hostname, $protocol = 'inet', $retries = 5, $numrecords = 1) {
1957

    
1958
	$recresult = array();
1959
	$returnres = array();
1960
	for ($i = 0; $i < $retries; $i++) {
1961
		switch ($protocol) {
1962
			case 'any':
1963
				$checkproto = 'is_ipaddr';
1964
				$dnsproto = DNS_ANY;
1965
				$dnstype = array('A', 'AAAA');
1966
				break;
1967
			case 'inet6':
1968
				$checkproto = 'is_ipaddrv6';
1969
				$dnsproto = DNS_AAAA;
1970
				$dnstype = array('AAAA');
1971
				break;
1972
			case 'inet': 
1973
			default:
1974
				$checkproto = 'is_ipaddrv4';
1975
				$dnsproto = DNS_A;
1976
				$dnstype = array('A');
1977
				break;
1978
		}
1979
		if ($checkproto($hostname)) {
1980
			return $hostname;
1981
		}
1982
		$dnsresult = @dns_get_record($hostname, $dnsproto);
1983
		if (!empty($dnsresult)) {
1984
			foreach ($dnsresult as $dnsrec => $ip) {
1985
				if (is_array($ip)) {
1986
					if (in_array($ip['type'], $dnstype)) {
1987
					    if ($checkproto($ip['ip'])) { 
1988
						    $recresult[] = $ip['ip'];
1989
					    }
1990
					    if ($checkproto($ip['ipv6'])) { 
1991
						    $recresult[] = $ip['ipv6'];
1992
					    }
1993
					}
1994
				}
1995
			}
1996
		}
1997

    
1998
		sleep(1);
1999
	}
2000

    
2001
	if (!empty($recresult)) {
2002
		if ($numrecords == 1) {
2003
			return $recresult[0];
2004
		} else {
2005
			return array_slice($recresult, 0, $numrecords);
2006
		}
2007
	}
2008

    
2009
	return false;
2010
}
2011

    
2012
function format_bytes($bytes) {
2013
	if ($bytes >= 1099511627776) {
2014
		return sprintf("%.2f TiB", $bytes/1099511627776);
2015
	} else if ($bytes >= 1073741824) {
2016
		return sprintf("%.2f GiB", $bytes/1073741824);
2017
	} else if ($bytes >= 1048576) {
2018
		return sprintf("%.2f MiB", $bytes/1048576);
2019
	} else if ($bytes >= 1024) {
2020
		return sprintf("%.0f KiB", $bytes/1024);
2021
	} else {
2022
		return sprintf("%d B", $bytes);
2023
	}
2024
}
2025

    
2026
function format_number($num, $precision = 3) {
2027
	$units = array('', 'K', 'M', 'G', 'T');
2028

    
2029
	$i = 0;
2030
	while ($num > 1000 && $i < count($units)) {
2031
		$num /= 1000;
2032
		$i++;
2033
	}
2034
	$num = round($num, $precision);
2035

    
2036
	return ("$num {$units[$i]}");
2037
}
2038

    
2039

    
2040
function unformat_number($formated_num) {
2041
	$num = strtoupper($formated_num);
2042
    
2043
	if ( strpos($num,"T") !== false ) {
2044
		$num = str_replace("T","",$num) * 1000 * 1000 * 1000 * 1000;
2045
	} else if ( strpos($num,"G") !== false ) {
2046
		$num = str_replace("G","",$num) * 1000 * 1000 * 1000;
2047
	} else if ( strpos($num,"M") !== false ) {
2048
		$num = str_replace("M","",$num) * 1000 * 1000;
2049
	} else if ( strpos($num,"K") !== false ) {
2050
		$num = str_replace("K","",$num) * 1000;
2051
	}
2052
    
2053
	return $num;
2054
}
2055

    
2056
function update_filter_reload_status($text, $new=false) {
2057
	global $g;
2058

    
2059
	if ($new) {
2060
		file_put_contents("{$g['varrun_path']}/filter_reload_status", $text  . PHP_EOL);
2061
	} else {
2062
		file_put_contents("{$g['varrun_path']}/filter_reload_status", $text  . PHP_EOL, FILE_APPEND);
2063
	}
2064
}
2065

    
2066
/****** util/return_dir_as_array
2067
 * NAME
2068
 *   return_dir_as_array - Return a directory's contents as an array.
2069
 * INPUTS
2070
 *   $dir          - string containing the path to the desired directory.
2071
 *   $filter_regex - string containing a regular expression to filter file names. Default empty.
2072
 * RESULT
2073
 *   $dir_array - array containing the directory's contents. This array will be empty if the path specified is invalid.
2074
 ******/
2075
function return_dir_as_array($dir, $filter_regex = '') {
2076
	$dir_array = array();
2077
	if (is_dir($dir)) {
2078
		if ($dh = opendir($dir)) {
2079
			while (($file = readdir($dh)) !== false) {
2080
				if (($file == ".") || ($file == "..")) {
2081
					continue;
2082
				}
2083

    
2084
				if (empty($filter_regex) || preg_match($filter_regex, $file)) {
2085
					array_push($dir_array, $file);
2086
				}
2087
			}
2088
			closedir($dh);
2089
		}
2090
	}
2091
	return $dir_array;
2092
}
2093

    
2094
function run_plugins($directory) {
2095
	global $config, $g;
2096

    
2097
	/* process packager manager custom rules */
2098
	$files = return_dir_as_array($directory);
2099
	if (is_array($files)) {
2100
		foreach ($files as $file) {
2101
			if (stristr($file, ".sh") == true) {
2102
				mwexec($directory . $file . " start");
2103
			} else if (!is_dir($directory . "/" . $file) && stristr($file, ".inc")) {
2104
				require_once($directory . "/" . $file);
2105
			}
2106
		}
2107
	}
2108
}
2109

    
2110
/*
2111
 *    safe_mkdir($path, $mode = 0755)
2112
 *    create directory if it doesn't already exist and isn't a file!
2113
 */
2114
function safe_mkdir($path, $mode = 0755) {
2115
	global $g;
2116

    
2117
	if (!is_file($path) && !is_dir($path)) {
2118
		return @mkdir($path, $mode, true);
2119
	} else {
2120
		return false;
2121
	}
2122
}
2123

    
2124
/*
2125
 * get_sysctl($names)
2126
 * Get values of sysctl OID's listed in $names (accepts an array or a single
2127
 * name) and return an array of key/value pairs set for those that exist
2128
 */
2129
function get_sysctl($names) {
2130
	if (empty($names)) {
2131
		return array();
2132
	}
2133

    
2134
	if (is_array($names)) {
2135
		$name_list = array();
2136
		foreach ($names as $name) {
2137
			$name_list[] = escapeshellarg($name);
2138
		}
2139
	} else {
2140
		$name_list = array(escapeshellarg($names));
2141
	}
2142

    
2143
	exec("/sbin/sysctl -iq " . implode(" ", $name_list), $output);
2144
	$values = array();
2145
	foreach ($output as $line) {
2146
		$line = explode(": ", $line, 2);
2147
		if (count($line) == 2) {
2148
			$values[$line[0]] = $line[1];
2149
		}
2150
	}
2151

    
2152
	return $values;
2153
}
2154

    
2155
/*
2156
 * get_single_sysctl($name)
2157
 * Wrapper for get_sysctl() to simplify read of a single sysctl value
2158
 * return the value for sysctl $name or empty string if it doesn't exist
2159
 */
2160
function get_single_sysctl($name) {
2161
	if (empty($name)) {
2162
		return "";
2163
	}
2164

    
2165
	$value = get_sysctl($name);
2166
	if (empty($value) || !isset($value[$name])) {
2167
		return "";
2168
	}
2169

    
2170
	return $value[$name];
2171
}
2172

    
2173
/*
2174
 * set_sysctl($value_list)
2175
 * Set sysctl OID's listed as key/value pairs and return
2176
 * an array with keys set for those that succeeded
2177
 */
2178
function set_sysctl($values) {
2179
	if (empty($values)) {
2180
		return array();
2181
	}
2182

    
2183
	$value_list = array();
2184
	foreach ($values as $key => $value) {
2185
		$value_list[] = escapeshellarg($key) . "=" . escapeshellarg($value);
2186
	}
2187

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

    
2190
	/* Retry individually if failed (one or more read-only) */
2191
	if ($success <> 0 && count($value_list) > 1) {
2192
		foreach ($value_list as $value) {
2193
			exec("/sbin/sysctl -iq " . $value, $output);
2194
		}
2195
	}
2196

    
2197
	$ret = array();
2198
	foreach ($output as $line) {
2199
		$line = explode(": ", $line, 2);
2200
		if (count($line) == 2) {
2201
			$ret[$line[0]] = true;
2202
		}
2203
	}
2204

    
2205
	return $ret;
2206
}
2207

    
2208
/*
2209
 * set_single_sysctl($name, $value)
2210
 * Wrapper to set_sysctl() to make it simple to set only one sysctl
2211
 * returns boolean meaning if it succeeded
2212
 */
2213
function set_single_sysctl($name, $value) {
2214
	if (empty($name)) {
2215
		return false;
2216
	}
2217

    
2218
	$result = set_sysctl(array($name => $value));
2219

    
2220
	if (!isset($result[$name]) || $result[$name] != $value) {
2221
		return false;
2222
	}
2223

    
2224
	return true;
2225
}
2226

    
2227
/*
2228
 *     get_memory()
2229
 *     returns an array listing the amount of
2230
 *     memory installed in the hardware
2231
 *     [0] net memory available for the OS (FreeBSD) after some is taken by BIOS, video or whatever - e.g. 235 MBytes
2232
 *     [1] real (actual) memory of the system, should be the size of the RAM card/s - e.g. 256 MBytes
2233
 */
2234
function get_memory() {
2235
	$physmem = get_single_sysctl("hw.physmem");
2236
	$realmem = get_single_sysctl("hw.realmem");
2237
	/* convert from bytes to megabytes */
2238
	return array(($physmem/1048576), ($realmem/1048576));
2239
}
2240

    
2241
function mute_kernel_msgs() {
2242
	global $g, $config;
2243

    
2244
	if ($config['system']['enableserial']) {
2245
		return;
2246
	}
2247
	exec("/sbin/conscontrol mute on");
2248
}
2249

    
2250
function unmute_kernel_msgs() {
2251
	global $g;
2252

    
2253
	exec("/sbin/conscontrol mute off");
2254
}
2255

    
2256
function start_devd() {
2257
	global $g;
2258

    
2259
	/* Generate hints for the kernel loader. */
2260
	$module_paths = explode(";", get_single_sysctl("kern.module_path"));
2261
	foreach ($module_paths as $id => $path) {
2262
		if (!is_dir($path) || file_exists("{$path}/linker.hints")) {
2263
			continue;
2264
		}
2265
		if (($files = scandir($path)) == false) {
2266
			continue;
2267
		}
2268
		$found = false;
2269
		foreach ($files as $id => $file) {
2270
			if (strlen($file) > 3 &&
2271
			    strcasecmp(substr($file, -3), ".ko") == 0) {
2272
				$found = true;
2273
				break;
2274
			}
2275
		}
2276
		if ($found == false) {
2277
			continue;
2278
		}
2279
		$_gb = exec("/usr/sbin/kldxref $path");
2280
		unset($_gb);
2281
	}
2282

    
2283
	/* Use the undocumented -q options of devd to quiet its log spamming */
2284
	$_gb = exec("/sbin/devd -q -f /etc/{$g['product_name']}-devd.conf");
2285
	sleep(1);
2286
	unset($_gb);
2287
}
2288

    
2289
function is_interface_vlan_mismatch() {
2290
	global $config, $g;
2291

    
2292
	if (is_array($config['vlans']['vlan'])) {
2293
		foreach ($config['vlans']['vlan'] as $vlan) {
2294
			if (substr($vlan['if'], 0, 4) == "lagg") {
2295
				return false;
2296
			}
2297
			if (does_interface_exist($vlan['if']) == false) {
2298
				return true;
2299
			}
2300
		}
2301
	}
2302

    
2303
	return false;
2304
}
2305

    
2306
function is_interface_mismatch() {
2307
	global $config, $g;
2308

    
2309
	$do_assign = false;
2310
	$i = 0;
2311
	$missing_interfaces = array();
2312
	if (is_array($config['interfaces'])) {
2313
		foreach ($config['interfaces'] as $ifname => $ifcfg) {
2314
			if (interface_is_vlan($ifcfg['if']) != NULL ||
2315
			    interface_is_qinq($ifcfg['if']) != NULL ||
2316
			    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'])) {
2317
				// Do not check these interfaces.
2318
				$i++;
2319
				continue;
2320
			} else if (does_interface_exist($ifcfg['if']) == false) {
2321
				$missing_interfaces[] = $ifcfg['if'];
2322
				$do_assign = true;
2323
			} else {
2324
				$i++;
2325
			}
2326
		}
2327
	}
2328

    
2329
	if (file_exists("{$g['tmp_path']}/assign_complete")) {
2330
		$do_assign = false;
2331
	}
2332

    
2333
	if (!empty($missing_interfaces) && $do_assign) {
2334
		file_put_contents("{$g['tmp_path']}/missing_interfaces", implode(' ', $missing_interfaces));
2335
	} else {
2336
		@unlink("{$g['tmp_path']}/missing_interfaces");
2337
	}
2338

    
2339
	return $do_assign;
2340
}
2341

    
2342
/* sync carp entries to other firewalls */
2343
function carp_sync_client() {
2344
	global $g;
2345
	send_event("filter sync");
2346
}
2347

    
2348
/****f* util/isAjax
2349
 * NAME
2350
 *   isAjax - reports if the request is driven from prototype
2351
 * INPUTS
2352
 *   none
2353
 * RESULT
2354
 *   true/false
2355
 ******/
2356
function isAjax() {
2357
	return isset ($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest';
2358
}
2359

    
2360
/****f* util/timeout
2361
 * NAME
2362
 *   timeout - console input with timeout countdown. Note: erases 2 char of screen for timer. Leave space.
2363
 * INPUTS
2364
 *   optional, seconds to wait before timeout. Default 9 seconds.
2365
 * RESULT
2366
 *   returns 1 char of user input or null if no input.
2367
 ******/
2368
function timeout($timer = 9) {
2369
	while (!isset($key)) {
2370
		if ($timer >= 9) {
2371
			echo chr(8) . chr(8) . ($timer == 9 ? chr(32) : null) . "{$timer}";
2372
		} else {
2373
			echo chr(8). "{$timer}";
2374
		}
2375
		`/bin/stty -icanon min 0 time 25`;
2376
		$key = trim(`KEY=\`dd count=1 2>/dev/null\`; echo \$KEY`);
2377
		`/bin/stty icanon`;
2378
		if ($key == '') {
2379
			unset($key);
2380
		}
2381
		$timer--;
2382
		if ($timer == 0) {
2383
			break;
2384
		}
2385
	}
2386
	return $key;
2387
}
2388

    
2389
/****f* util/msort
2390
 * NAME
2391
 *   msort - sort array
2392
 * INPUTS
2393
 *   $array to be sorted, field to sort by, direction of sort
2394
 * RESULT
2395
 *   returns newly sorted array
2396
 ******/
2397
function msort($array, $id = "id", $sort_ascending = true) {
2398
	$temp_array = array();
2399
	if (!is_array($array)) {
2400
		return $temp_array;
2401
	}
2402
	while (count($array)>0) {
2403
		$lowest_id = 0;
2404
		$index = 0;
2405
		foreach ($array as $item) {
2406
			if (isset($item[$id])) {
2407
				if ($array[$lowest_id][$id]) {
2408
					if (strtolower($item[$id]) < strtolower($array[$lowest_id][$id])) {
2409
						$lowest_id = $index;
2410
					}
2411
				}
2412
			}
2413
			$index++;
2414
		}
2415
		$temp_array[] = $array[$lowest_id];
2416
		$array = array_merge(array_slice($array, 0, $lowest_id), array_slice($array, $lowest_id + 1));
2417
	}
2418
	if ($sort_ascending) {
2419
		return $temp_array;
2420
	} else {
2421
		return array_reverse($temp_array);
2422
	}
2423
}
2424

    
2425
/****f* util/is_URL
2426
 * NAME
2427
 *   is_URL
2428
 * INPUTS
2429
 *   string to check
2430
 * RESULT
2431
 *   Returns true if item is a URL
2432
 ******/
2433
function is_URL($url) {
2434
	$match = preg_match("'\b(([\w-]+://?|www[.])[^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|/)))'", $url);
2435
	if ($match) {
2436
		return true;
2437
	}
2438
	return false;
2439
}
2440

    
2441
function is_file_included($file = "") {
2442
	$files = get_included_files();
2443
	if (in_array($file, $files)) {
2444
		return true;
2445
	}
2446

    
2447
	return false;
2448
}
2449

    
2450
/*
2451
 * Replace a value on a deep associative array using regex
2452
 */
2453
function array_replace_values_recursive($data, $match, $replace) {
2454
	if (empty($data)) {
2455
		return $data;
2456
	}
2457

    
2458
	if (is_string($data)) {
2459
		$data = preg_replace("/{$match}/", $replace, $data);
2460
	} else if (is_array($data)) {
2461
		foreach ($data as $k => $v) {
2462
			$data[$k] = array_replace_values_recursive($v, $match, $replace);
2463
		}
2464
	}
2465

    
2466
	return $data;
2467
}
2468

    
2469
/*
2470
	This function was borrowed from a comment on PHP.net at the following URL:
2471
	http://www.php.net/manual/en/function.array-merge-recursive.php#73843
2472
 */
2473
function array_merge_recursive_unique($array0, $array1) {
2474

    
2475
	$arrays = func_get_args();
2476
	$remains = $arrays;
2477

    
2478
	// We walk through each arrays and put value in the results (without
2479
	// considering previous value).
2480
	$result = array();
2481

    
2482
	// loop available array
2483
	foreach ($arrays as $array) {
2484

    
2485
		// The first remaining array is $array. We are processing it. So
2486
		// we remove it from remaining arrays.
2487
		array_shift($remains);
2488

    
2489
		// We don't care non array param, like array_merge since PHP 5.0.
2490
		if (is_array($array)) {
2491
			// Loop values
2492
			foreach ($array as $key => $value) {
2493
				if (is_array($value)) {
2494
					// we gather all remaining arrays that have such key available
2495
					$args = array();
2496
					foreach ($remains as $remain) {
2497
						if (array_key_exists($key, $remain)) {
2498
							array_push($args, $remain[$key]);
2499
						}
2500
					}
2501

    
2502
					if (count($args) > 2) {
2503
						// put the recursion
2504
						$result[$key] = call_user_func_array(__FUNCTION__, $args);
2505
					} else {
2506
						foreach ($value as $vkey => $vval) {
2507
							if (!is_array($result[$key])) {
2508
								$result[$key] = array();
2509
							}
2510
							$result[$key][$vkey] = $vval;
2511
						}
2512
					}
2513
				} else {
2514
					// simply put the value
2515
					$result[$key] = $value;
2516
				}
2517
			}
2518
		}
2519
	}
2520
	return $result;
2521
}
2522

    
2523

    
2524
/*
2525
 * converts a string like "a,b,c,d"
2526
 * into an array like array("a" => "b", "c" => "d")
2527
 */
2528
function explode_assoc($delimiter, $string) {
2529
	$array = explode($delimiter, $string);
2530
	$result = array();
2531
	$numkeys = floor(count($array) / 2);
2532
	for ($i = 0; $i < $numkeys; $i += 1) {
2533
		$result[$array[$i * 2]] = $array[$i * 2 + 1];
2534
	}
2535
	return $result;
2536
}
2537

    
2538
/*
2539
 * Given a string of text with some delimiter, look for occurrences
2540
 * of some string and replace all of those.
2541
 * $text - the text string (e.g. "abc,defg,x123,ipv4,xyz")
2542
 * $delimiter - the delimiter (e.g. ",")
2543
 * $element - the element to match (e.g. "defg")
2544
 * $replacement - the string to replace it with (e.g. "42")
2545
 * Returns the resulting delimited string (e.g. "abc,42,x123,ipv4,xyz")
2546
 */
2547
function replace_element_in_list($text, $delimiter, $element, $replacement) {
2548
	$textArray = explode($delimiter, $text);
2549
	while (($entry = array_search($element, $textArray)) !== false) {
2550
		$textArray[$entry] = $replacement;
2551
	}
2552
	return implode(',', $textArray);
2553
}
2554

    
2555
/* Try to change a static route, if it doesn't exist, add it */
2556
function route_add_or_change($args) {
2557
	global $config;
2558

    
2559
	if (empty($args)) {
2560
		return false;
2561
	}
2562

    
2563
	/* First, try to add it */
2564
	$_gb = exec(escapeshellcmd("/sbin/route add " . $args), $output, $rc);
2565
		
2566
	if (isset($config['system']['route-debug'])) {
2567
		$add_change = 'add';
2568
		$mt = microtime();
2569
		log_error("ROUTING debug: $mt - ADD RC={$rc} - $args");
2570
	}
2571

    
2572
	if ($rc != 0) {
2573
		/* If it fails, try to change it */
2574
		$_gb = exec(escapeshellcmd("/sbin/route change " . $args),
2575
		    $output, $rc);
2576

    
2577
		if (isset($config['system']['route-debug'])) {
2578
			$add_change = 'change';
2579
			$mt = microtime();
2580
			log_error("ROUTING debug: $mt - CHG RC={$rc} - $args");
2581
		}
2582
	}
2583
	if (isset($config['system']['route-debug'])) {
2584
		file_put_contents("/dev/console", "\n[".getmypid()."] ROUTE: {$add_change} {$args} result: {$rc}");
2585
	}
2586

    
2587
	return ($rc == 0);
2588
}
2589

    
2590
function set_ipv6routes_mtu($interface, $mtu) {
2591
	global $config, $g;
2592

    
2593
	$ipv6mturoutes = array();
2594
	$if = convert_real_interface_to_friendly_interface_name($interface);
2595
	if (!$config['interfaces'][$if]['ipaddrv6']) {
2596
		return;
2597
	}
2598
	$a_gateways = return_gateways_array();
2599
	$a_staticroutes = get_staticroutes(false, false, true);
2600
	foreach ($a_gateways as $gate) {
2601
		foreach ($a_staticroutes as $sroute) {
2602
			if ($gate['interface'] == $interface &&
2603
			    $sroute['gateway'] == $gate['name']) {
2604
				$tgt = $sroute['network'];
2605
				$gateway = $gate['gateway'];
2606
				$ipv6mturoutes[$tgt] = $gateway;
2607
			}
2608
		}
2609
		if ($gate['interface'] == $interface &&
2610
		    $gate['isdefaultgw']) {
2611
			$tgt = "default";
2612
			$gateway = $gate['gateway'];
2613
			$ipv6mturoutes[$tgt] = $gateway;
2614
		}
2615
	}
2616
	foreach ($ipv6mturoutes as $tgt => $gateway) {
2617
		mwexec("/sbin/route change -6 -mtu " . escapeshellarg($mtu) .
2618
		    " " . escapeshellarg($tgt) . " " .
2619
		    escapeshellarg($gateway));
2620
	}
2621
}
2622

    
2623
function alias_to_subnets_recursive($name, $returnhostnames = false) {
2624
	global $aliastable;
2625
	$result = array();
2626
	if (!isset($aliastable[$name])) {
2627
		return $result;
2628
	}
2629
	$subnets = preg_split('/\s+/', $aliastable[$name]);
2630
	foreach ($subnets as $net) {
2631
		if (is_alias($net)) {
2632
			$sub = alias_to_subnets_recursive($net, $returnhostnames);
2633
			$result = array_merge($result, $sub);
2634
			continue;
2635
		} elseif (!is_subnet($net)) {
2636
			if (is_ipaddrv4($net)) {
2637
				$net .= "/32";
2638
			} else if (is_ipaddrv6($net)) {
2639
				$net .= "/128";
2640
			} else if ($returnhostnames === false || !is_fqdn($net)) {
2641
				continue;
2642
			}
2643
		}
2644
		$result[] = $net;
2645
	}
2646
	return $result;
2647
}
2648

    
2649
function get_staticroutes($returnsubnetsonly = false, $returnhostnames = false, $returnenabledroutesonly = false) {
2650
	global $config, $aliastable;
2651

    
2652
	/* Bail if there are no routes, but return an array always so callers don't have to check. */
2653
	init_config_arr(array('staticroutes', 'route'));
2654
	if (empty($config['staticroutes']['route'])) {
2655
		return array();
2656
	}
2657

    
2658
	$allstaticroutes = array();
2659
	$allsubnets = array();
2660
	/* Loop through routes and expand aliases as we find them. */
2661
	foreach ($config['staticroutes']['route'] as $route) {
2662
		if ($returnenabledroutesonly && isset($route['disabled'])) {
2663
			continue;
2664
		}
2665

    
2666
		if (is_alias($route['network'])) {
2667
			foreach (alias_to_subnets_recursive($route['network'], $returnhostnames) as $net) {
2668
				$temproute = $route;
2669
				$temproute['network'] = $net;
2670
				$allstaticroutes[] = $temproute;
2671
				$allsubnets[] = $net;
2672
			}
2673
		} elseif (is_subnet($route['network'])) {
2674
			$allstaticroutes[] = $route;
2675
			$allsubnets[] = $route['network'];
2676
		}
2677
	}
2678
	if ($returnsubnetsonly) {
2679
		return $allsubnets;
2680
	} else {
2681
		return $allstaticroutes;
2682
	}
2683
}
2684

    
2685
/****f* util/get_alias_list
2686
 * NAME
2687
 *   get_alias_list - Provide a list of aliases.
2688
 * INPUTS
2689
 *   $type          - Optional, can be a string or array specifying what type(s) of aliases you need.
2690
 * RESULT
2691
 *   Array containing list of aliases.
2692
 *   If $type is unspecified, all aliases are returned.
2693
 *   If $type is a string, all aliases of the type specified in $type are returned.
2694
 *   If $type is an array, all aliases of any type specified in any element of $type are returned.
2695
 */
2696
function get_alias_list($type = null) {
2697
	global $config;
2698
	$result = array();
2699
	if ($config['aliases']['alias'] <> "" && is_array($config['aliases']['alias'])) {
2700
		foreach ($config['aliases']['alias'] as $alias) {
2701
			if ($type === null) {
2702
				$result[] = $alias['name'];
2703
			} else if (is_array($type)) {
2704
				if (in_array($alias['type'], $type)) {
2705
					$result[] = $alias['name'];
2706
				}
2707
			} else if ($type === $alias['type']) {
2708
				$result[] = $alias['name'];
2709
			}
2710
		}
2711
	}
2712
	return $result;
2713
}
2714

    
2715
/* returns an array consisting of every element of $haystack that is not equal to $needle. */
2716
function array_exclude($needle, $haystack) {
2717
	$result = array();
2718
	if (is_array($haystack)) {
2719
		foreach ($haystack as $thing) {
2720
			if ($needle !== $thing) {
2721
				$result[] = $thing;
2722
			}
2723
		}
2724
	}
2725
	return $result;
2726
}
2727

    
2728
/* Define what is preferred, IPv4 or IPv6 */
2729
function prefer_ipv4_or_ipv6() {
2730
	global $config;
2731

    
2732
	if (isset($config['system']['prefer_ipv4'])) {
2733
		mwexec("/etc/rc.d/ip6addrctl prefer_ipv4");
2734
	} else {
2735
		mwexec("/etc/rc.d/ip6addrctl prefer_ipv6");
2736
	}
2737
}
2738

    
2739
/* Redirect to page passing parameters via POST */
2740
function post_redirect($page, $params) {
2741
	if (!is_array($params)) {
2742
		return;
2743
	}
2744

    
2745
	print "<html><body><form action=\"{$page}\" name=\"formredir\" method=\"post\">\n";
2746
	foreach ($params as $key => $value) {
2747
		print "<input type=\"hidden\" name=\"{$key}\" value=\"{$value}\" />\n";
2748
	}
2749
	print "</form>\n";
2750
	print "<script type=\"text/javascript\">\n";
2751
	print "//<![CDATA[\n";
2752
	print "document.formredir.submit();\n";
2753
	print "//]]>\n";
2754
	print "</script>\n";
2755
	print "</body></html>\n";
2756
}
2757

    
2758
/* Locate disks that can be queried for S.M.A.R.T. data. */
2759
function get_smart_drive_list() {
2760
	/* SMART supports some disks directly, and some controllers directly,
2761
	 * See https://redmine.pfsense.org/issues/9042 */
2762
	$supported_disk_types = array("ad", "da", "ada");
2763
	$supported_controller_types = array("nvme");
2764
	$disk_list = explode(" ", get_single_sysctl("kern.disks"));
2765
	foreach ($disk_list as $id => $disk) {
2766
		// We only want certain kinds of disks for S.M.A.R.T.
2767
		// 1 is a match, 0 is no match, False is any problem processing the regex
2768
		if (preg_match("/^(" . implode("|", $supported_disk_types) . ").*[0-9]{1,2}$/", $disk) !== 1) {
2769
			unset($disk_list[$id]);
2770
			continue;
2771
		}
2772
	}
2773
	foreach ($supported_controller_types as $controller) {
2774
		$devices = glob("/dev/{$controller}*");
2775
		if (!is_array($devices)) {
2776
			continue;
2777
		}
2778
		foreach ($devices as $device) {
2779
			$disk_list[] = basename($device);
2780
		}
2781
	}
2782
	sort($disk_list);
2783
	return $disk_list;
2784
}
2785

    
2786
// Validate a network address
2787
//	$addr: the address to validate
2788
//	$type: IPV4|IPV6|IPV4V6
2789
//	$label: the label used by the GUI to display this value. Required to compose an error message
2790
//	$err_msg: pointer to the callers error message array so that error messages can be added to it here
2791
//	$alias: are aliases permitted for this address?
2792
// Returns:
2793
//	IPV4 - if $addr is a valid IPv4 address
2794
//	IPV6 - if $addr is a valid IPv6 address
2795
//	ALIAS - if $alias=true and $addr is an alias
2796
//	false - otherwise
2797

    
2798
function validateipaddr(&$addr, $type, $label, &$err_msg, $alias=false) {
2799
	switch ($type) {
2800
		case IPV4:
2801
			if (is_ipaddrv4($addr)) {
2802
				return IPV4;
2803
			} else if ($alias) {
2804
				if (is_alias($addr)) {
2805
					return ALIAS;
2806
				} else {
2807
					$err_msg[] = sprintf(gettext("%s must be a valid IPv4 address or alias."), $label);
2808
					return false;
2809
				}
2810
			} else {
2811
				$err_msg[] = sprintf(gettext("%s must be a valid IPv4 address."), $label);
2812
				return false;
2813
			}
2814
		break;
2815
		case IPV6:
2816
			if (is_ipaddrv6($addr)) {
2817
				$addr = strtolower($addr);
2818
				return IPV6;
2819
			} else if ($alias) {
2820
				if (is_alias($addr)) {
2821
					return ALIAS;
2822
				} else {
2823
					$err_msg[] = sprintf(gettext("%s must be a valid IPv6 address or alias."), $label);
2824
					return false;
2825
				}
2826
			} else {
2827
				$err_msg[] = sprintf(gettext("%s must be a valid IPv6 address."), $label);
2828
				return false;
2829
			}
2830
		break;
2831
		case IPV4V6:
2832
			if (is_ipaddrv6($addr)) {
2833
				$addr = strtolower($addr);
2834
				return IPV6;
2835
			} else if (is_ipaddrv4($addr)) {
2836
				return IPV4;
2837
			} else if ($alias) {
2838
				if (is_alias($addr)) {
2839
					return ALIAS;
2840
				} else {
2841
					$err_msg[] = sprintf(gettext("%s must be a valid IPv4 or IPv6 address or alias."), $label);
2842
					return false;
2843
				}
2844
			} else {
2845
				$err_msg[] = sprintf(gettext("%s must be a valid IPv4 or IPv6 address."), $label);
2846
				return false;
2847
			}
2848
		break;
2849
	}
2850

    
2851
	return false;
2852
}
2853

    
2854
/* From DUID configuration inputs, format a string that looks (more) like the expected raw DUID format:
2855
 * 1) For DUIDs entered as a known DUID type, convert to a hexstring and prepend the DUID number, after having done the following:
2856
 *     a) For DUID-LLT and DUID-EN, convert the time/enterprise ID input to hex and append the link-layer address/identifier input.
2857
 *     b) For DUID-LLT and DUID-LL, prepend a hardware type of 1.
2858
 *     c) For DUID-UUID, remove any "-".
2859
 * 2) Replace any remaining "-" with ":".
2860
 * 3) If any components are input with just a single char (hex digit hopefully), put a "0" in front.
2861
 * 4) The first two components should be a 16-bit integer (little- or big-endian, depending on the current machine type) that
2862
 *    is equal to the number of other components. If not, prepend this as "nn:00" (all pfSense builds are little-endian).
2863
 *    This is convenience, because the DUID reported by dhcp6c in logs does not include this count, which corresponds to the
2864
 *    option-len field of DHCPv6's OPTION_CLIENTID option.
2865
 *
2866
 * The final result should be closer to:
2867
 *
2868
 * "nn:00:00:0n:nn:nn:nn:..."
2869
 *
2870
 * This function does not validate the input. is_duid() will do validation.
2871
 */
2872
function format_duid($duidtype, $duidpt1, $duidpt2=null) {
2873
	if ($duidpt2)
2874
		$duidpt1 = implode(':', str_split(str_pad(dechex($duidpt1), 8, '0', STR_PAD_LEFT), 2)) . ':' . $duidpt2;
2875

    
2876
	/* Make hexstrings */
2877
	if ($duidtype) {
2878
		switch ($duidtype) {
2879
		/* Add a hardware type to DUID-LLT and DUID-LL; assume Ethernet */
2880
		case 1:
2881
		case 3:
2882
			$duidpt1 = '00:01:' . $duidpt1;
2883
			break;
2884
		/* Remove '-' from given UUID and insert ':' every 2 characters */
2885
		case 4:
2886
			$duidpt1 = implode(':', str_split(str_replace('-', '', $duidpt1), 2));
2887
			break;
2888
		default:
2889
		}
2890
		$duidpt1 = '00:0' . $duidtype . ':' . $duidpt1;
2891
	}
2892

    
2893
	$values = explode(':', strtolower(str_replace('-', ':', $duidpt1)));
2894

    
2895
	if (hexdec($values[0]) != count($values) - 2)
2896
		array_unshift($values, dechex(count($values)), '00');
2897

    
2898
	array_walk($values, function(&$value) {
2899
		$value = str_pad($value, 2, '0', STR_PAD_LEFT);
2900
	});
2901

    
2902
	return implode(":", $values);
2903
}
2904

    
2905
/* Returns true if $dhcp6duid is a valid DUID entry.
2906
 * Parse the entry to check for valid length according to known DUID types.
2907
 */
2908
function is_duid($dhcp6duid) {
2909
	$values = explode(":", $dhcp6duid);
2910
	if (hexdec($values[0]) == count($values) - 2) {
2911
		switch (hexdec($values[2] . $values[3])) {
2912
		case 0:
2913
			return false;
2914
			break;
2915
		case 1:
2916
			if (count($values) != 16 || strlen($dhcp6duid) != 47)
2917
				return false;
2918
			break;
2919
		case 3:
2920
			if (count($values) != 12 || strlen($dhcp6duid) != 35)
2921
				return false;
2922
			break;
2923
		case 4:
2924
			if (count($values) != 20 || strlen($dhcp6duid) != 59)
2925
				return false;
2926
			break;
2927
		/* DUID is up to 128 octets; allow 2 octets for type code, 2 more for option-len */
2928
		default:
2929
			if (count($values) > 132 || strlen($dhcp6duid) != count($values) * 3 - 1)
2930
				return false;
2931
		}
2932
	} else
2933
		return false;
2934

    
2935
	for ($i = 0; $i < count($values); $i++) {
2936
		if (ctype_xdigit($values[$i]) == false)
2937
			return false;
2938
		if (hexdec($values[$i]) < 0 || hexdec($values[$i]) > 255)
2939
			return false;
2940
	}
2941

    
2942
	return true;
2943
}
2944

    
2945
/* Write the DHCP6 DUID file */
2946
function write_dhcp6_duid($duidstring) {
2947
	// Create the hex array from the dhcp6duid config entry and write to file
2948
	global $g;
2949

    
2950
	if(!is_duid($duidstring)) {
2951
		log_error(gettext("Error: attempting to write DUID file - Invalid DUID detected"));
2952
		return false;
2953
	}
2954
	$temp = str_replace(":","",$duidstring);
2955
	$duid_binstring = pack("H*",$temp);
2956
	if ($fd = fopen("{$g['vardb_path']}/dhcp6c_duid", "wb")) {
2957
		fwrite($fd, $duid_binstring);
2958
		fclose($fd);
2959
		return true;
2960
	}
2961
	log_error(gettext("Error: attempting to write DUID file - File write error"));
2962
	return false;
2963
}
2964

    
2965
/* returns duid string from 'vardb_path']}/dhcp6c_duid' */
2966
function get_duid_from_file() {
2967
	global $g;
2968

    
2969
	$duid_ASCII = "";
2970
	$count = 0;
2971

    
2972
	if (file_exists("{$g['vardb_path']}/dhcp6c_duid") &&
2973
	    ($fd = fopen("{$g['vardb_path']}/dhcp6c_duid", "r"))) {
2974
		$fsize = filesize("{$g['vardb_path']}/dhcp6c_duid");
2975
		if ($fsize <= 132) {
2976
			$buffer = fread($fd, $fsize);
2977
			while($count < $fsize) {
2978
				$duid_ASCII .= bin2hex($buffer[$count]);
2979
				$count++;
2980
				if($count < $fsize) {
2981
					$duid_ASCII .= ":";
2982
				}
2983
			}
2984
		}
2985
		fclose($fd);
2986
	}
2987
	//if no file or error with read then the string returns blanked DUID string
2988
	if(!is_duid($duid_ASCII)) {
2989
		return "--:--:--:--:--:--:--:--:--:--:--:--:--:--:--:--";
2990
	}
2991
	return($duid_ASCII);
2992
}
2993

    
2994
/* Replaces the Mac OS 9 and earlier (\r) and DOS/Windows (\r\n) newlines with the Unix equivalent (\n). */
2995
function unixnewlines($text) {
2996
	return preg_replace('/\r\n?/', "\n", $text);
2997
}
2998

    
2999
function array_remove_duplicate($array, $field) {
3000
	foreach ($array as $sub) {
3001
		if (isset($sub[$field])) {
3002
			$cmp[] = $sub[$field];
3003
		}
3004
	}
3005
	$unique = array_unique(array_reverse($cmp, true));
3006
	foreach ($unique as $k => $rien) {
3007
		$new[] = $array[$k];
3008
	}
3009
	return $new;
3010
}
3011

    
3012
function dhcpd_date_adjust_gmt($dt) {
3013
	global $config;
3014

    
3015
	init_config_arr(array('dhcpd'));
3016

    
3017
	foreach ($config['dhcpd'] as $dhcpditem) {
3018
		if ($dhcpditem['dhcpleaseinlocaltime'] == "yes") {
3019
			$ts = strtotime($dt . " GMT");
3020
			if ($ts !== false) {
3021
				return strftime("%Y/%m/%d %H:%M:%S", $ts);
3022
			}
3023
		}
3024
	}
3025

    
3026
	/*
3027
	 * If we did not need to convert to local time or the conversion
3028
	 * failed, just return the input.
3029
	 */
3030
	return $dt;
3031
}
3032

    
3033
global $supported_image_types;
3034
$supported_image_types = array(
3035
	IMAGETYPE_JPEG,
3036
	IMAGETYPE_PNG,
3037
	IMAGETYPE_GIF,
3038
	IMAGETYPE_WEBP
3039
);
3040

    
3041
function is_supported_image($image_filename) {
3042
	global $supported_image_types;
3043
	$img_info = getimagesize($image_filename);
3044

    
3045
	/* If it's not an image, or it isn't in the supported list, return false */
3046
	if (($img_info === false) ||
3047
	    !in_array($img_info[2], array_keys($supported_image_types))) {
3048
		return false;
3049
	} else {
3050
		return $img_info[2];
3051
	}
3052
}
3053

    
3054
function get_lagg_ports ($laggport) {
3055
	$laggp = array();
3056
	foreach ($laggport as $lgp) {
3057
		list($lpname, $lpinfo) = explode(" ", $lgp);
3058
		preg_match('~<(.+)>~', $lpinfo, $lgportmode);
3059
		if ($lgportmode[1]) {
3060
			$laggp[] = $lpname . " (" . $lgportmode[1] . ")";
3061
		} else {
3062
			$laggp[] = $lpname;
3063
		}
3064
	}
3065
	if ($laggp) {
3066
		return implode(", ", $laggp);
3067
	} else {
3068
		return false;
3069
	}
3070
}
3071

    
3072
?>
(53-53/61)