Migrate addhost.php to lnms device:add (#13870)

* Migrate addhost.php to lnms device:add
Have snmp-scan.py call lnms device:add (make exit codes line up so this works)
Fix issue with ping only devices trying to detect os via snmp
Reorder options in device:add help and improve formatting
Update docs to remove references to addhost.php
Fix a bit of code that was in functional code

* fixes

* fix snmp version message
This commit is contained in:
Tony Murray 2022-04-04 10:41:18 -05:00 committed by GitHub
parent 96b708a10e
commit 75ba74fe5b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 167 additions and 187 deletions

View File

@ -33,6 +33,7 @@ use LibreNMS\Config;
use LibreNMS\Exceptions\InvalidOidException;
use LibreNMS\Interfaces\Discovery\DiscoveryItem;
use LibreNMS\OS;
use LibreNMS\Util\Compare;
class YamlDiscovery
{
@ -352,7 +353,7 @@ class YamlDiscovery
}
}
if (compare_var($tmp_value, $skip_value['value'], $op)) {
if (Compare::values($tmp_value, $skip_value['value'], $op)) {
return true;
}
}

View File

@ -48,7 +48,7 @@ class HostUnreachableException extends \Exception
public function addReason(string $snmpVersion, string $credentials)
{
$vars = [
'snmpver' => $snmpVersion,
'version' => $snmpVersion,
'credentials' => $credentials,
];

View File

@ -31,6 +31,7 @@ use LibreNMS\Config;
use LibreNMS\Interfaces\Module;
use LibreNMS\OS;
use LibreNMS\RRD\RrdDefinition;
use LibreNMS\Util\Compare;
use LibreNMS\Util\Time;
use Log;
use SnmpQuery;
@ -201,7 +202,7 @@ class Core implements Module
->mibDir($value['mib_dir'] ?? $mibdir)
->get(isset($value['mib']) ? "{$value['mib']}::{$value['oid']}" : $value['oid'])
->value();
if (compare_var($get_value, $value['value'], $value['op'] ?? 'contains') == $check) {
if (Compare::values($get_value, $value['value'], $value['op'] ?? 'contains') == $check) {
return false;
}
} elseif ($key == 'snmpwalk') {
@ -210,7 +211,7 @@ class Core implements Module
->mibDir($value['mib_dir'] ?? $mibdir)
->walk(isset($value['mib']) ? "{$value['mib']}::{$value['oid']}" : $value['oid'])
->raw();
if (compare_var($walk_value, $value['value'], $value['op'] ?? 'contains') == $check) {
if (Compare::values($walk_value, $value['value'], $value['op'] ?? 'contains') == $check) {
return false;
}
}

94
LibreNMS/Util/Compare.php Normal file
View File

@ -0,0 +1,94 @@
<?php
/**
* Compare.php
*
* -Description-
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* @link https://www.librenms.org
*
* @copyright 2022 Tony Murray
* @author Tony Murray <murraytony@gmail.com>
*/
namespace LibreNMS\Util;
use Illuminate\Support\Str;
class Compare
{
/**
* Perform comparison of two items based on give comparison method
* Valid comparisons: =, !=, ==, !==, >=, <=, >, <, contains, starts, ends, regex
* contains, starts, ends: $a haystack, $b needle(s)
* regex: $a subject, $b regex
*
* @param mixed $a
* @param mixed $b
* @param string $comparison =, !=, ==, !== >=, <=, >, <, contains, starts, ends, regex
* @return bool
*/
public static function values($a, $b, $comparison = '=')
{
// handle PHP8 change to implicit casting
if (is_numeric($a) || is_numeric($b)) {
$a = cast_number($a);
$b = is_array($b) ? $b : cast_number($b);
}
switch ($comparison) {
case '=':
return $a == $b;
case '!=':
return $a != $b;
case '==':
return $a === $b;
case '!==':
return $a !== $b;
case '>=':
return $a >= $b;
case '<=':
return $a <= $b;
case '>':
return $a > $b;
case '<':
return $a < $b;
case 'contains':
return Str::contains($a, $b);
case 'not_contains':
return ! Str::contains($a, $b);
case 'starts':
return Str::startsWith($a, $b);
case 'not_starts':
return ! Str::startsWith($a, $b);
case 'ends':
return Str::endsWith($a, $b);
case 'not_ends':
return ! Str::endsWith($a, $b);
case 'regex':
return (bool) preg_match($b, $a);
case 'not_regex':
return ! ((bool) preg_match($b, $a));
case 'in_array':
return in_array($a, $b);
case 'not_in_array':
return ! in_array($a, $b);
case 'exists':
return isset($a) == $b;
default:
return false;
}
}
}

View File

@ -9,6 +9,8 @@
* @copyright (C) 2006 - 2012 Adam Armstrong
*/
use App\Actions\Device\ValidateDeviceAndCreate;
use App\Models\Device;
use LibreNMS\Config;
use LibreNMS\Enum\PortAssociationMode;
use LibreNMS\Exceptions\HostUnreachableException;
@ -18,16 +20,14 @@ require __DIR__ . '/includes/init.php';
$options = getopt('Pbg:p:f::');
$device = new Device;
if (isset($options['g']) && $options['g'] >= 0) {
$cmd = array_shift($argv);
array_shift($argv);
array_shift($argv);
array_unshift($argv, $cmd);
$poller_group = $options['g'];
} elseif (Config::get('distributed_poller') === true) {
$poller_group = Config::get('default_poller_group');
} else {
$poller_group = 0;
$device->poller_group = $options['g'];
}
if (isset($options['f']) && $options['f'] == 0) {
@ -39,12 +39,11 @@ if (isset($options['f']) && $options['f'] == 0) {
$force_add = false;
}
$port_assoc_mode = Config::get('default_port_association_mode');
$valid_assoc_modes = PortAssociationMode::getModes();
if (isset($options['p'])) {
$port_assoc_mode = $options['p'];
if (! in_array($port_assoc_mode, $valid_assoc_modes)) {
echo "Invalid port association mode '" . $port_assoc_mode . "'\n";
$device->port_association_mode = $options['p'];
if (! in_array($device->port_association_mode, $valid_assoc_modes)) {
echo "Invalid port association mode '" . $device->port_association_mode . "'\n";
echo 'Valid modes: ' . join(', ', $valid_assoc_modes) . "\n";
exit(1);
}
@ -69,138 +68,95 @@ if (isset($options['b'])) {
$transports_regex = implode('|', Config::get('snmp.transports'));
if (! empty($argv[1])) {
$host = strtolower($argv[1]);
$community = $argv[2];
$snmpver = strtolower($argv[3]);
$device->hostname = strtolower($argv[1]);
$device->snmpver = strtolower($argv[3]);
$port = 161;
$transport = 'udp';
$additional = [];
if (isset($options['b'])) {
$additional = [
'ping_fallback' => 1,
];
}
if (isset($options['P'])) {
$community = '';
$snmpver = 'v2c';
$additional = [
'snmp_disable' => 1,
'os' => $argv[2] ? $argv[2] : 'ping',
'hardware' => $argv[3] ? $argv[3] : '',
];
} elseif ($snmpver === 'v3') {
$seclevel = $community;
// These values are the same as in defaults.inc.php
$v3 = [
'authlevel' => 'noAuthNoPriv',
'authname' => 'root',
'authpass' => '',
'authalgo' => 'MD5',
'cryptopass' => '',
'cryptoalgo' => 'AES',
];
$device->snmp_disable = 1;
$device->os = $argv[2] ?: 'ping';
$device->hardware = $argv[3] ?: '';
} elseif ($device->snmpver === 'v3') {
$seclevel = $argv[2];
// v3
if ($seclevel === 'nanp' or $seclevel === 'any' or $seclevel === 'noAuthNoPriv') {
$v3['authlevel'] = 'noAuthNoPriv';
$device->authlevel = 'noAuthNoPriv';
$v3args = array_slice($argv, 4);
while ($arg = array_shift($v3args)) {
// parse all remaining args
if (is_numeric($arg)) {
$port = $arg;
$device->port = $arg;
} elseif (preg_match('/^(' . $transports_regex . ')$/', $arg)) {
$transport = $arg;
$device->transport = $arg;
} else {
// should add a sanity check of chars allowed in user
$user = $arg;
$device->authname = $arg;
}
}
if ($seclevel === 'nanp') {
$v3_config = Config::get('snmp.v3');
array_unshift($v3_config, $v3);
Config::set('snmp.v3', $v3_config);
}
} elseif ($seclevel === 'anp' or $seclevel === 'authNoPriv') {
$v3['authlevel'] = 'authNoPriv';
$device->authlevel = 'authNoPriv';
$v3args = array_slice($argv, 4);
$v3['authname'] = array_shift($v3args);
$v3['authpass'] = array_shift($v3args);
$device->authname = array_shift($v3args);
$device->authpass = array_shift($v3args);
while ($arg = array_shift($v3args)) {
// parse all remaining args
if (is_numeric($arg)) {
$port = $arg;
$device->port = $arg;
} elseif (preg_match('/^(' . $transports_regex . ')$/i', $arg)) {
$transport = $arg;
$device->transport = $arg;
} elseif (preg_match('/^(sha|md5)$/i', $arg)) {
$v3['authalgo'] = $arg;
$device->authalgo = $arg;
} else {
echo 'Invalid argument: ' . $arg . "\n";
exit(1);
}
}
$v3_config = Config::get('snmp.v3');
array_unshift($v3_config, $v3);
Config::set('snmp.v3', $v3_config);
} elseif ($seclevel === 'ap' or $seclevel === 'authPriv') {
$v3['authlevel'] = 'authPriv';
$device->authlevel = 'authPriv';
$v3args = array_slice($argv, 4);
$v3['authname'] = array_shift($v3args);
$v3['authpass'] = array_shift($v3args);
$v3['cryptopass'] = array_shift($v3args);
$device->authname = array_shift($v3args);
$device->authpass = array_shift($v3args);
$device->cryptopass = array_shift($v3args);
while ($arg = array_shift($v3args)) {
// parse all remaining args
if (is_numeric($arg)) {
$port = $arg;
$device->port = $arg;
} elseif (preg_match('/^(' . $transports_regex . ')$/i', $arg)) {
$transport = $arg;
$device->transport = $arg;
} elseif (preg_match('/^(sha|md5)$/i', $arg)) {
$v3['authalgo'] = $arg;
$device->authalgo = $arg;
} elseif (preg_match('/^(aes|des)$/i', $arg)) {
$v3['cryptoalgo'] = $arg;
$device->cryptoalgo = $arg;
} else {
echo 'Invalid argument: ' . $arg . "\n";
exit(1);
}
}//end while
$v3_config = Config::get('snmp.v3');
array_unshift($v3_config, $v3);
Config::set('snmp.v3', $v3_config);
}
} else {
// v2c or v1
$v2args = array_slice($argv, 2);
$device->community = $argv[2];
while ($arg = array_shift($v2args)) {
// parse all remaining args
if (is_numeric($arg)) {
$port = $arg;
$device->port = $arg;
} elseif (preg_match('/(' . $transports_regex . ')/i', $arg)) {
$transport = $arg;
$device->transport = $arg;
} elseif (preg_match('/^(v1|v2c)$/i', $arg)) {
$snmpver = $arg;
$device->snmpver = $arg;
}
}
if ($community) {
$comm_config = Config::get('snmp.community');
array_unshift($comm_config, $community);
Config::set('snmp.community', $comm_config);
}
}//end if
try {
$device_id = addHost($host, $snmpver, $port, $transport, $poller_group, $force_add, $port_assoc_mode, $additional);
$device = device_by_id_cache($device_id);
echo "Added device {$device['hostname']} ($device_id)\n";
$result = (new ValidateDeviceAndCreate($device, $force_add, isset($options['b'])))->execute();
echo "Added device $device->hostname ($device->device_id)\n";
exit(0);
} catch (HostUnreachableException $e) {
print_error($e->getMessage());

View File

@ -92,10 +92,12 @@ class ValidateDeviceAndCreate
$this->detectCredentials();
$this->cleanCredentials();
$this->device->sysName = SnmpQuery::device($this->device)->get('SNMPv2-MIB::sysName.0')->value();
$this->exceptIfSysNameExists();
if (! $this->device->snmp_disable) {
$this->device->sysName = SnmpQuery::device($this->device)->get('SNMPv2-MIB::sysName.0')->value();
$this->exceptIfSysNameExists();
$this->device->os = Core::detectOS($this->device);
$this->device->os = Core::detectOS($this->device);
}
}
return $this->device->save();

View File

@ -48,22 +48,22 @@ class DeviceAdd extends LnmsCommand
];
$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('v1', '1', InputOption::VALUE_NONE);
$this->addOption('v2c', '2', InputOption::VALUE_NONE);
$this->addOption('v3', '3', InputOption::VALUE_NONE);
$this->addOption('community', 'c', InputOption::VALUE_REQUIRED);
$this->addOption('transport', 't', InputOption::VALUE_REQUIRED, null, Config::get('snmp.transports.0', 'udp'));
$this->addOption('port', 'r', InputOption::VALUE_REQUIRED, null, Config::get('snmp.port', 161));
$this->addOption('transport', 't', InputOption::VALUE_REQUIRED, null, Config::get('snmp.transports.0', 'udp'));
$this->addOption('display-name', 'd', InputOption::VALUE_REQUIRED);
$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('force', 'f', InputOption::VALUE_NONE);
$this->addOption('ping-fallback', 'b', InputOption::VALUE_NONE);
$this->addOption('poller-group', 'g', InputOption::VALUE_REQUIRED, null, Config::get('default_poller_group'));
$this->addOption('port-association-mode', 'p', InputOption::VALUE_REQUIRED, null, Config::get('default_port_association_mode'));
$this->addOption('ping-only', 'P', InputOption::VALUE_NONE);
$this->addOption('os', 'o', InputOption::VALUE_REQUIRED);
$this->addOption('hardware', 'w', InputOption::VALUE_REQUIRED);
@ -127,7 +127,7 @@ class DeviceAdd extends LnmsCommand
$this->error($e->getMessage() . PHP_EOL . implode(PHP_EOL, $e->getReasons()));
$this->line(trans('commands.device:add.messages.try_force'));
return 1;
return 2;
} catch (HostExistsException $e) {
// host exists errors
$this->error($e->getMessage());
@ -136,12 +136,12 @@ class DeviceAdd extends LnmsCommand
$this->line(trans('commands.device:add.messages.try_force'));
}
return 2;
return 3;
} catch (Exception $e) {
// other errors?
$this->error(get_class($e) . ': ' . $e->getMessage());
return 3;
return 1;
}
}
}

View File

@ -23,32 +23,20 @@ Using the command line via ssh you can add a new device by changing to
the directory of your LibreNMS install and typing (be sure to put the
correct details).
Preferred method:
```bash
./lnms device:add [--v1|--v2c] [-c yourSNMPcommunity] yourhostname
./lnms device:add yourhostname [--v1|--v2c] [-c yourSNMPcommunity]
```
You can use `./lnms device:add --help` for a list of available options and defaults.
Alternative:
```bash
./addhost.php yourhostname [community] [v1|v2c] [port] [udp|udp6|tcp|tcp6]
```
As an example, if your device with the name `mydevice.example.com` is
configured to use the community `my_company` using snmp `v2c` then you
would enter:
Preferred method:
```bash
./lnms device:add --v2c -c my_company mydevice.example.com
```
Alternative:
```bash
./addhost.php mydevice.example.com my_company v2c
```
> Please note that if the community contains special characters such
> as `$` then you will need to wrap it in `'`. I.e: `'Pa$$w0rd'`.

View File

@ -11,13 +11,14 @@ Please see the following [doc](/Installation/Install-LibreNMS.md)
You have two options for adding a new device into LibreNMS.
1: Using the command line via ssh you can add a new device by changing
to the directory of your LibreNMS install and typing (be sure to
put the correct details).
to the directory of your LibreNMS install and typing:
```bash
./addhost.php [community] [v1|v2c] [port] [udp|udp6|tcp|tcp6]
lnms device:add [hostname or ip]
```
To see all options run: `lnms device:add -h`
> Please note that if the community contains special characters such
> as `$` then you will need to wrap it in `'`. I.e: `'Pa$$w0rd'`.

View File

@ -114,69 +114,6 @@ function logfile($string)
fclose($fd);
}
/**
* Perform comparison of two items based on give comparison method
* Valid comparisons: =, !=, ==, !==, >=, <=, >, <, contains, starts, ends, regex
* contains, starts, ends: $a haystack, $b needle(s)
* regex: $a subject, $b regex
*
* @param mixed $a
* @param mixed $b
* @param string $comparison =, !=, ==, !== >=, <=, >, <, contains, starts, ends, regex
* @return bool
*/
function compare_var($a, $b, $comparison = '=')
{
// handle PHP8 change to implicit casting
if (is_numeric($a) || is_numeric($b)) {
$a = cast_number($a);
$b = is_array($b) ? $b : cast_number($b);
}
switch ($comparison) {
case '=':
return $a == $b;
case '!=':
return $a != $b;
case '==':
return $a === $b;
case '!==':
return $a !== $b;
case '>=':
return $a >= $b;
case '<=':
return $a <= $b;
case '>':
return $a > $b;
case '<':
return $a < $b;
case 'contains':
return Str::contains($a, $b);
case 'not_contains':
return ! Str::contains($a, $b);
case 'starts':
return Str::startsWith($a, $b);
case 'not_starts':
return ! Str::startsWith($a, $b);
case 'ends':
return Str::endsWith($a, $b);
case 'not_ends':
return ! Str::endsWith($a, $b);
case 'regex':
return (bool) preg_match($b, $a);
case 'not_regex':
return ! ((bool) preg_match($b, $a));
case 'in_array':
return in_array($a, $b);
case 'not_in_array':
return ! in_array($a, $b);
case 'exists':
return isset($a) == $b;
default:
return false;
}
}
function percent_colour($perc)
{
$r = min(255, 5 * ($perc - 25));

View File

@ -73,7 +73,7 @@ return [
'v1' => 'Use SNMP v1',
'v2c' => 'Use SNMP v2c',
'v3' => 'Use SNMP v3',
'display-name' => 'A string to display as the name of this device, defaults to hostname. May be a simple template using replacements: {{ $hostname }}, {{ $sysName }}, {{ $sysName_fallback }}, {{ $ip }}',
'display-name' => "A string to display as the name of this device, defaults to hostname.\nMay be a simple template using replacements: {{ \$hostname }}, {{ \$sysName }}, {{ \$sysName_fallback }}, {{ \$ip }}",
'force' => 'Just add the device, do not make any safety checks',
'group' => 'Poller group (for distributed polling)',
'ping-fallback' => 'Add the device as ping only if it does not respond to SNMP',

View File

@ -128,8 +128,8 @@ def scan_host(scan_ip):
arguments = [
"/usr/bin/env",
"php",
"addhost.php",
"lnms",
"device:add",
"-g",
POLLER_GROUP,
hostname or scan_ip,
@ -261,7 +261,7 @@ Example: """
networks = []
for net in netargs if netargs else CONFIG.get("nets", []):
try:
networks.append(ip_network(u"%s" % net, True))
networks.append(ip_network("%s" % net, True))
debug("Network parsed: {}".format(net), 2)
except ValueError as e:
parser.error("Invalid network format {}".format(e))

View File

@ -164,7 +164,7 @@ class AddHostCliTest extends DBTestCase
->assertExitCode(0)
->execute();
$this->artisan('device:add', ['device spec' => 'existing'])
->assertExitCode(2)
->assertExitCode(3)
->execute();
}
}