Project

General

Profile

Download (94.8 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 <[email protected]>.
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 = '') {
2653
	if (!empty($ipprotocol)) {
2654
		$family = $ipprotocol;
2655
	} else if (is_v4($target)) {
2656
		$family = 'inet';
2657
	} else if (is_v6($target)) {
2658
		$family = 'inet6';
2659
	}
2660

    
2661
	if (empty($family)) {
2662
		return array();
2663
	}
2664

    
2665
	$rtable = route_table();
2666

    
2667
	if (empty($rtable)) {
2668
		return array();
2669
	}
2670

    
2671
	$result = array();
2672
	foreach ($rtable[$family] as $item) {
2673
		if ($item['destination'] == $target ||
2674
		    ip_in_subnet($target, $item['destination'])) {
2675
			$result[] = $item;
2676
		}
2677
	}
2678

    
2679
	return $result;
2680
}
2681

    
2682
/* Get default route */
2683
function route_get_default($ipprotocol) {
2684
	if (empty($ipprotocol) || ($ipprotocol != 'inet' &&
2685
	    $ipprotocol != 'inet6')) {
2686
		return '';
2687
	}
2688

    
2689
	$route = route_get('default', $ipprotocol);
2690

    
2691
	if (empty($route)) {
2692
		return '';
2693
	}
2694

    
2695
	if (!isset($route[0]['gateway'])) {
2696
		return '';
2697
	}
2698

    
2699
	return $route[0]['gateway'];
2700
}
2701

    
2702
/* Delete a static route */
2703
function route_del($target, $ipprotocol = '') {
2704
	global $config;
2705

    
2706
	if (empty($target)) {
2707
		return;
2708
	}
2709

    
2710
	if (!empty($ipprotocol) && $ipprotocol != 'inet' &&
2711
	    $ipprotocol != 'inet6') {
2712
		return false;
2713
	}
2714

    
2715
	$route = route_get($target, $ipprotocol);
2716

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

    
2721
	$target_prefix = '';
2722
	if (is_subnet($target)) {
2723
		$target_prefix = '-net';
2724
	} else if (is_ipaddr($target)) {
2725
		$target_prefix = '-host';
2726
	}
2727

    
2728
	if (!empty($ipprotocol)) {
2729
		$target_prefix .= " -{$ipprotocol}";
2730
	} else if (is_v6($target)) {
2731
		$target_prefix .= ' -inet6';
2732
	} else if (is_v4($target)) {
2733
		$target_prefix .= ' -inet';
2734
	}
2735

    
2736
	foreach ($route as $item) {
2737
		if (substr($item['gateway'], 0, 5) == 'link#') {
2738
			continue;
2739
		}
2740

    
2741
		if (is_macaddr($item['gateway'])) {
2742
			$gw = '-iface ' . $item['interface-name'];
2743
		} else {
2744
			$gw = $item['gateway'];
2745
		}
2746

    
2747
		$_gb = exec(escapeshellcmd("/sbin/route del {$target_prefix} " .
2748
		    "{$target} {$gw}"), $output, $rc);
2749

    
2750
		if (isset($config['system']['route-debug'])) {
2751
			log_error("ROUTING debug: " . microtime() .
2752
			    " - DEL RC={$rc} - {$target} - gw: " . $gw);
2753
			file_put_contents("/dev/console", "\n[" . getmypid() .
2754
			    "] ROUTE DEL: {$target_prefix} {$target} {$gw} " .
2755
			    "result: {$rc}");
2756
		}
2757
	}
2758
}
2759

    
2760
/*
2761
 * Add static route.  If it already exists, remove it and re-add
2762
 *
2763
 * $target - IP, subnet or 'default'
2764
 * $gw     - gateway address
2765
 * $iface  - Network interface
2766
 * $args   - Extra arguments for /sbin/route
2767
 * $ipprotocol - 'inet' or 'inet6'.  Mandatory when $target == 'default'
2768
 *
2769
 */
2770
function route_add_or_change($target, $gw, $iface = '', $args = '',
2771
    $ipprotocol = '') {
2772
	global $config;
2773

    
2774
	if (empty($target) || (empty($gw) && empty($iface))) {
2775
		return false;
2776
	}
2777

    
2778
	if ($target == 'default' && empty($ipprotocol)) {
2779
		return false;
2780
	}
2781

    
2782
	if (!empty($ipprotocol) && $ipprotocol != 'inet' &&
2783
	    $ipprotocol != 'inet6') {
2784
		return false;
2785
	}
2786

    
2787
	/* use '-host' for IPv6 /128 routes, see https://redmine.pfsense.org/issues/11594 */
2788
	if (is_subnetv4($target) || (is_subnetv6($target) && (subnet_size($target) > 1))) {
2789
		$target_prefix = '-net';
2790
	} else if (is_ipaddr($target)) {
2791
		$target_prefix = '-host';
2792
	}
2793

    
2794
	if (!empty($ipprotocol)) {
2795
		$target_prefix .= " -{$ipprotocol}";
2796
	} else if (is_v6($target)) {
2797
		$target_prefix .= ' -inet6';
2798
	} else if (is_v4($target)) {
2799
		$target_prefix .= ' -inet';
2800
	}
2801

    
2802
	/* If there is another route to the same target, remove it */
2803
	route_del($target, $ipprotocol);
2804

    
2805
	$params = '';
2806
	if (!empty($iface) && does_interface_exist($iface)) {
2807
		$params .= " -iface {$iface}";
2808
	}
2809
	if (is_ipaddr($gw)) {
2810
		$params .= " " . $gw;
2811
	}
2812

    
2813
	if (empty($params)) {
2814
		log_error("route_add_or_change: Invalid gateway {$gw} and/or " .
2815
		    "network interface {$iface}");
2816
		return false;
2817
	}
2818

    
2819
	$_gb = exec(escapeshellcmd("/sbin/route add {$target_prefix} " .
2820
	    "{$target} {$args} {$params}"), $output, $rc);
2821

    
2822
	if (isset($config['system']['route-debug'])) {
2823
		log_error("ROUTING debug: " . microtime() .
2824
		    " - ADD RC={$rc} - {$target} {$args}");
2825
		file_put_contents("/dev/console", "\n[" . getmypid() .
2826
		    "] ROUTE ADD: {$target_prefix} {$target} {$args} " .
2827
		    "{$params} result: {$rc}");
2828
	}
2829

    
2830
	return ($rc == 0);
2831
}
2832

    
2833
function set_ipv6routes_mtu($interface, $mtu) {
2834
	global $config, $g;
2835

    
2836
	$ipv6mturoutes = array();
2837
	$if = convert_real_interface_to_friendly_interface_name($interface);
2838
	if (!$config['interfaces'][$if]['ipaddrv6']) {
2839
		return;
2840
	}
2841
	$a_gateways = return_gateways_array();
2842
	$a_staticroutes = get_staticroutes(false, false, true);
2843
	foreach ($a_gateways as $gate) {
2844
		foreach ($a_staticroutes as $sroute) {
2845
			if ($gate['interface'] == $interface &&
2846
			    $sroute['gateway'] == $gate['name']) {
2847
				$tgt = $sroute['network'];
2848
				$gateway = $gate['gateway'];
2849
				$ipv6mturoutes[$tgt] = $gateway;
2850
			}
2851
		}
2852
		if ($gate['interface'] == $interface &&
2853
		    $gate['isdefaultgw']) {
2854
			$tgt = "default";
2855
			$gateway = $gate['gateway'];
2856
			$ipv6mturoutes[$tgt] = $gateway;
2857
		}
2858
	}
2859
	foreach ($ipv6mturoutes as $tgt => $gateway) {
2860
		mwexec("/sbin/route change -6 -mtu " . escapeshellarg($mtu) .
2861
		    " " . escapeshellarg($tgt) . " " .
2862
		    escapeshellarg($gateway));
2863
	}
2864
}
2865

    
2866
function alias_to_subnets_recursive($name, $returnhostnames = false) {
2867
	global $aliastable;
2868
	$result = array();
2869
	if (!isset($aliastable[$name])) {
2870
		return $result;
2871
	}
2872
	$subnets = preg_split('/\s+/', $aliastable[$name]);
2873
	foreach ($subnets as $net) {
2874
		if (is_alias($net)) {
2875
			$sub = alias_to_subnets_recursive($net, $returnhostnames);
2876
			$result = array_merge($result, $sub);
2877
			continue;
2878
		} elseif (!is_subnet($net)) {
2879
			if (is_ipaddrv4($net)) {
2880
				$net .= "/32";
2881
			} else if (is_ipaddrv6($net)) {
2882
				$net .= "/128";
2883
			} else if ($returnhostnames === false || !is_fqdn($net)) {
2884
				continue;
2885
			}
2886
		}
2887
		$result[] = $net;
2888
	}
2889
	return $result;
2890
}
2891

    
2892
function get_staticroutes($returnsubnetsonly = false, $returnhostnames = false, $returnenabledroutesonly = false) {
2893
	global $config, $aliastable;
2894

    
2895
	/* Bail if there are no routes, but return an array always so callers don't have to check. */
2896
	init_config_arr(array('staticroutes', 'route'));
2897
	if (empty($config['staticroutes']['route'])) {
2898
		return array();
2899
	}
2900

    
2901
	$allstaticroutes = array();
2902
	$allsubnets = array();
2903
	/* Loop through routes and expand aliases as we find them. */
2904
	foreach ($config['staticroutes']['route'] as $route) {
2905
		if ($returnenabledroutesonly && isset($route['disabled'])) {
2906
			continue;
2907
		}
2908

    
2909
		if (is_alias($route['network'])) {
2910
			foreach (alias_to_subnets_recursive($route['network'], $returnhostnames) as $net) {
2911
				$temproute = $route;
2912
				$temproute['network'] = $net;
2913
				$allstaticroutes[] = $temproute;
2914
				$allsubnets[] = $net;
2915
			}
2916
		} elseif (is_subnet($route['network'])) {
2917
			$allstaticroutes[] = $route;
2918
			$allsubnets[] = $route['network'];
2919
		}
2920
	}
2921
	if ($returnsubnetsonly) {
2922
		return $allsubnets;
2923
	} else {
2924
		return $allstaticroutes;
2925
	}
2926
}
2927

    
2928
/****f* util/get_alias_list
2929
 * NAME
2930
 *   get_alias_list - Provide a list of aliases.
2931
 * INPUTS
2932
 *   $type          - Optional, can be a string or array specifying what type(s) of aliases you need.
2933
 * RESULT
2934
 *   Array containing list of aliases.
2935
 *   If $type is unspecified, all aliases are returned.
2936
 *   If $type is a string, all aliases of the type specified in $type are returned.
2937
 *   If $type is an array, all aliases of any type specified in any element of $type are returned.
2938
 */
2939
function get_alias_list($type = null) {
2940
	global $config;
2941
	$result = array();
2942
	if ($config['aliases']['alias'] <> "" && is_array($config['aliases']['alias'])) {
2943
		foreach ($config['aliases']['alias'] as $alias) {
2944
			if ($type === null) {
2945
				$result[] = $alias['name'];
2946
			} else if (is_array($type)) {
2947
				if (in_array($alias['type'], $type)) {
2948
					$result[] = $alias['name'];
2949
				}
2950
			} else if ($type === $alias['type']) {
2951
				$result[] = $alias['name'];
2952
			}
2953
		}
2954
	}
2955
	return $result;
2956
}
2957

    
2958
/* returns an array consisting of every element of $haystack that is not equal to $needle. */
2959
function array_exclude($needle, $haystack) {
2960
	$result = array();
2961
	if (is_array($haystack)) {
2962
		foreach ($haystack as $thing) {
2963
			if ($needle !== $thing) {
2964
				$result[] = $thing;
2965
			}
2966
		}
2967
	}
2968
	return $result;
2969
}
2970

    
2971
/* Define what is preferred, IPv4 or IPv6 */
2972
function prefer_ipv4_or_ipv6() {
2973
	global $config;
2974

    
2975
	if (isset($config['system']['prefer_ipv4'])) {
2976
		mwexec("/etc/rc.d/ip6addrctl prefer_ipv4");
2977
	} else {
2978
		mwexec("/etc/rc.d/ip6addrctl prefer_ipv6");
2979
	}
2980
}
2981

    
2982
/* Redirect to page passing parameters via POST */
2983
function post_redirect($page, $params) {
2984
	if (!is_array($params)) {
2985
		return;
2986
	}
2987

    
2988
	print "<html><body><form action=\"{$page}\" name=\"formredir\" method=\"post\">\n";
2989
	foreach ($params as $key => $value) {
2990
		print "<input type=\"hidden\" name=\"{$key}\" value=\"{$value}\" />\n";
2991
	}
2992
	print "</form>\n";
2993
	print "<script type=\"text/javascript\">\n";
2994
	print "//<![CDATA[\n";
2995
	print "document.formredir.submit();\n";
2996
	print "//]]>\n";
2997
	print "</script>\n";
2998
	print "</body></html>\n";
2999
}
3000

    
3001
/* Locate disks that can be queried for S.M.A.R.T. data. */
3002
function get_smart_drive_list() {
3003
	/* SMART supports some disks directly, and some controllers directly,
3004
	 * See https://redmine.pfsense.org/issues/9042 */
3005
	$supported_disk_types = array("ad", "da", "ada");
3006
	$supported_controller_types = array("nvme");
3007
	$disk_list = explode(" ", get_single_sysctl("kern.disks"));
3008
	foreach ($disk_list as $id => $disk) {
3009
		// We only want certain kinds of disks for S.M.A.R.T.
3010
		// 1 is a match, 0 is no match, False is any problem processing the regex
3011
		if (preg_match("/^(" . implode("|", $supported_disk_types) . ").*[0-9]{1,2}$/", $disk) !== 1) {
3012
			unset($disk_list[$id]);
3013
			continue;
3014
		}
3015
	}
3016
	foreach ($supported_controller_types as $controller) {
3017
		$devices = glob("/dev/{$controller}*");
3018
		if (!is_array($devices)) {
3019
			continue;
3020
		}
3021
		foreach ($devices as $device) {
3022
			$disk_list[] = basename($device);
3023
		}
3024
	}
3025
	sort($disk_list);
3026
	return $disk_list;
3027
}
3028

    
3029
// Validate a network address
3030
//	$addr: the address to validate
3031
//	$type: IPV4|IPV6|IPV4V6
3032
//	$label: the label used by the GUI to display this value. Required to compose an error message
3033
//	$err_msg: pointer to the callers error message array so that error messages can be added to it here
3034
//	$alias: are aliases permitted for this address?
3035
// Returns:
3036
//	IPV4 - if $addr is a valid IPv4 address
3037
//	IPV6 - if $addr is a valid IPv6 address
3038
//	ALIAS - if $alias=true and $addr is an alias
3039
//	false - otherwise
3040

    
3041
function validateipaddr(&$addr, $type, $label, &$err_msg, $alias=false) {
3042
	switch ($type) {
3043
		case IPV4:
3044
			if (is_ipaddrv4($addr)) {
3045
				return IPV4;
3046
			} else if ($alias) {
3047
				if (is_alias($addr)) {
3048
					return ALIAS;
3049
				} else {
3050
					$err_msg[] = sprintf(gettext("%s must be a valid IPv4 address or alias."), $label);
3051
					return false;
3052
				}
3053
			} else {
3054
				$err_msg[] = sprintf(gettext("%s must be a valid IPv4 address."), $label);
3055
				return false;
3056
			}
3057
		break;
3058
		case IPV6:
3059
			if (is_ipaddrv6($addr)) {
3060
				$addr = strtolower($addr);
3061
				return IPV6;
3062
			} else if ($alias) {
3063
				if (is_alias($addr)) {
3064
					return ALIAS;
3065
				} else {
3066
					$err_msg[] = sprintf(gettext("%s must be a valid IPv6 address or alias."), $label);
3067
					return false;
3068
				}
3069
			} else {
3070
				$err_msg[] = sprintf(gettext("%s must be a valid IPv6 address."), $label);
3071
				return false;
3072
			}
3073
		break;
3074
		case IPV4V6:
3075
			if (is_ipaddrv6($addr)) {
3076
				$addr = strtolower($addr);
3077
				return IPV6;
3078
			} else if (is_ipaddrv4($addr)) {
3079
				return IPV4;
3080
			} else if ($alias) {
3081
				if (is_alias($addr)) {
3082
					return ALIAS;
3083
				} else {
3084
					$err_msg[] = sprintf(gettext("%s must be a valid IPv4 or IPv6 address or alias."), $label);
3085
					return false;
3086
				}
3087
			} else {
3088
				$err_msg[] = sprintf(gettext("%s must be a valid IPv4 or IPv6 address."), $label);
3089
				return false;
3090
			}
3091
		break;
3092
	}
3093

    
3094
	return false;
3095
}
3096

    
3097
/* From DUID configuration inputs, format a string that looks (more) like the expected raw DUID format:
3098
 * 1) For DUIDs entered as a known DUID type, convert to a hexstring and prepend the DUID number, after having done the following:
3099
 *     a) For DUID-LLT and DUID-EN, convert the time/enterprise ID input to hex and append the link-layer address/identifier input.
3100
 *     b) For DUID-LLT and DUID-LL, prepend a hardware type of 1.
3101
 *     c) For DUID-UUID, remove any "-".
3102
 * 2) Replace any remaining "-" with ":".
3103
 * 3) If any components are input with just a single char (hex digit hopefully), put a "0" in front.
3104
 * 4) The first two components should be a 16-bit integer (little- or big-endian, depending on the current machine type) that
3105
 *    is equal to the number of other components. If not, prepend this as "nn:00" (all pfSense builds are little-endian).
3106
 *    This is convenience, because the DUID reported by dhcp6c in logs does not include this count, which corresponds to the
3107
 *    option-len field of DHCPv6's OPTION_CLIENTID option.
3108
 *
3109
 * The final result should be closer to:
3110
 *
3111
 * "nn:00:00:0n:nn:nn:nn:..."
3112
 *
3113
 * This function does not validate the input. is_duid() will do validation.
3114
 */
3115
function format_duid($duidtype, $duidpt1, $duidpt2=null) {
3116
	if ($duidpt2)
3117
		$duidpt1 = implode(':', str_split(str_pad(dechex($duidpt1), 8, '0', STR_PAD_LEFT), 2)) . ':' . $duidpt2;
3118

    
3119
	/* Make hexstrings */
3120
	if ($duidtype) {
3121
		switch ($duidtype) {
3122
		/* Add a hardware type to DUID-LLT and DUID-LL; assume Ethernet */
3123
		case 1:
3124
		case 3:
3125
			$duidpt1 = '00:01:' . $duidpt1;
3126
			break;
3127
		/* Remove '-' from given UUID and insert ':' every 2 characters */
3128
		case 4:
3129
			$duidpt1 = implode(':', str_split(str_replace('-', '', $duidpt1), 2));
3130
			break;
3131
		default:
3132
		}
3133
		$duidpt1 = '00:0' . $duidtype . ':' . $duidpt1;
3134
	}
3135

    
3136
	$values = explode(':', strtolower(str_replace('-', ':', $duidpt1)));
3137

    
3138
	if (hexdec($values[0]) != count($values) - 2)
3139
		array_unshift($values, dechex(count($values)), '00');
3140

    
3141
	array_walk($values, function(&$value) {
3142
		$value = str_pad($value, 2, '0', STR_PAD_LEFT);
3143
	});
3144

    
3145
	return implode(":", $values);
3146
}
3147

    
3148
/* Returns true if $dhcp6duid is a valid DUID entry.
3149
 * Parse the entry to check for valid length according to known DUID types.
3150
 */
3151
function is_duid($dhcp6duid) {
3152
	$values = explode(":", $dhcp6duid);
3153
	if (hexdec($values[0]) == count($values) - 2) {
3154
		switch (hexdec($values[2] . $values[3])) {
3155
		case 0:
3156
			return false;
3157
			break;
3158
		case 1:
3159
			if (count($values) != 16 || strlen($dhcp6duid) != 47)
3160
				return false;
3161
			break;
3162
		case 3:
3163
			if (count($values) != 12 || strlen($dhcp6duid) != 35)
3164
				return false;
3165
			break;
3166
		case 4:
3167
			if (count($values) != 20 || strlen($dhcp6duid) != 59)
3168
				return false;
3169
			break;
3170
		/* DUID is up to 128 octets; allow 2 octets for type code, 2 more for option-len */
3171
		default:
3172
			if (count($values) > 132 || strlen($dhcp6duid) != count($values) * 3 - 1)
3173
				return false;
3174
		}
3175
	} else
3176
		return false;
3177

    
3178
	for ($i = 0; $i < count($values); $i++) {
3179
		if (ctype_xdigit($values[$i]) == false)
3180
			return false;
3181
		if (hexdec($values[$i]) < 0 || hexdec($values[$i]) > 255)
3182
			return false;
3183
	}
3184

    
3185
	return true;
3186
}
3187

    
3188
/* Write the DHCP6 DUID file */
3189
function write_dhcp6_duid($duidstring) {
3190
	// Create the hex array from the dhcp6duid config entry and write to file
3191
	global $g;
3192

    
3193
	if(!is_duid($duidstring)) {
3194
		log_error(gettext("Error: attempting to write DUID file - Invalid DUID detected"));
3195
		return false;
3196
	}
3197
	$temp = str_replace(":","",$duidstring);
3198
	$duid_binstring = pack("H*",$temp);
3199
	if ($fd = fopen("{$g['vardb_path']}/dhcp6c_duid", "wb")) {
3200
		fwrite($fd, $duid_binstring);
3201
		fclose($fd);
3202
		return true;
3203
	}
3204
	log_error(gettext("Error: attempting to write DUID file - File write error"));
3205
	return false;
3206
}
3207

    
3208
/* returns duid string from 'vardb_path']}/dhcp6c_duid' */
3209
function get_duid_from_file() {
3210
	global $g;
3211

    
3212
	$duid_ASCII = "";
3213
	$count = 0;
3214

    
3215
	if (file_exists("{$g['vardb_path']}/dhcp6c_duid") &&
3216
	    ($fd = fopen("{$g['vardb_path']}/dhcp6c_duid", "r"))) {
3217
		$fsize = filesize("{$g['vardb_path']}/dhcp6c_duid");
3218
		if ($fsize <= 132) {
3219
			$buffer = fread($fd, $fsize);
3220
			while($count < $fsize) {
3221
				$duid_ASCII .= bin2hex($buffer[$count]);
3222
				$count++;
3223
				if($count < $fsize) {
3224
					$duid_ASCII .= ":";
3225
				}
3226
			}
3227
		}
3228
		fclose($fd);
3229
	}
3230
	//if no file or error with read then the string returns blanked DUID string
3231
	if(!is_duid($duid_ASCII)) {
3232
		return "--:--:--:--:--:--:--:--:--:--:--:--:--:--:--:--";
3233
	}
3234
	return($duid_ASCII);
3235
}
3236

    
3237
/* Replaces the Mac OS 9 and earlier (\r) and DOS/Windows (\r\n) newlines with the Unix equivalent (\n). */
3238
function unixnewlines($text) {
3239
	return preg_replace('/\r\n?/', "\n", $text);
3240
}
3241

    
3242
function array_remove_duplicate($array, $field) {
3243
	foreach ($array as $sub) {
3244
		if (isset($sub[$field])) {
3245
			$cmp[] = $sub[$field];
3246
		}
3247
	}
3248
	$unique = array_unique(array_reverse($cmp, true));
3249
	foreach ($unique as $k => $rien) {
3250
		$new[] = $array[$k];
3251
	}
3252
	return $new;
3253
}
3254

    
3255
function dhcpd_date_adjust_gmt($dt) {
3256
	global $config;
3257

    
3258
	init_config_arr(array('dhcpd'));
3259

    
3260
	foreach ($config['dhcpd'] as $dhcpditem) {
3261
		if ($dhcpditem['dhcpleaseinlocaltime'] == "yes") {
3262
			$ts = strtotime($dt . " GMT");
3263
			if ($ts !== false) {
3264
				return strftime("%Y/%m/%d %H:%M:%S", $ts);
3265
			}
3266
		}
3267
	}
3268

    
3269
	/*
3270
	 * If we did not need to convert to local time or the conversion
3271
	 * failed, just return the input.
3272
	 */
3273
	return $dt;
3274
}
3275

    
3276
global $supported_image_types;
3277
$supported_image_types = array(
3278
	IMAGETYPE_JPEG,
3279
	IMAGETYPE_PNG,
3280
	IMAGETYPE_GIF,
3281
	IMAGETYPE_WEBP
3282
);
3283

    
3284
function is_supported_image($image_filename) {
3285
	global $supported_image_types;
3286
	$img_info = getimagesize($image_filename);
3287

    
3288
	/* If it's not an image, or it isn't in the supported list, return false */
3289
	if (($img_info === false) ||
3290
	    !in_array($img_info[2], array_keys($supported_image_types))) {
3291
		return false;
3292
	} else {
3293
		return $img_info[2];
3294
	}
3295
}
3296

    
3297
function get_lagg_ports ($laggport) {
3298
	$laggp = array();
3299
	foreach ($laggport as $lgp) {
3300
		list($lpname, $lpinfo) = explode(" ", $lgp);
3301
		preg_match('~<(.+)>~', $lpinfo, $lgportmode);
3302
		if ($lgportmode[1]) {
3303
			$laggp[] = $lpname . " (" . $lgportmode[1] . ")";
3304
		} else {
3305
			$laggp[] = $lpname;
3306
		}
3307
	}
3308
	if ($laggp) {
3309
		return implode(", ", $laggp);
3310
	} else {
3311
		return false;
3312
	}
3313
}
3314

    
3315
function cisco_to_cidr($addr) {
3316
	if (!is_ipaddr($addr)) {
3317
		throw new Exception('Invalid IP Addr');
3318
	}
3319

    
3320
	$mask = decbin(~ip2long($addr));
3321
	$mask = substr($mask, -32);
3322
	$k = 0;
3323
	for ($i = 0; $i <= 32; $i++) {
3324
		$k += intval($mask[$i]);
3325
	}
3326
	return $k;
3327
}
3328

    
3329
function cisco_extract_index($prule) {
3330
	$index = explode("#", $prule);
3331
	if (is_numeric($index[1])) {
3332
		return intval($index[1]);
3333
	} else {
3334
		syslog(LOG_WARNING, "Error parsing rule {$prule}: Could not extract index");
3335
	}
3336
	return -1;;
3337
}
3338

    
3339
function parse_cisco_acl_rule($rule, $devname, $dir, $proto) {
3340
	$rule_orig = $rule;
3341
	$rule = explode(" ", $rule);
3342
	$tmprule = "";
3343
	$index = 0;
3344

    
3345
	if ($rule[$index] == "permit") {
3346
		$startrule = "pass {$dir} quick on {$devname} ";
3347
	} else if ($rule[$index] == "deny") {
3348
		$startrule = "block {$dir} quick on {$devname} ";
3349
	} else {
3350
		return;
3351
	}
3352

    
3353
	$index++;
3354

    
3355
	switch ($rule[$index]) {
3356
		case "ip":
3357
			break;
3358
		case "icmp":
3359
			$icmp = ($proto == "inet") ? "icmp" : "ipv6-icmp";
3360
			$tmprule .= "proto {$icmp} ";
3361
			break;
3362
		case "tcp":
3363
		case "udp":
3364
			$tmprule .= "proto {$rule[$index]} ";
3365
			break;
3366
		default:
3367
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid protocol.");
3368
			return;
3369
	}
3370
	$index++;
3371

    
3372
	/* Source */
3373
	if (trim($rule[$index]) == "host") {
3374
		$index++;
3375
		if ((is_ipaddrv4(trim($rule[$index])) && ($proto == "inet")) ||
3376
		    (is_ipaddrv6(trim($rule[$index])) && ($proto == "inet6")) ||
3377
		    (trim($rule[$index]) == "{clientip}")) {
3378
			$tmprule .= "from {$rule[$index]} ";
3379
			$index++;
3380
		} else {
3381
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source host '{$rule[$index]}'.");
3382
			return;
3383
		}
3384
	} elseif (is_subnetv6(trim($rule[$index])) && ($proto == "inet6")) {
3385
		$tmprule .= "from {$rule[$index]} ";
3386
		$index++;
3387
	} elseif (trim($rule[$index]) == "any") {
3388
		$tmprule .= "from any ";
3389
		$index++;
3390
	} else {
3391
		$network = $rule[$index];
3392
		$netmask = $rule[++$index];
3393

    
3394
		if (is_ipaddrv4($network) && ($proto == "inet")) {
3395
			try {
3396
				$netmask = cisco_to_cidr($netmask);
3397
			} catch(Exception $e) {
3398
				syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source netmask '$netmask'.");
3399
				return;
3400
			}
3401
			$tmprule .= "from {$network}/{$netmask} ";
3402
		} else {
3403
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source network '$network'.");
3404
			return;
3405
		}
3406

    
3407
		$index++;
3408
	}
3409

    
3410
	/* Source Operator */
3411
	if (in_array(trim($rule[$index]), array("lt", "gt", "eq", "neq"))) {
3412
		switch(trim($rule[$index])) {
3413
			case "lt":
3414
				$operator = "<";
3415
				break;
3416
			case "gt":
3417
				$operator = ">";
3418
				break;
3419
			case "eq":
3420
				$operator = "=";
3421
				break;
3422
			case "neq":
3423
				$operator = "!=";
3424
				break;
3425
		}
3426

    
3427
		$port = $rule[++$index];
3428
		if (is_port($port)) {
3429
			$tmprule .= "port {$operator} {$port} ";
3430
		} else {
3431
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source port: '$port' not a numeric value between 0 and 65535.");
3432
			return;
3433
		}
3434
		$index++;
3435
	} else if (trim($rule[$index]) == "range") {
3436
		$port = array($rule[++$index], $rule[++$index]);
3437
		if (is_port($port[0]) && is_port($port[1])) {
3438
			$port[0]--;
3439
			$port[1]++;
3440
			$tmprule .= "port {$port[0]} >< {$port[1]} ";
3441
		} else {
3442
			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.");
3443
			return;
3444
		}
3445
		$index++;
3446
	}
3447

    
3448
	/* Destination */
3449
	if (trim($rule[$index]) == "host") {
3450
		$index++;
3451
		if ((is_ipaddrv4(trim($rule[$index])) && ($proto == "inet")) ||
3452
		    (is_ipaddrv6(trim($rule[$index])) && ($proto == "inet6")) ||
3453
		    (trim($rule[$index]) == "{clientip}")) {
3454
			$tmprule .= "to {$rule[$index]} ";
3455
			$index++;
3456
		} else {
3457
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination host '{$rule[$index]}'.");
3458
			return;
3459
		}
3460
	} elseif (is_subnetv6(trim($rule[$index])) && ($proto == "inet6")) {
3461
		$tmprule .= "to {$rule[$index]} ";
3462
		$index++;
3463
	} elseif (trim($rule[$index]) == "any") {
3464
		$tmprule .= "to any ";
3465
		$index++;
3466
	} else {
3467
		$network = $rule[$index];
3468
		$netmask = $rule[++$index];
3469

    
3470
		if (is_ipaddrv4($network) && ($proto == "inet")) {
3471
			try {
3472
				$netmask = cisco_to_cidr($netmask);
3473
			} catch(Exception $e) {
3474
				syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination network '$network'.");
3475
				return;
3476
			}
3477
			$tmprule .= "to {$network}/{$netmask} ";
3478
		} else {
3479
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination network '$network'.");
3480
			return;
3481
		}
3482

    
3483
		$index++;
3484
	}
3485

    
3486
	/* Destination Operator */
3487
	if (in_array(trim($rule[$index]), array("lt", "gt", "eq", "neq"))) {
3488
		switch(trim($rule[$index])) {
3489
			case "lt":
3490
				$operator = "<";
3491
				break;
3492
			case "gt":
3493
				$operator = ">";
3494
				break;
3495
			case "eq":
3496
				$operator = "=";
3497
				break;
3498
			case "neq":
3499
				$operator = "!=";
3500
				break;
3501
		}
3502

    
3503
		$port = $rule[++$index];
3504
		if (is_port($port)) {
3505
			$tmprule .= "port {$operator} {$port} ";
3506
		} else {
3507
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination port: '$port' not a numeric value between 0 and 65535.");
3508
			return;
3509
		}
3510
		$index++;
3511
	} else if (trim($rule[$index]) == "range") {
3512
		$port = array($rule[++$index], $rule[++$index]);
3513
		if (is_port($port[0]) && is_port($port[1])) {
3514
			$port[0]--;
3515
			$port[1]++;
3516
			$tmprule .= "port {$port[0]} >< {$port[1]} ";
3517
		} else {
3518
			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.");
3519
			return;
3520
		}
3521
		$index++;
3522
	}
3523

    
3524
	$tmprule = $startrule . $proto . " " . $tmprule;
3525
	return $tmprule;
3526
}
3527

    
3528
function parse_cisco_acl($attribs, $dev) {
3529
	global $attributes;
3530

    
3531
	if (!is_array($attribs)) {
3532
		return "";
3533
	}
3534
	$finalrules = "";
3535
	if (is_array($attribs['ciscoavpair'])) {
3536
		$inrules = array('inet' => array(), 'inet6' => array());
3537
		$outrules = array('inet' => array(), 'inet6' => array());
3538
		foreach ($attribs['ciscoavpair'] as $avrules) {
3539
			$rule = explode("=", $avrules);
3540
			$dir = "";
3541
			if (strstr($rule[0], "inacl")) {
3542
				$dir = "in";
3543
			} else if (strstr($rule[0], "outacl")) {
3544
				$dir = "out";
3545
			} else if (strstr($rule[0], "dns-servers")) {
3546
				$attributes['dns-servers'] = explode(" ", $rule[1]);
3547
				continue;
3548
			} else if (strstr($rule[0], "route")) {
3549
				if (!is_array($attributes['routes'])) {
3550
					$attributes['routes'] = array();
3551
				}
3552
				$attributes['routes'][] = $rule[1];
3553
				continue;
3554
			}
3555
			$rindex = cisco_extract_index($rule[0]);
3556
			if ($rindex < 0) {
3557
				continue;
3558
			}
3559

    
3560
			if (strstr($rule[0], "ipv6")) {
3561
				$proto = "inet6";
3562
			} else {
3563
				$proto = "inet";
3564
			}
3565

    
3566
			$tmprule = parse_cisco_acl_rule($rule[1], $dev, $dir, $proto);
3567

    
3568
			if ($dir == "in") {
3569
				$inrules[$proto][$rindex] = $tmprule;
3570
			} else if ($dir == "out") {
3571
				$outrules[$proto][$rindex] = $tmprule;
3572
			}
3573
		}
3574

    
3575

    
3576
		$state = "";
3577
		foreach (array('inet', 'inet6') as $ip) {
3578
			if (!empty($outrules[$ip])) {
3579
				$state = "no state";
3580
			}
3581
			ksort($inrules[$ip], SORT_NUMERIC);
3582
			foreach ($inrules[$ip] as $inrule) {
3583
				$finalrules .= "{$inrule} {$state}\n";
3584
			}
3585
			if (!empty($outrules[$ip])) {
3586
				ksort($outrules[$ip], SORT_NUMERIC);
3587
				foreach ($outrules[$ip] as $outrule) {
3588
					$finalrules .= "{$outrule} {$state}\n";
3589
				}
3590
			}
3591
		}
3592
	}
3593
	return $finalrules;
3594
}
3595

    
3596
function alias_idn_to_utf8($alias) {
3597
	if (is_alias($alias)) {
3598
		return $alias;
3599
	} else {
3600
		return idn_to_utf8($alias);
3601
	}
3602
}
3603

    
3604
function alias_idn_to_ascii($alias) {
3605
	if (is_alias($alias)) {
3606
		return $alias;
3607
	} else {
3608
		return idn_to_ascii($alias);
3609
	}
3610
}
3611

    
3612
?>
(53-53/61)
OSZAR »