diff --git a/LibreNMS/Enum/PortAssociationMode.php b/LibreNMS/Enum/PortAssociationMode.php new file mode 100644 index 0000000000..c10adf83f1 --- /dev/null +++ b/LibreNMS/Enum/PortAssociationMode.php @@ -0,0 +1,75 @@ +. + * + * @link https://www.librenms.org + * + * @copyright 2022 Tony Murray + * @author Tony Murray + */ + +namespace LibreNMS\Enum; + +class PortAssociationMode +{ + const ifIndex = 1; + const ifName = 2; + const ifDescr = 3; + const ifAlias = 4; + + /** + * Get mode names keyed by id + * + * @return string[] + */ + public static function getModes(): array + { + return [ + self::ifIndex => 'ifIndex', + self::ifName => 'ifName', + self::ifDescr => 'ifDescr', + self::ifAlias => 'ifAlias', + ]; + } + + /** + * Translate a named port association mode to an integer for storage + * + * @param string $name + * @return int|null + */ + public static function getId(string $name): ?int + { + $names = array_flip(self::getModes()); + + return $names[$name] ?? null; + } + + /** + * Get name of given port association mode id + * + * @param int $id + * @return string|null + */ + public static function getName(int $id): ?string + { + $modes = self::getModes(); + + return $modes[$id] ?? null; + } +} diff --git a/LibreNMS/Exceptions/HostIpExistsException.php b/LibreNMS/Exceptions/HostIpExistsException.php index bfa060016d..35346a10f9 100644 --- a/LibreNMS/Exceptions/HostIpExistsException.php +++ b/LibreNMS/Exceptions/HostIpExistsException.php @@ -27,4 +27,31 @@ namespace LibreNMS\Exceptions; class HostIpExistsException extends HostExistsException { + /** + * @var string + */ + public $hostname; + /** + * @var string + */ + public $existing_hostname; + /** + * @var string + */ + public $ip; + + public function __construct(string $hostname, string $existing_hostname, string $ip) + { + $this->hostname = $hostname; + $this->existing_hostname = $existing_hostname; + $this->ip = $ip; + + $message = trans('exceptions.host_exists.ip_exists', [ + 'hostname' => $hostname, + 'existing' => $existing_hostname, + 'ip' => $ip, + ]); + + parent::__construct($message); + } } diff --git a/LibreNMS/Exceptions/HostSysnameExistsException.php b/LibreNMS/Exceptions/HostSysnameExistsException.php new file mode 100644 index 0000000000..72956f2315 --- /dev/null +++ b/LibreNMS/Exceptions/HostSysnameExistsException.php @@ -0,0 +1,28 @@ +hostname = $hostname; + $this->sysname = $sysname; + + $message = trans('exceptions.host_exists.sysname_exists', [ + 'hostname' => $hostname, + 'sysname' => $sysname, + ]); + + parent::__construct($message); + } +} diff --git a/LibreNMS/Exceptions/HostUnreachableException.php b/LibreNMS/Exceptions/HostUnreachableException.php index 68d799e6d4..f32adee9e6 100644 --- a/LibreNMS/Exceptions/HostUnreachableException.php +++ b/LibreNMS/Exceptions/HostUnreachableException.php @@ -42,11 +42,21 @@ class HostUnreachableException extends \Exception /** * Add additional reasons * - * @param string $message + * @param string $snmpVersion + * @param string $credentials */ - public function addReason($message) + public function addReason(string $snmpVersion, string $credentials) { - $this->reasons[] = $message; + $vars = [ + 'snmpver' => $snmpVersion, + 'credentials' => $credentials, + ]; + + if ($snmpVersion == 'v3') { + $this->reasons[] = trans('exceptions.host_unreachable.no_reply_credentials', $vars); + } else { + $this->reasons[] = trans('exceptions.host_unreachable.no_reply_community', $vars); + } } /** diff --git a/LibreNMS/Exceptions/HostUnreachablePingException.php b/LibreNMS/Exceptions/HostUnreachablePingException.php index 816fb98f6b..0e063594f0 100644 --- a/LibreNMS/Exceptions/HostUnreachablePingException.php +++ b/LibreNMS/Exceptions/HostUnreachablePingException.php @@ -25,6 +25,29 @@ namespace LibreNMS\Exceptions; +use LibreNMS\Util\IP; + class HostUnreachablePingException extends HostUnreachableException { + /** + * @var string + */ + public $hostname; + /** + * @var string + */ + public $ip; + + public function __construct(string $hostname) + { + $this->hostname = $hostname; + $this->ip = gethostbyname($hostname); + + $message = trans('exceptions.host_unreachable.unpingable', [ + 'hostname' => $hostname, + 'ip' => IP::isValid($this->ip) ? $this->ip : trans('exceptions.host_unreachable.unresolvable'), + ]); + + parent::__construct($message); + } } diff --git a/LibreNMS/Exceptions/HostUnreachableSnmpException.php b/LibreNMS/Exceptions/HostUnreachableSnmpException.php index 4f07020277..020ff34989 100644 --- a/LibreNMS/Exceptions/HostUnreachableSnmpException.php +++ b/LibreNMS/Exceptions/HostUnreachableSnmpException.php @@ -27,4 +27,17 @@ namespace LibreNMS\Exceptions; class HostUnreachableSnmpException extends HostUnreachableException { + /** + * @var string + */ + public $hostname; + + public function __construct(string $hostname) + { + $this->hostname = $hostname; + $message = trans('exceptions.host_unreachable.unsnmpable', [ + 'hostname' => $hostname, + ]); + parent::__construct($message); + } } diff --git a/LibreNMS/Exceptions/HostnameExistsException.php b/LibreNMS/Exceptions/HostnameExistsException.php new file mode 100644 index 0000000000..3689a2b5f5 --- /dev/null +++ b/LibreNMS/Exceptions/HostnameExistsException.php @@ -0,0 +1,26 @@ +hostname = $hostname; + + $message = trans('exceptions.host_exists.hostname_exists', [ + 'hostname' => $hostname, + ]); + + parent::__construct($message); + } +} diff --git a/LibreNMS/Exceptions/SnmpVersionUnsupportedException.php b/LibreNMS/Exceptions/SnmpVersionUnsupportedException.php index bea520d55c..54579d617b 100644 --- a/LibreNMS/Exceptions/SnmpVersionUnsupportedException.php +++ b/LibreNMS/Exceptions/SnmpVersionUnsupportedException.php @@ -27,4 +27,16 @@ namespace LibreNMS\Exceptions; class SnmpVersionUnsupportedException extends \Exception { + /** + * @var string + */ + public $snmpVersion; + + public function __construct(string $snmpVersion) + { + $this->snmpVersion = $snmpVersion; + $message = trans('exceptions.snmp_version_unsupported.message', ['snmpver' => $snmpVersion]); + + parent::__construct($message); + } } diff --git a/addhost.php b/addhost.php index cd5d6d3f83..0de66c0dae 100755 --- a/addhost.php +++ b/addhost.php @@ -10,6 +10,7 @@ */ use LibreNMS\Config; +use LibreNMS\Enum\PortAssociationMode; use LibreNMS\Exceptions\HostUnreachableException; $init_modules = []; @@ -39,7 +40,7 @@ if (isset($options['f']) && $options['f'] == 0) { } $port_assoc_mode = Config::get('default_port_association_mode'); -$valid_assoc_modes = get_port_assoc_modes(); +$valid_assoc_modes = PortAssociationMode::getModes(); if (isset($options['p'])) { $port_assoc_mode = $options['p']; if (! in_array($port_assoc_mode, $valid_assoc_modes)) { diff --git a/app/Actions/Device/ValidateDeviceAndCreate.php b/app/Actions/Device/ValidateDeviceAndCreate.php new file mode 100644 index 0000000000..a6d9dd1c5d --- /dev/null +++ b/app/Actions/Device/ValidateDeviceAndCreate.php @@ -0,0 +1,235 @@ +. + * + * @package LibreNMS + * @link http://librenms.org + * @copyright 2022 Tony Murray + * @author Tony Murray + */ + +namespace App\Actions\Device; + +use App\Models\Device; +use Illuminate\Support\Arr; +use LibreNMS\Config; +use LibreNMS\Exceptions\HostIpExistsException; +use LibreNMS\Exceptions\HostnameExistsException; +use LibreNMS\Exceptions\HostSysnameExistsException; +use LibreNMS\Exceptions\HostUnreachablePingException; +use LibreNMS\Exceptions\HostUnreachableSnmpException; +use LibreNMS\Exceptions\SnmpVersionUnsupportedException; +use LibreNMS\Modules\Core; +use SnmpQuery; + +class ValidateDeviceAndCreate +{ + /** + * @var \App\Models\Device + */ + private $device; + /** + * @var bool + */ + private $force; + /** + * @var bool + */ + private $ping_fallback; + /** + * @var \LibreNMS\Polling\ConnectivityHelper + */ + private $connectivity; + + public function __construct(Device $device, bool $force = false, bool $ping_fallback = false) + { + $this->device = $device; + $this->force = $force; + $this->ping_fallback = $ping_fallback; + $this->connectivity = new \LibreNMS\Polling\ConnectivityHelper($this->device); + } + + /** + * @return bool + * + * @throws \LibreNMS\Exceptions\HostExistsException + * @throws \LibreNMS\Exceptions\HostUnreachablePingException + * @throws \LibreNMS\Exceptions\HostUnreachableException + * @throws \LibreNMS\Exceptions\SnmpVersionUnsupportedException + */ + public function execute(): bool + { + if ($this->device->exists) { + return false; + } + + $this->exceptIfHostnameExists(); + + // defaults + $this->device->os = $this->device->os ?: 'generic'; + $this->device->status_reason = ''; + $this->device->sysName = $this->device->sysName ?: $this->device->hostname; + + if (! $this->force) { + $this->exceptIfIpExists(); + + if (! $this->connectivity->isPingable()->success()) { + throw new HostUnreachablePingException($this->device->hostname); + } + + $this->detectCredentials(); + $this->cleanCredentials(); + + $this->device->sysName = SnmpQuery::device($this->device)->get('SNMPv2-MIB::sysName.0')->value(); + $this->exceptIfSysNameExists(); + + $this->device->os = Core::detectOS($this->device); + } + + return $this->device->save(); + } + + /** + * @throws \LibreNMS\Exceptions\HostUnreachableException + * @throws \LibreNMS\Exceptions\SnmpVersionUnsupportedException + */ + private function detectCredentials(): void + { + if ($this->device->snmp_disable) { + return; + } + + $host_unreachable_exception = new HostUnreachableSnmpException($this->device->hostname); + + // which snmp version should we try (and in what order) + $snmp_versions = $this->device->snmpver ? [$this->device->snmpver] : Config::get('snmp.version'); + + $communities = \LibreNMS\Config::get('snmp.community'); + if ($this->device->community) { + array_unshift($communities, $this->device->community); + } + $communities = array_unique($communities); + + $v3_credentials = \LibreNMS\Config::get('snmp.v3'); + array_unshift($v3_credentials, $this->device->only(['authlevel', 'authname', 'authpass', 'authalgo', 'cryptopass', 'cryptoalgo'])); + $v3_credentials = array_unique($v3_credentials, SORT_REGULAR); + + foreach ($snmp_versions as $snmp_version) { + $this->device->snmpver = $snmp_version; + + if ($snmp_version === 'v3') { + // Try each set of parameters from config + foreach ($v3_credentials as $v3) { + $this->device->fill(Arr::only($v3, ['authlevel', 'authname', 'authpass', 'authalgo', 'cryptopass', 'cryptoalgo'])); + + if ($this->connectivity->isSNMPable()) { + return; + } else { + $host_unreachable_exception->addReason($snmp_version, $this->device->authname . '/' . $this->device->authlevel); + } + } + } elseif ($snmp_version === 'v2c' || $snmp_version === 'v1') { + // try each community from config + foreach ($communities as $community) { + $this->device->community = $community; + if ($this->connectivity->isSNMPable()) { + return; + } else { + $host_unreachable_exception->addReason($snmp_version, $this->device->community); + } + } + } else { + throw new SnmpVersionUnsupportedException($snmp_version); + } + } + + if ($this->ping_fallback) { + $this->device->snmp_disable = 1; + $this->device->os = 'ping'; + + return; + } + + throw $host_unreachable_exception; + } + + private function cleanCredentials(): void + { + if ($this->device->snmpver == 'v3') { + $this->device->community = null; + } else { + $this->device->authlevel = null; + $this->device->authname = null; + $this->device->authalgo = null; + $this->device->cryptopass = null; + $this->device->cryptoalgo = null; + } + } + + /** + * @throws \LibreNMS\Exceptions\HostExistsException + */ + private function exceptIfHostnameExists(): void + { + if (Device::where('hostname', $this->device->hostname)->exists()) { + throw new HostnameExistsException($this->device->hostname); + } + } + + /** + * @throws \LibreNMS\Exceptions\HostExistsException + */ + private function exceptIfIpExists(): void + { + if ($this->device->overwrite_ip) { + $ip = $this->device->overwrite_ip; + } elseif (Config::get('addhost_alwayscheckip')) { + $ip = gethostbyname($this->device->hostname); + } else { + $ip = $this->device->hostname; + } + + $existing = Device::findByIp($ip); + + if ($existing) { + throw new HostIpExistsException($this->device->hostname, $existing->hostname, $ip); + } + } + + /** + * Check if a device with match hostname or sysname exists in the database. + * Throw and error if they do. + * + * @return void + * + * @throws \LibreNMS\Exceptions\HostExistsException + */ + private function exceptIfSysNameExists(): void + { + if (Config::get('allow_duplicate_sysName')) { + return; + } + + if (Device::where('sysName', $this->device->sysName) + ->when(Config::get('mydomain'), function ($query, $domain) { + $query->orWhere('sysName', rtrim($this->device->sysName, '.') . '.' . $domain); + })->exists()) { + throw new HostSysnameExistsException($this->device->hostname, $this->device->sysName); + } + } +} diff --git a/app/Console/Commands/DeviceAdd.php b/app/Console/Commands/DeviceAdd.php new file mode 100644 index 0000000000..6c9414a1a0 --- /dev/null +++ b/app/Console/Commands/DeviceAdd.php @@ -0,0 +1,147 @@ +optionValues = [ + 'transport' => ['udp', 'udp6', 'tcp', 'tcp6'], + 'port-association-mode' => PortAssociationMode::getModes(), + 'auth-protocol' => \LibreNMS\SNMPCapabilities::supportedAuthAlgorithms(), + 'privacy-protocol' => \LibreNMS\SNMPCapabilities::supportedCryptoAlgorithms(), + ]; + + $this->addArgument('device spec', InputArgument::REQUIRED); + $this->addOption('v1', null, InputOption::VALUE_NONE); + $this->addOption('v2c', null, InputOption::VALUE_NONE); + $this->addOption('v3', null, InputOption::VALUE_NONE); + $this->addOption('display-name', 'd', InputOption::VALUE_REQUIRED); + $this->addOption('force', 'f', InputOption::VALUE_NONE); + $this->addOption('poller-group', 'g', InputOption::VALUE_REQUIRED, null, Config::get('default_poller_group')); + $this->addOption('ping-fallback', 'b', InputOption::VALUE_NONE); + $this->addOption('port-association-mode', 'p', InputOption::VALUE_REQUIRED, null, Config::get('default_port_association_mode')); + $this->addOption('community', 'c', InputOption::VALUE_REQUIRED); + $this->addOption('transport', 't', InputOption::VALUE_REQUIRED, null, 'udp'); + $this->addOption('port', 'r', InputOption::VALUE_REQUIRED, null, 161); + $this->addOption('security-name', 'u', InputOption::VALUE_REQUIRED, null, 'root'); + $this->addOption('auth-password', 'A', InputOption::VALUE_REQUIRED); + $this->addOption('auth-protocol', 'a', InputOption::VALUE_REQUIRED, null, 'MD5'); + $this->addOption('privacy-password', 'X', InputOption::VALUE_REQUIRED); + $this->addOption('privacy-protocol', 'x', InputOption::VALUE_REQUIRED, null, 'AES'); + $this->addOption('ping-only', 'P', InputOption::VALUE_NONE); + $this->addOption('os', 'o', InputOption::VALUE_REQUIRED, null, 'ping'); + $this->addOption('hardware', 'w', InputOption::VALUE_REQUIRED); + $this->addOption('sysName', 's', InputOption::VALUE_REQUIRED); + } + + /** + * Execute the console command. + * + * @return int + */ + public function handle() + { + $this->configureOutputOptions(); + + $this->validate([ + 'port' => 'numeric|between:1,65535', + 'poller-group' => ['numeric', Rule::in(PollerGroup::pluck('id')->prepend(0))], + ]); + + $auth = $this->option('auth-password'); + $priv = $this->option('privacy-password'); + $device = new Device([ + 'hostname' => $this->argument('device spec'), + 'display' => $this->option('display-name'), + 'snmpver' => $this->option('v3') ? 'v3' : ($this->option('v2c') ? 'v2c' : ($this->option('v1') ? 'v1' : '')), + 'port' => $this->option('port'), + 'transport' => $this->option('transport'), + 'poller_group' => $this->option('poller-group'), + 'port_association_mode' => PortAssociationMode::getId($this->option('port-association-mode')), + 'community' => $this->option('community'), + 'authlevel' => ($auth ? 'auth' : 'noAuth') . (($priv && $auth) ? 'Priv' : 'NoPriv'), + 'authname' => $this->option('security-name'), + 'authpass' => $this->option('auth-password'), + 'authalgo' => $this->option('auth-protocol'), + 'cryptopass' => $this->option('privacy-password'), + 'cryptoalgo' => $this->option('privacy-protocol'), + ]); + + if ($this->option('ping-only')) { + $device->snmp_disable = 1; + $device->os = $this->option('os'); + $device->hardware = $this->option('hardware'); + $device->sysName = $this->option('sysName'); + } + + try { + $result = (new ValidateDeviceAndCreate($device, $this->option('force'), $this->option('ping-fallback')))->execute(); + + if (! $result) { + $this->error(trans('commands.device:add.messages.save_failed', ['hostname' => $device->hostname])); + + return 4; + } + + $this->info(trans('commands.device:add.messages.added', ['hostname' => $device->hostname, 'device_id' => $device->device_id])); + + return 0; + } catch (HostUnreachableException $e) { + // host unreachable errors + $this->error($e->getMessage() . PHP_EOL . implode(PHP_EOL, $e->getReasons())); + $this->line(trans('commands.device:add.messages.try_force')); + + return 1; + } catch (HostExistsException $e) { + // host exists errors + $this->error($e->getMessage()); + + if (! $e instanceof HostnameExistsException) { + $this->line(trans('commands.device:add.messages.try_force')); + } + + return 2; + } catch (Exception $e) { + // other errors? + $this->error(get_class($e) . ': ' . $e->getMessage()); + + return 3; + } + } +} diff --git a/app/Console/LnmsCommand.php b/app/Console/LnmsCommand.php index 4e84cb5c20..21fc08dca4 100644 --- a/app/Console/LnmsCommand.php +++ b/app/Console/LnmsCommand.php @@ -26,6 +26,7 @@ namespace App\Console; use Illuminate\Console\Command; +use Illuminate\Validation\Rule; use Illuminate\Validation\ValidationException; use LibreNMS\Util\Debug; use Symfony\Component\Console\Exception\InvalidArgumentException; @@ -35,6 +36,9 @@ abstract class LnmsCommand extends Command { protected $developer = false; + /** @var string[][]|null */ + protected $optionValues; + /** * Create a new command instance. * @@ -97,6 +101,10 @@ abstract class LnmsCommand extends Command $description = __('commands.' . $this->getName() . '.options.' . $name); } + if (isset($this->optionValues[$name])) { + $description .= ' [' . implode(', ', $this->optionValues[$name]) . ']'; + } + parent::addOption($name, $shortcut, $mode, $description, $default); return $this; @@ -108,6 +116,19 @@ abstract class LnmsCommand extends Command */ protected function validate(array $rules, array $messages = []): array { + // auto create option value rules if they don't exist + if (isset($this->optionValues)) { + foreach ($this->optionValues as $option => $values) { + if (empty($rules[$option])) { + $rules[$option] = Rule::in($values); + $messages[$option . '.in'] = trans('commands.lnms.validation-errors.optionValue', [ + 'option' => $option, + 'values' => implode(', ', $values), + ]); + } + } + } + $error_messages = trans('commands.' . $this->getName() . '.validation-errors'); $validator = Validator::make( $this->arguments() + $this->options(), diff --git a/app/Models/Device.php b/app/Models/Device.php index c4a539a354..620bc02c41 100644 --- a/app/Models/Device.php +++ b/app/Models/Device.php @@ -79,7 +79,7 @@ class Device extends BaseModel // ---- Helper Functions ---- - public static function findByHostname($hostname) + public static function findByHostname(string $hostname): ?Device { return static::where('hostname', $hostname)->first(); } @@ -110,7 +110,7 @@ class Device extends BaseModel return $overwrite_ip ?: $hostname; } - public static function findByIp($ip) + public static function findByIp(?string $ip): ?Device { if (! IP::isValid($ip)) { return null; @@ -561,8 +561,8 @@ class Device extends BaseModel return $query->whereIn( $query->qualifyColumn('device_id'), function ($query) use ($deviceGroup) { $query->select('device_id') - ->from('device_group_device') - ->where('device_group_id', $deviceGroup); + ->from('device_group_device') + ->where('device_group_id', $deviceGroup); } ); } @@ -572,8 +572,8 @@ class Device extends BaseModel return $query->whereNotIn( $query->qualifyColumn('device_id'), function ($query) use ($deviceGroup) { $query->select('device_id') - ->from('device_group_device') - ->where('device_group_id', $deviceGroup); + ->from('device_group_device') + ->where('device_group_id', $deviceGroup); } ); } @@ -583,8 +583,8 @@ class Device extends BaseModel return $query->whereIn( $query->qualifyColumn('device_id'), function ($query) use ($serviceTemplate) { $query->select('device_id') - ->from('service_templates_device') - ->where('service_template_id', $serviceTemplate); + ->from('service_templates_device') + ->where('service_template_id', $serviceTemplate); } ); } @@ -594,8 +594,8 @@ class Device extends BaseModel return $query->whereNotIn( $query->qualifyColumn('device_id'), function ($query) use ($serviceTemplate) { $query->select('device_id') - ->from('service_templates_device') - ->where('service_template_id', $serviceTemplate); + ->from('service_templates_device') + ->where('service_template_id', $serviceTemplate); } ); } diff --git a/includes/common.php b/includes/common.php index 77a95f42e3..497d690a8e 100644 --- a/includes/common.php +++ b/includes/common.php @@ -651,47 +651,6 @@ function format_hostname($device): string ]); } -/** - * Return valid port association modes - * - * @return array - */ -function get_port_assoc_modes() -{ - return [ - 1 => 'ifIndex', - 2 => 'ifName', - 3 => 'ifDescr', - 4 => 'ifAlias', - ]; -} - -/** - * Get DB id of given port association mode name - * - * @param string $port_assoc_mode - * @return int - */ -function get_port_assoc_mode_id($port_assoc_mode) -{ - $modes = array_flip(get_port_assoc_modes()); - - return isset($modes[$port_assoc_mode]) ? $modes[$port_assoc_mode] : false; -} - -/** - * Get name of given port association_mode ID - * - * @param int $port_assoc_mode_id Port association mode ID - * @return bool - */ -function get_port_assoc_mode_name($port_assoc_mode_id) -{ - $modes = get_port_assoc_modes(); - - return isset($modes[$port_assoc_mode_id]) ? $modes[$port_assoc_mode_id] : false; -} - /** * Query all ports of the given device (by ID) and build port array and * port association maps for ifIndex, ifName, ifDescr. Query port stats diff --git a/includes/discovery/ports.inc.php b/includes/discovery/ports.inc.php index 8b1794aec9..bbe4842a22 100644 --- a/includes/discovery/ports.inc.php +++ b/includes/discovery/ports.inc.php @@ -3,6 +3,7 @@ // Build SNMP Cache Array use App\Models\PortGroup; use LibreNMS\Config; +use LibreNMS\Enum\PortAssociationMode; use LibreNMS\Util\StringHelpers; $descrSnmpFlags = '-OQUs'; @@ -42,7 +43,7 @@ d_echo($port_stats); // compatibility reasons. $port_association_mode = Config::get('default_port_association_mode'); if ($device['port_association_mode']) { - $port_association_mode = get_port_assoc_mode_name($device['port_association_mode']); + $port_association_mode = PortAssociationMode::getName($device['port_association_mode']); } // Build array of ports in the database and an ifIndex/ifName -> port_id map diff --git a/includes/functions.php b/includes/functions.php index 499cb62788..db6da3211f 100644 --- a/includes/functions.php +++ b/includes/functions.php @@ -12,15 +12,17 @@ use App\Models\Device; use Illuminate\Support\Facades\Http; use Illuminate\Support\Str; use LibreNMS\Config; +use LibreNMS\Enum\PortAssociationMode; use LibreNMS\Exceptions\HostExistsException; use LibreNMS\Exceptions\HostIpExistsException; +use LibreNMS\Exceptions\HostnameExistsException; +use LibreNMS\Exceptions\HostSysnameExistsException; use LibreNMS\Exceptions\HostUnreachableException; use LibreNMS\Exceptions\HostUnreachablePingException; +use LibreNMS\Exceptions\HostUnreachableSnmpException; use LibreNMS\Exceptions\InvalidPortAssocModeException; use LibreNMS\Exceptions\SnmpVersionUnsupportedException; use LibreNMS\Modules\Core; -use LibreNMS\Util\IPv4; -use LibreNMS\Util\IPv6; use LibreNMS\Util\Proxy; function array_sort_by_column($array, $on, $order = SORT_ASC) @@ -112,24 +114,6 @@ function logfile($string) fclose($fd); } -/** - * Check an array of regexes against a subject if any match, return true - * - * @param string $subject the string to match against - * @param array|string $regexes an array of regexes or single regex to check - * @return bool if any of the regexes matched, return true - */ -function preg_match_any($subject, $regexes) -{ - foreach ((array) $regexes as $regex) { - if (preg_match($regex, $subject)) { - return true; - } - } - - return false; -} - /** * Perform comparison of two items based on give comparison method * Valid comparisons: =, !=, ==, !==, >=, <=, >, <, contains, starts, ends, regex @@ -201,36 +185,6 @@ function percent_colour($perc) return sprintf('#%02x%02x%02x', $r, $b, $b); } -/** - * @param $device - * @return string the logo image path for this device. Images are often wide, not square. - */ -function getLogo($device) -{ - $img = getImageName($device, true, 'images/logos/'); - if (! Str::startsWith($img, 'generic')) { - return 'images/logos/' . $img; - } - - return getIcon($device); -} - -/** - * @param array $device - * @param string $class to apply to the image tag - * @return string an image tag with the logo for this device. Images are often wide, not square. - */ -function getLogoTag($device, $class = null) -{ - $tag = 'hostname) { - $message .= " ($device->hostname)"; - } - $message .= '. You may force add to ignore this.'; - throw new HostIpExistsException($message); + if ($force_add !== true && $existing = device_has_ip($ip)) { + throw new HostIpExistsException($host, $existing->hostname, $ip); } // Test reachability if (! $force_add) { if (! ((new \LibreNMS\Polling\ConnectivityHelper(new Device(['hostname' => $ip])))->isPingable()->success())) { - throw new HostUnreachablePingException("Could not ping $host"); + throw new HostUnreachablePingException($host); } } @@ -374,7 +323,7 @@ function addHost($host, $snmp_version = '', $port = 161, $transport = 'udp', $po if (isset($additional['snmp_disable']) && $additional['snmp_disable'] == 1) { return createHost($host, '', $snmp_version, $port, $transport, [], $poller_group, 1, true, $overwrite_ip, $additional); } - $host_unreachable_exception = new HostUnreachableException("Could not connect to $host, please check the snmp details and snmp reachability"); + $host_unreachable_exception = new HostUnreachableSnmpException($host); // try different snmp variables to add the device foreach ($snmpvers as $snmpver) { if ($snmpver === 'v3') { @@ -384,7 +333,7 @@ function addHost($host, $snmp_version = '', $port = 161, $transport = 'udp', $po if ($force_add === true || isSNMPable($device)) { return createHost($host, null, $snmpver, $port, $transport, $v3, $poller_group, $port_assoc_mode, $force_add, $overwrite_ip); } else { - $host_unreachable_exception->addReason("SNMP $snmpver: No reply with credentials " . $v3['authname'] . '/' . $v3['authlevel']); + $host_unreachable_exception->addReason($snmpver, $v3['authname'] . '/' . $v3['authlevel']); } } } elseif ($snmpver === 'v2c' || $snmpver === 'v1') { @@ -395,11 +344,11 @@ function addHost($host, $snmp_version = '', $port = 161, $transport = 'udp', $po if ($force_add === true || isSNMPable($device)) { return createHost($host, $community, $snmpver, $port, $transport, [], $poller_group, $port_assoc_mode, $force_add, $overwrite_ip); } else { - $host_unreachable_exception->addReason("SNMP $snmpver: No reply with community $community"); + $host_unreachable_exception->addReason($snmpver, $community); } } } else { - throw new SnmpVersionUnsupportedException("Unsupported SNMP Version \"$snmpver\", must be v1, v2c, or v3"); + throw new SnmpVersionUnsupportedException($snmpver); } } if (isset($additional['ping_fallback']) && $additional['ping_fallback'] == 1) { @@ -422,7 +371,7 @@ function deviceArray($host, $community, $snmpver, $port = 161, $transport = 'udp /* Get port_assoc_mode id if neccessary * We can work with names of IDs here */ if (! is_int($port_assoc_mode)) { - $port_assoc_mode = get_port_assoc_mode_id($port_assoc_mode); + $port_assoc_mode = PortAssociationMode::getId($port_assoc_mode); } $device['port_association_mode'] = $port_assoc_mode; @@ -518,7 +467,7 @@ function createHost( /* Get port_assoc_mode id if necessary * We can work with names of IDs here */ if (! is_int($port_assoc_mode)) { - $port_assoc_mode = get_port_assoc_mode_id($port_assoc_mode); + $port_assoc_mode = PortAssociationMode::getId($port_assoc_mode); } $device = new Device(array_merge([ @@ -543,7 +492,7 @@ function createHost( $device->sysName = SnmpQuery::device($device)->get('SNMPv2-MIB::sysName.0')->value(); if (host_exists($host, $device->sysName)) { - throw new HostExistsException("Already have host $host ({$device->sysName}) due to duplicate sysName"); + throw new HostSysnameExistsException($host, $device->sysName); } } if ($device->save()) { @@ -920,23 +869,7 @@ function fix_integer_value($value) */ function device_has_ip($ip) { - if (IPv6::isValid($ip)) { - $ip_address = \App\Models\Ipv6Address::query() - ->where('ipv6_address', IPv6::parse($ip, true)->uncompressed()) - ->with('port.device') - ->first(); - } elseif (IPv4::isValid($ip)) { - $ip_address = \App\Models\Ipv4Address::query() - ->where('ipv4_address', $ip) - ->with('port.device') - ->first(); - } - - if (isset($ip_address) && $ip_address->port) { - return $ip_address->port->device; - } - - return false; // not an ipv4 or ipv6 address... + return Device::findByIp($ip); } /** @@ -1312,17 +1245,6 @@ function q_bridge_bits2indices($hex_data) return $indices; } -function update_device_logo(&$device) -{ - $icon = getImageName($device, false); - if ($icon != $device['icon']) { - log_event('Device Icon changed ' . $device['icon'] . " => $icon", $device, 'system', 3); - $device['icon'] = $icon; - dbUpdate(['icon' => $icon], 'devices', 'device_id=?', [$device['device_id']]); - echo "Changed Icon! : $icon\n"; - } -} - /** * Function to generate Mac OUI Cache */ diff --git a/includes/helpers.php b/includes/helpers.php index b47edd2d7f..e902b87e17 100644 --- a/includes/helpers.php +++ b/includes/helpers.php @@ -95,3 +95,23 @@ if (! function_exists('trans_fb')) { return ($key === ($translation = trans($key, $replace, $locale))) ? $fallback : $translation; } } + +if (! function_exists('preg_match_any')) { + /** + * Check an array of regexes against a subject if any match, return true + * + * @param string $subject the string to match against + * @param array|string $regexes an array of regexes or single regex to check + * @return bool if any of the regexes matched, return true + */ + function preg_match_any($subject, $regexes) + { + foreach ((array) $regexes as $regex) { + if (preg_match($regex, $subject)) { + return true; + } + } + + return false; + } +} diff --git a/includes/html/pages/addhost.inc.php b/includes/html/pages/addhost.inc.php index e3890119f4..3d3ce6cb12 100644 --- a/includes/html/pages/addhost.inc.php +++ b/includes/html/pages/addhost.inc.php @@ -1,6 +1,7 @@ hasGlobalAdmin()) { @@ -286,7 +287,7 @@ echo "