1<?php 2/* 3 * LibreNMS - Common Functions 4 * 5 * Original Observium version by: Adam Armstrong, Tom Laermans 6 * Copyright (c) 2009-2012 Adam Armstrong. 7 * 8 * Additions for LibreNMS by: Neil Lathwood, Paul Gear, Tim DuFrane 9 * Copyright (c) 2014-2015 Neil Lathwood <http://www.lathwood.co.uk> 10 * Copyright (c) 2014-2015 Gear Consulting Pty Ltd <http://libertysys.com.au/> 11 * 12 * This program is free software: you can redistribute it and/or modify it 13 * under the terms of the GNU General Public License as published by the 14 * Free Software Foundation, either version 3 of the License, or (at your 15 * option) any later version. Please see LICENSE.txt at the top level of 16 * the source code distribution for details. 17 */ 18 19use LibreNMS\Config; 20use LibreNMS\Enum\Alert; 21use LibreNMS\Exceptions\InvalidIpException; 22use LibreNMS\Util\Debug; 23use LibreNMS\Util\Git; 24use LibreNMS\Util\IP; 25use LibreNMS\Util\Laravel; 26use Symfony\Component\Process\Process; 27 28function generate_priority_status($priority) 29{ 30 $map = [ 31 'emerg' => 2, 32 'alert' => 2, 33 'crit' => 2, 34 'err' => 2, 35 'warning' => 1, 36 'notice' => 0, 37 'info' => 0, 38 'debug' => 3, 39 '' => 0, 40 ]; 41 42 return isset($map[$priority]) ? $map[$priority] : 0; 43} 44 45function graylog_severity_label($severity) 46{ 47 $map = [ 48 '0' => 'label-danger', 49 '1' => 'label-danger', 50 '2' => 'label-danger', 51 '3' => 'label-danger', 52 '4' => 'label-warning', 53 '5' => 'label-info', 54 '6' => 'label-info', 55 '7' => 'label-default', 56 '' => 'label-info', 57 ]; 58 $barColor = isset($map[$severity]) ? $map[$severity] : 'label-info'; 59 60 return '<span class="alert-status ' . $barColor . '" style="margin-right:8px;float:left;"></span>'; 61} 62 63/** 64 * Execute and snmp command, filter debug output unless -v is specified 65 * 66 * @param array $command 67 * @return null|string 68 */ 69function external_exec($command) 70{ 71 $device = DeviceCache::getPrimary(); 72 73 $proc = new Process($command); 74 $proc->setTimeout(Config::get('snmp.exec_timeout', 1200)); 75 76 if (Debug::isEnabled() && ! Debug::isVerbose()) { 77 $patterns = [ 78 '/-c\' \'[\S]+\'/', 79 '/-u\' \'[\S]+\'/', 80 '/-U\' \'[\S]+\'/', 81 '/-A\' \'[\S]+\'/', 82 '/-X\' \'[\S]+\'/', 83 '/-P\' \'[\S]+\'/', 84 '/-H\' \'[\S]+\'/', 85 '/(udp|udp6|tcp|tcp6):([^:]+):([\d]+)/', 86 ]; 87 $replacements = [ 88 '-c\' \'COMMUNITY\'', 89 '-u\' \'USER\'', 90 '-U\' \'USER\'', 91 '-A\' \'PASSWORD\'', 92 '-X\' \'PASSWORD\'', 93 '-P\' \'PASSWORD\'', 94 '-H\' \'HOSTNAME\'', 95 '\1:HOSTNAME:\3', 96 ]; 97 98 $debug_command = preg_replace($patterns, $replacements, $proc->getCommandLine()); 99 c_echo('SNMP[%c' . $debug_command . "%n]\n"); 100 } elseif (Debug::isVerbose()) { 101 c_echo('SNMP[%c' . $proc->getCommandLine() . "%n]\n"); 102 } 103 104 $proc->run(); 105 $output = $proc->getOutput(); 106 107 if ($proc->getExitCode()) { 108 if (Str::startsWith($proc->getErrorOutput(), 'Invalid authentication protocol specified')) { 109 Log::event('Unsupported SNMP authentication algorithm - ' . $proc->getExitCode(), optional($device)->device_id, 'poller', Alert::ERROR); 110 } elseif (Str::startsWith($proc->getErrorOutput(), 'Invalid privacy protocol specified')) { 111 Log::event('Unsupported SNMP privacy algorithm - ' . $proc->getExitCode(), optional($device)->device_id, 'poller', Alert::ERROR); 112 } 113 d_echo('Exitcode: ' . $proc->getExitCode()); 114 d_echo($proc->getErrorOutput()); 115 } 116 117 if (Debug::isEnabled() && ! Debug::isVerbose()) { 118 $ip_regex = '/(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)/'; 119 $debug_output = preg_replace($ip_regex, '*', $output); 120 d_echo($debug_output . PHP_EOL); 121 } elseif (Debug::isVerbose()) { 122 d_echo($output . PHP_EOL); 123 } 124 d_echo($proc->getErrorOutput()); 125 126 return $output; 127} 128 129function shorthost($hostname, $len = 12) 130{ 131 // IP addresses should not be shortened 132 if (filter_var($hostname, FILTER_VALIDATE_IP)) { 133 return $hostname; 134 } 135 $len = Config::get('shorthost_target_length', $len); 136 137 $parts = explode('.', $hostname); 138 $shorthost = $parts[0]; 139 $i = 1; 140 while ($i < count($parts) && strlen($shorthost . '.' . $parts[$i]) < $len) { 141 $shorthost = $shorthost . '.' . $parts[$i]; 142 $i++; 143 } 144 145 return $shorthost; 146} 147 148function print_error($text) 149{ 150 if (Laravel::isCli()) { 151 c_echo('%r' . $text . "%n\n"); 152 } else { 153 echo '<div class="alert alert-danger"><i class="fa fa-fw fa-exclamation-circle" aria-hidden="true"></i> ' . $text . '</div>'; 154 } 155} 156 157function print_message($text) 158{ 159 if (Laravel::isCli()) { 160 c_echo('%g' . $text . "%n\n"); 161 } else { 162 echo '<div class="alert alert-success"><i class="fa fa-fw fa-check-circle" aria-hidden="true"></i> ' . $text . '</div>'; 163 } 164} 165 166function get_sensor_rrd($device, $sensor) 167{ 168 return Rrd::name($device['hostname'], get_sensor_rrd_name($device, $sensor)); 169} 170 171function get_sensor_rrd_name($device, $sensor) 172{ 173 // For IPMI, sensors tend to change order, and there is no index, so we prefer to use the description as key here. 174 if (Config::getOsSetting($device['os'], 'sensor_descr') || $sensor['poller_type'] == 'ipmi') { 175 return ['sensor', $sensor['sensor_class'], $sensor['sensor_type'], $sensor['sensor_descr']]; 176 } else { 177 return ['sensor', $sensor['sensor_class'], $sensor['sensor_type'], $sensor['sensor_index']]; 178 } 179} 180 181function get_port_rrdfile_path($hostname, $port_id, $suffix = '') 182{ 183 return Rrd::name($hostname, Rrd::portName($port_id, $suffix)); 184} 185 186function get_port_by_index_cache($device_id, $ifIndex) 187{ 188 global $port_index_cache; 189 190 if (isset($port_index_cache[$device_id][$ifIndex]) && is_array($port_index_cache[$device_id][$ifIndex])) { 191 $port = $port_index_cache[$device_id][$ifIndex]; 192 } else { 193 $port = get_port_by_ifIndex($device_id, $ifIndex); 194 $port_index_cache[$device_id][$ifIndex] = $port; 195 } 196 197 return $port; 198} 199 200function get_port_by_ifIndex($device_id, $ifIndex) 201{ 202 return dbFetchRow('SELECT * FROM `ports` WHERE `device_id` = ? AND `ifIndex` = ?', [$device_id, $ifIndex]); 203} 204 205function table_from_entity_type($type) 206{ 207 // Fuck you, english pluralisation. 208 if ($type == 'storage') { 209 return $type; 210 } else { 211 return $type . 's'; 212 } 213} 214 215function get_entity_by_id_cache($type, $id) 216{ 217 global $entity_cache; 218 219 $table = table_from_entity_type($type); 220 221 if (is_array($entity_cache[$type][$id])) { 222 $entity = $entity_cache[$type][$id]; 223 } else { 224 $entity = dbFetchRow('SELECT * FROM `' . $table . '` WHERE `' . $type . '_id` = ?', [$id]); 225 $entity_cache[$type][$id] = $entity; 226 } 227 228 return $entity; 229} 230 231function get_port_by_id($port_id) 232{ 233 if (is_numeric($port_id)) { 234 $port = dbFetchRow('SELECT * FROM `ports` WHERE `port_id` = ?', [$port_id]); 235 if (is_array($port)) { 236 return $port; 237 } else { 238 return false; 239 } 240 } 241} 242 243function get_application_by_id($application_id) 244{ 245 if (is_numeric($application_id)) { 246 $application = dbFetchRow('SELECT * FROM `applications` WHERE `app_id` = ?', [$application_id]); 247 if (is_array($application)) { 248 return $application; 249 } else { 250 return false; 251 } 252 } 253} 254 255function get_sensor_by_id($sensor_id) 256{ 257 if (is_numeric($sensor_id)) { 258 $sensor = dbFetchRow('SELECT * FROM `sensors` WHERE `sensor_id` = ?', [$sensor_id]); 259 if (is_array($sensor)) { 260 return $sensor; 261 } else { 262 return false; 263 } 264 } 265} 266 267function get_device_id_by_port_id($port_id) 268{ 269 if (is_numeric($port_id)) { 270 $device_id = dbFetchCell('SELECT `device_id` FROM `ports` WHERE `port_id` = ?', [$port_id]); 271 if (is_numeric($device_id)) { 272 return $device_id; 273 } else { 274 return false; 275 } 276 } 277} 278 279function get_device_id_by_app_id($app_id) 280{ 281 if (is_numeric($app_id)) { 282 $device_id = dbFetchCell('SELECT `device_id` FROM `applications` WHERE `app_id` = ?', [$app_id]); 283 if (is_numeric($device_id)) { 284 return $device_id; 285 } else { 286 return false; 287 } 288 } 289} 290 291function ifclass($ifOperStatus, $ifAdminStatus) 292{ 293 // fake a port model 294 return \LibreNMS\Util\Url::portLinkDisplayClass((object) ['ifOperStatus' => $ifOperStatus, 'ifAdminStatus' => $ifAdminStatus]); 295} 296 297function device_by_name($name) 298{ 299 return device_by_id_cache(getidbyname($name)); 300} 301 302function accesspoint_by_id($ap_id, $refresh = '0') 303{ 304 $ap = dbFetchRow('SELECT * FROM `access_points` WHERE `accesspoint_id` = ?', [$ap_id]); 305 306 return $ap; 307} 308 309function device_by_id_cache($device_id, $refresh = false) 310{ 311 $model = $refresh ? DeviceCache::refresh((int) $device_id) : DeviceCache::get((int) $device_id); 312 313 $device = $model->toArray(); 314 $device['location'] = $model->location->location ?? null; 315 $device['lat'] = $model->location->lat ?? null; 316 $device['lng'] = $model->location->lng ?? null; 317 $device['attribs'] = $model->getAttribs(); 318 $device['vrf_lite_cisco'] = $model->vrfLites->keyBy('context_name')->toArray(); 319 320 return $device; 321} 322 323function truncate($substring, $max = 50, $rep = '...') 324{ 325 if (strlen($substring) < 1) { 326 $string = $rep; 327 } else { 328 $string = $substring; 329 } 330 $leave = $max - strlen($rep); 331 if (strlen($string) > $max) { 332 return substr_replace($string, $rep, $leave); 333 } else { 334 return $string; 335 } 336} 337 338function gethostbyid($device_id) 339{ 340 return DeviceCache::get((int) $device_id)->hostname; 341} 342 343function strgen($length = 16) 344{ 345 $entropy = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'a', 'A', 'b', 'B', 'c', 'C', 'd', 'D', 'e', 346 'E', 'f', 'F', 'g', 'G', 'h', 'H', 'i', 'I', 'j', 'J', 'k', 'K', 'l', 'L', 'm', 'M', 'n', 347 'N', 'o', 'O', 'p', 'P', 'q', 'Q', 'r', 'R', 's', 'S', 't', 'T', 'u', 'U', 'v', 'V', 'w', 348 'W', 'x', 'X', 'y', 'Y', 'z', 'Z', ]; 349 $string = ''; 350 351 for ($i = 0; $i < $length; $i++) { 352 $key = mt_rand(0, 61); 353 $string .= $entropy[$key]; 354 } 355 356 return $string; 357} 358 359function getpeerhost($id) 360{ 361 return dbFetchCell('SELECT `device_id` from `bgpPeers` WHERE `bgpPeer_id` = ?', [$id]); 362} 363 364function getifindexbyid($id) 365{ 366 return dbFetchCell('SELECT `ifIndex` FROM `ports` WHERE `port_id` = ?', [$id]); 367} 368 369function getifbyid($id) 370{ 371 return dbFetchRow('SELECT * FROM `ports` WHERE `port_id` = ?', [$id]); 372} 373 374function getifdescrbyid($id) 375{ 376 return dbFetchCell('SELECT `ifDescr` FROM `ports` WHERE `port_id` = ?', [$id]); 377} 378 379function getidbyname($hostname) 380{ 381 return DeviceCache::getByHostname($hostname)->device_id; 382} 383 384function zeropad($num, $length = 2) 385{ 386 return str_pad($num, $length, '0', STR_PAD_LEFT); 387} 388 389function set_dev_attrib($device, $attrib_type, $attrib_value) 390{ 391 return DeviceCache::get((int) $device['device_id'])->setAttrib($attrib_type, $attrib_value); 392} 393 394function get_dev_attribs($device_id) 395{ 396 return DeviceCache::get((int) $device_id)->getAttribs(); 397} 398 399function get_dev_entity_state($device) 400{ 401 $state = []; 402 foreach (dbFetchRows('SELECT * FROM entPhysical_state WHERE `device_id` = ?', [$device]) as $entity) { 403 $state['group'][$entity['group']][$entity['entPhysicalIndex']][$entity['subindex']][$entity['key']] = $entity['value']; 404 $state['index'][$entity['entPhysicalIndex']][$entity['subindex']][$entity['group']][$entity['key']] = $entity['value']; 405 } 406 407 return $state; 408} 409 410function get_dev_attrib($device, $attrib_type) 411{ 412 return DeviceCache::get((int) $device['device_id'])->getAttrib($attrib_type); 413} 414 415function del_dev_attrib($device, $attrib_type) 416{ 417 return DeviceCache::get((int) $device['device_id'])->forgetAttrib($attrib_type); 418} 419 420/** 421 * Output using console color if possible 422 * https://github.com/pear/Console_Color2/blob/master/examples/documentation 423 * 424 * @param string $string the string to print with console color 425 * @param bool $enabled if set to false, this function does nothing 426 */ 427function c_echo($string, $enabled = true) 428{ 429 if (! $enabled) { 430 return; 431 } 432 433 if (Laravel::isCli()) { 434 global $console_color; 435 if ($console_color) { 436 echo $console_color->convert($string); 437 } else { 438 // limited functionality for validate.php 439 $search = [ 440 '/%n/', 441 '/%g/', 442 '/%R/', 443 '/%Y/', 444 '/%B/', 445 '/%((%)|.)/', // anything left over replace with empty string 446 ]; 447 $replace = [ 448 "\e[0m", 449 "\e[32m", 450 "\e[1;31m", 451 "\e[1;33m", 452 "\e[1;34m", 453 '', 454 ]; 455 echo preg_replace($search, $replace, $string); 456 } 457 } else { 458 echo preg_replace('/%((%)|.)/', '', $string); 459 } 460} 461 462/* 463 * @return true if client IP address is authorized to access graphs 464 */ 465function is_client_authorized($clientip) 466{ 467 if (Config::get('allow_unauth_graphs', false)) { 468 d_echo("Unauthorized graphs allowed\n"); 469 470 return true; 471 } 472 473 foreach (Config::get('allow_unauth_graphs_cidr', []) as $range) { 474 try { 475 if (IP::parse($clientip)->inNetwork($range)) { 476 d_echo("Unauthorized graphs allowed from $range\n"); 477 478 return true; 479 } 480 } catch (InvalidIpException $e) { 481 d_echo("Client IP ($clientip) is invalid.\n"); 482 } 483 } 484 485 return false; 486} // is_client_authorized 487 488/* 489 * @return an array of all graph subtypes for the given type 490 */ 491function get_graph_subtypes($type, $device = null) 492{ 493 $type = basename($type); 494 $types = []; 495 496 // find the subtypes defined in files 497 if ($handle = opendir(Config::get('install_dir') . "/includes/html/graphs/$type/")) { 498 while (false !== ($file = readdir($handle))) { 499 if ($file != '.' && $file != '..' && $file != 'auth.inc.php' && strstr($file, '.inc.php')) { 500 $types[] = str_replace('.inc.php', '', $file); 501 } 502 } 503 closedir($handle); 504 } 505 506 sort($types); 507 508 return $types; 509} // get_graph_subtypes 510 511function get_smokeping_files($device) 512{ 513 $smokeping = new \LibreNMS\Util\Smokeping(DeviceCache::get((int) $device['device_id'])); 514 515 return $smokeping->findFiles(); 516} 517 518function generate_smokeping_file($device, $file = '') 519{ 520 $smokeping = new \LibreNMS\Util\Smokeping(DeviceCache::get((int) $device['device_id'])); 521 522 return $smokeping->generateFileName($file); 523} 524 525/* 526 * @return rounded value to 10th/100th/1000th depending on input (valid: 10, 100, 1000) 527 */ 528function round_Nth($val, $round_to) 529{ 530 if (($round_to == '10') || ($round_to == '100') || ($round_to == '1000')) { 531 $diff = $val % $round_to; 532 if ($diff >= ($round_to / 2)) { 533 $ret = $val + ($round_to - $diff); 534 } else { 535 $ret = $val - $diff; 536 } 537 538 return $ret; 539 } 540} // end round_Nth 541 542function is_customoid_graph($type, $subtype) 543{ 544 if (! empty($subtype) && $type == 'customoid') { 545 return true; 546 } 547 548 return false; 549} // is_customoid_graph 550 551// 552// maintain a simple cache of objects 553// 554 555function object_add_cache($section, $obj) 556{ 557 global $object_cache; 558 $object_cache[$section][$obj] = true; 559} // object_add_cache 560 561function object_is_cached($section, $obj) 562{ 563 global $object_cache; 564 if (array_key_exists($obj, $object_cache)) { 565 return $object_cache[$section][$obj]; 566 } else { 567 return false; 568 } 569} // object_is_cached 570 571/** 572 * Checks if config allows us to ping this device 573 * $attribs contains an array of all of this devices 574 * attributes 575 * @param array $attribs Device attributes 576 * @return bool 577 **/ 578function can_ping_device($attribs) 579{ 580 if (Config::get('icmp_check') && ! (isset($attribs['override_icmp_disable']) && $attribs['override_icmp_disable'] == 'true')) { 581 return true; 582 } else { 583 return false; 584 } 585} // end can_ping_device 586 587function search_phrase_column($c) 588{ 589 global $searchPhrase; 590 591 return "$c LIKE '%$searchPhrase%'"; 592} // search_phrase_column 593 594/** 595 * Constructs the path to an RRD for the Ceph application 596 * @param string $gtype The type of rrd we're looking for 597 * @return string 598 **/ 599function ceph_rrd($gtype) 600{ 601 global $device; 602 global $vars; 603 604 if ($gtype == 'osd') { 605 $var = $vars['osd']; 606 } else { 607 $var = $vars['pool']; 608 } 609 610 return Rrd::name($device['hostname'], ['app', 'ceph', $vars['id'], $gtype, $var]); 611} // ceph_rrd 612 613/** 614 * Parse location field for coordinates 615 * @param string location The location field to look for coords in. 616 * @return array|bool Containing the lat and lng coords 617 **/ 618function parse_location($location) 619{ 620 preg_match('/\[(-?[0-9. ]+), *(-?[0-9. ]+)\]/', $location, $tmp_loc); 621 if (is_numeric($tmp_loc[1]) && is_numeric($tmp_loc[2])) { 622 return ['lat' => $tmp_loc[1], 'lng' => $tmp_loc[2]]; 623 } 624 625 return false; 626}//end parse_location() 627 628/** 629 * Returns version info 630 * @param bool $remote fetch remote version info from github 631 * @return array 632 */ 633function version_info($remote = false) 634{ 635 $version = \LibreNMS\Util\Version::get(); 636 $output = [ 637 'local_ver' => $version->local(), 638 ]; 639 if (Git::repoPresent() && Git::binaryExists()) { 640 if ($remote === true && Config::get('update_channel') == 'master') { 641 $api = curl_init(); 642 set_curl_proxy($api); 643 curl_setopt($api, CURLOPT_USERAGENT, 'LibreNMS'); 644 curl_setopt($api, CURLOPT_URL, Config::get('github_api') . 'commits/master'); 645 curl_setopt($api, CURLOPT_RETURNTRANSFER, 1); 646 curl_setopt($api, CURLOPT_TIMEOUT, 5); 647 curl_setopt($api, CURLOPT_TIMEOUT_MS, 5000); 648 curl_setopt($api, CURLOPT_CONNECTTIMEOUT, 5); 649 $output['github'] = json_decode(curl_exec($api), true); 650 } 651 [$local_sha, $local_date] = explode('|', rtrim(`git show --pretty='%H|%ct' -s HEAD`)); 652 $output['local_sha'] = $local_sha; 653 $output['local_date'] = $local_date; 654 $output['local_branch'] = rtrim(`git rev-parse --abbrev-ref HEAD`); 655 } else { 656 $output['local_ver'] = '21.5.1'; 657 # obtained via git show --pretty='%H|%ct' -s 21.5.1 658 $output['local_date'] = 1621427246; 659 } 660 $output['db_schema'] = vsprintf('%s (%s)', $version->database()); 661 $output['php_ver'] = phpversion(); 662 $output['python_ver'] = \LibreNMS\Util\Version::python(); 663 $output['mysql_ver'] = dbIsConnected() ? dbFetchCell('SELECT version()') : '?'; 664 $output['rrdtool_ver'] = str_replace('1.7.01.7.0', '1.7.0', implode(' ', array_slice(explode(' ', shell_exec( 665 Config::get('rrdtool', 'rrdtool') . ' --version |head -n1' 666 )), 1, 1))); 667 $output['netsnmp_ver'] = str_replace('version: ', '', rtrim(shell_exec( 668 Config::get('snmpget', 'snmpget') . ' -V 2>&1' 669 ))); 670 671 return $output; 672}//end version_info() 673 674/** 675 * Checks SNMPv3 capabilities 676 * 677 * SHA2 for Auth Algorithms (SHA-224,SHA-256,SHA-384,SHA-512) 678 * AES-192, AES-256 for Privacy Algorithms 679 */ 680function snmpv3_capabilities(): array 681{ 682 $process = new Process([Config::get('snmpget', 'snmpget'), '--help']); 683 $process->run(); 684 685 $ret['sha2'] = Str::contains($process->getErrorOutput(), 'SHA-512'); 686 $ret['aes256'] = Str::contains($process->getErrorOutput(), 'AES-256'); 687 688 return $ret; 689} 690 691/** 692 * Convert a MySQL binary v4 (4-byte) or v6 (16-byte) IP address to a printable string. 693 * @param string $ip A binary string containing an IP address, as returned from MySQL's INET6_ATON function 694 * @return string Empty if not valid. 695 */ 696// Fuction is from https://php.net/manual/en/function.inet-ntop.php 697function inet6_ntop($ip) 698{ 699 $l = strlen($ip); 700 if ($l == 4 or $l == 16) { 701 return inet_ntop(pack('A' . $l, $ip)); 702 } 703 704 return ''; 705} 706 707/** 708 * If hostname is an ip, use return sysName 709 * @param array $device (uses hostname and sysName fields) 710 * @param string $hostname 711 * @return string 712 */ 713function format_hostname($device, $hostname = null) 714{ 715 if (empty($hostname)) { 716 $hostname = $device['hostname']; 717 } 718 719 if (Config::get('force_hostname_to_sysname') && ! empty($device['sysName'])) { 720 if (\LibreNMS\Util\Validate::hostname($hostname) && ! IP::isValid($hostname)) { 721 return $device['sysName']; 722 } 723 } 724 725 if (Config::get('force_ip_to_sysname') && ! empty($device['sysName'])) { 726 if (IP::isValid($hostname)) { 727 return $device['sysName']; 728 } 729 } 730 731 return $hostname; 732} 733 734/** 735 * Return valid port association modes 736 * @return array 737 */ 738function get_port_assoc_modes() 739{ 740 return [ 741 1 => 'ifIndex', 742 2 => 'ifName', 743 3 => 'ifDescr', 744 4 => 'ifAlias', 745 ]; 746} 747 748/** 749 * Get DB id of given port association mode name 750 * @param string $port_assoc_mode 751 * @return int 752 */ 753function get_port_assoc_mode_id($port_assoc_mode) 754{ 755 $modes = array_flip(get_port_assoc_modes()); 756 757 return isset($modes[$port_assoc_mode]) ? $modes[$port_assoc_mode] : false; 758} 759 760/** 761 * Get name of given port association_mode ID 762 * @param int $port_assoc_mode_id Port association mode ID 763 * @return bool 764 */ 765function get_port_assoc_mode_name($port_assoc_mode_id) 766{ 767 $modes = get_port_assoc_modes(); 768 769 return isset($modes[$port_assoc_mode_id]) ? $modes[$port_assoc_mode_id] : false; 770} 771 772/** 773 * Query all ports of the given device (by ID) and build port array and 774 * port association maps for ifIndex, ifName, ifDescr. Query port stats 775 * if told to do so, too. 776 * @param int $device_id ID of device to query ports for 777 * @param bool $with_statistics Query port statistics, too. (optional, default false) 778 * @return array 779 */ 780function get_ports_mapped($device_id, $with_statistics = false) 781{ 782 $ports = []; 783 $maps = [ 784 'ifIndex' => [], 785 'ifName' => [], 786 'ifDescr' => [], 787 ]; 788 789 if ($with_statistics) { 790 /* ... including any related ports_statistics if requested */ 791 $query = 'SELECT *, `ports_statistics`.`port_id` AS `ports_statistics_port_id`, `ports`.`port_id` AS `port_id` FROM `ports` LEFT OUTER JOIN `ports_statistics` ON `ports`.`port_id` = `ports_statistics`.`port_id` WHERE `ports`.`device_id` = ? ORDER BY ports.port_id'; 792 } else { 793 /* Query all information available for ports for this device ... */ 794 $query = 'SELECT * FROM `ports` WHERE `device_id` = ? ORDER BY port_id'; 795 } 796 797 // Query known ports in order of discovery to make sure the latest 798 // discoverd/polled port is in the mapping tables. 799 foreach (dbFetchRows($query, [$device_id]) as $port) { 800 // Store port information by ports port_id from DB 801 $ports[$port['port_id']] = $port; 802 803 // Build maps from ifIndex, ifName, ifDescr to port_id 804 $maps['ifIndex'][$port['ifIndex']] = $port['port_id']; 805 $maps['ifName'][$port['ifName']] = $port['port_id']; 806 $maps['ifDescr'][$port['ifDescr']] = $port['port_id']; 807 } 808 809 return [ 810 'ports' => $ports, 811 'maps' => $maps, 812 ]; 813} 814 815/** 816 * Calculate port_id of given port using given devices port information and port association mode 817 * @param array $ports_mapped Port information of device queried by get_ports_mapped() 818 * @param array $port Port information as fetched from DB 819 * @param string $port_association_mode Port association mode to use for mapping 820 * @return int port_id (or Null) 821 */ 822function get_port_id($ports_mapped, $port, $port_association_mode) 823{ 824 // Get port_id according to port_association_mode used for this device 825 $port_id = null; 826 827 /* 828 * Information an all ports is available through $ports_mapped['ports'] 829 * This might come in handy sometime in the future to add you nifty new 830 * port mapping schema: 831 * 832 * $ports = $ports_mapped['ports']; 833 */ 834 $maps = $ports_mapped['maps']; 835 836 if (in_array($port_association_mode, ['ifIndex', 'ifName', 'ifDescr', 'ifAlias'])) { 837 $port_id = $maps[$port_association_mode][$port[$port_association_mode]]; 838 } 839 840 return $port_id; 841} 842 843/** 844 * Create a glue-chain 845 * @param array $tables Initial Tables to construct glue-chain 846 * @param string $target Glue to find (usual device_id) 847 * @param int $x Recursion Anchor 848 * @param array $hist History of processed tables 849 * @param array $last Glues on the fringe 850 * @return array|false 851 */ 852function ResolveGlues($tables, $target, $x = 0, $hist = [], $last = []) 853{ 854 if (sizeof($tables) == 1 && $x != 0) { 855 if (dbFetchCell('SELECT 1 FROM information_schema.COLUMNS WHERE TABLE_NAME = ? && COLUMN_NAME = ?', [$tables[0], $target]) == 1) { 856 return array_merge($last, [$tables[0] . '.' . $target]); 857 } else { 858 return false; 859 } 860 } else { 861 $x++; 862 if ($x > 30) { 863 //Too much recursion. Abort. 864 return false; 865 } 866 foreach ($tables as $table) { 867 if ($table == 'state_translations' && ($target == 'device_id' || $target == 'sensor_id')) { 868 // workaround for state_translations 869 return array_merge($last, [ 870 'state_translations.state_index_id', 871 'sensors_to_state_indexes.sensor_id', 872 "sensors.$target", 873 ]); 874 } elseif ($table == 'application_metrics' && $target == 'device_id') { 875 return array_merge($last, [ 876 'application_metrics.app_id', 877 "applications.$target", 878 ]); 879 } elseif ($table == 'locations' && $target == 'device_id') { 880 return array_merge($last, [ 881 'locations.id', 882 'devices.device_id.location_id', 883 ]); 884 } 885 886 $glues = dbFetchRows('SELECT COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_NAME = ? && COLUMN_NAME LIKE "%\_id"', [$table]); 887 if (sizeof($glues) == 1 && $glues[0]['COLUMN_NAME'] != $target) { 888 //Search for new candidates to expand 889 $ntables = []; 890 [$tmp] = explode('_', $glues[0]['COLUMN_NAME'], 2); 891 $ntables[] = $tmp; 892 $ntables[] = $tmp . 's'; 893 $tmp = dbFetchRows('SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_NAME LIKE "' . substr($table, 0, -1) . '_%" && TABLE_NAME != "' . $table . '"'); 894 foreach ($tmp as $expand) { 895 $ntables[] = $expand['TABLE_NAME']; 896 } 897 $tmp = ResolveGlues($ntables, $target, $x++, array_merge($tables, $ntables), array_merge($last, [$table . '.' . $glues[0]['COLUMN_NAME']])); 898 if (is_array($tmp)) { 899 return $tmp; 900 } 901 } else { 902 foreach ($glues as $glue) { 903 if ($glue['COLUMN_NAME'] == $target) { 904 return array_merge($last, [$table . '.' . $target]); 905 } else { 906 [$tmp] = explode('_', $glue['COLUMN_NAME']); 907 $tmp .= 's'; 908 if (! in_array($tmp, $tables) && ! in_array($tmp, $hist)) { 909 //Expand table 910 $tmp = ResolveGlues([$tmp], $target, $x++, array_merge($tables, [$tmp]), array_merge($last, [$table . '.' . $glue['COLUMN_NAME']])); 911 if (is_array($tmp)) { 912 return $tmp; 913 } 914 } 915 } 916 } 917 } 918 } 919 } 920 //You should never get here. 921 return false; 922} 923 924/** 925 * Determine if a given string contains a given substring. 926 * 927 * @param string $haystack 928 * @param string|array $needles 929 * @return bool 930 */ 931function str_i_contains($haystack, $needles) 932{ 933 foreach ((array) $needles as $needle) { 934 if ($needle != '' && stripos($haystack, $needle) !== false) { 935 return true; 936 } 937 } 938 939 return false; 940} 941 942/** 943 * Get alert_rules sql filter by minimal severity 944 * 945 * @param string|int $min_severity 946 * @param string $alert_rules_name 947 * @return string 948 */ 949function get_sql_filter_min_severity($min_severity, $alert_rules_name) 950{ 951 $alert_severities = [ 952 // alert_rules.status is enum('ok','warning','critical') 953 'ok' => 1, 954 'warning' => 2, 955 'critical' => 3, 956 'ok only' => 4, 957 'warning only' => 5, 958 'critical only' => 6, 959 ]; 960 if (is_numeric($min_severity)) { 961 $min_severity_id = $min_severity; 962 } elseif (! empty($min_severity)) { 963 $min_severity_id = $alert_severities[$min_severity]; 964 } 965 if (isset($min_severity_id)) { 966 return " AND `$alert_rules_name`.`severity` " . ($min_severity_id > 3 ? '' : '>') . '= ' . ($min_severity_id > 3 ? $min_severity_id - 3 : $min_severity_id); 967 } 968 969 return ''; 970} 971 972/** 973 * Load the os definition for the device and set type and os_group 974 * $device['os'] must be set 975 * 976 * @param array $device 977 */ 978function load_os(&$device) 979{ 980 if (! isset($device['os'])) { 981 d_echo("No OS to load\n"); 982 983 return; 984 } 985 986 \LibreNMS\Util\OS::loadDefinition($device['os']); 987 988 // Set type to a predefined type for the OS if it's not already set 989 $loaded_os_type = Config::get("os.{$device['os']}.type"); 990 if ((! isset($device['attribs']['override_device_type']) && $device['attribs']['override_device_type'] != 1) && array_key_exists('type', $device) && $loaded_os_type != $device['type']) { 991 log_event('Device type changed ' . $device['type'] . ' => ' . $loaded_os_type, $device, 'system', 3); 992 $device['type'] = $loaded_os_type; 993 dbUpdate(['type' => $loaded_os_type], 'devices', 'device_id=?', [$device['device_id']]); 994 d_echo("Device type changed to $loaded_os_type!\n"); 995 } 996 997 if ($os_group = Config::get("os.{$device['os']}.group")) { 998 $device['os_group'] = $os_group; 999 } else { 1000 unset($device['os_group']); 1001 } 1002} 1003 1004/** 1005 * Converts fahrenheit to celsius (with 2 decimal places) 1006 * if $scale is not fahrenheit, it assumes celsius and returns the value 1007 * 1008 * @param float $value 1009 * @param string $scale fahrenheit or celsius 1010 * @return string (containing a float) 1011 */ 1012function fahrenheit_to_celsius($value, $scale = 'fahrenheit') 1013{ 1014 if ($scale === 'fahrenheit') { 1015 $value = ($value - 32) / 1.8; 1016 } 1017 1018 return sprintf('%.02f', $value); 1019} 1020 1021/** 1022 * Converts celsius to fahrenheit (with 2 decimal places) 1023 * if $scale is not celsius, it assumes celsius and returns the value 1024 * 1025 * @param float $value 1026 * @param string $scale fahrenheit or celsius 1027 * @return string (containing a float) 1028 */ 1029function celsius_to_fahrenheit($value, $scale = 'celsius') 1030{ 1031 if ($scale === 'celsius') { 1032 $value = ($value * 1.8) + 32; 1033 } 1034 1035 return sprintf('%.02f', $value); 1036} 1037 1038/** 1039 * Converts string to float 1040 */ 1041function string_to_float($value) 1042{ 1043 return sprintf('%.02f', $value); 1044} 1045 1046/** 1047 * Converts uW to dBm 1048 * $value must be positive 1049 */ 1050function uw_to_dbm($value) 1051{ 1052 return 10 * log10($value / 1000); 1053} 1054 1055/** 1056 * Converts mW to dBm 1057 * $value must be positive 1058 */ 1059function mw_to_dbm($value) 1060{ 1061 return 10 * log10($value); 1062} 1063 1064/** 1065 * @param $value 1066 * @param null $default 1067 * @param int $min 1068 * @return null 1069 */ 1070function set_null($value, $default = null, $min = null) 1071{ 1072 if (! is_numeric($value)) { 1073 return $default; 1074 } elseif (is_nan($value)) { 1075 return $default; 1076 } elseif (is_infinite($value)) { 1077 return $default; 1078 } elseif (isset($min) && $value < $min) { 1079 return $default; 1080 } 1081 1082 return $value; 1083} 1084/* 1085 * @param $value 1086 * @param int $default 1087 * @return int 1088 */ 1089function set_numeric($value, $default = 0) 1090{ 1091 if (! is_numeric($value) || 1092 is_nan($value) || 1093 is_infinite($value) 1094 ) { 1095 $value = $default; 1096 } 1097 1098 return $value; 1099} 1100 1101function get_vm_parent_id($device) 1102{ 1103 if (empty($device['hostname'])) { 1104 return false; 1105 } 1106 1107 return dbFetchCell('SELECT `device_id` FROM `vminfo` WHERE `vmwVmDisplayName` = ? OR `vmwVmDisplayName` = ?', [$device['hostname'], $device['hostname'] . '.' . Config::get('mydomain')]); 1108} 1109 1110/** 1111 * Generate a class name from a lowercase string containing - or _ 1112 * Remove - and _ and camel case words 1113 * 1114 * @param string $name The string to convert to a class name 1115 * @param string $namespace namespace to prepend to the name for example: LibreNMS\ 1116 * @return string Class name 1117 */ 1118function str_to_class($name, $namespace = null) 1119{ 1120 $pre_format = str_replace(['-', '_'], ' ', $name); 1121 $class = str_replace(' ', '', ucwords(strtolower($pre_format))); 1122 $class = preg_replace_callback('/^(\d)(.)/', function ($matches) { 1123 $numbers = ['Zero', 'One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight', 'Nine']; 1124 1125 return $numbers[$matches[1]] . strtoupper($matches[2]); 1126 }, $class); 1127 1128 return $namespace . $class; 1129} 1130 1131/** 1132 * Index an array by a column 1133 * 1134 * @param array $array 1135 * @param string|int $column 1136 * @return array 1137 */ 1138function array_by_column($array, $column) 1139{ 1140 return array_combine(array_column($array, $column), $array); 1141} 1142