Project

General

Profile

Download (72.3 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
/*
3
 * util.inc
4
 *
5
 * part of pfSense (https://www.pfsense.org)
6
 * Copyright (c) 2004-2016 Rubicon Communications, LLC (Netgate)
7
 * All rights reserved.
8
 *
9
 * originally part of m0n0wall (http://m0n0.ch/wall)
10
 * Copyright (c) 2003-2004 Manuel Kasper <mk@neon1.net>.
11
 * All rights reserved.
12
 *
13
 * Redistribution and use in source and binary forms, with or without
14
 * modification, are permitted provided that the following conditions are met:
15
 *
16
 * 1. Redistributions of source code must retain the above copyright notice,
17
 *    this list of conditions and the following disclaimer.
18
 *
19
 * 2. Redistributions in binary form must reproduce the above copyright
20
 *    notice, this list of conditions and the following disclaimer in
21
 *    the documentation and/or other materials provided with the
22
 *    distribution.
23
 *
24
 * 3. All advertising materials mentioning features or use of this software
25
 *    must display the following acknowledgment:
26
 *    "This product includes software developed by the pfSense Project
27
 *    for use in the pfSense® software distribution. (http://www.pfsense.org/).
28
 *
29
 * 4. The names "pfSense" and "pfSense Project" must not be used to
30
 *    endorse or promote products derived from this software without
31
 *    prior written permission. For written permission, please contact
32
 *    coreteam@pfsense.org.
33
 *
34
 * 5. Products derived from this software may not be called "pfSense"
35
 *    nor may "pfSense" appear in their names without prior written
36
 *    permission of the Electric Sheep Fencing, LLC.
37
 *
38
 * 6. Redistributions of any form whatsoever must retain the following
39
 *    acknowledgment:
40
 *
41
 * "This product includes software developed by the pfSense Project
42
 * for use in the pfSense software distribution (http://www.pfsense.org/).
43
 *
44
 * THIS SOFTWARE IS PROVIDED BY THE pfSense PROJECT ``AS IS'' AND ANY
45
 * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
46
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
47
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE pfSense PROJECT OR
48
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
49
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
50
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
51
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
52
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
53
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
54
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
55
 * OF THE POSSIBILITY OF SUCH DAMAGE.
56
 */
57

    
58
define('VIP_ALL', 1);
59
define('VIP_CARP', 2);
60
define('VIP_IPALIAS', 3);
61

    
62
/* kill a process by pid file */
63
function killbypid($pidfile) {
64
	return sigkillbypid($pidfile, "TERM");
65
}
66

    
67
function isvalidpid($pidfile) {
68
	$output = "";
69
	if (file_exists($pidfile)) {
70
		exec("/bin/pgrep -nF {$pidfile}", $output, $retval);
71
		return (intval($retval) == 0);
72
	}
73
	return false;
74
}
75

    
76
function is_process_running($process) {
77
	$output = "";
78
	exec("/bin/pgrep -anx " . escapeshellarg($process), $output, $retval);
79

    
80
	return (intval($retval) == 0);
81
}
82

    
83
function isvalidproc($proc) {
84
	return is_process_running($proc);
85
}
86

    
87
/* sigkill a process by pid file */
88
/* return 1 for success and 0 for a failure */
89
function sigkillbypid($pidfile, $sig) {
90
	if (file_exists($pidfile)) {
91
		return mwexec("/bin/pkill " . escapeshellarg("-{$sig}") . " -F {$pidfile}", true);
92
	}
93

    
94
	return 0;
95
}
96

    
97
/* kill a process by name */
98
function sigkillbyname($procname, $sig) {
99
	if (isvalidproc($procname)) {
100
		return mwexec("/usr/bin/killall " . escapeshellarg("-{$sig}") . " " . escapeshellarg($procname), true);
101
	}
102
}
103

    
104
/* kill a process by name */
105
function killbyname($procname) {
106
	if (isvalidproc($procname)) {
107
		mwexec("/usr/bin/killall " . escapeshellarg($procname));
108
	}
109
}
110

    
111
function is_subsystem_dirty($subsystem = "") {
112
	global $g;
113

    
114
	if ($subsystem == "") {
115
		return false;
116
	}
117

    
118
	if (file_exists("{$g['varrun_path']}/{$subsystem}.dirty")) {
119
		return true;
120
	}
121

    
122
	return false;
123
}
124

    
125
function mark_subsystem_dirty($subsystem = "") {
126
	global $g;
127

    
128
	if (!file_put_contents("{$g['varrun_path']}/{$subsystem}.dirty", "DIRTY")) {
129
		log_error(sprintf(gettext("WARNING: Could not mark subsystem: %s dirty"), $subsystem));
130
	}
131
}
132

    
133
function clear_subsystem_dirty($subsystem = "") {
134
	global $g;
135

    
136
	@unlink("{$g['varrun_path']}/{$subsystem}.dirty");
137
}
138

    
139
/* lock configuration file */
140
function lock($lock, $op = LOCK_SH) {
141
	global $g;
142
	if (!$lock) {
143
		die(gettext("WARNING: A name must be given as parameter to lock() function."));
144
	}
145
	if (!file_exists("{$g['tmp_path']}/{$lock}.lock")) {
146
		@touch("{$g['tmp_path']}/{$lock}.lock");
147
		@chmod("{$g['tmp_path']}/{$lock}.lock", 0666);
148
	}
149
	if ($fp = fopen("{$g['tmp_path']}/{$lock}.lock", "w")) {
150
		if (flock($fp, $op)) {
151
			return $fp;
152
		} else {
153
			fclose($fp);
154
		}
155
	}
156
}
157

    
158
function try_lock($lock, $timeout = 5) {
159
	global $g;
160
	if (!$lock) {
161
		die(gettext("WARNING: A name must be given as parameter to try_lock() function."));
162
	}
163
	if (!file_exists("{$g['tmp_path']}/{$lock}.lock")) {
164
		@touch("{$g['tmp_path']}/{$lock}.lock");
165
		@chmod("{$g['tmp_path']}/{$lock}.lock", 0666);
166
	}
167
	if ($fp = fopen("{$g['tmp_path']}/{$lock}.lock", "w")) {
168
		$trycounter = 0;
169
		while (!flock($fp, LOCK_EX | LOCK_NB)) {
170
			if ($trycounter >= $timeout) {
171
				fclose($fp);
172
				return NULL;
173
			}
174
			sleep(1);
175
			$trycounter++;
176
		}
177

    
178
		return $fp;
179
	}
180

    
181
	return NULL;
182
}
183

    
184
/* unlock configuration file */
185
function unlock($cfglckkey = 0) {
186
	global $g;
187
	flock($cfglckkey, LOCK_UN);
188
	fclose($cfglckkey);
189
	return;
190
}
191

    
192
/* unlock forcefully configuration file */
193
function unlock_force($lock) {
194
	global $g;
195

    
196
	@unlink("{$g['tmp_path']}/{$lock}.lock");
197
}
198

    
199
function send_event($cmd) {
200
	global $g;
201

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

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

    
224
function send_multiple_events($cmds) {
225
	global $g;
226

    
227
	if (!isset($g['event_address'])) {
228
		$g['event_address'] = "unix:///var/run/check_reload_status";
229
	}
230

    
231
	if (!is_array($cmds)) {
232
		return;
233
	}
234

    
235
	while ($try < 3) {
236
		$fd = @fsockopen($g['event_address']);
237
		if ($fd) {
238
			foreach ($cmds as $cmd) {
239
				fwrite($fd, $cmd);
240
				$resp = fread($fd, 4096);
241
				if ($resp != "OK\n") {
242
					log_error("send_event: sent {$cmd} got {$resp}");
243
				}
244
			}
245
			fclose($fd);
246
			$try = 3;
247
		} else if (!is_process_running("check_reload_status")) {
248
			mwexec_bg("/usr/bin/nice -n20 /usr/local/sbin/check_reload_status");
249
		}
250
		$try++;
251
	}
252
}
253

    
254
function refcount_init($reference) {
255
	$shmid = @shmop_open($reference, "c", 0644, 10);
256
	@shmop_write($shmid, str_pad("0", 10, "\x0", STR_PAD_RIGHT), 0);
257
	@shmop_close($shmid);
258
}
259

    
260
function refcount_reference($reference) {
261
	/* Take out a lock across the shared memory read, increment, write sequence to make it atomic. */
262
	$shm_lck = lock("shm{$reference}", LOCK_EX);
263
	try {
264
		/* NOTE: A warning is generated when shared memory does not exist */
265
		$shmid = @shmop_open($reference, "w", 0, 0);
266
		if (!$shmid) {
267
			refcount_init($reference);
268
			$shmid = @shmop_open($reference, "w", 0, 0);
269
			if (!$shmid) {
270
				log_error(sprintf(gettext("Could not open shared memory %s"), $reference));
271
				unlock($shm_lck);
272
				return;
273
			}
274
		}
275
		$shm_data = @shmop_read($shmid, 0, 10);
276
		$shm_data = intval($shm_data) + 1;
277
		@shmop_write($shmid, str_pad($shm_data, 10, "\x0", STR_PAD_RIGHT), 0);
278
		@shmop_close($shmid);
279
		unlock($shm_lck);
280
	} catch (Exception $e) {
281
		log_error($e->getMessage());
282
		unlock($shm_lck);
283
	}
284

    
285
	return $shm_data;
286
}
287

    
288
function refcount_unreference($reference) {
289
	/* Take out a lock across the shared memory read, decrement, write sequence to make it atomic. */
290
	$shm_lck = lock("shm{$reference}", LOCK_EX);
291
	try {
292
		$shmid = @shmop_open($reference, "w", 0, 0);
293
		if (!$shmid) {
294
			refcount_init($reference);
295
			log_error(sprintf(gettext("Could not open shared memory %s"), $reference));
296
			unlock($shm_lck);
297
			return;
298
		}
299
		$shm_data = @shmop_read($shmid, 0, 10);
300
		$shm_data = intval($shm_data) - 1;
301
		if ($shm_data < 0) {
302
			//debug_backtrace();
303
			log_error(sprintf(gettext("Reference %s is going negative, not doing unreference."), $reference));
304
		} else {
305
			@shmop_write($shmid, str_pad($shm_data, 10, "\x0", STR_PAD_RIGHT), 0);
306
		}
307
		@shmop_close($shmid);
308
		unlock($shm_lck);
309
	} catch (Exception $e) {
310
		log_error($e->getMessage());
311
		unlock($shm_lck);
312
	}
313

    
314
	return $shm_data;
315
}
316

    
317
function refcount_read($reference) {
318
	/* This function just reads the current value of the refcount for information. */
319
	/* There is no need for locking. */
320
	$shmid = @shmop_open($reference, "a", 0, 0);
321
	if (!$shmid) {
322
		log_error(sprintf(gettext("Could not open shared memory for read %s"), $reference));
323
		return -1;
324
	}
325
	$shm_data = @shmop_read($shmid, 0, 10);
326
	@shmop_close($shmid);
327
	return $shm_data;
328
}
329

    
330
function is_module_loaded($module_name) {
331
	$module_name = str_replace(".ko", "", $module_name);
332
	$running = 0;
333
	$_gb = exec("/sbin/kldstat -qn {$module_name} 2>&1", $_gb, $running);
334
	if (intval($running) == 0) {
335
		return true;
336
	} else {
337
		return false;
338
	}
339
}
340

    
341
/* validate non-negative numeric string, or equivalent numeric variable */
342
function is_numericint($arg) {
343
	return (((is_int($arg) && $arg >= 0) || (is_string($arg) && strlen($arg) > 0 && ctype_digit($arg))) ? true : false);
344
}
345

    
346
/* Generate the (human readable) ipv4 or ipv6 subnet address (i.e., netmask, or subnet start IP)
347
   given an (human readable) ipv4 or ipv6 host address and subnet bit count */
348
function gen_subnet($ipaddr, $bits) {
349
	if (($sn = gen_subnetv6($ipaddr, $bits)) == '') {
350
		$sn = gen_subnetv4($ipaddr, $bits);  // try to avoid rechecking IPv4/v6
351
	}
352
	return $sn;
353
}
354

    
355
/* same as gen_subnet() but accepts IPv4 only */
356
function gen_subnetv4($ipaddr, $bits) {
357
	if (is_ipaddrv4($ipaddr) && is_numericint($bits) && $bits <= 32) {
358
		if ($bits == 0) {
359
			return '0.0.0.0';  // avoids <<32
360
		}
361
		return long2ip(ip2long($ipaddr) & ((0xFFFFFFFF << (32 - $bits)) & 0xFFFFFFFF));
362
	}
363
	return "";
364
}
365

    
366
/* same as gen_subnet() but accepts IPv6 only */
367
function gen_subnetv6($ipaddr, $bits) {
368
	if (is_ipaddrv6($ipaddr) && is_numericint($bits) && $bits <= 128) {
369
		return text_to_compressed_ip6(Net_IPv6::getNetmask($ipaddr, $bits));
370
	}
371
	return "";
372
}
373

    
374
/* Generate the (human readable) ipv4 or ipv6 subnet end address (i.e., highest address, end IP, or IPv4 broadcast address)
375
   given an (human readable) ipv4 or ipv6 host address and subnet bit count. */
376
function gen_subnet_max($ipaddr, $bits) {
377
	if (($sn = gen_subnetv6_max($ipaddr, $bits)) == '') {
378
		$sn = gen_subnetv4_max($ipaddr, $bits);  // try to avoid rechecking IPv4/v6
379
	}
380
	return $sn;
381
}
382

    
383
/* same as gen_subnet_max() but validates IPv4 only */
384
function gen_subnetv4_max($ipaddr, $bits) {
385
	if (is_ipaddrv4($ipaddr) && is_numericint($bits) && $bits <= 32) {
386
		if ($bits == 32) {
387
			return $ipaddr;
388
		}
389
		return long2ip32(ip2long($ipaddr) | (~gen_subnet_mask_long($bits) & 0xFFFFFFFF));
390
	}
391
	return "";
392
}
393

    
394
/* same as gen_subnet_max() but validates IPv6 only */
395
function gen_subnetv6_max($ipaddr, $bits) {
396
	if (is_ipaddrv6($ipaddr) && is_numericint($bits) && $bits <= 128) {
397
		$endip_bin = substr(Net_IPv6::_ip2Bin($ipaddr), 0, $bits) . str_repeat('1', 128 - $bits);
398
		return Net_IPv6::compress(Net_IPv6::_bin2Ip($endip_bin));
399
	}
400
	return "";
401
}
402

    
403
/* returns a subnet mask (long given a bit count) */
404
function gen_subnet_mask_long($bits) {
405
	$sm = 0;
406
	for ($i = 0; $i < $bits; $i++) {
407
		$sm >>= 1;
408
		$sm |= 0x80000000;
409
	}
410
	return $sm;
411
}
412

    
413
/* same as above but returns a string */
414
function gen_subnet_mask($bits) {
415
	return long2ip(gen_subnet_mask_long($bits));
416
}
417

    
418
/* Convert a prefix length to an IPv6 address-like mask notation. Very rare but at least ntp needs it. See #4463 */
419
function gen_subnet_mask_v6($bits) {
420
	/* Binary representation of the prefix length */
421
	$bin = str_repeat('1', $bits);
422
	/* Pad right with zeroes to reach the full address length */
423
	$bin = str_pad($bin, 128, '0', STR_PAD_RIGHT);
424
	/* Convert back to an IPv6 address style notation */
425
	return Net_IPv6::_bin2Ip($bin);
426
}
427

    
428
/* Convert long int to IPv4 address
429
   Returns '' if not valid IPv4 (including if any bits >32 are non-zero) */
430
function long2ip32($ip) {
431
	return long2ip($ip & 0xFFFFFFFF);
432
}
433

    
434
/* Convert IPv4 address to long int, truncated to 32-bits to avoid sign extension on 64-bit platforms.
435
   Returns '' if not valid IPv4. */
436
function ip2long32($ip) {
437
	return (ip2long($ip) & 0xFFFFFFFF);
438
}
439

    
440
/* Convert IPv4 address to unsigned long int.
441
   Returns '' if not valid IPv4. */
442
function ip2ulong($ip) {
443
	return sprintf("%u", ip2long32($ip));
444
}
445

    
446
/*
447
 * Convert textual IPv6 address string to compressed address
448
 */
449
function text_to_compressed_ip6($text) {
450
	// Force re-compression by passing parameter 2 (force) true.
451
	// This ensures that supposedly-compressed formats are uncompressed
452
	// first then re-compressed into strictly correct form.
453
	// e.g. 2001:0:0:4:0:0:0:1
454
	// 2001::4:0:0:0:1 is a strictly-incorrect compression,
455
	// but maybe the user entered it like that.
456
	// The "force" parameter will ensure it is returned as:
457
	// 2001:0:0:4::1
458
	return Net_IPv6::compress($text, true);
459
}
460

    
461
/* Find out how many IPs are contained within a given IP range
462
 *  e.g. 192.168.0.0 to 192.168.0.255 returns 256
463
 */
464
function ip_range_size_v4($startip, $endip) {
465
	if (is_ipaddrv4($startip) && is_ipaddrv4($endip)) {
466
		// Operate as unsigned long because otherwise it wouldn't work
467
		//   when crossing over from 127.255.255.255 / 128.0.0.0 barrier
468
		return abs(ip2ulong($startip) - ip2ulong($endip)) + 1;
469
	}
470
	return -1;
471
}
472

    
473
/* Find the smallest possible subnet mask which can contain a given number of IPs
474
 *  e.g. 512 IPs can fit in a /23, but 513 IPs need a /22
475
 */
476
function find_smallest_cidr_v4($number) {
477
	$smallest = 1;
478
	for ($b=32; $b > 0; $b--) {
479
		$smallest = ($number <= pow(2, $b)) ? $b : $smallest;
480
	}
481
	return (32-$smallest);
482
}
483

    
484
/* Return the previous IP address before the given address */
485
function ip_before($ip, $offset = 1) {
486
	return long2ip32(ip2long($ip) - $offset);
487
}
488

    
489
/* Return the next IP address after the given address */
490
function ip_after($ip, $offset = 1) {
491
	return long2ip32(ip2long($ip) + $offset);
492
}
493

    
494
/* Return true if the first IP is 'before' the second */
495
function ip_less_than($ip1, $ip2) {
496
	// Compare as unsigned long because otherwise it wouldn't work when
497
	//   crossing over from 127.255.255.255 / 128.0.0.0 barrier
498
	return ip2ulong($ip1) < ip2ulong($ip2);
499
}
500

    
501
/* Return true if the first IP is 'after' the second */
502
function ip_greater_than($ip1, $ip2) {
503
	// Compare as unsigned long because otherwise it wouldn't work
504
	//   when crossing over from 127.255.255.255 / 128.0.0.0 barrier
505
	return ip2ulong($ip1) > ip2ulong($ip2);
506
}
507

    
508
/* compare two IP addresses */
509
function ipcmp($a, $b) {
510
	if (ip_less_than($a, $b)) {
511
		return -1;
512
	} else if (ip_greater_than($a, $b)) {
513
		return 1;
514
	} else {
515
		return 0;
516
	}
517
}
518

    
519
/* Convert a range of IPv4 addresses to an array of individual addresses. */
520
/* Note: IPv6 ranges are not yet supported here. */
521
function ip_range_to_address_array($startip, $endip, $max_size = 5000) {
522
	if (!is_ipaddrv4($startip) || !is_ipaddrv4($endip)) {
523
		return false;
524
	}
525

    
526
	if (ip_greater_than($startip, $endip)) {
527
		// Swap start and end so we can process sensibly.
528
		$temp = $startip;
529
		$startip = $endip;
530
		$endip = $temp;
531
	}
532

    
533
	if (ip_range_size_v4($startip, $endip) > $max_size) {
534
		return false;
535
	}
536

    
537
	// Container for IP addresses within this range.
538
	$rangeaddresses = array();
539
	$end_int = ip2ulong($endip);
540
	for ($ip_int = ip2ulong($startip); $ip_int <= $end_int; $ip_int++) {
541
		$rangeaddresses[] = long2ip($ip_int);
542
	}
543

    
544
	return $rangeaddresses;
545
}
546

    
547
/* 	Convert an IPv4 or IPv6 IP range to an array of subnets which can contain the range.
548
	Algorithm and embodying code PD'ed by Stilez - enjoy as you like :-)
549

    
550
	Documented on pfsense dev list 19-20 May 2013. Summary:
551

    
552
	The algorithm looks at patterns of 0's and 1's in the least significant bit(s), whether IPv4 or IPv6.
553
	These are all that needs checking to identify a _guaranteed_ correct, minimal and optimal subnet array.
554

    
555
	As a result, string/binary pattern matching of the binary IP is very efficient. It uses just 2 pattern-matching rules
556
	to chop off increasingly larger subnets at both ends that can't be part of larger subnets, until nothing's left.
557

    
558
	(a) If any range has EITHER low bit 1 (in startip) or 0 (in endip), that end-point is _always guaranteed_ to be optimally
559
	represented by its own 'single IP' CIDR; the remaining range then shrinks by one IP up or down, causing the new end-point's
560
	low bit to change from 1->0 (startip) or 0->1 (endip). Only one edge case needs checking: if a range contains exactly 2
561
	adjacent IPs of this format, then the two IPs themselves are required to span it, and we're done.
562
	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
563
	low bits can now be ignored.
564

    
565
	(b) If any range has BOTH startip and endip ending in some number of 0's and 1's respectively, these low bits can
566
	*always* be ignored and "bit-shifted" for subnet spanning. So provided we remember the bits we've place-shifted, we can
567
	_always_ right-shift and chop off those bits, leaving a smaller range that has EITHER startip ending in 1 or endip ending
568
	in 0 (ie can now apply (a) again) or the entire range has vanished and we're done.
569
	We then loop to redo (a) again on the remaining (place shifted) range until after a few loops, the remaining (place shifted)
570
	range 'vanishes' by meeting the exit criteria of (a) or (b), and we're done.
571
*/
572

    
573
function ip_range_to_subnet_array($ip1, $ip2) {
574

    
575
	if (is_ipaddrv4($ip1) && is_ipaddrv4($ip2)) {
576
		$proto = 'ipv4';  // for clarity
577
		$bits = 32;
578
		$ip1bin = decbin(ip2long32($ip1));
579
		$ip2bin = decbin(ip2long32($ip2));
580
	} elseif (is_ipaddrv6($ip1) && is_ipaddrv6($ip2)) {
581
		$proto = 'ipv6';
582
		$bits = 128;
583
		$ip1bin = Net_IPv6::_ip2Bin($ip1);
584
		$ip2bin = Net_IPv6::_ip2Bin($ip2);
585
	} else {
586
		return array();
587
	}
588

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

    
593
	if ($ip1bin == $ip2bin) {
594
		return array($ip1 . '/' . $bits); // exit if ip1=ip2 (trivial case)
595
	}
596

    
597
	if ($ip1bin > $ip2bin) {
598
		list ($ip1bin, $ip2bin) = array($ip2bin, $ip1bin);  // swap if needed (ensures ip1 < ip2)
599
	}
600

    
601
	$rangesubnets = array();
602
	$netsize = 0;
603

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

    
608
		// 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)
609

    
610
		if (substr($ip1bin, -1, 1) == '1') {
611
			// the start ip must be in a separate one-IP cidr range
612
			$new_subnet_ip = substr($ip1bin, $netsize, $bits - $netsize) . str_repeat('0', $netsize);
613
			$rangesubnets[$new_subnet_ip] = $bits - $netsize;
614
			$n = strrpos($ip1bin, '0');  //can't be all 1's
615
			$ip1bin = ($n == 0 ? '' : substr($ip1bin, 0, $n)) . '1' . str_repeat('0', $bits - $n - 1);  // BINARY VERSION OF $ip1 += 1
616
		}
617

    
618
		// 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)
619

    
620
		if (substr($ip2bin, -1, 1) == '0') {
621
			// the end ip must be in a separate one-IP cidr range
622
			$new_subnet_ip = substr($ip2bin, $netsize, $bits - $netsize) . str_repeat('0', $netsize);
623
			$rangesubnets[$new_subnet_ip] = $bits - $netsize;
624
			$n = strrpos($ip2bin, '1');  //can't be all 0's
625
			$ip2bin = ($n == 0 ? '' : substr($ip2bin, 0, $n)) . '0' . str_repeat('1', $bits - $n - 1);  // BINARY VERSION OF $ip2 -= 1
626
			// already checked for the edge case where end = start+1 and start ends in 0x1, above, so it's safe
627
		}
628

    
629
		// this is the only edge case arising from increment/decrement.
630
		// 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)
631

    
632
		if ($ip2bin < $ip1bin) {
633
			continue;
634
		}
635

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

    
639
		$shift = $bits - max(strrpos($ip1bin, '0'), strrpos($ip2bin, '1'));  // num of low bits which are '0' in ip1 and '1' in ip2
640
		$ip1bin = str_repeat('0', $shift) . substr($ip1bin, 0, $bits - $shift);
641
		$ip2bin = str_repeat('0', $shift) . substr($ip2bin, 0, $bits - $shift);
642
		$netsize += $shift;
643
		if ($ip1bin == $ip2bin) {
644
			// we're done.
645
			$new_subnet_ip = substr($ip1bin, $netsize, $bits - $netsize) . str_repeat('0', $netsize);
646
			$rangesubnets[$new_subnet_ip] = $bits - $netsize;
647
			continue;
648
		}
649

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

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

    
655
	ksort($rangesubnets, SORT_STRING);
656
	$out = array();
657

    
658
	foreach ($rangesubnets as $ip => $netmask) {
659
		if ($proto == 'ipv4') {
660
			$i = str_split($ip, 8);
661
			$out[] = implode('.', array(bindec($i[0]), bindec($i[1]), bindec($i[2]), bindec($i[3]))) . '/' . $netmask;
662
		} else {
663
			$out[] = Net_IPv6::compress(Net_IPv6::_bin2Ip($ip)) . '/' . $netmask;
664
		}
665
	}
666

    
667
	return $out;
668
}
669

    
670
/* returns true if $range is a valid pair of IPv4 or IPv6 addresses separated by a "-"
671
	false - if not a valid pair
672
	true (numeric 4 or 6) - if valid, gives type of addresses */
673
function is_iprange($range) {
674
	if (substr_count($range, '-') != 1) {
675
		return false;
676
	}
677
	list($ip1, $ip2) = explode ('-', $range);
678
	if (is_ipaddrv4($ip1) && is_ipaddrv4($ip2)) {
679
		return 4;
680
	}
681
	if (is_ipaddrv6($ip1) && is_ipaddrv6($ip2)) {
682
		return 6;
683
	}
684
	return false;
685
}
686

    
687
/* returns true if $ipaddr is a valid dotted IPv4 address or a IPv6
688
	false - not valid
689
	true (numeric 4 or 6) - if valid, gives type of address */
690
function is_ipaddr($ipaddr) {
691
	if (is_ipaddrv4($ipaddr)) {
692
		return 4;
693
	}
694
	if (is_ipaddrv6($ipaddr)) {
695
		return 6;
696
	}
697
	return false;
698
}
699

    
700
/* returns true if $ipaddr is a valid IPv6 address */
701
function is_ipaddrv6($ipaddr) {
702
	if (!is_string($ipaddr) || empty($ipaddr)) {
703
		return false;
704
	}
705
	if (strstr($ipaddr, "%") && is_linklocal($ipaddr)) {
706
		$tmpip = explode("%", $ipaddr);
707
		$ipaddr = $tmpip[0];
708
	}
709
	return Net_IPv6::checkIPv6($ipaddr);
710
}
711

    
712
/* returns true if $ipaddr is a valid dotted IPv4 address */
713
function is_ipaddrv4($ipaddr) {
714
	if (!is_string($ipaddr) || empty($ipaddr) || ip2long($ipaddr) === FALSE) {
715
		return false;
716
	}
717
	return true;
718
}
719

    
720
/* returns 4 or 6 respectively (== TRUE) if $ipaddr is a valid IPv4 or IPv6 linklocal address
721
   returns '' if not a valid linklocal address */
722
function is_linklocal($ipaddr) {
723
	if (is_ipaddrv4($ipaddr)) {
724
		// input is IPv4
725
		// test if it's 169.254.x.x per rfc3927 2.1
726
		$ip4 = explode(".", $ipaddr);
727
		if ($ip4[0] == '169' && $ip4[1] == '254') {
728
			return 4;
729
		}
730
	} elseif (Net_IPv6::getAddressType($ipaddr) == NET_IPV6_LOCAL_LINK) {
731
		return 6;
732
	}
733
	return '';
734
}
735

    
736
/* returns scope of a linklocal address */
737
function get_ll_scope($addr) {
738
	if (!is_linklocal($addr) || !strstr($addr, "%")) {
739
		return "";
740
	}
741
	list ($ll, $scope) = explode("%", $addr);
742
	return $scope;
743
}
744

    
745
/* returns true if $ipaddr is a valid literal IPv6 address */
746
function is_literalipaddrv6($ipaddr) {
747
	if (substr($ipaddr,0,1) == '[' && substr($ipaddr,-1,1) == ']') {
748
		// if it's data wrapped in "[ ... ]" then test if middle part is valid IPv6
749
		return is_ipaddrv6(substr($ipaddr,1,-1));
750
	}
751
	return false;
752
}
753

    
754
/* returns true if $iport is a valid IPv4:port or [Literal IPv6]:port
755
	false - not valid
756
	true (numeric 4 or 6) - if valid, gives type of address */
757
function is_ipaddrwithport($ipport) {
758
	$c = strrpos($ipport, ":");
759
	if ($c === false) {
760
		return false;  // can't split at final colon if no colon exists
761
	}
762

    
763
	if (!is_port(substr($ipport, $c + 1))) {
764
		return false;  // no valid port after last colon
765
	}
766

    
767
	$ip = substr($ipport, 0, $c);  // else is text before last colon a valid IP
768
	if (is_literalipaddrv6($ip)) {
769
		return 6;
770
	} elseif (is_ipaddrv4($ip)) {
771
		return 4;
772
	} else {
773
		return false;
774
	}
775
}
776

    
777
function is_hostnamewithport($hostport) {
778
	$parts = explode(":", $hostport);
779
	// no need to validate with is_string(); if it's not a string then explode won't return 2 parts anyway
780
	if (count($parts) == 2) {
781
		return is_hostname($parts[0]) && is_port($parts[1]);
782
	}
783
	return false;
784
}
785

    
786
/* returns true if $ipaddr is a valid dotted IPv4 address or an alias thereof */
787
function is_ipaddroralias($ipaddr) {
788
	global $config;
789

    
790
	if (is_alias($ipaddr)) {
791
		if (is_array($config['aliases']['alias'])) {
792
			foreach ($config['aliases']['alias'] as $alias) {
793
				if ($alias['name'] == $ipaddr && !preg_match("/port/i", $alias['type'])) {
794
					return true;
795
				}
796
			}
797
		}
798
		return false;
799
	} else {
800
		return is_ipaddr($ipaddr);
801
	}
802

    
803
}
804

    
805
/* returns true if $subnet is a valid IPv4 or IPv6 subnet in CIDR format
806
	false - if not a valid subnet
807
	true (numeric 4 or 6) - if valid, gives type of subnet */
808
function is_subnet($subnet) {
809
	if (is_string($subnet) && preg_match('/^(?:([0-9.]{7,15})|([0-9a-f:]{2,39}))\/(\d{1,3})$/i', $subnet, $parts)) {
810
		if (is_ipaddrv4($parts[1]) && $parts[3] <= 32) {
811
			return 4;
812
		}
813
		if (is_ipaddrv6($parts[2]) && $parts[3] <= 128) {
814
			return 6;
815
		}
816
	}
817
	return false;
818
}
819

    
820
/* same as is_subnet() but accepts IPv4 only */
821
function is_subnetv4($subnet) {
822
	return (is_subnet($subnet) == 4);
823
}
824

    
825
/* same as is_subnet() but accepts IPv6 only */
826
function is_subnetv6($subnet) {
827
	return (is_subnet($subnet) == 6);
828
}
829

    
830
/* returns true if $subnet is a valid subnet in CIDR format or an alias thereof */
831
function is_subnetoralias($subnet) {
832
	global $aliastable;
833

    
834
	if (isset($aliastable[$subnet]) && is_subnet($aliastable[$subnet])) {
835
		return true;
836
	} else {
837
		return is_subnet($subnet);
838
	}
839
}
840

    
841
/* Get number of addresses in an IPv4/IPv6 subnet (represented as a string)
842
   optional $exact=true forces error (0) to be returned if it can't be represented exactly
843
   Exact result not possible above PHP_MAX_INT which is about 2^31 addresses on x32 or 2^63 on x64
844
   Returns 0 for bad data or if cannot represent size as an INT when $exact is set. */
845
function subnet_size($subnet, $exact=false) {
846
	$parts = explode("/", $subnet);
847
	$iptype = is_ipaddr($parts[0]);
848
	if (count($parts) == 2 && $iptype) {
849
		return subnet_size_by_netmask($iptype, $parts[1], $exact);
850
	}
851
	return 0;
852
}
853

    
854
/* Get number of addresses in an IPv4/IPv6 subnet (represented numerically as IP type + bits)
855
   optional $exact=true forces error (0) to be returned if it can't be represented exactly
856
   Hard to think where we might need to count exactly a huge subnet but an overflow detection option is probably sensible
857
   Returns 0 for bad data or if cannot represent size as an INT when $exact is set. */
858
function subnet_size_by_netmask($iptype, $bits, $exact=false) {
859
	if (!is_numericint($bits)) {
860
		return 0;
861
	} elseif ($iptype == 4 && $bits <= 32) {
862
		$snsize = 32 - $bits;
863
	} elseif ($iptype == 6 && $bits <= 128) {
864
		$snsize = 128 - $bits;
865
	} else {
866
		return 0;
867
	}
868

    
869
	// 2**N returns an exact result as an INT if possible, and a float/double if not.
870
	// 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
871
	$result = 2 ** $snsize;
872

    
873
	if ($exact && !is_int($result)) {
874
		//exact required but can't represent result exactly as an INT
875
		return 0;
876
	} else {
877
		// result ok, will be an INT where possible (guaranteed up to 2^31 addresses on x32/x64) and a float for 'huge' subnets
878
		return $result;
879
	}
880
}
881

    
882
/* function used by pfblockerng */
883
function subnetv4_expand($subnet) {
884
	$result = array();
885
	list ($ip, $bits) = explode("/", $subnet);
886
	$net = ip2long($ip);
887
	$mask = (0xffffffff << (32 - $bits));
888
	$net &= $mask;
889
	$size = round(exp(log(2) * (32 - $bits)));
890
	for ($i = 0; $i < $size; $i += 1) {
891
		$result[] = long2ip($net | $i);
892
	}
893
	return $result;
894
}
895

    
896
/* find out whether two IPv4/IPv6 CIDR subnets overlap.
897
   Note: CIDR overlap implies one is identical or included so largest sn will be the same */
898
function check_subnets_overlap($subnet1, $bits1, $subnet2, $bits2) {
899
	if (is_ipaddrv4($subnet1)) {
900
		return check_subnetsv4_overlap($subnet1, $bits1, $subnet2, $bits2);
901
	} else {
902
		return check_subnetsv6_overlap($subnet1, $bits1, $subnet2, $bits2);
903
	}
904
}
905

    
906
/* find out whether two IPv4 CIDR subnets overlap.
907
   Note: CIDR overlap means sn1/sn2 are identical or one is included in other. So sn using largest $bits will be the same  */
908
function check_subnetsv4_overlap($subnet1, $bits1, $subnet2, $bits2) {
909
	$largest_sn = min($bits1, $bits2);
910
	$subnetv4_start1 = gen_subnetv4($subnet1, $largest_sn);
911
	$subnetv4_start2 = gen_subnetv4($subnet2, $largest_sn);
912

    
913
	if ($subnetv4_start1 == '' || $subnetv4_start2 == '') {
914
		// One or both args is not a valid IPv4 subnet
915
		//FIXME: needs to return "bad data" not true/false if bad. For now return false, best we can do until fixed
916
		return false;
917
	}
918
	return ($subnetv4_start1 == $subnetv4_start2);
919
}
920

    
921
/* find out whether two IPv6 CIDR subnets overlap.
922
   Note: CIDR overlap means sn1/sn2 are identical or one is included in other. So sn using largest $bits will be the same  */
923
function check_subnetsv6_overlap($subnet1, $bits1, $subnet2, $bits2) {
924
	$largest_sn = min($bits1, $bits2);
925
	$subnetv6_start1 = gen_subnetv6($subnet1, $largest_sn);
926
	$subnetv6_start2 = gen_subnetv6($subnet2, $largest_sn);
927

    
928
	if ($subnetv6_start1 == '' || $subnetv6_start2 == '') {
929
		// One or both args is not a valid IPv6 subnet
930
		//FIXME: needs to return "bad data" not true/false if bad. For now return false, best we can do until fixed
931
		return false;
932
	}
933
	return ($subnetv6_start1 == $subnetv6_start2);
934
}
935

    
936
/* return all PTR zones for a IPv6 network */
937
function get_v6_ptr_zones($subnet, $bits) {
938
	$result = array();
939

    
940
	if (!is_ipaddrv6($subnet)) {
941
		return $result;
942
	}
943

    
944
	if (!is_numericint($bits) || $bits > 128) {
945
		return $result;
946
	}
947

    
948
	/*
949
	 * Find a small nibble boundary subnet mask
950
	 * e.g. a /29 will create 8 /32 PTR zones
951
	 */
952
	$small_sn = $bits;
953
	while ($small_sn % 4 != 0) {
954
		$small_sn++;
955
	}
956

    
957
	/* Get network prefix */
958
	$small_subnet = Net_IPv6::getNetmask($subnet, $bits);
959

    
960
	/*
961
	 * While small network is part of bigger one, increase 4-bit in last
962
	 * digit to get next small network
963
	 */
964
	while (Net_IPv6::isInNetmask($small_subnet, $subnet, $bits)) {
965
		/* Get a pure hex value */
966
		$unpacked = unpack('H*hex', inet_pton($small_subnet));
967
		/* Create PTR record using $small_sn / 4 chars */
968
		$result[] = implode('.', array_reverse(str_split(substr(
969
		    $unpacked['hex'], 0, $small_sn / 4)))).'.ip6.arpa';
970

    
971
		/* Detect what part of IP should be increased */
972
		$change_part = (int) ($small_sn / 16);
973
		if ($small_sn % 16 == 0) {
974
			$change_part--;
975
		}
976

    
977
		/* Increase 1 to desired part */
978
		$parts = explode(":", Net_IPv6::uncompress($small_subnet));
979
		$parts[$change_part]++;
980
		$small_subnet = implode(":", $parts);
981
	}
982

    
983
	return $result;
984
}
985

    
986
/* return true if $addr is in $subnet, false if not */
987
function ip_in_subnet($addr, $subnet) {
988
	if (is_ipaddrv6($addr) && is_subnetv6($subnet)) {
989
		return (Net_IPv6::isInNetmask($addr, $subnet));
990
	} else if (is_ipaddrv4($addr) && is_subnetv4($subnet)) {
991
		list($ip, $mask) = explode('/', $subnet);
992
		$mask = (0xffffffff << (32 - $mask)) & 0xffffffff;
993
		return ((ip2long($addr) & $mask) == (ip2long($ip) & $mask));
994
	}
995
	return false;
996
}
997

    
998
/* returns true if $hostname is just a valid hostname (top part without any of the domain part) */
999
function is_unqualified_hostname($hostname) {
1000
	if (!is_string($hostname)) {
1001
		return false;
1002
	}
1003

    
1004
	if (preg_match('/^(?:[a-z0-9_]|[a-z0-9_][a-z0-9_\-]*[a-z0-9_])$/i', $hostname)) {
1005
		return true;
1006
	} else {
1007
		return false;
1008
	}
1009
}
1010

    
1011
/* returns true if $hostname is a valid hostname, with or without being a fully-qualified domain name. */
1012
function is_hostname($hostname, $allow_wildcard=false) {
1013
	if (!is_string($hostname)) {
1014
		return false;
1015
	}
1016

    
1017
	if (is_domain($hostname, $allow_wildcard)) {
1018
		if ((substr_count($hostname, ".") == 1) && ($hostname[strlen($hostname)-1] == ".")) {
1019
			/* Only a single dot at the end like "test." - hosts cannot be directly in the root domain. */
1020
			return false;
1021
		} else {
1022
			return true;
1023
		}
1024
	} else {
1025
		return false;
1026
	}
1027
}
1028

    
1029
/* returns true if $domain is a valid domain name */
1030
function is_domain($domain, $allow_wildcard=false) {
1031
	if (!is_string($domain)) {
1032
		return false;
1033
	}
1034
	if ($allow_wildcard) {
1035
		$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';
1036
	} else {
1037
		$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';
1038
	}
1039

    
1040
	if (preg_match($domain_regex, $domain)) {
1041
		return true;
1042
	} else {
1043
		return false;
1044
	}
1045
}
1046

    
1047
/* returns true if $macaddr is a valid MAC address */
1048
function is_macaddr($macaddr, $partial=false) {
1049
	$repeat = ($partial) ? '1,5' : '5';
1050
	return preg_match('/^[0-9A-F]{2}(?:[:][0-9A-F]{2}){'.$repeat.'}$/i', $macaddr) == 1 ? true : false;
1051
}
1052

    
1053
/*
1054
	If $return_message is true then
1055
		returns a text message about the reason that the name is invalid.
1056
		the text includes the type of "thing" that is being checked, passed in $object. (e.g. "alias", "gateway group", "schedule")
1057
	else
1058
		returns true if $name is a valid name for an alias
1059
		returns false if $name is not a valid name for an alias
1060

    
1061
	Aliases cannot be:
1062
		bad chars: anything except a-z 0-9 and underscore
1063
		bad names: empty string, pure numeric, pure underscore
1064
		reserved words: pre-defined service/protocol/port names which should not be ambiguous, and the words "port" and  "pass" */
1065

    
1066
function is_validaliasname($name, $return_message = false, $object = "alias") {
1067
	/* Array of reserved words */
1068
	$reserved = array("port", "pass");
1069

    
1070
	if (!is_string($name) || strlen($name) >= 32 || preg_match('/(^_*$|^\d*$|[^a-z0-9_])/i', $name)) {
1071
		if ($return_message) {
1072
			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, _');
1073
		} else {
1074
			return false;
1075
		}
1076
	}
1077
	if (in_array($name, $reserved, true)) {
1078
		if ($return_message) {
1079
			return sprintf(gettext('The %1$s name must not be either of the reserved words %2$s or %3$s.'), $object, "'port'", "'pass'");
1080
		} else {
1081
			return false;
1082
		}
1083
	}
1084
	if (getprotobyname($name)) {
1085
		if ($return_message) {
1086
			return sprintf(gettext('The %1$s name must not be a well-known IP protocol name such as TCP, UDP, ICMP etc.'), $object);
1087
		} else {
1088
			return false;
1089
		}
1090
	}
1091
	if (getservbyname($name, "tcp") || getservbyname($name, "udp")) {
1092
		if ($return_message) {
1093
			return sprintf(gettext('The %1$s name must not be a well-known TCP or UDP port name such as ssh, smtp, pop3, tftp, http, openvpn etc.'), $object);
1094
		} else {
1095
			return false;
1096
		}
1097
	}
1098
	if ($return_message) {
1099
		return sprintf(gettext("The %1$s name is valid."), $object);
1100
	} else {
1101
		return true;
1102
	}
1103
}
1104

    
1105
/* returns a text message indicating if the alias name is valid, or the reason it is not valid. */
1106
function invalidaliasnamemsg($name, $object = "alias") {
1107
	return is_validaliasname($name, true, $object);
1108
}
1109

    
1110
/*
1111
 * returns true if $range is a valid integer range between $min and $max
1112
 * range delimiter can be ':' or '-'
1113
 */
1114
function is_intrange($range, $min, $max) {
1115
	$values = preg_split("/[:-]/", $range);
1116

    
1117
	if (!is_array($values) || count($values) != 2) {
1118
		return false;
1119
	}
1120

    
1121
	if (!ctype_digit($values[0]) || !ctype_digit($values[1])) {
1122
		return false;
1123
	}
1124

    
1125
	$values[0] = intval($values[0]);
1126
	$values[1] = intval($values[1]);
1127

    
1128
	if ($values[0] >= $values[1]) {
1129
		return false;
1130
	}
1131

    
1132
	if ($values[0] < $min || $values[1] > $max) {
1133
		return false;
1134
	}
1135

    
1136
	return true;
1137
}
1138

    
1139
/* returns true if $port is a valid TCP/UDP port */
1140
function is_port($port) {
1141
	if (ctype_digit($port) && ((intval($port) >= 1) && (intval($port) <= 65535))) {
1142
		return true;
1143
	}
1144
	if (getservbyname($port, "tcp") || getservbyname($port, "udp")) {
1145
		return true;
1146
	}
1147
	return false;
1148
}
1149

    
1150
/* returns true if $portrange is a valid TCP/UDP portrange ("<port>:<port>") */
1151
function is_portrange($portrange) {
1152
	$ports = explode(":", $portrange);
1153

    
1154
	return (count($ports) == 2 && is_port($ports[0]) && is_port($ports[1]));
1155
}
1156

    
1157
/* returns true if $port is a valid TCP/UDP port number or range ("<port>:<port>") */
1158
function is_port_or_range($port) {
1159
	return (is_port($port) || is_portrange($port));
1160
}
1161

    
1162
/* returns true if $port is an alias that is a port type */
1163
function is_portalias($port) {
1164
	global $config;
1165

    
1166
	if (is_alias($port)) {
1167
		if (is_array($config['aliases']['alias'])) {
1168
			foreach ($config['aliases']['alias'] as $alias) {
1169
				if ($alias['name'] == $port && preg_match("/port/i", $alias['type'])) {
1170
					return true;
1171
				}
1172
			}
1173
		}
1174
	}
1175
	return false;
1176
}
1177

    
1178
/* returns true if $port is a valid port number or an alias thereof */
1179
function is_port_or_alias($port) {
1180
	return (is_port($port) || is_portalias($port));
1181
}
1182

    
1183
/* returns true if $port is a valid TCP/UDP port number or range ("<port>:<port>") or an alias thereof */
1184
function is_port_or_range_or_alias($port) {
1185
	return (is_port($port) || is_portrange($port) || is_portalias($port));
1186
}
1187

    
1188
/* create ranges of sequential port numbers (200:215) and remove duplicates */
1189
function group_ports($ports, $kflc = false) {
1190
	if (!is_array($ports) || empty($ports)) {
1191
		return;
1192
	}
1193

    
1194
	$uniq = array();
1195
	$comments = array();
1196
	foreach ($ports as $port) {
1197
		if (($kflc) && (strpos($port, '#') === 0)) {	// Keep Full Line Comments (lines beginning with #).
1198
			$comments[] = $port;
1199
		} else if (is_portrange($port)) {
1200
			list($begin, $end) = explode(":", $port);
1201
			if ($begin > $end) {
1202
				$aux = $begin;
1203
				$begin = $end;
1204
				$end = $aux;
1205
			}
1206
			for ($i = $begin; $i <= $end; $i++) {
1207
				if (!in_array($i, $uniq)) {
1208
					$uniq[] = $i;
1209
				}
1210
			}
1211
		} else if (is_port($port)) {
1212
			if (!in_array($port, $uniq)) {
1213
				$uniq[] = $port;
1214
			}
1215
		}
1216
	}
1217
	sort($uniq, SORT_NUMERIC);
1218

    
1219
	$result = array();
1220
	foreach ($uniq as $idx => $port) {
1221
		if ($idx == 0) {
1222
			$result[] = $port;
1223
			continue;
1224
		}
1225

    
1226
		$last = end($result);
1227
		if (is_portrange($last)) {
1228
			list($begin, $end) = explode(":", $last);
1229
		} else {
1230
			$begin = $end = $last;
1231
		}
1232

    
1233
		if ($port == ($end+1)) {
1234
			$end++;
1235
			$result[count($result)-1] = "{$begin}:{$end}";
1236
		} else {
1237
			$result[] = $port;
1238
		}
1239
	}
1240

    
1241
	return array_merge($comments, $result);
1242
}
1243

    
1244
/* returns true if $val is a valid shaper bandwidth value */
1245
function is_valid_shaperbw($val) {
1246
	return (preg_match("/^(\d+(?:\.\d+)?)([MKG]?b|%)$/", $val));
1247
}
1248

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

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

    
1260
	return false;
1261
}
1262

    
1263
/* returns true if $test is in the range between $start and $end */
1264
function is_inrange_v6($test, $start, $end) {
1265
	if (!is_ipaddrv6($test) || !is_ipaddrv6($start) || !is_ipaddrv6($end)) {
1266
		return false;
1267
	}
1268

    
1269
	if (inet_pton($test) <= inet_pton($end) &&
1270
	    inet_pton($test) >= inet_pton($start)) {
1271
		return true;
1272
	}
1273

    
1274
	return false;
1275
}
1276

    
1277
/* returns true if $test is in the range between $start and $end */
1278
function is_inrange($test, $start, $end) {
1279
	return is_ipaddrv6($test) ? is_inrange_v6($test, $start, $end) : is_inrange_v4($test, $start, $end);
1280
}
1281

    
1282
function get_configured_vip_list($family = 'all', $type = VIP_ALL) {
1283
	global $config;
1284

    
1285
	$list = array();
1286
	if (!is_array($config['virtualip']['vip']) || empty($config['virtualip']['vip'])) {
1287
		return ($list);
1288
	}
1289

    
1290
	$viparr = &$config['virtualip']['vip'];
1291
	foreach ($viparr as $vip) {
1292

    
1293
		if ($type == VIP_CARP) {
1294
			if ($vip['mode'] != "carp")
1295
				continue;
1296
		} elseif ($type == VIP_IPALIAS) {
1297
			if ($vip['mode'] != "ipalias")
1298
				continue;
1299
		} else {
1300
			if ($vip['mode'] != "carp" && $vip['mode'] != "ipalias")
1301
				continue;
1302
		}
1303

    
1304
		if ($family == 'all' ||
1305
		    ($family == 'inet' && is_ipaddrv4($vip['subnet'])) ||
1306
		    ($family == 'inet6' && is_ipaddrv6($vip['subnet']))) {
1307
			$list["_vip{$vip['uniqid']}"] = $vip['subnet'];
1308
		}
1309
	}
1310
	return ($list);
1311
}
1312

    
1313
function get_configured_vip($vipinterface = '') {
1314

    
1315
	return (get_configured_vip_detail($vipinterface, 'all', 'vip'));
1316
}
1317

    
1318
function get_configured_vip_interface($vipinterface = '') {
1319

    
1320
	return (get_configured_vip_detail($vipinterface, 'all', 'iface'));
1321
}
1322

    
1323
function get_configured_vip_ipv4($vipinterface = '') {
1324

    
1325
	return (get_configured_vip_detail($vipinterface, 'inet', 'ip'));
1326
}
1327

    
1328
function get_configured_vip_ipv6($vipinterface = '') {
1329

    
1330
	return (get_configured_vip_detail($vipinterface, 'inet6', 'ip'));
1331
}
1332

    
1333
function get_configured_vip_subnetv4($vipinterface = '') {
1334

    
1335
	return (get_configured_vip_detail($vipinterface, 'inet', 'subnet'));
1336
}
1337

    
1338
function get_configured_vip_subnetv6($vipinterface = '') {
1339

    
1340
	return (get_configured_vip_detail($vipinterface, 'inet6', 'subnet'));
1341
}
1342

    
1343
function get_configured_vip_detail($vipinterface = '', $family = 'inet', $what = 'ip') {
1344
	global $config;
1345

    
1346
	if (empty($vipinterface) || !is_array($config['virtualip']['vip']) ||
1347
	    empty($config['virtualip']['vip'])) {
1348
		return (NULL);
1349
	}
1350

    
1351
	$viparr = &$config['virtualip']['vip'];
1352
	foreach ($viparr as $vip) {
1353
		if ($vip['mode'] != "carp" && $vip['mode'] != "ipalias") {
1354
			continue;
1355
		}
1356

    
1357
		if ($vipinterface != "_vip{$vip['uniqid']}") {
1358
			continue;
1359
		}
1360

    
1361
		switch ($what) {
1362
			case 'subnet':
1363
				if ($family == 'inet' && is_ipaddrv4($vip['subnet']))
1364
					return ($vip['subnet_bits']);
1365
				else if ($family == 'inet6' && is_ipaddrv6($vip['subnet']))
1366
					return ($vip['subnet_bits']);
1367
				break;
1368
			case 'iface':
1369
				return ($vip['interface']);
1370
				break;
1371
			case 'vip':
1372
				return ($vip);
1373
				break;
1374
			case 'ip':
1375
			default:
1376
				if ($family == 'inet' && is_ipaddrv4($vip['subnet'])) {
1377
					return ($vip['subnet']);
1378
				} else if ($family == 'inet6' && is_ipaddrv6($vip['subnet'])) {
1379
					return ($vip['subnet']);
1380
				}
1381
				break;
1382
		}
1383
		break;
1384
	}
1385

    
1386
	return (NULL);
1387
}
1388

    
1389
/* comparison function for sorting by the order in which interfaces are normally created */
1390
function compare_interface_friendly_names($a, $b) {
1391
	if ($a == $b) {
1392
		return 0;
1393
	} else if ($a == 'wan') {
1394
		return -1;
1395
	} else if ($b == 'wan') {
1396
		return 1;
1397
	} else if ($a == 'lan') {
1398
		return -1;
1399
	} else if ($b == 'lan') {
1400
		return 1;
1401
	}
1402

    
1403
	return strnatcmp($a, $b);
1404
}
1405

    
1406
/* return the configured interfaces list. */
1407
function get_configured_interface_list($only_opt = false, $withdisabled = false) {
1408
	global $config;
1409

    
1410
	$iflist = array();
1411

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

    
1422
	return $iflist;
1423
}
1424

    
1425
/* return the configured interfaces list. */
1426
function get_configured_interface_list_by_realif($only_opt = false, $withdisabled = false) {
1427
	global $config;
1428

    
1429
	$iflist = array();
1430

    
1431
	/* if list */
1432
	foreach ($config['interfaces'] as $if => $ifdetail) {
1433
		if ($only_opt && ($if == "wan" || $if == "lan")) {
1434
			continue;
1435
		}
1436
		if (isset($ifdetail['enable']) || $withdisabled == true) {
1437
			$tmpif = get_real_interface($if);
1438
			if (!empty($tmpif)) {
1439
				$iflist[$tmpif] = $if;
1440
			}
1441
		}
1442
	}
1443

    
1444
	return $iflist;
1445
}
1446

    
1447
/* return the configured interfaces list with their description. */
1448
function get_configured_interface_with_descr($only_opt = false, $withdisabled = false) {
1449
	global $config, $user_settings;
1450

    
1451
	$iflist = array();
1452

    
1453
	/* if list */
1454
	foreach ($config['interfaces'] as $if => $ifdetail) {
1455
		if ($only_opt && ($if == "wan" || $if == "lan")) {
1456
			continue;
1457
		}
1458
		if (isset($ifdetail['enable']) || $withdisabled == true) {
1459
			if (empty($ifdetail['descr'])) {
1460
				$iflist[$if] = strtoupper($if);
1461
			} else {
1462
				$iflist[$if] = strtoupper($ifdetail['descr']);
1463
			}
1464
		}
1465
	}
1466

    
1467
	if ($user_settings['webgui']['interfacessort']) {
1468
		asort($iflist);
1469
	}
1470

    
1471
	return $iflist;
1472
}
1473

    
1474
/*
1475
 *   get_configured_ip_addresses() - Return a list of all configured
1476
 *   IPv4 addresses.
1477
 *
1478
 */
1479
function get_configured_ip_addresses() {
1480
	global $config;
1481

    
1482
	if (!function_exists('get_interface_ip')) {
1483
		require_once("interfaces.inc");
1484
	}
1485
	$ip_array = array();
1486
	$interfaces = get_configured_interface_list();
1487
	if (is_array($interfaces)) {
1488
		foreach ($interfaces as $int) {
1489
			$ipaddr = get_interface_ip($int);
1490
			$ip_array[$int] = $ipaddr;
1491
		}
1492
	}
1493
	$interfaces = get_configured_vip_list('inet');
1494
	if (is_array($interfaces)) {
1495
		foreach ($interfaces as $int => $ipaddr) {
1496
			$ip_array[$int] = $ipaddr;
1497
		}
1498
	}
1499

    
1500
	/* pppoe server */
1501
	if (is_array($config['pppoes']) && is_array($config['pppoes']['pppoe'])) {
1502
		foreach ($config['pppoes']['pppoe'] as $pppoe) {
1503
			if ($pppoe['mode'] == "server") {
1504
				if (is_ipaddr($pppoe['localip'])) {
1505
					$int = "pppoes". $pppoe['pppoeid'];
1506
					$ip_array[$int] = $pppoe['localip'];
1507
				}
1508
			}
1509
		}
1510
	}
1511

    
1512
	return $ip_array;
1513
}
1514

    
1515
/*
1516
 *   get_configured_ipv6_addresses() - Return a list of all configured
1517
 *   IPv6 addresses.
1518
 *
1519
 */
1520
function get_configured_ipv6_addresses($linklocal_fallback = false) {
1521
	require_once("interfaces.inc");
1522
	$ipv6_array = array();
1523
	$interfaces = get_configured_interface_list();
1524
	if (is_array($interfaces)) {
1525
		foreach ($interfaces as $int) {
1526
			$ipaddrv6 = text_to_compressed_ip6(get_interface_ipv6($int, false, $linklocal_fallback));
1527
			$ipv6_array[$int] = $ipaddrv6;
1528
		}
1529
	}
1530
	$interfaces = get_configured_vip_list('inet6');
1531
	if (is_array($interfaces)) {
1532
		foreach ($interfaces as $int => $ipaddrv6) {
1533
			$ipv6_array[$int] = text_to_compressed_ip6($ipaddrv6);
1534
		}
1535
	}
1536
	return $ipv6_array;
1537
}
1538

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

    
1642
			case "friendly":
1643
				if ($friendly != "") {
1644
					$toput['if'] = $ifname;
1645
					$iflist[$friendly] = $toput;
1646
				}
1647
				break;
1648
			}
1649
		}
1650
	}
1651
	return $iflist;
1652
}
1653

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

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

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

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

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

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

    
1739
	if ($clearsigmask) {
1740
		pcntl_sigprocmask(SIG_SETMASK, $oldset);
1741
	}
1742

    
1743
	return $retval;
1744
}
1745

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

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

    
1774
	$aliastable = array();
1775

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

    
1785
/* check if an alias exists */
1786
function is_alias($name) {
1787
	global $aliastable;
1788

    
1789
	return isset($aliastable[$name]);
1790
}
1791

    
1792
function alias_get_type($name) {
1793
	global $config;
1794

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

    
1803
	return "";
1804
}
1805

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

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

    
1837
function alias_expand_urltable($name) {
1838
	global $config;
1839
	$urltable_prefix = "/var/db/aliastables/";
1840
	$urltable_filename = $urltable_prefix . $name . ".txt";
1841

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

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

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

    
1895
function mac_format($clientmac) {
1896
	global $config, $cpzone;
1897

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

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

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

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

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

    
1914
		default:
1915
			return $clientmac;
1916
	}
1917
}
1918

    
1919
function resolve_retry($hostname, $retries = 5) {
1920

    
1921
	if (is_ipaddr($hostname)) {
1922
		return $hostname;
1923
	}
1924

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

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

    
1934
		sleep(1);
1935
	}
1936

    
1937
	return false;
1938
}
1939

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

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

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

    
1964
	return ("$num {$units[$i]}");
1965
}
1966

    
1967
function update_filter_reload_status($text, $new=false) {
1968
	global $g;
1969

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

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

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

    
2005
function run_plugins($directory) {
2006
	global $config, $g;
2007

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

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

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

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

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

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

    
2063
	return $values;
2064
}
2065

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

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

    
2081
	return $value[$name];
2082
}
2083

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

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

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

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

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

    
2116
	return $ret;
2117
}
2118

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

    
2129
	$result = set_sysctl(array($name => $value));
2130

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

    
2135
	return true;
2136
}
2137

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

    
2152
function mute_kernel_msgs() {
2153
	global $g, $config;
2154
	// Do not mute serial console.  The kernel gets very very cranky
2155
	// and will start dishing you cannot control tty errors.
2156
	if ($g['platform'] == 'nanobsd') {
2157
		return;
2158
	}
2159
	if ($config['system']['enableserial']) {
2160
		return;
2161
	}
2162
	exec("/sbin/conscontrol mute on");
2163
}
2164

    
2165
function unmute_kernel_msgs() {
2166
	global $g;
2167
	// Do not mute serial console.  The kernel gets very very cranky
2168
	// and will start dishing you cannot control tty errors.
2169
	if ($g['platform'] == 'nanobsd') {
2170
		return;
2171
	}
2172
	exec("/sbin/conscontrol mute off");
2173
}
2174

    
2175
function start_devd() {
2176
	/* Use the undocumented -q options of devd to quiet its log spamming */
2177
	$_gb = exec("/sbin/devd -q");
2178
	sleep(1);
2179
	unset($_gb);
2180
}
2181

    
2182
function is_interface_vlan_mismatch() {
2183
	global $config, $g;
2184

    
2185
	if (is_array($config['vlans']['vlan'])) {
2186
		foreach ($config['vlans']['vlan'] as $vlan) {
2187
			if (substr($vlan['if'], 0, 4) == "lagg") {
2188
				return false;
2189
			}
2190
			if (does_interface_exist($vlan['if']) == false) {
2191
				return true;
2192
			}
2193
		}
2194
	}
2195

    
2196
	return false;
2197
}
2198

    
2199
function is_interface_mismatch() {
2200
	global $config, $g;
2201

    
2202
	$do_assign = false;
2203
	$i = 0;
2204
	$missing_interfaces = array();
2205
	if (is_array($config['interfaces'])) {
2206
		foreach ($config['interfaces'] as $ifname => $ifcfg) {
2207
			if (preg_match("/^enc|^cua|^tun|^tap|^l2tp|^pptp|^ppp|^ovpn|^gif|^gre|^lagg|^bridge|vlan|_wlan|_\d{0,4}_\d{0,4}$/i", $ifcfg['if'])) {
2208
				// Do not check these interfaces.
2209
				$i++;
2210
				continue;
2211
			} else if (does_interface_exist($ifcfg['if']) == false) {
2212
				$missing_interfaces[] = $ifcfg['if'];
2213
				$do_assign = true;
2214
			} else {
2215
				$i++;
2216
			}
2217
		}
2218
	}
2219

    
2220
	if (file_exists("{$g['tmp_path']}/assign_complete")) {
2221
		$do_assign = false;
2222
	}
2223

    
2224
	if (!empty($missing_interfaces) && $do_assign) {
2225
		file_put_contents("{$g['tmp_path']}/missing_interfaces", implode(' ', $missing_interfaces));
2226
	} else {
2227
		@unlink("{$g['tmp_path']}/missing_interfaces");
2228
	}
2229

    
2230
	return $do_assign;
2231
}
2232

    
2233
/* sync carp entries to other firewalls */
2234
function carp_sync_client() {
2235
	global $g;
2236
	send_event("filter sync");
2237
}
2238

    
2239
/****f* util/isAjax
2240
 * NAME
2241
 *   isAjax - reports if the request is driven from prototype
2242
 * INPUTS
2243
 *   none
2244
 * RESULT
2245
 *   true/false
2246
 ******/
2247
function isAjax() {
2248
	return isset ($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest';
2249
}
2250

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

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

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

    
2329
function is_file_included($file = "") {
2330
	$files = get_included_files();
2331
	if (in_array($file, $files)) {
2332
		return true;
2333
	}
2334

    
2335
	return false;
2336
}
2337

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

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

    
2354
	return $data;
2355
}
2356

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

    
2363
	$arrays = func_get_args();
2364
	$remains = $arrays;
2365

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

    
2370
	// loop available array
2371
	foreach ($arrays as $array) {
2372

    
2373
		// The first remaining array is $array. We are processing it. So
2374
		// we remove it from remaining arrays.
2375
		array_shift($remains);
2376

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

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

    
2408

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
2624
	return false;
2625
}
2626

    
2627
/* Replaces the Mac OS 9 and earlier (\r) and DOS/Windows (\r\n) newlines with the Unix equivalent (\n). */
2628
function unixnewlines($text) {
2629
	return preg_replace('/\r\n?/', "\n", $text);
2630
}
2631

    
2632
?>
(57-57/67)