Project

General

Profile

Download (76.4 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
function is_v4($ip_or_subnet) {
776
	return is_ipaddrv4($ip_or_subnet) || is_subnetv4($ip_or_subnet);
777
}
778

    
779
function is_v6($ip_or_subnet) {
780
	return is_ipaddrv6($ip_or_subnet) || is_subnetv6($ip_or_subnet);
781
}
782

    
783
/* same as is_subnet() but accepts IPv4 only */
784
function is_subnetv4($subnet) {
785
	return (is_subnet($subnet) == 4);
786
}
787

    
788
/* same as is_subnet() but accepts IPv6 only */
789
function is_subnetv6($subnet) {
790
	return (is_subnet($subnet) == 6);
791
}
792

    
793
/* returns true if $subnet is a valid subnet in CIDR format or an alias thereof */
794
function is_subnetoralias($subnet) {
795
	global $aliastable;
796

    
797
	if (isset($aliastable[$subnet]) && is_subnet($aliastable[$subnet])) {
798
		return true;
799
	} else {
800
		return is_subnet($subnet);
801
	}
802
}
803

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

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

    
832
	// 2**N returns an exact result as an INT if possible, and a float/double if not.
833
	// 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
834
	$result = 2 ** $snsize;
835

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

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

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

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

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

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

    
891
	if ($subnetv6_start1 == '' || $subnetv6_start2 == '') {
892
		// One or both args is not a valid IPv6 subnet
893
		//FIXME: needs to return "bad data" not true/false if bad. For now return false, best we can do until fixed
894
		return false;
895
	}
896
	return ($subnetv6_start1 == $subnetv6_start2);
897
}
898

    
899
/* return all PTR zones for a IPv6 network */
900
function get_v6_ptr_zones($subnet, $bits) {
901
	$result = array();
902

    
903
	if (!is_ipaddrv6($subnet)) {
904
		return $result;
905
	}
906

    
907
	if (!is_numericint($bits) || $bits > 128) {
908
		return $result;
909
	}
910

    
911
	/*
912
	 * Find a small nibble boundary subnet mask
913
	 * e.g. a /29 will create 8 /32 PTR zones
914
	 */
915
	$small_sn = $bits;
916
	while ($small_sn % 4 != 0) {
917
		$small_sn++;
918
	}
919

    
920
	/* Get network prefix */
921
	$small_subnet = Net_IPv6::getNetmask($subnet, $bits);
922

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

    
934
		/* Detect what part of IP should be increased */
935
		$change_part = (int) ($small_sn / 16);
936
		if ($small_sn % 16 == 0) {
937
			$change_part--;
938
		}
939

    
940
		/* Increase 1 to desired part */
941
		$parts = explode(":", Net_IPv6::uncompress($small_subnet));
942
		$parts[$change_part]++;
943
		$small_subnet = implode(":", $parts);
944
	}
945

    
946
	return $result;
947
}
948

    
949
/* return true if $addr is in $subnet, false if not */
950
function ip_in_subnet($addr, $subnet) {
951
	if (is_ipaddrv6($addr) && is_subnetv6($subnet)) {
952
		return (Net_IPv6::isInNetmask($addr, $subnet));
953
	} else if (is_ipaddrv4($addr) && is_subnetv4($subnet)) {
954
		list($ip, $mask) = explode('/', $subnet);
955
		$mask = (0xffffffff << (32 - $mask)) & 0xffffffff;
956
		return ((ip2long($addr) & $mask) == (ip2long($ip) & $mask));
957
	}
958
	return false;
959
}
960

    
961
/* returns true if $hostname is just a valid hostname (top part without any of the domain part) */
962
function is_unqualified_hostname($hostname) {
963
	if (!is_string($hostname)) {
964
		return false;
965
	}
966

    
967
	if (preg_match('/^(?:[a-z0-9_]|[a-z0-9_][a-z0-9_\-]*[a-z0-9_])$/i', $hostname)) {
968
		return true;
969
	} else {
970
		return false;
971
	}
972
}
973

    
974
/* returns true if $hostname is a valid hostname, with or without being a fully-qualified domain name. */
975
function is_hostname($hostname, $allow_wildcard=false) {
976
	if (!is_string($hostname)) {
977
		return false;
978
	}
979

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

    
992
/* returns true if $domain is a valid domain name */
993
function is_domain($domain, $allow_wildcard=false) {
994
	if (!is_string($domain)) {
995
		return false;
996
	}
997
	if ($allow_wildcard) {
998
		$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';
999
	} else {
1000
		$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';
1001
	}
1002

    
1003
	if (preg_match($domain_regex, $domain)) {
1004
		return true;
1005
	} else {
1006
		return false;
1007
	}
1008
}
1009

    
1010
/* returns true if $macaddr is a valid MAC address */
1011
function is_macaddr($macaddr, $partial=false) {
1012
	$values = explode(":", $macaddr);
1013

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

    
1029
	return true;
1030
}
1031

    
1032
/*
1033
	If $return_message is true then
1034
		returns a text message about the reason that the name is invalid.
1035
		the text includes the type of "thing" that is being checked, passed in $object. (e.g. "alias", "gateway group", "schedule")
1036
	else
1037
		returns true if $name is a valid name for an alias
1038
		returns false if $name is not a valid name for an alias
1039

    
1040
	Aliases cannot be:
1041
		bad chars: anything except a-z 0-9 and underscore
1042
		bad names: empty string, pure numeric, pure underscore
1043
		reserved words: pre-defined service/protocol/port names which should not be ambiguous, and the words "port" and  "pass" */
1044

    
1045
function is_validaliasname($name, $return_message = false, $object = "alias") {
1046
	/* Array of reserved words */
1047
	$reserved = array("port", "pass");
1048

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

    
1084
/* returns a text message indicating if the alias name is valid, or the reason it is not valid. */
1085
function invalidaliasnamemsg($name, $object = "alias") {
1086
	return is_validaliasname($name, true, $object);
1087
}
1088

    
1089
/*
1090
 * returns true if $range is a valid integer range between $min and $max
1091
 * range delimiter can be ':' or '-'
1092
 */
1093
function is_intrange($range, $min, $max) {
1094
	$values = preg_split("/[:-]/", $range);
1095

    
1096
	if (!is_array($values) || count($values) != 2) {
1097
		return false;
1098
	}
1099

    
1100
	if (!ctype_digit($values[0]) || !ctype_digit($values[1])) {
1101
		return false;
1102
	}
1103

    
1104
	$values[0] = intval($values[0]);
1105
	$values[1] = intval($values[1]);
1106

    
1107
	if ($values[0] >= $values[1]) {
1108
		return false;
1109
	}
1110

    
1111
	if ($values[0] < $min || $values[1] > $max) {
1112
		return false;
1113
	}
1114

    
1115
	return true;
1116
}
1117

    
1118
/* returns true if $port is a valid TCP/UDP port */
1119
function is_port($port) {
1120
	if (ctype_digit($port) && ((intval($port) >= 1) && (intval($port) <= 65535))) {
1121
		return true;
1122
	}
1123
	if (getservbyname($port, "tcp") || getservbyname($port, "udp")) {
1124
		return true;
1125
	}
1126
	return false;
1127
}
1128

    
1129
/* returns true if $portrange is a valid TCP/UDP portrange ("<port>:<port>") */
1130
function is_portrange($portrange) {
1131
	$ports = explode(":", $portrange);
1132

    
1133
	return (count($ports) == 2 && is_port($ports[0]) && is_port($ports[1]));
1134
}
1135

    
1136
/* returns true if $port is a valid TCP/UDP port number or range ("<port>:<port>") */
1137
function is_port_or_range($port) {
1138
	return (is_port($port) || is_portrange($port));
1139
}
1140

    
1141
/* returns true if $port is an alias that is a port type */
1142
function is_portalias($port) {
1143
	global $config;
1144

    
1145
	if (is_alias($port)) {
1146
		if (is_array($config['aliases']['alias'])) {
1147
			foreach ($config['aliases']['alias'] as $alias) {
1148
				if ($alias['name'] == $port && preg_match("/port/i", $alias['type'])) {
1149
					return true;
1150
				}
1151
			}
1152
		}
1153
	}
1154
	return false;
1155
}
1156

    
1157
/* returns true if $port is a valid port number or an alias thereof */
1158
function is_port_or_alias($port) {
1159
	return (is_port($port) || is_portalias($port));
1160
}
1161

    
1162
/* returns true if $port is a valid TCP/UDP port number or range ("<port>:<port>") or an alias thereof */
1163
function is_port_or_range_or_alias($port) {
1164
	return (is_port($port) || is_portrange($port) || is_portalias($port));
1165
}
1166

    
1167
/* create ranges of sequential port numbers (200:215) and remove duplicates */
1168
function group_ports($ports, $kflc = false) {
1169
	if (!is_array($ports) || empty($ports)) {
1170
		return;
1171
	}
1172

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

    
1198
	$result = array();
1199
	foreach ($uniq as $idx => $port) {
1200
		if ($idx == 0) {
1201
			$result[] = $port;
1202
			continue;
1203
		}
1204

    
1205
		$last = end($result);
1206
		if (is_portrange($last)) {
1207
			list($begin, $end) = explode(":", $last);
1208
		} else {
1209
			$begin = $end = $last;
1210
		}
1211

    
1212
		if ($port == ($end+1)) {
1213
			$end++;
1214
			$result[count($result)-1] = "{$begin}:{$end}";
1215
		} else {
1216
			$result[] = $port;
1217
		}
1218
	}
1219

    
1220
	return array_merge($comments, $result);
1221
}
1222

    
1223
/* returns true if $val is a valid shaper bandwidth value */
1224
function is_valid_shaperbw($val) {
1225
	return (preg_match("/^(\d+(?:\.\d+)?)([MKG]?b|%)$/", $val));
1226
}
1227

    
1228
/* returns true if $test is in the range between $start and $end */
1229
function is_inrange_v4($test, $start, $end) {
1230
	if (!is_ipaddrv4($test) || !is_ipaddrv4($start) || !is_ipaddrv4($end)) {
1231
		return false;
1232
	}
1233

    
1234
	if (ip2ulong($test) <= ip2ulong($end) &&
1235
	    ip2ulong($test) >= ip2ulong($start)) {
1236
		return true;
1237
	}
1238

    
1239
	return false;
1240
}
1241

    
1242
/* returns true if $test is in the range between $start and $end */
1243
function is_inrange_v6($test, $start, $end) {
1244
	if (!is_ipaddrv6($test) || !is_ipaddrv6($start) || !is_ipaddrv6($end)) {
1245
		return false;
1246
	}
1247

    
1248
	if (inet_pton($test) <= inet_pton($end) &&
1249
	    inet_pton($test) >= inet_pton($start)) {
1250
		return true;
1251
	}
1252

    
1253
	return false;
1254
}
1255

    
1256
/* returns true if $test is in the range between $start and $end */
1257
function is_inrange($test, $start, $end) {
1258
	return is_ipaddrv6($test) ? is_inrange_v6($test, $start, $end) : is_inrange_v4($test, $start, $end);
1259
}
1260

    
1261
function get_configured_vip_list($family = 'all', $type = VIP_ALL) {
1262
	global $config;
1263

    
1264
	$list = array();
1265
	if (!is_array($config['virtualip']['vip']) || empty($config['virtualip']['vip'])) {
1266
		return ($list);
1267
	}
1268

    
1269
	$viparr = &$config['virtualip']['vip'];
1270
	foreach ($viparr as $vip) {
1271

    
1272
		if ($type == VIP_CARP) {
1273
			if ($vip['mode'] != "carp")
1274
				continue;
1275
		} elseif ($type == VIP_IPALIAS) {
1276
			if ($vip['mode'] != "ipalias")
1277
				continue;
1278
		} else {
1279
			if ($vip['mode'] != "carp" && $vip['mode'] != "ipalias")
1280
				continue;
1281
		}
1282

    
1283
		if ($family == 'all' ||
1284
		    ($family == 'inet' && is_ipaddrv4($vip['subnet'])) ||
1285
		    ($family == 'inet6' && is_ipaddrv6($vip['subnet']))) {
1286
			$list["_vip{$vip['uniqid']}"] = $vip['subnet'];
1287
		}
1288
	}
1289
	return ($list);
1290
}
1291

    
1292
function get_configured_vip($vipinterface = '') {
1293

    
1294
	return (get_configured_vip_detail($vipinterface, 'all', 'vip'));
1295
}
1296

    
1297
function get_configured_vip_interface($vipinterface = '') {
1298

    
1299
	return (get_configured_vip_detail($vipinterface, 'all', 'iface'));
1300
}
1301

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

    
1304
	return (get_configured_vip_detail($vipinterface, 'inet', 'ip'));
1305
}
1306

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

    
1309
	return (get_configured_vip_detail($vipinterface, 'inet6', 'ip'));
1310
}
1311

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

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

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

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

    
1322
function get_configured_vip_detail($vipinterface = '', $family = 'inet', $what = 'ip') {
1323
	global $config;
1324

    
1325
	if (empty($vipinterface) || !is_array($config['virtualip']['vip']) ||
1326
	    empty($config['virtualip']['vip'])) {
1327
		return (NULL);
1328
	}
1329

    
1330
	$viparr = &$config['virtualip']['vip'];
1331
	foreach ($viparr as $vip) {
1332
		if ($vip['mode'] != "carp" && $vip['mode'] != "ipalias") {
1333
			continue;
1334
		}
1335

    
1336
		if ($vipinterface != "_vip{$vip['uniqid']}") {
1337
			continue;
1338
		}
1339

    
1340
		switch ($what) {
1341
			case 'subnet':
1342
				if ($family == 'inet' && is_ipaddrv4($vip['subnet']))
1343
					return ($vip['subnet_bits']);
1344
				else if ($family == 'inet6' && is_ipaddrv6($vip['subnet']))
1345
					return ($vip['subnet_bits']);
1346
				break;
1347
			case 'iface':
1348
				return ($vip['interface']);
1349
				break;
1350
			case 'vip':
1351
				return ($vip);
1352
				break;
1353
			case 'ip':
1354
			default:
1355
				if ($family == 'inet' && is_ipaddrv4($vip['subnet'])) {
1356
					return ($vip['subnet']);
1357
				} else if ($family == 'inet6' && is_ipaddrv6($vip['subnet'])) {
1358
					return ($vip['subnet']);
1359
				}
1360
				break;
1361
		}
1362
		break;
1363
	}
1364

    
1365
	return (NULL);
1366
}
1367

    
1368
/* comparison function for sorting by the order in which interfaces are normally created */
1369
function compare_interface_friendly_names($a, $b) {
1370
	if ($a == $b) {
1371
		return 0;
1372
	} else if ($a == 'wan') {
1373
		return -1;
1374
	} else if ($b == 'wan') {
1375
		return 1;
1376
	} else if ($a == 'lan') {
1377
		return -1;
1378
	} else if ($b == 'lan') {
1379
		return 1;
1380
	}
1381

    
1382
	return strnatcmp($a, $b);
1383
}
1384

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

    
1389
	$iflist = array();
1390

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

    
1398
	return $iflist;
1399
}
1400

    
1401
/* return the configured interfaces list. */
1402
function get_configured_interface_list_by_realif($withdisabled = false) {
1403
	global $config;
1404

    
1405
	$iflist = array();
1406

    
1407
	/* if list */
1408
	foreach ($config['interfaces'] as $if => $ifdetail) {
1409
		if (isset($ifdetail['enable']) || $withdisabled == true) {
1410
			$tmpif = get_real_interface($if);
1411
			if (!empty($tmpif)) {
1412
				$iflist[$tmpif] = $if;
1413
			}
1414
		}
1415
	}
1416

    
1417
	return $iflist;
1418
}
1419

    
1420
/* return the configured interfaces list with their description. */
1421
function get_configured_interface_with_descr($withdisabled = false) {
1422
	global $config, $user_settings;
1423

    
1424
	$iflist = array();
1425

    
1426
	/* if list */
1427
	foreach ($config['interfaces'] as $if => $ifdetail) {
1428
		if (isset($ifdetail['enable']) || $withdisabled == true) {
1429
			if (empty($ifdetail['descr'])) {
1430
				$iflist[$if] = strtoupper($if);
1431
			} else {
1432
				$iflist[$if] = strtoupper($ifdetail['descr']);
1433
			}
1434
		}
1435
	}
1436

    
1437
	if ($user_settings['webgui']['interfacessort']) {
1438
		asort($iflist);
1439
	}
1440

    
1441
	return $iflist;
1442
}
1443

    
1444
/*
1445
 *   get_configured_ip_addresses() - Return a list of all configured
1446
 *   IPv4 addresses.
1447
 *
1448
 */
1449
function get_configured_ip_addresses() {
1450
	global $config;
1451

    
1452
	if (!function_exists('get_interface_ip')) {
1453
		require_once("interfaces.inc");
1454
	}
1455
	$ip_array = array();
1456
	$interfaces = get_configured_interface_list();
1457
	if (is_array($interfaces)) {
1458
		foreach ($interfaces as $int) {
1459
			$ipaddr = get_interface_ip($int);
1460
			$ip_array[$int] = $ipaddr;
1461
		}
1462
	}
1463
	$interfaces = get_configured_vip_list('inet');
1464
	if (is_array($interfaces)) {
1465
		foreach ($interfaces as $int => $ipaddr) {
1466
			$ip_array[$int] = $ipaddr;
1467
		}
1468
	}
1469

    
1470
	/* pppoe server */
1471
	if (is_array($config['pppoes']) && is_array($config['pppoes']['pppoe'])) {
1472
		foreach ($config['pppoes']['pppoe'] as $pppoe) {
1473
			if ($pppoe['mode'] == "server") {
1474
				if (is_ipaddr($pppoe['localip'])) {
1475
					$int = "pppoes". $pppoe['pppoeid'];
1476
					$ip_array[$int] = $pppoe['localip'];
1477
				}
1478
			}
1479
		}
1480
	}
1481

    
1482
	return $ip_array;
1483
}
1484

    
1485
/*
1486
 *   get_configured_ipv6_addresses() - Return a list of all configured
1487
 *   IPv6 addresses.
1488
 *
1489
 */
1490
function get_configured_ipv6_addresses($linklocal_fallback = false) {
1491
	require_once("interfaces.inc");
1492
	$ipv6_array = array();
1493
	$interfaces = get_configured_interface_list();
1494
	if (is_array($interfaces)) {
1495
		foreach ($interfaces as $int) {
1496
			$ipaddrv6 = text_to_compressed_ip6(get_interface_ipv6($int, false, $linklocal_fallback));
1497
			$ipv6_array[$int] = $ipaddrv6;
1498
		}
1499
	}
1500
	$interfaces = get_configured_vip_list('inet6');
1501
	if (is_array($interfaces)) {
1502
		foreach ($interfaces as $int => $ipaddrv6) {
1503
			$ipv6_array[$int] = text_to_compressed_ip6($ipaddrv6);
1504
		}
1505
	}
1506
	return $ipv6_array;
1507
}
1508

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

    
1614
			case "friendly":
1615
				if ($friendly != "") {
1616
					$toput['if'] = $ifname;
1617
					$iflist[$friendly] = $toput;
1618
				}
1619
				break;
1620
			}
1621
		}
1622
	}
1623
	return $iflist;
1624
}
1625

    
1626
function get_lagg_interface_list() {
1627
	global $config;
1628

    
1629
	$plist = array();
1630
	if (isset($config['laggs']['lagg']) && is_array($config['laggs']['lagg'])) {
1631
		foreach ($config['laggs']['lagg'] as $lagg) {
1632
			$lagg['mac'] = get_interface_mac($lagg['laggif']);
1633
			$lagg['islagg'] = true;
1634
			$plist[$lagg['laggif']] = $lagg;
1635
		}
1636
	}
1637

    
1638
	return ($plist);
1639
}
1640

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

    
1663
/****f* util/log_auth
1664
* NAME
1665
*   log_auth   - Sends a string to syslog as LOG_AUTH facility
1666
* INPUTS
1667
*   $error     - string containing the syslog message.
1668
* RESULT
1669
*   null
1670
******/
1671
function log_auth($error) {
1672
	global $g;
1673
	$page = $_SERVER['SCRIPT_NAME'];
1674
	syslog(LOG_AUTH, "$page: $error");
1675
	if ($g['debug']) {
1676
		syslog(LOG_WARNING, var_dump(debug_backtrace()));
1677
	}
1678
	return;
1679
}
1680

    
1681
/****f* util/exec_command
1682
 * NAME
1683
 *   exec_command - Execute a command and return a string of the result.
1684
 * INPUTS
1685
 *   $command   - String of the command to be executed.
1686
 * RESULT
1687
 *   String containing the command's result.
1688
 * NOTES
1689
 *   This function returns the command's stdout and stderr.
1690
 ******/
1691
function exec_command($command) {
1692
	$output = array();
1693
	exec($command . ' 2>&1', $output);
1694
	return(implode("\n", $output));
1695
}
1696

    
1697
/* wrapper for exec()
1698
   Executes in background or foreground.
1699
   For background execution, returns PID of background process to allow calling code control */
1700
function mwexec($command, $nologentry = false, $clearsigmask = false, $background = false) {
1701
	global $g;
1702
	$retval = 0;
1703

    
1704
	if ($g['debug']) {
1705
		if (!$_SERVER['REMOTE_ADDR']) {
1706
			echo "mwexec(): $command" . ($background ? " [BG]":"") . "\n";
1707
		}
1708
	}
1709
	if ($clearsigmask) {
1710
		$oldset = array();
1711
		pcntl_sigprocmask(SIG_SETMASK, array(), $oldset);
1712
	}
1713

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

    
1726
	if ($clearsigmask) {
1727
		pcntl_sigprocmask(SIG_SETMASK, $oldset);
1728
	}
1729

    
1730
	return $retval;
1731
}
1732

    
1733
/* wrapper for exec() in background */
1734
function mwexec_bg($command, $clearsigmask = false) {
1735
	return mwexec($command, false, $clearsigmask, true);
1736
}
1737

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

    
1761
	$aliastable = array();
1762

    
1763
	if (is_array($config['aliases']['alias'])) {
1764
		foreach ($config['aliases']['alias'] as $alias) {
1765
			if ($alias['name']) {
1766
				$aliastable[$alias['name']] = $alias['address'];
1767
			}
1768
		}
1769
	}
1770
}
1771

    
1772
/* check if an alias exists */
1773
function is_alias($name) {
1774
	global $aliastable;
1775

    
1776
	return isset($aliastable[$name]);
1777
}
1778

    
1779
function alias_get_type($name) {
1780
	global $config;
1781

    
1782
	if (is_array($config['aliases']['alias'])) {
1783
		foreach ($config['aliases']['alias'] as $alias) {
1784
			if ($name == $alias['name']) {
1785
				return $alias['type'];
1786
			}
1787
		}
1788
	}
1789

    
1790
	return "";
1791
}
1792

    
1793
/* expand a host or network alias, if necessary */
1794
function alias_expand($name) {
1795
	global $config, $aliastable;
1796
	$urltable_prefix = "/var/db/aliastables/";
1797
	$urltable_filename = $urltable_prefix . $name . ".txt";
1798

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

    
1824
function alias_expand_urltable($name) {
1825
	global $config;
1826
	$urltable_prefix = "/var/db/aliastables/";
1827
	$urltable_filename = $urltable_prefix . $name . ".txt";
1828

    
1829
	if (is_array($config['aliases']['alias'])) {
1830
		foreach ($config['aliases']['alias'] as $alias) {
1831
			if (preg_match("/urltable/i", $alias['type']) && ($alias['name'] == $name)) {
1832
				if (is_URL($alias["url"]) && file_exists($urltable_filename)) {
1833
					if (!filesize($urltable_filename)) {
1834
						// file exists, but is empty, try to sync
1835
						send_event("service sync alias {$name}");
1836
					}
1837
					return $urltable_filename;
1838
				} else {
1839
					send_event("service sync alias {$name}");
1840
					break;
1841
				}
1842
			}
1843
		}
1844
	}
1845
	return null;
1846
}
1847

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

    
1873
/* return a fieldname that is safe for xml usage */
1874
function xml_safe_fieldname($fieldname) {
1875
	$replace = array(
1876
	    '/', '-', ' ', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')',
1877
	    '_', '+', '=', '{', '}', '[', ']', '|', '/', '<', '>', '?',
1878
	    ':', ',', '.', '\'', '\\'
1879
	);
1880
	return strtolower(str_replace($replace, "", $fieldname));
1881
}
1882

    
1883
function mac_format($clientmac) {
1884
	global $config, $cpzone;
1885

    
1886
	$mac = explode(":", $clientmac);
1887
	$mac_format = $cpzone ? $config['captiveportal'][$cpzone]['radmac_format'] : false;
1888

    
1889
	switch ($mac_format) {
1890
		case 'singledash':
1891
			return "$mac[0]$mac[1]$mac[2]-$mac[3]$mac[4]$mac[5]";
1892

    
1893
		case 'ietf':
1894
			return "$mac[0]-$mac[1]-$mac[2]-$mac[3]-$mac[4]-$mac[5]";
1895

    
1896
		case 'cisco':
1897
			return "$mac[0]$mac[1].$mac[2]$mac[3].$mac[4]$mac[5]";
1898

    
1899
		case 'unformatted':
1900
			return "$mac[0]$mac[1]$mac[2]$mac[3]$mac[4]$mac[5]";
1901

    
1902
		default:
1903
			return $clientmac;
1904
	}
1905
}
1906

    
1907
function resolve_retry($hostname, $retries = 5) {
1908

    
1909
	if (is_ipaddr($hostname)) {
1910
		return $hostname;
1911
	}
1912

    
1913
	for ($i = 0; $i < $retries; $i++) {
1914
		// FIXME: gethostbyname does not work for AAAA hostnames, boo, hiss
1915
		$ip = gethostbyname($hostname);
1916

    
1917
		if ($ip && $ip != $hostname) {
1918
			/* success */
1919
			return $ip;
1920
		}
1921

    
1922
		sleep(1);
1923
	}
1924

    
1925
	return false;
1926
}
1927

    
1928
function format_bytes($bytes) {
1929
	if ($bytes >= 1099511627776) {
1930
		return sprintf("%.2f TiB", $bytes/1099511627776);
1931
	} else if ($bytes >= 1073741824) {
1932
		return sprintf("%.2f GiB", $bytes/1073741824);
1933
	} else if ($bytes >= 1048576) {
1934
		return sprintf("%.2f MiB", $bytes/1048576);
1935
	} else if ($bytes >= 1024) {
1936
		return sprintf("%.0f KiB", $bytes/1024);
1937
	} else {
1938
		return sprintf("%d B", $bytes);
1939
	}
1940
}
1941

    
1942
function format_number($num, $precision = 3) {
1943
	$units = array('', 'K', 'M', 'G', 'T');
1944

    
1945
	$i = 0;
1946
	while ($num > 1000 && $i < count($units)) {
1947
		$num /= 1000;
1948
		$i++;
1949
	}
1950
	$num = round($num, $precision);
1951

    
1952
	return ("$num {$units[$i]}");
1953
}
1954

    
1955
function update_filter_reload_status($text, $new=false) {
1956
	global $g;
1957

    
1958
	if ($new) {
1959
		file_put_contents("{$g['varrun_path']}/filter_reload_status", $text  . PHP_EOL);
1960
	} else {
1961
		file_put_contents("{$g['varrun_path']}/filter_reload_status", $text  . PHP_EOL, FILE_APPEND);
1962
	}
1963
}
1964

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

    
1983
				if (empty($filter_regex) || preg_match($filter_regex, $file)) {
1984
					array_push($dir_array, $file);
1985
				}
1986
			}
1987
			closedir($dh);
1988
		}
1989
	}
1990
	return $dir_array;
1991
}
1992

    
1993
function run_plugins($directory) {
1994
	global $config, $g;
1995

    
1996
	/* process packager manager custom rules */
1997
	$files = return_dir_as_array($directory);
1998
	if (is_array($files)) {
1999
		foreach ($files as $file) {
2000
			if (stristr($file, ".sh") == true) {
2001
				mwexec($directory . $file . " start");
2002
			} else if (!is_dir($directory . "/" . $file) && stristr($file, ".inc")) {
2003
				require_once($directory . "/" . $file);
2004
			}
2005
		}
2006
	}
2007
}
2008

    
2009
/*
2010
 *    safe_mkdir($path, $mode = 0755)
2011
 *    create directory if it doesn't already exist and isn't a file!
2012
 */
2013
function safe_mkdir($path, $mode = 0755) {
2014
	global $g;
2015

    
2016
	if (!is_file($path) && !is_dir($path)) {
2017
		return @mkdir($path, $mode, true);
2018
	} else {
2019
		return false;
2020
	}
2021
}
2022

    
2023
/*
2024
 * get_sysctl($names)
2025
 * Get values of sysctl OID's listed in $names (accepts an array or a single
2026
 * name) and return an array of key/value pairs set for those that exist
2027
 */
2028
function get_sysctl($names) {
2029
	if (empty($names)) {
2030
		return array();
2031
	}
2032

    
2033
	if (is_array($names)) {
2034
		$name_list = array();
2035
		foreach ($names as $name) {
2036
			$name_list[] = escapeshellarg($name);
2037
		}
2038
	} else {
2039
		$name_list = array(escapeshellarg($names));
2040
	}
2041

    
2042
	exec("/sbin/sysctl -iq " . implode(" ", $name_list), $output);
2043
	$values = array();
2044
	foreach ($output as $line) {
2045
		$line = explode(": ", $line, 2);
2046
		if (count($line) == 2) {
2047
			$values[$line[0]] = $line[1];
2048
		}
2049
	}
2050

    
2051
	return $values;
2052
}
2053

    
2054
/*
2055
 * get_single_sysctl($name)
2056
 * Wrapper for get_sysctl() to simplify read of a single sysctl value
2057
 * return the value for sysctl $name or empty string if it doesn't exist
2058
 */
2059
function get_single_sysctl($name) {
2060
	if (empty($name)) {
2061
		return "";
2062
	}
2063

    
2064
	$value = get_sysctl($name);
2065
	if (empty($value) || !isset($value[$name])) {
2066
		return "";
2067
	}
2068

    
2069
	return $value[$name];
2070
}
2071

    
2072
/*
2073
 * set_sysctl($value_list)
2074
 * Set sysctl OID's listed as key/value pairs and return
2075
 * an array with keys set for those that succeeded
2076
 */
2077
function set_sysctl($values) {
2078
	if (empty($values)) {
2079
		return array();
2080
	}
2081

    
2082
	$value_list = array();
2083
	foreach ($values as $key => $value) {
2084
		$value_list[] = escapeshellarg($key) . "=" . escapeshellarg($value);
2085
	}
2086

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

    
2089
	/* Retry individually if failed (one or more read-only) */
2090
	if ($success <> 0 && count($value_list) > 1) {
2091
		foreach ($value_list as $value) {
2092
			exec("/sbin/sysctl -iq " . $value, $output);
2093
		}
2094
	}
2095

    
2096
	$ret = array();
2097
	foreach ($output as $line) {
2098
		$line = explode(": ", $line, 2);
2099
		if (count($line) == 2) {
2100
			$ret[$line[0]] = true;
2101
		}
2102
	}
2103

    
2104
	return $ret;
2105
}
2106

    
2107
/*
2108
 * set_single_sysctl($name, $value)
2109
 * Wrapper to set_sysctl() to make it simple to set only one sysctl
2110
 * returns boolean meaning if it succeeded
2111
 */
2112
function set_single_sysctl($name, $value) {
2113
	if (empty($name)) {
2114
		return false;
2115
	}
2116

    
2117
	$result = set_sysctl(array($name => $value));
2118

    
2119
	if (!isset($result[$name]) || $result[$name] != $value) {
2120
		return false;
2121
	}
2122

    
2123
	return true;
2124
}
2125

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

    
2140
function mute_kernel_msgs() {
2141
	global $g, $config;
2142

    
2143
	if ($config['system']['enableserial']) {
2144
		return;
2145
	}
2146
	exec("/sbin/conscontrol mute on");
2147
}
2148

    
2149
function unmute_kernel_msgs() {
2150
	global $g;
2151

    
2152
	exec("/sbin/conscontrol mute off");
2153
}
2154

    
2155
function start_devd() {
2156
	global $g;
2157

    
2158
	/* Use the undocumented -q options of devd to quiet its log spamming */
2159
	$_gb = exec("/sbin/devd -q -f /etc/{$g['product_name']}-devd.conf");
2160
	sleep(1);
2161
	unset($_gb);
2162
}
2163

    
2164
function is_interface_vlan_mismatch() {
2165
	global $config, $g;
2166

    
2167
	if (is_array($config['vlans']['vlan'])) {
2168
		foreach ($config['vlans']['vlan'] as $vlan) {
2169
			if (substr($vlan['if'], 0, 4) == "lagg") {
2170
				return false;
2171
			}
2172
			if (does_interface_exist($vlan['if']) == false) {
2173
				return true;
2174
			}
2175
		}
2176
	}
2177

    
2178
	return false;
2179
}
2180

    
2181
function is_interface_mismatch() {
2182
	global $config, $g;
2183

    
2184
	$do_assign = false;
2185
	$i = 0;
2186
	$missing_interfaces = array();
2187
	if (is_array($config['interfaces'])) {
2188
		foreach ($config['interfaces'] as $ifname => $ifcfg) {
2189
			if (interface_is_vlan($ifcfg['if']) != NULL ||
2190
			    interface_is_qinq($ifcfg['if']) != NULL ||
2191
			    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'])) {
2192
				// Do not check these interfaces.
2193
				$i++;
2194
				continue;
2195
			} else if (does_interface_exist($ifcfg['if']) == false) {
2196
				$missing_interfaces[] = $ifcfg['if'];
2197
				$do_assign = true;
2198
			} else {
2199
				$i++;
2200
			}
2201
		}
2202
	}
2203

    
2204
	if (file_exists("{$g['tmp_path']}/assign_complete")) {
2205
		$do_assign = false;
2206
	}
2207

    
2208
	if (!empty($missing_interfaces) && $do_assign) {
2209
		file_put_contents("{$g['tmp_path']}/missing_interfaces", implode(' ', $missing_interfaces));
2210
	} else {
2211
		@unlink("{$g['tmp_path']}/missing_interfaces");
2212
	}
2213

    
2214
	return $do_assign;
2215
}
2216

    
2217
/* sync carp entries to other firewalls */
2218
function carp_sync_client() {
2219
	global $g;
2220
	send_event("filter sync");
2221
}
2222

    
2223
/****f* util/isAjax
2224
 * NAME
2225
 *   isAjax - reports if the request is driven from prototype
2226
 * INPUTS
2227
 *   none
2228
 * RESULT
2229
 *   true/false
2230
 ******/
2231
function isAjax() {
2232
	return isset ($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest';
2233
}
2234

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

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

    
2300
/****f* util/is_URL
2301
 * NAME
2302
 *   is_URL
2303
 * INPUTS
2304
 *   string to check
2305
 * RESULT
2306
 *   Returns true if item is a URL
2307
 ******/
2308
function is_URL($url) {
2309
	$match = preg_match("'\b(([\w-]+://?|www[.])[^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|/)))'", $url);
2310
	if ($match) {
2311
		return true;
2312
	}
2313
	return false;
2314
}
2315

    
2316
function is_file_included($file = "") {
2317
	$files = get_included_files();
2318
	if (in_array($file, $files)) {
2319
		return true;
2320
	}
2321

    
2322
	return false;
2323
}
2324

    
2325
/*
2326
 * Replace a value on a deep associative array using regex
2327
 */
2328
function array_replace_values_recursive($data, $match, $replace) {
2329
	if (empty($data)) {
2330
		return $data;
2331
	}
2332

    
2333
	if (is_string($data)) {
2334
		$data = preg_replace("/{$match}/", $replace, $data);
2335
	} else if (is_array($data)) {
2336
		foreach ($data as $k => $v) {
2337
			$data[$k] = array_replace_values_recursive($v, $match, $replace);
2338
		}
2339
	}
2340

    
2341
	return $data;
2342
}
2343

    
2344
/*
2345
	This function was borrowed from a comment on PHP.net at the following URL:
2346
	http://www.php.net/manual/en/function.array-merge-recursive.php#73843
2347
 */
2348
function array_merge_recursive_unique($array0, $array1) {
2349

    
2350
	$arrays = func_get_args();
2351
	$remains = $arrays;
2352

    
2353
	// We walk through each arrays and put value in the results (without
2354
	// considering previous value).
2355
	$result = array();
2356

    
2357
	// loop available array
2358
	foreach ($arrays as $array) {
2359

    
2360
		// The first remaining array is $array. We are processing it. So
2361
		// we remove it from remaining arrays.
2362
		array_shift($remains);
2363

    
2364
		// We don't care non array param, like array_merge since PHP 5.0.
2365
		if (is_array($array)) {
2366
			// Loop values
2367
			foreach ($array as $key => $value) {
2368
				if (is_array($value)) {
2369
					// we gather all remaining arrays that have such key available
2370
					$args = array();
2371
					foreach ($remains as $remain) {
2372
						if (array_key_exists($key, $remain)) {
2373
							array_push($args, $remain[$key]);
2374
						}
2375
					}
2376

    
2377
					if (count($args) > 2) {
2378
						// put the recursion
2379
						$result[$key] = call_user_func_array(__FUNCTION__, $args);
2380
					} else {
2381
						foreach ($value as $vkey => $vval) {
2382
							if (!is_array($result[$key])) {
2383
								$result[$key] = array();
2384
							}
2385
							$result[$key][$vkey] = $vval;
2386
						}
2387
					}
2388
				} else {
2389
					// simply put the value
2390
					$result[$key] = $value;
2391
				}
2392
			}
2393
		}
2394
	}
2395
	return $result;
2396
}
2397

    
2398

    
2399
/*
2400
 * converts a string like "a,b,c,d"
2401
 * into an array like array("a" => "b", "c" => "d")
2402
 */
2403
function explode_assoc($delimiter, $string) {
2404
	$array = explode($delimiter, $string);
2405
	$result = array();
2406
	$numkeys = floor(count($array) / 2);
2407
	for ($i = 0; $i < $numkeys; $i += 1) {
2408
		$result[$array[$i * 2]] = $array[$i * 2 + 1];
2409
	}
2410
	return $result;
2411
}
2412

    
2413
/*
2414
 * Given a string of text with some delimiter, look for occurrences
2415
 * of some string and replace all of those.
2416
 * $text - the text string (e.g. "abc,defg,x123,ipv4,xyz")
2417
 * $delimiter - the delimiter (e.g. ",")
2418
 * $element - the element to match (e.g. "defg")
2419
 * $replacement - the string to replace it with (e.g. "42")
2420
 * Returns the resulting delimited string (e.g. "abc,42,x123,ipv4,xyz")
2421
 */
2422
function replace_element_in_list($text, $delimiter, $element, $replacement) {
2423
	$textArray = explode($delimiter, $text);
2424
	while (($entry = array_search($element, $textArray)) !== false) {
2425
		$textArray[$entry] = $replacement;
2426
	}
2427
	return implode(',', $textArray);
2428
}
2429

    
2430
/* Try to change a static route, if it doesn't exist, add it */
2431
function route_add_or_change($args) {
2432
	global $config;
2433

    
2434
	if (empty($args)) {
2435
		return false;
2436
	}
2437

    
2438
	/* First, try to add it */
2439
	$_gb = exec(escapeshellcmd("/sbin/route add " . $args), $output, $rc);
2440
		
2441
	if (isset($config['system']['route-debug'])) {
2442
		$add_change = 'add';
2443
		$mt = microtime();
2444
		log_error("ROUTING debug: $mt - ADD RC={$rc} - $args");
2445
	}
2446

    
2447
	if ($rc != 0) {
2448
		/* If it fails, try to change it */
2449
		$_gb = exec(escapeshellcmd("/sbin/route change " . $args),
2450
		    $output, $rc);
2451

    
2452
		if (isset($config['system']['route-debug'])) {
2453
			$add_change = 'change';
2454
			$mt = microtime();
2455
			log_error("ROUTING debug: $mt - CHG RC={$rc} - $args");
2456
		}
2457
	}
2458
	if (isset($config['system']['route-debug'])) {
2459
		file_put_contents("/dev/console", "\n[".getmypid()."] ROUTE: {$add_change} {$args} result: {$rc}");
2460
	}
2461

    
2462
	return ($rc == 0);
2463
}
2464

    
2465
function alias_to_subnets_recursive($name, $returnhostnames = false) {
2466
	global $aliastable;
2467
	$result = array();
2468
	if (!isset($aliastable[$name])) {
2469
		return $result;
2470
	}
2471
	$subnets = preg_split('/\s+/', $aliastable[$name]);
2472
	foreach ($subnets as $net) {
2473
		if (is_alias($net)) {
2474
			$sub = alias_to_subnets_recursive($net, $returnhostnames);
2475
			$result = array_merge($result, $sub);
2476
			continue;
2477
		} elseif (!is_subnet($net)) {
2478
			if (is_ipaddrv4($net)) {
2479
				$net .= "/32";
2480
			} else if (is_ipaddrv6($net)) {
2481
				$net .= "/128";
2482
			} else if ($returnhostnames === false || !is_fqdn($net)) {
2483
				continue;
2484
			}
2485
		}
2486
		$result[] = $net;
2487
	}
2488
	return $result;
2489
}
2490

    
2491
function get_staticroutes($returnsubnetsonly = false, $returnhostnames = false, $returnenabledroutesonly = false) {
2492
	global $config, $aliastable;
2493

    
2494
	/* Bail if there are no routes, but return an array always so callers don't have to check. */
2495
	if (!is_array($config['staticroutes']['route'])) {
2496
		return array();
2497
	}
2498

    
2499
	$allstaticroutes = array();
2500
	$allsubnets = array();
2501
	/* Loop through routes and expand aliases as we find them. */
2502
	foreach ($config['staticroutes']['route'] as $route) {
2503
		if ($returnenabledroutesonly && isset($route['disabled'])) {
2504
			continue;
2505
		}
2506

    
2507
		if (is_alias($route['network'])) {
2508
			foreach (alias_to_subnets_recursive($route['network'], $returnhostnames) as $net) {
2509
				$temproute = $route;
2510
				$temproute['network'] = $net;
2511
				$allstaticroutes[] = $temproute;
2512
				$allsubnets[] = $net;
2513
			}
2514
		} elseif (is_subnet($route['network'])) {
2515
			$allstaticroutes[] = $route;
2516
			$allsubnets[] = $route['network'];
2517
		}
2518
	}
2519
	if ($returnsubnetsonly) {
2520
		return $allsubnets;
2521
	} else {
2522
		return $allstaticroutes;
2523
	}
2524
}
2525

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

    
2556
/* returns an array consisting of every element of $haystack that is not equal to $needle. */
2557
function array_exclude($needle, $haystack) {
2558
	$result = array();
2559
	if (is_array($haystack)) {
2560
		foreach ($haystack as $thing) {
2561
			if ($needle !== $thing) {
2562
				$result[] = $thing;
2563
			}
2564
		}
2565
	}
2566
	return $result;
2567
}
2568

    
2569
/* Define what is preferred, IPv4 or IPv6 */
2570
function prefer_ipv4_or_ipv6() {
2571
	global $config;
2572

    
2573
	if (isset($config['system']['prefer_ipv4'])) {
2574
		mwexec("/etc/rc.d/ip6addrctl prefer_ipv4");
2575
	} else {
2576
		mwexec("/etc/rc.d/ip6addrctl prefer_ipv6");
2577
	}
2578
}
2579

    
2580
/* Redirect to page passing parameters via POST */
2581
function post_redirect($page, $params) {
2582
	if (!is_array($params)) {
2583
		return;
2584
	}
2585

    
2586
	print "<html><body><form action=\"{$page}\" name=\"formredir\" method=\"post\">\n";
2587
	foreach ($params as $key => $value) {
2588
		print "<input type=\"hidden\" name=\"{$key}\" value=\"{$value}\" />\n";
2589
	}
2590
	print "</form>\n";
2591
	print "<script type=\"text/javascript\">\n";
2592
	print "//<![CDATA[\n";
2593
	print "document.formredir.submit();\n";
2594
	print "//]]>\n";
2595
	print "</script>\n";
2596
	print "</body></html>\n";
2597
}
2598

    
2599
/* Locate disks that can be queried for S.M.A.R.T. data. */
2600
function get_smart_drive_list() {
2601
	$disk_list = explode(" ", get_single_sysctl("kern.disks"));
2602
	foreach ($disk_list as $id => $disk) {
2603
		// We only want certain kinds of disks for S.M.A.R.T.
2604
		// 1 is a match, 0 is no match, False is any problem processing the regex
2605
		if (preg_match("/^(ad|da|ada).*[0-9]{1,2}$/", $disk) !== 1) {
2606
			unset($disk_list[$id]);
2607
		}
2608
	}
2609
	sort($disk_list);
2610
	return $disk_list;
2611
}
2612

    
2613
// Validate a network address
2614
//	$addr: the address to validate
2615
//	$type: IPV4|IPV6|IPV4V6
2616
//	$label: the label used by the GUI to display this value. Required to compose an error message
2617
//	$err_msg: pointer to the callers error message array so that error messages can be added to it here
2618
//	$alias: are aliases permitted for this address?
2619
// Returns:
2620
//	IPV4 - if $addr is a valid IPv4 address
2621
//	IPV6 - if $addr is a valid IPv6 address
2622
//	ALIAS - if $alias=true and $addr is an alias
2623
//	false - otherwise
2624

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

    
2678
	return false;
2679
}
2680

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

    
2703
	/* Make hexstrings */
2704
	if ($duidtype) {
2705
		switch ($duidtype) {
2706
		/* Add a hardware type to DUID-LLT and DUID-LL; assume Ethernet */
2707
		case 1:
2708
		case 3:
2709
			$duidpt1 = '00:01:' . $duidpt1;
2710
			break;
2711
		/* Remove '-' from given UUID and insert ':' every 2 characters */
2712
		case 4:
2713
			$duidpt1 = implode(':', str_split(str_replace('-', '', $duidpt1), 2));
2714
			break;
2715
		default:
2716
		}
2717
		$duidpt1 = '00:0' . $duidtype . ':' . $duidpt1;
2718
	}
2719

    
2720
	$values = explode(':', strtolower(str_replace('-', ':', $duidpt1)));
2721

    
2722
	if (hexdec($values[0]) != count($values) - 2)
2723
		array_unshift($values, dechex(count($values)), '00');
2724

    
2725
	array_walk($values, function(&$value) {
2726
		$value = str_pad($value, 2, '0', STR_PAD_LEFT);
2727
	});
2728

    
2729
	return implode(":", $values);
2730
}
2731

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

    
2762
	for ($i = 0; $i < count($values); $i++) {
2763
		if (ctype_xdigit($values[$i]) == false)
2764
			return false;
2765
		if (hexdec($values[$i]) < 0 || hexdec($values[$i]) > 255)
2766
			return false;
2767
	}
2768

    
2769
	return true;
2770
}
2771

    
2772
/* Write the DHCP6 DUID file */
2773
function write_dhcp6_duid($duidstring) {
2774
	// Create the hex array from the dhcp6duid config entry and write to file
2775
	global $g;
2776

    
2777
	if(!is_duid($duidstring)) {
2778
		log_error(gettext("Error: attempting to write DUID file - Invalid DUID detected"));
2779
		return false;
2780
	}
2781
	$temp = str_replace(":","",$duidstring);
2782
	$duid_binstring = pack("H*",$temp);
2783
	if ($fd = fopen("{$g['vardb_path']}/dhcp6c_duid", "wb")) {
2784
		fwrite($fd, $duid_binstring);
2785
		fclose($fd);
2786
		return true;
2787
	}
2788
	log_error(gettext("Error: attempting to write DUID file - File write error"));
2789
	return false;
2790
}
2791

    
2792
/* returns duid string from 'vardb_path']}/dhcp6c_duid' */
2793
function get_duid_from_file() {
2794
	global $g;
2795

    
2796
	$duid_ASCII = "";
2797
	$count = 0;
2798

    
2799
	if (file_exists("{$g['vardb_path']}/dhcp6c_duid") &&
2800
	    ($fd = fopen("{$g['vardb_path']}/dhcp6c_duid", "r"))) {
2801
		$fsize = filesize("{$g['vardb_path']}/dhcp6c_duid");
2802
		if ($fsize <= 132) {
2803
			$buffer = fread($fd, $fsize);
2804
			while($count < $fsize) {
2805
				$duid_ASCII .= bin2hex($buffer[$count]);
2806
				$count++;
2807
				if($count < $fsize) {
2808
					$duid_ASCII .= ":";
2809
				}
2810
			}
2811
		}
2812
		fclose($fd);
2813
	}
2814
	//if no file or error with read then the string returns blanked DUID string
2815
	if(!is_duid($duid_ASCII)) {
2816
		return "--:--:--:--:--:--:--:--:--:--:--:--:--:--:--:--";
2817
	}
2818
	return($duid_ASCII);
2819
}
2820

    
2821
/* Replaces the Mac OS 9 and earlier (\r) and DOS/Windows (\r\n) newlines with the Unix equivalent (\n). */
2822
function unixnewlines($text) {
2823
	return preg_replace('/\r\n?/', "\n", $text);
2824
}
2825

    
2826
?>
(52-52/60)