Project

General

Profile

Download (97.6 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
/*
3
 * util.inc
4
 *
5
 * part of pfSense (https://www.pfsense.org)
6
 * Copyright (c) 2004-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
/* returns true if $ipaddr is a valid dotted IPv4 address */
684
function is_ipaddrv4($ipaddr) {
685
	if (!is_string($ipaddr) || empty($ipaddr) || ip2long($ipaddr) === FALSE) {
686
		return false;
687
	}
688
	return true;
689
}
690

    
691
function is_mcast($ipaddr) {
692
	if (is_mcastv4($ipaddr)) {
693
		return 4;
694
	}
695
	if (is_mcastv6($ipaddr)) {
696
		return 6;
697
	}
698
	return false;
699
}
700

    
701
function is_mcastv4($ipaddr) {
702
	if (!is_ipaddrv4($ipaddr) ||
703
	    !preg_match('/^2(?:2[4-9]|3\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d?|0)){3}$/', $ipaddr)) {
704
		return false;
705
	}
706
	return true;
707
}
708

    
709
function is_mcastv6($ipaddr) {
710
	if (!is_ipaddrv6($ipaddr) || !preg_match('/^ff.+$/', $ipaddr)) {
711
		return false;
712
	}
713
	return true;
714
}
715

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

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

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

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

    
759
	if (!is_port(substr($ipport, $c + 1))) {
760
		return false;  // no valid port after last colon
761
	}
762

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

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

    
782
/* returns true if $ipaddr is a valid dotted IPv4 address or an alias thereof */
783
function is_ipaddroralias($ipaddr) {
784
	global $config;
785

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

    
799
}
800

    
801
/* returns true if $subnet is a valid IPv4 or IPv6 subnet in CIDR format
802
	false - if not a valid subnet
803
	true (numeric 4 or 6) - if valid, gives type of subnet */
804
function is_subnet($subnet) {
805
	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)) {
806
		if (is_ipaddrv4($parts[1]) && $parts[3] <= 32) {
807
			return 4;
808
		}
809
		if (is_ipaddrv6($parts[2]) && $parts[3] <= 128) {
810
			return 6;
811
		}
812
	}
813
	return false;
814
}
815

    
816
function is_v4($ip_or_subnet) {
817
	return is_ipaddrv4($ip_or_subnet) || is_subnetv4($ip_or_subnet);
818
}
819

    
820
function is_v6($ip_or_subnet) {
821
	return is_ipaddrv6($ip_or_subnet) || is_subnetv6($ip_or_subnet);
822
}
823

    
824
/* same as is_subnet() but accepts IPv4 only */
825
function is_subnetv4($subnet) {
826
	return (is_subnet($subnet) == 4);
827
}
828

    
829
/* same as is_subnet() but accepts IPv6 only */
830
function is_subnetv6($subnet) {
831
	return (is_subnet($subnet) == 6);
832
}
833

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

    
838
	if (isset($aliastable[$subnet]) && is_subnet($aliastable[$subnet])) {
839
		return true;
840
	} else {
841
		return is_subnet($subnet);
842
	}
843
}
844

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

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

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

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

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

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

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

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

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

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

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

    
944
	if (!is_ipaddrv6($subnet)) {
945
		return $result;
946
	}
947

    
948
	if (!is_numericint($bits) || $bits > 128) {
949
		return $result;
950
	}
951

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

    
961
	/* Get network prefix */
962
	$small_subnet = Net_IPv6::getNetmask($subnet, $bits);
963

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

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

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

    
987
	return $result;
988
}
989

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

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

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

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

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

    
1033
/* returns true if $domain is a valid domain name */
1034
function is_domain($domain, $allow_wildcard=false, $trailing_dot=true) {
1035
	if (!is_string($domain)) {
1036
		return false;
1037
	}
1038
	if (!$trailing_dot && ($domain[strlen($domain)-1] == ".")) {
1039
		return false;
1040
	}
1041
	if ($allow_wildcard) {
1042
		$domain_regex = '/^(?:(?:[a-z_0-9\*]|[a-z_0-9][a-z_0-9\-]*[a-z_0-9])\.)*(?:[a-z_0-9]|[a-z_0-9][a-z_0-9\-]*[a-z_0-9\.])$/i';
1043
	} else {
1044
		$domain_regex = '/^(?:(?:[a-z_0-9]|[a-z_0-9][a-z_0-9\-]*[a-z_0-9])\.)*(?:[a-z_0-9]|[a-z_0-9][a-z_0-9\-]*[a-z_0-9\.])$/i';
1045
	}
1046

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

    
1054
/* returns true if $macaddr is a valid MAC address */
1055
function is_macaddr($macaddr, $partial=false) {
1056
	$values = explode(":", $macaddr);
1057

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

    
1073
	return true;
1074
}
1075

    
1076
/*
1077
	If $return_message is true then
1078
		returns a text message about the reason that the name is invalid.
1079
		the text includes the type of "thing" that is being checked, passed in $object. (e.g. "alias", "gateway group", "schedule")
1080
	else
1081
		returns true if $name is a valid name for an alias
1082
		returns false if $name is not a valid name for an alias
1083

    
1084
	Aliases cannot be:
1085
		bad chars: anything except a-z 0-9 and underscore
1086
		bad names: empty string, pure numeric, pure underscore
1087
		reserved words: pre-defined service/protocol/port names which should not be ambiguous, and the words "port" and  "pass" */
1088

    
1089
function is_validaliasname($name, $return_message = false, $object = "alias") {
1090
	/* Array of reserved words */
1091
	$reserved = array("port", "pass");
1092

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

    
1128
/* returns a text message indicating if the alias name is valid, or the reason it is not valid. */
1129
function invalidaliasnamemsg($name, $object = "alias") {
1130
	return is_validaliasname($name, true, $object);
1131
}
1132

    
1133
/*
1134
 * returns true if $range is a valid integer range between $min and $max
1135
 * range delimiter can be ':' or '-'
1136
 */
1137
function is_intrange($range, $min, $max) {
1138
	$values = preg_split("/[:-]/", $range);
1139

    
1140
	if (!is_array($values) || count($values) != 2) {
1141
		return false;
1142
	}
1143

    
1144
	if (!ctype_digit($values[0]) || !ctype_digit($values[1])) {
1145
		return false;
1146
	}
1147

    
1148
	$values[0] = intval($values[0]);
1149
	$values[1] = intval($values[1]);
1150

    
1151
	if ($values[0] >= $values[1]) {
1152
		return false;
1153
	}
1154

    
1155
	if ($values[0] < $min || $values[1] > $max) {
1156
		return false;
1157
	}
1158

    
1159
	return true;
1160
}
1161

    
1162
/* returns true if $port is a valid TCP/UDP port */
1163
function is_port($port) {
1164
	if (ctype_digit($port) && ((intval($port) >= 1) && (intval($port) <= 65535))) {
1165
		return true;
1166
	}
1167
	if (getservbyname($port, "tcp") || getservbyname($port, "udp")) {
1168
		return true;
1169
	}
1170
	return false;
1171
}
1172

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

    
1181
		foreach($netstatarr as $index => $portstats){
1182
			array_push($port_info, $portstats['local']['port']);
1183
		}
1184
	}
1185

    
1186
	return in_array($port, $port_info);
1187
}
1188

    
1189
/* returns true if $portrange is a valid TCP/UDP portrange ("<port>:<port>") */
1190
function is_portrange($portrange) {
1191
	$ports = explode(":", $portrange);
1192

    
1193
	return (count($ports) == 2 && is_port($ports[0]) && is_port($ports[1]));
1194
}
1195

    
1196
/* returns true if $port is a valid TCP/UDP port number or range ("<port>:<port>") */
1197
function is_port_or_range($port) {
1198
	return (is_port($port) || is_portrange($port));
1199
}
1200

    
1201
/* returns true if $port is an alias that is a port type */
1202
function is_portalias($port) {
1203
	global $config;
1204

    
1205
	if (is_alias($port)) {
1206
		if (is_array($config['aliases']['alias'])) {
1207
			foreach ($config['aliases']['alias'] as $alias) {
1208
				if ($alias['name'] == $port && preg_match("/port/i", $alias['type'])) {
1209
					return true;
1210
				}
1211
			}
1212
		}
1213
	}
1214
	return false;
1215
}
1216

    
1217
/* returns true if $port is a valid port number or an alias thereof */
1218
function is_port_or_alias($port) {
1219
	return (is_port($port) || is_portalias($port));
1220
}
1221

    
1222
/* returns true if $port is a valid TCP/UDP port number or range ("<port>:<port>") or an alias thereof */
1223
function is_port_or_range_or_alias($port) {
1224
	return (is_port($port) || is_portrange($port) || is_portalias($port));
1225
}
1226

    
1227
/* create ranges of sequential port numbers (200:215) and remove duplicates */
1228
function group_ports($ports, $kflc = false) {
1229
	if (!is_array($ports) || empty($ports)) {
1230
		return;
1231
	}
1232

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

    
1258
	$result = array();
1259
	foreach ($uniq as $idx => $port) {
1260
		if ($idx == 0) {
1261
			$result[] = $port;
1262
			continue;
1263
		}
1264

    
1265
		$last = end($result);
1266
		if (is_portrange($last)) {
1267
			list($begin, $end) = explode(":", $last);
1268
		} else {
1269
			$begin = $end = $last;
1270
		}
1271

    
1272
		if ($port == ($end+1)) {
1273
			$end++;
1274
			$result[count($result)-1] = "{$begin}:{$end}";
1275
		} else {
1276
			$result[] = $port;
1277
		}
1278
	}
1279

    
1280
	return array_merge($comments, $result);
1281
}
1282

    
1283
/* returns true if $val is a valid shaper bandwidth value */
1284
function is_valid_shaperbw($val) {
1285
	return (preg_match("/^(\d+(?:\.\d+)?)([MKG]?b|%)$/", $val));
1286
}
1287

    
1288
/* returns true if $test is in the range between $start and $end */
1289
function is_inrange_v4($test, $start, $end) {
1290
	if (!is_ipaddrv4($test) || !is_ipaddrv4($start) || !is_ipaddrv4($end)) {
1291
		return false;
1292
	}
1293

    
1294
	if (ip2ulong($test) <= ip2ulong($end) &&
1295
	    ip2ulong($test) >= ip2ulong($start)) {
1296
		return true;
1297
	}
1298

    
1299
	return false;
1300
}
1301

    
1302
/* returns true if $test is in the range between $start and $end */
1303
function is_inrange_v6($test, $start, $end) {
1304
	if (!is_ipaddrv6($test) || !is_ipaddrv6($start) || !is_ipaddrv6($end)) {
1305
		return false;
1306
	}
1307

    
1308
	if (inet_pton($test) <= inet_pton($end) &&
1309
	    inet_pton($test) >= inet_pton($start)) {
1310
		return true;
1311
	}
1312

    
1313
	return false;
1314
}
1315

    
1316
/* returns true if $test is in the range between $start and $end */
1317
function is_inrange($test, $start, $end) {
1318
	return is_ipaddrv6($test) ? is_inrange_v6($test, $start, $end) : is_inrange_v4($test, $start, $end);
1319
}
1320

    
1321
function build_vip_list($fif, $family = "all") {
1322
	$list = array('address' => gettext('Interface Address'));
1323

    
1324
	$viplist = get_configured_vip_list($family);
1325
	foreach ($viplist as $vip => $address) {
1326
		if ($fif == get_configured_vip_interface($vip)) {
1327
			$list[$vip] = "$address";
1328
			if (get_vip_descr($address)) {
1329
				$list[$vip] .= " (". get_vip_descr($address) .")";
1330
			}
1331
		}
1332
	}
1333

    
1334
	return($list);
1335
}
1336

    
1337
function get_configured_vip_list($family = 'all', $type = VIP_ALL) {
1338
	global $config;
1339

    
1340
	$list = array();
1341
	if (!is_array($config['virtualip']) ||
1342
	    !is_array($config['virtualip']['vip']) ||
1343
	    empty($config['virtualip']['vip'])) {
1344
		return ($list);
1345
	}
1346

    
1347
	$viparr = &$config['virtualip']['vip'];
1348
	foreach ($viparr as $vip) {
1349

    
1350
		if ($type == VIP_CARP) {
1351
			if ($vip['mode'] != "carp")
1352
				continue;
1353
		} elseif ($type == VIP_IPALIAS) {
1354
			if ($vip['mode'] != "ipalias")
1355
				continue;
1356
		} else {
1357
			if ($vip['mode'] != "carp" && $vip['mode'] != "ipalias")
1358
				continue;
1359
		}
1360

    
1361
		if ($family == 'all' ||
1362
		    ($family == 'inet' && is_ipaddrv4($vip['subnet'])) ||
1363
		    ($family == 'inet6' && is_ipaddrv6($vip['subnet']))) {
1364
			$list["_vip{$vip['uniqid']}"] = $vip['subnet'];
1365
		}
1366
	}
1367
	return ($list);
1368
}
1369

    
1370
function get_configured_vip($vipinterface = '') {
1371

    
1372
	return (get_configured_vip_detail($vipinterface, 'all', 'vip'));
1373
}
1374

    
1375
function get_configured_vip_interface($vipinterface = '') {
1376

    
1377
	return (get_configured_vip_detail($vipinterface, 'all', 'iface'));
1378
}
1379

    
1380
function get_configured_vip_ipv4($vipinterface = '') {
1381

    
1382
	return (get_configured_vip_detail($vipinterface, 'inet', 'ip'));
1383
}
1384

    
1385
function get_configured_vip_ipv6($vipinterface = '') {
1386

    
1387
	return (get_configured_vip_detail($vipinterface, 'inet6', 'ip'));
1388
}
1389

    
1390
function get_configured_vip_subnetv4($vipinterface = '') {
1391

    
1392
	return (get_configured_vip_detail($vipinterface, 'inet', 'subnet'));
1393
}
1394

    
1395
function get_configured_vip_subnetv6($vipinterface = '') {
1396

    
1397
	return (get_configured_vip_detail($vipinterface, 'inet6', 'subnet'));
1398
}
1399

    
1400
function get_configured_vip_detail($vipinterface = '', $family = 'inet', $what = 'ip') {
1401
	global $config;
1402

    
1403
	if (empty($vipinterface) ||
1404
	    !is_array($config['virtualip']) ||
1405
	    !is_array($config['virtualip']['vip']) ||
1406
	    empty($config['virtualip']['vip'])) {
1407
		return (NULL);
1408
	}
1409

    
1410
	$viparr = &$config['virtualip']['vip'];
1411
	foreach ($viparr as $vip) {
1412
		if ($vip['mode'] != "carp" && $vip['mode'] != "ipalias") {
1413
			continue;
1414
		}
1415

    
1416
		if ($vipinterface != "_vip{$vip['uniqid']}") {
1417
			continue;
1418
		}
1419

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

    
1445
	return (NULL);
1446
}
1447

    
1448
/* comparison function for sorting by the order in which interfaces are normally created */
1449
function compare_interface_friendly_names($a, $b) {
1450
	if ($a == $b) {
1451
		return 0;
1452
	} else if ($a == 'wan') {
1453
		return -1;
1454
	} else if ($b == 'wan') {
1455
		return 1;
1456
	} else if ($a == 'lan') {
1457
		return -1;
1458
	} else if ($b == 'lan') {
1459
		return 1;
1460
	}
1461

    
1462
	return strnatcmp($a, $b);
1463
}
1464

    
1465
/* return the configured interfaces list. */
1466
function get_configured_interface_list($withdisabled = false) {
1467
	global $config;
1468

    
1469
	$iflist = array();
1470

    
1471
	/* if list */
1472
	foreach ($config['interfaces'] as $if => $ifdetail) {
1473
		if (isset($ifdetail['enable']) || $withdisabled == true) {
1474
			$iflist[$if] = $if;
1475
		}
1476
	}
1477

    
1478
	return $iflist;
1479
}
1480

    
1481
/* return the configured interfaces list. */
1482
function get_configured_interface_list_by_realif($withdisabled = false) {
1483
	global $config;
1484

    
1485
	$iflist = array();
1486

    
1487
	/* if list */
1488
	foreach ($config['interfaces'] as $if => $ifdetail) {
1489
		if (isset($ifdetail['enable']) || $withdisabled == true) {
1490
			$tmpif = get_real_interface($if);
1491
			if (!empty($tmpif)) {
1492
				$iflist[$tmpif] = $if;
1493
			}
1494
		}
1495
	}
1496

    
1497
	return $iflist;
1498
}
1499

    
1500
/* return the configured interfaces list with their description. */
1501
function get_configured_interface_with_descr($withdisabled = false) {
1502
	global $config, $user_settings;
1503

    
1504
	$iflist = array();
1505

    
1506
	/* if list */
1507
	foreach ($config['interfaces'] as $if => $ifdetail) {
1508
		if (isset($ifdetail['enable']) || $withdisabled == true) {
1509
			if (empty($ifdetail['descr'])) {
1510
				$iflist[$if] = strtoupper($if);
1511
			} else {
1512
				$iflist[$if] = strtoupper($ifdetail['descr']);
1513
			}
1514
		}
1515
	}
1516

    
1517
	if ($user_settings['webgui']['interfacessort']) {
1518
		asort($iflist);
1519
	}
1520

    
1521
	return $iflist;
1522
}
1523

    
1524
/*
1525
 *   get_configured_ip_addresses() - Return a list of all configured
1526
 *   IPv4 addresses.
1527
 *
1528
 */
1529
function get_configured_ip_addresses() {
1530
	global $config;
1531

    
1532
	if (!function_exists('get_interface_ip')) {
1533
		require_once("interfaces.inc");
1534
	}
1535
	$ip_array = array();
1536
	$interfaces = get_configured_interface_list();
1537
	if (is_array($interfaces)) {
1538
		foreach ($interfaces as $int) {
1539
			$ipaddr = get_interface_ip($int);
1540
			$ip_array[$int] = $ipaddr;
1541
		}
1542
	}
1543
	$interfaces = get_configured_vip_list('inet');
1544
	if (is_array($interfaces)) {
1545
		foreach ($interfaces as $int => $ipaddr) {
1546
			$ip_array[$int] = $ipaddr;
1547
		}
1548
	}
1549

    
1550
	/* pppoe server */
1551
	if (is_array($config['pppoes']) && is_array($config['pppoes']['pppoe'])) {
1552
		foreach ($config['pppoes']['pppoe'] as $pppoe) {
1553
			if ($pppoe['mode'] == "server") {
1554
				if (is_ipaddr($pppoe['localip'])) {
1555
					$int = "poes". $pppoe['pppoeid'];
1556
					$ip_array[$int] = $pppoe['localip'];
1557
				}
1558
			}
1559
		}
1560
	}
1561

    
1562
	return $ip_array;
1563
}
1564

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

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

    
1717
			case "friendly":
1718
				if ($friendly != "") {
1719
					$toput['if'] = $ifname;
1720
					$iflist[$friendly] = $toput;
1721
				}
1722
				break;
1723
			}
1724
		}
1725
	}
1726
	return $iflist;
1727
}
1728

    
1729
function get_lagg_interface_list() {
1730
	global $config;
1731

    
1732
	$plist = array();
1733
	if (isset($config['laggs']['lagg']) && is_array($config['laggs']['lagg'])) {
1734
		foreach ($config['laggs']['lagg'] as $lagg) {
1735
			$lagg['mac'] = get_interface_mac($lagg['laggif']);
1736
			$lagg['islagg'] = true;
1737
			$plist[$lagg['laggif']] = $lagg;
1738
		}
1739
	}
1740

    
1741
	return ($plist);
1742
}
1743

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

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

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

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

    
1807
	if ($g['debug']) {
1808
		if (!$_SERVER['REMOTE_ADDR']) {
1809
			echo "mwexec(): $command" . ($background ? " [BG]":"") . "\n";
1810
		}
1811
	}
1812
	if ($clearsigmask) {
1813
		$oldset = array();
1814
		pcntl_sigprocmask(SIG_SETMASK, array(), $oldset);
1815
	}
1816

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

    
1829
	if ($clearsigmask) {
1830
		pcntl_sigprocmask(SIG_SETMASK, $oldset);
1831
	}
1832

    
1833
	return $retval;
1834
}
1835

    
1836
/* wrapper for exec() in background */
1837
function mwexec_bg($command, $clearsigmask = false) {
1838
	return mwexec($command, false, $clearsigmask, true);
1839
}
1840

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

    
1865
/* make a global alias table (for faster lookups) */
1866
function alias_make_table() {
1867
	global $aliastable, $config;
1868

    
1869
	$aliastable = array();
1870

    
1871
	init_config_arr(array('aliases', 'alias'));
1872
	foreach ($config['aliases']['alias'] as $alias) {
1873
		if ($alias['name']) {
1874
			$aliastable[$alias['name']] = $alias['address'];
1875
		}
1876
	}
1877
}
1878

    
1879
/* check if an alias exists */
1880
function is_alias($name) {
1881
	global $aliastable;
1882

    
1883
	return isset($aliastable[$name]);
1884
}
1885

    
1886
function alias_get_type($name) {
1887
	global $config;
1888

    
1889
	if (is_array($config['aliases']['alias'])) {
1890
		foreach ($config['aliases']['alias'] as $alias) {
1891
			if ($name == $alias['name']) {
1892
				return $alias['type'];
1893
			}
1894
		}
1895
	}
1896

    
1897
	return "";
1898
}
1899

    
1900
/* expand a host or network alias, if necessary */
1901
function alias_expand($name) {
1902
	global $config, $aliastable;
1903
	$urltable_prefix = "/var/db/aliastables/";
1904
	$urltable_filename = $urltable_prefix . $name . ".txt";
1905

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

    
1937
function alias_expand_urltable($name) {
1938
	global $config;
1939
	$urltable_prefix = "/var/db/aliastables/";
1940
	$urltable_filename = $urltable_prefix . $name . ".txt";
1941

    
1942
	if (!is_array($config['aliases']['alias'])) {
1943
		return null;
1944
	}
1945

    
1946
	foreach ($config['aliases']['alias'] as $alias) {
1947
		if (!preg_match("/urltable/i", $alias['type']) ||
1948
		    ($alias['name'] != $name)) {
1949
			continue;
1950
		}
1951

    
1952
		if (is_URL($alias["url"]) && file_exists($urltable_filename)) {
1953
			if (!filesize($urltable_filename)) {
1954
				// file exists, but is empty, try to sync
1955
				send_event("service sync alias {$name}");
1956
			}
1957
			return $urltable_filename;
1958
		} else {
1959
			send_event("service sync alias {$name}");
1960
			break;
1961
		}
1962
	}
1963
	return null;
1964
}
1965

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

    
1991
/* return a fieldname that is safe for xml usage */
1992
function xml_safe_fieldname($fieldname) {
1993
	$replace = array(
1994
	    '/', '-', ' ', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')',
1995
	    '_', '+', '=', '{', '}', '[', ']', '|', '/', '<', '>', '?',
1996
	    ':', ',', '.', '\'', '\\'
1997
	);
1998
	return strtolower(str_replace($replace, "", $fieldname));
1999
}
2000

    
2001
function mac_format($clientmac) {
2002
	global $config, $cpzone;
2003

    
2004
	$mac = explode(":", $clientmac);
2005
	$mac_format = $cpzone ? $config['captiveportal'][$cpzone]['radmac_format'] : false;
2006

    
2007
	switch ($mac_format) {
2008
		case 'singledash':
2009
			return "$mac[0]$mac[1]$mac[2]-$mac[3]$mac[4]$mac[5]";
2010

    
2011
		case 'ietf':
2012
			return "$mac[0]-$mac[1]-$mac[2]-$mac[3]-$mac[4]-$mac[5]";
2013

    
2014
		case 'cisco':
2015
			return "$mac[0]$mac[1].$mac[2]$mac[3].$mac[4]$mac[5]";
2016

    
2017
		case 'unformatted':
2018
			return "$mac[0]$mac[1]$mac[2]$mac[3]$mac[4]$mac[5]";
2019

    
2020
		default:
2021
			return $clientmac;
2022
	}
2023
}
2024

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

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

    
2067
		sleep(1);
2068
	}
2069

    
2070
	if (!empty($recresult)) {
2071
		if ($numrecords == 1) {
2072
			return $recresult[0];
2073
		} else {
2074
			return array_slice($recresult, 0, $numrecords);
2075
		}
2076
	}
2077

    
2078
	return false;
2079
}
2080

    
2081
function format_bytes($bytes) {
2082
	if ($bytes >= 1099511627776) {
2083
		return sprintf("%.2f TiB", $bytes/1099511627776);
2084
	} else if ($bytes >= 1073741824) {
2085
		return sprintf("%.2f GiB", $bytes/1073741824);
2086
	} else if ($bytes >= 1048576) {
2087
		return sprintf("%.2f MiB", $bytes/1048576);
2088
	} else if ($bytes >= 1024) {
2089
		return sprintf("%.0f KiB", $bytes/1024);
2090
	} else {
2091
		return sprintf("%d B", $bytes);
2092
	}
2093
}
2094

    
2095
function format_number($num, $precision = 3) {
2096
	$units = array('', 'K', 'M', 'G', 'T');
2097

    
2098
	$i = 0;
2099
	while ($num > 1000 && $i < count($units)) {
2100
		$num /= 1000;
2101
		$i++;
2102
	}
2103
	$num = round($num, $precision);
2104

    
2105
	return ("$num {$units[$i]}");
2106
}
2107

    
2108

    
2109
function unformat_number($formated_num) {
2110
	$num = strtoupper($formated_num);
2111
    
2112
	if ( strpos($num,"T") !== false ) {
2113
		$num = str_replace("T","",$num) * 1000 * 1000 * 1000 * 1000;
2114
	} else if ( strpos($num,"G") !== false ) {
2115
		$num = str_replace("G","",$num) * 1000 * 1000 * 1000;
2116
	} else if ( strpos($num,"M") !== false ) {
2117
		$num = str_replace("M","",$num) * 1000 * 1000;
2118
	} else if ( strpos($num,"K") !== false ) {
2119
		$num = str_replace("K","",$num) * 1000;
2120
	}
2121
    
2122
	return $num;
2123
}
2124

    
2125
function update_filter_reload_status($text, $new=false) {
2126
	global $g;
2127

    
2128
	if ($new) {
2129
		file_put_contents("{$g['varrun_path']}/filter_reload_status", $text  . PHP_EOL);
2130
	} else {
2131
		file_put_contents("{$g['varrun_path']}/filter_reload_status", $text  . PHP_EOL, FILE_APPEND);
2132
	}
2133
}
2134

    
2135
/****** util/return_dir_as_array
2136
 * NAME
2137
 *   return_dir_as_array - Return a directory's contents as an array.
2138
 * INPUTS
2139
 *   $dir          - string containing the path to the desired directory.
2140
 *   $filter_regex - string containing a regular expression to filter file names. Default empty.
2141
 * RESULT
2142
 *   $dir_array - array containing the directory's contents. This array will be empty if the path specified is invalid.
2143
 ******/
2144
function return_dir_as_array($dir, $filter_regex = '') {
2145
	$dir_array = array();
2146
	if (is_dir($dir)) {
2147
		if ($dh = opendir($dir)) {
2148
			while (($file = readdir($dh)) !== false) {
2149
				if (($file == ".") || ($file == "..")) {
2150
					continue;
2151
				}
2152

    
2153
				if (empty($filter_regex) || preg_match($filter_regex, $file)) {
2154
					array_push($dir_array, $file);
2155
				}
2156
			}
2157
			closedir($dh);
2158
		}
2159
	}
2160
	return $dir_array;
2161
}
2162

    
2163
function run_plugins($directory) {
2164
	global $config, $g;
2165

    
2166
	/* process packager manager custom rules */
2167
	$files = return_dir_as_array($directory);
2168
	if (is_array($files)) {
2169
		foreach ($files as $file) {
2170
			if (stristr($file, ".sh") == true) {
2171
				mwexec($directory . $file . " start");
2172
			} else if (!is_dir($directory . "/" . $file) && stristr($file, ".inc")) {
2173
				require_once($directory . "/" . $file);
2174
			}
2175
		}
2176
	}
2177
}
2178

    
2179
/*
2180
 *    safe_mkdir($path, $mode = 0755)
2181
 *    create directory if it doesn't already exist and isn't a file!
2182
 */
2183
function safe_mkdir($path, $mode = 0755) {
2184
	global $g;
2185

    
2186
	if (!is_file($path) && !is_dir($path)) {
2187
		return @mkdir($path, $mode, true);
2188
	} else {
2189
		return false;
2190
	}
2191
}
2192

    
2193
/*
2194
 * get_sysctl($names)
2195
 * Get values of sysctl OID's listed in $names (accepts an array or a single
2196
 * name) and return an array of key/value pairs set for those that exist
2197
 */
2198
function get_sysctl($names) {
2199
	if (empty($names)) {
2200
		return array();
2201
	}
2202

    
2203
	if (is_array($names)) {
2204
		$name_list = array();
2205
		foreach ($names as $name) {
2206
			$name_list[] = escapeshellarg($name);
2207
		}
2208
	} else {
2209
		$name_list = array(escapeshellarg($names));
2210
	}
2211

    
2212
	exec("/sbin/sysctl -iq " . implode(" ", $name_list), $output);
2213
	$values = array();
2214
	foreach ($output as $line) {
2215
		$line = explode(": ", $line, 2);
2216
		if (count($line) == 2) {
2217
			$values[$line[0]] = $line[1];
2218
		}
2219
	}
2220

    
2221
	return $values;
2222
}
2223

    
2224
/*
2225
 * get_single_sysctl($name)
2226
 * Wrapper for get_sysctl() to simplify read of a single sysctl value
2227
 * return the value for sysctl $name or empty string if it doesn't exist
2228
 */
2229
function get_single_sysctl($name) {
2230
	if (empty($name)) {
2231
		return "";
2232
	}
2233

    
2234
	$value = get_sysctl($name);
2235
	if (empty($value) || !isset($value[$name])) {
2236
		return "";
2237
	}
2238

    
2239
	return $value[$name];
2240
}
2241

    
2242
/*
2243
 * set_sysctl($value_list)
2244
 * Set sysctl OID's listed as key/value pairs and return
2245
 * an array with keys set for those that succeeded
2246
 */
2247
function set_sysctl($values) {
2248
	if (empty($values)) {
2249
		return array();
2250
	}
2251

    
2252
	$value_list = array();
2253
	foreach ($values as $key => $value) {
2254
		$value_list[] = escapeshellarg($key) . "=" . escapeshellarg($value);
2255
	}
2256

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

    
2259
	/* Retry individually if failed (one or more read-only) */
2260
	if ($success <> 0 && count($value_list) > 1) {
2261
		foreach ($value_list as $value) {
2262
			exec("/sbin/sysctl -iq " . $value, $output);
2263
		}
2264
	}
2265

    
2266
	$ret = array();
2267
	foreach ($output as $line) {
2268
		$line = explode(": ", $line, 2);
2269
		if (count($line) == 2) {
2270
			$ret[$line[0]] = true;
2271
		}
2272
	}
2273

    
2274
	return $ret;
2275
}
2276

    
2277
/*
2278
 * set_single_sysctl($name, $value)
2279
 * Wrapper to set_sysctl() to make it simple to set only one sysctl
2280
 * returns boolean meaning if it succeeded
2281
 */
2282
function set_single_sysctl($name, $value) {
2283
	if (empty($name)) {
2284
		return false;
2285
	}
2286

    
2287
	$result = set_sysctl(array($name => $value));
2288

    
2289
	if (!isset($result[$name]) || $result[$name] != $value) {
2290
		return false;
2291
	}
2292

    
2293
	return true;
2294
}
2295

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

    
2310
function mute_kernel_msgs() {
2311
	global $g, $config;
2312

    
2313
	if ($config['system']['enableserial']) {
2314
		return;
2315
	}
2316
	exec("/sbin/conscontrol mute on");
2317
}
2318

    
2319
function unmute_kernel_msgs() {
2320
	global $g;
2321

    
2322
	exec("/sbin/conscontrol mute off");
2323
}
2324

    
2325
function start_devd() {
2326
	global $g;
2327

    
2328
	/* Generate hints for the kernel loader. */
2329
	$module_paths = explode(";", get_single_sysctl("kern.module_path"));
2330
	foreach ($module_paths as $id => $path) {
2331
		if (!is_dir($path) || file_exists("{$path}/linker.hints")) {
2332
			continue;
2333
		}
2334
		if (($files = scandir($path)) == false) {
2335
			continue;
2336
		}
2337
		$found = false;
2338
		foreach ($files as $id => $file) {
2339
			if (strlen($file) > 3 &&
2340
			    strcasecmp(substr($file, -3), ".ko") == 0) {
2341
				$found = true;
2342
				break;
2343
			}
2344
		}
2345
		if ($found == false) {
2346
			continue;
2347
		}
2348
		$_gb = exec("/usr/sbin/kldxref $path");
2349
		unset($_gb);
2350
	}
2351

    
2352
	/* Use the undocumented -q options of devd to quiet its log spamming */
2353
	$_gb = exec("/sbin/devd -q -f /etc/{$g['product_name']}-devd.conf");
2354
	sleep(1);
2355
	unset($_gb);
2356
}
2357

    
2358
function is_interface_vlan_mismatch() {
2359
	global $config, $g;
2360

    
2361
	if (is_array($config['vlans']['vlan'])) {
2362
		foreach ($config['vlans']['vlan'] as $vlan) {
2363
			if (substr($vlan['if'], 0, 4) == "lagg") {
2364
				return false;
2365
			}
2366
			if (does_interface_exist($vlan['if']) == false) {
2367
				return true;
2368
			}
2369
		}
2370
	}
2371

    
2372
	return false;
2373
}
2374

    
2375
function is_interface_mismatch() {
2376
	global $config, $g;
2377

    
2378
	$do_assign = false;
2379
	$i = 0;
2380
	$missing_interfaces = array();
2381
	if (is_array($config['interfaces'])) {
2382
		foreach ($config['interfaces'] as $ifname => $ifcfg) {
2383
			if (interface_is_vlan($ifcfg['if']) != NULL ||
2384
			    interface_is_qinq($ifcfg['if']) != NULL ||
2385
			    preg_match("/^enc|^cua|^tun|^tap|^l2tp|^pptp|^ppp|^ovpn|^ipsec|^gif|^gre|^lagg|^bridge|vlan|^wg|_wlan|_\d{0,4}_\d{0,4}$/i", $ifcfg['if'])) {
2386
				// Do not check these interfaces.
2387
				$i++;
2388
				continue;
2389
			} else if (does_interface_exist($ifcfg['if']) == false) {
2390
				$missing_interfaces[] = $ifcfg['if'];
2391
				$do_assign = true;
2392
			} else {
2393
				$i++;
2394
			}
2395
		}
2396
	}
2397

    
2398
	if (file_exists("{$g['tmp_path']}/assign_complete")) {
2399
		$do_assign = false;
2400
	}
2401

    
2402
	if (!empty($missing_interfaces) && $do_assign) {
2403
		file_put_contents("{$g['tmp_path']}/missing_interfaces", implode(' ', $missing_interfaces));
2404
	} else {
2405
		@unlink("{$g['tmp_path']}/missing_interfaces");
2406
	}
2407

    
2408
	return $do_assign;
2409
}
2410

    
2411
/* sync carp entries to other firewalls */
2412
function carp_sync_client() {
2413
	global $g;
2414
	send_event("filter sync");
2415
}
2416

    
2417
/****f* util/isAjax
2418
 * NAME
2419
 *   isAjax - reports if the request is driven from prototype
2420
 * INPUTS
2421
 *   none
2422
 * RESULT
2423
 *   true/false
2424
 ******/
2425
function isAjax() {
2426
	return isset ($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest';
2427
}
2428

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

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

    
2494
/****f* util/is_URL
2495
 * NAME
2496
 *   is_URL
2497
 * INPUTS
2498
 *   string to check
2499
 * RESULT
2500
 *   Returns true if item is a URL
2501
 ******/
2502
function is_URL($url) {
2503
	$match = preg_match("'\b(([\w-]+://?|www[.])[^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|/)))'", $url);
2504
	if ($match) {
2505
		return true;
2506
	}
2507
	return false;
2508
}
2509

    
2510
function is_file_included($file = "") {
2511
	$files = get_included_files();
2512
	if (in_array($file, $files)) {
2513
		return true;
2514
	}
2515

    
2516
	return false;
2517
}
2518

    
2519
/*
2520
 * Replace a value on a deep associative array using regex
2521
 */
2522
function array_replace_values_recursive($data, $match, $replace) {
2523
	if (empty($data)) {
2524
		return $data;
2525
	}
2526

    
2527
	if (is_string($data)) {
2528
		$data = preg_replace("/{$match}/", $replace, $data);
2529
	} else if (is_array($data)) {
2530
		foreach ($data as $k => $v) {
2531
			$data[$k] = array_replace_values_recursive($v, $match, $replace);
2532
		}
2533
	}
2534

    
2535
	return $data;
2536
}
2537

    
2538
/*
2539
	This function was borrowed from a comment on PHP.net at the following URL:
2540
	http://www.php.net/manual/en/function.array-merge-recursive.php#73843
2541
 */
2542
function array_merge_recursive_unique($array0, $array1) {
2543

    
2544
	$arrays = func_get_args();
2545
	$remains = $arrays;
2546

    
2547
	// We walk through each arrays and put value in the results (without
2548
	// considering previous value).
2549
	$result = array();
2550

    
2551
	// loop available array
2552
	foreach ($arrays as $array) {
2553

    
2554
		// The first remaining array is $array. We are processing it. So
2555
		// we remove it from remaining arrays.
2556
		array_shift($remains);
2557

    
2558
		// We don't care non array param, like array_merge since PHP 5.0.
2559
		if (is_array($array)) {
2560
			// Loop values
2561
			foreach ($array as $key => $value) {
2562
				if (is_array($value)) {
2563
					// we gather all remaining arrays that have such key available
2564
					$args = array();
2565
					foreach ($remains as $remain) {
2566
						if (array_key_exists($key, $remain)) {
2567
							array_push($args, $remain[$key]);
2568
						}
2569
					}
2570

    
2571
					if (count($args) > 2) {
2572
						// put the recursion
2573
						$result[$key] = call_user_func_array(__FUNCTION__, $args);
2574
					} else {
2575
						foreach ($value as $vkey => $vval) {
2576
							if (!is_array($result[$key])) {
2577
								$result[$key] = array();
2578
							}
2579
							$result[$key][$vkey] = $vval;
2580
						}
2581
					}
2582
				} else {
2583
					// simply put the value
2584
					$result[$key] = $value;
2585
				}
2586
			}
2587
		}
2588
	}
2589
	return $result;
2590
}
2591

    
2592

    
2593
/*
2594
 * converts a string like "a,b,c,d"
2595
 * into an array like array("a" => "b", "c" => "d")
2596
 */
2597
function explode_assoc($delimiter, $string) {
2598
	$array = explode($delimiter, $string);
2599
	$result = array();
2600
	$numkeys = floor(count($array) / 2);
2601
	for ($i = 0; $i < $numkeys; $i += 1) {
2602
		$result[$array[$i * 2]] = $array[$i * 2 + 1];
2603
	}
2604
	return $result;
2605
}
2606

    
2607
/*
2608
 * Given a string of text with some delimiter, look for occurrences
2609
 * of some string and replace all of those.
2610
 * $text - the text string (e.g. "abc,defg,x123,ipv4,xyz")
2611
 * $delimiter - the delimiter (e.g. ",")
2612
 * $element - the element to match (e.g. "defg")
2613
 * $replacement - the string to replace it with (e.g. "42")
2614
 * Returns the resulting delimited string (e.g. "abc,42,x123,ipv4,xyz")
2615
 */
2616
function replace_element_in_list($text, $delimiter, $element, $replacement) {
2617
	$textArray = explode($delimiter, $text);
2618
	while (($entry = array_search($element, $textArray)) !== false) {
2619
		$textArray[$entry] = $replacement;
2620
	}
2621
	return implode(',', $textArray);
2622
}
2623

    
2624
/* Return system's route table */
2625
function route_table() {
2626
	$_gb = exec("/usr/bin/netstat --libxo json -nWr", $rawdata, $rc);
2627

    
2628
	if ($rc != 0) {
2629
		return array();
2630
	}
2631

    
2632
	$netstatarr = json_decode(implode(" ", $rawdata), JSON_OBJECT_AS_ARRAY);
2633
	$netstatarr = $netstatarr['statistics']['route-information']
2634
	    ['route-table']['rt-family'];
2635

    
2636
	$result = array();
2637
	$result['inet'] = array();
2638
	$result['inet6'] = array();
2639
	foreach ($netstatarr as $item) {
2640
		if ($item['address-family'] == 'Internet') {
2641
			$result['inet'] = $item['rt-entry'];
2642
		} else if ($item['address-family'] == 'Internet6') {
2643
			$result['inet6'] = $item['rt-entry'];
2644
		}
2645
	}
2646
	unset($netstatarr);
2647

    
2648
	return $result;
2649
}
2650

    
2651
/* Get static route for specific destination */
2652
function route_get($target, $ipprotocol = '', $useroute = false) {
2653
	global $config;
2654

    
2655
	if (!empty($ipprotocol)) {
2656
		$family = $ipprotocol;
2657
	} else if (is_v4($target)) {
2658
		$family = 'inet';
2659
	} else if (is_v6($target)) {
2660
		$family = 'inet6';
2661
	}
2662

    
2663
	if (empty($family)) {
2664
		return array();
2665
	}
2666

    
2667
	if ($useroute) {
2668
		if ($family == 'inet') {
2669
			$inet = '4';
2670
		} else {
2671
			$inet = '6';
2672
		}
2673
		$interface = exec("/sbin/route -n{$inet} get {$target} | /usr/bin/awk '/interface:/{print $2}'");
2674
		if (empty($interface)) {
2675
			return array();
2676
		} elseif ($interface == 'lo0') {
2677
			// interface assigned IP address
2678
			foreach ($config['interfaces'] as $intf => $infconf) {
2679
				if ((($inet == '4') && (get_interface_ip($intf) == $target)) ||
2680
				    (($inet == '6') && (get_interface_ipv6($intf) == $target))) {
2681
					$interface = convert_friendly_interface_to_real_interface_name($intf);
2682
					$gateway = $interface;
2683
					break;
2684
				}
2685
			}
2686
		} else {
2687
			$gateway = exec("/sbin/route -n{$inet} get {$target} | /usr/bin/awk '/gateway:/{print $2}'");
2688
		}
2689
		$result[] = array('gateway' => $gateway, 'interface-name' => $interface);
2690
	} else {
2691
		$rtable = route_table();
2692
		if (empty($rtable)) {
2693
			return array();
2694
		}
2695

    
2696
		$result = array();
2697
		foreach ($rtable[$family] as $item) {
2698
			if ($item['destination'] == $target ||
2699
			    ip_in_subnet($target, $item['destination'])) {
2700
				$result[] = $item;
2701
			}
2702
		}
2703
	}
2704

    
2705
	return $result;
2706
}
2707

    
2708
/* Get default route */
2709
function route_get_default($ipprotocol) {
2710
	if (empty($ipprotocol) || ($ipprotocol != 'inet' &&
2711
	    $ipprotocol != 'inet6')) {
2712
		return '';
2713
	}
2714

    
2715
	$route = route_get('default', $ipprotocol, true);
2716

    
2717
	if (empty($route)) {
2718
		return '';
2719
	}
2720

    
2721
	if (!isset($route[0]['gateway'])) {
2722
		return '';
2723
	}
2724

    
2725
	return $route[0]['gateway'];
2726
}
2727

    
2728
/* Delete a static route */
2729
function route_del($target, $ipprotocol = '') {
2730
	global $config;
2731

    
2732
	if (empty($target)) {
2733
		return;
2734
	}
2735

    
2736
	if (!empty($ipprotocol) && $ipprotocol != 'inet' &&
2737
	    $ipprotocol != 'inet6') {
2738
		return false;
2739
	}
2740

    
2741
	$route = route_get($target, $ipprotocol);
2742

    
2743
	if (empty($route)) {
2744
		return;
2745
	}
2746

    
2747
	$target_prefix = '';
2748
	if (is_subnet($target)) {
2749
		$target_prefix = '-net';
2750
	} else if (is_ipaddr($target)) {
2751
		$target_prefix = '-host';
2752
	}
2753

    
2754
	if (!empty($ipprotocol)) {
2755
		$target_prefix .= " -{$ipprotocol}";
2756
	} else if (is_v6($target)) {
2757
		$target_prefix .= ' -inet6';
2758
	} else if (is_v4($target)) {
2759
		$target_prefix .= ' -inet';
2760
	}
2761

    
2762
	foreach ($route as $item) {
2763
		if (substr($item['gateway'], 0, 5) == 'link#') {
2764
			continue;
2765
		}
2766

    
2767
		if (is_macaddr($item['gateway'])) {
2768
			$gw = '-iface ' . $item['interface-name'];
2769
		} else {
2770
			$gw = $item['gateway'];
2771
		}
2772

    
2773
		$_gb = exec(escapeshellcmd("/sbin/route del {$target_prefix} " .
2774
		    "{$target} {$gw}"), $output, $rc);
2775

    
2776
		if (isset($config['system']['route-debug'])) {
2777
			log_error("ROUTING debug: " . microtime() .
2778
			    " - DEL RC={$rc} - {$target} - gw: " . $gw);
2779
			file_put_contents("/dev/console", "\n[" . getmypid() .
2780
			    "] ROUTE DEL: {$target_prefix} {$target} {$gw} " .
2781
			    "result: {$rc}");
2782
		}
2783
	}
2784
}
2785

    
2786
/*
2787
 * Add static route.  If it already exists, remove it and re-add
2788
 *
2789
 * $target - IP, subnet or 'default'
2790
 * $gw     - gateway address
2791
 * $iface  - Network interface
2792
 * $args   - Extra arguments for /sbin/route
2793
 * $ipprotocol - 'inet' or 'inet6'.  Mandatory when $target == 'default'
2794
 *
2795
 */
2796
function route_add_or_change($target, $gw, $iface = '', $args = '',
2797
    $ipprotocol = '') {
2798
	global $config;
2799

    
2800
	if (empty($target) || (empty($gw) && empty($iface))) {
2801
		return false;
2802
	}
2803

    
2804
	if ($target == 'default' && empty($ipprotocol)) {
2805
		return false;
2806
	}
2807

    
2808
	if (!empty($ipprotocol) && $ipprotocol != 'inet' &&
2809
	    $ipprotocol != 'inet6') {
2810
		return false;
2811
	}
2812

    
2813
	/* use '-host' for IPv6 /128 routes, see https://redmine.pfsense.org/issues/11594 */
2814
	if (is_subnetv4($target) || (is_subnetv6($target) && (subnet_size($target) > 1))) {
2815
		$target_prefix = '-net';
2816
	} else if (is_ipaddr($target)) {
2817
		$target_prefix = '-host';
2818
	}
2819

    
2820
	if (!empty($ipprotocol)) {
2821
		$target_prefix .= " -{$ipprotocol}";
2822
	} else if (is_v6($target)) {
2823
		$target_prefix .= ' -inet6';
2824
	} else if (is_v4($target)) {
2825
		$target_prefix .= ' -inet';
2826
	}
2827

    
2828
	/* If there is another route to the same target, remove it */
2829
	route_del($target, $ipprotocol);
2830

    
2831
	$params = '';
2832
	if (!empty($iface) && does_interface_exist($iface)) {
2833
		$params .= " -iface {$iface}";
2834
	}
2835
	if (is_ipaddr($gw)) {
2836
		$params .= " " . $gw;
2837
	}
2838

    
2839
	if (empty($params)) {
2840
		log_error("route_add_or_change: Invalid gateway {$gw} and/or " .
2841
		    "network interface {$iface}");
2842
		return false;
2843
	}
2844

    
2845
	$_gb = exec(escapeshellcmd("/sbin/route add {$target_prefix} " .
2846
	    "{$target} {$args} {$params}"), $output, $rc);
2847

    
2848
	if (isset($config['system']['route-debug'])) {
2849
		log_error("ROUTING debug: " . microtime() .
2850
		    " - ADD RC={$rc} - {$target} {$args}");
2851
		file_put_contents("/dev/console", "\n[" . getmypid() .
2852
		    "] ROUTE ADD: {$target_prefix} {$target} {$args} " .
2853
		    "{$params} result: {$rc}");
2854
	}
2855

    
2856
	return ($rc == 0);
2857
}
2858

    
2859
function set_ipv6routes_mtu($interface, $mtu) {
2860
	global $config, $g;
2861

    
2862
	$ipv6mturoutes = array();
2863
	$if = convert_real_interface_to_friendly_interface_name($interface);
2864
	if (!$config['interfaces'][$if]['ipaddrv6']) {
2865
		return;
2866
	}
2867
	$a_gateways = return_gateways_array();
2868
	$a_staticroutes = get_staticroutes(false, false, true);
2869
	foreach ($a_gateways as $gate) {
2870
		foreach ($a_staticroutes as $sroute) {
2871
			if ($gate['interface'] == $interface &&
2872
			    $sroute['gateway'] == $gate['name']) {
2873
				$tgt = $sroute['network'];
2874
				$gateway = $gate['gateway'];
2875
				$ipv6mturoutes[$tgt] = $gateway;
2876
			}
2877
		}
2878
		if ($gate['interface'] == $interface &&
2879
		    $gate['isdefaultgw']) {
2880
			$tgt = "default";
2881
			$gateway = $gate['gateway'];
2882
			$ipv6mturoutes[$tgt] = $gateway;
2883
		}
2884
	}
2885
	foreach ($ipv6mturoutes as $tgt => $gateway) {
2886
		mwexec("/sbin/route change -6 -mtu " . escapeshellarg($mtu) .
2887
		    " " . escapeshellarg($tgt) . " " .
2888
		    escapeshellarg($gateway));
2889
	}
2890
}
2891

    
2892
function alias_to_subnets_recursive($name, $returnhostnames = false) {
2893
	global $aliastable;
2894
	$result = array();
2895
	if (!isset($aliastable[$name])) {
2896
		return $result;
2897
	}
2898
	$subnets = preg_split('/\s+/', $aliastable[$name]);
2899
	foreach ($subnets as $net) {
2900
		if (is_alias($net)) {
2901
			$sub = alias_to_subnets_recursive($net, $returnhostnames);
2902
			$result = array_merge($result, $sub);
2903
			continue;
2904
		} elseif (!is_subnet($net)) {
2905
			if (is_ipaddrv4($net)) {
2906
				$net .= "/32";
2907
			} else if (is_ipaddrv6($net)) {
2908
				$net .= "/128";
2909
			} else if ($returnhostnames === false || !is_fqdn($net)) {
2910
				continue;
2911
			}
2912
		}
2913
		$result[] = $net;
2914
	}
2915
	return $result;
2916
}
2917

    
2918
function get_staticroutes($returnsubnetsonly = false, $returnhostnames = false, $returnenabledroutesonly = false) {
2919
	global $config, $aliastable;
2920

    
2921
	/* Bail if there are no routes, but return an array always so callers don't have to check. */
2922
	init_config_arr(array('staticroutes', 'route'));
2923
	if (empty($config['staticroutes']['route'])) {
2924
		return array();
2925
	}
2926

    
2927
	$allstaticroutes = array();
2928
	$allsubnets = array();
2929
	/* Loop through routes and expand aliases as we find them. */
2930
	foreach ($config['staticroutes']['route'] as $route) {
2931
		if ($returnenabledroutesonly && isset($route['disabled'])) {
2932
			continue;
2933
		}
2934

    
2935
		if (is_alias($route['network'])) {
2936
			foreach (alias_to_subnets_recursive($route['network'], $returnhostnames) as $net) {
2937
				$temproute = $route;
2938
				$temproute['network'] = $net;
2939
				$allstaticroutes[] = $temproute;
2940
				$allsubnets[] = $net;
2941
			}
2942
		} elseif (is_subnet($route['network'])) {
2943
			$allstaticroutes[] = $route;
2944
			$allsubnets[] = $route['network'];
2945
		}
2946
	}
2947
	if ($returnsubnetsonly) {
2948
		return $allsubnets;
2949
	} else {
2950
		return $allstaticroutes;
2951
	}
2952
}
2953

    
2954
/****f* util/get_alias_list
2955
 * NAME
2956
 *   get_alias_list - Provide a list of aliases.
2957
 * INPUTS
2958
 *   $type          - Optional, can be a string or array specifying what type(s) of aliases you need.
2959
 * RESULT
2960
 *   Array containing list of aliases.
2961
 *   If $type is unspecified, all aliases are returned.
2962
 *   If $type is a string, all aliases of the type specified in $type are returned.
2963
 *   If $type is an array, all aliases of any type specified in any element of $type are returned.
2964
 */
2965
function get_alias_list($type = null) {
2966
	global $config;
2967
	$result = array();
2968
	if ($config['aliases']['alias'] <> "" && is_array($config['aliases']['alias'])) {
2969
		foreach ($config['aliases']['alias'] as $alias) {
2970
			if ($type === null) {
2971
				$result[] = $alias['name'];
2972
			} else if (is_array($type)) {
2973
				if (in_array($alias['type'], $type)) {
2974
					$result[] = $alias['name'];
2975
				}
2976
			} else if ($type === $alias['type']) {
2977
				$result[] = $alias['name'];
2978
			}
2979
		}
2980
	}
2981
	return $result;
2982
}
2983

    
2984
/* returns an array consisting of every element of $haystack that is not equal to $needle. */
2985
function array_exclude($needle, $haystack) {
2986
	$result = array();
2987
	if (is_array($haystack)) {
2988
		foreach ($haystack as $thing) {
2989
			if ($needle !== $thing) {
2990
				$result[] = $thing;
2991
			}
2992
		}
2993
	}
2994
	return $result;
2995
}
2996

    
2997
/* Define what is preferred, IPv4 or IPv6 */
2998
function prefer_ipv4_or_ipv6() {
2999
	global $config;
3000

    
3001
	if (isset($config['system']['prefer_ipv4'])) {
3002
		mwexec("/etc/rc.d/ip6addrctl prefer_ipv4");
3003
	} else {
3004
		mwexec("/etc/rc.d/ip6addrctl prefer_ipv6");
3005
	}
3006
}
3007

    
3008
/* Redirect to page passing parameters via POST */
3009
function post_redirect($page, $params) {
3010
	if (!is_array($params)) {
3011
		return;
3012
	}
3013

    
3014
	print "<html><body><form action=\"{$page}\" name=\"formredir\" method=\"post\">\n";
3015
	foreach ($params as $key => $value) {
3016
		print "<input type=\"hidden\" name=\"{$key}\" value=\"{$value}\" />\n";
3017
	}
3018
	print "</form>\n";
3019
	print "<script type=\"text/javascript\">\n";
3020
	print "//<![CDATA[\n";
3021
	print "document.formredir.submit();\n";
3022
	print "//]]>\n";
3023
	print "</script>\n";
3024
	print "</body></html>\n";
3025
}
3026

    
3027
/* Locate disks that can be queried for S.M.A.R.T. data. */
3028
function get_smart_drive_list() {
3029
	/* SMART supports some disks directly, and some controllers directly,
3030
	 * See https://redmine.pfsense.org/issues/9042 */
3031
	$supported_disk_types = array("ad", "da", "ada");
3032
	$supported_controller_types = array("nvme");
3033
	$disk_list = explode(" ", get_single_sysctl("kern.disks"));
3034
	foreach ($disk_list as $id => $disk) {
3035
		// We only want certain kinds of disks for S.M.A.R.T.
3036
		// 1 is a match, 0 is no match, False is any problem processing the regex
3037
		if (preg_match("/^(" . implode("|", $supported_disk_types) . ").*[0-9]{1,2}$/", $disk) !== 1) {
3038
			unset($disk_list[$id]);
3039
			continue;
3040
		}
3041
	}
3042
	foreach ($supported_controller_types as $controller) {
3043
		$devices = glob("/dev/{$controller}*");
3044
		if (!is_array($devices)) {
3045
			continue;
3046
		}
3047
		foreach ($devices as $device) {
3048
			$disk_list[] = basename($device);
3049
		}
3050
	}
3051
	sort($disk_list);
3052
	return $disk_list;
3053
}
3054

    
3055
// Validate a network address
3056
//	$addr: the address to validate
3057
//	$type: IPV4|IPV6|IPV4V6
3058
//	$label: the label used by the GUI to display this value. Required to compose an error message
3059
//	$err_msg: pointer to the callers error message array so that error messages can be added to it here
3060
//	$alias: are aliases permitted for this address?
3061
// Returns:
3062
//	IPV4 - if $addr is a valid IPv4 address
3063
//	IPV6 - if $addr is a valid IPv6 address
3064
//	ALIAS - if $alias=true and $addr is an alias
3065
//	false - otherwise
3066

    
3067
function validateipaddr(&$addr, $type, $label, &$err_msg, $alias=false) {
3068
	switch ($type) {
3069
		case IPV4:
3070
			if (is_ipaddrv4($addr)) {
3071
				return IPV4;
3072
			} else if ($alias) {
3073
				if (is_alias($addr)) {
3074
					return ALIAS;
3075
				} else {
3076
					$err_msg[] = sprintf(gettext("%s must be a valid IPv4 address or alias."), $label);
3077
					return false;
3078
				}
3079
			} else {
3080
				$err_msg[] = sprintf(gettext("%s must be a valid IPv4 address."), $label);
3081
				return false;
3082
			}
3083
		break;
3084
		case IPV6:
3085
			if (is_ipaddrv6($addr)) {
3086
				$addr = strtolower($addr);
3087
				return IPV6;
3088
			} else if ($alias) {
3089
				if (is_alias($addr)) {
3090
					return ALIAS;
3091
				} else {
3092
					$err_msg[] = sprintf(gettext("%s must be a valid IPv6 address or alias."), $label);
3093
					return false;
3094
				}
3095
			} else {
3096
				$err_msg[] = sprintf(gettext("%s must be a valid IPv6 address."), $label);
3097
				return false;
3098
			}
3099
		break;
3100
		case IPV4V6:
3101
			if (is_ipaddrv6($addr)) {
3102
				$addr = strtolower($addr);
3103
				return IPV6;
3104
			} else if (is_ipaddrv4($addr)) {
3105
				return IPV4;
3106
			} else if ($alias) {
3107
				if (is_alias($addr)) {
3108
					return ALIAS;
3109
				} else {
3110
					$err_msg[] = sprintf(gettext("%s must be a valid IPv4 or IPv6 address or alias."), $label);
3111
					return false;
3112
				}
3113
			} else {
3114
				$err_msg[] = sprintf(gettext("%s must be a valid IPv4 or IPv6 address."), $label);
3115
				return false;
3116
			}
3117
		break;
3118
	}
3119

    
3120
	return false;
3121
}
3122

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

    
3145
	/* Make hexstrings */
3146
	if ($duidtype) {
3147
		switch ($duidtype) {
3148
		/* Add a hardware type to DUID-LLT and DUID-LL; assume Ethernet */
3149
		case 1:
3150
		case 3:
3151
			$duidpt1 = '00:01:' . $duidpt1;
3152
			break;
3153
		/* Remove '-' from given UUID and insert ':' every 2 characters */
3154
		case 4:
3155
			$duidpt1 = implode(':', str_split(str_replace('-', '', $duidpt1), 2));
3156
			break;
3157
		default:
3158
		}
3159
		$duidpt1 = '00:0' . $duidtype . ':' . $duidpt1;
3160
	}
3161

    
3162
	$values = explode(':', strtolower(str_replace('-', ':', $duidpt1)));
3163

    
3164
	if (hexdec($values[0]) != count($values) - 2)
3165
		array_unshift($values, dechex(count($values)), '00');
3166

    
3167
	array_walk($values, function(&$value) {
3168
		$value = str_pad($value, 2, '0', STR_PAD_LEFT);
3169
	});
3170

    
3171
	return implode(":", $values);
3172
}
3173

    
3174
/* Returns true if $dhcp6duid is a valid DUID entry.
3175
 * Parse the entry to check for valid length according to known DUID types.
3176
 */
3177
function is_duid($dhcp6duid) {
3178
	$values = explode(":", $dhcp6duid);
3179
	if (hexdec($values[0]) == count($values) - 2) {
3180
		switch (hexdec($values[2] . $values[3])) {
3181
		case 0:
3182
			return false;
3183
			break;
3184
		case 1:
3185
			if (count($values) != 16 || strlen($dhcp6duid) != 47)
3186
				return false;
3187
			break;
3188
		case 3:
3189
			if (count($values) != 12 || strlen($dhcp6duid) != 35)
3190
				return false;
3191
			break;
3192
		case 4:
3193
			if (count($values) != 20 || strlen($dhcp6duid) != 59)
3194
				return false;
3195
			break;
3196
		/* DUID is up to 128 octets; allow 2 octets for type code, 2 more for option-len */
3197
		default:
3198
			if (count($values) > 132 || strlen($dhcp6duid) != count($values) * 3 - 1)
3199
				return false;
3200
		}
3201
	} else
3202
		return false;
3203

    
3204
	for ($i = 0; $i < count($values); $i++) {
3205
		if (ctype_xdigit($values[$i]) == false)
3206
			return false;
3207
		if (hexdec($values[$i]) < 0 || hexdec($values[$i]) > 255)
3208
			return false;
3209
	}
3210

    
3211
	return true;
3212
}
3213

    
3214
/* Write the DHCP6 DUID file */
3215
function write_dhcp6_duid($duidstring) {
3216
	// Create the hex array from the dhcp6duid config entry and write to file
3217
	global $g;
3218

    
3219
	if(!is_duid($duidstring)) {
3220
		log_error(gettext("Error: attempting to write DUID file - Invalid DUID detected"));
3221
		return false;
3222
	}
3223
	$temp = str_replace(":","",$duidstring);
3224
	$duid_binstring = pack("H*",$temp);
3225
	if ($fd = fopen("{$g['vardb_path']}/dhcp6c_duid", "wb")) {
3226
		fwrite($fd, $duid_binstring);
3227
		fclose($fd);
3228
		return true;
3229
	}
3230
	log_error(gettext("Error: attempting to write DUID file - File write error"));
3231
	return false;
3232
}
3233

    
3234
/* returns duid string from 'vardb_path']}/dhcp6c_duid' */
3235
function get_duid_from_file() {
3236
	global $g;
3237

    
3238
	$duid_ASCII = "";
3239
	$count = 0;
3240

    
3241
	if (file_exists("{$g['vardb_path']}/dhcp6c_duid") &&
3242
	    ($fd = fopen("{$g['vardb_path']}/dhcp6c_duid", "r"))) {
3243
		$fsize = filesize("{$g['vardb_path']}/dhcp6c_duid");
3244
		if ($fsize <= 132) {
3245
			$buffer = fread($fd, $fsize);
3246
			while($count < $fsize) {
3247
				$duid_ASCII .= bin2hex($buffer[$count]);
3248
				$count++;
3249
				if($count < $fsize) {
3250
					$duid_ASCII .= ":";
3251
				}
3252
			}
3253
		}
3254
		fclose($fd);
3255
	}
3256
	//if no file or error with read then the string returns blanked DUID string
3257
	if(!is_duid($duid_ASCII)) {
3258
		return "--:--:--:--:--:--:--:--:--:--:--:--:--:--:--:--";
3259
	}
3260
	return($duid_ASCII);
3261
}
3262

    
3263
/* Replaces the Mac OS 9 and earlier (\r) and DOS/Windows (\r\n) newlines with the Unix equivalent (\n). */
3264
function unixnewlines($text) {
3265
	return preg_replace('/\r\n?/', "\n", $text);
3266
}
3267

    
3268
function array_remove_duplicate($array, $field) {
3269
	foreach ($array as $sub) {
3270
		if (isset($sub[$field])) {
3271
			$cmp[] = $sub[$field];
3272
		}
3273
	}
3274
	$unique = array_unique(array_reverse($cmp, true));
3275
	foreach ($unique as $k => $rien) {
3276
		$new[] = $array[$k];
3277
	}
3278
	return $new;
3279
}
3280

    
3281
function dhcpd_date_adjust_gmt($dt) {
3282
	global $config;
3283

    
3284
	init_config_arr(array('dhcpd'));
3285

    
3286
	foreach ($config['dhcpd'] as $dhcpditem) {
3287
		if ($dhcpditem['dhcpleaseinlocaltime'] == "yes") {
3288
			$ts = strtotime($dt . " GMT");
3289
			if ($ts !== false) {
3290
				return strftime("%Y/%m/%d %H:%M:%S", $ts);
3291
			}
3292
		}
3293
	}
3294

    
3295
	/*
3296
	 * If we did not need to convert to local time or the conversion
3297
	 * failed, just return the input.
3298
	 */
3299
	return $dt;
3300
}
3301

    
3302
global $supported_image_types;
3303
$supported_image_types = array(
3304
	IMAGETYPE_JPEG,
3305
	IMAGETYPE_PNG,
3306
	IMAGETYPE_GIF,
3307
	IMAGETYPE_WEBP
3308
);
3309

    
3310
function is_supported_image($image_filename) {
3311
	global $supported_image_types;
3312
	$img_info = getimagesize($image_filename);
3313

    
3314
	/* If it's not an image, or it isn't in the supported list, return false */
3315
	if (($img_info === false) ||
3316
	    !in_array($img_info[2], array_keys($supported_image_types))) {
3317
		return false;
3318
	} else {
3319
		return $img_info[2];
3320
	}
3321
}
3322

    
3323
function get_lagg_ports ($laggport) {
3324
	$laggp = array();
3325
	foreach ($laggport as $lgp) {
3326
		list($lpname, $lpinfo) = explode(" ", $lgp);
3327
		preg_match('~<(.+)>~', $lpinfo, $lgportmode);
3328
		if ($lgportmode[1]) {
3329
			$laggp[] = $lpname . " (" . $lgportmode[1] . ")";
3330
		} else {
3331
			$laggp[] = $lpname;
3332
		}
3333
	}
3334
	if ($laggp) {
3335
		return implode(", ", $laggp);
3336
	} else {
3337
		return false;
3338
	}
3339
}
3340

    
3341
function cisco_to_cidr($addr) {
3342
	if (!is_ipaddr($addr)) {
3343
		throw new Exception('Invalid IP Addr');
3344
	}
3345

    
3346
	$mask = decbin(~ip2long($addr));
3347
	$mask = substr($mask, -32);
3348
	$k = 0;
3349
	for ($i = 0; $i <= 32; $i++) {
3350
		$k += intval($mask[$i]);
3351
	}
3352
	return $k;
3353
}
3354

    
3355
function cisco_extract_index($prule) {
3356
	$index = explode("#", $prule);
3357
	if (is_numeric($index[1])) {
3358
		return intval($index[1]);
3359
	} else {
3360
		syslog(LOG_WARNING, "Error parsing rule {$prule}: Could not extract index");
3361
	}
3362
	return -1;;
3363
}
3364

    
3365
function parse_cisco_acl_rule($rule, $devname, $dir, $proto) {
3366
	$rule_orig = $rule;
3367
	$rule = explode(" ", $rule);
3368
	$tmprule = "";
3369
	$index = 0;
3370

    
3371
	if ($rule[$index] == "permit") {
3372
		$startrule = "pass {$dir} quick on {$devname} ";
3373
	} else if ($rule[$index] == "deny") {
3374
		$startrule = "block {$dir} quick on {$devname} ";
3375
	} else {
3376
		return;
3377
	}
3378

    
3379
	$index++;
3380

    
3381
	switch ($rule[$index]) {
3382
		case "ip":
3383
			break;
3384
		case "icmp":
3385
			$icmp = ($proto == "inet") ? "icmp" : "ipv6-icmp";
3386
			$tmprule .= "proto {$icmp} ";
3387
			break;
3388
		case "tcp":
3389
		case "udp":
3390
			$tmprule .= "proto {$rule[$index]} ";
3391
			break;
3392
		default:
3393
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid protocol.");
3394
			return;
3395
	}
3396
	$index++;
3397

    
3398
	/* Source */
3399
	if (trim($rule[$index]) == "host") {
3400
		$index++;
3401
		if ((is_ipaddrv4(trim($rule[$index])) && ($proto == "inet")) ||
3402
		    (is_ipaddrv6(trim($rule[$index])) && ($proto == "inet6")) ||
3403
		    (trim($rule[$index]) == "{clientip}")) {
3404
			$tmprule .= "from {$rule[$index]} ";
3405
			$index++;
3406
		} else {
3407
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source host '{$rule[$index]}'.");
3408
			return;
3409
		}
3410
	} elseif (is_subnetv6(trim($rule[$index])) && ($proto == "inet6")) {
3411
		$tmprule .= "from {$rule[$index]} ";
3412
		$index++;
3413
	} elseif (trim($rule[$index]) == "any") {
3414
		$tmprule .= "from any ";
3415
		$index++;
3416
	} else {
3417
		$network = $rule[$index];
3418
		$netmask = $rule[++$index];
3419

    
3420
		if (is_ipaddrv4($network) && ($proto == "inet")) {
3421
			try {
3422
				$netmask = cisco_to_cidr($netmask);
3423
			} catch(Exception $e) {
3424
				syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source netmask '$netmask'.");
3425
				return;
3426
			}
3427
			$tmprule .= "from {$network}/{$netmask} ";
3428
		} else {
3429
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source network '$network'.");
3430
			return;
3431
		}
3432

    
3433
		$index++;
3434
	}
3435

    
3436
	/* Source Operator */
3437
	if (in_array(trim($rule[$index]), array("lt", "gt", "eq", "neq"))) {
3438
		switch(trim($rule[$index])) {
3439
			case "lt":
3440
				$operator = "<";
3441
				break;
3442
			case "gt":
3443
				$operator = ">";
3444
				break;
3445
			case "eq":
3446
				$operator = "=";
3447
				break;
3448
			case "neq":
3449
				$operator = "!=";
3450
				break;
3451
		}
3452

    
3453
		$port = $rule[++$index];
3454
		if (is_port($port)) {
3455
			$tmprule .= "port {$operator} {$port} ";
3456
		} else {
3457
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source port: '$port' not a numeric value between 0 and 65535.");
3458
			return;
3459
		}
3460
		$index++;
3461
	} else if (trim($rule[$index]) == "range") {
3462
		$port = array($rule[++$index], $rule[++$index]);
3463
		if (is_port($port[0]) && is_port($port[1])) {
3464
			$port[0]--;
3465
			$port[1]++;
3466
			$tmprule .= "port {$port[0]} >< {$port[1]} ";
3467
		} else {
3468
			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.");
3469
			return;
3470
		}
3471
		$index++;
3472
	}
3473

    
3474
	/* Destination */
3475
	if (trim($rule[$index]) == "host") {
3476
		$index++;
3477
		if ((is_ipaddrv4(trim($rule[$index])) && ($proto == "inet")) ||
3478
		    (is_ipaddrv6(trim($rule[$index])) && ($proto == "inet6")) ||
3479
		    (trim($rule[$index]) == "{clientip}")) {
3480
			$tmprule .= "to {$rule[$index]} ";
3481
			$index++;
3482
		} else {
3483
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination host '{$rule[$index]}'.");
3484
			return;
3485
		}
3486
	} elseif (is_subnetv6(trim($rule[$index])) && ($proto == "inet6")) {
3487
		$tmprule .= "to {$rule[$index]} ";
3488
		$index++;
3489
	} elseif (trim($rule[$index]) == "any") {
3490
		$tmprule .= "to any ";
3491
		$index++;
3492
	} else {
3493
		$network = $rule[$index];
3494
		$netmask = $rule[++$index];
3495

    
3496
		if (is_ipaddrv4($network) && ($proto == "inet")) {
3497
			try {
3498
				$netmask = cisco_to_cidr($netmask);
3499
			} catch(Exception $e) {
3500
				syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination network '$network'.");
3501
				return;
3502
			}
3503
			$tmprule .= "to {$network}/{$netmask} ";
3504
		} else {
3505
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination network '$network'.");
3506
			return;
3507
		}
3508

    
3509
		$index++;
3510
	}
3511

    
3512
	/* Destination Operator */
3513
	if (in_array(trim($rule[$index]), array("lt", "gt", "eq", "neq"))) {
3514
		switch(trim($rule[$index])) {
3515
			case "lt":
3516
				$operator = "<";
3517
				break;
3518
			case "gt":
3519
				$operator = ">";
3520
				break;
3521
			case "eq":
3522
				$operator = "=";
3523
				break;
3524
			case "neq":
3525
				$operator = "!=";
3526
				break;
3527
		}
3528

    
3529
		$port = $rule[++$index];
3530
		if (is_port($port)) {
3531
			$tmprule .= "port {$operator} {$port} ";
3532
		} else {
3533
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination port: '$port' not a numeric value between 0 and 65535.");
3534
			return;
3535
		}
3536
		$index++;
3537
	} else if (trim($rule[$index]) == "range") {
3538
		$port = array($rule[++$index], $rule[++$index]);
3539
		if (is_port($port[0]) && is_port($port[1])) {
3540
			$port[0]--;
3541
			$port[1]++;
3542
			$tmprule .= "port {$port[0]} >< {$port[1]} ";
3543
		} else {
3544
			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.");
3545
			return;
3546
		}
3547
		$index++;
3548
	}
3549

    
3550
	$tmprule = $startrule . $proto . " " . $tmprule;
3551
	return $tmprule;
3552
}
3553

    
3554
function parse_cisco_acl($attribs, $dev) {
3555
	global $attributes;
3556

    
3557
	if (!is_array($attribs)) {
3558
		return "";
3559
	}
3560
	$finalrules = "";
3561
	if (is_array($attribs['ciscoavpair'])) {
3562
		$inrules = array('inet' => array(), 'inet6' => array());
3563
		$outrules = array('inet' => array(), 'inet6' => array());
3564
		foreach ($attribs['ciscoavpair'] as $avrules) {
3565
			$rule = explode("=", $avrules);
3566
			$dir = "";
3567
			if (strstr($rule[0], "inacl")) {
3568
				$dir = "in";
3569
			} else if (strstr($rule[0], "outacl")) {
3570
				$dir = "out";
3571
			} else if (strstr($rule[0], "dns-servers")) {
3572
				$attributes['dns-servers'] = explode(" ", $rule[1]);
3573
				continue;
3574
			} else if (strstr($rule[0], "route")) {
3575
				if (!is_array($attributes['routes'])) {
3576
					$attributes['routes'] = array();
3577
				}
3578
				$attributes['routes'][] = $rule[1];
3579
				continue;
3580
			}
3581
			$rindex = cisco_extract_index($rule[0]);
3582
			if ($rindex < 0) {
3583
				continue;
3584
			}
3585

    
3586
			if (strstr($rule[0], "ipv6")) {
3587
				$proto = "inet6";
3588
			} else {
3589
				$proto = "inet";
3590
			}
3591

    
3592
			$tmprule = parse_cisco_acl_rule($rule[1], $dev, $dir, $proto);
3593

    
3594
			if ($dir == "in") {
3595
				$inrules[$proto][$rindex] = $tmprule;
3596
			} else if ($dir == "out") {
3597
				$outrules[$proto][$rindex] = $tmprule;
3598
			}
3599
		}
3600

    
3601

    
3602
		$state = "";
3603
		foreach (array('inet', 'inet6') as $ip) {
3604
			if (!empty($outrules[$ip])) {
3605
				$state = "no state";
3606
			}
3607
			ksort($inrules[$ip], SORT_NUMERIC);
3608
			foreach ($inrules[$ip] as $inrule) {
3609
				$finalrules .= "{$inrule} {$state}\n";
3610
			}
3611
			if (!empty($outrules[$ip])) {
3612
				ksort($outrules[$ip], SORT_NUMERIC);
3613
				foreach ($outrules[$ip] as $outrule) {
3614
					$finalrules .= "{$outrule} {$state}\n";
3615
				}
3616
			}
3617
		}
3618
	}
3619
	return $finalrules;
3620
}
3621

    
3622
function alias_idn_to_utf8($alias) {
3623
	if (is_alias($alias)) {
3624
		return $alias;
3625
	} else {
3626
		return idn_to_utf8($alias);
3627
	}
3628
}
3629

    
3630
function alias_idn_to_ascii($alias) {
3631
	if (is_alias($alias)) {
3632
		return $alias;
3633
	} else {
3634
		return idn_to_ascii($alias);
3635
	}
3636
}
3637

    
3638
// These funtions were in guiconfig.inc but have been moved here so non GUI processes can use them
3639
function address_to_pconfig($adr, &$padr, &$pmask, &$pnot, &$pbeginport, &$pendport) {
3640
	if (isset($adr['any'])) {
3641
		$padr = "any";
3642
	} else if ($adr['network']) {
3643
		$padr = $adr['network'];
3644
	} else if ($adr['address']) {
3645
		list($padr, $pmask) = explode("/", $adr['address']);
3646
		if (!$pmask) {
3647
			if (is_ipaddrv6($padr)) {
3648
				$pmask = 128;
3649
			} else {
3650
				$pmask = 32;
3651
			}
3652
		}
3653
	}
3654

    
3655
	if (isset($adr['not'])) {
3656
		$pnot = 1;
3657
	} else {
3658
		$pnot = 0;
3659
	}
3660

    
3661
	if ($adr['port']) {
3662
		list($pbeginport, $pendport) = explode("-", $adr['port']);
3663
		if (!$pendport) {
3664
			$pendport = $pbeginport;
3665
		}
3666
	} else if (!is_alias($pbeginport) && !is_alias($pendport)) {
3667
		$pbeginport = "any";
3668
		$pendport = "any";
3669
	}
3670
}
3671

    
3672
function pconfig_to_address(&$adr, $padr, $pmask, $pnot = false, $pbeginport = 0, $pendport = 0) {
3673
	$adr = array();
3674

    
3675
	if ($padr == "any") {
3676
		$adr['any'] = true;
3677
	} else if (is_specialnet($padr)) {
3678
		$adr['network'] = $padr;
3679
	} else {
3680
		$adr['address'] = $padr;
3681
		if (is_ipaddrv6($padr)) {
3682
			if ($pmask != 128) {
3683
				$adr['address'] .= "/" . $pmask;
3684
			}
3685
		} else {
3686
			if ($pmask != 32) {
3687
				$adr['address'] .= "/" . $pmask;
3688
			}
3689
		}
3690
	}
3691

    
3692
	if ($pnot) {
3693
		$adr['not'] = true;
3694
	} else {
3695
		unset($adr['not']);
3696
	}
3697

    
3698
	if (($pbeginport != 0) && ($pbeginport != "any")) {
3699
		if ($pbeginport != $pendport) {
3700
			$adr['port'] = $pbeginport . "-" . $pendport;
3701
		} else {
3702
			$adr['port'] = $pbeginport;
3703
		}
3704
	}
3705

    
3706
	/*
3707
	 * If the port is still unset, then it must not be numeric, but could
3708
	 * be an alias or a well-known/registered service.
3709
	 * See https://redmine.pfsense.org/issues/8410
3710
	 */
3711
	if (!isset($adr['port']) && is_port_or_alias($pbeginport)) {
3712
		$adr['port'] = $pbeginport;
3713
	}
3714
}
3715

    
3716
function is_specialnet($net) {
3717
	global $specialsrcdst;
3718

    
3719
	if (!$net) {
3720
		return false;
3721
	}
3722
	if (in_array($net, $specialsrcdst)) {
3723
		return true;
3724
	} else {
3725
		return false;
3726
	}
3727
}
3728
?>
(53-53/61)