Project

General

Profile

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

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

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

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

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

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

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

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

    
71
	return 0;
72
}
73

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

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

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

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

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

    
99
	return false;
100
}
101

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

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

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

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

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

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

    
155
		return $fp;
156
	}
157

    
158
	return NULL;
159
}
160

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
360
	foreach ( $parts as $v ) {
361

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

    
365
	}
366

    
367
	return $binstr;
368
}
369

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

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

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

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

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

    
391
	return $ip;
392
}
393

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

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

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

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

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

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

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

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

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

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

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

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

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

    
499
	return $rangeaddresses;
500
}
501

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

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

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

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

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

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

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

    
563
		// step #1 if start ip (as shifted) ends in any '1's, then it must have a single cidr to itself (any cidr would include the '0' below it)
564

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

    
573
		// step #2, if end ip (as shifted) ends in any zeros then that must have a cidr to itself (as cidr cant span the 1->0 gap)
574

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

    
584
		// this is the only edge case arising from increment/decrement.
585
		// it happens if the range at start of loop is exactly 2 adjacent ips, that spanned the 1->0 gap. (we will have enumerated both by now)
586

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

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

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

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

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

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

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

    
622
	return $out;
623
}
624

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

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

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

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

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

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

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

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

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

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

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

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

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

    
766
}
767

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

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

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

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

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

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

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

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

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

    
840
	// 2**N returns an exact result as an INT if possible, and a float/double if not.
841
	// Detect this switch, rather than comparing $result<=PHP_MAX_INT or $bits >=8*PHP_INT_SIZE as it's (probably) easier to get completely reliable
842
	$result = 2 ** $snsize;
843

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
954
	return $result;
955
}
956

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

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

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

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

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

    
1000
/* returns true if $domain is a valid domain name */
1001
function is_domain($domain, $allow_wildcard=false) {
1002
	if (!is_string($domain)) {
1003
		return false;
1004
	}
1005
	if ($allow_wildcard) {
1006
		$domain_regex = '/^(?:(?:[a-z_0-9\*]|[a-z_0-9][a-z_0-9\-]*[a-z_0-9])\.)*(?:[a-z_0-9]|[a-z_0-9][a-z_0-9\-]*[a-z_0-9\.])$/i';
1007
	} else {
1008
		$domain_regex = '/^(?:(?:[a-z_0-9]|[a-z_0-9][a-z_0-9\-]*[a-z_0-9])\.)*(?:[a-z_0-9]|[a-z_0-9][a-z_0-9\-]*[a-z_0-9\.])$/i';
1009
	}
1010

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

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

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

    
1037
	return true;
1038
}
1039

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

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

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

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

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

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

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

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

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

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

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

    
1123
	return true;
1124
}
1125

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1228
	return array_merge($comments, $result);
1229
}
1230

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

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

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

    
1247
	return false;
1248
}
1249

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

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

    
1261
	return false;
1262
}
1263

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

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

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

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

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

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

    
1302
function get_configured_vip($vipinterface = '') {
1303

    
1304
	return (get_configured_vip_detail($vipinterface, 'all', 'vip'));
1305
}
1306

    
1307
function get_configured_vip_interface($vipinterface = '') {
1308

    
1309
	return (get_configured_vip_detail($vipinterface, 'all', 'iface'));
1310
}
1311

    
1312
function get_configured_vip_ipv4($vipinterface = '') {
1313

    
1314
	return (get_configured_vip_detail($vipinterface, 'inet', 'ip'));
1315
}
1316

    
1317
function get_configured_vip_ipv6($vipinterface = '') {
1318

    
1319
	return (get_configured_vip_detail($vipinterface, 'inet6', 'ip'));
1320
}
1321

    
1322
function get_configured_vip_subnetv4($vipinterface = '') {
1323

    
1324
	return (get_configured_vip_detail($vipinterface, 'inet', 'subnet'));
1325
}
1326

    
1327
function get_configured_vip_subnetv6($vipinterface = '') {
1328

    
1329
	return (get_configured_vip_detail($vipinterface, 'inet6', 'subnet'));
1330
}
1331

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

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

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

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

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

    
1377
	return (NULL);
1378
}
1379

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

    
1394
	return strnatcmp($a, $b);
1395
}
1396

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

    
1401
	$iflist = array();
1402

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

    
1410
	return $iflist;
1411
}
1412

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

    
1417
	$iflist = array();
1418

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

    
1429
	return $iflist;
1430
}
1431

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

    
1436
	$iflist = array();
1437

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

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

    
1453
	return $iflist;
1454
}
1455

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

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

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

    
1494
	return $ip_array;
1495
}
1496

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

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

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

    
1638
function get_lagg_interface_list() {
1639
	global $config;
1640

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

    
1650
	return ($plist);
1651
}
1652

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

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

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

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

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

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

    
1738
	if ($clearsigmask) {
1739
		pcntl_sigprocmask(SIG_SETMASK, $oldset);
1740
	}
1741

    
1742
	return $retval;
1743
}
1744

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

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

    
1773
	$aliastable = array();
1774

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

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

    
1788
	return isset($aliastable[$name]);
1789
}
1790

    
1791
function alias_get_type($name) {
1792
	global $config;
1793

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

    
1802
	return "";
1803
}
1804

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

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

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

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

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

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

    
1895
function mac_format($clientmac) {
1896
	global $config, $cpzone;
1897

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

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

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

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

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

    
1914
		default:
1915
			return $clientmac;
1916
	}
1917
}
1918

    
1919
function resolve_retry($hostname, $retries = 5) {
1920

    
1921
	if (is_ipaddr($hostname)) {
1922
		return $hostname;
1923
	}
1924

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

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

    
1934
		sleep(1);
1935
	}
1936

    
1937
	return false;
1938
}
1939

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

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

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

    
1964
	return ("$num {$units[$i]}");
1965
}
1966

    
1967
function update_filter_reload_status($text, $new=false) {
1968
	global $g;
1969

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

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

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

    
2005
function run_plugins($directory) {
2006
	global $config, $g;
2007

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

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

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

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

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

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

    
2063
	return $values;
2064
}
2065

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

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

    
2081
	return $value[$name];
2082
}
2083

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

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

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

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

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

    
2116
	return $ret;
2117
}
2118

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

    
2129
	$result = set_sysctl(array($name => $value));
2130

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

    
2135
	return true;
2136
}
2137

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

    
2152
function mute_kernel_msgs() {
2153
	global $g, $config;
2154

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

    
2161
function unmute_kernel_msgs() {
2162
	global $g;
2163

    
2164
	exec("/sbin/conscontrol mute off");
2165
}
2166

    
2167
function start_devd() {
2168
	global $g;
2169

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

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

    
2200
function is_interface_vlan_mismatch() {
2201
	global $config, $g;
2202

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

    
2214
	return false;
2215
}
2216

    
2217
function is_interface_mismatch() {
2218
	global $config, $g;
2219

    
2220
	$do_assign = false;
2221
	$i = 0;
2222
	$missing_interfaces = array();
2223
	if (is_array($config['interfaces'])) {
2224
		foreach ($config['interfaces'] as $ifname => $ifcfg) {
2225
			if (interface_is_vlan($ifcfg['if']) != NULL ||
2226
			    interface_is_qinq($ifcfg['if']) != NULL ||
2227
			    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'])) {
2228
				// Do not check these interfaces.
2229
				$i++;
2230
				continue;
2231
			} else if (does_interface_exist($ifcfg['if']) == false) {
2232
				$missing_interfaces[] = $ifcfg['if'];
2233
				$do_assign = true;
2234
			} else {
2235
				$i++;
2236
			}
2237
		}
2238
	}
2239

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

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

    
2250
	return $do_assign;
2251
}
2252

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

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

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

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

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

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

    
2358
	return false;
2359
}
2360

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

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

    
2377
	return $data;
2378
}
2379

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

    
2386
	$arrays = func_get_args();
2387
	$remains = $arrays;
2388

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

    
2393
	// loop available array
2394
	foreach ($arrays as $array) {
2395

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

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

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

    
2434

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

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

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

    
2470
	if (empty($args)) {
2471
		return false;
2472
	}
2473

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

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

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

    
2498
	return ($rc == 0);
2499
}
2500

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
2728
	return false;
2729
}
2730

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

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

    
2770
	$values = explode(':', strtolower(str_replace('-', ':', $duidpt1)));
2771

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

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

    
2779
	return implode(":", $values);
2780
}
2781

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

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

    
2819
	return true;
2820
}
2821

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

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

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

    
2846
	$duid_ASCII = "";
2847
	$count = 0;
2848

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

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

    
2876
function array_remove_duplicate($array, $field) {
2877
	foreach ($array as $sub) {
2878
		if (isset($sub[$field])) {
2879
			$cmp[] = $sub[$field];
2880
		}
2881
	}
2882
	$unique = array_unique(array_reverse($cmp, true));
2883
	foreach ($unique as $k => $rien) {
2884
		$new[] = $array[$k];
2885
	}
2886
	return $new;
2887
}
2888

    
2889
function dhcpd_date_adjust_gmt($dt) {
2890
	global $config;
2891

    
2892
	init_config_arr(array('dhcpd'));
2893

    
2894
	foreach ($config['dhcpd'] as $dhcpditem) {
2895
		if ($dhcpditem['dhcpleaseinlocaltime'] == "yes") {
2896
			$ts = strtotime($dt . " GMT");
2897
			if ($ts !== false) {
2898
				return strftime("%Y/%m/%d %H:%M:%S", $ts);
2899
			}
2900
		}
2901
	}
2902

    
2903
	/*
2904
	 * If we did not need to convert to local time or the conversion
2905
	 * failed, just return the input.
2906
	 */
2907
	return $dt;
2908
}
2909

    
2910
?>
(52-52/59)