Project

General

Profile

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

    
28
define('VIP_ALL', 1);
29
define('VIP_CARP', 2);
30
define('VIP_IPALIAS', 3);
31

    
32
/* kill a process by pid file */
33
function killbypid($pidfile, $waitfor = 0) {
34
	return sigkillbypid($pidfile, "TERM", $waitfor);
35
}
36

    
37
function isvalidpid($pidfile) {
38
	$output = "";
39
	if (file_exists($pidfile)) {
40
		exec("/bin/pgrep -qnF {$pidfile} 2>/dev/null", $output, $retval);
41
		return (intval($retval) == 0);
42
	}
43
	return false;
44
}
45

    
46
function is_process_running($process) {
47
	$output = "";
48
	if (!empty($process)) {
49
		exec("/bin/pgrep -anx " . escapeshellarg($process), $output, $retval);
50
		return (intval($retval) == 0);
51
	}
52
	return false;
53
}
54

    
55
function isvalidproc($proc) {
56
	return is_process_running($proc);
57
}
58

    
59
/* sigkill a process by pid file, and wait for it to terminate or remove the .pid file for $waitfor seconds */
60
/* return 1 for success and 0 for a failure */
61
function sigkillbypid($pidfile, $sig, $waitfor = 0) {
62
	if (isvalidpid($pidfile)) {
63
		$result = mwexec("/bin/pkill " . escapeshellarg("-{$sig}") .
64
		    " -F {$pidfile}", true);
65
		$waitcounter = $waitfor * 10;
66
		while(isvalidpid($pidfile) && $waitcounter > 0) {
67
			$waitcounter = $waitcounter - 1;
68
			usleep(100000);
69
		}
70
		return $result;
71
	}
72

    
73
	return 0;
74
}
75

    
76
/* kill a process by name */
77
function sigkillbyname($procname, $sig) {
78
	if (isvalidproc($procname)) {
79
		return mwexec("/usr/bin/killall " . escapeshellarg("-{$sig}") . " " . escapeshellarg($procname), true);
80
	}
81
}
82

    
83
/* kill a process by name */
84
function killbyname($procname) {
85
	if (isvalidproc($procname)) {
86
		mwexec("/usr/bin/killall " . escapeshellarg($procname));
87
	}
88
}
89

    
90
function is_subsystem_dirty($subsystem = "") {
91
	global $g;
92

    
93
	if ($subsystem == "") {
94
		return false;
95
	}
96

    
97
	if (file_exists("{$g['varrun_path']}/{$subsystem}.dirty")) {
98
		return true;
99
	}
100

    
101
	return false;
102
}
103

    
104
function mark_subsystem_dirty($subsystem = "") {
105
	global $g;
106

    
107
	if (!file_put_contents("{$g['varrun_path']}/{$subsystem}.dirty", "DIRTY")) {
108
		log_error(sprintf(gettext("WARNING: Could not mark subsystem: %s dirty"), $subsystem));
109
	}
110
}
111

    
112
function clear_subsystem_dirty($subsystem = "") {
113
	global $g;
114

    
115
	@unlink("{$g['varrun_path']}/{$subsystem}.dirty");
116
}
117

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

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

    
157
		return $fp;
158
	}
159

    
160
	return NULL;
161
}
162

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

    
171
/* unlock forcefully configuration file */
172
function unlock_force($lock) {
173
	global $g;
174

    
175
	@unlink("{$g['tmp_path']}/{$lock}.lock");
176
}
177

    
178
function send_event($cmd) {
179
	global $g;
180

    
181
	if (!isset($g['event_address'])) {
182
		$g['event_address'] = "unix:///var/run/check_reload_status";
183
	}
184

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

    
203
function send_multiple_events($cmds) {
204
	global $g;
205

    
206
	if (!isset($g['event_address'])) {
207
		$g['event_address'] = "unix:///var/run/check_reload_status";
208
	}
209

    
210
	if (!is_array($cmds)) {
211
		return;
212
	}
213

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

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

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

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

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

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

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

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

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

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

    
316
/* same as above but returns a string */
317
function gen_subnet_mask($bits) {
318
	return long2ip(gen_subnet_mask_long($bits));
319
}
320

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

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

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

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

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

    
357
	$ip = Net_IPv6::removeNetmaskSpec($ip);
358
	$ip = Net_IPv6::Uncompress($ip);
359

    
360
	$parts = explode(':', $ip);
361

    
362
	foreach ( $parts as $v ) {
363

    
364
		$str     = base_convert($v, 16, 2);
365
		$binstr .= str_pad($str, 16, '0', STR_PAD_LEFT);
366

    
367
	}
368

    
369
	return $binstr;
370
}
371

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

    
380
	if (strlen($bin) < 128) {
381
		$bin = str_pad($bin, 128, '0', STR_PAD_LEFT);
382
	}
383

    
384
	$parts = str_split($bin, "16");
385

    
386
	foreach ( $parts as $v ) {
387
		$str = base_convert($v, 2, 16);
388
		$ip .= $str.":";
389
	}
390

    
391
	$ip = substr($ip, 0, -1);
392

    
393
	return $ip;
394
}
395

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

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

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

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

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

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

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

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

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

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

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

    
490
	if (ip_range_size_v4($startip, $endip) > $max_size) {
491
		return false;
492
	}
493

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

    
501
	return $rangeaddresses;
502
}
503

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

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

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

    
550
	if ($ip1bin == $ip2bin) {
551
		return array($ip1 . '/' . $bits); // exit if ip1=ip2 (trivial case)
552
	}
553

    
554
	if ($ip1bin > $ip2bin) {
555
		list ($ip1bin, $ip2bin) = array($ip2bin, $ip1bin);  // swap if needed (ensures ip1 < ip2)
556
	}
557

    
558
	$rangesubnets = array();
559
	$netsize = 0;
560

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

    
565
		// 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)
566

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

    
575
		// 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)
576

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

    
586
		// this is the only edge case arising from increment/decrement.
587
		// 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)
588

    
589
		if ($ip2bin < $ip1bin) {
590
			continue;
591
		}
592

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

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

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

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

    
612
	ksort($rangesubnets, SORT_STRING);
613
	$out = array();
614

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

    
624
	return $out;
625
}
626

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

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

    
657
/* returns true if $ipaddr is a valid IPv6 address */
658
function is_ipaddrv6($ipaddr) {
659
	if (!is_string($ipaddr) || empty($ipaddr)) {
660
		return false;
661
	}
662
	/*
663
	 * While Net_IPv6::checkIPv6() considers IPv6/mask a valid IPv6,
664
	 * is_ipaddrv6() needs to be more strict to keep the compatibility
665
	 * with is_ipaddrv4().
666
	 */
667
	if (strstr($ipaddr, "/")) {
668
		return false;
669
	}
670
	if (strstr($ipaddr, "%") && is_linklocal($ipaddr)) {
671
		$tmpip = explode("%", $ipaddr);
672
		$ipaddr = $tmpip[0];
673
	}
674
	return Net_IPv6::checkIPv6($ipaddr);
675
}
676

    
677
/* returns true if $ipaddr is a valid dotted IPv4 address */
678
function is_ipaddrv4($ipaddr) {
679
	if (!is_string($ipaddr) || empty($ipaddr) || ip2long($ipaddr) === FALSE) {
680
		return false;
681
	}
682
	return true;
683
}
684

    
685
function is_mcast($ipaddr) {
686
	if (is_mcastv4($ipaddr)) {
687
		return 4;
688
	}
689
	if (is_mcastv6($ipaddr)) {
690
		return 6;
691
	}
692
	return false;
693
}
694

    
695
function is_mcastv4($ipaddr) {
696
	if (!is_ipaddrv4($ipaddr) ||
697
	    !preg_match('/^2(?:2[4-9]|3\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d?|0)){3}$/', $ipaddr)) {
698
		return false;
699
	}
700
	return true;
701
}
702

    
703
function is_mcastv6($ipaddr) {
704
	if (!is_ipaddrv6($ipaddr) || !preg_match('/^ff.+$/', $ipaddr)) {
705
		return false;
706
	}
707
	return true;
708
}
709

    
710
/* returns 4 or 6 respectively (== TRUE) if $ipaddr is a valid IPv4 or IPv6 linklocal address
711
   returns '' if not a valid linklocal address */
712
function is_linklocal($ipaddr) {
713
	if (is_ipaddrv4($ipaddr)) {
714
		// input is IPv4
715
		// test if it's 169.254.x.x per rfc3927 2.1
716
		$ip4 = explode(".", $ipaddr);
717
		if ($ip4[0] == '169' && $ip4[1] == '254') {
718
			return 4;
719
		}
720
	} elseif (Net_IPv6::getAddressType($ipaddr) == NET_IPV6_LOCAL_LINK) {
721
		return 6;
722
	}
723
	return '';
724
}
725

    
726
/* returns scope of a linklocal address */
727
function get_ll_scope($addr) {
728
	if (!is_linklocal($addr) || !strstr($addr, "%")) {
729
		return "";
730
	}
731
	list ($ll, $scope) = explode("%", $addr);
732
	return $scope;
733
}
734

    
735
/* returns true if $ipaddr is a valid literal IPv6 address */
736
function is_literalipaddrv6($ipaddr) {
737
	if (substr($ipaddr,0,1) == '[' && substr($ipaddr,-1,1) == ']') {
738
		// if it's data wrapped in "[ ... ]" then test if middle part is valid IPv6
739
		return is_ipaddrv6(substr($ipaddr,1,-1));
740
	}
741
	return false;
742
}
743

    
744
/* returns true if $iport is a valid IPv4:port or [Literal IPv6]:port
745
	false - not valid
746
	true (numeric 4 or 6) - if valid, gives type of address */
747
function is_ipaddrwithport($ipport) {
748
	$c = strrpos($ipport, ":");
749
	if ($c === false) {
750
		return false;  // can't split at final colon if no colon exists
751
	}
752

    
753
	if (!is_port(substr($ipport, $c + 1))) {
754
		return false;  // no valid port after last colon
755
	}
756

    
757
	$ip = substr($ipport, 0, $c);  // else is text before last colon a valid IP
758
	if (is_literalipaddrv6($ip)) {
759
		return 6;
760
	} elseif (is_ipaddrv4($ip)) {
761
		return 4;
762
	} else {
763
		return false;
764
	}
765
}
766

    
767
function is_hostnamewithport($hostport) {
768
	$parts = explode(":", $hostport);
769
	// no need to validate with is_string(); if it's not a string then explode won't return 2 parts anyway
770
	if (count($parts) == 2) {
771
		return is_hostname($parts[0]) && is_port($parts[1]);
772
	}
773
	return false;
774
}
775

    
776
/* returns true if $ipaddr is a valid dotted IPv4 address or an alias thereof */
777
function is_ipaddroralias($ipaddr) {
778
	global $config;
779

    
780
	if (is_alias($ipaddr)) {
781
		if (is_array($config['aliases']['alias'])) {
782
			foreach ($config['aliases']['alias'] as $alias) {
783
				if ($alias['name'] == $ipaddr && !preg_match("/port/i", $alias['type'])) {
784
					return true;
785
				}
786
			}
787
		}
788
		return false;
789
	} else {
790
		return is_ipaddr($ipaddr);
791
	}
792

    
793
}
794

    
795
/* returns true if $subnet is a valid IPv4 or IPv6 subnet in CIDR format
796
	false - if not a valid subnet
797
	true (numeric 4 or 6) - if valid, gives type of subnet */
798
function is_subnet($subnet) {
799
	if (is_string($subnet) && preg_match('/^(?:([0-9.]{7,15})|([0-9a-f:]{2,39}|[0-9a-f:]{2,30}[0-9.]{7,15}))\/(\d{1,3})$/i', $subnet, $parts)) {
800
		if (is_ipaddrv4($parts[1]) && $parts[3] <= 32) {
801
			return 4;
802
		}
803
		if (is_ipaddrv6($parts[2]) && $parts[3] <= 128) {
804
			return 6;
805
		}
806
	}
807
	return false;
808
}
809

    
810
function is_v4($ip_or_subnet) {
811
	return is_ipaddrv4($ip_or_subnet) || is_subnetv4($ip_or_subnet);
812
}
813

    
814
function is_v6($ip_or_subnet) {
815
	return is_ipaddrv6($ip_or_subnet) || is_subnetv6($ip_or_subnet);
816
}
817

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
938
	if (!is_ipaddrv6($subnet)) {
939
		return $result;
940
	}
941

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

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

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

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

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

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

    
981
	return $result;
982
}
983

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

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

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

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

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

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

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

    
1048
/* returns true if $macaddr is a valid MAC address */
1049
function is_macaddr($macaddr, $partial=false) {
1050
	$values = explode(":", $macaddr);
1051

    
1052
	/* Verify if the MAC address has a proper amount of parts for either a partial or full match. */
1053
	if ($partial) {
1054
		if ((count($values) < 1) || (count($values) > 6)) {
1055
			return false;
1056
		}
1057
	} elseif (count($values) != 6) {
1058
		return false;
1059
	}
1060
	for ($i = 0; $i < count($values); $i++) {
1061
		if (ctype_xdigit($values[$i]) == false)
1062
			return false;
1063
		if (hexdec($values[$i]) < 0 || hexdec($values[$i]) > 255)
1064
			return false;
1065
	}
1066

    
1067
	return true;
1068
}
1069

    
1070
/*
1071
	If $return_message is true then
1072
		returns a text message about the reason that the name is invalid.
1073
		the text includes the type of "thing" that is being checked, passed in $object. (e.g. "alias", "gateway group", "schedule")
1074
	else
1075
		returns true if $name is a valid name for an alias
1076
		returns false if $name is not a valid name for an alias
1077

    
1078
	Aliases cannot be:
1079
		bad chars: anything except a-z 0-9 and underscore
1080
		bad names: empty string, pure numeric, pure underscore
1081
		reserved words: pre-defined service/protocol/port names which should not be ambiguous, and the words "port" and  "pass" */
1082

    
1083
function is_validaliasname($name, $return_message = false, $object = "alias") {
1084
	/* Array of reserved words */
1085
	$reserved = array("port", "pass");
1086

    
1087
	if (!is_string($name) || strlen($name) >= 32 || preg_match('/(^_*$|^\d*$|[^a-z0-9_])/i', $name)) {
1088
		if ($return_message) {
1089
			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, _');
1090
		} else {
1091
			return false;
1092
		}
1093
	}
1094
	if (in_array($name, $reserved, true)) {
1095
		if ($return_message) {
1096
			return sprintf(gettext('The %1$s name must not be either of the reserved words %2$s or %3$s.'), $object, "'port'", "'pass'");
1097
		} else {
1098
			return false;
1099
		}
1100
	}
1101
	if (getprotobyname($name)) {
1102
		if ($return_message) {
1103
			return sprintf(gettext('The %1$s name must not be an IP protocol name such as TCP, UDP, ICMP etc.'), $object);
1104
		} else {
1105
			return false;
1106
		}
1107
	}
1108
	if (getservbyname($name, "tcp") || getservbyname($name, "udp")) {
1109
		if ($return_message) {
1110
			return sprintf(gettext('The %1$s name must not be a well-known or registered TCP or UDP port name such as ssh, smtp, pop3, tftp, http, openvpn etc.'), $object);
1111
		} else {
1112
			return false;
1113
		}
1114
	}
1115
	if ($return_message) {
1116
		return sprintf(gettext("The %1$s name is valid."), $object);
1117
	} else {
1118
		return true;
1119
	}
1120
}
1121

    
1122
/* returns a text message indicating if the alias name is valid, or the reason it is not valid. */
1123
function invalidaliasnamemsg($name, $object = "alias") {
1124
	return is_validaliasname($name, true, $object);
1125
}
1126

    
1127
/*
1128
 * returns true if $range is a valid integer range between $min and $max
1129
 * range delimiter can be ':' or '-'
1130
 */
1131
function is_intrange($range, $min, $max) {
1132
	$values = preg_split("/[:-]/", $range);
1133

    
1134
	if (!is_array($values) || count($values) != 2) {
1135
		return false;
1136
	}
1137

    
1138
	if (!ctype_digit($values[0]) || !ctype_digit($values[1])) {
1139
		return false;
1140
	}
1141

    
1142
	$values[0] = intval($values[0]);
1143
	$values[1] = intval($values[1]);
1144

    
1145
	if ($values[0] >= $values[1]) {
1146
		return false;
1147
	}
1148

    
1149
	if ($values[0] < $min || $values[1] > $max) {
1150
		return false;
1151
	}
1152

    
1153
	return true;
1154
}
1155

    
1156
/* returns true if $port is a valid TCP/UDP port */
1157
function is_port($port) {
1158
	if (ctype_digit($port) && ((intval($port) >= 1) && (intval($port) <= 65535))) {
1159
		return true;
1160
	}
1161
	if (getservbyname($port, "tcp") || getservbyname($port, "udp")) {
1162
		return true;
1163
	}
1164
	return false;
1165
}
1166

    
1167
/* returns true if $port is in use */
1168
function is_port_in_use($port, $proto = "tcp", $ip_version = 4) {
1169
	$port_info = array();
1170
	exec("/usr/bin/netstat --libxo json -an " . escapeshellarg('-' . $ip_version) . " -p " . escapeshellarg($proto), $rawdata, $rc);
1171
	if ($rc == 0) {
1172
		$netstatarr = json_decode(implode(" ", $rawdata), JSON_OBJECT_AS_ARRAY);
1173
		$netstatarr = $netstatarr['statistics']['socket'];
1174

    
1175
		foreach($netstatarr as $index => $portstats){
1176
			array_push($port_info, $portstats['local']['port']);
1177
		}
1178
	}
1179

    
1180
	return in_array($port, $port_info);
1181
}
1182

    
1183
/* returns true if $portrange is a valid TCP/UDP portrange ("<port>:<port>") */
1184
function is_portrange($portrange) {
1185
	$ports = explode(":", $portrange);
1186

    
1187
	return (count($ports) == 2 && is_port($ports[0]) && is_port($ports[1]));
1188
}
1189

    
1190
/* returns true if $port is a valid TCP/UDP port number or range ("<port>:<port>") */
1191
function is_port_or_range($port) {
1192
	return (is_port($port) || is_portrange($port));
1193
}
1194

    
1195
/* returns true if $port is an alias that is a port type */
1196
function is_portalias($port) {
1197
	global $config;
1198

    
1199
	if (is_alias($port)) {
1200
		if (is_array($config['aliases']['alias'])) {
1201
			foreach ($config['aliases']['alias'] as $alias) {
1202
				if ($alias['name'] == $port && preg_match("/port/i", $alias['type'])) {
1203
					return true;
1204
				}
1205
			}
1206
		}
1207
	}
1208
	return false;
1209
}
1210

    
1211
/* returns true if $port is a valid port number or an alias thereof */
1212
function is_port_or_alias($port) {
1213
	return (is_port($port) || is_portalias($port));
1214
}
1215

    
1216
/* returns true if $port is a valid TCP/UDP port number or range ("<port>:<port>") or an alias thereof */
1217
function is_port_or_range_or_alias($port) {
1218
	return (is_port($port) || is_portrange($port) || is_portalias($port));
1219
}
1220

    
1221
/* create ranges of sequential port numbers (200:215) and remove duplicates */
1222
function group_ports($ports, $kflc = false) {
1223
	if (!is_array($ports) || empty($ports)) {
1224
		return;
1225
	}
1226

    
1227
	$uniq = array();
1228
	$comments = array();
1229
	foreach ($ports as $port) {
1230
		if (($kflc) && (strpos($port, '#') === 0)) {	// Keep Full Line Comments (lines beginning with #).
1231
			$comments[] = $port;
1232
		} else if (is_portrange($port)) {
1233
			list($begin, $end) = explode(":", $port);
1234
			if ($begin > $end) {
1235
				$aux = $begin;
1236
				$begin = $end;
1237
				$end = $aux;
1238
			}
1239
			for ($i = $begin; $i <= $end; $i++) {
1240
				if (!in_array($i, $uniq)) {
1241
					$uniq[] = $i;
1242
				}
1243
			}
1244
		} else if (is_port($port)) {
1245
			if (!in_array($port, $uniq)) {
1246
				$uniq[] = $port;
1247
			}
1248
		}
1249
	}
1250
	sort($uniq, SORT_NUMERIC);
1251

    
1252
	$result = array();
1253
	foreach ($uniq as $idx => $port) {
1254
		if ($idx == 0) {
1255
			$result[] = $port;
1256
			continue;
1257
		}
1258

    
1259
		$last = end($result);
1260
		if (is_portrange($last)) {
1261
			list($begin, $end) = explode(":", $last);
1262
		} else {
1263
			$begin = $end = $last;
1264
		}
1265

    
1266
		if ($port == ($end+1)) {
1267
			$end++;
1268
			$result[count($result)-1] = "{$begin}:{$end}";
1269
		} else {
1270
			$result[] = $port;
1271
		}
1272
	}
1273

    
1274
	return array_merge($comments, $result);
1275
}
1276

    
1277
/* returns true if $val is a valid shaper bandwidth value */
1278
function is_valid_shaperbw($val) {
1279
	return (preg_match("/^(\d+(?:\.\d+)?)([MKG]?b|%)$/", $val));
1280
}
1281

    
1282
/* returns true if $test is in the range between $start and $end */
1283
function is_inrange_v4($test, $start, $end) {
1284
	if (!is_ipaddrv4($test) || !is_ipaddrv4($start) || !is_ipaddrv4($end)) {
1285
		return false;
1286
	}
1287

    
1288
	if (ip2ulong($test) <= ip2ulong($end) &&
1289
	    ip2ulong($test) >= ip2ulong($start)) {
1290
		return true;
1291
	}
1292

    
1293
	return false;
1294
}
1295

    
1296
/* returns true if $test is in the range between $start and $end */
1297
function is_inrange_v6($test, $start, $end) {
1298
	if (!is_ipaddrv6($test) || !is_ipaddrv6($start) || !is_ipaddrv6($end)) {
1299
		return false;
1300
	}
1301

    
1302
	if (inet_pton($test) <= inet_pton($end) &&
1303
	    inet_pton($test) >= inet_pton($start)) {
1304
		return true;
1305
	}
1306

    
1307
	return false;
1308
}
1309

    
1310
/* returns true if $test is in the range between $start and $end */
1311
function is_inrange($test, $start, $end) {
1312
	return is_ipaddrv6($test) ? is_inrange_v6($test, $start, $end) : is_inrange_v4($test, $start, $end);
1313
}
1314

    
1315
function build_vip_list($fif, $family = "all") {
1316
	$list = array('address' => gettext('Interface Address'));
1317

    
1318
	$viplist = get_configured_vip_list($family);
1319
	foreach ($viplist as $vip => $address) {
1320
		if ($fif == get_configured_vip_interface($vip)) {
1321
			$list[$vip] = "$address";
1322
			if (get_vip_descr($address)) {
1323
				$list[$vip] .= " (". get_vip_descr($address) .")";
1324
			}
1325
		}
1326
	}
1327

    
1328
	return($list);
1329
}
1330

    
1331
function get_configured_vip_list($family = 'all', $type = VIP_ALL) {
1332
	global $config;
1333

    
1334
	$list = array();
1335
	if (!is_array($config['virtualip']) ||
1336
	    !is_array($config['virtualip']['vip']) ||
1337
	    empty($config['virtualip']['vip'])) {
1338
		return ($list);
1339
	}
1340

    
1341
	$viparr = &$config['virtualip']['vip'];
1342
	foreach ($viparr as $vip) {
1343

    
1344
		if ($type == VIP_CARP) {
1345
			if ($vip['mode'] != "carp")
1346
				continue;
1347
		} elseif ($type == VIP_IPALIAS) {
1348
			if ($vip['mode'] != "ipalias")
1349
				continue;
1350
		} else {
1351
			if ($vip['mode'] != "carp" && $vip['mode'] != "ipalias")
1352
				continue;
1353
		}
1354

    
1355
		if ($family == 'all' ||
1356
		    ($family == 'inet' && is_ipaddrv4($vip['subnet'])) ||
1357
		    ($family == 'inet6' && is_ipaddrv6($vip['subnet']))) {
1358
			$list["_vip{$vip['uniqid']}"] = $vip['subnet'];
1359
		}
1360
	}
1361
	return ($list);
1362
}
1363

    
1364
function get_configured_vip($vipinterface = '') {
1365

    
1366
	return (get_configured_vip_detail($vipinterface, 'all', 'vip'));
1367
}
1368

    
1369
function get_configured_vip_interface($vipinterface = '') {
1370

    
1371
	return (get_configured_vip_detail($vipinterface, 'all', 'iface'));
1372
}
1373

    
1374
function get_configured_vip_ipv4($vipinterface = '') {
1375

    
1376
	return (get_configured_vip_detail($vipinterface, 'inet', 'ip'));
1377
}
1378

    
1379
function get_configured_vip_ipv6($vipinterface = '') {
1380

    
1381
	return (get_configured_vip_detail($vipinterface, 'inet6', 'ip'));
1382
}
1383

    
1384
function get_configured_vip_subnetv4($vipinterface = '') {
1385

    
1386
	return (get_configured_vip_detail($vipinterface, 'inet', 'subnet'));
1387
}
1388

    
1389
function get_configured_vip_subnetv6($vipinterface = '') {
1390

    
1391
	return (get_configured_vip_detail($vipinterface, 'inet6', 'subnet'));
1392
}
1393

    
1394
function get_configured_vip_detail($vipinterface = '', $family = 'inet', $what = 'ip') {
1395
	global $config;
1396

    
1397
	if (empty($vipinterface) ||
1398
	    !is_array($config['virtualip']) ||
1399
	    !is_array($config['virtualip']['vip']) ||
1400
	    empty($config['virtualip']['vip'])) {
1401
		return (NULL);
1402
	}
1403

    
1404
	$viparr = &$config['virtualip']['vip'];
1405
	foreach ($viparr as $vip) {
1406
		if ($vip['mode'] != "carp" && $vip['mode'] != "ipalias") {
1407
			continue;
1408
		}
1409

    
1410
		if ($vipinterface != "_vip{$vip['uniqid']}") {
1411
			continue;
1412
		}
1413

    
1414
		switch ($what) {
1415
			case 'subnet':
1416
				if ($family == 'inet' && is_ipaddrv4($vip['subnet']))
1417
					return ($vip['subnet_bits']);
1418
				else if ($family == 'inet6' && is_ipaddrv6($vip['subnet']))
1419
					return ($vip['subnet_bits']);
1420
				break;
1421
			case 'iface':
1422
				return ($vip['interface']);
1423
				break;
1424
			case 'vip':
1425
				return ($vip);
1426
				break;
1427
			case 'ip':
1428
			default:
1429
				if ($family == 'inet' && is_ipaddrv4($vip['subnet'])) {
1430
					return ($vip['subnet']);
1431
				} else if ($family == 'inet6' && is_ipaddrv6($vip['subnet'])) {
1432
					return ($vip['subnet']);
1433
				}
1434
				break;
1435
		}
1436
		break;
1437
	}
1438

    
1439
	return (NULL);
1440
}
1441

    
1442
/* comparison function for sorting by the order in which interfaces are normally created */
1443
function compare_interface_friendly_names($a, $b) {
1444
	if ($a == $b) {
1445
		return 0;
1446
	} else if ($a == 'wan') {
1447
		return -1;
1448
	} else if ($b == 'wan') {
1449
		return 1;
1450
	} else if ($a == 'lan') {
1451
		return -1;
1452
	} else if ($b == 'lan') {
1453
		return 1;
1454
	}
1455

    
1456
	return strnatcmp($a, $b);
1457
}
1458

    
1459
/* return the configured interfaces list. */
1460
function get_configured_interface_list($withdisabled = false) {
1461
	global $config;
1462

    
1463
	$iflist = array();
1464

    
1465
	/* if list */
1466
	foreach ($config['interfaces'] as $if => $ifdetail) {
1467
		if (isset($ifdetail['enable']) || $withdisabled == true) {
1468
			$iflist[$if] = $if;
1469
		}
1470
	}
1471

    
1472
	return $iflist;
1473
}
1474

    
1475
/* return the configured interfaces list. */
1476
function get_configured_interface_list_by_realif($withdisabled = false) {
1477
	global $config;
1478

    
1479
	$iflist = array();
1480

    
1481
	/* if list */
1482
	foreach ($config['interfaces'] as $if => $ifdetail) {
1483
		if (isset($ifdetail['enable']) || $withdisabled == true) {
1484
			$tmpif = get_real_interface($if);
1485
			if (!empty($tmpif)) {
1486
				$iflist[$tmpif] = $if;
1487
			}
1488
		}
1489
	}
1490

    
1491
	return $iflist;
1492
}
1493

    
1494
/* return the configured interfaces list with their description. */
1495
function get_configured_interface_with_descr($withdisabled = false) {
1496
	global $config, $user_settings;
1497

    
1498
	$iflist = array();
1499

    
1500
	/* if list */
1501
	foreach ($config['interfaces'] as $if => $ifdetail) {
1502
		if (isset($ifdetail['enable']) || $withdisabled == true) {
1503
			if (empty($ifdetail['descr'])) {
1504
				$iflist[$if] = strtoupper($if);
1505
			} else {
1506
				$iflist[$if] = strtoupper($ifdetail['descr']);
1507
			}
1508
		}
1509
	}
1510

    
1511
	if ($user_settings['webgui']['interfacessort']) {
1512
		asort($iflist);
1513
	}
1514

    
1515
	return $iflist;
1516
}
1517

    
1518
/*
1519
 *   get_configured_ip_addresses() - Return a list of all configured
1520
 *   IPv4 addresses.
1521
 *
1522
 */
1523
function get_configured_ip_addresses() {
1524
	global $config;
1525

    
1526
	if (!function_exists('get_interface_ip')) {
1527
		require_once("interfaces.inc");
1528
	}
1529
	$ip_array = array();
1530
	$interfaces = get_configured_interface_list();
1531
	if (is_array($interfaces)) {
1532
		foreach ($interfaces as $int) {
1533
			$ipaddr = get_interface_ip($int);
1534
			$ip_array[$int] = $ipaddr;
1535
		}
1536
	}
1537
	$interfaces = get_configured_vip_list('inet');
1538
	if (is_array($interfaces)) {
1539
		foreach ($interfaces as $int => $ipaddr) {
1540
			$ip_array[$int] = $ipaddr;
1541
		}
1542
	}
1543

    
1544
	/* pppoe server */
1545
	if (is_array($config['pppoes']) && is_array($config['pppoes']['pppoe'])) {
1546
		foreach ($config['pppoes']['pppoe'] as $pppoe) {
1547
			if ($pppoe['mode'] == "server") {
1548
				if (is_ipaddr($pppoe['localip'])) {
1549
					$int = "poes". $pppoe['pppoeid'];
1550
					$ip_array[$int] = $pppoe['localip'];
1551
				}
1552
			}
1553
		}
1554
	}
1555

    
1556
	return $ip_array;
1557
}
1558

    
1559
/*
1560
 *   get_configured_ipv6_addresses() - Return a list of all configured
1561
 *   IPv6 addresses.
1562
 *
1563
 */
1564
function get_configured_ipv6_addresses($linklocal_fallback = false) {
1565
	require_once("interfaces.inc");
1566
	$ipv6_array = array();
1567
	$interfaces = get_configured_interface_list();
1568
	if (is_array($interfaces)) {
1569
		foreach ($interfaces as $int) {
1570
			$ipaddrv6 = text_to_compressed_ip6(get_interface_ipv6($int, false, $linklocal_fallback));
1571
			$ipv6_array[$int] = $ipaddrv6;
1572
		}
1573
	}
1574
	$interfaces = get_configured_vip_list('inet6');
1575
	if (is_array($interfaces)) {
1576
		foreach ($interfaces as $int => $ipaddrv6) {
1577
			$ipv6_array[$int] = text_to_compressed_ip6($ipaddrv6);
1578
		}
1579
	}
1580
	return $ipv6_array;
1581
}
1582

    
1583
/*
1584
 *   get_interface_list() - Return a list of all physical interfaces
1585
 *   along with MAC and status.
1586
 *
1587
 *   $mode = "active" - use ifconfig -lu
1588
 *           "media"  - use ifconfig to check physical connection
1589
 *			status (much slower)
1590
 */
1591
function get_interface_list($mode = "active", $keyby = "physical", $vfaces = "") {
1592
	global $config;
1593
	$upints = array();
1594
	/* get a list of virtual interface types */
1595
	if (!$vfaces) {
1596
		$vfaces = array(
1597
				'bridge',
1598
				'ppp',
1599
				'pppoe',
1600
				'poes',
1601
				'pptp',
1602
				'l2tp',
1603
				'l2tps',
1604
				'sl',
1605
				'gif',
1606
				'gre',
1607
				'faith',
1608
				'lo',
1609
				'ng',
1610
				'_vlan',
1611
				'_wlan',
1612
				'pflog',
1613
				'plip',
1614
				'pfsync',
1615
				'enc',
1616
				'tun',
1617
				'lagg',
1618
				'vip',
1619
				'ipfw',
1620
				'vxlan'
1621
		);
1622
	} else {
1623
		$vfaces = array(
1624
				'bridge',
1625
				'poes',
1626
				'sl',
1627
				'faith',
1628
				'lo',
1629
				'ng',
1630
				'_vlan',
1631
				'_wlan',
1632
				'pflog',
1633
				'plip',
1634
				'pfsync',
1635
				'enc',
1636
				'tun',
1637
				'lagg',
1638
				'vip',
1639
				'ipfw',
1640
				'l2tps'
1641
		);
1642
	}
1643
	switch ($mode) {
1644
		case "active":
1645
			$upints = pfSense_interface_listget(IFF_UP);
1646
			break;
1647
		case "media":
1648
			$intlist = pfSense_interface_listget();
1649
			$ifconfig = "";
1650
			exec("/sbin/ifconfig -a", $ifconfig);
1651
			$regexp = '/(' . implode('|', $intlist) . '):\s/';
1652
			$ifstatus = preg_grep('/status:/', $ifconfig);
1653
			foreach ($ifstatus as $status) {
1654
				$int = array_shift($intlist);
1655
				if (stristr($status, "active")) {
1656
					$upints[] = $int;
1657
				}
1658
			}
1659
			break;
1660
		default:
1661
			$upints = pfSense_interface_listget();
1662
			break;
1663
	}
1664
	/* build interface list with netstat */
1665
	$linkinfo = "";
1666
	exec("/usr/bin/netstat -inW -f link | awk '{ print $1, $4 }'", $linkinfo);
1667
	array_shift($linkinfo);
1668
	/* build ip address list with netstat */
1669
	$ipinfo = "";
1670
	exec("/usr/bin/netstat -inW -f inet | awk '{ print $1, $4 }'", $ipinfo);
1671
	array_shift($ipinfo);
1672
	foreach ($linkinfo as $link) {
1673
		$friendly = "";
1674
		$alink = explode(" ", $link);
1675
		$ifname = rtrim(trim($alink[0]), '*');
1676
		/* trim out all numbers before checking for vfaces */
1677
		if (!in_array(array_shift(preg_split('/(\d-)*\d$/', $ifname)), $vfaces) &&
1678
		    interface_is_vlan($ifname) == NULL &&
1679
		    interface_is_qinq($ifname) == NULL &&
1680
		    !stristr($ifname, "_wlan") &&
1681
		    !stristr($ifname, "_stf")) {
1682
			$toput = array(
1683
					"mac" => trim($alink[1]),
1684
					"up" => in_array($ifname, $upints)
1685
				);
1686
			foreach ($ipinfo as $ip) {
1687
				$aip = explode(" ", $ip);
1688
				if ($aip[0] == $ifname) {
1689
					$toput['ipaddr'] = $aip[1];
1690
				}
1691
			}
1692
			if (is_array($config['interfaces'])) {
1693
				foreach ($config['interfaces'] as $name => $int) {
1694
					if ($int['if'] == $ifname) {
1695
						$friendly = $name;
1696
					}
1697
				}
1698
			}
1699
			switch ($keyby) {
1700
			case "physical":
1701
				if ($friendly != "") {
1702
					$toput['friendly'] = $friendly;
1703
				}
1704
				$dmesg_arr = array();
1705
				exec("/sbin/dmesg |grep $ifname | head -n1", $dmesg_arr);
1706
				preg_match_all("/<(.*?)>/i", $dmesg_arr[0], $dmesg);
1707
				$toput['dmesg'] = $dmesg[1][0];
1708
				$iflist[$ifname] = $toput;
1709
				break;
1710
			case "ppp":
1711

    
1712
			case "friendly":
1713
				if ($friendly != "") {
1714
					$toput['if'] = $ifname;
1715
					$iflist[$friendly] = $toput;
1716
				}
1717
				break;
1718
			}
1719
		}
1720
	}
1721
	return $iflist;
1722
}
1723

    
1724
function get_lagg_interface_list() {
1725
	global $config;
1726

    
1727
	$plist = array();
1728
	if (isset($config['laggs']['lagg']) && is_array($config['laggs']['lagg'])) {
1729
		foreach ($config['laggs']['lagg'] as $lagg) {
1730
			$lagg['mac'] = get_interface_mac($lagg['laggif']);
1731
			$lagg['islagg'] = true;
1732
			$plist[$lagg['laggif']] = $lagg;
1733
		}
1734
	}
1735

    
1736
	return ($plist);
1737
}
1738

    
1739
/****f* util/log_error
1740
* NAME
1741
*   log_error  - Sends a string to syslog.
1742
* INPUTS
1743
*   $error     - string containing the syslog message.
1744
* RESULT
1745
*   null
1746
******/
1747
function log_error($error) {
1748
	global $g;
1749
	$page = $_SERVER['SCRIPT_NAME'];
1750
	if (empty($page)) {
1751
		$files = get_included_files();
1752
		$page = basename($files[0]);
1753
	}
1754
	syslog(LOG_ERR, "$page: $error");
1755
	if ($g['debug']) {
1756
		syslog(LOG_WARNING, var_dump(debug_backtrace()));
1757
	}
1758
	return;
1759
}
1760

    
1761
/****f* util/log_auth
1762
* NAME
1763
*   log_auth   - Sends a string to syslog as LOG_AUTH facility
1764
* INPUTS
1765
*   $error     - string containing the syslog message.
1766
* RESULT
1767
*   null
1768
******/
1769
function log_auth($error) {
1770
	global $g;
1771
	$page = $_SERVER['SCRIPT_NAME'];
1772
	syslog(LOG_AUTH, "$page: $error");
1773
	if ($g['debug']) {
1774
		syslog(LOG_WARNING, var_dump(debug_backtrace()));
1775
	}
1776
	return;
1777
}
1778

    
1779
/****f* util/exec_command
1780
 * NAME
1781
 *   exec_command - Execute a command and return a string of the result.
1782
 * INPUTS
1783
 *   $command   - String of the command to be executed.
1784
 * RESULT
1785
 *   String containing the command's result.
1786
 * NOTES
1787
 *   This function returns the command's stdout and stderr.
1788
 ******/
1789
function exec_command($command) {
1790
	$output = array();
1791
	exec($command . ' 2>&1', $output);
1792
	return(implode("\n", $output));
1793
}
1794

    
1795
/* wrapper for exec()
1796
   Executes in background or foreground.
1797
   For background execution, returns PID of background process to allow calling code control */
1798
function mwexec($command, $nologentry = false, $clearsigmask = false, $background = false) {
1799
	global $g;
1800
	$retval = 0;
1801

    
1802
	if ($g['debug']) {
1803
		if (!$_SERVER['REMOTE_ADDR']) {
1804
			echo "mwexec(): $command" . ($background ? " [BG]":"") . "\n";
1805
		}
1806
	}
1807
	if ($clearsigmask) {
1808
		$oldset = array();
1809
		pcntl_sigprocmask(SIG_SETMASK, array(), $oldset);
1810
	}
1811

    
1812
	if ($background) {
1813
		// start background process and return PID
1814
		$retval = exec("/usr/bin/nohup $command > /dev/null 2>&1 & echo $!");
1815
	} else {
1816
		// run in foreground, and (optionally) log if nonzero return
1817
		$outputarray = array();
1818
		exec("$command 2>&1", $outputarray, $retval);
1819
		if (($retval <> 0) && (!$nologentry || isset($config['system']['developerspew']))) {
1820
			log_error(sprintf(gettext("The command '%1\$s' returned exit code '%2\$d', the output was '%3\$s' "), $command, $retval, implode(" ", $outputarray)));
1821
		}
1822
	}
1823

    
1824
	if ($clearsigmask) {
1825
		pcntl_sigprocmask(SIG_SETMASK, $oldset);
1826
	}
1827

    
1828
	return $retval;
1829
}
1830

    
1831
/* wrapper for exec() in background */
1832
function mwexec_bg($command, $clearsigmask = false) {
1833
	return mwexec($command, false, $clearsigmask, true);
1834
}
1835

    
1836
/*	unlink a file, or pattern-match of a file, if it exists
1837
	if the file/path contains glob() compatible wildcards, all matching files will be unlinked
1838
	any warning/errors are suppressed (e.g. no matching files to delete)
1839
	If there are matching file(s) and they were all unlinked OK, then return true.
1840
	Otherwise return false (the requested file(s) did not exist, or could not be deleted)
1841
	This allows the caller to know if they were the one to successfully delete the file(s).
1842
*/
1843
function unlink_if_exists($fn) {
1844
	$to_do = glob($fn);
1845
	if (is_array($to_do) && count($to_do) > 0) {
1846
		// Returns an array of true/false indicating if each unlink worked
1847
		$results = @array_map("unlink", $to_do);
1848
		// If there is no false in the array, then all went well
1849
		$result = !in_array(false, $results, true);
1850
	} else {
1851
		$result = @unlink($fn);
1852
	}
1853
	return $result;
1854
}
1855
/* make a global alias table (for faster lookups) */
1856
function alias_make_table() {
1857
	global $aliastable, $config;
1858

    
1859
	$aliastable = array();
1860

    
1861
	init_config_arr(array('aliases', 'alias'));
1862
	foreach ($config['aliases']['alias'] as $alias) {
1863
		if ($alias['name']) {
1864
			$aliastable[$alias['name']] = $alias['address'];
1865
		}
1866
	}
1867
}
1868

    
1869
/* check if an alias exists */
1870
function is_alias($name) {
1871
	global $aliastable;
1872

    
1873
	return isset($aliastable[$name]);
1874
}
1875

    
1876
function alias_get_type($name) {
1877
	global $config;
1878

    
1879
	if (is_array($config['aliases']['alias'])) {
1880
		foreach ($config['aliases']['alias'] as $alias) {
1881
			if ($name == $alias['name']) {
1882
				return $alias['type'];
1883
			}
1884
		}
1885
	}
1886

    
1887
	return "";
1888
}
1889

    
1890
/* expand a host or network alias, if necessary */
1891
function alias_expand($name) {
1892
	global $config, $aliastable;
1893
	$urltable_prefix = "/var/db/aliastables/";
1894
	$urltable_filename = $urltable_prefix . $name . ".txt";
1895

    
1896
	if (isset($aliastable[$name])) {
1897
		// alias names cannot be strictly numeric. redmine #4289
1898
		if (is_numericint($name)) {
1899
			return null;
1900
		}
1901
		// make sure if it's a ports alias, it actually exists. redmine #5845
1902
		foreach ($config['aliases']['alias'] as $alias) {
1903
			if ($alias['name'] == $name) {
1904
				if ($alias['type'] == "urltable_ports") {
1905
					if (is_URL($alias['url']) && file_exists($urltable_filename) && filesize($urltable_filename)) {
1906
						return "\${$name}";
1907
					} else {
1908
						return null;
1909
					}
1910
				}
1911
			}
1912
		}
1913
		return "\${$name}";
1914
	} else if (is_ipaddr($name) || is_subnet($name) || is_port_or_range($name)) {
1915
		return "{$name}";
1916
	} else {
1917
		return null;
1918
	}
1919
}
1920

    
1921
function alias_expand_urltable($name) {
1922
	global $config;
1923
	$urltable_prefix = "/var/db/aliastables/";
1924
	$urltable_filename = $urltable_prefix . $name . ".txt";
1925

    
1926
	if (is_array($config['aliases']['alias'])) {
1927
		foreach ($config['aliases']['alias'] as $alias) {
1928
			if (preg_match("/urltable/i", $alias['type']) && ($alias['name'] == $name)) {
1929
				if (is_URL($alias["url"]) && file_exists($urltable_filename)) {
1930
					if (!filesize($urltable_filename)) {
1931
						// file exists, but is empty, try to sync
1932
						send_event("service sync alias {$name}");
1933
					}
1934
					return $urltable_filename;
1935
				} else {
1936
					send_event("service sync alias {$name}");
1937
					break;
1938
				}
1939
			}
1940
		}
1941
	}
1942
	return null;
1943
}
1944

    
1945
/* obtain MAC address given an IP address by looking at the ARP/NDP table */
1946
function arp_get_mac_by_ip($ip, $do_ping = true) {
1947
	unset($macaddr);
1948
	$retval = 1;
1949
	switch (is_ipaddr($ip)) {
1950
		case 4:
1951
			if ($do_ping === true) {
1952
				mwexec("/sbin/ping -c 1 -t 1 " . escapeshellarg($ip), true);
1953
			}
1954
			$macaddr = exec("/usr/sbin/arp -n " . escapeshellarg($ip) . " | /usr/bin/awk '{print $4}'", $output, $retval);
1955
			break;
1956
		case 6:
1957
			if ($do_ping === true) {
1958
				mwexec("/sbin/ping6 -c 1 -X 1 " . escapeshellarg($ip), true);
1959
			}
1960
			$macaddr = exec("/usr/sbin/ndp -n " . escapeshellarg($ip) . " | /usr/bin/awk '{print $2}'", $output, $retval);
1961
			break;
1962
	}
1963
	if ($retval == 0 && is_macaddr($macaddr)) {
1964
		return $macaddr;
1965
	} else {
1966
		return false;
1967
	}
1968
}
1969

    
1970
/* return a fieldname that is safe for xml usage */
1971
function xml_safe_fieldname($fieldname) {
1972
	$replace = array(
1973
	    '/', '-', ' ', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')',
1974
	    '_', '+', '=', '{', '}', '[', ']', '|', '/', '<', '>', '?',
1975
	    ':', ',', '.', '\'', '\\'
1976
	);
1977
	return strtolower(str_replace($replace, "", $fieldname));
1978
}
1979

    
1980
function mac_format($clientmac) {
1981
	global $config, $cpzone;
1982

    
1983
	$mac = explode(":", $clientmac);
1984
	$mac_format = $cpzone ? $config['captiveportal'][$cpzone]['radmac_format'] : false;
1985

    
1986
	switch ($mac_format) {
1987
		case 'singledash':
1988
			return "$mac[0]$mac[1]$mac[2]-$mac[3]$mac[4]$mac[5]";
1989

    
1990
		case 'ietf':
1991
			return "$mac[0]-$mac[1]-$mac[2]-$mac[3]-$mac[4]-$mac[5]";
1992

    
1993
		case 'cisco':
1994
			return "$mac[0]$mac[1].$mac[2]$mac[3].$mac[4]$mac[5]";
1995

    
1996
		case 'unformatted':
1997
			return "$mac[0]$mac[1]$mac[2]$mac[3]$mac[4]$mac[5]";
1998

    
1999
		default:
2000
			return $clientmac;
2001
	}
2002
}
2003

    
2004
function resolve_retry($hostname, $protocol = 'inet', $retries = 5, $numrecords = 1) {
2005

    
2006
	$recresult = array();
2007
	$returnres = array();
2008
	for ($i = 0; $i < $retries; $i++) {
2009
		switch ($protocol) {
2010
			case 'any':
2011
				$checkproto = 'is_ipaddr';
2012
				$dnsproto = DNS_ANY;
2013
				$dnstype = array('A', 'AAAA');
2014
				break;
2015
			case 'inet6':
2016
				$checkproto = 'is_ipaddrv6';
2017
				$dnsproto = DNS_AAAA;
2018
				$dnstype = array('AAAA');
2019
				break;
2020
			case 'inet': 
2021
			default:
2022
				$checkproto = 'is_ipaddrv4';
2023
				$dnsproto = DNS_A;
2024
				$dnstype = array('A');
2025
				break;
2026
		}
2027
		if ($checkproto($hostname)) {
2028
			return $hostname;
2029
		}
2030
		$dnsresult = @dns_get_record($hostname, $dnsproto);
2031
		if (!empty($dnsresult)) {
2032
			foreach ($dnsresult as $dnsrec => $ip) {
2033
				if (is_array($ip)) {
2034
					if (in_array($ip['type'], $dnstype)) {
2035
					    if ($checkproto($ip['ip'])) { 
2036
						    $recresult[] = $ip['ip'];
2037
					    }
2038
					    if ($checkproto($ip['ipv6'])) { 
2039
						    $recresult[] = $ip['ipv6'];
2040
					    }
2041
					}
2042
				}
2043
			}
2044
		}
2045

    
2046
		sleep(1);
2047
	}
2048

    
2049
	if (!empty($recresult)) {
2050
		if ($numrecords == 1) {
2051
			return $recresult[0];
2052
		} else {
2053
			return array_slice($recresult, 0, $numrecords);
2054
		}
2055
	}
2056

    
2057
	return false;
2058
}
2059

    
2060
function format_bytes($bytes) {
2061
	if ($bytes >= 1099511627776) {
2062
		return sprintf("%.2f TiB", $bytes/1099511627776);
2063
	} else if ($bytes >= 1073741824) {
2064
		return sprintf("%.2f GiB", $bytes/1073741824);
2065
	} else if ($bytes >= 1048576) {
2066
		return sprintf("%.2f MiB", $bytes/1048576);
2067
	} else if ($bytes >= 1024) {
2068
		return sprintf("%.0f KiB", $bytes/1024);
2069
	} else {
2070
		return sprintf("%d B", $bytes);
2071
	}
2072
}
2073

    
2074
function format_number($num, $precision = 3) {
2075
	$units = array('', 'K', 'M', 'G', 'T');
2076

    
2077
	$i = 0;
2078
	while ($num > 1000 && $i < count($units)) {
2079
		$num /= 1000;
2080
		$i++;
2081
	}
2082
	$num = round($num, $precision);
2083

    
2084
	return ("$num {$units[$i]}");
2085
}
2086

    
2087

    
2088
function unformat_number($formated_num) {
2089
	$num = strtoupper($formated_num);
2090
    
2091
	if ( strpos($num,"T") !== false ) {
2092
		$num = str_replace("T","",$num) * 1000 * 1000 * 1000 * 1000;
2093
	} else if ( strpos($num,"G") !== false ) {
2094
		$num = str_replace("G","",$num) * 1000 * 1000 * 1000;
2095
	} else if ( strpos($num,"M") !== false ) {
2096
		$num = str_replace("M","",$num) * 1000 * 1000;
2097
	} else if ( strpos($num,"K") !== false ) {
2098
		$num = str_replace("K","",$num) * 1000;
2099
	}
2100
    
2101
	return $num;
2102
}
2103

    
2104
function update_filter_reload_status($text, $new=false) {
2105
	global $g;
2106

    
2107
	if ($new) {
2108
		file_put_contents("{$g['varrun_path']}/filter_reload_status", $text  . PHP_EOL);
2109
	} else {
2110
		file_put_contents("{$g['varrun_path']}/filter_reload_status", $text  . PHP_EOL, FILE_APPEND);
2111
	}
2112
}
2113

    
2114
/****** util/return_dir_as_array
2115
 * NAME
2116
 *   return_dir_as_array - Return a directory's contents as an array.
2117
 * INPUTS
2118
 *   $dir          - string containing the path to the desired directory.
2119
 *   $filter_regex - string containing a regular expression to filter file names. Default empty.
2120
 * RESULT
2121
 *   $dir_array - array containing the directory's contents. This array will be empty if the path specified is invalid.
2122
 ******/
2123
function return_dir_as_array($dir, $filter_regex = '') {
2124
	$dir_array = array();
2125
	if (is_dir($dir)) {
2126
		if ($dh = opendir($dir)) {
2127
			while (($file = readdir($dh)) !== false) {
2128
				if (($file == ".") || ($file == "..")) {
2129
					continue;
2130
				}
2131

    
2132
				if (empty($filter_regex) || preg_match($filter_regex, $file)) {
2133
					array_push($dir_array, $file);
2134
				}
2135
			}
2136
			closedir($dh);
2137
		}
2138
	}
2139
	return $dir_array;
2140
}
2141

    
2142
function run_plugins($directory) {
2143
	global $config, $g;
2144

    
2145
	/* process packager manager custom rules */
2146
	$files = return_dir_as_array($directory);
2147
	if (is_array($files)) {
2148
		foreach ($files as $file) {
2149
			if (stristr($file, ".sh") == true) {
2150
				mwexec($directory . $file . " start");
2151
			} else if (!is_dir($directory . "/" . $file) && stristr($file, ".inc")) {
2152
				require_once($directory . "/" . $file);
2153
			}
2154
		}
2155
	}
2156
}
2157

    
2158
/*
2159
 *    safe_mkdir($path, $mode = 0755)
2160
 *    create directory if it doesn't already exist and isn't a file!
2161
 */
2162
function safe_mkdir($path, $mode = 0755) {
2163
	global $g;
2164

    
2165
	if (!is_file($path) && !is_dir($path)) {
2166
		return @mkdir($path, $mode, true);
2167
	} else {
2168
		return false;
2169
	}
2170
}
2171

    
2172
/*
2173
 * get_sysctl($names)
2174
 * Get values of sysctl OID's listed in $names (accepts an array or a single
2175
 * name) and return an array of key/value pairs set for those that exist
2176
 */
2177
function get_sysctl($names) {
2178
	if (empty($names)) {
2179
		return array();
2180
	}
2181

    
2182
	if (is_array($names)) {
2183
		$name_list = array();
2184
		foreach ($names as $name) {
2185
			$name_list[] = escapeshellarg($name);
2186
		}
2187
	} else {
2188
		$name_list = array(escapeshellarg($names));
2189
	}
2190

    
2191
	exec("/sbin/sysctl -iq " . implode(" ", $name_list), $output);
2192
	$values = array();
2193
	foreach ($output as $line) {
2194
		$line = explode(": ", $line, 2);
2195
		if (count($line) == 2) {
2196
			$values[$line[0]] = $line[1];
2197
		}
2198
	}
2199

    
2200
	return $values;
2201
}
2202

    
2203
/*
2204
 * get_single_sysctl($name)
2205
 * Wrapper for get_sysctl() to simplify read of a single sysctl value
2206
 * return the value for sysctl $name or empty string if it doesn't exist
2207
 */
2208
function get_single_sysctl($name) {
2209
	if (empty($name)) {
2210
		return "";
2211
	}
2212

    
2213
	$value = get_sysctl($name);
2214
	if (empty($value) || !isset($value[$name])) {
2215
		return "";
2216
	}
2217

    
2218
	return $value[$name];
2219
}
2220

    
2221
/*
2222
 * set_sysctl($value_list)
2223
 * Set sysctl OID's listed as key/value pairs and return
2224
 * an array with keys set for those that succeeded
2225
 */
2226
function set_sysctl($values) {
2227
	if (empty($values)) {
2228
		return array();
2229
	}
2230

    
2231
	$value_list = array();
2232
	foreach ($values as $key => $value) {
2233
		$value_list[] = escapeshellarg($key) . "=" . escapeshellarg($value);
2234
	}
2235

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

    
2238
	/* Retry individually if failed (one or more read-only) */
2239
	if ($success <> 0 && count($value_list) > 1) {
2240
		foreach ($value_list as $value) {
2241
			exec("/sbin/sysctl -iq " . $value, $output);
2242
		}
2243
	}
2244

    
2245
	$ret = array();
2246
	foreach ($output as $line) {
2247
		$line = explode(": ", $line, 2);
2248
		if (count($line) == 2) {
2249
			$ret[$line[0]] = true;
2250
		}
2251
	}
2252

    
2253
	return $ret;
2254
}
2255

    
2256
/*
2257
 * set_single_sysctl($name, $value)
2258
 * Wrapper to set_sysctl() to make it simple to set only one sysctl
2259
 * returns boolean meaning if it succeeded
2260
 */
2261
function set_single_sysctl($name, $value) {
2262
	if (empty($name)) {
2263
		return false;
2264
	}
2265

    
2266
	$result = set_sysctl(array($name => $value));
2267

    
2268
	if (!isset($result[$name]) || $result[$name] != $value) {
2269
		return false;
2270
	}
2271

    
2272
	return true;
2273
}
2274

    
2275
/*
2276
 *     get_memory()
2277
 *     returns an array listing the amount of
2278
 *     memory installed in the hardware
2279
 *     [0] net memory available for the OS (FreeBSD) after some is taken by BIOS, video or whatever - e.g. 235 MBytes
2280
 *     [1] real (actual) memory of the system, should be the size of the RAM card/s - e.g. 256 MBytes
2281
 */
2282
function get_memory() {
2283
	$physmem = get_single_sysctl("hw.physmem");
2284
	$realmem = get_single_sysctl("hw.realmem");
2285
	/* convert from bytes to megabytes */
2286
	return array(($physmem/1048576), ($realmem/1048576));
2287
}
2288

    
2289
function mute_kernel_msgs() {
2290
	global $g, $config;
2291

    
2292
	if ($config['system']['enableserial']) {
2293
		return;
2294
	}
2295
	exec("/sbin/conscontrol mute on");
2296
}
2297

    
2298
function unmute_kernel_msgs() {
2299
	global $g;
2300

    
2301
	exec("/sbin/conscontrol mute off");
2302
}
2303

    
2304
function start_devd() {
2305
	global $g;
2306

    
2307
	/* Generate hints for the kernel loader. */
2308
	$module_paths = explode(";", get_single_sysctl("kern.module_path"));
2309
	foreach ($module_paths as $id => $path) {
2310
		if (!is_dir($path) || file_exists("{$path}/linker.hints")) {
2311
			continue;
2312
		}
2313
		if (($files = scandir($path)) == false) {
2314
			continue;
2315
		}
2316
		$found = false;
2317
		foreach ($files as $id => $file) {
2318
			if (strlen($file) > 3 &&
2319
			    strcasecmp(substr($file, -3), ".ko") == 0) {
2320
				$found = true;
2321
				break;
2322
			}
2323
		}
2324
		if ($found == false) {
2325
			continue;
2326
		}
2327
		$_gb = exec("/usr/sbin/kldxref $path");
2328
		unset($_gb);
2329
	}
2330

    
2331
	/* Use the undocumented -q options of devd to quiet its log spamming */
2332
	$_gb = exec("/sbin/devd -q -f /etc/{$g['product_name']}-devd.conf");
2333
	sleep(1);
2334
	unset($_gb);
2335
}
2336

    
2337
function is_interface_vlan_mismatch() {
2338
	global $config, $g;
2339

    
2340
	if (is_array($config['vlans']['vlan'])) {
2341
		foreach ($config['vlans']['vlan'] as $vlan) {
2342
			if (substr($vlan['if'], 0, 4) == "lagg") {
2343
				return false;
2344
			}
2345
			if (does_interface_exist($vlan['if']) == false) {
2346
				return true;
2347
			}
2348
		}
2349
	}
2350

    
2351
	return false;
2352
}
2353

    
2354
function is_interface_mismatch() {
2355
	global $config, $g;
2356

    
2357
	$do_assign = false;
2358
	$i = 0;
2359
	$missing_interfaces = array();
2360
	if (is_array($config['interfaces'])) {
2361
		foreach ($config['interfaces'] as $ifname => $ifcfg) {
2362
			if (interface_is_vlan($ifcfg['if']) != NULL ||
2363
			    interface_is_qinq($ifcfg['if']) != NULL ||
2364
			    preg_match("/^enc|^cua|^tun|^tap|^l2tp|^pptp|^ppp|^ovpn|^ipsec|^gif|^gre|^lagg|^bridge|^vxlan|vlan|_wlan|_\d{0,4}_\d{0,4}$/i", $ifcfg['if'])) {
2365
				// Do not check these interfaces.
2366
				$i++;
2367
				continue;
2368
			} else if (does_interface_exist($ifcfg['if']) == false) {
2369
				$missing_interfaces[] = $ifcfg['if'];
2370
				$do_assign = true;
2371
			} else {
2372
				$i++;
2373
			}
2374
		}
2375
	}
2376

    
2377
	if (file_exists("{$g['tmp_path']}/assign_complete")) {
2378
		$do_assign = false;
2379
	}
2380

    
2381
	if (!empty($missing_interfaces) && $do_assign) {
2382
		file_put_contents("{$g['tmp_path']}/missing_interfaces", implode(' ', $missing_interfaces));
2383
	} else {
2384
		@unlink("{$g['tmp_path']}/missing_interfaces");
2385
	}
2386

    
2387
	return $do_assign;
2388
}
2389

    
2390
/* sync carp entries to other firewalls */
2391
function carp_sync_client() {
2392
	global $g;
2393
	send_event("filter sync");
2394
}
2395

    
2396
/****f* util/isAjax
2397
 * NAME
2398
 *   isAjax - reports if the request is driven from prototype
2399
 * INPUTS
2400
 *   none
2401
 * RESULT
2402
 *   true/false
2403
 ******/
2404
function isAjax() {
2405
	return isset ($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest';
2406
}
2407

    
2408
/****f* util/timeout
2409
 * NAME
2410
 *   timeout - console input with timeout countdown. Note: erases 2 char of screen for timer. Leave space.
2411
 * INPUTS
2412
 *   optional, seconds to wait before timeout. Default 9 seconds.
2413
 * RESULT
2414
 *   returns 1 char of user input or null if no input.
2415
 ******/
2416
function timeout($timer = 9) {
2417
	while (!isset($key)) {
2418
		if ($timer >= 9) {
2419
			echo chr(8) . chr(8) . ($timer == 9 ? chr(32) : null) . "{$timer}";
2420
		} else {
2421
			echo chr(8). "{$timer}";
2422
		}
2423
		`/bin/stty -icanon min 0 time 25`;
2424
		$key = trim(`KEY=\`dd count=1 2>/dev/null\`; echo \$KEY`);
2425
		`/bin/stty icanon`;
2426
		if ($key == '') {
2427
			unset($key);
2428
		}
2429
		$timer--;
2430
		if ($timer == 0) {
2431
			break;
2432
		}
2433
	}
2434
	return $key;
2435
}
2436

    
2437
/****f* util/msort
2438
 * NAME
2439
 *   msort - sort array
2440
 * INPUTS
2441
 *   $array to be sorted, field to sort by, direction of sort
2442
 * RESULT
2443
 *   returns newly sorted array
2444
 ******/
2445
function msort($array, $id = "id", $sort_ascending = true) {
2446
	$temp_array = array();
2447
	if (!is_array($array)) {
2448
		return $temp_array;
2449
	}
2450
	while (count($array)>0) {
2451
		$lowest_id = 0;
2452
		$index = 0;
2453
		foreach ($array as $item) {
2454
			if (isset($item[$id])) {
2455
				if ($array[$lowest_id][$id]) {
2456
					if (strtolower($item[$id]) < strtolower($array[$lowest_id][$id])) {
2457
						$lowest_id = $index;
2458
					}
2459
				}
2460
			}
2461
			$index++;
2462
		}
2463
		$temp_array[] = $array[$lowest_id];
2464
		$array = array_merge(array_slice($array, 0, $lowest_id), array_slice($array, $lowest_id + 1));
2465
	}
2466
	if ($sort_ascending) {
2467
		return $temp_array;
2468
	} else {
2469
		return array_reverse($temp_array);
2470
	}
2471
}
2472

    
2473
/****f* util/is_URL
2474
 * NAME
2475
 *   is_URL
2476
 * INPUTS
2477
 *   string to check
2478
 * RESULT
2479
 *   Returns true if item is a URL
2480
 ******/
2481
function is_URL($url) {
2482
	$match = preg_match("'\b(([\w-]+://?|www[.])[^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|/)))'", $url);
2483
	if ($match) {
2484
		return true;
2485
	}
2486
	return false;
2487
}
2488

    
2489
function is_file_included($file = "") {
2490
	$files = get_included_files();
2491
	if (in_array($file, $files)) {
2492
		return true;
2493
	}
2494

    
2495
	return false;
2496
}
2497

    
2498
/*
2499
 * Replace a value on a deep associative array using regex
2500
 */
2501
function array_replace_values_recursive($data, $match, $replace) {
2502
	if (empty($data)) {
2503
		return $data;
2504
	}
2505

    
2506
	if (is_string($data)) {
2507
		$data = preg_replace("/{$match}/", $replace, $data);
2508
	} else if (is_array($data)) {
2509
		foreach ($data as $k => $v) {
2510
			$data[$k] = array_replace_values_recursive($v, $match, $replace);
2511
		}
2512
	}
2513

    
2514
	return $data;
2515
}
2516

    
2517
/*
2518
	This function was borrowed from a comment on PHP.net at the following URL:
2519
	http://www.php.net/manual/en/function.array-merge-recursive.php#73843
2520
 */
2521
function array_merge_recursive_unique($array0, $array1) {
2522

    
2523
	$arrays = func_get_args();
2524
	$remains = $arrays;
2525

    
2526
	// We walk through each arrays and put value in the results (without
2527
	// considering previous value).
2528
	$result = array();
2529

    
2530
	// loop available array
2531
	foreach ($arrays as $array) {
2532

    
2533
		// The first remaining array is $array. We are processing it. So
2534
		// we remove it from remaining arrays.
2535
		array_shift($remains);
2536

    
2537
		// We don't care non array param, like array_merge since PHP 5.0.
2538
		if (is_array($array)) {
2539
			// Loop values
2540
			foreach ($array as $key => $value) {
2541
				if (is_array($value)) {
2542
					// we gather all remaining arrays that have such key available
2543
					$args = array();
2544
					foreach ($remains as $remain) {
2545
						if (array_key_exists($key, $remain)) {
2546
							array_push($args, $remain[$key]);
2547
						}
2548
					}
2549

    
2550
					if (count($args) > 2) {
2551
						// put the recursion
2552
						$result[$key] = call_user_func_array(__FUNCTION__, $args);
2553
					} else {
2554
						foreach ($value as $vkey => $vval) {
2555
							if (!is_array($result[$key])) {
2556
								$result[$key] = array();
2557
							}
2558
							$result[$key][$vkey] = $vval;
2559
						}
2560
					}
2561
				} else {
2562
					// simply put the value
2563
					$result[$key] = $value;
2564
				}
2565
			}
2566
		}
2567
	}
2568
	return $result;
2569
}
2570

    
2571

    
2572
/*
2573
 * converts a string like "a,b,c,d"
2574
 * into an array like array("a" => "b", "c" => "d")
2575
 */
2576
function explode_assoc($delimiter, $string) {
2577
	$array = explode($delimiter, $string);
2578
	$result = array();
2579
	$numkeys = floor(count($array) / 2);
2580
	for ($i = 0; $i < $numkeys; $i += 1) {
2581
		$result[$array[$i * 2]] = $array[$i * 2 + 1];
2582
	}
2583
	return $result;
2584
}
2585

    
2586
/*
2587
 * Given a string of text with some delimiter, look for occurrences
2588
 * of some string and replace all of those.
2589
 * $text - the text string (e.g. "abc,defg,x123,ipv4,xyz")
2590
 * $delimiter - the delimiter (e.g. ",")
2591
 * $element - the element to match (e.g. "defg")
2592
 * $replacement - the string to replace it with (e.g. "42")
2593
 * Returns the resulting delimited string (e.g. "abc,42,x123,ipv4,xyz")
2594
 */
2595
function replace_element_in_list($text, $delimiter, $element, $replacement) {
2596
	$textArray = explode($delimiter, $text);
2597
	while (($entry = array_search($element, $textArray)) !== false) {
2598
		$textArray[$entry] = $replacement;
2599
	}
2600
	return implode(',', $textArray);
2601
}
2602

    
2603
/* Return system's route table */
2604
function route_table() {
2605
	$_gb = exec("/usr/bin/netstat --libxo json -nWr", $rawdata, $rc);
2606

    
2607
	if ($rc != 0) {
2608
		return array();
2609
	}
2610

    
2611
	$netstatarr = json_decode(implode(" ", $rawdata), JSON_OBJECT_AS_ARRAY);
2612
	$netstatarr = $netstatarr['statistics']['route-information']
2613
	    ['route-table']['rt-family'];
2614

    
2615
	$result = array();
2616
	$result['inet'] = array();
2617
	$result['inet6'] = array();
2618
	foreach ($netstatarr as $item) {
2619
		if ($item['address-family'] == 'Internet') {
2620
			$result['inet'] = $item['rt-entry'];
2621
		} else if ($item['address-family'] == 'Internet6') {
2622
			$result['inet6'] = $item['rt-entry'];
2623
		}
2624
	}
2625
	unset($netstatarr);
2626

    
2627
	return $result;
2628
}
2629

    
2630
/* Get static route for specific destination */
2631
function route_get($target, $ipprotocol = '') {
2632
	if (!empty($ipprotocol)) {
2633
		$family = $ipprotocol;
2634
	} else if (is_v4($target)) {
2635
		$family = 'inet';
2636
	} else if (is_v6($target)) {
2637
		$family = 'inet6';
2638
	}
2639

    
2640
	if (empty($family)) {
2641
		return array();
2642
	}
2643

    
2644
	$rtable = route_table();
2645

    
2646
	if (empty($rtable)) {
2647
		return array();
2648
	}
2649

    
2650
	$result = array();
2651
	foreach ($rtable[$family] as $item) {
2652
		if ($item['destination'] == $target ||
2653
		    ip_in_subnet($target, $item['destination'])) {
2654
			$result[] = $item;
2655
		}
2656
	}
2657

    
2658
	return $result;
2659
}
2660

    
2661
/* Get default route */
2662
function route_get_default($ipprotocol) {
2663
	if (empty($ipprotocol) || ($ipprotocol != 'inet' &&
2664
	    $ipprotocol != 'inet6')) {
2665
		return '';
2666
	}
2667

    
2668
	$route = route_get('default', $ipprotocol);
2669

    
2670
	if (empty($route)) {
2671
		return '';
2672
	}
2673

    
2674
	if (!isset($route[0]['gateway'])) {
2675
		return '';
2676
	}
2677

    
2678
	return $route[0]['gateway'];
2679
}
2680

    
2681
/* Delete a static route */
2682
function route_del($target, $ipprotocol = '') {
2683
	global $config;
2684

    
2685
	if (empty($target)) {
2686
		return;
2687
	}
2688

    
2689
	if (!empty($ipprotocol) && $ipprotocol != 'inet' &&
2690
	    $ipprotocol != 'inet6') {
2691
		return false;
2692
	}
2693

    
2694
	$route = route_get($target, $ipprotocol);
2695

    
2696
	if (empty($route)) {
2697
		return;
2698
	}
2699

    
2700
	$target_prefix = '';
2701
	if (is_subnet($target)) {
2702
		$target_prefix = '-net';
2703
	} else if (is_ipaddr($target)) {
2704
		$target_prefix = '-host';
2705
	}
2706

    
2707
	if (!empty($ipprotocol)) {
2708
		$target_prefix .= " -{$ipprotocol}";
2709
	} else if (is_v6($target)) {
2710
		$target_prefix .= ' -inet6';
2711
	} else if (is_v4($target)) {
2712
		$target_prefix .= ' -inet';
2713
	}
2714

    
2715
	foreach ($route as $item) {
2716
		if (substr($item['gateway'], 0, 5) == 'link#') {
2717
			continue;
2718
		}
2719

    
2720
		if (is_macaddr($item['gateway'])) {
2721
			$gw = '-iface ' . $item['interface-name'];
2722
		} else {
2723
			$gw = $item['gateway'];
2724
		}
2725

    
2726
		$_gb = exec(escapeshellcmd("/sbin/route del {$target_prefix} " .
2727
		    "{$target} {$gw}"), $output, $rc);
2728

    
2729
		if (isset($config['system']['route-debug'])) {
2730
			log_error("ROUTING debug: " . microtime() .
2731
			    " - DEL RC={$rc} - {$target} - gw: " . $gw);
2732
			file_put_contents("/dev/console", "\n[" . getmypid() .
2733
			    "] ROUTE DEL: {$target_prefix} {$target} {$gw} " .
2734
			    "result: {$rc}");
2735
		}
2736
	}
2737
}
2738

    
2739
/*
2740
 * Add static route.  If it already exists, remove it and re-add
2741
 *
2742
 * $target - IP, subnet or 'default'
2743
 * $gw     - gateway address
2744
 * $iface  - Network interface
2745
 * $args   - Extra arguments for /sbin/route
2746
 * $ipprotocol - 'inet' or 'inet6'.  Mandatory when $target == 'default'
2747
 *
2748
 */
2749
function route_add_or_change($target, $gw, $iface = '', $args = '',
2750
    $ipprotocol = '') {
2751
	global $config;
2752

    
2753
	if (empty($target) || (empty($gw) && empty($iface))) {
2754
		return false;
2755
	}
2756

    
2757
	if ($target == 'default' && empty($ipprotocol)) {
2758
		return false;
2759
	}
2760

    
2761
	if (!empty($ipprotocol) && $ipprotocol != 'inet' &&
2762
	    $ipprotocol != 'inet6') {
2763
		return false;
2764
	}
2765

    
2766
	if (is_subnet($target)) {
2767
		$target_prefix = '-net';
2768
	} else if (is_ipaddr($target)) {
2769
		$target_prefix = '-host';
2770
	}
2771

    
2772
	if (!empty($ipprotocol)) {
2773
		$target_prefix .= " -{$ipprotocol}";
2774
	} else if (is_v6($target)) {
2775
		$target_prefix .= ' -inet6';
2776
	} else if (is_v4($target)) {
2777
		$target_prefix .= ' -inet';
2778
	}
2779

    
2780
	/* If there is another route to the same target, remove it */
2781
	route_del($target, $ipprotocol);
2782

    
2783
	$params = '';
2784
	if (!empty($iface) && does_interface_exist($iface)) {
2785
		$params .= " -iface {$gw}";
2786
	}
2787
	if (is_ipaddr($gw)) {
2788
		$params .= " " . $gw;
2789
	}
2790

    
2791
	if (empty($params)) {
2792
		log_error("route_add_or_change: Invalid gateway {$gw} and/or " .
2793
		    "network interface {$iface}");
2794
		return false;
2795
	}
2796

    
2797
	$_gb = exec(escapeshellcmd("/sbin/route add {$target_prefix} " .
2798
	    "{$target} {$args} {$params}"), $output, $rc);
2799

    
2800
	if (isset($config['system']['route-debug'])) {
2801
		log_error("ROUTING debug: " . microtime() .
2802
		    " - ADD RC={$rc} - {$target} {$args}");
2803
		file_put_contents("/dev/console", "\n[" . getmypid() .
2804
		    "] ROUTE ADD: {$target_prefix} {$target} {$args} " .
2805
		    "{$params} result: {$rc}");
2806
	}
2807

    
2808
	return ($rc == 0);
2809
}
2810

    
2811
function set_ipv6routes_mtu($interface, $mtu) {
2812
	global $config, $g;
2813

    
2814
	$ipv6mturoutes = array();
2815
	$if = convert_real_interface_to_friendly_interface_name($interface);
2816
	if (!$config['interfaces'][$if]['ipaddrv6']) {
2817
		return;
2818
	}
2819
	$a_gateways = return_gateways_array();
2820
	$a_staticroutes = get_staticroutes(false, false, true);
2821
	foreach ($a_gateways as $gate) {
2822
		foreach ($a_staticroutes as $sroute) {
2823
			if ($gate['interface'] == $interface &&
2824
			    $sroute['gateway'] == $gate['name']) {
2825
				$tgt = $sroute['network'];
2826
				$gateway = $gate['gateway'];
2827
				$ipv6mturoutes[$tgt] = $gateway;
2828
			}
2829
		}
2830
		if ($gate['interface'] == $interface &&
2831
		    $gate['isdefaultgw']) {
2832
			$tgt = "default";
2833
			$gateway = $gate['gateway'];
2834
			$ipv6mturoutes[$tgt] = $gateway;
2835
		}
2836
	}
2837
	foreach ($ipv6mturoutes as $tgt => $gateway) {
2838
		mwexec("/sbin/route change -6 -mtu " . escapeshellarg($mtu) .
2839
		    " " . escapeshellarg($tgt) . " " .
2840
		    escapeshellarg($gateway));
2841
	}
2842
}
2843

    
2844
function alias_to_subnets_recursive($name, $returnhostnames = false) {
2845
	global $aliastable;
2846
	$result = array();
2847
	if (!isset($aliastable[$name])) {
2848
		return $result;
2849
	}
2850
	$subnets = preg_split('/\s+/', $aliastable[$name]);
2851
	foreach ($subnets as $net) {
2852
		if (is_alias($net)) {
2853
			$sub = alias_to_subnets_recursive($net, $returnhostnames);
2854
			$result = array_merge($result, $sub);
2855
			continue;
2856
		} elseif (!is_subnet($net)) {
2857
			if (is_ipaddrv4($net)) {
2858
				$net .= "/32";
2859
			} else if (is_ipaddrv6($net)) {
2860
				$net .= "/128";
2861
			} else if ($returnhostnames === false || !is_fqdn($net)) {
2862
				continue;
2863
			}
2864
		}
2865
		$result[] = $net;
2866
	}
2867
	return $result;
2868
}
2869

    
2870
function get_staticroutes($returnsubnetsonly = false, $returnhostnames = false, $returnenabledroutesonly = false) {
2871
	global $config, $aliastable;
2872

    
2873
	/* Bail if there are no routes, but return an array always so callers don't have to check. */
2874
	init_config_arr(array('staticroutes', 'route'));
2875
	if (empty($config['staticroutes']['route'])) {
2876
		return array();
2877
	}
2878

    
2879
	$allstaticroutes = array();
2880
	$allsubnets = array();
2881
	/* Loop through routes and expand aliases as we find them. */
2882
	foreach ($config['staticroutes']['route'] as $route) {
2883
		if ($returnenabledroutesonly && isset($route['disabled'])) {
2884
			continue;
2885
		}
2886

    
2887
		if (is_alias($route['network'])) {
2888
			foreach (alias_to_subnets_recursive($route['network'], $returnhostnames) as $net) {
2889
				$temproute = $route;
2890
				$temproute['network'] = $net;
2891
				$allstaticroutes[] = $temproute;
2892
				$allsubnets[] = $net;
2893
			}
2894
		} elseif (is_subnet($route['network'])) {
2895
			$allstaticroutes[] = $route;
2896
			$allsubnets[] = $route['network'];
2897
		}
2898
	}
2899
	if ($returnsubnetsonly) {
2900
		return $allsubnets;
2901
	} else {
2902
		return $allstaticroutes;
2903
	}
2904
}
2905

    
2906
/****f* util/get_alias_list
2907
 * NAME
2908
 *   get_alias_list - Provide a list of aliases.
2909
 * INPUTS
2910
 *   $type          - Optional, can be a string or array specifying what type(s) of aliases you need.
2911
 * RESULT
2912
 *   Array containing list of aliases.
2913
 *   If $type is unspecified, all aliases are returned.
2914
 *   If $type is a string, all aliases of the type specified in $type are returned.
2915
 *   If $type is an array, all aliases of any type specified in any element of $type are returned.
2916
 */
2917
function get_alias_list($type = null) {
2918
	global $config;
2919
	$result = array();
2920
	if ($config['aliases']['alias'] <> "" && is_array($config['aliases']['alias'])) {
2921
		foreach ($config['aliases']['alias'] as $alias) {
2922
			if ($type === null) {
2923
				$result[] = $alias['name'];
2924
			} else if (is_array($type)) {
2925
				if (in_array($alias['type'], $type)) {
2926
					$result[] = $alias['name'];
2927
				}
2928
			} else if ($type === $alias['type']) {
2929
				$result[] = $alias['name'];
2930
			}
2931
		}
2932
	}
2933
	return $result;
2934
}
2935

    
2936
/* returns an array consisting of every element of $haystack that is not equal to $needle. */
2937
function array_exclude($needle, $haystack) {
2938
	$result = array();
2939
	if (is_array($haystack)) {
2940
		foreach ($haystack as $thing) {
2941
			if ($needle !== $thing) {
2942
				$result[] = $thing;
2943
			}
2944
		}
2945
	}
2946
	return $result;
2947
}
2948

    
2949
/* Define what is preferred, IPv4 or IPv6 */
2950
function prefer_ipv4_or_ipv6() {
2951
	global $config;
2952

    
2953
	if (isset($config['system']['prefer_ipv4'])) {
2954
		mwexec("/etc/rc.d/ip6addrctl prefer_ipv4");
2955
	} else {
2956
		mwexec("/etc/rc.d/ip6addrctl prefer_ipv6");
2957
	}
2958
}
2959

    
2960
/* Redirect to page passing parameters via POST */
2961
function post_redirect($page, $params) {
2962
	if (!is_array($params)) {
2963
		return;
2964
	}
2965

    
2966
	print "<html><body><form action=\"{$page}\" name=\"formredir\" method=\"post\">\n";
2967
	foreach ($params as $key => $value) {
2968
		print "<input type=\"hidden\" name=\"{$key}\" value=\"{$value}\" />\n";
2969
	}
2970
	print "</form>\n";
2971
	print "<script type=\"text/javascript\">\n";
2972
	print "//<![CDATA[\n";
2973
	print "document.formredir.submit();\n";
2974
	print "//]]>\n";
2975
	print "</script>\n";
2976
	print "</body></html>\n";
2977
}
2978

    
2979
/* Locate disks that can be queried for S.M.A.R.T. data. */
2980
function get_smart_drive_list() {
2981
	/* SMART supports some disks directly, and some controllers directly,
2982
	 * See https://redmine.pfsense.org/issues/9042 */
2983
	$supported_disk_types = array("ad", "da", "ada");
2984
	$supported_controller_types = array("nvme");
2985
	$disk_list = explode(" ", get_single_sysctl("kern.disks"));
2986
	foreach ($disk_list as $id => $disk) {
2987
		// We only want certain kinds of disks for S.M.A.R.T.
2988
		// 1 is a match, 0 is no match, False is any problem processing the regex
2989
		if (preg_match("/^(" . implode("|", $supported_disk_types) . ").*[0-9]{1,2}$/", $disk) !== 1) {
2990
			unset($disk_list[$id]);
2991
			continue;
2992
		}
2993
	}
2994
	foreach ($supported_controller_types as $controller) {
2995
		$devices = glob("/dev/{$controller}*");
2996
		if (!is_array($devices)) {
2997
			continue;
2998
		}
2999
		foreach ($devices as $device) {
3000
			$disk_list[] = basename($device);
3001
		}
3002
	}
3003
	sort($disk_list);
3004
	return $disk_list;
3005
}
3006

    
3007
// Validate a network address
3008
//	$addr: the address to validate
3009
//	$type: IPV4|IPV6|IPV4V6
3010
//	$label: the label used by the GUI to display this value. Required to compose an error message
3011
//	$err_msg: pointer to the callers error message array so that error messages can be added to it here
3012
//	$alias: are aliases permitted for this address?
3013
// Returns:
3014
//	IPV4 - if $addr is a valid IPv4 address
3015
//	IPV6 - if $addr is a valid IPv6 address
3016
//	ALIAS - if $alias=true and $addr is an alias
3017
//	false - otherwise
3018

    
3019
function validateipaddr(&$addr, $type, $label, &$err_msg, $alias=false) {
3020
	switch ($type) {
3021
		case IPV4:
3022
			if (is_ipaddrv4($addr)) {
3023
				return IPV4;
3024
			} else if ($alias) {
3025
				if (is_alias($addr)) {
3026
					return ALIAS;
3027
				} else {
3028
					$err_msg[] = sprintf(gettext("%s must be a valid IPv4 address or alias."), $label);
3029
					return false;
3030
				}
3031
			} else {
3032
				$err_msg[] = sprintf(gettext("%s must be a valid IPv4 address."), $label);
3033
				return false;
3034
			}
3035
		break;
3036
		case IPV6:
3037
			if (is_ipaddrv6($addr)) {
3038
				$addr = strtolower($addr);
3039
				return IPV6;
3040
			} else if ($alias) {
3041
				if (is_alias($addr)) {
3042
					return ALIAS;
3043
				} else {
3044
					$err_msg[] = sprintf(gettext("%s must be a valid IPv6 address or alias."), $label);
3045
					return false;
3046
				}
3047
			} else {
3048
				$err_msg[] = sprintf(gettext("%s must be a valid IPv6 address."), $label);
3049
				return false;
3050
			}
3051
		break;
3052
		case IPV4V6:
3053
			if (is_ipaddrv6($addr)) {
3054
				$addr = strtolower($addr);
3055
				return IPV6;
3056
			} else if (is_ipaddrv4($addr)) {
3057
				return IPV4;
3058
			} else if ($alias) {
3059
				if (is_alias($addr)) {
3060
					return ALIAS;
3061
				} else {
3062
					$err_msg[] = sprintf(gettext("%s must be a valid IPv4 or IPv6 address or alias."), $label);
3063
					return false;
3064
				}
3065
			} else {
3066
				$err_msg[] = sprintf(gettext("%s must be a valid IPv4 or IPv6 address."), $label);
3067
				return false;
3068
			}
3069
		break;
3070
	}
3071

    
3072
	return false;
3073
}
3074

    
3075
/* From DUID configuration inputs, format a string that looks (more) like the expected raw DUID format:
3076
 * 1) For DUIDs entered as a known DUID type, convert to a hexstring and prepend the DUID number, after having done the following:
3077
 *     a) For DUID-LLT and DUID-EN, convert the time/enterprise ID input to hex and append the link-layer address/identifier input.
3078
 *     b) For DUID-LLT and DUID-LL, prepend a hardware type of 1.
3079
 *     c) For DUID-UUID, remove any "-".
3080
 * 2) Replace any remaining "-" with ":".
3081
 * 3) If any components are input with just a single char (hex digit hopefully), put a "0" in front.
3082
 * 4) The first two components should be a 16-bit integer (little- or big-endian, depending on the current machine type) that
3083
 *    is equal to the number of other components. If not, prepend this as "nn:00" (all pfSense builds are little-endian).
3084
 *    This is convenience, because the DUID reported by dhcp6c in logs does not include this count, which corresponds to the
3085
 *    option-len field of DHCPv6's OPTION_CLIENTID option.
3086
 *
3087
 * The final result should be closer to:
3088
 *
3089
 * "nn:00:00:0n:nn:nn:nn:..."
3090
 *
3091
 * This function does not validate the input. is_duid() will do validation.
3092
 */
3093
function format_duid($duidtype, $duidpt1, $duidpt2=null) {
3094
	if ($duidpt2)
3095
		$duidpt1 = implode(':', str_split(str_pad(dechex($duidpt1), 8, '0', STR_PAD_LEFT), 2)) . ':' . $duidpt2;
3096

    
3097
	/* Make hexstrings */
3098
	if ($duidtype) {
3099
		switch ($duidtype) {
3100
		/* Add a hardware type to DUID-LLT and DUID-LL; assume Ethernet */
3101
		case 1:
3102
		case 3:
3103
			$duidpt1 = '00:01:' . $duidpt1;
3104
			break;
3105
		/* Remove '-' from given UUID and insert ':' every 2 characters */
3106
		case 4:
3107
			$duidpt1 = implode(':', str_split(str_replace('-', '', $duidpt1), 2));
3108
			break;
3109
		default:
3110
		}
3111
		$duidpt1 = '00:0' . $duidtype . ':' . $duidpt1;
3112
	}
3113

    
3114
	$values = explode(':', strtolower(str_replace('-', ':', $duidpt1)));
3115

    
3116
	if (hexdec($values[0]) != count($values) - 2)
3117
		array_unshift($values, dechex(count($values)), '00');
3118

    
3119
	array_walk($values, function(&$value) {
3120
		$value = str_pad($value, 2, '0', STR_PAD_LEFT);
3121
	});
3122

    
3123
	return implode(":", $values);
3124
}
3125

    
3126
/* Returns true if $dhcp6duid is a valid DUID entry.
3127
 * Parse the entry to check for valid length according to known DUID types.
3128
 */
3129
function is_duid($dhcp6duid) {
3130
	$values = explode(":", $dhcp6duid);
3131
	if (hexdec($values[0]) == count($values) - 2) {
3132
		switch (hexdec($values[2] . $values[3])) {
3133
		case 0:
3134
			return false;
3135
			break;
3136
		case 1:
3137
			if (count($values) != 16 || strlen($dhcp6duid) != 47)
3138
				return false;
3139
			break;
3140
		case 3:
3141
			if (count($values) != 12 || strlen($dhcp6duid) != 35)
3142
				return false;
3143
			break;
3144
		case 4:
3145
			if (count($values) != 20 || strlen($dhcp6duid) != 59)
3146
				return false;
3147
			break;
3148
		/* DUID is up to 128 octets; allow 2 octets for type code, 2 more for option-len */
3149
		default:
3150
			if (count($values) > 132 || strlen($dhcp6duid) != count($values) * 3 - 1)
3151
				return false;
3152
		}
3153
	} else
3154
		return false;
3155

    
3156
	for ($i = 0; $i < count($values); $i++) {
3157
		if (ctype_xdigit($values[$i]) == false)
3158
			return false;
3159
		if (hexdec($values[$i]) < 0 || hexdec($values[$i]) > 255)
3160
			return false;
3161
	}
3162

    
3163
	return true;
3164
}
3165

    
3166
/* Write the DHCP6 DUID file */
3167
function write_dhcp6_duid($duidstring) {
3168
	// Create the hex array from the dhcp6duid config entry and write to file
3169
	global $g;
3170

    
3171
	if(!is_duid($duidstring)) {
3172
		log_error(gettext("Error: attempting to write DUID file - Invalid DUID detected"));
3173
		return false;
3174
	}
3175
	$temp = str_replace(":","",$duidstring);
3176
	$duid_binstring = pack("H*",$temp);
3177
	if ($fd = fopen("{$g['vardb_path']}/dhcp6c_duid", "wb")) {
3178
		fwrite($fd, $duid_binstring);
3179
		fclose($fd);
3180
		return true;
3181
	}
3182
	log_error(gettext("Error: attempting to write DUID file - File write error"));
3183
	return false;
3184
}
3185

    
3186
/* returns duid string from 'vardb_path']}/dhcp6c_duid' */
3187
function get_duid_from_file() {
3188
	global $g;
3189

    
3190
	$duid_ASCII = "";
3191
	$count = 0;
3192

    
3193
	if (file_exists("{$g['vardb_path']}/dhcp6c_duid") &&
3194
	    ($fd = fopen("{$g['vardb_path']}/dhcp6c_duid", "r"))) {
3195
		$fsize = filesize("{$g['vardb_path']}/dhcp6c_duid");
3196
		if ($fsize <= 132) {
3197
			$buffer = fread($fd, $fsize);
3198
			while($count < $fsize) {
3199
				$duid_ASCII .= bin2hex($buffer[$count]);
3200
				$count++;
3201
				if($count < $fsize) {
3202
					$duid_ASCII .= ":";
3203
				}
3204
			}
3205
		}
3206
		fclose($fd);
3207
	}
3208
	//if no file or error with read then the string returns blanked DUID string
3209
	if(!is_duid($duid_ASCII)) {
3210
		return "--:--:--:--:--:--:--:--:--:--:--:--:--:--:--:--";
3211
	}
3212
	return($duid_ASCII);
3213
}
3214

    
3215
/* Replaces the Mac OS 9 and earlier (\r) and DOS/Windows (\r\n) newlines with the Unix equivalent (\n). */
3216
function unixnewlines($text) {
3217
	return preg_replace('/\r\n?/', "\n", $text);
3218
}
3219

    
3220
function array_remove_duplicate($array, $field) {
3221
	foreach ($array as $sub) {
3222
		if (isset($sub[$field])) {
3223
			$cmp[] = $sub[$field];
3224
		}
3225
	}
3226
	$unique = array_unique(array_reverse($cmp, true));
3227
	foreach ($unique as $k => $rien) {
3228
		$new[] = $array[$k];
3229
	}
3230
	return $new;
3231
}
3232

    
3233
function dhcpd_date_adjust_gmt($dt) {
3234
	global $config;
3235

    
3236
	init_config_arr(array('dhcpd'));
3237

    
3238
	foreach ($config['dhcpd'] as $dhcpditem) {
3239
		if ($dhcpditem['dhcpleaseinlocaltime'] == "yes") {
3240
			$ts = strtotime($dt . " GMT");
3241
			if ($ts !== false) {
3242
				return strftime("%Y/%m/%d %H:%M:%S", $ts);
3243
			}
3244
		}
3245
	}
3246

    
3247
	/*
3248
	 * If we did not need to convert to local time or the conversion
3249
	 * failed, just return the input.
3250
	 */
3251
	return $dt;
3252
}
3253

    
3254
global $supported_image_types;
3255
$supported_image_types = array(
3256
	IMAGETYPE_JPEG,
3257
	IMAGETYPE_PNG,
3258
	IMAGETYPE_GIF,
3259
	IMAGETYPE_WEBP
3260
);
3261

    
3262
function is_supported_image($image_filename) {
3263
	global $supported_image_types;
3264
	$img_info = getimagesize($image_filename);
3265

    
3266
	/* If it's not an image, or it isn't in the supported list, return false */
3267
	if (($img_info === false) ||
3268
	    !in_array($img_info[2], array_keys($supported_image_types))) {
3269
		return false;
3270
	} else {
3271
		return $img_info[2];
3272
	}
3273
}
3274

    
3275
function get_lagg_ports ($laggport) {
3276
	$laggp = array();
3277
	foreach ($laggport as $lgp) {
3278
		list($lpname, $lpinfo) = explode(" ", $lgp);
3279
		preg_match('~<(.+)>~', $lpinfo, $lgportmode);
3280
		if ($lgportmode[1]) {
3281
			$laggp[] = $lpname . " (" . $lgportmode[1] . ")";
3282
		} else {
3283
			$laggp[] = $lpname;
3284
		}
3285
	}
3286
	if ($laggp) {
3287
		return implode(", ", $laggp);
3288
	} else {
3289
		return false;
3290
	}
3291
}
3292

    
3293
function cisco_to_cidr($addr) {
3294
	if (!is_ipaddr($addr)) {
3295
		throw new Exception('Invalid IP Addr');
3296
	}
3297

    
3298
	$mask = decbin(~ip2long($addr));
3299
	$mask = substr($mask, -32);
3300
	$k = 0;
3301
	for ($i = 0; $i <= 32; $i++) {
3302
		$k += intval($mask[$i]);
3303
	}
3304
	return $k;
3305
}
3306

    
3307
function cisco_extract_index($prule) {
3308
	$index = explode("#", $prule);
3309
	if (is_numeric($index[1])) {
3310
		return intval($index[1]);
3311
	} else {
3312
		syslog(LOG_WARNING, "Error parsing rule {$prule}: Could not extract index");
3313
	}
3314
	return -1;;
3315
}
3316

    
3317
function parse_cisco_acl_rule($rule, $devname, $dir, $proto) {
3318
	$rule_orig = $rule;
3319
	$rule = explode(" ", $rule);
3320
	$tmprule = "";
3321
	$index = 0;
3322

    
3323
	if ($rule[$index] == "permit") {
3324
		$startrule = "pass {$dir} quick on {$devname} ";
3325
	} else if ($rule[$index] == "deny") {
3326
		$startrule = "block {$dir} quick on {$devname} ";
3327
	} else {
3328
		return;
3329
	}
3330

    
3331
	$index++;
3332

    
3333
	switch ($rule[$index]) {
3334
		case "ip":
3335
			break;
3336
		case "icmp":
3337
			$icmp = ($proto == "inet") ? "icmp" : "ipv6-icmp";
3338
			$tmprule .= "proto {$icmp} ";
3339
			break;
3340
		case "tcp":
3341
		case "udp":
3342
			$tmprule .= "proto {$rule[$index]} ";
3343
			break;
3344
		default:
3345
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid protocol.");
3346
			return;
3347
	}
3348
	$index++;
3349

    
3350
	/* Source */
3351
	if (trim($rule[$index]) == "host") {
3352
		$index++;
3353
		if ((is_ipaddrv4(trim($rule[$index])) && ($proto == "inet")) ||
3354
		    (is_ipaddrv6(trim($rule[$index])) && ($proto == "inet6"))) {
3355
			$tmprule .= "from {$rule[$index]} ";
3356
			$index++;
3357
		} else {
3358
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source host '{$rule[$index]}'.");
3359
			return;
3360
		}
3361
	} elseif (is_subnetv6(trim($rule[$index])) && ($proto == "inet6")) {
3362
		$tmprule .= "from {$rule[$index]} ";
3363
		$index++;
3364
	} elseif (trim($rule[$index]) == "any") {
3365
		$tmprule .= "from any ";
3366
		$index++;
3367
	} else {
3368
		$network = $rule[$index];
3369
		$netmask = $rule[++$index];
3370

    
3371
		if (is_ipaddrv4($network) && ($proto == "inet")) {
3372
			try {
3373
				$netmask = cisco_to_cidr($netmask);
3374
			} catch(Exception $e) {
3375
				syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source netmask '$netmask'.");
3376
				return;
3377
			}
3378
			$tmprule .= "from {$network}/{$netmask}";
3379
		} else {
3380
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source network '$network'.");
3381
			return;
3382
		}
3383

    
3384
		$index++;
3385
	}
3386

    
3387
	/* Source Operator */
3388
	if (in_array(trim($rule[$index]), array("lt", "gt", "eq", "neq"))) {
3389
		switch(trim($rule[$index])) {
3390
			case "lt":
3391
				$operator = "<";
3392
				break;
3393
			case "gt":
3394
				$operator = ">";
3395
				break;
3396
			case "eq":
3397
				$operator = "=";
3398
				break;
3399
			case "neq":
3400
				$operator = "!=";
3401
				break;
3402
		}
3403

    
3404
		$port = $rule[++$index];
3405
		if (is_port($port)) {
3406
			$tmprule .= "port {$operator} {$port} ";
3407
		} else {
3408
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source port: '$port' not a numeric value between 0 and 65535.");
3409
			return;
3410
		}
3411
		$index++;
3412
	} else if (trim($rule[$index]) == "range") {
3413
		$port = array($rule[++$index], $rule[++$index]);
3414
		if (is_port($port[0]) && is_port($port[1])) {
3415
			$port[0]--;
3416
			$port[1]++;
3417
			$tmprule .= "port {$port[0]} >< {$port[1]} ";
3418
		} else {
3419
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source ports: '$port[0]' & '$port[1]' one or both are not a numeric value between 0 and 65535.");
3420
			return;
3421
		}
3422
		$index++;
3423
	}
3424

    
3425
	/* Destination */
3426
	if (trim($rule[$index]) == "host") {
3427
		$index++;
3428
		if ((is_ipaddrv4(trim($rule[$index])) && ($proto == "inet")) ||
3429
		    (is_ipaddrv6(trim($rule[$index])) && ($proto == "inet6"))) {
3430
			$tmprule .= "to {$rule[$index]} ";
3431
			$index++;
3432
		} else {
3433
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination host '{$rule[$index]}'.");
3434
			return;
3435
		}
3436
	} elseif (is_subnetv6(trim($rule[$index])) && ($proto == "inet6")) {
3437
		$tmprule .= "to {$rule[$index]} ";
3438
		$index++;
3439
	} elseif (trim($rule[$index]) == "any") {
3440
		$tmprule .= "to any ";
3441
		$index++;
3442
	} else {
3443
		$network = $rule[$index];
3444
		$netmask = $rule[++$index];
3445

    
3446
		if (is_ipaddrv4($network) && ($proto == "inet")) {
3447
			try {
3448
				$netmask = cisco_to_cidr($netmask);
3449
			} catch(Exception $e) {
3450
				syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination network '$network'.");
3451
				return;
3452
			}
3453
			$tmprule .= "to {$network}/{$netmask}";
3454
		} else {
3455
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination network '$network'.");
3456
			return;
3457
		}
3458

    
3459
		$index++;
3460
	}
3461

    
3462
	/* Destination Operator */
3463
	if (in_array(trim($rule[$index]), array("lt", "gt", "eq", "neq"))) {
3464
		switch(trim($rule[$index])) {
3465
			case "lt":
3466
				$operator = "<";
3467
				break;
3468
			case "gt":
3469
				$operator = ">";
3470
				break;
3471
			case "eq":
3472
				$operator = "=";
3473
				break;
3474
			case "neq":
3475
				$operator = "!=";
3476
				break;
3477
		}
3478

    
3479
		$port = $rule[++$index];
3480
		if (is_port($port)) {
3481
			$tmprule .= "port {$operator} {$port} ";
3482
		} else {
3483
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination port: '$port' not a numeric value between 0 and 65535.");
3484
			return;
3485
		}
3486
		$index++;
3487
	} else if (trim($rule[$index]) == "range") {
3488
		$port = array($rule[++$index], $rule[++$index]);
3489
		if (is_port($port[0]) && is_port($port[1])) {
3490
			$port[0]--;
3491
			$port[1]++;
3492
			$tmprule .= "port {$port[0]} >< {$port[1]} ";
3493
		} else {
3494
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination ports: '$port[0]' '$port[1]' one or both are not a numeric value between 0 and 65535.");
3495
			return;
3496
		}
3497
		$index++;
3498
	}
3499

    
3500
	$tmprule = $startrule . $proto . " " . $tmprule;
3501
	return $tmprule;
3502
}
3503

    
3504
function parse_cisco_acl($attribs, $dev) {
3505
	global $attributes;
3506

    
3507
	if (!is_array($attribs)) {
3508
		return "";
3509
	}
3510
	$finalrules = "";
3511
	if (is_array($attribs['ciscoavpair'])) {
3512
		$inrules = array('inet' => array(), 'inet6' => array());
3513
		$outrules = array('inet' => array(), 'inet6' => array());
3514
		foreach ($attribs['ciscoavpair'] as $avrules) {
3515
			$rule = explode("=", $avrules);
3516
			$dir = "";
3517
			if (strstr($rule[0], "inacl")) {
3518
				$dir = "in";
3519
			} else if (strstr($rule[0], "outacl")) {
3520
				$dir = "out";
3521
			} else if (strstr($rule[0], "dns-servers")) {
3522
				$attributes['dns-servers'] = explode(" ", $rule[1]);
3523
				continue;
3524
			} else if (strstr($rule[0], "route")) {
3525
				if (!is_array($attributes['routes'])) {
3526
					$attributes['routes'] = array();
3527
				}
3528
				$attributes['routes'][] = $rule[1];
3529
				continue;
3530
			}
3531
			$rindex = cisco_extract_index($rule[0]);
3532
			if ($rindex < 0) {
3533
				continue;
3534
			}
3535

    
3536
			if (strstr($rule[0], "ipv6")) {
3537
				$proto = "inet6";
3538
			} else {
3539
				$proto = "inet";
3540
			}
3541

    
3542
			$tmprule = parse_cisco_acl_rule($rule[1], $dev, $dir, $proto);
3543

    
3544
			if ($dir == "in") {
3545
				$inrules[$proto][$rindex] = $tmprule;
3546
			} else if ($dir == "out") {
3547
				$outrules[$proto][$rindex] = $tmprule;
3548
			}
3549
		}
3550

    
3551

    
3552
		$state = "";
3553
		foreach (array('inet', 'inet6') as $ip) {
3554
			if (!empty($outrules[$ip])) {
3555
				$state = "no state";
3556
			}
3557
			ksort($inrules[$ip], SORT_NUMERIC);
3558
			foreach ($inrules[$ip] as $inrule) {
3559
				$finalrules .= "{$inrule} {$state}\n";
3560
			}
3561
			if (!empty($outrules[$ip])) {
3562
				ksort($outrules[$ip], SORT_NUMERIC);
3563
				foreach ($outrules[$ip] as $outrule) {
3564
					$finalrules .= "{$outrule} {$state}\n";
3565
				}
3566
			}
3567
		}
3568
	}
3569
	return $finalrules;
3570
}
3571

    
3572
function alias_idn_to_utf8($alias) {
3573
	if (is_alias($alias)) {
3574
		return $alias;
3575
	} else {
3576
		return idn_to_utf8($alias);
3577
	}
3578
}
3579

    
3580
function alias_idn_to_ascii($alias) {
3581
	if (is_alias($alias)) {
3582
		return $alias;
3583
	} else {
3584
		return idn_to_ascii($alias);
3585
	}
3586
}
3587

    
3588
?>
(53-53/61)