From 46bc6e545a17e77202aaf01ec0cd8d5a46567525 Mon Sep 17 00:00:00 2001 From: Renato Botelho Date: Tue, 25 Aug 2015 08:08:24 -0300 Subject: Move main pfSense content to src/ --- src/etc/inc/util.inc | 2259 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2259 insertions(+) create mode 100644 src/etc/inc/util.inc (limited to 'src/etc/inc/util.inc') diff --git a/src/etc/inc/util.inc b/src/etc/inc/util.inc new file mode 100644 index 0000000..b2c797b --- /dev/null +++ b/src/etc/inc/util.inc @@ -0,0 +1,2259 @@ +. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +/* + pfSense_BUILDER_BINARIES: /bin/ps /bin/kill /usr/bin/killall /sbin/ifconfig /usr/bin/netstat + pfSense_BUILDER_BINARIES: /usr/bin/awk /sbin/dmesg /sbin/ping /usr/local/sbin/gzsig /usr/sbin/arp + pfSense_BUILDER_BINARIES: /sbin/conscontrol /sbin/devd /bin/ps + pfSense_MODULE: utils +*/ + +/* kill a process by pid file */ +function killbypid($pidfile) { + return sigkillbypid($pidfile, "TERM"); +} + +function isvalidpid($pidfile) { + $output = ""; + if (file_exists($pidfile)) { + exec("/bin/pgrep -nF {$pidfile}", $output, $retval); + return (intval($retval) == 0); + } + return false; +} + +function is_process_running($process) { + $output = ""; + exec("/bin/pgrep -anx " . escapeshellarg($process), $output, $retval); + + return (intval($retval) == 0); +} + +function isvalidproc($proc) { + return is_process_running($proc); +} + +/* sigkill a process by pid file */ +/* return 1 for success and 0 for a failure */ +function sigkillbypid($pidfile, $sig) { + if (file_exists($pidfile)) { + return mwexec("/bin/pkill " . escapeshellarg("-{$sig}") . " -F {$pidfile}", true); + } + + return 0; +} + +/* kill a process by name */ +function sigkillbyname($procname, $sig) { + if (isvalidproc($procname)) { + return mwexec("/usr/bin/killall " . escapeshellarg("-{$sig}") . " " . escapeshellarg($procname), true); + } +} + +/* kill a process by name */ +function killbyname($procname) { + if (isvalidproc($procname)) { + mwexec("/usr/bin/killall " . escapeshellarg($procname)); + } +} + +function is_subsystem_dirty($subsystem = "") { + global $g; + + if ($subsystem == "") { + return false; + } + + if (file_exists("{$g['varrun_path']}/{$subsystem}.dirty")) { + return true; + } + + return false; +} + +function mark_subsystem_dirty($subsystem = "") { + global $g; + + if (!file_put_contents("{$g['varrun_path']}/{$subsystem}.dirty", "DIRTY")) { + log_error(sprintf(gettext("WARNING: Could not mark subsystem: %s dirty"), $subsystem)); + } +} + +function clear_subsystem_dirty($subsystem = "") { + global $g; + + @unlink("{$g['varrun_path']}/{$subsystem}.dirty"); +} + +function config_lock() { + return; +} +function config_unlock() { + return; +} + +/* lock configuration file */ +function lock($lock, $op = LOCK_SH) { + global $g, $cfglckkeyconsumers; + if (!$lock) { + die(gettext("WARNING: You must give a name as parameter to lock() function.")); + } + if (!file_exists("{$g['tmp_path']}/{$lock}.lock")) { + @touch("{$g['tmp_path']}/{$lock}.lock"); + @chmod("{$g['tmp_path']}/{$lock}.lock", 0666); + } + $cfglckkeyconsumers++; + if ($fp = fopen("{$g['tmp_path']}/{$lock}.lock", "w")) { + if (flock($fp, $op)) { + return $fp; + } else { + fclose($fp); + } + } +} + +function try_lock($lock, $timeout = 5) { + global $g, $cfglckkeyconsumers; + if (!$lock) { + die(gettext("WARNING: You must give a name as parameter to try_lock() function.")); + } + if (!file_exists("{$g['tmp_path']}/{$lock}.lock")) { + @touch("{$g['tmp_path']}/{$lock}.lock"); + @chmod("{$g['tmp_path']}/{$lock}.lock", 0666); + } + $cfglckkeyconsumers++; + if ($fp = fopen("{$g['tmp_path']}/{$lock}.lock", "w")) { + $trycounter = 0; + while (!flock($fp, LOCK_EX | LOCK_NB)) { + if ($trycounter >= $timeout) { + fclose($fp); + return NULL; + } + sleep(1); + $trycounter++; + } + + return $fp; + } + + return NULL; +} + +/* unlock configuration file */ +function unlock($cfglckkey = 0) { + global $g, $cfglckkeyconsumers; + flock($cfglckkey, LOCK_UN); + fclose($cfglckkey); + return; +} + +/* unlock forcefully configuration file */ +function unlock_force($lock) { + global $g; + + @unlink("{$g['tmp_path']}/{$lock}.lock"); +} + +function send_event($cmd) { + global $g; + + if (!isset($g['event_address'])) { + $g['event_address'] = "unix:///var/run/check_reload_status"; + } + + $try = 0; + while ($try < 3) { + $fd = @fsockopen($g['event_address']); + if ($fd) { + fwrite($fd, $cmd); + $resp = fread($fd, 4096); + if ($resp != "OK\n") { + log_error("send_event: sent {$cmd} got {$resp}"); + } + fclose($fd); + $try = 3; + } else if (!is_process_running("check_reload_status")) { + mwexec_bg("/usr/bin/nice -n20 /usr/local/sbin/check_reload_status"); + } + $try++; + } +} + +function send_multiple_events($cmds) { + global $g; + + if (!isset($g['event_address'])) { + $g['event_address'] = "unix:///var/run/check_reload_status"; + } + + if (!is_array($cmds)) { + return; + } + + while ($try < 3) { + $fd = @fsockopen($g['event_address']); + if ($fd) { + foreach ($cmds as $cmd) { + fwrite($fd, $cmd); + $resp = fread($fd, 4096); + if ($resp != "OK\n") { + log_error("send_event: sent {$cmd} got {$resp}"); + } + } + fclose($fd); + $try = 3; + } else if (!is_process_running("check_reload_status")) { + mwexec_bg("/usr/bin/nice -n20 /usr/local/sbin/check_reload_status"); + } + $try++; + } +} + +function refcount_init($reference) { + $shmid = @shmop_open($reference, "c", 0644, 10); + @shmop_write($shmid, str_pad("0", 10, "\x0", STR_PAD_RIGHT), 0); + @shmop_close($shmid); +} + +function refcount_reference($reference) { + /* Take out a lock across the shared memory read, increment, write sequence to make it atomic. */ + $shm_lck = lock("shm{$reference}", LOCK_EX); + try { + /* NOTE: A warning is generated when shared memory does not exist */ + $shmid = @shmop_open($reference, "w", 0, 0); + if (!$shmid) { + refcount_init($reference); + $shmid = @shmop_open($reference, "w", 0, 0); + if (!$shmid) { + log_error(gettext("Could not open shared memory {$reference}")); + unlock($shm_lck); + return; + } + } + $shm_data = @shmop_read($shmid, 0, 10); + $shm_data = intval($shm_data) + 1; + @shmop_write($shmid, str_pad($shm_data, 10, "\x0", STR_PAD_RIGHT), 0); + @shmop_close($shmid); + unlock($shm_lck); + } catch (Exception $e) { + log_error($e->getMessage()); + unlock($shm_lck); + } + + return $shm_data; +} + +function refcount_unreference($reference) { + /* Take out a lock across the shared memory read, decrement, write sequence to make it atomic. */ + $shm_lck = lock("shm{$reference}", LOCK_EX); + try { + $shmid = @shmop_open($reference, "w", 0, 0); + if (!$shmid) { + refcount_init($reference); + log_error(gettext("Could not open shared memory {$reference}")); + unlock($shm_lck); + return; + } + $shm_data = @shmop_read($shmid, 0, 10); + $shm_data = intval($shm_data) - 1; + if ($shm_data < 0) { + //debug_backtrace(); + log_error(sprintf(gettext("Reference %s is going negative, not doing unreference."), $reference)); + } else { + @shmop_write($shmid, str_pad($shm_data, 10, "\x0", STR_PAD_RIGHT), 0); + } + @shmop_close($shmid); + unlock($shm_lck); + } catch (Exception $e) { + log_error($e->getMessage()); + unlock($shm_lck); + } + + return $shm_data; +} + +function refcount_read($reference) { + /* This function just reads the current value of the refcount for information. */ + /* There is no need for locking. */ + $shmid = @shmop_open($reference, "a", 0, 0); + if (!$shmid) { + log_error(gettext("Could not open shared memory for read {$reference}")); + return -1; + } + $shm_data = @shmop_read($shmid, 0, 10); + @shmop_close($shmid); + return $shm_data; +} + +function is_module_loaded($module_name) { + $module_name = str_replace(".ko", "", $module_name); + $running = 0; + $_gb = exec("/sbin/kldstat -qn {$module_name} 2>&1", $_gb, $running); + if (intval($running) == 0) { + return true; + } else { + return false; + } +} + +/* validate non-negative numeric string, or equivalent numeric variable */ +function is_numericint($arg) { + return (((is_int($arg) && $arg >= 0) || (is_string($arg) && strlen($arg) > 0 && ctype_digit($arg))) ? true : false); +} + +/* Generate the (human readable) ipv4 or ipv6 subnet address (i.e., netmask, or subnet start IP) + given an (human readable) ipv4 or ipv6 host address and subnet bit count */ +function gen_subnet($ipaddr, $bits) { + if (($sn = gen_subnetv6($ipaddr, $bits)) == '') { + $sn = gen_subnetv4($ipaddr, $bits); // try to avoid rechecking IPv4/v6 + } + return $sn; +} + +/* same as gen_subnet() but accepts IPv4 only */ +function gen_subnetv4($ipaddr, $bits) { + if (is_ipaddrv4($ipaddr) && is_numericint($bits) && $bits <= 32) { + if ($bits == 0) { + return '0.0.0.0'; // avoids <<32 + } + return long2ip(ip2long($ipaddr) & ((0xFFFFFFFF << (32 - $bits)) & 0xFFFFFFFF)); + } + return ""; +} + +/* same as gen_subnet() but accepts IPv6 only */ +function gen_subnetv6($ipaddr, $bits) { + if (is_ipaddrv6($ipaddr) && is_numericint($bits) && $bits <= 128) { + return Net_IPv6::compress(Net_IPv6::getNetmask($ipaddr, $bits)); + } + return ""; +} + +/* Generate the (human readable) ipv4 or ipv6 subnet end address (i.e., highest address, end IP, or IPv4 broadcast address) + given an (human readable) ipv4 or ipv6 host address and subnet bit count. */ +function gen_subnet_max($ipaddr, $bits) { + if (($sn = gen_subnetv6_max($ipaddr, $bits)) == '') { + $sn = gen_subnetv4_max($ipaddr, $bits); // try to avoid rechecking IPv4/v6 + } + return $sn; +} + +/* same as gen_subnet_max() but validates IPv4 only */ +function gen_subnetv4_max($ipaddr, $bits) { + if (is_ipaddrv4($ipaddr) && is_numericint($bits) && $bits <= 32) { + if ($bits == 32) { + return $ipaddr; + } + return long2ip32(ip2long($ipaddr) | ~gen_subnet_mask_long($bits)); + } + return ""; +} + +/* same as gen_subnet_max() but validates IPv6 only */ +function gen_subnetv6_max($ipaddr, $bits) { + if (is_ipaddrv6($ipaddr) && is_numericint($bits) && $bits <= 128) { + $endip_bin = substr(Net_IPv6::_ip2Bin($ipaddr), 0, $bits) . str_repeat('1', 128 - $bits); + return Net_IPv6::compress(Net_IPv6::_bin2Ip($endip_bin)); + } + return ""; +} + +/* returns a subnet mask (long given a bit count) */ +function gen_subnet_mask_long($bits) { + $sm = 0; + for ($i = 0; $i < $bits; $i++) { + $sm >>= 1; + $sm |= 0x80000000; + } + return $sm; +} + +/* same as above but returns a string */ +function gen_subnet_mask($bits) { + return long2ip(gen_subnet_mask_long($bits)); +} + +/* Convert long int to IP address, truncating to 32-bits. */ +function long2ip32($ip) { + return long2ip($ip & 0xFFFFFFFF); +} + +/* Convert IP address to long int, truncated to 32-bits to avoid sign extension on 64-bit platforms. */ +function ip2long32($ip) { + return (ip2long($ip) & 0xFFFFFFFF); +} + +/* Convert IP address to unsigned long int. */ +function ip2ulong($ip) { + return sprintf("%u", ip2long32($ip)); +} + +/* Find out how many IPs are contained within a given IP range + * e.g. 192.168.0.0 to 192.168.0.255 returns 256 + */ +function ip_range_size_v4($startip, $endip) { + if (is_ipaddrv4($startip) && is_ipaddrv4($endip)) { + // Operate as unsigned long because otherwise it wouldn't work + // when crossing over from 127.255.255.255 / 128.0.0.0 barrier + return abs(ip2ulong($startip) - ip2ulong($endip)) + 1; + } + return -1; +} + +/* Find the smallest possible subnet mask which can contain a given number of IPs + * e.g. 512 IPs can fit in a /23, but 513 IPs need a /22 + */ +function find_smallest_cidr_v4($number) { + $smallest = 1; + for ($b=32; $b > 0; $b--) { + $smallest = ($number <= pow(2, $b)) ? $b : $smallest; + } + return (32-$smallest); +} + +/* Return the previous IP address before the given address */ +function ip_before($ip, $offset = 1) { + return long2ip32(ip2long($ip) - $offset); +} + +/* Return the next IP address after the given address */ +function ip_after($ip, $offset = 1) { + return long2ip32(ip2long($ip) + $offset); +} + +/* Return true if the first IP is 'before' the second */ +function ip_less_than($ip1, $ip2) { + // Compare as unsigned long because otherwise it wouldn't work when + // crossing over from 127.255.255.255 / 128.0.0.0 barrier + return ip2ulong($ip1) < ip2ulong($ip2); +} + +/* Return true if the first IP is 'after' the second */ +function ip_greater_than($ip1, $ip2) { + // Compare as unsigned long because otherwise it wouldn't work + // when crossing over from 127.255.255.255 / 128.0.0.0 barrier + return ip2ulong($ip1) > ip2ulong($ip2); +} + +/* compare two IP addresses */ +function ipcmp($a, $b) { + if (ip_less_than($a, $b)) { + return -1; + } else if (ip_greater_than($a, $b)) { + return 1; + } else { + return 0; + } +} + +/* Convert a range of IPv4 addresses to an array of individual addresses. */ +/* Note: IPv6 ranges are not yet supported here. */ +function ip_range_to_address_array($startip, $endip, $max_size = 5000) { + if (!is_ipaddrv4($startip) || !is_ipaddrv4($endip)) { + return false; + } + + if (ip_greater_than($startip, $endip)) { + // Swap start and end so we can process sensibly. + $temp = $startip; + $startip = $endip; + $endip = $temp; + } + + if (ip_range_size_v4($startip, $endip) > $max_size) { + return false; + } + + // Container for IP addresses within this range. + $rangeaddresses = array(); + $end_int = ip2ulong($endip); + for ($ip_int = ip2ulong($startip); $ip_int <= $end_int; $ip_int++) { + $rangeaddresses[] = long2ip($ip_int); + } + + return $rangeaddresses; +} + +/* Convert a range of IPv4 addresses to an array of subnets which can contain the range. */ +/* Note: IPv6 ranges are not yet supported here. */ +function ip_range_to_subnet_array($startip, $endip) { + if (!is_ipaddrv4($startip) || !is_ipaddrv4($endip)) { + return array(); + } + + if (ip_greater_than($startip, $endip)) { + // Swap start and end so we can process sensibly. + $temp = $startip; + $startip = $endip; + $endip = $temp; + } + + // Container for subnets within this range. + $rangesubnets = array(); + + // Figure out what the smallest subnet is that holds the number of IPs in the given range. + $cidr = find_smallest_cidr_v4(ip_range_size_v4($startip, $endip)); + + // Loop here to reduce subnet size and retest as needed. We need to make sure + // that the target subnet is wholly contained between $startip and $endip. + for ($cidr; $cidr <= 32; $cidr++) { + // Find the network and broadcast addresses for the subnet being tested. + $targetsub_min = gen_subnet($startip, $cidr); + $targetsub_max = gen_subnet_max($startip, $cidr); + + // Check best case where the range is exactly one subnet. + if (($targetsub_min == $startip) && ($targetsub_max == $endip)) { + // Hooray, the range is exactly this subnet! + return array("{$startip}/{$cidr}"); + } + + // These remaining scenarios will find a subnet that uses the largest + // chunk possible of the range being tested, and leave the rest to be + // tested recursively after the loop. + + // Check if the subnet begins with $startip and ends before $endip + if (($targetsub_min == $startip) && ip_less_than($targetsub_max, $endip)) { + break; + } + + // Check if the subnet ends at $endip and starts after $startip + if (ip_greater_than($targetsub_min, $startip) && ($targetsub_max == $endip)) { + break; + } + + // Check if the subnet is between $startip and $endip + if (ip_greater_than($targetsub_min, $startip) && ip_less_than($targetsub_max, $endip)) { + break; + } + } + + // Some logic that will recursively search from $startip to the first IP before the start of the subnet we just found. + // NOTE: This may never be hit, the way the above algo turned out, but is left for completeness. + if ($startip != $targetsub_min) { + $rangesubnets = array_merge($rangesubnets, ip_range_to_subnet_array($startip, ip_before($targetsub_min))); + } + + // Add in the subnet we found before, to preserve ordering + $rangesubnets[] = "{$targetsub_min}/{$cidr}"; + + // And some more logic that will search after the subnet we found to fill in to the end of the range. + if ($endip != $targetsub_max) { + $rangesubnets = array_merge($rangesubnets, ip_range_to_subnet_array(ip_after($targetsub_max), $endip)); + } + return $rangesubnets; +} + +/* returns true if $range is a valid pair of IPv4 or IPv6 addresses separated by a "-" + false - if not a valid pair + true (numeric 4 or 6) - if valid, gives type of addresses */ +function is_iprange($range) { + if (substr_count($range, '-') != 1) { + return false; + } + list($ip1, $ip2) = explode ('-', $range); + if (is_ipaddrv4($ip1) && is_ipaddrv4($ip2)) { + return 4; + } + if (is_ipaddrv6($ip1) && is_ipaddrv6($ip2)) { + return 6; + } + return false; +} + +/* returns true if $ipaddr is a valid dotted IPv4 address or a IPv6 + false - not valid + true (numeric 4 or 6) - if valid, gives type of address */ +function is_ipaddr($ipaddr) { + if (is_ipaddrv4($ipaddr)) { + return 4; + } + if (is_ipaddrv6($ipaddr)) { + return 6; + } + return false; +} + +/* returns true if $ipaddr is a valid IPv6 address */ +function is_ipaddrv6($ipaddr) { + if (!is_string($ipaddr) || empty($ipaddr)) { + return false; + } + if (strstr($ipaddr, "%") && is_linklocal($ipaddr)) { + $tmpip = explode("%", $ipaddr); + $ipaddr = $tmpip[0]; + } + return Net_IPv6::checkIPv6($ipaddr); +} + +/* returns true if $ipaddr is a valid dotted IPv4 address */ +function is_ipaddrv4($ipaddr) { + if (!is_string($ipaddr) || empty($ipaddr) || ip2long($ipaddr) === FALSE) { + return false; + } + return true; +} + +/* returns true if $ipaddr is a valid IPv6 linklocal address */ +function is_linklocal($ipaddr) { + return (strtolower(substr($ipaddr, 0, 5)) == "fe80:"); +} + +/* returns scope of a linklocal address */ +function get_ll_scope($addr) { + if (!is_linklocal($addr) || !strstr($addr, "%")) { + return ""; + } + list ($ll, $scope) = explode("%", $addr); + return $scope; +} + +/* returns true if $ipaddr is a valid literal IPv6 address */ +function is_literalipaddrv6($ipaddr) { + if (preg_match("/\[([0-9a-f:]+)\]/i", $ipaddr, $match)) { + $ipaddr = $match[1]; + } else { + return false; + } + + return is_ipaddrv6($ipaddr); +} + +/* returns true if $iport is a valid IPv4/IPv6 address + port + false - not valid + true (numeric 4 or 6) - if valid, gives type of address */ +function is_ipaddrwithport($ipport) { + $c = strrpos($ipport, ":"); + if ($c === false) { + return false; // can't split at final colon if no colon exists + } + + if (!is_port(substr($ipport, $c + 1))) { + return false; // no valid port after last colon + } + + $ip = substr($ipport, 0, $c); // else is text before last colon a valid IP + if (is_literalipaddrv6($ip)) { + return 6; + } elseif (is_ipaddrv4($ip)) { + return 4; + } else { + return false; + } +} + +function is_hostnamewithport($hostport) { + $parts = explode(":", $hostport); + $port = array_pop($parts); + if (count($parts) == 1) { + return is_hostname($parts[0]) && is_port($port); + } else { + return false; + } +} + +/* returns true if $ipaddr is a valid dotted IPv4 address or an alias thereof */ +function is_ipaddroralias($ipaddr) { + global $config; + + if (is_alias($ipaddr)) { + if (is_array($config['aliases']['alias'])) { + foreach ($config['aliases']['alias'] as $alias) { + if ($alias['name'] == $ipaddr && !preg_match("/port/i", $alias['type'])) { + return true; + } + } + } + return false; + } else { + return is_ipaddr($ipaddr); + } + +} + +/* returns true if $subnet is a valid IPv4 or IPv6 subnet in CIDR format + false - if not a valid subnet + true (numeric 4 or 6) - if valid, gives type of subnet */ +function is_subnet($subnet) { + if (is_string($subnet) && preg_match('/^(?:([0-9.]{7,15})|([0-9a-f:]{2,39}))\/(\d{1,3})$/i', $subnet, $parts)) { + if (is_ipaddrv4($parts[1]) && $parts[3] <= 32) { + return 4; + } + if (is_ipaddrv6($parts[2]) && $parts[3] <= 128) { + return 6; + } + } + return false; +} + +/* same as is_subnet() but accepts IPv4 only */ +function is_subnetv4($subnet) { + return (is_subnet($subnet) == 4); +} + +/* same as is_subnet() but accepts IPv6 only */ +function is_subnetv6($subnet) { + return (is_subnet($subnet) == 6); +} + +/* returns true if $subnet is a valid subnet in CIDR format or an alias thereof */ +function is_subnetoralias($subnet) { + global $aliastable; + + if (isset($aliastable[$subnet]) && is_subnet($aliastable[$subnet])) { + return true; + } else { + return is_subnet($subnet); + } +} + +function subnet_size($subnet) { + if (is_subnetv4($subnet)) { + list ($ip, $bits) = explode("/", $subnet); + return round(exp(log(2) * (32 - $bits))); + } + else if (is_subnetv6($subnet)) { + list ($ip, $bits) = explode("/", $subnet); + return round(exp(log(2) * (128 - $bits))); + } + else { + return 0; + } +} + + +function subnet_expand($subnet) { + if (is_subnetv4($subnet)) { + return subnetv4_expand($subnet); + } else if (is_subnetv6($subnet)) { + return subnetv6_expand($subnet); + } else { + return $subnet; + } +} + +function subnetv4_expand($subnet) { + $result = array(); + list ($ip, $bits) = explode("/", $subnet); + $net = ip2long($ip); + $mask = (0xffffffff << (32 - $bits)); + $net &= $mask; + $size = round(exp(log(2) * (32 - $bits))); + for ($i = 0; $i < $size; $i += 1) { + $result[] = long2ip($net | $i); + } + return $result; +} + +/* find out whether two subnets overlap */ +function check_subnets_overlap($subnet1, $bits1, $subnet2, $bits2) { + + if (!is_numeric($bits1)) { + $bits1 = 32; + } + if (!is_numeric($bits2)) { + $bits2 = 32; + } + + if ($bits1 < $bits2) { + $relbits = $bits1; + } else { + $relbits = $bits2; + } + + $sn1 = gen_subnet_mask_long($relbits) & ip2long($subnet1); + $sn2 = gen_subnet_mask_long($relbits) & ip2long($subnet2); + + return ($sn1 == $sn2); +} + +/* find out whether two IPv6 subnets overlap */ +function check_subnetsv6_overlap($subnet1, $bits1, $subnet2, $bits2) { + $sub1_min = gen_subnetv6($subnet1, $bits1); + $sub1_max = gen_subnetv6_max($subnet1, $bits1); + $sub2_min = gen_subnetv6($subnet2, $bits2); + $sub2_max = gen_subnetv6_max($subnet2, $bits2); + + return (is_inrange_v6($sub1_min, $sub2_min, $sub2_max) || is_inrange_v6($sub1_max, $sub2_min, $sub2_max) || is_inrange_v6($sub2_min, $sub1_min, $sub1_max)); +} + +/* return true if $addr is in $subnet, false if not */ +function ip_in_subnet($addr, $subnet) { + if (is_ipaddrv6($addr) && is_subnetv6($subnet)) { + return (Net_IPv6::isInNetmask($addr, $subnet)); + } else if (is_ipaddrv4($addr) && is_subnetv4($subnet)) { + list($ip, $mask) = explode('/', $subnet); + $mask = (0xffffffff << (32 - $mask)) & 0xffffffff; + return ((ip2long($addr) & $mask) == (ip2long($ip) & $mask)); + } + return false; +} + +/* returns true if $hostname is just a valid hostname (top part without any of the domain part) */ +function is_unqualified_hostname($hostname) { + if (!is_string($hostname)) { + return false; + } + + if (preg_match('/^(?:[a-z0-9_]|[a-z0-9_][a-z0-9_\-]*[a-z0-9_])$/i', $hostname)) { + return true; + } else { + return false; + } +} + +/* returns true if $hostname is a valid hostname, with or without being a fully-qualified domain name. */ +function is_hostname($hostname) { + if (!is_string($hostname)) { + return false; + } + + if (is_domain($hostname)) { + if ((substr_count($hostname, ".") == 1) && ($hostname[strlen($hostname)-1] == ".")) { + /* Only a single dot at the end like "test." - hosts cannot be directly in the root domain. */ + return false; + } else { + return true; + } + } else { + return false; + } +} + +/* returns true if $domain is a valid domain name */ +function is_domain($domain) { + if (!is_string($domain)) { + return false; + } + + if (preg_match('/^(?:(?:[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', $domain)) { + return true; + } else { + return false; + } +} + +/* returns true if $macaddr is a valid MAC address */ +function is_macaddr($macaddr, $partial=false) { + $repeat = ($partial) ? '1,5' : '5'; + return preg_match('/^[0-9A-F]{2}(?:[:][0-9A-F]{2}){'.$repeat.'}$/i', $macaddr) == 1 ? true : false; +} + +/* returns true if $name is a valid name for an alias + returns NULL if a reserved word is used + returns FALSE for bad chars in the name - this allows calling code to determine what the problem was. + aliases cannot be: + bad chars: anything except a-z 0-9 and underscore + bad names: empty string, pure numeric, pure underscore + reserved words: pre-defined service/protocol/port names which should not be ambiguous, and the words "port" and "pass" */ + +function is_validaliasname($name) { + /* Array of reserved words */ + $reserved = array("port", "pass"); + + if (!is_string($name) || strlen($name) >= 32 || preg_match('/(^_*$|^\d*$|[^a-z0-9_])/i', $name)) { + return false; + } + if (in_array($name, $reserved, true) || getservbyname($name, "tcp") || getservbyname($name, "udp") || getprotobyname($name)) { + return; /* return NULL */ + } + return true; +} + +/* returns true if $port is a valid TCP/UDP port */ +function is_port($port) { + if (ctype_digit($port) && ((intval($port) >= 1) && (intval($port) <= 65535))) { + return true; + } + if (getservbyname($port, "tcp") || getservbyname($port, "udp")) { + return true; + } + return false; +} + +/* returns true if $portrange is a valid TCP/UDP portrange (":") */ +function is_portrange($portrange) { + $ports = explode(":", $portrange); + + return (count($ports) == 2 && is_port($ports[0]) && is_port($ports[1])); +} + +/* returns true if $port is a valid port number or an alias thereof */ +function is_portoralias($port) { + global $config; + + if (is_alias($port)) { + if (is_array($config['aliases']['alias'])) { + foreach ($config['aliases']['alias'] as $alias) { + if ($alias['name'] == $port && preg_match("/port/i", $alias['type'])) { + return true; + } + } + } + return false; + } else { + return is_port($port); + } +} + +/* create ranges of sequential port numbers (200:215) and remove duplicates */ +function group_ports($ports) { + if (!is_array($ports) || empty($ports)) { + return; + } + + $uniq = array(); + foreach ($ports as $port) { + if (is_portrange($port)) { + list($begin, $end) = explode(":", $port); + if ($begin > $end) { + $aux = $begin; + $begin = $end; + $end = $aux; + } + for ($i = $begin; $i <= $end; $i++) { + if (!in_array($i, $uniq)) { + $uniq[] = $i; + } + } + } else if (is_port($port)) { + if (!in_array($port, $uniq)) { + $uniq[] = $port; + } + } + } + sort($uniq, SORT_NUMERIC); + + $result = array(); + foreach ($uniq as $idx => $port) { + if ($idx == 0) { + $result[] = $port; + continue; + } + + $last = end($result); + if (is_portrange($last)) { + list($begin, $end) = explode(":", $last); + } else { + $begin = $end = $last; + } + + if ($port == ($end+1)) { + $end++; + $result[count($result)-1] = "{$begin}:{$end}"; + } else { + $result[] = $port; + } + } + + return $result; +} + +/* returns true if $val is a valid shaper bandwidth value */ +function is_valid_shaperbw($val) { + return (preg_match("/^(\d+(?:\.\d+)?)([MKG]?b|%)$/", $val)); +} + +/* returns true if $test is in the range between $start and $end */ +function is_inrange_v4($test, $start, $end) { + if ((ip2ulong($test) <= ip2ulong($end)) && (ip2ulong($test) >= ip2ulong($start))) { + return true; + } else { + return false; + } +} + +/* returns true if $test is in the range between $start and $end */ +function is_inrange_v6($test, $start, $end) { + if ((inet_pton($test) <= inet_pton($end)) && (inet_pton($test) >= inet_pton($start))) { + return true; + } else { + return false; + } +} + +/* returns true if $test is in the range between $start and $end */ +function is_inrange($test, $start, $end) { + return is_ipaddrv6($test) ? is_inrange_v6($test, $start, $end) : is_inrange_v4($test, $start, $end); +} + +/* XXX: return the configured carp interface list */ +function get_configured_carp_interface_list($carpinterface = '', $family = 'inet', $what = 'ip') { + global $config; + + $iflist = array(); + + if (is_array($config['virtualip']['vip'])) { + $viparr = &$config['virtualip']['vip']; + foreach ($viparr as $vip) { + switch ($vip['mode']) { + case "carp": + if (!empty($carpinterface)) { + if ($carpinterface == "_vip{$vip['uniqid']}") { + switch ($what) { + case 'subnet': + if ($family == 'inet' && is_ipaddrv4($vip['subnet'])) { + return $vip['subnet_bits']; + } else if ($family == 'inet6' && is_ipaddrv6($vip['subnet'])) { + return $vip['subnet_bits']; + } + break; + case 'iface': + if ($family == 'inet' && is_ipaddrv4($vip['subnet'])) { + return $vip['interface']; + } else if ($family == 'inet6' && is_ipaddrv6($vip['subnet'])) { + return $vip['interface']; + } + break; + case 'vip': + if ($family == 'inet' && is_ipaddrv4($vip['subnet'])) { + return $vip; + } else if ($family == 'inet6' && is_ipaddrv6($vip['subnet'])) { + return $vip; + } + break; + case 'ip': + default: + if ($family == 'inet' && is_ipaddrv4($vip['subnet'])) { + return $vip['subnet']; + } else if ($family == 'inet6' && is_ipaddrv6($vip['subnet'])) { + return $vip['subnet']; + } + break; + } + } + } else { + $iflist["_vip{$vip['uniqid']}"] = $vip['subnet']; + } + break; + } + } + } + + return $iflist; +} + +/* return the configured IP aliases list */ +function get_configured_ip_aliases_list($returnfullentry = false) { + global $config; + + $alias_list = array(); + + if (is_array($config['virtualip']['vip'])) { + $viparr = &$config['virtualip']['vip']; + foreach ($viparr as $vip) { + if ($vip['mode'] == "ipalias") { + if ($returnfullentry) { + $alias_list[$vip['subnet']] = $vip; + } else { + $alias_list[$vip['subnet']] = $vip['interface']; + } + } + } + } + + return $alias_list; +} + +/* return all configured aliases list (IP, carp, proxyarp and other) */ +function get_configured_vips_list() { + global $config; + + $alias_list = array(); + + if (is_array($config['virtualip']['vip'])) { + $viparr = &$config['virtualip']['vip']; + foreach ($viparr as $vip) { + if ($vip['mode'] == "carp") { + $alias_list[] = array("ipaddr" => $vip['subnet'], "if" => "{$vip['interface']}_vip{$vip['vhid']}"); + } else { + $alias_list[] = array("ipaddr" => $vip['subnet'], "if" => $vip['interface']); + } + } + } + + return $alias_list; +} + +/* comparison function for sorting by the order in which interfaces are normally created */ +function compare_interface_friendly_names($a, $b) { + if ($a == $b) { + return 0; + } else if ($a == 'wan') { + return -1; + } else if ($b == 'wan') { + return 1; + } else if ($a == 'lan') { + return -1; + } else if ($b == 'lan') { + return 1; + } + + return strnatcmp($a, $b); +} + +/* return the configured interfaces list. */ +function get_configured_interface_list($only_opt = false, $withdisabled = false) { + global $config; + + $iflist = array(); + + /* if list */ + foreach ($config['interfaces'] as $if => $ifdetail) { + if ($only_opt && ($if == "wan" || $if == "lan")) { + continue; + } + if (isset($ifdetail['enable']) || $withdisabled == true) { + $iflist[$if] = $if; + } + } + + return $iflist; +} + +/* return the configured interfaces list. */ +function get_configured_interface_list_by_realif($only_opt = false, $withdisabled = false) { + global $config; + + $iflist = array(); + + /* if list */ + foreach ($config['interfaces'] as $if => $ifdetail) { + if ($only_opt && ($if == "wan" || $if == "lan")) { + continue; + } + if (isset($ifdetail['enable']) || $withdisabled == true) { + $tmpif = get_real_interface($if); + if (!empty($tmpif)) { + $iflist[$tmpif] = $if; + } + } + } + + return $iflist; +} + +/* return the configured interfaces list with their description. */ +function get_configured_interface_with_descr($only_opt = false, $withdisabled = false) { + global $config; + + $iflist = array(); + + /* if list */ + foreach ($config['interfaces'] as $if => $ifdetail) { + if ($only_opt && ($if == "wan" || $if == "lan")) { + continue; + } + if (isset($ifdetail['enable']) || $withdisabled == true) { + if (empty($ifdetail['descr'])) { + $iflist[$if] = strtoupper($if); + } else { + $iflist[$if] = strtoupper($ifdetail['descr']); + } + } + } + + return $iflist; +} + +/* + * get_configured_ip_addresses() - Return a list of all configured + * interfaces IP Addresses + * + */ +function get_configured_ip_addresses() { + global $config; + + if (!function_exists('get_interface_ip')) { + require_once("interfaces.inc"); + } + $ip_array = array(); + $interfaces = get_configured_interface_list(); + if (is_array($interfaces)) { + foreach ($interfaces as $int) { + $ipaddr = get_interface_ip($int); + $ip_array[$int] = $ipaddr; + } + } + $interfaces = get_configured_carp_interface_list(); + if (is_array($interfaces)) { + foreach ($interfaces as $int => $ipaddr) { + $ip_array[$int] = $ipaddr; + } + } + + /* pppoe server */ + if (is_array($config['pppoes']) && is_array($config['pppoes']['pppoe'])) { + foreach ($config['pppoes']['pppoe'] as $pppoe) { + if ($pppoe['mode'] == "server") { + if (is_ipaddr($pppoe['localip'])) { + $int = "pppoes". $pppoe['pppoeid']; + $ip_array[$int] = $pppoe['localip']; + } + } + } + } + + return $ip_array; +} + +/* + * get_configured_ipv6_addresses() - Return a list of all configured + * interfaces IPv6 Addresses + * + */ +function get_configured_ipv6_addresses() { + require_once("interfaces.inc"); + $ipv6_array = array(); + $interfaces = get_configured_interface_list(); + if (is_array($interfaces)) { + foreach ($interfaces as $int) { + $ipaddrv6 = get_interface_ipv6($int); + $ipv6_array[$int] = $ipaddrv6; + } + } + $interfaces = get_configured_carp_interface_list(); + if (is_array($interfaces)) { + foreach ($interfaces as $int => $ipaddrv6) { + $ipv6_array[$int] = $ipaddrv6; + } + } + return $ipv6_array; +} + +/* + * get_interface_list() - Return a list of all physical interfaces + * along with MAC and status. + * + * $mode = "active" - use ifconfig -lu + * "media" - use ifconfig to check physical connection + * status (much slower) + */ +function get_interface_list($mode = "active", $keyby = "physical", $vfaces = "") { + global $config; + $upints = array(); + /* get a list of virtual interface types */ + if (!$vfaces) { + $vfaces = array( + 'bridge', + 'ppp', + 'pppoe', + 'pptp', + 'l2tp', + 'sl', + 'gif', + 'gre', + 'faith', + 'lo', + 'ng', + '_vlan', + '_wlan', + 'pflog', + 'plip', + 'pfsync', + 'enc', + 'tun', + 'carp', + 'lagg', + 'vip', + 'ipfw' + ); + } + switch ($mode) { + case "active": + $upints = pfSense_interface_listget(IFF_UP); + break; + case "media": + $intlist = pfSense_interface_listget(); + $ifconfig = ""; + exec("/sbin/ifconfig -a", $ifconfig); + $regexp = '/(' . implode('|', $intlist) . '):\s/'; + $ifstatus = preg_grep('/status:/', $ifconfig); + foreach ($ifstatus as $status) { + $int = array_shift($intlist); + if (stristr($status, "active")) { + $upints[] = $int; + } + } + break; + default: + $upints = pfSense_interface_listget(); + break; + } + /* build interface list with netstat */ + $linkinfo = ""; + exec("/usr/bin/netstat -inW -f link | awk '{ print $1, $4 }'", $linkinfo); + array_shift($linkinfo); + /* build ip address list with netstat */ + $ipinfo = ""; + exec("/usr/bin/netstat -inW -f inet | awk '{ print $1, $4 }'", $ipinfo); + array_shift($ipinfo); + foreach ($linkinfo as $link) { + $friendly = ""; + $alink = explode(" ", $link); + $ifname = rtrim(trim($alink[0]), '*'); + /* trim out all numbers before checking for vfaces */ + if (!in_array(array_shift(preg_split('/\d/', $ifname)), $vfaces) && + !stristr($ifname, "_vlan") && !stristr($ifname, "_wlan")) { + $toput = array( + "mac" => trim($alink[1]), + "up" => in_array($ifname, $upints) + ); + foreach ($ipinfo as $ip) { + $aip = explode(" ", $ip); + if ($aip[0] == $ifname) { + $toput['ipaddr'] = $aip[1]; + } + } + if (is_array($config['interfaces'])) { + foreach ($config['interfaces'] as $name => $int) { + if ($int['if'] == $ifname) { + $friendly = $name; + } + } + } + switch ($keyby) { + case "physical": + if ($friendly != "") { + $toput['friendly'] = $friendly; + } + $dmesg_arr = array(); + exec("/sbin/dmesg |grep $ifname | head -n1", $dmesg_arr); + preg_match_all("/<(.*?)>/i", $dmesg_arr[0], $dmesg); + $toput['dmesg'] = $dmesg[1][0]; + $iflist[$ifname] = $toput; + break; + case "ppp": + + case "friendly": + if ($friendly != "") { + $toput['if'] = $ifname; + $iflist[$friendly] = $toput; + } + break; + } + } + } + return $iflist; +} + +/****f* util/log_error +* NAME +* log_error - Sends a string to syslog. +* INPUTS +* $error - string containing the syslog message. +* RESULT +* null +******/ +function log_error($error) { + global $g; + $page = $_SERVER['SCRIPT_NAME']; + if (empty($page)) { + $files = get_included_files(); + $page = basename($files[0]); + } + syslog(LOG_ERR, "$page: $error"); + if ($g['debug']) { + syslog(LOG_WARNING, var_dump(debug_backtrace())); + } + return; +} + +/****f* util/log_auth +* NAME +* log_auth - Sends a string to syslog as LOG_AUTH facility +* INPUTS +* $error - string containing the syslog message. +* RESULT +* null +******/ +function log_auth($error) { + global $g; + $page = $_SERVER['SCRIPT_NAME']; + syslog(LOG_AUTH, "$page: $error"); + if ($g['debug']) { + syslog(LOG_WARNING, var_dump(debug_backtrace())); + } + return; +} + +/****f* util/exec_command + * NAME + * exec_command - Execute a command and return a string of the result. + * INPUTS + * $command - String of the command to be executed. + * RESULT + * String containing the command's result. + * NOTES + * This function returns the command's stdout and stderr. + ******/ +function exec_command($command) { + $output = array(); + exec($command . ' 2>&1', $output); + return(implode("\n", $output)); +} + +/* wrapper for exec() */ +function mwexec($command, $mute = false, $clearsigmask = false) { + global $g; + + if ($g['debug']) { + if (!$_SERVER['REMOTE_ADDR']) { + echo "mwexec(): $command\n"; + } + } + $oarr = array(); + $retval = 0; + + if ($clearsigmask) { + $oldset = array(); + pcntl_sigprocmask(SIG_SETMASK, array(), $oldset); + } + $garbage = exec("$command 2>&1", $oarr, $retval); + if ($clearsigmask) { + pcntl_sigprocmask(SIG_SETMASK, $oldset); + } + + if (isset($config['system']['developerspew'])) { + $mute = false; + } + if (($retval <> 0) && ($mute === false)) { + $output = implode(" ", $oarr); + log_error(sprintf(gettext("The command '%1\$s' returned exit code '%2\$d', the output was '%3\$s' "), $command, $retval, $output)); + unset($output); + } + unset($oarr); + return $retval; +} + +/* wrapper for exec() in background */ +function mwexec_bg($command, $clearsigmask = false) { + global $g; + + if ($g['debug']) { + if (!$_SERVER['REMOTE_ADDR']) { + echo "mwexec(): $command\n"; + } + } + + if ($clearsigmask) { + $oldset = array(); + pcntl_sigprocmask(SIG_SETMASK, array(), $oldset); + } + $_gb = exec("/usr/bin/nohup $command > /dev/null 2>&1 &"); + if ($clearsigmask) { + pcntl_sigprocmask(SIG_SETMASK, $oldset); + } + unset($_gb); +} + +/* unlink a file, if it exists */ +function unlink_if_exists($fn) { + $to_do = glob($fn); + if (is_array($to_do)) { + foreach ($to_do as $filename) { + @unlink($filename); + } + } else { + @unlink($fn); + } +} +/* make a global alias table (for faster lookups) */ +function alias_make_table($config) { + global $aliastable; + + $aliastable = array(); + + if (is_array($config['aliases']['alias'])) { + foreach ($config['aliases']['alias'] as $alias) { + if ($alias['name']) { + $aliastable[$alias['name']] = $alias['address']; + } + } + } +} + +/* check if an alias exists */ +function is_alias($name) { + global $aliastable; + + return isset($aliastable[$name]); +} + +function alias_get_type($name) { + global $config; + + if (is_array($config['aliases']['alias'])) { + foreach ($config['aliases']['alias'] as $alias) { + if ($name == $alias['name']) { + return $alias['type']; + } + } + } + + return ""; +} + +/* expand a host or network alias, if necessary */ +function alias_expand($name) { + global $aliastable; + + if (isset($aliastable[$name])) { + // alias names cannot be strictly numeric. redmine #4289 + if (is_numericint($name)) { + return null; + } + return "\${$name}"; + } else if (is_ipaddr($name) || is_subnet($name) || is_port($name) || is_portrange($name)) { + return "{$name}"; + } else { + return null; + } +} + +function alias_expand_urltable($name) { + global $config; + $urltable_prefix = "/var/db/aliastables/"; + $urltable_filename = $urltable_prefix . $name . ".txt"; + + if (is_array($config['aliases']['alias'])) { + foreach ($config['aliases']['alias'] as $alias) { + if (preg_match("/urltable/i", $alias['type']) && ($alias['name'] == $name)) { + if (is_URL($alias["url"]) && file_exists($urltable_filename) && filesize($urltable_filename)) { + return $urltable_filename; + } else { + send_event("service sync alias {$name}"); + break; + } + } + } + } + return null; +} + +/* verify (and remove) the digital signature on a file - returns 0 if OK */ +function verify_digital_signature($fname) { + global $g; + + if (!file_exists("/usr/local/sbin/gzsig")) { + return 4; + } + + return mwexec("/usr/local/sbin/gzsig verify {$g['etc_path']}/pubkey.pem < " . escapeshellarg($fname)); +} + +/* obtain MAC address given an IP address by looking at the ARP table */ +function arp_get_mac_by_ip($ip) { + mwexec("/sbin/ping -c 1 -t 1 " . escapeshellarg($ip), true); + $arpoutput = ""; + exec("/usr/sbin/arp -n " . escapeshellarg($ip), $arpoutput); + + if ($arpoutput[0]) { + $arpi = explode(" ", $arpoutput[0]); + $macaddr = $arpi[3]; + if (is_macaddr($macaddr)) { + return $macaddr; + } else { + return false; + } + } + + return false; +} + +/* return a fieldname that is safe for xml usage */ +function xml_safe_fieldname($fieldname) { + $replace = array('/', '-', ' ', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', + '_', '+', '=', '{', '}', '[', ']', '|', '/', '<', '>', '?', + ':', ',', '.', '\'', '\\' + ); + return strtolower(str_replace($replace, "", $fieldname)); +} + +function mac_format($clientmac) { + global $config, $cpzone; + + $mac = explode(":", $clientmac); + $mac_format = $cpzone ? $config['captiveportal'][$cpzone]['radmac_format'] : false; + + switch ($mac_format) { + case 'singledash': + return "$mac[0]$mac[1]$mac[2]-$mac[3]$mac[4]$mac[5]"; + + case 'ietf': + return "$mac[0]-$mac[1]-$mac[2]-$mac[3]-$mac[4]-$mac[5]"; + + case 'cisco': + return "$mac[0]$mac[1].$mac[2]$mac[3].$mac[4]$mac[5]"; + + case 'unformatted': + return "$mac[0]$mac[1]$mac[2]$mac[3]$mac[4]$mac[5]"; + + default: + return $clientmac; + } +} + +function resolve_retry($hostname, $retries = 5) { + + if (is_ipaddr($hostname)) { + return $hostname; + } + + for ($i = 0; $i < $retries; $i++) { + // FIXME: gethostbyname does not work for AAAA hostnames, boo, hiss + $ip = gethostbyname($hostname); + + if ($ip && $ip != $hostname) { + /* success */ + return $ip; + } + + sleep(1); + } + + return false; +} + +function format_bytes($bytes) { + if ($bytes >= 1073741824) { + return sprintf("%.2f GB", $bytes/1073741824); + } else if ($bytes >= 1048576) { + return sprintf("%.2f MB", $bytes/1048576); + } else if ($bytes >= 1024) { + return sprintf("%.0f KB", $bytes/1024); + } else { + return sprintf("%d bytes", $bytes); + } +} + +function update_filter_reload_status($text) { + global $g; + + file_put_contents("{$g['varrun_path']}/filter_reload_status", $text); +} + +/****** util/return_dir_as_array + * NAME + * return_dir_as_array - Return a directory's contents as an array. + * INPUTS + * $dir - string containing the path to the desired directory. + * $filter_regex - string containing a regular expression to filter file names. Default empty. + * RESULT + * $dir_array - array containing the directory's contents. This array will be empty if the path specified is invalid. + ******/ +function return_dir_as_array($dir, $filter_regex = '') { + $dir_array = array(); + if (is_dir($dir)) { + if ($dh = opendir($dir)) { + while (($file = readdir($dh)) !== false) { + if (($file == ".") || ($file == "..")) { + continue; + } + + if (empty($filter_regex) || preg_match($filter_regex, $file)) { + array_push($dir_array, $file); + } + } + closedir($dh); + } + } + return $dir_array; +} + +function run_plugins($directory) { + global $config, $g; + + /* process packager manager custom rules */ + $files = return_dir_as_array($directory); + if (is_array($files)) { + foreach ($files as $file) { + if (stristr($file, ".sh") == true) { + mwexec($directory . $file . " start"); + } else if (!is_dir($directory . "/" . $file) && stristr($file, ".inc")) { + require_once($directory . "/" . $file); + } + } + } +} + +/* + * safe_mkdir($path, $mode = 0755) + * create directory if it doesn't already exist and isn't a file! + */ +function safe_mkdir($path, $mode = 0755) { + global $g; + + if (!is_file($path) && !is_dir($path)) { + return @mkdir($path, $mode, true); + } else { + return false; + } +} + +/* + * get_sysctl($names) + * Get values of sysctl OID's listed in $names (accepts an array or a single + * name) and return an array of key/value pairs set for those that exist + */ +function get_sysctl($names) { + if (empty($names)) { + return array(); + } + + if (is_array($names)) { + $name_list = array(); + foreach ($names as $name) { + $name_list[] = escapeshellarg($name); + } + } else { + $name_list = array(escapeshellarg($names)); + } + + exec("/sbin/sysctl -i " . implode(" ", $name_list), $output); + $values = array(); + foreach ($output as $line) { + $line = explode(": ", $line, 2); + if (count($line) == 2) { + $values[$line[0]] = $line[1]; + } + } + + return $values; +} + +/* + * get_single_sysctl($name) + * Wrapper for get_sysctl() to simplify read of a single sysctl value + * return the value for sysctl $name or empty string if it doesn't exist + */ +function get_single_sysctl($name) { + if (empty($name)) { + return ""; + } + + $value = get_sysctl($name); + if (empty($value) || !isset($value[$name])) { + return ""; + } + + return $value[$name]; +} + +/* + * set_sysctl($value_list) + * Set sysctl OID's listed as key/value pairs and return + * an array with keys set for those that succeeded + */ +function set_sysctl($values) { + if (empty($values)) { + return array(); + } + + $value_list = array(); + foreach ($values as $key => $value) { + $value_list[] = escapeshellarg($key) . "=" . escapeshellarg($value); + } + + exec("/sbin/sysctl -i " . implode(" ", $value_list), $output, $success); + + /* Retry individually if failed (one or more read-only) */ + if ($success <> 0 && count($value_list) > 1) { + foreach ($value_list as $value) { + exec("/sbin/sysctl -i " . $value, $output); + } + } + + $ret = array(); + foreach ($output as $line) { + $line = explode(": ", $line, 2); + if (count($line) == 2) { + $ret[$line[0]] = true; + } + } + + return $ret; +} + +/* + * set_single_sysctl($name, $value) + * Wrapper to set_sysctl() to make it simple to set only one sysctl + * returns boolean meaning if it succeeded + */ +function set_single_sysctl($name, $value) { + if (empty($name)) { + return false; + } + + $result = set_sysctl(array($name => $value)); + + if (!isset($result[$name]) || $result[$name] != $value) { + return false; + } + + return true; +} + +/* + * get_memory() + * returns an array listing the amount of + * memory installed in the hardware + * [0] net memory available for the OS (FreeBSD) after some is taken by BIOS, video or whatever - e.g. 235 MBytes + * [1] real (actual) memory of the system, should be the size of the RAM card/s - e.g. 256 MBytes + */ +function get_memory() { + $physmem = get_single_sysctl("hw.physmem"); + $realmem = get_single_sysctl("hw.realmem"); + /* convert from bytes to megabytes */ + return array(($physmem/1048576), ($realmem/1048576)); +} + +function mute_kernel_msgs() { + global $g, $config; + // Do not mute serial console. The kernel gets very very cranky + // and will start dishing you cannot control tty errors. + if ($g['platform'] == 'nanobsd') { + return; + } + if ($config['system']['enableserial']) { + return; + } + exec("/sbin/conscontrol mute on"); +} + +function unmute_kernel_msgs() { + global $g; + // Do not mute serial console. The kernel gets very very cranky + // and will start dishing you cannot control tty errors. + if ($g['platform'] == 'nanobsd') { + return; + } + exec("/sbin/conscontrol mute off"); +} + +function start_devd() { + /* Use the undocumented -q options of devd to quiet its log spamming */ + $_gb = exec("/sbin/devd -q"); + sleep(1); + unset($_gb); +} + +function is_interface_vlan_mismatch() { + global $config, $g; + + if (is_array($config['vlans']['vlan'])) { + foreach ($config['vlans']['vlan'] as $vlan) { + if (does_interface_exist($vlan['if']) == false) { + return true; + } + } + } + + return false; +} + +function is_interface_mismatch() { + global $config, $g; + + $do_assign = false; + $i = 0; + $missing_interfaces = array(); + if (is_array($config['interfaces'])) { + foreach ($config['interfaces'] as $ifname => $ifcfg) { + if (preg_match("/^enc|^cua|^tun|^tap|^l2tp|^pptp|^ppp|^ovpn|^gif|^gre|^lagg|^bridge|vlan|_wlan/i", $ifcfg['if'])) { + // Do not check these interfaces. + $i++; + continue; + } else if (does_interface_exist($ifcfg['if']) == false) { + $missing_interfaces[] = $ifcfg['if']; + $do_assign = true; + } else { + $i++; + } + } + } + + if (file_exists("{$g['tmp_path']}/assign_complete")) { + $do_assign = false; + } + + if (!empty($missing_interfaces) && $do_assign) { + file_put_contents("{$g['tmp_path']}/missing_interfaces", implode(' ', $missing_interfaces)); + } else { + @unlink("{$g['tmp_path']}/missing_interfaces"); + } + + return $do_assign; +} + +/* sync carp entries to other firewalls */ +function carp_sync_client() { + global $g; + send_event("filter sync"); +} + +/****f* util/isAjax + * NAME + * isAjax - reports if the request is driven from prototype + * INPUTS + * none + * RESULT + * true/false + ******/ +function isAjax() { + return isset ($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest'; +} + +/****f* util/timeout + * NAME + * timeout - console input with timeout countdown. Note: erases 2 char of screen for timer. Leave space. + * INPUTS + * optional, seconds to wait before timeout. Default 9 seconds. + * RESULT + * returns 1 char of user input or null if no input. + ******/ +function timeout($timer = 9) { + while (!isset($key)) { + if ($timer >= 9) { + echo chr(8) . chr(8) . ($timer == 9 ? chr(32) : null) . "{$timer}"; + } else { + echo chr(8). "{$timer}"; + } + `/bin/stty -icanon min 0 time 25`; + $key = trim(`KEY=\`dd count=1 2>/dev/null\`; echo \$KEY`); + `/bin/stty icanon`; + if ($key == '') { + unset($key); + } + $timer--; + if ($timer == 0) { + break; + } + } + return $key; +} + +/****f* util/msort + * NAME + * msort - sort array + * INPUTS + * $array to be sorted, field to sort by, direction of sort + * RESULT + * returns newly sorted array + ******/ +function msort($array, $id = "id", $sort_ascending = true) { + $temp_array = array(); + while (count($array)>0) { + $lowest_id = 0; + $index = 0; + foreach ($array as $item) { + if (isset($item[$id])) { + if ($array[$lowest_id][$id]) { + if (strtolower($item[$id]) < strtolower($array[$lowest_id][$id])) { + $lowest_id = $index; + } + } + } + $index++; + } + $temp_array[] = $array[$lowest_id]; + $array = array_merge(array_slice($array, 0, $lowest_id), array_slice($array, $lowest_id + 1)); + } + if ($sort_ascending) { + return $temp_array; + } else { + return array_reverse($temp_array); + } +} + +/****f* util/is_URL + * NAME + * is_URL + * INPUTS + * string to check + * RESULT + * Returns true if item is a URL + ******/ +function is_URL($url) { + $match = preg_match("'\b(([\w-]+://?|www[.])[^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|/)))'", $url); + if ($match) { + return true; + } + return false; +} + +function is_file_included($file = "") { + $files = get_included_files(); + if (in_array($file, $files)) { + return true; + } + + return false; +} + +/* + * Replace a value on a deep associative array using regex + */ +function array_replace_values_recursive($data, $match, $replace) { + if (empty($data)) { + return $data; + } + + if (is_string($data)) { + $data = preg_replace("/{$match}/", $replace, $data); + } else if (is_array($data)) { + foreach ($data as $k => $v) { + $data[$k] = array_replace_values_recursive($v, $match, $replace); + } + } + + return $data; +} + +/* + This function was borrowed from a comment on PHP.net at the following URL: + http://www.php.net/manual/en/function.array-merge-recursive.php#73843 + */ +function array_merge_recursive_unique($array0, $array1) { + + $arrays = func_get_args(); + $remains = $arrays; + + // We walk through each arrays and put value in the results (without + // considering previous value). + $result = array(); + + // loop available array + foreach ($arrays as $array) { + + // The first remaining array is $array. We are processing it. So + // we remove it from remaining arrays. + array_shift($remains); + + // We don't care non array param, like array_merge since PHP 5.0. + if (is_array($array)) { + // Loop values + foreach ($array as $key => $value) { + if (is_array($value)) { + // we gather all remaining arrays that have such key available + $args = array(); + foreach ($remains as $remain) { + if (array_key_exists($key, $remain)) { + array_push($args, $remain[$key]); + } + } + + if (count($args) > 2) { + // put the recursion + $result[$key] = call_user_func_array(__FUNCTION__, $args); + } else { + foreach ($value as $vkey => $vval) { + $result[$key][$vkey] = $vval; + } + } + } else { + // simply put the value + $result[$key] = $value; + } + } + } + } + return $result; +} + + +/* + * converts a string like "a,b,c,d" + * into an array like array("a" => "b", "c" => "d") + */ +function explode_assoc($delimiter, $string) { + $array = explode($delimiter, $string); + $result = array(); + $numkeys = floor(count($array) / 2); + for ($i = 0; $i < $numkeys; $i += 1) { + $result[$array[$i * 2]] = $array[$i * 2 + 1]; + } + return $result; +} + +function get_staticroutes($returnsubnetsonly = false, $returnhostnames = false) { + global $config, $aliastable; + + /* Bail if there are no routes, but return an array always so callers don't have to check. */ + if (!is_array($config['staticroutes']['route'])) { + return array(); + } + + $allstaticroutes = array(); + $allsubnets = array(); + /* Loop through routes and expand aliases as we find them. */ + foreach ($config['staticroutes']['route'] as $route) { + if (is_alias($route['network'])) { + if (!isset($aliastable[$route['network']])) { + continue; + } + + $subnets = preg_split('/\s+/', $aliastable[$route['network']]); + foreach ($subnets as $net) { + if (!is_subnet($net)) { + if (is_ipaddrv4($net)) { + $net .= "/32"; + } else if (is_ipaddrv6($net)) { + $net .= "/128"; + } else if ($returnhostnames === false || !is_fqdn($net)) { + continue; + } + } + $temproute = $route; + $temproute['network'] = $net; + $allstaticroutes[] = $temproute; + $allsubnets[] = $net; + } + } elseif (is_subnet($route['network'])) { + $allstaticroutes[] = $route; + $allsubnets[] = $route['network']; + } + } + if ($returnsubnetsonly) { + return $allsubnets; + } else { + return $allstaticroutes; + } +} + +/****f* util/get_alias_list + * NAME + * get_alias_list - Provide a list of aliases. + * INPUTS + * $type - Optional, can be a string or array specifying what type(s) of aliases you need. + * RESULT + * Array containing list of aliases. + * If $type is unspecified, all aliases are returned. + * If $type is a string, all aliases of the type specified in $type are returned. + * If $type is an array, all aliases of any type specified in any element of $type are returned. + */ +function get_alias_list($type = null) { + global $config; + $result = array(); + if ($config['aliases']['alias'] <> "" && is_array($config['aliases']['alias'])) { + foreach ($config['aliases']['alias'] as $alias) { + if ($type === null) { + $result[] = $alias['name']; + } else if (is_array($type)) { + if (in_array($alias['type'], $type)) { + $result[] = $alias['name']; + } + } else if ($type === $alias['type']) { + $result[] = $alias['name']; + } + } + } + return $result; +} + +/* returns an array consisting of every element of $haystack that is not equal to $needle. */ +function array_exclude($needle, $haystack) { + $result = array(); + if (is_array($haystack)) { + foreach ($haystack as $thing) { + if ($needle !== $thing) { + $result[] = $thing; + } + } + } + return $result; +} + +function get_current_theme() { + global $config, $g; + /* + * if user has selected a custom template, use it. + * otherwise default to pfsense template + */ + if (($g["disablethemeselection"] === true) && !empty($g["default_theme"]) && (is_dir($g["www_path"].'/themes/'.$g["default_theme"]))) { + $theme = $g["default_theme"]; + } elseif ($config['theme'] <> "" && (is_dir($g["www_path"].'/themes/'.$config['theme']))) { + $theme = $config['theme']; + } else { + $theme = "pfsense"; + } + /* + * If this device is an apple ipod/iphone + * switch the theme to one that works with it. + */ + $lowres_ua = array("iPhone", "iPod", "iPad", "Android", "BlackBerry", "Opera Mini", "Opera Mobi", "PlayBook", "IEMobile"); + foreach ($lowres_ua as $useragent) { + if (strstr($_SERVER['HTTP_USER_AGENT'], $useragent)) { + $theme = (empty($g['theme_lowres']) && (is_dir($g["www_path"].'/themes/'.$g['theme_lowres']))) ? "pfsense" : $g['theme_lowres']; + } + } + return $theme; +} + +/* Define what is preferred, IPv4 or IPv6 */ +function prefer_ipv4_or_ipv6() { + global $config; + + if (isset($config['system']['prefer_ipv4'])) { + mwexec("/etc/rc.d/ip6addrctl prefer_ipv4"); + } else { + mwexec("/etc/rc.d/ip6addrctl prefer_ipv6"); + } +} + +/* Redirect to page passing parameters via POST */ +function post_redirect($page, $params) { + if (!is_array($params)) { + return; + } + + print "
\n"; + foreach ($params as $key => $value) { + print "\n"; + } + print "
\n"; + print "\n"; +} + +/* Locate disks that can be queried for S.M.A.R.T. data. */ +function get_smart_drive_list() { + $disk_list = explode(" ", get_single_sysctl("kern.disks")); + foreach ($disk_list as $id => $disk) { + // We only want certain kinds of disks for S.M.A.R.T. + // 1 is a match, 0 is no match, False is any problem processing the regex + if (preg_match("/^(ad|da|ada).*[0-9]{1,2}$/", $disk) !== 1) { + unset($disk_list[$id]); + } + } + sort($disk_list); + return $disk_list; +} + +?> -- cgit v1.1