Project

General

Profile

Download (71.6 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
function config_lock() {
140
	return;
141
}
142
function config_unlock() {
143
	return;
144
}
145

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

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

    
185
		return $fp;
186
	}
187

    
188
	return NULL;
189
}
190

    
191
/* unlock configuration file */
192
function unlock($cfglckkey = 0) {
193
	global $g;
194
	flock($cfglckkey, LOCK_UN);
195
	fclose($cfglckkey);
196
	return;
197
}
198

    
199
/* unlock forcefully configuration file */
200
function unlock_force($lock) {
201
	global $g;
202

    
203
	@unlink("{$g['tmp_path']}/{$lock}.lock");
204
}
205

    
206
function send_event($cmd) {
207
	global $g;
208

    
209
	if (!isset($g['event_address'])) {
210
		$g['event_address'] = "unix:///var/run/check_reload_status";
211
	}
212

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

    
231
function send_multiple_events($cmds) {
232
	global $g;
233

    
234
	if (!isset($g['event_address'])) {
235
		$g['event_address'] = "unix:///var/run/check_reload_status";
236
	}
237

    
238
	if (!is_array($cmds)) {
239
		return;
240
	}
241

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

    
261
function refcount_init($reference) {
262
	$shmid = @shmop_open($reference, "c", 0644, 10);
263
	@shmop_write($shmid, str_pad("0", 10, "\x0", STR_PAD_RIGHT), 0);
264
	@shmop_close($shmid);
265
}
266

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

    
292
	return $shm_data;
293
}
294

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

    
321
	return $shm_data;
322
}
323

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

    
337
function is_module_loaded($module_name) {
338
	$module_name = str_replace(".ko", "", $module_name);
339
	$running = 0;
340
	$_gb = exec("/sbin/kldstat -qn {$module_name} 2>&1", $_gb, $running);
341
	if (intval($running) == 0) {
342
		return true;
343
	} else {
344
		return false;
345
	}
346
}
347

    
348
/* validate non-negative numeric string, or equivalent numeric variable */
349
function is_numericint($arg) {
350
	return (((is_int($arg) && $arg >= 0) || (is_string($arg) && strlen($arg) > 0 && ctype_digit($arg))) ? true : false);
351
}
352

    
353
/* Generate the (human readable) ipv4 or ipv6 subnet address (i.e., netmask, or subnet start IP)
354
   given an (human readable) ipv4 or ipv6 host address and subnet bit count */
355
function gen_subnet($ipaddr, $bits) {
356
	if (($sn = gen_subnetv6($ipaddr, $bits)) == '') {
357
		$sn = gen_subnetv4($ipaddr, $bits);  // try to avoid rechecking IPv4/v6
358
	}
359
	return $sn;
360
}
361

    
362
/* same as gen_subnet() but accepts IPv4 only */
363
function gen_subnetv4($ipaddr, $bits) {
364
	if (is_ipaddrv4($ipaddr) && is_numericint($bits) && $bits <= 32) {
365
		if ($bits == 0) {
366
			return '0.0.0.0';  // avoids <<32
367
		}
368
		return long2ip(ip2long($ipaddr) & ((0xFFFFFFFF << (32 - $bits)) & 0xFFFFFFFF));
369
	}
370
	return "";
371
}
372

    
373
/* same as gen_subnet() but accepts IPv6 only */
374
function gen_subnetv6($ipaddr, $bits) {
375
	if (is_ipaddrv6($ipaddr) && is_numericint($bits) && $bits <= 128) {
376
		return text_to_compressed_ip6(Net_IPv6::getNetmask($ipaddr, $bits));
377
	}
378
	return "";
379
}
380

    
381
/* Generate the (human readable) ipv4 or ipv6 subnet end address (i.e., highest address, end IP, or IPv4 broadcast address)
382
   given an (human readable) ipv4 or ipv6 host address and subnet bit count. */
383
function gen_subnet_max($ipaddr, $bits) {
384
	if (($sn = gen_subnetv6_max($ipaddr, $bits)) == '') {
385
		$sn = gen_subnetv4_max($ipaddr, $bits);  // try to avoid rechecking IPv4/v6
386
	}
387
	return $sn;
388
}
389

    
390
/* same as gen_subnet_max() but validates IPv4 only */
391
function gen_subnetv4_max($ipaddr, $bits) {
392
	if (is_ipaddrv4($ipaddr) && is_numericint($bits) && $bits <= 32) {
393
		if ($bits == 32) {
394
			return $ipaddr;
395
		}
396
		return long2ip32(ip2long($ipaddr) | (~gen_subnet_mask_long($bits) & 0xFFFFFFFF));
397
	}
398
	return "";
399
}
400

    
401
/* same as gen_subnet_max() but validates IPv6 only */
402
function gen_subnetv6_max($ipaddr, $bits) {
403
	if (is_ipaddrv6($ipaddr) && is_numericint($bits) && $bits <= 128) {
404
		$endip_bin = substr(Net_IPv6::_ip2Bin($ipaddr), 0, $bits) . str_repeat('1', 128 - $bits);
405
		return Net_IPv6::compress(Net_IPv6::_bin2Ip($endip_bin));
406
	}
407
	return "";
408
}
409

    
410
/* returns a subnet mask (long given a bit count) */
411
function gen_subnet_mask_long($bits) {
412
	$sm = 0;
413
	for ($i = 0; $i < $bits; $i++) {
414
		$sm >>= 1;
415
		$sm |= 0x80000000;
416
	}
417
	return $sm;
418
}
419

    
420
/* same as above but returns a string */
421
function gen_subnet_mask($bits) {
422
	return long2ip(gen_subnet_mask_long($bits));
423
}
424

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

    
435
/* Convert long int to IPv4 address
436
   Returns '' if not valid IPv4 (including if any bits >32 are non-zero) */
437
function long2ip32($ip) {
438
	return long2ip($ip & 0xFFFFFFFF);
439
}
440

    
441
/* Convert IPv4 address to long int, truncated to 32-bits to avoid sign extension on 64-bit platforms.
442
   Returns '' if not valid IPv4. */
443
function ip2long32($ip) {
444
	return (ip2long($ip) & 0xFFFFFFFF);
445
}
446

    
447
/* Convert IPv4 address to unsigned long int.
448
   Returns '' if not valid IPv4. */
449
function ip2ulong($ip) {
450
	return sprintf("%u", ip2long32($ip));
451
}
452

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

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

    
480
/* Find the smallest possible subnet mask which can contain a given number of IPs
481
 *  e.g. 512 IPs can fit in a /23, but 513 IPs need a /22
482
 */
483
function find_smallest_cidr_v4($number) {
484
	$smallest = 1;
485
	for ($b=32; $b > 0; $b--) {
486
		$smallest = ($number <= pow(2, $b)) ? $b : $smallest;
487
	}
488
	return (32-$smallest);
489
}
490

    
491
/* Return the previous IP address before the given address */
492
function ip_before($ip, $offset = 1) {
493
	return long2ip32(ip2long($ip) - $offset);
494
}
495

    
496
/* Return the next IP address after the given address */
497
function ip_after($ip, $offset = 1) {
498
	return long2ip32(ip2long($ip) + $offset);
499
}
500

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

    
508
/* Return true if the first IP is 'after' the second */
509
function ip_greater_than($ip1, $ip2) {
510
	// Compare as unsigned long because otherwise it wouldn't work
511
	//   when crossing over from 127.255.255.255 / 128.0.0.0 barrier
512
	return ip2ulong($ip1) > ip2ulong($ip2);
513
}
514

    
515
/* compare two IP addresses */
516
function ipcmp($a, $b) {
517
	if (ip_less_than($a, $b)) {
518
		return -1;
519
	} else if (ip_greater_than($a, $b)) {
520
		return 1;
521
	} else {
522
		return 0;
523
	}
524
}
525

    
526
/* Convert a range of IPv4 addresses to an array of individual addresses. */
527
/* Note: IPv6 ranges are not yet supported here. */
528
function ip_range_to_address_array($startip, $endip, $max_size = 5000) {
529
	if (!is_ipaddrv4($startip) || !is_ipaddrv4($endip)) {
530
		return false;
531
	}
532

    
533
	if (ip_greater_than($startip, $endip)) {
534
		// Swap start and end so we can process sensibly.
535
		$temp = $startip;
536
		$startip = $endip;
537
		$endip = $temp;
538
	}
539

    
540
	if (ip_range_size_v4($startip, $endip) > $max_size) {
541
		return false;
542
	}
543

    
544
	// Container for IP addresses within this range.
545
	$rangeaddresses = array();
546
	$end_int = ip2ulong($endip);
547
	for ($ip_int = ip2ulong($startip); $ip_int <= $end_int; $ip_int++) {
548
		$rangeaddresses[] = long2ip($ip_int);
549
	}
550

    
551
	return $rangeaddresses;
552
}
553

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

    
557
	Documented on pfsense dev list 19-20 May 2013. Summary:
558

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

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

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

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

    
580
function ip_range_to_subnet_array($ip1, $ip2) {
581

    
582
	if (is_ipaddrv4($ip1) && is_ipaddrv4($ip2)) {
583
		$proto = 'ipv4';  // for clarity
584
		$bits = 32;
585
		$ip1bin = decbin(ip2long32($ip1));
586
		$ip2bin = decbin(ip2long32($ip2));
587
	} elseif (is_ipaddrv6($ip1) && is_ipaddrv6($ip2)) {
588
		$proto = 'ipv6';
589
		$bits = 128;
590
		$ip1bin = Net_IPv6::_ip2Bin($ip1);
591
		$ip2bin = Net_IPv6::_ip2Bin($ip2);
592
	} else {
593
		return array();
594
	}
595

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

    
600
	if ($ip1bin == $ip2bin) {
601
		return array($ip1 . '/' . $bits); // exit if ip1=ip2 (trivial case)
602
	}
603

    
604
	if ($ip1bin > $ip2bin) {
605
		list ($ip1bin, $ip2bin) = array($ip2bin, $ip1bin);  // swap if needed (ensures ip1 < ip2)
606
	}
607

    
608
	$rangesubnets = array();
609
	$netsize = 0;
610

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

    
615
		// 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)
616

    
617
		if (substr($ip1bin, -1, 1) == '1') {
618
			// the start ip must be in a separate one-IP cidr range
619
			$new_subnet_ip = substr($ip1bin, $netsize, $bits - $netsize) . str_repeat('0', $netsize);
620
			$rangesubnets[$new_subnet_ip] = $bits - $netsize;
621
			$n = strrpos($ip1bin, '0');  //can't be all 1's
622
			$ip1bin = ($n == 0 ? '' : substr($ip1bin, 0, $n)) . '1' . str_repeat('0', $bits - $n - 1);  // BINARY VERSION OF $ip1 += 1
623
		}
624

    
625
		// 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)
626

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

    
636
		// this is the only edge case arising from increment/decrement.
637
		// 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)
638

    
639
		if ($ip2bin < $ip1bin) {
640
			continue;
641
		}
642

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

    
646
		$shift = $bits - max(strrpos($ip1bin, '0'), strrpos($ip2bin, '1'));  // num of low bits which are '0' in ip1 and '1' in ip2
647
		$ip1bin = str_repeat('0', $shift) . substr($ip1bin, 0, $bits - $shift);
648
		$ip2bin = str_repeat('0', $shift) . substr($ip2bin, 0, $bits - $shift);
649
		$netsize += $shift;
650
		if ($ip1bin == $ip2bin) {
651
			// we're done.
652
			$new_subnet_ip = substr($ip1bin, $netsize, $bits - $netsize) . str_repeat('0', $netsize);
653
			$rangesubnets[$new_subnet_ip] = $bits - $netsize;
654
			continue;
655
		}
656

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

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

    
662
	ksort($rangesubnets, SORT_STRING);
663
	$out = array();
664

    
665
	foreach ($rangesubnets as $ip => $netmask) {
666
		if ($proto == 'ipv4') {
667
			$i = str_split($ip, 8);
668
			$out[] = implode('.', array(bindec($i[0]), bindec($i[1]), bindec($i[2]), bindec($i[3]))) . '/' . $netmask;
669
		} else {
670
			$out[] = Net_IPv6::compress(Net_IPv6::_bin2Ip($ip)) . '/' . $netmask;
671
		}
672
	}
673

    
674
	return $out;
675
}
676

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

    
694
/* returns true if $ipaddr is a valid dotted IPv4 address or a IPv6
695
	false - not valid
696
	true (numeric 4 or 6) - if valid, gives type of address */
697
function is_ipaddr($ipaddr) {
698
	if (is_ipaddrv4($ipaddr)) {
699
		return 4;
700
	}
701
	if (is_ipaddrv6($ipaddr)) {
702
		return 6;
703
	}
704
	return false;
705
}
706

    
707
/* returns true if $ipaddr is a valid IPv6 address */
708
function is_ipaddrv6($ipaddr) {
709
	if (!is_string($ipaddr) || empty($ipaddr)) {
710
		return false;
711
	}
712
	if (strstr($ipaddr, "%") && is_linklocal($ipaddr)) {
713
		$tmpip = explode("%", $ipaddr);
714
		$ipaddr = $tmpip[0];
715
	}
716
	return Net_IPv6::checkIPv6($ipaddr);
717
}
718

    
719
/* returns true if $ipaddr is a valid dotted IPv4 address */
720
function is_ipaddrv4($ipaddr) {
721
	if (!is_string($ipaddr) || empty($ipaddr) || ip2long($ipaddr) === FALSE) {
722
		return false;
723
	}
724
	return true;
725
}
726

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

    
743
/* returns scope of a linklocal address */
744
function get_ll_scope($addr) {
745
	if (!is_linklocal($addr) || !strstr($addr, "%")) {
746
		return "";
747
	}
748
	list ($ll, $scope) = explode("%", $addr);
749
	return $scope;
750
}
751

    
752
/* returns true if $ipaddr is a valid literal IPv6 address */
753
function is_literalipaddrv6($ipaddr) {
754
	if (substr($ipaddr,0,1) == '[' && substr($ipaddr,-1,1) == ']') {
755
		// if it's data wrapped in "[ ... ]" then test if middle part is valid IPv6
756
		return is_ipaddrv6(substr($ipaddr,1,-1));
757
	}
758
	return false;
759
}
760

    
761
/* returns true if $iport is a valid IPv4:port or [Literal IPv6]:port
762
	false - not valid
763
	true (numeric 4 or 6) - if valid, gives type of address */
764
function is_ipaddrwithport($ipport) {
765
	$c = strrpos($ipport, ":");
766
	if ($c === false) {
767
		return false;  // can't split at final colon if no colon exists
768
	}
769

    
770
	if (!is_port(substr($ipport, $c + 1))) {
771
		return false;  // no valid port after last colon
772
	}
773

    
774
	$ip = substr($ipport, 0, $c);  // else is text before last colon a valid IP
775
	if (is_literalipaddrv6($ip)) {
776
		return 6;
777
	} elseif (is_ipaddrv4($ip)) {
778
		return 4;
779
	} else {
780
		return false;
781
	}
782
}
783

    
784
function is_hostnamewithport($hostport) {
785
	$parts = explode(":", $hostport);
786
	// no need to validate with is_string(); if it's not a string then explode won't return 2 parts anyway
787
	if (count($parts) == 2) {
788
		return is_hostname($parts[0]) && is_port($parts[1]);
789
	}
790
	return false;
791
}
792

    
793
/* returns true if $ipaddr is a valid dotted IPv4 address or an alias thereof */
794
function is_ipaddroralias($ipaddr) {
795
	global $config;
796

    
797
	if (is_alias($ipaddr)) {
798
		if (is_array($config['aliases']['alias'])) {
799
			foreach ($config['aliases']['alias'] as $alias) {
800
				if ($alias['name'] == $ipaddr && !preg_match("/port/i", $alias['type'])) {
801
					return true;
802
				}
803
			}
804
		}
805
		return false;
806
	} else {
807
		return is_ipaddr($ipaddr);
808
	}
809

    
810
}
811

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

    
827
/* same as is_subnet() but accepts IPv4 only */
828
function is_subnetv4($subnet) {
829
	return (is_subnet($subnet) == 4);
830
}
831

    
832
/* same as is_subnet() but accepts IPv6 only */
833
function is_subnetv6($subnet) {
834
	return (is_subnet($subnet) == 6);
835
}
836

    
837
/* returns true if $subnet is a valid subnet in CIDR format or an alias thereof */
838
function is_subnetoralias($subnet) {
839
	global $aliastable;
840

    
841
	if (isset($aliastable[$subnet]) && is_subnet($aliastable[$subnet])) {
842
		return true;
843
	} else {
844
		return is_subnet($subnet);
845
	}
846
}
847

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

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

    
876
	// 2**N returns an exact result as an INT if possible, and a float/double if not.
877
	// 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
878
	$result = 2 ** $snsize;
879

    
880
	if ($exact && !is_int($result)) {
881
		//exact required but can't represent result exactly as an INT
882
		return 0;
883
	} else {
884
		// result ok, will be an INT where possible (guaranteed up to 2^31 addresses on x32/x64) and a float for 'huge' subnets
885
		return $result;
886
	}
887
}
888

    
889
/* function used by pfblockerng */
890
function subnetv4_expand($subnet) {
891
	$result = array();
892
	list ($ip, $bits) = explode("/", $subnet);
893
	$net = ip2long($ip);
894
	$mask = (0xffffffff << (32 - $bits));
895
	$net &= $mask;
896
	$size = round(exp(log(2) * (32 - $bits)));
897
	for ($i = 0; $i < $size; $i += 1) {
898
		$result[] = long2ip($net | $i);
899
	}
900
	return $result;
901
}
902

    
903
/* find out whether two IPv4/IPv6 CIDR subnets overlap.
904
   Note: CIDR overlap implies one is identical or included so largest sn will be the same */
905
function check_subnets_overlap($subnet1, $bits1, $subnet2, $bits2) {
906
	if (is_ipaddrv4($subnet1)) {
907
		return check_subnetsv4_overlap($subnet1, $bits1, $subnet2, $bits2);
908
	} else {
909
		return check_subnetsv6_overlap($subnet1, $bits1, $subnet2, $bits2);
910
	}
911
}
912

    
913
/* find out whether two IPv4 CIDR subnets overlap.
914
   Note: CIDR overlap means sn1/sn2 are identical or one is included in other. So sn using largest $bits will be the same  */
915
function check_subnetsv4_overlap($subnet1, $bits1, $subnet2, $bits2) {
916
	$largest_sn = min($bits1, $bits2);
917
	$subnetv4_start1 = gen_subnetv4($subnet1, $largest_sn);
918
	$subnetv4_start2 = gen_subnetv4($subnet2, $largest_sn);
919

    
920
	if ($subnetv4_start1 == '' || $subnetv4_start2 == '') {
921
		// One or both args is not a valid IPv4 subnet
922
		//FIXME: needs to return "bad data" not true/false if bad. For now return false, best we can do until fixed
923
		return false;
924
	}
925
	return ($subnetv4_start1 == $subnetv4_start2);
926
}
927

    
928
/* find out whether two IPv6 CIDR subnets overlap.
929
   Note: CIDR overlap means sn1/sn2 are identical or one is included in other. So sn using largest $bits will be the same  */
930
function check_subnetsv6_overlap($subnet1, $bits1, $subnet2, $bits2) {
931
	$largest_sn = min($bits1, $bits2);
932
	$subnetv6_start1 = gen_subnetv6($subnet1, $largest_sn);
933
	$subnetv6_start2 = gen_subnetv6($subnet2, $largest_sn);
934

    
935
	if ($subnetv6_start1 == '' || $subnetv6_start2 == '') {
936
		// One or both args is not a valid IPv6 subnet
937
		//FIXME: needs to return "bad data" not true/false if bad. For now return false, best we can do until fixed
938
		return false;
939
	}
940
	return ($subnetv6_start1 == $subnetv6_start2);
941
}
942

    
943
/* return all PTR zones for a IPv6 network */
944
function get_v6_ptr_zones($subnet, $bits) {
945
	$result = array();
946

    
947
	if (!is_ipaddrv6($subnet)) {
948
		return $result;
949
	}
950

    
951
	if (!is_numericint($bits) || $bits > 128) {
952
		return $result;
953
	}
954

    
955
	/*
956
	 * Find a small nibble boundary subnet mask
957
	 * e.g. a /29 will create 8 /32 PTR zones
958
	 */
959
	$small_sn = $bits;
960
	while ($small_sn % 4 != 0) {
961
		$small_sn++;
962
	}
963

    
964
	/* Get network prefix */
965
	$small_subnet = Net_IPv6::getNetmask($subnet, $bits);
966

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

    
978
		/* Detect what part of IP should be increased */
979
		$change_part = (int) ($small_sn / 16);
980
		if ($small_sn % 16 == 0) {
981
			$change_part--;
982
		}
983

    
984
		/* Increase 1 to desired part */
985
		$parts = explode(":", Net_IPv6::uncompress($small_subnet));
986
		$parts[$change_part]++;
987
		$small_subnet = implode(":", $parts);
988
	}
989

    
990
	return $result;
991
}
992

    
993
/* return true if $addr is in $subnet, false if not */
994
function ip_in_subnet($addr, $subnet) {
995
	if (is_ipaddrv6($addr) && is_subnetv6($subnet)) {
996
		return (Net_IPv6::isInNetmask($addr, $subnet));
997
	} else if (is_ipaddrv4($addr) && is_subnetv4($subnet)) {
998
		list($ip, $mask) = explode('/', $subnet);
999
		$mask = (0xffffffff << (32 - $mask)) & 0xffffffff;
1000
		return ((ip2long($addr) & $mask) == (ip2long($ip) & $mask));
1001
	}
1002
	return false;
1003
}
1004

    
1005
/* returns true if $hostname is just a valid hostname (top part without any of the domain part) */
1006
function is_unqualified_hostname($hostname) {
1007
	if (!is_string($hostname)) {
1008
		return false;
1009
	}
1010

    
1011
	if (preg_match('/^(?:[a-z0-9_]|[a-z0-9_][a-z0-9_\-]*[a-z0-9_])$/i', $hostname)) {
1012
		return true;
1013
	} else {
1014
		return false;
1015
	}
1016
}
1017

    
1018
/* returns true if $hostname is a valid hostname, with or without being a fully-qualified domain name. */
1019
function is_hostname($hostname, $allow_wildcard=false) {
1020
	if (!is_string($hostname)) {
1021
		return false;
1022
	}
1023

    
1024
	if (is_domain($hostname, $allow_wildcard)) {
1025
		if ((substr_count($hostname, ".") == 1) && ($hostname[strlen($hostname)-1] == ".")) {
1026
			/* Only a single dot at the end like "test." - hosts cannot be directly in the root domain. */
1027
			return false;
1028
		} else {
1029
			return true;
1030
		}
1031
	} else {
1032
		return false;
1033
	}
1034
}
1035

    
1036
/* returns true if $domain is a valid domain name */
1037
function is_domain($domain, $allow_wildcard=false) {
1038
	if (!is_string($domain)) {
1039
		return false;
1040
	}
1041
	if ($allow_wildcard) {
1042
		$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';
1043
	} else {
1044
		$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';
1045
	}
1046

    
1047
	if (preg_match($domain_regex, $domain)) {
1048
		return true;
1049
	} else {
1050
		return false;
1051
	}
1052
}
1053

    
1054
/* returns true if $macaddr is a valid MAC address */
1055
function is_macaddr($macaddr, $partial=false) {
1056
	$repeat = ($partial) ? '1,5' : '5';
1057
	return preg_match('/^[0-9A-F]{2}(?:[:][0-9A-F]{2}){'.$repeat.'}$/i', $macaddr) == 1 ? true : false;
1058
}
1059

    
1060
/*
1061
	If $return_message is true then
1062
		returns a text message about the reason that the name is invalid.
1063
		the text includes the type of "thing" that is being checked, passed in $object. (e.g. "alias", "gateway group", "schedule")
1064
	else
1065
		returns true if $name is a valid name for an alias
1066
		returns false if $name is not a valid name for an alias
1067

    
1068
	Aliases cannot be:
1069
		bad chars: anything except a-z 0-9 and underscore
1070
		bad names: empty string, pure numeric, pure underscore
1071
		reserved words: pre-defined service/protocol/port names which should not be ambiguous, and the words "port" and  "pass" */
1072

    
1073
function is_validaliasname($name, $return_message = false, $object = "alias") {
1074
	/* Array of reserved words */
1075
	$reserved = array("port", "pass");
1076

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

    
1112
/* returns a text message indicating if the alias name is valid, or the reason it is not valid. */
1113
function invalidaliasnamemsg($name, $object = "alias") {
1114
	return is_validaliasname($name, true, $object);
1115
}
1116

    
1117
/*
1118
 * returns true if $range is a valid integer range between $min and $max
1119
 * range delimiter can be ':' or '-'
1120
 */
1121
function is_intrange($range, $min, $max) {
1122
	$values = preg_split("/[:-]/", $range);
1123

    
1124
	if (!is_array($values) || count($values) != 2) {
1125
		return false;
1126
	}
1127

    
1128
	if (!ctype_digit($values[0]) || !ctype_digit($values[1])) {
1129
		return false;
1130
	}
1131

    
1132
	$values[0] = intval($values[0]);
1133
	$values[1] = intval($values[1]);
1134

    
1135
	if ($values[0] >= $values[1]) {
1136
		return false;
1137
	}
1138

    
1139
	if ($values[0] < $min || $values[1] > $max) {
1140
		return false;
1141
	}
1142

    
1143
	return true;
1144
}
1145

    
1146
/* returns true if $port is a valid TCP/UDP port */
1147
function is_port($port) {
1148
	if (ctype_digit($port) && ((intval($port) >= 1) && (intval($port) <= 65535))) {
1149
		return true;
1150
	}
1151
	if (getservbyname($port, "tcp") || getservbyname($port, "udp")) {
1152
		return true;
1153
	}
1154
	return false;
1155
}
1156

    
1157
/* returns true if $portrange is a valid TCP/UDP portrange ("<port>:<port>") */
1158
function is_portrange($portrange) {
1159
	$ports = explode(":", $portrange);
1160

    
1161
	return (count($ports) == 2 && is_port($ports[0]) && is_port($ports[1]));
1162
}
1163

    
1164
/* returns true if $port is a valid port number or an alias thereof */
1165
function is_portoralias($port) {
1166
	global $config;
1167

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

    
1182
/* create ranges of sequential port numbers (200:215) and remove duplicates */
1183
function group_ports($ports, $kflc = false) {
1184
	if (!is_array($ports) || empty($ports)) {
1185
		return;
1186
	}
1187

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

    
1213
	$result = array();
1214
	foreach ($uniq as $idx => $port) {
1215
		if ($idx == 0) {
1216
			$result[] = $port;
1217
			continue;
1218
		}
1219

    
1220
		$last = end($result);
1221
		if (is_portrange($last)) {
1222
			list($begin, $end) = explode(":", $last);
1223
		} else {
1224
			$begin = $end = $last;
1225
		}
1226

    
1227
		if ($port == ($end+1)) {
1228
			$end++;
1229
			$result[count($result)-1] = "{$begin}:{$end}";
1230
		} else {
1231
			$result[] = $port;
1232
		}
1233
	}
1234

    
1235
	return array_merge($comments, $result);
1236
}
1237

    
1238
/* returns true if $val is a valid shaper bandwidth value */
1239
function is_valid_shaperbw($val) {
1240
	return (preg_match("/^(\d+(?:\.\d+)?)([MKG]?b|%)$/", $val));
1241
}
1242

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

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

    
1254
	return false;
1255
}
1256

    
1257
/* returns true if $test is in the range between $start and $end */
1258
function is_inrange_v6($test, $start, $end) {
1259
	if (!is_ipaddrv6($test) || !is_ipaddrv6($start) || !is_ipaddrv6($end)) {
1260
		return false;
1261
	}
1262

    
1263
	if (inet_pton($test) <= inet_pton($end) &&
1264
	    inet_pton($test) >= inet_pton($start)) {
1265
		return true;
1266
	}
1267

    
1268
	return false;
1269
}
1270

    
1271
/* returns true if $test is in the range between $start and $end */
1272
function is_inrange($test, $start, $end) {
1273
	return is_ipaddrv6($test) ? is_inrange_v6($test, $start, $end) : is_inrange_v4($test, $start, $end);
1274
}
1275

    
1276
function get_configured_vip_list($family = 'all', $type = VIP_ALL) {
1277
	global $config;
1278

    
1279
	$list = array();
1280
	if (!is_array($config['virtualip']['vip']) || empty($config['virtualip']['vip'])) {
1281
		return ($list);
1282
	}
1283

    
1284
	$viparr = &$config['virtualip']['vip'];
1285
	foreach ($viparr as $vip) {
1286

    
1287
		if ($type == VIP_CARP) {
1288
			if ($vip['mode'] != "carp")
1289
				continue;
1290
		} elseif ($type == VIP_IPALIAS) {
1291
			if ($vip['mode'] != "ipalias")
1292
				continue;
1293
		} else {
1294
			if ($vip['mode'] != "carp" && $vip['mode'] != "ipalias")
1295
				continue;
1296
		}
1297

    
1298
		if ($family == 'all' ||
1299
		    ($family == 'inet' && is_ipaddrv4($vip['subnet'])) ||
1300
		    ($family == 'inet6' && is_ipaddrv6($vip['subnet']))) {
1301
			$list["_vip{$vip['uniqid']}"] = $vip['subnet'];
1302
		}
1303
	}
1304
	return ($list);
1305
}
1306

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

    
1309
	return (get_configured_vip_detail($vipinterface, 'all', 'vip'));
1310
}
1311

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

    
1314
	return (get_configured_vip_detail($vipinterface, 'all', 'iface'));
1315
}
1316

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

    
1319
	return (get_configured_vip_detail($vipinterface, 'inet', 'ip'));
1320
}
1321

    
1322
function get_configured_vip_ipv6($vipinterface = '') {
1323

    
1324
	return (get_configured_vip_detail($vipinterface, 'inet6', 'ip'));
1325
}
1326

    
1327
function get_configured_vip_subnetv4($vipinterface = '') {
1328

    
1329
	return (get_configured_vip_detail($vipinterface, 'inet', 'subnet'));
1330
}
1331

    
1332
function get_configured_vip_subnetv6($vipinterface = '') {
1333

    
1334
	return (get_configured_vip_detail($vipinterface, 'inet6', 'subnet'));
1335
}
1336

    
1337
function get_configured_vip_detail($vipinterface = '', $family = 'inet', $what = 'ip') {
1338
	global $config;
1339

    
1340
	if (empty($vipinterface) || !is_array($config['virtualip']['vip']) ||
1341
	    empty($config['virtualip']['vip'])) {
1342
		return (NULL);
1343
	}
1344

    
1345
	$viparr = &$config['virtualip']['vip'];
1346
	foreach ($viparr as $vip) {
1347
		if ($vip['mode'] != "carp" && $vip['mode'] != "ipalias") {
1348
			continue;
1349
		}
1350

    
1351
		if ($vipinterface != "_vip{$vip['uniqid']}") {
1352
			continue;
1353
		}
1354

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

    
1380
	return (NULL);
1381
}
1382

    
1383
/* comparison function for sorting by the order in which interfaces are normally created */
1384
function compare_interface_friendly_names($a, $b) {
1385
	if ($a == $b) {
1386
		return 0;
1387
	} else if ($a == 'wan') {
1388
		return -1;
1389
	} else if ($b == 'wan') {
1390
		return 1;
1391
	} else if ($a == 'lan') {
1392
		return -1;
1393
	} else if ($b == 'lan') {
1394
		return 1;
1395
	}
1396

    
1397
	return strnatcmp($a, $b);
1398
}
1399

    
1400
/* return the configured interfaces list. */
1401
function get_configured_interface_list($only_opt = false, $withdisabled = false) {
1402
	global $config;
1403

    
1404
	$iflist = array();
1405

    
1406
	/* if list */
1407
	foreach ($config['interfaces'] as $if => $ifdetail) {
1408
		if ($only_opt && ($if == "wan" || $if == "lan")) {
1409
			continue;
1410
		}
1411
		if (isset($ifdetail['enable']) || $withdisabled == true) {
1412
			$iflist[$if] = $if;
1413
		}
1414
	}
1415

    
1416
	return $iflist;
1417
}
1418

    
1419
/* return the configured interfaces list. */
1420
function get_configured_interface_list_by_realif($only_opt = false, $withdisabled = false) {
1421
	global $config;
1422

    
1423
	$iflist = array();
1424

    
1425
	/* if list */
1426
	foreach ($config['interfaces'] as $if => $ifdetail) {
1427
		if ($only_opt && ($if == "wan" || $if == "lan")) {
1428
			continue;
1429
		}
1430
		if (isset($ifdetail['enable']) || $withdisabled == true) {
1431
			$tmpif = get_real_interface($if);
1432
			if (!empty($tmpif)) {
1433
				$iflist[$tmpif] = $if;
1434
			}
1435
		}
1436
	}
1437

    
1438
	return $iflist;
1439
}
1440

    
1441
/* return the configured interfaces list with their description. */
1442
function get_configured_interface_with_descr($only_opt = false, $withdisabled = false) {
1443
	global $config;
1444

    
1445
	$iflist = array();
1446

    
1447
	/* if list */
1448
	foreach ($config['interfaces'] as $if => $ifdetail) {
1449
		if ($only_opt && ($if == "wan" || $if == "lan")) {
1450
			continue;
1451
		}
1452
		if (isset($ifdetail['enable']) || $withdisabled == true) {
1453
			if (empty($ifdetail['descr'])) {
1454
				$iflist[$if] = strtoupper($if);
1455
			} else {
1456
				$iflist[$if] = strtoupper($ifdetail['descr']);
1457
			}
1458
		}
1459
	}
1460

    
1461
	return $iflist;
1462
}
1463

    
1464
/*
1465
 *   get_configured_ip_addresses() - Return a list of all configured
1466
 *   IPv4 addresses.
1467
 *
1468
 */
1469
function get_configured_ip_addresses() {
1470
	global $config;
1471

    
1472
	if (!function_exists('get_interface_ip')) {
1473
		require_once("interfaces.inc");
1474
	}
1475
	$ip_array = array();
1476
	$interfaces = get_configured_interface_list();
1477
	if (is_array($interfaces)) {
1478
		foreach ($interfaces as $int) {
1479
			$ipaddr = get_interface_ip($int);
1480
			$ip_array[$int] = $ipaddr;
1481
		}
1482
	}
1483
	$interfaces = get_configured_vip_list('inet');
1484
	if (is_array($interfaces)) {
1485
		foreach ($interfaces as $int => $ipaddr) {
1486
			$ip_array[$int] = $ipaddr;
1487
		}
1488
	}
1489

    
1490
	/* pppoe server */
1491
	if (is_array($config['pppoes']) && is_array($config['pppoes']['pppoe'])) {
1492
		foreach ($config['pppoes']['pppoe'] as $pppoe) {
1493
			if ($pppoe['mode'] == "server") {
1494
				if (is_ipaddr($pppoe['localip'])) {
1495
					$int = "pppoes". $pppoe['pppoeid'];
1496
					$ip_array[$int] = $pppoe['localip'];
1497
				}
1498
			}
1499
		}
1500
	}
1501

    
1502
	return $ip_array;
1503
}
1504

    
1505
/*
1506
 *   get_configured_ipv6_addresses() - Return a list of all configured
1507
 *   IPv6 addresses.
1508
 *
1509
 */
1510
function get_configured_ipv6_addresses($linklocal_fallback = false) {
1511
	require_once("interfaces.inc");
1512
	$ipv6_array = array();
1513
	$interfaces = get_configured_interface_list();
1514
	if (is_array($interfaces)) {
1515
		foreach ($interfaces as $int) {
1516
			$ipaddrv6 = text_to_compressed_ip6(get_interface_ipv6($int, false, $linklocal_fallback));
1517
			$ipv6_array[$int] = $ipaddrv6;
1518
		}
1519
	}
1520
	$interfaces = get_configured_vip_list('inet6');
1521
	if (is_array($interfaces)) {
1522
		foreach ($interfaces as $int => $ipaddrv6) {
1523
			$ipv6_array[$int] = text_to_compressed_ip6($ipaddrv6);
1524
		}
1525
	}
1526
	return $ipv6_array;
1527
}
1528

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

    
1632
			case "friendly":
1633
				if ($friendly != "") {
1634
					$toput['if'] = $ifname;
1635
					$iflist[$friendly] = $toput;
1636
				}
1637
				break;
1638
			}
1639
		}
1640
	}
1641
	return $iflist;
1642
}
1643

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

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

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

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

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

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

    
1729
	if ($clearsigmask) {
1730
		pcntl_sigprocmask(SIG_SETMASK, $oldset);
1731
	}
1732

    
1733
	return $retval;
1734
}
1735

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

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

    
1764
	$aliastable = array();
1765

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

    
1775
/* check if an alias exists */
1776
function is_alias($name) {
1777
	global $aliastable;
1778

    
1779
	return isset($aliastable[$name]);
1780
}
1781

    
1782
function alias_get_type($name) {
1783
	global $config;
1784

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

    
1793
	return "";
1794
}
1795

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

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

    
1827
function alias_expand_urltable($name) {
1828
	global $config;
1829
	$urltable_prefix = "/var/db/aliastables/";
1830
	$urltable_filename = $urltable_prefix . $name . ".txt";
1831

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

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

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

    
1885
function mac_format($clientmac) {
1886
	global $config, $cpzone;
1887

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

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

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

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

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

    
1904
		default:
1905
			return $clientmac;
1906
	}
1907
}
1908

    
1909
function resolve_retry($hostname, $retries = 5) {
1910

    
1911
	if (is_ipaddr($hostname)) {
1912
		return $hostname;
1913
	}
1914

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

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

    
1924
		sleep(1);
1925
	}
1926

    
1927
	return false;
1928
}
1929

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

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

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

    
1954
	return ("$num {$units[$i]}");
1955
}
1956

    
1957
function update_filter_reload_status($text, $new=false) {
1958
	global $g;
1959

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

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

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

    
1995
function run_plugins($directory) {
1996
	global $config, $g;
1997

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

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

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

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

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

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

    
2053
	return $values;
2054
}
2055

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

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

    
2071
	return $value[$name];
2072
}
2073

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

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

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

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

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

    
2106
	return $ret;
2107
}
2108

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

    
2119
	$result = set_sysctl(array($name => $value));
2120

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

    
2125
	return true;
2126
}
2127

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

    
2142
function mute_kernel_msgs() {
2143
	global $g, $config;
2144
	// Do not mute serial console.  The kernel gets very very cranky
2145
	// and will start dishing you cannot control tty errors.
2146
	if ($g['platform'] == 'nanobsd') {
2147
		return;
2148
	}
2149
	if ($config['system']['enableserial']) {
2150
		return;
2151
	}
2152
	exec("/sbin/conscontrol mute on");
2153
}
2154

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

    
2165
function start_devd() {
2166
	/* Use the undocumented -q options of devd to quiet its log spamming */
2167
	$_gb = exec("/sbin/devd -q");
2168
	sleep(1);
2169
	unset($_gb);
2170
}
2171

    
2172
function is_interface_vlan_mismatch() {
2173
	global $config, $g;
2174

    
2175
	if (is_array($config['vlans']['vlan'])) {
2176
		foreach ($config['vlans']['vlan'] as $vlan) {
2177
			if (substr($vlan['if'], 0, 4) == "lagg") {
2178
				return false;
2179
			}
2180
			if (does_interface_exist($vlan['if']) == false) {
2181
				return true;
2182
			}
2183
		}
2184
	}
2185

    
2186
	return false;
2187
}
2188

    
2189
function is_interface_mismatch() {
2190
	global $config, $g;
2191

    
2192
	$do_assign = false;
2193
	$i = 0;
2194
	$missing_interfaces = array();
2195
	if (is_array($config['interfaces'])) {
2196
		foreach ($config['interfaces'] as $ifname => $ifcfg) {
2197
			if (preg_match("/^enc|^cua|^tun|^tap|^l2tp|^pptp|^ppp|^ovpn|^gif|^gre|^lagg|^bridge|vlan|_wlan/i", $ifcfg['if'])) {
2198
				// Do not check these interfaces.
2199
				$i++;
2200
				continue;
2201
			} else if (does_interface_exist($ifcfg['if']) == false) {
2202
				$missing_interfaces[] = $ifcfg['if'];
2203
				$do_assign = true;
2204
			} else {
2205
				$i++;
2206
			}
2207
		}
2208
	}
2209

    
2210
	if (file_exists("{$g['tmp_path']}/assign_complete")) {
2211
		$do_assign = false;
2212
	}
2213

    
2214
	if (!empty($missing_interfaces) && $do_assign) {
2215
		file_put_contents("{$g['tmp_path']}/missing_interfaces", implode(' ', $missing_interfaces));
2216
	} else {
2217
		@unlink("{$g['tmp_path']}/missing_interfaces");
2218
	}
2219

    
2220
	return $do_assign;
2221
}
2222

    
2223
/* sync carp entries to other firewalls */
2224
function carp_sync_client() {
2225
	global $g;
2226
	send_event("filter sync");
2227
}
2228

    
2229
/****f* util/isAjax
2230
 * NAME
2231
 *   isAjax - reports if the request is driven from prototype
2232
 * INPUTS
2233
 *   none
2234
 * RESULT
2235
 *   true/false
2236
 ******/
2237
function isAjax() {
2238
	return isset ($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest';
2239
}
2240

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

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

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

    
2319
function is_file_included($file = "") {
2320
	$files = get_included_files();
2321
	if (in_array($file, $files)) {
2322
		return true;
2323
	}
2324

    
2325
	return false;
2326
}
2327

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

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

    
2344
	return $data;
2345
}
2346

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

    
2353
	$arrays = func_get_args();
2354
	$remains = $arrays;
2355

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

    
2360
	// loop available array
2361
	foreach ($arrays as $array) {
2362

    
2363
		// The first remaining array is $array. We are processing it. So
2364
		// we remove it from remaining arrays.
2365
		array_shift($remains);
2366

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

    
2380
					if (count($args) > 2) {
2381
						// put the recursion
2382
						$result[$key] = call_user_func_array(__FUNCTION__, $args);
2383
					} else {
2384
						foreach ($value as $vkey => $vval) {
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
function get_staticroutes($returnsubnetsonly = false, $returnhostnames = false, $returnenabledroutesonly = false) {
2414
	global $config, $aliastable;
2415

    
2416
	/* Bail if there are no routes, but return an array always so callers don't have to check. */
2417
	if (!is_array($config['staticroutes']['route'])) {
2418
		return array();
2419
	}
2420

    
2421
	$allstaticroutes = array();
2422
	$allsubnets = array();
2423
	/* Loop through routes and expand aliases as we find them. */
2424
	foreach ($config['staticroutes']['route'] as $route) {
2425
		if ($returnenabledroutesonly && isset($route['disabled'])) {
2426
			continue;
2427
		}
2428

    
2429
		if (is_alias($route['network'])) {
2430
			if (!isset($aliastable[$route['network']])) {
2431
				continue;
2432
			}
2433

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

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

    
2492
/* returns an array consisting of every element of $haystack that is not equal to $needle. */
2493
function array_exclude($needle, $haystack) {
2494
	$result = array();
2495
	if (is_array($haystack)) {
2496
		foreach ($haystack as $thing) {
2497
			if ($needle !== $thing) {
2498
				$result[] = $thing;
2499
			}
2500
		}
2501
	}
2502
	return $result;
2503
}
2504

    
2505
/* Define what is preferred, IPv4 or IPv6 */
2506
function prefer_ipv4_or_ipv6() {
2507
	global $config;
2508

    
2509
	if (isset($config['system']['prefer_ipv4'])) {
2510
		mwexec("/etc/rc.d/ip6addrctl prefer_ipv4");
2511
	} else {
2512
		mwexec("/etc/rc.d/ip6addrctl prefer_ipv6");
2513
	}
2514
}
2515

    
2516
/* Redirect to page passing parameters via POST */
2517
function post_redirect($page, $params) {
2518
	if (!is_array($params)) {
2519
		return;
2520
	}
2521

    
2522
	print "<html><body><form action=\"{$page}\" name=\"formredir\" method=\"post\">\n";
2523
	foreach ($params as $key => $value) {
2524
		print "<input type=\"hidden\" name=\"{$key}\" value=\"{$value}\" />\n";
2525
	}
2526
	print "</form>\n";
2527
	print "<script type=\"text/javascript\">\n";
2528
	print "//<![CDATA[\n";
2529
	print "document.formredir.submit();\n";
2530
	print "//]]>\n";
2531
	print "</script>\n";
2532
	print "</body></html>\n";
2533
}
2534

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

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

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

    
2614
	return false;
2615
}
2616
?>
(55-55/65)