Project

General

Profile

Download (71.8 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
/*
3
 * util.inc
4
 *
5
 * part of pfSense (https://www.pfsense.org)
6
 * Copyright (c) 2004-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
function config_lock() {
109
	return;
110
}
111
function config_unlock() {
112
	return;
113
}
114

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

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

    
154
		return $fp;
155
	}
156

    
157
	return NULL;
158
}
159

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
359
	foreach ( $parts as $v ) {
360

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

    
364
	}
365

    
366
	return $binstr;
367
}
368

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

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

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

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

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

    
390
	return $ip;
391
}
392

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

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

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

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

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

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

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

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

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

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

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

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

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

    
498
	return $rangeaddresses;
499
}
500

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
621
	return $out;
622
}
623

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

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

    
654
/* returns true if $ipaddr is a valid IPv6 address */
655
function is_ipaddrv6($ipaddr) {
656
	if (!is_string($ipaddr) || empty($ipaddr)) {
657
		return false;
658
	}
659
	if (strstr($ipaddr, "%") && is_linklocal($ipaddr)) {
660
		$tmpip = explode("%", $ipaddr);
661
		$ipaddr = $tmpip[0];
662
	}
663
	return Net_IPv6::checkIPv6($ipaddr);
664
}
665

    
666
/* returns true if $ipaddr is a valid dotted IPv4 address */
667
function is_ipaddrv4($ipaddr) {
668
	if (!is_string($ipaddr) || empty($ipaddr) || ip2long($ipaddr) === FALSE) {
669
		return false;
670
	}
671
	return true;
672
}
673

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

    
690
/* returns scope of a linklocal address */
691
function get_ll_scope($addr) {
692
	if (!is_linklocal($addr) || !strstr($addr, "%")) {
693
		return "";
694
	}
695
	list ($ll, $scope) = explode("%", $addr);
696
	return $scope;
697
}
698

    
699
/* returns true if $ipaddr is a valid literal IPv6 address */
700
function is_literalipaddrv6($ipaddr) {
701
	if (substr($ipaddr,0,1) == '[' && substr($ipaddr,-1,1) == ']') {
702
		// if it's data wrapped in "[ ... ]" then test if middle part is valid IPv6
703
		return is_ipaddrv6(substr($ipaddr,1,-1));
704
	}
705
	return false;
706
}
707

    
708
/* returns true if $iport is a valid IPv4:port or [Literal IPv6]:port
709
	false - not valid
710
	true (numeric 4 or 6) - if valid, gives type of address */
711
function is_ipaddrwithport($ipport) {
712
	$c = strrpos($ipport, ":");
713
	if ($c === false) {
714
		return false;  // can't split at final colon if no colon exists
715
	}
716

    
717
	if (!is_port(substr($ipport, $c + 1))) {
718
		return false;  // no valid port after last colon
719
	}
720

    
721
	$ip = substr($ipport, 0, $c);  // else is text before last colon a valid IP
722
	if (is_literalipaddrv6($ip)) {
723
		return 6;
724
	} elseif (is_ipaddrv4($ip)) {
725
		return 4;
726
	} else {
727
		return false;
728
	}
729
}
730

    
731
function is_hostnamewithport($hostport) {
732
	$parts = explode(":", $hostport);
733
	// no need to validate with is_string(); if it's not a string then explode won't return 2 parts anyway
734
	if (count($parts) == 2) {
735
		return is_hostname($parts[0]) && is_port($parts[1]);
736
	}
737
	return false;
738
}
739

    
740
/* returns true if $ipaddr is a valid dotted IPv4 address or an alias thereof */
741
function is_ipaddroralias($ipaddr) {
742
	global $config;
743

    
744
	if (is_alias($ipaddr)) {
745
		if (is_array($config['aliases']['alias'])) {
746
			foreach ($config['aliases']['alias'] as $alias) {
747
				if ($alias['name'] == $ipaddr && !preg_match("/port/i", $alias['type'])) {
748
					return true;
749
				}
750
			}
751
		}
752
		return false;
753
	} else {
754
		return is_ipaddr($ipaddr);
755
	}
756

    
757
}
758

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

    
774
/* same as is_subnet() but accepts IPv4 only */
775
function is_subnetv4($subnet) {
776
	return (is_subnet($subnet) == 4);
777
}
778

    
779
/* same as is_subnet() but accepts IPv6 only */
780
function is_subnetv6($subnet) {
781
	return (is_subnet($subnet) == 6);
782
}
783

    
784
/* returns true if $subnet is a valid subnet in CIDR format or an alias thereof */
785
function is_subnetoralias($subnet) {
786
	global $aliastable;
787

    
788
	if (isset($aliastable[$subnet]) && is_subnet($aliastable[$subnet])) {
789
		return true;
790
	} else {
791
		return is_subnet($subnet);
792
	}
793
}
794

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

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

    
823
	// 2**N returns an exact result as an INT if possible, and a float/double if not.
824
	// 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
825
	$result = 2 ** $snsize;
826

    
827
	if ($exact && !is_int($result)) {
828
		//exact required but can't represent result exactly as an INT
829
		return 0;
830
	} else {
831
		// result ok, will be an INT where possible (guaranteed up to 2^31 addresses on x32/x64) and a float for 'huge' subnets
832
		return $result;
833
	}
834
}
835

    
836
/* function used by pfblockerng */
837
function subnetv4_expand($subnet) {
838
	$result = array();
839
	list ($ip, $bits) = explode("/", $subnet);
840
	$net = ip2long($ip);
841
	$mask = (0xffffffff << (32 - $bits));
842
	$net &= $mask;
843
	$size = round(exp(log(2) * (32 - $bits)));
844
	for ($i = 0; $i < $size; $i += 1) {
845
		$result[] = long2ip($net | $i);
846
	}
847
	return $result;
848
}
849

    
850
/* find out whether two IPv4/IPv6 CIDR subnets overlap.
851
   Note: CIDR overlap implies one is identical or included so largest sn will be the same */
852
function check_subnets_overlap($subnet1, $bits1, $subnet2, $bits2) {
853
	if (is_ipaddrv4($subnet1)) {
854
		return check_subnetsv4_overlap($subnet1, $bits1, $subnet2, $bits2);
855
	} else {
856
		return check_subnetsv6_overlap($subnet1, $bits1, $subnet2, $bits2);
857
	}
858
}
859

    
860
/* find out whether two IPv4 CIDR subnets overlap.
861
   Note: CIDR overlap means sn1/sn2 are identical or one is included in other. So sn using largest $bits will be the same  */
862
function check_subnetsv4_overlap($subnet1, $bits1, $subnet2, $bits2) {
863
	$largest_sn = min($bits1, $bits2);
864
	$subnetv4_start1 = gen_subnetv4($subnet1, $largest_sn);
865
	$subnetv4_start2 = gen_subnetv4($subnet2, $largest_sn);
866

    
867
	if ($subnetv4_start1 == '' || $subnetv4_start2 == '') {
868
		// One or both args is not a valid IPv4 subnet
869
		//FIXME: needs to return "bad data" not true/false if bad. For now return false, best we can do until fixed
870
		return false;
871
	}
872
	return ($subnetv4_start1 == $subnetv4_start2);
873
}
874

    
875
/* find out whether two IPv6 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_subnetsv6_overlap($subnet1, $bits1, $subnet2, $bits2) {
878
	$largest_sn = min($bits1, $bits2);
879
	$subnetv6_start1 = gen_subnetv6($subnet1, $largest_sn);
880
	$subnetv6_start2 = gen_subnetv6($subnet2, $largest_sn);
881

    
882
	if ($subnetv6_start1 == '' || $subnetv6_start2 == '') {
883
		// One or both args is not a valid IPv6 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 ($subnetv6_start1 == $subnetv6_start2);
888
}
889

    
890
/* return all PTR zones for a IPv6 network */
891
function get_v6_ptr_zones($subnet, $bits) {
892
	$result = array();
893

    
894
	if (!is_ipaddrv6($subnet)) {
895
		return $result;
896
	}
897

    
898
	if (!is_numericint($bits) || $bits > 128) {
899
		return $result;
900
	}
901

    
902
	/*
903
	 * Find a small nibble boundary subnet mask
904
	 * e.g. a /29 will create 8 /32 PTR zones
905
	 */
906
	$small_sn = $bits;
907
	while ($small_sn % 4 != 0) {
908
		$small_sn++;
909
	}
910

    
911
	/* Get network prefix */
912
	$small_subnet = Net_IPv6::getNetmask($subnet, $bits);
913

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

    
925
		/* Detect what part of IP should be increased */
926
		$change_part = (int) ($small_sn / 16);
927
		if ($small_sn % 16 == 0) {
928
			$change_part--;
929
		}
930

    
931
		/* Increase 1 to desired part */
932
		$parts = explode(":", Net_IPv6::uncompress($small_subnet));
933
		$parts[$change_part]++;
934
		$small_subnet = implode(":", $parts);
935
	}
936

    
937
	return $result;
938
}
939

    
940
/* return true if $addr is in $subnet, false if not */
941
function ip_in_subnet($addr, $subnet) {
942
	if (is_ipaddrv6($addr) && is_subnetv6($subnet)) {
943
		return (Net_IPv6::isInNetmask($addr, $subnet));
944
	} else if (is_ipaddrv4($addr) && is_subnetv4($subnet)) {
945
		list($ip, $mask) = explode('/', $subnet);
946
		$mask = (0xffffffff << (32 - $mask)) & 0xffffffff;
947
		return ((ip2long($addr) & $mask) == (ip2long($ip) & $mask));
948
	}
949
	return false;
950
}
951

    
952
/* returns true if $hostname is just a valid hostname (top part without any of the domain part) */
953
function is_unqualified_hostname($hostname) {
954
	if (!is_string($hostname)) {
955
		return false;
956
	}
957

    
958
	if (preg_match('/^(?:[a-z0-9_]|[a-z0-9_][a-z0-9_\-]*[a-z0-9_])$/i', $hostname)) {
959
		return true;
960
	} else {
961
		return false;
962
	}
963
}
964

    
965
/* returns true if $hostname is a valid hostname, with or without being a fully-qualified domain name. */
966
function is_hostname($hostname, $allow_wildcard=false) {
967
	if (!is_string($hostname)) {
968
		return false;
969
	}
970

    
971
	if (is_domain($hostname, $allow_wildcard)) {
972
		if ((substr_count($hostname, ".") == 1) && ($hostname[strlen($hostname)-1] == ".")) {
973
			/* Only a single dot at the end like "test." - hosts cannot be directly in the root domain. */
974
			return false;
975
		} else {
976
			return true;
977
		}
978
	} else {
979
		return false;
980
	}
981
}
982

    
983
/* returns true if $domain is a valid domain name */
984
function is_domain($domain, $allow_wildcard=false) {
985
	if (!is_string($domain)) {
986
		return false;
987
	}
988
	if ($allow_wildcard) {
989
		$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';
990
	} else {
991
		$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';
992
	}
993

    
994
	if (preg_match($domain_regex, $domain)) {
995
		return true;
996
	} else {
997
		return false;
998
	}
999
}
1000

    
1001
/* returns true if $macaddr is a valid MAC address */
1002
function is_macaddr($macaddr, $partial=false) {
1003
	$values = explode(":", $macaddr);
1004

    
1005
	/* Verify if the MAC address has a proper amount of parts for either a partial or full match. */
1006
	if ($partial) {
1007
		if ((count($values) < 1) || (count($values) > 6)) {
1008
			return false;
1009
		}
1010
	} elseif (count($values) != 6) {
1011
		return false;
1012
	}
1013
	for ($i = 0; $i < count($values); $i++) {
1014
		if (ctype_xdigit($values[$i]) == false)
1015
			return false;
1016
		if (hexdec($values[$i]) < 0 || hexdec($values[$i]) > 255)
1017
			return false;
1018
	}
1019

    
1020
	return true;
1021
}
1022

    
1023
/*
1024
	If $return_message is true then
1025
		returns a text message about the reason that the name is invalid.
1026
		the text includes the type of "thing" that is being checked, passed in $object. (e.g. "alias", "gateway group", "schedule")
1027
	else
1028
		returns true if $name is a valid name for an alias
1029
		returns false if $name is not a valid name for an alias
1030

    
1031
	Aliases cannot be:
1032
		bad chars: anything except a-z 0-9 and underscore
1033
		bad names: empty string, pure numeric, pure underscore
1034
		reserved words: pre-defined service/protocol/port names which should not be ambiguous, and the words "port" and  "pass" */
1035

    
1036
function is_validaliasname($name, $return_message = false, $object = "alias") {
1037
	/* Array of reserved words */
1038
	$reserved = array("port", "pass");
1039

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

    
1075
/* returns a text message indicating if the alias name is valid, or the reason it is not valid. */
1076
function invalidaliasnamemsg($name, $object = "alias") {
1077
	return is_validaliasname($name, true, $object);
1078
}
1079

    
1080
/*
1081
 * returns true if $range is a valid integer range between $min and $max
1082
 * range delimiter can be ':' or '-'
1083
 */
1084
function is_intrange($range, $min, $max) {
1085
	$values = preg_split("/[:-]/", $range);
1086

    
1087
	if (!is_array($values) || count($values) != 2) {
1088
		return false;
1089
	}
1090

    
1091
	if (!ctype_digit($values[0]) || !ctype_digit($values[1])) {
1092
		return false;
1093
	}
1094

    
1095
	$values[0] = intval($values[0]);
1096
	$values[1] = intval($values[1]);
1097

    
1098
	if ($values[0] >= $values[1]) {
1099
		return false;
1100
	}
1101

    
1102
	if ($values[0] < $min || $values[1] > $max) {
1103
		return false;
1104
	}
1105

    
1106
	return true;
1107
}
1108

    
1109
/* returns true if $port is a valid TCP/UDP port */
1110
function is_port($port) {
1111
	if (ctype_digit($port) && ((intval($port) >= 1) && (intval($port) <= 65535))) {
1112
		return true;
1113
	}
1114
	if (getservbyname($port, "tcp") || getservbyname($port, "udp")) {
1115
		return true;
1116
	}
1117
	return false;
1118
}
1119

    
1120
/* returns true if $portrange is a valid TCP/UDP portrange ("<port>:<port>") */
1121
function is_portrange($portrange) {
1122
	$ports = explode(":", $portrange);
1123

    
1124
	return (count($ports) == 2 && is_port($ports[0]) && is_port($ports[1]));
1125
}
1126

    
1127
/* returns true if $port is a valid port number or an alias thereof */
1128
function is_portoralias($port) {
1129
	global $config;
1130

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

    
1145
/* create ranges of sequential port numbers (200:215) and remove duplicates */
1146
function group_ports($ports, $kflc = false) {
1147
	if (!is_array($ports) || empty($ports)) {
1148
		return;
1149
	}
1150

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

    
1176
	$result = array();
1177
	foreach ($uniq as $idx => $port) {
1178
		if ($idx == 0) {
1179
			$result[] = $port;
1180
			continue;
1181
		}
1182

    
1183
		$last = end($result);
1184
		if (is_portrange($last)) {
1185
			list($begin, $end) = explode(":", $last);
1186
		} else {
1187
			$begin = $end = $last;
1188
		}
1189

    
1190
		if ($port == ($end+1)) {
1191
			$end++;
1192
			$result[count($result)-1] = "{$begin}:{$end}";
1193
		} else {
1194
			$result[] = $port;
1195
		}
1196
	}
1197

    
1198
	return array_merge($comments, $result);
1199
}
1200

    
1201
/* returns true if $val is a valid shaper bandwidth value */
1202
function is_valid_shaperbw($val) {
1203
	return (preg_match("/^(\d+(?:\.\d+)?)([MKG]?b|%)$/", $val));
1204
}
1205

    
1206
/* returns true if $test is in the range between $start and $end */
1207
function is_inrange_v4($test, $start, $end) {
1208
	if (!is_ipaddrv4($test) || !is_ipaddrv4($start) || !is_ipaddrv4($end)) {
1209
		return false;
1210
	}
1211

    
1212
	if (ip2ulong($test) <= ip2ulong($end) &&
1213
	    ip2ulong($test) >= ip2ulong($start)) {
1214
		return true;
1215
	}
1216

    
1217
	return false;
1218
}
1219

    
1220
/* returns true if $test is in the range between $start and $end */
1221
function is_inrange_v6($test, $start, $end) {
1222
	if (!is_ipaddrv6($test) || !is_ipaddrv6($start) || !is_ipaddrv6($end)) {
1223
		return false;
1224
	}
1225

    
1226
	if (inet_pton($test) <= inet_pton($end) &&
1227
	    inet_pton($test) >= inet_pton($start)) {
1228
		return true;
1229
	}
1230

    
1231
	return false;
1232
}
1233

    
1234
/* returns true if $test is in the range between $start and $end */
1235
function is_inrange($test, $start, $end) {
1236
	return is_ipaddrv6($test) ? is_inrange_v6($test, $start, $end) : is_inrange_v4($test, $start, $end);
1237
}
1238

    
1239
function get_configured_vip_list($family = 'all', $type = VIP_ALL) {
1240
	global $config;
1241

    
1242
	$list = array();
1243
	if (!is_array($config['virtualip']['vip']) || empty($config['virtualip']['vip'])) {
1244
		return ($list);
1245
	}
1246

    
1247
	$viparr = &$config['virtualip']['vip'];
1248
	foreach ($viparr as $vip) {
1249

    
1250
		if ($type == VIP_CARP) {
1251
			if ($vip['mode'] != "carp")
1252
				continue;
1253
		} elseif ($type == VIP_IPALIAS) {
1254
			if ($vip['mode'] != "ipalias")
1255
				continue;
1256
		} else {
1257
			if ($vip['mode'] != "carp" && $vip['mode'] != "ipalias")
1258
				continue;
1259
		}
1260

    
1261
		if ($family == 'all' ||
1262
		    ($family == 'inet' && is_ipaddrv4($vip['subnet'])) ||
1263
		    ($family == 'inet6' && is_ipaddrv6($vip['subnet']))) {
1264
			$list["_vip{$vip['uniqid']}"] = $vip['subnet'];
1265
		}
1266
	}
1267
	return ($list);
1268
}
1269

    
1270
function get_configured_vip($vipinterface = '') {
1271

    
1272
	return (get_configured_vip_detail($vipinterface, 'all', 'vip'));
1273
}
1274

    
1275
function get_configured_vip_interface($vipinterface = '') {
1276

    
1277
	return (get_configured_vip_detail($vipinterface, 'all', 'iface'));
1278
}
1279

    
1280
function get_configured_vip_ipv4($vipinterface = '') {
1281

    
1282
	return (get_configured_vip_detail($vipinterface, 'inet', 'ip'));
1283
}
1284

    
1285
function get_configured_vip_ipv6($vipinterface = '') {
1286

    
1287
	return (get_configured_vip_detail($vipinterface, 'inet6', 'ip'));
1288
}
1289

    
1290
function get_configured_vip_subnetv4($vipinterface = '') {
1291

    
1292
	return (get_configured_vip_detail($vipinterface, 'inet', 'subnet'));
1293
}
1294

    
1295
function get_configured_vip_subnetv6($vipinterface = '') {
1296

    
1297
	return (get_configured_vip_detail($vipinterface, 'inet6', 'subnet'));
1298
}
1299

    
1300
function get_configured_vip_detail($vipinterface = '', $family = 'inet', $what = 'ip') {
1301
	global $config;
1302

    
1303
	if (empty($vipinterface) || !is_array($config['virtualip']['vip']) ||
1304
	    empty($config['virtualip']['vip'])) {
1305
		return (NULL);
1306
	}
1307

    
1308
	$viparr = &$config['virtualip']['vip'];
1309
	foreach ($viparr as $vip) {
1310
		if ($vip['mode'] != "carp" && $vip['mode'] != "ipalias") {
1311
			continue;
1312
		}
1313

    
1314
		if ($vipinterface != "_vip{$vip['uniqid']}") {
1315
			continue;
1316
		}
1317

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

    
1343
	return (NULL);
1344
}
1345

    
1346
/* comparison function for sorting by the order in which interfaces are normally created */
1347
function compare_interface_friendly_names($a, $b) {
1348
	if ($a == $b) {
1349
		return 0;
1350
	} else if ($a == 'wan') {
1351
		return -1;
1352
	} else if ($b == 'wan') {
1353
		return 1;
1354
	} else if ($a == 'lan') {
1355
		return -1;
1356
	} else if ($b == 'lan') {
1357
		return 1;
1358
	}
1359

    
1360
	return strnatcmp($a, $b);
1361
}
1362

    
1363
/* return the configured interfaces list. */
1364
function get_configured_interface_list($only_opt = false, $withdisabled = false) {
1365
	global $config;
1366

    
1367
	$iflist = array();
1368

    
1369
	/* if list */
1370
	foreach ($config['interfaces'] as $if => $ifdetail) {
1371
		if ($only_opt && ($if == "wan" || $if == "lan")) {
1372
			continue;
1373
		}
1374
		if (isset($ifdetail['enable']) || $withdisabled == true) {
1375
			$iflist[$if] = $if;
1376
		}
1377
	}
1378

    
1379
	return $iflist;
1380
}
1381

    
1382
/* return the configured interfaces list. */
1383
function get_configured_interface_list_by_realif($only_opt = false, $withdisabled = false) {
1384
	global $config;
1385

    
1386
	$iflist = array();
1387

    
1388
	/* if list */
1389
	foreach ($config['interfaces'] as $if => $ifdetail) {
1390
		if ($only_opt && ($if == "wan" || $if == "lan")) {
1391
			continue;
1392
		}
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($only_opt = false, $withdisabled = false) {
1406
	global $config;
1407

    
1408
	$iflist = array();
1409

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

    
1424
	return $iflist;
1425
}
1426

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

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

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

    
1465
	return $ip_array;
1466
}
1467

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

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

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

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

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

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

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

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

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

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

    
1696
	return $retval;
1697
}
1698

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

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

    
1727
	$aliastable = array();
1728

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

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

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

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

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

    
1756
	return "";
1757
}
1758

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1888
		sleep(1);
1889
	}
1890

    
1891
	return false;
1892
}
1893

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
2017
	return $values;
2018
}
2019

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

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

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

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

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

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

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

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

    
2070
	return $ret;
2071
}
2072

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

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

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

    
2089
	return true;
2090
}
2091

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

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

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

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

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

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

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

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

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

    
2144
	return false;
2145
}
2146

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

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

    
2168
	if (file_exists("{$g['tmp_path']}/assign_complete")) {
2169
		$do_assign = false;
2170
	}
2171

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

    
2178
	return $do_assign;
2179
}
2180

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

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

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

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

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

    
2277
function is_file_included($file = "") {
2278
	$files = get_included_files();
2279
	if (in_array($file, $files)) {
2280
		return true;
2281
	}
2282

    
2283
	return false;
2284
}
2285

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

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

    
2302
	return $data;
2303
}
2304

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

    
2311
	$arrays = func_get_args();
2312
	$remains = $arrays;
2313

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

    
2318
	// loop available array
2319
	foreach ($arrays as $array) {
2320

    
2321
		// The first remaining array is $array. We are processing it. So
2322
		// we remove it from remaining arrays.
2323
		array_shift($remains);
2324

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

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

    
2356

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

    
2371
/* Try to change a static route, if it doesn't exist, add it */
2372
function route_add_or_change($args) {
2373
	global $config;
2374

    
2375
	if (empty($args)) {
2376
		return false;
2377
	}
2378

    
2379
	/* First, try to add it */
2380
	$_gb = exec(escapeshellcmd("/sbin/route add " . $args), $output, $rc);
2381

    
2382
	if (isset($config['system']['route-debug'])) {
2383
		$mt = microtime();
2384
		log_error("ROUTING debug: $mt - ADD RC={$rc} - $args");
2385
	}
2386

    
2387
	if ($rc != 0) {
2388
		/* If it fails, try to change it */
2389
		$_gb = exec(escapeshellcmd("/sbin/route change " . $args),
2390
		    $output, $rc);
2391

    
2392
		if (isset($config['system']['route-debug'])) {
2393
			$mt = microtime();
2394
			log_error("ROUTING debug: $mt - CHG RC={$rc} - $args");
2395
		}
2396
	}
2397

    
2398
	return ($rc == 0);
2399
}
2400

    
2401
function get_staticroutes($returnsubnetsonly = false, $returnhostnames = false, $returnenabledroutesonly = false) {
2402
	global $config, $aliastable;
2403

    
2404
	/* Bail if there are no routes, but return an array always so callers don't have to check. */
2405
	if (!is_array($config['staticroutes']['route'])) {
2406
		return array();
2407
	}
2408

    
2409
	$allstaticroutes = array();
2410
	$allsubnets = array();
2411
	/* Loop through routes and expand aliases as we find them. */
2412
	foreach ($config['staticroutes']['route'] as $route) {
2413
		if ($returnenabledroutesonly && isset($route['disabled'])) {
2414
			continue;
2415
		}
2416

    
2417
		if (is_alias($route['network'])) {
2418
			if (!isset($aliastable[$route['network']])) {
2419
				continue;
2420
			}
2421

    
2422
			$subnets = preg_split('/\s+/', $aliastable[$route['network']]);
2423
			foreach ($subnets as $net) {
2424
				if (!is_subnet($net)) {
2425
					if (is_ipaddrv4($net)) {
2426
						$net .= "/32";
2427
					} else if (is_ipaddrv6($net)) {
2428
						$net .= "/128";
2429
					} else if ($returnhostnames === false || !is_fqdn($net)) {
2430
						continue;
2431
					}
2432
				}
2433
				$temproute = $route;
2434
				$temproute['network'] = $net;
2435
				$allstaticroutes[] = $temproute;
2436
				$allsubnets[] = $net;
2437
			}
2438
		} elseif (is_subnet($route['network'])) {
2439
			$allstaticroutes[] = $route;
2440
			$allsubnets[] = $route['network'];
2441
		}
2442
	}
2443
	if ($returnsubnetsonly) {
2444
		return $allsubnets;
2445
	} else {
2446
		return $allstaticroutes;
2447
	}
2448
}
2449

    
2450
/****f* util/get_alias_list
2451
 * NAME
2452
 *   get_alias_list - Provide a list of aliases.
2453
 * INPUTS
2454
 *   $type          - Optional, can be a string or array specifying what type(s) of aliases you need.
2455
 * RESULT
2456
 *   Array containing list of aliases.
2457
 *   If $type is unspecified, all aliases are returned.
2458
 *   If $type is a string, all aliases of the type specified in $type are returned.
2459
 *   If $type is an array, all aliases of any type specified in any element of $type are returned.
2460
 */
2461
function get_alias_list($type = null) {
2462
	global $config;
2463
	$result = array();
2464
	if ($config['aliases']['alias'] <> "" && is_array($config['aliases']['alias'])) {
2465
		foreach ($config['aliases']['alias'] as $alias) {
2466
			if ($type === null) {
2467
				$result[] = $alias['name'];
2468
			} else if (is_array($type)) {
2469
				if (in_array($alias['type'], $type)) {
2470
					$result[] = $alias['name'];
2471
				}
2472
			} else if ($type === $alias['type']) {
2473
				$result[] = $alias['name'];
2474
			}
2475
		}
2476
	}
2477
	return $result;
2478
}
2479

    
2480
/* returns an array consisting of every element of $haystack that is not equal to $needle. */
2481
function array_exclude($needle, $haystack) {
2482
	$result = array();
2483
	if (is_array($haystack)) {
2484
		foreach ($haystack as $thing) {
2485
			if ($needle !== $thing) {
2486
				$result[] = $thing;
2487
			}
2488
		}
2489
	}
2490
	return $result;
2491
}
2492

    
2493
/* Define what is preferred, IPv4 or IPv6 */
2494
function prefer_ipv4_or_ipv6() {
2495
	global $config;
2496

    
2497
	if (isset($config['system']['prefer_ipv4'])) {
2498
		mwexec("/etc/rc.d/ip6addrctl prefer_ipv4");
2499
	} else {
2500
		mwexec("/etc/rc.d/ip6addrctl prefer_ipv6");
2501
	}
2502
}
2503

    
2504
/* Redirect to page passing parameters via POST */
2505
function post_redirect($page, $params) {
2506
	if (!is_array($params)) {
2507
		return;
2508
	}
2509

    
2510
	print "<html><body><form action=\"{$page}\" name=\"formredir\" method=\"post\">\n";
2511
	foreach ($params as $key => $value) {
2512
		print "<input type=\"hidden\" name=\"{$key}\" value=\"{$value}\" />\n";
2513
	}
2514
	print "</form>\n";
2515
	print "<script type=\"text/javascript\">\n";
2516
	print "//<![CDATA[\n";
2517
	print "document.formredir.submit();\n";
2518
	print "//]]>\n";
2519
	print "</script>\n";
2520
	print "</body></html>\n";
2521
}
2522

    
2523
/* Locate disks that can be queried for S.M.A.R.T. data. */
2524
function get_smart_drive_list() {
2525
	$disk_list = explode(" ", get_single_sysctl("kern.disks"));
2526
	foreach ($disk_list as $id => $disk) {
2527
		// We only want certain kinds of disks for S.M.A.R.T.
2528
		// 1 is a match, 0 is no match, False is any problem processing the regex
2529
		if (preg_match("/^(ad|da|ada).*[0-9]{1,2}$/", $disk) !== 1) {
2530
			unset($disk_list[$id]);
2531
		}
2532
	}
2533
	sort($disk_list);
2534
	return $disk_list;
2535
}
2536

    
2537
// Validate a network address
2538
//	$addr: the address to validate
2539
//	$type: IPV4|IPV6|IPV4V6
2540
//	$label: the label used by the GUI to display this value. Required to compose an error message
2541
//	$err_msg: pointer to the callers error message array so that error messages can be added to it here
2542
//	$alias: are aliases permitted for this address?
2543
// Returns:
2544
//	IPV4 - if $addr is a valid IPv4 address
2545
//	IPV6 - if $addr is a valid IPv6 address
2546
//	ALIAS - if $alias=true and $addr is an alias
2547
//	false - otherwise
2548

    
2549
function validateipaddr(&$addr, $type, $label, &$err_msg, $alias=false) {
2550
	switch ($type) {
2551
		case IPV4:
2552
			if (is_ipaddrv4($addr)) {
2553
				return IPV4;
2554
			} else if ($alias) {
2555
				if (is_alias($addr)) {
2556
					return ALIAS;
2557
				} else {
2558
					$err_msg[] = sprintf(gettext("%s must be a valid IPv4 address or alias."), $label);
2559
					return false;
2560
				}
2561
			} else {
2562
				$err_msg[] = sprintf(gettext("%s must be a valid IPv4 address."), $label);
2563
				return false;
2564
			}
2565
		break;
2566
		case IPV6:
2567
			if (is_ipaddrv6($addr)) {
2568
				$addr = strtolower($addr);
2569
				return IPV6;
2570
			} else if ($alias) {
2571
				if (is_alias($addr)) {
2572
					return ALIAS;
2573
				} else {
2574
					$err_msg[] = sprintf(gettext("%s must be a valid IPv6 address or alias."), $label);
2575
					return false;
2576
				}
2577
			} else {
2578
				$err_msg[] = sprintf(gettext("%s must be a valid IPv6 address."), $label);
2579
				return false;
2580
			}
2581
		break;
2582
		case IPV4V6:
2583
			if (is_ipaddrv6($addr)) {
2584
				$addr = strtolower($addr);
2585
				return IPV6;
2586
			} else if (is_ipaddrv4($addr)) {
2587
				return IPV4;
2588
			} else if ($alias) {
2589
				if (is_alias($addr)) {
2590
					return ALIAS;
2591
				} else {
2592
					$err_msg[] = sprintf(gettext("%s must be a valid IPv4 or IPv6 address or alias."), $label);
2593
					return false;
2594
				}
2595
			} else {
2596
				$err_msg[] = sprintf(gettext("%s must be a valid IPv4 or IPv6 address."), $label);
2597
				return false;
2598
			}
2599
		break;
2600
	}
2601

    
2602
	return false;
2603
}
2604

    
2605
/* format a string to look (more) like the expected DUID format:
2606
 * 1) Replace any "-" with ":"
2607
 * 2) If the user inputs 14 components, then add the expected "0e:00:" to the front.
2608
 *    This is convenience, because the actual DUID (which is reported in logs) is the last 14 components.
2609
 * 3) If any components are input with just a single char (hex digit hopefully), put a "0" in front.
2610
 *
2611
 * The final result should be closer to:
2612
 *
2613
 * "0e:00:00:01:00:01:nn:nn:nn:nn:nn:nn:nn:nn:nn:nn"
2614
 *
2615
 * This function does not validate the input. is_duid() will do validation.
2616
*/
2617
function format_duid($dhcp6duid) {
2618
	$values = explode(":", strtolower(str_replace("-", ":", $dhcp6duid)));
2619
	if (count($values) == 14) {
2620
		array_unshift($values, "0e", "00");
2621
	}
2622

    
2623
	array_walk($values, function(&$value) {
2624
		$value = str_pad($value, 2, '0', STR_PAD_LEFT);
2625
	});
2626

    
2627
	return implode(":", $values);
2628
}
2629

    
2630
/* returns true if $dhcp6duid is a valid duid entry */
2631
function is_duid($dhcp6duid) {
2632
	$values = explode(":", $dhcp6duid);
2633
	if (count($values) != 16 || strlen($dhcp6duid) != 47) {
2634
		return false;
2635
	}
2636
	for ($i = 0; $i < 16; $i++) {
2637
		if (ctype_xdigit($values[$i]) == false)
2638
			return false;
2639
		if (hexdec($values[$i]) < 0 || hexdec($values[$i]) > 255)
2640
			return false;
2641
	}
2642
	return true;
2643
}
2644

    
2645
/* Write the DHCP6 DUID file */
2646
function write_dhcp6_duid($duidstring) {
2647
	// Create the hex array from the dhcp6duid config entry and write to file
2648
	global $g;
2649

    
2650
	if(!is_duid($duidstring)) {
2651
		log_error(gettext("Error: attempting to write DUID file - Invalid DUID detected"));
2652
		return false;
2653
	}
2654
	$temp = str_replace(":","",$duidstring);
2655
	$duid_binstring = pack("H*",$temp);
2656
	if ($fd = fopen("{$g['vardb_path']}/dhcp6c_duid", "wb")) {
2657
		fwrite($fd, $duid_binstring);
2658
		fclose($fd);
2659
		return true;
2660
	}
2661
	log_error(gettext("Error: attempting to write DUID file - File write error"));
2662
	return false;
2663
}
2664

    
2665
/* returns duid string from 'vardb_path']}/dhcp6c_duid' */
2666
function get_duid_from_file() {
2667
	global $g;
2668

    
2669
	$duid_ASCII = "";
2670
	$count = 0;
2671

    
2672
	if (file_exists("{$g['vardb_path']}/dhcp6c_duid") &&
2673
	    ($fd = fopen("{$g['vardb_path']}/dhcp6c_duid", "r"))) {
2674
		if(filesize("{$g['vardb_path']}/dhcp6c_duid")==16) {
2675
			$buffer = fread($fd,16);
2676
			while($count < 16) {
2677
				$duid_ASCII .= bin2hex($buffer[$count]);
2678
				$count++;
2679
				if($count < 16) {
2680
					$duid_ASCII .= ":";
2681
				}
2682
			}
2683
		}
2684
		fclose($fd);
2685
	}
2686
	//if no file or error with read then the string returns blanked DUID string
2687
	if(!is_duid($duid_ASCII)) {
2688
		return "--:--:--:--:--:--:--:--:--:--:--:--:--:--:--:--";
2689
	}
2690
	return($duid_ASCII);
2691
}
2692
?>
(43-43/51)