Project

General

Profile

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

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

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

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

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

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

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

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

    
69
	return 0;
70
}
71

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

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

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

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

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

    
97
	return false;
98
}
99

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

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

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

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

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

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

    
153
		return $fp;
154
	}
155

    
156
	return NULL;
157
}
158

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
358
	foreach ( $parts as $v ) {
359

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

    
363
	}
364

    
365
	return $binstr;
366
}
367

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

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

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

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

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

    
389
	return $ip;
390
}
391

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

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

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

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

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

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

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

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

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

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

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

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

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

    
497
	return $rangeaddresses;
498
}
499

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
620
	return $out;
621
}
622

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

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

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

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

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

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

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

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

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

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

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

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

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

    
764
}
765

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
952
	return $result;
953
}
954

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

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

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

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

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

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

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

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

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

    
1035
	return true;
1036
}
1037

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

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

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

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

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

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

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

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

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

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

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

    
1121
	return true;
1122
}
1123

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1245
	return false;
1246
}
1247

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

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

    
1259
	return false;
1260
}
1261

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1375
	return (NULL);
1376
}
1377

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

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

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

    
1399
	$iflist = array();
1400

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

    
1408
	return $iflist;
1409
}
1410

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

    
1415
	$iflist = array();
1416

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

    
1427
	return $iflist;
1428
}
1429

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

    
1434
	$iflist = array();
1435

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

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

    
1451
	return $iflist;
1452
}
1453

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

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

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

    
1492
	return $ip_array;
1493
}
1494

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

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

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

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

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

    
1648
	return ($plist);
1649
}
1650

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

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

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

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

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

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

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

    
1740
	return $retval;
1741
}
1742

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

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

    
1771
	$aliastable = array();
1772

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

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

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

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

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

    
1800
	return "";
1801
}
1802

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1932
		sleep(1);
1933
	}
1934

    
1935
	return false;
1936
}
1937

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
2061
	return $values;
2062
}
2063

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

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

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

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

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

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

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

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

    
2114
	return $ret;
2115
}
2116

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

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

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

    
2133
	return true;
2134
}
2135

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

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

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

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

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

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

    
2168
	/* Use the undocumented -q options of devd to quiet its log spamming */
2169
	$_gb = exec("/sbin/devd -q -f /etc/{$g['product_name']}-devd.conf");
2170
	sleep(1);
2171
	unset($_gb);
2172
}
2173

    
2174
function is_interface_vlan_mismatch() {
2175
	global $config, $g;
2176

    
2177
	if (is_array($config['vlans']['vlan'])) {
2178
		foreach ($config['vlans']['vlan'] as $vlan) {
2179
			if (substr($vlan['if'], 0, 4) == "lagg") {
2180
				return false;
2181
			}
2182
			if (does_interface_exist($vlan['if']) == false) {
2183
				return true;
2184
			}
2185
		}
2186
	}
2187

    
2188
	return false;
2189
}
2190

    
2191
function is_interface_mismatch() {
2192
	global $config, $g;
2193

    
2194
	$do_assign = false;
2195
	$i = 0;
2196
	$missing_interfaces = array();
2197
	if (is_array($config['interfaces'])) {
2198
		foreach ($config['interfaces'] as $ifname => $ifcfg) {
2199
			if (interface_is_vlan($ifcfg['if']) != NULL ||
2200
			    interface_is_qinq($ifcfg['if']) != NULL ||
2201
			    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'])) {
2202
				// Do not check these interfaces.
2203
				$i++;
2204
				continue;
2205
			} else if (does_interface_exist($ifcfg['if']) == false) {
2206
				$missing_interfaces[] = $ifcfg['if'];
2207
				$do_assign = true;
2208
			} else {
2209
				$i++;
2210
			}
2211
		}
2212
	}
2213

    
2214
	if (file_exists("{$g['tmp_path']}/assign_complete")) {
2215
		$do_assign = false;
2216
	}
2217

    
2218
	if (!empty($missing_interfaces) && $do_assign) {
2219
		file_put_contents("{$g['tmp_path']}/missing_interfaces", implode(' ', $missing_interfaces));
2220
	} else {
2221
		@unlink("{$g['tmp_path']}/missing_interfaces");
2222
	}
2223

    
2224
	return $do_assign;
2225
}
2226

    
2227
/* sync carp entries to other firewalls */
2228
function carp_sync_client() {
2229
	global $g;
2230
	send_event("filter sync");
2231
}
2232

    
2233
/****f* util/isAjax
2234
 * NAME
2235
 *   isAjax - reports if the request is driven from prototype
2236
 * INPUTS
2237
 *   none
2238
 * RESULT
2239
 *   true/false
2240
 ******/
2241
function isAjax() {
2242
	return isset ($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest';
2243
}
2244

    
2245
/****f* util/timeout
2246
 * NAME
2247
 *   timeout - console input with timeout countdown. Note: erases 2 char of screen for timer. Leave space.
2248
 * INPUTS
2249
 *   optional, seconds to wait before timeout. Default 9 seconds.
2250
 * RESULT
2251
 *   returns 1 char of user input or null if no input.
2252
 ******/
2253
function timeout($timer = 9) {
2254
	while (!isset($key)) {
2255
		if ($timer >= 9) {
2256
			echo chr(8) . chr(8) . ($timer == 9 ? chr(32) : null) . "{$timer}";
2257
		} else {
2258
			echo chr(8). "{$timer}";
2259
		}
2260
		`/bin/stty -icanon min 0 time 25`;
2261
		$key = trim(`KEY=\`dd count=1 2>/dev/null\`; echo \$KEY`);
2262
		`/bin/stty icanon`;
2263
		if ($key == '') {
2264
			unset($key);
2265
		}
2266
		$timer--;
2267
		if ($timer == 0) {
2268
			break;
2269
		}
2270
	}
2271
	return $key;
2272
}
2273

    
2274
/****f* util/msort
2275
 * NAME
2276
 *   msort - sort array
2277
 * INPUTS
2278
 *   $array to be sorted, field to sort by, direction of sort
2279
 * RESULT
2280
 *   returns newly sorted array
2281
 ******/
2282
function msort($array, $id = "id", $sort_ascending = true) {
2283
	$temp_array = array();
2284
	if (!is_array($array)) {
2285
		return $temp_array;
2286
	}
2287
	while (count($array)>0) {
2288
		$lowest_id = 0;
2289
		$index = 0;
2290
		foreach ($array as $item) {
2291
			if (isset($item[$id])) {
2292
				if ($array[$lowest_id][$id]) {
2293
					if (strtolower($item[$id]) < strtolower($array[$lowest_id][$id])) {
2294
						$lowest_id = $index;
2295
					}
2296
				}
2297
			}
2298
			$index++;
2299
		}
2300
		$temp_array[] = $array[$lowest_id];
2301
		$array = array_merge(array_slice($array, 0, $lowest_id), array_slice($array, $lowest_id + 1));
2302
	}
2303
	if ($sort_ascending) {
2304
		return $temp_array;
2305
	} else {
2306
		return array_reverse($temp_array);
2307
	}
2308
}
2309

    
2310
/****f* util/is_URL
2311
 * NAME
2312
 *   is_URL
2313
 * INPUTS
2314
 *   string to check
2315
 * RESULT
2316
 *   Returns true if item is a URL
2317
 ******/
2318
function is_URL($url) {
2319
	$match = preg_match("'\b(([\w-]+://?|www[.])[^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|/)))'", $url);
2320
	if ($match) {
2321
		return true;
2322
	}
2323
	return false;
2324
}
2325

    
2326
function is_file_included($file = "") {
2327
	$files = get_included_files();
2328
	if (in_array($file, $files)) {
2329
		return true;
2330
	}
2331

    
2332
	return false;
2333
}
2334

    
2335
/*
2336
 * Replace a value on a deep associative array using regex
2337
 */
2338
function array_replace_values_recursive($data, $match, $replace) {
2339
	if (empty($data)) {
2340
		return $data;
2341
	}
2342

    
2343
	if (is_string($data)) {
2344
		$data = preg_replace("/{$match}/", $replace, $data);
2345
	} else if (is_array($data)) {
2346
		foreach ($data as $k => $v) {
2347
			$data[$k] = array_replace_values_recursive($v, $match, $replace);
2348
		}
2349
	}
2350

    
2351
	return $data;
2352
}
2353

    
2354
/*
2355
	This function was borrowed from a comment on PHP.net at the following URL:
2356
	http://www.php.net/manual/en/function.array-merge-recursive.php#73843
2357
 */
2358
function array_merge_recursive_unique($array0, $array1) {
2359

    
2360
	$arrays = func_get_args();
2361
	$remains = $arrays;
2362

    
2363
	// We walk through each arrays and put value in the results (without
2364
	// considering previous value).
2365
	$result = array();
2366

    
2367
	// loop available array
2368
	foreach ($arrays as $array) {
2369

    
2370
		// The first remaining array is $array. We are processing it. So
2371
		// we remove it from remaining arrays.
2372
		array_shift($remains);
2373

    
2374
		// We don't care non array param, like array_merge since PHP 5.0.
2375
		if (is_array($array)) {
2376
			// Loop values
2377
			foreach ($array as $key => $value) {
2378
				if (is_array($value)) {
2379
					// we gather all remaining arrays that have such key available
2380
					$args = array();
2381
					foreach ($remains as $remain) {
2382
						if (array_key_exists($key, $remain)) {
2383
							array_push($args, $remain[$key]);
2384
						}
2385
					}
2386

    
2387
					if (count($args) > 2) {
2388
						// put the recursion
2389
						$result[$key] = call_user_func_array(__FUNCTION__, $args);
2390
					} else {
2391
						foreach ($value as $vkey => $vval) {
2392
							if (!is_array($result[$key])) {
2393
								$result[$key] = array();
2394
							}
2395
							$result[$key][$vkey] = $vval;
2396
						}
2397
					}
2398
				} else {
2399
					// simply put the value
2400
					$result[$key] = $value;
2401
				}
2402
			}
2403
		}
2404
	}
2405
	return $result;
2406
}
2407

    
2408

    
2409
/*
2410
 * converts a string like "a,b,c,d"
2411
 * into an array like array("a" => "b", "c" => "d")
2412
 */
2413
function explode_assoc($delimiter, $string) {
2414
	$array = explode($delimiter, $string);
2415
	$result = array();
2416
	$numkeys = floor(count($array) / 2);
2417
	for ($i = 0; $i < $numkeys; $i += 1) {
2418
		$result[$array[$i * 2]] = $array[$i * 2 + 1];
2419
	}
2420
	return $result;
2421
}
2422

    
2423
/*
2424
 * Given a string of text with some delimiter, look for occurrences
2425
 * of some string and replace all of those.
2426
 * $text - the text string (e.g. "abc,defg,x123,ipv4,xyz")
2427
 * $delimiter - the delimiter (e.g. ",")
2428
 * $element - the element to match (e.g. "defg")
2429
 * $replacement - the string to replace it with (e.g. "42")
2430
 * Returns the resulting delimited string (e.g. "abc,42,x123,ipv4,xyz")
2431
 */
2432
function replace_element_in_list($text, $delimiter, $element, $replacement) {
2433
	$textArray = explode($delimiter, $text);
2434
	while (($entry = array_search($element, $textArray)) !== false) {
2435
		$textArray[$entry] = $replacement;
2436
	}
2437
	return implode(',', $textArray);
2438
}
2439

    
2440
/* Try to change a static route, if it doesn't exist, add it */
2441
function route_add_or_change($args) {
2442
	global $config;
2443

    
2444
	if (empty($args)) {
2445
		return false;
2446
	}
2447

    
2448
	/* First, try to add it */
2449
	$_gb = exec(escapeshellcmd("/sbin/route add " . $args), $output, $rc);
2450
		
2451
	if (isset($config['system']['route-debug'])) {
2452
		$add_change = 'add';
2453
		$mt = microtime();
2454
		log_error("ROUTING debug: $mt - ADD RC={$rc} - $args");
2455
	}
2456

    
2457
	if ($rc != 0) {
2458
		/* If it fails, try to change it */
2459
		$_gb = exec(escapeshellcmd("/sbin/route change " . $args),
2460
		    $output, $rc);
2461

    
2462
		if (isset($config['system']['route-debug'])) {
2463
			$add_change = 'change';
2464
			$mt = microtime();
2465
			log_error("ROUTING debug: $mt - CHG RC={$rc} - $args");
2466
		}
2467
	}
2468
	if (isset($config['system']['route-debug'])) {
2469
		file_put_contents("/dev/console", "\n[".getmypid()."] ROUTE: {$add_change} {$args} result: {$rc}");
2470
	}
2471

    
2472
	return ($rc == 0);
2473
}
2474

    
2475
function alias_to_subnets_recursive($name, $returnhostnames = false) {
2476
	global $aliastable;
2477
	$result = array();
2478
	if (!isset($aliastable[$name])) {
2479
		return $result;
2480
	}
2481
	$subnets = preg_split('/\s+/', $aliastable[$name]);
2482
	foreach ($subnets as $net) {
2483
		if (is_alias($net)) {
2484
			$sub = alias_to_subnets_recursive($net, $returnhostnames);
2485
			$result = array_merge($result, $sub);
2486
			continue;
2487
		} elseif (!is_subnet($net)) {
2488
			if (is_ipaddrv4($net)) {
2489
				$net .= "/32";
2490
			} else if (is_ipaddrv6($net)) {
2491
				$net .= "/128";
2492
			} else if ($returnhostnames === false || !is_fqdn($net)) {
2493
				continue;
2494
			}
2495
		}
2496
		$result[] = $net;
2497
	}
2498
	return $result;
2499
}
2500

    
2501
function get_staticroutes($returnsubnetsonly = false, $returnhostnames = false, $returnenabledroutesonly = false) {
2502
	global $config, $aliastable;
2503

    
2504
	/* Bail if there are no routes, but return an array always so callers don't have to check. */
2505
	if (!is_array($config['staticroutes']['route'])) {
2506
		return array();
2507
	}
2508

    
2509
	$allstaticroutes = array();
2510
	$allsubnets = array();
2511
	/* Loop through routes and expand aliases as we find them. */
2512
	foreach ($config['staticroutes']['route'] as $route) {
2513
		if ($returnenabledroutesonly && isset($route['disabled'])) {
2514
			continue;
2515
		}
2516

    
2517
		if (is_alias($route['network'])) {
2518
			foreach (alias_to_subnets_recursive($route['network'], $returnhostnames) as $net) {
2519
				$temproute = $route;
2520
				$temproute['network'] = $net;
2521
				$allstaticroutes[] = $temproute;
2522
				$allsubnets[] = $net;
2523
			}
2524
		} elseif (is_subnet($route['network'])) {
2525
			$allstaticroutes[] = $route;
2526
			$allsubnets[] = $route['network'];
2527
		}
2528
	}
2529
	if ($returnsubnetsonly) {
2530
		return $allsubnets;
2531
	} else {
2532
		return $allstaticroutes;
2533
	}
2534
}
2535

    
2536
/****f* util/get_alias_list
2537
 * NAME
2538
 *   get_alias_list - Provide a list of aliases.
2539
 * INPUTS
2540
 *   $type          - Optional, can be a string or array specifying what type(s) of aliases you need.
2541
 * RESULT
2542
 *   Array containing list of aliases.
2543
 *   If $type is unspecified, all aliases are returned.
2544
 *   If $type is a string, all aliases of the type specified in $type are returned.
2545
 *   If $type is an array, all aliases of any type specified in any element of $type are returned.
2546
 */
2547
function get_alias_list($type = null) {
2548
	global $config;
2549
	$result = array();
2550
	if ($config['aliases']['alias'] <> "" && is_array($config['aliases']['alias'])) {
2551
		foreach ($config['aliases']['alias'] as $alias) {
2552
			if ($type === null) {
2553
				$result[] = $alias['name'];
2554
			} else if (is_array($type)) {
2555
				if (in_array($alias['type'], $type)) {
2556
					$result[] = $alias['name'];
2557
				}
2558
			} else if ($type === $alias['type']) {
2559
				$result[] = $alias['name'];
2560
			}
2561
		}
2562
	}
2563
	return $result;
2564
}
2565

    
2566
/* returns an array consisting of every element of $haystack that is not equal to $needle. */
2567
function array_exclude($needle, $haystack) {
2568
	$result = array();
2569
	if (is_array($haystack)) {
2570
		foreach ($haystack as $thing) {
2571
			if ($needle !== $thing) {
2572
				$result[] = $thing;
2573
			}
2574
		}
2575
	}
2576
	return $result;
2577
}
2578

    
2579
/* Define what is preferred, IPv4 or IPv6 */
2580
function prefer_ipv4_or_ipv6() {
2581
	global $config;
2582

    
2583
	if (isset($config['system']['prefer_ipv4'])) {
2584
		mwexec("/etc/rc.d/ip6addrctl prefer_ipv4");
2585
	} else {
2586
		mwexec("/etc/rc.d/ip6addrctl prefer_ipv6");
2587
	}
2588
}
2589

    
2590
/* Redirect to page passing parameters via POST */
2591
function post_redirect($page, $params) {
2592
	if (!is_array($params)) {
2593
		return;
2594
	}
2595

    
2596
	print "<html><body><form action=\"{$page}\" name=\"formredir\" method=\"post\">\n";
2597
	foreach ($params as $key => $value) {
2598
		print "<input type=\"hidden\" name=\"{$key}\" value=\"{$value}\" />\n";
2599
	}
2600
	print "</form>\n";
2601
	print "<script type=\"text/javascript\">\n";
2602
	print "//<![CDATA[\n";
2603
	print "document.formredir.submit();\n";
2604
	print "//]]>\n";
2605
	print "</script>\n";
2606
	print "</body></html>\n";
2607
}
2608

    
2609
/* Locate disks that can be queried for S.M.A.R.T. data. */
2610
function get_smart_drive_list() {
2611
	/* SMART supports some disks directly, and some controllers directly,
2612
	 * See https://redmine.pfsense.org/issues/9042 */
2613
	$supported_disk_types = array("ad", "da", "ada");
2614
	$supported_controller_types = array("nvme");
2615
	$disk_list = explode(" ", get_single_sysctl("kern.disks"));
2616
	foreach ($disk_list as $id => $disk) {
2617
		// We only want certain kinds of disks for S.M.A.R.T.
2618
		// 1 is a match, 0 is no match, False is any problem processing the regex
2619
		if (preg_match("/^(" . implode("|", $supported_disk_types) . ").*[0-9]{1,2}$/", $disk) !== 1) {
2620
			unset($disk_list[$id]);
2621
			continue;
2622
		}
2623
	}
2624
	foreach ($supported_controller_types as $controller) {
2625
		$devices = glob("/dev/{$controller}*");
2626
		if (!is_array($devices)) {
2627
			continue;
2628
		}
2629
		foreach ($devices as $device) {
2630
			$disk_list[] = basename($device);
2631
		}
2632
	}
2633
	sort($disk_list);
2634
	return $disk_list;
2635
}
2636

    
2637
// Validate a network address
2638
//	$addr: the address to validate
2639
//	$type: IPV4|IPV6|IPV4V6
2640
//	$label: the label used by the GUI to display this value. Required to compose an error message
2641
//	$err_msg: pointer to the callers error message array so that error messages can be added to it here
2642
//	$alias: are aliases permitted for this address?
2643
// Returns:
2644
//	IPV4 - if $addr is a valid IPv4 address
2645
//	IPV6 - if $addr is a valid IPv6 address
2646
//	ALIAS - if $alias=true and $addr is an alias
2647
//	false - otherwise
2648

    
2649
function validateipaddr(&$addr, $type, $label, &$err_msg, $alias=false) {
2650
	switch ($type) {
2651
		case IPV4:
2652
			if (is_ipaddrv4($addr)) {
2653
				return IPV4;
2654
			} else if ($alias) {
2655
				if (is_alias($addr)) {
2656
					return ALIAS;
2657
				} else {
2658
					$err_msg[] = sprintf(gettext("%s must be a valid IPv4 address or alias."), $label);
2659
					return false;
2660
				}
2661
			} else {
2662
				$err_msg[] = sprintf(gettext("%s must be a valid IPv4 address."), $label);
2663
				return false;
2664
			}
2665
		break;
2666
		case IPV6:
2667
			if (is_ipaddrv6($addr)) {
2668
				$addr = strtolower($addr);
2669
				return IPV6;
2670
			} else if ($alias) {
2671
				if (is_alias($addr)) {
2672
					return ALIAS;
2673
				} else {
2674
					$err_msg[] = sprintf(gettext("%s must be a valid IPv6 address or alias."), $label);
2675
					return false;
2676
				}
2677
			} else {
2678
				$err_msg[] = sprintf(gettext("%s must be a valid IPv6 address."), $label);
2679
				return false;
2680
			}
2681
		break;
2682
		case IPV4V6:
2683
			if (is_ipaddrv6($addr)) {
2684
				$addr = strtolower($addr);
2685
				return IPV6;
2686
			} else if (is_ipaddrv4($addr)) {
2687
				return IPV4;
2688
			} else if ($alias) {
2689
				if (is_alias($addr)) {
2690
					return ALIAS;
2691
				} else {
2692
					$err_msg[] = sprintf(gettext("%s must be a valid IPv4 or IPv6 address or alias."), $label);
2693
					return false;
2694
				}
2695
			} else {
2696
				$err_msg[] = sprintf(gettext("%s must be a valid IPv4 or IPv6 address."), $label);
2697
				return false;
2698
			}
2699
		break;
2700
	}
2701

    
2702
	return false;
2703
}
2704

    
2705
/* From DUID configuration inputs, format a string that looks (more) like the expected raw DUID format:
2706
 * 1) For DUIDs entered as a known DUID type, convert to a hexstring and prepend the DUID number, after having done the following:
2707
 *     a) For DUID-LLT and DUID-EN, convert the time/enterprise ID input to hex and append the link-layer address/identifier input.
2708
 *     b) For DUID-LLT and DUID-LL, prepend a hardware type of 1.
2709
 *     c) For DUID-UUID, remove any "-".
2710
 * 2) Replace any remaining "-" with ":".
2711
 * 3) If any components are input with just a single char (hex digit hopefully), put a "0" in front.
2712
 * 4) The first two components should be a 16-bit integer (little- or big-endian, depending on the current machine type) that
2713
 *    is equal to the number of other components. If not, prepend this as "nn:00" (all pfSense builds are little-endian).
2714
 *    This is convenience, because the DUID reported by dhcp6c in logs does not include this count, which corresponds to the
2715
 *    option-len field of DHCPv6's OPTION_CLIENTID option.
2716
 *
2717
 * The final result should be closer to:
2718
 *
2719
 * "nn:00:00:0n:nn:nn:nn:..."
2720
 *
2721
 * This function does not validate the input. is_duid() will do validation.
2722
 */
2723
function format_duid($duidtype, $duidpt1, $duidpt2=null) {
2724
	if ($duidpt2)
2725
		$duidpt1 = implode(':', str_split(str_pad(dechex($duidpt1), 8, '0', STR_PAD_LEFT), 2)) . ':' . $duidpt2;
2726

    
2727
	/* Make hexstrings */
2728
	if ($duidtype) {
2729
		switch ($duidtype) {
2730
		/* Add a hardware type to DUID-LLT and DUID-LL; assume Ethernet */
2731
		case 1:
2732
		case 3:
2733
			$duidpt1 = '00:01:' . $duidpt1;
2734
			break;
2735
		/* Remove '-' from given UUID and insert ':' every 2 characters */
2736
		case 4:
2737
			$duidpt1 = implode(':', str_split(str_replace('-', '', $duidpt1), 2));
2738
			break;
2739
		default:
2740
		}
2741
		$duidpt1 = '00:0' . $duidtype . ':' . $duidpt1;
2742
	}
2743

    
2744
	$values = explode(':', strtolower(str_replace('-', ':', $duidpt1)));
2745

    
2746
	if (hexdec($values[0]) != count($values) - 2)
2747
		array_unshift($values, dechex(count($values)), '00');
2748

    
2749
	array_walk($values, function(&$value) {
2750
		$value = str_pad($value, 2, '0', STR_PAD_LEFT);
2751
	});
2752

    
2753
	return implode(":", $values);
2754
}
2755

    
2756
/* Returns true if $dhcp6duid is a valid DUID entry.
2757
 * Parse the entry to check for valid length according to known DUID types.
2758
 */
2759
function is_duid($dhcp6duid) {
2760
	$values = explode(":", $dhcp6duid);
2761
	if (hexdec($values[0]) == count($values) - 2) {
2762
		switch (hexdec($values[2] . $values[3])) {
2763
		case 0:
2764
			return false;
2765
			break;
2766
		case 1:
2767
			if (count($values) != 16 || strlen($dhcp6duid) != 47)
2768
				return false;
2769
			break;
2770
		case 3:
2771
			if (count($values) != 12 || strlen($dhcp6duid) != 35)
2772
				return false;
2773
			break;
2774
		case 4:
2775
			if (count($values) != 20 || strlen($dhcp6duid) != 59)
2776
				return false;
2777
			break;
2778
		/* DUID is up to 128 octets; allow 2 octets for type code, 2 more for option-len */
2779
		default:
2780
			if (count($values) > 132 || strlen($dhcp6duid) != count($values) * 3 - 1)
2781
				return false;
2782
		}
2783
	} else
2784
		return false;
2785

    
2786
	for ($i = 0; $i < count($values); $i++) {
2787
		if (ctype_xdigit($values[$i]) == false)
2788
			return false;
2789
		if (hexdec($values[$i]) < 0 || hexdec($values[$i]) > 255)
2790
			return false;
2791
	}
2792

    
2793
	return true;
2794
}
2795

    
2796
/* Write the DHCP6 DUID file */
2797
function write_dhcp6_duid($duidstring) {
2798
	// Create the hex array from the dhcp6duid config entry and write to file
2799
	global $g;
2800

    
2801
	if(!is_duid($duidstring)) {
2802
		log_error(gettext("Error: attempting to write DUID file - Invalid DUID detected"));
2803
		return false;
2804
	}
2805
	$temp = str_replace(":","",$duidstring);
2806
	$duid_binstring = pack("H*",$temp);
2807
	if ($fd = fopen("{$g['vardb_path']}/dhcp6c_duid", "wb")) {
2808
		fwrite($fd, $duid_binstring);
2809
		fclose($fd);
2810
		return true;
2811
	}
2812
	log_error(gettext("Error: attempting to write DUID file - File write error"));
2813
	return false;
2814
}
2815

    
2816
/* returns duid string from 'vardb_path']}/dhcp6c_duid' */
2817
function get_duid_from_file() {
2818
	global $g;
2819

    
2820
	$duid_ASCII = "";
2821
	$count = 0;
2822

    
2823
	if (file_exists("{$g['vardb_path']}/dhcp6c_duid") &&
2824
	    ($fd = fopen("{$g['vardb_path']}/dhcp6c_duid", "r"))) {
2825
		$fsize = filesize("{$g['vardb_path']}/dhcp6c_duid");
2826
		if ($fsize <= 132) {
2827
			$buffer = fread($fd, $fsize);
2828
			while($count < $fsize) {
2829
				$duid_ASCII .= bin2hex($buffer[$count]);
2830
				$count++;
2831
				if($count < $fsize) {
2832
					$duid_ASCII .= ":";
2833
				}
2834
			}
2835
		}
2836
		fclose($fd);
2837
	}
2838
	//if no file or error with read then the string returns blanked DUID string
2839
	if(!is_duid($duid_ASCII)) {
2840
		return "--:--:--:--:--:--:--:--:--:--:--:--:--:--:--:--";
2841
	}
2842
	return($duid_ASCII);
2843
}
2844

    
2845
/* Replaces the Mac OS 9 and earlier (\r) and DOS/Windows (\r\n) newlines with the Unix equivalent (\n). */
2846
function unixnewlines($text) {
2847
	return preg_replace('/\r\n?/', "\n", $text);
2848
}
2849

    
2850
?>
(52-52/60)