2683 lines
105 KiB
PHP

<?php
/**
* Observium
*
* This file is part of Observium.
*
* @package observium
* @subpackage entities
* @copyright (C) 2006-2013 Adam Armstrong, (C) 2013-2023 Observium Limited
*
*/
/**
* Generate device array.
*
* @param string $hostname
* @param string $snmp_community
* @param string $snmp_version
* @param int $snmp_port
* @param string $snmp_transport
* @param array $options
*
* @return array
*/
function build_initial_device_array($hostname, $snmp_community, $snmp_version, $snmp_port = 161, $snmp_transport = 'udp', $options = [])
{
$device = [];
$device['hostname'] = $hostname;
$device['snmp_port'] = $snmp_port;
$device['snmp_transport'] = $snmp_transport;
$device['snmp_version'] = $snmp_version;
if ($snmp_version === "v2c" || $snmp_version === "v1") {
$device['snmp_community'] = $snmp_community;
} elseif ($snmp_version === "v3") {
$device['snmp_authlevel'] = $options['authlevel'];
$device['snmp_authname'] = $options['authname'];
$device['snmp_authpass'] = $options['authpass'];
$device['snmp_authalgo'] = $options['authalgo'];
$device['snmp_cryptopass'] = $options['cryptopass'];
$device['snmp_cryptoalgo'] = $options['cryptoalgo'];
}
foreach (['snmp_maxrep', 'snmp_timeout', 'snmp_retries'] as $param) {
if (isset($options[$param]) && is_numeric($options[$param])) {
$device[$param] = (int)$options[$param];
}
}
// Append SNMPable OIDs if passed
if (isset($options['snmpable']) && strlen($options['snmpable'])) {
$device['snmpable'] = $options['snmpable'];
}
// Append SNMP context if passed
if (isset($options['snmp_context']) && strlen($options['snmp_context'])) {
$device['snmp_context'] = $options['snmp_context'];
}
print_debug_vars($device);
return $device;
}
/**
* Add device to DB or queue.
*
* @param array $vars
*
* @return bool|int
*/
function add_device_vars($vars)
{
global $config;
$hostname = strip_tags($vars['hostname']);
// Add device to remote poller,
// only validate vars and add to pollers_actions
if (is_intnum($vars['poller_id']) && $vars['poller_id'] != $config['poller_id']) {
print_message("Requested add device with hostname '$hostname' to remote Poller [{$vars['poller_id']}].");
if (!(is_valid_hostname($hostname) || get_ip_version($hostname))) {
// Failed DNS lookup
print_error("Hostname '$hostname' is not valid.");
return FALSE;
}
if (!dbExist('pollers', '`poller_id` = ?', [$vars['poller_id']])) {
// Incorrect Poller ID
print_error("Device with hostname '$hostname' not added. Unknown target Poller requested.");
return FALSE;
}
if ($tmp_id = dbFetchCell('SELECT `poller_id` FROM `observium_actions` WHERE `action` = ? AND `identifier` = ?', ['device_add', $hostname])) {
// Incorrect Poller ID
print_error("Already queued addition device with hostname '$hostname' on remote Poller [$tmp_id].");
return FALSE;
}
if (dbExist('devices', '`hostname` = ?', [$hostname])) {
// found in database
print_error("Already got device with hostname '$hostname'.");
return FALSE;
}
if (function_exists('add_action_queue') &&
$action_id = add_action_queue('device_add', $hostname, $vars)) {
print_message("Device with hostname '$hostname' added to queue [$action_id] for addition on remote Poller [{$vars['poller_id']}].");
log_event("Device with hostname '$hostname' added to queue [$action_id] for addition on remote Poller [{$vars['poller_id']}].", NULL, 'info', NULL, 7);
return TRUE;
}
print_error("Device with hostname '$hostname' not added. Incorrect addition to actions queue.");
return FALSE;
}
// Keep original snmp/rrd config, for revert at end
$config_snmp = $config['snmp'];
$config_rrd = $config['rrd_override'];
$snmp_oids = [];
if (isset($vars['snmpable']) && !empty($vars['snmpable'])) {
foreach (explode(' ', $vars['snmpable']) as $oid) {
if (preg_match(OBS_PATTERN_SNMP_OID_NUM, $oid)) {
// Valid Numeric OID
$snmp_oids[] = $oid;
} elseif (str_contains($oid, '::') && $oid_num = snmp_translate($oid)) {
// Named MIB::Oid which we can translate
$snmp_oids[] = $oid_num;
} else {
print_warning("Invalid or unknown OID: " . $oid);
}
}
if (empty($snmp_oids)) {
print_error("Incorrect or not numeric OIDs passed for check device availability.");
return FALSE;
}
}
// Default snmp port
if (is_valid_param($vars['snmp_port'], 'port')) {
$snmp_port = (int)$vars['snmp_port'];
} else {
$snmp_port = 161;
}
// Default snmp version
if ($vars['snmp_version'] !== "v2c" &&
$vars['snmp_version'] !== "v3" &&
$vars['snmp_version'] !== "v1") {
$vars['snmp_version'] = $config['snmp']['version'];
}
switch ($vars['snmp_version']) {
case 'v2c':
case 'v1':
if (!safe_empty($vars['snmp_community'])) {
// Hrm, I not sure why strip_tags
$snmp_community = strip_tags($vars['snmp_community']);
$config['snmp']['community'] = [$snmp_community];
}
$snmp_version = $vars['snmp_version'];
print_message("Adding SNMP$snmp_version host $hostname port $snmp_port");
break;
case 'v3':
if (!safe_empty($vars['snmp_authlevel'])) {
$snmp_v3 = [
'authlevel' => $vars['snmp_authlevel'],
'authname' => $vars['snmp_authname'],
'authpass' => $vars['snmp_authpass'],
'authalgo' => $vars['snmp_authalgo'],
'cryptopass' => $vars['snmp_cryptopass'],
'cryptoalgo' => $vars['snmp_cryptoalgo'],
];
array_unshift($config['snmp']['v3'], $snmp_v3);
}
$snmp_version = "v3";
print_message("Adding SNMPv3 host $hostname port $snmp_port");
break;
default:
print_error("Unsupported SNMP Version. There was a dropdown menu, how did you reach this error?"); // We have a hacker!
return FALSE;
}
if (get_var_true($vars['ignorerrd'], 'confirm')) {
$config['rrd_override'] = TRUE;
}
$snmp_options = [];
if (get_var_true($vars['ping_skip'])) {
$snmp_options['ping_skip'] = TRUE;
}
if (is_valid_param($vars['snmp_timeout'], 'snmp_timeout')) {
$snmp_options['snmp_timeout'] = (int)$vars['snmp_timeout'];
}
if (is_valid_param($vars['snmp_retries'], 'snmp_retries')) {
$snmp_options['snmp_retries'] = (int)$vars['snmp_retries'];
}
// Optional max repetition
if (is_intnum($vars['snmp_maxrep']) && $vars['snmp_maxrep'] >= 0 && $vars['snmp_maxrep'] <= 500) {
$snmp_options['snmp_maxrep'] = trim($vars['snmp_maxrep']);
}
// Optional SNMPable OIDs
if ($snmp_oids) {
$snmp_options['snmpable'] = implode(' ', $snmp_oids);
}
// Optional SNMP Context
if (trim($vars['snmp_context']) !== '') {
$snmp_options['snmp_context'] = trim($vars['snmp_context']);
}
$result = add_device($hostname, $snmp_version, $snmp_port, strip_tags($vars['snmp_transport']), $snmp_options);
// Revert original snmp/rrd config
$config['snmp'] = $config_snmp;
$config['rrd_override'] = $config_rrd;
return $result;
}
/**
* Adds the new device to the database.
*
* Before adding the device, checks duplicates in the database and the availability of device over a network.
*
* @param string $hostname Device hostname
* @param string|array $snmp_version SNMP version(s) (default: $config['snmp']['version'])
* @param int $snmp_port SNMP port (default: 161)
* @param string $snmp_transport SNMP transport (default: udp)
* @param array $options Additional options can be passed ('ping_skip' - for skip ping test and add device attrib for skip
* pings later
* 'break' - for break recursion,
* 'test' - for skip adding, only test device availability)
* @param int $flags
*
* @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.
*/
// TESTME needs unit testing
function add_device($hostname, $snmp_version = [], $snmp_port = 161, $snmp_transport = 'udp', $options = [], $flags = OBS_DNS_ALL)
{
global $config;
// If $options['break'] set as TRUE, break recursive function execute
if (isset($options['break']) && $options['break']) {
return FALSE;
}
$return = FALSE; // By default return FALSE
// Reset snmp timeout and retries options for speedup device adding
unset($config['snmp']['timeout'], $config['snmp']['retries']);
$snmp_transport = strtolower($snmp_transport);
$hostname = strtolower(trim($hostname));
// Try detect if hostname is IP
switch (get_ip_version($hostname)) {
case 6:
case 4:
if ($config['require_hostname']) {
print_error("Hostname should be a valid resolvable FQDN name. Or set config option \$config['require_hostname'] as FALSE.");
return $return;
}
$hostname = ip_compress($hostname); // Always use compressed IPv6 name
$ip = $hostname;
break;
default:
if ($snmp_transport === 'udp6' || $snmp_transport === 'tcp6') { // IPv6 used only if transport 'udp6' or 'tcp6'
$flags ^= OBS_DNS_A; // exclude A
}
// Test DNS lookup.
$ip = gethostbyname6($hostname, $flags);
}
// Test if host exists in database
if (!dbExist('devices', '`hostname` = ?', [$hostname])) {
if ($ip) {
$ip_version = get_ip_version($ip);
// Test reachability
$options['ping_skip'] = isset($options['ping_skip']) && $options['ping_skip'];
if ($options['ping_skip']) {
$flags |= OBS_PING_SKIP;
}
if (is_pingable($hostname, $flags)) {
// Test directory exists in /rrd/
if (!$config['rrd_override'] && file_exists($config['rrd_dir'] . '/' . $hostname)) {
print_error("Directory <observium>/rrd/$hostname already exists.");
return FALSE;
}
// Detect snmp transport
if (str_istarts($snmp_transport, 'tcp')) {
$snmp_transport = ($ip_version == 4 ? 'tcp' : 'tcp6');
} else {
$snmp_transport = ($ip_version == 4 ? 'udp' : 'udp6');
}
// Detect snmp port
if (!is_valid_param($snmp_port, 'port')) {
$snmp_port = 161;
} else {
$snmp_port = (int)$snmp_port;
}
// Detect snmp version
if (empty($snmp_version)) {
// Here set default snmp version order
$i = 1;
$snmp_version_order = [];
foreach (['v2c', 'v3', 'v1'] as $tmp_version) {
if ($config['snmp']['version'] == $tmp_version) {
$snmp_version_order[0] = $tmp_version;
} else {
$snmp_version_order[$i] = $tmp_version;
}
$i++;
}
ksort($snmp_version_order);
foreach ($snmp_version_order as $tmp_version) {
$ret = add_device($hostname, $tmp_version, $snmp_port, $snmp_transport, $options);
if ($ret === FALSE) {
// Set $options['break'] for break recursive
$options['break'] = TRUE;
} elseif (is_intnum($ret) && $ret != 0) {
return $ret;
}
}
} elseif ($snmp_version === "v3") {
// Try each set of parameters from config
foreach ($config['snmp']['v3'] as $auth_iter => $snmp) {
$snmp['version'] = $snmp_version;
$snmp['port'] = $snmp_port;
$snmp['transport'] = $snmp_transport;
foreach (['snmp_maxrep', 'snmp_timeout', 'snmp_retries'] as $param) {
if (isset($options[$param]) && is_numeric($options[$param])) {
$snmp[$param] = (int)$options[$param];
}
}
// Append SNMPable oids if passed
if (isset($options['snmpable']) && strlen($options['snmpable'])) {
$snmp['snmpable'] = $options['snmpable'];
}
// Append SNMP context if passed
if (isset($options['snmp_context']) && strlen($options['snmp_context'])) {
$snmp['snmp_context'] = $options['snmp_context'];
}
$device = build_initial_device_array($hostname, NULL, $snmp_version, $snmp_port, $snmp_transport, $snmp);
if ($config['snmp']['hide_auth'] && OBS_DEBUG < 2) {
// Hide snmp auth
print_message("Trying v3 parameters *** / ### [$auth_iter] ... ");
} else {
print_message("Trying v3 parameters " . $device['snmp_authname'] . "/" . $device['snmp_authlevel'] . " ... ");
}
if (is_snmpable($device)) {
if (!check_device_duplicated($device)) {
if (isset($options['test']) && $options['test']) {
print_message('%WDevice "' . $hostname . '" has successfully been tested and available by ' . strtoupper($snmp_transport) . ' transport with SNMP ' . $snmp_version . ' credentials.%n', 'color');
$device_id = -1;
} else {
//$device_id = createHost($hostname, NULL, $snmp_version, $snmp_port, $snmp_transport, $snmp);
$device_id = create_device($hostname, $snmp);
if ($options['ping_skip']) {
set_entity_attrib('device', $device_id, 'ping_skip', 1);
// Force pingable check
if (is_pingable($hostname, $flags ^ OBS_PING_SKIP)) {
//print_warning("You passed the option the skip device ICMP echo pingable checks, but device responds to ICMP echo. Please check device preferences.");
print_message("You have checked the option to skip ICMP ping, but the device responds to an ICMP ping. Perhaps you need to check the device settings.");
}
}
}
return $device_id;
} else {
// When detected duplicate device, this mean it already SNMPable and not need check next auth!
return FALSE;
}
} else {
print_warning("No reply on credentials " . $device['snmp_authname'] . "/" . $device['snmp_authlevel'] . " using $snmp_version.");
}
}
} elseif ($snmp_version === "v2c" || $snmp_version === "v1") {
// Try each community from config
foreach ($config['snmp']['community'] as $auth_iter => $snmp_community) {
$snmp = [
'community' => $snmp_community,
'version' => $snmp_version,
'port' => $snmp_port,
'transport' => $snmp_transport,
];
foreach (['snmp_maxrep', 'snmp_timeout', 'snmp_retries'] as $param) {
if (isset($options[$param]) && is_numeric($options[$param])) {
$snmp[$param] = (int)$options[$param];
}
}
// Append SNMPable oids if passed
if (isset($options['snmpable']) && strlen($options['snmpable'])) {
$snmp['snmpable'] = $options['snmpable'];
}
// Append SNMP context if passed
if (isset($options['snmp_context']) && strlen($options['snmp_context'])) {
$snmp['snmp_context'] = $options['snmp_context'];
}
$device = build_initial_device_array($hostname, $snmp_community, $snmp_version, $snmp_port, $snmp_transport, $snmp);
if ($config['snmp']['hide_auth'] && OBS_DEBUG < 2) {
// Hide snmp auth
print_message("Trying $snmp_version community *** [$auth_iter] ...");
} else {
print_message("Trying $snmp_version community $snmp_community ...");
}
//r($options);
//r($snmp);
//r($device);
if (is_snmpable($device)) {
if (!check_device_duplicated($device)) {
if (isset($options['test']) && $options['test']) {
print_message('%WDevice "' . $hostname . '" has successfully been tested and available by ' . strtoupper($snmp_transport) . ' transport with SNMP ' . $snmp_version . ' credentials.%n', 'color');
$device_id = -1;
} else {
//$device_id = createHost($hostname, $snmp_community, $snmp_version, $snmp_port, $snmp_transport, $snmp);
$device_id = create_device($hostname, $snmp);
if ($options['ping_skip']) {
set_entity_attrib('device', $device_id, 'ping_skip', 1);
// Force pingable check
if (is_pingable($hostname, $flags ^ OBS_PING_SKIP)) {
//print_warning("You passed the option the skip device ICMP echo pingable checks, but device responds to ICMP echo. Please check device preferences.");
print_message("You have checked the option to skip ICMP ping, but the device responds to an ICMP ping. Perhaps you need to check the device settings.");
}
}
}
return $device_id;
} else {
// When detected duplicate device, this mean it already SNMPable and not need check next auth!
return FALSE;
}
} else {
if ($config['snmp']['hide_auth'] && OBS_DEBUG < 2) {
print_warning("No reply on given community *** using $snmp_version.");
} else {
print_warning("No reply on community $snmp_community using $snmp_version.");
}
$return = 0; // Return zero for continue trying next auth
}
}
} else {
print_error("Unsupported SNMP Version \"$snmp_version\".");
$return = 0; // Return zero for continue trying next auth
}
if (!$device_id) {
// Failed SNMP
print_error("Could not reach $hostname with given SNMP parameters using $snmp_version.");
$return = 0; // Return zero for continue trying next auth
}
} else {
// failed Reachability
print_error("Could not ping $hostname.");
}
} else {
// Failed DNS lookup
print_error("Could not resolve $hostname.");
}
} else {
// found in database
print_error("Already got device $hostname.");
}
return $return;
}
/**
* Detect the device's OS
*
* Order for detect:
* if device rechecking (know old os): complex discovery (all), sysObjectID, sysDescr, file check
* if device first checking: complex discovery (except network), sysObjectID, sysDescr, complex discovery (network), file check
*
* @param array $device Device array
*
* @return string Detected device os name
*/
function get_device_os($device)
{
global $config, $table_rows, $cache_os;
// If $recheck sets as TRUE, verified that 'os' corresponds to the old value.
// recheck only if old device exist in definitions
$recheck = isset($config['os'][$device['os']]);
// Always force snmpwalk in os discovery without bulk for prevent fetch timeouts
// https://jira.observium.org/browse/OBS-3922
if (!isset($device['snmp_nobulk'])) {
$device['snmp_nobulk'] = TRUE;
}
$sysDescr = snmp_fix_string(snmp_cache_oid($device, 'sysDescr.0', 'SNMPv2-MIB'));
$sysDescr_ok = snmp_status() || snmp_error_code() === OBS_SNMP_ERROR_EMPTY_RESPONSE; // Allow empty response for sysDescr (not timeouts)
$sysObjectID = snmp_cache_sysObjectID($device);
// Cache discovery os definitions
cache_discovery_definitions();
$discovery_os = $GLOBALS['cache']['discovery_os'];
$cache_os = [];
$table_rows = [];
$table_opts = ['max-table-width' => TRUE]; // Set maximum table width as available columns in terminal
$table_headers = ['%WOID%n', ''];
$table_rows[] = ['sysDescr', $sysDescr];
$table_rows[] = ['sysObjectID', $sysObjectID];
print_cli_table($table_rows, $table_headers, NULL, $table_opts);
//print_debug("Detect OS. sysDescr: '$sysDescr', sysObjectID: '$sysObjectID'");
$table_rows = []; // Reinit
//$table_opts = array('max-table-width' => 200);
$table_headers = ['%WOID%n', '%WMatched definition%n', ''];
// By first check all sysObjectID
foreach ($discovery_os['sysObjectID'] as $def => $cos) {
if (match_oid_num($sysObjectID, $def)) {
// Store matched OS, but by first need check by complex discovery arrays!
$sysObjectID_def = $def;
$sysObjectID_os = $cos;
//print_debug_vars($sysObjectID_def);
//print_debug_vars($sysObjectID_os);
break;
}
}
if ($recheck) {
$table_desc = 'Re-Detect OS matched';
$old_os = $device['os'];
/*
if (!$sysDescr_ok && !empty($old_os)) {
// If sysDescr empty - return old os, because some snmp error
print_debug("ERROR: sysDescr not received, OS re-check stopped.");
return $old_os;
}
*/
// Recheck by complex discovery array
// Yes, before sysObjectID, because complex more accurate and can intersect with it!
foreach ($discovery_os['discovery'][$old_os] as $def) {
if (match_discovery_oids($device, $def, $sysObjectID, $sysDescr)) {
print_cli_table($table_rows, $table_headers, $table_desc . " ($old_os: " . $config['os'][$old_os]['text'] . '):', $table_opts);
return $old_os;
}
}
foreach ($discovery_os['discovery_network'][$old_os] as $def) {
if (match_discovery_oids($device, $def, $sysObjectID, $sysDescr)) {
print_cli_table($table_rows, $table_headers, $table_desc . " ($old_os: " . $config['os'][$old_os]['text'] . '):', $table_opts);
return $old_os;
}
}
/** DISABLED.
* Recheck only by complex, networked and file rules
*
* // Recheck by sysObjectID
* if ($sysObjectID_os)
* {
* // If OS detected by sysObjectID just return it
* $table_rows[] = array('sysObjectID', $sysObjectID_def, $sysObjectID);
* print_cli_table($table_rows, $table_headers, $table_desc . " ($old_os: ".$config['os'][$old_os]['text'].'):', $table_opts);
* return $sysObjectID_os;
* }
*
* // Recheck by sysDescr from definitions
* foreach ($discovery_os['sysDescr'][$old_os] as $pattern)
* {
* if (preg_match($pattern, $sysDescr))
* {
* $table_rows[] = array('sysDescr', $pattern, $sysDescr);
* print_cli_table($table_rows, $table_headers, $table_desc . " ($old_os: ".$config['os'][$old_os]['text'].'):', $table_opts);
* return $old_os;
* }
* }
*/
// Recheck by include file (moved to end!)
// Else full recheck 'os'!
unset($os, $file);
} // End recheck
$table_desc = 'Detect OS matched';
// Check by complex discovery arrays (except networked)
// Yes, before sysObjectID, because complex more accurate and can intersect with it!
foreach ($discovery_os['discovery'] as $cos => $defs) {
foreach ($defs as $def) {
if (match_discovery_oids($device, $def, $sysObjectID, $sysDescr)) {
$os = $cos;
if (OBS_DEBUG && $sysObjectID_os && $sysObjectID_os !== $cos) {
print_cli("OS %b$cos%n discovered by complex definition match, but also found %r$sysObjectID_os%n by exact sysObjectID '$sysObjectID_def' definition.\n");
}
break 2;
}
}
}
// Check by sysObjectID
if (!$os && $sysObjectID_os) {
// If OS detected by sysObjectID just return it
$os = $sysObjectID_os;
$table_rows[] = ['sysObjectID', $sysObjectID_def, $sysObjectID];
print_cli_table($table_rows, $table_headers, $table_desc . " ($os: " . $config['os'][$os]['text'] . '):', $table_opts);
return $os;
}
if (!$os && $sysDescr) {
// Check by sysDescr from definitions
foreach ($discovery_os['sysDescr'] as $cos => $patterns) {
foreach ($patterns as $pattern) {
if (preg_match($pattern, $sysDescr)) {
$table_rows[] = ['sysDescr', $pattern, $sysDescr];
$os = $cos;
break 2;
}
}
}
}
// Check by complex discovery arrays, now networked
if (!$os) {
foreach ($discovery_os['discovery_network'] as $cos => $defs) {
foreach ($defs as $def) {
if (match_discovery_oids($device, $def, $sysObjectID, $sysDescr)) {
$os = $cos;
break 2;
}
}
}
}
if (!$os) {
$path = $config['install_dir'] . '/includes/discovery/os';
$sysObjectId = $sysObjectID; // old files use wrong variable name
// Recheck first
$recheck_file = FALSE;
if ($recheck && $old_os) {
if (is_file($path . "/$old_os.inc.php")) {
$recheck_file = $path . "/$old_os.inc.php";
} elseif (isset($config['os'][$old_os]['discovery_os']) &&
is_file($path . '/' . $config['os'][$old_os]['discovery_os'] . '.inc.php')) {
$recheck_file = $path . '/' . $config['os'][$old_os]['discovery_os'] . '.inc.php';
}
if ($recheck_file) {
print_debug("Including $recheck_file");
$sysObjectId = $sysObjectID; // old files use wrong variable name
include($recheck_file);
if ($os && $os == $old_os) {
$table_rows[] = ['file', $recheck_file, ''];
print_cli_table($table_rows, $table_headers, $table_desc . " ($old_os: " . $config['os'][$old_os]['text'] . '):', $table_opts);
return $old_os;
}
}
}
// Check all other by include file
$dir_handle = @opendir($path) or die("Unable to open $path");
while ($file = readdir($dir_handle)) {
if (preg_match('/\.inc\.php$/', $file) && $file !== $recheck_file) {
print_debug("Including $file");
include($path . '/' . $file);
if ($os) {
$table_rows[] = ['file', $file, ''];
break; // Stop while if os detected
}
}
}
closedir($dir_handle);
}
if ($os) {
print_cli_table($table_rows, $table_headers, $table_desc . " ($os: " . $config['os'][$os]['text'] . '):', $table_opts);
return $os;
}
return 'generic';
}
function check_device_os_changed(&$device) {
$old_os = $device['os'];
$device['os'] = get_device_os($device);
if ($device['os'] != $old_os) {
print_cli_data("Device OS changed", $old_os . " -> " . $device['os'], 1);
log_event('OS changed: ' . $old_os . ' -> ' . $device['os'], $device, 'device', $device['device_id'], 'warning');
// Additionally, reset icon and type for device if os changed
dbUpdate(['os' => $device['os'], 'icon' => ['NULL'], 'type' => ['NULL']], 'devices', '`device_id` = ?', [$device['device_id']]);
if (isset($device['attribs']['override_icon'])) {
del_entity_attrib('device', $device, 'override_icon');
}
if (isset($device['attribs']['override_type'])) {
del_entity_attrib('device', $device, 'override_type');
}
// Reset models cache
if (isset($GLOBALS['cache']['devices']['model'][$device['device_id']])) {
unset($GLOBALS['cache']['devices']['model'][$device['device_id']]);
}
return TRUE;
}
return FALSE;
}
/**
* Check duplicated devices in DB by sysName, snmpEngineID and entPhysicalSerialNum (if possible)
* Can work only on local poller
*
* If found duplicate devices return TRUE, in other cases return FALSE
*
* @param array $device Device array which should be checked for duplicates
*
* @return bool TRUE if duplicates found
*/
// TESTME needs unit testing
function check_device_duplicated($device)
{
switch (get_device_duplicated($device)) {
case 'hostname':
// Hostname should be uniq
// Return TRUE if we have device with same hostname in DB
print_error("Already got device with hostname (" . $device['hostname'] . ").");
return TRUE;
case 'ip_snmp_v1':
case 'ip_snmp_v2c':
if (get_ip_version($device['hostname'])) {
$dns_ip = $device['hostname'];
} elseif (in_array($device['snmp_transport'], ['udp6', 'tcp6'])) {
$dns_ip = gethostbyname6($device['hostname'], OBS_DNS_AAAA); // IPv6
} else {
$dns_ip = gethostbyname6($device['hostname'], OBS_DNS_A); // IPv4
}
print_error("Already got device with resolved IP ($dns_ip) and SNMP v1/v2c community.");
return TRUE;
case 'ip_snmp_v3':
if (get_ip_version($device['hostname'])) {
$dns_ip = $device['hostname'];
} elseif (in_array($device['snmp_transport'], ['udp6', 'tcp6'])) {
$dns_ip = gethostbyname6($device['hostname'], OBS_DNS_AAAA); // IPv6
} else {
$dns_ip = gethostbyname6($device['hostname'], OBS_DNS_A); // IPv4
}
print_error("Already got device with resolved IP ($dns_ip) and SNMP v3 auth.");
return TRUE;
}
$snmpEngineID = snmp_cache_snmpEngineID($device);
$sysName_orig = snmp_get_oid($device, 'sysName.0', 'SNMPv2-MIB');
$sysName = strtolower($sysName_orig);
if (empty($sysName)) {
$sysName_type = 'empty';
$sysName = FALSE;
} elseif (is_valid_hostname($sysName_orig, TRUE)) {
// sysName stored in db as lowercase, always compare as lowercase too!
$sysName_type = 'fqdn';
} else {
// sysName not FQDN, hard case, many devices have default sysname or user just not write full sysname
$sysName_type = 'notfqdn';
}
if (!empty($snmpEngineID)) {
$test_devices = dbFetchRows('SELECT * FROM `devices` WHERE `disabled` = 0 AND `snmpEngineID` = ?', [$snmpEngineID]);
foreach ($test_devices as $test) {
$compare = strtolower($test['sysName']) === $sysName;
if ($compare) {
// Check (if possible) serial, for cluster devices sysName and snmpEngineID same
$test_entPhysical = dbFetchRow('SELECT * FROM `entPhysical` WHERE `device_id` = ? AND `entPhysicalSerialNum` != ? ORDER BY `entPhysicalClass` LIMIT 1', [$test['device_id'], '']);
if (isset($test_entPhysical['entPhysicalSerialNum'])) {
// Compare by any common serial
$serial = snmp_get_oid($device, 'entPhysicalSerialNum.' . $test_entPhysical['entPhysicalIndex'], 'ENTITY-MIB');
$compare = strtolower($serial) === strtolower($test_entPhysical['entPhysicalSerialNum']);
if ($compare) {
// This devices really same, with same sysName, snmpEngineID and entPhysicalSerialNum
print_error("Already got device with SNMP-read sysName ($sysName), 'snmpEngineID' = $snmpEngineID and 'entPhysicalSerialNum' = $serial (" . $test['hostname'] . ").");
return TRUE;
}
} else {
// For not FQDN sysname check all (other) possible system Oids:
if ($sysName_type !== 'fqdn') {
$compare = compare_devices_oids($device, $test);
// Same sysName and snmpEngineID, but different some other system Oids
if (!$compare) {
continue;
}
}
// Return TRUE if have same snmpEngineID && sysName in DB
print_error("Already got device with SNMP-read sysName ($sysName) and 'snmpEngineID' = $snmpEngineID (" . $test['hostname'] . ").");
return TRUE;
}
}
}
} else {
if ($sysName_type === 'empty' && ($device['os'] === 'generic' || empty($device['os']))) {
// For some derp devices (ie, only ent tree Oids) detect os before next checks
$device['os'] = get_device_os($device);
$test_devices = dbFetchRows('SELECT * FROM `devices` WHERE `disabled` = 0 AND `sysName` = ? AND `os` = ?', [$sysName, $device['os']]);
} else {
// If snmpEngineID empty, check by sysName (and additional system Oids)
$test_devices = dbFetchRows('SELECT * FROM `devices` WHERE `disabled` = 0 AND `sysName` = ?', [$sysName]);
}
foreach ($test_devices as $test) {
// Last check (if possible) serial, for cluster devices sysName and snmpEngineID same
$test_entPhysical = dbFetchRow('SELECT * FROM `entPhysical` WHERE `device_id` = ? AND `entPhysicalSerialNum` != ? ORDER BY `entPhysicalClass` LIMIT 1', [$test['device_id'], '']);
if (isset($test_entPhysical['entPhysicalSerialNum'])) {
$serial = snmp_get_oid($device, "entPhysicalSerialNum." . $test_entPhysical['entPhysicalIndex'], "ENTITY-MIB");
$compare = strtolower($serial) === strtolower($test_entPhysical['entPhysicalSerialNum']);
if ($compare) {
// This devices really same, with same sysName, snmpEngineID and entPhysicalSerialNum
print_error("Already got device with SNMP-read sysName ($sysName) and 'entPhysicalSerialNum' = $serial (" . $test['hostname'] . ").");
return TRUE;
}
} else {
// Check all (other) possible system Oids:
$compare = compare_devices_oids($device, $test);
// Same sysName and other system Oids
if ($compare) {
// Derp case, when device not have uniq Oids.. completely, ie Hikvision cams
//if (empty($sysName) && )
// This devices really same, with same sysName and other system Oids
print_error("Already got device with SNMP-read sysName ($sysName) and other system Oids (" . $test['hostname'] . ").");
return TRUE;
}
}
}
// if (!$has_entPhysical)
// {
// // Return TRUE if we have same sysName in DB
// print_error("Already got device with SNMP-read sysName ($sysName).");
// return TRUE;
// }
}
// In all other cases return FALSE
return FALSE;
}
/**
* Get duplicated device from DB.
* Can return found devices as array(s):
* $return['hostname'] - Same hostname in DB
* $return['ip_snmp'] - Same DNS IP and SNMP port (but different SNMP auth, not exactly same!)
* $return['ip_snmp_v1'] - Same DNS IP and SNMPv1 with same community
* $return['ip_snmp_v2c'] - Same DNS IP and SNMPv2c with same community
* $return['ip_snmp_v3'] - Same DNS IP and SNMPv3 with same v3 auth
*
* @param array $device Device array
* @param array $return Return found duplicate device array (if argument passed)
*
* @return string
*/
function get_device_duplicated($device, &$return = [])
{
if (empty($device['hostname'])) {
return NULL;
}
// Check if we need return device
$return_devices = func_num_args() > 1;
$duplicate = NULL;
// Check by same hostname in DB
if ($device['device_id'] && $device['device_id'] > 0) {
// Exclude self device
$where = '`hostname` = ? AND `device_id` != ?';
$params = [$device['hostname'], $device['device_id']];
} else {
$where = '`hostname` = ?';
$params = [$device['hostname']];
}
if (dbExist('devices', $where, $params)) {
$duplicate = 'hostname';
if ($return_devices) {
$return[$duplicate][] = dbFetchRow("SELECT * FROM `devices` WHERE " . $where, $params);
}
return $duplicate;
}
// Check by network access and SNMP
if (get_ip_version($device['hostname'])) {
$dns_ip = $device['hostname'];
} elseif (in_array($device['snmp_transport'], ['udp6', 'tcp6'])) {
$dns_ip = gethostbyname6($device['hostname'], OBS_DNS_AAAA); // IPv6
} else {
$dns_ip = gethostbyname6($device['hostname'], OBS_DNS_A); // IPv4
}
$snmp_port = is_intnum($device['snmp_port']) ? $device['snmp_port'] : 161;
if ($device['snmp_context']) {
// Also check snmp context
$where = '`ip` = ? AND `snmp_port` = ? AND `snmp_context` = ?';
$params = [ip_compress($dns_ip), $snmp_port, $device['snmp_context']];
} else {
$where = '`ip` = ? AND `snmp_port` = ? AND `snmp_context` IS NULL';
$params = [ip_compress($dns_ip), $snmp_port];
}
if ($device['device_id'] && $device['device_id'] > 0) {
// Exclude self device
$where .= ' AND `device_id` != ?';
$params[] = $device['device_id'];
}
if ($dns_ip && dbExist('devices', $where, $params)) {
// Quick check if same Device IP and SNMP port already exist, when true check snmp auth
foreach (dbFetchRows('SELECT * FROM `devices` WHERE ' . $where, $params) as $entry) {
if ($device['snmp_transport'] === 'v3') {
// SNMP v3 auth check
$device['snmp_authlevel'] = strtolower($device['snmp_authlevel']);
//$entry['snmp_authlevel'] = strtolower($entry['snmp_authlevel']);
if ($device['snmp_authlevel'] === 'noauthnopriv' && $device['snmp_authname'] === $entry['snmp_authname']) {
// Exactly same host, v3 noAuthNoPriv
$duplicate = 'ip_snmp_' . $entry['snmp_transport'];
if ($return_devices) {
$return[$duplicate][] = $entry;
}
} elseif ($device['snmp_authlevel'] === 'authnopriv' && $device['snmp_authname'] === $entry['snmp_authname'] &&
$device['snmp_authpass'] === $entry['snmp_authpass'] && $device['snmp_authalgo'] === $entry['snmp_authalgo']) {
// Exactly same host, v3 authNoPriv
$duplicate = 'ip_snmp_' . $entry['snmp_transport'];
if ($return_devices) {
$return[$duplicate][] = $entry;
}
} elseif ($device['snmp_authlevel'] === 'authpriv' && $device['snmp_authname'] === $entry['snmp_authname'] &&
$device['snmp_authpass'] === $entry['snmp_authpass'] && $device['snmp_authalgo'] === $entry['snmp_authalgo'] &&
$device['snmp_cryptopass'] === $entry['snmp_cryptopass'] && $device['snmp_cryptoalgo'] === $entry['snmp_cryptoalgo']) {
// Exactly same host, v3 authNoPriv
$duplicate = 'ip_snmp_' . $entry['snmp_transport'];
if ($return_devices) {
$return[$duplicate][] = $entry;
}
} elseif ($return_devices) {
// Not exactly same, just return as array
$return['ip_snmp'][] = $entry;
}
} else {
// SNMP v1/v2c community check
if ($device['snmp_community'] === $entry['snmp_community']) {
// Exactly same host
$duplicate = 'ip_snmp_' . $entry['snmp_transport'];
if ($return_devices) {
$return[$duplicate][] = $entry;
}
} elseif ($return_devices) {
// Not exactly same, just return as array
$return['ip_snmp'][] = $entry;
}
}
// Stop loop if not required devices return
if (!$return_devices && $duplicate) {
break;
}
}
// FIXME. Probably need to check poller_id and private nets (can be same on different pollers?)
}
return $duplicate;
}
function detect_device_snmp_maxrep($device, $mib_oid = NULL, $force = FALSE, $start_max_rep = NULL) {
global $config;
if (!$force) {
if (!$config['snmp']['max-rep'] || snmp_nobulk($device)) {
return FALSE;
}
if (is_numeric($device['snmp_maxrep'])) {
// Already configured for a device
return $device['snmp_maxrep'];
}
}
// Check if snmp bulk errors detected
$params = [ $device['device_id'], OBS_SNMP_ERROR_BULK_REQUEST_TIMEOUT, [ time() - 86400 ] ];
// Detect max-rep value when bulk errors detected
if ($force ||
($config['snmp']['errors'] && dbExist('snmp_errors', '`device_id` = ? AND `error_code` = ? AND `updated` > ?', $params))) {
$snmp_max_rep = 10; // Default when isn't set
if (is_numeric($start_max_rep) && $start_max_rep > 0) {
// force max-rep
$snmp_max_rep = (int)$start_max_rep;
} elseif (isset($cache['devices']['model'][$device['device_id']]['snmp']['max-rep']) &&
is_numeric($cache['devices']['model'][$device['device_id']]['snmp']['max-rep'])) {
// Model specific can be FALSE
$snmp_max_rep = $cache['devices']['model'][$device['device_id']]['snmp']['max-rep'];
} elseif (isset($config['os'][$device['os']]['snmp']['max-rep']) &&
is_numeric($config['os'][$device['os']]['snmp']['max-rep'])) {
// OS specific
$snmp_max_rep = $config['os'][$device['os']]['snmp']['max-rep'];
}
print_debug("Found SNMP bulk errors, try detect minimum max-rep value, starting from '$snmp_max_rep'..");
$mib_oid = empty($mib_oid) ? 'IF-MIB::ifEntry' : $mib_oid;
// Mostly common Oid, probably get from snmp_errors instead
[ $mib, $oid ] = explode('::', $mib_oid, 2);
$device_tmp = $device;
$device_tmp['snmp_retries'] = 1;
$device_tmp['snmp_maxrep'] = 0; // Disable max-rep for first check
$data_tmp = snmpwalk_cache_oid($device_tmp, $oid, [], $mib);
if (snmp_status()) {
while ($snmp_max_rep > 0) {
$device_tmp['snmp_maxrep'] = $snmp_max_rep;
$data_tmp = snmpwalk_cache_oid($device_tmp, $oid, [], $mib);
if (snmp_status()) {
print_debug("Detected minimum SNMP max-rep value '$snmp_max_rep' (0 mean need disable).");
return (int)$snmp_max_rep;
}
$step = $snmp_max_rep > 10 ? 10 : 5;
$snmp_max_rep -= $step;
}
}
return 0; // Disable max-rep!
}
return FALSE;
}
/**
* Detect SNMP auth params without adding device by hostname or IP
* if SNMP auth detected return array with auth params or FALSE if not detected
*
* @param string $hostname
* @param int $snmp_port
* @param string $snmp_transport
* @param false $detect_ip_version
*
* @return array|false
*/
function detect_device_snmpauth($hostname, $snmp_port = 161, $snmp_transport = 'udp', $detect_ip_version = FALSE)
{
global $config;
// Additional checks for IP version
if ($detect_ip_version) {
$ip_version = get_ip_version($hostname);
if (!$ip_version) {
$ip = gethostbyname6($hostname);
$ip_version = get_ip_version($ip);
}
// Detect snmp transport
if (str_istarts($snmp_transport, 'tcp')) {
$snmp_transport = ($ip_version == 4 ? 'tcp' : 'tcp6');
} else {
$snmp_transport = ($ip_version == 4 ? 'udp' : 'udp6');
}
}
// Detect snmp port
if (!is_valid_param($snmp_port, 'port')) {
$snmp_port = 161;
} else {
$snmp_port = (int)$snmp_port;
}
// Here set default snmp version order
$i = 1;
$snmp_version_order = [];
foreach (['v2c', 'v3', 'v1'] as $tmp_version) {
if ($config['snmp']['version'] == $tmp_version) {
$snmp_version_order[0] = $tmp_version;
} else {
$snmp_version_order[$i] = $tmp_version;
}
$i++;
}
ksort($snmp_version_order);
foreach ($snmp_version_order as $snmp_version) {
if ($snmp_version === 'v3') {
// Try each set of parameters from config
foreach ($config['snmp']['v3'] as $auth_iter => $snmp_v3) {
$device = build_initial_device_array($hostname, NULL, $snmp_version, $snmp_port, $snmp_transport, $snmp_v3);
if ($config['snmp']['hide_auth'] && OBS_DEBUG < 2) {
// Hide snmp auth
print_message("Trying v3 parameters *** / ### [$auth_iter] ... ");
} else {
print_message("Trying v3 parameters " . $device['snmp_authname'] . "/" . $device['snmp_authlevel'] . " ... ");
}
if (is_snmpable($device)) {
return $device;
} elseif ($config['snmp']['hide_auth'] && OBS_DEBUG < 2) {
print_warning("No reply on credentials *** / ### using $snmp_version.");
} else {
print_warning("No reply on credentials " . $device['snmp_authname'] . "/" . $device['snmp_authlevel'] . " using $snmp_version.");
}
}
} else { // if ($snmp_version === "v2c" || $snmp_version === "v1")
// Try each community from config
foreach ($config['snmp']['community'] as $auth_iter => $snmp_community) {
$device = build_initial_device_array($hostname, $snmp_community, $snmp_version, $snmp_port, $snmp_transport);
if ($config['snmp']['hide_auth'] && OBS_DEBUG < 2) {
// Hide snmp auth
print_message("Trying $snmp_version community *** [$auth_iter] ...");
} else {
print_message("Trying $snmp_version community $snmp_community ...");
}
if (is_snmpable($device)) {
return $device;
} else {
print_warning("No reply on community $snmp_community using $snmp_version.");
}
}
}
}
return FALSE;
}
//DEPRECATED. Compatibility for old style
function create_host($hostname, $snmp_community, $snmp_version, $snmp_port = 161, $snmp_transport = 'udp', $snmp_extra = [])
{
$snmp_array = [
'snmp_version' => $snmp_version,
'snmp_port' => $snmp_port,
'snmp_transport' => $snmp_transport,
'community' => $snmp_community,
];
if (!empty($snmp_extra)) {
$snmp_array = array_merge($snmp_array, $snmp_extra);
}
return create_device($hostname, $snmp_array);
}
/**
* Add device into database
*
* @param string $hostname Device hostname
* @param array $snmp SNMP v1/v2c/v3 params and Extra options
*
* @return bool|string
*/
function create_device($hostname, $snmp = [])
{
$hostname = strtolower(trim($hostname));
$device = [
'hostname' => $hostname,
'sysName' => $hostname,
'status' => '1',
];
// Add snmp params & snmp extra
$snmp_params = [
'port', 'transport', 'version', 'timeout', 'retries', 'maxrep',
'community', 'authlevel', 'authname', 'authpass', 'authalgo',
'cryptopass', 'cryptoalgo', 'context',
];
foreach ($snmp_params as $param) {
$snmp_param = 'snmp_' . $param;
if (isset($snmp[$snmp_param])) {
// Or $snmp_v3['snmp_authlevel']
$device[$snmp_param] = $snmp[$snmp_param];
} elseif (isset($snmp[$param])) {
// Or $snmp_v3['authlevel']
$device[$snmp_param] = $snmp[$param];
} elseif ($param === 'port') {
$device[$snmp_param] = 161;
} elseif ($param === 'transport') {
$device[$snmp_param] = 'udp';
} else {
//$device[$snmp_param] = [ 'NULL' ];
}
}
// Append SNMPable oids if passed
if (isset($snmp['snmpable'])) {
$device['snmpable'] = $snmp['snmpable'];
}
// Local poller id (for distributed system)
$poller_id = $GLOBALS['config']['poller_id']; // $config['poller_id'] sets in sql-config.php
if (isset($GLOBALS['config']['poller_name']) &&
$poller_local_id = dbFetchCell("SELECT `poller_id` FROM `pollers` WHERE `poller_name` = ?", [$GLOBALS['config']['poller_name']])) {
$poller_local = $poller_id == $poller_local_id;
} else {
$poller_local = $poller_id == 0;
}
$device['poller_id'] = $poller_id;
// FIXME. Need ability for requests from remote poller or queue to remote
if ($poller_local) {
// SNMP requests only on local poller
$device['os'] = get_device_os($device);
$device['sysObjectID'] = snmp_cache_sysObjectID($device);
$device['snmpEngineID'] = snmp_cache_snmpEngineID($device);
$device['sysName'] = snmp_get_oid($device, 'sysName.0', 'SNMPv2-MIB');
$device['location'] = snmp_get_oid($device, 'sysLocation.0', 'SNMPv2-MIB', NULL, OBS_SNMP_ALL_UTF8);
$device['sysContact'] = snmp_get_oid($device, 'sysContact.0', 'SNMPv2-MIB', NULL, OBS_SNMP_ALL_UTF8);
}
if (!$poller_local || $device['os']) {
$device_id = dbInsert($device, 'devices');
if ($device_id) {
$device['device_id'] = $device_id;
$log_msg = "Device added: $hostname";
if ($poller_id > 0 &&
$poller_name = dbFetchCell("SELECT `poller_name` FROM `pollers` WHERE `poller_id` = ?", [$poller_id])) {
// Append poller name
$log_msg .= " (Poller: $poller_name [$poller_id])";
}
log_event($log_msg, $device_id, 'device', $device_id, 5); // severity 5, for logging user/console info
if (isset($device['sysObjectID']) && strlen($device['sysObjectID'])) {
log_event('sysObjectID -> ' . $device['sysObjectID'], $device, 'device', $device_id);
}
if (isset($device['snmpEngineID']) && strlen($device['snmpEngineID'])) {
log_event('snmpEngineID -> ' . $device['snmpEngineID'], $device, 'device', $device_id);
}
if ($poller_local && is_cli()) {
print_success("Now discovering " . $device['hostname'] . " (id = " . $device_id . ")");
$device['device_id'] = $device_id;
// Discover things we need when linking this to other hosts.
discover_device($device, $options = ['m' => 'os,mibs,ports,ip-addresses']);
// Reset `last_discovered` for full rediscover device by cron
dbUpdate(['last_discovered' => 'NULL'], 'devices', '`device_id` = ?', [$device_id]);
}
// Request for clear WUI cache
set_cache_clear('wui');
return ($device_id);
}
}
return FALSE;
}
/**
* Deletes device from database and RRD dir.
*
* @param int $id
* @param bool $delete_rrd
*
* @return false|string
*/
function delete_device($id, $delete_rrd = FALSE)
{
global $config;
$ret = PHP_EOL;
$device = device_by_id_cache($id);
$host = $device['hostname'];
if (!is_array($device)) {
return FALSE;
} else {
$ports = dbFetchRows("SELECT * FROM `ports` WHERE `device_id` = ?", [$id]);
if (!empty($ports)) {
$ret .= ' * Deleted interfaces: ';
$deleted_ports = [];
foreach ($ports as $int_data) {
$int_if = $int_data['ifDescr'];
$int_id = $int_data['port_id'];
delete_port($int_id, $delete_rrd);
$deleted_ports[] = "id=$int_id ($int_if)";
}
$ret .= implode(', ', $deleted_ports) . PHP_EOL;
}
// Remove entities from common tables
$deleted_entities = [];
foreach (get_device_entities($id) as $entity_type => $entity_ids) {
foreach ($config['entity_tables'] as $table) {
$where = '`entity_type` = ?' . generate_query_values_and($entity_ids, 'entity_id');
$table_status = dbDelete($table, $where, [$entity_type]);
if ($table_status) {
$deleted_entities[$entity_type] = 1;
}
}
}
if (count($deleted_entities)) {
$ret .= ' * Deleted common entity entries linked to device: ';
$ret .= implode(', ', array_keys($deleted_entities)) . PHP_EOL;
}
$deleted_tables = [];
$ret .= ' * Deleted device entries from tables: ';
foreach ($config['device_tables'] as $table) {
$where = '`device_id` = ?';
$table_status = dbDelete($table, $where, [$id]);
if ($table_status) {
$deleted_tables[] = $table;
}
}
// Remove autodiscovery entries
$table_status = dbDelete('autodiscovery', '`remote_device_id` = ?', [$id]);
if ($table_status) {
$deleted_tables[] = 'autodiscovery';
}
if (count($deleted_tables)) {
$ret .= implode(', ', $deleted_tables) . PHP_EOL;
// Request for clear WUI cache
set_cache_clear('wui');
}
if ($delete_rrd) {
$device_rrd = rtrim(get_rrd_path($device, ''), '/');
if (is_file($device_rrd . '/status.rrd')) {
external_exec("rm -rf " . escapeshellarg($device_rrd));
$ret .= ' * Deleted device RRDs dir: ' . $device_rrd . PHP_EOL;
}
}
log_event("Deleted device: $host", NULL, 'info', $id, 5); // severity 5, for logging user/console info
$ret .= " * Deleted device: $host";
}
return $ret;
}
function device_status_array(&$device)
{
global $attribs;
$flags = OBS_DNS_ALL;
if ($device['snmp_transport'] === 'udp6' || $device['snmp_transport'] === 'tcp6') { // Exclude IPv4 if used transport 'udp6' or 'tcp6'
$flags ^= OBS_DNS_A;
}
$attribs['ping_skip'] = isset($attribs['ping_skip']) && $attribs['ping_skip'];
if ($attribs['ping_skip']) {
$flags |= OBS_PING_SKIP; // Add skip ping flag
}
$device['status_pingable'] = is_pingable($device['hostname'], $flags);
$device['pingable'] = $device['status_pingable']; // Compat
if ($device['status_pingable']) {
$device['status_snmpable'] = is_snmpable($device);
if ($device['status_snmpable']) {
$ping_msg = ($attribs['ping_skip'] ? '' : 'PING (' . $device['status_pingable'] . 'ms) and ');
//print_cli_data("Device status", "Device is reachable by " . $ping_msg . "SNMP (".$device['status_snmpable']."ms)", 1);
$status_message = "Device is reachable by " . $ping_msg . "SNMP (" . $device['status_snmpable'] . "ms)";
$status = "1";
$status_type = 'ok';
} else {
//print_cli_data("Device status", "Device is not responding to SNMP requests", 1);
$status_message = "Device is not responding to SNMP requests";
$status = "0";
$status_type = 'snmp';
}
} else {
//print_cli_data("Device status", "Device is not responding to PINGs", 1);
$status_message = "Device is not responding to PINGs";
$status = "0";
//print_vars(get_status_var('ping_dns'));
if (isset_status_var('ping_dns') && get_status_var('ping_dns') !== 'ok') {
$status_message = "Device hostname is not resolved";
$status_type = 'dns';
} else {
$status_type = 'ping';
}
}
return ['status' => $status, 'status_type' => $status_type, 'message' => $status_message];
}
/**
* Return device name based on default hostname setting, ie purpose, descr, sysname
*
* @param array $device Device array
* @param integer $max_len Maximum length for returned name.
*
* @return string
*/
function device_name($device, $max_len = FALSE)
{
global $config;
switch (strtolower($config['web_device_name'])) {
case 'sysname':
$name_field = 'sysName';
break;
case 'purpose':
case 'descr':
case 'description':
$name_field = 'purpose';
break;
default:
$name_field = 'hostname';
}
if ($max_len && !is_intnum($max_len)) {
$max_len = $config['short_hostname']['length'];
}
if ($name_field !== 'hostname' && !safe_empty($device[$name_field])) {
if ($name_field === 'sysName' && $max_len && $max_len > 3) {
// short sysname when is valid hostname (do not escape here)
return short_hostname($device[$name_field], $max_len, FALSE);
}
return $device[$name_field];
}
if ($max_len && $max_len > 3) {
// short hostname (do not escape here)
return short_hostname($device['hostname'], $max_len, FALSE);
}
return $device['hostname'];
}
/**
* Return device hostname or ip address, based on setting $config['use_ip']
*
* @param array $device Device array
* @param boolean $brackets When TRUE, return IPv6 addresses with square brackets
*
* @return string
*/
function device_host($device, $brackets = FALSE)
{
// Return cached IP (only for poller, other processes not resolve IPs)
if (OBS_PROCESS_NAME === 'poller' && $GLOBALS['config']['use_ip'] && !safe_empty($device['ip'])) {
// Return IPv6 addresses with square brackets
return ($brackets && str_contains($device['ip'], ':') ? '[' . $device['ip'] . ']' : $device['ip']);
}
// Use brackets when hostname is just IPv6 address
return ($brackets && str_contains($device['hostname'], ':') ? '[' . $device['hostname'] . ']' : $device['hostname']);
//return $device['hostname'];
}
/**
* If a device is up, return its uptime, otherwise return the
* time since the last time we were able to poll it. This
* is not very accurate, but better than reporting what the
* uptime was at some time before it went down.
*
* @param array $device
* @param string $format
*
* @return string
*/
// TESTME needs unit testing
function device_uptime($device, $format = "long")
{
if ($device['status'] == 0) {
if ($device['last_polled'] == 0) {
return "Never polled";
}
$since = time() - strtotime($device['last_polled']);
//$reason = isset($device['status_type']) && $format == 'long' ? '('.strtoupper($device['status_type']).') ' : '';
$reason = isset($device['status_type']) ? '(' . strtoupper($device['status_type']) . ') ' : '';
return "Down $reason" . format_uptime($since, $format);
}
return format_uptime($device['uptime'], $format);
}
// DOCME needs phpdoc block
// TESTME needs unit testing
function device_by_name($name, $refresh = FALSE)
{
// FIXME - cache name > id too.
return device_by_id_cache(get_device_id_by_hostname($name), $refresh);
}
/**
* Returns a device_id when given an entity_id and an entity_type. Returns FALSE if the device isn't found.
*
* @param $entity_id
* @param $entity_type
*
* @return bool|integer
*/
function get_device_id_by_entity_id($entity_id, $entity_type)
{
if ($entity_type === 'device') {
return $entity_id;
}
// $entity = get_entity_by_id_cache($entity_type, $entity_id);
$translate = entity_type_translate_array($entity_type);
if (isset($translate['device_id_field']) && $translate['device_id_field'] &&
is_numeric($entity_id) && $entity_type) {
$device_id = dbFetchCell('SELECT `' . $translate['device_id_field'] . '` FROM `' . $translate['table'] . '` WHERE `' . $translate['id_field'] . '` = ?', [$entity_id]);
}
if (isset($device_id) && is_numeric($device_id)) {
return $device_id;
} else {
//bdump([$entity_id, $entity_type]);
}
return FALSE;
}
/**
* Get cached/humanized device array
*
* @param integer|string $device_id
* @param boolean $refresh
*
* @return array|mixed
*/
function device_by_id_cache($device_id, $refresh = FALSE)
{
global $cache;
if (!is_intnum($device_id)) {
return FALSE;
}
if (!$refresh &&
isset($cache['devices']['id'][$device_id]) && is_array($cache['devices']['id'][$device_id]))
{
$device = $cache['devices']['id'][$device_id];
// Note, cached $device can be not humanized (by cache-data)
$refresh = !isset($device['humanized']);
} else {
if ($GLOBALS['config']['geocoding']['enable']) {
$query = "SELECT * FROM `devices` LEFT JOIN `devices_locations` USING (`device_id`)";
} else {
$query = "SELECT * FROM `devices`";
}
$device = dbFetchRow($query . " WHERE `device_id` = ?", [ $device_id ]);
$refresh = TRUE; // Set refresh
}
if (!empty($device) && $refresh) {
humanize_device($device);
get_device_graphs($device);
// Add to memory cache
$cache['devices']['id'][$device_id] = $device;
}
return $device;
}
function get_device_graphs(&$device)
{
//if ($refresh || !isset($device['graphs']))
//{
// Fetch device graphs
foreach (dbFetchRows("SELECT * FROM `device_graphs` WHERE `device_id` = ?", [$device['device_id']]) as $graph) {
$device['graphs'][$graph['graph']] = $graph;
}
//}
}
// DOCME needs phpdoc block
// TESTME needs unit testing
function get_device_id_by_hostname($hostname) {
global $cache;
$id = $cache['devices']['hostname'][$hostname] ?? dbFetchCell("SELECT `device_id` FROM `devices` WHERE `hostname` = ?", [ $hostname ]);
if (is_numeric($id)) {
return $id;
}
return FALSE;
}
function get_device_hostname_by_id($id) {
global $cache;
if (is_array($id)) {
if (isset($id['hostname'])) {
return $id['hostname'];
}
$id = $id['device_id'];
}
if (is_intnum($id)) {
return FALSE;
}
return $cache['devices']['hostname_map'][$id] ?? dbFetchCell("SELECT `hostname` FROM `devices` WHERE `device_id` = ?", [ $id ]);
}
// DOCME needs phpdoc block
// TESTME needs unit testing
function get_device_id_by_port_id($port_id)
{
if (is_intnum($port_id)) {
$device_id = dbFetchCell("SELECT `device_id` FROM `ports` WHERE `port_id` = ?", [$port_id]);
if (is_numeric($device_id)) {
return $device_id;
}
}
return FALSE;
}
// DOCME needs phpdoc block
// TESTME needs unit testing
function get_device_id_by_mac($mac, $exclude_device_id = NULL)
{
$remote_mac = mac_zeropad($mac);
if ($remote_mac && $remote_mac !== '000000000000') {
$where = '`deleted` = ? AND `ifPhysAddress` = ?';
$params = [0, $remote_mac];
if (is_numeric($exclude_device_id)) {
$where .= ' AND `device_id` != ?';
$params[] = $exclude_device_id;
}
$device_id = dbFetchCell("SELECT `device_id` FROM `ports` WHERE $where LIMIT 1;", $params);
if (is_numeric($device_id)) {
return $device_id;
}
}
return FALSE;
}
// DOCME needs phpdoc block
// TESTME needs unit testing
function get_device_id_by_app_id($app_id)
{
if (is_numeric($app_id) &&
$device_id = dbFetchCell("SELECT `device_id` FROM `applications` WHERE `app_id` = ?", [$app_id])) {
return $device_id;
}
return FALSE;
}
function get_device_vrf_contexts($device) {
if (!safe_empty($device['snmp_context'])) {
// Device already configured with snmp context
return NULL;
}
if (isset($GLOBALS['config']['os'][$device['os']]['snmp']['virtual']) &&
$GLOBALS['config']['os'][$device['os']]['snmp']['virtual']) {
// Context permitted for os
return safe_json_decode(get_entity_attrib('device', $device, 'vrf_contexts'));
}
return NULL;
}
// DOCME needs phpdoc block
// TESTME needs unit testing
function get_device_entphysical_state($device)
{
$state = [];
foreach (dbFetchRows("SELECT * FROM `entPhysical-state` WHERE `device_id` = ?", [$device]) as $entity) {
$state['group'][$entity['group']][$entity['entPhysicalIndex']][$entity['subindex']][$entity['key']] = $entity['value'];
$state['index'][$entity['entPhysicalIndex']][$entity['subindex']][$entity['group']][$entity['key']] = $entity['value'];
}
return $state;
}
/**
* Return model array from definitions, based on device sysObjectID
*
* @param array $device Device array required keys -> os, sysObjectID
* @param string $sysObjectID_new If passed, then use "new" sysObjectID instead from device array
*
* @return array|FALSE Model array or FALSE if no model specific definitions
*/
function get_model_array($device, $sysObjectID_new = NULL)
{
global $config, $cache;
if (!isset($config['os'][$device['os']]['model'])) {
return FALSE;
}
$model = $config['os'][$device['os']]['model'];
if (!isset($config['model'][$model])) {
return FALSE;
}
$models = $config['model'][$model];
$set_cache = FALSE;
if ($sysObjectID_new && preg_match('/^\.\d[\d\.]+$/', $sysObjectID_new)) {
// Use passed as param sysObjectID
$sysObjectID = $sysObjectID_new;
} elseif (isset($cache['devices']['model'][$device['device_id']])) {
// Return already cached array if no passed param sysObjectID
return $cache['devices']['model'][$device['device_id']];
} elseif (preg_match('/^\.\d[\d\.]+$/', $device['sysObjectID'])) {
// Use sysObjectID from device array
$sysObjectID = $device['sysObjectID'];
$set_cache = TRUE;
} else {
// Just random non empty string
$sysObjectID = 'empty_sysObjectID_3948ffakc';
$set_cache = TRUE;
}
if ($set_cache && (!is_numeric($device['device_id']) || defined('__PHPUNIT_PHAR__'))) {
// Do not set cache for unknown device_id (not added device) or phpunit
$set_cache = FALSE;
}
// Exactly sysObjectID match
if (isset($models[$sysObjectID])) {
// Check by device params, ie os version (see Juniper EX model definitions)
if (isset($models[$sysObjectID]['test'])) {
// Single associated array with test condition
$models[$sysObjectID] = [$models[$sysObjectID]];
}
if (!is_array_assoc($models[$sysObjectID])) {
// For cases when multiple test conditions for same sysObjectID
$model_def = FALSE;
foreach ($models[$sysObjectID] as $def) {
if (discovery_check_requires($device, $def, $device, 'device')) {
continue;
}
$model_def = $def;
break;
}
$models[$sysObjectID] = $model_def;
}
if ($set_cache) {
$cache['devices']['model'][$device['device_id']] = $models[$sysObjectID];
}
//r($models[$sysObjectID]);
print_debug("Found device [{$device['device_id']}] model '$model' exact association for $sysObjectID");
return $models[$sysObjectID];
}
// Resort sysObjectID array by oids with from high to low order!
uksort($config['model'][$model], 'compare_numeric_oids_reverse');
foreach ($config['model'][$model] as $key => $entry) {
$id_partial = $key;
if (substr($id_partial, -1) !== '.') {
$id_partial .= '.';
}
if (strpos($sysObjectID, $id_partial) === 0) {
// Check by device params, ie os version (see Juniper EX model definitions)
if (isset($entry['test'])) {
// Single associated array with test condition
$entry = [$entry];
}
if (!is_array_assoc($entry)) {
// For cases when multiple test conditions for same sysObjectID
$model = FALSE;
foreach ($entry as $def) {
if (discovery_check_requires($device, $def, $device, 'device')) {
continue;
}
$model = $def;
break;
}
$entry = $model;
}
if ($set_cache) {
$cache['devices']['model'][$device['device_id']] = $entry;
}
print_debug("Found device [{$device['device_id']}] model '$model' average association for $sysObjectID -> $id_partial*");
return $entry;
}
}
// If model array not found, set cache entry to FALSE, for do not search again
if ($set_cache) {
$cache['devices']['model'][$device['device_id']] = FALSE;
}
print_debug("Not found device [{$device['device_id']}] model '$model' association for $sysObjectID");
return FALSE;
}
/**
* Get a device param based on device os/sysObjectID and model definitions
*
* @param array $device Device array required keys -> os, sysObjectID
* @param string $param Requested parameter, ie 'hardware', 'type', 'vendor'
* @param string $sysObjectID_new If passed, then use "new" sysObjectID instead from device
*
* @return string|null Device model parameter or empty string
*/
function get_model_param($device, $param, $sysObjectID_new = NULL) {
$model_array = get_model_array($device, $sysObjectID_new);
if ($param === 'hardware' || empty($param)) {
$param = 'name';
}
if (is_array($model_array) && isset($model_array[$param])) {
return $model_array[$param];
}
return NULL;
}
/**
* Generates list of possible device hostnames for external integrations (ie RANCID, Oxidized)
*
* @param array|string $device
* @param array|string $suffix
* @param bool $dprint
*
* @return array
*/
function generate_device_hostnames($device, $suffix = '', $dprint = FALSE)
{
global $config;
$hostnames = [];
if (is_intnum($device)) {
$device = device_by_id_cache($device);
if ($dprint) {
echo "Device: get by device id.<br />";
}
} elseif (is_string($device)) {
$hostname = $device;
$device = device_by_name($device);
// Device by IP
if (!$device && get_ip_version($hostname) &&
$ids = get_entity_ids_ip_by_network('device', $hostname)) {
$device = device_by_id_cache($ids[0]);
if ($dprint) {
echo "Device: get by device IP.<br />";
}
} elseif ($dprint) {
echo "Device: get by hostname.<br />";
}
}
// Not valid device
if (!is_array($device) || !isset($device['hostname'])) {
if ($dprint) {
echo "Device: No valid device array or hostname passed.<br />";
}
return $hostnames;
}
// Base hostname
$hostname = $device['hostname'];
$hostnames[] = $device['hostname'];
if ($dprint) {
echo("Hostname: $hostname<br />");
}
// Also check non-FQDN hostname.
$is_ip = (bool)get_ip_version($hostname);
if (!$is_ip && str_contains($hostname, '.')) {
[$shortname] = explode('.', $hostname);
$hostnames[] = $shortname;
if ($dprint) {
echo("Short hostname: $shortname<br />");
}
}
// Addition of a domain suffix for non-FQDN device names.
if (!$is_ip && !safe_empty($suffix)) {
foreach ((array)$suffix as $append) {
$fqdn = $hostname . '.' . trim($append, ' .');
if (is_valid_hostname($fqdn, TRUE)) {
$hostnames[] = $fqdn;
if ($dprint) {
echo("Hostname suffix passed, also looking for " . $fqdn . "<br />");
}
}
}
}
// Device sysName
$sysname = strtolower($device['sysName']);
if (!in_array($sysname, $hostnames, TRUE) &&
!in_array($sysname, (array)$config['devices']['ignore_sysname'], TRUE) &&
is_valid_hostname($sysname)) {
$hostnames[] = $sysname;
if ($dprint) {
echo("sysName: $sysname<br />");
}
// Addition of a domain suffix for non-FQDN device sysName.
if (!str_contains($sysname, '.') && !safe_empty($suffix)) {
foreach ((array)$suffix as $append) {
$fqdn = $sysname . '.' . trim($append, ' .');
if (!in_array($fqdn, $hostnames, TRUE) &&
is_valid_hostname($fqdn, TRUE)) {
$hostnames[] = $fqdn;
if ($dprint) {
echo("Hostname suffix passed, also looking for " . $fqdn . "<br />");
}
}
}
}
}
return $hostnames;
}
/**
* Generate device tags for use with some entities (ie probes)
*
* @param array $device
* @param bool $escape
*
* @return array
*/
function generate_device_tags($device, $escape = TRUE)
{
$params = [
// Basic
'hostname',
// SNMP
'snmp_version', 'snmp_community', 'snmp_context', 'snmp_authlevel', 'snmp_authname', 'snmp_authpass', 'snmp_authalgo',
'snmp_cryptopass', 'snmp_cryptoalgo', 'snmp_port', 'snmp_timeout', 'snmp_retries'
];
$device_tags = [];
foreach ($params as $variable) {
if (!empty($device[$variable]) || in_array($variable, ['snmp_timeout', 'snmp_authname'])) {
switch ($variable) {
case 'snmp_version':
$device_tags[$variable] = str_replace('v', '', $device[$variable]);
break;
case 'snmp_authalgo':
case 'snmp_cryptoalgo':
$device_tags[$variable] = strtoupper($device[$variable]);
break;
case 'snmp_authname':
// Always pass username, default observium (need for noathnopriv)
$device_tags[$variable] = strlen($device[$variable]) ? $device[$variable] : 'observium';
break;
case 'snmp_timeout':
// Always pass timeout, default 15
$device_tags[$variable] = is_numeric($device[$variable]) ? $device[$variable] : 15;
break;
default:
$device_tags[$variable] = $device[$variable];
}
// Escape for pass to shell cmd
if ($escape) {
$device_tags[$variable] = escapeshellarg($device_tags[$variable]);
}
}
}
return $device_tags;
}
/**
* Filter SNMP secrets from $device array
*
* @param array $device De
* @param boolean $filter When TRUE, filter device secrets
* @param string|null $replace When non-empty replace secrets with string
*
* @return void
*/
function device_filter_secrets(&$device, $filter = FALSE, $replace = NULL) {
if ($filter) {
// Filter secrets
foreach ([ 'snmp_community', 'snmp_authlevel', 'snmp_authname', 'snmp_authpass',
'snmp_authalgo', 'snmp_cryptopass', 'snmp_cryptoalgo', 'snmp_context' ] as $field) {
if (safe_empty($replace)) {
unset($device[$field]);
} elseif (isset($device[$field])) {
$device[$field] = $replace;
}
}
}
}
/**
* Poll device metatypes, see os, system and wifi modules.
*
* @param array $device Device array
* @param array $metatypes List of required metatypes
* @param array $poll_device
*
* @return array
*/
function poll_device_mib_metatypes($device, $metatypes, &$poll_device = [])
{
global $config;
foreach ($metatypes as $metatype) {
switch ($metatype) {
case 'sysUpTime':
$param = 'uptime'; // For sysUptime use simple param name
break;
case 'wifi_clients':
$param = 'wifi_clients';
$metatype = 'wifi_clients1';
break;
default:
$param = strtolower($metatype);
}
foreach (get_device_mibs_permitted($device) as $mib) { // Check every MIB supported by the device, in order
if (isset($config['mibs'][$mib][$param])) {
$metatype_uptime = $metatype === 'sysUpTime' || $metatype === 'reboot';
foreach ($config['mibs'][$mib][$param] as $entry) {
// For ability override metatype by vendor mib definition use 'force' boolean param
if (!isset($poll_device[$metatype]) || // Poll if metatype not already set
$metatype_uptime || // Or uptime/reboot (always polled)
(isset($entry['force']) && $entry['force']) || // Or forced override (see ekinops EKINOPS-MGNT2-MIB mib definition
!is_valid_param($poll_device[$metatype], $metatype)) { // Poll if metatype not valid by previous round
if (discovery_check_requires_pre($device, $entry, 'device')) {
// Examples in RFC UPS-MIB, QNAP NAS-MIB and APC PowerNet-MIB
print_debug("Definition entry [$metatype] [$mib::" . $entry['oid'] . $entry['oid_next'] . $entry['oid_count'] . "] skipped by pre test condition");
continue;
}
// Force HEX to UTF8 conversion by default
$flags = isset($entry['snmp_flags']) ? $entry['snmp_flags'] : OBS_SNMP_ALL_UTF8;
if (isset($entry['table'])) {
// Get Oid by table walk (with possible tests)
$value = NULL;
foreach (snmp_cache_table($device, $entry['table'], [], $mib, NULL, $flags) as $index => $values) {
if (!isset($values[$entry['oid']])) {
continue;
} // Skip unknown entries
if (isset($entry['test']) && discovery_check_requires($device, $entry, $values, 'device')) {
// Examples in MDS-SYSTEM-MIB
print_debug("Definition entry [$metatype] [$mib::" . $entry['oid'] . "] skipped by test condition");
continue;
}
// if test not required, use first entry (as getnext)
$value = $values[$entry['oid']];
$full_oid = "$mib::{$entry['oid']}.$index";
break;
}
} elseif (isset($entry['oid_num'])) { // Use numeric OID if set, otherwise fetch text based string
$value = snmp_get_oid($device, $entry['oid_num'], NULL, $flags);
$full_oid = $entry['oid_num'];
} elseif (isset($entry['oid_next'])) {
// If Oid passed without index part use snmpgetnext (see FCMGMT-MIB definitions)
$value = snmp_getnext_oid($device, $entry['oid_next'], $mib, NULL, $flags);
$full_oid = "$mib::{$entry['oid_next']}";
} elseif (isset($entry['oid_count'])) {
// This is special type of get data by snmpwalk and count entries
$data = snmpwalk_values($device, $entry['oid_count'], $mib);
$value = is_array($data) ? count($data) : '';
$full_oid = str_starts($entry['oid_count'], '.') ? $entry['oid_count'] : "$mib::{$entry['oid_count']}";
} else {
$value = snmp_get_oid($device, $entry['oid'], $mib, NULL, $flags);
$full_oid = "$mib::{$entry['oid']}";
}
if (snmp_status() && !safe_empty($value)) {
$polled = round(snmp_endtime());
// Additional Oids for current metaparam (see IMM-MIB hardware definition)
if (isset($entry['oid_extra']) && is_valid_param($value, $metatype)) {
$snmp_next = isset($entry['oid_next']); // Main value use get next?
$extra = [];
foreach ((array)$entry['oid_extra'] as $oid_extra) {
//$value_extra = trim(snmp_hexstring(snmp_get_oid($device, $oid_extra, $mib)));
if ($snmp_next && !str_contains($oid_extra, '.')) {
$value_extra = snmp_getnext_oid($device, $oid_extra, $mib, NULL, $flags);
} else {
$value_extra = snmp_get_oid($device, $oid_extra, $mib, NULL, $flags);
}
if (snmp_status() && !safe_empty($value_extra)) {
$extra[] = $value_extra;
}
}
if (count($extra)) {
if ($metatype === 'version' && preg_match('/^[\d\.]+$/', $value)) {
// version -> xxx.y.z
$value .= '.' . implode('.', $extra);
} elseif ($metatype === 'sysname') {
$tmp = $value . '.' . implode('.', $extra);
if (is_valid_hostname($tmp, TRUE)) {
// Ie: ALLOT-MIB
$value = $tmp;
}
unset($tmp);
} else {
// others -> xxx (y, z)
$value .= ' (' . implode(', ', $extra) . ')';
}
}
unset($oid_extra, $extra, $value_extra);
}
// Field found (no SNMP error), perform optional transformations.
if (isset($entry['transform'])) {
// Just simplify definition entry (unify with others)
$entry['transformations'] = $entry['transform'];
}
$value = trim(string_transform($value, $entry['transformations']));
// Detect uptime with MIB defined oids, see below uptimes 2.
if ($metatype_uptime) {
// Previous uptime from standard sysUpTime or other MIB::oid
$uptime_previous = isset($poll_device['device_uptime']) ? $poll_device['device_uptime'] : $poll_device['sysUpTime'];
if ($param === 'uptime' && !isset($entry['transformations']) && !is_numeric($value)) {
// Convert common uptime strings to seconds by default
// See MDS-SYSTEM-MIB
$value = uptime_to_seconds($value);
} elseif ($param === 'reboot' && is_numeric($value)) {
// Last reboot is same uptime, but as diff with current (polled) time (see INFINERA-ENTITY-CHASSIS-MIB)
$value = $polled - $value;
}
// Detect if new sysUpTime value more than the previous
if (is_numeric($value) && $value >= $uptime_previous) {
$poll_device['device_uptime'] = $value;
// Fixme, not know how re-add this message
//$uptimes['message'] = isset($entry['oid_num']) ? $entry['oid_num'] : $mib . '::' . $entry['oid'];
//$uptimes['message'] = 'Using device MIB poller '.$metatype.': ' . $uptimes['message'];
print_debug("Added System Uptime from SNMP definition fetch: 'device_uptime' = '$value'");
// Continue for other possible sysUpTime and use maximum value
// but from different MIBs when valid time found (example UBNT-UniFi-MIB)
continue 2;
}
// Continue for other possible sysUpTime and use maximum value
continue;
}
if (!is_valid_param($value, $metatype)) {
// Skip invalid values
continue;
}
$poll_device[$metatype] = $value;
print_debug("Added Device param from SNMP definition by [$full_oid]: '$metatype' = '$value'");
// Exit both foreach loops and move on to the next metatype.
break 2;
}
}
}
}
}
}
print_debug_vars($poll_device);
return $poll_device;
}
function poll_device_unix_packages($device, $metatypes, $defs = [])
{
global $attribs, $agent_data;
// packages definitions
$package_defs = [];
if (empty($defs)) {
// Used in poll os packages
foreach ($GLOBALS['config']['os'][$device['os']]['packages'] as $def) {
$package_defs[$def['name']] = $def;
}
$attrib_key = 'os_package_index';
} else {
foreach ($defs as $def) {
$package_defs[$def['name']] = $def;
}
$attrib_key = 'def_package_index';
}
if (safe_empty($package_defs)) {
return [];
}
$data = [];
// Unix-agent packages exist?
$agent_packages = !safe_empty($agent_data['dpkg']) || !safe_empty($agent_data['rpm']);
if ($agent_packages) {
// by unix-agent packages
//$sql = 'SELECT * FROM `packages` WHERE `device_id` = ? AND `status` = ? AND `name` IN (?, ?, ?)';
$sql = 'SELECT * FROM `packages` WHERE `device_id` = ? AND `status` = ?';
$sql .= generate_query_values_and(array_keys($package_defs), 'name');
$params = [$device['device_id'], 1];
if ($package = dbFetchRow($sql, $params)) {
//$name = $package['name'];
$def = $package_defs[$package['name']];
print_debug_vars($def, 1);
print_debug_vars($package, 1);
// Type, features, version, etc
foreach ($metatypes as $metatype) {
if ($metatype === 'version' || $metatype === 'distro_ver') {
// Version
$data[$metatype] = $package['version'];
if (isset($def['transform'], $data[$metatype])) {
$data[$metatype] = string_transform($data[$metatype], $def['transform']);
}
} elseif (isset($def[$metatype])) {
// all other metatypes
$data[$metatype] = $def[$metatype];
}
}
}
} elseif (is_device_mib($device, 'HOST-RESOURCES-MIB')) {
// by SNMP query
$found = FALSE;
if (isset($attribs[$attrib_key])) {
// fetch package version and check if indexes was not changed
$package = snmp_get_oid($device, 'hrSWInstalledName.' . $attribs[$attrib_key], 'HOST-RESOURCES-MIB');
if (snmp_status()) {
foreach ($package_defs as $def) {
if (preg_match($def['regex'], $package, $matches)) {
$found = TRUE;
print_debug_vars($def, 1);
print_debug_vars($package, 1);
// Type, features, version, etc
foreach ($metatypes as $metatype) {
if ($metatype === 'version' || $metatype === 'distro_ver') {
// Version
if (isset($matches['version'])) {
$data[$metatype] = $matches['version'];
if (isset($def['transform'])) {
$data[$metatype] = string_transform($data[$metatype], $def['transform']);
}
}
} elseif (isset($def[$metatype])) {
// all other metatypes
$data[$metatype] = $def[$metatype];
}
}
break;
}
}
}
if (!$found) {
// packages changed, re-walk all
print_debug("Packages changed, refetch all");
del_entity_attrib('device', $device['device_id'], 'os_package_index');
unset($attribs[$attrib_key]);
}
unset($package);
}
if (!$found) {
// walk all packages
$oids = snmpwalk_cache_oid($device, "hrSWInstalledName", [], 'HOST-RESOURCES-MIB');
foreach ($oids as $index => $entry) {
$package = $entry['hrSWInstalledName'];
foreach ($package_defs as $def) {
if (preg_match($def['regex'], $package, $matches)) {
$found = TRUE;
print_debug_vars($def, 1);
print_debug_vars($entry, 1);
// Type, features, version, etc
foreach ($metatypes as $metatype) {
if ($metatype === 'version' || $metatype === 'distro_ver') {
// Version
if (isset($matches['version'])) {
$data[$metatype] = $matches['version'];
if (isset($def['transform'])) {
$data[$metatype] = string_transform($data[$metatype], $def['transform']);
}
}
} elseif (isset($def[$metatype])) {
// all other metatypes
$data[$metatype] = $def[$metatype];
}
}
// store package index for faster polling
if (!isset($attribs[$attrib_key]) || $attribs[$attrib_key] != $index) {
set_entity_attrib('device', $device['device_id'], 'os_package_index', $index);
}
break 2;
}
}
}
}
}
return $data;
}
function cache_device_attribs_exist($device, $refresh = FALSE)
{
if (is_array($device)) {
$device_id = $device['device_id'];
} else {
$device_id = $device;
}
// Pre-check if entity attribs for device exist
if ($refresh || !isset($GLOBALS['cache']['devices_attribs'][$device_id])) {
$GLOBALS['cache']['devices_attribs'][$device_id] = [];
foreach (dbFetchColumn('SELECT DISTINCT `entity_type` FROM `entity_attribs` WHERE `device_id` = ?', [$device_id]) as $entity_type) {
$GLOBALS['cache']['devices_attribs'][$device_id][$entity_type] = TRUE;
}
//r($GLOBALS['cache']['devices_attribs'][$device_id]);
//$GLOBALS['cache']['devices_attribs'][$device_id][$entity_type] = dbExist('entity_attribs', '`entity_type` = ? AND `device_id` = ?', [ $entity_type, $device_id ]);
}
}
/**
* Determine if poller/discovery module is enabled for a device
*
* @param array $device Device array
* @param string $module Module name
* @param string|null $process Process name (poller, discovery). Try to detect if not passed.
*
* @return bool
*/
function is_module_enabled($device, $module, $process = NULL)
{
global $config;
// Detect used process (poller, discovery, etc)
if (empty($process)) {
$process = OBS_PROCESS_NAME;
}
if (!in_array($process, [ 'poller', 'discovery' ])) {
print_debug("Module [$module] skipped. Not specified process name (poller or discovery).");
return FALSE;
}
// Detect if we need to check submodule, ie: ports_ifstats
[ $module, $submodule ] = explode('_', $module, 2);
// Pre-check if module is known (discovery_modules or poller_modules global config)
if (!isset($config[$process . '_modules'][$module])) {
print_debug("Module [$module] not exist for $process.");
return FALSE;
}
if (!safe_empty($submodule)) {
$ok = check_submodule($device, $module, $submodule);
} else {
$ok = check_main_module($device, $module, $process);
}
if (!$ok) {
$GLOBALS['cache']['devices'][$process . '_modules'][$device['device_id']]['disabled'][] = $module;
} else {
$GLOBALS['cache']['devices'][$process . '_modules'][$device['device_id']]['enabled'][] = $module;
}
return $ok;
}
function check_submodule($device, $module, $submodule)
{
global $config;
$module_name = $module . '_' . $submodule;
$setting_name = 'enable_' . $module_name;
if (!isset($config[$setting_name])) {
print_debug("Submodule [$module_name] not exist.");
return FALSE;
}
if ($module_name === 'ports_junoseatmvp' && $device['os'] !== 'junose') {
return FALSE;
}
$attrib = get_entity_attrib('device', $device, $setting_name);
if (!safe_empty($attrib)) {
return (bool)$attrib;
}
$enabled = $config[$setting_name];
$model = get_model_array($device);
if (isset($model[$module_name])) {
$enabled = (bool)$model[$module_name];
} elseif (isset($config['os'][$device['os']]['modules'][$module_name])) {
$enabled = (bool)$config['os'][$device['os']]['modules'][$module_name];
} elseif ($device['os_group'] && isset($config['os_group'][$device['os_group']]['modules'][$module_name])) {
$enabled = (bool)$config['os_group'][$device['os_group']]['modules'][$module_name];
} elseif (isset($config['os_group']['default']['modules'][$module_name])) {
$enabled = (bool)$config['os_group']['default']['modules'][$module_name];
}
if ($enabled && $module_name === 'ports_separate_walk' &&
$device['os'] === 'junos' && !isset($model[$module_name])) { // Derp.. bad, bad junos
return safe_empty($device['version']) || strnatcmp($device['version'], '17') <= 0;
}
return (bool)$enabled;
}
function check_main_module($device, $module, $process)
{
global $config;
if ($process === 'poller') {
if (in_array($module, [ 'os', 'system' ])) {
return TRUE;
}
if (poller_module_excluded($device, $module)) {
$GLOBALS['cache']['devices'][$process . '_modules'][$device['device_id']]['excluded'][$module] = $module;
return FALSE;
}
$setting_name = 'poll_' . $module;
} elseif ($process === 'discovery') {
if ($module === 'os') {
return TRUE;
}
$setting_name = 'discover_' . $module;
} else {
return FALSE;
}
$attrib = get_entity_attrib('device', $device, $setting_name);
if (!safe_empty($attrib)) {
return (bool)$attrib;
}
$blacklist_name = $process . '_blacklist';
if (isset($config['os'][$device['os']][$blacklist_name]) &&
in_array($module, (array)$config['os'][$device['os']][$blacklist_name], TRUE)) {
return FALSE;
}
$enabled = $config[$process . '_modules'][$module];
if ($enabled && isset($config['os'][$device['os']]['modules'][$module])) {
$enabled = $config['os'][$device['os']]['modules'][$module];
} elseif ($enabled && $device['os_group'] && isset($config['os_group'][$device['os_group']]['modules'][$module])) {
$enabled = $config['os_group'][$device['os_group']]['modules'][$module];
} elseif ($enabled && isset($config['os_group']['default']['modules'][$module])) {
$enabled = $config['os_group']['default']['modules'][$module];
} elseif ($module === 'wifi' && !in_array($device['type'], [ 'network', 'firewall', 'wireless' ], TRUE)) {
$enabled = FALSE;
}
return (bool)$enabled;
}
///FIXME. It's not a very nice solution, but will approach as temporal.
// Function return FALSE, if poller module allowed for device os (otherwise TRUE).
function poller_module_excluded($device, $module)
{
global $config;
///FIXME. rename module: 'wmi' -> 'windows-wmi'
if ($module === 'wmi') {
return $device['os'] !== 'windows';
}
if ($module === 'ipmi') {
return (!isset($config['os'][$device['os']]['ipmi']) || !$config['os'][$device['os']]['ipmi']);
}
if ($module === 'unix-agent') {
return !($device['os_group'] === 'unix' || $device['os'] === 'generic');
}
if (isset($config['os'][$device['os']]['poller_blacklist']) &&
in_array($module, (array)$config['os'][$device['os']]['poller_blacklist'], TRUE)) {
return TRUE;
}
if (!str_contains($module, '-')) {
return FALSE; // Check modules only with a dash.
}
$os_test = explode('-', $module)[0];
//var_dump($os_test);
///FIXME. rename module: 'cipsec-tunnels' -> 'cisco-ipsec-tunnels'
if ($os_test === 'cisco' || $os_test === 'cipsec') {
return $device['os_group'] !== 'cisco';
}
//$os_groups = array('cisco', 'unix');
//foreach ($os_groups as $os_group)
//{
// if ($os_test == $os_group && $device['os_group'] != $os_group) { return TRUE; }
//}
$oses = [ 'junose', 'arista_eos', 'netscaler', 'arubaos' ];
foreach ($oses as $os) {
if (strpos($os, $os_test) !== FALSE && $device['os'] != $os) {
return TRUE;
}
}
return FALSE;
}
/**
* Set polled/discovered module time
*
* @param array $device Device array
* @param string $module Module name
* @param int $time Unixtime
* @param string|null $process Process name (poller, discovery). Try to detect if not passed.
*
* @return int Time entry ID
*/
function set_module_time($device, $module, $time = 0, $process = NULL) {
global $config;
// Detect used process (poller, discovery, etc)
if (is_null($process) || !in_array($process, [ 'poller', 'discovery' ])) {
$process = OBS_PROCESS_NAME;
}
if (!in_array($process, [ 'poller', 'discovery' ])) {
print_debug("Module [$module] skipped. Not specified process name (poller or discovery).");
return FALSE;
}
// Pre check if module is known (discovery_modules or poller_modules global config)
if (!isset($config[$process . '_modules'][$module])) {
print_debug("Module [$module] not exist.");
return FALSE;
}
if ($time < 1) { $time = time(); }
$time_field = 'module_'.$process;
$id = dbFetchCell('SELECT `device_module_id` FROM `devices_modules` WHERE `device_id` = ? AND `module_name` = ?', [ $device['device_id'], $module ]);
if (is_numeric($id)) {
// update
dbUpdate([ $time_field => $time ], 'devices_modules', '`device_module_id` = ?', [ $id ]);
} else {
// insert
$insert = [
'device_id' => $device['device_id'],
'module_name' => $module,
$time_field => $time
];
$id = dbInsert($insert, 'devices_modules');
}
return $id;
}
/**
* Get polled/discovered module time
*
* @param array $device Device array
* @param string $module Module name
* @param string|null $process Process name (poller, discovery). Try to detect if not passed.
*
* @return int Time entry ID
*/
function get_module_time($device, $module, $process = NULL) {
// Detect used process (poller, discovery, etc)
if (is_null($process) || !in_array($process, [ 'poller', 'discovery' ])) {
$process = OBS_PROCESS_NAME;
}
if (!in_array($process, [ 'poller', 'discovery' ])) {
print_debug("Module [$module] skipped. Not specified process name (poller or discovery).");
return FALSE;
}
$time_field = 'module_'.$process;
return dbFetchCell('SELECT `'.$time_field.'` FROM `devices_modules` WHERE `device_id` = ? AND `module_name` = ?', [ $device['device_id'], $module ]);
}
/**
* Print discovery/poller module stats
*
* @param array $device Device array
* @param string $module Module name
*
* @global array $GLOBALS ['module_stats']
*/
function print_module_stats($device, $module)
{
$log_event = FALSE;
$stats_msg = [];
foreach (['added', 'updated', 'deleted', 'unchanged'] as $key) {
if ($GLOBALS['module_stats'][$module][$key]) {
$stats_msg[] = (int)$GLOBALS['module_stats'][$module][$key] . ' ' . $key;
if ($key != 'unchanged') {
$log_event = TRUE;
}
}
}
if (!empty($GLOBALS['module_stats'][$module])) {
echo PHP_EOL;
}
if (count($stats_msg)) {
print_cli_data("Changes", implode(', ', $stats_msg));
}
if ($GLOBALS['module_stats'][$module]['time']) {
print_cli_data("Duration", $GLOBALS['module_stats'][$module]['time'] . "s");
}
if ($log_event) {
log_event(nicecase($module) . ': ' . implode(', ', $stats_msg) . '.', $device, 'device', $device['device_id']);
}
}
/* OBSOLETE, BUT STILL USED FUNCTIONS */
// CLEANME remove when all function calls will be deleted
function get_dev_attrib($device, $attrib_type)
{
// Call to new function
return get_entity_attrib('device', $device, $attrib_type);
}
// CLEANME remove when all function calls will be deleted
function get_dev_attribs($device_id)
{
// Call to new function
return get_entity_attribs('device', $device_id);
}
// CLEANME remove when all function calls will be deleted
function set_dev_attrib($device, $attrib_type, $attrib_value)
{
// Call to new function
return set_entity_attrib('device', $device, $attrib_type, $attrib_value);
}
// CLEANME remove when all function calls will be deleted
function del_dev_attrib($device, $attrib_type)
{
// Call to new function
return del_entity_attrib('device', $device, $attrib_type);
}
// EOF