Project

General

Profile

Download (118 KB) Statistics
| Branch: | Tag: | Revision:
1 86a5e1a8 Renato Botelho
<?php
2 417fc5c4 Scott Ullrich
/*
3 ac24dc24 Renato Botelho
 * util.inc
4
 *
5
 * part of pfSense (https://www.pfsense.org)
6 38809d47 Renato Botelho do Couto
 * Copyright (c) 2004-2013 BSD Perimeter
7
 * Copyright (c) 2013-2016 Electric Sheep Fencing
8 402c98a2 Reid Linnemann
 * Copyright (c) 2014-2023 Rubicon Communications, LLC (Netgate)
9 ac24dc24 Renato Botelho
 * All rights reserved.
10
 *
11
 * originally part of m0n0wall (http://m0n0.ch/wall)
12 c5d81585 Renato Botelho
 * Copyright (c) 2003-2004 Manuel Kasper <mk@neon1.net>.
13 ac24dc24 Renato Botelho
 * All rights reserved.
14
 *
15 b12ea3fb Renato Botelho
 * 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 ac24dc24 Renato Botelho
 *
19 b12ea3fb Renato Botelho
 * http://www.apache.org/licenses/LICENSE-2.0
20 ac24dc24 Renato Botelho
 *
21 b12ea3fb Renato Botelho
 * 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 995df6c3 Stephen Beaver
 */
27 eec3ca7f Reid Linnemann
//require_once('interfaces.inc');
28 dc337505 Reid Linnemann
require_once('Net/IPv6.php');
29 820562e8 NewEraCracker
define('VIP_ALL', 1);
30
define('VIP_CARP', 2);
31
define('VIP_IPALIAS', 3);
32 feefe2c3 Marcos Mendoza
// Special networks flags
33
define('SPECIALNET_NONE', 0);
34
define('SPECIALNET_ANY', 1);
35
define('SPECIALNET_COMPAT_ADDR', 22);
36
define('SPECIALNET_ADDR', 2);
37
define('SPECIALNET_COMPAT_ADDRAL', 33);
38
define('SPECIALNET_ADDRAL', 3);
39
define('SPECIALNET_NET', 4);
40
define('SPECIALNET_NETAL', 5);
41
define('SPECIALNET_SELF', 6);
42
define('SPECIALNET_CLIENTS', 7);
43
define('SPECIALNET_IFADDR', 8);
44
define('SPECIALNET_IFSUB', 9);
45
define('SPECIALNET_IFNET', 10);
46
define('SPECIALNET_GROUP', 11);
47
define('SPECIALNET_VIPS', 12);
48
define('SPECIALNET_CHECKPERM', 98);
49
define('SPECIALNET_EXCLUDE', 99);
50 ce94deb0 Luiz Otavio O Souza
51 5b237745 Scott Ullrich
/* kill a process by pid file */
52 66491555 PiBa-NL
function killbypid($pidfile, $waitfor = 0) {
53
	return sigkillbypid($pidfile, "TERM", $waitfor);
54 5b237745 Scott Ullrich
}
55
56 c4594e36 Phil Davis
function isvalidpid($pidfile) {
57 0e604b3a Ermal
	$output = "";
58 c4594e36 Phil Davis
	if (file_exists($pidfile)) {
59 b885c8cf Renato Botelho
		exec("/bin/pgrep -qnF {$pidfile} 2>/dev/null", $output, $retval);
60 c4594e36 Phil Davis
		return (intval($retval) == 0);
61
	}
62
	return false;
63 53aca1fd Scott Ullrich
}
64
65 6dc3a5c2 Ermal Lu?i
function is_process_running($process) {
66 01d4b621 Ermal
	$output = "";
67 050e18cf Viktor G
	if (!empty($process)) {
68
		exec("/bin/pgrep -anx " . escapeshellarg($process), $output, $retval);
69
		return (intval($retval) == 0);
70
	}
71
	return false;
72 6dc3a5c2 Ermal Lu?i
}
73
74 53aca1fd Scott Ullrich
function isvalidproc($proc) {
75 ba8495f0 Ermal
	return is_process_running($proc);
76 53aca1fd Scott Ullrich
}
77
78 66491555 PiBa-NL
/* sigkill a process by pid file, and wait for it to terminate or remove the .pid file for $waitfor seconds */
79 53aca1fd Scott Ullrich
/* return 1 for success and 0 for a failure */
80 66491555 PiBa-NL
function sigkillbypid($pidfile, $sig, $waitfor = 0) {
81 0b8b5069 Renato Botelho
	if (isvalidpid($pidfile)) {
82 66491555 PiBa-NL
		$result = mwexec("/bin/pkill " . escapeshellarg("-{$sig}") .
83 0b8b5069 Renato Botelho
		    " -F {$pidfile}", true);
84 66491555 PiBa-NL
		$waitcounter = $waitfor * 10;
85
		while(isvalidpid($pidfile) && $waitcounter > 0) {
86
			$waitcounter = $waitcounter - 1;
87
			usleep(100000);
88
		}
89
		return $result;
90 751533a2 Phil Davis
	}
91 ba8495f0 Ermal
92 53aca1fd Scott Ullrich
	return 0;
93
}
94
95
/* kill a process by name */
96
function sigkillbyname($procname, $sig) {
97 751533a2 Phil Davis
	if (isvalidproc($procname)) {
98 873c1701 Renato Botelho
		return mwexec("/usr/bin/killall " . escapeshellarg("-{$sig}") . " " . escapeshellarg($procname), true);
99 751533a2 Phil Davis
	}
100 5b237745 Scott Ullrich
}
101
102
/* kill a process by name */
103
function killbyname($procname) {
104 751533a2 Phil Davis
	if (isvalidproc($procname)) {
105 53aca1fd Scott Ullrich
		mwexec("/usr/bin/killall " . escapeshellarg($procname));
106 751533a2 Phil Davis
	}
107 5b237745 Scott Ullrich
}
108
109 a368a026 Ermal Lu?i
function is_subsystem_dirty($subsystem = "") {
110
	global $g;
111
112 751533a2 Phil Davis
	if ($subsystem == "") {
113 a368a026 Ermal Lu?i
		return false;
114 751533a2 Phil Davis
	}
115 a368a026 Ermal Lu?i
116 751533a2 Phil Davis
	if (file_exists("{$g['varrun_path']}/{$subsystem}.dirty")) {
117 a368a026 Ermal Lu?i
		return true;
118 751533a2 Phil Davis
	}
119 a368a026 Ermal Lu?i
120
	return false;
121
}
122
123
function mark_subsystem_dirty($subsystem = "") {
124
	global $g;
125
126 751533a2 Phil Davis
	if (!file_put_contents("{$g['varrun_path']}/{$subsystem}.dirty", "DIRTY")) {
127 fd7b47b6 Renato Botelho
		log_error(sprintf(gettext("WARNING: Could not mark subsystem: %s dirty"), $subsystem));
128 751533a2 Phil Davis
	}
129 a368a026 Ermal Lu?i
}
130
131
function clear_subsystem_dirty($subsystem = "") {
132
	global $g;
133
134
	@unlink("{$g['varrun_path']}/{$subsystem}.dirty");
135
}
136
137 c04144d1 Viktor G
function clear_filter_subsystems_dirty() {
138
	clear_subsystem_dirty('aliases');
139
	clear_subsystem_dirty('filter');
140
	clear_subsystem_dirty('natconf');
141
	clear_subsystem_dirty('shaper');
142
}
143
144 0027de0a Ermal Lu?i
/* lock configuration file */
145 b6c34bfc Ermal
function lock($lock, $op = LOCK_SH) {
146 42ea8f9a Renato Botelho
	global $g;
147 751533a2 Phil Davis
	if (!$lock) {
148 530e4707 NOYB
		die(gettext("WARNING: A name must be given as parameter to lock() function."));
149 751533a2 Phil Davis
	}
150 6bee76d5 Ermal
	if (!file_exists("{$g['tmp_path']}/{$lock}.lock")) {
151 9e7ef1a5 Scott Ullrich
		@touch("{$g['tmp_path']}/{$lock}.lock");
152 6bee76d5 Ermal
		@chmod("{$g['tmp_path']}/{$lock}.lock", 0666);
153
	}
154 b6c34bfc Ermal
	if ($fp = fopen("{$g['tmp_path']}/{$lock}.lock", "w")) {
155 751533a2 Phil Davis
		if (flock($fp, $op)) {
156 9e7ef1a5 Scott Ullrich
			return $fp;
157 751533a2 Phil Davis
		} else {
158 b6c34bfc Ermal
			fclose($fp);
159 751533a2 Phil Davis
		}
160 9e7ef1a5 Scott Ullrich
	}
161 0027de0a Ermal Lu?i
}
162
163 8171a2c2 Ermal
function try_lock($lock, $timeout = 5) {
164 42ea8f9a Renato Botelho
	global $g;
165 751533a2 Phil Davis
	if (!$lock) {
166 530e4707 NOYB
		die(gettext("WARNING: A name must be given as parameter to try_lock() function."));
167 751533a2 Phil Davis
	}
168 8171a2c2 Ermal
	if (!file_exists("{$g['tmp_path']}/{$lock}.lock")) {
169
		@touch("{$g['tmp_path']}/{$lock}.lock");
170
		@chmod("{$g['tmp_path']}/{$lock}.lock", 0666);
171
	}
172
	if ($fp = fopen("{$g['tmp_path']}/{$lock}.lock", "w")) {
173
		$trycounter = 0;
174 751533a2 Phil Davis
		while (!flock($fp, LOCK_EX | LOCK_NB)) {
175 8171a2c2 Ermal
			if ($trycounter >= $timeout) {
176
				fclose($fp);
177
				return NULL;
178
			}
179
			sleep(1);
180
			$trycounter++;
181
		}
182
183
		return $fp;
184
	}
185
186
	return NULL;
187
}
188
189 0027de0a Ermal Lu?i
/* unlock configuration file */
190 053f60e5 Christian McDonald
function unlock($cfglckkey = 0)
191
{
192
	if (!is_resource($cfglckkey))
193
		return;
194
195
	flock($cfglckkey, LOCK_UN);
196
	fclose($cfglckkey);
197 0027de0a Ermal Lu?i
}
198
199 8171a2c2 Ermal
/* unlock forcefully configuration file */
200
function unlock_force($lock) {
201
	global $g;
202
203
	@unlink("{$g['tmp_path']}/{$lock}.lock");
204
}
205
206 0ae6daf8 Ermal
function send_event($cmd) {
207
	global $g;
208
209 751533a2 Phil Davis
	if (!isset($g['event_address'])) {
210 1015b3a9 Warren Baker
		$g['event_address'] = "unix:///var/run/check_reload_status";
211 751533a2 Phil Davis
	}
212 86a5e1a8 Renato Botelho
213 838feb14 Ermal
	$try = 0;
214
	while ($try < 3) {
215 2568e151 Christian McDonald
		$fd = @fsockopen(g_get('event_address'));
216 838feb14 Ermal
		if ($fd) {
217
			fwrite($fd, $cmd);
218
			$resp = fread($fd, 4096);
219 751533a2 Phil Davis
			if ($resp != "OK\n") {
220 838feb14 Ermal
				log_error("send_event: sent {$cmd} got {$resp}");
221 751533a2 Phil Davis
			}
222 838feb14 Ermal
			fclose($fd);
223
			$try = 3;
224 751533a2 Phil Davis
		} else if (!is_process_running("check_reload_status")) {
225 838feb14 Ermal
			mwexec_bg("/usr/bin/nice -n20 /usr/local/sbin/check_reload_status");
226 751533a2 Phil Davis
		}
227 838feb14 Ermal
		$try++;
228 0ae6daf8 Ermal
	}
229
}
230
231
function send_multiple_events($cmds) {
232 1015b3a9 Warren Baker
	global $g;
233 0ae6daf8 Ermal
234 751533a2 Phil Davis
	if (!isset($g['event_address'])) {
235 1015b3a9 Warren Baker
		$g['event_address'] = "unix:///var/run/check_reload_status";
236 751533a2 Phil Davis
	}
237 86a5e1a8 Renato Botelho
238 751533a2 Phil Davis
	if (!is_array($cmds)) {
239 0ae6daf8 Ermal
		return;
240 751533a2 Phil Davis
	}
241 6e1f456f Ermal
242 dc337505 Reid Linnemann
	$try = 0;
243 915089b7 Ermal
	while ($try < 3) {
244 2568e151 Christian McDonald
		$fd = @fsockopen(g_get('event_address'));
245 915089b7 Ermal
		if ($fd) {
246
			foreach ($cmds as $cmd) {
247
				fwrite($fd, $cmd);
248
				$resp = fread($fd, 4096);
249 751533a2 Phil Davis
				if ($resp != "OK\n") {
250 915089b7 Ermal
					log_error("send_event: sent {$cmd} got {$resp}");
251 751533a2 Phil Davis
				}
252 915089b7 Ermal
			}
253
			fclose($fd);
254
			$try = 3;
255 751533a2 Phil Davis
		} else if (!is_process_running("check_reload_status")) {
256 915089b7 Ermal
			mwexec_bg("/usr/bin/nice -n20 /usr/local/sbin/check_reload_status");
257 751533a2 Phil Davis
		}
258 915089b7 Ermal
		$try++;
259
	}
260 0ae6daf8 Ermal
}
261
262 41b33b17 Christian McDonald
/**
263
 * Test if a kernel module exists on disk in well-known locations
264
 *
265
 * Locations:
266
 *   > /boot/kernel
267
 *   > /bood/modules
268
 *
269
 * @param string $module_name		The name of the kernel module
270
 * @param string ...$add_dirs		Additional directories to search
271
 *
272
 * @return bool
273
 */
274
function is_module_available(string $module_name, string ...$add_dirs): bool
275
{
276
	/* bail if module is already loaded */
277
	if (is_module_loaded($module_name)) {
278
		return (true);
279
	}
280
281
	/* ensure the $module_name ends with .ko */
282
	if (!str_ends_with($module_name, '.ko')) {
283
		$module_name .= '.ko';
284
	}
285
286
	/* build list of well-known locations */
287
	$mod_dirs = [
288
		'/boot/kernel',
289
		'/boot/modules',
290
		...$add_dirs
291
	];
292
293
	/* filter list of well-known locations and cast to bool */
294
	return ((bool) array_filter($mod_dirs, function($mod_dir) use ($module_name) {
295
		return (file_exists($mod_dir . '/' . $module_name));
296
	}));
297
}
298
299
/**
300
 * Test if a kernel module is loaded
301
 *
302
 * @param string $module_name	The name of the kernel module
303
 *
304
 * @param bool
305
 */
306
function is_module_loaded(string $module_name): bool
307
{
308
	/* sanitize input */
309
	$module_name = escapeshellarg(rtrim($module_name, '.ko'));
310
311
	$base_cmd = ['/sbin/kldstat', '-q'];
312
313
	/* first pass test by module name, tests for in-kernel modules */
314
	$cmd = implode(' ', [...$base_cmd, '-m', $module_name]);
315
	exec($cmd, $out, $rc);
316
317
	/* bailout early if we found it */
318
	if ($rc === 0) {
319
		return (true);
320 751533a2 Phil Davis
	}
321 41b33b17 Christian McDonald
322
	/* last pass test by file name */
323
	$cmd = implode(' ', [...$base_cmd, '-n', $module_name]);
324
	exec($cmd, $out, $rc);
325
326
	return ($rc === 0);
327 1ab56363 Ermal Lu?i
}
328
329 4caa9574 stilez
/* validate non-negative numeric string, or equivalent numeric variable */
330
function is_numericint($arg) {
331 28e2b611 jim-p
	return (((is_int($arg) && $arg >= 0) || (is_string($arg) && strlen($arg) > 0 && ctype_digit(strval($arg)))) ? true : false);
332 4caa9574 stilez
}
333
334 751533a2 Phil Davis
/* Generate the (human readable) ipv4 or ipv6 subnet address (i.e., netmask, or subnet start IP)
335 e89d2995 stilez
   given an (human readable) ipv4 or ipv6 host address and subnet bit count */
336 5b237745 Scott Ullrich
function gen_subnet($ipaddr, $bits) {
337 751533a2 Phil Davis
	if (($sn = gen_subnetv6($ipaddr, $bits)) == '') {
338 e89d2995 stilez
		$sn = gen_subnetv4($ipaddr, $bits);  // try to avoid rechecking IPv4/v6
339 751533a2 Phil Davis
	}
340 e89d2995 stilez
	return $sn;
341
}
342 3bad4691 Renato Botelho
343 e89d2995 stilez
/* same as gen_subnet() but accepts IPv4 only */
344
function gen_subnetv4($ipaddr, $bits) {
345
	if (is_ipaddrv4($ipaddr) && is_numericint($bits) && $bits <= 32) {
346 751533a2 Phil Davis
		if ($bits == 0) {
347 e89d2995 stilez
			return '0.0.0.0';  // avoids <<32
348 751533a2 Phil Davis
		}
349 e89d2995 stilez
		return long2ip(ip2long($ipaddr) & ((0xFFFFFFFF << (32 - $bits)) & 0xFFFFFFFF));
350
	}
351
	return "";
352 22b5abac Seth Mos
}
353
354 e89d2995 stilez
/* same as gen_subnet() but accepts IPv6 only */
355 22b5abac Seth Mos
function gen_subnetv6($ipaddr, $bits) {
356 751533a2 Phil Davis
	if (is_ipaddrv6($ipaddr) && is_numericint($bits) && $bits <= 128) {
357 587995fb Phil Davis
		return text_to_compressed_ip6(Net_IPv6::getNetmask($ipaddr, $bits));
358 751533a2 Phil Davis
	}
359 e89d2995 stilez
	return "";
360 5b237745 Scott Ullrich
}
361
362 3bad4691 Renato Botelho
/* Generate the (human readable) ipv4 or ipv6 subnet end address (i.e., highest address, end IP, or IPv4 broadcast address)
363 e89d2995 stilez
   given an (human readable) ipv4 or ipv6 host address and subnet bit count. */
364 5b237745 Scott Ullrich
function gen_subnet_max($ipaddr, $bits) {
365 751533a2 Phil Davis
	if (($sn = gen_subnetv6_max($ipaddr, $bits)) == '') {
366 e89d2995 stilez
		$sn = gen_subnetv4_max($ipaddr, $bits);  // try to avoid rechecking IPv4/v6
367 751533a2 Phil Davis
	}
368 e89d2995 stilez
	return $sn;
369
}
370 98bbf05a Scott Ullrich
371 e89d2995 stilez
/* same as gen_subnet_max() but validates IPv4 only */
372
function gen_subnetv4_max($ipaddr, $bits) {
373
	if (is_ipaddrv4($ipaddr) && is_numericint($bits) && $bits <= 32) {
374 751533a2 Phil Davis
		if ($bits == 32) {
375 e89d2995 stilez
			return $ipaddr;
376 751533a2 Phil Davis
		}
377 c18ba6bf Phil Davis
		return long2ip32(ip2long($ipaddr) | (~gen_subnet_mask_long($bits) & 0xFFFFFFFF));
378 e89d2995 stilez
	}
379
	return "";
380 5b237745 Scott Ullrich
}
381
382 e89d2995 stilez
/* same as gen_subnet_max() but validates IPv6 only */
383 c75a8185 Seth Mos
function gen_subnetv6_max($ipaddr, $bits) {
384 e89d2995 stilez
	if (is_ipaddrv6($ipaddr) && is_numericint($bits) && $bits <= 128) {
385 de645734 Renato Botelho
		$endip_bin = substr(ip6_to_bin($ipaddr), 0, $bits) . str_repeat('1', 128 - $bits);
386
		return bin_to_compressed_ip6($endip_bin);
387 e89d2995 stilez
	}
388
	return "";
389 c75a8185 Seth Mos
}
390
391 5b237745 Scott Ullrich
/* returns a subnet mask (long given a bit count) */
392
function gen_subnet_mask_long($bits) {
393
	$sm = 0;
394
	for ($i = 0; $i < $bits; $i++) {
395
		$sm >>= 1;
396
		$sm |= 0x80000000;
397
	}
398
	return $sm;
399
}
400
401
/* same as above but returns a string */
402
function gen_subnet_mask($bits) {
403
	return long2ip(gen_subnet_mask_long($bits));
404
}
405
406 31b15180 jim-p
/* Convert a prefix length to an IPv6 address-like mask notation. Very rare but at least ntp needs it. See #4463 */
407
function gen_subnet_mask_v6($bits) {
408
	/* Binary representation of the prefix length */
409
	$bin = str_repeat('1', $bits);
410
	/* Pad right with zeroes to reach the full address length */
411
	$bin = str_pad($bin, 128, '0', STR_PAD_RIGHT);
412
	/* Convert back to an IPv6 address style notation */
413 de645734 Renato Botelho
	return bin_to_ip6($bin);
414 31b15180 jim-p
}
415
416 ce9dc198 stilez
/* Convert long int to IPv4 address
417 f8a6c824 Chris Buechler
   Returns '' if not valid IPv4 (including if any bits >32 are non-zero) */
418 96033063 Erik Fonnesbeck
function long2ip32($ip) {
419 f8a6c824 Chris Buechler
	return long2ip($ip & 0xFFFFFFFF);
420
}
421 96033063 Erik Fonnesbeck
422 ce9dc198 stilez
/* Convert IPv4 address to long int, truncated to 32-bits to avoid sign extension on 64-bit platforms.
423
   Returns '' if not valid IPv4. */
424 96033063 Erik Fonnesbeck
function ip2long32($ip) {
425 f8a6c824 Chris Buechler
	return (ip2long($ip) & 0xFFFFFFFF);
426 96033063 Erik Fonnesbeck
}
427
428 ce9dc198 stilez
/* Convert IPv4 address to unsigned long int.
429
   Returns '' if not valid IPv4. */
430 ecd1f2d9 jim-p
function ip2ulong($ip) {
431 f8a6c824 Chris Buechler
	return sprintf("%u", ip2long32($ip));
432 ecd1f2d9 jim-p
}
433
434 de645734 Renato Botelho
/*
435
 * Convert IPv6 address to binary
436
 *
437
 * Obtained from: pear-Net_IPv6
438
 */
439
function ip6_to_bin($ip) {
440
	$binstr = '';
441
442
	$ip = Net_IPv6::removeNetmaskSpec($ip);
443
	$ip = Net_IPv6::Uncompress($ip);
444
445
	$parts = explode(':', $ip);
446
447
	foreach ( $parts as $v ) {
448
449
		$str     = base_convert($v, 16, 2);
450
		$binstr .= str_pad($str, 16, '0', STR_PAD_LEFT);
451
452
	}
453
454
	return $binstr;
455
}
456
457
/*
458
 * Convert IPv6 binary to uncompressed address
459
 *
460
 * Obtained from: pear-Net_IPv6
461
 */
462
function bin_to_ip6($bin) {
463
	$ip = "";
464
465
	if (strlen($bin) < 128) {
466
		$bin = str_pad($bin, 128, '0', STR_PAD_LEFT);
467
	}
468
469
	$parts = str_split($bin, "16");
470
471
	foreach ( $parts as $v ) {
472
		$str = base_convert($v, 2, 16);
473
		$ip .= $str.":";
474
	}
475
476
	$ip = substr($ip, 0, -1);
477
478
	return $ip;
479
}
480
481
/*
482
 * Convert IPv6 binary to compressed address
483
 */
484
function bin_to_compressed_ip6($bin) {
485 587995fb Phil Davis
	return text_to_compressed_ip6(bin_to_ip6($bin));
486
}
487
488
/*
489
 * Convert textual IPv6 address string to compressed address
490
 */
491
function text_to_compressed_ip6($text) {
492
	// Force re-compression by passing parameter 2 (force) true.
493
	// This ensures that supposedly-compressed formats are uncompressed
494
	// first then re-compressed into strictly correct form.
495
	// e.g. 2001:0:0:4:0:0:0:1
496
	// 2001::4:0:0:0:1 is a strictly-incorrect compression,
497
	// but maybe the user entered it like that.
498
	// The "force" parameter will ensure it is returned as:
499
	// 2001:0:0:4::1
500
	return Net_IPv6::compress($text, true);
501 de645734 Renato Botelho
}
502
503 ecd1f2d9 jim-p
/* Find out how many IPs are contained within a given IP range
504
 *  e.g. 192.168.0.0 to 192.168.0.255 returns 256
505
 */
506 bb67ac32 Phil Davis
function ip_range_size_v4($startip, $endip) {
507
	if (is_ipaddrv4($startip) && is_ipaddrv4($endip)) {
508 ecd1f2d9 jim-p
		// Operate as unsigned long because otherwise it wouldn't work
509
		//   when crossing over from 127.255.255.255 / 128.0.0.0 barrier
510
		return abs(ip2ulong($startip) - ip2ulong($endip)) + 1;
511
	}
512
	return -1;
513
}
514
515
/* Find the smallest possible subnet mask which can contain a given number of IPs
516
 *  e.g. 512 IPs can fit in a /23, but 513 IPs need a /22
517
 */
518 bb67ac32 Phil Davis
function find_smallest_cidr_v4($number) {
519 ecd1f2d9 jim-p
	$smallest = 1;
520
	for ($b=32; $b > 0; $b--) {
521 086cf944 Phil Davis
		$smallest = ($number <= pow(2, $b)) ? $b : $smallest;
522 ecd1f2d9 jim-p
	}
523
	return (32-$smallest);
524
}
525
526
/* Return the previous IP address before the given address */
527 aa181833 Phil Davis
function ip_before($ip, $offset = 1) {
528
	return long2ip32(ip2long($ip) - $offset);
529 ecd1f2d9 jim-p
}
530
531
/* Return the next IP address after the given address */
532 aa181833 Phil Davis
function ip_after($ip, $offset = 1) {
533
	return long2ip32(ip2long($ip) + $offset);
534 ecd1f2d9 jim-p
}
535
536
/* Return true if the first IP is 'before' the second */
537
function ip_less_than($ip1, $ip2) {
538
	// Compare as unsigned long because otherwise it wouldn't work when
539
	//   crossing over from 127.255.255.255 / 128.0.0.0 barrier
540
	return ip2ulong($ip1) < ip2ulong($ip2);
541
}
542
543
/* Return true if the first IP is 'after' the second */
544
function ip_greater_than($ip1, $ip2) {
545
	// Compare as unsigned long because otherwise it wouldn't work
546
	//   when crossing over from 127.255.255.255 / 128.0.0.0 barrier
547
	return ip2ulong($ip1) > ip2ulong($ip2);
548
}
549
550 b17ac4f7 stilez
/* compare two IP addresses */
551
function ipcmp($a, $b) {
552 0c3fff67 jim-p
	if (is_subnet($a)) {
553 dc337505 Reid Linnemann
		$a = explode('/', $a)[0];
554 0c3fff67 jim-p
	}
555
	if (is_subnet($b)) {
556 dc337505 Reid Linnemann
		$b = explode('/', $b)[0];
557 0c3fff67 jim-p
	}
558 751533a2 Phil Davis
	if (ip_less_than($a, $b)) {
559 b17ac4f7 stilez
		return -1;
560 751533a2 Phil Davis
	} else if (ip_greater_than($a, $b)) {
561 b17ac4f7 stilez
		return 1;
562 751533a2 Phil Davis
	} else {
563 b17ac4f7 stilez
		return 0;
564 751533a2 Phil Davis
	}
565 b17ac4f7 stilez
}
566
567 bb67ac32 Phil Davis
/* Convert a range of IPv4 addresses to an array of individual addresses. */
568
/* Note: IPv6 ranges are not yet supported here. */
569
function ip_range_to_address_array($startip, $endip, $max_size = 5000) {
570
	if (!is_ipaddrv4($startip) || !is_ipaddrv4($endip)) {
571
		return false;
572
	}
573
574
	if (ip_greater_than($startip, $endip)) {
575
		// Swap start and end so we can process sensibly.
576
		$temp = $startip;
577
		$startip = $endip;
578
		$endip = $temp;
579
	}
580
581 751533a2 Phil Davis
	if (ip_range_size_v4($startip, $endip) > $max_size) {
582 bb67ac32 Phil Davis
		return false;
583 751533a2 Phil Davis
	}
584
585 bb67ac32 Phil Davis
	// Container for IP addresses within this range.
586
	$rangeaddresses = array();
587
	$end_int = ip2ulong($endip);
588
	for ($ip_int = ip2ulong($startip); $ip_int <= $end_int; $ip_int++) {
589
		$rangeaddresses[] = long2ip($ip_int);
590
	}
591
592
	return $rangeaddresses;
593
}
594
595 4f3fc80d Renato Botelho
/*
596
 * Convert an IPv4 or IPv6 IP range to an array of subnets which can contain the range.
597
 * Algorithm and embodying code PD'ed by Stilez - enjoy as you like :-)
598
 *
599
 * Documented on pfsense dev list 19-20 May 2013. Summary:
600
 *
601
 * The algorithm looks at patterns of 0's and 1's in the least significant bit(s), whether IPv4 or IPv6.
602
 * These are all that needs checking to identify a _guaranteed_ correct, minimal and optimal subnet array.
603
 *
604
 * As a result, string/binary pattern matching of the binary IP is very efficient. It uses just 2 pattern-matching rules
605
 * to chop off increasingly larger subnets at both ends that can't be part of larger subnets, until nothing's left.
606
 *
607
 * (a) If any range has EITHER low bit 1 (in startip) or 0 (in endip), that end-point is _always guaranteed_ to be optimally
608
 * represented by its own 'single IP' CIDR; the remaining range then shrinks by one IP up or down, causing the new end-point's
609
 * low bit to change from 1->0 (startip) or 0->1 (endip). Only one edge case needs checking: if a range contains exactly 2
610
 * adjacent IPs of this format, then the two IPs themselves are required to span it, and we're done.
611
 * 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
612
 * low bits can now be ignored.
613
 *
614
 * (b) If any range has BOTH startip and endip ending in some number of 0's and 1's respectively, these low bits can
615
 * *always* be ignored and "bit-shifted" for subnet spanning. So provided we remember the bits we've place-shifted, we can
616
 * _always_ right-shift and chop off those bits, leaving a smaller range that has EITHER startip ending in 1 or endip ending
617
 * in 0 (ie can now apply (a) again) or the entire range has vanished and we're done.
618
 * We then loop to redo (a) again on the remaining (place shifted) range until after a few loops, the remaining (place shifted)
619
 * range 'vanishes' by meeting the exit criteria of (a) or (b), and we're done.
620
 */
621 ed516fa7 stilez
function ip_range_to_subnet_array($ip1, $ip2) {
622 bb67ac32 Phil Davis
623 ed516fa7 stilez
	if (is_ipaddrv4($ip1) && is_ipaddrv4($ip2)) {
624
		$proto = 'ipv4';  // for clarity
625
		$bits = 32;
626
		$ip1bin = decbin(ip2long32($ip1));
627
		$ip2bin = decbin(ip2long32($ip2));
628
	} elseif (is_ipaddrv6($ip1) && is_ipaddrv6($ip2)) {
629
		$proto = 'ipv6';
630
		$bits = 128;
631 de645734 Renato Botelho
		$ip1bin = ip6_to_bin($ip1);
632
		$ip2bin = ip6_to_bin($ip2);
633 19b802f4 Phil Davis
	} else {
634 ed516fa7 stilez
		return array();
635 19b802f4 Phil Davis
	}
636 ecd1f2d9 jim-p
637 ed516fa7 stilez
	// it's *crucial* that binary strings are guaranteed the expected length;  do this for certainty even though for IPv6 it's redundant
638
	$ip1bin = str_pad($ip1bin, $bits, '0', STR_PAD_LEFT);
639
	$ip2bin = str_pad($ip2bin, $bits, '0', STR_PAD_LEFT);
640 ecd1f2d9 jim-p
641 19b802f4 Phil Davis
	if ($ip1bin == $ip2bin) {
642 ed516fa7 stilez
		return array($ip1 . '/' . $bits); // exit if ip1=ip2 (trivial case)
643 19b802f4 Phil Davis
	}
644
645
	if ($ip1bin > $ip2bin) {
646 ed516fa7 stilez
		list ($ip1bin, $ip2bin) = array($ip2bin, $ip1bin);  // swap if needed (ensures ip1 < ip2)
647 19b802f4 Phil Davis
	}
648 ecd1f2d9 jim-p
649 ed516fa7 stilez
	$rangesubnets = array();
650
	$netsize = 0;
651
652
	do {
653
		// at loop start, $ip1 is guaranteed strictly less than $ip2 (important for edge case trapping and preventing accidental binary wrapround)
654
		// which means the assignments $ip1 += 1 and $ip2 -= 1 will always be "binary-wrapround-safe"
655
656
		// 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)
657 19b802f4 Phil Davis
658 ed516fa7 stilez
		if (substr($ip1bin, -1, 1) == '1') {
659
			// the start ip must be in a separate one-IP cidr range
660
			$new_subnet_ip = substr($ip1bin, $netsize, $bits - $netsize) . str_repeat('0', $netsize);
661
			$rangesubnets[$new_subnet_ip] = $bits - $netsize;
662
			$n = strrpos($ip1bin, '0');  //can't be all 1's
663
			$ip1bin = ($n == 0 ? '' : substr($ip1bin, 0, $n)) . '1' . str_repeat('0', $bits - $n - 1);  // BINARY VERSION OF $ip1 += 1
664 19b802f4 Phil Davis
		}
665 ed516fa7 stilez
666
		// 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)
667 19b802f4 Phil Davis
668 ed516fa7 stilez
		if (substr($ip2bin, -1, 1) == '0') {
669
			// the end ip must be in a separate one-IP cidr range
670
			$new_subnet_ip = substr($ip2bin, $netsize, $bits - $netsize) . str_repeat('0', $netsize);
671
			$rangesubnets[$new_subnet_ip] = $bits - $netsize;
672
			$n = strrpos($ip2bin, '1');  //can't be all 0's
673
			$ip2bin = ($n == 0 ? '' : substr($ip2bin, 0, $n)) . '0' . str_repeat('1', $bits - $n - 1);  // BINARY VERSION OF $ip2 -= 1
674
			// already checked for the edge case where end = start+1 and start ends in 0x1, above, so it's safe
675 ecd1f2d9 jim-p
		}
676
677 19b802f4 Phil Davis
		// this is the only edge case arising from increment/decrement.
678 ed516fa7 stilez
		// 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)
679 19b802f4 Phil Davis
680
		if ($ip2bin < $ip1bin) {
681 ed516fa7 stilez
			continue;
682 19b802f4 Phil Davis
		}
683 ed516fa7 stilez
684
		// step #3 the start and end ip MUST now end in '0's and '1's respectively
685
		// so we have a non-trivial range AND the last N bits are no longer important for CIDR purposes.
686
687
		$shift = $bits - max(strrpos($ip1bin, '0'), strrpos($ip2bin, '1'));  // num of low bits which are '0' in ip1 and '1' in ip2
688
		$ip1bin = str_repeat('0', $shift) . substr($ip1bin, 0, $bits - $shift);
689
		$ip2bin = str_repeat('0', $shift) . substr($ip2bin, 0, $bits - $shift);
690
		$netsize += $shift;
691
		if ($ip1bin == $ip2bin) {
692
			// we're done.
693
			$new_subnet_ip = substr($ip1bin, $netsize, $bits - $netsize) . str_repeat('0', $netsize);
694
			$rangesubnets[$new_subnet_ip] = $bits - $netsize;
695
			continue;
696 ecd1f2d9 jim-p
		}
697 19b802f4 Phil Davis
698 ed516fa7 stilez
		// at this point there's still a remaining range, and either startip ends with '1', or endip ends with '0'. So repeat cycle.
699
	} while ($ip1bin < $ip2bin);
700 ecd1f2d9 jim-p
701 ed516fa7 stilez
	// subnets are ordered by bit size. Re sort by IP ("naturally") and convert back to IPv4/IPv6
702 ecd1f2d9 jim-p
703 ed516fa7 stilez
	ksort($rangesubnets, SORT_STRING);
704
	$out = array();
705 ecd1f2d9 jim-p
706 ed516fa7 stilez
	foreach ($rangesubnets as $ip => $netmask) {
707
		if ($proto == 'ipv4') {
708
			$i = str_split($ip, 8);
709 19b802f4 Phil Davis
			$out[] = implode('.', array(bindec($i[0]), bindec($i[1]), bindec($i[2]), bindec($i[3]))) . '/' . $netmask;
710
		} else {
711 de645734 Renato Botelho
			$out[] = bin_to_compressed_ip6($ip) . '/' . $netmask;
712 19b802f4 Phil Davis
		}
713 ecd1f2d9 jim-p
	}
714
715 ed516fa7 stilez
	return $out;
716 ecd1f2d9 jim-p
}
717
718 bb67ac32 Phil Davis
/* returns true if $range is a valid pair of IPv4 or IPv6 addresses separated by a "-"
719 751533a2 Phil Davis
	false - if not a valid pair
720
	true (numeric 4 or 6) - if valid, gives type of addresses */
721 ecd1f2d9 jim-p
function is_iprange($range) {
722
	if (substr_count($range, '-') != 1) {
723
		return false;
724
	}
725
	list($ip1, $ip2) = explode ('-', $range);
726 751533a2 Phil Davis
	if (is_ipaddrv4($ip1) && is_ipaddrv4($ip2)) {
727 bb67ac32 Phil Davis
		return 4;
728 751533a2 Phil Davis
	}
729
	if (is_ipaddrv6($ip1) && is_ipaddrv6($ip2)) {
730 bb67ac32 Phil Davis
		return 6;
731 751533a2 Phil Davis
	}
732 bb67ac32 Phil Davis
	return false;
733 ecd1f2d9 jim-p
}
734
735 31495068 stilez
/* returns true if $ipaddr is a valid dotted IPv4 address or a IPv6
736 751533a2 Phil Davis
	false - not valid
737
	true (numeric 4 or 6) - if valid, gives type of address */
738 5b237745 Scott Ullrich
function is_ipaddr($ipaddr) {
739 751533a2 Phil Davis
	if (is_ipaddrv4($ipaddr)) {
740 31495068 stilez
		return 4;
741 47593ac6 Seth Mos
	}
742 751533a2 Phil Davis
	if (is_ipaddrv6($ipaddr)) {
743 31495068 stilez
		return 6;
744 47593ac6 Seth Mos
	}
745
	return false;
746
}
747
748 22b5abac Seth Mos
/* returns true if $ipaddr is a valid IPv6 address */
749 47593ac6 Seth Mos
function is_ipaddrv6($ipaddr) {
750 751533a2 Phil Davis
	if (!is_string($ipaddr) || empty($ipaddr)) {
751 b5b5bcc0 Ermal
		return false;
752 751533a2 Phil Davis
	}
753 2f87470c Luiz Souza
	/*
754
	 * While Net_IPv6::checkIPv6() considers IPv6/mask a valid IPv6,
755
	 * is_ipaddrv6() needs to be more strict to keep the compatibility
756
	 * with is_ipaddrv4().
757
	 */
758
	if (strstr($ipaddr, "/")) {
759
		return false;
760
	}
761 55909a9a Ermal
	if (strstr($ipaddr, "%") && is_linklocal($ipaddr)) {
762
		$tmpip = explode("%", $ipaddr);
763
		$ipaddr = $tmpip[0];
764
	}
765 8a89c115 jim-p
	/*
766
	 * Net_IPv6::checkIPv6 does not reject multiple attempts at compression
767
	 * so we must check it beforehand.
768
	 * https://redmine.pfsense.org/issues/13069
769
	 */
770
	if (substr_count($ipaddr, '::') > 1) {
771
		return false;
772
	}
773 1e5da31d Ermal
	return Net_IPv6::checkIPv6($ipaddr);
774 47593ac6 Seth Mos
}
775
776 90fd68c6 Viktor G
function is_ipaddrv6_v4map($ipaddr) {
777
	/* check RFC4291 par 2.2.2 format, ex: fd00::1.2.3.4
778
	 * see https://redmine.pfsense.org/issues/11446 */
779
	if (is_ipaddrv6($ipaddr) && preg_match('/^[0-9a-f:]{2,30}[0-9.]{7,15}$/i', $ipaddr)) {
780
		return true;
781
	}
782
	return false;
783
}
784
785 47593ac6 Seth Mos
/* returns true if $ipaddr is a valid dotted IPv4 address */
786
function is_ipaddrv4($ipaddr) {
787 c3b3e9c7 stilez
	if (!is_string($ipaddr) || empty($ipaddr) || ip2long($ipaddr) === FALSE) {
788 5b237745 Scott Ullrich
		return false;
789 751533a2 Phil Davis
	}
790 c3b3e9c7 stilez
	return true;
791 5b237745 Scott Ullrich
}
792
793 b6ea9c61 Viktor G
function is_mcast($ipaddr) {
794
	if (is_mcastv4($ipaddr)) {
795
		return 4;
796
	}
797
	if (is_mcastv6($ipaddr)) {
798
		return 6;
799
	}
800
	return false;
801
}
802
803
function is_mcastv4($ipaddr) {
804
	if (!is_ipaddrv4($ipaddr) ||
805
	    !preg_match('/^2(?:2[4-9]|3\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d?|0)){3}$/', $ipaddr)) {
806
		return false;
807
	}
808
	return true;
809
}
810
811
function is_mcastv6($ipaddr) {
812
	if (!is_ipaddrv6($ipaddr) || !preg_match('/^ff.+$/', $ipaddr)) {
813
		return false;
814
	}
815
	return true;
816
}
817
818 a2fd89dd stilez
/* returns 4 or 6 respectively (== TRUE) if $ipaddr is a valid IPv4 or IPv6 linklocal address
819 f38d984b Renato Botelho
   returns '' if not a valid linklocal address */
820 19341491 Renato Botelho
function is_linklocal($ipaddr) {
821 f38d984b Renato Botelho
	if (is_ipaddrv4($ipaddr)) {
822
		// input is IPv4
823
		// test if it's 169.254.x.x per rfc3927 2.1
824
		$ip4 = explode(".", $ipaddr);
825
		if ($ip4[0] == '169' && $ip4[1] == '254') {
826
			return 4;
827 a2fd89dd stilez
		}
828 f38d984b Renato Botelho
	} elseif (Net_IPv6::getAddressType($ipaddr) == NET_IPV6_LOCAL_LINK) {
829
		return 6;
830 a2fd89dd stilez
	}
831
	return '';
832 19341491 Renato Botelho
}
833 3f5f7ad3 smos
834 bd6ff328 Renato Botelho
/* returns scope of a linklocal address */
835
function get_ll_scope($addr) {
836 751533a2 Phil Davis
	if (!is_linklocal($addr) || !strstr($addr, "%")) {
837 bd6ff328 Renato Botelho
		return "";
838 751533a2 Phil Davis
	}
839 27cb0c5a Reid Linnemann
	return explode("%", $addr)[1];
840 bd6ff328 Renato Botelho
}
841
842 3f5f7ad3 smos
/* returns true if $ipaddr is a valid literal IPv6 address */
843
function is_literalipaddrv6($ipaddr) {
844 a2fd89dd stilez
	if (substr($ipaddr,0,1) == '[' && substr($ipaddr,-1,1) == ']') {
845
		// if it's data wrapped in "[ ... ]" then test if middle part is valid IPv6
846
		return is_ipaddrv6(substr($ipaddr,1,-1));
847 751533a2 Phil Davis
	}
848 a2fd89dd stilez
	return false;
849 3f5f7ad3 smos
}
850
851 a2fd89dd stilez
/* returns true if $iport is a valid IPv4:port or [Literal IPv6]:port
852 751533a2 Phil Davis
	false - not valid
853
	true (numeric 4 or 6) - if valid, gives type of address */
854 4a8a90ff jim-p
function is_ipaddrwithport($ipport) {
855 31495068 stilez
	$c = strrpos($ipport, ":");
856 751533a2 Phil Davis
	if ($c === false) {
857 31495068 stilez
		return false;  // can't split at final colon if no colon exists
858 751533a2 Phil Davis
	}
859
860
	if (!is_port(substr($ipport, $c + 1))) {
861 31495068 stilez
		return false;  // no valid port after last colon
862 751533a2 Phil Davis
	}
863 31495068 stilez
864
	$ip = substr($ipport, 0, $c);  // else is text before last colon a valid IP
865 751533a2 Phil Davis
	if (is_literalipaddrv6($ip)) {
866 31495068 stilez
		return 6;
867 751533a2 Phil Davis
	} elseif (is_ipaddrv4($ip)) {
868 31495068 stilez
		return 4;
869 751533a2 Phil Davis
	} else {
870 4a8a90ff jim-p
		return false;
871 751533a2 Phil Davis
	}
872 4a8a90ff jim-p
}
873
874 d3a2337a jim-p
function is_hostnamewithport($hostport) {
875
	$parts = explode(":", $hostport);
876 a2fd89dd stilez
	// no need to validate with is_string(); if it's not a string then explode won't return 2 parts anyway
877
	if (count($parts) == 2) {
878
		return is_hostname($parts[0]) && is_port($parts[1]);
879 d3a2337a jim-p
	}
880 a2fd89dd stilez
	return false;
881 d3a2337a jim-p
}
882
883 87f0be87 Chris Buechler
/* returns true if $ipaddr is a valid dotted IPv4 address or an alias thereof */
884 5b237745 Scott Ullrich
function is_ipaddroralias($ipaddr) {
885 1e578a7f Ermal Lu?i
	if (is_alias($ipaddr)) {
886 b30acd45 Reid Linnemann
		foreach (config_get_path('aliases/alias', []) as $alias) {
887
			if ($alias['name'] == $ipaddr && !preg_match("/port/i", $alias['type'])) {
888
				return true;
889 1e578a7f Ermal Lu?i
			}
890 5bbd08e1 Warren Baker
		}
891 1e578a7f Ermal Lu?i
		return false;
892 751533a2 Phil Davis
	} else {
893 87f0be87 Chris Buechler
		return is_ipaddr($ipaddr);
894 751533a2 Phil Davis
	}
895 87f0be87 Chris Buechler
896 5b237745 Scott Ullrich
}
897
898 a5e2a35f stilez
/* returns true if $subnet is a valid IPv4 or IPv6 subnet in CIDR format
899 751533a2 Phil Davis
	false - if not a valid subnet
900
	true (numeric 4 or 6) - if valid, gives type of subnet */
901 5b237745 Scott Ullrich
function is_subnet($subnet) {
902 35c60e99 Viktor G
	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)) {
903 751533a2 Phil Davis
		if (is_ipaddrv4($parts[1]) && $parts[3] <= 32) {
904 a5e2a35f stilez
			return 4;
905 751533a2 Phil Davis
		}
906
		if (is_ipaddrv6($parts[2]) && $parts[3] <= 128) {
907 a5e2a35f stilez
			return 6;
908 751533a2 Phil Davis
		}
909 b1b42a06 Warren Baker
	}
910
	return false;
911
}
912
913 905bd44e PiBa-NL
function is_v4($ip_or_subnet) {
914
	return is_ipaddrv4($ip_or_subnet) || is_subnetv4($ip_or_subnet);
915
}
916
917
function is_v6($ip_or_subnet) {
918
	return is_ipaddrv6($ip_or_subnet) || is_subnetv6($ip_or_subnet);
919
}
920
921 a5e2a35f stilez
/* same as is_subnet() but accepts IPv4 only */
922 b1b42a06 Warren Baker
function is_subnetv4($subnet) {
923 a5e2a35f stilez
	return (is_subnet($subnet) == 4);
924 5b237745 Scott Ullrich
}
925
926 a5e2a35f stilez
/* same as is_subnet() but accepts IPv6 only */
927 fdb9c1db Warren Baker
function is_subnetv6($subnet) {
928 a5e2a35f stilez
	return (is_subnet($subnet) == 6);
929 fdb9c1db Warren Baker
}
930
931 5b237745 Scott Ullrich
/* returns true if $subnet is a valid subnet in CIDR format or an alias thereof */
932
function is_subnetoralias($subnet) {
933
	global $aliastable;
934 98bbf05a Scott Ullrich
935 751533a2 Phil Davis
	if (isset($aliastable[$subnet]) && is_subnet($aliastable[$subnet])) {
936 5b237745 Scott Ullrich
		return true;
937 751533a2 Phil Davis
	} else {
938 5b237745 Scott Ullrich
		return is_subnet($subnet);
939 751533a2 Phil Davis
	}
940 5b237745 Scott Ullrich
}
941
942 cafe9038 stilez
/* Get number of addresses in an IPv4/IPv6 subnet (represented as a string)
943
   optional $exact=true forces error (0) to be returned if it can't be represented exactly
944
   Exact result not possible above PHP_MAX_INT which is about 2^31 addresses on x32 or 2^63 on x64
945 4402b5cb stilez
   Returns 0 for bad data or if cannot represent size as an INT when $exact is set. */
946
function subnet_size($subnet, $exact=false) {
947
	$parts = explode("/", $subnet);
948 0987677a stilez
	$iptype = is_ipaddr($parts[0]);
949
	if (count($parts) == 2 && $iptype) {
950
		return subnet_size_by_netmask($iptype, $parts[1], $exact);
951 4402b5cb stilez
	}
952
	return 0;
953
}
954
955 cafe9038 stilez
/* Get number of addresses in an IPv4/IPv6 subnet (represented numerically as IP type + bits)
956
   optional $exact=true forces error (0) to be returned if it can't be represented exactly
957
   Hard to think where we might need to count exactly a huge subnet but an overflow detection option is probably sensible
958
   Returns 0 for bad data or if cannot represent size as an INT when $exact is set. */
959 4402b5cb stilez
function subnet_size_by_netmask($iptype, $bits, $exact=false) {
960
	if (!is_numericint($bits)) {
961
		return 0;
962
	} elseif ($iptype == 4 && $bits <= 32) {
963
		$snsize = 32 - $bits;
964
	} elseif ($iptype == 6 && $bits <= 128) {
965
		$snsize = 128 - $bits;
966 19b802f4 Phil Davis
	} else {
967 b17ac4f7 stilez
		return 0;
968
	}
969 4402b5cb stilez
970 4f7956ad Steve Beaver
	// 2**N returns an exact result as an INT if possible, and a float/double if not.
971 4402b5cb stilez
	// 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
972
	$result = 2 ** $snsize;
973 4f7956ad Steve Beaver
974 4402b5cb stilez
	if ($exact && !is_int($result)) {
975
		//exact required but can't represent result exactly as an INT
976
		return 0;
977
	} else {
978
		// result ok, will be an INT where possible (guaranteed up to 2^31 addresses on x32/x64) and a float for 'huge' subnets
979
		return $result;
980
	}
981 b17ac4f7 stilez
}
982
983 2208be8b Chris Buechler
/* function used by pfblockerng */
984
function subnetv4_expand($subnet) {
985
	$result = array();
986
	list ($ip, $bits) = explode("/", $subnet);
987
	$net = ip2long($ip);
988
	$mask = (0xffffffff << (32 - $bits));
989
	$net &= $mask;
990
	$size = round(exp(log(2) * (32 - $bits)));
991
	for ($i = 0; $i < $size; $i += 1) {
992
		$result[] = long2ip($net | $i);
993
	}
994
	return $result;
995
}
996
997 e8d5be8e stilez
/* find out whether two IPv4/IPv6 CIDR subnets overlap.
998
   Note: CIDR overlap implies one is identical or included so largest sn will be the same */
999 b17ac4f7 stilez
function check_subnets_overlap($subnet1, $bits1, $subnet2, $bits2) {
1000 94eb702f stilez
	if (is_ipaddrv4($subnet1)) {
1001 e8d5be8e stilez
		return check_subnetsv4_overlap($subnet1, $bits1, $subnet2, $bits2);
1002 751533a2 Phil Davis
	} else {
1003 e8d5be8e stilez
		return check_subnetsv6_overlap($subnet1, $bits1, $subnet2, $bits2);
1004 751533a2 Phil Davis
	}
1005 b17ac4f7 stilez
}
1006
1007 e8d5be8e stilez
/* find out whether two IPv4 CIDR subnets overlap.
1008
   Note: CIDR overlap means sn1/sn2 are identical or one is included in other. So sn using largest $bits will be the same  */
1009
function check_subnetsv4_overlap($subnet1, $bits1, $subnet2, $bits2) {
1010 62512efa stilez
	$largest_sn = min($bits1, $bits2);
1011 e8d5be8e stilez
	$subnetv4_start1 = gen_subnetv4($subnet1, $largest_sn);
1012 9f4a788f stilez
	$subnetv4_start2 = gen_subnetv4($subnet2, $largest_sn);
1013 4f7956ad Steve Beaver
1014 9d3e8723 Phil Davis
	if ($subnetv4_start1 == '' || $subnetv4_start2 == '') {
1015 e8d5be8e stilez
		// One or both args is not a valid IPv4 subnet
1016
		//FIXME: needs to return "bad data" not true/false if bad. For now return false, best we can do until fixed
1017
		return false;
1018
	}
1019
	return ($subnetv4_start1 == $subnetv4_start2);
1020
}
1021 b17ac4f7 stilez
1022 e8d5be8e stilez
/* find out whether two IPv6 CIDR subnets overlap.
1023
   Note: CIDR overlap means sn1/sn2 are identical or one is included in other. So sn using largest $bits will be the same  */
1024 9f8266cd stilez
function check_subnetsv6_overlap($subnet1, $bits1, $subnet2, $bits2) {
1025 62512efa stilez
	$largest_sn = min($bits1, $bits2);
1026 9f8266cd stilez
	$subnetv6_start1 = gen_subnetv6($subnet1, $largest_sn);
1027 9f4a788f stilez
	$subnetv6_start2 = gen_subnetv6($subnet2, $largest_sn);
1028 4f7956ad Steve Beaver
1029 9d3e8723 Phil Davis
	if ($subnetv6_start1 == '' || $subnetv6_start2 == '') {
1030 e8d5be8e stilez
		// One or both args is not a valid IPv6 subnet
1031
		//FIXME: needs to return "bad data" not true/false if bad. For now return false, best we can do until fixed
1032
		return false;
1033
	}
1034
	return ($subnetv6_start1 == $subnetv6_start2);
1035 b17ac4f7 stilez
}
1036 4c62c1ff Renato Botelho
1037
/* return all PTR zones for a IPv6 network */
1038
function get_v6_ptr_zones($subnet, $bits) {
1039
	$result = array();
1040
1041
	if (!is_ipaddrv6($subnet)) {
1042
		return $result;
1043
	}
1044
1045
	if (!is_numericint($bits) || $bits > 128) {
1046
		return $result;
1047
	}
1048
1049
	/*
1050
	 * Find a small nibble boundary subnet mask
1051
	 * e.g. a /29 will create 8 /32 PTR zones
1052
	 */
1053
	$small_sn = $bits;
1054
	while ($small_sn % 4 != 0) {
1055
		$small_sn++;
1056
	}
1057
1058
	/* Get network prefix */
1059
	$small_subnet = Net_IPv6::getNetmask($subnet, $bits);
1060
1061
	/*
1062
	 * While small network is part of bigger one, increase 4-bit in last
1063
	 * digit to get next small network
1064
	 */
1065
	while (Net_IPv6::isInNetmask($small_subnet, $subnet, $bits)) {
1066
		/* Get a pure hex value */
1067
		$unpacked = unpack('H*hex', inet_pton($small_subnet));
1068
		/* Create PTR record using $small_sn / 4 chars */
1069
		$result[] = implode('.', array_reverse(str_split(substr(
1070
		    $unpacked['hex'], 0, $small_sn / 4)))).'.ip6.arpa';
1071
1072
		/* Detect what part of IP should be increased */
1073
		$change_part = (int) ($small_sn / 16);
1074
		if ($small_sn % 16 == 0) {
1075
			$change_part--;
1076
		}
1077
1078 53904d09 Renato Botelho
		/* Increase 1 to desired part */
1079 4c62c1ff Renato Botelho
		$parts = explode(":", Net_IPv6::uncompress($small_subnet));
1080 53904d09 Renato Botelho
		$parts[$change_part]++;
1081 4c62c1ff Renato Botelho
		$small_subnet = implode(":", $parts);
1082
	}
1083
1084
	return $result;
1085
}
1086 b17ac4f7 stilez
1087
/* return true if $addr is in $subnet, false if not */
1088 086cf944 Phil Davis
function ip_in_subnet($addr, $subnet) {
1089 751533a2 Phil Davis
	if (is_ipaddrv6($addr) && is_subnetv6($subnet)) {
1090 02af3d3e jim-p
		/* Normalize IPv6 prefix to its start address to avoid PHP errors
1091
		 * https://redmine.pfsense.org/issues/14256
1092
		 */
1093
		list($prefix, $length) = explode("/", $subnet);
1094
		$prefix = gen_subnetv6($prefix, $length);
1095
		$subnet = "{$prefix}/{$length}";
1096 b17ac4f7 stilez
		return (Net_IPv6::isInNetmask($addr, $subnet));
1097 0c5dd854 Renato Botelho
	} else if (is_ipaddrv4($addr) && is_subnetv4($subnet)) {
1098 b17ac4f7 stilez
		list($ip, $mask) = explode('/', $subnet);
1099
		$mask = (0xffffffff << (32 - $mask)) & 0xffffffff;
1100
		return ((ip2long($addr) & $mask) == (ip2long($ip) & $mask));
1101
	}
1102 0c5dd854 Renato Botelho
	return false;
1103 b17ac4f7 stilez
}
1104
1105 6bcbd862 Phil Davis
/* returns true if $hostname is just a valid hostname (top part without any of the domain part) */
1106
function is_unqualified_hostname($hostname) {
1107 751533a2 Phil Davis
	if (!is_string($hostname)) {
1108 6bcbd862 Phil Davis
		return false;
1109 751533a2 Phil Davis
	}
1110 6bcbd862 Phil Davis
1111 8fdddd51 Chris Buechler
	if (preg_match('/^(?:[a-z0-9_]|[a-z0-9_][a-z0-9_\-]*[a-z0-9_])$/i', $hostname)) {
1112 6bcbd862 Phil Davis
		return true;
1113 751533a2 Phil Davis
	} else {
1114 6bcbd862 Phil Davis
		return false;
1115 751533a2 Phil Davis
	}
1116 6bcbd862 Phil Davis
}
1117
1118
/* returns true if $hostname is a valid hostname, with or without being a fully-qualified domain name. */
1119 0edcccc3 Daniel Seebald
function is_hostname($hostname, $allow_wildcard=false) {
1120 751533a2 Phil Davis
	if (!is_string($hostname)) {
1121 5b237745 Scott Ullrich
		return false;
1122 751533a2 Phil Davis
	}
1123 98bbf05a Scott Ullrich
1124 cc882a8b Daniel Seebald
	if (is_domain($hostname, $allow_wildcard)) {
1125 5454fd1b Phil Davis
		if ((substr_count($hostname, ".") == 1) && ($hostname[strlen($hostname)-1] == ".")) {
1126
			/* Only a single dot at the end like "test." - hosts cannot be directly in the root domain. */
1127
			return false;
1128 37c677a1 Viktor G
		} else {
1129 5454fd1b Phil Davis
			return true;
1130
		}
1131 37c677a1 Viktor G
	} else {
1132
		return false;
1133 751533a2 Phil Davis
	}
1134 5b237745 Scott Ullrich
}
1135
1136
/* returns true if $domain is a valid domain name */
1137 8ee5aa03 Viktor Gurov
function is_domain($domain, $allow_wildcard=false, $trailing_dot=true) {
1138 751533a2 Phil Davis
	if (!is_string($domain)) {
1139 5b237745 Scott Ullrich
		return false;
1140 751533a2 Phil Davis
	}
1141 8ee5aa03 Viktor Gurov
	if (!$trailing_dot && ($domain[strlen($domain)-1] == ".")) {
1142
		return false;
1143
	}
1144 0edcccc3 Daniel Seebald
	if ($allow_wildcard) {
1145
		$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';
1146
	} else {
1147
		$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';
1148
	}
1149
1150
	if (preg_match($domain_regex, $domain)) {
1151 5b237745 Scott Ullrich
		return true;
1152 751533a2 Phil Davis
	} else {
1153 5b237745 Scott Ullrich
		return false;
1154 751533a2 Phil Davis
	}
1155 5b237745 Scott Ullrich
}
1156
1157
/* returns true if $macaddr is a valid MAC address */
1158 80e7011f jim-p
function is_macaddr($macaddr, $partial=false) {
1159 c982fdbc Luiz Otavio O Souza
	$values = explode(":", $macaddr);
1160 80e7011f jim-p
1161
	/* Verify if the MAC address has a proper amount of parts for either a partial or full match. */
1162
	if ($partial) {
1163
		if ((count($values) < 1) || (count($values) > 6)) {
1164
			return false;
1165
		}
1166
	} elseif (count($values) != 6) {
1167 c982fdbc Luiz Otavio O Souza
		return false;
1168
	}
1169 80e7011f jim-p
	for ($i = 0; $i < count($values); $i++) {
1170 c982fdbc Luiz Otavio O Souza
		if (ctype_xdigit($values[$i]) == false)
1171
			return false;
1172
		if (hexdec($values[$i]) < 0 || hexdec($values[$i]) > 255)
1173
			return false;
1174
	}
1175
1176
	return true;
1177 5b237745 Scott Ullrich
}
1178
1179 e1f5381f Phil Davis
/*
1180
	If $return_message is true then
1181
		returns a text message about the reason that the name is invalid.
1182
		the text includes the type of "thing" that is being checked, passed in $object. (e.g. "alias", "gateway group", "schedule")
1183
	else
1184
		returns true if $name is a valid name for an alias
1185
		returns false if $name is not a valid name for an alias
1186
1187
	Aliases cannot be:
1188
		bad chars: anything except a-z 0-9 and underscore
1189
		bad names: empty string, pure numeric, pure underscore
1190
		reserved words: pre-defined service/protocol/port names which should not be ambiguous, and the words "port" and  "pass" */
1191
1192
function is_validaliasname($name, $return_message = false, $object = "alias") {
1193 beeef1f0 Bill Marquette
	/* Array of reserved words */
1194 0c2badde Colin Smith
	$reserved = array("port", "pass");
1195 4ad9a1e7 stilez
1196 751533a2 Phil Davis
	if (!is_string($name) || strlen($name) >= 32 || preg_match('/(^_*$|^\d*$|[^a-z0-9_])/i', $name)) {
1197 e1f5381f Phil Davis
		if ($return_message) {
1198
			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, _');
1199
		} else {
1200
			return false;
1201
		}
1202 751533a2 Phil Davis
	}
1203 e1f5381f Phil Davis
	if (in_array($name, $reserved, true)) {
1204
		if ($return_message) {
1205
			return sprintf(gettext('The %1$s name must not be either of the reserved words %2$s or %3$s.'), $object, "'port'", "'pass'");
1206
		} else {
1207
			return false;
1208
		}
1209 751533a2 Phil Davis
	}
1210 e1f5381f Phil Davis
	if (getprotobyname($name)) {
1211
		if ($return_message) {
1212 a2405c1a jim-p
			return sprintf(gettext('The %1$s name must not be an IP protocol name such as TCP, UDP, ICMP etc.'), $object);
1213 e1f5381f Phil Davis
		} else {
1214
			return false;
1215
		}
1216
	}
1217
	if (getservbyname($name, "tcp") || getservbyname($name, "udp")) {
1218
		if ($return_message) {
1219 a2405c1a jim-p
			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);
1220 e1f5381f Phil Davis
		} else {
1221
			return false;
1222
		}
1223
	}
1224
	if ($return_message) {
1225 dc337505 Reid Linnemann
		return sprintf(gettext('The %1$s name is valid.'), $object);
1226 e1f5381f Phil Davis
	} else {
1227
		return true;
1228
	}
1229
}
1230
1231
/* returns a text message indicating if the alias name is valid, or the reason it is not valid. */
1232
function invalidaliasnamemsg($name, $object = "alias") {
1233
	return is_validaliasname($name, true, $object);
1234 5b237745 Scott Ullrich
}
1235
1236 ee956d19 Renato Botelho
/*
1237
 * returns true if $range is a valid integer range between $min and $max
1238
 * range delimiter can be ':' or '-'
1239
 */
1240
function is_intrange($range, $min, $max) {
1241
	$values = preg_split("/[:-]/", $range);
1242
1243
	if (!is_array($values) || count($values) != 2) {
1244
		return false;
1245
	}
1246
1247 28e2b611 jim-p
	if (!ctype_digit(strval($values[0])) || !ctype_digit(strval($values[1]))) {
1248 ee956d19 Renato Botelho
		return false;
1249
	}
1250
1251
	$values[0] = intval($values[0]);
1252
	$values[1] = intval($values[1]);
1253
1254
	if ($values[0] >= $values[1]) {
1255
		return false;
1256
	}
1257
1258
	if ($values[0] < $min || $values[1] > $max) {
1259
		return false;
1260
	}
1261
1262
	return true;
1263
}
1264
1265 7a654802 Marcos Mendoza
/* returns true if $port is a valid TCP/UDP/SCTP port */
1266 5b237745 Scott Ullrich
function is_port($port) {
1267 28e2b611 jim-p
	if (ctype_digit(strval($port)) && ((intval($port) >= 1) && (intval($port) <= 65535))) {
1268 75106235 PiBa-NL
		return true;
1269 751533a2 Phil Davis
	}
1270 7a654802 Marcos Mendoza
	if (getservbyname($port, "tcp") || getservbyname($port, "udp") || getservbyname($port, "sctp")) {
1271 9060f420 Renato Botelho
		return true;
1272 751533a2 Phil Davis
	}
1273 75106235 PiBa-NL
	return false;
1274 5b237745 Scott Ullrich
}
1275
1276 6df10582 Erik Schaeffer
/* returns true if $port is in use */
1277
function is_port_in_use($port, $proto = "tcp", $ip_version = 4) {
1278
	$port_info = array();
1279
	exec("/usr/bin/netstat --libxo json -an " . escapeshellarg('-' . $ip_version) . " -p " . escapeshellarg($proto), $rawdata, $rc);
1280
	if ($rc == 0) {
1281
		$netstatarr = json_decode(implode(" ", $rawdata), JSON_OBJECT_AS_ARRAY);
1282
		$netstatarr = $netstatarr['statistics']['socket'];
1283
1284 dc337505 Reid Linnemann
		foreach($netstatarr as $portstats){
1285 6df10582 Erik Schaeffer
			array_push($port_info, $portstats['local']['port']);
1286
		}
1287
	}
1288
1289
	return in_array($port, $port_info);
1290
}
1291
1292 7a654802 Marcos Mendoza
/* returns true if $portrange is a valid portrange ("<port>:<port>") */
1293 5a1eebc7 Scott Ullrich
function is_portrange($portrange) {
1294 5bbd08e1 Warren Baker
	$ports = explode(":", $portrange);
1295 5a1eebc7 Scott Ullrich
1296 e371f8b9 whjvenyl
	return (count($ports) == 2 && is_port($ports[0]) && is_port($ports[1]));
1297 5a1eebc7 Scott Ullrich
}
1298
1299 7a654802 Marcos Mendoza
/* returns true if $port is a valid port number or range ("<port>:<port>") */
1300 593e9fe3 Phil Davis
function is_port_or_range($port) {
1301 4081ecac Phil Davis
	return (is_port($port) || is_portrange($port));
1302
}
1303
1304 fe108b67 Phil Davis
/* returns true if $port is an alias that is a port type */
1305
function is_portalias($port) {
1306 5bbd08e1 Warren Baker
	if (is_alias($port)) {
1307 b30acd45 Reid Linnemann
		foreach (config_get_path('aliases/alias', []) as $alias) {
1308
			if ($alias['name'] == $port && preg_match("/port/i", $alias['type'])) {
1309
				return true;
1310 5bbd08e1 Warren Baker
			}
1311 751533a2 Phil Davis
		}
1312
	}
1313 fe108b67 Phil Davis
	return false;
1314
}
1315
1316
/* returns true if $port is a valid port number or an alias thereof */
1317
function is_port_or_alias($port) {
1318
	return (is_port($port) || is_portalias($port));
1319 1e578a7f Ermal Lu?i
}
1320
1321 7a654802 Marcos Mendoza
/* returns true if $port is a valid port number or range ("<port>:<port>") or an alias thereof */
1322 593e9fe3 Phil Davis
function is_port_or_range_or_alias($port) {
1323 fe108b67 Phil Davis
	return (is_port($port) || is_portrange($port) || is_portalias($port));
1324 4081ecac Phil Davis
}
1325
1326 d9f33a7f Renato Botelho
/* create ranges of sequential port numbers (200:215) and remove duplicates */
1327 f6622167 NOYB
function group_ports($ports, $kflc = false) {
1328 751533a2 Phil Davis
	if (!is_array($ports) || empty($ports)) {
1329 d9f33a7f Renato Botelho
		return;
1330 751533a2 Phil Davis
	}
1331 d9f33a7f Renato Botelho
1332
	$uniq = array();
1333 f6622167 NOYB
	$comments = array();
1334 d9f33a7f Renato Botelho
	foreach ($ports as $port) {
1335 f6622167 NOYB
		if (($kflc) && (strpos($port, '#') === 0)) {	// Keep Full Line Comments (lines beginning with #).
1336
			$comments[] = $port;
1337
		} else if (is_portrange($port)) {
1338 d9f33a7f Renato Botelho
			list($begin, $end) = explode(":", $port);
1339
			if ($begin > $end) {
1340
				$aux = $begin;
1341
				$begin = $end;
1342
				$end = $aux;
1343
			}
1344 751533a2 Phil Davis
			for ($i = $begin; $i <= $end; $i++) {
1345
				if (!in_array($i, $uniq)) {
1346 d9f33a7f Renato Botelho
					$uniq[] = $i;
1347 751533a2 Phil Davis
				}
1348
			}
1349 d9f33a7f Renato Botelho
		} else if (is_port($port)) {
1350 751533a2 Phil Davis
			if (!in_array($port, $uniq)) {
1351 d9f33a7f Renato Botelho
				$uniq[] = $port;
1352 751533a2 Phil Davis
			}
1353 d9f33a7f Renato Botelho
		}
1354
	}
1355
	sort($uniq, SORT_NUMERIC);
1356
1357
	$result = array();
1358
	foreach ($uniq as $idx => $port) {
1359
		if ($idx == 0) {
1360
			$result[] = $port;
1361
			continue;
1362
		}
1363
1364
		$last = end($result);
1365 751533a2 Phil Davis
		if (is_portrange($last)) {
1366 d9f33a7f Renato Botelho
			list($begin, $end) = explode(":", $last);
1367 751533a2 Phil Davis
		} else {
1368 d9f33a7f Renato Botelho
			$begin = $end = $last;
1369 751533a2 Phil Davis
		}
1370 d9f33a7f Renato Botelho
1371
		if ($port == ($end+1)) {
1372
			$end++;
1373
			$result[count($result)-1] = "{$begin}:{$end}";
1374
		} else {
1375
			$result[] = $port;
1376
		}
1377
	}
1378
1379 f6622167 NOYB
	return array_merge($comments, $result);
1380 d9f33a7f Renato Botelho
}
1381
1382 b8014f9d Scott Ullrich
/* returns true if $val is a valid shaper bandwidth value */
1383
function is_valid_shaperbw($val) {
1384 eaa37259 Ermal Luçi
	return (preg_match("/^(\d+(?:\.\d+)?)([MKG]?b|%)$/", $val));
1385 b8014f9d Scott Ullrich
}
1386
1387 54404519 Renato Botelho
/* returns true if $test is in the range between $start and $end */
1388
function is_inrange_v4($test, $start, $end) {
1389 8c48089f Renato Botelho
	if (!is_ipaddrv4($test) || !is_ipaddrv4($start) || !is_ipaddrv4($end)) {
1390 54404519 Renato Botelho
		return false;
1391 751533a2 Phil Davis
	}
1392 8c48089f Renato Botelho
1393
	if (ip2ulong($test) <= ip2ulong($end) &&
1394
	    ip2ulong($test) >= ip2ulong($start)) {
1395
		return true;
1396
	}
1397
1398
	return false;
1399 54404519 Renato Botelho
}
1400
1401 41b4867e Renato Botelho
/* returns true if $test is in the range between $start and $end */
1402
function is_inrange_v6($test, $start, $end) {
1403 8c48089f Renato Botelho
	if (!is_ipaddrv6($test) || !is_ipaddrv6($start) || !is_ipaddrv6($end)) {
1404 41b4867e Renato Botelho
		return false;
1405 751533a2 Phil Davis
	}
1406 8c48089f Renato Botelho
1407
	if (inet_pton($test) <= inet_pton($end) &&
1408
	    inet_pton($test) >= inet_pton($start)) {
1409
		return true;
1410
	}
1411
1412
	return false;
1413 41b4867e Renato Botelho
}
1414
1415 da6cb29e Renato Botelho
/* returns true if $test is in the range between $start and $end */
1416
function is_inrange($test, $start, $end) {
1417
	return is_ipaddrv6($test) ? is_inrange_v6($test, $start, $end) : is_inrange_v4($test, $start, $end);
1418
}
1419
1420 e8a7e86c Marcos Mendoza
/**
1421
 * Check if an ethertype is valid
1422
 *
1423
 * @param string $ethertype	The ethertype as hex string
1424
 *
1425
 * @return bool
1426
 */
1427
function is_ethertype(string $ethertype): bool {
1428
	$ethertype = strtolower($ethertype);
1429
1430
	if (!str_starts_with($ethertype, '0x')) {
1431
		return (false);
1432
	}
1433
1434
	$ethertype = substr($ethertype, 2);
1435
1436
	if (!ctype_xdigit($ethertype)) {
1437
		return (false);
1438
	}
1439
1440
	return (filter_var(hexdec($ethertype), FILTER_VALIDATE_INT, ['options' => ['min_range' => 0x1, 'max_range' => 0xffff]]));
1441
}
1442
1443 ab378cb7 Steve Beaver
function build_vip_list($fif, $family = "all") {
1444
	$list = array('address' => gettext('Interface Address'));
1445
1446
	$viplist = get_configured_vip_list($family);
1447
	foreach ($viplist as $vip => $address) {
1448
		if ($fif == get_configured_vip_interface($vip)) {
1449
			$list[$vip] = "$address";
1450
			if (get_vip_descr($address)) {
1451
				$list[$vip] .= " (". get_vip_descr($address) .")";
1452
			}
1453 d333e763 qwertiko GmbH
		} else {
1454
			/**
1455
			 * additional check necessary: Alias-on-CARP VIPs return a "_vip<id>" string
1456
			 * that needs to be resolved via an additional check to see if that VIP should
1457
			 * be displayed here or skipped, as the parent isn't configured on this interface
1458
			 */
1459
1460
			$parentif = get_configured_vip_interface($vip);
1461
			if (str_starts_with($parentif, "_vip")) {
1462
				if ($fif == get_configured_vip_interface($parentif)) {
1463
					$list[$vip] = "$address";
1464
					if (get_vip_descr($address)) {
1465
						$list[$vip] .= " (". get_vip_descr($address) .")";
1466
					}
1467
				}
1468
			}
1469 ab378cb7 Steve Beaver
		}
1470
	}
1471
1472
	return($list);
1473
}
1474
1475 ce94deb0 Luiz Otavio O Souza
function get_configured_vip_list($family = 'all', $type = VIP_ALL) {
1476 abcb2bed Ermal Lu?i
	global $config;
1477
1478 2a5960b0 Luiz Otavio O Souza
	$list = array();
1479 fab98cb6 Reid Linnemann
	if (!array_key_exists('virtualip', $config) ||
1480
		!is_array($config['virtualip']) ||
1481 c6c398c6 jim-p
	    !is_array($config['virtualip']['vip']) ||
1482
	    empty($config['virtualip']['vip'])) {
1483 2a5960b0 Luiz Otavio O Souza
		return ($list);
1484 d9901ff4 Chris Buechler
	}
1485 76153238 Luiz Otavio O Souza
1486
	$viparr = &$config['virtualip']['vip'];
1487
	foreach ($viparr as $vip) {
1488 ce94deb0 Luiz Otavio O Souza
1489
		if ($type == VIP_CARP) {
1490
			if ($vip['mode'] != "carp")
1491
				continue;
1492
		} elseif ($type == VIP_IPALIAS) {
1493
			if ($vip['mode'] != "ipalias")
1494
				continue;
1495
		} else {
1496
			if ($vip['mode'] != "carp" && $vip['mode'] != "ipalias")
1497
				continue;
1498 d9901ff4 Chris Buechler
		}
1499 2a5960b0 Luiz Otavio O Souza
1500
		if ($family == 'all' ||
1501
		    ($family == 'inet' && is_ipaddrv4($vip['subnet'])) ||
1502
		    ($family == 'inet6' && is_ipaddrv6($vip['subnet']))) {
1503
			$list["_vip{$vip['uniqid']}"] = $vip['subnet'];
1504 4e322e2c Phil Davis
		}
1505 2a5960b0 Luiz Otavio O Souza
	}
1506
	return ($list);
1507
}
1508
1509
function get_configured_vip($vipinterface = '') {
1510
1511
	return (get_configured_vip_detail($vipinterface, 'all', 'vip'));
1512
}
1513
1514
function get_configured_vip_interface($vipinterface = '') {
1515
1516
	return (get_configured_vip_detail($vipinterface, 'all', 'iface'));
1517
}
1518
1519
function get_configured_vip_ipv4($vipinterface = '') {
1520
1521
	return (get_configured_vip_detail($vipinterface, 'inet', 'ip'));
1522
}
1523 76153238 Luiz Otavio O Souza
1524 2a5960b0 Luiz Otavio O Souza
function get_configured_vip_ipv6($vipinterface = '') {
1525
1526
	return (get_configured_vip_detail($vipinterface, 'inet6', 'ip'));
1527
}
1528
1529
function get_configured_vip_subnetv4($vipinterface = '') {
1530
1531
	return (get_configured_vip_detail($vipinterface, 'inet', 'subnet'));
1532
}
1533
1534
function get_configured_vip_subnetv6($vipinterface = '') {
1535
1536
	return (get_configured_vip_detail($vipinterface, 'inet6', 'subnet'));
1537
}
1538
1539
function get_configured_vip_detail($vipinterface = '', $family = 'inet', $what = 'ip') {
1540
	global $config;
1541
1542 c6c398c6 jim-p
	if (empty($vipinterface) ||
1543
	    !is_array($config['virtualip']) ||
1544
	    !is_array($config['virtualip']['vip']) ||
1545 2a5960b0 Luiz Otavio O Souza
	    empty($config['virtualip']['vip'])) {
1546
		return (NULL);
1547
	}
1548
1549
	$viparr = &$config['virtualip']['vip'];
1550
	foreach ($viparr as $vip) {
1551 d9901ff4 Chris Buechler
		if ($vip['mode'] != "carp" && $vip['mode'] != "ipalias") {
1552 76153238 Luiz Otavio O Souza
			continue;
1553 d9901ff4 Chris Buechler
		}
1554 76153238 Luiz Otavio O Souza
1555 d9901ff4 Chris Buechler
		if ($vipinterface != "_vip{$vip['uniqid']}") {
1556 76153238 Luiz Otavio O Souza
			continue;
1557 d9901ff4 Chris Buechler
		}
1558 76153238 Luiz Otavio O Souza
1559
		switch ($what) {
1560
			case 'subnet':
1561 2a5960b0 Luiz Otavio O Souza
				if ($family == 'inet' && is_ipaddrv4($vip['subnet']))
1562
					return ($vip['subnet_bits']);
1563
				else if ($family == 'inet6' && is_ipaddrv6($vip['subnet']))
1564
					return ($vip['subnet_bits']);
1565 76153238 Luiz Otavio O Souza
				break;
1566
			case 'iface':
1567 2a5960b0 Luiz Otavio O Souza
				return ($vip['interface']);
1568 76153238 Luiz Otavio O Souza
				break;
1569
			case 'vip':
1570 2a5960b0 Luiz Otavio O Souza
				return ($vip);
1571 76153238 Luiz Otavio O Souza
				break;
1572
			case 'ip':
1573
			default:
1574 d9901ff4 Chris Buechler
				if ($family == 'inet' && is_ipaddrv4($vip['subnet'])) {
1575 2a5960b0 Luiz Otavio O Souza
					return ($vip['subnet']);
1576 d9901ff4 Chris Buechler
				} else if ($family == 'inet6' && is_ipaddrv6($vip['subnet'])) {
1577 2a5960b0 Luiz Otavio O Souza
					return ($vip['subnet']);
1578 d9901ff4 Chris Buechler
				}
1579 76153238 Luiz Otavio O Souza
				break;
1580 5bbd08e1 Warren Baker
		}
1581 76153238 Luiz Otavio O Souza
		break;
1582 5bbd08e1 Warren Baker
	}
1583 abcb2bed Ermal Lu?i
1584 e0e28fdf Luiz Otavio O Souza
	return (NULL);
1585 e6c60013 Renato Botelho
}
1586 67b0902f pierrepomes
1587 88bc2760 Erik Fonnesbeck
/* comparison function for sorting by the order in which interfaces are normally created */
1588
function compare_interface_friendly_names($a, $b) {
1589 751533a2 Phil Davis
	if ($a == $b) {
1590 88bc2760 Erik Fonnesbeck
		return 0;
1591 751533a2 Phil Davis
	} else if ($a == 'wan') {
1592 88bc2760 Erik Fonnesbeck
		return -1;
1593 751533a2 Phil Davis
	} else if ($b == 'wan') {
1594 88bc2760 Erik Fonnesbeck
		return 1;
1595 751533a2 Phil Davis
	} else if ($a == 'lan') {
1596 88bc2760 Erik Fonnesbeck
		return -1;
1597 751533a2 Phil Davis
	} else if ($b == 'lan') {
1598 88bc2760 Erik Fonnesbeck
		return 1;
1599 751533a2 Phil Davis
	}
1600 88bc2760 Erik Fonnesbeck
1601
	return strnatcmp($a, $b);
1602
}
1603
1604 97ac6eb4 Christian McDonald
/**
1605
 * Get the configured interfaces list
1606
 *
1607
 * @param bool $with_disabled Include disabled interfaces
1608
 *
1609
 * @return array
1610
 */
1611
function get_configured_interface_list(bool $with_disabled = false) : array
1612
{
1613
	$iflist = [];
1614
	foreach (config_get_path('interfaces', []) as $if => $if_detail) {
1615
		if ($with_disabled || isset($if_detail['enable'])) {
1616 c8abe1d4 Ermal Luçi
			$iflist[$if] = $if;
1617 751533a2 Phil Davis
		}
1618 42c9d20e Ermal Luçi
	}
1619 c8abe1d4 Ermal Luçi
1620 97ac6eb4 Christian McDonald
	return ($iflist);
1621 c8abe1d4 Ermal Luçi
}
1622
1623 97ac6eb4 Christian McDonald
/**
1624
 * Return the configured (and real) interfaces list.
1625
 *
1626
 * @param bool $with_disabled Include disabled interfaces
1627
 *
1628
 * @return array
1629
 */
1630
function get_configured_interface_list_by_realif(bool $with_disabled = false) : array
1631
{
1632
	$iflist = [];
1633
	foreach (config_get_path('interfaces', []) as $if => $if_detail) {
1634
		if ($with_disabled || isset($if_detail['enable'])) {
1635 bb34737f Ermal Lu?i
			$tmpif = get_real_interface($if);
1636 97ac6eb4 Christian McDonald
			if (empty($tmpif)) {
1637
				continue;
1638 751533a2 Phil Davis
			}
1639 97ac6eb4 Christian McDonald
			$iflist[$tmpif] = $if;
1640 bb34737f Ermal Lu?i
		}
1641 8735afe8 Erik Fonnesbeck
	}
1642 bb34737f Ermal Lu?i
1643 97ac6eb4 Christian McDonald
	return ($iflist);
1644 bb34737f Ermal Lu?i
}
1645
1646 97ac6eb4 Christian McDonald
/**
1647
 * Return the configured interfaces list with their description.
1648
 *
1649
 * @param bool $with_disabled Include disabled interfaces
1650
 *
1651
 * @return array
1652
 */
1653
function get_configured_interface_with_descr(bool $with_disabled = false) : array
1654
{
1655
	global $user_settings;
1656 c8abe1d4 Ermal Luçi
1657 97ac6eb4 Christian McDonald
	$iflist = [];
1658
	foreach (config_get_path('interfaces', []) as $if => $if_detail) {
1659
		if ($with_disabled || isset($if_detail['enable'])) {
1660 66b989e8 jim-p
			$iflist[$if] = strtoupper(array_get_path($if_detail, 'descr', $if));
1661 0e218dc1 Ermal Luçi
		}
1662 42c9d20e Ermal Luçi
	}
1663 1d3510cf Phil Davis
1664 97ac6eb4 Christian McDonald
	if (is_array($user_settings)
1665
	    && array_get_path($user_settings, 'webgui/interfacessort')) {
1666 1d3510cf Phil Davis
		asort($iflist);
1667
	}
1668
1669 97ac6eb4 Christian McDonald
	return ($iflist);
1670 c8abe1d4 Ermal Luçi
}
1671
1672 4fe9c2dc Scott Ullrich
/*
1673
 *   get_configured_ip_addresses() - Return a list of all configured
1674 2a5960b0 Luiz Otavio O Souza
 *   IPv4 addresses.
1675 4fe9c2dc Scott Ullrich
 *
1676
 */
1677
function get_configured_ip_addresses() {
1678 5dbd619f smos
	global $config;
1679 a1e4e2a7 Ermal
1680 751533a2 Phil Davis
	if (!function_exists('get_interface_ip')) {
1681 a1e4e2a7 Ermal
		require_once("interfaces.inc");
1682 751533a2 Phil Davis
	}
1683 4fe9c2dc Scott Ullrich
	$ip_array = array();
1684
	$interfaces = get_configured_interface_list();
1685 a1e4e2a7 Ermal
	if (is_array($interfaces)) {
1686 751533a2 Phil Davis
		foreach ($interfaces as $int) {
1687 d9114ce0 Scott Ullrich
			$ipaddr = get_interface_ip($int);
1688
			$ip_array[$int] = $ipaddr;
1689
		}
1690 4fe9c2dc Scott Ullrich
	}
1691 2a5960b0 Luiz Otavio O Souza
	$interfaces = get_configured_vip_list('inet');
1692 751533a2 Phil Davis
	if (is_array($interfaces)) {
1693
		foreach ($interfaces as $int => $ipaddr) {
1694 d9114ce0 Scott Ullrich
			$ip_array[$int] = $ipaddr;
1695 751533a2 Phil Davis
		}
1696
	}
1697 5dbd619f smos
1698
	/* pppoe server */
1699 a1e4e2a7 Ermal
	if (is_array($config['pppoes']) && is_array($config['pppoes']['pppoe'])) {
1700 751533a2 Phil Davis
		foreach ($config['pppoes']['pppoe'] as $pppoe) {
1701 5dbd619f smos
			if ($pppoe['mode'] == "server") {
1702 751533a2 Phil Davis
				if (is_ipaddr($pppoe['localip'])) {
1703 50b84727 Viktor G
					$int = "poes". $pppoe['pppoeid'];
1704 5dbd619f smos
					$ip_array[$int] = $pppoe['localip'];
1705
				}
1706
			}
1707
		}
1708
	}
1709 a1e4e2a7 Ermal
1710 4fe9c2dc Scott Ullrich
	return $ip_array;
1711
}
1712 c8abe1d4 Ermal Luçi
1713 e6f7e0be smos
/*
1714
 *   get_configured_ipv6_addresses() - Return a list of all configured
1715 2a5960b0 Luiz Otavio O Souza
 *   IPv6 addresses.
1716 e6f7e0be smos
 *
1717
 */
1718 6a53de6f NewEraCracker
function get_configured_ipv6_addresses($linklocal_fallback = false) {
1719 e6f7e0be smos
	require_once("interfaces.inc");
1720
	$ipv6_array = array();
1721
	$interfaces = get_configured_interface_list();
1722 751533a2 Phil Davis
	if (is_array($interfaces)) {
1723
		foreach ($interfaces as $int) {
1724 cde28bfa Phil Davis
			$ipaddrv6 = text_to_compressed_ip6(get_interface_ipv6($int, false, $linklocal_fallback));
1725 e6f7e0be smos
			$ipv6_array[$int] = $ipaddrv6;
1726
		}
1727
	}
1728 2a5960b0 Luiz Otavio O Souza
	$interfaces = get_configured_vip_list('inet6');
1729 751533a2 Phil Davis
	if (is_array($interfaces)) {
1730
		foreach ($interfaces as $int => $ipaddrv6) {
1731 cde28bfa Phil Davis
			$ipv6_array[$int] = text_to_compressed_ip6($ipaddrv6);
1732 751533a2 Phil Davis
		}
1733
	}
1734 e6f7e0be smos
	return $ipv6_array;
1735
}
1736
1737 36f546e9 Scott Ullrich
/*
1738
 *   get_interface_list() - Return a list of all physical interfaces
1739
 *   along with MAC and status.
1740
 *
1741
 *   $mode = "active" - use ifconfig -lu
1742
 *           "media"  - use ifconfig to check physical connection
1743
 *			status (much slower)
1744
 */
1745 6d0aa23d Reid Linnemann
function get_interface_list($mode = "active", $keyby = "physical", $vfaces = false) {
1746 86a5e1a8 Renato Botelho
	global $config;
1747 65bed2d2 Scott Ullrich
	$upints = array();
1748 86a5e1a8 Renato Botelho
	/* get a list of virtual interface types */
1749 751533a2 Phil Davis
	if (!$vfaces) {
1750 086cf944 Phil Davis
		$vfaces = array(
1751 9ce38409 Scott Ullrich
				'bridge',
1752
				'ppp',
1753 27c0c7c6 Ermal Lu?i
				'pppoe',
1754 50b84727 Viktor G
				'poes',
1755 27c0c7c6 Ermal Lu?i
				'pptp',
1756
				'l2tp',
1757 9ce38409 Scott Ullrich
				'sl',
1758
				'gif',
1759 613571ea Ermal Luçi
				'gre',
1760 9ce38409 Scott Ullrich
				'faith',
1761
				'lo',
1762
				'ng',
1763 27616d6e Seth Mos
				'_vlan',
1764 7c53bc7b Erik Fonnesbeck
				'_wlan',
1765 9ce38409 Scott Ullrich
				'pflog',
1766 a42d1da2 Scott Ullrich
				'plip',
1767 9ce38409 Scott Ullrich
				'pfsync',
1768
				'enc',
1769
				'tun',
1770 1fb2bf25 Ermal Lu?i
				'lagg',
1771 795e6194 Viktor G
				'vip'
1772 f8cc5da5 Viktor G
		);
1773
	} else {
1774
		$vfaces = array(
1775
				'bridge',
1776 50b84727 Viktor G
				'poes',
1777 f8cc5da5 Viktor G
				'sl',
1778
				'faith',
1779
				'lo',
1780
				'ng',
1781
				'_vlan',
1782
				'_wlan',
1783
				'pflog',
1784
				'plip',
1785
				'pfsync',
1786
				'enc',
1787
				'tun',
1788
				'lagg',
1789
				'vip',
1790 c150479c Viktor G
				'l2tps'
1791 9ce38409 Scott Ullrich
		);
1792 36f546e9 Scott Ullrich
	}
1793 751533a2 Phil Davis
	switch ($mode) {
1794
		case "active":
1795
			$upints = pfSense_interface_listget(IFF_UP);
1796
			break;
1797
		case "media":
1798
			$intlist = pfSense_interface_listget();
1799 dc337505 Reid Linnemann
			$ifconfig = [];
1800 751533a2 Phil Davis
			exec("/sbin/ifconfig -a", $ifconfig);
1801
			$ifstatus = preg_grep('/status:/', $ifconfig);
1802
			foreach ($ifstatus as $status) {
1803
				$int = array_shift($intlist);
1804
				if (stristr($status, "active")) {
1805
					$upints[] = $int;
1806
				}
1807
			}
1808
			break;
1809
		default:
1810
			$upints = pfSense_interface_listget();
1811
			break;
1812 20203646 Colin Smith
	}
1813 86a5e1a8 Renato Botelho
	/* build interface list with netstat */
1814 dc337505 Reid Linnemann
	$linkinfo = [];
1815 86a5e1a8 Renato Botelho
	exec("/usr/bin/netstat -inW -f link | awk '{ print $1, $4 }'", $linkinfo);
1816
	array_shift($linkinfo);
1817 89d1f0f2 Scott Ullrich
	/* build ip address list with netstat */
1818 dc337505 Reid Linnemann
	$ipinfo = [];
1819 89d1f0f2 Scott Ullrich
	exec("/usr/bin/netstat -inW -f inet | awk '{ print $1, $4 }'", $ipinfo);
1820
	array_shift($ipinfo);
1821 751533a2 Phil Davis
	foreach ($linkinfo as $link) {
1822 89d1f0f2 Scott Ullrich
		$friendly = "";
1823 5bbd08e1 Warren Baker
		$alink = explode(" ", $link);
1824
		$ifname = rtrim(trim($alink[0]), '*');
1825
		/* trim out all numbers before checking for vfaces */
1826 4faf9170 Viktor G
		if (!in_array(array_shift(preg_split('/(\d-)*\d$/', $ifname)), $vfaces) &&
1827 c4865164 Renato Botelho
		    interface_is_vlan($ifname) == NULL &&
1828
		    interface_is_qinq($ifname) == NULL &&
1829 d764f8fc Viktor G
		    !stristr($ifname, "_wlan") &&
1830
		    !stristr($ifname, "_stf")) {
1831 20203646 Colin Smith
			$toput = array(
1832
					"mac" => trim($alink[1]),
1833
					"up" => in_array($ifname, $upints)
1834
				);
1835 751533a2 Phil Davis
			foreach ($ipinfo as $ip) {
1836 89d1f0f2 Scott Ullrich
				$aip = explode(" ", $ip);
1837 751533a2 Phil Davis
				if ($aip[0] == $ifname) {
1838 89d1f0f2 Scott Ullrich
					$toput['ipaddr'] = $aip[1];
1839
				}
1840
			}
1841 72993196 Ermal
			if (is_array($config['interfaces'])) {
1842 751533a2 Phil Davis
				foreach ($config['interfaces'] as $name => $int) {
1843
					if ($int['if'] == $ifname) {
1844
						$friendly = $name;
1845
					}
1846
				}
1847 20203646 Colin Smith
			}
1848 751533a2 Phil Davis
			switch ($keyby) {
1849 20203646 Colin Smith
			case "physical":
1850 751533a2 Phil Davis
				if ($friendly != "") {
1851 89d1f0f2 Scott Ullrich
					$toput['friendly'] = $friendly;
1852
				}
1853 a296c95d Seth Mos
				$dmesg_arr = array();
1854
				exec("/sbin/dmesg |grep $ifname | head -n1", $dmesg_arr);
1855
				preg_match_all("/<(.*?)>/i", $dmesg_arr[0], $dmesg);
1856
				$toput['dmesg'] = $dmesg[1][0];
1857 20203646 Colin Smith
				$iflist[$ifname] = $toput;
1858 3154d7ed Colin Smith
				break;
1859 4aca19b3 Scott Ullrich
			case "ppp":
1860 86a5e1a8 Renato Botelho
1861 20203646 Colin Smith
			case "friendly":
1862 751533a2 Phil Davis
				if ($friendly != "") {
1863 89d1f0f2 Scott Ullrich
					$toput['if'] = $ifname;
1864
					$iflist[$friendly] = $toput;
1865
				}
1866 3154d7ed Colin Smith
				break;
1867
			}
1868 5bbd08e1 Warren Baker
		}
1869
	}
1870
	return $iflist;
1871 5b237745 Scott Ullrich
}
1872
1873 f2286620 Luiz Souza
function get_lagg_interface_list() {
1874
	global $config;
1875
1876
	$plist = array();
1877
	if (isset($config['laggs']['lagg']) && is_array($config['laggs']['lagg'])) {
1878
		foreach ($config['laggs']['lagg'] as $lagg) {
1879
			$lagg['mac'] = get_interface_mac($lagg['laggif']);
1880
			$lagg['islagg'] = true;
1881
			$plist[$lagg['laggif']] = $lagg;
1882
		}
1883
	}
1884
1885
	return ($plist);
1886
}
1887
1888 2b4d37de Ermal Lu?i
/****f* util/log_error
1889
* NAME
1890
*   log_error  - Sends a string to syslog.
1891
* INPUTS
1892
*   $error     - string containing the syslog message.
1893
* RESULT
1894
*   null
1895
******/
1896
function log_error($error) {
1897 5bbd08e1 Warren Baker
	global $g;
1898
	$page = $_SERVER['SCRIPT_NAME'];
1899 866b1d61 jim-p
	if (empty($page)) {
1900
		$files = get_included_files();
1901
		$page = basename($files[0]);
1902
	}
1903 0d0cb047 jim-p
	syslog(LOG_ERR, "$page: $error");
1904 2568e151 Christian McDonald
	if (g_get('debug')) {
1905 dc337505 Reid Linnemann
		syslog(LOG_WARNING, var_export(debug_backtrace()));
1906 751533a2 Phil Davis
	}
1907 5bbd08e1 Warren Baker
	return;
1908 2b4d37de Ermal Lu?i
}
1909
1910 3aba1835 Scott Ullrich
/****f* util/log_auth
1911
* NAME
1912 1198abf9 PiBa-NL
*   log_auth   - Sends a string to syslog as LOG_AUTH facility
1913 3aba1835 Scott Ullrich
* INPUTS
1914
*   $error     - string containing the syslog message.
1915
* RESULT
1916
*   null
1917
******/
1918
function log_auth($error) {
1919 5bbd08e1 Warren Baker
	global $g;
1920
	$page = $_SERVER['SCRIPT_NAME'];
1921 6c186dae jim-p
	$level = config_path_enabled('system/webgui', 'quietlogin') ? LOG_NOTICE|LOG_AUTH : LOG_AUTH;
1922
	syslog($level, "{$page}: {$error}");
1923 2568e151 Christian McDonald
	if (g_get('debug')) {
1924 dc337505 Reid Linnemann
		syslog(LOG_WARNING, var_export(debug_backtrace()));
1925 751533a2 Phil Davis
	}
1926 5bbd08e1 Warren Baker
	return;
1927 3aba1835 Scott Ullrich
}
1928
1929 83bc3749 Ermal Lu?i
/****f* util/exec_command
1930
 * NAME
1931
 *   exec_command - Execute a command and return a string of the result.
1932
 * INPUTS
1933
 *   $command   - String of the command to be executed.
1934
 * RESULT
1935
 *   String containing the command's result.
1936
 * NOTES
1937
 *   This function returns the command's stdout and stderr.
1938
 ******/
1939
function exec_command($command) {
1940 5bbd08e1 Warren Baker
	$output = array();
1941 873c1701 Renato Botelho
	exec($command . ' 2>&1', $output);
1942 5bbd08e1 Warren Baker
	return(implode("\n", $output));
1943 83bc3749 Ermal Lu?i
}
1944
1945 e00ad357 Renato Botelho
/* wrapper for exec()
1946 f0b41548 stilez
   Executes in background or foreground.
1947
   For background execution, returns PID of background process to allow calling code control */
1948
function mwexec($command, $nologentry = false, $clearsigmask = false, $background = false) {
1949 5b237745 Scott Ullrich
	global $g;
1950 f0b41548 stilez
	$retval = 0;
1951 435a418f Ermal
1952 2568e151 Christian McDonald
	if (g_get('debug')) {
1953 751533a2 Phil Davis
		if (!$_SERVER['REMOTE_ADDR']) {
1954 f0b41548 stilez
			echo "mwexec(): $command" . ($background ? " [BG]":"") . "\n";
1955 751533a2 Phil Davis
		}
1956 f9db3cda Seth Mos
	}
1957 b61e8960 jim-p
	if ($clearsigmask) {
1958
		$oldset = array();
1959
		pcntl_sigprocmask(SIG_SETMASK, array(), $oldset);
1960
	}
1961 f0b41548 stilez
1962 2b1f6ed2 stilez
	if ($background) {
1963
		// start background process and return PID
1964
		$retval = exec("/usr/bin/nohup $command > /dev/null 2>&1 & echo $!");
1965
	} else {
1966
		// run in foreground, and (optionally) log if nonzero return
1967
		$outputarray = array();
1968 f0b41548 stilez
		exec("$command 2>&1", $outputarray, $retval);
1969 4e322e2c Phil Davis
		if (($retval <> 0) && (!$nologentry || isset($config['system']['developerspew']))) {
1970 f812b883 stilez
			log_error(sprintf(gettext("The command '%1\$s' returned exit code '%2\$d', the output was '%3\$s' "), $command, $retval, implode(" ", $outputarray)));
1971 4e322e2c Phil Davis
		}
1972 f0b41548 stilez
	}
1973
1974 b61e8960 jim-p
	if ($clearsigmask) {
1975
		pcntl_sigprocmask(SIG_SETMASK, $oldset);
1976
	}
1977 435a418f Ermal
1978 98bbf05a Scott Ullrich
	return $retval;
1979 5b237745 Scott Ullrich
}
1980
1981
/* wrapper for exec() in background */
1982 b61e8960 jim-p
function mwexec_bg($command, $clearsigmask = false) {
1983 f0b41548 stilez
	return mwexec($command, false, $clearsigmask, true);
1984 5b237745 Scott Ullrich
}
1985
1986 3673b6d0 Renato Botelho do Couto
/*
1987
 * Unlink a file, or pattern-match of a file, if it exists
1988
 *
1989
 * If the file/path contains glob() compatible wildcards, all matching files
1990
 * will be unlinked.
1991
 * Any warning/errors are suppressed (e.g. no matching files to delete)
1992
 * If there are matching file(s) and they were all unlinked OK, then return
1993
 * true.  Otherwise return false (the requested file(s) did not exist, or
1994
 * could not be deleted), this allows the caller to know if they were the one
1995
 * to successfully delete the file(s).
1996
 */
1997 5b237745 Scott Ullrich
function unlink_if_exists($fn) {
1998 336cb718 Scott Ullrich
	$to_do = glob($fn);
1999 a85ad858 stilez
	if (is_array($to_do) && count($to_do) > 0) {
2000 3673b6d0 Renato Botelho do Couto
		// Returns an array of boolean indicating if each unlink worked
2001 d96a39ba Phil Davis
		$results = @array_map("unlink", $to_do);
2002
		// If there is no false in the array, then all went well
2003
		$result = !in_array(false, $results, true);
2004 336cb718 Scott Ullrich
	} else {
2005 d96a39ba Phil Davis
		$result = @unlink($fn);
2006 336cb718 Scott Ullrich
	}
2007 d96a39ba Phil Davis
	return $result;
2008 5b237745 Scott Ullrich
}
2009 3673b6d0 Renato Botelho do Couto
2010 5b237745 Scott Ullrich
/* make a global alias table (for faster lookups) */
2011 dbc1b8ee jim-p
function alias_make_table() {
2012 b30acd45 Reid Linnemann
	global $aliastable;
2013 98bbf05a Scott Ullrich
2014 5b237745 Scott Ullrich
	$aliastable = array();
2015 98bbf05a Scott Ullrich
2016 b30acd45 Reid Linnemann
	foreach (config_get_path('aliases/alias', []) as $alias) {
2017 217f42ec jim-p
		if (!is_array($alias) || empty($alias)) {
2018
			continue;
2019
		}
2020 dbc1b8ee jim-p
		if ($alias['name']) {
2021
			$aliastable[$alias['name']] = $alias['address'];
2022 5b237745 Scott Ullrich
		}
2023
	}
2024
}
2025 5ffa3389 Ermal
2026 5b237745 Scott Ullrich
/* check if an alias exists */
2027
function is_alias($name) {
2028
	global $aliastable;
2029 98bbf05a Scott Ullrich
2030 5b237745 Scott Ullrich
	return isset($aliastable[$name]);
2031 b8014f9d Scott Ullrich
}
2032 27ff8a3c Scott Ullrich
2033 5ffa3389 Ermal
function alias_get_type($name) {
2034 86a5e1a8 Renato Botelho
2035 b30acd45 Reid Linnemann
	foreach (config_get_path('aliases/alias', []) as $alias) {
2036
		if ($name == $alias['name']) {
2037
			return $alias['type'];
2038 5ffa3389 Ermal
		}
2039
	}
2040
2041 86a5e1a8 Renato Botelho
	return "";
2042 5ffa3389 Ermal
}
2043
2044 3789fca8 Marcos Mendoza
/* expand a url alias, if necessary */
2045 5b237745 Scott Ullrich
function alias_expand($name) {
2046 b30acd45 Reid Linnemann
	global $aliastable;
2047 2ec7ab35 Chris Buechler
	$urltable_prefix = "/var/db/aliastables/";
2048
	$urltable_filename = $urltable_prefix . $name . ".txt";
2049 98bbf05a Scott Ullrich
2050 751533a2 Phil Davis
	if (isset($aliastable[$name])) {
2051 a97a77a2 Phil Davis
		// alias names cannot be strictly numeric. redmine #4289
2052
		if (is_numericint($name)) {
2053
			return null;
2054
		}
2055 3673b6d0 Renato Botelho do Couto
		/*
2056 3789fca8 Marcos Mendoza
		 * make sure if it's a url alias, it actually exists.
2057
		 * redmine #5845, #13068
2058 3673b6d0 Renato Botelho do Couto
		 */
2059 b30acd45 Reid Linnemann
		foreach (config_get_path('aliases/alias', []) as $alias) {
2060 2ec7ab35 Chris Buechler
			if ($alias['name'] == $name) {
2061 3789fca8 Marcos Mendoza
				if (in_array($alias['type'], ['url', 'url_ports', 'urltable', "urltable_ports"])) {
2062 3673b6d0 Renato Botelho do Couto
					if (is_URL($alias['url']) &&
2063
					    file_exists($urltable_filename) &&
2064 3ee90a3e Viktor G
					    !empty(trim(file_get_contents($urltable_filename)))) {
2065 2ec7ab35 Chris Buechler
						return "\${$name}";
2066
					} else {
2067
						return null;
2068
					}
2069
				}
2070
			}
2071
		}
2072 4335dc87 Bill Marquette
		return "\${$name}";
2073 3673b6d0 Renato Botelho do Couto
	} else if (is_ipaddr($name) || is_subnet($name) ||
2074
	    is_port_or_range($name)) {
2075 57989da5 Scott Ullrich
		return "{$name}";
2076 751533a2 Phil Davis
	} else {
2077 5b237745 Scott Ullrich
		return null;
2078 751533a2 Phil Davis
	}
2079 5b237745 Scott Ullrich
}
2080
2081 c7de8be4 jim-p
function alias_expand_urltable($name) {
2082
	$urltable_prefix = "/var/db/aliastables/";
2083
	$urltable_filename = $urltable_prefix . $name . ".txt";
2084
2085 b30acd45 Reid Linnemann
	foreach (config_get_path('aliases/alias', []) as $alias) {
2086 3673b6d0 Renato Botelho do Couto
		if (!preg_match("/urltable/i", $alias['type']) ||
2087
		    ($alias['name'] != $name)) {
2088
			continue;
2089
		}
2090
2091
		if (is_URL($alias["url"]) && file_exists($urltable_filename)) {
2092
			if (!filesize($urltable_filename)) {
2093
				// file exists, but is empty, try to sync
2094
				send_event("service sync alias {$name}");
2095 5ffa3389 Ermal
			}
2096 3673b6d0 Renato Botelho do Couto
			return $urltable_filename;
2097
		} else {
2098
			send_event("service sync alias {$name}");
2099
			break;
2100 c7de8be4 jim-p
		}
2101
	}
2102
	return null;
2103
}
2104
2105 dd83f869 lukehamburg
/* obtain MAC address given an IP address by looking at the ARP/NDP table */
2106 20cf8d8e lukehamburg
function arp_get_mac_by_ip($ip, $do_ping = true) {
2107 6c2f0930 lukehamburg
	unset($macaddr);
2108
	$retval = 1;
2109 dd83f869 lukehamburg
	switch (is_ipaddr($ip)) {
2110
		case 4:
2111 c516cb28 lukehamburg
			if ($do_ping === true) {
2112
				mwexec("/sbin/ping -c 1 -t 1 " . escapeshellarg($ip), true);
2113
			}
2114 6c2f0930 lukehamburg
			$macaddr = exec("/usr/sbin/arp -n " . escapeshellarg($ip) . " | /usr/bin/awk '{print $4}'", $output, $retval);
2115 dd83f869 lukehamburg
			break;
2116
		case 6:
2117 c516cb28 lukehamburg
			if ($do_ping === true) {
2118
				mwexec("/sbin/ping6 -c 1 -X 1 " . escapeshellarg($ip), true);
2119
			}
2120 6c2f0930 lukehamburg
			$macaddr = exec("/usr/sbin/ndp -n " . escapeshellarg($ip) . " | /usr/bin/awk '{print $2}'", $output, $retval);
2121 dd83f869 lukehamburg
			break;
2122
	}
2123 6c2f0930 lukehamburg
	if ($retval == 0 && is_macaddr($macaddr)) {
2124
		return $macaddr;
2125
	} else {
2126
		return false;
2127 5b237745 Scott Ullrich
	}
2128
}
2129
2130 98bbf05a Scott Ullrich
/* return a fieldname that is safe for xml usage */
2131
function xml_safe_fieldname($fieldname) {
2132 4f3fc80d Renato Botelho
	$replace = array(
2133
	    '/', '-', ' ', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')',
2134
	    '_', '+', '=', '{', '}', '[', ']', '|', '/', '<', '>', '?',
2135
	    ':', ',', '.', '\'', '\\'
2136
	);
2137 ddce8ef2 Colin Smith
	return strtolower(str_replace($replace, "", $fieldname));
2138 98bbf05a Scott Ullrich
}
2139
2140 805b9ab6 Ermal
function mac_format($clientmac) {
2141 86a5e1a8 Renato Botelho
	global $config, $cpzone;
2142 4129df39 Scott Ullrich
2143 86a5e1a8 Renato Botelho
	$mac = explode(":", $clientmac);
2144
	$mac_format = $cpzone ? $config['captiveportal'][$cpzone]['radmac_format'] : false;
2145 4129df39 Scott Ullrich
2146 751533a2 Phil Davis
	switch ($mac_format) {
2147
		case 'singledash':
2148
			return "$mac[0]$mac[1]$mac[2]-$mac[3]$mac[4]$mac[5]";
2149 4129df39 Scott Ullrich
2150 751533a2 Phil Davis
		case 'ietf':
2151
			return "$mac[0]-$mac[1]-$mac[2]-$mac[3]-$mac[4]-$mac[5]";
2152 4129df39 Scott Ullrich
2153 751533a2 Phil Davis
		case 'cisco':
2154
			return "$mac[0]$mac[1].$mac[2]$mac[3].$mac[4]$mac[5]";
2155 4129df39 Scott Ullrich
2156 751533a2 Phil Davis
		case 'unformatted':
2157
			return "$mac[0]$mac[1]$mac[2]$mac[3]$mac[4]$mac[5]";
2158 4129df39 Scott Ullrich
2159 751533a2 Phil Davis
		default:
2160
			return $clientmac;
2161 86a5e1a8 Renato Botelho
	}
2162 4129df39 Scott Ullrich
}
2163
2164 1622230a Steve Beaver
function resolve_retry($hostname, $protocol = 'inet') {
2165 5ed5f14d Viktor G
	$retries = 10;
2166 f7163f44 Steve Beaver
	$numrecords = 1;
2167 d3ac1cea Viktor G
	$recresult = array();
2168 1622230a Steve Beaver
2169 f381d8d8 Steve Beaver
	switch ($protocol) {
2170
		case 'any':
2171
			$checkproto = 'is_ipaddr';
2172
			$dnsproto = DNS_ANY;
2173
			$dnstype = array('A', 'AAAA');
2174
			break;
2175
		case 'inet6':
2176
			$checkproto = 'is_ipaddrv6';
2177
			$dnsproto = DNS_AAAA;
2178
			$dnstype = array('AAAA');
2179
			break;
2180 fd30ce6a Christian McDonald
		case 'inet':
2181 f381d8d8 Steve Beaver
		default:
2182
			$checkproto = 'is_ipaddrv4';
2183
			$dnsproto = DNS_A;
2184
			$dnstype = array('A');
2185
			break;
2186
	}
2187 1622230a Steve Beaver
2188 f381d8d8 Steve Beaver
	for ($i = 0; $i < $retries; $i++) {
2189 d3ac1cea Viktor G
		if ($checkproto($hostname)) {
2190
			return $hostname;
2191
		}
2192 1622230a Steve Beaver
2193 8f85087b Viktor Gurov
		$dnsresult = @dns_get_record($hostname, $dnsproto);
2194 1622230a Steve Beaver
2195 8f85087b Viktor Gurov
		if (!empty($dnsresult)) {
2196 dc337505 Reid Linnemann
			foreach ($dnsresult as $ip) {
2197 8f85087b Viktor Gurov
				if (is_array($ip)) {
2198
					if (in_array($ip['type'], $dnstype)) {
2199 fd30ce6a Christian McDonald
						if ($checkproto($ip['ip'])) {
2200 1622230a Steve Beaver
							$recresult[] = $ip['ip'];
2201
						}
2202
2203 fd30ce6a Christian McDonald
						if ($checkproto($ip['ipv6'])) {
2204 1622230a Steve Beaver
							$recresult[] = $ip['ipv6'];
2205
						}
2206 8f85087b Viktor Gurov
					}
2207 d3ac1cea Viktor G
				}
2208
			}
2209 5bbd08e1 Warren Baker
		}
2210 979cd6db Scott Ullrich
2211 1622230a Steve Beaver
		// Return on success
2212
		if (!empty($recresult)) {
2213
			if ($numrecords == 1) {
2214
				return $recresult[0];
2215
			} else {
2216
				return array_slice($recresult, 0, $numrecords);
2217
			}
2218 d3ac1cea Viktor G
		}
2219 1622230a Steve Beaver
2220
		usleep(100000);
2221 d3ac1cea Viktor G
	}
2222
2223 5bbd08e1 Warren Baker
	return false;
2224 979cd6db Scott Ullrich
}
2225
2226 44bfd1fa Scott Ullrich
function format_bytes($bytes) {
2227 4eac105f Phil Davis
	if ($bytes >= 1099511627776) {
2228 7b512ab3 Phil Davis
		return sprintf("%.2f TiB", $bytes/1099511627776);
2229 4eac105f Phil Davis
	} else if ($bytes >= 1073741824) {
2230 7b512ab3 Phil Davis
		return sprintf("%.2f GiB", $bytes/1073741824);
2231 44bfd1fa Scott Ullrich
	} else if ($bytes >= 1048576) {
2232 7b512ab3 Phil Davis
		return sprintf("%.2f MiB", $bytes/1048576);
2233 44bfd1fa Scott Ullrich
	} else if ($bytes >= 1024) {
2234 7b512ab3 Phil Davis
		return sprintf("%.0f KiB", $bytes/1024);
2235 44bfd1fa Scott Ullrich
	} else {
2236 10ae204f Stephen Beaver
		return sprintf("%d B", $bytes);
2237 44bfd1fa Scott Ullrich
	}
2238
}
2239
2240 fd30ce6a Christian McDonald
function format_number(int $num, int $precision = 3): string
2241
{
2242
    $units = ['', 'K', 'M', 'G', 'T'];
2243
    for ($i = 0; $num >= 1000; $i++) {
2244
        $num /= 1000;
2245
    }
2246
    return (round($num, $precision) . $units[$i]);
2247 cc2cff0b Luiz Otavio O Souza
}
2248
2249 15c2e494 joshuasign
function unformat_number($formated_num) {
2250
	$num = strtoupper($formated_num);
2251 fd30ce6a Christian McDonald
2252 15c2e494 joshuasign
	if ( strpos($num,"T") !== false ) {
2253
		$num = str_replace("T","",$num) * 1000 * 1000 * 1000 * 1000;
2254
	} else if ( strpos($num,"G") !== false ) {
2255
		$num = str_replace("G","",$num) * 1000 * 1000 * 1000;
2256
	} else if ( strpos($num,"M") !== false ) {
2257
		$num = str_replace("M","",$num) * 1000 * 1000;
2258
	} else if ( strpos($num,"K") !== false ) {
2259
		$num = str_replace("K","",$num) * 1000;
2260
	}
2261 fd30ce6a Christian McDonald
2262 15c2e494 joshuasign
	return $num;
2263
}
2264
2265 4f7956ad Steve Beaver
function update_filter_reload_status($text, $new=false) {
2266 5bbd08e1 Warren Baker
	global $g;
2267 2b4d37de Ermal Lu?i
2268 4f7956ad Steve Beaver
	if ($new) {
2269
		file_put_contents("{$g['varrun_path']}/filter_reload_status", $text  . PHP_EOL);
2270
	} else {
2271
		file_put_contents("{$g['varrun_path']}/filter_reload_status", $text  . PHP_EOL, FILE_APPEND);
2272
	}
2273 2b4d37de Ermal Lu?i
}
2274
2275 a2219caf Renato Botelho
/****** util/return_dir_as_array
2276 2b4d37de Ermal Lu?i
 * NAME
2277
 *   return_dir_as_array - Return a directory's contents as an array.
2278
 * INPUTS
2279 a2219caf Renato Botelho
 *   $dir          - string containing the path to the desired directory.
2280
 *   $filter_regex - string containing a regular expression to filter file names. Default empty.
2281 2b4d37de Ermal Lu?i
 * RESULT
2282
 *   $dir_array - array containing the directory's contents. This array will be empty if the path specified is invalid.
2283
 ******/
2284 a2219caf Renato Botelho
function return_dir_as_array($dir, $filter_regex = '') {
2285 5bbd08e1 Warren Baker
	$dir_array = array();
2286
	if (is_dir($dir)) {
2287
		if ($dh = opendir($dir)) {
2288
			while (($file = readdir($dh)) !== false) {
2289 751533a2 Phil Davis
				if (($file == ".") || ($file == "..")) {
2290 a2219caf Renato Botelho
					continue;
2291 751533a2 Phil Davis
				}
2292 a2219caf Renato Botelho
2293 751533a2 Phil Davis
				if (empty($filter_regex) || preg_match($filter_regex, $file)) {
2294 5bbd08e1 Warren Baker
					array_push($dir_array, $file);
2295 751533a2 Phil Davis
				}
2296 5bbd08e1 Warren Baker
			}
2297
			closedir($dh);
2298
		}
2299
	}
2300
	return $dir_array;
2301 2b4d37de Ermal Lu?i
}
2302
2303
function run_plugins($directory) {
2304 5bbd08e1 Warren Baker
	/* process packager manager custom rules */
2305
	$files = return_dir_as_array($directory);
2306
	if (is_array($files)) {
2307
		foreach ($files as $file) {
2308 751533a2 Phil Davis
			if (stristr($file, ".sh") == true) {
2309 5bbd08e1 Warren Baker
				mwexec($directory . $file . " start");
2310 086cf944 Phil Davis
			} else if (!is_dir($directory . "/" . $file) && stristr($file, ".inc")) {
2311 5bbd08e1 Warren Baker
				require_once($directory . "/" . $file);
2312 751533a2 Phil Davis
			}
2313 2990acf8 Scott Ullrich
		}
2314 5bbd08e1 Warren Baker
	}
2315 2b4d37de Ermal Lu?i
}
2316
2317
/*
2318
 *    safe_mkdir($path, $mode = 0755)
2319
 *    create directory if it doesn't already exist and isn't a file!
2320
 */
2321 6c07db48 Phil Davis
function safe_mkdir($path, $mode = 0755) {
2322 5bbd08e1 Warren Baker
	if (!is_file($path) && !is_dir($path)) {
2323
		return @mkdir($path, $mode, true);
2324
	} else {
2325
		return false;
2326
	}
2327 2b4d37de Ermal Lu?i
}
2328
2329 aa4f498d Erik Fonnesbeck
/*
2330
 * get_sysctl($names)
2331
 * Get values of sysctl OID's listed in $names (accepts an array or a single
2332
 * name) and return an array of key/value pairs set for those that exist
2333
 */
2334
function get_sysctl($names) {
2335 751533a2 Phil Davis
	if (empty($names)) {
2336 aa4f498d Erik Fonnesbeck
		return array();
2337 751533a2 Phil Davis
	}
2338 aa4f498d Erik Fonnesbeck
2339
	if (is_array($names)) {
2340
		$name_list = array();
2341
		foreach ($names as $name) {
2342
			$name_list[] = escapeshellarg($name);
2343
		}
2344 751533a2 Phil Davis
	} else {
2345 aa4f498d Erik Fonnesbeck
		$name_list = array(escapeshellarg($names));
2346 751533a2 Phil Davis
	}
2347 aa4f498d Erik Fonnesbeck
2348 3c44c845 Luiz Souza
	exec("/sbin/sysctl -iq " . implode(" ", $name_list), $output);
2349 aa4f498d Erik Fonnesbeck
	$values = array();
2350
	foreach ($output as $line) {
2351
		$line = explode(": ", $line, 2);
2352 751533a2 Phil Davis
		if (count($line) == 2) {
2353 aa4f498d Erik Fonnesbeck
			$values[$line[0]] = $line[1];
2354 751533a2 Phil Davis
		}
2355 aa4f498d Erik Fonnesbeck
	}
2356
2357
	return $values;
2358
}
2359
2360 ff23363d Renato Botelho
/*
2361
 * get_single_sysctl($name)
2362
 * Wrapper for get_sysctl() to simplify read of a single sysctl value
2363
 * return the value for sysctl $name or empty string if it doesn't exist
2364
 */
2365
function get_single_sysctl($name) {
2366 751533a2 Phil Davis
	if (empty($name)) {
2367 ff23363d Renato Botelho
		return "";
2368 751533a2 Phil Davis
	}
2369 ff23363d Renato Botelho
2370
	$value = get_sysctl($name);
2371 751533a2 Phil Davis
	if (empty($value) || !isset($value[$name])) {
2372 ff23363d Renato Botelho
		return "";
2373 751533a2 Phil Davis
	}
2374 ff23363d Renato Botelho
2375
	return $value[$name];
2376
}
2377
2378 aa4f498d Erik Fonnesbeck
/*
2379
 * set_sysctl($value_list)
2380
 * Set sysctl OID's listed as key/value pairs and return
2381
 * an array with keys set for those that succeeded
2382
 */
2383
function set_sysctl($values) {
2384 751533a2 Phil Davis
	if (empty($values)) {
2385 aa4f498d Erik Fonnesbeck
		return array();
2386 751533a2 Phil Davis
	}
2387 aa4f498d Erik Fonnesbeck
2388
	$value_list = array();
2389
	foreach ($values as $key => $value) {
2390
		$value_list[] = escapeshellarg($key) . "=" . escapeshellarg($value);
2391
	}
2392
2393 3c44c845 Luiz Souza
	exec("/sbin/sysctl -iq " . implode(" ", $value_list), $output, $success);
2394 aa4f498d Erik Fonnesbeck
2395
	/* Retry individually if failed (one or more read-only) */
2396
	if ($success <> 0 && count($value_list) > 1) {
2397
		foreach ($value_list as $value) {
2398 3c44c845 Luiz Souza
			exec("/sbin/sysctl -iq " . $value, $output);
2399 aa4f498d Erik Fonnesbeck
		}
2400
	}
2401
2402
	$ret = array();
2403
	foreach ($output as $line) {
2404
		$line = explode(": ", $line, 2);
2405 751533a2 Phil Davis
		if (count($line) == 2) {
2406 aa4f498d Erik Fonnesbeck
			$ret[$line[0]] = true;
2407 751533a2 Phil Davis
		}
2408 aa4f498d Erik Fonnesbeck
	}
2409
2410
	return $ret;
2411
}
2412
2413 82f75815 Renato Botelho
/*
2414
 * set_single_sysctl($name, $value)
2415
 * Wrapper to set_sysctl() to make it simple to set only one sysctl
2416 751533a2 Phil Davis
 * returns boolean meaning if it succeeded
2417 82f75815 Renato Botelho
 */
2418
function set_single_sysctl($name, $value) {
2419 751533a2 Phil Davis
	if (empty($name)) {
2420 82f75815 Renato Botelho
		return false;
2421 751533a2 Phil Davis
	}
2422 82f75815 Renato Botelho
2423
	$result = set_sysctl(array($name => $value));
2424
2425 751533a2 Phil Davis
	if (!isset($result[$name]) || $result[$name] != $value) {
2426 82f75815 Renato Botelho
		return false;
2427 751533a2 Phil Davis
	}
2428 82f75815 Renato Botelho
2429
	return true;
2430
}
2431
2432 2b4d37de Ermal Lu?i
/*
2433
 *     get_memory()
2434
 *     returns an array listing the amount of
2435
 *     memory installed in the hardware
2436 517fb89e Phil Davis
 *     [0] net memory available for the OS (FreeBSD) after some is taken by BIOS, video or whatever - e.g. 235 MBytes
2437
 *     [1] real (actual) memory of the system, should be the size of the RAM card/s - e.g. 256 MBytes
2438 2b4d37de Ermal Lu?i
 */
2439
function get_memory() {
2440 971de1f9 Renato Botelho
	$physmem = get_single_sysctl("hw.physmem");
2441
	$realmem = get_single_sysctl("hw.realmem");
2442 054c2541 jim-p
	/* Ensure $physmem has a sane value */
2443
	if (!is_numericint($physmem) &&
2444
	    is_numericint($realmem)) {
2445
		$physmem = $realmem;
2446
	}
2447
	/* Ensure $realmem has a sane value */
2448
	if (!is_numericint($realmem) &&
2449
	    is_numericint($physmem)) {
2450
		$realmem = $physmem;
2451
	}
2452
	/* If both are invalid, something deeper is wrong */
2453
	if (!is_numericint($physmem) &&
2454
	    is_numericint($realmem)) {
2455
		/* Try checking by pages instead */
2456
		$membypages = (int) get_single_sysctl("vm.stats.vm.v_page_count") * (int) get_single_sysctl("vm.stats.vm.v_page_size");
2457
		if (is_numericint($membypages)) {
2458
			$physmem = $membypages;
2459
			$realmem = $membypages;
2460
		} else {
2461
			/* Everything failed, return zeroes */
2462
			$physmem = 0;
2463
			$realmem = 0;
2464
		}
2465
	}
2466 5cd73772 Ermal
	/* convert from bytes to megabytes */
2467 054c2541 jim-p
	return array(((int) $physmem/1048576), ((int) $realmem/1048576));
2468 2b4d37de Ermal Lu?i
}
2469
2470
function mute_kernel_msgs() {
2471 dc337505 Reid Linnemann
	if (config_path_enabled('system','enableserial')) {
2472 86a5e1a8 Renato Botelho
		return;
2473 751533a2 Phil Davis
	}
2474 5bbd08e1 Warren Baker
	exec("/sbin/conscontrol mute on");
2475 2b4d37de Ermal Lu?i
}
2476
2477
function unmute_kernel_msgs() {
2478 5bbd08e1 Warren Baker
	exec("/sbin/conscontrol mute off");
2479 2b4d37de Ermal Lu?i
}
2480
2481
function start_devd() {
2482 505e3e0e Renato Botelho
	global $g;
2483
2484 91677c09 Luiz Souza
	/* Generate hints for the kernel loader. */
2485
	$module_paths = explode(";", get_single_sysctl("kern.module_path"));
2486 dc337505 Reid Linnemann
	foreach ($module_paths as $path) {
2487 18b8e415 jim-p
		if (!is_dir($path) ||
2488
		    (($files = scandir($path)) == false)) {
2489 91677c09 Luiz Souza
			continue;
2490
		}
2491
		$found = false;
2492 dc337505 Reid Linnemann
		foreach ($files as $file) {
2493 91677c09 Luiz Souza
			if (strlen($file) > 3 &&
2494
			    strcasecmp(substr($file, -3), ".ko") == 0) {
2495
				$found = true;
2496
				break;
2497
			}
2498
		}
2499
		if ($found == false) {
2500
			continue;
2501
		}
2502
		$_gb = exec("/usr/sbin/kldxref $path");
2503
		unset($_gb);
2504
	}
2505
2506 751533a2 Phil Davis
	/* Use the undocumented -q options of devd to quiet its log spamming */
2507 505e3e0e Renato Botelho
	$_gb = exec("/sbin/devd -q -f /etc/{$g['product_name']}-devd.conf");
2508 5bbd08e1 Warren Baker
	sleep(1);
2509 a7f79eda Ermal LUÇI
	unset($_gb);
2510 2b4d37de Ermal Lu?i
}
2511
2512 66bcba1b Ermal
function is_interface_vlan_mismatch() {
2513 dc337505 Reid Linnemann
	foreach (config_get_path('vlans/vlan', []) as $vlan) {
2514
		if (substr($vlan['if'], 0, 4) == "lagg") {
2515
			return false;
2516
		}
2517
		if (does_interface_exist($vlan['if']) == false) {
2518
			return true;
2519 5bbd08e1 Warren Baker
		}
2520
	}
2521 66bcba1b Ermal
2522
	return false;
2523
}
2524
2525 2b4d37de Ermal Lu?i
function is_interface_mismatch() {
2526 857da904 Scott Ullrich
	global $config, $g;
2527 2b4d37de Ermal Lu?i
2528 857da904 Scott Ullrich
	$do_assign = false;
2529
	$i = 0;
2530 e0a45ce0 Erik Fonnesbeck
	$missing_interfaces = array();
2531 72993196 Ermal
	if (is_array($config['interfaces'])) {
2532 dc337505 Reid Linnemann
		foreach ($config['interfaces'] as $ifcfg) {
2533 12bcf7e9 Luiz Souza
			if (interface_is_vlan($ifcfg['if']) != NULL ||
2534 0793de1a Luiz Souza
			    interface_is_qinq($ifcfg['if']) != NULL ||
2535 c13bf6d4 Christian McDonald
			    preg_match("/^bridge|^cua|^enc|^gif|^gre|^ipsec|^l2tp|^lagg|^ngeth|^ovpn|^ppp|^pptp|^tap|^tun|^ue|vlan|^wg|_wlan|_\d{0,4}_\d{0,4}$/i", $ifcfg['if'])) {
2536 857da904 Scott Ullrich
				// Do not check these interfaces.
2537
				$i++;
2538
				continue;
2539 751533a2 Phil Davis
			} else if (does_interface_exist($ifcfg['if']) == false) {
2540 e0a45ce0 Erik Fonnesbeck
				$missing_interfaces[] = $ifcfg['if'];
2541 72993196 Ermal
				$do_assign = true;
2542 751533a2 Phil Davis
			} else {
2543 857da904 Scott Ullrich
				$i++;
2544 751533a2 Phil Davis
			}
2545 857da904 Scott Ullrich
		}
2546 72993196 Ermal
	}
2547 2b4d37de Ermal Lu?i
2548 d57eab57 Viktor G
	/* VLAN/QinQ-only interface mismatch detection
2549
	 * see https://redmine.pfsense.org/issues/12170 */
2550
	init_config_arr(array('vlans', 'vlan'));
2551
	foreach ($config['vlans']['vlan'] as $vlan) {
2552 fd30ce6a Christian McDonald
		if (!does_interface_exist($vlan['if']) &&
2553 d57eab57 Viktor G
		    !preg_match("/^enc|^cua|^tun|^tap|^l2tp|^pptp|^ppp|^ovpn|^ipsec|^gif|^gre|^lagg|^bridge|vlan|_wlan|_\d{0,4}_\d{0,4}$/i", $vlan['if'])) {
2554
			$missing_interfaces[] = $vlan['if'];
2555
			$do_assign = true;
2556
		}
2557
	}
2558
	init_config_arr(array('qinqs', 'qinqentry'));
2559
	foreach ($config['qinqs']['qinqentry'] as $qinq) {
2560
		if (!does_interface_exist($qinq['if']) &&
2561
		    !preg_match("/^enc|^cua|^tun|^tap|^l2tp|^pptp|^ppp|^ovpn|^ipsec|^gif|^gre|^lagg|^bridge|vlan|_wlan|_\d{0,4}_\d{0,4}$/i", $qinq['if'])) {
2562
			$missing_interfaces[] = $qinq['if'];
2563
			$do_assign = true;
2564
		}
2565
	}
2566
2567 751533a2 Phil Davis
	if (file_exists("{$g['tmp_path']}/assign_complete")) {
2568 e0a45ce0 Erik Fonnesbeck
		$do_assign = false;
2569 751533a2 Phil Davis
	}
2570 e0a45ce0 Erik Fonnesbeck
2571 751533a2 Phil Davis
	if (!empty($missing_interfaces) && $do_assign) {
2572 e0a45ce0 Erik Fonnesbeck
		file_put_contents("{$g['tmp_path']}/missing_interfaces", implode(' ', $missing_interfaces));
2573 751533a2 Phil Davis
	} else {
2574 e0a45ce0 Erik Fonnesbeck
		@unlink("{$g['tmp_path']}/missing_interfaces");
2575 751533a2 Phil Davis
	}
2576 2b4d37de Ermal Lu?i
2577 857da904 Scott Ullrich
	return $do_assign;
2578 2b4d37de Ermal Lu?i
}
2579
2580 6e8f7b53 Ermal Lu?i
/* sync carp entries to other firewalls */
2581
function carp_sync_client() {
2582 0ae6daf8 Ermal
	send_event("filter sync");
2583 6e8f7b53 Ermal Lu?i
}
2584
2585 6dc88d53 Ermal Luci
/****f* util/isAjax
2586
 * NAME
2587
 *   isAjax - reports if the request is driven from prototype
2588
 * INPUTS
2589
 *   none
2590
 * RESULT
2591
 *   true/false
2592
 ******/
2593
function isAjax() {
2594 5bbd08e1 Warren Baker
	return isset ($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest';
2595 6dc88d53 Ermal Luci
}
2596
2597 dad2b40e Tim Allender
/****f* util/timeout
2598
 * NAME
2599
 *   timeout - console input with timeout countdown. Note: erases 2 char of screen for timer. Leave space.
2600
 * INPUTS
2601
 *   optional, seconds to wait before timeout. Default 9 seconds.
2602
 * RESULT
2603
 *   returns 1 char of user input or null if no input.
2604
 ******/
2605
function timeout($timer = 9) {
2606 751533a2 Phil Davis
	while (!isset($key)) {
2607
		if ($timer >= 9) {
2608 6c07db48 Phil Davis
			echo chr(8) . chr(8) . ($timer == 9 ? chr(32) : null) . "{$timer}";
2609 751533a2 Phil Davis
		} else {
2610
			echo chr(8). "{$timer}";
2611
		}
2612 dad2b40e Tim Allender
		`/bin/stty -icanon min 0 time 25`;
2613
		$key = trim(`KEY=\`dd count=1 2>/dev/null\`; echo \$KEY`);
2614
		`/bin/stty icanon`;
2615 751533a2 Phil Davis
		if ($key == '') {
2616 dad2b40e Tim Allender
			unset($key);
2617 751533a2 Phil Davis
		}
2618 dad2b40e Tim Allender
		$timer--;
2619 751533a2 Phil Davis
		if ($timer == 0) {
2620 dad2b40e Tim Allender
			break;
2621 751533a2 Phil Davis
		}
2622 dad2b40e Tim Allender
	}
2623 86a5e1a8 Renato Botelho
	return $key;
2624 dad2b40e Tim Allender
}
2625 6dc88d53 Ermal Luci
2626 fdf3af3f Scott Ullrich
/****f* util/msort
2627
 * NAME
2628
 *   msort - sort array
2629
 * INPUTS
2630
 *   $array to be sorted, field to sort by, direction of sort
2631
 * RESULT
2632
 *   returns newly sorted array
2633
 ******/
2634 6c07db48 Phil Davis
function msort($array, $id = "id", $sort_ascending = true) {
2635 4a8bc5a2 Scott Ullrich
	$temp_array = array();
2636 a47598aa Renato Botelho
	if (!is_array($array)) {
2637
		return $temp_array;
2638
	}
2639 751533a2 Phil Davis
	while (count($array)>0) {
2640 4a8bc5a2 Scott Ullrich
		$lowest_id = 0;
2641 6c07db48 Phil Davis
		$index = 0;
2642 4a8bc5a2 Scott Ullrich
		foreach ($array as $item) {
2643
			if (isset($item[$id])) {
2644
				if ($array[$lowest_id][$id]) {
2645
					if (strtolower($item[$id]) < strtolower($array[$lowest_id][$id])) {
2646
						$lowest_id = $index;
2647
					}
2648
				}
2649
			}
2650
			$index++;
2651
		}
2652
		$temp_array[] = $array[$lowest_id];
2653 086cf944 Phil Davis
		$array = array_merge(array_slice($array, 0, $lowest_id), array_slice($array, $lowest_id + 1));
2654 4a8bc5a2 Scott Ullrich
	}
2655
	if ($sort_ascending) {
2656
		return $temp_array;
2657
	} else {
2658 86a5e1a8 Renato Botelho
		return array_reverse($temp_array);
2659 4a8bc5a2 Scott Ullrich
	}
2660
}
2661
2662 5e9dd72a sullrich
/****f* util/is_URL
2663
 * NAME
2664
 *   is_URL
2665
 * INPUTS
2666 9fc1648e jim-p
 *   $url: string to check
2667
 *   $httponly: Only allow HTTP or HTTPS scheme
2668 5e9dd72a sullrich
 * RESULT
2669
 *   Returns true if item is a URL
2670
 ******/
2671 9fc1648e jim-p
function is_URL($url, $httponly = false) {
2672 7a14ab5d Marcos Mendoza
	if (filter_var($url, FILTER_VALIDATE_URL)) {
2673 9fc1648e jim-p
		if ($httponly) {
2674 7a14ab5d Marcos Mendoza
			return in_array(strtolower(parse_url($url, PHP_URL_SCHEME)), ['http', 'https']);
2675 9fc1648e jim-p
		}
2676 7a14ab5d Marcos Mendoza
		return true;
2677 751533a2 Phil Davis
	}
2678 5e9dd72a sullrich
	return false;
2679
}
2680
2681 ab94ba00 Ermal Lu?i
function is_file_included($file = "") {
2682
	$files = get_included_files();
2683 751533a2 Phil Davis
	if (in_array($file, $files)) {
2684 ab94ba00 Ermal Lu?i
		return true;
2685 751533a2 Phil Davis
	}
2686 86a5e1a8 Renato Botelho
2687 ab94ba00 Ermal Lu?i
	return false;
2688
}
2689
2690 f2cc3344 Renato Botelho
/*
2691
 * Replace a value on a deep associative array using regex
2692
 */
2693
function array_replace_values_recursive($data, $match, $replace) {
2694 751533a2 Phil Davis
	if (empty($data)) {
2695 f2cc3344 Renato Botelho
		return $data;
2696 751533a2 Phil Davis
	}
2697 f2cc3344 Renato Botelho
2698 751533a2 Phil Davis
	if (is_string($data)) {
2699 f2cc3344 Renato Botelho
		$data = preg_replace("/{$match}/", $replace, $data);
2700 751533a2 Phil Davis
	} else if (is_array($data)) {
2701
		foreach ($data as $k => $v) {
2702 f2cc3344 Renato Botelho
			$data[$k] = array_replace_values_recursive($v, $match, $replace);
2703 751533a2 Phil Davis
		}
2704
	}
2705 f2cc3344 Renato Botelho
2706
	return $data;
2707
}
2708
2709 0d90fcaf jim-p
/*
2710
	This function was borrowed from a comment on PHP.net at the following URL:
2711 8fa5038b Marcos Mendoza
	https://www.php.net/manual/en/function.array-merge-recursive.php#73843
2712 0d90fcaf jim-p
 */
2713 5bbd08e1 Warren Baker
function array_merge_recursive_unique($array0, $array1) {
2714
2715
	$arrays = func_get_args();
2716
	$remains = $arrays;
2717 0d90fcaf jim-p
2718 5bbd08e1 Warren Baker
	// We walk through each arrays and put value in the results (without
2719
	// considering previous value).
2720
	$result = array();
2721 0d90fcaf jim-p
2722 5bbd08e1 Warren Baker
	// loop available array
2723 751533a2 Phil Davis
	foreach ($arrays as $array) {
2724 0d90fcaf jim-p
2725 5bbd08e1 Warren Baker
		// The first remaining array is $array. We are processing it. So
2726 751533a2 Phil Davis
		// we remove it from remaining arrays.
2727 86a5e1a8 Renato Botelho
		array_shift($remains);
2728 0d90fcaf jim-p
2729 5bbd08e1 Warren Baker
		// We don't care non array param, like array_merge since PHP 5.0.
2730 751533a2 Phil Davis
		if (is_array($array)) {
2731 5bbd08e1 Warren Baker
			// Loop values
2732 751533a2 Phil Davis
			foreach ($array as $key => $value) {
2733
				if (is_array($value)) {
2734 5bbd08e1 Warren Baker
					// we gather all remaining arrays that have such key available
2735
					$args = array();
2736 751533a2 Phil Davis
					foreach ($remains as $remain) {
2737
						if (array_key_exists($key, $remain)) {
2738 5bbd08e1 Warren Baker
							array_push($args, $remain[$key]);
2739
						}
2740
					}
2741
2742 751533a2 Phil Davis
					if (count($args) > 2) {
2743 5bbd08e1 Warren Baker
						// put the recursion
2744
						$result[$key] = call_user_func_array(__FUNCTION__, $args);
2745
					} else {
2746 751533a2 Phil Davis
						foreach ($value as $vkey => $vval) {
2747 b2c97ede jim-p
							if (!is_array($result[$key])) {
2748
								$result[$key] = array();
2749
							}
2750 5bbd08e1 Warren Baker
							$result[$key][$vkey] = $vval;
2751
						}
2752
					}
2753
				} else {
2754
					// simply put the value
2755
					$result[$key] = $value;
2756
				}
2757
			}
2758
		}
2759
	}
2760
	return $result;
2761 0d90fcaf jim-p
}
2762
2763 f898c1a9 jim-p
2764 9a456170 Darren Embry
/*
2765
 * converts a string like "a,b,c,d"
2766
 * into an array like array("a" => "b", "c" => "d")
2767
 */
2768
function explode_assoc($delimiter, $string) {
2769
	$array = explode($delimiter, $string);
2770
	$result = array();
2771
	$numkeys = floor(count($array) / 2);
2772
	for ($i = 0; $i < $numkeys; $i += 1) {
2773
		$result[$array[$i * 2]] = $array[$i * 2 + 1];
2774
	}
2775
	return $result;
2776
}
2777
2778 bea18841 Phil Davis
/*
2779
 * Given a string of text with some delimiter, look for occurrences
2780
 * of some string and replace all of those.
2781
 * $text - the text string (e.g. "abc,defg,x123,ipv4,xyz")
2782
 * $delimiter - the delimiter (e.g. ",")
2783
 * $element - the element to match (e.g. "defg")
2784
 * $replacement - the string to replace it with (e.g. "42")
2785
 * Returns the resulting delimited string (e.g. "abc,42,x123,ipv4,xyz")
2786
 */
2787
function replace_element_in_list($text, $delimiter, $element, $replacement) {
2788
	$textArray = explode($delimiter, $text);
2789
	while (($entry = array_search($element, $textArray)) !== false) {
2790
		$textArray[$entry] = $replacement;
2791
	}
2792
	return implode(',', $textArray);
2793
}
2794
2795 c428cdf4 Renato Botelho do Couto
/* Return system's route table */
2796
function route_table() {
2797 dc337505 Reid Linnemann
	exec("/usr/bin/netstat --libxo json -nWr", $rawdata, $rc);
2798 c428cdf4 Renato Botelho do Couto
2799
	if ($rc != 0) {
2800
		return array();
2801
	}
2802
2803
	$netstatarr = json_decode(implode(" ", $rawdata), JSON_OBJECT_AS_ARRAY);
2804
	$netstatarr = $netstatarr['statistics']['route-information']
2805
	    ['route-table']['rt-family'];
2806
2807
	$result = array();
2808
	$result['inet'] = array();
2809
	$result['inet6'] = array();
2810
	foreach ($netstatarr as $item) {
2811
		if ($item['address-family'] == 'Internet') {
2812
			$result['inet'] = $item['rt-entry'];
2813
		} else if ($item['address-family'] == 'Internet6') {
2814
			$result['inet6'] = $item['rt-entry'];
2815
		}
2816
	}
2817
	unset($netstatarr);
2818
2819
	return $result;
2820
}
2821
2822 de739376 Viktor G
/* check if route is static (not BGP/OSPF) */
2823
function is_static_route($target, $ipprotocol = '') {
2824
	if (is_v4($target) || (($target == 'default') && ($ipprotocol == 'inet'))) {
2825
		$inet = '4';
2826
	} elseif (is_v6($target) || (($target == 'default') && ($ipprotocol == 'inet6'))) {
2827
		$inet = '6';
2828
	} else {
2829
		return false;
2830
	}
2831
2832
	if (exec("/sbin/route -n{$inet} get " . escapeshellarg($target) . " 2>/dev/null | egrep 'flags: <.*STATIC.*>'")) {
2833
		return true;
2834
	}
2835
2836
	return false;
2837
}
2838
2839 c428cdf4 Renato Botelho do Couto
/* Get static route for specific destination */
2840 7990de53 Viktor G
function route_get($target, $ipprotocol = '', $useroute = false) {
2841
	global $config;
2842
2843 c428cdf4 Renato Botelho do Couto
	if (!empty($ipprotocol)) {
2844
		$family = $ipprotocol;
2845
	} else if (is_v4($target)) {
2846
		$family = 'inet';
2847
	} else if (is_v6($target)) {
2848
		$family = 'inet6';
2849
	}
2850
2851
	if (empty($family)) {
2852
		return array();
2853
	}
2854
2855 7990de53 Viktor G
	if ($useroute) {
2856
		if ($family == 'inet') {
2857
			$inet = '4';
2858
		} else {
2859
			$inet = '6';
2860
		}
2861 46ff02ac Viktor G
		$interface = exec("/sbin/route -n{$inet} get {$target} 2>/dev/null | /usr/bin/awk '/interface:/{print $2}'");
2862 7990de53 Viktor G
		if (empty($interface)) {
2863
			return array();
2864
		} elseif ($interface == 'lo0') {
2865
			// interface assigned IP address
2866 dc337505 Reid Linnemann
			foreach (array_keys($config['interfaces']) as $intf) {
2867 7990de53 Viktor G
				if ((($inet == '4') && (get_interface_ip($intf) == $target)) ||
2868
				    (($inet == '6') && (get_interface_ipv6($intf) == $target))) {
2869
					$interface = convert_friendly_interface_to_real_interface_name($intf);
2870
					$gateway = $interface;
2871
					break;
2872
				}
2873
			}
2874
		} else {
2875 46ff02ac Viktor G
			$gateway = exec("/sbin/route -n{$inet} get {$target} 2>/dev/null | /usr/bin/awk '/gateway:/{print $2}'");
2876 07b780c8 Viktor G
			if (!$gateway) {
2877
				// non-local gateway
2878
				$gateway = get_interface_mac($interface);
2879
			}
2880 7990de53 Viktor G
		}
2881
		$result[] = array('gateway' => $gateway, 'interface-name' => $interface);
2882
	} else {
2883
		$rtable = route_table();
2884
		if (empty($rtable)) {
2885
			return array();
2886
		}
2887 ffe95182 Renato Botelho do Couto
2888 7990de53 Viktor G
		$result = array();
2889
		foreach ($rtable[$family] as $item) {
2890
			if ($item['destination'] == $target ||
2891
			    ip_in_subnet($target, $item['destination'])) {
2892
				$result[] = $item;
2893
			}
2894 c428cdf4 Renato Botelho do Couto
		}
2895
	}
2896
2897
	return $result;
2898
}
2899
2900
/* Get default route */
2901
function route_get_default($ipprotocol) {
2902
	if (empty($ipprotocol) || ($ipprotocol != 'inet' &&
2903
	    $ipprotocol != 'inet6')) {
2904
		return '';
2905
	}
2906
2907 7990de53 Viktor G
	$route = route_get('default', $ipprotocol, true);
2908 c428cdf4 Renato Botelho do Couto
2909
	if (empty($route)) {
2910
		return '';
2911
	}
2912
2913
	if (!isset($route[0]['gateway'])) {
2914
		return '';
2915
	}
2916
2917
	return $route[0]['gateway'];
2918
}
2919
2920
/* Delete a static route */
2921
function route_del($target, $ipprotocol = '') {
2922 94bd7fb3 Renato Botelho
	global $config;
2923
2924 c428cdf4 Renato Botelho do Couto
	if (empty($target)) {
2925
		return;
2926
	}
2927
2928
	if (!empty($ipprotocol) && $ipprotocol != 'inet' &&
2929
	    $ipprotocol != 'inet6') {
2930 94bd7fb3 Renato Botelho
		return false;
2931
	}
2932
2933 07b780c8 Viktor G
	$route = route_get($target, $ipprotocol, true);
2934 c428cdf4 Renato Botelho do Couto
2935
	if (empty($route)) {
2936
		return;
2937 94bd7fb3 Renato Botelho
	}
2938
2939 c428cdf4 Renato Botelho do Couto
	$target_prefix = '';
2940
	if (is_subnet($target)) {
2941
		$target_prefix = '-net';
2942
	} else if (is_ipaddr($target)) {
2943
		$target_prefix = '-host';
2944
	}
2945
2946
	if (!empty($ipprotocol)) {
2947
		$target_prefix .= " -{$ipprotocol}";
2948
	} else if (is_v6($target)) {
2949
		$target_prefix .= ' -inet6';
2950
	} else if (is_v4($target)) {
2951
		$target_prefix .= ' -inet';
2952
	}
2953
2954
	foreach ($route as $item) {
2955
		if (substr($item['gateway'], 0, 5) == 'link#') {
2956
			continue;
2957
		}
2958
2959
		if (is_macaddr($item['gateway'])) {
2960
			$gw = '-iface ' . $item['interface-name'];
2961
		} else {
2962
			$gw = $item['gateway'];
2963
		}
2964
2965 dc337505 Reid Linnemann
		exec(escapeshellcmd("/sbin/route del {$target_prefix} " .
2966 c428cdf4 Renato Botelho do Couto
		    "{$target} {$gw}"), $output, $rc);
2967 94bd7fb3 Renato Botelho
2968
		if (isset($config['system']['route-debug'])) {
2969 c428cdf4 Renato Botelho do Couto
			log_error("ROUTING debug: " . microtime() .
2970
			    " - DEL RC={$rc} - {$target} - gw: " . $gw);
2971
			file_put_contents("/dev/console", "\n[" . getmypid() .
2972
			    "] ROUTE DEL: {$target_prefix} {$target} {$gw} " .
2973
			    "result: {$rc}");
2974 94bd7fb3 Renato Botelho
		}
2975
	}
2976 c428cdf4 Renato Botelho do Couto
}
2977
2978
/*
2979
 * Add static route.  If it already exists, remove it and re-add
2980
 *
2981
 * $target - IP, subnet or 'default'
2982
 * $gw     - gateway address
2983
 * $iface  - Network interface
2984
 * $args   - Extra arguments for /sbin/route
2985
 * $ipprotocol - 'inet' or 'inet6'.  Mandatory when $target == 'default'
2986
 *
2987
 */
2988
function route_add_or_change($target, $gw, $iface = '', $args = '',
2989
    $ipprotocol = '') {
2990
	global $config;
2991
2992
	if (empty($target) || (empty($gw) && empty($iface))) {
2993
		return false;
2994
	}
2995
2996
	if ($target == 'default' && empty($ipprotocol)) {
2997
		return false;
2998
	}
2999
3000
	if (!empty($ipprotocol) && $ipprotocol != 'inet' &&
3001
	    $ipprotocol != 'inet6') {
3002
		return false;
3003
	}
3004
3005 d9818e01 Viktor G
	/* use '-host' for IPv6 /128 routes, see https://redmine.pfsense.org/issues/11594 */
3006
	if (is_subnetv4($target) || (is_subnetv6($target) && (subnet_size($target) > 1))) {
3007 c428cdf4 Renato Botelho do Couto
		$target_prefix = '-net';
3008
	} else if (is_ipaddr($target)) {
3009
		$target_prefix = '-host';
3010
	}
3011
3012
	if (!empty($ipprotocol)) {
3013
		$target_prefix .= " -{$ipprotocol}";
3014
	} else if (is_v6($target)) {
3015
		$target_prefix .= ' -inet6';
3016
	} else if (is_v4($target)) {
3017
		$target_prefix .= ' -inet';
3018
	}
3019
3020
	/* If there is another route to the same target, remove it */
3021
	route_del($target, $ipprotocol);
3022
3023
	$params = '';
3024
	if (!empty($iface) && does_interface_exist($iface)) {
3025 087d28fa Viktor G
		$params .= " -iface {$iface}";
3026 c428cdf4 Renato Botelho do Couto
	}
3027
	if (is_ipaddr($gw)) {
3028 cca31114 Viktor G
		/* set correct linklocal gateway address,
3029 fd30ce6a Christian McDonald
		 * see https://redmine.pfsense.org/issues/11713
3030 0466339c Viktor G
		 * and https://redmine.pfsense.org/issues/11806 */
3031
		if (is_ipaddrv6($gw) && is_linklocal($gw) && empty(get_ll_scope($gw))) {
3032 cca31114 Viktor G
			$routeget = route_get($gw, 'inet6', true);
3033
			$gw .= "%" . $routeget[0]['interface-name'];
3034
		}
3035 c428cdf4 Renato Botelho do Couto
		$params .= " " . $gw;
3036
	}
3037
3038
	if (empty($params)) {
3039
		log_error("route_add_or_change: Invalid gateway {$gw} and/or " .
3040
		    "network interface {$iface}");
3041
		return false;
3042
	}
3043
3044 dc337505 Reid Linnemann
	exec(escapeshellcmd("/sbin/route add {$target_prefix} " .
3045 c428cdf4 Renato Botelho do Couto
	    "{$target} {$args} {$params}"), $output, $rc);
3046
3047 43a9b03d PiBa-NL
	if (isset($config['system']['route-debug'])) {
3048 c428cdf4 Renato Botelho do Couto
		log_error("ROUTING debug: " . microtime() .
3049
		    " - ADD RC={$rc} - {$target} {$args}");
3050 33f28cc5 Renato Botelho do Couto
		file_put_contents("/dev/console", "\n[" . getmypid() .
3051 c428cdf4 Renato Botelho do Couto
		    "] ROUTE ADD: {$target_prefix} {$target} {$args} " .
3052
		    "{$params} result: {$rc}");
3053 43a9b03d PiBa-NL
	}
3054 94bd7fb3 Renato Botelho
3055
	return ($rc == 0);
3056
}
3057
3058 c297d257 Viktor Gurov
function set_ipv6routes_mtu($interface, $mtu) {
3059 dc337505 Reid Linnemann
	global $config;
3060 0249b6f8 Renato Botelho do Couto
3061
	$ipv6mturoutes = array();
3062
	$if = convert_real_interface_to_friendly_interface_name($interface);
3063
	if (!$config['interfaces'][$if]['ipaddrv6']) {
3064
		return;
3065
	}
3066
	$a_gateways = return_gateways_array();
3067
	$a_staticroutes = get_staticroutes(false, false, true);
3068
	foreach ($a_gateways as $gate) {
3069
		foreach ($a_staticroutes as $sroute) {
3070 4979c993 Viktor G
			if (($gate['interface'] == $interface) &&
3071 9b391783 Christopher
			    ($sroute['gateway'] == $gate['name']) &&
3072
			    (is_ipaddrv6($gate['gateway']))) {
3073 0249b6f8 Renato Botelho do Couto
				$tgt = $sroute['network'];
3074
				$gateway = $gate['gateway'];
3075
				$ipv6mturoutes[$tgt] = $gateway;
3076
			}
3077
		}
3078 4979c993 Viktor G
		if (($gate['interface'] == $interface) &&
3079
		    $gate['isdefaultgw'] && is_ipaddrv6($gate['gateway'])) {
3080 0249b6f8 Renato Botelho do Couto
			$tgt = "default";
3081
			$gateway = $gate['gateway'];
3082
			$ipv6mturoutes[$tgt] = $gateway;
3083
		}
3084
	}
3085
	foreach ($ipv6mturoutes as $tgt => $gateway) {
3086
		mwexec("/sbin/route change -6 -mtu " . escapeshellarg($mtu) .
3087
		    " " . escapeshellarg($tgt) . " " .
3088
		    escapeshellarg($gateway));
3089
	}
3090 c297d257 Viktor Gurov
}
3091
3092 71f0623e PiBa-NL
function alias_to_subnets_recursive($name, $returnhostnames = false) {
3093
	global $aliastable;
3094
	$result = array();
3095
	if (!isset($aliastable[$name])) {
3096
		return $result;
3097
	}
3098
	$subnets = preg_split('/\s+/', $aliastable[$name]);
3099
	foreach ($subnets as $net) {
3100
		if (is_alias($net)) {
3101
			$sub = alias_to_subnets_recursive($net, $returnhostnames);
3102
			$result = array_merge($result, $sub);
3103
			continue;
3104
		} elseif (!is_subnet($net)) {
3105
			if (is_ipaddrv4($net)) {
3106
				$net .= "/32";
3107
			} else if (is_ipaddrv6($net)) {
3108
				$net .= "/128";
3109
			} else if ($returnhostnames === false || !is_fqdn($net)) {
3110
				continue;
3111
			}
3112
		}
3113
		$result[] = $net;
3114
	}
3115
	return $result;
3116
}
3117
3118 cf08b49e Phil Davis
function get_staticroutes($returnsubnetsonly = false, $returnhostnames = false, $returnenabledroutesonly = false) {
3119 dc337505 Reid Linnemann
	global $config;
3120 f898c1a9 jim-p
3121
	/* Bail if there are no routes, but return an array always so callers don't have to check. */
3122 dbc1b8ee jim-p
	init_config_arr(array('staticroutes', 'route'));
3123
	if (empty($config['staticroutes']['route'])) {
3124 f898c1a9 jim-p
		return array();
3125 751533a2 Phil Davis
	}
3126 f898c1a9 jim-p
3127 bcab1b07 Ermal
	$allstaticroutes = array();
3128
	$allsubnets = array();
3129 f898c1a9 jim-p
	/* Loop through routes and expand aliases as we find them. */
3130
	foreach ($config['staticroutes']['route'] as $route) {
3131 cf08b49e Phil Davis
		if ($returnenabledroutesonly && isset($route['disabled'])) {
3132
			continue;
3133
		}
3134
3135 f898c1a9 jim-p
		if (is_alias($route['network'])) {
3136 71f0623e PiBa-NL
			foreach (alias_to_subnets_recursive($route['network'], $returnhostnames) as $net) {
3137 f898c1a9 jim-p
				$temproute = $route;
3138
				$temproute['network'] = $net;
3139
				$allstaticroutes[] = $temproute;
3140
				$allsubnets[] = $net;
3141
			}
3142
		} elseif (is_subnet($route['network'])) {
3143
			$allstaticroutes[] = $route;
3144
			$allsubnets[] = $route['network'];
3145
		}
3146
	}
3147 751533a2 Phil Davis
	if ($returnsubnetsonly) {
3148 f898c1a9 jim-p
		return $allsubnets;
3149 751533a2 Phil Davis
	} else {
3150 f898c1a9 jim-p
		return $allstaticroutes;
3151 751533a2 Phil Davis
	}
3152 f898c1a9 jim-p
}
3153 a0539faa Darren Embry
3154
/****f* util/get_alias_list
3155
 * NAME
3156
 *   get_alias_list - Provide a list of aliases.
3157
 * INPUTS
3158
 *   $type          - Optional, can be a string or array specifying what type(s) of aliases you need.
3159
 * RESULT
3160
 *   Array containing list of aliases.
3161
 *   If $type is unspecified, all aliases are returned.
3162
 *   If $type is a string, all aliases of the type specified in $type are returned.
3163
 *   If $type is an array, all aliases of any type specified in any element of $type are returned.
3164
 */
3165
function get_alias_list($type = null) {
3166
	$result = array();
3167 33766cbc Reid Linnemann
	foreach (config_get_path('aliases/alias', []) as $alias) {
3168
		if ($type === null) {
3169
			$result[] = $alias['name'];
3170
		} else if (is_array($type)) {
3171
			if (in_array($alias['type'], $type)) {
3172 a0539faa Darren Embry
				$result[] = $alias['name'];
3173
			}
3174 33766cbc Reid Linnemann
		} else if ($type === $alias['type']) {
3175
			$result[] = $alias['name'];
3176 a0539faa Darren Embry
		}
3177 86a5e1a8 Renato Botelho
	}
3178 a0539faa Darren Embry
	return $result;
3179
}
3180
3181 5c4a6ada jim-p
/* Returns the current alias contents sorted by name in case insensitive and
3182
 * natural order.
3183
 * https://redmine.pfsense.org/issues/14015 */
3184
function get_sorted_aliases() {
3185
	$aliases = config_get_path('aliases/alias', []);
3186
	uasort($aliases, function ($a, $b) { return strnatcasecmp($a['name'], $b['name']); });
3187
	return $aliases;
3188
}
3189
3190 4dfd930e Darren Embry
/* returns an array consisting of every element of $haystack that is not equal to $needle. */
3191
function array_exclude($needle, $haystack) {
3192
	$result = array();
3193
	if (is_array($haystack)) {
3194
		foreach ($haystack as $thing) {
3195
			if ($needle !== $thing) {
3196
				$result[] = $thing;
3197
			}
3198
		}
3199
	}
3200
	return $result;
3201
}
3202
3203 77a341a4 Renato Botelho
/* Define what is preferred, IPv4 or IPv6 */
3204
function prefer_ipv4_or_ipv6() {
3205 33766cbc Reid Linnemann
	if (config_path_enabled('system', 'prefer_ipv4')) {
3206 77a341a4 Renato Botelho
		mwexec("/etc/rc.d/ip6addrctl prefer_ipv4");
3207 751533a2 Phil Davis
	} else {
3208 77a341a4 Renato Botelho
		mwexec("/etc/rc.d/ip6addrctl prefer_ipv6");
3209 751533a2 Phil Davis
	}
3210 77a341a4 Renato Botelho
}
3211
3212 111bea0d Renato Botelho
/* Redirect to page passing parameters via POST */
3213
function post_redirect($page, $params) {
3214 751533a2 Phil Davis
	if (!is_array($params)) {
3215 111bea0d Renato Botelho
		return;
3216 751533a2 Phil Davis
	}
3217 111bea0d Renato Botelho
3218
	print "<html><body><form action=\"{$page}\" name=\"formredir\" method=\"post\">\n";
3219
	foreach ($params as $key => $value) {
3220
		print "<input type=\"hidden\" name=\"{$key}\" value=\"{$value}\" />\n";
3221
	}
3222 8fd9052f Colin Fleming
	print "</form>\n";
3223
	print "<script type=\"text/javascript\">\n";
3224
	print "//<![CDATA[\n";
3225
	print "document.formredir.submit();\n";
3226
	print "//]]>\n";
3227
	print "</script>\n";
3228 111bea0d Renato Botelho
	print "</body></html>\n";
3229
}
3230
3231 ea20169a jim-p
/* Locate disks that can be queried for S.M.A.R.T. data. */
3232
function get_smart_drive_list() {
3233 e738a4c9 jim-p
	/* SMART supports some disks directly, and some controllers directly,
3234
	 * See https://redmine.pfsense.org/issues/9042 */
3235
	$supported_disk_types = array("ad", "da", "ada");
3236
	$supported_controller_types = array("nvme");
3237 ea20169a jim-p
	$disk_list = explode(" ", get_single_sysctl("kern.disks"));
3238
	foreach ($disk_list as $id => $disk) {
3239
		// We only want certain kinds of disks for S.M.A.R.T.
3240 a68c6785 Phil Davis
		// 1 is a match, 0 is no match, False is any problem processing the regex
3241 e738a4c9 jim-p
		if (preg_match("/^(" . implode("|", $supported_disk_types) . ").*[0-9]{1,2}$/", $disk) !== 1) {
3242 ea20169a jim-p
			unset($disk_list[$id]);
3243 e738a4c9 jim-p
			continue;
3244
		}
3245
	}
3246
	foreach ($supported_controller_types as $controller) {
3247
		$devices = glob("/dev/{$controller}*");
3248
		if (!is_array($devices)) {
3249
			continue;
3250
		}
3251
		foreach ($devices as $device) {
3252
			$disk_list[] = basename($device);
3253 ea20169a jim-p
		}
3254
	}
3255
	sort($disk_list);
3256
	return $disk_list;
3257
}
3258
3259 77a8a7d6 Steve Beaver
// Validate a network address
3260
//	$addr: the address to validate
3261
//	$type: IPV4|IPV6|IPV4V6
3262 c393f1d1 Steve Beaver
//	$label: the label used by the GUI to display this value. Required to compose an error message
3263 77a8a7d6 Steve Beaver
//	$err_msg: pointer to the callers error message array so that error messages can be added to it here
3264
//	$alias: are aliases permitted for this address?
3265 24eb39e2 Phil Davis
// Returns:
3266 74999ad8 Phil Davis
//	IPV4 - if $addr is a valid IPv4 address
3267
//	IPV6 - if $addr is a valid IPv6 address
3268
//	ALIAS - if $alias=true and $addr is an alias
3269 24eb39e2 Phil Davis
//	false - otherwise
3270
3271 77a8a7d6 Steve Beaver
function validateipaddr(&$addr, $type, $label, &$err_msg, $alias=false) {
3272
	switch ($type) {
3273
		case IPV4:
3274
			if (is_ipaddrv4($addr)) {
3275 dc938839 Phil Davis
				return IPV4;
3276 77a8a7d6 Steve Beaver
			} else if ($alias) {
3277
				if (is_alias($addr)) {
3278 dc938839 Phil Davis
					return ALIAS;
3279 77a8a7d6 Steve Beaver
				} else {
3280 bb9747b2 Phil Davis
					$err_msg[] = sprintf(gettext("%s must be a valid IPv4 address or alias."), $label);
3281 77a8a7d6 Steve Beaver
					return false;
3282
				}
3283
			} else {
3284 bb9747b2 Phil Davis
				$err_msg[] = sprintf(gettext("%s must be a valid IPv4 address."), $label);
3285 77a8a7d6 Steve Beaver
				return false;
3286
			}
3287
		break;
3288
		case IPV6:
3289
			if (is_ipaddrv6($addr)) {
3290
				$addr = strtolower($addr);
3291 dc938839 Phil Davis
				return IPV6;
3292 aa2b8133 Phil Davis
			} else if ($alias) {
3293 77a8a7d6 Steve Beaver
				if (is_alias($addr)) {
3294 dc938839 Phil Davis
					return ALIAS;
3295 77a8a7d6 Steve Beaver
				} else {
3296 bb9747b2 Phil Davis
					$err_msg[] = sprintf(gettext("%s must be a valid IPv6 address or alias."), $label);
3297 77a8a7d6 Steve Beaver
					return false;
3298
				}
3299
			} else {
3300 bb9747b2 Phil Davis
				$err_msg[] = sprintf(gettext("%s must be a valid IPv6 address."), $label);
3301 77a8a7d6 Steve Beaver
				return false;
3302
			}
3303
		break;
3304
		case IPV4V6:
3305
			if (is_ipaddrv6($addr)) {
3306
				$addr = strtolower($addr);
3307 dc938839 Phil Davis
				return IPV6;
3308 77a8a7d6 Steve Beaver
			} else if (is_ipaddrv4($addr)) {
3309 dc938839 Phil Davis
				return IPV4;
3310 aa2b8133 Phil Davis
			} else if ($alias) {
3311 77a8a7d6 Steve Beaver
				if (is_alias($addr)) {
3312 dc938839 Phil Davis
					return ALIAS;
3313 77a8a7d6 Steve Beaver
				} else {
3314 bb9747b2 Phil Davis
					$err_msg[] = sprintf(gettext("%s must be a valid IPv4 or IPv6 address or alias."), $label);
3315 77a8a7d6 Steve Beaver
					return false;
3316
				}
3317
			} else {
3318 bb9747b2 Phil Davis
				$err_msg[] = sprintf(gettext("%s must be a valid IPv4 or IPv6 address."), $label);
3319 77a8a7d6 Steve Beaver
				return false;
3320
			}
3321
		break;
3322
	}
3323
3324
	return false;
3325
}
3326 7be23d53 marjohn56
3327 64b9d133 kangtastic
/* From DUID configuration inputs, format a string that looks (more) like the expected raw DUID format:
3328 fffb9eed kangtastic
 * 1) For DUIDs entered as a known DUID type, convert to a hexstring and prepend the DUID number, after having done the following:
3329
 *     a) For DUID-LLT and DUID-EN, convert the time/enterprise ID input to hex and append the link-layer address/identifier input.
3330
 *     b) For DUID-LLT and DUID-LL, prepend a hardware type of 1.
3331
 *     c) For DUID-UUID, remove any "-".
3332
 * 2) Replace any remaining "-" with ":".
3333 f4bbec8b Phil Davis
 * 3) If any components are input with just a single char (hex digit hopefully), put a "0" in front.
3334 fffb9eed kangtastic
 * 4) The first two components should be a 16-bit integer (little- or big-endian, depending on the current machine type) that
3335 7955bcce kangtastic
 *    is equal to the number of other components. If not, prepend this as "nn:00" (all pfSense builds are little-endian).
3336 fffb9eed kangtastic
 *    This is convenience, because the DUID reported by dhcp6c in logs does not include this count, which corresponds to the
3337
 *    option-len field of DHCPv6's OPTION_CLIENTID option.
3338 f4bbec8b Phil Davis
 *
3339
 * The final result should be closer to:
3340
 *
3341 7955bcce kangtastic
 * "nn:00:00:0n:nn:nn:nn:..."
3342 f4bbec8b Phil Davis
 *
3343
 * This function does not validate the input. is_duid() will do validation.
3344 64b9d133 kangtastic
 */
3345
function format_duid($duidtype, $duidpt1, $duidpt2=null) {
3346 fffb9eed kangtastic
	if ($duidpt2)
3347
		$duidpt1 = implode(':', str_split(str_pad(dechex($duidpt1), 8, '0', STR_PAD_LEFT), 2)) . ':' . $duidpt2;
3348 64b9d133 kangtastic
3349 fffb9eed kangtastic
	/* Make hexstrings */
3350
	if ($duidtype) {
3351
		switch ($duidtype) {
3352
		/* Add a hardware type to DUID-LLT and DUID-LL; assume Ethernet */
3353
		case 1:
3354
		case 3:
3355
			$duidpt1 = '00:01:' . $duidpt1;
3356
			break;
3357
		/* Remove '-' from given UUID and insert ':' every 2 characters */
3358
		case 4:
3359
			$duidpt1 = implode(':', str_split(str_replace('-', '', $duidpt1), 2));
3360
			break;
3361
		default:
3362
		}
3363
		$duidpt1 = '00:0' . $duidtype . ':' . $duidpt1;
3364 f4bbec8b Phil Davis
	}
3365 fd2e503a Phil Davis
3366 fffb9eed kangtastic
	$values = explode(':', strtolower(str_replace('-', ':', $duidpt1)));
3367
3368 7955bcce kangtastic
	if (hexdec($values[0]) != count($values) - 2)
3369 64b9d133 kangtastic
		array_unshift($values, dechex(count($values)), '00');
3370 fd2e503a Phil Davis
3371
	array_walk($values, function(&$value) {
3372 4f3fc80d Renato Botelho
		$value = str_pad($value, 2, '0', STR_PAD_LEFT);
3373 fd2e503a Phil Davis
	});
3374
3375 febfd592 Phil Davis
	return implode(":", $values);
3376 f4bbec8b Phil Davis
}
3377
3378 64b9d133 kangtastic
/* Returns true if $dhcp6duid is a valid DUID entry.
3379 7955bcce kangtastic
 * Parse the entry to check for valid length according to known DUID types.
3380 64b9d133 kangtastic
 */
3381 7be23d53 marjohn56
function is_duid($dhcp6duid) {
3382
	$values = explode(":", $dhcp6duid);
3383 7955bcce kangtastic
	if (hexdec($values[0]) == count($values) - 2) {
3384 64b9d133 kangtastic
		switch (hexdec($values[2] . $values[3])) {
3385 fffb9eed kangtastic
		case 0:
3386
			return false;
3387
			break;
3388 64b9d133 kangtastic
		case 1:
3389 fffb9eed kangtastic
			if (count($values) != 16 || strlen($dhcp6duid) != 47)
3390 64b9d133 kangtastic
				return false;
3391
			break;
3392
		case 3:
3393 fffb9eed kangtastic
			if (count($values) != 12 || strlen($dhcp6duid) != 35)
3394 64b9d133 kangtastic
				return false;
3395
			break;
3396
		case 4:
3397 fffb9eed kangtastic
			if (count($values) != 20 || strlen($dhcp6duid) != 59)
3398 64b9d133 kangtastic
				return false;
3399
			break;
3400 fffb9eed kangtastic
		/* DUID is up to 128 octets; allow 2 octets for type code, 2 more for option-len */
3401 64b9d133 kangtastic
		default:
3402 fffb9eed kangtastic
			if (count($values) > 132 || strlen($dhcp6duid) != count($values) * 3 - 1)
3403 64b9d133 kangtastic
				return false;
3404
		}
3405 fffb9eed kangtastic
	} else
3406 7be23d53 marjohn56
		return false;
3407 fffb9eed kangtastic
3408 64b9d133 kangtastic
	for ($i = 0; $i < count($values); $i++) {
3409 7be23d53 marjohn56
		if (ctype_xdigit($values[$i]) == false)
3410
			return false;
3411
		if (hexdec($values[$i]) < 0 || hexdec($values[$i]) > 255)
3412
			return false;
3413
	}
3414 fffb9eed kangtastic
3415 7be23d53 marjohn56
	return true;
3416
}
3417
3418
/* Write the DHCP6 DUID file */
3419
function write_dhcp6_duid($duidstring) {
3420
	// Create the hex array from the dhcp6duid config entry and write to file
3421
	global $g;
3422 4f3fc80d Renato Botelho
3423
	if(!is_duid($duidstring)) {
3424 7be23d53 marjohn56
		log_error(gettext("Error: attempting to write DUID file - Invalid DUID detected"));
3425
		return false;
3426
	}
3427
	$temp = str_replace(":","",$duidstring);
3428
	$duid_binstring = pack("H*",$temp);
3429
	if ($fd = fopen("{$g['vardb_path']}/dhcp6c_duid", "wb")) {
3430
		fwrite($fd, $duid_binstring);
3431
		fclose($fd);
3432
		return true;
3433
	}
3434
	log_error(gettext("Error: attempting to write DUID file - File write error"));
3435
	return false;
3436
}
3437 2acedbbf marjohn56
3438 9e08a2bd marjohn56
/* returns duid string from 'vardb_path']}/dhcp6c_duid' */
3439 4f3fc80d Renato Botelho
function get_duid_from_file() {
3440 9e08a2bd marjohn56
	global $g;
3441 4f3fc80d Renato Botelho
3442 2acedbbf marjohn56
	$duid_ASCII = "";
3443 9e08a2bd marjohn56
	$count = 0;
3444 a271ed3d Renato Botelho
3445
	if (file_exists("{$g['vardb_path']}/dhcp6c_duid") &&
3446
	    ($fd = fopen("{$g['vardb_path']}/dhcp6c_duid", "r"))) {
3447 64b9d133 kangtastic
		$fsize = filesize("{$g['vardb_path']}/dhcp6c_duid");
3448
		if ($fsize <= 132) {
3449
			$buffer = fread($fd, $fsize);
3450
			while($count < $fsize) {
3451 2acedbbf marjohn56
				$duid_ASCII .= bin2hex($buffer[$count]);
3452 9e08a2bd marjohn56
				$count++;
3453 64b9d133 kangtastic
				if($count < $fsize) {
3454 9e08a2bd marjohn56
					$duid_ASCII .= ":";
3455
				}
3456
			}
3457
		}
3458
		fclose($fd);
3459
	}
3460
	//if no file or error with read then the string returns blanked DUID string
3461 2acedbbf marjohn56
	if(!is_duid($duid_ASCII)) {
3462 9e08a2bd marjohn56
		return "--:--:--:--:--:--:--:--:--:--:--:--:--:--:--:--";
3463
	}
3464 4f3fc80d Renato Botelho
	return($duid_ASCII);
3465 9e08a2bd marjohn56
}
3466 117776e0 doktornotor
3467
/* Replaces the Mac OS 9 and earlier (\r) and DOS/Windows (\r\n) newlines with the Unix equivalent (\n). */
3468
function unixnewlines($text) {
3469
	return preg_replace('/\r\n?/', "\n", $text);
3470
}
3471
3472 923eb50e Renato Botelho
function array_remove_duplicate($array, $field) {
3473 5e53a7b5 Viktor G
	$cmp = array();
3474 923eb50e Renato Botelho
	foreach ($array as $sub) {
3475
		if (isset($sub[$field])) {
3476
			$cmp[] = $sub[$field];
3477
		}
3478
	}
3479
	$unique = array_unique(array_reverse($cmp, true));
3480 dc337505 Reid Linnemann
	foreach (array_keys($unique) as $k) {
3481 923eb50e Renato Botelho
		$new[] = $array[$k];
3482
	}
3483
	return $new;
3484
}
3485
3486 fab98cb6 Reid Linnemann
/**
3487 c4117e83 Reid Linnemann
 * Return a value specified by a path of keys in a nested array, if it exists.
3488 fab98cb6 Reid Linnemann
 * @param $arr array value to search
3489
 * @param $path string path with '/' separators
3490
 * @param $default mixed value to return if the path is not found
3491 1333725e Reid Linnemann
 * @returns mixed value at path or $default if the path does not exist or if the
3492 5538e593 Reid Linnemann
 *          path keys an empty string and $default is non-null
3493 fab98cb6 Reid Linnemann
 */
3494
function array_get_path(array &$arr, string $path, $default = null) {
3495 4b78c041 Reid Linnemann
	$vpath = explode('/', $path);
3496 fab98cb6 Reid Linnemann
	$el = $arr;
3497
	foreach ($vpath as $key) {
3498 4b78c041 Reid Linnemann
		if (mb_strlen($key) == 0) {
3499 92539bd2 Reid Linnemann
			continue;
3500
		}
3501 fab98cb6 Reid Linnemann
		if (is_array($el) && array_key_exists($key, $el)) {
3502
			$el = $el[$key];
3503
		} else {
3504
			return ($default);
3505
		}
3506
	}
3507 1333725e Reid Linnemann
3508 5538e593 Reid Linnemann
	if (($default !== null) && ($el === '')) {
3509 1333725e Reid Linnemann
		return ($default);
3510
	}
3511 fd30ce6a Christian McDonald
3512 fab98cb6 Reid Linnemann
	return ($el);
3513
}
3514
3515 54457c75 Christian McDonald
/*
3516
 * Initialize an arbitrary array multiple levels deep only if unset
3517
 * @param $arr top of array
3518
 * @param $path string path with '/' separators
3519
 */
3520
function array_init_path(mixed &$arr, ?string $path)
3521
{
3522
	if (!is_array($arr)) {
3523
		$arr = [];
3524
	}
3525
	if (is_null($path)) {
3526
		return;
3527
	}
3528
	$tmp = &$arr;
3529
	foreach (explode('/', $path) as $key) {
3530
		if (!is_array($tmp[$key])) {
3531
			$tmp[$key] = [];
3532
		}
3533
		$tmp = &$tmp[$key];
3534
	}
3535
}
3536
3537 fab98cb6 Reid Linnemann
/**
3538 c4117e83 Reid Linnemann
 * Set a value by path in a nested array, creating arrays for intermediary keys
3539
 * as necessary. If the path cannot be reached because an intermediary exists
3540
 * but is not empty or an array, return $default.
3541 fab98cb6 Reid Linnemann
 * @param $arr array value to search
3542
 * @param $path string path with '/' separators
3543 fd30ce6a Christian McDonald
 * @param $value mixed
3544 fab98cb6 Reid Linnemann
 * @param $default mixed value to return if the path is not found
3545
 * @returns mixed $val or $default if the path prefix does not exist
3546
 */
3547
function array_set_path(array &$arr, string $path, $value, $default = null) {
3548 4b78c041 Reid Linnemann
	$vpath = explode('/', $path);
3549
	$vkey = null;
3550
	do {
3551
		$vkey = array_pop($vpath);
3552
	} while (mb_strlen($vkey) == 0);
3553
	if ($vkey == null) {
3554
		return ($default);
3555
	}
3556 fab98cb6 Reid Linnemann
	$el =& $arr;
3557
	foreach ($vpath as $key) {
3558 4b78c041 Reid Linnemann
		if (mb_strlen($key) == 0) {
3559 92539bd2 Reid Linnemann
			continue;
3560
		}
3561 c4117e83 Reid Linnemann
		if (array_key_exists($key, $el) && !empty($el[$key])) {
3562
			if (!is_array($el[$key])) {
3563
					return ($default);
3564
			}
3565 fab98cb6 Reid Linnemann
		} else {
3566 c4117e83 Reid Linnemann
				$el[$key] = [];
3567 fab98cb6 Reid Linnemann
		}
3568 c4117e83 Reid Linnemann
		$el =& $el[$key];
3569 fab98cb6 Reid Linnemann
	}
3570 eec3ca7f Reid Linnemann
	$el[$vkey] = $value;
3571 fab98cb6 Reid Linnemann
	return ($value);
3572
}
3573
3574
/**
3575 c4117e83 Reid Linnemann
 * Determine whether a path in a nested array has a non-null value keyed by
3576 fd30ce6a Christian McDonald
 * $enable_key.
3577 fab98cb6 Reid Linnemann
 * @param $arr array value to search
3578
 * @param $path string path with '/' separators
3579
 * @param $enable_key string an optional alternative key value for the enable key
3580
 * @returns bool true if $enable_key exists in the array at $path, and has a
3581
 * non-null value, otherwise false
3582
 */
3583
function array_path_enabled(array &$arr, string $path, $enable_key = "enable") {
3584 dc337505 Reid Linnemann
	$el = array_get_path($arr, $path, []);
3585 fab98cb6 Reid Linnemann
	if (is_array($el) && isset($el[$enable_key])) {
3586
		return (true);
3587
	}
3588
	return (false);
3589
}
3590
3591 eec3ca7f Reid Linnemann
/**
3592 c4117e83 Reid Linnemann
 * Remove a key from the nested array by path.
3593 eec3ca7f Reid Linnemann
 * @param $arr array value to search
3594
 * @param $path string path with '/' separators
3595
 * @returns array copy of the removed value or null
3596
 */
3597
function array_del_path(array &$arr, string $path) {
3598 4b78c041 Reid Linnemann
	$vpath = explode('/', $path);
3599 eec3ca7f Reid Linnemann
	$vkey = array_pop($vpath);
3600
	$el =& $arr;
3601
	foreach($vpath as $key) {
3602 4b78c041 Reid Linnemann
		if (mb_strlen($key) == 0) {
3603 92539bd2 Reid Linnemann
			continue;
3604
		}
3605 eec3ca7f Reid Linnemann
		if (is_array($el) && array_key_exists($key, $el)) {
3606
			$el =& $el[$key];
3607
		} else {
3608
			return null;
3609
		}
3610
	}
3611 9a9a6b3e Kristof Provost
3612
	if (!(is_array($el) && array_key_exists($vkey, $el))) {
3613
		return null;
3614
	}
3615
3616 eec3ca7f Reid Linnemann
	$ret = $el[$vkey];
3617
	unset($el[$vkey]);
3618
	return ($ret);
3619
}
3620
3621
3622 626c7734 Renato Botelho
function dhcpd_date_adjust_gmt($dt) {
3623
	init_config_arr(array('dhcpd'));
3624
3625 33766cbc Reid Linnemann
	foreach (config_get_path('dhcpd', []) as $dhcpditem) {
3626 fae6b2c0 jim-p
		if (empty($dhcpditem)) {
3627
			continue;
3628
		}
3629 626c7734 Renato Botelho
		if ($dhcpditem['dhcpleaseinlocaltime'] == "yes") {
3630
			$ts = strtotime($dt . " GMT");
3631
			if ($ts !== false) {
3632
				return strftime("%Y/%m/%d %H:%M:%S", $ts);
3633
			}
3634
		}
3635
	}
3636
3637
	/*
3638
	 * If we did not need to convert to local time or the conversion
3639
	 * failed, just return the input.
3640
	 */
3641
	return $dt;
3642
}
3643
3644 09d59743 jim-p
global $supported_image_types;
3645
$supported_image_types = array(
3646
	IMAGETYPE_JPEG,
3647
	IMAGETYPE_PNG,
3648
	IMAGETYPE_GIF,
3649
	IMAGETYPE_WEBP
3650
);
3651
3652
function is_supported_image($image_filename) {
3653
	global $supported_image_types;
3654
	$img_info = getimagesize($image_filename);
3655
3656
	/* If it's not an image, or it isn't in the supported list, return false */
3657
	if (($img_info === false) ||
3658
	    !in_array($img_info[2], array_keys($supported_image_types))) {
3659
		return false;
3660
	} else {
3661
		return $img_info[2];
3662
	}
3663
}
3664
3665 49e36202 Viktor Gurov
function get_lagg_ports ($laggport) {
3666
	$laggp = array();
3667
	foreach ($laggport as $lgp) {
3668
		list($lpname, $lpinfo) = explode(" ", $lgp);
3669
		preg_match('~<(.+)>~', $lpinfo, $lgportmode);
3670
		if ($lgportmode[1]) {
3671
			$laggp[] = $lpname . " (" . $lgportmode[1] . ")";
3672
		} else {
3673
			$laggp[] = $lpname;
3674
		}
3675
	}
3676
	if ($laggp) {
3677
		return implode(", ", $laggp);
3678
	} else {
3679
		return false;
3680
	}
3681
}
3682
3683 4537e922 Viktor G
function cisco_to_cidr($addr) {
3684
	if (!is_ipaddr($addr)) {
3685 5df5c9b4 jim-p
		throw new Exception('Value is not in dotted quad notation.');
3686 4537e922 Viktor G
	}
3687
3688
	$mask = decbin(~ip2long($addr));
3689
	$mask = substr($mask, -32);
3690
	$k = 0;
3691
	for ($i = 0; $i <= 32; $i++) {
3692
		$k += intval($mask[$i]);
3693
	}
3694
	return $k;
3695
}
3696
3697
function cisco_extract_index($prule) {
3698
	$index = explode("#", $prule);
3699
	if (is_numeric($index[1])) {
3700
		return intval($index[1]);
3701
	} else {
3702
		syslog(LOG_WARNING, "Error parsing rule {$prule}: Could not extract index");
3703
	}
3704
	return -1;;
3705
}
3706
3707
function parse_cisco_acl_rule($rule, $devname, $dir, $proto) {
3708
	$rule_orig = $rule;
3709
	$rule = explode(" ", $rule);
3710
	$tmprule = "";
3711
	$index = 0;
3712
3713
	if ($rule[$index] == "permit") {
3714
		$startrule = "pass {$dir} quick on {$devname} ";
3715
	} else if ($rule[$index] == "deny") {
3716
		$startrule = "block {$dir} quick on {$devname} ";
3717
	} else {
3718
		return;
3719
	}
3720
3721
	$index++;
3722
3723
	switch ($rule[$index]) {
3724
		case "ip":
3725
			break;
3726
		case "icmp":
3727
			$icmp = ($proto == "inet") ? "icmp" : "ipv6-icmp";
3728
			$tmprule .= "proto {$icmp} ";
3729
			break;
3730
		case "tcp":
3731
		case "udp":
3732
			$tmprule .= "proto {$rule[$index]} ";
3733
			break;
3734
		default:
3735
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid protocol.");
3736
			return;
3737
	}
3738
	$index++;
3739
3740
	/* Source */
3741
	if (trim($rule[$index]) == "host") {
3742
		$index++;
3743 6bb8cdd4 Viktor G
		if ((($proto == 'inet') && (is_ipaddrv4(trim($rule[$index])) || (trim($rule[$index]) == "{clientip}"))) ||
3744
		    (($proto == 'inet6') && (is_ipaddrv6(trim($rule[$index])) || (trim($rule[$index]) == "{clientipv6}")))) {
3745 daaa7474 fl0l0u
			if ($GLOBALS['attributes']['framed_ip']) {
3746
				$tmprule .= "from {$GLOBALS['attributes']['framed_ip']} ";
3747 6e8c4db2 fl0l0u
			} else {
3748
				$tmprule .= "from {$rule[$index]} ";
3749
			}
3750 4537e922 Viktor G
			$index++;
3751
		} else {
3752
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source host '{$rule[$index]}'.");
3753
			return;
3754
		}
3755
	} elseif (is_subnetv6(trim($rule[$index])) && ($proto == "inet6")) {
3756
		$tmprule .= "from {$rule[$index]} ";
3757
		$index++;
3758
	} elseif (trim($rule[$index]) == "any") {
3759
		$tmprule .= "from any ";
3760
		$index++;
3761
	} else {
3762
		$network = $rule[$index];
3763
		$netmask = $rule[++$index];
3764
3765
		if (is_ipaddrv4($network) && ($proto == "inet")) {
3766
			try {
3767
				$netmask = cisco_to_cidr($netmask);
3768 5df5c9b4 jim-p
			} catch(Exception $e) {
3769
				syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source netmask '$netmask' (" . $e->getMessage() . ").");
3770 4537e922 Viktor G
				return;
3771
			}
3772 321fbbdb bashkarev
			$tmprule .= "from {$network}/{$netmask} ";
3773 4537e922 Viktor G
		} else {
3774
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source network '$network'.");
3775
			return;
3776
		}
3777
3778
		$index++;
3779
	}
3780
3781
	/* Source Operator */
3782
	if (in_array(trim($rule[$index]), array("lt", "gt", "eq", "neq"))) {
3783
		switch(trim($rule[$index])) {
3784
			case "lt":
3785
				$operator = "<";
3786
				break;
3787
			case "gt":
3788
				$operator = ">";
3789
				break;
3790
			case "eq":
3791
				$operator = "=";
3792
				break;
3793
			case "neq":
3794
				$operator = "!=";
3795
				break;
3796
		}
3797
3798
		$port = $rule[++$index];
3799
		if (is_port($port)) {
3800
			$tmprule .= "port {$operator} {$port} ";
3801
		} else {
3802
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid source port: '$port' not a numeric value between 0 and 65535.");
3803
			return;
3804
		}
3805
		$index++;
3806
	} else if (trim($rule[$index]) == "range") {
3807
		$port = array($rule[++$index], $rule[++$index]);
3808
		if (is_port($port[0]) && is_port($port[1])) {
3809 b5c9be99 fl0l0u
			$tmprule .= "port {$port[0]}:{$port[1]} ";
3810 4537e922 Viktor G
		} else {
3811
			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.");
3812
			return;
3813
		}
3814
		$index++;
3815
	}
3816
3817
	/* Destination */
3818
	if (trim($rule[$index]) == "host") {
3819
		$index++;
3820 6bb8cdd4 Viktor G
		if ((($proto == 'inet') && (is_ipaddrv4(trim($rule[$index])) || (trim($rule[$index]) == "{clientip}"))) ||
3821
		    (($proto == 'inet6') && (is_ipaddrv6(trim($rule[$index])) || (trim($rule[$index]) == "{clientipv6}")))) {
3822 4537e922 Viktor G
			$tmprule .= "to {$rule[$index]} ";
3823
			$index++;
3824
		} else {
3825
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination host '{$rule[$index]}'.");
3826
			return;
3827
		}
3828
	} elseif (is_subnetv6(trim($rule[$index])) && ($proto == "inet6")) {
3829
		$tmprule .= "to {$rule[$index]} ";
3830
		$index++;
3831
	} elseif (trim($rule[$index]) == "any") {
3832
		$tmprule .= "to any ";
3833
		$index++;
3834
	} else {
3835
		$network = $rule[$index];
3836
		$netmask = $rule[++$index];
3837
3838
		if (is_ipaddrv4($network) && ($proto == "inet")) {
3839
			try {
3840
				$netmask = cisco_to_cidr($netmask);
3841 5df5c9b4 jim-p
			} catch(Exception $e) {
3842 02724a5a jim-p
				syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination netmask '$netmask' (" . $e->getMessage() . ").");
3843 4537e922 Viktor G
				return;
3844
			}
3845 321fbbdb bashkarev
			$tmprule .= "to {$network}/{$netmask} ";
3846 4537e922 Viktor G
		} else {
3847
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination network '$network'.");
3848
			return;
3849
		}
3850
3851
		$index++;
3852
	}
3853
3854
	/* Destination Operator */
3855
	if (in_array(trim($rule[$index]), array("lt", "gt", "eq", "neq"))) {
3856
		switch(trim($rule[$index])) {
3857
			case "lt":
3858
				$operator = "<";
3859
				break;
3860
			case "gt":
3861
				$operator = ">";
3862
				break;
3863
			case "eq":
3864
				$operator = "=";
3865
				break;
3866
			case "neq":
3867
				$operator = "!=";
3868
				break;
3869
		}
3870
3871
		$port = $rule[++$index];
3872
		if (is_port($port)) {
3873
			$tmprule .= "port {$operator} {$port} ";
3874
		} else {
3875
			syslog(LOG_WARNING, "Error parsing rule {$rule_orig}: Invalid destination port: '$port' not a numeric value between 0 and 65535.");
3876
			return;
3877
		}
3878
		$index++;
3879
	} else if (trim($rule[$index]) == "range") {
3880
		$port = array($rule[++$index], $rule[++$index]);
3881
		if (is_port($port[0]) && is_port($port[1])) {
3882 b5c9be99 fl0l0u
			$tmprule .= "port {$port[0]}:{$port[1]} ";
3883 4537e922 Viktor G
		} else {
3884
			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.");
3885
			return;
3886
		}
3887
		$index++;
3888
	}
3889
3890
	$tmprule = $startrule . $proto . " " . $tmprule;
3891
	return $tmprule;
3892
}
3893
3894
function parse_cisco_acl($attribs, $dev) {
3895
	global $attributes;
3896
3897
	if (!is_array($attribs)) {
3898
		return "";
3899
	}
3900
	$finalrules = "";
3901
	if (is_array($attribs['ciscoavpair'])) {
3902
		$inrules = array('inet' => array(), 'inet6' => array());
3903
		$outrules = array('inet' => array(), 'inet6' => array());
3904
		foreach ($attribs['ciscoavpair'] as $avrules) {
3905
			$rule = explode("=", $avrules);
3906
			$dir = "";
3907
			if (strstr($rule[0], "inacl")) {
3908
				$dir = "in";
3909
			} else if (strstr($rule[0], "outacl")) {
3910
				$dir = "out";
3911
			} else if (strstr($rule[0], "dns-servers")) {
3912
				$attributes['dns-servers'] = explode(" ", $rule[1]);
3913
				continue;
3914
			} else if (strstr($rule[0], "route")) {
3915
				if (!is_array($attributes['routes'])) {
3916
					$attributes['routes'] = array();
3917
				}
3918
				$attributes['routes'][] = $rule[1];
3919
				continue;
3920
			}
3921
			$rindex = cisco_extract_index($rule[0]);
3922
			if ($rindex < 0) {
3923
				continue;
3924
			}
3925
3926
			if (strstr($rule[0], "ipv6")) {
3927
				$proto = "inet6";
3928
			} else {
3929
				$proto = "inet";
3930
			}
3931
3932
			$tmprule = parse_cisco_acl_rule($rule[1], $dev, $dir, $proto);
3933 55f0061a Marcos Mendoza
			if (!empty($tmprule)) {
3934
				if ($dir == "in") {
3935
					$inrules[$proto][$rindex] = $tmprule;
3936
				} else if ($dir == "out") {
3937
					$outrules[$proto][$rindex] = $tmprule;
3938
				}
3939 4537e922 Viktor G
			}
3940
		}
3941
3942
3943
		$state = "";
3944
		foreach (array('inet', 'inet6') as $ip) {
3945
			if (!empty($outrules[$ip])) {
3946
				$state = "no state";
3947
			}
3948
			ksort($inrules[$ip], SORT_NUMERIC);
3949
			foreach ($inrules[$ip] as $inrule) {
3950
				$finalrules .= "{$inrule} {$state}\n";
3951
			}
3952
			if (!empty($outrules[$ip])) {
3953
				ksort($outrules[$ip], SORT_NUMERIC);
3954
				foreach ($outrules[$ip] as $outrule) {
3955
					$finalrules .= "{$outrule} {$state}\n";
3956
				}
3957
			}
3958
		}
3959
	}
3960
	return $finalrules;
3961
}
3962
3963 c91be02b Viktor G
function alias_idn_to_utf8($alias) {
3964
	if (is_alias($alias)) {
3965
		return $alias;
3966
	} else {
3967
		return idn_to_utf8($alias);
3968
	}
3969
}
3970
3971
function alias_idn_to_ascii($alias) {
3972
	if (is_alias($alias)) {
3973
		return $alias;
3974
	} else {
3975
		return idn_to_ascii($alias);
3976
	}
3977
}
3978
3979 4864d7f6 Josh Soref
// These functions were in guiconfig.inc but have been moved here so non GUI processes can use them
3980 2428d0a9 Steve Beaver
function address_to_pconfig($adr, &$padr, &$pmask, &$pnot, &$pbeginport, &$pendport) {
3981
	if (isset($adr['any'])) {
3982
		$padr = "any";
3983
	} else if ($adr['network']) {
3984
		$padr = $adr['network'];
3985
	} else if ($adr['address']) {
3986
		list($padr, $pmask) = explode("/", $adr['address']);
3987
		if (!$pmask) {
3988
			if (is_ipaddrv6($padr)) {
3989
				$pmask = 128;
3990
			} else {
3991
				$pmask = 32;
3992
			}
3993
		}
3994
	}
3995
3996
	if (isset($adr['not'])) {
3997
		$pnot = 1;
3998
	} else {
3999
		$pnot = 0;
4000
	}
4001
4002
	if ($adr['port']) {
4003
		list($pbeginport, $pendport) = explode("-", $adr['port']);
4004
		if (!$pendport) {
4005
			$pendport = $pbeginport;
4006
		}
4007
	} else if (!is_alias($pbeginport) && !is_alias($pendport)) {
4008
		$pbeginport = "any";
4009
		$pendport = "any";
4010
	}
4011
}
4012
4013 303c51fc Viktor G
function pconfig_to_address(&$adr, $padr, $pmask, $pnot = false, $pbeginport = 0, $pendport = 0, $addmask = false) {
4014 2428d0a9 Steve Beaver
	$adr = array();
4015
4016
	if ($padr == "any") {
4017
		$adr['any'] = true;
4018 ccf3b257 Marcos Mendoza
	} else if (get_specialnet($padr)) {
4019 303c51fc Viktor G
		if ($addmask) {
4020
			$padr .= "/" . $pmask;
4021
		}
4022 2428d0a9 Steve Beaver
		$adr['network'] = $padr;
4023
	} else {
4024
		$adr['address'] = $padr;
4025
		if (is_ipaddrv6($padr)) {
4026
			if ($pmask != 128) {
4027
				$adr['address'] .= "/" . $pmask;
4028
			}
4029
		} else {
4030
			if ($pmask != 32) {
4031
				$adr['address'] .= "/" . $pmask;
4032
			}
4033
		}
4034
	}
4035
4036
	if ($pnot) {
4037
		$adr['not'] = true;
4038
	} else {
4039
		unset($adr['not']);
4040
	}
4041
4042
	if (($pbeginport != 0) && ($pbeginport != "any")) {
4043
		if ($pbeginport != $pendport) {
4044
			$adr['port'] = $pbeginport . "-" . $pendport;
4045
		} else {
4046
			$adr['port'] = $pbeginport;
4047
		}
4048
	}
4049
4050
	/*
4051
	 * If the port is still unset, then it must not be numeric, but could
4052
	 * be an alias or a well-known/registered service.
4053
	 * See https://redmine.pfsense.org/issues/8410
4054
	 */
4055
	if (!isset($adr['port']) && is_port_or_alias($pbeginport)) {
4056
		$adr['port'] = $pbeginport;
4057
	}
4058
}
4059
4060 feefe2c3 Marcos Mendoza
function get_specialnet(?string $net = '', array $flags = [], ?string $if = ''):bool|array {
4061
	if ($net === null || $if === null) {
4062 2428d0a9 Steve Beaver
		return false;
4063
	}
4064 feefe2c3 Marcos Mendoza
	$checkpermission = false;
4065
	$allnetflags = [SPECIALNET_ANY, SPECIALNET_SELF, SPECIALNET_CLIENTS,
4066
	                SPECIALNET_IFADDR, SPECIALNET_IFSUB, SPECIALNET_IFNET,
4067
	                SPECIALNET_GROUP, SPECIALNET_VIPS];
4068 ccf3b257 Marcos Mendoza
4069 feefe2c3 Marcos Mendoza
	if (empty($flags)) {
4070
		$flags = $allnetflags;
4071
	} else {
4072
		if (in_array(SPECIALNET_CHECKPERM, $flags)) {
4073
			$checkpermission = true;
4074 ccf3b257 Marcos Mendoza
		}
4075 feefe2c3 Marcos Mendoza
		if (in_array(SPECIALNET_EXCLUDE, $flags)) {
4076
			$flags = array_diff($allnetflags, $flags);
4077
		}
4078
	}
4079
	$specialnet = [];
4080
	if (in_array(SPECIALNET_IFADDR, $flags) || in_array(SPECIALNET_IFSUB, $flags) || in_array(SPECIALNET_IFNET, $flags)) {
4081
		$ifmacros = get_configured_interface_with_descr();
4082
	}
4083
	foreach ($flags as $include) {
4084
		switch ($include) {
4085
			case SPECIALNET_NONE:
4086
				$specialnet = array_merge($specialnet, ['none' => gettext('None')]);
4087
				break;
4088
			case SPECIALNET_ANY:
4089
				$specialnet = array_merge($specialnet, ['any' => gettext('Any')]);
4090
				break;
4091
			case SPECIALNET_COMPAT_ADDR:
4092
			case SPECIALNET_ADDR:
4093
				if ($include == SPECIALNET_COMPAT_ADDR) {
4094
					$ifname = 'single';
4095
				} else {
4096
					$ifname = 'address';
4097
				}
4098
				$specialnet = array_merge($specialnet, [$ifname => gettext('Address')]);
4099
				break;
4100
			case SPECIALNET_COMPAT_ADDRAL:
4101
			case SPECIALNET_ADDRAL:
4102
				if ($include == SPECIALNET_COMPAT_ADDRAL) {
4103
					$ifname = 'single';
4104
				} else {
4105
					$ifname = 'address';
4106
				}
4107
				$specialnet = array_merge($specialnet, [$ifname => gettext('Address or Alias')]);
4108
				break;
4109
			case SPECIALNET_NET:
4110
			case SPECIALNET_NETAL:
4111
				if ($include == SPECIALNET_NET) {
4112
					$ifdescr = 'Network';
4113
				} else {
4114
					$ifdescr = 'Network or Alias';
4115
				}
4116
				$specialnet = array_merge($specialnet, ['network' => gettext($ifdescr)]);
4117
				break;
4118
			case SPECIALNET_SELF:
4119
				$specialnet = array_merge($specialnet, ['(self)' => gettext('This Firewall (self)')]);
4120
				break;
4121
			case SPECIALNET_CLIENTS:
4122
				if ($checkpermission) {
4123
					$ifallowed = [];
4124
					if (have_ruleint_access("pptp")) {
4125
						$ifallowed['pptp'] = gettext('PPTP clients');
4126
					}
4127
					if (have_ruleint_access("pppoe")) {
4128
						$ifallowed['pppoe'] = gettext('PPPoE clients');
4129
					}
4130
					if (have_ruleint_access("l2tp")) {
4131
						$ifallowed['l2tp'] = gettext('L2TP clients');
4132
					}
4133
					$specialnet = array_merge($specialnet, $ifallowed);
4134
				} else {
4135
					$specialnet = array_merge($specialnet, [
4136
					                          'pptp' => gettext('PPTP clients'),
4137
					                          'pppoe' => gettext('PPPoE clients'),
4138 e4bba4ab Marcos Mendoza
					                          'l2tp' => gettext('L2TP clients')]);
4139 feefe2c3 Marcos Mendoza
				}
4140
				break;
4141
			case SPECIALNET_IFADDR:
4142
			case SPECIALNET_IFSUB:
4143
			case SPECIALNET_IFNET:
4144
				if ($include == SPECIALNET_IFADDR) {
4145
					$ifname_suffix = 'ip';
4146
					$ifdescr_suffix = ' address';
4147
				} elseif ($include == SPECIALNET_IFSUB) {
4148
					$ifname_suffix = '';
4149
					$ifdescr_suffix = ' subnet';
4150
				} else {
4151
					$ifname_suffix = '';
4152
					$ifdescr_suffix = ' subnets';
4153
				}
4154
				foreach ($ifmacros as $ifname => $ifdescr) {
4155
					// Only add macros for allowed interfaces when checking permissions
4156
					if ($checkpermission && !have_ruleint_access($ifname)) {
4157
						continue;
4158
					}
4159
					// If an interface is specified, only add interface macros for that interface
4160
					if (!empty($if)) {
4161
						if ($if != $ifname) {
4162
							continue;
4163
						} else {
4164
							$specialnet[$ifname . $ifname_suffix] = $ifdescr . $ifdescr_suffix;
4165
							break;
4166
						}
4167
					} else {
4168
						$specialnet[$ifname . $ifname_suffix] = $ifdescr . $ifdescr_suffix;
4169
					}
4170
				}
4171
				break;
4172
			case SPECIALNET_GROUP:
4173
				foreach (config_get_path('ifgroups/ifgroupentry', []) as $ifgen) {
4174
					$ifgenmembers = explode(' ', $ifgen['members']);
4175
					foreach ($ifgenmembers as $ifgmember) {
4176
						// Skip interface groups with members which are not allowed
4177
						if ($checkpermission && !have_ruleint_access($ifgmember)) {
4178
							continue 2;
4179
						}
4180
						// Skip interface groups which do not include the specified interface
4181
						if (!empty($if) && $if != $ifgmember &&
4182
						    $ifgmember == $ifgenmembers[array_key_last($ifgenmembers)]) {
4183
							continue 2;
4184
						}
4185
					}
4186
					$specialnet[$ifgen['ifname']] = $ifgen['ifname'] . ' networks';
4187
				}
4188
				break;
4189
			case SPECIALNET_VIPS:
4190
				$tmp_vips = [];
4191
				foreach (config_get_path('virtualip/vip', []) as $vip) {
4192
					// Only add VIPs for allowed interfaces when checking permissions
4193
					if ($checkpermission && !empty($vip['interface']) && !have_ruleint_access($vip['interface'])) {
4194
						continue;
4195
					}
4196
					$tmp_vips[$vip['subnet'] . '/' . $vip['subnet_bits']] = $vip;
4197
				}
4198
				asort($tmp_vips, SORT_NATURAL);
4199
				foreach ($tmp_vips as $vipsn => $sn) {
4200
					if (($sn['mode'] == "proxyarp" || $sn['mode'] == "other") && $sn['type'] == "network") {
4201
						$specialnet[$vipsn] = 'Subnet: ' . $sn['subnet'] . '/' . $sn['subnet_bits'] . ' (' . $sn['descr'] . ')';
4202
						// Skip expanding IPv4 VIPs with the option, as well as any IPv6 network
4203
						if (isset($sn['noexpand']) || strstr($sn['subnet'], ':' !== false)) {
4204
							continue;
4205
						}
4206
						$start = ip2long32(gen_subnet($sn['subnet'], $sn['subnet_bits']));
4207
						$end = ip2long32(gen_subnet_max($sn['subnet'], $sn['subnet_bits']));
4208
						$len = $end - $start;
4209
						for ($i = 0; $i <= $len; $i++) {
4210
							$snip = ip2long32($start+$i);
4211
			
4212
							$specialnet[$snip] = $snip . ' (' . $sn['descr'] . ')';
4213
						}
4214
					} else {
4215
						$specialnet[$sn['subnet']] = $sn['subnet'] . ' (' . $sn['descr'] . ')';
4216
					}
4217
				}
4218
				break;
4219
			default:
4220
				break;
4221 9fbd5798 Marcos Mendoza
		}
4222 ccf3b257 Marcos Mendoza
	}
4223
4224
	if (empty($net)) {
4225 feefe2c3 Marcos Mendoza
		return $specialnet;
4226
	} elseif (array_key_exists($net, $specialnet)) {
4227 2428d0a9 Steve Beaver
		return true;
4228
	} else {
4229
		return false;
4230
	}
4231
}
4232 6d98e931 Viktor G
4233
function is_interface_ipaddr($interface) {
4234 33766cbc Reid Linnemann
	if (!empty(config_get_path("interfaces/{$interface}/ipaddr"))) {
4235 6d98e931 Viktor G
		return true;
4236
	}
4237
	return false;
4238
}
4239
4240
function is_interface_ipaddrv6($interface) {
4241 33766cbc Reid Linnemann
	if (!empty(config_get_path("interfaces/{$interface}/ipaddrv6"))) {
4242 6d98e931 Viktor G
		return true;
4243
	}
4244
	return false;
4245
}
4246
4247 57a737f1 jim-p
function escape_filter_regex($filtertext) {
4248
	/* If the caller (user) has not already put a backslash before a slash, to escape it in the regex, */
4249
	/* then this will do it. Take out any "\/" already there, then turn all ordinary "/" into "\/".    */
4250
	return str_replace('/', '\/', str_replace('\/', '/', $filtertext));
4251
}
4252
4253
/*
4254
 * Check if a given pattern has the same number of two different unescaped
4255
 * characters.
4256
 * For example, it can ensure a pattern has balanced sets of parentheses,
4257
 * braces, and brackets.
4258
 */
4259
function is_pattern_balanced_char($pattern, $open, $close) {
4260
	/* First remove escaped versions */
4261
	$pattern = str_replace('\\' . $open, '', $pattern);
4262
	$pattern = str_replace('\\' . $close, '', $pattern);
4263
	/* Check if the counts of both characters match in the target pattern */
4264
	return (substr_count($pattern, $open) == substr_count($pattern, $close));
4265
}
4266
4267
/*
4268
 * Check if a pattern contains balanced sets of parentheses, braces, and
4269
 * brackets.
4270
 */
4271
function is_pattern_balanced($pattern) {
4272
	if (is_pattern_balanced_char($pattern, '(', ')') &&
4273
	    is_pattern_balanced_char($pattern, '{', '}') &&
4274
	    is_pattern_balanced_char($pattern, '[', ']')) {
4275
		/* Balanced if all are true */
4276
		return true;
4277
	}
4278
	return false;
4279
}
4280
4281
function cleanup_regex_pattern($filtertext) {
4282
	/* Cleanup filter to prevent backreferences. */
4283
	$filtertext = escape_filter_regex($filtertext);
4284
4285
	/* Remove \<digit>+ backreferences
4286
	 * To match \ it must be escaped as \\\\ in PHP for preg_replace() */
4287
	$filtertext = preg_replace('/\\\\\\d+/', '', $filtertext);
4288
4289 8cd3f92f jim-p
	/* Check for unbalanced parentheses, braces, and brackets which
4290
	 * may be an error or attempt to circumvent protections.
4291 cf757a80 jim-p
	 * Also discard any pattern that attempts problematic duplication
4292
	 * methods. */
4293 8cd3f92f jim-p
	if (!is_pattern_balanced($filtertext) ||
4294
	    (substr_count($filtertext, ')*') > 0) ||
4295
	    (substr_count($filtertext, ')+') > 0) ||
4296 cf757a80 jim-p
	    (substr_count($filtertext, '{') > 0)) {
4297 57a737f1 jim-p
		return '';
4298
	}
4299
4300
	return $filtertext;
4301
}
4302
4303 cd974f08 Viktor G
function ip6_to_asn1($addr) {
4304
	/* IPv6 MIB uses an OCTET STRING of length 16 to represent
4305
	 * 128-bit IPv6 address in network byte order.
4306 fd30ce6a Christian McDonald
	 * see https://datatracker.ietf.org/doc/html/rfc2465#section-3
4307 cd974f08 Viktor G
	 * i.e. fc00:3::4 = 252.0.0.3.0.0.0.0.0.0.0.0.0.0.0.4
4308
	 */
4309
4310
	if (!is_ipaddrv6($addr)) {
4311
		return false;
4312
	}
4313 dc337505 Reid Linnemann
	$ipv6str = "";
4314
	$octstr = "";
4315 cd974f08 Viktor G
	foreach (explode(':', Net_IPv6::uncompress($addr)) as $v) {
4316
		$ipv6str .= str_pad($v, 4, '0', STR_PAD_LEFT);
4317
	}
4318
	foreach (str_split($ipv6str, 2) as $v) {
4319
		$octstr .= base_convert($v, 16, 10) . '.';
4320
	}
4321
4322
	return $octstr;
4323
}
4324
4325 e638072c Viktor G
function interfaces_interrupts() {
4326
	exec("/usr/bin/vmstat -i --libxo json", $rawdata, $rc);
4327
	$interrupts = array();
4328
	if ($rc == 0) {
4329
		$vnstatarr = json_decode(implode(" ", $rawdata), JSON_OBJECT_AS_ARRAY);
4330
		$interruptarr = $vnstatarr['interrupt-statistics']['interrupt'];
4331
4332
		foreach ($interruptarr as $int){
4333
			preg_match("/irq\d+: ([a-z0-9]+)/", $int['name'], $matches);
4334
			$name = $matches[1];
4335
			if (array_key_exists($name, $interrupts)) {
4336
				/* interface with multiple queues */
4337
				$interrupts[$name]['total'] += $int['total'];
4338
				$interrupts[$name]['rate'] += $int['rate'];
4339
			} else {
4340
				$interrupts[$name]['total'] = $int['total'];
4341
				$interrupts[$name]['rate'] = $int['rate'];
4342
			}
4343
		}
4344
	}
4345
4346
	return $interrupts;
4347
}
4348
4349 7c2468c5 Viktor G
function dummynet_load_module($max_qlimit) {
4350
	if (!is_module_loaded("dummynet.ko")) {
4351
		mute_kernel_msgs();
4352
		mwexec("/sbin/kldload dummynet");
4353
		unmute_kernel_msgs();
4354
	}
4355
	$sysctls = (array(
4356
			"net.inet.ip.dummynet.io_fast" => "1",
4357
			"net.inet.ip.dummynet.hash_size" => "256",
4358
			"net.inet.ip.dummynet.pipe_slot_limit" => $max_qlimit
4359
	));
4360
	init_config_arr(array('sysctl', 'item'));
4361 33766cbc Reid Linnemann
	foreach (config_get_path('sysctl/item', []) as $item) {
4362
		if (preg_match('/net\.inet\.ip\.dummynet\./', $item['tunable'])) {
4363
			$sysctls[$item['tunable']] = $item['value'];
4364 7c2468c5 Viktor G
		}
4365
	}
4366
	set_sysctl($sysctls);
4367
}
4368
4369
function get_interface_vip_ips($interface) {
4370
	global $config;
4371
	$vipips = '';
4372
4373
	init_config_arr(array('virtualip', 'vip'));
4374
	foreach ($config['virtualip']['vip'] as $vip) {
4375
		if (($vip['interface'] == $interface) &&
4376
		    (($vip['mode'] == "carp") || ($vip['mode'] == "ipalias"))) {
4377
			$vipips .= $vip['subnet'] . ' ';
4378
		}
4379
	}
4380
	return $vipips;
4381
}
4382
4383 eb295a1b Ermal
?>