Project

General

Profile

Download (96.3 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
/*
3
 * util.inc
4
 *
5
 * part of pfSense (https://www.pfsense.org)
6
 * Copyright (c) 2004-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 (is_subnet($a)) {
468
		list($a, $amask) = explode('/', $a);
469
	}
470
	if (is_subnet($b)) {
471
		list($b, $bmask) = explode('/', $b);
472
	}
473
	if (ip_less_than($a, $b)) {
474
		return -1;
475
	} else if (ip_greater_than($a, $b)) {
476
		return 1;
477
	} else {
478
		return 0;
479
	}
480
}
481

    
482
/* Convert a range of IPv4 addresses to an array of individual addresses. */
483
/* Note: IPv6 ranges are not yet supported here. */
484
function ip_range_to_address_array($startip, $endip, $max_size = 5000) {
485
	if (!is_ipaddrv4($startip) || !is_ipaddrv4($endip)) {
486
		return false;
487
	}
488

    
489
	if (ip_greater_than($startip, $endip)) {
490
		// Swap start and end so we can process sensibly.
491
		$temp = $startip;
492
		$startip = $endip;
493
		$endip = $temp;
494
	}
495

    
496
	if (ip_range_size_v4($startip, $endip) > $max_size) {
497
		return false;
498
	}
499

    
500
	// Container for IP addresses within this range.
501
	$rangeaddresses = array();
502
	$end_int = ip2ulong($endip);
503
	for ($ip_int = ip2ulong($startip); $ip_int <= $end_int; $ip_int++) {
504
		$rangeaddresses[] = long2ip($ip_int);
505
	}
506

    
507
	return $rangeaddresses;
508
}
509

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

    
538
	if (is_ipaddrv4($ip1) && is_ipaddrv4($ip2)) {
539
		$proto = 'ipv4';  // for clarity
540
		$bits = 32;
541
		$ip1bin = decbin(ip2long32($ip1));
542
		$ip2bin = decbin(ip2long32($ip2));
543
	} elseif (is_ipaddrv6($ip1) && is_ipaddrv6($ip2)) {
544
		$proto = 'ipv6';
545
		$bits = 128;
546
		$ip1bin = ip6_to_bin($ip1);
547
		$ip2bin = ip6_to_bin($ip2);
548
	} else {
549
		return array();
550
	}
551

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

    
556
	if ($ip1bin == $ip2bin) {
557
		return array($ip1 . '/' . $bits); // exit if ip1=ip2 (trivial case)
558
	}
559

    
560
	if ($ip1bin > $ip2bin) {
561
		list ($ip1bin, $ip2bin) = array($ip2bin, $ip1bin);  // swap if needed (ensures ip1 < ip2)
562
	}
563

    
564
	$rangesubnets = array();
565
	$netsize = 0;
566

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

    
571
		// 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)
572

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

    
581
		// 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)
582

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

    
592
		// this is the only edge case arising from increment/decrement.
593
		// 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)
594

    
595
		if ($ip2bin < $ip1bin) {
596
			continue;
597
		}
598

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

    
602
		$shift = $bits - max(strrpos($ip1bin, '0'), strrpos($ip2bin, '1'));  // num of low bits which are '0' in ip1 and '1' in ip2
603
		$ip1bin = str_repeat('0', $shift) . substr($ip1bin, 0, $bits - $shift);
604
		$ip2bin = str_repeat('0', $shift) . substr($ip2bin, 0, $bits - $shift);
605
		$netsize += $shift;
606
		if ($ip1bin == $ip2bin) {
607
			// we're done.
608
			$new_subnet_ip = substr($ip1bin, $netsize, $bits - $netsize) . str_repeat('0', $netsize);
609
			$rangesubnets[$new_subnet_ip] = $bits - $netsize;
610
			continue;
611
		}
612

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

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

    
618
	ksort($rangesubnets, SORT_STRING);
619
	$out = array();
620

    
621
	foreach ($rangesubnets as $ip => $netmask) {
622
		if ($proto == 'ipv4') {
623
			$i = str_split($ip, 8);
624
			$out[] = implode('.', array(bindec($i[0]), bindec($i[1]), bindec($i[2]), bindec($i[3]))) . '/' . $netmask;
625
		} else {
626
			$out[] = bin_to_compressed_ip6($ip) . '/' . $netmask;
627
		}
628
	}
629

    
630
	return $out;
631
}
632

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

    
650
/* returns true if $ipaddr is a valid dotted IPv4 address or a IPv6
651
	false - not valid
652
	true (numeric 4 or 6) - if valid, gives type of address */
653
function is_ipaddr($ipaddr) {
654
	if (is_ipaddrv4($ipaddr)) {
655
		return 4;
656
	}
657
	if (is_ipaddrv6($ipaddr)) {
658
		return 6;
659
	}
660
	return false;
661
}
662

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

    
683
function is_ipaddrv6_v4map($ipaddr) {
684
	/* check RFC4291 par 2.2.2 format, ex: fd00::1.2.3.4
685
	 * see https://redmine.pfsense.org/issues/11446 */
686
	if (is_ipaddrv6($ipaddr) && preg_match('/^[0-9a-f:]{2,30}[0-9.]{7,15}$/i', $ipaddr)) {
687
		return true;
688
	}
689
	return false;
690
}
691

    
692
/* returns true if $ipaddr is a valid dotted IPv4 address */
693
function is_ipaddrv4($ipaddr) {
694
	if (!is_string($ipaddr) || empty($ipaddr) || ip2long($ipaddr) === FALSE) {
695
		return false;
696
	}
697
	return true;
698
}
699

    
700
function is_mcast($ipaddr) {
701
	if (is_mcastv4($ipaddr)) {
702
		return 4;
703
	}
704
	if (is_mcastv6($ipaddr)) {
705
		return 6;
706
	}
707
	return false;
708
}
709

    
710
function is_mcastv4($ipaddr) {
711
	if (!is_ipaddrv4($ipaddr) ||
712
	    !preg_match('/^2(?:2[4-9]|3\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d?|0)){3}$/', $ipaddr)) {
713
		return false;
714
	}
715
	return true;
716
}
717

    
718
function is_mcastv6($ipaddr) {
719
	if (!is_ipaddrv6($ipaddr) || !preg_match('/^ff.+$/', $ipaddr)) {
720
		return false;
721
	}
722
	return true;
723
}
724

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

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

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

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

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

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

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

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

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

    
808
}
809

    
810
/* returns true if $subnet is a valid IPv4 or IPv6 subnet in CIDR format
811
	false - if not a valid subnet
812
	true (numeric 4 or 6) - if valid, gives type of subnet */
813
function is_subnet($subnet) {
814
	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)) {
815
		if (is_ipaddrv4($parts[1]) && $parts[3] <= 32) {
816
			return 4;
817
		}
818
		if (is_ipaddrv6($parts[2]) && $parts[3] <= 128) {
819
			return 6;
820
		}
821
	}
822
	return false;
823
}
824

    
825
function is_v4($ip_or_subnet) {
826
	return is_ipaddrv4($ip_or_subnet) || is_subnetv4($ip_or_subnet);
827
}
828

    
829
function is_v6($ip_or_subnet) {
830
	return is_ipaddrv6($ip_or_subnet) || is_subnetv6($ip_or_subnet);
831
}
832

    
833
/* same as is_subnet() but accepts IPv4 only */
834
function is_subnetv4($subnet) {
835
	return (is_subnet($subnet) == 4);
836
}
837

    
838
/* same as is_subnet() but accepts IPv6 only */
839
function is_subnetv6($subnet) {
840
	return (is_subnet($subnet) == 6);
841
}
842

    
843
/* returns true if $subnet is a valid subnet in CIDR format or an alias thereof */
844
function is_subnetoralias($subnet) {
845
	global $aliastable;
846

    
847
	if (isset($aliastable[$subnet]) && is_subnet($aliastable[$subnet])) {
848
		return true;
849
	} else {
850
		return is_subnet($subnet);
851
	}
852
}
853

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

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

    
882
	// 2**N returns an exact result as an INT if possible, and a float/double if not.
883
	// 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
884
	$result = 2 ** $snsize;
885

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

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

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

    
919
/* find out whether two IPv4 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_subnetsv4_overlap($subnet1, $bits1, $subnet2, $bits2) {
922
	$largest_sn = min($bits1, $bits2);
923
	$subnetv4_start1 = gen_subnetv4($subnet1, $largest_sn);
924
	$subnetv4_start2 = gen_subnetv4($subnet2, $largest_sn);
925

    
926
	if ($subnetv4_start1 == '' || $subnetv4_start2 == '') {
927
		// One or both args is not a valid IPv4 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 ($subnetv4_start1 == $subnetv4_start2);
932
}
933

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

    
941
	if ($subnetv6_start1 == '' || $subnetv6_start2 == '') {
942
		// One or both args is not a valid IPv6 subnet
943
		//FIXME: needs to return "bad data" not true/false if bad. For now return false, best we can do until fixed
944
		return false;
945
	}
946
	return ($subnetv6_start1 == $subnetv6_start2);
947
}
948

    
949
/* return all PTR zones for a IPv6 network */
950
function get_v6_ptr_zones($subnet, $bits) {
951
	$result = array();
952

    
953
	if (!is_ipaddrv6($subnet)) {
954
		return $result;
955
	}
956

    
957
	if (!is_numericint($bits) || $bits > 128) {
958
		return $result;
959
	}
960

    
961
	/*
962
	 * Find a small nibble boundary subnet mask
963
	 * e.g. a /29 will create 8 /32 PTR zones
964
	 */
965
	$small_sn = $bits;
966
	while ($small_sn % 4 != 0) {
967
		$small_sn++;
968
	}
969

    
970
	/* Get network prefix */
971
	$small_subnet = Net_IPv6::getNetmask($subnet, $bits);
972

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

    
984
		/* Detect what part of IP should be increased */
985
		$change_part = (int) ($small_sn / 16);
986
		if ($small_sn % 16 == 0) {
987
			$change_part--;
988
		}
989

    
990
		/* Increase 1 to desired part */
991
		$parts = explode(":", Net_IPv6::uncompress($small_subnet));
992
		$parts[$change_part]++;
993
		$small_subnet = implode(":", $parts);
994
	}
995

    
996
	return $result;
997
}
998

    
999
/* return true if $addr is in $subnet, false if not */
1000
function ip_in_subnet($addr, $subnet) {
1001
	if (is_ipaddrv6($addr) && is_subnetv6($subnet)) {
1002
		return (Net_IPv6::isInNetmask($addr, $subnet));
1003
	} else if (is_ipaddrv4($addr) && is_subnetv4($subnet)) {
1004
		list($ip, $mask) = explode('/', $subnet);
1005
		$mask = (0xffffffff << (32 - $mask)) & 0xffffffff;
1006
		return ((ip2long($addr) & $mask) == (ip2long($ip) & $mask));
1007
	}
1008
	return false;
1009
}
1010

    
1011
/* returns true if $hostname is just a valid hostname (top part without any of the domain part) */
1012
function is_unqualified_hostname($hostname) {
1013
	if (!is_string($hostname)) {
1014
		return false;
1015
	}
1016

    
1017
	if (preg_match('/^(?:[a-z0-9_]|[a-z0-9_][a-z0-9_\-]*[a-z0-9_])$/i', $hostname)) {
1018
		return true;
1019
	} else {
1020
		return false;
1021
	}
1022
}
1023

    
1024
/* returns true if $hostname is a valid hostname, with or without being a fully-qualified domain name. */
1025
function is_hostname($hostname, $allow_wildcard=false) {
1026
	if (!is_string($hostname)) {
1027
		return false;
1028
	}
1029

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

    
1042
/* returns true if $domain is a valid domain name */
1043
function is_domain($domain, $allow_wildcard=false, $trailing_dot=true) {
1044
	if (!is_string($domain)) {
1045
		return false;
1046
	}
1047
	if (!$trailing_dot && ($domain[strlen($domain)-1] == ".")) {
1048
		return false;
1049
	}
1050
	if ($allow_wildcard) {
1051
		$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';
1052
	} else {
1053
		$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';
1054
	}
1055

    
1056
	if (preg_match($domain_regex, $domain)) {
1057
		return true;
1058
	} else {
1059
		return false;
1060
	}
1061
}
1062

    
1063
/* returns true if $macaddr is a valid MAC address */
1064
function is_macaddr($macaddr, $partial=false) {
1065
	$values = explode(":", $macaddr);
1066

    
1067
	/* Verify if the MAC address has a proper amount of parts for either a partial or full match. */
1068
	if ($partial) {
1069
		if ((count($values) < 1) || (count($values) > 6)) {
1070
			return false;
1071
		}
1072
	} elseif (count($values) != 6) {
1073
		return false;
1074
	}
1075
	for ($i = 0; $i < count($values); $i++) {
1076
		if (ctype_xdigit($values[$i]) == false)
1077
			return false;
1078
		if (hexdec($values[$i]) < 0 || hexdec($values[$i]) > 255)
1079
			return false;
1080
	}
1081

    
1082
	return true;
1083
}
1084

    
1085
/*
1086
	If $return_message is true then
1087
		returns a text message about the reason that the name is invalid.
1088
		the text includes the type of "thing" that is being checked, passed in $object. (e.g. "alias", "gateway group", "schedule")
1089
	else
1090
		returns true if $name is a valid name for an alias
1091
		returns false if $name is not a valid name for an alias
1092

    
1093
	Aliases cannot be:
1094
		bad chars: anything except a-z 0-9 and underscore
1095
		bad names: empty string, pure numeric, pure underscore
1096
		reserved words: pre-defined service/protocol/port names which should not be ambiguous, and the words "port" and  "pass" */
1097

    
1098
function is_validaliasname($name, $return_message = false, $object = "alias") {
1099
	/* Array of reserved words */
1100
	$reserved = array("port", "pass");
1101

    
1102
	if (!is_string($name) || strlen($name) >= 32 || preg_match('/(^_*$|^\d*$|[^a-z0-9_])/i', $name)) {
1103
		if ($return_message) {
1104
			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, _');
1105
		} else {
1106
			return false;
1107
		}
1108
	}
1109
	if (in_array($name, $reserved, true)) {
1110
		if ($return_message) {
1111
			return sprintf(gettext('The %1$s name must not be either of the reserved words %2$s or %3$s.'), $object, "'port'", "'pass'");
1112
		} else {
1113
			return false;
1114
		}
1115
	}
1116
	if (getprotobyname($name)) {
1117
		if ($return_message) {
1118
			return sprintf(gettext('The %1$s name must not be an IP protocol name such as TCP, UDP, ICMP etc.'), $object);
1119
		} else {
1120
			return false;
1121
		}
1122
	}
1123
	if (getservbyname($name, "tcp") || getservbyname($name, "udp")) {
1124
		if ($return_message) {
1125
			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);
1126
		} else {
1127
			return false;
1128
		}
1129
	}
1130
	if ($return_message) {
1131
		return sprintf(gettext("The %1$s name is valid."), $object);
1132
	} else {
1133
		return true;
1134
	}
1135
}
1136

    
1137
/* returns a text message indicating if the alias name is valid, or the reason it is not valid. */
1138
function invalidaliasnamemsg($name, $object = "alias") {
1139
	return is_validaliasname($name, true, $object);
1140
}
1141

    
1142
/*
1143
 * returns true if $range is a valid integer range between $min and $max
1144
 * range delimiter can be ':' or '-'
1145
 */
1146
function is_intrange($range, $min, $max) {
1147
	$values = preg_split("/[:-]/", $range);
1148

    
1149
	if (!is_array($values) || count($values) != 2) {
1150
		return false;
1151
	}
1152

    
1153
	if (!ctype_digit($values[0]) || !ctype_digit($values[1])) {
1154
		return false;
1155
	}
1156

    
1157
	$values[0] = intval($values[0]);
1158
	$values[1] = intval($values[1]);
1159

    
1160
	if ($values[0] >= $values[1]) {
1161
		return false;
1162
	}
1163

    
1164
	if ($values[0] < $min || $values[1] > $max) {
1165
		return false;
1166
	}
1167

    
1168
	return true;
1169
}
1170

    
1171
/* returns true if $port is a valid TCP/UDP port */
1172
function is_port($port) {
1173
	if (ctype_digit($port) && ((intval($port) >= 1) && (intval($port) <= 65535))) {
1174
		return true;
1175
	}
1176
	if (getservbyname($port, "tcp") || getservbyname($port, "udp")) {
1177
		return true;
1178
	}
1179
	return false;
1180
}
1181

    
1182
/* returns true if $port is in use */
1183
function is_port_in_use($port, $proto = "tcp", $ip_version = 4) {
1184
	$port_info = array();
1185
	exec("/usr/bin/netstat --libxo json -an " . escapeshellarg('-' . $ip_version) . " -p " . escapeshellarg($proto), $rawdata, $rc);
1186
	if ($rc == 0) {
1187
		$netstatarr = json_decode(implode(" ", $rawdata), JSON_OBJECT_AS_ARRAY);
1188
		$netstatarr = $netstatarr['statistics']['socket'];
1189

    
1190
		foreach($netstatarr as $index => $portstats){
1191
			array_push($port_info, $portstats['local']['port']);
1192
		}
1193
	}
1194

    
1195
	return in_array($port, $port_info);
1196
}
1197

    
1198
/* returns true if $portrange is a valid TCP/UDP portrange ("<port>:<port>") */
1199
function is_portrange($portrange) {
1200
	$ports = explode(":", $portrange);
1201

    
1202
	return (count($ports) == 2 && is_port($ports[0]) && is_port($ports[1]));
1203
}
1204

    
1205
/* returns true if $port is a valid TCP/UDP port number or range ("<port>:<port>") */
1206
function is_port_or_range($port) {
1207
	return (is_port($port) || is_portrange($port));
1208
}
1209

    
1210
/* returns true if $port is an alias that is a port type */
1211
function is_portalias($port) {
1212
	global $config;
1213

    
1214
	if (is_alias($port)) {
1215
		if (is_array($config['aliases']['alias'])) {
1216
			foreach ($config['aliases']['alias'] as $alias) {
1217
				if ($alias['name'] == $port && preg_match("/port/i", $alias['type'])) {
1218
					return true;
1219
				}
1220
			}
1221
		}
1222
	}
1223
	return false;
1224
}
1225

    
1226
/* returns true if $port is a valid port number or an alias thereof */
1227
function is_port_or_alias($port) {
1228
	return (is_port($port) || is_portalias($port));
1229
}
1230

    
1231
/* returns true if $port is a valid TCP/UDP port number or range ("<port>:<port>") or an alias thereof */
1232
function is_port_or_range_or_alias($port) {
1233
	return (is_port($port) || is_portrange($port) || is_portalias($port));
1234
}
1235

    
1236
/* create ranges of sequential port numbers (200:215) and remove duplicates */
1237
function group_ports($ports, $kflc = false) {
1238
	if (!is_array($ports) || empty($ports)) {
1239
		return;
1240
	}
1241

    
1242
	$uniq = array();
1243
	$comments = array();
1244
	foreach ($ports as $port) {
1245
		if (($kflc) && (strpos($port, '#') === 0)) {	// Keep Full Line Comments (lines beginning with #).
1246
			$comments[] = $port;
1247
		} else if (is_portrange($port)) {
1248
			list($begin, $end) = explode(":", $port);
1249
			if ($begin > $end) {
1250
				$aux = $begin;
1251
				$begin = $end;
1252
				$end = $aux;
1253
			}
1254
			for ($i = $begin; $i <= $end; $i++) {
1255
				if (!in_array($i, $uniq)) {
1256
					$uniq[] = $i;
1257
				}
1258
			}
1259
		} else if (is_port($port)) {
1260
			if (!in_array($port, $uniq)) {
1261
				$uniq[] = $port;
1262
			}
1263
		}
1264
	}
1265
	sort($uniq, SORT_NUMERIC);
1266

    
1267
	$result = array();
1268
	foreach ($uniq as $idx => $port) {
1269
		if ($idx == 0) {
1270
			$result[] = $port;
1271
			continue;
1272
		}
1273

    
1274
		$last = end($result);
1275
		if (is_portrange($last)) {
1276
			list($begin, $end) = explode(":", $last);
1277
		} else {
1278
			$begin = $end = $last;
1279
		}
1280

    
1281
		if ($port == ($end+1)) {
1282
			$end++;
1283
			$result[count($result)-1] = "{$begin}:{$end}";
1284
		} else {
1285
			$result[] = $port;
1286
		}
1287
	}
1288

    
1289
	return array_merge($comments, $result);
1290
}
1291

    
1292
/* returns true if $val is a valid shaper bandwidth value */
1293
function is_valid_shaperbw($val) {
1294
	return (preg_match("/^(\d+(?:\.\d+)?)([MKG]?b|%)$/", $val));
1295
}
1296

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

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

    
1308
	return false;
1309
}
1310

    
1311
/* returns true if $test is in the range between $start and $end */
1312
function is_inrange_v6($test, $start, $end) {
1313
	if (!is_ipaddrv6($test) || !is_ipaddrv6($start) || !is_ipaddrv6($end)) {
1314
		return false;
1315
	}
1316

    
1317
	if (inet_pton($test) <= inet_pton($end) &&
1318
	    inet_pton($test) >= inet_pton($start)) {
1319
		return true;
1320
	}
1321

    
1322
	return false;
1323
}
1324

    
1325
/* returns true if $test is in the range between $start and $end */
1326
function is_inrange($test, $start, $end) {
1327
	return is_ipaddrv6($test) ? is_inrange_v6($test, $start, $end) : is_inrange_v4($test, $start, $end);
1328
}
1329

    
1330
function build_vip_list($fif, $family = "all") {
1331
	$list = array('address' => gettext('Interface Address'));
1332

    
1333
	$viplist = get_configured_vip_list($family);
1334
	foreach ($viplist as $vip => $address) {
1335
		if ($fif == get_configured_vip_interface($vip)) {
1336
			$list[$vip] = "$address";
1337
			if (get_vip_descr($address)) {
1338
				$list[$vip] .= " (". get_vip_descr($address) .")";
1339
			}
1340
		}
1341
	}
1342

    
1343
	return($list);
1344
}
1345

    
1346
function get_configured_vip_list($family = 'all', $type = VIP_ALL) {
1347
	global $config;
1348

    
1349
	$list = array();
1350
	if (!is_array($config['virtualip']) ||
1351
	    !is_array($config['virtualip']['vip']) ||
1352
	    empty($config['virtualip']['vip'])) {
1353
		return ($list);
1354
	}
1355

    
1356
	$viparr = &$config['virtualip']['vip'];
1357
	foreach ($viparr as $vip) {
1358

    
1359
		if ($type == VIP_CARP) {
1360
			if ($vip['mode'] != "carp")
1361
				continue;
1362
		} elseif ($type == VIP_IPALIAS) {
1363
			if ($vip['mode'] != "ipalias")
1364
				continue;
1365
		} else {
1366
			if ($vip['mode'] != "carp" && $vip['mode'] != "ipalias")
1367
				continue;
1368
		}
1369

    
1370
		if ($family == 'all' ||
1371
		    ($family == 'inet' && is_ipaddrv4($vip['subnet'])) ||
1372
		    ($family == 'inet6' && is_ipaddrv6($vip['subnet']))) {
1373
			$list["_vip{$vip['uniqid']}"] = $vip['subnet'];
1374
		}
1375
	}
1376
	return ($list);
1377
}
1378

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

    
1381
	return (get_configured_vip_detail($vipinterface, 'all', 'vip'));
1382
}
1383

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

    
1386
	return (get_configured_vip_detail($vipinterface, 'all', 'iface'));
1387
}
1388

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

    
1391
	return (get_configured_vip_detail($vipinterface, 'inet', 'ip'));
1392
}
1393

    
1394
function get_configured_vip_ipv6($vipinterface = '') {
1395

    
1396
	return (get_configured_vip_detail($vipinterface, 'inet6', 'ip'));
1397
}
1398

    
1399
function get_configured_vip_subnetv4($vipinterface = '') {
1400

    
1401
	return (get_configured_vip_detail($vipinterface, 'inet', 'subnet'));
1402
}
1403

    
1404
function get_configured_vip_subnetv6($vipinterface = '') {
1405

    
1406
	return (get_configured_vip_detail($vipinterface, 'inet6', 'subnet'));
1407
}
1408

    
1409
function get_configured_vip_detail($vipinterface = '', $family = 'inet', $what = 'ip') {
1410
	global $config;
1411

    
1412
	if (empty($vipinterface) ||
1413
	    !is_array($config['virtualip']) ||
1414
	    !is_array($config['virtualip']['vip']) ||
1415
	    empty($config['virtualip']['vip'])) {
1416
		return (NULL);
1417
	}
1418

    
1419
	$viparr = &$config['virtualip']['vip'];
1420
	foreach ($viparr as $vip) {
1421
		if ($vip['mode'] != "carp" && $vip['mode'] != "ipalias") {
1422
			continue;
1423
		}
1424

    
1425
		if ($vipinterface != "_vip{$vip['uniqid']}") {
1426
			continue;
1427
		}
1428

    
1429
		switch ($what) {
1430
			case 'subnet':
1431
				if ($family == 'inet' && is_ipaddrv4($vip['subnet']))
1432
					return ($vip['subnet_bits']);
1433
				else if ($family == 'inet6' && is_ipaddrv6($vip['subnet']))
1434
					return ($vip['subnet_bits']);
1435
				break;
1436
			case 'iface':
1437
				return ($vip['interface']);
1438
				break;
1439
			case 'vip':
1440
				return ($vip);
1441
				break;
1442
			case 'ip':
1443
			default:
1444
				if ($family == 'inet' && is_ipaddrv4($vip['subnet'])) {
1445
					return ($vip['subnet']);
1446
				} else if ($family == 'inet6' && is_ipaddrv6($vip['subnet'])) {
1447
					return ($vip['subnet']);
1448
				}
1449
				break;
1450
		}
1451
		break;
1452
	}
1453

    
1454
	return (NULL);
1455
}
1456

    
1457
/* comparison function for sorting by the order in which interfaces are normally created */
1458
function compare_interface_friendly_names($a, $b) {
1459
	if ($a == $b) {
1460
		return 0;
1461
	} else if ($a == 'wan') {
1462
		return -1;
1463
	} else if ($b == 'wan') {
1464
		return 1;
1465
	} else if ($a == 'lan') {
1466
		return -1;
1467
	} else if ($b == 'lan') {
1468
		return 1;
1469
	}
1470

    
1471
	return strnatcmp($a, $b);
1472
}
1473

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

    
1478
	$iflist = array();
1479

    
1480
	/* if list */
1481
	foreach ($config['interfaces'] as $if => $ifdetail) {
1482
		if (isset($ifdetail['enable']) || $withdisabled == true) {
1483
			$iflist[$if] = $if;
1484
		}
1485
	}
1486

    
1487
	return $iflist;
1488
}
1489

    
1490
/* return the configured interfaces list. */
1491
function get_configured_interface_list_by_realif($withdisabled = false) {
1492
	global $config;
1493

    
1494
	$iflist = array();
1495

    
1496
	/* if list */
1497
	foreach ($config['interfaces'] as $if => $ifdetail) {
1498
		if (isset($ifdetail['enable']) || $withdisabled == true) {
1499
			$tmpif = get_real_interface($if);
1500
			if (!empty($tmpif)) {
1501
				$iflist[$tmpif] = $if;
1502
			}
1503
		}
1504
	}
1505

    
1506
	return $iflist;
1507
}
1508

    
1509
/* return the configured interfaces list with their description. */
1510
function get_configured_interface_with_descr($withdisabled = false) {
1511
	global $config, $user_settings;
1512

    
1513
	$iflist = array();
1514

    
1515
	/* if list */
1516
	foreach ($config['interfaces'] as $if => $ifdetail) {
1517
		if (isset($ifdetail['enable']) || $withdisabled == true) {
1518
			if (empty($ifdetail['descr'])) {
1519
				$iflist[$if] = strtoupper($if);
1520
			} else {
1521
				$iflist[$if] = strtoupper($ifdetail['descr']);
1522
			}
1523
		}
1524
	}
1525

    
1526
	if ($user_settings['webgui']['interfacessort']) {
1527
		asort($iflist);
1528
	}
1529

    
1530
	return $iflist;
1531
}
1532

    
1533
/*
1534
 *   get_configured_ip_addresses() - Return a list of all configured
1535
 *   IPv4 addresses.
1536
 *
1537
 */
1538
function get_configured_ip_addresses() {
1539
	global $config;
1540

    
1541
	if (!function_exists('get_interface_ip')) {
1542
		require_once("interfaces.inc");
1543
	}
1544
	$ip_array = array();
1545
	$interfaces = get_configured_interface_list();
1546
	if (is_array($interfaces)) {
1547
		foreach ($interfaces as $int) {
1548
			$ipaddr = get_interface_ip($int);
1549
			$ip_array[$int] = $ipaddr;
1550
		}
1551
	}
1552
	$interfaces = get_configured_vip_list('inet');
1553
	if (is_array($interfaces)) {
1554
		foreach ($interfaces as $int => $ipaddr) {
1555
			$ip_array[$int] = $ipaddr;
1556
		}
1557
	}
1558

    
1559
	/* pppoe server */
1560
	if (is_array($config['pppoes']) && is_array($config['pppoes']['pppoe'])) {
1561
		foreach ($config['pppoes']['pppoe'] as $pppoe) {
1562
			if ($pppoe['mode'] == "server") {
1563
				if (is_ipaddr($pppoe['localip'])) {
1564
					$int = "poes". $pppoe['pppoeid'];
1565
					$ip_array[$int] = $pppoe['localip'];
1566
				}
1567
			}
1568
		}
1569
	}
1570

    
1571
	return $ip_array;
1572
}
1573

    
1574
/*
1575
 *   get_configured_ipv6_addresses() - Return a list of all configured
1576
 *   IPv6 addresses.
1577
 *
1578
 */
1579
function get_configured_ipv6_addresses($linklocal_fallback = false) {
1580
	require_once("interfaces.inc");
1581
	$ipv6_array = array();
1582
	$interfaces = get_configured_interface_list();
1583
	if (is_array($interfaces)) {
1584
		foreach ($interfaces as $int) {
1585
			$ipaddrv6 = text_to_compressed_ip6(get_interface_ipv6($int, false, $linklocal_fallback));
1586
			$ipv6_array[$int] = $ipaddrv6;
1587
		}
1588
	}
1589
	$interfaces = get_configured_vip_list('inet6');
1590
	if (is_array($interfaces)) {
1591
		foreach ($interfaces as $int => $ipaddrv6) {
1592
			$ipv6_array[$int] = text_to_compressed_ip6($ipaddrv6);
1593
		}
1594
	}
1595
	return $ipv6_array;
1596
}
1597

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

    
1726
			case "friendly":
1727
				if ($friendly != "") {
1728
					$toput['if'] = $ifname;
1729
					$iflist[$friendly] = $toput;
1730
				}
1731
				break;
1732
			}
1733
		}
1734
	}
1735
	return $iflist;
1736
}
1737

    
1738
function get_lagg_interface_list() {
1739
	global $config;
1740

    
1741
	$plist = array();
1742
	if (isset($config['laggs']['lagg']) && is_array($config['laggs']['lagg'])) {
1743
		foreach ($config['laggs']['lagg'] as $lagg) {
1744
			$lagg['mac'] = get_interface_mac($lagg['laggif']);
1745
			$lagg['islagg'] = true;
1746
			$plist[$lagg['laggif']] = $lagg;
1747
		}
1748
	}
1749

    
1750
	return ($plist);
1751
}
1752

    
1753
/****f* util/log_error
1754
* NAME
1755
*   log_error  - Sends a string to syslog.
1756
* INPUTS
1757
*   $error     - string containing the syslog message.
1758
* RESULT
1759
*   null
1760
******/
1761
function log_error($error) {
1762
	global $g;
1763
	$page = $_SERVER['SCRIPT_NAME'];
1764
	if (empty($page)) {
1765
		$files = get_included_files();
1766
		$page = basename($files[0]);
1767
	}
1768
	syslog(LOG_ERR, "$page: $error");
1769
	if ($g['debug']) {
1770
		syslog(LOG_WARNING, var_dump(debug_backtrace()));
1771
	}
1772
	return;
1773
}
1774

    
1775
/****f* util/log_auth
1776
* NAME
1777
*   log_auth   - Sends a string to syslog as LOG_AUTH facility
1778
* INPUTS
1779
*   $error     - string containing the syslog message.
1780
* RESULT
1781
*   null
1782
******/
1783
function log_auth($error) {
1784
	global $g;
1785
	$page = $_SERVER['SCRIPT_NAME'];
1786
	syslog(LOG_AUTH, "$page: $error");
1787
	if ($g['debug']) {
1788
		syslog(LOG_WARNING, var_dump(debug_backtrace()));
1789
	}
1790
	return;
1791
}
1792

    
1793
/****f* util/exec_command
1794
 * NAME
1795
 *   exec_command - Execute a command and return a string of the result.
1796
 * INPUTS
1797
 *   $command   - String of the command to be executed.
1798
 * RESULT
1799
 *   String containing the command's result.
1800
 * NOTES
1801
 *   This function returns the command's stdout and stderr.
1802
 ******/
1803
function exec_command($command) {
1804
	$output = array();
1805
	exec($command . ' 2>&1', $output);
1806
	return(implode("\n", $output));
1807
}
1808

    
1809
/* wrapper for exec()
1810
   Executes in background or foreground.
1811
   For background execution, returns PID of background process to allow calling code control */
1812
function mwexec($command, $nologentry = false, $clearsigmask = false, $background = false) {
1813
	global $g;
1814
	$retval = 0;
1815

    
1816
	if ($g['debug']) {
1817
		if (!$_SERVER['REMOTE_ADDR']) {
1818
			echo "mwexec(): $command" . ($background ? " [BG]":"") . "\n";
1819
		}
1820
	}
1821
	if ($clearsigmask) {
1822
		$oldset = array();
1823
		pcntl_sigprocmask(SIG_SETMASK, array(), $oldset);
1824
	}
1825

    
1826
	if ($background) {
1827
		// start background process and return PID
1828
		$retval = exec("/usr/bin/nohup $command > /dev/null 2>&1 & echo $!");
1829
	} else {
1830
		// run in foreground, and (optionally) log if nonzero return
1831
		$outputarray = array();
1832
		exec("$command 2>&1", $outputarray, $retval);
1833
		if (($retval <> 0) && (!$nologentry || isset($config['system']['developerspew']))) {
1834
			log_error(sprintf(gettext("The command '%1\$s' returned exit code '%2\$d', the output was '%3\$s' "), $command, $retval, implode(" ", $outputarray)));
1835
		}
1836
	}
1837

    
1838
	if ($clearsigmask) {
1839
		pcntl_sigprocmask(SIG_SETMASK, $oldset);
1840
	}
1841

    
1842
	return $retval;
1843
}
1844

    
1845
/* wrapper for exec() in background */
1846
function mwexec_bg($command, $clearsigmask = false) {
1847
	return mwexec($command, false, $clearsigmask, true);
1848
}
1849

    
1850
/*
1851
 * Unlink a file, or pattern-match of a file, if it exists
1852
 *
1853
 * If the file/path contains glob() compatible wildcards, all matching files
1854
 * will be unlinked.
1855
 * Any warning/errors are suppressed (e.g. no matching files to delete)
1856
 * If there are matching file(s) and they were all unlinked OK, then return
1857
 * true.  Otherwise return false (the requested file(s) did not exist, or
1858
 * could not be deleted), this allows the caller to know if they were the one
1859
 * to successfully delete the file(s).
1860
 */
1861
function unlink_if_exists($fn) {
1862
	$to_do = glob($fn);
1863
	if (is_array($to_do) && count($to_do) > 0) {
1864
		// Returns an array of boolean indicating if each unlink worked
1865
		$results = @array_map("unlink", $to_do);
1866
		// If there is no false in the array, then all went well
1867
		$result = !in_array(false, $results, true);
1868
	} else {
1869
		$result = @unlink($fn);
1870
	}
1871
	return $result;
1872
}
1873

    
1874
/* make a global alias table (for faster lookups) */
1875
function alias_make_table() {
1876
	global $aliastable, $config;
1877

    
1878
	$aliastable = array();
1879

    
1880
	init_config_arr(array('aliases', 'alias'));
1881
	foreach ($config['aliases']['alias'] as $alias) {
1882
		if ($alias['name']) {
1883
			$aliastable[$alias['name']] = $alias['address'];
1884
		}
1885
	}
1886
}
1887

    
1888
/* check if an alias exists */
1889
function is_alias($name) {
1890
	global $aliastable;
1891

    
1892
	return isset($aliastable[$name]);
1893
}
1894

    
1895
function alias_get_type($name) {
1896
	global $config;
1897

    
1898
	if (is_array($config['aliases']['alias'])) {
1899
		foreach ($config['aliases']['alias'] as $alias) {
1900
			if ($name == $alias['name']) {
1901
				return $alias['type'];
1902
			}
1903
		}
1904
	}
1905

    
1906
	return "";
1907
}
1908

    
1909
/* expand a host or network alias, if necessary */
1910
function alias_expand($name) {
1911
	global $config, $aliastable;
1912
	$urltable_prefix = "/var/db/aliastables/";
1913
	$urltable_filename = $urltable_prefix . $name . ".txt";
1914

    
1915
	if (isset($aliastable[$name])) {
1916
		// alias names cannot be strictly numeric. redmine #4289
1917
		if (is_numericint($name)) {
1918
			return null;
1919
		}
1920
		/*
1921
		 * make sure if it's a ports alias, it actually exists.
1922
		 * redmine #5845
1923
		 */
1924
		foreach ($config['aliases']['alias'] as $alias) {
1925
			if ($alias['name'] == $name) {
1926
				if ($alias['type'] == "urltable_ports") {
1927
					if (is_URL($alias['url']) &&
1928
					    file_exists($urltable_filename) &&
1929
					    filesize($urltable_filename)) {
1930
						return "\${$name}";
1931
					} else {
1932
						return null;
1933
					}
1934
				}
1935
			}
1936
		}
1937
		return "\${$name}";
1938
	} else if (is_ipaddr($name) || is_subnet($name) ||
1939
	    is_port_or_range($name)) {
1940
		return "{$name}";
1941
	} else {
1942
		return null;
1943
	}
1944
}
1945

    
1946
function alias_expand_urltable($name) {
1947
	global $config;
1948
	$urltable_prefix = "/var/db/aliastables/";
1949
	$urltable_filename = $urltable_prefix . $name . ".txt";
1950

    
1951
	if (!is_array($config['aliases']['alias'])) {
1952
		return null;
1953
	}
1954

    
1955
	foreach ($config['aliases']['alias'] as $alias) {
1956
		if (!preg_match("/urltable/i", $alias['type']) ||
1957
		    ($alias['name'] != $name)) {
1958
			continue;
1959
		}
1960

    
1961
		if (is_URL($alias["url"]) && file_exists($urltable_filename)) {
1962
			if (!filesize($urltable_filename)) {
1963
				// file exists, but is empty, try to sync
1964
				send_event("service sync alias {$name}");
1965
			}
1966
			return $urltable_filename;
1967
		} else {
1968
			send_event("service sync alias {$name}");
1969
			break;
1970
		}
1971
	}
1972
	return null;
1973
}
1974

    
1975
/* obtain MAC address given an IP address by looking at the ARP/NDP table */
1976
function arp_get_mac_by_ip($ip, $do_ping = true) {
1977
	unset($macaddr);
1978
	$retval = 1;
1979
	switch (is_ipaddr($ip)) {
1980
		case 4:
1981
			if ($do_ping === true) {
1982
				mwexec("/sbin/ping -c 1 -t 1 " . escapeshellarg($ip), true);
1983
			}
1984
			$macaddr = exec("/usr/sbin/arp -n " . escapeshellarg($ip) . " | /usr/bin/awk '{print $4}'", $output, $retval);
1985
			break;
1986
		case 6:
1987
			if ($do_ping === true) {
1988
				mwexec("/sbin/ping6 -c 1 -X 1 " . escapeshellarg($ip), true);
1989
			}
1990
			$macaddr = exec("/usr/sbin/ndp -n " . escapeshellarg($ip) . " | /usr/bin/awk '{print $2}'", $output, $retval);
1991
			break;
1992
	}
1993
	if ($retval == 0 && is_macaddr($macaddr)) {
1994
		return $macaddr;
1995
	} else {
1996
		return false;
1997
	}
1998
}
1999

    
2000
/* return a fieldname that is safe for xml usage */
2001
function xml_safe_fieldname($fieldname) {
2002
	$replace = array(
2003
	    '/', '-', ' ', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')',
2004
	    '_', '+', '=', '{', '}', '[', ']', '|', '/', '<', '>', '?',
2005
	    ':', ',', '.', '\'', '\\'
2006
	);
2007
	return strtolower(str_replace($replace, "", $fieldname));
2008
}
2009

    
2010
function mac_format($clientmac) {
2011
	global $config, $cpzone;
2012

    
2013
	$mac = explode(":", $clientmac);
2014
	$mac_format = $cpzone ? $config['captiveportal'][$cpzone]['radmac_format'] : false;
2015

    
2016
	switch ($mac_format) {
2017
		case 'singledash':
2018
			return "$mac[0]$mac[1]$mac[2]-$mac[3]$mac[4]$mac[5]";
2019

    
2020
		case 'ietf':
2021
			return "$mac[0]-$mac[1]-$mac[2]-$mac[3]-$mac[4]-$mac[5]";
2022

    
2023
		case 'cisco':
2024
			return "$mac[0]$mac[1].$mac[2]$mac[3].$mac[4]$mac[5]";
2025

    
2026
		case 'unformatted':
2027
			return "$mac[0]$mac[1]$mac[2]$mac[3]$mac[4]$mac[5]";
2028

    
2029
		default:
2030
			return $clientmac;
2031
	}
2032
}
2033

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

    
2036
	$recresult = array();
2037
	$returnres = array();
2038
	for ($i = 0; $i < $retries; $i++) {
2039
		switch ($protocol) {
2040
			case 'any':
2041
				$checkproto = 'is_ipaddr';
2042
				$dnsproto = DNS_ANY;
2043
				$dnstype = array('A', 'AAAA');
2044
				break;
2045
			case 'inet6':
2046
				$checkproto = 'is_ipaddrv6';
2047
				$dnsproto = DNS_AAAA;
2048
				$dnstype = array('AAAA');
2049
				break;
2050
			case 'inet': 
2051
			default:
2052
				$checkproto = 'is_ipaddrv4';
2053
				$dnsproto = DNS_A;
2054
				$dnstype = array('A');
2055
				break;
2056
		}
2057
		if ($checkproto($hostname)) {
2058
			return $hostname;
2059
		}
2060
		$dnsresult = @dns_get_record($hostname, $dnsproto);
2061
		if (!empty($dnsresult)) {
2062
			foreach ($dnsresult as $dnsrec => $ip) {
2063
				if (is_array($ip)) {
2064
					if (in_array($ip['type'], $dnstype)) {
2065
					    if ($checkproto($ip['ip'])) { 
2066
						    $recresult[] = $ip['ip'];
2067
					    }
2068
					    if ($checkproto($ip['ipv6'])) { 
2069
						    $recresult[] = $ip['ipv6'];
2070
					    }
2071
					}
2072
				}
2073
			}
2074
		}
2075

    
2076
		sleep(1);
2077
	}
2078

    
2079
	if (!empty($recresult)) {
2080
		if ($numrecords == 1) {
2081
			return $recresult[0];
2082
		} else {
2083
			return array_slice($recresult, 0, $numrecords);
2084
		}
2085
	}
2086

    
2087
	return false;
2088
}
2089

    
2090
function format_bytes($bytes) {
2091
	if ($bytes >= 1099511627776) {
2092
		return sprintf("%.2f TiB", $bytes/1099511627776);
2093
	} else if ($bytes >= 1073741824) {
2094
		return sprintf("%.2f GiB", $bytes/1073741824);
2095
	} else if ($bytes >= 1048576) {
2096
		return sprintf("%.2f MiB", $bytes/1048576);
2097
	} else if ($bytes >= 1024) {
2098
		return sprintf("%.0f KiB", $bytes/1024);
2099
	} else {
2100
		return sprintf("%d B", $bytes);
2101
	}
2102
}
2103

    
2104
function format_number($num, $precision = 3) {
2105
	$units = array('', 'K', 'M', 'G', 'T');
2106

    
2107
	$i = 0;
2108
	while ($num > 1000 && $i < count($units)) {
2109
		$num /= 1000;
2110
		$i++;
2111
	}
2112
	$num = round($num, $precision);
2113

    
2114
	return ("$num {$units[$i]}");
2115
}
2116

    
2117

    
2118
function unformat_number($formated_num) {
2119
	$num = strtoupper($formated_num);
2120
    
2121
	if ( strpos($num,"T") !== false ) {
2122
		$num = str_replace("T","",$num) * 1000 * 1000 * 1000 * 1000;
2123
	} else if ( strpos($num,"G") !== false ) {
2124
		$num = str_replace("G","",$num) * 1000 * 1000 * 1000;
2125
	} else if ( strpos($num,"M") !== false ) {
2126
		$num = str_replace("M","",$num) * 1000 * 1000;
2127
	} else if ( strpos($num,"K") !== false ) {
2128
		$num = str_replace("K","",$num) * 1000;
2129
	}
2130
    
2131
	return $num;
2132
}
2133

    
2134
function update_filter_reload_status($text, $new=false) {
2135
	global $g;
2136

    
2137
	if ($new) {
2138
		file_put_contents("{$g['varrun_path']}/filter_reload_status", $text  . PHP_EOL);
2139
	} else {
2140
		file_put_contents("{$g['varrun_path']}/filter_reload_status", $text  . PHP_EOL, FILE_APPEND);
2141
	}
2142
}
2143

    
2144
/****** util/return_dir_as_array
2145
 * NAME
2146
 *   return_dir_as_array - Return a directory's contents as an array.
2147
 * INPUTS
2148
 *   $dir          - string containing the path to the desired directory.
2149
 *   $filter_regex - string containing a regular expression to filter file names. Default empty.
2150
 * RESULT
2151
 *   $dir_array - array containing the directory's contents. This array will be empty if the path specified is invalid.
2152
 ******/
2153
function return_dir_as_array($dir, $filter_regex = '') {
2154
	$dir_array = array();
2155
	if (is_dir($dir)) {
2156
		if ($dh = opendir($dir)) {
2157
			while (($file = readdir($dh)) !== false) {
2158
				if (($file == ".") || ($file == "..")) {
2159
					continue;
2160
				}
2161

    
2162
				if (empty($filter_regex) || preg_match($filter_regex, $file)) {
2163
					array_push($dir_array, $file);
2164
				}
2165
			}
2166
			closedir($dh);
2167
		}
2168
	}
2169
	return $dir_array;
2170
}
2171

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

    
2175
	/* process packager manager custom rules */
2176
	$files = return_dir_as_array($directory);
2177
	if (is_array($files)) {
2178
		foreach ($files as $file) {
2179
			if (stristr($file, ".sh") == true) {
2180
				mwexec($directory . $file . " start");
2181
			} else if (!is_dir($directory . "/" . $file) && stristr($file, ".inc")) {
2182
				require_once($directory . "/" . $file);
2183
			}
2184
		}
2185
	}
2186
}
2187

    
2188
/*
2189
 *    safe_mkdir($path, $mode = 0755)
2190
 *    create directory if it doesn't already exist and isn't a file!
2191
 */
2192
function safe_mkdir($path, $mode = 0755) {
2193
	global $g;
2194

    
2195
	if (!is_file($path) && !is_dir($path)) {
2196
		return @mkdir($path, $mode, true);
2197
	} else {
2198
		return false;
2199
	}
2200
}
2201

    
2202
/*
2203
 * get_sysctl($names)
2204
 * Get values of sysctl OID's listed in $names (accepts an array or a single
2205
 * name) and return an array of key/value pairs set for those that exist
2206
 */
2207
function get_sysctl($names) {
2208
	if (empty($names)) {
2209
		return array();
2210
	}
2211

    
2212
	if (is_array($names)) {
2213
		$name_list = array();
2214
		foreach ($names as $name) {
2215
			$name_list[] = escapeshellarg($name);
2216
		}
2217
	} else {
2218
		$name_list = array(escapeshellarg($names));
2219
	}
2220

    
2221
	exec("/sbin/sysctl -iq " . implode(" ", $name_list), $output);
2222
	$values = array();
2223
	foreach ($output as $line) {
2224
		$line = explode(": ", $line, 2);
2225
		if (count($line) == 2) {
2226
			$values[$line[0]] = $line[1];
2227
		}
2228
	}
2229

    
2230
	return $values;
2231
}
2232

    
2233
/*
2234
 * get_single_sysctl($name)
2235
 * Wrapper for get_sysctl() to simplify read of a single sysctl value
2236
 * return the value for sysctl $name or empty string if it doesn't exist
2237
 */
2238
function get_single_sysctl($name) {
2239
	if (empty($name)) {
2240
		return "";
2241
	}
2242

    
2243
	$value = get_sysctl($name);
2244
	if (empty($value) || !isset($value[$name])) {
2245
		return "";
2246
	}
2247

    
2248
	return $value[$name];
2249
}
2250

    
2251
/*
2252
 * set_sysctl($value_list)
2253
 * Set sysctl OID's listed as key/value pairs and return
2254
 * an array with keys set for those that succeeded
2255
 */
2256
function set_sysctl($values) {
2257
	if (empty($values)) {
2258
		return array();
2259
	}
2260

    
2261
	$value_list = array();
2262
	foreach ($values as $key => $value) {
2263
		$value_list[] = escapeshellarg($key) . "=" . escapeshellarg($value);
2264
	}
2265

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

    
2268
	/* Retry individually if failed (one or more read-only) */
2269
	if ($success <> 0 && count($value_list) > 1) {
2270
		foreach ($value_list as $value) {
2271
			exec("/sbin/sysctl -iq " . $value, $output);
2272
		}
2273
	}
2274

    
2275
	$ret = array();
2276
	foreach ($output as $line) {
2277
		$line = explode(": ", $line, 2);
2278
		if (count($line) == 2) {
2279
			$ret[$line[0]] = true;
2280
		}
2281
	}
2282

    
2283
	return $ret;
2284
}
2285

    
2286
/*
2287
 * set_single_sysctl($name, $value)
2288
 * Wrapper to set_sysctl() to make it simple to set only one sysctl
2289
 * returns boolean meaning if it succeeded
2290
 */
2291
function set_single_sysctl($name, $value) {
2292
	if (empty($name)) {
2293
		return false;
2294
	}
2295

    
2296
	$result = set_sysctl(array($name => $value));
2297

    
2298
	if (!isset($result[$name]) || $result[$name] != $value) {
2299
		return false;
2300
	}
2301

    
2302
	return true;
2303
}
2304

    
2305
/*
2306
 *     get_memory()
2307
 *     returns an array listing the amount of
2308
 *     memory installed in the hardware
2309
 *     [0] net memory available for the OS (FreeBSD) after some is taken by BIOS, video or whatever - e.g. 235 MBytes
2310
 *     [1] real (actual) memory of the system, should be the size of the RAM card/s - e.g. 256 MBytes
2311
 */
2312
function get_memory() {
2313
	$physmem = get_single_sysctl("hw.physmem");
2314
	$realmem = get_single_sysctl("hw.realmem");
2315
	/* convert from bytes to megabytes */
2316
	return array(($physmem/1048576), ($realmem/1048576));
2317
}
2318

    
2319
function mute_kernel_msgs() {
2320
	global $g, $config;
2321

    
2322
	if ($config['system']['enableserial']) {
2323
		return;
2324
	}
2325
	exec("/sbin/conscontrol mute on");
2326
}
2327

    
2328
function unmute_kernel_msgs() {
2329
	global $g;
2330

    
2331
	exec("/sbin/conscontrol mute off");
2332
}
2333

    
2334
function start_devd() {
2335
	global $g;
2336

    
2337
	/* Generate hints for the kernel loader. */
2338
	$module_paths = explode(";", get_single_sysctl("kern.module_path"));
2339
	foreach ($module_paths as $id => $path) {
2340
		if (!is_dir($path) || file_exists("{$path}/linker.hints")) {
2341
			continue;
2342
		}
2343
		if (($files = scandir($path)) == false) {
2344
			continue;
2345
		}
2346
		$found = false;
2347
		foreach ($files as $id => $file) {
2348
			if (strlen($file) > 3 &&
2349
			    strcasecmp(substr($file, -3), ".ko") == 0) {
2350
				$found = true;
2351
				break;
2352
			}
2353
		}
2354
		if ($found == false) {
2355
			continue;
2356
		}
2357
		$_gb = exec("/usr/sbin/kldxref $path");
2358
		unset($_gb);
2359
	}
2360

    
2361
	/* Use the undocumented -q options of devd to quiet its log spamming */
2362
	$_gb = exec("/sbin/devd -q -f /etc/{$g['product_name']}-devd.conf");
2363
	sleep(1);
2364
	unset($_gb);
2365
}
2366

    
2367
function is_interface_vlan_mismatch() {
2368
	global $config, $g;
2369

    
2370
	if (is_array($config['vlans']['vlan'])) {
2371
		foreach ($config['vlans']['vlan'] as $vlan) {
2372
			if (substr($vlan['if'], 0, 4) == "lagg") {
2373
				return false;
2374
			}
2375
			if (does_interface_exist($vlan['if']) == false) {
2376
				return true;
2377
			}
2378
		}
2379
	}
2380

    
2381
	return false;
2382
}
2383

    
2384
function is_interface_mismatch() {
2385
	global $config, $g;
2386

    
2387
	$do_assign = false;
2388
	$i = 0;
2389
	$missing_interfaces = array();
2390
	if (is_array($config['interfaces'])) {
2391
		foreach ($config['interfaces'] as $ifname => $ifcfg) {
2392
			if (interface_is_vlan($ifcfg['if']) != NULL ||
2393
			    interface_is_qinq($ifcfg['if']) != NULL ||
2394
			    preg_match("/^enc|^cua|^tun|^tap|^l2tp|^pptp|^ppp|^ovpn|^ipsec|^gif|^gre|^lagg|^bridge|vlan|_wlan|_\d{0,4}_\d{0,4}$/i", $ifcfg['if'])) {
2395
				// Do not check these interfaces.
2396
				$i++;
2397
				continue;
2398
			} else if (does_interface_exist($ifcfg['if']) == false) {
2399
				$missing_interfaces[] = $ifcfg['if'];
2400
				$do_assign = true;
2401
			} else {
2402
				$i++;
2403
			}
2404
		}
2405
	}
2406

    
2407
	if (file_exists("{$g['tmp_path']}/assign_complete")) {
2408
		$do_assign = false;
2409
	}
2410

    
2411
	if (!empty($missing_interfaces) && $do_assign) {
2412
		file_put_contents("{$g['tmp_path']}/missing_interfaces", implode(' ', $missing_interfaces));
2413
	} else {
2414
		@unlink("{$g['tmp_path']}/missing_interfaces");
2415
	}
2416

    
2417
	return $do_assign;
2418
}
2419

    
2420
/* sync carp entries to other firewalls */
2421
function carp_sync_client() {
2422
	global $g;
2423
	send_event("filter sync");
2424
}
2425

    
2426
/****f* util/isAjax
2427
 * NAME
2428
 *   isAjax - reports if the request is driven from prototype
2429
 * INPUTS
2430
 *   none
2431
 * RESULT
2432
 *   true/false
2433
 ******/
2434
function isAjax() {
2435
	return isset ($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest';
2436
}
2437

    
2438
/****f* util/timeout
2439
 * NAME
2440
 *   timeout - console input with timeout countdown. Note: erases 2 char of screen for timer. Leave space.
2441
 * INPUTS
2442
 *   optional, seconds to wait before timeout. Default 9 seconds.
2443
 * RESULT
2444
 *   returns 1 char of user input or null if no input.
2445
 ******/
2446
function timeout($timer = 9) {
2447
	while (!isset($key)) {
2448
		if ($timer >= 9) {
2449
			echo chr(8) . chr(8) . ($timer == 9 ? chr(32) : null) . "{$timer}";
2450
		} else {
2451
			echo chr(8). "{$timer}";
2452
		}
2453
		`/bin/stty -icanon min 0 time 25`;
2454
		$key = trim(`KEY=\`dd count=1 2>/dev/null\`; echo \$KEY`);
2455
		`/bin/stty icanon`;
2456
		if ($key == '') {
2457
			unset($key);
2458
		}
2459
		$timer--;
2460
		if ($timer == 0) {
2461
			break;
2462
		}
2463
	}
2464
	return $key;
2465
}
2466

    
2467
/****f* util/msort
2468
 * NAME
2469
 *   msort - sort array
2470
 * INPUTS
2471
 *   $array to be sorted, field to sort by, direction of sort
2472
 * RESULT
2473
 *   returns newly sorted array
2474
 ******/
2475
function msort($array, $id = "id", $sort_ascending = true) {
2476
	$temp_array = array();
2477
	if (!is_array($array)) {
2478
		return $temp_array;
2479
	}
2480
	while (count($array)>0) {
2481
		$lowest_id = 0;
2482
		$index = 0;
2483
		foreach ($array as $item) {
2484
			if (isset($item[$id])) {
2485
				if ($array[$lowest_id][$id]) {
2486
					if (strtolower($item[$id]) < strtolower($array[$lowest_id][$id])) {
2487
						$lowest_id = $index;
2488
					}
2489
				}
2490
			}
2491
			$index++;
2492
		}
2493
		$temp_array[] = $array[$lowest_id];
2494
		$array = array_merge(array_slice($array, 0, $lowest_id), array_slice($array, $lowest_id + 1));
2495
	}
2496
	if ($sort_ascending) {
2497
		return $temp_array;
2498
	} else {
2499
		return array_reverse($temp_array);
2500
	}
2501
}
2502

    
2503
/****f* util/is_URL
2504
 * NAME
2505
 *   is_URL
2506
 * INPUTS
2507
 *   string to check
2508
 * RESULT
2509
 *   Returns true if item is a URL
2510
 ******/
2511
function is_URL($url) {
2512
	$match = preg_match("'\b(([\w-]+://?|www[.])[^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|/)))'", $url);
2513
	if ($match) {
2514
		return true;
2515
	}
2516
	return false;
2517
}
2518

    
2519
function is_file_included($file = "") {
2520
	$files = get_included_files();
2521
	if (in_array($file, $files)) {
2522
		return true;
2523
	}
2524

    
2525
	return false;
2526
}
2527

    
2528
/*
2529
 * Replace a value on a deep associative array using regex
2530
 */
2531
function array_replace_values_recursive($data, $match, $replace) {
2532
	if (empty($data)) {
2533
		return $data;
2534
	}
2535

    
2536
	if (is_string($data)) {
2537
		$data = preg_replace("/{$match}/", $replace, $data);
2538
	} else if (is_array($data)) {
2539
		foreach ($data as $k => $v) {
2540
			$data[$k] = array_replace_values_recursive($v, $match, $replace);
2541
		}
2542
	}
2543

    
2544
	return $data;
2545
}
2546

    
2547
/*
2548
	This function was borrowed from a comment on PHP.net at the following URL:
2549
	http://www.php.net/manual/en/function.array-merge-recursive.php#73843
2550
 */
2551
function array_merge_recursive_unique($array0, $array1) {
2552

    
2553
	$arrays = func_get_args();
2554
	$remains = $arrays;
2555

    
2556
	// We walk through each arrays and put value in the results (without
2557
	// considering previous value).
2558
	$result = array();
2559

    
2560
	// loop available array
2561
	foreach ($arrays as $array) {
2562

    
2563
		// The first remaining array is $array. We are processing it. So
2564
		// we remove it from remaining arrays.
2565
		array_shift($remains);
2566

    
2567
		// We don't care non array param, like array_merge since PHP 5.0.
2568
		if (is_array($array)) {
2569
			// Loop values
2570
			foreach ($array as $key => $value) {
2571
				if (is_array($value)) {
2572
					// we gather all remaining arrays that have such key available
2573
					$args = array();
2574
					foreach ($remains as $remain) {
2575
						if (array_key_exists($key, $remain)) {
2576
							array_push($args, $remain[$key]);
2577
						}
2578
					}
2579

    
2580
					if (count($args) > 2) {
2581
						// put the recursion
2582
						$result[$key] = call_user_func_array(__FUNCTION__, $args);
2583
					} else {
2584
						foreach ($value as $vkey => $vval) {
2585
							if (!is_array($result[$key])) {
2586
								$result[$key] = array();
2587
							}
2588
							$result[$key][$vkey] = $vval;
2589
						}
2590
					}
2591
				} else {
2592
					// simply put the value
2593
					$result[$key] = $value;
2594
				}
2595
			}
2596
		}
2597
	}
2598
	return $result;
2599
}
2600

    
2601

    
2602
/*
2603
 * converts a string like "a,b,c,d"
2604
 * into an array like array("a" => "b", "c" => "d")
2605
 */
2606
function explode_assoc($delimiter, $string) {
2607
	$array = explode($delimiter, $string);
2608
	$result = array();
2609
	$numkeys = floor(count($array) / 2);
2610
	for ($i = 0; $i < $numkeys; $i += 1) {
2611
		$result[$array[$i * 2]] = $array[$i * 2 + 1];
2612
	}
2613
	return $result;
2614
}
2615

    
2616
/*
2617
 * Given a string of text with some delimiter, look for occurrences
2618
 * of some string and replace all of those.
2619
 * $text - the text string (e.g. "abc,defg,x123,ipv4,xyz")
2620
 * $delimiter - the delimiter (e.g. ",")
2621
 * $element - the element to match (e.g. "defg")
2622
 * $replacement - the string to replace it with (e.g. "42")
2623
 * Returns the resulting delimited string (e.g. "abc,42,x123,ipv4,xyz")
2624
 */
2625
function replace_element_in_list($text, $delimiter, $element, $replacement) {
2626
	$textArray = explode($delimiter, $text);
2627
	while (($entry = array_search($element, $textArray)) !== false) {
2628
		$textArray[$entry] = $replacement;
2629
	}
2630
	return implode(',', $textArray);
2631
}
2632

    
2633
/* Return system's route table */
2634
function route_table() {
2635
	$_gb = exec("/usr/bin/netstat --libxo json -nWr", $rawdata, $rc);
2636

    
2637
	if ($rc != 0) {
2638
		return array();
2639
	}
2640

    
2641
	$netstatarr = json_decode(implode(" ", $rawdata), JSON_OBJECT_AS_ARRAY);
2642
	$netstatarr = $netstatarr['statistics']['route-information']
2643
	    ['route-table']['rt-family'];
2644

    
2645
	$result = array();
2646
	$result['inet'] = array();
2647
	$result['inet6'] = array();
2648
	foreach ($netstatarr as $item) {
2649
		if ($item['address-family'] == 'Internet') {
2650
			$result['inet'] = $item['rt-entry'];
2651
		} else if ($item['address-family'] == 'Internet6') {
2652
			$result['inet6'] = $item['rt-entry'];
2653
		}
2654
	}
2655
	unset($netstatarr);
2656

    
2657
	return $result;
2658
}
2659

    
2660
/* Get static route for specific destination */
2661
function route_get($target, $ipprotocol = '', $useroute = false) {
2662
	global $config;
2663

    
2664
	if (!empty($ipprotocol)) {
2665
		$family = $ipprotocol;
2666
	} else if (is_v4($target)) {
2667
		$family = 'inet';
2668
	} else if (is_v6($target)) {
2669
		$family = 'inet6';
2670
	}
2671

    
2672
	if (empty($family)) {
2673
		return array();
2674
	}
2675

    
2676
	if ($useroute) {
2677
		if ($family == 'inet') {
2678
			$inet = '4';
2679
		} else {
2680
			$inet = '6';
2681
		}
2682
		$interface = exec("/sbin/route -n{$inet} get {$target} 2>/dev/null | /usr/bin/awk '/interface:/{print $2}'");
2683
		if (empty($interface)) {
2684
			return array();
2685
		} elseif ($interface == 'lo0') {
2686
			// interface assigned IP address
2687
			foreach ($config['interfaces'] as $intf => $infconf) {
2688
				if ((($inet == '4') && (get_interface_ip($intf) == $target)) ||
2689
				    (($inet == '6') && (get_interface_ipv6($intf) == $target))) {
2690
					$interface = convert_friendly_interface_to_real_interface_name($intf);
2691
					$gateway = $interface;
2692
					break;
2693
				}
2694
			}
2695
		} else {
2696
			$gateway = exec("/sbin/route -n{$inet} get {$target} 2>/dev/null | /usr/bin/awk '/gateway:/{print $2}'");
2697
			if (!$gateway) {
2698
				// non-local gateway
2699
				$gateway = get_interface_mac($interface);
2700
			}
2701
		}
2702
		$result[] = array('gateway' => $gateway, 'interface-name' => $interface);
2703
	} else {
2704
		$rtable = route_table();
2705
		if (empty($rtable)) {
2706
			return array();
2707
		}
2708

    
2709
		$result = array();
2710
		foreach ($rtable[$family] as $item) {
2711
			if ($item['destination'] == $target ||
2712
			    ip_in_subnet($target, $item['destination'])) {
2713
				$result[] = $item;
2714
			}
2715
		}
2716
	}
2717

    
2718
	return $result;
2719
}
2720

    
2721
/* Get default route */
2722
function route_get_default($ipprotocol) {
2723
	if (empty($ipprotocol) || ($ipprotocol != 'inet' &&
2724
	    $ipprotocol != 'inet6')) {
2725
		return '';
2726
	}
2727

    
2728
	$route = route_get('default', $ipprotocol, true);
2729

    
2730
	if (empty($route)) {
2731
		return '';
2732
	}
2733

    
2734
	if (!isset($route[0]['gateway'])) {
2735
		return '';
2736
	}
2737

    
2738
	return $route[0]['gateway'];
2739
}
2740

    
2741
/* Delete a static route */
2742
function route_del($target, $ipprotocol = '') {
2743
	global $config;
2744

    
2745
	if (empty($target)) {
2746
		return;
2747
	}
2748

    
2749
	if (!empty($ipprotocol) && $ipprotocol != 'inet' &&
2750
	    $ipprotocol != 'inet6') {
2751
		return false;
2752
	}
2753

    
2754
	$route = route_get($target, $ipprotocol, true);
2755

    
2756
	if (empty($route)) {
2757
		return;
2758
	}
2759

    
2760
	$target_prefix = '';
2761
	if (is_subnet($target)) {
2762
		$target_prefix = '-net';
2763
	} else if (is_ipaddr($target)) {
2764
		$target_prefix = '-host';
2765
	}
2766

    
2767
	if (!empty($ipprotocol)) {
2768
		$target_prefix .= " -{$ipprotocol}";
2769
	} else if (is_v6($target)) {
2770
		$target_prefix .= ' -inet6';
2771
	} else if (is_v4($target)) {
2772
		$target_prefix .= ' -inet';
2773
	}
2774

    
2775
	foreach ($route as $item) {
2776
		if (substr($item['gateway'], 0, 5) == 'link#') {
2777
			continue;
2778
		}
2779

    
2780
		if (is_macaddr($item['gateway'])) {
2781
			$gw = '-iface ' . $item['interface-name'];
2782
		} else {
2783
			$gw = $item['gateway'];
2784
		}
2785

    
2786
		$_gb = exec(escapeshellcmd("/sbin/route del {$target_prefix} " .
2787
		    "{$target} {$gw}"), $output, $rc);
2788

    
2789
		if (isset($config['system']['route-debug'])) {
2790
			log_error("ROUTING debug: " . microtime() .
2791
			    " - DEL RC={$rc} - {$target} - gw: " . $gw);
2792
			file_put_contents("/dev/console", "\n[" . getmypid() .
2793
			    "] ROUTE DEL: {$target_prefix} {$target} {$gw} " .
2794
			    "result: {$rc}");
2795
		}
2796
	}
2797
}
2798

    
2799
/*
2800
 * Add static route.  If it already exists, remove it and re-add
2801
 *
2802
 * $target - IP, subnet or 'default'
2803
 * $gw     - gateway address
2804
 * $iface  - Network interface
2805
 * $args   - Extra arguments for /sbin/route
2806
 * $ipprotocol - 'inet' or 'inet6'.  Mandatory when $target == 'default'
2807
 *
2808
 */
2809
function route_add_or_change($target, $gw, $iface = '', $args = '',
2810
    $ipprotocol = '') {
2811
	global $config;
2812

    
2813
	if (empty($target) || (empty($gw) && empty($iface))) {
2814
		return false;
2815
	}
2816

    
2817
	if ($target == 'default' && empty($ipprotocol)) {
2818
		return false;
2819
	}
2820

    
2821
	if (!empty($ipprotocol) && $ipprotocol != 'inet' &&
2822
	    $ipprotocol != 'inet6') {
2823
		return false;
2824
	}
2825

    
2826
	/* use '-host' for IPv6 /128 routes, see https://redmine.pfsense.org/issues/11594 */
2827
	if (is_subnetv4($target) || (is_subnetv6($target) && (subnet_size($target) > 1))) {
2828
		$target_prefix = '-net';
2829
	} else if (is_ipaddr($target)) {
2830
		$target_prefix = '-host';
2831
	}
2832

    
2833
	if (!empty($ipprotocol)) {
2834
		$target_prefix .= " -{$ipprotocol}";
2835
	} else if (is_v6($target)) {
2836
		$target_prefix .= ' -inet6';
2837
	} else if (is_v4($target)) {
2838
		$target_prefix .= ' -inet';
2839
	}
2840

    
2841
	/* If there is another route to the same target, remove it */
2842
	route_del($target, $ipprotocol);
2843

    
2844
	$params = '';
2845
	if (!empty($iface) && does_interface_exist($iface)) {
2846
		$params .= " -iface {$iface}";
2847
	}
2848
	if (is_ipaddr($gw)) {
2849
		/* set correct linklocal gateway address,
2850
		 * see https://redmine.pfsense.org/issues/11713 */
2851
		if (is_linklocal($gw) && empty(get_ll_scope($gw))) {
2852
			$routeget = route_get($gw, 'inet6', true);
2853
			$gw .= "%" . $routeget[0]['interface-name'];
2854
		}
2855
		$params .= " " . $gw;
2856
	}
2857

    
2858
	if (empty($params)) {
2859
		log_error("route_add_or_change: Invalid gateway {$gw} and/or " .
2860
		    "network interface {$iface}");
2861
		return false;
2862
	}
2863

    
2864
	$_gb = exec(escapeshellcmd("/sbin/route add {$target_prefix} " .
2865
	    "{$target} {$args} {$params}"), $output, $rc);
2866

    
2867
	if (isset($config['system']['route-debug'])) {
2868
		log_error("ROUTING debug: " . microtime() .
2869
		    " - ADD RC={$rc} - {$target} {$args}");
2870
		file_put_contents("/dev/console", "\n[" . getmypid() .
2871
		    "] ROUTE ADD: {$target_prefix} {$target} {$args} " .
2872
		    "{$params} result: {$rc}");
2873
	}
2874

    
2875
	return ($rc == 0);
2876
}
2877

    
2878
function set_ipv6routes_mtu($interface, $mtu) {
2879
	global $config, $g;
2880

    
2881
	$ipv6mturoutes = array();
2882
	$if = convert_real_interface_to_friendly_interface_name($interface);
2883
	if (!$config['interfaces'][$if]['ipaddrv6']) {
2884
		return;
2885
	}
2886
	$a_gateways = return_gateways_array();
2887
	$a_staticroutes = get_staticroutes(false, false, true);
2888
	foreach ($a_gateways as $gate) {
2889
		foreach ($a_staticroutes as $sroute) {
2890
			if ($gate['interface'] == $interface &&
2891
			    $sroute['gateway'] == $gate['name']) {
2892
				$tgt = $sroute['network'];
2893
				$gateway = $gate['gateway'];
2894
				$ipv6mturoutes[$tgt] = $gateway;
2895
			}
2896
		}
2897
		if ($gate['interface'] == $interface &&
2898
		    $gate['isdefaultgw']) {
2899
			$tgt = "default";
2900
			$gateway = $gate['gateway'];
2901
			$ipv6mturoutes[$tgt] = $gateway;
2902
		}
2903
	}
2904
	foreach ($ipv6mturoutes as $tgt => $gateway) {
2905
		mwexec("/sbin/route change -6 -mtu " . escapeshellarg($mtu) .
2906
		    " " . escapeshellarg($tgt) . " " .
2907
		    escapeshellarg($gateway));
2908
	}
2909
}
2910

    
2911
function alias_to_subnets_recursive($name, $returnhostnames = false) {
2912
	global $aliastable;
2913
	$result = array();
2914
	if (!isset($aliastable[$name])) {
2915
		return $result;
2916
	}
2917
	$subnets = preg_split('/\s+/', $aliastable[$name]);
2918
	foreach ($subnets as $net) {
2919
		if (is_alias($net)) {
2920
			$sub = alias_to_subnets_recursive($net, $returnhostnames);
2921
			$result = array_merge($result, $sub);
2922
			continue;
2923
		} elseif (!is_subnet($net)) {
2924
			if (is_ipaddrv4($net)) {
2925
				$net .= "/32";
2926
			} else if (is_ipaddrv6($net)) {
2927
				$net .= "/128";
2928
			} else if ($returnhostnames === false || !is_fqdn($net)) {
2929
				continue;
2930
			}
2931
		}
2932
		$result[] = $net;
2933
	}
2934
	return $result;
2935
}
2936

    
2937
function get_staticroutes($returnsubnetsonly = false, $returnhostnames = false, $returnenabledroutesonly = false) {
2938
	global $config, $aliastable;
2939

    
2940
	/* Bail if there are no routes, but return an array always so callers don't have to check. */
2941
	init_config_arr(array('staticroutes', 'route'));
2942
	if (empty($config['staticroutes']['route'])) {
2943
		return array();
2944
	}
2945

    
2946
	$allstaticroutes = array();
2947
	$allsubnets = array();
2948
	/* Loop through routes and expand aliases as we find them. */
2949
	foreach ($config['staticroutes']['route'] as $route) {
2950
		if ($returnenabledroutesonly && isset($route['disabled'])) {
2951
			continue;
2952
		}
2953

    
2954
		if (is_alias($route['network'])) {
2955
			foreach (alias_to_subnets_recursive($route['network'], $returnhostnames) as $net) {
2956
				$temproute = $route;
2957
				$temproute['network'] = $net;
2958
				$allstaticroutes[] = $temproute;
2959
				$allsubnets[] = $net;
2960
			}
2961
		} elseif (is_subnet($route['network'])) {
2962
			$allstaticroutes[] = $route;
2963
			$allsubnets[] = $route['network'];
2964
		}
2965
	}
2966
	if ($returnsubnetsonly) {
2967
		return $allsubnets;
2968
	} else {
2969
		return $allstaticroutes;
2970
	}
2971
}
2972

    
2973
/****f* util/get_alias_list
2974
 * NAME
2975
 *   get_alias_list - Provide a list of aliases.
2976
 * INPUTS
2977
 *   $type          - Optional, can be a string or array specifying what type(s) of aliases you need.
2978
 * RESULT
2979
 *   Array containing list of aliases.
2980
 *   If $type is unspecified, all aliases are returned.
2981
 *   If $type is a string, all aliases of the type specified in $type are returned.
2982
 *   If $type is an array, all aliases of any type specified in any element of $type are returned.
2983
 */
2984
function get_alias_list($type = null) {
2985
	global $config;
2986
	$result = array();
2987
	if ($config['aliases']['alias'] <> "" && is_array($config['aliases']['alias'])) {
2988
		foreach ($config['aliases']['alias'] as $alias) {
2989
			if ($type === null) {
2990
				$result[] = $alias['name'];
2991
			} else if (is_array($type)) {
2992
				if (in_array($alias['type'], $type)) {
2993
					$result[] = $alias['name'];
2994
				}
2995
			} else if ($type === $alias['type']) {
2996
				$result[] = $alias['name'];
2997
			}
2998
		}
2999
	}
3000
	return $result;
3001
}
3002

    
3003
/* returns an array consisting of every element of $haystack that is not equal to $needle. */
3004
function array_exclude($needle, $haystack) {
3005
	$result = array();
3006
	if (is_array($haystack)) {
3007
		foreach ($haystack as $thing) {
3008
			if ($needle !== $thing) {
3009
				$result[] = $thing;
3010
			}
3011
		}
3012
	}
3013
	return $result;
3014
}
3015

    
3016
/* Define what is preferred, IPv4 or IPv6 */
3017
function prefer_ipv4_or_ipv6() {
3018
	global $config;
3019

    
3020
	if (isset($config['system']['prefer_ipv4'])) {
3021
		mwexec("/etc/rc.d/ip6addrctl prefer_ipv4");
3022
	} else {
3023
		mwexec("/etc/rc.d/ip6addrctl prefer_ipv6");
3024
	}
3025
}
3026

    
3027
/* Redirect to page passing parameters via POST */
3028
function post_redirect($page, $params) {
3029
	if (!is_array($params)) {
3030
		return;
3031
	}
3032

    
3033
	print "<html><body><form action=\"{$page}\" name=\"formredir\" method=\"post\">\n";
3034
	foreach ($params as $key => $value) {
3035
		print "<input type=\"hidden\" name=\"{$key}\" value=\"{$value}\" />\n";
3036
	}
3037
	print "</form>\n";
3038
	print "<script type=\"text/javascript\">\n";
3039
	print "//<![CDATA[\n";
3040
	print "document.formredir.submit();\n";
3041
	print "//]]>\n";
3042
	print "</script>\n";
3043
	print "</body></html>\n";
3044
}
3045

    
3046
/* Locate disks that can be queried for S.M.A.R.T. data. */
3047
function get_smart_drive_list() {
3048
	/* SMART supports some disks directly, and some controllers directly,
3049
	 * See https://redmine.pfsense.org/issues/9042 */
3050
	$supported_disk_types = array("ad", "da", "ada");
3051
	$supported_controller_types = array("nvme");
3052
	$disk_list = explode(" ", get_single_sysctl("kern.disks"));
3053
	foreach ($disk_list as $id => $disk) {
3054
		// We only want certain kinds of disks for S.M.A.R.T.
3055
		// 1 is a match, 0 is no match, False is any problem processing the regex
3056
		if (preg_match("/^(" . implode("|", $supported_disk_types) . ").*[0-9]{1,2}$/", $disk) !== 1) {
3057
			unset($disk_list[$id]);
3058
			continue;
3059
		}
3060
	}
3061
	foreach ($supported_controller_types as $controller) {
3062
		$devices = glob("/dev/{$controller}*");
3063
		if (!is_array($devices)) {
3064
			continue;
3065
		}
3066
		foreach ($devices as $device) {
3067
			$disk_list[] = basename($device);
3068
		}
3069
	}
3070
	sort($disk_list);
3071
	return $disk_list;
3072
}
3073

    
3074
// Validate a network address
3075
//	$addr: the address to validate
3076
//	$type: IPV4|IPV6|IPV4V6
3077
//	$label: the label used by the GUI to display this value. Required to compose an error message
3078
//	$err_msg: pointer to the callers error message array so that error messages can be added to it here
3079
//	$alias: are aliases permitted for this address?
3080
// Returns:
3081
//	IPV4 - if $addr is a valid IPv4 address
3082
//	IPV6 - if $addr is a valid IPv6 address
3083
//	ALIAS - if $alias=true and $addr is an alias
3084
//	false - otherwise
3085

    
3086
function validateipaddr(&$addr, $type, $label, &$err_msg, $alias=false) {
3087
	switch ($type) {
3088
		case IPV4:
3089
			if (is_ipaddrv4($addr)) {
3090
				return IPV4;
3091
			} else if ($alias) {
3092
				if (is_alias($addr)) {
3093
					return ALIAS;
3094
				} else {
3095
					$err_msg[] = sprintf(gettext("%s must be a valid IPv4 address or alias."), $label);
3096
					return false;
3097
				}
3098
			} else {
3099
				$err_msg[] = sprintf(gettext("%s must be a valid IPv4 address."), $label);
3100
				return false;
3101
			}
3102
		break;
3103
		case IPV6:
3104
			if (is_ipaddrv6($addr)) {
3105
				$addr = strtolower($addr);
3106
				return IPV6;
3107
			} else if ($alias) {
3108
				if (is_alias($addr)) {
3109
					return ALIAS;
3110
				} else {
3111
					$err_msg[] = sprintf(gettext("%s must be a valid IPv6 address or alias."), $label);
3112
					return false;
3113
				}
3114
			} else {
3115
				$err_msg[] = sprintf(gettext("%s must be a valid IPv6 address."), $label);
3116
				return false;
3117
			}
3118
		break;
3119
		case IPV4V6:
3120
			if (is_ipaddrv6($addr)) {
3121
				$addr = strtolower($addr);
3122
				return IPV6;
3123
			} else if (is_ipaddrv4($addr)) {
3124
				return IPV4;
3125
			} else if ($alias) {
3126
				if (is_alias($addr)) {
3127
					return ALIAS;
3128
				} else {
3129
					$err_msg[] = sprintf(gettext("%s must be a valid IPv4 or IPv6 address or alias."), $label);
3130
					return false;
3131
				}
3132
			} else {
3133
				$err_msg[] = sprintf(gettext("%s must be a valid IPv4 or IPv6 address."), $label);
3134
				return false;
3135
			}
3136
		break;
3137
	}
3138

    
3139
	return false;
3140
}
3141

    
3142
/* From DUID configuration inputs, format a string that looks (more) like the expected raw DUID format:
3143
 * 1) For DUIDs entered as a known DUID type, convert to a hexstring and prepend the DUID number, after having done the following:
3144
 *     a) For DUID-LLT and DUID-EN, convert the time/enterprise ID input to hex and append the link-layer address/identifier input.
3145
 *     b) For DUID-LLT and DUID-LL, prepend a hardware type of 1.
3146
 *     c) For DUID-UUID, remove any "-".
3147
 * 2) Replace any remaining "-" with ":".
3148
 * 3) If any components are input with just a single char (hex digit hopefully), put a "0" in front.
3149
 * 4) The first two components should be a 16-bit integer (little- or big-endian, depending on the current machine type) that
3150
 *    is equal to the number of other components. If not, prepend this as "nn:00" (all pfSense builds are little-endian).
3151
 *    This is convenience, because the DUID reported by dhcp6c in logs does not include this count, which corresponds to the
3152
 *    option-len field of DHCPv6's OPTION_CLIENTID option.
3153
 *
3154
 * The final result should be closer to:
3155
 *
3156
 * "nn:00:00:0n:nn:nn:nn:..."
3157
 *
3158
 * This function does not validate the input. is_duid() will do validation.
3159
 */
3160
function format_duid($duidtype, $duidpt1, $duidpt2=null) {
3161
	if ($duidpt2)
3162
		$duidpt1 = implode(':', str_split(str_pad(dechex($duidpt1), 8, '0', STR_PAD_LEFT), 2)) . ':' . $duidpt2;
3163

    
3164
	/* Make hexstrings */
3165
	if ($duidtype) {
3166
		switch ($duidtype) {
3167
		/* Add a hardware type to DUID-LLT and DUID-LL; assume Ethernet */
3168
		case 1:
3169
		case 3:
3170
			$duidpt1 = '00:01:' . $duidpt1;
3171
			break;
3172
		/* Remove '-' from given UUID and insert ':' every 2 characters */
3173
		case 4:
3174
			$duidpt1 = implode(':', str_split(str_replace('-', '', $duidpt1), 2));
3175
			break;
3176
		default:
3177
		}
3178
		$duidpt1 = '00:0' . $duidtype . ':' . $duidpt1;
3179
	}
3180

    
3181
	$values = explode(':', strtolower(str_replace('-', ':', $duidpt1)));
3182

    
3183
	if (hexdec($values[0]) != count($values) - 2)
3184
		array_unshift($values, dechex(count($values)), '00');
3185

    
3186
	array_walk($values, function(&$value) {
3187
		$value = str_pad($value, 2, '0', STR_PAD_LEFT);
3188
	});
3189

    
3190
	return implode(":", $values);
3191
}
3192

    
3193
/* Returns true if $dhcp6duid is a valid DUID entry.
3194
 * Parse the entry to check for valid length according to known DUID types.
3195
 */
3196
function is_duid($dhcp6duid) {
3197
	$values = explode(":", $dhcp6duid);
3198
	if (hexdec($values[0]) == count($values) - 2) {
3199
		switch (hexdec($values[2] . $values[3])) {
3200
		case 0:
3201
			return false;
3202
			break;
3203
		case 1:
3204
			if (count($values) != 16 || strlen($dhcp6duid) != 47)
3205
				return false;
3206
			break;
3207
		case 3:
3208
			if (count($values) != 12 || strlen($dhcp6duid) != 35)
3209
				return false;
3210
			break;
3211
		case 4:
3212
			if (count($values) != 20 || strlen($dhcp6duid) != 59)
3213
				return false;
3214
			break;
3215
		/* DUID is up to 128 octets; allow 2 octets for type code, 2 more for option-len */
3216
		default:
3217
			if (count($values) > 132 || strlen($dhcp6duid) != count($values) * 3 - 1)
3218
				return false;
3219
		}
3220
	} else
3221
		return false;
3222

    
3223
	for ($i = 0; $i < count($values); $i++) {
3224
		if (ctype_xdigit($values[$i]) == false)
3225
			return false;
3226
		if (hexdec($values[$i]) < 0 || hexdec($values[$i]) > 255)
3227
			return false;
3228
	}
3229

    
3230
	return true;
3231
}
3232

    
3233
/* Write the DHCP6 DUID file */
3234
function write_dhcp6_duid($duidstring) {
3235
	// Create the hex array from the dhcp6duid config entry and write to file
3236
	global $g;
3237

    
3238
	if(!is_duid($duidstring)) {
3239
		log_error(gettext("Error: attempting to write DUID file - Invalid DUID detected"));
3240
		return false;
3241
	}
3242
	$temp = str_replace(":","",$duidstring);
3243
	$duid_binstring = pack("H*",$temp);
3244
	if ($fd = fopen("{$g['vardb_path']}/dhcp6c_duid", "wb")) {
3245
		fwrite($fd, $duid_binstring);
3246
		fclose($fd);
3247
		return true;
3248
	}
3249
	log_error(gettext("Error: attempting to write DUID file - File write error"));
3250
	return false;
3251
}
3252

    
3253
/* returns duid string from 'vardb_path']}/dhcp6c_duid' */
3254
function get_duid_from_file() {
3255
	global $g;
3256

    
3257
	$duid_ASCII = "";
3258
	$count = 0;
3259

    
3260
	if (file_exists("{$g['vardb_path']}/dhcp6c_duid") &&
3261
	    ($fd = fopen("{$g['vardb_path']}/dhcp6c_duid", "r"))) {
3262
		$fsize = filesize("{$g['vardb_path']}/dhcp6c_duid");
3263
		if ($fsize <= 132) {
3264
			$buffer = fread($fd, $fsize);
3265
			while($count < $fsize) {
3266
				$duid_ASCII .= bin2hex($buffer[$count]);
3267
				$count++;
3268
				if($count < $fsize) {
3269
					$duid_ASCII .= ":";
3270
				}
3271
			}
3272
		}
3273
		fclose($fd);
3274
	}
3275
	//if no file or error with read then the string returns blanked DUID string
3276
	if(!is_duid($duid_ASCII)) {
3277
		return "--:--:--:--:--:--:--:--:--:--:--:--:--:--:--:--";
3278
	}
3279
	return($duid_ASCII);
3280
}
3281

    
3282
/* Replaces the Mac OS 9 and earlier (\r) and DOS/Windows (\r\n) newlines with the Unix equivalent (\n). */
3283
function unixnewlines($text) {
3284
	return preg_replace('/\r\n?/', "\n", $text);
3285
}
3286

    
3287
function array_remove_duplicate($array, $field) {
3288
	foreach ($array as $sub) {
3289
		if (isset($sub[$field])) {
3290
			$cmp[] = $sub[$field];
3291
		}
3292
	}
3293
	$unique = array_unique(array_reverse($cmp, true));
3294
	foreach ($unique as $k => $rien) {
3295
		$new[] = $array[$k];
3296
	}
3297
	return $new;
3298
}
3299

    
3300
function dhcpd_date_adjust_gmt($dt) {
3301
	global $config;
3302

    
3303
	init_config_arr(array('dhcpd'));
3304

    
3305
	foreach ($config['dhcpd'] as $dhcpditem) {
3306
		if ($dhcpditem['dhcpleaseinlocaltime'] == "yes") {
3307
			$ts = strtotime($dt . " GMT");
3308
			if ($ts !== false) {
3309
				return strftime("%Y/%m/%d %H:%M:%S", $ts);
3310
			}
3311
		}
3312
	}
3313

    
3314
	/*
3315
	 * If we did not need to convert to local time or the conversion
3316
	 * failed, just return the input.
3317
	 */
3318
	return $dt;
3319
}
3320

    
3321
global $supported_image_types;
3322
$supported_image_types = array(
3323
	IMAGETYPE_JPEG,
3324
	IMAGETYPE_PNG,
3325
	IMAGETYPE_GIF,
3326
	IMAGETYPE_WEBP
3327
);
3328

    
3329
function is_supported_image($image_filename) {
3330
	global $supported_image_types;
3331
	$img_info = getimagesize($image_filename);
3332

    
3333
	/* If it's not an image, or it isn't in the supported list, return false */
3334
	if (($img_info === false) ||
3335
	    !in_array($img_info[2], array_keys($supported_image_types))) {
3336
		return false;
3337
	} else {
3338
		return $img_info[2];
3339
	}
3340
}
3341

    
3342
function get_lagg_ports ($laggport) {
3343
	$laggp = array();
3344
	foreach ($laggport as $lgp) {
3345
		list($lpname, $lpinfo) = explode(" ", $lgp);
3346
		preg_match('~<(.+)>~', $lpinfo, $lgportmode);
3347
		if ($lgportmode[1]) {
3348
			$laggp[] = $lpname . " (" . $lgportmode[1] . ")";
3349
		} else {
3350
			$laggp[] = $lpname;
3351
		}
3352
	}
3353
	if ($laggp) {
3354
		return implode(", ", $laggp);
3355
	} else {
3356
		return false;
3357
	}
3358
}
3359

    
3360
function cisco_to_cidr($addr) {
3361
	if (!is_ipaddr($addr)) {
3362
		throw new Exception('Invalid IP Addr');
3363
	}
3364

    
3365
	$mask = decbin(~ip2long($addr));
3366
	$mask = substr($mask, -32);
3367
	$k = 0;
3368
	for ($i = 0; $i <= 32; $i++) {
3369
		$k += intval($mask[$i]);
3370
	}
3371
	return $k;
3372
}
3373

    
3374
function cisco_extract_index($prule) {
3375
	$index = explode("#", $prule);
3376
	if (is_numeric($index[1])) {
3377
		return intval($index[1]);
3378
	} else {
3379
		syslog(LOG_WARNING, "Error parsing rule {$prule}: Could not extract index");
3380
	}
3381
	return -1;;
3382
}
3383

    
3384
function parse_cisco_acl_rule($rule, $devname, $dir, $proto) {
3385
	$rule_orig = $rule;
3386
	$rule = explode(" ", $rule);
3387
	$tmprule = "";
3388
	$index = 0;
3389

    
3390
	if ($rule[$index] == "permit") {
3391
		$startrule = "pass {$dir} quick on {$devname} ";
3392
	} else if ($rule[$index] == "deny") {
3393
		$startrule = "block {$dir} quick on {$devname} ";
3394
	} else {
3395
		return;
3396
	}
3397

    
3398
	$index++;
3399

    
3400
	switch ($rule[$index]) {
3401
		case "ip":
3402
			break;
3403
		case "icmp":
3404
			$icmp = ($proto == "inet") ? "icmp" : "ipv6-icmp";
3405
			$tmprule .= "proto {$icmp} ";
3406
			break;
3407
		case "tcp":
3408
		case "udp":
3409
			$tmprule .= "proto {$rule[$index]} ";
3410
			break;
3411
		default:
3412
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid protocol.");
3413
			return;
3414
	}
3415
	$index++;
3416

    
3417
	/* Source */
3418
	if (trim($rule[$index]) == "host") {
3419
		$index++;
3420
		if ((is_ipaddrv4(trim($rule[$index])) && ($proto == "inet")) ||
3421
		    (is_ipaddrv6(trim($rule[$index])) && ($proto == "inet6")) ||
3422
		    (trim($rule[$index]) == "{clientip}")) {
3423
			$tmprule .= "from {$rule[$index]} ";
3424
			$index++;
3425
		} else {
3426
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source host '{$rule[$index]}'.");
3427
			return;
3428
		}
3429
	} elseif (is_subnetv6(trim($rule[$index])) && ($proto == "inet6")) {
3430
		$tmprule .= "from {$rule[$index]} ";
3431
		$index++;
3432
	} elseif (trim($rule[$index]) == "any") {
3433
		$tmprule .= "from any ";
3434
		$index++;
3435
	} else {
3436
		$network = $rule[$index];
3437
		$netmask = $rule[++$index];
3438

    
3439
		if (is_ipaddrv4($network) && ($proto == "inet")) {
3440
			try {
3441
				$netmask = cisco_to_cidr($netmask);
3442
			} catch(Exception $e) {
3443
				syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source netmask '$netmask'.");
3444
				return;
3445
			}
3446
			$tmprule .= "from {$network}/{$netmask} ";
3447
		} else {
3448
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source network '$network'.");
3449
			return;
3450
		}
3451

    
3452
		$index++;
3453
	}
3454

    
3455
	/* Source Operator */
3456
	if (in_array(trim($rule[$index]), array("lt", "gt", "eq", "neq"))) {
3457
		switch(trim($rule[$index])) {
3458
			case "lt":
3459
				$operator = "<";
3460
				break;
3461
			case "gt":
3462
				$operator = ">";
3463
				break;
3464
			case "eq":
3465
				$operator = "=";
3466
				break;
3467
			case "neq":
3468
				$operator = "!=";
3469
				break;
3470
		}
3471

    
3472
		$port = $rule[++$index];
3473
		if (is_port($port)) {
3474
			$tmprule .= "port {$operator} {$port} ";
3475
		} else {
3476
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source port: '$port' not a numeric value between 0 and 65535.");
3477
			return;
3478
		}
3479
		$index++;
3480
	} else if (trim($rule[$index]) == "range") {
3481
		$port = array($rule[++$index], $rule[++$index]);
3482
		if (is_port($port[0]) && is_port($port[1])) {
3483
			$port[0]--;
3484
			$port[1]++;
3485
			$tmprule .= "port {$port[0]} >< {$port[1]} ";
3486
		} else {
3487
			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.");
3488
			return;
3489
		}
3490
		$index++;
3491
	}
3492

    
3493
	/* Destination */
3494
	if (trim($rule[$index]) == "host") {
3495
		$index++;
3496
		if ((is_ipaddrv4(trim($rule[$index])) && ($proto == "inet")) ||
3497
		    (is_ipaddrv6(trim($rule[$index])) && ($proto == "inet6")) ||
3498
		    (trim($rule[$index]) == "{clientip}")) {
3499
			$tmprule .= "to {$rule[$index]} ";
3500
			$index++;
3501
		} else {
3502
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination host '{$rule[$index]}'.");
3503
			return;
3504
		}
3505
	} elseif (is_subnetv6(trim($rule[$index])) && ($proto == "inet6")) {
3506
		$tmprule .= "to {$rule[$index]} ";
3507
		$index++;
3508
	} elseif (trim($rule[$index]) == "any") {
3509
		$tmprule .= "to any ";
3510
		$index++;
3511
	} else {
3512
		$network = $rule[$index];
3513
		$netmask = $rule[++$index];
3514

    
3515
		if (is_ipaddrv4($network) && ($proto == "inet")) {
3516
			try {
3517
				$netmask = cisco_to_cidr($netmask);
3518
			} catch(Exception $e) {
3519
				syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination network '$network'.");
3520
				return;
3521
			}
3522
			$tmprule .= "to {$network}/{$netmask} ";
3523
		} else {
3524
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination network '$network'.");
3525
			return;
3526
		}
3527

    
3528
		$index++;
3529
	}
3530

    
3531
	/* Destination Operator */
3532
	if (in_array(trim($rule[$index]), array("lt", "gt", "eq", "neq"))) {
3533
		switch(trim($rule[$index])) {
3534
			case "lt":
3535
				$operator = "<";
3536
				break;
3537
			case "gt":
3538
				$operator = ">";
3539
				break;
3540
			case "eq":
3541
				$operator = "=";
3542
				break;
3543
			case "neq":
3544
				$operator = "!=";
3545
				break;
3546
		}
3547

    
3548
		$port = $rule[++$index];
3549
		if (is_port($port)) {
3550
			$tmprule .= "port {$operator} {$port} ";
3551
		} else {
3552
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination port: '$port' not a numeric value between 0 and 65535.");
3553
			return;
3554
		}
3555
		$index++;
3556
	} else if (trim($rule[$index]) == "range") {
3557
		$port = array($rule[++$index], $rule[++$index]);
3558
		if (is_port($port[0]) && is_port($port[1])) {
3559
			$port[0]--;
3560
			$port[1]++;
3561
			$tmprule .= "port {$port[0]} >< {$port[1]} ";
3562
		} else {
3563
			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.");
3564
			return;
3565
		}
3566
		$index++;
3567
	}
3568

    
3569
	$tmprule = $startrule . $proto . " " . $tmprule;
3570
	return $tmprule;
3571
}
3572

    
3573
function parse_cisco_acl($attribs, $dev) {
3574
	global $attributes;
3575

    
3576
	if (!is_array($attribs)) {
3577
		return "";
3578
	}
3579
	$finalrules = "";
3580
	if (is_array($attribs['ciscoavpair'])) {
3581
		$inrules = array('inet' => array(), 'inet6' => array());
3582
		$outrules = array('inet' => array(), 'inet6' => array());
3583
		foreach ($attribs['ciscoavpair'] as $avrules) {
3584
			$rule = explode("=", $avrules);
3585
			$dir = "";
3586
			if (strstr($rule[0], "inacl")) {
3587
				$dir = "in";
3588
			} else if (strstr($rule[0], "outacl")) {
3589
				$dir = "out";
3590
			} else if (strstr($rule[0], "dns-servers")) {
3591
				$attributes['dns-servers'] = explode(" ", $rule[1]);
3592
				continue;
3593
			} else if (strstr($rule[0], "route")) {
3594
				if (!is_array($attributes['routes'])) {
3595
					$attributes['routes'] = array();
3596
				}
3597
				$attributes['routes'][] = $rule[1];
3598
				continue;
3599
			}
3600
			$rindex = cisco_extract_index($rule[0]);
3601
			if ($rindex < 0) {
3602
				continue;
3603
			}
3604

    
3605
			if (strstr($rule[0], "ipv6")) {
3606
				$proto = "inet6";
3607
			} else {
3608
				$proto = "inet";
3609
			}
3610

    
3611
			$tmprule = parse_cisco_acl_rule($rule[1], $dev, $dir, $proto);
3612

    
3613
			if ($dir == "in") {
3614
				$inrules[$proto][$rindex] = $tmprule;
3615
			} else if ($dir == "out") {
3616
				$outrules[$proto][$rindex] = $tmprule;
3617
			}
3618
		}
3619

    
3620

    
3621
		$state = "";
3622
		foreach (array('inet', 'inet6') as $ip) {
3623
			if (!empty($outrules[$ip])) {
3624
				$state = "no state";
3625
			}
3626
			ksort($inrules[$ip], SORT_NUMERIC);
3627
			foreach ($inrules[$ip] as $inrule) {
3628
				$finalrules .= "{$inrule} {$state}\n";
3629
			}
3630
			if (!empty($outrules[$ip])) {
3631
				ksort($outrules[$ip], SORT_NUMERIC);
3632
				foreach ($outrules[$ip] as $outrule) {
3633
					$finalrules .= "{$outrule} {$state}\n";
3634
				}
3635
			}
3636
		}
3637
	}
3638
	return $finalrules;
3639
}
3640

    
3641
function alias_idn_to_utf8($alias) {
3642
	if (is_alias($alias)) {
3643
		return $alias;
3644
	} else {
3645
		return idn_to_utf8($alias);
3646
	}
3647
}
3648

    
3649
function alias_idn_to_ascii($alias) {
3650
	if (is_alias($alias)) {
3651
		return $alias;
3652
	} else {
3653
		return idn_to_ascii($alias);
3654
	}
3655
}
3656

    
3657
?>
(53-53/61)