Issues (2963)

includes/snmp.inc.php (1 issue)

1
<?php
2
/*
3
 * LibreNMS - SNMP Functions
4
 *
5
 * Original Observium code by: Adam Armstrong, Tom Laermans
6
 * Copyright (c) 2010-2012 Adam Armstrong.
7
 *
8
 * Additions for LibreNMS by Paul Gear
9
 * Copyright (c) 2014-2015 Gear Consulting Pty Ltd <http://libertysys.com.au/>
10
 *
11
 * This program is free software: you can redistribute it and/or modify it
12
 * under the terms of the GNU General Public License as published by the
13
 * Free Software Foundation, either version 3 of the License, or (at your
14
 * option) any later version.  Please see LICENSE.txt at the top level of
15
 * the source code distribution for details.
16
 */
17
18
use App\Models\Device;
19
use Illuminate\Support\Str;
20
use LibreNMS\Config;
21
use LibreNMS\Util\Debug;
22
use Symfony\Component\Process\Exception\ProcessTimedOutException;
23
24
function string_to_oid($string)
25
{
26
    $oid = strlen($string);
27
    for ($i = 0; $i != strlen($string); $i++) {
28
        $oid .= '.' . ord($string[$i]);
29
    }
30
31
    return $oid;
32
}//end string_to_oid()
33
34
function prep_snmp_setting($device, $setting)
35
{
36
    if (isset($device[$setting]) && is_numeric($device[$setting]) && $device[$setting] > 0) {
37
        return $device[$setting];
38
    }
39
40
    return Config::get("snmp.$setting");
41
}//end prep_snmp_setting()
42
43
/**
44
 * @param  array  $device
45
 * @return array will contain a list of mib dirs
46
 */
47
function get_mib_dir($device)
48
{
49
    $dirs = [];
50
51
    if (file_exists(Config::get('mib_dir') . '/' . $device['os'])) {
52
        $dirs[] = Config::get('mib_dir') . '/' . $device['os'];
53
    }
54
55
    if (isset($device['os_group'])) {
56
        if (file_exists(Config::get('mib_dir') . '/' . $device['os_group'])) {
57
            $dirs[] = Config::get('mib_dir') . '/' . $device['os_group'];
58
        }
59
60
        if ($group_mibdir = Config::get("os_groups.{$device['os_group']}.mib_dir")) {
61
            if (is_array($group_mibdir)) {
62
                foreach ($group_mibdir as $k => $dir) {
63
                    $dirs[] = Config::get('mib_dir') . '/' . $dir;
64
                }
65
            }
66
        }
67
    }
68
69
    if ($os_mibdir = Config::get("os.{$device['os']}.mib_dir")) {
70
        $dirs[] = Config::get('mib_dir') . '/' . $os_mibdir;
71
    }
72
73
    return $dirs;
74
}
75
76
/**
77
 * Generate the mib search directory argument for snmpcmd
78
 * If null return the default mib dir
79
 * If $mibdir is empty '', return an empty string
80
 *
81
 * @param  string  $mibdir  should be the name of the directory within \LibreNMS\Config::get('mib_dir')
82
 * @param  array|null  $device
83
 * @return string The option string starting with -M
84
 */
85
function mibdir($mibdir = null, $device = null)
86
{
87
    $dirs = is_array($device) ? get_mib_dir($device) : [];
88
89
    $base = Config::get('mib_dir');
90
    $dirs[] = "$base/$mibdir";
91
92
    // make sure base directory is included first
93
    array_unshift($dirs, $base);
94
95
    // remove trailing /, remove empty dirs, and remove duplicates
96
    $dirs = array_unique(array_filter(array_map(function ($dir) {
97
        return rtrim($dir, '/');
98
    }, $dirs)));
99
100
    return implode(':', $dirs);
101
}//end mibdir()
102
103
/**
104
 * Generate an snmpget command
105
 *
106
 * @param  array  $device  the we will be connecting to
107
 * @param  array|string  $oids  the oids to fetch, separated by spaces
108
 * @param  array|string  $options  extra snmp command options, usually this is output options
109
 * @param  string  $mib  an additional mib to add to this command
110
 * @param  string  $mibdir  a mib directory to search for mibs, usually prepended with +
111
 * @return array the fully assembled command, ready to run
112
 */
113
function gen_snmpget_cmd($device, $oids, $options = null, $mib = null, $mibdir = null)
114
{
115
    $snmpcmd = [Config::get('snmpget')];
116
117
    return gen_snmp_cmd($snmpcmd, $device, $oids, $options, $mib, $mibdir);
118
} // end gen_snmpget_cmd()
119
120
/**
121
 * Generate an snmpwalk command
122
 *
123
 * @param  array  $device  the we will be connecting to
124
 * @param  array|string  $oids  the oids to fetch, separated by spaces
125
 * @param  array|string  $options  extra snmp command options, usually this is output options
126
 * @param  string  $mib  an additional mib to add to this command
127
 * @param  string  $mibdir  a mib directory to search for mibs, usually prepended with +
128
 * @return array the fully assembled command, ready to run
129
 */
130
function gen_snmpwalk_cmd($device, $oids, $options = null, $mib = null, $mibdir = null, $strIndexing = null)
131
{
132
    if ($device['snmpver'] == 'v1' || (isset($device['os']) && Config::getOsSetting($device['os'], 'snmp_bulk', true) == false)) {
133
        $snmpcmd = [Config::get('snmpwalk')];
134
    } else {
135
        $snmpcmd = [Config::get('snmpbulkwalk')];
136
        $max_repeaters = get_device_max_repeaters($device);
137
        if ($max_repeaters > 0) {
138
            $snmpcmd[] = "-Cr$max_repeaters";
139
        }
140
    }
141
142
    return gen_snmp_cmd($snmpcmd, $device, $oids, $options, $mib, $mibdir, $strIndexing);
143
} //end gen_snmpwalk_cmd()
144
145
/**
146
 * Generate an snmp command
147
 *
148
 * @param  array  $cmd  the snmp command to run, like snmpget plus any additional arguments in an array
149
 * @param  array  $device  the we will be connecting to
150
 * @param  array|string  $oids  the oids to fetch, separated by spaces
151
 * @param  array|string  $options  extra snmp command options, usually this is output options
152
 * @param  string  $mib  an additional mib to add to this command
153
 * @param  string  $mibdir  a mib directory to search for mibs, usually prepended with +
154
 * @return array the fully assembled command, ready to run
155
 */
156
function gen_snmp_cmd($cmd, $device, $oids, $options = null, $mib = null, $mibdir = null, $strIndexing = null)
157
{
158
    if (! isset($device['transport'])) {
159
        $device['transport'] = 'udp';
160
    }
161
162
    $cmd = snmp_gen_auth($device, $cmd, $strIndexing);
163
    $cmd = $options ? array_merge($cmd, (array) $options) : $cmd;
164
    if ($mib) {
165
        array_push($cmd, '-m', $mib);
166
    }
167
    array_push($cmd, '-M', mibdir($mibdir, $device));
168
169
    $timeout = prep_snmp_setting($device, 'timeout');
170
    if ($timeout && $timeout !== 1) {
171
        array_push($cmd, '-t', $timeout);
172
    }
173
174
    $retries = prep_snmp_setting($device, 'retries');
175
    if ($retries && $retries !== 5) {
176
        array_push($cmd, '-r', $retries);
177
    }
178
179
    $pollertarget = \LibreNMS\Util\Rewrite::addIpv6Brackets(Device::pollerTarget($device));
180
    $cmd[] = $device['transport'] . ':' . $pollertarget . ':' . $device['port'];
181
    $cmd = array_merge($cmd, (array) $oids);
182
183
    return $cmd;
184
} // end gen_snmp_cmd()
185
186
function snmp_get_multi($device, $oids, $options = '-OQUs', $mib = null, $mibdir = null, $array = [])
187
{
188
    $time_start = microtime(true);
189
190
    if (! is_array($oids)) {
191
        $oids = explode(' ', $oids);
192
    }
193
194
    $cmd = gen_snmpget_cmd($device, $oids, $options, $mib, $mibdir);
195
    $data = trim(external_exec($cmd));
196
197
    foreach (explode("\n", $data) as $entry) {
198
        [$oid,$value] = explode('=', $entry, 2);
199
        $oid = trim($oid);
200
        $value = trim($value, "\" \n\r");
201
        [$oid, $index] = explode('.', $oid, 2);
202
203
        if (! Str::contains($value, 'at this OID')) {
204
            if (is_null($index)) {
205
                if (empty($oid)) {
206
                    continue; // no index or oid
207
                }
208
                $array[$oid] = $value;
209
            } else {
210
                $array[$index][$oid] = $value;
211
            }
212
        }
213
    }
214
215
    recordSnmpStatistic('snmpget', $time_start);
216
217
    return $array;
218
}//end snmp_get_multi()
219
220
function snmp_get_multi_oid($device, $oids, $options = '-OUQn', $mib = null, $mibdir = null)
221
{
222
    $time_start = microtime(true);
223
    $oid_limit = get_device_oid_limit($device);
224
225
    if (! is_array($oids)) {
226
        $oids = explode(' ', $oids);
227
    }
228
229
    $data = [];
230
    foreach (array_chunk($oids, $oid_limit) as $chunk) {
231
        $output = external_exec(gen_snmpget_cmd($device, $chunk, $options, $mib, $mibdir));
232
        $result = trim(str_replace('Wrong Type (should be OBJECT IDENTIFIER): ', '', $output));
233
        if ($result) {
234
            $data = array_merge($data, explode("\n", $result));
235
        }
236
    }
237
238
    $array = [];
239
    $oid = '';
240
    foreach ($data as $entry) {
241
        if (Str::contains($entry, '=')) {
242
            [$oid,$value] = explode('=', $entry, 2);
243
            $oid = trim($oid);
244
            $value = trim($value, "\\\" \n\r");
245
            if (! strstr($value, 'at this OID') && isset($oid)) {
246
                $array[$oid] = $value;
247
            }
248
        } else {
249
            if (isset($array[$oid])) {
250
                // if appending, add a line return
251
                $array[$oid] .= PHP_EOL . $entry;
252
            } else {
253
                $array[$oid] = $entry;
254
            }
255
        }
256
    }
257
258
    recordSnmpStatistic('snmpget', $time_start);
259
260
    return $array;
261
}//end snmp_get_multi_oid()
262
263
/**
264
 * Simple snmpget, returns the output of the get or false if the get failed.
265
 *
266
 * @param  array  $device
267
 * @param  array|string  $oid
268
 * @param  array|string  $options
269
 * @param  string  $mib
270
 * @param  string  $mibdir
271
 * @return bool|string
272
 */
273
function snmp_get($device, $oid, $options = null, $mib = null, $mibdir = null)
274
{
275
    $time_start = microtime(true);
276
277
    if (strstr($oid, ' ')) {
278
        throw new Exception("snmp_get called for multiple OIDs: $oid");
279
    }
280
281
    $output = external_exec(gen_snmpget_cmd($device, $oid, $options, $mib, $mibdir));
282
    $output = str_replace('Wrong Type (should be OBJECT IDENTIFIER): ', '', $output);
283
    $data = trim($output, "\\\" \n\r");
284
285
    recordSnmpStatistic('snmpget', $time_start);
286
    if (preg_match('/(No Such Instance|No Such Object|No more variables left|Authentication failure)/i', $data)) {
287
        return false;
288
    } elseif ($data || $data === '0') {
289
        return $data;
290
    } else {
291
        return false;
292
    }
293
}//end snmp_get()
294
295
/**
296
 * Calls snmpgetnext.  Getnext returns the next oid after the specified oid.
297
 * For example instead of get sysName.0, you can getnext sysName to get the .0 value.
298
 *
299
 * @param  array  $device  Target device
300
 * @param  array|string  $oid  The oid to getnext
301
 * @param  array|string  $options  Options to pass to snmpgetnext (-Oqv for example)
302
 * @param  string  $mib  The MIB to use
303
 * @param  string  $mibdir  Optional mib directory to search
304
 * @return string|false the output or false if the data could not be fetched
305
 */
306
function snmp_getnext($device, $oid, $options = null, $mib = null, $mibdir = null)
307
{
308
    $time_start = microtime(true);
309
310
    $snmpcmd = [Config::get('snmpgetnext', 'snmpgetnext')];
311
    $cmd = gen_snmp_cmd($snmpcmd, $device, $oid, $options, $mib, $mibdir);
312
    $data = trim(external_exec($cmd), "\" \n\r");
313
314
    recordSnmpStatistic('snmpgetnext', $time_start);
315
    if (preg_match('/(No Such Instance|No Such Object|No more variables left|Authentication failure)/i', $data)) {
316
        return false;
317
    } elseif ($data || $data === '0') {
318
        return $data;
319
    }
320
321
    return false;
322
}
323
324
/**
325
 * Calls snmpgetnext for multiple OIDs.  Getnext returns the next oid after the specified oid.
326
 * For example instead of get sysName.0, you can getnext sysName to get the .0 value.
327
 *
328
 * @param  array  $device  Target device
329
 * @param  array  $oids  The oids to getnext
330
 * @param  string  $options  Options to pass to snmpgetnext (-OQUs for example)
331
 * @param  string  $mib  The MIB to use
332
 * @param  string  $mibdir  Optional mib directory to search
333
 * @return array|false the output or false if the data could not be fetched
334
 */
335
function snmp_getnext_multi($device, $oids, $options = '-OQUs', $mib = null, $mibdir = null, $array = [])
336
{
337
    $time_start = microtime(true);
338
    if (! is_array($oids)) {
339
        $oids = explode(' ', $oids);
340
    }
341
    $snmpcmd = [Config::get('snmpgetnext', 'snmpgetnext')];
342
    $cmd = gen_snmp_cmd($snmpcmd, $device, $oids, $options, $mib, $mibdir);
343
    $data = trim(external_exec($cmd), "\" \n\r");
344
345
    foreach (explode("\n", $data) as $entry) {
346
        [$oid,$value] = explode('=', $entry, 2);
347
        $oid = trim($oid);
348
        $value = trim($value, "\" \n\r");
349
        [$oid, $index] = explode('.', $oid, 2);
350
        if (! Str::contains($value, 'at this OID')) {
351
            if (empty($oid)) {
352
                continue; // no index or oid
353
            } else {
354
                $array[$oid] = $value;
355
            }
356
        }
357
    }
358
    recordSnmpStatistic('snmpgetnext', $time_start);
0 ignored issues
show
It seems like $time_start can also be of type string; however, parameter $start_time of recordSnmpStatistic() does only seem to accept double, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

358
    recordSnmpStatistic('snmpgetnext', /** @scrutinizer ignore-type */ $time_start);
Loading history...
359
360
    return $array;
361
}//end snmp_getnext_multi()
362
363
/**
364
 * @param $device
365
 * @return bool
366
 */
367
function snmp_check($device)
368
{
369
    $time_start = microtime(true);
370
371
    try {
372
        $oid = '.1.3.6.1.2.1.1.2.0';
373
        $cmd = gen_snmpget_cmd($device, $oid, '-Oqvn');
374
        $proc = new \Symfony\Component\Process\Process($cmd);
375
        $proc->run();
376
        $code = $proc->getExitCode();
377
        Log::debug("SNMP Check response code: $code");
378
    } catch (ProcessTimedOutException $e) {
379
        Log::debug("Device didn't respond to snmpget before {$e->getExceededTimeout()}s timeout");
380
    }
381
382
    recordSnmpStatistic('snmpget', $time_start);
383
384
    if ($code === 0) {
385
        return true;
386
    }
387
388
    return false;
389
}//end snmp_check()
390
391
function snmp_walk($device, $oid, $options = null, $mib = null, $mibdir = null)
392
{
393
    $time_start = microtime(true);
394
395
    $cmd = gen_snmpwalk_cmd($device, $oid, $options, $mib, $mibdir);
396
    $data = trim(external_exec($cmd));
397
398
    $data = str_replace('"', '', $data);
399
    $data = str_replace('End of MIB', '', $data);
400
401
    if (is_string($data) && preg_match('/No Such (Object|Instance)/i', $data)) {
402
        d_echo('Invalid snmp_walk() data = ' . print_r($data, true));
403
        $data = false;
404
    } elseif (preg_match('/Wrong Type(.*)should be/', $data)) {
405
        $data = preg_replace('/Wrong Type \(should be .*\): /', '', $data);
406
    } else {
407
        if (Str::endsWith($data, '(It is past the end of the MIB tree)')) {
408
            $no_more_pattern = '/.*No more variables left in this MIB View \(It is past the end of the MIB tree\)[\n]?/';
409
            $data = preg_replace($no_more_pattern, '', $data);
410
        }
411
    }
412
413
    recordSnmpStatistic('snmpwalk', $time_start);
414
415
    return $data;
416
}//end snmp_walk()
417
418
function snmpwalk_cache_cip($device, $oid, $array = [], $mib = 0)
419
{
420
    $cmd = gen_snmpwalk_cmd($device, $oid, '-OsnQ', $mib);
421
    $data = trim(external_exec($cmd));
422
423
    // echo("Caching: $oid\n");
424
    foreach (explode("\n", $data) as $entry) {
425
        [$this_oid, $this_value] = preg_split('/=/', $entry);
426
        $this_oid = trim($this_oid);
427
        $this_value = trim($this_value);
428
        $this_oid = substr($this_oid, 30);
429
        [$ifIndex, $dir, $a, $b, $c, $d, $e, $f] = explode('.', $this_oid);
430
        $h_a = zeropad(dechex($a));
431
        $h_b = zeropad(dechex($b));
432
        $h_c = zeropad(dechex($c));
433
        $h_d = zeropad(dechex($d));
434
        $h_e = zeropad(dechex($e));
435
        $h_f = zeropad(dechex($f));
436
        $mac = "$h_a$h_b$h_c$h_d$h_e$h_f";
437
438
        if ($dir == '1') {
439
            $dir = 'input';
440
        } elseif ($dir == '2') {
441
            $dir = 'output';
442
        }
443
444
        if ($mac && $dir) {
445
            $array[$ifIndex][$mac][$oid][$dir] = $this_value;
446
        }
447
    }//end foreach
448
449
    return $array;
450
}//end snmpwalk_cache_cip()
451
452
function snmp_cache_ifIndex($device)
453
{
454
    // FIXME: this is not yet using our own snmp_*
455
    $cmd = gen_snmpwalk_cmd($device, 'ifIndex', '-OQs', 'IF-MIB');
456
    $data = trim(external_exec($cmd));
457
458
    $array = [];
459
    foreach (explode("\n", $data) as $entry) {
460
        [$this_oid, $this_value] = preg_split('/=/', $entry);
461
        [$this_oid, $this_index] = explode('.', $this_oid, 2);
462
        $this_index = trim($this_index);
463
        $this_oid = trim($this_oid);
464
        $this_value = trim($this_value);
465
        if (! strstr($this_value, 'at this OID') && $this_index) {
466
            $array[] = $this_value;
467
        }
468
    }
469
470
    return $array;
471
}//end snmp_cache_ifIndex()
472
473
function snmpwalk_cache_oid($device, $oid, $array, $mib = null, $mibdir = null, $snmpflags = '-OQUs')
474
{
475
    $data = snmp_walk($device, $oid, $snmpflags, $mib, $mibdir);
476
    foreach (explode("\n", $data) as $entry) {
477
        if (! Str::contains($entry, ' =') && ! empty($entry) && isset($index, $oid)) {
478
            $array[$index][$oid] .= "\n$entry";
479
            continue;
480
        }
481
482
        [$oid,$value] = explode('=', $entry, 2);
483
        $oid = trim($oid);
484
        $value = trim($value, "\" \\\n\r");
485
        [$oid, $index] = explode('.', $oid, 2);
486
        if (! strstr($value, 'at this OID') && ! empty($oid)) {
487
            $array[$index][$oid] = $value;
488
        }
489
    }
490
491
    return $array;
492
}//end snmpwalk_cache_oid()
493
494
function snmpwalk_cache_numerical_oid($device, $oid, $array, $mib = null, $mibdir = null, $snmpflags = '-OQUsn')
495
{
496
    $data = snmp_walk($device, $oid, $snmpflags, $mib, $mibdir);
497
    foreach (explode("\n", $data) as $entry) {
498
        [$oid,$value] = explode('=', $entry, 2);
499
        $oid = trim($oid);
500
        $value = trim($value);
501
        [$index,] = explode('.', strrev($oid), 2);
502
        if (! strstr($value, 'at this OID') && isset($oid) && isset($index)) {
503
            $array[$index][$oid] = $value;
504
        }
505
    }
506
507
    return $array;
508
}//end snmpwalk_cache_oid()
509
510
function snmpwalk_cache_long_oid($device, $oid, $noid, $array, $mib = null, $mibdir = null, $snmpflags = '-OQnU')
511
{
512
    $data = snmp_walk($device, $oid, $snmpflags, $mib, $mibdir);
513
514
    if (empty($data)) {
515
        return $array;
516
    }
517
518
    foreach (explode("\n", $data) as $entry) {
519
        [$tmp_oid,$value] = explode('=', $entry, 2);
520
        $tmp_oid = trim($tmp_oid);
521
        $value = trim($value);
522
        $tmp_index = str_replace($noid, '', $tmp_oid);
523
        $index = md5($tmp_index);
524
        if (! empty($index) && ! empty($oid)) {
525
            $array[$index][$oid] = $value;
526
            if (empty($array[$index]['orig'])) {
527
                $array[$index]['orig'] = $tmp_index;
528
            }
529
        }
530
    }
531
532
    return $array;
533
}//end snmpwalk_cache_oid()
534
535
/**
536
 * Just like snmpwalk_cache_oid except that it returns the numerical oid as the index
537
 * this is useful when the oid is indexed by the mac address and snmpwalk would
538
 * return periods (.) for non-printable numbers, thus making many different indexes appear
539
 * to be the same.
540
 *
541
 * @param  array  $device
542
 * @param  string  $oid
543
 * @param  array  $array  Pass an array to add the cache to, useful for multiple calls
544
 * @param  string  $mib
545
 * @param  string  $mibdir
546
 * @return bool|array
547
 */
548
function snmpwalk_cache_oid_num($device, $oid, $array, $mib = null, $mibdir = null)
549
{
550
    return snmpwalk_cache_oid($device, $oid, $array, $mib, $mibdir, $snmpflags = '-OQUn');
551
}//end snmpwalk_cache_oid_num()
552
553
function snmpwalk_cache_multi_oid($device, $oid, $array, $mib = null, $mibdir = null, $snmpflags = '-OQUs')
554
{
555
    global $cache;
556
557
    if (! (is_array($cache['snmp'][$device['device_id']]) && array_key_exists($oid, $cache['snmp'][$device['device_id']]))) {
558
        $data = snmp_walk($device, $oid, $snmpflags, $mib, $mibdir);
559
        foreach (explode("\n", $data) as $entry) {
560
            [$r_oid,$value] = explode('=', $entry, 2);
561
            $r_oid = trim($r_oid);
562
            $value = trim($value);
563
            $oid_parts = explode('.', $r_oid);
564
            $r_oid = array_shift($oid_parts);
565
            $index = array_shift($oid_parts);
566
            foreach ($oid_parts as $tmp_oid) {
567
                $index .= '.' . $tmp_oid;
568
            }
569
570
            if (! strstr($value, 'at this OID') && isset($r_oid) && isset($index)) {
571
                $array[$index][$r_oid] = $value;
572
            }
573
        }//end foreach
574
575
        $cache['snmp'][$device['device_id']][$oid] = $array;
576
    }//end if
577
578
    return $cache['snmp'][$device['device_id']][$oid];
579
}//end snmpwalk_cache_multi_oid()
580
581
function snmpwalk_cache_double_oid($device, $oid, $array, $mib = null, $mibdir = null)
582
{
583
    $data = snmp_walk($device, $oid, '-OQUs', $mib, $mibdir);
584
585
    foreach (explode("\n", $data) as $entry) {
586
        [$oid,$value] = explode('=', $entry, 2);
587
        $oid = trim($oid);
588
        $value = trim($value);
589
        [$oid, $first, $second] = explode('.', $oid);
590
        if (! strstr($value, 'at this OID') && isset($oid) && isset($first) && isset($second)) {
591
            $double = $first . '.' . $second;
592
            $array[$double][$oid] = $value;
593
        }
594
    }
595
596
    return $array;
597
}//end snmpwalk_cache_double_oid()
598
599
function snmpwalk_cache_index($device, $oid, $array, $mib = null, $mibdir = null)
600
{
601
    $data = snmp_walk($device, $oid, '-OQUs', $mib, $mibdir);
602
603
    foreach (explode("\n", $data) as $entry) {
604
        [$oid,$value] = explode('=', $entry, 2);
605
        $oid = trim($oid);
606
        $value = trim($value);
607
        [$oid, $first] = explode('.', $oid);
608
        if (! strstr($value, 'at this OID') && isset($oid) && isset($first)) {
609
            $array[$oid][$first] = $value;
610
        }
611
    }
612
613
    return $array;
614
}//end snmpwalk_cache_double_oid()
615
616
function snmpwalk_cache_triple_oid($device, $oid, $array, $mib = null, $mibdir = null)
617
{
618
    $data = snmp_walk($device, $oid, '-OQUs', $mib, $mibdir);
619
620
    foreach (explode("\n", $data) as $entry) {
621
        [$oid,$value] = explode('=', $entry, 2);
622
        $oid = trim($oid);
623
        $value = trim($value);
624
        [$oid, $first, $second, $third] = explode('.', $oid);
625
        if (! strstr($value, 'at this OID') && isset($oid) && isset($first) && isset($second)) {
626
            $index = $first . '.' . $second . '.' . $third;
627
            $array[$index][$oid] = $value;
628
        }
629
    }
630
631
    return $array;
632
}//end snmpwalk_cache_triple_oid()
633
634
/**
635
 * Walk an snmp mib oid and group items together based on the index.
636
 * This is intended to be used with a string based oid.
637
 * Any extra index data past $depth will be added after the oidName to keep grouping consistent.
638
 *
639
 * Example:
640
 * snmpwalk_group($device, 'ifTable', 'IF-MIB');
641
 * [
642
 *   1 => [ 'ifIndex' => '1', 'ifDescr' => 'lo', 'ifMtu' => '65536', ...],
643
 *   2 => [ 'ifIndex' => '2', 'ifDescr' => 'enp0s25', 'ifMtu' => '1500', ...],
644
 * ]
645
 *
646
 * @param  array  $device  Target device
647
 * @param  string  $oid  The string based oid to walk
648
 * @param  string  $mib  The MIB to use
649
 * @param  int  $depth  how many indexes to group
650
 * @param  array  $array  optionally insert the entries into an existing array (helpful for grouping multiple walks)
651
 * @param  string  $mibdir  custom mib dir to search for mib
652
 * @return array grouped array of data
653
 */
654
function snmpwalk_group($device, $oid, $mib = '', $depth = 1, $array = [], $mibdir = null, $strIndexing = null)
655
{
656
    d_echo("communityStringIndexing $strIndexing\n");
657
    $cmd = gen_snmpwalk_cmd($device, $oid, '-OQUsetX', $mib, $mibdir, $strIndexing);
658
    $data = rtrim(external_exec($cmd));
659
660
    $line = strtok($data, "\n");
661
    while ($line !== false) {
662
        if (Str::contains($line, 'at this OID') || Str::contains($line, 'this MIB View')) {
663
            $line = strtok("\n");
664
            continue;
665
        }
666
667
        [$address, $value] = explode(' =', $line, 2);
668
        preg_match_all('/([^[\]]+)/', $address, $parts);
669
        $parts = $parts[1];
670
        array_splice($parts, $depth, 0, array_shift($parts)); // move the oid name to the correct depth
671
672
        $line = strtok("\n"); // get the next line and concatenate multi-line values
673
        while ($line !== false && ! Str::contains($line, '=')) {
674
            $value .= $line . PHP_EOL;
675
            $line = strtok("\n");
676
        }
677
678
        // merge the parts into an array, creating keys if they don't exist
679
        $tmp = &$array;
680
        foreach ($parts as $part) {
681
            // we don't want to remove dots inside quotes, only outside
682
            $key = trim(trim($part, '.'), '"');
683
            $tmp = &$tmp[$key];
684
        }
685
        $tmp = trim($value, "\" \n\r"); // assign the value as the leaf
686
    }
687
688
    return $array;
689
}
690
691
function snmpwalk_cache_twopart_oid($device, $oid, $array, $mib = 0, $mibdir = null, $snmpflags = '-OQUs')
692
{
693
    $cmd = gen_snmpwalk_cmd($device, $oid, $snmpflags, $mib, $mibdir);
694
    $data = trim(external_exec($cmd));
695
696
    foreach (explode("\n", $data) as $entry) {
697
        [$oid,$value] = explode('=', $entry, 2);
698
        $oid = trim($oid);
699
        $value = trim($value);
700
        $value = str_replace('"', '', $value);
701
        [$oid, $first, $second] = explode('.', $oid);
702
        if (! strstr($value, 'at this OID') && isset($oid) && isset($first) && isset($second)) {
703
            $array[$first][$second][$oid] = $value;
704
        }
705
    }
706
707
    return $array;
708
}//end snmpwalk_cache_twopart_oid()
709
710
function snmpwalk_cache_threepart_oid($device, $oid, $array, $mib = 0)
711
{
712
    $cmd = gen_snmpwalk_cmd($device, $oid, '-OQUs', $mib);
713
    $data = trim(external_exec($cmd));
714
715
    foreach (explode("\n", $data) as $entry) {
716
        [$oid,$value] = explode('=', $entry, 2);
717
        $oid = trim($oid);
718
        $value = trim($value);
719
        $value = str_replace('"', '', $value);
720
        [$oid, $first, $second, $third] = explode('.', $oid);
721
722
        if (Debug::isEnabled()) {
723
            echo "$entry || $oid || $first || $second || $third\n";
724
        }
725
726
        if (! strstr($value, 'at this OID') && isset($oid) && isset($first) && isset($second) && isset($third)) {
727
            $array[$first][$second][$third][$oid] = $value;
728
        }
729
    }
730
731
    return $array;
732
}//end snmpwalk_cache_threepart_oid()
733
734
/**
735
 * generate snmp auth arguments
736
 *
737
 * @param  array  $device
738
 * @param  array  $cmd
739
 * @return array
740
 */
741
function snmp_gen_auth(&$device, $cmd = [], $strIndexing = null)
742
{
743
    if ($device['snmpver'] === 'v3') {
744
        array_push($cmd, '-v3', '-l', $device['authlevel']);
745
        array_push($cmd, '-n', isset($device['context_name']) ? $device['context_name'] : '');
746
747
        $authlevel = strtolower($device['authlevel']);
748
        if ($authlevel === 'noauthnopriv') {
749
            // We have to provide a username anyway (see Net-SNMP doc)
750
            array_push($cmd, '-u', ! empty($device['authname']) ? $device['authname'] : 'root');
751
        } elseif ($authlevel === 'authnopriv') {
752
            array_push($cmd, '-a', $device['authalgo']);
753
            array_push($cmd, '-A', $device['authpass']);
754
            array_push($cmd, '-u', $device['authname']);
755
        } elseif ($authlevel === 'authpriv') {
756
            array_push($cmd, '-a', $device['authalgo']);
757
            array_push($cmd, '-A', $device['authpass']);
758
            array_push($cmd, '-u', $device['authname']);
759
            array_push($cmd, '-x', $device['cryptoalgo']);
760
            array_push($cmd, '-X', $device['cryptopass']);
761
        } else {
762
            d_echo('DEBUG: ' . $device['snmpver'] . " : Unsupported SNMPv3 AuthLevel (wtf have you done ?)\n");
763
        }
764
    } elseif ($device['snmpver'] === 'v2c' || $device['snmpver'] === 'v1') {
765
        array_push($cmd, '-' . $device['snmpver'], '-c', $device['community'] . ($strIndexing != null ? '@' . $strIndexing : null));
766
    } else {
767
        d_echo('DEBUG: ' . $device['snmpver'] . " : Unsupported SNMP Version (shouldn't be possible to get here)\n");
768
    }
769
770
    return $cmd;
771
}//end snmp_gen_auth()
772
773
/**
774
 * SNMP translate between numeric and textual oids
775
 *
776
 * Default options for a numeric oid is -Os
777
 * Default options for a textual oid is -On
778
 * You may override these by setting $options (an empty string for no options)
779
 *
780
 * @param  string  $oid
781
 * @param  string  $mib
782
 * @param  string  $mibdir  the mib directory (relative to the LibreNMS mibs directory)
783
 * @param  array|string  $options  Options to pass to snmptranslate
784
 * @param  array|null  $device
785
 * @return string
786
 */
787
function snmp_translate($oid, $mib = 'ALL', $mibdir = null, $options = null, $device = null)
788
{
789
    $cmd = [Config::get('snmptranslate', 'snmptranslate'), '-M', mibdir($mibdir, $device), '-m', $mib];
790
791
    if (oid_is_numeric($oid)) {
792
        $default_options = ['-Os', '-Pu'];
793
    } else {
794
        if ($mib != 'ALL' && ! Str::contains($oid, '::')) {
795
            $oid = "$mib::$oid";
796
        }
797
        $default_options = ['-On', '-Pu'];
798
    }
799
    $options = is_null($options) ? $default_options : $options;
800
    $cmd = array_merge($cmd, (array) $options);
801
    $cmd[] = $oid;
802
803
    return trim(external_exec($cmd));
804
}
805
806
/**
807
 * SNMPWalk_array_num - performs a numeric SNMPWalk and returns an array containing $count indexes
808
 * One Index:
809
 *  From: 1.3.6.1.4.1.9.9.166.1.15.1.1.27.18.655360 = 0
810
 *  To: $array['1.3.6.1.4.1.9.9.166.1.15.1.1.27.18']['655360'] = 0
811
 * Two Indexes:
812
 *  From: 1.3.6.1.4.1.9.9.166.1.15.1.1.27.18.655360 = 0
813
 *  To: $array['1.3.6.1.4.1.9.9.166.1.15.1.1.27']['18']['655360'] = 0
814
 * And so on...
815
 * Think snmpwalk_cache_*_oid but for numeric data.
816
 *
817
 * Why is this useful?
818
 * Some SNMP data contains a single index (eg. ifIndex in IF-MIB) and some is dual indexed
819
 * (eg. PolicyIndex/ObjectsIndex in CISCO-CLASS-BASED-QOS-MIB).
820
 * The resulting array allows us to easily access the top level index we want and iterate over the data from there.
821
 *
822
 * @param $device
823
 * @param $OID
824
 * @param  int  $indexes
825
 *
826
 * @internal param $string
827
 *
828
 * @return bool|array
829
 */
830
function snmpwalk_array_num($device, $oid, $indexes = 1)
831
{
832
    $array = [];
833
    $string = snmp_walk($device, $oid, '-Osqn');
834
835
    if ($string === false) {
836
        // False means: No Such Object.
837
        return false;
838
    }
839
    if ($string == '') {
840
        // Empty means SNMP timeout or some such.
841
        return null;
842
    }
843
844
    // Let's turn the string into something we can work with.
845
    foreach (explode("\n", $string) as $line) {
846
        if ($line[0] == '.') {
847
            // strip the leading . if it exists.
848
            $line = substr($line, 1);
849
        }
850
        [$key, $value] = explode(' ', $line, 2);
851
        $prop_id = explode('.', $key);
852
        $value = trim($value);
853
854
        // if we have requested more levels that exist, set to the max.
855
        if ($indexes > count($prop_id)) {
856
            $indexes = count($prop_id) - 1;
857
        }
858
859
        for ($i = 0; $i < $indexes; $i++) {
860
            // Pop the index off.
861
            $index = array_pop($prop_id);
862
            $value = [$index => $value];
863
        }
864
865
        // Rebuild our key
866
        $key = implode('.', $prop_id);
867
868
        // Add the entry to the master array
869
        $array = array_replace_recursive($array, [$key => $value]);
870
    }
871
872
    return $array;
873
}
874
875
/**
876
 * @param $device
877
 * @return bool
878
 */
879
function get_device_max_repeaters($device)
880
{
881
    return $device['attribs']['snmp_max_repeaters'] ??
882
        Config::getOsSetting($device['os'], 'snmp.max_repeaters', Config::get('snmp.max_repeaters', false));
883
}
884
885
/**
886
 * Check if a given oid is numeric.
887
 *
888
 * @param  string  $oid
889
 * @return bool
890
 */
891
function oid_is_numeric($oid)
892
{
893
    return \LibreNMS\Device\YamlDiscovery::oidIsNumeric($oid);
894
}
895