1<?php 2 3/** 4 * Observium 5 * 6 * This file is part of Observium. 7 * 8 * @package observium 9 * @subpackage functions 10 * @author Adam Armstrong <adama@observium.org> 11 * @copyright (C) 2006-2013 Adam Armstrong, (C) 2013-2019 Observium Limited 12 * 13 */ 14 15// Observium Includes 16 17require_once($config['install_dir'] . "/includes/common.inc.php"); 18include_once($config['install_dir'] . "/includes/encrypt.inc.php"); 19include_once($config['install_dir'] . "/includes/rrdtool.inc.php"); 20include_once($config['install_dir'] . "/includes/influx.inc.php"); 21include_once($config['install_dir'] . "/includes/syslog.inc.php"); 22include_once($config['install_dir'] . "/includes/rewrites.inc.php"); 23include_once($config['install_dir'] . "/includes/templates.inc.php"); 24include_once($config['install_dir'] . "/includes/snmp.inc.php"); 25include_once($config['install_dir'] . "/includes/services.inc.php"); 26include_once($config['install_dir'] . "/includes/entities.inc.php"); 27include_once($config['install_dir'] . "/includes/wifi.inc.php"); 28include_once($config['install_dir'] . "/includes/geolocation.inc.php"); 29 30include_once($config['install_dir'] . "/includes/alerts.inc.php"); 31 32//if (OBSERVIUM_EDITION != 'community') // OBSERVIUM_EDITION - not defined here.. 33//{ 34foreach (array('groups', 'billing', // Not exist in community edition 35 'community', // community edition specific 36 'custom', // custom functions, i.e. short_hostname 37 ) as $entry) 38{ 39 $file = $config['install_dir'] . '/includes/' . $entry . '.inc.php'; 40 if (is_file($file)) { include_once($file); } 41} 42 43 44// DOCME needs phpdoc block 45// Send to AMQP via UDP-based python proxy. 46// TESTME needs unit testing 47// MOVEME to includes/common.inc.php 48function messagebus_send($message) 49{ 50 global $config; 51 52 if ($socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP)) 53 { 54 $message = json_encode($message); 55 print_debug('Sending JSON via AMQP: ' . $message); 56 socket_sendto($socket, $message, strlen($message), 0, $config['amqp']['proxy']['host'], $config['amqp']['proxy']['port']); 57 socket_close($socket); 58 return TRUE; 59 } else { 60 print_error("Failed to create UDP socket towards AMQP proxy."); 61 return FALSE; 62 } 63} 64 65 66 67/** 68 * Transforms a given string using an array of different transformations, in order. 69 * 70 * @param string $string Original string to be transformed 71 * @param array $transformations Transformation array 72 * 73 * Available transformations: 74 * 'action' => 'prepend' Prepend static 'string' 75 * 'action' => 'append' Append static 'string' 76 * 'action' => 'trim' Trim 'characters' from both sides of the string 77 * 'action' => 'ltrim' Trim 'characters' from the left of the string 78 * 'action' => 'rtrim' Trim 'characters' from the right of the string 79 * 'action' => 'replace' Case-sensitively replace 'from' string by 'to'; 'from' can be an array of strings 80 * 'action' => 'ireplace' Case-insensitively replace 'from' string by 'to'; 'from' can be an array of strings 81 * 'action' => 'timeticks' Convert standart Timeticks to seconds 82 * 'action' => 'explode' Explode string by 'delimiter' (default ' ') and fetch array element (first (default), last) 83 * 'action' => 'regex_replace' Replace 'from' with 'to'. 84 * 85 * @return string Transformed string 86 */ 87function string_transform($string, $transformations) 88{ 89 if (!is_array($transformations) || empty($transformations)) 90 { 91 // Bail out if no transformations are given 92 return $string; 93 } 94 95 // Simplify single action definition with less array nesting 96 if (isset($transformations['action'])) 97 { 98 $transformations = array($transformations); 99 } 100 101 foreach ($transformations as $transformation) 102 { 103 $msg = " String '$string' transformed by action [".$transformation['action']."] to: "; 104 switch ($transformation['action']) 105 { 106 case 'prepend': 107 $string = $transformation['string'] . $string; 108 break; 109 110 case 'append': 111 $string .= $transformation['string']; 112 break; 113 114 case 'upper': 115 $string = strtoupper($string); 116 break; 117 118 case 'lower': 119 $string = strtolower($string); 120 break; 121 122 case 'nicecase': 123 $string = nicecase($string); 124 break; 125 126 case 'trim': 127 case 'ltrim': 128 case 'rtrim': 129 if (isset($transformation['chars']) && !isset($transformation['characters'])) 130 { 131 // Just simple for brain memory key 132 $transformation['characters'] = $transformation['chars']; 133 } 134 if (!isset($transformation['characters'])) 135 { 136 $transformation['characters'] = " \t\n\r\0\x0B"; 137 } 138 139 if ($transformation['action'] == 'rtrim') 140 { 141 $string = rtrim($string, $transformation['characters']); 142 } 143 else if ($transformation['action'] == 'ltrim') 144 { 145 $string = ltrim($string, $transformation['characters']); 146 } else { 147 $string = trim($string, $transformation['characters']); 148 } 149 break; 150 151 case 'replace': 152 $string = str_replace($transformation['from'], $transformation['to'], $string); 153 break; 154 155 case 'ireplace': 156 $string = str_ireplace($transformation['from'], $transformation['to'], $string); 157 break; 158 159 case 'regex_replace': 160 case 'preg_replace': 161 $tmp_string = preg_replace($transformation['from'], $transformation['to'], $string); 162 if (strlen($tmp_string)) 163 { 164 $string = $tmp_string; 165 } 166 break; 167 168 case 'regex_match': 169 case 'preg_match': 170 if (preg_match($transformation['from'], $string, $matches)) 171 { 172 $string = array_tag_replace($matches, $transformation['to']); 173 } 174 break; 175 176 case 'map': 177 // Map string by key -> value 178 if (is_array($transformation['map']) && isset($transformation['map'][$string])) 179 { 180 $string = $transformation['map'][$string]; 181 } 182 break; 183 184 case 'timeticks': 185 // Timeticks: (2542831) 7:03:48.31 186 $string = timeticks_to_sec($string); 187 break; 188 189 case 'asdot': 190 // BGP 32bit ASN from asdot to plain 191 $string = bgp_asdot_to_asplain($string); 192 break; 193 194 case 'units': 195 // 200kbps -> 200000, 50M -> 52428800 196 $string = unit_string_to_numeric($string); 197 break; 198 199 case 'entity_name': 200 $string = rewrite_entity_name($string); 201 break; 202 203 case 'explode': 204 // String delimiter (default is single space " ") 205 if (isset($transformation['delimiter']) && strlen($transformation['delimiter'])) 206 { 207 $delimiter = $transformation['delimiter']; 208 } else { 209 $delimiter = ' '; 210 } 211 $array = explode($delimiter, $string); 212 // Get array index (default is first) 213 if (!isset($transformation['index'])) 214 { 215 $transformation['index'] = 'first'; 216 } 217 switch ($transformation['index']) 218 { 219 case 'first': 220 case 'begin': 221 $string = array_shift($array); 222 break; 223 case 'last': 224 case 'end': 225 $string = array_pop($array); 226 break; 227 default: 228 229 if (strlen($array[$transformation['index']])) 230 { 231 $string = $array[$transformation['index']]; 232 } 233 } 234 break; 235 236 default: 237 // FIXME echo HALP, unknown transformation! 238 break; 239 } 240 print_debug($msg . "'$string'"); 241 } 242 243 return $string; 244} 245 246// DOCME needs phpdoc block 247// Sorts an $array by a passed field. 248// TESTME needs unit testing 249// MOVEME to includes/common.inc.php 250function array_sort($array, $on, $order='SORT_ASC') 251{ 252 $new_array = array(); 253 $sortable_array = array(); 254 255 if (count($array) > 0) 256 { 257 foreach ($array as $k => $v) 258 { 259 if (is_array($v)) 260 { 261 foreach ($v as $k2 => $v2) 262 { 263 if ($k2 == $on) 264 { 265 $sortable_array[$k] = $v2; 266 } 267 } 268 } else { 269 $sortable_array[$k] = $v; 270 } 271 } 272 273 switch ($order) 274 { 275 case 'SORT_ASC': 276 asort($sortable_array); 277 break; 278 case 'SORT_DESC': 279 arsort($sortable_array); 280 break; 281 } 282 283 foreach ($sortable_array as $k => $v) 284 { 285 $new_array[$k] = $array[$k]; 286 } 287 } 288 289 return $new_array; 290} 291 292/** hex2float 293* (Convert 8 digit hexadecimal value to float (single-precision 32bits) 294* Accepts 8 digit hexadecimal values in a string 295* @usage: 296* hex2float32n("429241f0"); returns -> "73.128784179688" 297* */ 298function hex2float($number) { 299 $binfinal = sprintf("%032b",hexdec($number)); 300 $sign = substr($binfinal, 0, 1); 301 $exp = substr($binfinal, 1, 8); 302 $mantissa = "1".substr($binfinal, 9); 303 $mantissa = str_split($mantissa); 304 $exp = bindec($exp)-127; 305 $significand=0; 306 for ($i = 0; $i < 24; $i++) { 307 $significand += (1 / pow(2,$i))*$mantissa[$i]; 308 } 309 return $significand * pow(2,$exp) * ($sign*-2+1); 310} 311 312// A function to process numerical values according to a $scale value 313// Functionised to allow us to have "magic" scales which do special things 314// Initially used for dec>hex>float values used by accuview 315function scale_value($value, $scale) 316{ 317 318 if ($scale == '161616') // This is used by Accuview Accuvim II 319 { 320 //CLEANME. Not required anymore, use unit name "accuenergy" 321 return hex2float(dechex($value)); 322 } else if($scale != 0) { 323 return $value * $scale; 324 } else { 325 return $value; 326 } 327 328} 329 330/** 331 * Custom value unit conversion functions for some vendors, 332 * who do not know how use snmp float conversions, 333 * do not know physics, mathematics and in general badly studied at school 334 */ 335 336function value_unit_accuenergy($value) 337{ 338 return hex2float(dechex($value)); 339} 340 341// See: https://jira.observium.org/browse/OBS-2941 342// Oids "pm10010mpMesrlineNetRxInputPwrPortn" and "pm10010mpMesrlineNetTxLaserOutputPwrPortn" in EKINOPS-Pm10010mp-MIB 343// If AV<32768: Tx_Pwr(dBm) = AV/100 344// If AV>=32768: Tx_Pwr(dBm) = (AV-65536)/100 345function value_unit_ekinops_dbm1($value) 346{ 347 if ($value >= 32768 && $value <= 65536) 348 { 349 return ($value - 65536) / 100; 350 } 351 else if ($value > 65536 || $value < 0) 352 { 353 return FALSE; 354 } 355 356 return $value / 100; 357} 358 359// See: https://jira.observium.org/browse/OBS-2941 360// oids "pm10010mpMesrclientNetTxPwrPortn" and "pm10010mpMesrclientNetRxPwrPortn" in EKINOPS-Pm10010mp-MIB 361// Power = 10*log(AV)-40) (Unit = dBm) 362function value_unit_ekinops_dbm2($value) 363{ 364 //$si_value = 10 * log10($value) + 30; // this is how watts converted to dbm 365 $si_value = 10 * log10($value) - 40; // BUT this how convert it EKINOPS.... WHY?????? 366 367 return $si_value; 368} 369 370// Another sort array function 371// http://php.net/manual/en/function.array-multisort.php#100534 372// DOCME needs phpdoc block 373// TESTME needs unit testing 374// MOVEME to includes/common.inc.php 375function array_sort_by() 376{ 377 $args = func_get_args(); 378 $data = array_shift($args); 379 foreach ($args as $n => $field) 380 { 381 if (is_string($field)) 382 { 383 $tmp = array(); 384 foreach ($data as $key => $row) 385 { 386 $tmp[$key] = $row[$field]; 387 } 388 $args[$n] = $tmp; 389 } 390 } 391 $args[] = &$data; 392 call_user_func_array('array_multisort', $args); 393 return array_pop($args); 394} 395 396/** 397 * Given two arrays, the function diff will return an array of the changes. 398 * 399 * @param array $old First array 400 * @param array $new Second array 401 * @return array Array with diffs and same elements 402 */ 403function diff($old, $new) 404{ 405 406 $matrix = array(); 407 $maxlen = 0; 408 foreach ($old as $oindex => $ovalue) 409 { 410 $nkeys = array_keys($new, $ovalue); 411 foreach ($nkeys as $nindex) 412 { 413 $matrix[$oindex][$nindex] = isset($matrix[$oindex - 1][$nindex - 1]) ? $matrix[$oindex - 1][$nindex - 1] + 1 : 1; 414 if ($matrix[$oindex][$nindex] > $maxlen) 415 { 416 $maxlen = $matrix[$oindex][$nindex]; 417 $omax = $oindex + 1 - $maxlen; 418 $nmax = $nindex + 1 - $maxlen; 419 } 420 } 421 } 422 if ($maxlen == 0) 423 { 424 return array(array('d'=>$old, 'i'=>$new)); 425 } 426 427 return array_merge( 428 diff(array_slice($old, 0, $omax), array_slice($new, 0, $nmax)), 429 array_slice($new, $nmax, $maxlen), 430 diff(array_slice($old, $omax + $maxlen), array_slice($new, $nmax + $maxlen))); 431} 432 433/** 434 * Return similar part of two strings (or empty) 435 * 436 * @param string $old First string 437 * @param string $new Second string 438 * @return string Similar part of two strings 439 */ 440function str_similar($old, $new) 441{ 442 443 $ret = array(); 444 $diff = diff(preg_split("/[\s]+/u", $old), preg_split("/[\s]+/u", $new)); 445 foreach ($diff as $k) 446 { 447 if (!is_array($k)) 448 { 449 $ret[] = $k; 450 } 451 } 452 return implode(' ', $ret); 453} 454 455/** 456 * Return sets of all similar strings from passed array. 457 * 458 * @param array $array Array with strings for find similar 459 * @param boolean $return_flip If TRUE return pairs String -> Similar part, 460 * instead vice versa by default return set of arrays Similar part -> Strings 461 * @param integer $similarity Percent of similarity compared string, mostly common is 90% 462 * @return array Array with sets of similar strings 463 */ 464function find_similar($array, $return_flip = FALSE, $similarity = 89) 465{ 466 natsort($array); 467 //var_dump($array); 468 $array2 = $array; 469 $same_array = array(); 470 $same_array_flip = array(); 471 472 // $i = 0; // DEBUG 473 foreach ($array as $k => $old) 474 { 475 foreach ($array2 as $k2 => $new) 476 { 477 if ($k === $k2) { continue; } // Skip same array elements 478 479 // Detect string similarity 480 similar_text($old, $new, $perc); 481 482 // $i++; echo "$i ($perc %): '$old' <> '$new' (".str_similar($old, $new).")\n"; // DEBUG 483 484 if ($perc > $similarity) 485 { 486 if (isset($same_array_flip[$old])) 487 { 488 // This is found already similar string by previous round(s) 489 $same = $same_array_flip[$old]; 490 $same_array_flip[$new] = $same; 491 } 492 else if (isset($same_array_flip[$new])) 493 { 494 // This is found already similar string by previous round(s) 495 $same = $same_array_flip[$new]; 496 $same_array_flip[$old] = $same; 497 } else { 498 // New similarity, get similar string part 499 $same = str_similar($old, $new); 500 501 // Return array pairs as: 502 // String -> Similar part 503 $same_array_flip[$old] = $same; 504 $same_array_flip[$new] = $same; 505 } 506 507 // Return array elements as: 508 // Similar part -> Strings 509 if (!isset($same_array[$same]) || !in_array($old, $same_array[$same])) { 510 $same_array[$same][] = $old; 511 } 512 $same_array[$same][] = $new; 513 514 unset($array2[$k]); // Remove array element if similar found 515 516 break; 517 } 518 } 519 if (!isset($same_array_flip[$old])) 520 { 521 // Similarity not found, just add as single string 522 $same_array_flip[$old] = $old; 523 $same_array[$old][] = $old; 524 } 525 } 526 527 if ($return_flip) 528 { 529 // Return array pairs as: 530 // String -> Similar part 531 return $same_array_flip; 532 } else { 533 // Return array elements as: 534 // Similar part -> Strings 535 return $same_array; 536 } 537} 538 539/** 540 * Includes filename with global config variable 541 * 542 * @param string $filename Filename for include 543 * 544 * @return boolean Status of include 545 */ 546function include_wrapper($filename) 547{ 548 global $config; 549 550 $status = include($filename); 551 552 return (boolean)$status; 553} 554 555// Strip all non-alphanumeric characters from a string. 556// DOCME needs phpdoc block 557// TESTME needs unit testing 558// MOVEME to includes/common.inc.php 559function only_alphanumeric($string) 560{ 561 return preg_replace('/[^a-zA-Z0-9]/', '', $string); 562} 563 564/** 565 * Detect the device's OS 566 * 567 * Order for detect: 568 * if device rechecking (know old os): complex discovery (all), sysObjectID, sysDescr, file check 569 * if device first checking: complex discovery (except network), sysObjectID, sysDescr, complex discovery (network), file check 570 * 571 * @param array $device Device array 572 * @return string Detected device os name 573 */ 574function get_device_os($device) 575{ 576 global $config, $table_rows, $cache_os; 577 578 // If $recheck sets as TRUE, verified that 'os' corresponds to the old value. 579 // recheck only if old device exist in definitions 580 $recheck = isset($config['os'][$device['os']]); 581 582 $sysDescr = snmp_fix_string(snmp_get_oid($device, 'sysDescr.0', 'SNMPv2-MIB')); 583 $sysDescr_ok = $GLOBALS['snmp_status'] || $GLOBALS['snmp_error_code'] === 1; // Allow empty response for sysDescr (not timeouts) 584 $sysObjectID = snmp_cache_sysObjectID($device); 585 /* 586 $sysObjectID = snmp_get($device, 'sysObjectID.0', '-Ovqn', 'SNMPv2-MIB'); 587 if (strpos($sysObjectID, 'Wrong Type') !== FALSE) 588 { 589 // Wrong Type (should be OBJECT IDENTIFIER): "1.3.6.1.4.1.25651.1.2" 590 list(, $sysObjectID) = explode(':', $sysObjectID); 591 $sysObjectID = '.'.trim($sysObjectID, ' ."'); 592 } 593 */ 594 595 // Cache discovery os definitions 596 cache_discovery_definitions(); 597 $discovery_os = $GLOBALS['cache']['discovery_os']; 598 $cache_os = array(); 599 600 $table_rows = array(); 601 $table_opts = array('max-table-width' => TRUE); // Set maximum table width as available columns in terminal 602 $table_headers = array('%WOID%n', ''); 603 $table_rows[] = array('sysDescr', $sysDescr); 604 $table_rows[] = array('sysObjectID', $sysObjectID); 605 print_cli_table($table_rows, $table_headers, NULL, $table_opts); 606 //print_debug("Detect OS. sysDescr: '$sysDescr', sysObjectID: '$sysObjectID'"); 607 608 $table_rows = array(); // Reinit 609 //$table_opts = array('max-table-width' => 200); 610 $table_headers = array('%WOID%n', '%WMatched definition%n', ''); 611 // By first check all sysObjectID 612 foreach ($discovery_os['sysObjectID'] as $def => $cos) 613 { 614 if (match_sysObjectID($sysObjectID, $def)) 615 { 616 // Store matched OS, but by first need check by complex discovery arrays! 617 $sysObjectID_def = $def; 618 $sysObjectID_os = $cos; 619 break; 620 } 621 } 622 623 if ($recheck) 624 { 625 $table_desc = 'Re-Detect OS matched'; 626 $old_os = $device['os']; 627 628 if (!$sysDescr_ok && !empty($old_os)) 629 { 630 // If sysDescr empty - return old os, because some snmp error 631 // print_debug("ERROR: sysDescr not received, OS re-check stopped."); 632 // return $old_os; 633 } 634 635 // Recheck by complex discovery array 636 // Yes, before sysObjectID, because complex more accurate and can intersect with it! 637 foreach ($discovery_os['discovery'][$old_os] as $def) 638 { 639 if (match_discovery_os($sysObjectID, $sysDescr, $def, $device)) 640 { 641 print_cli_table($table_rows, $table_headers, $table_desc . " ($old_os: ".$config['os'][$old_os]['text'].'):', $table_opts); 642 return $old_os; 643 } 644 } 645 foreach ($discovery_os['discovery_network'][$old_os] as $def) 646 { 647 if (match_discovery_os($sysObjectID, $sysDescr, $def, $device)) 648 { 649 print_cli_table($table_rows, $table_headers, $table_desc . " ($old_os: ".$config['os'][$old_os]['text'].'):', $table_opts); 650 return $old_os; 651 } 652 } 653 654 /** DISABLED. 655 * Recheck only by complex, networked and file rules 656 657 // Recheck by sysObjectID 658 if ($sysObjectID_os) 659 { 660 // If OS detected by sysObjectID just return it 661 $table_rows[] = array('sysObjectID', $sysObjectID_def, $sysObjectID); 662 print_cli_table($table_rows, $table_headers, $table_desc . " ($old_os: ".$config['os'][$old_os]['text'].'):', $table_opts); 663 return $sysObjectID_os; 664 } 665 666 // Recheck by sysDescr from definitions 667 foreach ($discovery_os['sysDescr'][$old_os] as $pattern) 668 { 669 if (preg_match($pattern, $sysDescr)) 670 { 671 $table_rows[] = array('sysDescr', $pattern, $sysDescr); 672 print_cli_table($table_rows, $table_headers, $table_desc . " ($old_os: ".$config['os'][$old_os]['text'].'):', $table_opts); 673 return $old_os; 674 } 675 } 676 */ 677 678 // Recheck by include file (moved to end!) 679 680 // Else full recheck 'os'! 681 unset($os, $file); 682 683 } // End recheck 684 685 $table_desc = 'Detect OS matched'; 686 687 // Check by complex discovery arrays (except networked) 688 // Yes, before sysObjectID, because complex more accurate and can intersect with it! 689 foreach ($discovery_os['discovery'] as $cos => $defs) 690 { 691 foreach ($defs as $def) 692 { 693 if (match_discovery_os($sysObjectID, $sysDescr, $def, $device)) { $os = $cos; break 2; } 694 } 695 } 696 697 // Check by sysObjectID 698 if (!$os && $sysObjectID_os) 699 { 700 // If OS detected by sysObjectID just return it 701 $os = $sysObjectID_os; 702 $table_rows[] = array('sysObjectID', $sysObjectID_def, $sysObjectID); 703 print_cli_table($table_rows, $table_headers, $table_desc . " ($os: ".$config['os'][$os]['text'].'):', $table_opts); 704 return $os; 705 } 706 707 if (!$os && $sysDescr) 708 { 709 // Check by sysDescr from definitions 710 foreach ($discovery_os['sysDescr'] as $cos => $patterns) 711 { 712 foreach ($patterns as $pattern) 713 { 714 if (preg_match($pattern, $sysDescr)) 715 { 716 $table_rows[] = array('sysDescr', $pattern, $sysDescr); 717 $os = $cos; 718 break 2; 719 } 720 } 721 } 722 } 723 724 // Check by complex discovery arrays, now networked 725 if (!$os) 726 { 727 foreach ($discovery_os['discovery_network'] as $cos => $defs) 728 { 729 foreach ($defs as $def) 730 { 731 if (match_discovery_os($sysObjectID, $sysDescr, $def, $device)) { $os = $cos; break 2; } 732 } 733 } 734 } 735 736 if (!$os) 737 { 738 $path = $config['install_dir'] . '/includes/discovery/os'; 739 $sysObjectId = $sysObjectID; // old files use wrong variable name 740 741 // Recheck first 742 $recheck_file = FALSE; 743 if ($recheck && $old_os) 744 { 745 if (is_file($path . "/$old_os.inc.php")) 746 { 747 $recheck_file = $path . "/$old_os.inc.php"; 748 } 749 else if (isset($config['os'][$old_os]['discovery_os']) && 750 is_file($path . '/'.$config['os'][$old_os]['discovery_os'] . '.inc.php')) 751 { 752 $recheck_file = $path . '/'.$config['os'][$old_os]['discovery_os'] . '.inc.php'; 753 } 754 755 if ($recheck_file) 756 { 757 print_debug("Including $recheck_file"); 758 759 $sysObjectId = $sysObjectID; // old files use wrong variable name 760 include($recheck_file); 761 762 if ($os && $os == $old_os) 763 { 764 $table_rows[] = array('file', $recheck_file, ''); 765 print_cli_table($table_rows, $table_headers, $table_desc . " ($old_os: ".$config['os'][$old_os]['text'].'):', $table_opts); 766 return $old_os; 767 } 768 } 769 } 770 771 // Check all other by include file 772 $dir_handle = @opendir($path) or die("Unable to open $path"); 773 while ($file = readdir($dir_handle)) 774 { 775 if (preg_match('/\.inc\.php$/', $file) && $file !== $recheck_file) 776 { 777 print_debug("Including $file"); 778 779 include($path . '/' . $file); 780 781 if ($os) 782 { 783 $table_rows[] = array('file', $file, ''); 784 break; // Stop while if os detected 785 } 786 } 787 } 788 closedir($dir_handle); 789 } 790 791 if ($os) 792 { 793 print_cli_table($table_rows, $table_headers, $table_desc . " ($os: ".$config['os'][$os]['text'].'):', $table_opts); 794 return $os; 795 } else { 796 return 'generic'; 797 } 798} 799 800/** 801 * Compares sysObjectID with $needle. Return TRUE if match. 802 * 803 * @param string $sysObjectID Walked sysObjectID from device 804 * @param string $needle Compare with this 805 * @return boolean TRUE if match, otherwise FALSE 806 */ 807function match_sysObjectID($sysObjectID, $needle) 808{ 809 if (substr($needle, -1) === '.') 810 { 811 // Use wildcard compare if sysObjectID definition have '.' at end, ie: 812 // .1.3.6.1.4.1.2011.1. 813 if (str_starts($sysObjectID, $needle)) { return TRUE; } 814 } else { 815 // Use exact match sysObjectID definition or wildcard compare with '.' at end, ie: 816 // .1.3.6.1.4.1.2011.2.27 817 if ($sysObjectID === $needle || str_starts($sysObjectID, $needle.'.')) { return TRUE; } 818 } 819 820 return FALSE; 821} 822 823/** 824 * Compares complex sysObjectID/sysDescr definition with $needle. Return TRUE if match. 825 * 826 * @param string $sysObjectID Walked sysObjectID from device 827 * @param string $sysDescr Walked sysDescr from device 828 * @param array $needle Compare with this definition array 829 * @param array $device Device array, optional if compare used not standard OIDs 830 * @return boolean TRUE if match, otherwise FALSE 831 */ 832function match_discovery_os($sysObjectID, $sysDescr, $needle, $device = array()) 833{ 834 global $table_rows, $cache_os; 835 836 $needle_oids = array_keys($needle); 837 $needle_count = count($needle_oids); 838 839 // Match sysObjectID and sysDescr always first! 840 $needle_oids_order = array_merge(array('sysObjectID', 'sysDescr'), $needle_oids); 841 $needle_oids_order = array_unique($needle_oids_order); 842 $needle_oids_order = array_intersect($needle_oids_order, $needle_oids); 843 844 foreach ($needle_oids_order as $oid) 845 { 846 $match = FALSE; 847 switch ($oid) 848 { 849 case 'sysObjectID': 850 foreach ((array)$needle[$oid] as $def) 851 { 852 //var_dump($def); 853 //var_dump($sysObjectID); 854 //var_dump(match_sysObjectID($sysObjectID, $def)); 855 if (match_sysObjectID($sysObjectID, $def)) 856 { 857 $match_defs[$oid] = array($def, $sysObjectID); 858 $needle_count--; 859 $match = TRUE; 860 break; 861 } 862 } 863 break; 864 865 case 'sysDescr': 866 foreach ((array)$needle[$oid] as $def) 867 { 868 //print_vars($def); 869 //print_vars($sysDescr); 870 //print_vars(preg_match($def, $sysDescr)); 871 if (preg_match($def, $sysDescr)) 872 { 873 $match_defs[$oid] = array($def, $sysDescr); 874 $needle_count--; 875 $match = TRUE; 876 break; 877 } 878 } 879 break; 880 881 case 'sysName': 882 // other common SNMPv2-MIB fetch first 883 if (!isset($cache_os[$oid])) 884 { 885 $value = snmp_fix_string(snmp_get_oid($device, $oid . '.0', 'SNMPv2-MIB')); 886 $value_ok = $GLOBALS['snmp_status'] || $GLOBALS['snmp_error_code'] === 1; // Allow empty response 887 $cache_os[$oid] = array('ok' => $value_ok, 'value' => $value); 888 } else { 889 // Use already cached data 890 $value = $cache_os[$oid]['value']; 891 $value_ok = $cache_os[$oid]['ok']; 892 } 893 foreach ((array)$needle[$oid] as $def) 894 { 895 //print_vars($def); 896 //print_vars($value); 897 //print_vars(preg_match($def, $value)); 898 if ($value_ok && preg_match($def, $value)) 899 { 900 $match_defs[$oid] = array($def, $value); 901 $needle_count--; 902 $match = TRUE; 903 break; 904 } 905 } 906 break; 907 908 default: 909 // All other oids, 910 // fetch by first, than compare with pattern 911 if (!isset($cache_os[$oid])) 912 { 913 $value = snmp_fix_string(snmp_get_oid($device, $oid)); 914 $value_ok = $GLOBALS['snmp_status'] || $GLOBALS['snmp_error_code'] === 1; // Allow empty response 915 $cache_os[$oid] = array('ok' => $value_ok, 'value' => $value); 916 } else { 917 // Use already cached data 918 $value = $cache_os[$oid]['value']; 919 $value_ok = $cache_os[$oid]['ok']; 920 } 921 foreach ((array)$needle[$oid] as $def) 922 { 923 // print_vars($def); 924 // print_vars($value); 925 // print_vars(preg_match($def, $value)); 926 if ($value_ok && preg_match($def, $value)) 927 { 928 $match_defs[$oid] = array($def, $value); 929 $needle_count--; 930 $match = TRUE; 931 break; 932 } 933 } 934 break; 935 } 936 937 // Stop all other checks, last oid not match with any.. 938 if (!$match) { return FALSE; } 939 } 940 941 // Match only if all oids found and matched 942 $match = $needle_count === 0; 943 944 // Store detailed info 945 if ($match) 946 { 947 foreach ($match_defs as $oid => $def) 948 { 949 $table_rows[] = array($oid, $def[0], $def[1]); 950 } 951 } 952 953 return $match; 954} 955 956function cache_discovery_definitions() 957{ 958 global $config, $cache; 959 960 // Cache/organize discovery definitions 961 if (!isset($cache['discovery_os'])) 962 { 963 foreach (array_keys($config['os']) as $cos) 964 { 965 // Generate full array with sysObjectID from definitions 966 foreach ($config['os'][$cos]['sysObjectID'] as $oid) 967 { 968 $oid = trim($oid); 969 if ($oid[0] != '.') { $oid = '.' . $oid; } // Add first point if not already added 970 971 if (isset($cache['discovery_os']['sysObjectID'][$oid]) && strpos($cache['discovery_os']['sysObjectID'][$oid], 'test_') !== 0) 972 { 973 print_error("Duplicate sysObjectID '$oid' in definitions for OSes: ".$cache['discovery_os']['sysObjectID'][$oid]." and $cos!"); 974 continue; 975 } 976 // sysObjectID -> os 977 $cache['discovery_os']['sysObjectID'][$oid] = $cos; 978 $cache['discovery_os']['sysObjectID_cos'][$oid][] = $cos; // Collect how many same sysObjectID known by definitions 979 //$sysObjectID_def[$oid] = $cos; 980 } 981 982 // Generate full array with sysDescr from definitions 983 if (isset($config['os'][$cos]['sysDescr'])) 984 { 985 // os -> sysDescr (list) 986 $cache['discovery_os']['sysDescr'][$cos] = $config['os'][$cos]['sysDescr']; 987 } 988 989 // Complex match with combinations of sysDescr / sysObjectID and any other 990 foreach ($config['os'][$cos]['discovery'] as $discovery) 991 { 992 $oids = array_keys($discovery); 993 if (!in_array('sysObjectID', $oids)) 994 { 995 // Check if definition have additional "networked" OIDs (without sysObjectID checks) 996 $def_name = 'discovery_network'; 997 } else { 998 $def_name = 'discovery'; 999 } 1000 1001 if (count($oids) === 1) 1002 { 1003 // single oids convert to old array format 1004 switch (array_shift($oids)) 1005 { 1006 case 'sysObjectID': 1007 foreach ((array)$discovery['sysObjectID'] as $oid) 1008 { 1009 $oid = trim($oid); 1010 if ($oid[0] != '.') { $oid = '.' . $oid; } // Add first point if not already added 1011 1012 if (isset($cache['discovery_os']['sysObjectID'][$oid]) && strpos($cache['discovery_os']['sysObjectID'][$oid], 'test_') !== 0) 1013 { 1014 print_error("Duplicate sysObjectID '$oid' in definitions for OSes: ".$cache['discovery_os']['sysObjectID'][$oid]." and $cos!"); 1015 continue; 1016 } 1017 // sysObjectID -> os 1018 $cache['discovery_os']['sysObjectID'][$oid] = $cos; 1019 $cache['discovery_os']['sysObjectID_cos'][$oid][] = $cos; // Collect how many same sysObjectID known by definitions 1020 } 1021 break; 1022 case 'sysDescr': 1023 // os -> sysDescr (list) 1024 if (isset($cache['discovery_os']['sysDescr'][$cos])) 1025 { 1026 $cache['discovery_os']['sysDescr'][$cos] = array_unique(array_merge((array)$cache['discovery_os']['sysDescr'][$cos], (array)$discovery['sysDescr'])); 1027 } else { 1028 $cache['discovery_os']['sysDescr'][$cos] = (array)$discovery['sysDescr']; 1029 } 1030 break; 1031 case 'file': 1032 $cache['discovery_os']['file'][$cos] = $discovery['file']; 1033 break; 1034 default: 1035 // All other leave as is 1036 $cache['discovery_os'][$def_name][$cos][] = $discovery; 1037 } 1038 } else { 1039 if ($def_name == 'discovery') // This have sysObjectID 1040 { 1041 $new_oids = array(); 1042 foreach ((array)$discovery['sysObjectID'] as $oid) 1043 { 1044 $oid = trim($oid); 1045 if ($oid[0] != '.') { $oid = '.' . $oid; } // Add first point if not already added 1046 $new_oids[] = $oid; 1047 $cache['discovery_os']['sysObjectID_cos'][$oid][] = $cos; // Collect how many same sysObjectID known by definitions 1048 } 1049 $discovery['sysObjectID'] = $new_oids; // Override sysObjectIDs with normalized 1050 } 1051 // Leave complex definitions as is 1052 $cache['discovery_os'][$def_name][$cos][] = $discovery; 1053 } 1054 } 1055 } 1056 1057 // NOTE: Currently too hard for detect if same sysObjectID used in multiple OSes, and how get best OS 1058 // Best os should match by max params 1059 // Remove all single sysObjectIDs count 1060 foreach ($cache['discovery_os']['sysObjectID_cos'] as $oid => $oses) 1061 { 1062 $oses = array_unique($oses); 1063 if (count($oses) < 2) 1064 { 1065 // Single sysObjectID, no additional matches needed 1066 unset($cache['discovery_os']['sysObjectID_cos'][$oid]); 1067 } else { 1068 /* 1069 if (isset($cache['discovery_os']['sysObjectID'][$oid])) 1070 { 1071 // Move simple check to complex 1072 $cos = $cache['discovery_os']['sysObjectID'][$oid]; 1073 ///$cache['discovery_os']['discovery'][$cos][] = array('sysObjectID' => $oid); 1074 //unset($cache['discovery_os']['sysObjectID'][$oid]); 1075 } 1076 */ 1077 $cache['discovery_os']['sysObjectID_cos'][$oid] = $oses; 1078 } 1079 } 1080 1081 // Resort sysObjectID array by oids with from high to low order! 1082 //krsort($cache['discovery_os']['sysObjectID']); 1083 uksort($cache['discovery_os']['sysObjectID'], 'compare_numeric_oids_reverse'); 1084 1085 //print_vars($cache['discovery_os']['sysObjectID_cos']); 1086 //print_vars($cache['discovery_os']); 1087 } 1088} 1089 1090/** 1091 * Compare two numeric oids and return -1, 0, 1 1092 * ie: .1.2.1. vs 1.2.2 1093 */ 1094function compare_numeric_oids($oid1, $oid2) 1095{ 1096 $oid1_array = explode('.', ltrim($oid1, '.')); 1097 $oid2_array = explode('.', ltrim($oid2, '.')); 1098 1099 $count1 = count($oid1_array); 1100 $count2 = count($oid2_array); 1101 1102 for ($i = 0; $i <= min($count1, $count2) - 1; $i++) 1103 { 1104 $int1 = intval($oid1_array[$i]); 1105 $int2 = intval($oid2_array[$i]); 1106 if ($int1 > $int2) { return 1; } 1107 else if ($int1 < $int2) { return -1; } 1108 } 1109 if ($count1 > $count2) { return 1; } 1110 else if ($count1 < $count2) { return -1; } 1111 1112 return 0; 1113} 1114 1115/** 1116 * Compare two numeric oids and return -1, 0, 1 1117 * here reverse order 1118 * ie: .1.2.1. vs 1.2.2 1119 */ 1120function compare_numeric_oids_reverse($oid1, $oid2) 1121{ 1122 return compare_numeric_oids($oid2, $oid1); 1123} 1124 1125// Rename a device 1126// DOCME needs phpdoc block 1127// TESTME needs unit testing 1128function renamehost($id, $new, $source = 'console', $options = array()) 1129{ 1130 global $config; 1131 1132 $new = strtolower(trim($new)); 1133 1134 // Test if new host exists in database 1135 //if (dbFetchCell('SELECT COUNT(`device_id`) FROM `devices` WHERE `hostname` = ?', array($new)) == 0) 1136 if (!dbExist('devices', '`hostname` = ?', array($new))) 1137 { 1138 $flags = OBS_DNS_ALL; 1139 $transport = strtolower(dbFetchCell("SELECT `snmp_transport` FROM `devices` WHERE `device_id` = ?", array($id))); 1140 1141 // Try detect if hostname is IP 1142 switch (get_ip_version($new)) 1143 { 1144 case 6: 1145 $new = Net_IPv6::compress($new, TRUE); // Always use compressed IPv6 name 1146 case 4: 1147 if ($config['require_hostname']) 1148 { 1149 print_error("Hostname should be a valid resolvable FQDN name. Or set config option \$config['require_hostname'] as FALSE."); 1150 return FALSE; 1151 } 1152 $ip = $new; 1153 break; 1154 default: 1155 if ($transport == 'udp6' || $transport == 'tcp6') // Exclude IPv4 if used transport 'udp6' or 'tcp6' 1156 { 1157 $flags = $flags ^ OBS_DNS_A; // exclude A 1158 } 1159 // Test DNS lookup. 1160 $ip = gethostbyname6($new, $flags); 1161 } 1162 1163 if ($ip) 1164 { 1165 $options['ping_skip'] = (isset($options['ping_skip']) && $options['ping_skip']) || get_entity_attrib('device', $id, 'ping_skip'); 1166 if ($options['ping_skip']) 1167 { 1168 // Skip ping checks 1169 $flags = $flags | OBS_PING_SKIP; 1170 } 1171 // Test reachability 1172 if (isPingable($new, $flags)) 1173 { 1174 // Test directory mess in /rrd/ 1175 if (!file_exists($config['rrd_dir'].'/'.$new)) 1176 { 1177 $host = dbFetchCell("SELECT `hostname` FROM `devices` WHERE `device_id` = ?", array($id)); 1178 if (!file_exists($config['rrd_dir'].'/'.$host)) 1179 { 1180 print_warning("Old RRD directory does not exist, rename skipped."); 1181 } 1182 else if (!rename($config['rrd_dir'].'/'.$host, $config['rrd_dir'].'/'.$new)) 1183 { 1184 print_error("NOT renamed. Error while renaming RRD directory."); 1185 return FALSE; 1186 } 1187 $return = dbUpdate(array('hostname' => $new), 'devices', '`device_id` = ?', array($id)); 1188 if ($options['ping_skip']) 1189 { 1190 set_entity_attrib('device', $id, 'ping_skip', 1); 1191 } 1192 log_event("Device hostname changed: $host -> $new", $id, 'device', $id, 5); // severity 5, for logging user/console info 1193 return TRUE; 1194 } else { 1195 // directory already exists 1196 print_error("NOT renamed. Directory rrd/$new already exists"); 1197 } 1198 } else { 1199 // failed Reachability 1200 print_error("NOT renamed. Could not ping $new"); 1201 } 1202 } else { 1203 // Failed DNS lookup 1204 print_error("NOT renamed. Could not resolve $new"); 1205 } 1206 } else { 1207 // found in database 1208 print_error("NOT renamed. Already got host $new"); 1209 } 1210 return FALSE; 1211} 1212 1213// Deletes device from database and RRD dir. 1214// DOCME needs phpdoc block 1215// TESTME needs unit testing 1216function delete_device($id, $delete_rrd = FALSE) 1217{ 1218 global $config; 1219 1220 $ret = PHP_EOL; 1221 $device = device_by_id_cache($id); 1222 $host = $device['hostname']; 1223 1224 if (!is_array($device)) 1225 { 1226 return FALSE; 1227 } else { 1228 $ports = dbFetchRows("SELECT * FROM `ports` WHERE `device_id` = ?", array($id)); 1229 if (!empty($ports)) 1230 { 1231 $ret .= ' * Deleted interfaces: '; 1232 $deleted_ports = []; 1233 foreach ($ports as $int_data) 1234 { 1235 $int_if = $int_data['ifDescr']; 1236 $int_id = $int_data['port_id']; 1237 delete_port($int_id, $delete_rrd); 1238 $deleted_ports[] = "id=$int_id ($int_if)"; 1239 } 1240 $ret .= implode(', ', $deleted_ports).PHP_EOL; 1241 } 1242 1243 // Remove entities from common tables 1244 $deleted_entities = array(); 1245 foreach (get_device_entities($id) as $entity_type => $entity_ids) 1246 { 1247 foreach ($config['entity_tables'] as $table) 1248 { 1249 $where = '`entity_type` = ?' . generate_query_values($entity_ids, 'entity_id'); 1250 $table_status = dbDelete($table, $where, array($entity_type)); 1251 if ($table_status) { $deleted_entities[$entity_type] = 1; } 1252 } 1253 } 1254 if (count($deleted_entities)) 1255 { 1256 $ret .= ' * Deleted common entity entries linked to device: '; 1257 $ret .= implode(', ', array_keys($deleted_entities)) . PHP_EOL; 1258 } 1259 1260 $deleted_tables = array(); 1261 $ret .= ' * Deleted device entries from tables: '; 1262 foreach ($config['device_tables'] as $table) 1263 { 1264 $where = '`device_id` = ?'; 1265 $table_status = dbDelete($table, $where, array($id)); 1266 if ($table_status) { $deleted_tables[] = $table; } 1267 } 1268 if (count($deleted_tables)) 1269 { 1270 $ret .= implode(', ', $deleted_tables).PHP_EOL; 1271 1272 // Request for clear WUI cache 1273 set_cache_clear('wui'); 1274 } 1275 1276 if ($delete_rrd) 1277 { 1278 $device_rrd = rtrim(get_rrd_path($device, ''), '/'); 1279 if (is_file($device_rrd.'/status.rrd')) 1280 { 1281 external_exec("rm -rf ".escapeshellarg($device_rrd)); 1282 $ret .= ' * Deleted device RRDs dir: ' . $device_rrd . PHP_EOL; 1283 } 1284 1285 } 1286 1287 log_event("Deleted device: $host", $id, 'device', $id, 5); // severity 5, for logging user/console info 1288 $ret .= " * Deleted device: $host"; 1289 } 1290 1291 return $ret; 1292} 1293 1294function add_device_vars($vars) 1295{ 1296 1297 global $config; 1298 1299 $hostname = strip_tags($vars['hostname']); 1300 1301 // Keep original snmp/rrd config, for revert at end 1302 $config_snmp = $config['snmp']; 1303 $config_rrd = $config['rrd_override']; 1304 1305 // Default snmp port 1306 if ($vars['snmp_port'] && is_numeric($vars['snmp_port'])) 1307 { 1308 $snmp_port = (int)$vars['snmp_port']; 1309 } else { 1310 $snmp_port = 161; 1311 } 1312 1313 // Default snmp version 1314 if ($vars['snmp_version'] !== "v2c" && 1315 $vars['snmp_version'] !== "v3" && 1316 $vars['snmp_version'] !== "v1") 1317 { 1318 $vars['snmp_version'] = $config['snmp']['version']; 1319 } 1320 1321 switch ($vars['snmp_version']) 1322 { 1323 case 'v2c': 1324 case 'v1': 1325 1326 if (strlen($vars['snmp_community'])) 1327 { 1328 // Hrm, I not sure why strip_tags 1329 $snmp_community = strip_tags($vars['snmp_community']); 1330 $config['snmp']['community'] = array($snmp_community); 1331 } 1332 1333 $snmp_version = $vars['snmp_version']; 1334 1335 print_message("Adding SNMP$snmp_version host $hostname port $snmp_port"); 1336 break; 1337 1338 case 'v3': 1339 1340 if (strlen($vars['snmp_authlevel'])) 1341 { 1342 $snmp_v3 = array ( 1343 'authlevel' => $vars['snmp_authlevel'], 1344 'authname' => $vars['snmp_authname'], 1345 'authpass' => $vars['snmp_authpass'], 1346 'authalgo' => $vars['snmp_authalgo'], 1347 'cryptopass' => $vars['snmp_cryptopass'], 1348 'cryptoalgo' => $vars['snmp_cryptoalgo'], 1349 ); 1350 1351 array_unshift($config['snmp']['v3'], $snmp_v3); 1352 } 1353 1354 $snmp_version = "v3"; 1355 1356 print_message("Adding SNMPv3 host $hostname port $snmp_port"); 1357 break; 1358 1359 default: 1360 print_error("Unsupported SNMP Version. There was a dropdown menu, how did you reach this error?"); // We have a hacker! 1361 return FALSE; 1362 } 1363 1364 if ($vars['ignorerrd'] == 'confirm' || 1365 $vars['ignorerrd'] == '1' || 1366 $vars['ignorerrd'] == 'on') 1367 { 1368 $config['rrd_override'] = TRUE; 1369 } 1370 1371 $snmp_options = array(); 1372 if ($vars['ping_skip'] == '1' || $vars['ping_skip'] == 'on') 1373 { 1374 $snmp_options['ping_skip'] = TRUE; 1375 } 1376 1377 // Optional SNMP Context 1378 if (strlen(trim($vars['snmp_context']))) 1379 { 1380 $snmp_options['snmp_context'] = trim($vars['snmp_context']); 1381 } 1382 1383 $result = add_device($hostname, $snmp_version, $snmp_port, strip_tags($vars['snmp_transport']), $snmp_options); 1384 1385 // Revert original snmp/rrd config 1386 $config['snmp'] = $config_snmp; 1387 $config['rrd_override'] = $config_rrd; 1388 1389 return $result; 1390 1391} 1392 1393 1394 1395/** 1396 * Adds the new device to the database. 1397 * 1398 * Before adding the device, checks duplicates in the database and the availability of device over a network. 1399 * 1400 * @param string $hostname Device hostname 1401 * @param string|array $snmp_version SNMP version(s) (default: $config['snmp']['version']) 1402 * @param string $snmp_port SNMP port (default: 161) 1403 * @param string $snmp_transport SNMP transport (default: udp) 1404 * @param array $options Additional options can be passed ('ping_skip' - for skip ping test and add device attrib for skip pings later 1405 * 'break' - for break recursion, 1406 * 'test' - for skip adding, only test device availability) 1407 * 1408 * @return mixed Returns $device_id number if added, 0 (zero) if device not accessible with current auth and FALSE if device complete not accessible by network. When testing, returns -1 if the device is available. 1409 */ 1410// TESTME needs unit testing 1411function add_device($hostname, $snmp_version = array(), $snmp_port = 161, $snmp_transport = 'udp', $options = array(), $flags = OBS_DNS_ALL) 1412{ 1413 global $config; 1414 1415 // If $options['break'] set as TRUE, break recursive function execute 1416 if (isset($options['break']) && $options['break']) { return FALSE; } 1417 $return = FALSE; // By default return FALSE 1418 1419 // Reset snmp timeout and retries options for speedup device adding 1420 unset($config['snmp']['timeout'], $config['snmp']['retries']); 1421 1422 $snmp_transport = strtolower($snmp_transport); 1423 1424 $hostname = strtolower(trim($hostname)); 1425 1426 // Try detect if hostname is IP 1427 switch (get_ip_version($hostname)) 1428 { 1429 case 6: 1430 $hostname = Net_IPv6::compress($hostname, TRUE); // Always use compressed IPv6 name 1431 case 4: 1432 if ($config['require_hostname']) 1433 { 1434 print_error("Hostname should be a valid resolvable FQDN name. Or set config option \$config['require_hostname'] as FALSE."); 1435 return $return; 1436 } 1437 $ip = $hostname; 1438 break; 1439 default: 1440 if ($snmp_transport == 'udp6' || $snmp_transport == 'tcp6') // IPv6 used only if transport 'udp6' or 'tcp6' 1441 { 1442 $flags = $flags ^ OBS_DNS_A; // exclude A 1443 } 1444 // Test DNS lookup. 1445 $ip = gethostbyname6($hostname, $flags); 1446 } 1447 1448 // Test if host exists in database 1449 //if (dbFetchCell("SELECT COUNT(*) FROM `devices` WHERE `hostname` = ?", array($hostname)) == '0') 1450 if (!dbExist('devices', '`hostname` = ?', array($hostname))) 1451 { 1452 if ($ip) 1453 { 1454 $ip_version = get_ip_version($ip); 1455 1456 // Test reachability 1457 $options['ping_skip'] = isset($options['ping_skip']) && $options['ping_skip']; 1458 if ($options['ping_skip']) 1459 { 1460 $flags = $flags | OBS_PING_SKIP; 1461 } 1462 if (isPingable($hostname, $flags)) 1463 { 1464 // Test directory exists in /rrd/ 1465 if (!$config['rrd_override'] && file_exists($config['rrd_dir'].'/'.$hostname)) 1466 { 1467 print_error("Directory <observium>/rrd/$hostname already exists."); 1468 return FALSE; 1469 } 1470 1471 // Detect snmp transport 1472 if (stripos($snmp_transport, 'tcp') !== FALSE) 1473 { 1474 $snmp_transport = ($ip_version == 4 ? 'tcp' : 'tcp6'); 1475 } else { 1476 $snmp_transport = ($ip_version == 4 ? 'udp' : 'udp6'); 1477 } 1478 // Detect snmp port 1479 if (!is_numeric($snmp_port) || $snmp_port < 1 || $snmp_port > 65535) 1480 { 1481 $snmp_port = 161; 1482 } else { 1483 $snmp_port = (int)$snmp_port; 1484 } 1485 // Detect snmp version 1486 if (empty($snmp_version)) 1487 { 1488 // Here set default snmp version order 1489 $i = 1; 1490 $snmp_version_order = array(); 1491 foreach (array('v2c', 'v3', 'v1') as $tmp_version) 1492 { 1493 if ($config['snmp']['version'] == $tmp_version) 1494 { 1495 $snmp_version_order[0] = $tmp_version; 1496 } else { 1497 $snmp_version_order[$i] = $tmp_version; 1498 } 1499 $i++; 1500 } 1501 ksort($snmp_version_order); 1502 1503 foreach ($snmp_version_order as $tmp_version) 1504 { 1505 $ret = add_device($hostname, $tmp_version, $snmp_port, $snmp_transport, $options); 1506 if ($ret === FALSE) 1507 { 1508 // Set $options['break'] for break recursive 1509 $options['break'] = TRUE; 1510 } 1511 else if (is_numeric($ret) && $ret != 0) 1512 { 1513 return $ret; 1514 } 1515 } 1516 } 1517 else if ($snmp_version === "v3") 1518 { 1519 // Try each set of parameters from config 1520 foreach ($config['snmp']['v3'] as $auth_iter => $snmp_extra) 1521 { 1522 // Append SNMP context if passed 1523 if (isset($options['snmp_context']) && strlen($options['snmp_context'])) 1524 { 1525 $snmp_extra['snmp_context'] = $options['snmp_context']; 1526 } 1527 1528 $device = build_initial_device_array($hostname, NULL, $snmp_version, $snmp_port, $snmp_transport, $snmp_extra); 1529 1530 if ($config['snmp']['hide_auth'] && OBS_DEBUG < 2) 1531 { 1532 // Hide snmp auth 1533 print_message("Trying v3 parameters *** / ### [$auth_iter] ... "); 1534 } else { 1535 print_message("Trying v3 parameters " . $device['snmp_authname'] . "/" . $device['snmp_authlevel'] . " ... "); 1536 } 1537 1538 if (isSNMPable($device)) 1539 { 1540 if (!check_device_duplicated($device)) 1541 { 1542 if (isset($options['test']) && $options['test']) 1543 { 1544 print_message('%WDevice "'.$hostname.'" has successfully been tested and available by '.strtoupper($snmp_transport).' transport with SNMP '.$snmp_version.' credentials.%n', 'color'); 1545 $device_id = -1; 1546 } else { 1547 $device_id = createHost($hostname, NULL, $snmp_version, $snmp_port, $snmp_transport, $snmp_extra); 1548 if ($options['ping_skip']) 1549 { 1550 set_entity_attrib('device', $device_id, 'ping_skip', 1); 1551 // Force pingable check 1552 if (isPingable($hostname, $flags ^ OBS_PING_SKIP)) 1553 { 1554 print_warning("You passed the option the skip device ICMP echo pingable checks, but device responds to ICMP echo. Please check device preferences."); 1555 } 1556 } 1557 } 1558 return $device_id; 1559 } else { 1560 // When detected duplicate device, this mean it already SNMPable and not need check next auth! 1561 return FALSE; 1562 } 1563 } else { 1564 print_warning("No reply on credentials " . $device['snmp_authname'] . "/" . $device['snmp_authlevel'] . " using $snmp_version."); 1565 } 1566 } 1567 } 1568 else if ($snmp_version === "v2c" || $snmp_version === "v1") 1569 { 1570 // Try each community from config 1571 foreach ($config['snmp']['community'] as $auth_iter => $snmp_community) 1572 { 1573 // Append SNMP context if passed 1574 $snmp_extra = array(); 1575 if (isset($options['snmp_context']) && strlen($options['snmp_context'])) 1576 { 1577 $snmp_extra['snmp_context'] = $options['snmp_context']; 1578 } 1579 $device = build_initial_device_array($hostname, $snmp_community, $snmp_version, $snmp_port, $snmp_transport, $snmp_extra); 1580 1581 if ($config['snmp']['hide_auth'] && OBS_DEBUG < 2) 1582 { 1583 // Hide snmp auth 1584 print_message("Trying $snmp_version community *** [$auth_iter] ..."); 1585 } else { 1586 print_message("Trying $snmp_version community $snmp_community ..."); 1587 } 1588 1589 if (isSNMPable($device)) 1590 { 1591 if (!check_device_duplicated($device)) 1592 { 1593 if (isset($options['test']) && $options['test']) 1594 { 1595 print_message('%WDevice "'.$hostname.'" has successfully been tested and available by '.strtoupper($snmp_transport).' transport with SNMP '.$snmp_version.' credentials.%n', 'color'); 1596 $device_id = -1; 1597 } else { 1598 $device_id = createHost($hostname, $snmp_community, $snmp_version, $snmp_port, $snmp_transport, $snmp_extra); 1599 if ($options['ping_skip']) 1600 { 1601 set_entity_attrib('device', $device_id, 'ping_skip', 1); 1602 // Force pingable check 1603 if (isPingable($hostname, $flags ^ OBS_PING_SKIP)) 1604 { 1605 print_warning("You passed the option the skip device ICMP echo pingable checks, but device responds to ICMP echo. Please check device preferences."); 1606 } 1607 } 1608 } 1609 return $device_id; 1610 } else { 1611 // When detected duplicate device, this mean it already SNMPable and not need check next auth! 1612 return FALSE; 1613 } 1614 } else { 1615 if ($config['snmp']['hide_auth'] && OBS_DEBUG < 2) 1616 { 1617 print_warning("No reply on given community *** using $snmp_version."); 1618 } else { 1619 print_warning("No reply on community $snmp_community using $snmp_version."); 1620 } 1621 $return = 0; // Return zero for continue trying next auth 1622 } 1623 } 1624 } else { 1625 print_error("Unsupported SNMP Version \"$snmp_version\"."); 1626 $return = 0; // Return zero for continue trying next auth 1627 } 1628 1629 if (!$device_id) 1630 { 1631 // Failed SNMP 1632 print_error("Could not reach $hostname with given SNMP parameters using $snmp_version."); 1633 $return = 0; // Return zero for continue trying next auth 1634 } 1635 } else { 1636 // failed Reachability 1637 print_error("Could not ping $hostname."); 1638 } 1639 } else { 1640 // Failed DNS lookup 1641 print_error("Could not resolve $hostname."); 1642 } 1643 } else { 1644 // found in database 1645 print_error("Already got device $hostname."); 1646 } 1647 1648 return $return; 1649} 1650 1651/** 1652 * Check duplicated devices in DB by sysName, snmpEngineID and entPhysicalSerialNum (if possible) 1653 * 1654 * If found duplicate devices return TRUE, in other cases return FALSE 1655 * 1656 * @param array $device Device array which should be checked for duplicates 1657 * @return bool TRUE if duplicates found 1658 */ 1659// TESTME needs unit testing 1660function check_device_duplicated($device) 1661{ 1662 // Hostname should be uniq 1663 if ($device['hostname'] && 1664 //dbFetchCell("SELECT COUNT(*) FROM `devices` WHERE `hostname` = ?", array($device['hostname'])) != '0') 1665 dbExist('devices', '`hostname` = ?', array($device['hostname']))) 1666 { 1667 // Retun TRUE if have device with same hostname in DB 1668 print_error("Already got device with hostname (".$device['hostname'].")."); 1669 return TRUE; 1670 } 1671 1672 $snmpEngineID = snmp_cache_snmpEngineID($device); 1673 $sysName = snmp_get_oid($device, 'sysName.0', 'SNMPv2-MIB'); 1674 if (empty($sysName) || strpos($sysName, '.') === FALSE) 1675 { 1676 $sysName = FALSE; 1677 } else{ 1678 // sysName stored in db as lowercase, always compare as lowercase too! 1679 $sysName = strtolower($sysName); 1680 } 1681 1682 if (!empty($snmpEngineID)) 1683 { 1684 $test_devices = dbFetchRows('SELECT * FROM `devices` WHERE `disabled` = 0 AND `snmpEngineID` = ?', array($snmpEngineID)); 1685 foreach ($test_devices as $test) 1686 { 1687 $compare = strtolower($test['sysName']) === $sysName; 1688 if ($compare) 1689 { 1690 // Last check (if possible) serial, for cluster devices sysName and snmpEngineID same 1691 $test_entPhysical = dbFetchRow('SELECT * FROM `entPhysical` WHERE `device_id` = ? AND `entPhysicalSerialNum` != ? ORDER BY `entPhysicalClass` LIMIT 1', array($test['device_id'], '')); 1692 if (isset($test_entPhysical['entPhysicalSerialNum'])) 1693 { 1694 $serial = snmp_get_oid($device, 'entPhysicalSerialNum.'.$test_entPhysical['entPhysicalIndex'], 'ENTITY-MIB'); 1695 $compare = strtolower($serial) == strtolower($test_entPhysical['entPhysicalSerialNum']); 1696 if ($compare) 1697 { 1698 // This devices really same, with same sysName, snmpEngineID and entPhysicalSerialNum 1699 print_error("Already got device with SNMP-read sysName ($sysName), 'snmpEngineID' = $snmpEngineID and 'entPhysicalSerialNum' = $serial (".$test['hostname'].")."); 1700 return TRUE; 1701 } 1702 } else { 1703 // Return TRUE if have same snmpEngineID && sysName in DB 1704 print_error("Already got device with SNMP-read sysName ($sysName) and 'snmpEngineID' = $snmpEngineID (".$test['hostname'].")."); 1705 return TRUE; 1706 } 1707 } 1708 } 1709 } else { 1710 // If snmpEngineID empty, check only by sysName 1711 $test_devices = dbFetchRows('SELECT * FROM `devices` WHERE `disabled` = 0 AND `sysName` = ?', array($sysName)); 1712 if ($sysName !== FALSE && is_array($test_devices) && count($test_devices) > 0) 1713 { 1714 $has_serial = FALSE; 1715 foreach ($test_devices as $test) 1716 { 1717 // Last check (if possible) serial, for cluster devices sysName and snmpEngineID same 1718 $test_entPhysical = dbFetchRow('SELECT * FROM `entPhysical` WHERE `device_id` = ? AND `entPhysicalSerialNum` != ? ORDER BY `entPhysicalClass` LIMIT 1', array($test['device_id'], '')); 1719 if (isset($test_entPhysical['entPhysicalSerialNum'])) 1720 { 1721 $serial = snmp_get_oid($device, "entPhysicalSerialNum.".$test_entPhysical['entPhysicalIndex'], "ENTITY-MIB"); 1722 $compare = strtolower($serial) == strtolower($test_entPhysical['entPhysicalSerialNum']); 1723 if ($compare) 1724 { 1725 // This devices really same, with same sysName, snmpEngineID and entPhysicalSerialNum 1726 print_error("Already got device with SNMP-read sysName ($sysName) and 'entPhysicalSerialNum' = $serial (".$test['hostname'].")."); 1727 return TRUE; 1728 } 1729 $has_serial = TRUE; 1730 } 1731 } 1732 if (!$has_entPhysical) 1733 { 1734 // Return TRUE if have same sysName in DB 1735 print_error("Already got device with SNMP-read sysName ($sysName)."); 1736 return TRUE; 1737 } 1738 } 1739 } 1740 1741 // In all other cases return FALSE 1742 return FALSE; 1743} 1744 1745// DOCME needs phpdoc block 1746// TESTME needs unit testing 1747// MOVEME to includes/common.inc.php 1748function scanUDP($host, $port, $timeout) 1749{ 1750 $handle = fsockopen($host, $port, $errno, $errstr, 2); 1751 socket_set_timeout($handle, $timeout); 1752 $write = fwrite($handle,"\x00"); 1753 if (!$write) { next; } 1754 $startTime = time(); 1755 $header = fread($handle, 1); 1756 $endTime = time(); 1757 $timeDiff = $endTime - $startTime; 1758 1759 if ($timeDiff >= $timeout) 1760 { 1761 fclose($handle); return 1; 1762 } else { fclose($handle); return 0; } 1763} 1764 1765// DOCME needs phpdoc block 1766// TESTME needs unit testing 1767function build_initial_device_array($hostname, $snmp_community, $snmp_version, $snmp_port = 161, $snmp_transport = 'udp', $snmp_extra = array()) 1768{ 1769 $device = array(); 1770 $device['hostname'] = $hostname; 1771 $device['snmp_port'] = $snmp_port; 1772 $device['snmp_transport'] = $snmp_transport; 1773 $device['snmp_version'] = $snmp_version; 1774 1775 if ($snmp_version === "v2c" || $snmp_version === "v1") 1776 { 1777 $device['snmp_community'] = $snmp_community; 1778 } 1779 else if ($snmp_version == "v3") 1780 { 1781 $device['snmp_authlevel'] = $snmp_extra['authlevel']; 1782 $device['snmp_authname'] = $snmp_extra['authname']; 1783 $device['snmp_authpass'] = $snmp_extra['authpass']; 1784 $device['snmp_authalgo'] = $snmp_extra['authalgo']; 1785 $device['snmp_cryptopass'] = $snmp_extra['cryptopass']; 1786 $device['snmp_cryptoalgo'] = $snmp_extra['cryptoalgo']; 1787 } 1788 // Append SNMP context if passed 1789 if (isset($snmp_extra['snmp_context']) && strlen($snmp_extra['snmp_context'])) 1790 { 1791 $device['snmp_context'] = $snmp_extra['snmp_context']; 1792 } 1793 1794 print_debug_vars($device); 1795 1796 return $device; 1797} 1798 1799// DOCME needs phpdoc block 1800// TESTME needs unit testing 1801// MOVEME to includes/common.inc.php 1802function netmask2cidr($netmask) 1803{ 1804 $addr = Net_IPv4::parseAddress("1.2.3.4/$netmask"); 1805 return $addr->bitmask; 1806} 1807 1808// DOCME needs phpdoc block 1809// TESTME needs unit testing 1810// MOVEME to includes/common.inc.php 1811function cidr2netmask($cidr) 1812{ 1813 return (long2ip(ip2long("255.255.255.255") << (32-$cidr))); 1814} 1815 1816// Detect SNMP auth params without adding device by hostname or IP 1817// if SNMP auth detected return array with auth params or FALSE if not detected 1818// DOCME needs phpdoc block 1819// TESTME needs unit testing 1820function detect_device_snmpauth($hostname, $snmp_port = 161, $snmp_transport = 'udp', $detect_ip_version = FALSE) 1821{ 1822 global $config; 1823 1824 // Additional checks for IP version 1825 if ($detect_ip_version) 1826 { 1827 $ip_version = get_ip_version($hostname); 1828 if (!$ip_version) 1829 { 1830 $ip = gethostbyname6($hostname); 1831 $ip_version = get_ip_version($ip); 1832 } 1833 // Detect snmp transport 1834 if (stripos($snmp_transport, 'tcp') !== FALSE) 1835 { 1836 $snmp_transport = ($ip_version == 4 ? 'tcp' : 'tcp6'); 1837 } else { 1838 $snmp_transport = ($ip_version == 4 ? 'udp' : 'udp6'); 1839 } 1840 } 1841 // Detect snmp port 1842 if (!is_numeric($snmp_port) || $snmp_port < 1 || $snmp_port > 65535) 1843 { 1844 $snmp_port = 161; 1845 } else { 1846 $snmp_port = (int)$snmp_port; 1847 } 1848 1849 // Here set default snmp version order 1850 $i = 1; 1851 $snmp_version_order = array(); 1852 foreach (array('v2c', 'v3', 'v1') as $tmp_version) 1853 { 1854 if ($config['snmp']['version'] == $tmp_version) 1855 { 1856 $snmp_version_order[0] = $tmp_version; 1857 } else { 1858 $snmp_version_order[$i] = $tmp_version; 1859 } 1860 $i++; 1861 } 1862 ksort($snmp_version_order); 1863 1864 foreach ($snmp_version_order as $snmp_version) 1865 { 1866 if ($snmp_version === 'v3') 1867 { 1868 // Try each set of parameters from config 1869 foreach ($config['snmp']['v3'] as $auth_iter => $snmp_v3) 1870 { 1871 $device = build_initial_device_array($hostname, NULL, $snmp_version, $snmp_port, $snmp_transport, $snmp_v3); 1872 1873 if ($config['snmp']['hide_auth'] && OBS_DEBUG < 2) 1874 { 1875 // Hide snmp auth 1876 print_message("Trying v3 parameters *** / ### [$auth_iter] ... "); 1877 } else { 1878 print_message("Trying v3 parameters " . $device['snmp_authname'] . "/" . $device['snmp_authlevel'] . " ... "); 1879 } 1880 1881 if (isSNMPable($device)) 1882 { 1883 return $device; 1884 } else { 1885 if ($config['snmp']['hide_auth'] && OBS_DEBUG < 2) 1886 { 1887 print_warning("No reply on credentials *** / ### using $snmp_version."); 1888 } else { 1889 print_warning("No reply on credentials " . $device['snmp_authname'] . "/" . $device['snmp_authlevel'] . " using $snmp_version."); 1890 } 1891 } 1892 } 1893 } else { // if ($snmp_version === "v2c" || $snmp_version === "v1") 1894 // Try each community from config 1895 foreach ($config['snmp']['community'] as $auth_iter => $snmp_community) 1896 { 1897 $device = build_initial_device_array($hostname, $snmp_community, $snmp_version, $snmp_port, $snmp_transport); 1898 1899 if ($config['snmp']['hide_auth'] && OBS_DEBUG < 2) 1900 { 1901 // Hide snmp auth 1902 print_message("Trying $snmp_version community *** [$auth_iter] ..."); 1903 } else { 1904 print_message("Trying $snmp_version community $snmp_community ..."); 1905 } 1906 1907 if (isSNMPable($device)) 1908 { 1909 return $device; 1910 } else { 1911 print_warning("No reply on community $snmp_community using $snmp_version."); 1912 } 1913 } 1914 } 1915 } 1916 1917 return FALSE; 1918} 1919 1920/** 1921 * Checks device availability by snmp query common oids 1922 * 1923 * @param array $device Device array 1924 * @return float SNMP query runtime in milliseconds 1925 */ 1926// TESTME needs unit testing 1927function isSNMPable($device) 1928{ 1929 if (isset($device['os'][0]) && isset($GLOBALS['config']['os'][$device['os']]['snmpable']) && $device['os'] != 'generic') 1930 { 1931 // Known device os, and defined custom snmpable OIDs 1932 $pos = snmp_get_multi_oid($device, $GLOBALS['config']['os'][$device['os']]['snmpable'], array(), 'SNMPv2-MIB', NULL, OBS_SNMP_ALL_NUMERIC); 1933 $count = count($pos); 1934 } else { 1935 // Normal checks by sysObjectID and sysUpTime 1936 $pos = snmp_get_multi_oid($device, 'sysObjectID.0 sysUpTime.0', array(), 'SNMPv2-MIB'); 1937 $count = count($pos[0]); 1938 1939 if ($count === 0 && (empty($device['os']) || !isset($GLOBALS['config']['os'][$device['os']]))) 1940 { 1941 // New device (or os changed) try to all snmpable OIDs 1942 foreach (array_chunk($GLOBALS['config']['os']['generic']['snmpable'], 3) as $snmpable) 1943 { 1944 $pos = snmp_get_multi_oid($device, $snmpable, array(), 'SNMPv2-MIB', NULL, OBS_SNMP_ALL_NUMERIC); 1945 if ($count = count($pos)) { break; } // stop foreach on first oids set 1946 } 1947 } 1948 } 1949 1950 if ($GLOBALS['snmp_status'] && $count > 0) 1951 { 1952 // SNMP response time in milliseconds. 1953 $time_snmp = $GLOBALS['exec_status']['runtime'] * 1000; 1954 $time_snmp = number_format($time_snmp, 2, '.', ''); 1955 return $time_snmp; 1956 } 1957 1958 return 0; 1959} 1960 1961/** 1962 * Checks device availability by icmp echo response 1963 * If flag OBS_PING_SKIP passed, pings skipped and returns 0.001 (1ms) 1964 * 1965 * @param string $hostname Device hostname or IP address 1966 * @param int Flags. Supported OBS_DNS_A, OBS_DNS_AAAA and OBS_PING_SKIP 1967 * @return float Average response time for used retries count (default retries is 3) 1968 */ 1969function isPingable($hostname, $flags = OBS_DNS_ALL) 1970{ 1971 global $config; 1972 1973 $ping_debug = isset($config['ping']['debug']) && $config['ping']['debug']; 1974 $try_a = is_flag_set(OBS_DNS_A, $flags); 1975 1976 set_status_var('ping_dns', 'ok'); // Set initially dns status as ok 1977 if (is_flag_set(OBS_PING_SKIP, $flags)) 1978 { 1979 return 0.001; // Ping is skipped, just return 1ms 1980 } 1981 1982 // Timeout, default is 500ms (as in fping) 1983 $timeout = (isset($config['ping']['timeout']) ? (int)$config['ping']['timeout'] : 500); 1984 if ($timeout < 50) { $timeout = 50; } 1985 else if ($timeout > 2000) { $timeout = 2000; } 1986 1987 // Retries, default is 3 1988 $retries = (isset($config['ping']['retries']) ? (int)$config['ping']['retries'] : 3); 1989 if ($retries < 1) { $retries = 3; } 1990 else if ($retries > 10) { $retries = 10; } 1991 1992 if ($ip_version = get_ip_version($hostname)) 1993 { 1994 // Ping by IP 1995 if ($ip_version === 6) 1996 { 1997 $cmd = $config['fping6'] . " -t $timeout -c 1 -q $hostname 2>&1"; 1998 } else { 1999 if (!$try_a) 2000 { 2001 if ($ping_debug) { logfile('debug.log', __FUNCTION__ . "() | DEVICE: $hostname | Passed IPv4 address but device use IPv6 transport"); } 2002 print_debug('Into function ' . __FUNCTION__ . '() passed IPv4 address ('.$hostname.'but device use IPv6 transport'); 2003 set_status_var('ping_dns', 'incorrect'); // Incorrect 2004 return 0; 2005 } 2006 // Forced check for actual IPv4 address 2007 $cmd = $config['fping'] . " -t $timeout -c 1 -q $hostname 2>&1"; 2008 } 2009 } else { 2010 // First try IPv4 2011 $ip = ($try_a ? gethostbyname($hostname) : FALSE); // Do not check IPv4 if transport IPv6 2012 if ($ip && $ip != $hostname) 2013 { 2014 $cmd = $config['fping'] . " -t $timeout -c 1 -q $ip 2>&1"; 2015 } else { 2016 $ip = gethostbyname6($hostname, OBS_DNS_AAAA); 2017 // Second try IPv6 2018 if ($ip) 2019 { 2020 $cmd = $config['fping6'] . " -t $timeout -c 1 -q $ip 2>&1"; 2021 } else { 2022 // No DNS records 2023 if ($ping_debug) { logfile('debug.log', __FUNCTION__ . "() | DEVICE: $hostname | NO DNS record found"); } 2024 set_status_var('ping_dns', 'alert'); 2025 return 0; 2026 } 2027 } 2028 } 2029 2030 // Sleep interval between retries, max 1 sec, min 333ms (1s/3), 2031 // next retry will increase interval by 1.5 Backoff factor (see fping -B option) 2032 // We not use fping native retries, because fping waiting for all responses, but we wait only first OK 2033 $sleep = floor(1000000 / $retries); 2034 if ($sleep < 333000) { $sleep = 333000; } 2035 2036 $ping = 0; // Init false 2037 for ($i=1; $i <= $retries; $i++) 2038 { 2039 $output = external_exec($cmd); 2040 if ($GLOBALS['exec_status']['exitcode'] === 0) 2041 { 2042 // normal $output = '8.8.8.8 : xmt/rcv/%loss = 1/1/0%, min/avg/max = 1.21/1.21/1.21' 2043 $tmp = explode('/', $output); 2044 $ping = $tmp[7]; // Avg 2045 if (!$ping) { $ping = 0.001; } // Protection from zero (exclude false status) 2046 } 2047 if ($ping) { break; } 2048 2049 if ($ping_debug) 2050 { 2051 $ping_times = format_unixtime($GLOBALS['exec_status']['endtime'] - $GLOBALS['exec_status']['runtime'], 'H:i:s.v') . ', ' . round($GLOBALS['exec_status']['runtime'], 3) . 's'; 2052 logfile('debug.log', __FUNCTION__ . "() | DEVICE: $hostname | $ping_times | FPING OUT ($i): " . $output); 2053 //log_event(__FUNCTION__ . "() | DEVICE: $hostname | FPING OUT ($i): " . $output, $device_id, 'device', $device_id, 7); // severity 7, debug 2054 // WARNING, this is very long operation, increase polling time up to 10s 2055 if ($i == $retries && is_file($config['mtr'])) 2056 { 2057 $mtr = $config['mtr'] . " -r -n -c 3 $ip"; 2058 logfile('debug.log', __FUNCTION__ . "() | DEVICE: $hostname | MTR OUT:\n" . external_exec($mtr)); 2059 } 2060 } 2061 2062 if ($i < $retries) 2063 { 2064 // Sleep and increase next sleep interval 2065 usleep($sleep); 2066 $sleep *= 1.5; // Backoff factor 1.5 2067 } 2068 } 2069 2070 return $ping; 2071} 2072 2073// DOCME needs phpdoc block 2074// TESTME needs unit testing 2075// MOVEME to includes/common.inc.php 2076function is_odd($number) 2077{ 2078 return $number & 1; // 0 = even, 1 = odd 2079} 2080 2081// TESTME needs unit testing 2082/** 2083 * Add device into database 2084 * 2085 * @param string $hostname Device hostname 2086 * @param string $snmp_community SNMP v1/v2c community 2087 * @param string $snmp_version SNMP version (v1, v2c, v3) 2088 * @param int $snmp_port SNMP port (default: 161) 2089 * @param string $snmp_transport SNMP transport (udp, udp6, tcp, tcp6) 2090 * @param array $snmp_extra SNMP v3 auth params and/or SNMP context 2091 * 2092 * @return bool|string 2093 */ 2094function createHost($hostname, $snmp_community = NULL, $snmp_version, $snmp_port = 161, $snmp_transport = 'udp', $snmp_extra = array()) 2095{ 2096 $hostname = trim(strtolower($hostname)); 2097 2098 $device = array('hostname' => $hostname, 2099 'sysName' => $hostname, 2100 'status' => '1', 2101 'snmp_community' => $snmp_community, 2102 'snmp_port' => $snmp_port, 2103 'snmp_transport' => $snmp_transport, 2104 'snmp_version' => $snmp_version 2105 ); 2106 2107 // Add snmp v3 auth params & snmp context 2108 foreach (array('authlevel', 'authname', 'authpass', 'authalgo', 'cryptopass', 'cryptoalgo', 'context') as $v3_key) 2109 { 2110 if (isset($snmp_extra['snmp_'.$v3_key])) 2111 { 2112 // Or $snmp_v3['snmp_authlevel'] 2113 $device['snmp_'.$v3_key] = $snmp_extra['snmp_'.$v3_key]; 2114 } 2115 else if (isset($snmp_extra[$v3_key])) 2116 { 2117 // Or $snmp_v3['authlevel'] 2118 $device['snmp_'.$v3_key] = $snmp_extra[$v3_key]; 2119 } 2120 } 2121 2122 $device['os'] = get_device_os($device); 2123 $device['snmpEngineID'] = snmp_cache_snmpEngineID($device); 2124 $device['sysName'] = snmp_get_oid($device, 'sysName.0', 'SNMPv2-MIB'); 2125 $device['location'] = snmp_get_oid($device, 'sysLocation.0', 'SNMPv2-MIB'); 2126 $device['sysContact'] = snmp_get_oid($device, 'sysContact.0', 'SNMPv2-MIB'); 2127 2128 if(isset($GLOBALS['config']['poller_name'])) 2129 { 2130 $poller_id = dbFetchCell("SELECT `poller_id` FROM `pollers` WHERE `poller_name` = ?", array($GLOBALS['config']['poller_name'])); 2131 2132 if(is_numeric($poller_id)) 2133 { 2134 $device['poller_id'] = $poller_id; 2135 } 2136 } 2137 2138 if ($device['os']) 2139 { 2140 $device_id = dbInsert($device, 'devices'); 2141 if ($device_id) 2142 { 2143 log_event("Device added: $hostname", $device_id, 'device', $device_id, 5); // severity 5, for logging user/console info 2144 if (is_cli()) 2145 { 2146 print_success("Now discovering ".$device['hostname']." (id = ".$device_id.")"); 2147 $device['device_id'] = $device_id; 2148 // Discover things we need when linking this to other hosts. 2149 discover_device($device, $options = array('m' => 'ports')); 2150 discover_device($device, $options = array('m' => 'ipv4-addresses')); 2151 discover_device($device, $options = array('m' => 'ipv6-addresses')); 2152 log_event("snmpEngineID -> ".$device['snmpEngineID'], $device, 'device', $device['device_id']); 2153 // Reset `last_discovered` for full rediscover device by cron 2154 dbUpdate(array('last_discovered' => 'NULL'), 'devices', '`device_id` = ?', array($device_id)); 2155 array_push($GLOBALS['devices'], $device_id); // FIXME, seems as $devices var not used anymore 2156 } 2157 2158 // Request for clear WUI cache 2159 set_cache_clear('wui'); 2160 2161 return($device_id); 2162 } else { 2163 return FALSE; 2164 } 2165 } else { 2166 return FALSE; 2167 } 2168} 2169 2170// Allows overwriting elements of an array of OIDs with replacement values from a private MIB. 2171// Currently used by ports to replace OIDs with private ports tables. 2172 2173function merge_private_mib(&$device, $entity_type, $mib, &$entity_stats, $limit_oids = NULL) 2174{ 2175 2176 global $config; 2177 2178 foreach ($config['mibs'][$mib][$entity_type] as $table => $def) 2179 { 2180 2181 if (isset($def['oids']) && is_array($def['oids'])) 2182 { 2183 2184 print_cli_data_field($mib); 2185 echo $table . ' '; 2186 2187 $walked = array(); 2188 2189 // Walk to $entity_tmp, link to $entity_stats if we're not rewriting 2190 if (isset($def['map'])) 2191 { 2192 $entity_tmp = array(); 2193 if (isset($def['map']['oid'])) 2194 { 2195 echo $def['map']['oid'] . ' '; 2196 $entity_tmp = snmpwalk_cache_oid($device, $def['map']['oid'], $entity_tmp, $mib); 2197 2198 // Skip next Oids walk if no response 2199 if (!snmp_status()) { continue; } 2200 2201 $walked[] = $def['map']['oid']; 2202 } 2203 foreach ((array)$def['map']['oid_extra'] as $oid) 2204 { 2205 echo $oid . ' '; 2206 $entity_tmp = snmpwalk_cache_oid($device, $oid, $entity_tmp, $mib); 2207 $walked[] = $oid; 2208 } 2209 } else { 2210 $entity_tmp = &$entity_stats; 2211 } 2212 2213 // Populated $entity_tmp 2214 foreach ($def['oids'] as $oid => $entry) 2215 { 2216 // Skip if there's an OID list and we're not on it. 2217 if (isset($limit_oids) && !in_array($oid, $limit_oids)) { continue; } 2218 2219 // If this OID is being used twice, don't walk it again. 2220 if (!isset($entry['oid']) || in_array($entry['oid'], $walked)) { continue; } 2221 2222 echo $entry['oid'] . ' '; 2223 $flags = isset($entry['snmp_flags']) ? $entry['snmp_flags'] : OBS_SNMP_ALL; 2224 $entity_tmp = snmpwalk_cache_oid($device, $entry['oid'], $entity_tmp, $mib, NULL, $flags); 2225 2226 // Skip next Oids walk if no response 2227 if (!snmp_status() && $oid == array_key_first($def['oids'])) { continue 2; } 2228 2229 $walked[] = $entry['oid']; 2230 } 2231 2232 // Rewrite indexes using map from $entity_tmp to $entity_stats 2233 if (isset($def['map'])) 2234 { 2235 $entity_new = array(); 2236 $map_tmp = array(); 2237 // Generate mapping list 2238 if (isset($def['map']['index'])) 2239 { 2240 // Index by tags 2241 foreach ($entity_tmp as $index => $entry) 2242 { 2243 $entry['index'] = $index; 2244 $map_index = array_tag_replace($entry, $def['map']['index']); 2245 $map_tmp[$index] = $map_index; 2246 } 2247 } else { 2248 // Mapping by Oid 2249 foreach ($entity_stats as $index => $entry) 2250 { 2251 if (isset($entry[$def['map']['map']])) 2252 { 2253 $map_tmp[$entry[$def['map']['map']]] = $index; 2254 } 2255 } 2256 } 2257 2258 foreach ($entity_tmp as $index => $entry) 2259 { 2260 if (isset($map_tmp[$index])) 2261 { 2262 foreach ($entry as $oid => $value) 2263 { 2264 $entity_new[$map_tmp[$index]][$oid] = $value; 2265 } 2266 } 2267 } 2268 } else { 2269 $entity_new = $entity_tmp; 2270 } 2271 2272 echo '['; // start change list 2273 2274 foreach ($entity_new as $index => $port) 2275 { 2276 foreach ($def['oids'] as $oid => $entry) 2277 { 2278 // Skip if there's an OID list and we're not on it. 2279 if (isset($limit_oids) && !in_array($oid, $limit_oids)) 2280 { 2281 continue; 2282 } 2283 2284 $mib_oid = $entry['oid']; 2285 if (isset($entry['oid']) && isset($port[$mib_oid])) 2286 { 2287 $entity_stats[$index][$oid] = $port[$mib_oid]; 2288 2289 if (isset($entry['transformations'])) 2290 { 2291 // Translate to standard IF-MIB values 2292 $entity_stats[$index][$oid] = string_transform($entity_stats[$index][$oid], $entry['transformations']); 2293 echo 'T'; 2294 } 2295 2296 echo '.'; 2297 } 2298 else if (isset($entry['value'])) 2299 { 2300 // Set fixed value 2301 $entity_stats[$index][$oid] = $entry['value']; 2302 } 2303 else 2304 { 2305 echo '!'; 2306 } 2307 } 2308 } 2309 echo ']'; // end change list 2310 echo PHP_EOL; // end CLI DATA FIELD 2311 } 2312 } 2313 return TRUE; 2314} 2315 2316// BOOLEAN safe function to check if hostname resolves as IPv4 or IPv6 address 2317// DOCME needs phpdoc block 2318// TESTME needs unit testing 2319// MOVEME to includes/common.inc.php 2320function isDomainResolves($hostname, $flags = OBS_DNS_ALL) 2321{ 2322 return (TRUE && gethostbyname6($hostname, $flags)); 2323} 2324 2325/** 2326 * Returns IP version for string or FALSE if string not an IP 2327 * 2328 * Examples: 2329 * get_ip_version('127.0.0.1') === 4 2330 * get_ip_version('::1') === 6 2331 * get_ip_version('my_hostname') === FALSE 2332 * 2333 * @param sting $address IP address string 2334 * @return mixed IP version or FALSE if passed incorrect address 2335 */ 2336function get_ip_version($address) 2337{ 2338 $address_version = FALSE; 2339 if (strpos($address, '/') !== FALSE) 2340 { 2341 // Dump condition, 2342 // IPs with CIDR not correct for us here 2343 } 2344 //else if (strpos($address, '.') !== FALSE && Net_IPv4::validateIP($address)) 2345 else if (preg_match('%^'.OBS_PATTERN_IPV4.'$%', $address)) 2346 { 2347 $address_version = 4; 2348 } 2349 //else if (strpos($address, ':') !== FALSE && Net_IPv6::checkIPv6($address)) 2350 else if (preg_match('%^'.OBS_PATTERN_IPV6.'$%i', $address)) 2351 { 2352 $address_version = 6; 2353 } 2354 return $address_version; 2355} 2356 2357/** 2358 * Check if a given IPv4 address (and prefix length) is valid. 2359 * 2360 * @param string $ipv4_address IPv4 Address 2361 * @param string $ipv4_prefixlen IPv4 Prefix length (optional, either 24 or 255.255.255.0) 2362 * 2363 * @return bool Returns TRUE if address is valid, FALSE if not valid. 2364 */ 2365// TESTME needs unit testing 2366function is_ipv4_valid($ipv4_address, $ipv4_prefixlen = NULL) 2367{ 2368 2369 if (str_contains($ipv4_address, '/')) 2370 { 2371 list($ipv4_address, $ipv4_prefixlen) = explode('/', $ipv4_address); 2372 } 2373 $ip_full = $ipv4_address . '/' . $ipv4_prefixlen; 2374 2375 // False if invalid IPv4 syntax 2376 if (strlen($ipv4_prefixlen) && 2377 !preg_match('%^'.OBS_PATTERN_IPV4_NET.'$%', $ip_full)) 2378 { 2379 // Address with prefix 2380 return FALSE; 2381 } 2382 else if (!preg_match('%^'.OBS_PATTERN_IPV4.'$%i', $ipv4_address)) 2383 { 2384 // Address withot prefix 2385 return FALSE; 2386 } 2387 2388 $ipv4_type = get_ip_type($ip_full); 2389 2390 // False if link-local, unspecified or any used defined 2391 if (in_array($ipv4_type, $GLOBALS['config']['ip-address']['ignore_type'])) 2392 { 2393 return FALSE; 2394 } 2395 2396 return TRUE; 2397} 2398 2399/** 2400 * Check if a given IPv6 address (and prefix length) is valid. 2401 * Link-local addresses are considered invalid. 2402 * 2403 * @param string $ipv6_address IPv6 Address 2404 * @param string $ipv6_prefixlen IPv6 Prefix length (optional) 2405 * 2406 * @return bool Returns TRUE if address is valid, FALSE if not valid. 2407 */ 2408// TESTME needs unit testing 2409function is_ipv6_valid($ipv6_address, $ipv6_prefixlen = NULL) 2410{ 2411 if (str_contains($ipv6_address, '/')) 2412 { 2413 list($ipv6_address, $ipv6_prefixlen) = explode('/', $ipv6_address); 2414 } 2415 $ip_full = $ipv6_address . '/' . $ipv6_prefixlen; 2416 2417 // False if invalid IPv6 syntax 2418 if (strlen($ipv6_prefixlen) && 2419 !preg_match('%^'.OBS_PATTERN_IPV6_NET.'$%i', $ip_full)) 2420 { 2421 // Address with prefix 2422 return FALSE; 2423 } 2424 else if (!preg_match('%^'.OBS_PATTERN_IPV6.'$%i', $ipv6_address)) 2425 { 2426 // Address withot prefix 2427 return FALSE; 2428 } 2429 2430 $ipv6_type = get_ip_type($ip_full); 2431 2432 // False if link-local, unspecified or any used defined 2433 if (in_array($ipv6_type, $GLOBALS['config']['ip-address']['ignore_type'])) 2434 { 2435 return FALSE; 2436 } 2437 2438 return TRUE; 2439} 2440 2441/** 2442 * Detect IP type. 2443 * Based on https://www.ripe.net/manage-ips-and-asns/ipv6/ipv6-address-types/ipv6-address-types 2444 * 2445 * Known types: 2446 * - unspecified : ::/128, 0.0.0.0 2447 * - loopback : ::1/128, 127.0.0.1 2448 * - ipv4mapped : only for IPv6 ::ffff/96 (::ffff:192.0.2.47) 2449 * - private : fc00::/7 (fdf8:f53b:82e4::53), 2450 * 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 2451 * - link-local : fe80::/10 (fe80::200:5aee:feaa:20a2), 2452 * 169.254.0.0/16 2453 * - teredo : only for IPv6 2001:0000::/32 (2001:0000:4136:e378:8000:63bf:3fff:fdd2) 2454 * - benchmark : 2001:0002::/48 (2001:0002:6c::430), 2455 * 198.18.0.0/15 2456 * - orchid : only for IPv6 2001:0010::/28 (2001:10:240:ab::a) 2457 * - 6to4 : 2002::/16 (2002:cb0a:3cdd:1::1), 2458 * 192.88.99.0/24 2459 * - documentation : 2001:db8::/32 (2001:db8:8:4::2), 2460 * 192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24 2461 * - global-unicast : only for IPv6 2000::/3 2462 * - multicast : ff00::/8 (ff01:0:0:0:0:0:0:2), 224.0.0.0/4 2463 * - unicast : all other 2464 * 2465 * @param string $address IPv4 or IPv6 address string 2466 * @return string IP type 2467 */ 2468function get_ip_type($address) 2469{ 2470 global $config; 2471 2472 list($ip, $bits) = explode('/', trim($address)); // Remove subnet/mask if exist 2473 2474 $ip_version = get_ip_version($ip); 2475 switch ($ip_version) 2476 { 2477 case 4: 2478 2479 // Detect IPv4 broadcast 2480 if (strlen($bits)) 2481 { 2482 $ip_parse = Net_IPv4::parseAddress($address); 2483 if ($ip == $ip_parse->broadcast && $ip_parse->bitmask < 31) // Do not set /31 and /32 as broadcast! 2484 { 2485 $ip_type = 'broadcast'; 2486 break; 2487 } 2488 } 2489 2490 // no break here! 2491 case 6: 2492 2493 $ip_type = ($ip_version == 4) ? 'unicast': 'reserved'; // Default for any valid address 2494 foreach ($config['ip_types'] as $type => $entry) 2495 { 2496 if (isset($entry['networks']) && match_network($ip, $entry['networks'], TRUE)) 2497 { 2498 // Stop loop if IP founded in networks 2499 $ip_type = $type; 2500 break; 2501 } 2502 2503 } 2504 break; 2505 2506 default: 2507 // Not valid IP address 2508 return FALSE; 2509 } 2510 2511 return $ip_type; 2512} 2513 2514/** 2515 * Parse string for valid IP/Network queries. 2516 * 2517 * Valid queries example: 2518 * - 10.0.0.0/8 - exactly IPv4 network, matches to 10.255.0.2, 10.0.0.0, 10.255.255.255 2519 * - 10.12.0.3/8 - same as previous, NOTE network detected by prefix: 10.0.0.0/8 2520 * - 10.12.0.3 - single IPv4 address 2521 * - *.12.0.3, 10.*.3 - * matching by any string in address 2522 * - ?.12.0.3, 10.?.?.3 - ? matching by single char 2523 * - 10.12 - match by part of address, matches to 10.12.2.3, 10.122.3.3, 1.2.110.120 2524 * - 1762::b03:1:ae00/119 - exactly IPv6 network, matches to 1762::b03:1:ae00, 1762::B03:1:AF18, 1762::b03:1:afff 2525 * - 1762:0:0:0:0:B03:1:AF18/119 - same as previous, NOTE network detected by prefix: 1762::b03:1:ae00/119 2526 * - 1762:0:0:0:0:B03:1:AF18 - single IPv6 address 2527 * - *::B03:1:AF18, 1762::*:AF18 - * matching by any string in address 2528 * - ?::B03:1:AF18, 1762::?:AF18 - ? matching by single char 2529 * - 1762:b03 - match by part of address, matches to 1:AF18:1762::B03, 1762::b03:1:ae00 2530 * 2531 * Return array contain this params: 2532 * 'query_type' - which query type required. 2533 * Possible: single (single IP address), 2534 * network (addresses inside network), 2535 * like, %like% (part of address with masks *, ?) 2536 * 'ip_version' - numeric IP version (4, 6) 2537 * 'ip_type' - ipv4 or ipv6 2538 * 'address' - string with passed IP address without prefixes or address part 2539 * 'prefix' - detected network prefix 2540 * 'network' - detected network with prefix 2541 * 'network_start' - first address of network 2542 * 'network_end' - last address of network (broadcast) 2543 * 2544 * @param string $network Network/IP query string 2545 * @return array Array with parsed network params 2546 */ 2547function parse_network($network) 2548{ 2549 $network = trim($network); 2550 2551 $array = array( 2552 'query_type' => 'network', // Default query type by valid network with prefix 2553 ); 2554 2555 if (preg_match('%^'.OBS_PATTERN_IPV4_NET.'$%', $network, $matches)) 2556 { 2557 // Match by valid IPv4 network 2558 $array['ip_version'] = 4; 2559 $array['ip_type'] = 'ipv4'; 2560 $array['address'] = $matches['ipv4']; // Same as IP 2561 //$array['prefix'] = $matches['ipv4_prefix']; 2562 2563 // Convert Cisco Inverse netmask to normal mask 2564 if (isset($matches['ipv4_inverse_mask'])) 2565 { 2566 $matches['ipv4_mask'] = inet_pton($matches['ipv4_inverse_mask']); 2567 $matches['ipv4_mask'] = inet_ntop(~$matches['ipv4_mask']); // Binary inverse and back to IP string 2568 $matches['ipv4_network'] = $matches['ipv4'] . '/' . $matches['ipv4_mask']; 2569 } 2570 2571 if ($matches['ipv4_prefix'] == '32' || $matches['ipv4_mask'] == '255.255.255.255') 2572 { 2573 $array['prefix'] = '32'; 2574 $array['network_start'] = $array['address']; 2575 $array['network_end'] = $array['address']; 2576 $array['network'] = $matches['ipv4_network']; // Network with prefix 2577 $array['query_type'] = 'single'; // Single IP query 2578 } else { 2579 $address = Net_IPv4::parseAddress($matches['ipv4_network']); 2580 //print_vars($address); 2581 $array['prefix'] = $address->bitmask . ''; 2582 $array['network_start'] = $address->network; 2583 $array['network_end'] = $address->broadcast; 2584 $array['network'] = $array['network_start'] . '/' . $array['prefix']; // Network with prefix 2585 } 2586 } 2587 else if (preg_match('%^'.OBS_PATTERN_IPV6_NET.'$%i', $network, $matches)) 2588 { 2589 // Match by valid IPv6 network 2590 $array['ip_version'] = 6; 2591 $array['ip_type'] = 'ipv6'; 2592 $array['address'] = $matches['ipv6']; // Same as IP 2593 $array['prefix'] = $matches['ipv6_prefix']; 2594 if ($array['prefix'] == 128) 2595 { 2596 $array['network_start'] = $array['address']; 2597 $array['network_end'] = $array['address']; 2598 $array['network'] = $matches['ipv6_network']; // Network with prefix 2599 $array['query_type'] = 'single'; // Single IP query 2600 } else { 2601 $address = Net_IPv6::parseAddress($array['address'], $array['prefix']); 2602 //print_vars($address); 2603 $array['network_start'] = $address['start']; 2604 $array['network_end'] = $address['end']; 2605 $array['network'] = $array['network_start'] . '/' . $array['prefix']; // Network with prefix 2606 } 2607 } 2608 else if ($ip_version = get_ip_version($network)) 2609 { 2610 // Single IPv4/IPv6 2611 $array['ip_version'] = $ip_version; 2612 $array['address'] = $network; 2613 $array['prefix'] = $matches['ipv6_prefix']; 2614 if ($ip_version == 4) 2615 { 2616 $array['ip_type'] = 'ipv4'; 2617 $array['prefix'] = '32'; 2618 } else { 2619 $array['ip_type'] = 'ipv6'; 2620 $array['prefix'] = '128'; 2621 } 2622 $array['network_start'] = $array['address']; 2623 $array['network_end'] = $array['address']; 2624 $array['network'] = $network . '/' . $array['prefix']; // Add prefix 2625 $array['query_type'] = 'single'; // Single IP query 2626 } 2627 else if (preg_match('/^[\d\.\?\*]+$/', $network)) 2628 { 2629 // Match IPv4 by mask 2630 $array['ip_version'] = 4; 2631 $array['ip_type'] = 'ipv4'; 2632 $array['address'] = $network; 2633 if (str_contains($network, array('?', '*'))) 2634 { 2635 // If network contains * or ! 2636 $array['query_type'] = 'like'; 2637 } else { 2638 // All other cases 2639 $array['query_type'] = '%like%'; 2640 } 2641 } 2642 else if (preg_match('/^[abcdef\d\:\?\*]+$/i', $network)) 2643 { 2644 // Match IPv6 by mask 2645 $array['ip_version'] = 6; 2646 $array['ip_type'] = 'ipv6'; 2647 $array['address'] = $network; 2648 if (str_contains($network, array('?', '*'))) 2649 { 2650 // If network contains * or ! 2651 $array['query_type'] = 'like'; 2652 } else { 2653 // All other cases 2654 $array['query_type'] = '%like%'; 2655 } 2656 } else { 2657 // Not valid network string passed 2658 return FALSE; 2659 } 2660 2661 // Add binary addresses for single and network queries 2662 switch ($array['query_type']) 2663 { 2664 case 'single': 2665 $array['address_binary'] = inet_pton($array['address']); 2666 break; 2667 case 'network': 2668 $array['network_start_binary'] = inet_pton($array['network_start']); 2669 $array['network_end_binary'] = inet_pton($array['network_end']); 2670 break; 2671 } 2672 2673 return $array; 2674} 2675 2676/** 2677 * Determines whether or not the supplied IP address is within the supplied network (IPv4 or IPv6). 2678 * 2679 * @param string $ip IP Address 2680 * @param string $nets IPv4/v6 networks 2681 * @param bool $first FIXME 2682 * 2683 * @return bool Returns TRUE if address is found in supplied network, FALSE if it is not. 2684 */ 2685// TESTME needs unit testing 2686function match_network($ip, $nets, $first = FALSE) 2687{ 2688 $return = FALSE; 2689 $ip_version = get_ip_version($ip); 2690 if ($ip_version) 2691 { 2692 if (!is_array($nets)) { $nets = array($nets); } 2693 foreach ($nets as $net) 2694 { 2695 $ip_in_net = FALSE; 2696 2697 $revert = (preg_match('/^\!/', $net) ? TRUE : FALSE); // NOT match network 2698 if ($revert) 2699 { 2700 $net = preg_replace('/^\!/', '', $net); 2701 } 2702 2703 if ($ip_version == 4) 2704 { 2705 if (strpos($net, '.') === FALSE) { continue; } // NOT IPv4 net, skip 2706 if (strpos($net, '/') === FALSE) { $net .= '/32'; } // NET without mask as single IP 2707 $ip_in_net = Net_IPv4::ipInNetwork($ip, $net); 2708 } else { 2709 //print_vars($ip); echo(' '); print_vars($net); echo(PHP_EOL); 2710 if (strpos($net, ':') === FALSE) { continue; } // NOT IPv6 net, skip 2711 if (strpos($net, '/') === FALSE) { $net .= '/128'; } // NET without mask as single IP 2712 $ip_in_net = Net_IPv6::isInNetmask($ip, $net); 2713 } 2714 2715 if ($revert && $ip_in_net) { return FALSE; } // Return FALSE if IP found in network where should NOT match 2716 if ($first && $ip_in_net) { return TRUE; } // Return TRUE if IP found in first match 2717 $return = $return || $ip_in_net; 2718 } 2719 } 2720 2721 return $return; 2722} 2723 2724/** 2725 * Convert SNMP hex string to binary map, mostly used for VLANs discovery 2726 * 2727 * Examples: 2728 * "00 40" => "0000000001000000" 2729 * 2730 * @param string $hex HEX encoded string 2731 * @return string Binary string 2732 */ 2733function hex2binmap($hex) 2734{ 2735 $hex = str_replace(array(' ', "\n"), '', $hex); 2736 2737 $binary = ''; 2738 $length = strlen($hex); 2739 for ($i = 0; $i < $length; $i++) 2740 { 2741 $char = $hex[$i]; 2742 $binary .= zeropad(base_convert($char, 16, 2), 4); 2743 } 2744 2745 return $binary; 2746} 2747 2748/** 2749 * Convert HEX encoded IP value to pretty IP string 2750 * 2751 * Examples: 2752 * IPv4 "C1 9C 5A 26" => "193.156.90.38" 2753 * IPv4 "J}4:" => "74.125.52.58" 2754 * IPv6 "20 01 07 F8 00 12 00 01 00 00 00 00 00 05 02 72" => "2001:07f8:0012:0001:0000:0000:0005:0272" 2755 * IPv6 "20:01:07:F8:00:12:00:01:00:00:00:00:00:05:02:72" => "2001:07f8:0012:0001:0000:0000:0005:0272" 2756 * 2757 * @param string $ip_hex HEX encoded IP address 2758 * 2759 * @return string IP address or original input string if not contains IP address 2760 */ 2761function hex2ip($ip_hex) 2762{ 2763 $ip = trim($ip_hex, "\"\t\n\r\0\x0B"); 2764 2765 // IPv6z, ie: 2a:02:a0:10:80:03:00:00:00:00:00:00:00:00:00:01%503316482 2766 if (str_contains($ip, '%')) 2767 { 2768 list($ip) = explode('%', $ip); 2769 } 2770 2771 $len = strlen($ip); 2772 if ($len === 5 && $ip[0] === ' ') 2773 { 2774 $ip = substr($ip, 1); 2775 $len = 4; 2776 } 2777 if ($len === 4) 2778 { 2779 // IPv4 hex string converted to SNMP string 2780 $ip = str2hex($ip); 2781 $len = strlen($ip); 2782 } 2783 2784 $ip = str_replace(' ', '', $ip); 2785 2786 if ($len > 8) 2787 { 2788 // For IPv6 2789 $ip = str_replace(':', '', $ip); 2790 $len = strlen($ip); 2791 } 2792 2793 if (!ctype_xdigit($ip)) 2794 { 2795 return $ip_hex; 2796 } 2797 2798 switch ($len) 2799 { 2800 case 8: 2801 // IPv4 2802 $ip_array = array(); 2803 foreach (str_split($ip, 2) as $entry) 2804 { 2805 $ip_array[] = hexdec($entry); 2806 } 2807 $separator = '.'; 2808 break; 2809 2810 case 16: 2811 // Cisco incorrect IPv4 (54 2E 68 02 FF FF FF FF) 2812 $ip_array = array(); 2813 foreach (str_split($ip, 2) as $i => $entry) 2814 { 2815 if ($i == 4) { break; } 2816 $ip_array[] = hexdec($entry); 2817 } 2818 $separator = '.'; 2819 break; 2820 2821 case 32: 2822 // IPv6 2823 $ip_array = str_split(strtolower($ip), 4); 2824 $separator = ':'; 2825 break; 2826 2827 default: 2828 // Try convert hex string to string 2829 $ip = snmp_hexstring($ip_hex); 2830 if (get_ip_version($ip)) 2831 { 2832 return $ip; 2833 } 2834 return $ip_hex; 2835 } 2836 $ip = implode($separator, $ip_array); 2837 2838 return $ip; 2839} 2840 2841/** 2842 * Convert IP string to HEX encoded value. 2843 * 2844 * Examples: 2845 * IPv4 "193.156.90.38" => "C1 9C 5A 26" 2846 * IPv6 "2001:07f8:0012:0001:0000:0000:0005:0272" => "20 01 07 f8 00 12 00 01 00 00 00 00 00 05 02 72" 2847 * IPv6 "2001:7f8:12:1::5:0272" => "20 01 07 f8 00 12 00 01 00 00 00 00 00 05 02 72" 2848 * 2849 * @param string $ip IP address string 2850 * @param string $separator Separator for HEX parts 2851 * 2852 * @return string HEX encoded address 2853 */ 2854function ip2hex($ip, $separator = ' ') 2855{ 2856 $ip_hex = trim($ip, " \"\t\n\r\0\x0B"); 2857 $ip_version = get_ip_version($ip_hex); 2858 2859 if ($ip_version === 4) 2860 { 2861 // IPv4 2862 $ip_array = array(); 2863 foreach (explode('.', $ip_hex) as $entry) 2864 { 2865 $ip_array[] = zeropad(dechex($entry)); 2866 } 2867 } 2868 else if ($ip_version === 6) 2869 { 2870 // IPv6 2871 $ip_hex = str_replace(':', '', Net_IPv6::uncompress($ip_hex, TRUE)); 2872 $ip_array = str_split($ip_hex, 2); 2873 } else { 2874 return $ip; 2875 } 2876 $ip_hex = implode($separator, $ip_array); 2877 2878 return $ip_hex; 2879} 2880 2881// DOCME needs phpdoc block 2882// TESTME needs unit testing 2883function snmp2ipv6($ipv6_snmp) 2884{ 2885 $ipv6 = explode('.',$ipv6_snmp); 2886 2887 // Workaround stupid Microsoft bug in Windows 2008 -- this is fixed length! 2888 // < fenestro> "because whoever implemented this mib for Microsoft was ignorant of RFC 2578 section 7.7 (2)" 2889 if (count($ipv6) == 17 && $ipv6[0] == 16) 2890 { 2891 array_shift($ipv6); 2892 } 2893 2894 for ($i = 0;$i <= 15;$i++) { $ipv6[$i] = zeropad(dechex($ipv6[$i])); } 2895 for ($i = 0;$i <= 15;$i+=2) { $ipv6_2[] = $ipv6[$i] . $ipv6[$i+1]; } 2896 2897 return implode(':',$ipv6_2); 2898} 2899 2900// DOCME needs phpdoc block 2901// TESTME needs unit testing 2902function ipv62snmp($ipv6) 2903{ 2904 $ipv6_ex = explode(':',Net_IPv6::uncompress($ipv6)); 2905 for ($i = 0;$i < 8;$i++) { $ipv6_ex[$i] = zeropad($ipv6_ex[$i],4); } 2906 $ipv6_ip = implode('',$ipv6_ex); 2907 for ($i = 0;$i < 32;$i+=2) $ipv6_split[] = hexdec(substr($ipv6_ip,$i,2)); 2908 2909 return implode('.',$ipv6_split); 2910} 2911 2912// DOCME needs phpdoc block 2913// TESTME needs unit testing 2914// MOVEME to includes/common.inc.php 2915function get_astext($asn) 2916{ 2917 global $config, $cache; 2918 2919 // Fetch pre-set AS text from config first 2920 if (isset($config['astext'][$asn])) 2921 { 2922 return $config['astext'][$asn]; 2923 } else { 2924 // Not preconfigured, check cache before doing a new DNS request 2925 if (!isset($cache['astext'][$asn])) 2926 { 2927 $result = dns_get_record("AS$asn.asn.cymru.com", DNS_TXT); 2928 if (OBS_DEBUG > 1) 2929 { 2930 print_vars($result); 2931 } 2932 $txt = explode('|', $result[0]['txt']); 2933 $cache['astext'][$asn] = trim(str_replace('"', '', $txt[4])); 2934 } 2935 2936 return $cache['astext'][$asn]; 2937 } 2938} 2939 2940/** 2941 * Use this function to write to the eventlog table 2942 * 2943 * @param string $text Message text 2944 * @param integer|array $device Device array or device id 2945 * @param string $type Entity type (ie port, device, global) 2946 * @param integer $reference Reference ID to current entity type 2947 * @param integer $severity Event severity (0 - 8) 2948 * @return integer Event DB id 2949 */ 2950// TESTME needs unit testing 2951function log_event($text, $device = NULL, $type = NULL, $reference = NULL, $severity = 6) 2952{ 2953 if (is_null($device) && is_null($type)) 2954 { 2955 // Without device and type - is global events 2956 $type = 'global'; 2957 } else { 2958 $type = strtolower($type); 2959 } 2960 2961 // Global events not have device_id 2962 if ($type != 'global') 2963 { 2964 if (!is_array($device)) { $device = device_by_id_cache($device); } 2965 if ($device['ignore'] && $type != 'device') { return FALSE; } // Do not log events if device ignored 2966 } 2967 2968 if ($type == 'port') 2969 { 2970 if (is_array($reference)) 2971 { 2972 $port = $reference; 2973 $reference = $port['port_id']; 2974 } else { 2975 $port = get_port_by_id_cache($reference); 2976 } 2977 if ($port['ignore']) { return FALSE; } // Do not log events if interface ignored 2978 } 2979 2980 $severity = priority_string_to_numeric($severity); // Convert named severities to numeric 2981 if (($type == 'device' && $severity == 5) || isset($_SESSION['username'])) // Severity "Notification" additional log info about username or cli 2982 { 2983 $severity = ($severity == 6 ? 5 : $severity); // If severity default, change to notification 2984 if (isset($_SESSION['username'])) 2985 { 2986 $text .= ' (by user: '.$_SESSION['username'].')'; 2987 } 2988 else if (is_cli()) 2989 { 2990 if (is_cron()) 2991 { 2992 $text .= ' (by cron)'; 2993 } else { 2994 $text .= ' (by console, user ' . $_SERVER['USER'] . ')'; 2995 } 2996 } 2997 } 2998 2999 $insert = array('device_id' => ($device['device_id'] ? $device['device_id'] : 0), // Currently db schema not allow NULL value for device_id 3000 'entity_id' => (is_numeric($reference) ? $reference : array('NULL')), 3001 'entity_type' => ($type ? $type : array('NULL')), 3002 'timestamp' => array("NOW()"), 3003 'severity' => $severity, 3004 'message' => $text); 3005 3006 $id = dbInsert($insert, 'eventlog'); 3007 3008 return $id; 3009} 3010 3011// Parse string with emails. Return array with email (as key) and name (as value) 3012// DOCME needs phpdoc block 3013// MOVEME to includes/common.inc.php 3014function parse_email($emails) 3015{ 3016 $result = array(); 3017 3018 if (is_string($emails)) 3019 { 3020 $emails = preg_split('/[,;]\s{0,}/', $emails); 3021 foreach ($emails as $email) 3022 { 3023 $email = trim($email); 3024 if (preg_match('/^\s*' . OBS_PATTERN_EMAIL_LONG . '\s*$/iu', $email, $matches)) 3025 { 3026 $email = trim($matches['email']); 3027 $name = trim($matches['name'], " \t\n'\""); 3028 $result[$email] = (!empty($name) ? $name : NULL); 3029 } 3030 else if (strpos($email, "@") && !preg_match('/\s/', $email)) 3031 { 3032 $result[$email] = NULL; 3033 } else { 3034 return FALSE; 3035 } 3036 } 3037 } else { 3038 // Return FALSE if input not string 3039 return FALSE; 3040 } 3041 return $result; 3042} 3043 3044/** 3045 * Converting string to hex 3046 * 3047 * By Greg Winiarski of ditio.net 3048 * http://ditio.net/2008/11/04/php-string-to-hex-and-hex-to-string-functions/ 3049 * We claim no copyright over this function and assume that it is free to use. 3050 * 3051 * @param string $string 3052 * 3053 * @return string 3054 */ 3055// MOVEME to includes/common.inc.php 3056function str2hex($string) 3057{ 3058 $hex=''; 3059 for ($i=0; $i < strlen($string); $i++) 3060 { 3061 $hex .= dechex(ord($string[$i])); 3062 } 3063 return $hex; 3064} 3065 3066/** 3067 * Converting hex to string 3068 * 3069 * By Greg Winiarski of ditio.net 3070 * http://ditio.net/2008/11/04/php-string-to-hex-and-hex-to-string-functions/ 3071 * We claim no copyright over this function and assume that it is free to use. 3072 * 3073 * @param string $hex HEX string 3074 * @param string $eol EOL char, default is \n 3075 * 3076 * @return string 3077 */ 3078// TESTME needs unit testing 3079// MOVEME to includes/common.inc.php 3080function hex2str($hex, $eol = "\n") 3081{ 3082 $string=''; 3083 3084 $hex = str_replace(' ', '', $hex); 3085 for ($i = 0; $i < strlen($hex) - 1; $i += 2) 3086 { 3087 $hex_chr = $hex[$i].$hex[$i+1]; 3088 if ($hex_chr == '00') 3089 { 3090 // 00 is EOL 3091 $string .= $eol; 3092 } else { 3093 $string .= chr(hexdec($hex_chr)); 3094 } 3095 } 3096 3097 return $string; 3098} 3099 3100/** 3101 * Converting hex/dec coded ascii char to UTF-8 char 3102 * 3103 * Used together with snmp_fix_string() 3104 * 3105 * @param string $hex 3106 * 3107 * @return string 3108 */ 3109function convert_ord_char($ord) 3110{ 3111 if (is_array($ord)) 3112 { 3113 $ord = array_shift($ord); 3114 } 3115 if (preg_match('/^(?:<|x)([0-9a-f]+)>?$/i', $ord, $match)) 3116 { 3117 $ord = hexdec($match[1]); 3118 } 3119 else if (is_numeric($ord)) 3120 { 3121 $ord = intval($ord); 3122 } 3123 else if (preg_match('/^[\p{L}]+$/u', $ord)) 3124 { 3125 // Unicode chars 3126 return $ord; 3127 } else { 3128 // Non-printable chars 3129 $ord = ord($ord); 3130 } 3131 3132 $no_bytes = 0; 3133 $byte = array(); 3134 3135 if ($ord < 128) 3136 { 3137 return chr($ord); 3138 } 3139 else if ($ord < 2048) 3140 { 3141 $no_bytes = 2; 3142 } 3143 else if ($ord < 65536) 3144 { 3145 $no_bytes = 3; 3146 } 3147 else if ($ord < 1114112) 3148 { 3149 $no_bytes = 4; 3150 } else { 3151 return; 3152 } 3153 switch($no_bytes) 3154 { 3155 case 2: 3156 $prefix = array(31, 192); 3157 break; 3158 case 3: 3159 $prefix = array(15, 224); 3160 break; 3161 case 4: 3162 $prefix = array(7, 240); 3163 break; 3164 } 3165 3166 for ($i = 0; $i < $no_bytes; $i++) 3167 { 3168 $byte[$no_bytes - $i - 1] = (($ord & (63 * pow(2, 6 * $i))) / pow(2, 6 * $i)) & 63 | 128; 3169 } 3170 3171 $byte[0] = ($byte[0] & $prefix[0]) | $prefix[1]; 3172 3173 $ret = ''; 3174 for ($i = 0; $i < $no_bytes; $i++) 3175 { 3176 $ret .= chr($byte[$i]); 3177 } 3178 3179 return $ret; 3180} 3181 3182// Check if the supplied string is a hex string 3183// FIXME This is test for SNMP hex string, for just hex string use ctype_xdigit() 3184// DOCME needs phpdoc block 3185// TESTME needs unit testing 3186// MOVEME to includes/snmp.inc.php 3187function isHexString($str) 3188{ 3189 return (preg_match("/^[a-f0-9][a-f0-9](\ +[a-f0-9][a-f0-9])*$/is", trim($str)) ? TRUE : FALSE); 3190} 3191 3192// Include all .inc.php files in $dir 3193// DOCME needs phpdoc block 3194// TESTME needs unit testing 3195// MOVEME to includes/common.inc.php 3196function include_dir($dir, $regex = "") 3197{ 3198 global $device, $config, $valid; 3199 3200 if ($regex == "") 3201 { 3202 $regex = "/\.inc\.php$/"; 3203 } 3204 3205 if ($handle = opendir($config['install_dir'] . '/' . $dir)) 3206 { 3207 while (false !== ($file = readdir($handle))) 3208 { 3209 if (filetype($config['install_dir'] . '/' . $dir . '/' . $file) == 'file' && preg_match($regex, $file)) 3210 { 3211 print_debug("Including: " . $config['install_dir'] . '/' . $dir . '/' . $file); 3212 3213 include($config['install_dir'] . '/' . $dir . '/' . $file); 3214 } 3215 } 3216 3217 closedir($handle); 3218 } 3219} 3220 3221// DOCME needs phpdoc block 3222// TESTME needs unit testing 3223function is_port_valid($port, $device) 3224{ 3225 global $config; 3226 $valid = TRUE; 3227 3228 if (isset($port['ifOperStatus']) && strlen($port['ifOperStatus']) && // Currently skiped empty ifOperStatus for exclude false positives 3229 !in_array($port['ifOperStatus'], array('testing', 'dormant', 'down', 'lowerLayerDown', 'unknown', 'up', 'monitoring'))) 3230 { 3231 // See http://tools.cisco.com/Support/SNMP/do/BrowseOID.do?objectInput=ifOperStatus 3232 $valid = FALSE; 3233 print_debug("ignored (by ifOperStatus = notPresent or invalid value)."); 3234 } 3235 3236 $ports_skip_ifType = isset($config['os'][$device['os']]['ports_skip_ifType']) && $config['os'][$device['os']]['ports_skip_ifType']; 3237 if ($valid && !isset($port['ifType']) && !$ports_skip_ifType) 3238 { 3239 /* Some devices (ie D-Link) report ports without any usefull info, example: 3240 [74] => Array 3241 ( 3242 [ifName] => po22 3243 [ifInMulticastPkts] => 0 3244 [ifInBroadcastPkts] => 0 3245 [ifOutMulticastPkts] => 0 3246 [ifOutBroadcastPkts] => 0 3247 [ifLinkUpDownTrapEnable] => enabled 3248 [ifHighSpeed] => 0 3249 [ifPromiscuousMode] => false 3250 [ifConnectorPresent] => false 3251 [ifAlias] => po22 3252 [ifCounterDiscontinuityTime] => 0:0:00:00.00 3253 ) 3254 */ 3255 $valid = FALSE; 3256 print_debug("ignored (by empty ifType)."); 3257 } 3258 3259 if ($port['ifDescr'] === '' && $config['os'][$device['os']]['ifType_ifDescr'] && $port['ifIndex']) 3260 { 3261 // This happen on some liebert UPS devices 3262 $type = rewrite_iftype($port['ifType']); 3263 if ($type) 3264 { 3265 $port['ifDescr'] = $type . ' ' . $port['ifIndex']; 3266 } 3267 } 3268 3269 $if = ($config['os'][$device['os']]['ifname'] ? $port['ifName'] : $port['ifDescr']); 3270 3271 if ($valid && is_array($config['bad_if'])) 3272 { 3273 foreach ($config['bad_if'] as $bi) 3274 { 3275 if (stripos($port['ifDescr'], $bi) !== FALSE) 3276 { 3277 $valid = FALSE; 3278 print_debug("ignored (by ifDescr): ".$port['ifDescr']." [ $bi ]"); 3279 break; 3280 } 3281 elseif (stripos($port['ifName'], $bi) !== FALSE) 3282 { 3283 $valid = FALSE; 3284 print_debug("ignored (by ifName): ".$port['ifName']." [ $bi ]"); 3285 break; 3286 } 3287 } 3288 } 3289 3290 if ($valid && is_array($config['bad_ifalias_regexp'])) 3291 { 3292 foreach ($config['bad_ifalias_regexp'] as $bi) 3293 { 3294 if (preg_match($bi . 'i', $port['ifAlias'])) 3295 { 3296 $valid = FALSE; 3297 print_debug("ignored (by ifAlias): ".$port['ifAlias']." [ $bi ]"); 3298 break; 3299 } 3300 } 3301 } 3302 3303 if ($valid && is_array($config['bad_if_regexp'])) 3304 { 3305 foreach ($config['bad_if_regexp'] as $bi) 3306 { 3307 if (preg_match($bi . 'i', $port['ifName'])) 3308 { 3309 $valid = FALSE; 3310 print_debug("ignored (by ifName regexp): ".$port['ifName']." [ $bi ]"); 3311 break; 3312 } 3313 elseif (preg_match($bi . 'i', $port['ifDescr'])) 3314 { 3315 $valid = FALSE; 3316 print_debug("ignored (by ifDescr regexp): ".$port['ifDescr']." [ $bi ]"); 3317 break; 3318 } 3319 } 3320 } 3321 3322 if ($valid && is_array($config['bad_iftype'])) 3323 { 3324 foreach ($config['bad_iftype'] as $bi) 3325 { 3326 if (strpos($port['ifType'], $bi) !== FALSE) 3327 { 3328 $valid = FALSE; 3329 print_debug("ignored (by ifType): ".$port['ifType']." [ $bi ]"); 3330 break; 3331 } 3332 } 3333 } 3334 if ($valid && empty($port['ifDescr']) && empty($port['ifName'])) 3335 { 3336 $valid = FALSE; 3337 print_debug("ignored (by empty ifDescr and ifName)."); 3338 } 3339 if ($valid && $device['os'] == 'catos' && strstr($if, "vlan")) { $valid = FALSE; } 3340 3341 return $valid; 3342} 3343 3344/** 3345 * Validate BGP peer 3346 * 3347 * @param array $peer BGP peer array from discovery or polling process 3348 * @param array $device Common device array 3349 * @return boolean TRUE if peer array valid 3350 */ 3351function is_bgp_peer_valid($peer, $device) 3352{ 3353 $valid = TRUE; 3354 3355 if (isset($peer['admin_status']) && empty($peer['admin_status'])) 3356 { 3357 $valid = FALSE; 3358 print_debug("Peer ignored (by empty Admin Status)."); 3359 } 3360 3361 if ($valid && !(is_numeric($peer['as']) && $peer['as'] != 0)) 3362 { 3363 $valid = FALSE; 3364 print_debug("Peer ignored (by invalid AS number '".$peer['as']."')."); 3365 } 3366 3367 if ($valid && !get_ip_version($peer['ip'])) 3368 { 3369 $valid = FALSE; 3370 print_debug("Peer ignored (by invalid Remote IP '".$peer['ip']."')."); 3371 } 3372 3373 return $valid; 3374} 3375 3376/** 3377 * Detect is BGP AS number in private range, see: 3378 * https://tools.ietf.org/html/rfc6996 3379 * https://tools.ietf.org/html/rfc7300 3380 * 3381 * @param string|int $as AS number 3382 * @return boolean TRUE if AS number in private range 3383 */ 3384function is_bgp_as_private($as) 3385{ 3386 $as = bgp_asdot_to_asplain($as); // Convert ASdot to ASplain 3387 3388 // Note 65535 and 5294967295 not really Private ASNs, 3389 // this is Reserved for use by Well-known Communities 3390 $private = ($as >= 64512 && $as <= 65535) || // 16-bit private ASn 3391 ($as >= 4200000000 && $as <= 5294967295); // 32-bit private ASn 3392 3393 return $private; 3394} 3395 3396/** 3397 * Convert AS number from asplain to asdot format (for 32bit ASn). 3398 * 3399 * @param string|int $as AS number in plain or dot format 3400 * @return string AS number in dot format (for 32bit ASn) 3401 */ 3402function bgp_asplain_to_asdot($as) 3403{ 3404 if (strpos($as, '.') || // Already asdot format 3405 ($as < 65536)) // 16bit ASn no need to formatting 3406 { 3407 return $as; 3408 } 3409 3410 $as2 = $as % 65536; 3411 $as1 = ($as - $as2) / 65536; 3412 3413 return intval($as1) . '.' . intval($as2); 3414} 3415 3416/** 3417 * Convert AS number from asdot to asplain format (for 32bit ASn). 3418 * 3419 * @param string|int $as AS number in plain or dot format 3420 * @return string AS number in plain format (for 32bit ASn) 3421 */ 3422function bgp_asdot_to_asplain($as) 3423{ 3424 if (strpos($as, '.') === FALSE) // Already asplain format 3425 { 3426 return $as; 3427 } 3428 3429 list($as1, $as2) = explode('.', $as, 2); 3430 $as = $as1 * 65536 + $as2; 3431 3432 return "$as"; 3433} 3434 3435/** 3436 * Convert BGP peer index to vendor MIB specific entries 3437 * 3438 * @param array $peer Array with walked peer oids 3439 * @param string $index Peer index 3440 * @param string $mib MIB name 3441 */ 3442function parse_bgp_peer_index(&$peer, $index, $mib = 'BGP4V2-MIB') 3443{ 3444 $address_types = $GLOBALS['config']['mibs']['INET-ADDRESS-MIB']['rewrite']['InetAddressType']; 3445 $index_parts = explode('.', $index); 3446 switch ($mib) 3447 { 3448 case 'BGP4-MIB': 3449 // bgpPeerRemoteAddr 3450 if (get_ip_version($index)) 3451 { 3452 $peer['bgpPeerRemoteAddr'] = $index; 3453 } 3454 break; 3455 3456 case 'ARISTA-BGP4V2-MIB': 3457 // 1. aristaBgp4V2PeerInstance 3458 $peer['aristaBgp4V2PeerInstance'] = array_shift($index_parts); 3459 // 2. aristaBgp4V2PeerRemoteAddrType 3460 $peer_addr_type = array_shift($index_parts); 3461 if (strlen($peer['aristaBgp4V2PeerRemoteAddrType']) == 0) 3462 { 3463 $peer['aristaBgp4V2PeerRemoteAddrType'] = $peer_addr_type; 3464 } 3465 if (isset($address_types[$peer['aristaBgp4V2PeerRemoteAddrType']])) 3466 { 3467 $peer['aristaBgp4V2PeerRemoteAddrType'] = $address_types[$peer['aristaBgp4V2PeerRemoteAddrType']]; 3468 } 3469 // 3. length of the IP address 3470 $ip_len = array_shift($index_parts); 3471 // 4. IP address 3472 $ip_parts = array_slice($index_parts, 0, $ip_len); 3473 3474 // 5. aristaBgp4V2PeerRemoteAddr 3475 $peer_ip = implode('.', $ip_parts); 3476 if ($ip_len == 16) 3477 { 3478 $peer_ip = snmp2ipv6($peer_ip); 3479 } 3480 if ($peer_addr_type = get_ip_version($peer_ip)) 3481 { 3482 $peer['aristaBgp4V2PeerRemoteAddr'] = $peer_ip; 3483 $peer['aristaBgp4V2PeerRemoteAddrType'] = 'ipv' . $peer_addr_type; // FIXME. not sure, but seems as Arista use only ipv4/ipv6 for afi 3484 } 3485 break; 3486 3487 case 'BGP4V2-MIB': 3488 case 'FOUNDRY-BGP4V2-MIB': // BGP4V2-MIB draft 3489 // 1. bgp4V2PeerInstance 3490 $peer['bgp4V2PeerInstance'] = array_shift($index_parts); 3491 // 2. bgp4V2PeerLocalAddrType 3492 $local_addr_type = array_shift($index_parts); 3493 if (strlen($peer['bgp4V2PeerLocalAddrType']) == 0) 3494 { 3495 $peer['bgp4V2PeerLocalAddrType'] = $local_addr_type; 3496 } 3497 if (isset($address_types[$peer['bgp4V2PeerLocalAddrType']])) 3498 { 3499 $peer['bgp4V2PeerLocalAddrType'] = $address_types[$peer['bgp4V2PeerLocalAddrType']]; 3500 } 3501 // 3. length of the local IP address 3502 $ip_len = array_shift($index_parts); 3503 // 4. IP address 3504 $ip_parts = array_slice($index_parts, 0, $ip_len); 3505 3506 // 5. bgp4V2PeerLocalAddr 3507 $local_ip = implode('.', $ip_parts); 3508 if ($ip_len == 16) 3509 { 3510 $local_ip = snmp2ipv6($local_ip); 3511 } 3512 if (get_ip_version($local_ip)) 3513 { 3514 $peer['bgp4V2PeerLocalAddr'] = $local_ip; 3515 } 3516 3517 // Get second part of index 3518 $index_parts = array_slice($index_parts, $ip_len); 3519 $peer_addr_type = array_shift($index_parts); 3520 if (strlen($peer['bgp4V2PeerRemoteAddrType']) == 0) 3521 { 3522 $peer['bgp4V2PeerRemoteAddrType'] = $peer_addr_type; 3523 } 3524 if (isset($address_types[$peer['bgp4V2PeerRemoteAddrType']])) 3525 { 3526 $peer['bgp4V2PeerRemoteAddrType'] = $address_types[$peer['bgp4V2PeerRemoteAddrType']]; 3527 } 3528 // 6. length of the IP address 3529 $ip_len = array_shift($index_parts); 3530 // 7. IP address 3531 $ip_parts = array_slice($index_parts, 0, $ip_len); 3532 3533 // 8. bgp4V2PeerRemoteAddr 3534 $peer_ip = implode('.', $ip_parts); 3535 if ($ip_len == 16) 3536 { 3537 $peer_ip = snmp2ipv6($peer_ip); 3538 } 3539 if ($peer_addr_type = get_ip_version($peer_ip)) 3540 { 3541 $peer['bgp4V2PeerRemoteAddr'] = $peer_ip; 3542 $peer['bgp4V2PeerRemoteAddrType'] = 'ipv' . $peer_addr_type; 3543 } 3544 break; 3545 3546 case 'BGP4-V2-MIB-JUNIPER': 3547 // 1. jnxBgpM2PeerRoutingInstance 3548 $peer['jnxBgpM2PeerRoutingInstance'] = array_shift($index_parts); 3549 // 2. jnxBgpM2PeerLocalAddrType 3550 $local_addr_type = array_shift($index_parts); 3551 if (strlen($peer['jnxBgpM2PeerLocalAddrType']) == 0) 3552 { 3553 $peer['jnxBgpM2PeerLocalAddrType'] = $local_addr_type; 3554 } 3555 if (isset($address_types[$peer['jnxBgpM2PeerLocalAddrType']])) 3556 { 3557 $peer['jnxBgpM2PeerLocalAddrType'] = $address_types[$peer['jnxBgpM2PeerLocalAddrType']]; 3558 } 3559 // 3. length of the local IP address 3560 $ip_len = (strstr($peer['jnxBgpM2PeerLocalAddrType'], 'ipv6') ? 16 : 4); 3561 // 4. IP address 3562 $ip_parts = array_slice($index_parts, 0, $ip_len); 3563 3564 // 5. jnxBgpM2PeerLocalAddr 3565 $local_ip = implode('.', $ip_parts); 3566 if ($ip_len == 16) 3567 { 3568 $local_ip = snmp2ipv6($local_ip); 3569 } 3570 if (get_ip_version($local_ip)) 3571 { 3572 $peer['jnxBgpM2PeerLocalAddr'] = $local_ip; 3573 } 3574 3575 // Get second part of index 3576 $index_parts = array_slice($index_parts, $ip_len); 3577 // 6. jnxBgpM2PeerRemoteAddrType 3578 $peer_addr_type = array_shift($index_parts); 3579 if (strlen($peer['jnxBgpM2PeerRemoteAddrType']) == 0) 3580 { 3581 $peer['jnxBgpM2PeerRemoteAddrType'] = $peer_addr_type; 3582 } 3583 if (isset($address_types[$peer['jnxBgpM2PeerRemoteAddrType']])) 3584 { 3585 $peer['jnxBgpM2PeerRemoteAddrType'] = $address_types[$peer['jnxBgpM2PeerRemoteAddrType']]; 3586 } 3587 // 7. length of the remote IP address 3588 $ip_len = (strstr($peer['jnxBgpM2PeerRemoteAddrType'], 'ipv6') ? 16 : 4); 3589 // 8. IP address 3590 $ip_parts = array_slice($index_parts, 0, $ip_len); 3591 3592 // 9. jnxBgpM2PeerRemoteAddr 3593 $peer_ip = implode('.', $ip_parts); 3594 if ($ip_len == 16) 3595 { 3596 $peer_ip = snmp2ipv6($peer_ip); 3597 } 3598 if (get_ip_version($peer_ip)) 3599 { 3600 $peer['jnxBgpM2PeerRemoteAddr'] = $peer_ip; 3601 } 3602 break; 3603 3604 case 'FORCE10-BGP4-V2-MIB': 3605 // 1. f10BgpM2PeerInstance 3606 $peer['f10BgpM2PeerInstance'] = array_shift($index_parts); 3607 // 2. f10BgpM2PeerLocalAddrType 3608 $local_addr_type = array_shift($index_parts); 3609 if (strlen($peer['f10BgpM2PeerLocalAddrType']) == 0) 3610 { 3611 $peer['f10BgpM2PeerLocalAddrType'] = $local_addr_type; 3612 } 3613 if (isset($address_types[$peer['f10BgpM2PeerLocalAddrType']])) 3614 { 3615 $peer['f10BgpM2PeerLocalAddrType'] = $address_types[$peer['f10BgpM2PeerLocalAddrType']]; 3616 } 3617 // 3. length of the local IP address 3618 $ip_len = (strstr($peer['f10BgpM2PeerLocalAddrType'], 'ipv6') ? 16 : 4); 3619 // 4. IP address 3620 $ip_parts = array_slice($index_parts, 0, $ip_len); 3621 3622 // 5. f10BgpM2PeerLocalAddr 3623 $local_ip = implode('.', $ip_parts); 3624 if ($ip_len == 16) 3625 { 3626 $local_ip = snmp2ipv6($local_ip); 3627 } 3628 if (get_ip_version($local_ip)) 3629 { 3630 $peer['f10BgpM2PeerLocalAddr'] = $local_ip; 3631 } 3632 3633 // Get second part of index 3634 $index_parts = array_slice($index_parts, $ip_len); 3635 // 6. f10BgpM2PeerRemoteAddrType 3636 $peer_addr_type = array_shift($index_parts); 3637 if (strlen($peer['f10BgpM2PeerRemoteAddrType']) == 0) 3638 { 3639 $peer['f10BgpM2PeerRemoteAddrType'] = $peer_addr_type; 3640 } 3641 if (isset($address_types[$peer['f10BgpM2PeerRemoteAddrType']])) 3642 { 3643 $peer['f10BgpM2PeerRemoteAddrType'] = $address_types[$peer['f10BgpM2PeerRemoteAddrType']]; 3644 } 3645 // 7. length of the remote IP address 3646 $ip_len = (strstr($peer['f10BgpM2PeerRemoteAddrType'], 'ipv6') ? 16 : 4); 3647 // 8. IP address 3648 $ip_parts = array_slice($index_parts, 0, $ip_len); 3649 3650 // 9. f10BgpM2PeerRemoteAddr 3651 $peer_ip = implode('.', $ip_parts); 3652 if ($ip_len == 16) 3653 { 3654 $peer_ip = snmp2ipv6($peer_ip); 3655 } 3656 if (get_ip_version($peer_ip)) 3657 { 3658 $peer['f10BgpM2PeerRemoteAddr'] = $peer_ip; 3659 } 3660 break; 3661 3662 } 3663 3664} 3665 3666# Parse CSV files with or without header, and return a multidimensional array 3667// DOCME needs phpdoc block 3668// TESTME needs unit testing 3669// MOVEME to includes/common.inc.php 3670function parse_csv($content, $has_header = 1, $separator = ",") 3671{ 3672 $lines = explode("\n", $content); 3673 $result = array(); 3674 3675 # If the CSV file has a header, load up the titles into $headers 3676 if ($has_header) 3677 { 3678 $headcount = 1; 3679 $header = array_shift($lines); 3680 foreach (explode($separator,$header) as $heading) 3681 { 3682 if (trim($heading) != "") 3683 { 3684 $headers[$headcount] = trim($heading); 3685 $headcount++; 3686 } 3687 } 3688 } 3689 3690 # Process every line 3691 foreach ($lines as $line) 3692 { 3693 if ($line != "") 3694 { 3695 $entrycount = 1; 3696 foreach (explode($separator,$line) as $entry) 3697 { 3698 # If we use header, place the value inside the named array entry 3699 # Otherwise, just stuff it in numbered fields in the array 3700 if (trim($entry) != "") 3701 { 3702 if ($has_header) 3703 { 3704 $line_array[$headers[$entrycount]] = trim($entry); 3705 } else { 3706 $line_array[] = trim($entry); 3707 } 3708 } 3709 $entrycount++; 3710 } 3711 3712 # Add resulting line array to final result 3713 $result[] = $line_array; unset($line_array); 3714 } 3715 } 3716 3717 return $result; 3718} 3719 3720function get_defined_settings() 3721{ 3722 $config = []; 3723 include($GLOBALS['config']['install_dir'] . "/config.php"); 3724 3725 return $config; 3726} 3727 3728function get_default_settings() 3729{ 3730 $config = []; 3731 include($GLOBALS['config']['install_dir'] . "/includes/defaults.inc.php"); 3732 3733 return $config; 3734} 3735 3736// Load configuration from SQL into supplied variable (pass by reference!) 3737function load_sqlconfig(&$config) 3738{ 3739 $config_defined = get_defined_settings(); // defined in config.php 3740 3741 // Override some whitelisted definitions from config.php 3742 foreach ($config_defined as $key => $definition) 3743 { 3744 if (in_array($key, $config['definitions_whitelist']) && version_compare(PHP_VERSION, '5.3.0') >= 0 && 3745 is_array($definition) && is_array($config[$key])) 3746 { 3747 /* Fix mib definitions for dumb users, who copied old defaults.php 3748 where mibs was just MIB => 1, 3749 This definition should be array */ 3750 // Fetch first element and validate that this is array 3751 if ($key == 'mibs' && !is_array(array_shift(array_values($definition)))) { continue; } 3752 3753 $config[$key] = array_replace_recursive($config[$key], $definition); 3754 } 3755 } 3756 3757 foreach (dbFetchRows("SELECT * FROM `config`") as $item) 3758 { 3759 // Convert boo|bee|baa config value into $config['boo']['bee']['baa'] 3760 $tree = explode('|', $item['config_key']); 3761 3762 //if (array_key_exists($tree[0], $config_defined)) { continue; } // This complete skip option if first level key defined in $config 3763 3764 // Unfortunately, I don't know of a better way to do this... 3765 // Perhaps using array_map() ? Unclear... hacky. :[ 3766 // FIXME use a loop with references! (cf. nested location menu) 3767 switch (count($tree)) 3768 { 3769 case 1: 3770 //if (isset($config_defined[$tree[0]])) { continue; } // Note, false for null values 3771 if (array_key_exists($tree[0], $config_defined)) { break; } 3772 $config[$tree[0]] = unserialize($item['config_value']); 3773 break; 3774 case 2: 3775 if (isset($config_defined[$tree[0]][$tree[1]])) { break; } // Note, false for null values 3776 $config[$tree[0]][$tree[1]] = unserialize($item['config_value']); 3777 break; 3778 case 3: 3779 if (isset($config_defined[$tree[0]][$tree[1]][$tree[2]])) { break; } // Note, false for null values 3780 $config[$tree[0]][$tree[1]][$tree[2]] = unserialize($item['config_value']); 3781 break; 3782 case 4: 3783 if (isset($config_defined[$tree[0]][$tree[1]][$tree[2]][$tree[3]])) { break; } // Note, false for null values 3784 $config[$tree[0]][$tree[1]][$tree[2]][$tree[3]] = unserialize($item['config_value']); 3785 break; 3786 case 5: 3787 if (isset($config_defined[$tree[0]][$tree[1]][$tree[2]][$tree[3]][$tree[4]])) { break; } // Note, false for null values 3788 $config[$tree[0]][$tree[1]][$tree[2]][$tree[3]][$tree[4]] = unserialize($item['config_value']); 3789 break; 3790 default: 3791 print_error("Too many array levels for SQL configuration parser!"); 3792 } 3793 } 3794} 3795 3796function isset_array_key($key, &$array, $split = '|') 3797{ 3798 // Convert boo|bee|baa key into $array['boo']['bee']['baa'] 3799 $tree = explode($split, $key); 3800 3801 switch (count($tree)) 3802 { 3803 case 1: 3804 //if (isset($array[$tree[0]])) { continue; } // Note, false for null values 3805 return array_key_exists($tree[0], $array); 3806 break; 3807 case 2: 3808 //if (isset($array[$tree[0]][$tree[1]])) { continue; } // Note, false for null values 3809 return isset($array[$tree[0]]) && array_key_exists($tree[1], $array[$tree[0]]); 3810 break; 3811 case 3: 3812 //if (isset($array[$tree[0]][$tree[1]][$tree[2]])) { continue; } // Note, false for null values 3813 return isset($array[$tree[0]][$tree[1]]) && array_key_exists($tree[2], $array[$tree[0]][$tree[1]]); 3814 break; 3815 case 4: 3816 //if (isset($array[$tree[0]][$tree[1]][$tree[2]][$tree[3]])) { continue; } // Note, false for null values 3817 return isset($array[$tree[0]][$tree[1]][$tree[2]]) && array_key_exists($tree[3], $array[$tree[0]][$tree[1]][$tree[2]]); 3818 break; 3819 case 5: 3820 //if (isset($array[$tree[0]][$tree[1]][$tree[2]][$tree[3]][$tree[4]])) { continue; } // Note, false for null values 3821 return isset($array[$tree[0]][$tree[1]][$tree[2]][$tree[3]]) && array_key_exists($tree[4], $array[$tree[0]][$tree[1]][$tree[2]][$tree[3]]); 3822 break; 3823 default: 3824 print_error("Too many array levels for array"); 3825 } 3826 3827 return FALSE; 3828} 3829 3830function set_sql_config($key, $value, $force = TRUE) 3831{ 3832 3833 if (!$force && // Currently configuration store forced also if not exist in defaults 3834 !isset_array_key($key, $GLOBALS['config'])) 3835 { 3836 print_error("Not exist config key ($key)."); 3837 return FALSE; 3838 } 3839 3840 $s_value = serialize($value); // in db we store serialized config value 3841 3842 $sql = 'SELECT * FROM `config` WHERE `config_key` = ? LIMIT 1'; 3843 if ($in_db = dbFetchRow($sql, [$key])) 3844 { 3845 // Exist, compare? and update 3846 if ($s_value != $in_db[$key]) 3847 { 3848 dbUpdate(['config_value' => $s_value], 'config', '`config_key` = ?', [$key]); 3849 } 3850 } 3851 else { 3852 // Insert new 3853 dbInsert(array('config_key' => $key, 'config_value' => $s_value), 'config'); 3854 } 3855 3856 return TRUE; 3857} 3858 3859function del_sql_config($key) 3860{ 3861 if (dbExist('config', '`config_key` = ?', [$key])) 3862 { 3863 dbDelete('config', '`config_key` = ?', [$key]); 3864 } 3865 3866 return TRUE; 3867} 3868 3869// Convert SI scales to scalar scale. Example return: 3870// si_to_scale('milli'); // return 0.001 3871// si_to_scale('femto', 8); // return 1.0E-23 3872// si_to_scale('-2'); // return 0.01 3873// DOCME needs phpdoc block 3874// MOVEME to includes/common.inc.php 3875function si_to_scale($si = 'units', $precision = NULL) 3876{ 3877 // See all scales here: http://tools.cisco.com/Support/SNMP/do/BrowseOID.do?local=en&translate=Translate&typeName=SensorDataScale 3878 $si = strtolower($si); 3879 $si_array = array('yocto' => -24, 'zepto' => -21, 'atto' => -18, 3880 'femto' => -15, 'pico' => -12, 'nano' => -9, 3881 'micro' => -6, 'milli' => -3, 'centi' => -2, 3882 'deci' => -1, 'units' => 0, 'deca' => 1, 3883 'hecto' => 2, 'kilo' => 3, 'mega' => 6, 3884 'giga' => 9, 'tera' => 12, 'peta' => 15, 3885 'exa' => 18, 'zetta' => 21, 'yotta' => 24); 3886 $exp = 0; 3887 if (isset($si_array[$si])) 3888 { 3889 $exp = $si_array[$si]; 3890 } 3891 else if (is_numeric($si)) 3892 { 3893 $exp = (int)$si; 3894 } 3895 3896 if (is_numeric($precision) && $precision > 0) 3897 { 3898 /** 3899 * NOTES. For EntitySensorPrecision: 3900 * If an object of this type contains a value in the range 1 to 9, it represents the number of decimal places in the 3901 * fractional part of an associated EntitySensorValue fixed-point number. 3902 * If an object of this type contains a value in the range -8 to -1, it represents the number of accurate digits in the 3903 * associated EntitySensorValue fixed-point number. 3904 */ 3905 $exp -= (int)$precision; 3906 } 3907 3908 $scale = pow(10, $exp); 3909 3910 return $scale; 3911} 3912 3913/** 3914 * Compare variables considering epsilon for float numbers 3915 * returns: 0 - variables same, 1 - $a greater than $b, -1 - $a less than $b 3916 * 3917 * @param mixed $a First compare number 3918 * @param mixed $b Second compare number 3919 * @param float $epsilon 3920 * 3921 * @return integer $compare 3922 */ 3923// MOVEME to includes/common.inc.php 3924function float_cmp($a, $b, $epsilon = NULL) 3925{ 3926 $epsilon = (is_numeric($epsilon) ? abs((float)$epsilon) : 0.00001); // Default epsilon for float compare 3927 $compare = FALSE; 3928 $both = 0; 3929 // Convert to float if possible 3930 if (is_numeric($a)) { $a = (float)$a; $both++; } 3931 if (is_numeric($b)) { $b = (float)$b; $both++; } 3932 3933 if ($both === 2) 3934 { 3935 // Compare numeric variables as float numbers 3936 // Based on compare logic from http://floating-point-gui.de/errors/comparison/ 3937 if ($a === $b) 3938 { 3939 $compare = 0; // Variables same 3940 $test = 0; 3941 } else { 3942 $diff = abs($a - $b); 3943 //$pow_epsilon = pow($epsilon, 2); 3944 if ($a == 0 || $b == 0) 3945 { 3946 // Around zero 3947 $test = $diff; 3948 $epsilon = pow($epsilon, 2); 3949 if ($test < $epsilon) { $compare = 0; } 3950 } else { 3951 // Note, still exist issue with numbers around zero (ie: -0.00000001, 0.00000002) 3952 $test = $diff / min(abs($a) + abs($b), PHP_INT_MAX); 3953 if ($test < $epsilon) { $compare = 0; } 3954 } 3955 } 3956 3957 if (OBS_DEBUG > 1) 3958 { 3959 print_message('Compare float numbers: "'.$a.'" with "'.$b.'", epsilon: "'.$epsilon.'", comparision: "'.$test.' < '.$epsilon.'", numbers: '.($compare === 0 ? 'SAME' : 'DIFFERENT')); 3960 } 3961 } else { 3962 // All other compare as usual 3963 if ($a === $b) 3964 { 3965 $compare = 0; // Variables same 3966 } 3967 } 3968 if ($compare === FALSE) 3969 { 3970 // Compare if variables not same 3971 if ($a > $b) 3972 { 3973 $compare = 1; // $a greater than $b 3974 } else { 3975 $compare = -1; // $a less than $b 3976 } 3977 } 3978 3979 return $compare; 3980} 3981 3982/** 3983 * Add integer numbers. 3984 * This function better to use with big Counter64 numbers 3985 * 3986 * @param int|string $a The first number 3987 * @param int|string $b The second number 3988 * @return string A number representing the sum of the arguments. 3989 */ 3990function int_add($a, $b) 3991{ 3992 switch (OBS_MATH) 3993 { 3994 case 'gmp': 3995 // Convert values to string 3996 $a = gmp_init_float($a); 3997 $b = gmp_init_float($b); 3998 // Better to use GMP extension, for more correct operations with big numbers 3999 // $a = "18446742978492891134"; $b = "0"; $sum = gmp_add($a, $b); echo gmp_strval($sum) . "\n"; // Result: 18446742978492891134 4000 // $a = "18446742978492891134"; $b = "0"; $sum = $a + $b; printf("%.0f\n", $sum); // Result: 18446742978492891136 4001 $sum = gmp_add($a, $b); 4002 $sum = gmp_strval($sum); // Convert GMP number to string 4003 print_debug("GMP ADD: $a + $b = $sum"); 4004 break; 4005 case 'bc': 4006 // Convert values to string 4007 $a = strval($a); 4008 $b = strval($b); 4009 $sum = bcadd($a, $b); 4010 print_debug("BC ADD: $a + $b = $sum"); 4011 break; 4012 default: 4013 // Fallback to php math 4014 $sum = $a + $b; 4015 // Convert this values to int string, for prevent rrd update error with big Counter64 numbers, 4016 // see: http://jira.observium.org/browse/OBSERVIUM-1749 4017 $sum = sprintf("%.0f", $sum); 4018 print_debug("PHP ADD: $a + $b = $sum"); 4019 } 4020 4021 return $sum; 4022} 4023 4024/** 4025 * Subtract integer numbers. 4026 * This function better to use with big Counter64 numbers 4027 * 4028 * @param int|string $a The first number 4029 * @param int|string $b The second number 4030 * @return string A number representing the subtract of the arguments. 4031 */ 4032function int_sub($a, $b) 4033{ 4034 switch (OBS_MATH) 4035 { 4036 case 'gmp': 4037 // Convert values to string 4038 $a = gmp_init_float($a); 4039 $b = gmp_init_float($b); 4040 $sub = gmp_sub($a, $b); 4041 $sub = gmp_strval($sub); // Convert GMP number to string 4042 print_debug("GMP SUB: $a - $b = $sub"); 4043 break; 4044 case 'bc': 4045 // Convert values to string 4046 $a = strval($a); 4047 $b = strval($b); 4048 $sub = bcsub($a, $b); 4049 print_debug("BC SUB: $a - $b = $sub"); 4050 break; 4051 default: 4052 // Fallback to php math 4053 $sub = $a - $b; 4054 // Convert this values to int string, for prevent rrd update error with big Counter64 numbers, 4055 // see: http://jira.observium.org/browse/OBSERVIUM-1749 4056 $sub = sprintf("%.0f", $sub); 4057 print_debug("PHP SUB: $a - $b = $sub"); 4058 } 4059 4060 return $sub; 4061} 4062 4063/** 4064 * GMP have troubles with float number math 4065 * 4066 * php > $sum = 1111111111111111111111111.1; echo sprintf("%.0f", $sum)."\n"; echo sprintf("%d", $sum)."\n"; echo strval($sum)."\n"; echo $sum; 40671111111111111111092469760 40688375319363669983232 40691.1111111111111E+24 40701.1111111111111E+24 4071php > $sum = "1111111111111111111111111.1"; echo sprintf("%.0f", $sum)."\n"; echo sprintf("%d", $sum)."\n"; echo strval($sum)."\n"; echo $sum; 40721111111111111111092469760 40739223372036854775807 40741111111111111111111111111.1 40751111111111111111111111111.1 4076 */ 4077function gmp_init_float($value) 4078{ 4079 if (is_int($value)) 4080 { 4081 return $value; 4082 } 4083 if (is_float($value)) 4084 { 4085 return sprintf("%.0f", $value); 4086 } 4087 if (strpos($value, '.') !== FALSE) 4088 { 4089 // Return int part of string 4090 list($value) = explode('.', $value); 4091 return $value; 4092 } 4093 4094 return "$value"; 4095} 4096 4097// Translate syslog priorities from string to numbers 4098// ie: ('emerg','alert','crit','err','warning','notice') >> ('0', '1', '2', '3', '4', '5') 4099// Note, this is safe function, for unknown data return 15 4100// DOCME needs phpdoc block 4101function priority_string_to_numeric($value) 4102{ 4103 $priority = 15; // Default priority for unknown data 4104 if (!is_numeric($value)) 4105 { 4106 foreach ($GLOBALS['config']['syslog']['priorities'] as $pri => $entry) 4107 { 4108 if (stripos($entry['name'], substr($value, 0, 3)) === 0) { $priority = $pri; break; } 4109 } 4110 } 4111 else if ($value == (int)$value && $value >= 0 && $value < 16) 4112 { 4113 $priority = (int)$value; 4114 } 4115 4116 return $priority; 4117} 4118 4119/** 4120 * Translate syslog facilities from string to numeric 4121 * 4122 */ 4123function facility_string_to_numeric($facility) 4124{ 4125 if (!is_numeric($facility)) 4126 { 4127 foreach ($GLOBALS['config']['syslog']['facilities'] as $f => $entry) 4128 { 4129 if ($entry['name'] == $facility) 4130 { 4131 $facility = $f; 4132 break; 4133 } 4134 } 4135 } 4136 else if ($facility == (int)$facility && $facility >= 0 && $facility <= 23) 4137 { 4138 $facility = (int)$facility; 4139 } 4140 4141 return $facility; 4142} 4143 4144function array_merge_indexed(&$array1, &$array2) 4145{ 4146 $merged = $array1; 4147 //print_vars($merged); 4148 4149 foreach ($array2 as $key => &$value) 4150 { 4151 if (is_array($value) && isset($merged[$key]) && is_array($merged[$key])) 4152 { 4153 $merged[$key] = array_merge_indexed($merged[$key], $value); 4154 } else { 4155 $merged[$key] = $value; 4156 } 4157 } 4158 4159 //print_vars($merged); 4160 return $merged; 4161} 4162 4163// Merge 2 arrays by their index, ie: 4164// Array( [1] => [TestCase] = '1' ) + Array( [1] => [Bananas] = 'Yes ) 4165// becomes 4166// Array( [1] => [TestCase] = '1', [Bananas] = 'Yes' ) 4167// 4168// array_merge_recursive() only works for string keys, not numeric as we get from snmp functions. 4169// 4170// Accepts infinite parameters. 4171// 4172// Currently not used. Does not cope well with multilevel arrays. 4173// DOCME needs phpdoc block 4174// MOVEME to includes/common.inc.php 4175/* 4176function array_merge_indexed() 4177{ 4178 $array = array(); 4179 4180 foreach (func_get_args() as $array2) 4181 { 4182 if (count($array2) == 0) continue; // Skip for loop for empty array, infinite loop ahead. 4183 for ($i = 0; $i <= count($array2); $i++) 4184 { 4185 foreach (array_keys($array2[$i]) as $key) 4186 { 4187 $array[$i][$key] = $array2[$i][$key]; 4188 } 4189 } 4190 } 4191 4192 return $array; 4193} 4194*/ 4195 4196// DOCME needs phpdoc block 4197// TESTME needs unit testing 4198function print_cli_heading($contents, $level = 2) 4199{ 4200 if (OBS_QUIET || !is_cli()) { return; } // Silent exit if not cli or quiet 4201 4202// $tl = html_entity_decode('╔', ENT_NOQUOTES, 'UTF-8'); // top left corner 4203// $tr = html_entity_decode('╗', ENT_NOQUOTES, 'UTF-8'); // top right corner 4204// $bl = html_entity_decode('╚', ENT_NOQUOTES, 'UTF-8'); // bottom left corner 4205// $br = html_entity_decode('╝', ENT_NOQUOTES, 'UTF-8'); // bottom right corner 4206// $v = html_entity_decode('║', ENT_NOQUOTES, 'UTF-8'); // vertical wall 4207// $h = html_entity_decode('═', ENT_NOQUOTES, 'UTF-8'); // horizontal wall 4208 4209// print_message($tl . str_repeat($h, strlen($contents)+2) . $tr . "\n" . 4210// $v . ' '.$contents.' ' . $v . "\n" . 4211// $bl . str_repeat($h, strlen($contents)+2) . $br . "\n", 'color'); 4212 4213 $level_colours = array('0' => '%W', '1' => '%g', '2' => '%c' , '3' => '%p'); 4214 4215 //print_message(str_repeat(" ", $level). $level_colours[$level]."##### %W". $contents ."%n\n", 'color'); 4216 print_message($level_colours[$level]."##### %W". $contents .$level_colours[$level]." #####%n\n", 'color'); 4217} 4218 4219// DOCME needs phpdoc block 4220// TESTME needs unit testing 4221function print_cli_data($field, $data = NULL, $level = 2) 4222{ 4223 if (OBS_QUIET || !is_cli()) { return; } // Silent exit if not cli or quiet 4224 4225 //$level_colours = array('0' => '%W', '1' => '%g', '2' => '%c' , '3' => '%p'); 4226 4227 //print_cli(str_repeat(" ", $level) . $level_colours[$level]." o %W".str_pad($field, 20). "%n "); 4228 //print_cli($level_colours[$level]." o %W".str_pad($field, 20). "%n "); // strlen == 24 4229 print_cli_data_field($field, $level); 4230 4231 $field_len = 0; 4232 $max_len = 110; 4233 4234 $lines = explode("\n", $data); 4235 4236 foreach ($lines as $line) 4237 { 4238 $len = strlen($line) + 24; 4239 if ($len > $max_len) 4240 { 4241 $len = $field_len; 4242 $data = explode(" ", $line); 4243 foreach ($data as $datum) 4244 { 4245 $len = $len + strlen($datum); 4246 if ($len > $max_len) 4247 { 4248 $len = strlen($datum); 4249 //$datum = "\n". str_repeat(" ", 26+($level * 2)). $datum; 4250 $datum = "\n". str_repeat(" ", 24). $datum; 4251 } else { 4252 $datum .= ' '; 4253 } 4254 print_cli($datum); 4255 } 4256 } else { 4257 $datum = str_repeat(" ", $field_len). $line; 4258 print_cli($datum); 4259 } 4260 $field_len = 24; 4261 print_cli(PHP_EOL); 4262 } 4263} 4264 4265// DOCME needs phpdoc block 4266// TESTME needs unit testing 4267function print_cli_data_field($field, $level = 2) 4268{ 4269 if (OBS_QUIET || !is_cli()) { return; } // Silent exit if not cli or quiet 4270 4271 $level_colours = array('0' => '%W', '1' => '%g', '2' => '%c' , '3' => '%p', '4' => '%y'); 4272 4273 // print_cli(str_repeat(" ", $level) . $level_colours[$level]." o %W".str_pad($field, 20). "%n "); 4274 print_cli($level_colours[$level]." o %W".str_pad($field, 20). "%n "); 4275} 4276 4277// DOCME needs phpdoc block 4278// TESTME needs unit testing 4279function print_cli_table($table_rows, $table_header = array(), $descr = NULL, $options = array()) 4280{ 4281 // FIXME, probably need ability to view this tables in WUI?! 4282 if (OBS_QUIET || !is_cli()) { return; } // Silent exit if not cli or quiet 4283 4284 if (!is_array($table_rows)) { print_debug("print_cli_table() argument $table_rows should be an array. Please report this error to developers."); return; } 4285 4286 if (!cli_is_piped() || OBS_DEBUG) 4287 { 4288 $count_rows = count($table_rows); 4289 if ($count_rows == 0) { return; } 4290 4291 if (strlen($descr)) 4292 { 4293 print_cli_data($descr, '', 3); 4294 } 4295 4296 // Init table and renderer 4297 $table = new \cli\Table(); 4298 cli\Colors::enable(TRUE); 4299 4300 // Set default maximum width globally 4301 if (!isset($options['max-table-width'])) 4302 { 4303 $options['max-table-width'] = 240; 4304 //$options['max-table-width'] = TRUE; 4305 } 4306 // WARNING, min-column-width not worked in cli Class, I wait when issue will fixed 4307 //$options['min-column-width'] = 30; 4308 if (!empty($options)) 4309 { 4310 $renderer = new cli\Table\Ascii; 4311 if (isset($options['max-table-width'])) 4312 { 4313 if ($options['max-table-width'] === TRUE) 4314 { 4315 // Set maximum table width as available columns in terminal 4316 $options['max-table-width'] = cli\Shell::columns(); 4317 } 4318 if (is_numeric($options['max-table-width'])) 4319 { 4320 $renderer->setConstraintWidth($options['max-table-width']); 4321 } 4322 } 4323 if (isset($options['min-column-width'])) 4324 { 4325 $cols = array(); 4326 foreach (current($table_rows) as $col) 4327 { 4328 $cols[] = $options['min-column-width']; 4329 } 4330 //var_dump($cols); 4331 $renderer->setWidths($cols); 4332 } 4333 $table->setRenderer($renderer); 4334 } 4335 4336 $count_header = count($table_header); 4337 if ($count_header) 4338 { 4339 $table->setHeaders($table_header); 4340 } 4341 $table->setRows($table_rows); 4342 $table->display(); 4343 echo(PHP_EOL); 4344 } else { 4345 print_cli_data("Notice", "Table output suppressed due to piped output.".PHP_EOL); 4346 } 4347} 4348 4349/** 4350 * Prints Observium banner containing ASCII logo and version information for use in CLI utilities. 4351 */ 4352function print_cli_banner() 4353{ 4354 if (OBS_QUIET || !is_cli()) { return; } // Silent exit if not cli or quiet 4355 4356 print_message("%W 4357 ___ _ _ 4358 / _ \ | |__ ___ ___ _ __ __ __(_) _ _ _ __ ___ 4359| | | || '_ \ / __| / _ \| '__|\ \ / /| || | | || '_ ` _ \ 4360| |_| || |_) |\__ \| __/| | \ V / | || |_| || | | | | | 4361 \___/ |_.__/ |___/ \___||_| \_/ |_| \__,_||_| |_| |_|%c 4362". 4363 str_pad(OBSERVIUM_PRODUCT_LONG." ".OBSERVIUM_VERSION, 59, " ", STR_PAD_LEFT)."\n". 4364 str_pad("http://www.observium.org" , 59, " ", STR_PAD_LEFT)."%N\n", 'color'); 4365 4366 // One time alert about deprecated (eol) php version 4367 if (version_compare(PHP_VERSION, OBS_MIN_PHP_VERSION, '<')) 4368 { 4369 $php_version = PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION . '.' . PHP_RELEASE_VERSION; 4370 print_message(" 4371 4372+---------------------------------------------------------+ 4373| | 4374| %rDANGER! ACHTUNG! BHUMAHUE!%n | 4375| | 4376". 4377 str_pad("| %WYour PHP version is too old (%r".$php_version."%W),", 64, ' ')."%n| 4378| %Wfunctionality may be broken. Please update your PHP!%n | 4379| %WCurrently recommended version(s): >%g7.1.x%n | 4380| | 4381| See additional information here: | 4382| %c". 4383 str_pad(OBSERVIUM_URL . '/docs/software_requirements/' , 56, ' ')."%n| 4384| | 4385+---------------------------------------------------------+ 4386", 'color'); 4387 } 4388} 4389 4390// TESTME needs unit testing 4391/** 4392 * Creates a list of php files available in the html/pages/front directory, to show in a 4393 * dropdown on the web configuration page. 4394 * 4395 * @return array List of front page files available 4396 */ 4397function config_get_front_page_files() 4398{ 4399 global $config; 4400 4401 $frontpages = array(); 4402 4403 foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($config['html_dir'] . '/pages/front')) as $file) 4404 { 4405 $filename = $file->getFileName(); 4406 if ($filename[0] != '.') 4407 { 4408 $frontpages["pages/front/$filename"] = nicecase(basename($filename,'.php')); 4409 } 4410 } 4411 4412 return $frontpages; 4413} 4414 4415/** 4416 * Triggers a rediscovery of the given device at the following discovery -h new run. 4417 * 4418 * @param array $device Device array. 4419 * @param array $modules Array with modules required for rediscovery, if empty rediscover device full 4420 * 4421 * @return mixed Status of added or not force device discovery 4422 */ 4423// TESTME needs unit testing 4424function force_discovery($device, $modules = array()) 4425{ 4426 $return = FALSE; 4427 4428 if (count($modules) == 0) 4429 { 4430 // Modules not passed, just full rediscover device 4431 $return = dbUpdate(array('force_discovery' => 1), 'devices', '`device_id` = ?', array($device['device_id'])); 4432 } else { 4433 // Modules passed, check if modules valid and enabled 4434 $modules = (array)$modules; 4435 $forced_modules = get_entity_attrib('device', $device['device_id'], 'force_discovery_modules'); 4436 if ($forced_modules) 4437 { 4438 // Already forced modules exist, merge it with new 4439 $modules = array_unique(array_merge($modules, json_decode($forced_modules, TRUE))); 4440 } 4441 4442 $valid_modules = array(); 4443 foreach ($GLOBALS['config']['discovery_modules'] as $module => $ok) 4444 { 4445 // Filter by valid and enabled modules 4446 if ($ok && in_array($module, $modules)) 4447 { 4448 $valid_modules[] = $module; 4449 } 4450 } 4451 4452 if (count($valid_modules)) 4453 { 4454 $return = dbUpdate(array('force_discovery' => 1), 'devices', '`device_id` = ?', array($device['device_id'])); 4455 set_entity_attrib('device', $device['device_id'], 'force_discovery_modules', json_encode($valid_modules)); 4456 } 4457 } 4458 4459 return $return; 4460} 4461 4462// From http://stackoverflow.com/questions/9339619/php-checking-if-the-last-character-is-a-if-not-then-tack-it-on 4463// Assumed free to use :) 4464// DOCME needs phpdoc block 4465// TESTME needs unit testing 4466function fix_path_slash($p) 4467{ 4468 $p = str_replace('\\','/',trim($p)); 4469 return (substr($p,-1)!='/') ? $p.='/' : $p; 4470} 4471 4472/** 4473 * Calculates missing fields of a mempool based on supplied information and returns them all. 4474 * This function also applies the scaling as requested. 4475 * Also works for storage. 4476 * 4477 * @param float $scale Scaling to apply to the supplied values. 4478 * @param int $used Used value of mempool, before scaling, or NULL. 4479 * @param int $total Total value of mempool, before scaling, or NULL. 4480 * @param int $free Free value of mempool, before scaling, or NULL. 4481 * @param int $perc Used percentage value of mempool, or NULL. 4482 * @param array $options Additional options, ie separate scales for used/total/free 4483 * 4484 * @return array Array consisting of 'used', 'total', 'free' and 'perc' fields 4485 */ 4486function calculate_mempool_properties($scale, $used, $total, $free, $perc = NULL, $options = array()) 4487{ 4488 // Scale, before maths! 4489 foreach (array('total', 'used', 'free') as $param) 4490 { 4491 if (is_numeric($$param)) 4492 { 4493 if (isset($options['scale_'.$param])) 4494 { 4495 // Separate sclae for current param 4496 $$param *= $options['scale_'.$param]; 4497 } 4498 else if ($scale != 0 && $scale != 1) 4499 { 4500 // Common scale 4501 $$param *= $scale; 4502 } 4503 } 4504 } 4505 4506 if (is_numeric($total) && is_numeric($free)) 4507 { 4508 $used = $total - $free; 4509 $perc = round($used / $total * 100, 2); 4510 } 4511 else if (is_numeric($used) && is_numeric($free)) 4512 { 4513 $total = $used + $free; 4514 $perc = round($used / $total * 100, 2); 4515 } 4516 else if (is_numeric($total) && is_numeric($perc)) 4517 { 4518 $used = $total * $perc / 100; 4519 $free = $total - $used; 4520 } 4521 else if (is_numeric($total) && is_numeric($used)) 4522 { 4523 $free = $total - $used; 4524 $perc = round($used / $total * 100, 2); 4525 } 4526 else if (is_numeric($perc)) 4527 { 4528 $total = 100; 4529 $used = $perc; 4530 $free = 100 - $perc; 4531 //$scale = 1; // Reset scale for percentage-only 4532 } 4533 if (OBS_DEBUG && ($perc < 0 || $perc > 100)) 4534 { 4535 print_error('Incorrect scales or passed params to function ' . __FUNCTION__ . '()'); 4536 } 4537 4538 return array('used' => $used, 'total' => $total, 'free' => $free, 'perc' => $perc); 4539} 4540 4541/** 4542 * Get all values from specific key in a multidimensional array 4543 * 4544 * @param $key string 4545 * @param $arr array 4546 * @return null|string|array 4547 */ 4548 4549function array_value_recursive($key, array $arr){ 4550 $val = array(); 4551 array_walk_recursive($arr, function($v, $k) use($key, &$val){ 4552 if($k == $key) array_push($val, $v); 4553 }); 4554 return count($val) > 1 ? $val : array_pop($val); 4555} 4556 4557function discovery_check_if_type_exist(&$valid, $entry, $entity_type) 4558{ 4559 4560 if (isset($entry['skip_if_valid_exist'])) 4561 { 4562 $tree = explode('->', $entry['skip_if_valid_exist']); 4563 //print_vars($tree); 4564 switch (count($tree)) 4565 { 4566 case 1: 4567 if (isset($valid[$entity_type][$tree[0]]) && 4568 count($valid[$entity_type][$tree[0]])) 4569 { 4570 print_debug("Excluded by valid exist: ".$entry['skip_if_valid_exist']); 4571 return TRUE; 4572 } 4573 break; 4574 case 2: 4575 if (isset($valid[$entity_type][$tree[0]][$tree[1]]) && 4576 count($valid[$entity_type][$tree[0]][$tree[1]])) 4577 { 4578 print_debug("Excluded by valid exist: ".$entry['skip_if_valid_exist']); 4579 return TRUE; 4580 } 4581 break; 4582 case 3: 4583 if (isset($valid[$entity_type][$tree[0]][$tree[1]][$tree[2]]) && 4584 count($valid[$entity_type][$tree[0]][$tree[1]][$tree[2]])) 4585 { 4586 print_debug("Excluded by valid exist: ".$entry['skip_if_valid_exist']); 4587 return TRUE; 4588 } 4589 break; 4590 default: 4591 print_debug("Too many array levels for valid sensor!"); 4592 } 4593 } 4594 return FALSE; 4595} 4596 4597function discovery_check_requires_pre($device, $entry, $entity_type) 4598{ 4599 4600 if (isset($entry['pre_test']) && is_array($entry['pre_test'])) 4601 { 4602 // Convert single test condition to multi-level condition 4603 if (isset($entry['pre_test']['operator'])) { 4604 $entry['pre_test'] = array($entry['pre_test']); 4605 } 4606 4607 foreach ($entry['pre_test'] as $test) 4608 { 4609 if (isset($test['oid'])) 4610 { 4611 // Fetch just the value eof the OID. 4612 $test['data'] = snmp_cache_oid($device, $test['oid'], NULL, NULL, OBS_SNMP_ALL); 4613 $oid = $test['oid']; 4614 } 4615 else if (isset($test['field'])) 4616 { 4617 $test['data'] = $entry[$test['field']]; 4618 $oid = $test['field']; 4619 } else { 4620 print_debug("Not correct Field (".$test['field'].") passed to discovery_check_requires(). Need add it to 'oid_extra' definition."); 4621 return FALSE; 4622 } 4623 if (test_condition($test['data'], $test['operator'], $test['value']) === FALSE) 4624 { 4625 print_debug("Excluded by not test condition: $oid [".$test['data']."] ".$test['operator']." [".implode(', ', (array)$test['value'])."]"); 4626 return TRUE; 4627 } 4628 } 4629 } 4630 4631 return FALSE; 4632} 4633 4634function discovery_check_requires($device, $entry, $array, $entity_type) 4635{ 4636 if (isset($entry['test']) && is_array($entry['test'])) 4637 { 4638 // Convert single test condition to multi-level condition 4639 if (isset($entry['test']['operator'])) { 4640 $entry['test'] = array($entry['test']); 4641 } 4642 4643 foreach ($entry['test'] as $test) 4644 { 4645 if (isset($test['oid'])) 4646 { 4647 // Fetch just the value eof the OID. 4648 $test['data'] = snmp_cache_oid($device, $test['oid'], NULL, NULL, OBS_SNMP_ALL); 4649 $oid = $test['oid']; 4650 } 4651 else if (isset($test['field'])) 4652 { 4653 $test['data'] = $array[$test['field']]; 4654 if (!isset($array[$test['field']])) 4655 { 4656 // Show debug error (some time Oid fetched, but not exist for current index) 4657 print_debug("Not correct Field (" . $test['field'] . ") passed to discovery_check_requires(). Need add it to 'oid_extra' definition."); 4658 //return FALSE; 4659 } 4660 $oid = $test['field']; 4661 } 4662 if (test_condition($test['data'], $test['operator'], $test['value']) === FALSE) 4663 { 4664 print_debug("Excluded by not test condition: $oid [".$test['data']."] ".$test['operator']." [".implode(', ', (array)$test['value'])."]"); 4665 return TRUE; 4666 } 4667 } 4668 } 4669 4670 return FALSE; 4671} 4672 4673 4674// EOF 4675