Project

General

Profile

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

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

    
30
/* kill a process by pid file */
31
function killbypid($pidfile) {
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
	/*
653
	 * While Net_IPv6::checkIPv6() considers IPv6/mask a valid IPv6,
654
	 * is_ipaddrv6() needs to be more strict to keep the compatibility
655
	 * with is_ipaddrv4().
656
	 */
657
	if (strstr($ipaddr, "/")) {
658
		return false;
659
	}
660
	if (strstr($ipaddr, "%") && is_linklocal($ipaddr)) {
661
		$tmpip = explode("%", $ipaddr);
662
		$ipaddr = $tmpip[0];
663
	}
664
	return Net_IPv6::checkIPv6($ipaddr);
665
}
666

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

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

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

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

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

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

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

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

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

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

    
758
}
759

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
938
	return $result;
939
}
940

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

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

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

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

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

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

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

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

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

    
1021
	return true;
1022
}
1023

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

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

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

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

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

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

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

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

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

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

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

    
1107
	return true;
1108
}
1109

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

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

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

    
1128
/* returns true if $port is a valid TCP/UDP port number or range ("<port>:<port>") */
1129
function is_port_or_range($port) {
1130
	return (is_port($port) || is_portrange($port));
1131
}
1132

    
1133
/* returns true if $port is an alias that is a port type */
1134
function is_portalias($port) {
1135
	global $config;
1136

    
1137
	if (is_alias($port)) {
1138
		if (is_array($config['aliases']['alias'])) {
1139
			foreach ($config['aliases']['alias'] as $alias) {
1140
				if ($alias['name'] == $port && preg_match("/port/i", $alias['type'])) {
1141
					return true;
1142
				}
1143
			}
1144
		}
1145
	}
1146
	return false;
1147
}
1148

    
1149
/* returns true if $port is a valid port number or an alias thereof */
1150
function is_port_or_alias($port) {
1151
	return (is_port($port) || is_portalias($port));
1152
}
1153

    
1154
/* returns true if $port is a valid TCP/UDP port number or range ("<port>:<port>") or an alias thereof */
1155
function is_port_or_range_or_alias($port) {
1156
	return (is_port($port) || is_portrange($port) || is_portalias($port));
1157
}
1158

    
1159
/* create ranges of sequential port numbers (200:215) and remove duplicates */
1160
function group_ports($ports, $kflc = false) {
1161
	if (!is_array($ports) || empty($ports)) {
1162
		return;
1163
	}
1164

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

    
1190
	$result = array();
1191
	foreach ($uniq as $idx => $port) {
1192
		if ($idx == 0) {
1193
			$result[] = $port;
1194
			continue;
1195
		}
1196

    
1197
		$last = end($result);
1198
		if (is_portrange($last)) {
1199
			list($begin, $end) = explode(":", $last);
1200
		} else {
1201
			$begin = $end = $last;
1202
		}
1203

    
1204
		if ($port == ($end+1)) {
1205
			$end++;
1206
			$result[count($result)-1] = "{$begin}:{$end}";
1207
		} else {
1208
			$result[] = $port;
1209
		}
1210
	}
1211

    
1212
	return array_merge($comments, $result);
1213
}
1214

    
1215
/* returns true if $val is a valid shaper bandwidth value */
1216
function is_valid_shaperbw($val) {
1217
	return (preg_match("/^(\d+(?:\.\d+)?)([MKG]?b|%)$/", $val));
1218
}
1219

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

    
1226
	if (ip2ulong($test) <= ip2ulong($end) &&
1227
	    ip2ulong($test) >= ip2ulong($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_v6($test, $start, $end) {
1236
	if (!is_ipaddrv6($test) || !is_ipaddrv6($start) || !is_ipaddrv6($end)) {
1237
		return false;
1238
	}
1239

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

    
1245
	return false;
1246
}
1247

    
1248
/* returns true if $test is in the range between $start and $end */
1249
function is_inrange($test, $start, $end) {
1250
	return is_ipaddrv6($test) ? is_inrange_v6($test, $start, $end) : is_inrange_v4($test, $start, $end);
1251
}
1252

    
1253
function get_configured_vip_list($family = 'all', $type = VIP_ALL) {
1254
	global $config;
1255

    
1256
	$list = array();
1257
	if (!is_array($config['virtualip']['vip']) || empty($config['virtualip']['vip'])) {
1258
		return ($list);
1259
	}
1260

    
1261
	$viparr = &$config['virtualip']['vip'];
1262
	foreach ($viparr as $vip) {
1263

    
1264
		if ($type == VIP_CARP) {
1265
			if ($vip['mode'] != "carp")
1266
				continue;
1267
		} elseif ($type == VIP_IPALIAS) {
1268
			if ($vip['mode'] != "ipalias")
1269
				continue;
1270
		} else {
1271
			if ($vip['mode'] != "carp" && $vip['mode'] != "ipalias")
1272
				continue;
1273
		}
1274

    
1275
		if ($family == 'all' ||
1276
		    ($family == 'inet' && is_ipaddrv4($vip['subnet'])) ||
1277
		    ($family == 'inet6' && is_ipaddrv6($vip['subnet']))) {
1278
			$list["_vip{$vip['uniqid']}"] = $vip['subnet'];
1279
		}
1280
	}
1281
	return ($list);
1282
}
1283

    
1284
function get_configured_vip($vipinterface = '') {
1285

    
1286
	return (get_configured_vip_detail($vipinterface, 'all', 'vip'));
1287
}
1288

    
1289
function get_configured_vip_interface($vipinterface = '') {
1290

    
1291
	return (get_configured_vip_detail($vipinterface, 'all', 'iface'));
1292
}
1293

    
1294
function get_configured_vip_ipv4($vipinterface = '') {
1295

    
1296
	return (get_configured_vip_detail($vipinterface, 'inet', 'ip'));
1297
}
1298

    
1299
function get_configured_vip_ipv6($vipinterface = '') {
1300

    
1301
	return (get_configured_vip_detail($vipinterface, 'inet6', 'ip'));
1302
}
1303

    
1304
function get_configured_vip_subnetv4($vipinterface = '') {
1305

    
1306
	return (get_configured_vip_detail($vipinterface, 'inet', 'subnet'));
1307
}
1308

    
1309
function get_configured_vip_subnetv6($vipinterface = '') {
1310

    
1311
	return (get_configured_vip_detail($vipinterface, 'inet6', 'subnet'));
1312
}
1313

    
1314
function get_configured_vip_detail($vipinterface = '', $family = 'inet', $what = 'ip') {
1315
	global $config;
1316

    
1317
	if (empty($vipinterface) || !is_array($config['virtualip']['vip']) ||
1318
	    empty($config['virtualip']['vip'])) {
1319
		return (NULL);
1320
	}
1321

    
1322
	$viparr = &$config['virtualip']['vip'];
1323
	foreach ($viparr as $vip) {
1324
		if ($vip['mode'] != "carp" && $vip['mode'] != "ipalias") {
1325
			continue;
1326
		}
1327

    
1328
		if ($vipinterface != "_vip{$vip['uniqid']}") {
1329
			continue;
1330
		}
1331

    
1332
		switch ($what) {
1333
			case 'subnet':
1334
				if ($family == 'inet' && is_ipaddrv4($vip['subnet']))
1335
					return ($vip['subnet_bits']);
1336
				else if ($family == 'inet6' && is_ipaddrv6($vip['subnet']))
1337
					return ($vip['subnet_bits']);
1338
				break;
1339
			case 'iface':
1340
				return ($vip['interface']);
1341
				break;
1342
			case 'vip':
1343
				return ($vip);
1344
				break;
1345
			case 'ip':
1346
			default:
1347
				if ($family == 'inet' && is_ipaddrv4($vip['subnet'])) {
1348
					return ($vip['subnet']);
1349
				} else if ($family == 'inet6' && is_ipaddrv6($vip['subnet'])) {
1350
					return ($vip['subnet']);
1351
				}
1352
				break;
1353
		}
1354
		break;
1355
	}
1356

    
1357
	return (NULL);
1358
}
1359

    
1360
/* comparison function for sorting by the order in which interfaces are normally created */
1361
function compare_interface_friendly_names($a, $b) {
1362
	if ($a == $b) {
1363
		return 0;
1364
	} else if ($a == 'wan') {
1365
		return -1;
1366
	} else if ($b == 'wan') {
1367
		return 1;
1368
	} else if ($a == 'lan') {
1369
		return -1;
1370
	} else if ($b == 'lan') {
1371
		return 1;
1372
	}
1373

    
1374
	return strnatcmp($a, $b);
1375
}
1376

    
1377
/* return the configured interfaces list. */
1378
function get_configured_interface_list($withdisabled = false) {
1379
	global $config;
1380

    
1381
	$iflist = array();
1382

    
1383
	/* if list */
1384
	foreach ($config['interfaces'] as $if => $ifdetail) {
1385
		if (isset($ifdetail['enable']) || $withdisabled == true) {
1386
			$iflist[$if] = $if;
1387
		}
1388
	}
1389

    
1390
	return $iflist;
1391
}
1392

    
1393
/* return the configured interfaces list. */
1394
function get_configured_interface_list_by_realif($withdisabled = false) {
1395
	global $config;
1396

    
1397
	$iflist = array();
1398

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

    
1409
	return $iflist;
1410
}
1411

    
1412
/* return the configured interfaces list with their description. */
1413
function get_configured_interface_with_descr($withdisabled = false) {
1414
	global $config, $user_settings;
1415

    
1416
	$iflist = array();
1417

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

    
1429
	if ($user_settings['webgui']['interfacessort']) {
1430
		asort($iflist);
1431
	}
1432

    
1433
	return $iflist;
1434
}
1435

    
1436
/*
1437
 *   get_configured_ip_addresses() - Return a list of all configured
1438
 *   IPv4 addresses.
1439
 *
1440
 */
1441
function get_configured_ip_addresses() {
1442
	global $config;
1443

    
1444
	if (!function_exists('get_interface_ip')) {
1445
		require_once("interfaces.inc");
1446
	}
1447
	$ip_array = array();
1448
	$interfaces = get_configured_interface_list();
1449
	if (is_array($interfaces)) {
1450
		foreach ($interfaces as $int) {
1451
			$ipaddr = get_interface_ip($int);
1452
			$ip_array[$int] = $ipaddr;
1453
		}
1454
	}
1455
	$interfaces = get_configured_vip_list('inet');
1456
	if (is_array($interfaces)) {
1457
		foreach ($interfaces as $int => $ipaddr) {
1458
			$ip_array[$int] = $ipaddr;
1459
		}
1460
	}
1461

    
1462
	/* pppoe server */
1463
	if (is_array($config['pppoes']) && is_array($config['pppoes']['pppoe'])) {
1464
		foreach ($config['pppoes']['pppoe'] as $pppoe) {
1465
			if ($pppoe['mode'] == "server") {
1466
				if (is_ipaddr($pppoe['localip'])) {
1467
					$int = "pppoes". $pppoe['pppoeid'];
1468
					$ip_array[$int] = $pppoe['localip'];
1469
				}
1470
			}
1471
		}
1472
	}
1473

    
1474
	return $ip_array;
1475
}
1476

    
1477
/*
1478
 *   get_configured_ipv6_addresses() - Return a list of all configured
1479
 *   IPv6 addresses.
1480
 *
1481
 */
1482
function get_configured_ipv6_addresses($linklocal_fallback = false) {
1483
	require_once("interfaces.inc");
1484
	$ipv6_array = array();
1485
	$interfaces = get_configured_interface_list();
1486
	if (is_array($interfaces)) {
1487
		foreach ($interfaces as $int) {
1488
			$ipaddrv6 = text_to_compressed_ip6(get_interface_ipv6($int, false, $linklocal_fallback));
1489
			$ipv6_array[$int] = $ipaddrv6;
1490
		}
1491
	}
1492
	$interfaces = get_configured_vip_list('inet6');
1493
	if (is_array($interfaces)) {
1494
		foreach ($interfaces as $int => $ipaddrv6) {
1495
			$ipv6_array[$int] = text_to_compressed_ip6($ipaddrv6);
1496
		}
1497
	}
1498
	return $ipv6_array;
1499
}
1500

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

    
1606
			case "friendly":
1607
				if ($friendly != "") {
1608
					$toput['if'] = $ifname;
1609
					$iflist[$friendly] = $toput;
1610
				}
1611
				break;
1612
			}
1613
		}
1614
	}
1615
	return $iflist;
1616
}
1617

    
1618
function get_lagg_interface_list() {
1619
	global $config;
1620

    
1621
	$plist = array();
1622
	if (isset($config['laggs']['lagg']) && is_array($config['laggs']['lagg'])) {
1623
		foreach ($config['laggs']['lagg'] as $lagg) {
1624
			$lagg['mac'] = get_interface_mac($lagg['laggif']);
1625
			$lagg['islagg'] = true;
1626
			$plist[$lagg['laggif']] = $lagg;
1627
		}
1628
	}
1629

    
1630
	return ($plist);
1631
}
1632

    
1633
/****f* util/log_error
1634
* NAME
1635
*   log_error  - Sends a string to syslog.
1636
* INPUTS
1637
*   $error     - string containing the syslog message.
1638
* RESULT
1639
*   null
1640
******/
1641
function log_error($error) {
1642
	global $g;
1643
	$page = $_SERVER['SCRIPT_NAME'];
1644
	if (empty($page)) {
1645
		$files = get_included_files();
1646
		$page = basename($files[0]);
1647
	}
1648
	syslog(LOG_ERR, "$page: $error");
1649
	if ($g['debug']) {
1650
		syslog(LOG_WARNING, var_dump(debug_backtrace()));
1651
	}
1652
	return;
1653
}
1654

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

    
1673
/****f* util/exec_command
1674
 * NAME
1675
 *   exec_command - Execute a command and return a string of the result.
1676
 * INPUTS
1677
 *   $command   - String of the command to be executed.
1678
 * RESULT
1679
 *   String containing the command's result.
1680
 * NOTES
1681
 *   This function returns the command's stdout and stderr.
1682
 ******/
1683
function exec_command($command) {
1684
	$output = array();
1685
	exec($command . ' 2>&1', $output);
1686
	return(implode("\n", $output));
1687
}
1688

    
1689
/* wrapper for exec()
1690
   Executes in background or foreground.
1691
   For background execution, returns PID of background process to allow calling code control */
1692
function mwexec($command, $nologentry = false, $clearsigmask = false, $background = false) {
1693
	global $g;
1694
	$retval = 0;
1695

    
1696
	if ($g['debug']) {
1697
		if (!$_SERVER['REMOTE_ADDR']) {
1698
			echo "mwexec(): $command" . ($background ? " [BG]":"") . "\n";
1699
		}
1700
	}
1701
	if ($clearsigmask) {
1702
		$oldset = array();
1703
		pcntl_sigprocmask(SIG_SETMASK, array(), $oldset);
1704
	}
1705

    
1706
	if ($background) {
1707
		// start background process and return PID
1708
		$retval = exec("/usr/bin/nohup $command > /dev/null 2>&1 & echo $!");
1709
	} else {
1710
		// run in foreground, and (optionally) log if nonzero return
1711
		$outputarray = array();
1712
		exec("$command 2>&1", $outputarray, $retval);
1713
		if (($retval <> 0) && (!$nologentry || isset($config['system']['developerspew']))) {
1714
			log_error(sprintf(gettext("The command '%1\$s' returned exit code '%2\$d', the output was '%3\$s' "), $command, $retval, implode(" ", $outputarray)));
1715
		}
1716
	}
1717

    
1718
	if ($clearsigmask) {
1719
		pcntl_sigprocmask(SIG_SETMASK, $oldset);
1720
	}
1721

    
1722
	return $retval;
1723
}
1724

    
1725
/* wrapper for exec() in background */
1726
function mwexec_bg($command, $clearsigmask = false) {
1727
	return mwexec($command, false, $clearsigmask, true);
1728
}
1729

    
1730
/*	unlink a file, or pattern-match of a file, if it exists
1731
	if the file/path contains glob() compatible wildcards, all matching files will be unlinked
1732
	any warning/errors are suppressed (e.g. no matching files to delete)
1733
	If there are matching file(s) and they were all unlinked OK, then return true.
1734
	Otherwise return false (the requested file(s) did not exist, or could not be deleted)
1735
	This allows the caller to know if they were the one to successfully delete the file(s).
1736
*/
1737
function unlink_if_exists($fn) {
1738
	$to_do = glob($fn);
1739
	if (is_array($to_do) && count($to_do) > 0) {
1740
		// Returns an array of true/false indicating if each unlink worked
1741
		$results = @array_map("unlink", $to_do);
1742
		// If there is no false in the array, then all went well
1743
		$result = !in_array(false, $results, true);
1744
	} else {
1745
		$result = @unlink($fn);
1746
	}
1747
	return $result;
1748
}
1749
/* make a global alias table (for faster lookups) */
1750
function alias_make_table($config) {
1751
	global $aliastable;
1752

    
1753
	$aliastable = array();
1754

    
1755
	if (is_array($config['aliases']['alias'])) {
1756
		foreach ($config['aliases']['alias'] as $alias) {
1757
			if ($alias['name']) {
1758
				$aliastable[$alias['name']] = $alias['address'];
1759
			}
1760
		}
1761
	}
1762
}
1763

    
1764
/* check if an alias exists */
1765
function is_alias($name) {
1766
	global $aliastable;
1767

    
1768
	return isset($aliastable[$name]);
1769
}
1770

    
1771
function alias_get_type($name) {
1772
	global $config;
1773

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

    
1782
	return "";
1783
}
1784

    
1785
/* expand a host or network alias, if necessary */
1786
function alias_expand($name) {
1787
	global $config, $aliastable;
1788
	$urltable_prefix = "/var/db/aliastables/";
1789
	$urltable_filename = $urltable_prefix . $name . ".txt";
1790

    
1791
	if (isset($aliastable[$name])) {
1792
		// alias names cannot be strictly numeric. redmine #4289
1793
		if (is_numericint($name)) {
1794
			return null;
1795
		}
1796
		// make sure if it's a ports alias, it actually exists. redmine #5845
1797
		foreach ($config['aliases']['alias'] as $alias) {
1798
			if ($alias['name'] == $name) {
1799
				if ($alias['type'] == "urltable_ports") {
1800
					if (is_URL($alias['url']) && file_exists($urltable_filename) && filesize($urltable_filename)) {
1801
						return "\${$name}";
1802
					} else {
1803
						return null;
1804
					}
1805
				}
1806
			}
1807
		}
1808
		return "\${$name}";
1809
	} else if (is_ipaddr($name) || is_subnet($name) || is_port_or_range($name)) {
1810
		return "{$name}";
1811
	} else {
1812
		return null;
1813
	}
1814
}
1815

    
1816
function alias_expand_urltable($name) {
1817
	global $config;
1818
	$urltable_prefix = "/var/db/aliastables/";
1819
	$urltable_filename = $urltable_prefix . $name . ".txt";
1820

    
1821
	if (is_array($config['aliases']['alias'])) {
1822
		foreach ($config['aliases']['alias'] as $alias) {
1823
			if (preg_match("/urltable/i", $alias['type']) && ($alias['name'] == $name)) {
1824
				if (is_URL($alias["url"]) && file_exists($urltable_filename)) {
1825
					if (!filesize($urltable_filename)) {
1826
						// file exists, but is empty, try to sync
1827
						send_event("service sync alias {$name}");
1828
					}
1829
					return $urltable_filename;
1830
				} else {
1831
					send_event("service sync alias {$name}");
1832
					break;
1833
				}
1834
			}
1835
		}
1836
	}
1837
	return null;
1838
}
1839

    
1840
/* obtain MAC address given an IP address by looking at the ARP/NDP table */
1841
function arp_get_mac_by_ip($ip, $do_ping = true) {
1842
	unset($macaddr);
1843
	$retval = 1;
1844
	switch (is_ipaddr($ip)) {
1845
		case 4:
1846
			if ($do_ping === true) {
1847
				mwexec("/sbin/ping -c 1 -t 1 " . escapeshellarg($ip), true);
1848
			}
1849
			$macaddr = exec("/usr/sbin/arp -n " . escapeshellarg($ip) . " | /usr/bin/awk '{print $4}'", $output, $retval);
1850
			break;
1851
		case 6:
1852
			if ($do_ping === true) {
1853
				mwexec("/sbin/ping6 -c 1 -X 1 " . escapeshellarg($ip), true);
1854
			}
1855
			$macaddr = exec("/usr/sbin/ndp -n " . escapeshellarg($ip) . " | /usr/bin/awk '{print $2}'", $output, $retval);
1856
			break;
1857
	}
1858
	if ($retval == 0 && is_macaddr($macaddr)) {
1859
		return $macaddr;
1860
	} else {
1861
		return false;
1862
	}
1863
}
1864

    
1865
/* return a fieldname that is safe for xml usage */
1866
function xml_safe_fieldname($fieldname) {
1867
	$replace = array(
1868
	    '/', '-', ' ', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')',
1869
	    '_', '+', '=', '{', '}', '[', ']', '|', '/', '<', '>', '?',
1870
	    ':', ',', '.', '\'', '\\'
1871
	);
1872
	return strtolower(str_replace($replace, "", $fieldname));
1873
}
1874

    
1875
function mac_format($clientmac) {
1876
	global $config, $cpzone;
1877

    
1878
	$mac = explode(":", $clientmac);
1879
	$mac_format = $cpzone ? $config['captiveportal'][$cpzone]['radmac_format'] : false;
1880

    
1881
	switch ($mac_format) {
1882
		case 'singledash':
1883
			return "$mac[0]$mac[1]$mac[2]-$mac[3]$mac[4]$mac[5]";
1884

    
1885
		case 'ietf':
1886
			return "$mac[0]-$mac[1]-$mac[2]-$mac[3]-$mac[4]-$mac[5]";
1887

    
1888
		case 'cisco':
1889
			return "$mac[0]$mac[1].$mac[2]$mac[3].$mac[4]$mac[5]";
1890

    
1891
		case 'unformatted':
1892
			return "$mac[0]$mac[1]$mac[2]$mac[3]$mac[4]$mac[5]";
1893

    
1894
		default:
1895
			return $clientmac;
1896
	}
1897
}
1898

    
1899
function resolve_retry($hostname, $retries = 5) {
1900

    
1901
	if (is_ipaddr($hostname)) {
1902
		return $hostname;
1903
	}
1904

    
1905
	for ($i = 0; $i < $retries; $i++) {
1906
		// FIXME: gethostbyname does not work for AAAA hostnames, boo, hiss
1907
		$ip = gethostbyname($hostname);
1908

    
1909
		if ($ip && $ip != $hostname) {
1910
			/* success */
1911
			return $ip;
1912
		}
1913

    
1914
		sleep(1);
1915
	}
1916

    
1917
	return false;
1918
}
1919

    
1920
function format_bytes($bytes) {
1921
	if ($bytes >= 1099511627776) {
1922
		return sprintf("%.2f TiB", $bytes/1099511627776);
1923
	} else if ($bytes >= 1073741824) {
1924
		return sprintf("%.2f GiB", $bytes/1073741824);
1925
	} else if ($bytes >= 1048576) {
1926
		return sprintf("%.2f MiB", $bytes/1048576);
1927
	} else if ($bytes >= 1024) {
1928
		return sprintf("%.0f KiB", $bytes/1024);
1929
	} else {
1930
		return sprintf("%d B", $bytes);
1931
	}
1932
}
1933

    
1934
function format_number($num, $precision = 3) {
1935
	$units = array('', 'K', 'M', 'G', 'T');
1936

    
1937
	$i = 0;
1938
	while ($num > 1000 && $i < count($units)) {
1939
		$num /= 1000;
1940
		$i++;
1941
	}
1942
	$num = round($num, $precision);
1943

    
1944
	return ("$num {$units[$i]}");
1945
}
1946

    
1947
function update_filter_reload_status($text, $new=false) {
1948
	global $g;
1949

    
1950
	if ($new) {
1951
		file_put_contents("{$g['varrun_path']}/filter_reload_status", $text  . PHP_EOL);
1952
	} else {
1953
		file_put_contents("{$g['varrun_path']}/filter_reload_status", $text  . PHP_EOL, FILE_APPEND);
1954
	}
1955
}
1956

    
1957
/****** util/return_dir_as_array
1958
 * NAME
1959
 *   return_dir_as_array - Return a directory's contents as an array.
1960
 * INPUTS
1961
 *   $dir          - string containing the path to the desired directory.
1962
 *   $filter_regex - string containing a regular expression to filter file names. Default empty.
1963
 * RESULT
1964
 *   $dir_array - array containing the directory's contents. This array will be empty if the path specified is invalid.
1965
 ******/
1966
function return_dir_as_array($dir, $filter_regex = '') {
1967
	$dir_array = array();
1968
	if (is_dir($dir)) {
1969
		if ($dh = opendir($dir)) {
1970
			while (($file = readdir($dh)) !== false) {
1971
				if (($file == ".") || ($file == "..")) {
1972
					continue;
1973
				}
1974

    
1975
				if (empty($filter_regex) || preg_match($filter_regex, $file)) {
1976
					array_push($dir_array, $file);
1977
				}
1978
			}
1979
			closedir($dh);
1980
		}
1981
	}
1982
	return $dir_array;
1983
}
1984

    
1985
function run_plugins($directory) {
1986
	global $config, $g;
1987

    
1988
	/* process packager manager custom rules */
1989
	$files = return_dir_as_array($directory);
1990
	if (is_array($files)) {
1991
		foreach ($files as $file) {
1992
			if (stristr($file, ".sh") == true) {
1993
				mwexec($directory . $file . " start");
1994
			} else if (!is_dir($directory . "/" . $file) && stristr($file, ".inc")) {
1995
				require_once($directory . "/" . $file);
1996
			}
1997
		}
1998
	}
1999
}
2000

    
2001
/*
2002
 *    safe_mkdir($path, $mode = 0755)
2003
 *    create directory if it doesn't already exist and isn't a file!
2004
 */
2005
function safe_mkdir($path, $mode = 0755) {
2006
	global $g;
2007

    
2008
	if (!is_file($path) && !is_dir($path)) {
2009
		return @mkdir($path, $mode, true);
2010
	} else {
2011
		return false;
2012
	}
2013
}
2014

    
2015
/*
2016
 * get_sysctl($names)
2017
 * Get values of sysctl OID's listed in $names (accepts an array or a single
2018
 * name) and return an array of key/value pairs set for those that exist
2019
 */
2020
function get_sysctl($names) {
2021
	if (empty($names)) {
2022
		return array();
2023
	}
2024

    
2025
	if (is_array($names)) {
2026
		$name_list = array();
2027
		foreach ($names as $name) {
2028
			$name_list[] = escapeshellarg($name);
2029
		}
2030
	} else {
2031
		$name_list = array(escapeshellarg($names));
2032
	}
2033

    
2034
	exec("/sbin/sysctl -iq " . implode(" ", $name_list), $output);
2035
	$values = array();
2036
	foreach ($output as $line) {
2037
		$line = explode(": ", $line, 2);
2038
		if (count($line) == 2) {
2039
			$values[$line[0]] = $line[1];
2040
		}
2041
	}
2042

    
2043
	return $values;
2044
}
2045

    
2046
/*
2047
 * get_single_sysctl($name)
2048
 * Wrapper for get_sysctl() to simplify read of a single sysctl value
2049
 * return the value for sysctl $name or empty string if it doesn't exist
2050
 */
2051
function get_single_sysctl($name) {
2052
	if (empty($name)) {
2053
		return "";
2054
	}
2055

    
2056
	$value = get_sysctl($name);
2057
	if (empty($value) || !isset($value[$name])) {
2058
		return "";
2059
	}
2060

    
2061
	return $value[$name];
2062
}
2063

    
2064
/*
2065
 * set_sysctl($value_list)
2066
 * Set sysctl OID's listed as key/value pairs and return
2067
 * an array with keys set for those that succeeded
2068
 */
2069
function set_sysctl($values) {
2070
	if (empty($values)) {
2071
		return array();
2072
	}
2073

    
2074
	$value_list = array();
2075
	foreach ($values as $key => $value) {
2076
		$value_list[] = escapeshellarg($key) . "=" . escapeshellarg($value);
2077
	}
2078

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

    
2081
	/* Retry individually if failed (one or more read-only) */
2082
	if ($success <> 0 && count($value_list) > 1) {
2083
		foreach ($value_list as $value) {
2084
			exec("/sbin/sysctl -iq " . $value, $output);
2085
		}
2086
	}
2087

    
2088
	$ret = array();
2089
	foreach ($output as $line) {
2090
		$line = explode(": ", $line, 2);
2091
		if (count($line) == 2) {
2092
			$ret[$line[0]] = true;
2093
		}
2094
	}
2095

    
2096
	return $ret;
2097
}
2098

    
2099
/*
2100
 * set_single_sysctl($name, $value)
2101
 * Wrapper to set_sysctl() to make it simple to set only one sysctl
2102
 * returns boolean meaning if it succeeded
2103
 */
2104
function set_single_sysctl($name, $value) {
2105
	if (empty($name)) {
2106
		return false;
2107
	}
2108

    
2109
	$result = set_sysctl(array($name => $value));
2110

    
2111
	if (!isset($result[$name]) || $result[$name] != $value) {
2112
		return false;
2113
	}
2114

    
2115
	return true;
2116
}
2117

    
2118
/*
2119
 *     get_memory()
2120
 *     returns an array listing the amount of
2121
 *     memory installed in the hardware
2122
 *     [0] net memory available for the OS (FreeBSD) after some is taken by BIOS, video or whatever - e.g. 235 MBytes
2123
 *     [1] real (actual) memory of the system, should be the size of the RAM card/s - e.g. 256 MBytes
2124
 */
2125
function get_memory() {
2126
	$physmem = get_single_sysctl("hw.physmem");
2127
	$realmem = get_single_sysctl("hw.realmem");
2128
	/* convert from bytes to megabytes */
2129
	return array(($physmem/1048576), ($realmem/1048576));
2130
}
2131

    
2132
function mute_kernel_msgs() {
2133
	global $g, $config;
2134

    
2135
	if ($config['system']['enableserial']) {
2136
		return;
2137
	}
2138
	exec("/sbin/conscontrol mute on");
2139
}
2140

    
2141
function unmute_kernel_msgs() {
2142
	global $g;
2143

    
2144
	exec("/sbin/conscontrol mute off");
2145
}
2146

    
2147
function start_devd() {
2148
	global $g;
2149

    
2150
	/* Use the undocumented -q options of devd to quiet its log spamming */
2151
	$_gb = exec("/sbin/devd -q -f /etc/{$g['product_name']}-devd.conf");
2152
	sleep(1);
2153
	unset($_gb);
2154
}
2155

    
2156
function is_interface_vlan_mismatch() {
2157
	global $config, $g;
2158

    
2159
	if (is_array($config['vlans']['vlan'])) {
2160
		foreach ($config['vlans']['vlan'] as $vlan) {
2161
			if (substr($vlan['if'], 0, 4) == "lagg") {
2162
				return false;
2163
			}
2164
			if (does_interface_exist($vlan['if']) == false) {
2165
				return true;
2166
			}
2167
		}
2168
	}
2169

    
2170
	return false;
2171
}
2172

    
2173
function is_interface_mismatch() {
2174
	global $config, $g;
2175

    
2176
	$do_assign = false;
2177
	$i = 0;
2178
	$missing_interfaces = array();
2179
	if (is_array($config['interfaces'])) {
2180
		foreach ($config['interfaces'] as $ifname => $ifcfg) {
2181
			if (interface_is_vlan($ifcfg['if']) != NULL ||
2182
			    interface_is_qinq($ifcfg['if']) != NULL ||
2183
			    preg_match("/^enc|^cua|^tun|^tap|^l2tp|^pptp|^ppp|^ovpn|^ipsec|^gif|^gre|^lagg|^bridge|vlan|_wlan|_\d{0,4}_\d{0,4}$/i", $ifcfg['if'])) {
2184
				// Do not check these interfaces.
2185
				$i++;
2186
				continue;
2187
			} else if (does_interface_exist($ifcfg['if']) == false) {
2188
				$missing_interfaces[] = $ifcfg['if'];
2189
				$do_assign = true;
2190
			} else {
2191
				$i++;
2192
			}
2193
		}
2194
	}
2195

    
2196
	if (file_exists("{$g['tmp_path']}/assign_complete")) {
2197
		$do_assign = false;
2198
	}
2199

    
2200
	if (!empty($missing_interfaces) && $do_assign) {
2201
		file_put_contents("{$g['tmp_path']}/missing_interfaces", implode(' ', $missing_interfaces));
2202
	} else {
2203
		@unlink("{$g['tmp_path']}/missing_interfaces");
2204
	}
2205

    
2206
	return $do_assign;
2207
}
2208

    
2209
/* sync carp entries to other firewalls */
2210
function carp_sync_client() {
2211
	global $g;
2212
	send_event("filter sync");
2213
}
2214

    
2215
/****f* util/isAjax
2216
 * NAME
2217
 *   isAjax - reports if the request is driven from prototype
2218
 * INPUTS
2219
 *   none
2220
 * RESULT
2221
 *   true/false
2222
 ******/
2223
function isAjax() {
2224
	return isset ($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest';
2225
}
2226

    
2227
/****f* util/timeout
2228
 * NAME
2229
 *   timeout - console input with timeout countdown. Note: erases 2 char of screen for timer. Leave space.
2230
 * INPUTS
2231
 *   optional, seconds to wait before timeout. Default 9 seconds.
2232
 * RESULT
2233
 *   returns 1 char of user input or null if no input.
2234
 ******/
2235
function timeout($timer = 9) {
2236
	while (!isset($key)) {
2237
		if ($timer >= 9) {
2238
			echo chr(8) . chr(8) . ($timer == 9 ? chr(32) : null) . "{$timer}";
2239
		} else {
2240
			echo chr(8). "{$timer}";
2241
		}
2242
		`/bin/stty -icanon min 0 time 25`;
2243
		$key = trim(`KEY=\`dd count=1 2>/dev/null\`; echo \$KEY`);
2244
		`/bin/stty icanon`;
2245
		if ($key == '') {
2246
			unset($key);
2247
		}
2248
		$timer--;
2249
		if ($timer == 0) {
2250
			break;
2251
		}
2252
	}
2253
	return $key;
2254
}
2255

    
2256
/****f* util/msort
2257
 * NAME
2258
 *   msort - sort array
2259
 * INPUTS
2260
 *   $array to be sorted, field to sort by, direction of sort
2261
 * RESULT
2262
 *   returns newly sorted array
2263
 ******/
2264
function msort($array, $id = "id", $sort_ascending = true) {
2265
	$temp_array = array();
2266
	if (!is_array($array)) {
2267
		return $temp_array;
2268
	}
2269
	while (count($array)>0) {
2270
		$lowest_id = 0;
2271
		$index = 0;
2272
		foreach ($array as $item) {
2273
			if (isset($item[$id])) {
2274
				if ($array[$lowest_id][$id]) {
2275
					if (strtolower($item[$id]) < strtolower($array[$lowest_id][$id])) {
2276
						$lowest_id = $index;
2277
					}
2278
				}
2279
			}
2280
			$index++;
2281
		}
2282
		$temp_array[] = $array[$lowest_id];
2283
		$array = array_merge(array_slice($array, 0, $lowest_id), array_slice($array, $lowest_id + 1));
2284
	}
2285
	if ($sort_ascending) {
2286
		return $temp_array;
2287
	} else {
2288
		return array_reverse($temp_array);
2289
	}
2290
}
2291

    
2292
/****f* util/is_URL
2293
 * NAME
2294
 *   is_URL
2295
 * INPUTS
2296
 *   string to check
2297
 * RESULT
2298
 *   Returns true if item is a URL
2299
 ******/
2300
function is_URL($url) {
2301
	$match = preg_match("'\b(([\w-]+://?|www[.])[^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|/)))'", $url);
2302
	if ($match) {
2303
		return true;
2304
	}
2305
	return false;
2306
}
2307

    
2308
function is_file_included($file = "") {
2309
	$files = get_included_files();
2310
	if (in_array($file, $files)) {
2311
		return true;
2312
	}
2313

    
2314
	return false;
2315
}
2316

    
2317
/*
2318
 * Replace a value on a deep associative array using regex
2319
 */
2320
function array_replace_values_recursive($data, $match, $replace) {
2321
	if (empty($data)) {
2322
		return $data;
2323
	}
2324

    
2325
	if (is_string($data)) {
2326
		$data = preg_replace("/{$match}/", $replace, $data);
2327
	} else if (is_array($data)) {
2328
		foreach ($data as $k => $v) {
2329
			$data[$k] = array_replace_values_recursive($v, $match, $replace);
2330
		}
2331
	}
2332

    
2333
	return $data;
2334
}
2335

    
2336
/*
2337
	This function was borrowed from a comment on PHP.net at the following URL:
2338
	http://www.php.net/manual/en/function.array-merge-recursive.php#73843
2339
 */
2340
function array_merge_recursive_unique($array0, $array1) {
2341

    
2342
	$arrays = func_get_args();
2343
	$remains = $arrays;
2344

    
2345
	// We walk through each arrays and put value in the results (without
2346
	// considering previous value).
2347
	$result = array();
2348

    
2349
	// loop available array
2350
	foreach ($arrays as $array) {
2351

    
2352
		// The first remaining array is $array. We are processing it. So
2353
		// we remove it from remaining arrays.
2354
		array_shift($remains);
2355

    
2356
		// We don't care non array param, like array_merge since PHP 5.0.
2357
		if (is_array($array)) {
2358
			// Loop values
2359
			foreach ($array as $key => $value) {
2360
				if (is_array($value)) {
2361
					// we gather all remaining arrays that have such key available
2362
					$args = array();
2363
					foreach ($remains as $remain) {
2364
						if (array_key_exists($key, $remain)) {
2365
							array_push($args, $remain[$key]);
2366
						}
2367
					}
2368

    
2369
					if (count($args) > 2) {
2370
						// put the recursion
2371
						$result[$key] = call_user_func_array(__FUNCTION__, $args);
2372
					} else {
2373
						foreach ($value as $vkey => $vval) {
2374
							$result[$key][$vkey] = $vval;
2375
						}
2376
					}
2377
				} else {
2378
					// simply put the value
2379
					$result[$key] = $value;
2380
				}
2381
			}
2382
		}
2383
	}
2384
	return $result;
2385
}
2386

    
2387

    
2388
/*
2389
 * converts a string like "a,b,c,d"
2390
 * into an array like array("a" => "b", "c" => "d")
2391
 */
2392
function explode_assoc($delimiter, $string) {
2393
	$array = explode($delimiter, $string);
2394
	$result = array();
2395
	$numkeys = floor(count($array) / 2);
2396
	for ($i = 0; $i < $numkeys; $i += 1) {
2397
		$result[$array[$i * 2]] = $array[$i * 2 + 1];
2398
	}
2399
	return $result;
2400
}
2401

    
2402
/*
2403
 * Given a string of text with some delimiter, look for occurrences
2404
 * of some string and replace all of those.
2405
 * $text - the text string (e.g. "abc,defg,x123,ipv4,xyz")
2406
 * $delimiter - the delimiter (e.g. ",")
2407
 * $element - the element to match (e.g. "defg")
2408
 * $replacement - the string to replace it with (e.g. "42")
2409
 * Returns the resulting delimited string (e.g. "abc,42,x123,ipv4,xyz")
2410
 */
2411
function replace_element_in_list($text, $delimiter, $element, $replacement) {
2412
	$textArray = explode($delimiter, $text);
2413
	while (($entry = array_search($element, $textArray)) !== false) {
2414
		$textArray[$entry] = $replacement;
2415
	}
2416
	return implode(',', $textArray);
2417
}
2418

    
2419
/* Try to change a static route, if it doesn't exist, add it */
2420
function route_add_or_change($args) {
2421
	global $config;
2422

    
2423
	if (empty($args)) {
2424
		return false;
2425
	}
2426

    
2427
	/* First, try to add it */
2428
	$_gb = exec(escapeshellcmd("/sbin/route add " . $args), $output, $rc);
2429
		
2430
	if (isset($config['system']['route-debug'])) {
2431
		$add_change = 'add';
2432
		$mt = microtime();
2433
		log_error("ROUTING debug: $mt - ADD RC={$rc} - $args");
2434
	}
2435

    
2436
	if ($rc != 0) {
2437
		/* If it fails, try to change it */
2438
		$_gb = exec(escapeshellcmd("/sbin/route change " . $args),
2439
		    $output, $rc);
2440

    
2441
		if (isset($config['system']['route-debug'])) {
2442
			$add_change = 'change';
2443
			$mt = microtime();
2444
			log_error("ROUTING debug: $mt - CHG RC={$rc} - $args");
2445
		}
2446
	}
2447
	if (isset($config['system']['route-debug'])) {
2448
		file_put_contents("/dev/console", "\n[".getmypid()."] ROUTE: {$add_change} {$args} result: {$rc}");
2449
	}
2450

    
2451
	return ($rc == 0);
2452
}
2453

    
2454
function alias_to_subnets_recursive($name, $returnhostnames = false) {
2455
	global $aliastable;
2456
	$result = array();
2457
	if (!isset($aliastable[$name])) {
2458
		return $result;
2459
	}
2460
	$subnets = preg_split('/\s+/', $aliastable[$name]);
2461
	foreach ($subnets as $net) {
2462
		if (is_alias($net)) {
2463
			$sub = alias_to_subnets_recursive($net, $returnhostnames);
2464
			$result = array_merge($result, $sub);
2465
			continue;
2466
		} elseif (!is_subnet($net)) {
2467
			if (is_ipaddrv4($net)) {
2468
				$net .= "/32";
2469
			} else if (is_ipaddrv6($net)) {
2470
				$net .= "/128";
2471
			} else if ($returnhostnames === false || !is_fqdn($net)) {
2472
				continue;
2473
			}
2474
		}
2475
		$result[] = $net;
2476
	}
2477
	return $result;
2478
}
2479

    
2480
function get_staticroutes($returnsubnetsonly = false, $returnhostnames = false, $returnenabledroutesonly = false) {
2481
	global $config, $aliastable;
2482

    
2483
	/* Bail if there are no routes, but return an array always so callers don't have to check. */
2484
	if (!is_array($config['staticroutes']['route'])) {
2485
		return array();
2486
	}
2487

    
2488
	$allstaticroutes = array();
2489
	$allsubnets = array();
2490
	/* Loop through routes and expand aliases as we find them. */
2491
	foreach ($config['staticroutes']['route'] as $route) {
2492
		if ($returnenabledroutesonly && isset($route['disabled'])) {
2493
			continue;
2494
		}
2495

    
2496
		if (is_alias($route['network'])) {
2497
			foreach (alias_to_subnets_recursive($route['network'], $returnhostnames) as $net) {
2498
				$temproute = $route;
2499
				$temproute['network'] = $net;
2500
				$allstaticroutes[] = $temproute;
2501
				$allsubnets[] = $net;
2502
			}
2503
		} elseif (is_subnet($route['network'])) {
2504
			$allstaticroutes[] = $route;
2505
			$allsubnets[] = $route['network'];
2506
		}
2507
	}
2508
	if ($returnsubnetsonly) {
2509
		return $allsubnets;
2510
	} else {
2511
		return $allstaticroutes;
2512
	}
2513
}
2514

    
2515
/****f* util/get_alias_list
2516
 * NAME
2517
 *   get_alias_list - Provide a list of aliases.
2518
 * INPUTS
2519
 *   $type          - Optional, can be a string or array specifying what type(s) of aliases you need.
2520
 * RESULT
2521
 *   Array containing list of aliases.
2522
 *   If $type is unspecified, all aliases are returned.
2523
 *   If $type is a string, all aliases of the type specified in $type are returned.
2524
 *   If $type is an array, all aliases of any type specified in any element of $type are returned.
2525
 */
2526
function get_alias_list($type = null) {
2527
	global $config;
2528
	$result = array();
2529
	if ($config['aliases']['alias'] <> "" && is_array($config['aliases']['alias'])) {
2530
		foreach ($config['aliases']['alias'] as $alias) {
2531
			if ($type === null) {
2532
				$result[] = $alias['name'];
2533
			} else if (is_array($type)) {
2534
				if (in_array($alias['type'], $type)) {
2535
					$result[] = $alias['name'];
2536
				}
2537
			} else if ($type === $alias['type']) {
2538
				$result[] = $alias['name'];
2539
			}
2540
		}
2541
	}
2542
	return $result;
2543
}
2544

    
2545
/* returns an array consisting of every element of $haystack that is not equal to $needle. */
2546
function array_exclude($needle, $haystack) {
2547
	$result = array();
2548
	if (is_array($haystack)) {
2549
		foreach ($haystack as $thing) {
2550
			if ($needle !== $thing) {
2551
				$result[] = $thing;
2552
			}
2553
		}
2554
	}
2555
	return $result;
2556
}
2557

    
2558
/* Define what is preferred, IPv4 or IPv6 */
2559
function prefer_ipv4_or_ipv6() {
2560
	global $config;
2561

    
2562
	if (isset($config['system']['prefer_ipv4'])) {
2563
		mwexec("/etc/rc.d/ip6addrctl prefer_ipv4");
2564
	} else {
2565
		mwexec("/etc/rc.d/ip6addrctl prefer_ipv6");
2566
	}
2567
}
2568

    
2569
/* Redirect to page passing parameters via POST */
2570
function post_redirect($page, $params) {
2571
	if (!is_array($params)) {
2572
		return;
2573
	}
2574

    
2575
	print "<html><body><form action=\"{$page}\" name=\"formredir\" method=\"post\">\n";
2576
	foreach ($params as $key => $value) {
2577
		print "<input type=\"hidden\" name=\"{$key}\" value=\"{$value}\" />\n";
2578
	}
2579
	print "</form>\n";
2580
	print "<script type=\"text/javascript\">\n";
2581
	print "//<![CDATA[\n";
2582
	print "document.formredir.submit();\n";
2583
	print "//]]>\n";
2584
	print "</script>\n";
2585
	print "</body></html>\n";
2586
}
2587

    
2588
/* Locate disks that can be queried for S.M.A.R.T. data. */
2589
function get_smart_drive_list() {
2590
	$disk_list = explode(" ", get_single_sysctl("kern.disks"));
2591
	foreach ($disk_list as $id => $disk) {
2592
		// We only want certain kinds of disks for S.M.A.R.T.
2593
		// 1 is a match, 0 is no match, False is any problem processing the regex
2594
		if (preg_match("/^(ad|da|ada).*[0-9]{1,2}$/", $disk) !== 1) {
2595
			unset($disk_list[$id]);
2596
		}
2597
	}
2598
	sort($disk_list);
2599
	return $disk_list;
2600
}
2601

    
2602
// Validate a network address
2603
//	$addr: the address to validate
2604
//	$type: IPV4|IPV6|IPV4V6
2605
//	$label: the label used by the GUI to display this value. Required to compose an error message
2606
//	$err_msg: pointer to the callers error message array so that error messages can be added to it here
2607
//	$alias: are aliases permitted for this address?
2608
// Returns:
2609
//	IPV4 - if $addr is a valid IPv4 address
2610
//	IPV6 - if $addr is a valid IPv6 address
2611
//	ALIAS - if $alias=true and $addr is an alias
2612
//	false - otherwise
2613

    
2614
function validateipaddr(&$addr, $type, $label, &$err_msg, $alias=false) {
2615
	switch ($type) {
2616
		case IPV4:
2617
			if (is_ipaddrv4($addr)) {
2618
				return IPV4;
2619
			} else if ($alias) {
2620
				if (is_alias($addr)) {
2621
					return ALIAS;
2622
				} else {
2623
					$err_msg[] = sprintf(gettext("%s must be a valid IPv4 address or alias."), $label);
2624
					return false;
2625
				}
2626
			} else {
2627
				$err_msg[] = sprintf(gettext("%s must be a valid IPv4 address."), $label);
2628
				return false;
2629
			}
2630
		break;
2631
		case IPV6:
2632
			if (is_ipaddrv6($addr)) {
2633
				$addr = strtolower($addr);
2634
				return IPV6;
2635
			} else if ($alias) {
2636
				if (is_alias($addr)) {
2637
					return ALIAS;
2638
				} else {
2639
					$err_msg[] = sprintf(gettext("%s must be a valid IPv6 address or alias."), $label);
2640
					return false;
2641
				}
2642
			} else {
2643
				$err_msg[] = sprintf(gettext("%s must be a valid IPv6 address."), $label);
2644
				return false;
2645
			}
2646
		break;
2647
		case IPV4V6:
2648
			if (is_ipaddrv6($addr)) {
2649
				$addr = strtolower($addr);
2650
				return IPV6;
2651
			} else if (is_ipaddrv4($addr)) {
2652
				return IPV4;
2653
			} else if ($alias) {
2654
				if (is_alias($addr)) {
2655
					return ALIAS;
2656
				} else {
2657
					$err_msg[] = sprintf(gettext("%s must be a valid IPv4 or IPv6 address or alias."), $label);
2658
					return false;
2659
				}
2660
			} else {
2661
				$err_msg[] = sprintf(gettext("%s must be a valid IPv4 or IPv6 address."), $label);
2662
				return false;
2663
			}
2664
		break;
2665
	}
2666

    
2667
	return false;
2668
}
2669

    
2670
/* From DUID configuration inputs, format a string that looks (more) like the expected raw DUID format:
2671
 * 1) For DUIDs entered as a known DUID type, convert to a hexstring and prepend the DUID number, after having done the following:
2672
 *     a) For DUID-LLT and DUID-EN, convert the time/enterprise ID input to hex and append the link-layer address/identifier input.
2673
 *     b) For DUID-LLT and DUID-LL, prepend a hardware type of 1.
2674
 *     c) For DUID-UUID, remove any "-".
2675
 * 2) Replace any remaining "-" with ":".
2676
 * 3) If any components are input with just a single char (hex digit hopefully), put a "0" in front.
2677
 * 4) The first two components should be a 16-bit integer (little- or big-endian, depending on the current machine type) that
2678
 *    is equal to the number of other components. If not, prepend this as "nn:00" (all pfSense builds are little-endian).
2679
 *    This is convenience, because the DUID reported by dhcp6c in logs does not include this count, which corresponds to the
2680
 *    option-len field of DHCPv6's OPTION_CLIENTID option.
2681
 *
2682
 * The final result should be closer to:
2683
 *
2684
 * "nn:00:00:0n:nn:nn:nn:..."
2685
 *
2686
 * This function does not validate the input. is_duid() will do validation.
2687
 */
2688
function format_duid($duidtype, $duidpt1, $duidpt2=null) {
2689
	if ($duidpt2)
2690
		$duidpt1 = implode(':', str_split(str_pad(dechex($duidpt1), 8, '0', STR_PAD_LEFT), 2)) . ':' . $duidpt2;
2691

    
2692
	/* Make hexstrings */
2693
	if ($duidtype) {
2694
		switch ($duidtype) {
2695
		/* Add a hardware type to DUID-LLT and DUID-LL; assume Ethernet */
2696
		case 1:
2697
		case 3:
2698
			$duidpt1 = '00:01:' . $duidpt1;
2699
			break;
2700
		/* Remove '-' from given UUID and insert ':' every 2 characters */
2701
		case 4:
2702
			$duidpt1 = implode(':', str_split(str_replace('-', '', $duidpt1), 2));
2703
			break;
2704
		default:
2705
		}
2706
		$duidpt1 = '00:0' . $duidtype . ':' . $duidpt1;
2707
	}
2708

    
2709
	$values = explode(':', strtolower(str_replace('-', ':', $duidpt1)));
2710

    
2711
	if (hexdec($values[0]) != count($values) - 2)
2712
		array_unshift($values, dechex(count($values)), '00');
2713

    
2714
	array_walk($values, function(&$value) {
2715
		$value = str_pad($value, 2, '0', STR_PAD_LEFT);
2716
	});
2717

    
2718
	return implode(":", $values);
2719
}
2720

    
2721
/* Returns true if $dhcp6duid is a valid DUID entry.
2722
 * Parse the entry to check for valid length according to known DUID types.
2723
 */
2724
function is_duid($dhcp6duid) {
2725
	$values = explode(":", $dhcp6duid);
2726
	if (hexdec($values[0]) == count($values) - 2) {
2727
		switch (hexdec($values[2] . $values[3])) {
2728
		case 0:
2729
			return false;
2730
			break;
2731
		case 1:
2732
			if (count($values) != 16 || strlen($dhcp6duid) != 47)
2733
				return false;
2734
			break;
2735
		case 3:
2736
			if (count($values) != 12 || strlen($dhcp6duid) != 35)
2737
				return false;
2738
			break;
2739
		case 4:
2740
			if (count($values) != 20 || strlen($dhcp6duid) != 59)
2741
				return false;
2742
			break;
2743
		/* DUID is up to 128 octets; allow 2 octets for type code, 2 more for option-len */
2744
		default:
2745
			if (count($values) > 132 || strlen($dhcp6duid) != count($values) * 3 - 1)
2746
				return false;
2747
		}
2748
	} else
2749
		return false;
2750

    
2751
	for ($i = 0; $i < count($values); $i++) {
2752
		if (ctype_xdigit($values[$i]) == false)
2753
			return false;
2754
		if (hexdec($values[$i]) < 0 || hexdec($values[$i]) > 255)
2755
			return false;
2756
	}
2757

    
2758
	return true;
2759
}
2760

    
2761
/* Write the DHCP6 DUID file */
2762
function write_dhcp6_duid($duidstring) {
2763
	// Create the hex array from the dhcp6duid config entry and write to file
2764
	global $g;
2765

    
2766
	if(!is_duid($duidstring)) {
2767
		log_error(gettext("Error: attempting to write DUID file - Invalid DUID detected"));
2768
		return false;
2769
	}
2770
	$temp = str_replace(":","",$duidstring);
2771
	$duid_binstring = pack("H*",$temp);
2772
	if ($fd = fopen("{$g['vardb_path']}/dhcp6c_duid", "wb")) {
2773
		fwrite($fd, $duid_binstring);
2774
		fclose($fd);
2775
		return true;
2776
	}
2777
	log_error(gettext("Error: attempting to write DUID file - File write error"));
2778
	return false;
2779
}
2780

    
2781
/* returns duid string from 'vardb_path']}/dhcp6c_duid' */
2782
function get_duid_from_file() {
2783
	global $g;
2784

    
2785
	$duid_ASCII = "";
2786
	$count = 0;
2787

    
2788
	if (file_exists("{$g['vardb_path']}/dhcp6c_duid") &&
2789
	    ($fd = fopen("{$g['vardb_path']}/dhcp6c_duid", "r"))) {
2790
		$fsize = filesize("{$g['vardb_path']}/dhcp6c_duid");
2791
		if ($fsize <= 132) {
2792
			$buffer = fread($fd, $fsize);
2793
			while($count < $fsize) {
2794
				$duid_ASCII .= bin2hex($buffer[$count]);
2795
				$count++;
2796
				if($count < $fsize) {
2797
					$duid_ASCII .= ":";
2798
				}
2799
			}
2800
		}
2801
		fclose($fd);
2802
	}
2803
	//if no file or error with read then the string returns blanked DUID string
2804
	if(!is_duid($duid_ASCII)) {
2805
		return "--:--:--:--:--:--:--:--:--:--:--:--:--:--:--:--";
2806
	}
2807
	return($duid_ASCII);
2808
}
2809

    
2810
/* Replaces the Mac OS 9 and earlier (\r) and DOS/Windows (\r\n) newlines with the Unix equivalent (\n). */
2811
function unixnewlines($text) {
2812
	return preg_replace('/\r\n?/', "\n", $text);
2813
}
2814

    
2815
?>
(52-52/60)