Project

General

Profile

Download (73 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
/*
3
 * util.inc
4
 *
5
 * part of pfSense (https://www.pfsense.org)
6
 * Copyright (c) 2004-2016 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) {
32
	return sigkillbypid($pidfile, "TERM");
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 */
56
/* return 1 for success and 0 for a failure */
57
function sigkillbypid($pidfile, $sig) {
58
	if (isvalidpid($pidfile)) {
59
		return mwexec("/bin/pkill " . escapeshellarg("-{$sig}") .
60
		    " -F {$pidfile}", true);
61
	}
62

    
63
	return 0;
64
}
65

    
66
/* kill a process by name */
67
function sigkillbyname($procname, $sig) {
68
	if (isvalidproc($procname)) {
69
		return mwexec("/usr/bin/killall " . escapeshellarg("-{$sig}") . " " . escapeshellarg($procname), true);
70
	}
71
}
72

    
73
/* kill a process by name */
74
function killbyname($procname) {
75
	if (isvalidproc($procname)) {
76
		mwexec("/usr/bin/killall " . escapeshellarg($procname));
77
	}
78
}
79

    
80
function is_subsystem_dirty($subsystem = "") {
81
	global $g;
82

    
83
	if ($subsystem == "") {
84
		return false;
85
	}
86

    
87
	if (file_exists("{$g['varrun_path']}/{$subsystem}.dirty")) {
88
		return true;
89
	}
90

    
91
	return false;
92
}
93

    
94
function mark_subsystem_dirty($subsystem = "") {
95
	global $g;
96

    
97
	if (!file_put_contents("{$g['varrun_path']}/{$subsystem}.dirty", "DIRTY")) {
98
		log_error(sprintf(gettext("WARNING: Could not mark subsystem: %s dirty"), $subsystem));
99
	}
100
}
101

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

    
105
	@unlink("{$g['varrun_path']}/{$subsystem}.dirty");
106
}
107

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

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

    
147
		return $fp;
148
	}
149

    
150
	return NULL;
151
}
152

    
153
/* unlock configuration file */
154
function unlock($cfglckkey = 0) {
155
	global $g;
156
	flock($cfglckkey, LOCK_UN);
157
	fclose($cfglckkey);
158
	return;
159
}
160

    
161
/* unlock forcefully configuration file */
162
function unlock_force($lock) {
163
	global $g;
164

    
165
	@unlink("{$g['tmp_path']}/{$lock}.lock");
166
}
167

    
168
function send_event($cmd) {
169
	global $g;
170

    
171
	if (!isset($g['event_address'])) {
172
		$g['event_address'] = "unix:///var/run/check_reload_status";
173
	}
174

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

    
193
function send_multiple_events($cmds) {
194
	global $g;
195

    
196
	if (!isset($g['event_address'])) {
197
		$g['event_address'] = "unix:///var/run/check_reload_status";
198
	}
199

    
200
	if (!is_array($cmds)) {
201
		return;
202
	}
203

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

    
223
function is_module_loaded($module_name) {
224
	$module_name = str_replace(".ko", "", $module_name);
225
	$running = 0;
226
	$_gb = exec("/sbin/kldstat -qn {$module_name} 2>&1", $_gb, $running);
227
	if (intval($running) == 0) {
228
		return true;
229
	} else {
230
		return false;
231
	}
232
}
233

    
234
/* validate non-negative numeric string, or equivalent numeric variable */
235
function is_numericint($arg) {
236
	return (((is_int($arg) && $arg >= 0) || (is_string($arg) && strlen($arg) > 0 && ctype_digit($arg))) ? true : false);
237
}
238

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

    
248
/* same as gen_subnet() but accepts IPv4 only */
249
function gen_subnetv4($ipaddr, $bits) {
250
	if (is_ipaddrv4($ipaddr) && is_numericint($bits) && $bits <= 32) {
251
		if ($bits == 0) {
252
			return '0.0.0.0';  // avoids <<32
253
		}
254
		return long2ip(ip2long($ipaddr) & ((0xFFFFFFFF << (32 - $bits)) & 0xFFFFFFFF));
255
	}
256
	return "";
257
}
258

    
259
/* same as gen_subnet() but accepts IPv6 only */
260
function gen_subnetv6($ipaddr, $bits) {
261
	if (is_ipaddrv6($ipaddr) && is_numericint($bits) && $bits <= 128) {
262
		return text_to_compressed_ip6(Net_IPv6::getNetmask($ipaddr, $bits));
263
	}
264
	return "";
265
}
266

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

    
276
/* same as gen_subnet_max() but validates IPv4 only */
277
function gen_subnetv4_max($ipaddr, $bits) {
278
	if (is_ipaddrv4($ipaddr) && is_numericint($bits) && $bits <= 32) {
279
		if ($bits == 32) {
280
			return $ipaddr;
281
		}
282
		return long2ip32(ip2long($ipaddr) | (~gen_subnet_mask_long($bits) & 0xFFFFFFFF));
283
	}
284
	return "";
285
}
286

    
287
/* same as gen_subnet_max() but validates IPv6 only */
288
function gen_subnetv6_max($ipaddr, $bits) {
289
	if (is_ipaddrv6($ipaddr) && is_numericint($bits) && $bits <= 128) {
290
		$endip_bin = substr(ip6_to_bin($ipaddr), 0, $bits) . str_repeat('1', 128 - $bits);
291
		return bin_to_compressed_ip6($endip_bin);
292
	}
293
	return "";
294
}
295

    
296
/* returns a subnet mask (long given a bit count) */
297
function gen_subnet_mask_long($bits) {
298
	$sm = 0;
299
	for ($i = 0; $i < $bits; $i++) {
300
		$sm >>= 1;
301
		$sm |= 0x80000000;
302
	}
303
	return $sm;
304
}
305

    
306
/* same as above but returns a string */
307
function gen_subnet_mask($bits) {
308
	return long2ip(gen_subnet_mask_long($bits));
309
}
310

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

    
321
/* Convert long int to IPv4 address
322
   Returns '' if not valid IPv4 (including if any bits >32 are non-zero) */
323
function long2ip32($ip) {
324
	return long2ip($ip & 0xFFFFFFFF);
325
}
326

    
327
/* Convert IPv4 address to long int, truncated to 32-bits to avoid sign extension on 64-bit platforms.
328
   Returns '' if not valid IPv4. */
329
function ip2long32($ip) {
330
	return (ip2long($ip) & 0xFFFFFFFF);
331
}
332

    
333
/* Convert IPv4 address to unsigned long int.
334
   Returns '' if not valid IPv4. */
335
function ip2ulong($ip) {
336
	return sprintf("%u", ip2long32($ip));
337
}
338

    
339
/*
340
 * Convert IPv6 address to binary
341
 *
342
 * Obtained from: pear-Net_IPv6
343
 */
344
function ip6_to_bin($ip) {
345
	$binstr = '';
346

    
347
	$ip = Net_IPv6::removeNetmaskSpec($ip);
348
	$ip = Net_IPv6::Uncompress($ip);
349

    
350
	$parts = explode(':', $ip);
351

    
352
	foreach ( $parts as $v ) {
353

    
354
		$str     = base_convert($v, 16, 2);
355
		$binstr .= str_pad($str, 16, '0', STR_PAD_LEFT);
356

    
357
	}
358

    
359
	return $binstr;
360
}
361

    
362
/*
363
 * Convert IPv6 binary to uncompressed address
364
 *
365
 * Obtained from: pear-Net_IPv6
366
 */
367
function bin_to_ip6($bin) {
368
	$ip = "";
369

    
370
	if (strlen($bin) < 128) {
371
		$bin = str_pad($bin, 128, '0', STR_PAD_LEFT);
372
	}
373

    
374
	$parts = str_split($bin, "16");
375

    
376
	foreach ( $parts as $v ) {
377
		$str = base_convert($v, 2, 16);
378
		$ip .= $str.":";
379
	}
380

    
381
	$ip = substr($ip, 0, -1);
382

    
383
	return $ip;
384
}
385

    
386
/*
387
 * Convert IPv6 binary to compressed address
388
 */
389
function bin_to_compressed_ip6($bin) {
390
	return text_to_compressed_ip6(bin_to_ip6($bin));
391
}
392

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

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

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

    
431
/* Return the previous IP address before the given address */
432
function ip_before($ip, $offset = 1) {
433
	return long2ip32(ip2long($ip) - $offset);
434
}
435

    
436
/* Return the next IP address after the given address */
437
function ip_after($ip, $offset = 1) {
438
	return long2ip32(ip2long($ip) + $offset);
439
}
440

    
441
/* Return true if the first IP is 'before' the second */
442
function ip_less_than($ip1, $ip2) {
443
	// Compare as unsigned long because otherwise it wouldn't work when
444
	//   crossing over from 127.255.255.255 / 128.0.0.0 barrier
445
	return ip2ulong($ip1) < ip2ulong($ip2);
446
}
447

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

    
455
/* compare two IP addresses */
456
function ipcmp($a, $b) {
457
	if (ip_less_than($a, $b)) {
458
		return -1;
459
	} else if (ip_greater_than($a, $b)) {
460
		return 1;
461
	} else {
462
		return 0;
463
	}
464
}
465

    
466
/* Convert a range of IPv4 addresses to an array of individual addresses. */
467
/* Note: IPv6 ranges are not yet supported here. */
468
function ip_range_to_address_array($startip, $endip, $max_size = 5000) {
469
	if (!is_ipaddrv4($startip) || !is_ipaddrv4($endip)) {
470
		return false;
471
	}
472

    
473
	if (ip_greater_than($startip, $endip)) {
474
		// Swap start and end so we can process sensibly.
475
		$temp = $startip;
476
		$startip = $endip;
477
		$endip = $temp;
478
	}
479

    
480
	if (ip_range_size_v4($startip, $endip) > $max_size) {
481
		return false;
482
	}
483

    
484
	// Container for IP addresses within this range.
485
	$rangeaddresses = array();
486
	$end_int = ip2ulong($endip);
487
	for ($ip_int = ip2ulong($startip); $ip_int <= $end_int; $ip_int++) {
488
		$rangeaddresses[] = long2ip($ip_int);
489
	}
490

    
491
	return $rangeaddresses;
492
}
493

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

    
522
	if (is_ipaddrv4($ip1) && is_ipaddrv4($ip2)) {
523
		$proto = 'ipv4';  // for clarity
524
		$bits = 32;
525
		$ip1bin = decbin(ip2long32($ip1));
526
		$ip2bin = decbin(ip2long32($ip2));
527
	} elseif (is_ipaddrv6($ip1) && is_ipaddrv6($ip2)) {
528
		$proto = 'ipv6';
529
		$bits = 128;
530
		$ip1bin = ip6_to_bin($ip1);
531
		$ip2bin = ip6_to_bin($ip2);
532
	} else {
533
		return array();
534
	}
535

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

    
540
	if ($ip1bin == $ip2bin) {
541
		return array($ip1 . '/' . $bits); // exit if ip1=ip2 (trivial case)
542
	}
543

    
544
	if ($ip1bin > $ip2bin) {
545
		list ($ip1bin, $ip2bin) = array($ip2bin, $ip1bin);  // swap if needed (ensures ip1 < ip2)
546
	}
547

    
548
	$rangesubnets = array();
549
	$netsize = 0;
550

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

    
555
		// 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)
556

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

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

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

    
576
		// this is the only edge case arising from increment/decrement.
577
		// 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)
578

    
579
		if ($ip2bin < $ip1bin) {
580
			continue;
581
		}
582

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

    
586
		$shift = $bits - max(strrpos($ip1bin, '0'), strrpos($ip2bin, '1'));  // num of low bits which are '0' in ip1 and '1' in ip2
587
		$ip1bin = str_repeat('0', $shift) . substr($ip1bin, 0, $bits - $shift);
588
		$ip2bin = str_repeat('0', $shift) . substr($ip2bin, 0, $bits - $shift);
589
		$netsize += $shift;
590
		if ($ip1bin == $ip2bin) {
591
			// we're done.
592
			$new_subnet_ip = substr($ip1bin, $netsize, $bits - $netsize) . str_repeat('0', $netsize);
593
			$rangesubnets[$new_subnet_ip] = $bits - $netsize;
594
			continue;
595
		}
596

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

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

    
602
	ksort($rangesubnets, SORT_STRING);
603
	$out = array();
604

    
605
	foreach ($rangesubnets as $ip => $netmask) {
606
		if ($proto == 'ipv4') {
607
			$i = str_split($ip, 8);
608
			$out[] = implode('.', array(bindec($i[0]), bindec($i[1]), bindec($i[2]), bindec($i[3]))) . '/' . $netmask;
609
		} else {
610
			$out[] = bin_to_compressed_ip6($ip) . '/' . $netmask;
611
		}
612
	}
613

    
614
	return $out;
615
}
616

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

    
634
/* returns true if $ipaddr is a valid dotted IPv4 address or a IPv6
635
	false - not valid
636
	true (numeric 4 or 6) - if valid, gives type of address */
637
function is_ipaddr($ipaddr) {
638
	if (is_ipaddrv4($ipaddr)) {
639
		return 4;
640
	}
641
	if (is_ipaddrv6($ipaddr)) {
642
		return 6;
643
	}
644
	return false;
645
}
646

    
647
/* returns true if $ipaddr is a valid IPv6 address */
648
function is_ipaddrv6($ipaddr) {
649
	if (!is_string($ipaddr) || empty($ipaddr)) {
650
		return false;
651
	}
652
	if (strstr($ipaddr, "%") && is_linklocal($ipaddr)) {
653
		$tmpip = explode("%", $ipaddr);
654
		$ipaddr = $tmpip[0];
655
	}
656
	return Net_IPv6::checkIPv6($ipaddr);
657
}
658

    
659
/* returns true if $ipaddr is a valid dotted IPv4 address */
660
function is_ipaddrv4($ipaddr) {
661
	if (!is_string($ipaddr) || empty($ipaddr) || ip2long($ipaddr) === FALSE) {
662
		return false;
663
	}
664
	return true;
665
}
666

    
667
/* returns 4 or 6 respectively (== TRUE) if $ipaddr is a valid IPv4 or IPv6 linklocal address
668
   returns '' if not a valid linklocal address */
669
function is_linklocal($ipaddr) {
670
	if (is_ipaddrv4($ipaddr)) {
671
		// input is IPv4
672
		// test if it's 169.254.x.x per rfc3927 2.1
673
		$ip4 = explode(".", $ipaddr);
674
		if ($ip4[0] == '169' && $ip4[1] == '254') {
675
			return 4;
676
		}
677
	} elseif (Net_IPv6::getAddressType($ipaddr) == NET_IPV6_LOCAL_LINK) {
678
		return 6;
679
	}
680
	return '';
681
}
682

    
683
/* returns scope of a linklocal address */
684
function get_ll_scope($addr) {
685
	if (!is_linklocal($addr) || !strstr($addr, "%")) {
686
		return "";
687
	}
688
	list ($ll, $scope) = explode("%", $addr);
689
	return $scope;
690
}
691

    
692
/* returns true if $ipaddr is a valid literal IPv6 address */
693
function is_literalipaddrv6($ipaddr) {
694
	if (substr($ipaddr,0,1) == '[' && substr($ipaddr,-1,1) == ']') {
695
		// if it's data wrapped in "[ ... ]" then test if middle part is valid IPv6
696
		return is_ipaddrv6(substr($ipaddr,1,-1));
697
	}
698
	return false;
699
}
700

    
701
/* returns true if $iport is a valid IPv4:port or [Literal IPv6]:port
702
	false - not valid
703
	true (numeric 4 or 6) - if valid, gives type of address */
704
function is_ipaddrwithport($ipport) {
705
	$c = strrpos($ipport, ":");
706
	if ($c === false) {
707
		return false;  // can't split at final colon if no colon exists
708
	}
709

    
710
	if (!is_port(substr($ipport, $c + 1))) {
711
		return false;  // no valid port after last colon
712
	}
713

    
714
	$ip = substr($ipport, 0, $c);  // else is text before last colon a valid IP
715
	if (is_literalipaddrv6($ip)) {
716
		return 6;
717
	} elseif (is_ipaddrv4($ip)) {
718
		return 4;
719
	} else {
720
		return false;
721
	}
722
}
723

    
724
function is_hostnamewithport($hostport) {
725
	$parts = explode(":", $hostport);
726
	// no need to validate with is_string(); if it's not a string then explode won't return 2 parts anyway
727
	if (count($parts) == 2) {
728
		return is_hostname($parts[0]) && is_port($parts[1]);
729
	}
730
	return false;
731
}
732

    
733
/* returns true if $ipaddr is a valid dotted IPv4 address or an alias thereof */
734
function is_ipaddroralias($ipaddr) {
735
	global $config;
736

    
737
	if (is_alias($ipaddr)) {
738
		if (is_array($config['aliases']['alias'])) {
739
			foreach ($config['aliases']['alias'] as $alias) {
740
				if ($alias['name'] == $ipaddr && !preg_match("/port/i", $alias['type'])) {
741
					return true;
742
				}
743
			}
744
		}
745
		return false;
746
	} else {
747
		return is_ipaddr($ipaddr);
748
	}
749

    
750
}
751

    
752
/* returns true if $subnet is a valid IPv4 or IPv6 subnet in CIDR format
753
	false - if not a valid subnet
754
	true (numeric 4 or 6) - if valid, gives type of subnet */
755
function is_subnet($subnet) {
756
	if (is_string($subnet) && preg_match('/^(?:([0-9.]{7,15})|([0-9a-f:]{2,39}))\/(\d{1,3})$/i', $subnet, $parts)) {
757
		if (is_ipaddrv4($parts[1]) && $parts[3] <= 32) {
758
			return 4;
759
		}
760
		if (is_ipaddrv6($parts[2]) && $parts[3] <= 128) {
761
			return 6;
762
		}
763
	}
764
	return false;
765
}
766

    
767
/* same as is_subnet() but accepts IPv4 only */
768
function is_subnetv4($subnet) {
769
	return (is_subnet($subnet) == 4);
770
}
771

    
772
/* same as is_subnet() but accepts IPv6 only */
773
function is_subnetv6($subnet) {
774
	return (is_subnet($subnet) == 6);
775
}
776

    
777
/* returns true if $subnet is a valid subnet in CIDR format or an alias thereof */
778
function is_subnetoralias($subnet) {
779
	global $aliastable;
780

    
781
	if (isset($aliastable[$subnet]) && is_subnet($aliastable[$subnet])) {
782
		return true;
783
	} else {
784
		return is_subnet($subnet);
785
	}
786
}
787

    
788
/* Get number of addresses in an IPv4/IPv6 subnet (represented as a string)
789
   optional $exact=true forces error (0) to be returned if it can't be represented exactly
790
   Exact result not possible above PHP_MAX_INT which is about 2^31 addresses on x32 or 2^63 on x64
791
   Returns 0 for bad data or if cannot represent size as an INT when $exact is set. */
792
function subnet_size($subnet, $exact=false) {
793
	$parts = explode("/", $subnet);
794
	$iptype = is_ipaddr($parts[0]);
795
	if (count($parts) == 2 && $iptype) {
796
		return subnet_size_by_netmask($iptype, $parts[1], $exact);
797
	}
798
	return 0;
799
}
800

    
801
/* Get number of addresses in an IPv4/IPv6 subnet (represented numerically as IP type + bits)
802
   optional $exact=true forces error (0) to be returned if it can't be represented exactly
803
   Hard to think where we might need to count exactly a huge subnet but an overflow detection option is probably sensible
804
   Returns 0 for bad data or if cannot represent size as an INT when $exact is set. */
805
function subnet_size_by_netmask($iptype, $bits, $exact=false) {
806
	if (!is_numericint($bits)) {
807
		return 0;
808
	} elseif ($iptype == 4 && $bits <= 32) {
809
		$snsize = 32 - $bits;
810
	} elseif ($iptype == 6 && $bits <= 128) {
811
		$snsize = 128 - $bits;
812
	} else {
813
		return 0;
814
	}
815

    
816
	// 2**N returns an exact result as an INT if possible, and a float/double if not.
817
	// 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
818
	$result = 2 ** $snsize;
819

    
820
	if ($exact && !is_int($result)) {
821
		//exact required but can't represent result exactly as an INT
822
		return 0;
823
	} else {
824
		// result ok, will be an INT where possible (guaranteed up to 2^31 addresses on x32/x64) and a float for 'huge' subnets
825
		return $result;
826
	}
827
}
828

    
829
/* function used by pfblockerng */
830
function subnetv4_expand($subnet) {
831
	$result = array();
832
	list ($ip, $bits) = explode("/", $subnet);
833
	$net = ip2long($ip);
834
	$mask = (0xffffffff << (32 - $bits));
835
	$net &= $mask;
836
	$size = round(exp(log(2) * (32 - $bits)));
837
	for ($i = 0; $i < $size; $i += 1) {
838
		$result[] = long2ip($net | $i);
839
	}
840
	return $result;
841
}
842

    
843
/* find out whether two IPv4/IPv6 CIDR subnets overlap.
844
   Note: CIDR overlap implies one is identical or included so largest sn will be the same */
845
function check_subnets_overlap($subnet1, $bits1, $subnet2, $bits2) {
846
	if (is_ipaddrv4($subnet1)) {
847
		return check_subnetsv4_overlap($subnet1, $bits1, $subnet2, $bits2);
848
	} else {
849
		return check_subnetsv6_overlap($subnet1, $bits1, $subnet2, $bits2);
850
	}
851
}
852

    
853
/* find out whether two IPv4 CIDR subnets overlap.
854
   Note: CIDR overlap means sn1/sn2 are identical or one is included in other. So sn using largest $bits will be the same  */
855
function check_subnetsv4_overlap($subnet1, $bits1, $subnet2, $bits2) {
856
	$largest_sn = min($bits1, $bits2);
857
	$subnetv4_start1 = gen_subnetv4($subnet1, $largest_sn);
858
	$subnetv4_start2 = gen_subnetv4($subnet2, $largest_sn);
859

    
860
	if ($subnetv4_start1 == '' || $subnetv4_start2 == '') {
861
		// One or both args is not a valid IPv4 subnet
862
		//FIXME: needs to return "bad data" not true/false if bad. For now return false, best we can do until fixed
863
		return false;
864
	}
865
	return ($subnetv4_start1 == $subnetv4_start2);
866
}
867

    
868
/* find out whether two IPv6 CIDR subnets overlap.
869
   Note: CIDR overlap means sn1/sn2 are identical or one is included in other. So sn using largest $bits will be the same  */
870
function check_subnetsv6_overlap($subnet1, $bits1, $subnet2, $bits2) {
871
	$largest_sn = min($bits1, $bits2);
872
	$subnetv6_start1 = gen_subnetv6($subnet1, $largest_sn);
873
	$subnetv6_start2 = gen_subnetv6($subnet2, $largest_sn);
874

    
875
	if ($subnetv6_start1 == '' || $subnetv6_start2 == '') {
876
		// One or both args is not a valid IPv6 subnet
877
		//FIXME: needs to return "bad data" not true/false if bad. For now return false, best we can do until fixed
878
		return false;
879
	}
880
	return ($subnetv6_start1 == $subnetv6_start2);
881
}
882

    
883
/* return all PTR zones for a IPv6 network */
884
function get_v6_ptr_zones($subnet, $bits) {
885
	$result = array();
886

    
887
	if (!is_ipaddrv6($subnet)) {
888
		return $result;
889
	}
890

    
891
	if (!is_numericint($bits) || $bits > 128) {
892
		return $result;
893
	}
894

    
895
	/*
896
	 * Find a small nibble boundary subnet mask
897
	 * e.g. a /29 will create 8 /32 PTR zones
898
	 */
899
	$small_sn = $bits;
900
	while ($small_sn % 4 != 0) {
901
		$small_sn++;
902
	}
903

    
904
	/* Get network prefix */
905
	$small_subnet = Net_IPv6::getNetmask($subnet, $bits);
906

    
907
	/*
908
	 * While small network is part of bigger one, increase 4-bit in last
909
	 * digit to get next small network
910
	 */
911
	while (Net_IPv6::isInNetmask($small_subnet, $subnet, $bits)) {
912
		/* Get a pure hex value */
913
		$unpacked = unpack('H*hex', inet_pton($small_subnet));
914
		/* Create PTR record using $small_sn / 4 chars */
915
		$result[] = implode('.', array_reverse(str_split(substr(
916
		    $unpacked['hex'], 0, $small_sn / 4)))).'.ip6.arpa';
917

    
918
		/* Detect what part of IP should be increased */
919
		$change_part = (int) ($small_sn / 16);
920
		if ($small_sn % 16 == 0) {
921
			$change_part--;
922
		}
923

    
924
		/* Increase 1 to desired part */
925
		$parts = explode(":", Net_IPv6::uncompress($small_subnet));
926
		$parts[$change_part]++;
927
		$small_subnet = implode(":", $parts);
928
	}
929

    
930
	return $result;
931
}
932

    
933
/* return true if $addr is in $subnet, false if not */
934
function ip_in_subnet($addr, $subnet) {
935
	if (is_ipaddrv6($addr) && is_subnetv6($subnet)) {
936
		return (Net_IPv6::isInNetmask($addr, $subnet));
937
	} else if (is_ipaddrv4($addr) && is_subnetv4($subnet)) {
938
		list($ip, $mask) = explode('/', $subnet);
939
		$mask = (0xffffffff << (32 - $mask)) & 0xffffffff;
940
		return ((ip2long($addr) & $mask) == (ip2long($ip) & $mask));
941
	}
942
	return false;
943
}
944

    
945
/* returns true if $hostname is just a valid hostname (top part without any of the domain part) */
946
function is_unqualified_hostname($hostname) {
947
	if (!is_string($hostname)) {
948
		return false;
949
	}
950

    
951
	if (preg_match('/^(?:[a-z0-9_]|[a-z0-9_][a-z0-9_\-]*[a-z0-9_])$/i', $hostname)) {
952
		return true;
953
	} else {
954
		return false;
955
	}
956
}
957

    
958
/* returns true if $hostname is a valid hostname, with or without being a fully-qualified domain name. */
959
function is_hostname($hostname, $allow_wildcard=false) {
960
	if (!is_string($hostname)) {
961
		return false;
962
	}
963

    
964
	if (is_domain($hostname, $allow_wildcard)) {
965
		if ((substr_count($hostname, ".") == 1) && ($hostname[strlen($hostname)-1] == ".")) {
966
			/* Only a single dot at the end like "test." - hosts cannot be directly in the root domain. */
967
			return false;
968
		} else {
969
			return true;
970
		}
971
	} else {
972
		return false;
973
	}
974
}
975

    
976
/* returns true if $domain is a valid domain name */
977
function is_domain($domain, $allow_wildcard=false) {
978
	if (!is_string($domain)) {
979
		return false;
980
	}
981
	if ($allow_wildcard) {
982
		$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';
983
	} else {
984
		$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';
985
	}
986

    
987
	if (preg_match($domain_regex, $domain)) {
988
		return true;
989
	} else {
990
		return false;
991
	}
992
}
993

    
994
/* returns true if $macaddr is a valid MAC address */
995
function is_macaddr($macaddr, $partial=false) {
996
	$values = explode(":", $macaddr);
997

    
998
	/* Verify if the MAC address has a proper amount of parts for either a partial or full match. */
999
	if ($partial) {
1000
		if ((count($values) < 1) || (count($values) > 6)) {
1001
			return false;
1002
		}
1003
	} elseif (count($values) != 6) {
1004
		return false;
1005
	}
1006
	for ($i = 0; $i < count($values); $i++) {
1007
		if (ctype_xdigit($values[$i]) == false)
1008
			return false;
1009
		if (hexdec($values[$i]) < 0 || hexdec($values[$i]) > 255)
1010
			return false;
1011
	}
1012

    
1013
	return true;
1014
}
1015

    
1016
/*
1017
	If $return_message is true then
1018
		returns a text message about the reason that the name is invalid.
1019
		the text includes the type of "thing" that is being checked, passed in $object. (e.g. "alias", "gateway group", "schedule")
1020
	else
1021
		returns true if $name is a valid name for an alias
1022
		returns false if $name is not a valid name for an alias
1023

    
1024
	Aliases cannot be:
1025
		bad chars: anything except a-z 0-9 and underscore
1026
		bad names: empty string, pure numeric, pure underscore
1027
		reserved words: pre-defined service/protocol/port names which should not be ambiguous, and the words "port" and  "pass" */
1028

    
1029
function is_validaliasname($name, $return_message = false, $object = "alias") {
1030
	/* Array of reserved words */
1031
	$reserved = array("port", "pass");
1032

    
1033
	if (!is_string($name) || strlen($name) >= 32 || preg_match('/(^_*$|^\d*$|[^a-z0-9_])/i', $name)) {
1034
		if ($return_message) {
1035
			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, _');
1036
		} else {
1037
			return false;
1038
		}
1039
	}
1040
	if (in_array($name, $reserved, true)) {
1041
		if ($return_message) {
1042
			return sprintf(gettext('The %1$s name must not be either of the reserved words %2$s or %3$s.'), $object, "'port'", "'pass'");
1043
		} else {
1044
			return false;
1045
		}
1046
	}
1047
	if (getprotobyname($name)) {
1048
		if ($return_message) {
1049
			return sprintf(gettext('The %1$s name must not be a well-known IP protocol name such as TCP, UDP, ICMP etc.'), $object);
1050
		} else {
1051
			return false;
1052
		}
1053
	}
1054
	if (getservbyname($name, "tcp") || getservbyname($name, "udp")) {
1055
		if ($return_message) {
1056
			return sprintf(gettext('The %1$s name must not be a well-known TCP or UDP port name such as ssh, smtp, pop3, tftp, http, openvpn etc.'), $object);
1057
		} else {
1058
			return false;
1059
		}
1060
	}
1061
	if ($return_message) {
1062
		return sprintf(gettext("The %1$s name is valid."), $object);
1063
	} else {
1064
		return true;
1065
	}
1066
}
1067

    
1068
/* returns a text message indicating if the alias name is valid, or the reason it is not valid. */
1069
function invalidaliasnamemsg($name, $object = "alias") {
1070
	return is_validaliasname($name, true, $object);
1071
}
1072

    
1073
/*
1074
 * returns true if $range is a valid integer range between $min and $max
1075
 * range delimiter can be ':' or '-'
1076
 */
1077
function is_intrange($range, $min, $max) {
1078
	$values = preg_split("/[:-]/", $range);
1079

    
1080
	if (!is_array($values) || count($values) != 2) {
1081
		return false;
1082
	}
1083

    
1084
	if (!ctype_digit($values[0]) || !ctype_digit($values[1])) {
1085
		return false;
1086
	}
1087

    
1088
	$values[0] = intval($values[0]);
1089
	$values[1] = intval($values[1]);
1090

    
1091
	if ($values[0] >= $values[1]) {
1092
		return false;
1093
	}
1094

    
1095
	if ($values[0] < $min || $values[1] > $max) {
1096
		return false;
1097
	}
1098

    
1099
	return true;
1100
}
1101

    
1102
/* returns true if $port is a valid TCP/UDP port */
1103
function is_port($port) {
1104
	if (ctype_digit($port) && ((intval($port) >= 1) && (intval($port) <= 65535))) {
1105
		return true;
1106
	}
1107
	if (getservbyname($port, "tcp") || getservbyname($port, "udp")) {
1108
		return true;
1109
	}
1110
	return false;
1111
}
1112

    
1113
/* returns true if $portrange is a valid TCP/UDP portrange ("<port>:<port>") */
1114
function is_portrange($portrange) {
1115
	$ports = explode(":", $portrange);
1116

    
1117
	return (count($ports) == 2 && is_port($ports[0]) && is_port($ports[1]));
1118
}
1119

    
1120
/* returns true if $port is a valid TCP/UDP port number or range ("<port>:<port>") */
1121
function is_port_or_range($port) {
1122
	return (is_port($port) || is_portrange($port));
1123
}
1124

    
1125
/* returns true if $port is an alias that is a port type */
1126
function is_portalias($port) {
1127
	global $config;
1128

    
1129
	if (is_alias($port)) {
1130
		if (is_array($config['aliases']['alias'])) {
1131
			foreach ($config['aliases']['alias'] as $alias) {
1132
				if ($alias['name'] == $port && preg_match("/port/i", $alias['type'])) {
1133
					return true;
1134
				}
1135
			}
1136
		}
1137
	}
1138
	return false;
1139
}
1140

    
1141
/* returns true if $port is a valid port number or an alias thereof */
1142
function is_port_or_alias($port) {
1143
	return (is_port($port) || is_portalias($port));
1144
}
1145

    
1146
/* returns true if $port is a valid TCP/UDP port number or range ("<port>:<port>") or an alias thereof */
1147
function is_port_or_range_or_alias($port) {
1148
	return (is_port($port) || is_portrange($port) || is_portalias($port));
1149
}
1150

    
1151
/* create ranges of sequential port numbers (200:215) and remove duplicates */
1152
function group_ports($ports, $kflc = false) {
1153
	if (!is_array($ports) || empty($ports)) {
1154
		return;
1155
	}
1156

    
1157
	$uniq = array();
1158
	$comments = array();
1159
	foreach ($ports as $port) {
1160
		if (($kflc) && (strpos($port, '#') === 0)) {	// Keep Full Line Comments (lines beginning with #).
1161
			$comments[] = $port;
1162
		} else if (is_portrange($port)) {
1163
			list($begin, $end) = explode(":", $port);
1164
			if ($begin > $end) {
1165
				$aux = $begin;
1166
				$begin = $end;
1167
				$end = $aux;
1168
			}
1169
			for ($i = $begin; $i <= $end; $i++) {
1170
				if (!in_array($i, $uniq)) {
1171
					$uniq[] = $i;
1172
				}
1173
			}
1174
		} else if (is_port($port)) {
1175
			if (!in_array($port, $uniq)) {
1176
				$uniq[] = $port;
1177
			}
1178
		}
1179
	}
1180
	sort($uniq, SORT_NUMERIC);
1181

    
1182
	$result = array();
1183
	foreach ($uniq as $idx => $port) {
1184
		if ($idx == 0) {
1185
			$result[] = $port;
1186
			continue;
1187
		}
1188

    
1189
		$last = end($result);
1190
		if (is_portrange($last)) {
1191
			list($begin, $end) = explode(":", $last);
1192
		} else {
1193
			$begin = $end = $last;
1194
		}
1195

    
1196
		if ($port == ($end+1)) {
1197
			$end++;
1198
			$result[count($result)-1] = "{$begin}:{$end}";
1199
		} else {
1200
			$result[] = $port;
1201
		}
1202
	}
1203

    
1204
	return array_merge($comments, $result);
1205
}
1206

    
1207
/* returns true if $val is a valid shaper bandwidth value */
1208
function is_valid_shaperbw($val) {
1209
	return (preg_match("/^(\d+(?:\.\d+)?)([MKG]?b|%)$/", $val));
1210
}
1211

    
1212
/* returns true if $test is in the range between $start and $end */
1213
function is_inrange_v4($test, $start, $end) {
1214
	if (!is_ipaddrv4($test) || !is_ipaddrv4($start) || !is_ipaddrv4($end)) {
1215
		return false;
1216
	}
1217

    
1218
	if (ip2ulong($test) <= ip2ulong($end) &&
1219
	    ip2ulong($test) >= ip2ulong($start)) {
1220
		return true;
1221
	}
1222

    
1223
	return false;
1224
}
1225

    
1226
/* returns true if $test is in the range between $start and $end */
1227
function is_inrange_v6($test, $start, $end) {
1228
	if (!is_ipaddrv6($test) || !is_ipaddrv6($start) || !is_ipaddrv6($end)) {
1229
		return false;
1230
	}
1231

    
1232
	if (inet_pton($test) <= inet_pton($end) &&
1233
	    inet_pton($test) >= inet_pton($start)) {
1234
		return true;
1235
	}
1236

    
1237
	return false;
1238
}
1239

    
1240
/* returns true if $test is in the range between $start and $end */
1241
function is_inrange($test, $start, $end) {
1242
	return is_ipaddrv6($test) ? is_inrange_v6($test, $start, $end) : is_inrange_v4($test, $start, $end);
1243
}
1244

    
1245
function get_configured_vip_list($family = 'all', $type = VIP_ALL) {
1246
	global $config;
1247

    
1248
	$list = array();
1249
	if (!is_array($config['virtualip']['vip']) || empty($config['virtualip']['vip'])) {
1250
		return ($list);
1251
	}
1252

    
1253
	$viparr = &$config['virtualip']['vip'];
1254
	foreach ($viparr as $vip) {
1255

    
1256
		if ($type == VIP_CARP) {
1257
			if ($vip['mode'] != "carp")
1258
				continue;
1259
		} elseif ($type == VIP_IPALIAS) {
1260
			if ($vip['mode'] != "ipalias")
1261
				continue;
1262
		} else {
1263
			if ($vip['mode'] != "carp" && $vip['mode'] != "ipalias")
1264
				continue;
1265
		}
1266

    
1267
		if ($family == 'all' ||
1268
		    ($family == 'inet' && is_ipaddrv4($vip['subnet'])) ||
1269
		    ($family == 'inet6' && is_ipaddrv6($vip['subnet']))) {
1270
			$list["_vip{$vip['uniqid']}"] = $vip['subnet'];
1271
		}
1272
	}
1273
	return ($list);
1274
}
1275

    
1276
function get_configured_vip($vipinterface = '') {
1277

    
1278
	return (get_configured_vip_detail($vipinterface, 'all', 'vip'));
1279
}
1280

    
1281
function get_configured_vip_interface($vipinterface = '') {
1282

    
1283
	return (get_configured_vip_detail($vipinterface, 'all', 'iface'));
1284
}
1285

    
1286
function get_configured_vip_ipv4($vipinterface = '') {
1287

    
1288
	return (get_configured_vip_detail($vipinterface, 'inet', 'ip'));
1289
}
1290

    
1291
function get_configured_vip_ipv6($vipinterface = '') {
1292

    
1293
	return (get_configured_vip_detail($vipinterface, 'inet6', 'ip'));
1294
}
1295

    
1296
function get_configured_vip_subnetv4($vipinterface = '') {
1297

    
1298
	return (get_configured_vip_detail($vipinterface, 'inet', 'subnet'));
1299
}
1300

    
1301
function get_configured_vip_subnetv6($vipinterface = '') {
1302

    
1303
	return (get_configured_vip_detail($vipinterface, 'inet6', 'subnet'));
1304
}
1305

    
1306
function get_configured_vip_detail($vipinterface = '', $family = 'inet', $what = 'ip') {
1307
	global $config;
1308

    
1309
	if (empty($vipinterface) || !is_array($config['virtualip']['vip']) ||
1310
	    empty($config['virtualip']['vip'])) {
1311
		return (NULL);
1312
	}
1313

    
1314
	$viparr = &$config['virtualip']['vip'];
1315
	foreach ($viparr as $vip) {
1316
		if ($vip['mode'] != "carp" && $vip['mode'] != "ipalias") {
1317
			continue;
1318
		}
1319

    
1320
		if ($vipinterface != "_vip{$vip['uniqid']}") {
1321
			continue;
1322
		}
1323

    
1324
		switch ($what) {
1325
			case 'subnet':
1326
				if ($family == 'inet' && is_ipaddrv4($vip['subnet']))
1327
					return ($vip['subnet_bits']);
1328
				else if ($family == 'inet6' && is_ipaddrv6($vip['subnet']))
1329
					return ($vip['subnet_bits']);
1330
				break;
1331
			case 'iface':
1332
				return ($vip['interface']);
1333
				break;
1334
			case 'vip':
1335
				return ($vip);
1336
				break;
1337
			case 'ip':
1338
			default:
1339
				if ($family == 'inet' && is_ipaddrv4($vip['subnet'])) {
1340
					return ($vip['subnet']);
1341
				} else if ($family == 'inet6' && is_ipaddrv6($vip['subnet'])) {
1342
					return ($vip['subnet']);
1343
				}
1344
				break;
1345
		}
1346
		break;
1347
	}
1348

    
1349
	return (NULL);
1350
}
1351

    
1352
/* comparison function for sorting by the order in which interfaces are normally created */
1353
function compare_interface_friendly_names($a, $b) {
1354
	if ($a == $b) {
1355
		return 0;
1356
	} else if ($a == 'wan') {
1357
		return -1;
1358
	} else if ($b == 'wan') {
1359
		return 1;
1360
	} else if ($a == 'lan') {
1361
		return -1;
1362
	} else if ($b == 'lan') {
1363
		return 1;
1364
	}
1365

    
1366
	return strnatcmp($a, $b);
1367
}
1368

    
1369
/* return the configured interfaces list. */
1370
function get_configured_interface_list($withdisabled = false) {
1371
	global $config;
1372

    
1373
	$iflist = array();
1374

    
1375
	/* if list */
1376
	foreach ($config['interfaces'] as $if => $ifdetail) {
1377
		if (isset($ifdetail['enable']) || $withdisabled == true) {
1378
			$iflist[$if] = $if;
1379
		}
1380
	}
1381

    
1382
	return $iflist;
1383
}
1384

    
1385
/* return the configured interfaces list. */
1386
function get_configured_interface_list_by_realif($withdisabled = false) {
1387
	global $config;
1388

    
1389
	$iflist = array();
1390

    
1391
	/* if list */
1392
	foreach ($config['interfaces'] as $if => $ifdetail) {
1393
		if (isset($ifdetail['enable']) || $withdisabled == true) {
1394
			$tmpif = get_real_interface($if);
1395
			if (!empty($tmpif)) {
1396
				$iflist[$tmpif] = $if;
1397
			}
1398
		}
1399
	}
1400

    
1401
	return $iflist;
1402
}
1403

    
1404
/* return the configured interfaces list with their description. */
1405
function get_configured_interface_with_descr($withdisabled = false) {
1406
	global $config, $user_settings;
1407

    
1408
	$iflist = array();
1409

    
1410
	/* if list */
1411
	foreach ($config['interfaces'] as $if => $ifdetail) {
1412
		if (isset($ifdetail['enable']) || $withdisabled == true) {
1413
			if (empty($ifdetail['descr'])) {
1414
				$iflist[$if] = strtoupper($if);
1415
			} else {
1416
				$iflist[$if] = strtoupper($ifdetail['descr']);
1417
			}
1418
		}
1419
	}
1420

    
1421
	if ($user_settings['webgui']['interfacessort']) {
1422
		asort($iflist);
1423
	}
1424

    
1425
	return $iflist;
1426
}
1427

    
1428
/*
1429
 *   get_configured_ip_addresses() - Return a list of all configured
1430
 *   IPv4 addresses.
1431
 *
1432
 */
1433
function get_configured_ip_addresses() {
1434
	global $config;
1435

    
1436
	if (!function_exists('get_interface_ip')) {
1437
		require_once("interfaces.inc");
1438
	}
1439
	$ip_array = array();
1440
	$interfaces = get_configured_interface_list();
1441
	if (is_array($interfaces)) {
1442
		foreach ($interfaces as $int) {
1443
			$ipaddr = get_interface_ip($int);
1444
			$ip_array[$int] = $ipaddr;
1445
		}
1446
	}
1447
	$interfaces = get_configured_vip_list('inet');
1448
	if (is_array($interfaces)) {
1449
		foreach ($interfaces as $int => $ipaddr) {
1450
			$ip_array[$int] = $ipaddr;
1451
		}
1452
	}
1453

    
1454
	/* pppoe server */
1455
	if (is_array($config['pppoes']) && is_array($config['pppoes']['pppoe'])) {
1456
		foreach ($config['pppoes']['pppoe'] as $pppoe) {
1457
			if ($pppoe['mode'] == "server") {
1458
				if (is_ipaddr($pppoe['localip'])) {
1459
					$int = "pppoes". $pppoe['pppoeid'];
1460
					$ip_array[$int] = $pppoe['localip'];
1461
				}
1462
			}
1463
		}
1464
	}
1465

    
1466
	return $ip_array;
1467
}
1468

    
1469
/*
1470
 *   get_configured_ipv6_addresses() - Return a list of all configured
1471
 *   IPv6 addresses.
1472
 *
1473
 */
1474
function get_configured_ipv6_addresses($linklocal_fallback = false) {
1475
	require_once("interfaces.inc");
1476
	$ipv6_array = array();
1477
	$interfaces = get_configured_interface_list();
1478
	if (is_array($interfaces)) {
1479
		foreach ($interfaces as $int) {
1480
			$ipaddrv6 = text_to_compressed_ip6(get_interface_ipv6($int, false, $linklocal_fallback));
1481
			$ipv6_array[$int] = $ipaddrv6;
1482
		}
1483
	}
1484
	$interfaces = get_configured_vip_list('inet6');
1485
	if (is_array($interfaces)) {
1486
		foreach ($interfaces as $int => $ipaddrv6) {
1487
			$ipv6_array[$int] = text_to_compressed_ip6($ipaddrv6);
1488
		}
1489
	}
1490
	return $ipv6_array;
1491
}
1492

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

    
1596
			case "friendly":
1597
				if ($friendly != "") {
1598
					$toput['if'] = $ifname;
1599
					$iflist[$friendly] = $toput;
1600
				}
1601
				break;
1602
			}
1603
		}
1604
	}
1605
	return $iflist;
1606
}
1607

    
1608
/****f* util/log_error
1609
* NAME
1610
*   log_error  - Sends a string to syslog.
1611
* INPUTS
1612
*   $error     - string containing the syslog message.
1613
* RESULT
1614
*   null
1615
******/
1616
function log_error($error) {
1617
	global $g;
1618
	$page = $_SERVER['SCRIPT_NAME'];
1619
	if (empty($page)) {
1620
		$files = get_included_files();
1621
		$page = basename($files[0]);
1622
	}
1623
	syslog(LOG_ERR, "$page: $error");
1624
	if ($g['debug']) {
1625
		syslog(LOG_WARNING, var_dump(debug_backtrace()));
1626
	}
1627
	return;
1628
}
1629

    
1630
/****f* util/log_auth
1631
* NAME
1632
*   log_auth   - Sends a string to syslog as LOG_AUTH facility
1633
* INPUTS
1634
*   $error     - string containing the syslog message.
1635
* RESULT
1636
*   null
1637
******/
1638
function log_auth($error) {
1639
	global $g;
1640
	$page = $_SERVER['SCRIPT_NAME'];
1641
	syslog(LOG_AUTH, "$page: $error");
1642
	if ($g['debug']) {
1643
		syslog(LOG_WARNING, var_dump(debug_backtrace()));
1644
	}
1645
	return;
1646
}
1647

    
1648
/****f* util/exec_command
1649
 * NAME
1650
 *   exec_command - Execute a command and return a string of the result.
1651
 * INPUTS
1652
 *   $command   - String of the command to be executed.
1653
 * RESULT
1654
 *   String containing the command's result.
1655
 * NOTES
1656
 *   This function returns the command's stdout and stderr.
1657
 ******/
1658
function exec_command($command) {
1659
	$output = array();
1660
	exec($command . ' 2>&1', $output);
1661
	return(implode("\n", $output));
1662
}
1663

    
1664
/* wrapper for exec()
1665
   Executes in background or foreground.
1666
   For background execution, returns PID of background process to allow calling code control */
1667
function mwexec($command, $nologentry = false, $clearsigmask = false, $background = false) {
1668
	global $g;
1669
	$retval = 0;
1670

    
1671
	if ($g['debug']) {
1672
		if (!$_SERVER['REMOTE_ADDR']) {
1673
			echo "mwexec(): $command" . ($background ? " [BG]":"") . "\n";
1674
		}
1675
	}
1676
	if ($clearsigmask) {
1677
		$oldset = array();
1678
		pcntl_sigprocmask(SIG_SETMASK, array(), $oldset);
1679
	}
1680

    
1681
	if ($background) {
1682
		// start background process and return PID
1683
		$retval = exec("/usr/bin/nohup $command > /dev/null 2>&1 & echo $!");
1684
	} else {
1685
		// run in foreground, and (optionally) log if nonzero return
1686
		$outputarray = array();
1687
		exec("$command 2>&1", $outputarray, $retval);
1688
		if (($retval <> 0) && (!$nologentry || isset($config['system']['developerspew']))) {
1689
			log_error(sprintf(gettext("The command '%1\$s' returned exit code '%2\$d', the output was '%3\$s' "), $command, $retval, implode(" ", $outputarray)));
1690
		}
1691
	}
1692

    
1693
	if ($clearsigmask) {
1694
		pcntl_sigprocmask(SIG_SETMASK, $oldset);
1695
	}
1696

    
1697
	return $retval;
1698
}
1699

    
1700
/* wrapper for exec() in background */
1701
function mwexec_bg($command, $clearsigmask = false) {
1702
	return mwexec($command, false, $clearsigmask, true);
1703
}
1704

    
1705
/*	unlink a file, or pattern-match of a file, if it exists
1706
	if the file/path contains glob() compatible wildcards, all matching files will be unlinked
1707
	any warning/errors are suppressed (e.g. no matching files to delete)
1708
	If there are matching file(s) and they were all unlinked OK, then return true.
1709
	Otherwise return false (the requested file(s) did not exist, or could not be deleted)
1710
	This allows the caller to know if they were the one to successfully delete the file(s).
1711
*/
1712
function unlink_if_exists($fn) {
1713
	$to_do = glob($fn);
1714
	if (is_array($to_do) && count($to_do) > 0) {
1715
		// Returns an array of true/false indicating if each unlink worked
1716
		$results = @array_map("unlink", $to_do);
1717
		// If there is no false in the array, then all went well
1718
		$result = !in_array(false, $results, true);
1719
	} else {
1720
		$result = @unlink($fn);
1721
	}
1722
	return $result;
1723
}
1724
/* make a global alias table (for faster lookups) */
1725
function alias_make_table($config) {
1726
	global $aliastable;
1727

    
1728
	$aliastable = array();
1729

    
1730
	if (is_array($config['aliases']['alias'])) {
1731
		foreach ($config['aliases']['alias'] as $alias) {
1732
			if ($alias['name']) {
1733
				$aliastable[$alias['name']] = $alias['address'];
1734
			}
1735
		}
1736
	}
1737
}
1738

    
1739
/* check if an alias exists */
1740
function is_alias($name) {
1741
	global $aliastable;
1742

    
1743
	return isset($aliastable[$name]);
1744
}
1745

    
1746
function alias_get_type($name) {
1747
	global $config;
1748

    
1749
	if (is_array($config['aliases']['alias'])) {
1750
		foreach ($config['aliases']['alias'] as $alias) {
1751
			if ($name == $alias['name']) {
1752
				return $alias['type'];
1753
			}
1754
		}
1755
	}
1756

    
1757
	return "";
1758
}
1759

    
1760
/* expand a host or network alias, if necessary */
1761
function alias_expand($name) {
1762
	global $config, $aliastable;
1763
	$urltable_prefix = "/var/db/aliastables/";
1764
	$urltable_filename = $urltable_prefix . $name . ".txt";
1765

    
1766
	if (isset($aliastable[$name])) {
1767
		// alias names cannot be strictly numeric. redmine #4289
1768
		if (is_numericint($name)) {
1769
			return null;
1770
		}
1771
		// make sure if it's a ports alias, it actually exists. redmine #5845
1772
		foreach ($config['aliases']['alias'] as $alias) {
1773
			if ($alias['name'] == $name) {
1774
				if ($alias['type'] == "urltable_ports") {
1775
					if (is_URL($alias['url']) && file_exists($urltable_filename) && filesize($urltable_filename)) {
1776
						return "\${$name}";
1777
					} else {
1778
						return null;
1779
					}
1780
				}
1781
			}
1782
		}
1783
		return "\${$name}";
1784
	} else if (is_ipaddr($name) || is_subnet($name) || is_port_or_range($name)) {
1785
		return "{$name}";
1786
	} else {
1787
		return null;
1788
	}
1789
}
1790

    
1791
function alias_expand_urltable($name) {
1792
	global $config;
1793
	$urltable_prefix = "/var/db/aliastables/";
1794
	$urltable_filename = $urltable_prefix . $name . ".txt";
1795

    
1796
	if (is_array($config['aliases']['alias'])) {
1797
		foreach ($config['aliases']['alias'] as $alias) {
1798
			if (preg_match("/urltable/i", $alias['type']) && ($alias['name'] == $name)) {
1799
				if (is_URL($alias["url"]) && file_exists($urltable_filename)) {
1800
					if (!filesize($urltable_filename)) {
1801
						// file exists, but is empty, try to sync
1802
						send_event("service sync alias {$name}");
1803
					}
1804
					return $urltable_filename;
1805
				} else {
1806
					send_event("service sync alias {$name}");
1807
					break;
1808
				}
1809
			}
1810
		}
1811
	}
1812
	return null;
1813
}
1814

    
1815
/* obtain MAC address given an IP address by looking at the ARP/NDP table */
1816
function arp_get_mac_by_ip($ip, $do_ping = true) {
1817
	unset($macaddr);
1818
	$retval = 1;
1819
	switch (is_ipaddr($ip)) {
1820
		case 4:
1821
			if ($do_ping === true) {
1822
				mwexec("/sbin/ping -c 1 -t 1 " . escapeshellarg($ip), true);
1823
			}
1824
			$macaddr = exec("/usr/sbin/arp -n " . escapeshellarg($ip) . " | /usr/bin/awk '{print $4}'", $output, $retval);
1825
			break;
1826
		case 6:
1827
			if ($do_ping === true) {
1828
				mwexec("/sbin/ping6 -c 1 -X 1 " . escapeshellarg($ip), true);
1829
			}
1830
			$macaddr = exec("/usr/sbin/ndp -n " . escapeshellarg($ip) . " | /usr/bin/awk '{print $2}'", $output, $retval);
1831
			break;
1832
	}
1833
	if ($retval == 0 && is_macaddr($macaddr)) {
1834
		return $macaddr;
1835
	} else {
1836
		return false;
1837
	}
1838
}
1839

    
1840
/* return a fieldname that is safe for xml usage */
1841
function xml_safe_fieldname($fieldname) {
1842
	$replace = array(
1843
	    '/', '-', ' ', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')',
1844
	    '_', '+', '=', '{', '}', '[', ']', '|', '/', '<', '>', '?',
1845
	    ':', ',', '.', '\'', '\\'
1846
	);
1847
	return strtolower(str_replace($replace, "", $fieldname));
1848
}
1849

    
1850
function mac_format($clientmac) {
1851
	global $config, $cpzone;
1852

    
1853
	$mac = explode(":", $clientmac);
1854
	$mac_format = $cpzone ? $config['captiveportal'][$cpzone]['radmac_format'] : false;
1855

    
1856
	switch ($mac_format) {
1857
		case 'singledash':
1858
			return "$mac[0]$mac[1]$mac[2]-$mac[3]$mac[4]$mac[5]";
1859

    
1860
		case 'ietf':
1861
			return "$mac[0]-$mac[1]-$mac[2]-$mac[3]-$mac[4]-$mac[5]";
1862

    
1863
		case 'cisco':
1864
			return "$mac[0]$mac[1].$mac[2]$mac[3].$mac[4]$mac[5]";
1865

    
1866
		case 'unformatted':
1867
			return "$mac[0]$mac[1]$mac[2]$mac[3]$mac[4]$mac[5]";
1868

    
1869
		default:
1870
			return $clientmac;
1871
	}
1872
}
1873

    
1874
function resolve_retry($hostname, $retries = 5) {
1875

    
1876
	if (is_ipaddr($hostname)) {
1877
		return $hostname;
1878
	}
1879

    
1880
	for ($i = 0; $i < $retries; $i++) {
1881
		// FIXME: gethostbyname does not work for AAAA hostnames, boo, hiss
1882
		$ip = gethostbyname($hostname);
1883

    
1884
		if ($ip && $ip != $hostname) {
1885
			/* success */
1886
			return $ip;
1887
		}
1888

    
1889
		sleep(1);
1890
	}
1891

    
1892
	return false;
1893
}
1894

    
1895
function format_bytes($bytes) {
1896
	if ($bytes >= 1099511627776) {
1897
		return sprintf("%.2f TiB", $bytes/1099511627776);
1898
	} else if ($bytes >= 1073741824) {
1899
		return sprintf("%.2f GiB", $bytes/1073741824);
1900
	} else if ($bytes >= 1048576) {
1901
		return sprintf("%.2f MiB", $bytes/1048576);
1902
	} else if ($bytes >= 1024) {
1903
		return sprintf("%.0f KiB", $bytes/1024);
1904
	} else {
1905
		return sprintf("%d B", $bytes);
1906
	}
1907
}
1908

    
1909
function format_number($num, $precision = 3) {
1910
	$units = array('', 'K', 'M', 'G', 'T');
1911

    
1912
	$i = 0;
1913
	while ($num > 1000 && $i < count($units)) {
1914
		$num /= 1000;
1915
		$i++;
1916
	}
1917
	$num = round($num, $precision);
1918

    
1919
	return ("$num {$units[$i]}");
1920
}
1921

    
1922
function update_filter_reload_status($text, $new=false) {
1923
	global $g;
1924

    
1925
	if ($new) {
1926
		file_put_contents("{$g['varrun_path']}/filter_reload_status", $text  . PHP_EOL);
1927
	} else {
1928
		file_put_contents("{$g['varrun_path']}/filter_reload_status", $text  . PHP_EOL, FILE_APPEND);
1929
	}
1930
}
1931

    
1932
/****** util/return_dir_as_array
1933
 * NAME
1934
 *   return_dir_as_array - Return a directory's contents as an array.
1935
 * INPUTS
1936
 *   $dir          - string containing the path to the desired directory.
1937
 *   $filter_regex - string containing a regular expression to filter file names. Default empty.
1938
 * RESULT
1939
 *   $dir_array - array containing the directory's contents. This array will be empty if the path specified is invalid.
1940
 ******/
1941
function return_dir_as_array($dir, $filter_regex = '') {
1942
	$dir_array = array();
1943
	if (is_dir($dir)) {
1944
		if ($dh = opendir($dir)) {
1945
			while (($file = readdir($dh)) !== false) {
1946
				if (($file == ".") || ($file == "..")) {
1947
					continue;
1948
				}
1949

    
1950
				if (empty($filter_regex) || preg_match($filter_regex, $file)) {
1951
					array_push($dir_array, $file);
1952
				}
1953
			}
1954
			closedir($dh);
1955
		}
1956
	}
1957
	return $dir_array;
1958
}
1959

    
1960
function run_plugins($directory) {
1961
	global $config, $g;
1962

    
1963
	/* process packager manager custom rules */
1964
	$files = return_dir_as_array($directory);
1965
	if (is_array($files)) {
1966
		foreach ($files as $file) {
1967
			if (stristr($file, ".sh") == true) {
1968
				mwexec($directory . $file . " start");
1969
			} else if (!is_dir($directory . "/" . $file) && stristr($file, ".inc")) {
1970
				require_once($directory . "/" . $file);
1971
			}
1972
		}
1973
	}
1974
}
1975

    
1976
/*
1977
 *    safe_mkdir($path, $mode = 0755)
1978
 *    create directory if it doesn't already exist and isn't a file!
1979
 */
1980
function safe_mkdir($path, $mode = 0755) {
1981
	global $g;
1982

    
1983
	if (!is_file($path) && !is_dir($path)) {
1984
		return @mkdir($path, $mode, true);
1985
	} else {
1986
		return false;
1987
	}
1988
}
1989

    
1990
/*
1991
 * get_sysctl($names)
1992
 * Get values of sysctl OID's listed in $names (accepts an array or a single
1993
 * name) and return an array of key/value pairs set for those that exist
1994
 */
1995
function get_sysctl($names) {
1996
	if (empty($names)) {
1997
		return array();
1998
	}
1999

    
2000
	if (is_array($names)) {
2001
		$name_list = array();
2002
		foreach ($names as $name) {
2003
			$name_list[] = escapeshellarg($name);
2004
		}
2005
	} else {
2006
		$name_list = array(escapeshellarg($names));
2007
	}
2008

    
2009
	exec("/sbin/sysctl -i " . implode(" ", $name_list), $output);
2010
	$values = array();
2011
	foreach ($output as $line) {
2012
		$line = explode(": ", $line, 2);
2013
		if (count($line) == 2) {
2014
			$values[$line[0]] = $line[1];
2015
		}
2016
	}
2017

    
2018
	return $values;
2019
}
2020

    
2021
/*
2022
 * get_single_sysctl($name)
2023
 * Wrapper for get_sysctl() to simplify read of a single sysctl value
2024
 * return the value for sysctl $name or empty string if it doesn't exist
2025
 */
2026
function get_single_sysctl($name) {
2027
	if (empty($name)) {
2028
		return "";
2029
	}
2030

    
2031
	$value = get_sysctl($name);
2032
	if (empty($value) || !isset($value[$name])) {
2033
		return "";
2034
	}
2035

    
2036
	return $value[$name];
2037
}
2038

    
2039
/*
2040
 * set_sysctl($value_list)
2041
 * Set sysctl OID's listed as key/value pairs and return
2042
 * an array with keys set for those that succeeded
2043
 */
2044
function set_sysctl($values) {
2045
	if (empty($values)) {
2046
		return array();
2047
	}
2048

    
2049
	$value_list = array();
2050
	foreach ($values as $key => $value) {
2051
		$value_list[] = escapeshellarg($key) . "=" . escapeshellarg($value);
2052
	}
2053

    
2054
	exec("/sbin/sysctl -i " . implode(" ", $value_list), $output, $success);
2055

    
2056
	/* Retry individually if failed (one or more read-only) */
2057
	if ($success <> 0 && count($value_list) > 1) {
2058
		foreach ($value_list as $value) {
2059
			exec("/sbin/sysctl -i " . $value, $output);
2060
		}
2061
	}
2062

    
2063
	$ret = array();
2064
	foreach ($output as $line) {
2065
		$line = explode(": ", $line, 2);
2066
		if (count($line) == 2) {
2067
			$ret[$line[0]] = true;
2068
		}
2069
	}
2070

    
2071
	return $ret;
2072
}
2073

    
2074
/*
2075
 * set_single_sysctl($name, $value)
2076
 * Wrapper to set_sysctl() to make it simple to set only one sysctl
2077
 * returns boolean meaning if it succeeded
2078
 */
2079
function set_single_sysctl($name, $value) {
2080
	if (empty($name)) {
2081
		return false;
2082
	}
2083

    
2084
	$result = set_sysctl(array($name => $value));
2085

    
2086
	if (!isset($result[$name]) || $result[$name] != $value) {
2087
		return false;
2088
	}
2089

    
2090
	return true;
2091
}
2092

    
2093
/*
2094
 *     get_memory()
2095
 *     returns an array listing the amount of
2096
 *     memory installed in the hardware
2097
 *     [0] net memory available for the OS (FreeBSD) after some is taken by BIOS, video or whatever - e.g. 235 MBytes
2098
 *     [1] real (actual) memory of the system, should be the size of the RAM card/s - e.g. 256 MBytes
2099
 */
2100
function get_memory() {
2101
	$physmem = get_single_sysctl("hw.physmem");
2102
	$realmem = get_single_sysctl("hw.realmem");
2103
	/* convert from bytes to megabytes */
2104
	return array(($physmem/1048576), ($realmem/1048576));
2105
}
2106

    
2107
function mute_kernel_msgs() {
2108
	global $g, $config;
2109

    
2110
	if ($config['system']['enableserial']) {
2111
		return;
2112
	}
2113
	exec("/sbin/conscontrol mute on");
2114
}
2115

    
2116
function unmute_kernel_msgs() {
2117
	global $g;
2118

    
2119
	exec("/sbin/conscontrol mute off");
2120
}
2121

    
2122
function start_devd() {
2123
	global $g;
2124

    
2125
	/* Use the undocumented -q options of devd to quiet its log spamming */
2126
	$_gb = exec("/sbin/devd -q -f /etc/{$g['product_name']}-devd.conf");
2127
	sleep(1);
2128
	unset($_gb);
2129
}
2130

    
2131
function is_interface_vlan_mismatch() {
2132
	global $config, $g;
2133

    
2134
	if (is_array($config['vlans']['vlan'])) {
2135
		foreach ($config['vlans']['vlan'] as $vlan) {
2136
			if (substr($vlan['if'], 0, 4) == "lagg") {
2137
				return false;
2138
			}
2139
			if (does_interface_exist($vlan['if']) == false) {
2140
				return true;
2141
			}
2142
		}
2143
	}
2144

    
2145
	return false;
2146
}
2147

    
2148
function is_interface_mismatch() {
2149
	global $config, $g;
2150

    
2151
	$do_assign = false;
2152
	$i = 0;
2153
	$missing_interfaces = array();
2154
	if (is_array($config['interfaces'])) {
2155
		foreach ($config['interfaces'] as $ifname => $ifcfg) {
2156
			if (interface_is_vlan($ifcfg['if']) != NULL ||
2157
			    interface_is_qinq($ifcfg['if']) != NULL ||
2158
			    preg_match("/^enc|^cua|^tun|^tap|^l2tp|^pptp|^ppp|^ovpn|^gif|^gre|^lagg|^bridge|vlan|_wlan|_\d{0,4}_\d{0,4}$/i", $ifcfg['if'])) {
2159
				// Do not check these interfaces.
2160
				$i++;
2161
				continue;
2162
			} else if (does_interface_exist($ifcfg['if']) == false) {
2163
				$missing_interfaces[] = $ifcfg['if'];
2164
				$do_assign = true;
2165
			} else {
2166
				$i++;
2167
			}
2168
		}
2169
	}
2170

    
2171
	if (file_exists("{$g['tmp_path']}/assign_complete")) {
2172
		$do_assign = false;
2173
	}
2174

    
2175
	if (!empty($missing_interfaces) && $do_assign) {
2176
		file_put_contents("{$g['tmp_path']}/missing_interfaces", implode(' ', $missing_interfaces));
2177
	} else {
2178
		@unlink("{$g['tmp_path']}/missing_interfaces");
2179
	}
2180

    
2181
	return $do_assign;
2182
}
2183

    
2184
/* sync carp entries to other firewalls */
2185
function carp_sync_client() {
2186
	global $g;
2187
	send_event("filter sync");
2188
}
2189

    
2190
/****f* util/isAjax
2191
 * NAME
2192
 *   isAjax - reports if the request is driven from prototype
2193
 * INPUTS
2194
 *   none
2195
 * RESULT
2196
 *   true/false
2197
 ******/
2198
function isAjax() {
2199
	return isset ($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest';
2200
}
2201

    
2202
/****f* util/timeout
2203
 * NAME
2204
 *   timeout - console input with timeout countdown. Note: erases 2 char of screen for timer. Leave space.
2205
 * INPUTS
2206
 *   optional, seconds to wait before timeout. Default 9 seconds.
2207
 * RESULT
2208
 *   returns 1 char of user input or null if no input.
2209
 ******/
2210
function timeout($timer = 9) {
2211
	while (!isset($key)) {
2212
		if ($timer >= 9) {
2213
			echo chr(8) . chr(8) . ($timer == 9 ? chr(32) : null) . "{$timer}";
2214
		} else {
2215
			echo chr(8). "{$timer}";
2216
		}
2217
		`/bin/stty -icanon min 0 time 25`;
2218
		$key = trim(`KEY=\`dd count=1 2>/dev/null\`; echo \$KEY`);
2219
		`/bin/stty icanon`;
2220
		if ($key == '') {
2221
			unset($key);
2222
		}
2223
		$timer--;
2224
		if ($timer == 0) {
2225
			break;
2226
		}
2227
	}
2228
	return $key;
2229
}
2230

    
2231
/****f* util/msort
2232
 * NAME
2233
 *   msort - sort array
2234
 * INPUTS
2235
 *   $array to be sorted, field to sort by, direction of sort
2236
 * RESULT
2237
 *   returns newly sorted array
2238
 ******/
2239
function msort($array, $id = "id", $sort_ascending = true) {
2240
	$temp_array = array();
2241
	while (count($array)>0) {
2242
		$lowest_id = 0;
2243
		$index = 0;
2244
		foreach ($array as $item) {
2245
			if (isset($item[$id])) {
2246
				if ($array[$lowest_id][$id]) {
2247
					if (strtolower($item[$id]) < strtolower($array[$lowest_id][$id])) {
2248
						$lowest_id = $index;
2249
					}
2250
				}
2251
			}
2252
			$index++;
2253
		}
2254
		$temp_array[] = $array[$lowest_id];
2255
		$array = array_merge(array_slice($array, 0, $lowest_id), array_slice($array, $lowest_id + 1));
2256
	}
2257
	if ($sort_ascending) {
2258
		return $temp_array;
2259
	} else {
2260
		return array_reverse($temp_array);
2261
	}
2262
}
2263

    
2264
/****f* util/is_URL
2265
 * NAME
2266
 *   is_URL
2267
 * INPUTS
2268
 *   string to check
2269
 * RESULT
2270
 *   Returns true if item is a URL
2271
 ******/
2272
function is_URL($url) {
2273
	$match = preg_match("'\b(([\w-]+://?|www[.])[^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|/)))'", $url);
2274
	if ($match) {
2275
		return true;
2276
	}
2277
	return false;
2278
}
2279

    
2280
function is_file_included($file = "") {
2281
	$files = get_included_files();
2282
	if (in_array($file, $files)) {
2283
		return true;
2284
	}
2285

    
2286
	return false;
2287
}
2288

    
2289
/*
2290
 * Replace a value on a deep associative array using regex
2291
 */
2292
function array_replace_values_recursive($data, $match, $replace) {
2293
	if (empty($data)) {
2294
		return $data;
2295
	}
2296

    
2297
	if (is_string($data)) {
2298
		$data = preg_replace("/{$match}/", $replace, $data);
2299
	} else if (is_array($data)) {
2300
		foreach ($data as $k => $v) {
2301
			$data[$k] = array_replace_values_recursive($v, $match, $replace);
2302
		}
2303
	}
2304

    
2305
	return $data;
2306
}
2307

    
2308
/*
2309
	This function was borrowed from a comment on PHP.net at the following URL:
2310
	http://www.php.net/manual/en/function.array-merge-recursive.php#73843
2311
 */
2312
function array_merge_recursive_unique($array0, $array1) {
2313

    
2314
	$arrays = func_get_args();
2315
	$remains = $arrays;
2316

    
2317
	// We walk through each arrays and put value in the results (without
2318
	// considering previous value).
2319
	$result = array();
2320

    
2321
	// loop available array
2322
	foreach ($arrays as $array) {
2323

    
2324
		// The first remaining array is $array. We are processing it. So
2325
		// we remove it from remaining arrays.
2326
		array_shift($remains);
2327

    
2328
		// We don't care non array param, like array_merge since PHP 5.0.
2329
		if (is_array($array)) {
2330
			// Loop values
2331
			foreach ($array as $key => $value) {
2332
				if (is_array($value)) {
2333
					// we gather all remaining arrays that have such key available
2334
					$args = array();
2335
					foreach ($remains as $remain) {
2336
						if (array_key_exists($key, $remain)) {
2337
							array_push($args, $remain[$key]);
2338
						}
2339
					}
2340

    
2341
					if (count($args) > 2) {
2342
						// put the recursion
2343
						$result[$key] = call_user_func_array(__FUNCTION__, $args);
2344
					} else {
2345
						foreach ($value as $vkey => $vval) {
2346
							$result[$key][$vkey] = $vval;
2347
						}
2348
					}
2349
				} else {
2350
					// simply put the value
2351
					$result[$key] = $value;
2352
				}
2353
			}
2354
		}
2355
	}
2356
	return $result;
2357
}
2358

    
2359

    
2360
/*
2361
 * converts a string like "a,b,c,d"
2362
 * into an array like array("a" => "b", "c" => "d")
2363
 */
2364
function explode_assoc($delimiter, $string) {
2365
	$array = explode($delimiter, $string);
2366
	$result = array();
2367
	$numkeys = floor(count($array) / 2);
2368
	for ($i = 0; $i < $numkeys; $i += 1) {
2369
		$result[$array[$i * 2]] = $array[$i * 2 + 1];
2370
	}
2371
	return $result;
2372
}
2373

    
2374
/*
2375
 * Given a string of text with some delimiter, look for occurrences
2376
 * of some string and replace all of those.
2377
 * $text - the text string (e.g. "abc,defg,x123,ipv4,xyz")
2378
 * $delimiter - the delimiter (e.g. ",")
2379
 * $element - the element to match (e.g. "defg")
2380
 * $replacement - the string to replace it with (e.g. "42")
2381
 * Returns the resulting delimited string (e.g. "abc,42,x123,ipv4,xyz")
2382
 */
2383
function replace_element_in_list($text, $delimiter, $element, $replacement) {
2384
	$textArray = explode($delimiter, $text);
2385
	while (($entry = array_search($element, $textArray)) !== false) {
2386
		$textArray[$entry] = $replacement;
2387
	}
2388
	return implode(',', $textArray);
2389
}
2390

    
2391
/* Try to change a static route, if it doesn't exist, add it */
2392
function route_add_or_change($args) {
2393
	global $config;
2394

    
2395
	if (empty($args)) {
2396
		return false;
2397
	}
2398

    
2399
	/* First, try to add it */
2400
	$_gb = exec(escapeshellcmd("/sbin/route add " . $args), $output, $rc);
2401

    
2402
	if (isset($config['system']['route-debug'])) {
2403
		$mt = microtime();
2404
		log_error("ROUTING debug: $mt - ADD RC={$rc} - $args");
2405
	}
2406

    
2407
	if ($rc != 0) {
2408
		/* If it fails, try to change it */
2409
		$_gb = exec(escapeshellcmd("/sbin/route change " . $args),
2410
		    $output, $rc);
2411

    
2412
		if (isset($config['system']['route-debug'])) {
2413
			$mt = microtime();
2414
			log_error("ROUTING debug: $mt - CHG RC={$rc} - $args");
2415
		}
2416
	}
2417

    
2418
	return ($rc == 0);
2419
}
2420

    
2421
function get_staticroutes($returnsubnetsonly = false, $returnhostnames = false, $returnenabledroutesonly = false) {
2422
	global $config, $aliastable;
2423

    
2424
	/* Bail if there are no routes, but return an array always so callers don't have to check. */
2425
	if (!is_array($config['staticroutes']['route'])) {
2426
		return array();
2427
	}
2428

    
2429
	$allstaticroutes = array();
2430
	$allsubnets = array();
2431
	/* Loop through routes and expand aliases as we find them. */
2432
	foreach ($config['staticroutes']['route'] as $route) {
2433
		if ($returnenabledroutesonly && isset($route['disabled'])) {
2434
			continue;
2435
		}
2436

    
2437
		if (is_alias($route['network'])) {
2438
			if (!isset($aliastable[$route['network']])) {
2439
				continue;
2440
			}
2441

    
2442
			$subnets = preg_split('/\s+/', $aliastable[$route['network']]);
2443
			foreach ($subnets as $net) {
2444
				if (!is_subnet($net)) {
2445
					if (is_ipaddrv4($net)) {
2446
						$net .= "/32";
2447
					} else if (is_ipaddrv6($net)) {
2448
						$net .= "/128";
2449
					} else if ($returnhostnames === false || !is_fqdn($net)) {
2450
						continue;
2451
					}
2452
				}
2453
				$temproute = $route;
2454
				$temproute['network'] = $net;
2455
				$allstaticroutes[] = $temproute;
2456
				$allsubnets[] = $net;
2457
			}
2458
		} elseif (is_subnet($route['network'])) {
2459
			$allstaticroutes[] = $route;
2460
			$allsubnets[] = $route['network'];
2461
		}
2462
	}
2463
	if ($returnsubnetsonly) {
2464
		return $allsubnets;
2465
	} else {
2466
		return $allstaticroutes;
2467
	}
2468
}
2469

    
2470
/****f* util/get_alias_list
2471
 * NAME
2472
 *   get_alias_list - Provide a list of aliases.
2473
 * INPUTS
2474
 *   $type          - Optional, can be a string or array specifying what type(s) of aliases you need.
2475
 * RESULT
2476
 *   Array containing list of aliases.
2477
 *   If $type is unspecified, all aliases are returned.
2478
 *   If $type is a string, all aliases of the type specified in $type are returned.
2479
 *   If $type is an array, all aliases of any type specified in any element of $type are returned.
2480
 */
2481
function get_alias_list($type = null) {
2482
	global $config;
2483
	$result = array();
2484
	if ($config['aliases']['alias'] <> "" && is_array($config['aliases']['alias'])) {
2485
		foreach ($config['aliases']['alias'] as $alias) {
2486
			if ($type === null) {
2487
				$result[] = $alias['name'];
2488
			} else if (is_array($type)) {
2489
				if (in_array($alias['type'], $type)) {
2490
					$result[] = $alias['name'];
2491
				}
2492
			} else if ($type === $alias['type']) {
2493
				$result[] = $alias['name'];
2494
			}
2495
		}
2496
	}
2497
	return $result;
2498
}
2499

    
2500
/* returns an array consisting of every element of $haystack that is not equal to $needle. */
2501
function array_exclude($needle, $haystack) {
2502
	$result = array();
2503
	if (is_array($haystack)) {
2504
		foreach ($haystack as $thing) {
2505
			if ($needle !== $thing) {
2506
				$result[] = $thing;
2507
			}
2508
		}
2509
	}
2510
	return $result;
2511
}
2512

    
2513
/* Define what is preferred, IPv4 or IPv6 */
2514
function prefer_ipv4_or_ipv6() {
2515
	global $config;
2516

    
2517
	if (isset($config['system']['prefer_ipv4'])) {
2518
		mwexec("/etc/rc.d/ip6addrctl prefer_ipv4");
2519
	} else {
2520
		mwexec("/etc/rc.d/ip6addrctl prefer_ipv6");
2521
	}
2522
}
2523

    
2524
/* Redirect to page passing parameters via POST */
2525
function post_redirect($page, $params) {
2526
	if (!is_array($params)) {
2527
		return;
2528
	}
2529

    
2530
	print "<html><body><form action=\"{$page}\" name=\"formredir\" method=\"post\">\n";
2531
	foreach ($params as $key => $value) {
2532
		print "<input type=\"hidden\" name=\"{$key}\" value=\"{$value}\" />\n";
2533
	}
2534
	print "</form>\n";
2535
	print "<script type=\"text/javascript\">\n";
2536
	print "//<![CDATA[\n";
2537
	print "document.formredir.submit();\n";
2538
	print "//]]>\n";
2539
	print "</script>\n";
2540
	print "</body></html>\n";
2541
}
2542

    
2543
/* Locate disks that can be queried for S.M.A.R.T. data. */
2544
function get_smart_drive_list() {
2545
	$disk_list = explode(" ", get_single_sysctl("kern.disks"));
2546
	foreach ($disk_list as $id => $disk) {
2547
		// We only want certain kinds of disks for S.M.A.R.T.
2548
		// 1 is a match, 0 is no match, False is any problem processing the regex
2549
		if (preg_match("/^(ad|da|ada).*[0-9]{1,2}$/", $disk) !== 1) {
2550
			unset($disk_list[$id]);
2551
		}
2552
	}
2553
	sort($disk_list);
2554
	return $disk_list;
2555
}
2556

    
2557
// Validate a network address
2558
//	$addr: the address to validate
2559
//	$type: IPV4|IPV6|IPV4V6
2560
//	$label: the label used by the GUI to display this value. Required to compose an error message
2561
//	$err_msg: pointer to the callers error message array so that error messages can be added to it here
2562
//	$alias: are aliases permitted for this address?
2563
// Returns:
2564
//	IPV4 - if $addr is a valid IPv4 address
2565
//	IPV6 - if $addr is a valid IPv6 address
2566
//	ALIAS - if $alias=true and $addr is an alias
2567
//	false - otherwise
2568

    
2569
function validateipaddr(&$addr, $type, $label, &$err_msg, $alias=false) {
2570
	switch ($type) {
2571
		case IPV4:
2572
			if (is_ipaddrv4($addr)) {
2573
				return IPV4;
2574
			} else if ($alias) {
2575
				if (is_alias($addr)) {
2576
					return ALIAS;
2577
				} else {
2578
					$err_msg[] = sprintf(gettext("%s must be a valid IPv4 address or alias."), $label);
2579
					return false;
2580
				}
2581
			} else {
2582
				$err_msg[] = sprintf(gettext("%s must be a valid IPv4 address."), $label);
2583
				return false;
2584
			}
2585
		break;
2586
		case IPV6:
2587
			if (is_ipaddrv6($addr)) {
2588
				$addr = strtolower($addr);
2589
				return IPV6;
2590
			} else if ($alias) {
2591
				if (is_alias($addr)) {
2592
					return ALIAS;
2593
				} else {
2594
					$err_msg[] = sprintf(gettext("%s must be a valid IPv6 address or alias."), $label);
2595
					return false;
2596
				}
2597
			} else {
2598
				$err_msg[] = sprintf(gettext("%s must be a valid IPv6 address."), $label);
2599
				return false;
2600
			}
2601
		break;
2602
		case IPV4V6:
2603
			if (is_ipaddrv6($addr)) {
2604
				$addr = strtolower($addr);
2605
				return IPV6;
2606
			} else if (is_ipaddrv4($addr)) {
2607
				return IPV4;
2608
			} else if ($alias) {
2609
				if (is_alias($addr)) {
2610
					return ALIAS;
2611
				} else {
2612
					$err_msg[] = sprintf(gettext("%s must be a valid IPv4 or IPv6 address or alias."), $label);
2613
					return false;
2614
				}
2615
			} else {
2616
				$err_msg[] = sprintf(gettext("%s must be a valid IPv4 or IPv6 address."), $label);
2617
				return false;
2618
			}
2619
		break;
2620
	}
2621

    
2622
	return false;
2623
}
2624

    
2625
/* format a string to look (more) like the expected DUID format:
2626
 * 1) Replace any "-" with ":"
2627
 * 2) If the user inputs 14 components, then add the expected "0e:00:" to the front.
2628
 *    This is convenience, because the actual DUID (which is reported in logs) is the last 14 components.
2629
 * 3) If any components are input with just a single char (hex digit hopefully), put a "0" in front.
2630
 *
2631
 * The final result should be closer to:
2632
 *
2633
 * "0e:00:00:01:00:01:nn:nn:nn:nn:nn:nn:nn:nn:nn:nn"
2634
 *
2635
 * This function does not validate the input. is_duid() will do validation.
2636
*/
2637
function format_duid($dhcp6duid) {
2638
	$values = explode(":", strtolower(str_replace("-", ":", $dhcp6duid)));
2639
	if (count($values) == 14) {
2640
		array_unshift($values, "0e", "00");
2641
	}
2642

    
2643
	array_walk($values, function(&$value) {
2644
		$value = str_pad($value, 2, '0', STR_PAD_LEFT);
2645
	});
2646

    
2647
	return implode(":", $values);
2648
}
2649

    
2650
/* returns true if $dhcp6duid is a valid duid entry */
2651
function is_duid($dhcp6duid) {
2652
	$values = explode(":", $dhcp6duid);
2653
	if (count($values) != 16 || strlen($dhcp6duid) != 47) {
2654
		return false;
2655
	}
2656
	for ($i = 0; $i < 16; $i++) {
2657
		if (ctype_xdigit($values[$i]) == false)
2658
			return false;
2659
		if (hexdec($values[$i]) < 0 || hexdec($values[$i]) > 255)
2660
			return false;
2661
	}
2662
	return true;
2663
}
2664

    
2665
/* Write the DHCP6 DUID file */
2666
function write_dhcp6_duid($duidstring) {
2667
	// Create the hex array from the dhcp6duid config entry and write to file
2668
	global $g;
2669

    
2670
	if(!is_duid($duidstring)) {
2671
		log_error(gettext("Error: attempting to write DUID file - Invalid DUID detected"));
2672
		return false;
2673
	}
2674
	$temp = str_replace(":","",$duidstring);
2675
	$duid_binstring = pack("H*",$temp);
2676
	if ($fd = fopen("{$g['vardb_path']}/dhcp6c_duid", "wb")) {
2677
		fwrite($fd, $duid_binstring);
2678
		fclose($fd);
2679
		return true;
2680
	}
2681
	log_error(gettext("Error: attempting to write DUID file - File write error"));
2682
	return false;
2683
}
2684

    
2685
/* returns duid string from 'vardb_path']}/dhcp6c_duid' */
2686
function get_duid_from_file() {
2687
	global $g;
2688

    
2689
	$duid_ASCII = "";
2690
	$count = 0;
2691

    
2692
	if (file_exists("{$g['vardb_path']}/dhcp6c_duid") &&
2693
	    ($fd = fopen("{$g['vardb_path']}/dhcp6c_duid", "r"))) {
2694
		if(filesize("{$g['vardb_path']}/dhcp6c_duid")==16) {
2695
			$buffer = fread($fd,16);
2696
			while($count < 16) {
2697
				$duid_ASCII .= bin2hex($buffer[$count]);
2698
				$count++;
2699
				if($count < 16) {
2700
					$duid_ASCII .= ":";
2701
				}
2702
			}
2703
		}
2704
		fclose($fd);
2705
	}
2706
	//if no file or error with read then the string returns blanked DUID string
2707
	if(!is_duid($duid_ASCII)) {
2708
		return "--:--:--:--:--:--:--:--:--:--:--:--:--:--:--:--";
2709
	}
2710
	return($duid_ASCII);
2711
}
2712

    
2713
/* Replaces the Mac OS 9 and earlier (\r) and DOS/Windows (\r\n) newlines with the Unix equivalent (\n). */
2714
function unixnewlines($text) {
2715
	return preg_replace('/\r\n?/', "\n", $text);
2716
}
2717

    
2718
?>
(46-46/54)