1<?php
2
3/**
4 * Observium
5 *
6 *   This file is part of Observium.
7 *
8 * @package    observium
9 * @subpackage snmp
10 * @author     Adam Armstrong <adama@observium.org>
11 * @copyright  (C) 2006-2013 Adam Armstrong, (C) 2013-2019 Observium Limited
12 *
13 */
14
15## If anybody has again the idea to implement the PHP internal library calls,
16## be aware that it was tried and banned by lead dev Adam
17##
18## TRUE STORY. THAT SHIT IS WHACK. -- adama.
19
20//CLEANME:
21// snmpwalk_cache_oid_num()   - (deprecated) not used anymore
22// snmpget_entity_oids()      - (deprecated) not used anymore
23// snmp_cache_slotport_oid()  - (deprecated) not used anymore
24// snmp_walk_parser()         - (deprecated) used in poller module netscaler-vsvr, need rewrite
25// snmp_walk_parser2()        - (deprecated) not used anymore
26// parse_oid()                - (deprecated) not used anymore, use snmp_parse_line()
27// parse_oid2()               - (deprecated) used in poller/discovery module mac-accounting, need rewrite
28
29// `egrep -r 'snmpwalk_cache_oid *\( *\$' .       | grep -v snmp.inc.php | wc -l` => 519
30// `egrep -r 'snmpwalk_cache_multi_oid *\( *\$' . | grep -v snmp.inc.php | wc -l` => 387
31// snmpwalk_cache_multi_oid() - (duplicate) call to snmpwalk_cache_oid()
32
33/**
34 * MIB dirs generate functions
35 */
36
37/**
38 * Generates a list of mibdirs in the correct format for net-snmp
39 *
40 * @return string
41 * @global array  $config
42 * @param  array $mibs     An array of MIB dirs or a string containing a single MIB dir
43 */
44function mib_dirs($mibs = array())
45{
46  global $config;
47
48  $dirs = array($config['mib_dir'].'/rfc', $config['mib_dir'].'/net-snmp');
49
50  if (!is_array($mibs)) { $mibs = array($mibs); }
51
52  foreach ($mibs as $mib)
53  {
54    if (ctype_alnum(str_replace(array('-', '_'), '', $mib)))
55    {
56      // If mib name equals 'mibs' just add root mib_dir to list
57      $dirs[] = ($mib == 'mibs' ? $config['mib_dir'] : $config['mib_dir'].'/'.$mib);
58    }
59  }
60
61  return implode(':', array_unique($dirs));
62}
63
64
65/**
66 * Finds directories for requested MIBs as defined by the MIB definitions.
67 *
68 * @param string $mib One or more MIBs (separated by ':') to return the MIB dir for
69 *
70 * @return string Directories for requested MIBs, separated by ':' (for net-snmp)
71 *
72 */
73function snmp_mib2mibdirs($mib)
74{
75  global $config;
76
77  $def_mibdirs = array();
78
79  // As we accept multiple MIBs separated by :, process them all for definition entries
80  foreach (explode(':', $mib) as $xmib)
81  {
82    if (!empty($config['mibs'][$xmib]['mib_dir'])) // Array or non-empty string
83    {
84      // Add definition based MIB dir. Don't worry about deduplication, mib_dirs() sorts that out for us
85      $def_mibdirs = array_merge($def_mibdirs, (array)$config['mibs'][$xmib]['mib_dir']);
86    }
87  }
88
89/*
90  if (count($def_mibdirs))
91  {
92    // Use MIB dirs found via foreach above
93    return mib_dirs($def_mibdirs);
94  } else {
95    // No specific MIB dirs found, set default Observium MIB dir
96    return $config['mib_dir'];
97  }
98*/
99  return mib_dirs($def_mibdirs); // Always return set of mib dirs (prevent Cannot find module (LM-SENSORS-MIB): At line 1 in (none))
100}
101
102/**
103 * Expand ENTITY mib by vendor type MIB
104 *
105 * @param array $device Device array
106 * @param string $mib List of MIBs, separated by ':'
107 * @return string New list of MIBs expanded by additional MIBs, separated by ':'
108 */
109function snmp_mib_entity_vendortype($device, $mib)
110{
111  global $config;
112
113  $mibs = explode(':', $mib);
114
115  if (!in_array('ENTITY-MIB', $mibs))
116  {
117    // No entity mib in list, return original
118    return $mib;
119  }
120  else if (isset($config['os'][$device['os']]['vendortype_mib']))
121  {
122    $mibs[] = $config['os'][$device['os']]['vendortype_mib'];
123  }
124  else if (isset($config['os_group']['default']['vendortype_mib']))
125  {
126    $mibs[] = $config['os_group']['default']['vendortype_mib'];
127  }
128
129  // Reimplode mibs list
130  return implode(':', array_unique($mibs));
131}
132
133/**
134 * Convert/parse/validate oids & values
135 */
136
137/**
138 * De-wrap 32bit counters
139 * Crappy function to get workaround 32bit counter wrapping in HOST-RESOURCES-MIB
140 * See: http://blog.logicmonitor.com/2011/06/11/linux-monitoring-net-snmp-and-terabyte-file-systems/
141 *
142 * @param integer $value
143 * @return integer
144 */
145function snmp_dewrap32bit($value)
146{
147  if (is_numeric($value) && $value < 0)
148  {
149    return ($value + 4294967296);
150  } else {
151    return $value;
152  }
153}
154
155/**
156 * Combine High and Low sizes into full 64bit size
157 * Used in UCD-SNMP-MIB and NIMBLE-MIB
158 * Note, this function required 64bit system!
159 *
160 * @param integer $high High bits value
161 * @param integer $low  Low bits value
162 * @return integer Result sum 64bit
163 */
164function snmp_size64_high_low($high, $low)
165{
166  return $high * 4294967296 + $low;
167  //return $high << 32 + $low;
168}
169
170/**
171 * Clean returned numeric data from snmp output
172 * Supports only non-scientific numbers
173 * Examples: "  20,4" -> 20.4
174 *
175 * @param string $value
176 * @return mixed $numeric
177 */
178function snmp_fix_numeric($value)
179{
180  if (is_numeric($value)) { return $value + 0; } // If already numeric just return value
181  else if (!is_string($value)) { return $value; } // Non string values just return as is
182
183  $value = trim($value, " \t\n\r\0\x0B\"");
184  // Possible more derp case:
185  // CPU Temperature-Ctlr B: 58 C 136.40F
186  foreach (explode(': ', $value) as $numeric)
187  {
188    list($numeric) = explode(' ', $numeric);
189    $numeric = preg_replace('/[^0-9a-z\-,\.]/i', '', $numeric);
190    // Some retarded devices report data with spaces and commas: STRING: "  20,4"
191    $numeric = str_replace(',', '.', $numeric);
192    if (is_numeric($numeric))
193    {
194      // If cleaned data is numeric return number
195      return $numeric + 0;
196    }
197    else if (preg_match('/^(\d+(?:\.\d+)?)[a-z]+$/i', $numeric, $matches))
198    {
199      // Number with unit, ie "8232W"
200      return $matches[1] + 0;
201    }
202  }
203
204  // Else return original value
205  return $value;
206}
207
208/**
209 * Fixed ascii coded chars in snmp string as correct UTF-8 chars.
210 * Convert all Mac/Windows newline chars (\r\n, \r) to Unix char (\n)
211 *
212 * NOTE, currently support only one-byte unicode
213 *
214 * Examples: "This is a &#269;&#x5d0; test&#39; &#250;" -> "This is a čא test' ú"
215 *           "P<FA>lt stj<F3>rnst<F6><F0>"              -> "Púlt stjórnstöð"
216 *
217 * @param string $string
218 * @return string $string
219 */
220function snmp_fix_string($string)
221{
222  if (!preg_match('/^[[:print:]\p{L}]*$/mu', $string))
223  {
224    // find unprintable and all unicode chars, because old pcre library not always detect orb
225    $debug_msg = '>>> Founded unprintable chars in string:' . PHP_EOL . $string;
226    $string = preg_replace_callback('/[^[:print:]\x00-\x1F\x80-\x9F]/m', 'convert_ord_char', $string);
227    print_debug($debug_msg . PHP_EOL . '>>> Converted to:' . PHP_EOL . $string . PHP_EOL);
228  }
229
230  // Convert all Mac/Windows newline chars (\r\n, \r) to Unix char (\n)
231  $string = nl2nl($string);
232
233  return $string;
234}
235
236/**
237 * Convert an SNMP hex string to regular string
238 *
239 * @param string $string HEX string
240 * @param string $eol Symbol used as EOL (hex 00), default is \n, but last EOL removed
241 * @return string
242 */
243function snmp_hexstring($string, $eol = "\n")
244{
245  if (isHexString($string))
246  {
247    $ascii = hex2str($string, $eol);
248    // clear last EOL CHAR
249    return rtrim($ascii, $eol);
250  } else {
251    return $string;
252  }
253}
254
255/**
256 * Clean SNMP value, ie: trim quotes, spaces, remove "wrong type", fix incorrect UTF8 strings, etc
257 * @param	string	$value	Value
258 * @param	integer	$flags	OBS_SNMP_* flags
259 * @return	string			Cleaned value
260 */
261function snmp_value_clean($value, $flags = OBS_SNMP_ALL)
262{
263  // For null just return NULL
264  if (NULL === $value)
265  {
266    return $value;
267  }
268
269  // Clean quotes and trim
270  $value = trim_quotes($value, $flags);
271
272  // Remove Wrong Type string
273  if (strpos($value, 'Wrong Type') === 0)
274  {
275    $value = preg_replace('/Wrong Type .*?: (.*)/s', '\1', $value);
276  }
277
278  // Fix incorrect UTF8 strings
279  if (is_flag_set(OBS_DECODE_UTF8, $flags))
280  {
281    $value = snmp_fix_string($value);
282  }
283
284  /* Need use case
285  // Convert incorrect HEX strings back to string
286  if (!is_flag_set(OBS_SNMP_HEX, $flags))
287  {
288    $value = snmp_hexstring($value);
289  }
290  */
291
292  return $value;
293}
294
295/**
296 * Convert an SNMP index string (with len!) to regular string
297 * Opposite function for snmp_string_to_oid()
298 * Example:
299 *  9.79.98.115.101.114.118.105.117.109 -> Observium
300 *
301 * @param string $index
302 * @return string
303 */
304function snmp_oid_to_string($index)
305{
306  $index = (string)$index;
307  if ($index === '0') { return ''; } // This is just empty string!
308
309  if (preg_match('/^\.?(\d+(?:\.\d+)+)$/', $index, $matches))
310  {
311    $str_parts = explode('.', $matches[1]);
312    $str_len   = array_shift($str_parts);
313    if ($str_len != count($str_parts))
314    {
315      // break, incorrect index string (str len not match)
316      return $index;
317    }
318    $string = '';
319    foreach ($str_parts as $char)
320    {
321      if ($char > 255)
322      {
323        // break, incorrect index string
324        return $index;
325      }
326      $string .= zeropad(dechex($char));
327    }
328    return hex2str($string);
329  }
330
331  return $index;
332}
333
334/**
335 * Convert ASCII string to an SNMP index (with len!)
336 * Opposite function for snmp_oid_to_string()
337 * Example:
338 *  Observium -> 9.79.98.115.101.114.118.105.117.109
339 *
340 * @param string $string
341 * @return string
342 */
343function snmp_string_to_oid($string)
344{
345  // uses the first octet of index as length
346  $index = strlen((string)$string);
347  if ($index === 0)
348  {
349    // Empty string
350    return (string)$index;
351  }
352
353  // converts the index as string to decimal ascii codes
354  foreach (str_split($string) as $char)
355  {
356    $index .= '.' . ord($char);
357  }
358
359  return $index;
360}
361
362/**
363 * Check if returned snmp value is valid
364 *
365 * @param string $value
366 * @return bool
367 */
368function is_valid_snmp_value($value)
369{
370  $valid = strpos($value, 'at this OID') === FALSE &&
371           strpos($value, 'No more variables left') === FALSE &&
372           $value != 'NULL' && $value != 'null' && $value !== NULL;
373
374  return $valid;
375}
376
377/**
378 * Parse each line in output from snmpwalk into:
379 *   oid (raw), oid_name, index, index_parts, index_count, value
380 *
381 * This parser always used snmpwalk with base options: -OQUs
382 * and allowed to use additional options: bnexX
383 *
384 * Value always cleaned from unnecessary data by snmp_value_clean()
385 *
386 * @param string $line   snmpwalk output line
387 * @param integer $flags Common snmp flags
388 * @return array Array with parsed values
389 */
390function snmp_parse_line($line, $flags = OBS_SNMP_ALL)
391{
392  /**
393   * Note, this is parse line only for -OQUs (and additionally: bnexX)
394   *  Q - Removes the type information when displaying varbind values: SNMPv2-MIB::sysUpTime.0 = 1:15:09:27.63
395   *  U - Do not print the UNITS suffix at the end of the value
396   *  s - Do not display the name of the MIB
397   *  b - Display table indexes numerically: SNMP-VIEW-BASED-ACM-MIB::vacmSecurityModel.0.3.119.101.115 = xxx
398   *  n - Displays the OID numerically: .1.3.6.1.2.1.1.3.0 = Timeticks: (14096763) 1 day, 15:09:27.63
399   *  e - Removes the symbolic labels from enumeration values: forwarding(1) -> 1
400   *  x - Force display string values as Hex strings
401   *  X - Display table indexes in a more "program like" output: IPv6-MIB::ipv6RouteIfIndex[3ffe:100:ff00:0:0:0:0:0][64][1] = 2
402   */
403
404  list($r_oid, $value) = explode(' =', $line, 2);
405  $r_oid = trim($r_oid);
406  $value = snmp_value_clean($value, $flags);
407
408  $array = array('oid'   => $r_oid,
409                 'value' => $value);
410
411  if (is_flag_set(OBS_SNMP_NUMERIC, $flags))
412  {
413    // For numeric, just return raw oid and value
414    // Example: .1.3.6.1.2.1.1.3.0 = 15:09:27.63
415    if (isset($r_oid[0]))
416    {
417      // I think not possible, but I leave this here, just in case --mike
418      //if ($r_oid[0] !== '.')
419      //{
420      //  $array['index'] = '.' . $array['index'];
421      //}
422      $array['index_count'] = 1;
423    } else {
424      $array['index_count'] = 0;
425    }
426    $array['index']       = $r_oid;
427    return $array;
428  }
429
430  if ($is_table = is_flag_set(OBS_SNMP_TABLE, $flags))
431  {
432    // For table use another parse rules
433    // Example: ipv6RouteIfIndex[3ffe:100:ff00:0:0:0:0:0][64][1]
434    // also mixed index, ie wcAccessPointMac[6:20:c:c8:39:b].96
435    //                      wcAccessPointMac[6:20:c:c8:39:b]."sdf sdkfps"
436    //if (preg_match('/^(\S+?)((?:\[.+?\])+)((?:\.\d+)+)?/', $r_oid, $matches))
437    if (preg_match('/^(\S+?)((?:\[.+?\])+)((?:\.(?:\d+|"[^"]*"))+)?/', $r_oid, $matches))
438    {
439      $oid_parts = explode('][', trim($matches[2], '[]')); // square brackets part
440      array_unshift($oid_parts, $matches[1]);              // Oid name
441      if (isset($matches[3]))                              // Numeric part (if exist)
442      {
443        foreach (explode('.', ltrim($matches[3], '.')) as $oid_part)
444        {
445          $oid_parts[] = trim($oid_part, '"');
446        }
447      }
448    } else {
449      // Incorrect?
450      $oid_parts = array();
451    }
452    //foreach (explode('[', $r_oid) as $oid_part)
453    //{
454    //  $oid_parts[] = rtrim($oid_part, ']');
455    //}
456  }
457  else if (strpos($r_oid, '"') !== FALSE)
458  {
459    // Example: jnxVpnPwLocalSiteId.l2Circuit."ge-0/1/1.0".621
460    //$oid_part = stripslashes($r_oid);
461    $oid_part = $r_oid;
462    $oid_parts = array();
463    do
464    {
465      if (preg_match('/^"([^"]*)"(?:\.(.+))?/', $oid_part, $matches))
466      {
467        // Part with stripes
468        $oid_parts[] = $matches[1];
469        $oid_part    = $matches[2]; // Next part
470      } else {
471        $matches = explode('.', $oid_part, 2);
472        $oid_parts[] = $matches[0];
473        $oid_part    = $matches[1]; // Next part
474      }
475      // print_vars($matches);
476    } while (strlen($oid_part) > 0);
477    // print_vars($oid_parts);
478  } else {
479    // Simple, not always correct
480    // Example: vacmSecurityModel.0.3.119.101.115
481    $oid_parts = explode('.', $r_oid);
482  }
483  $array['oid_name']    = array_shift($oid_parts);
484  //$array['oid_name']    = end(explode('::', $array['oid_name'], 2)); // We use -Os
485  $array['index_parts'] = $oid_parts;
486  $array['index_count'] = count($oid_parts);
487  $array['index']       = implode('.', $oid_parts);
488  //var_dump($array);
489  return $array;
490}
491
492// Translate OID string to numeric:
493//'BGP4-V2-MIB-JUNIPER::jnxBgpM2PeerRemoteAs' -> '.1.3.6.1.4.1.2636.5.1.1.2.1.1.1.13'
494// or numeric OID to string:
495// '.1.3.6.1.4.1.9.1.685' -> 'ciscoAIRAP1240'
496// DOCME needs phpdoc block
497// TESTME needs unit testing
498function snmp_translate($oid, $mib = NULL, $mibdir = NULL)
499{
500  // $rewrite_oids set in rewrites.inc.php
501  global $config;
502
503  if (is_numeric(str_replace('.', '', $oid)))
504  {
505    $options = '-Os';
506  }
507  else if ($mib)
508  {
509    if (isset($config['mibs'][$mib]['translate'][$oid]))
510    {
511      print_debug("SNMP TRANSLATE (REWRITE): '$mib::$oid' -> '".$config['mibs'][$mib]['translate'][$oid]."'");
512      return $config['mibs'][$mib]['translate'][$oid];
513    }
514
515    $oid = $mib . '::' . $oid;
516  }
517
518  $cmd  = $config['snmptranslate'];
519  if (isset($options)) { $cmd .= ' ' . $options; } else { $cmd .= ' -On'; }
520
521  // Hardcode ignoring underscore parsing errors because net-snmp is dumb as a bag of rocks
522  // -Pu    Toggles whether to allow the underline character in MIB object names and other symbols.
523  //        Strictly speaking, this is not valid SMI syntax, but some vendor MIB files define such names.
524  $cmd .= ' -Pu';
525
526  if ($mib) { $cmd .= ' -m ' . $mib; }
527
528  // Set correct MIB directories based on passed dirs and OS definition
529  // If $mibdir variable is passed to the function, we use it directly
530  if ($mibdir)
531  {
532    $cmd .= " -M $mibdir";
533  } else {
534    $cmd .= ' -M ' . snmp_mib2mibdirs($mib);
535  }
536
537  $cmd .= ' \'' . $oid . '\'';
538  if (!OBS_DEBUG) { $cmd .= ' 2>/dev/null'; }
539
540  $data = trim(external_exec($cmd));
541
542  $GLOBALS['snmp_stats']['snmptranslate']['count']++;
543  $GLOBALS['snmp_stats']['snmptranslate']['time'] += $GLOBALS['exec_status']['runtime'];
544
545
546  if ($data && !strstr($data, 'Unknown'))
547  {
548    print_debug("SNMP TRANSLATE (CMD): '$oid' -> '".$data."'");
549    return $data;
550  } else {
551    return '';
552  }
553}
554
555
556/**
557 * Common SNMP functions for generate cmd and log errors
558 */
559
560/**
561 * Build a commandline string for net-snmp commands.
562 *
563 * @param  string $command
564 * @param  array  $device
565 * @param  string $oids
566 * @param  string $options
567 * @param  string $mib
568 * @param  string $mibdir Optional, correct path should be set in the MIB definition
569 * @param  integer $flags
570 * @global array  config
571 * @global array  debug
572 * @return string
573 */
574// TESTME needs unit testing
575function snmp_command($command, $device, $oids, $options, $mib = NULL, &$mibdir = NULL, $flags = OBS_SNMP_ALL)
576{
577  global $config, $cache;
578
579  get_model_array($device); // Pre-cache model options (if required)
580
581  $nobulk = $device['snmp_version'] == 'v1' ||                                            // v1 not support snmp bulk
582            (isset($device['snmp_nobulk']) && $device['snmp_nobulk']) ||                  // device specific option
583            (isset($cache['devices']['model'][$device['device_id']]['snmp']['nobulk']) &&
584                   $cache['devices']['model'][$device['device_id']]['snmp']['nobulk']) || // device model specific definition
585            (isset($config['os'][$device['os']]['snmp']['nobulk']) &&
586                   $config['os'][$device['os']]['snmp']['nobulk']);                       // os specific definition
587
588  // Get the full command path from the config. Choice between bulkwalk and walk. Add max-reps if needed.
589  switch($command)
590  {
591    case 'snmpwalk':
592      if ($nobulk)
593      {
594        $cmd = $config['snmpwalk'];
595      } else {
596        $cmd = $config['snmpbulkwalk'];
597        if (is_numeric($device['snmp_maxrep']))
598        {
599          // Device specific
600          $cmd .= ' -Cr'.escapeshellarg($device['snmp_maxrep']);
601        }
602        elseif ($config['snmp']['max-rep'] && isset($cache['devices']['model'][$device['device_id']]['snmp']['max-rep']))
603        {
604          // Device model specific
605          if (is_numeric($cache['devices']['model'][$device['device_id']]['snmp']['max-rep']))
606          {
607            // Model specific can be FALSE
608            $cmd .= ' -Cr'.escapeshellarg($cache['devices']['model'][$device['device_id']]['snmp']['max-rep']);
609          }
610        }
611        elseif ($config['snmp']['max-rep'] && is_numeric($config['os'][$device['os']]['snmp']['max-rep']))
612        {
613          // OS specific
614          $cmd .= ' -Cr'.escapeshellarg($config['os'][$device['os']]['snmp']['max-rep']);
615        }
616      }
617      // do not check returned OIDs are increasing
618      if (isset($cache['devices']['model'][$device['device_id']]['snmp']['noincrease']))
619      {
620        // Device model specific, can be FALSE
621        if ($cache['devices']['model'][$device['device_id']]['snmp']['noincrease'])
622        {
623          $cmd .= ' -Cc';
624        }
625      }
626      elseif (isset($config['os'][$device['os']]['snmp']['noincrease']) && $config['os'][$device['os']]['snmp']['noincrease'])
627      {
628        // OS specific
629        $cmd .= ' -Cc';
630      }
631      break;
632    case 'snmpget':
633    case 'snmpgetnext':
634      $cmd = $config[$command];
635      break;
636    case 'snmpbulkget':
637      // NOTE. Currently not used by us
638      if ($nobulk)
639      {
640        $cmd = $config['snmpget'];
641      } else {
642        $cmd = $config['snmpbulkget'];
643        // NOTE, for snmpbulkget max-repetitions work different than for snmpbulkwalk,
644        // it's returned exactly number (max as possible) _next_ Oid entries.
645        // Default in net-snmp is 10, this can cause troubles if passed oids more than 10
646        if (is_numeric($device['snmp_maxrep']))
647        {
648          // Device specific
649          $cmd .= ' -Cr'.escapeshellarg($device['snmp_maxrep']);
650        }
651        elseif ($config['snmp']['max-rep'] && isset($cache['devices']['model'][$device['device_id']]['snmp']['max-rep']))
652        {
653          // Device model specific
654          if (is_numeric($cache['devices']['model'][$device['device_id']]['snmp']['max-rep']))
655          {
656            // Model specific can be FALSE
657            $cmd .= ' -Cr'.escapeshellarg($cache['devices']['model'][$device['device_id']]['snmp']['max-rep']);
658          }
659        }
660        elseif ($config['snmp']['max-rep'] && is_numeric($config['os'][$device['os']]['snmp']['max-rep']))
661        {
662          // OS specific
663          $cmd .= ' -Cr'.escapeshellarg($config['os'][$device['os']]['snmp']['max-rep']);
664        }
665      }
666      break;
667    default:
668      print_error("Unknown command $command passed to snmp_command(). THIS SHOULD NOT HAPPEN. PLEASE REPORT TO DEVELOPERS.");
669      return FALSE;
670  }
671
672  // Set timeout values if set in the database, otherwise set to configured defaults
673  if (is_numeric($device['snmp_timeout']) && $device['snmp_timeout'] > 0)
674  {
675    $snmp_timeout = $device['snmp_timeout'];
676  } else if (isset($config['snmp']['timeout'])) {
677    $snmp_timeout = $config['snmp']['timeout'];
678  }
679
680  if (isset($snmp_timeout)) { $cmd .= ' -t ' . escapeshellarg($snmp_timeout); }
681
682  // Set retries if set in the database, otherwise set to configured defaults
683  if (is_numeric($device['snmp_retries']) && $device['snmp_retries'] >= 0)
684  {
685    $snmp_retries = $device['snmp_retries'];
686  }
687  else if (isset($config['snmp']['retries']))
688  {
689    $snmp_retries = $config['snmp']['retries'];
690  }
691  if (isset($snmp_retries)) { $cmd .= ' -r ' . escapeshellarg($snmp_retries); }
692
693  // If no specific transport is set for the device, default to UDP.
694  if (empty($device['snmp_transport'])) { $device['snmp_transport'] = 'udp'; }
695
696  // If no specific port is set for the device, default to 161.
697  if (!$device['snmp_port']) { $device['snmp_port'] = 161; }
698
699  // Add the SNMP authentication settings for the device
700  $cmd .= snmp_gen_auth($device);
701
702  // Hardcode ignoring underscore parsing errors because net-snmp is dumb as a bag of rocks
703  // -Pu    Toggles whether to allow the underline character in MIB object names and other symbols.
704  //        Strictly speaking, this is not valid SMI syntax, but some vendor MIB files define such names.
705  // -Pd    Disables the loading of MIB object DESCRIPTIONs when parsing MIB files.
706  //        This reduces the amount of memory used by the running application.
707  $cmd .= ' -Pud';
708
709  // Disables the use of DISPLAY-HINT information when assigning values.
710  if (is_flag_set(OBS_SNMP_HEX | OBS_SNMP_DISPLAY_HINT, $flags)) { $cmd .= ' -Ih'; }
711
712  if ($options) { $cmd .= ' ' . $options; }
713  if ($mib) { $cmd .= ' -m ' . $mib; }
714
715  // Set correct MIB directories based on passed dirs and OS definition
716  // If $mibdir variable is passed, we use it directly
717  if (empty($mibdir))
718  {
719    // Change to correct mibdir, required for store in snmp_errors
720    $mibdir = snmp_mib2mibdirs($mib);
721  }
722  $cmd .= " -M $mibdir";
723
724  // Add the device URI to the string
725  $cmd .= ' ' . escapeshellarg($device['snmp_transport']).':'.escapeshellarg($device['hostname']).':'.escapeshellarg($device['snmp_port']);
726
727  // Add the OID(s) to the string
728  $oids = trim($oids);
729  if ($oids === '')
730  {
731    print_error("Empty oids passed to snmp_command(). THIS SHOULD NOT HAPPEN. PLEASE REPORT TO DEVELOPERS.");
732    $GLOBALS['snmp_command'] = $cmd;
733    return FALSE;
734  } else {
735    $cmd .= ' ' . addslashes($oids); // Quote slashes for string indexes
736    $GLOBALS['snmp_command'] = $cmd;
737  }
738
739  // If we're not debugging, direct errors to /dev/null.
740  if (!OBS_DEBUG) { $cmd .= ' 2>/dev/null'; }
741
742  return $cmd;
743}
744
745/**
746 * Build authentication for net-snmp commands using device array
747 *
748 * @return string
749 * @param array $device
750 */
751// TESTME needs unit testing
752function snmp_gen_auth(&$device)
753{
754  $cmd = '';
755
756  switch ($device['snmp_version'])
757  {
758    case 'v3':
759      $cmd = ' -v3 -l ' . escapeshellarg($device['snmp_authlevel']);
760      /* NOTE.
761       * For proper work of 'vlan-' context on cisco, it is necessary to add 'match prefix' in snmp-server config --mike
762       * example: snmp-server group MONITOR v3 auth match prefix access SNMP-MONITOR
763       */
764      $cmd .= ' -n ' . escapeshellarg($device['snmp_context']); // Some devices, like HP, always require option '-n'
765
766      switch ($device['snmp_authlevel'])
767      {
768        case 'authPriv':
769          $cmd .= ' -x ' . escapeshellarg($device['snmp_cryptoalgo']);
770          $cmd .= ' -X ' . escapeshellarg($device['snmp_cryptopass']);
771          // no break here
772        case 'authNoPriv':
773          $cmd .= ' -a ' . escapeshellarg($device['snmp_authalgo']);
774          $cmd .= ' -A ' . escapeshellarg($device['snmp_authpass']);
775          $cmd .= ' -u ' . escapeshellarg($device['snmp_authname']);
776          break;
777        case 'noAuthNoPriv':
778          // We have to provide a username anyway (see Net-SNMP doc)
779          $cmd .= ' -u observium';
780          break;
781        default:
782          print_error('ERROR: Unsupported SNMPv3 snmp_authlevel (' . $device['snmp_authlevel'] . ')');
783      }
784      break;
785
786    case 'v2c':
787    case 'v1':
788      $cmd  = ' -' . $device['snmp_version'];
789
790      if (isset($device['snmp_context']) && strlen($device['snmp_context']))
791      {
792        // Commonly used by Vlan based Cisco community
793        $cmd .= ' -c ' . escapeshellarg($device['snmp_community'] . '@' . $device['snmp_context']);
794      } else {
795        $cmd .= ' -c ' . escapeshellarg($device['snmp_community']);
796      }
797
798      break;
799    default:
800      print_error('ERROR: ' . $device['snmp_version'] . ' : Unsupported SNMP Version.');
801  }
802
803  if (OBS_DEBUG === 1 && !$GLOBALS['config']['snmp']['hide_auth'])
804  {
805    $debug_auth = "DEBUG: SNMP Auth options = $cmd";
806    print_debug($debug_auth);
807  }
808
809  return $cmd;
810}
811
812/**
813 * Generate common snmp output options (-O)
814 *
815 * @param string  $command SNMP command
816 * @param integer $flags SNMP flags
817 * @return string Options string -Oxxx
818 */
819function snmp_gen_options($command = 'snmpwalk', $flags = 0)
820{
821
822  // Basic options,
823  // NOTE: 's' has no effect with 'v',
824  //       'Q' better than 'q' (no more Wrong Type):
825  switch($command)
826  {
827    case 'snmpwalk':
828      // walk require output with Oid.index = value
829      $output = 'QUs';
830      break;
831    case 'snmpget':
832      // get need only varbind values (without Oid)
833      $output = 'QUv';
834      break;
835  }
836
837  if (is_flag_set(OBS_SNMP_NUMERIC_INDEX, $flags)) { $output .= 'b'; }
838  if (is_flag_set(OBS_SNMP_NUMERIC,       $flags)) { $output .= 'n'; }
839  if (is_flag_set(OBS_SNMP_ENUM,          $flags)) { $output .= 'e'; }
840  if (is_flag_set(OBS_SNMP_HEX,           $flags)) { $output .= 'x'; }
841  if (is_flag_set(OBS_SNMP_ASCII,         $flags)) { $output .= 'a'; }
842  if (is_flag_set(OBS_SNMP_TABLE,         $flags)) { $output .= 'X'; }
843  if (is_flag_set(OBS_SNMP_TIMETICKS,     $flags)) { $output .= 't'; } // Display TimeTicks values as raw numbers: SNMPv2-MIB::sysUpTime.0 = 14096763
844
845  return "-O$output";
846}
847
848/**
849 * Detect SNMP errors and log it in DB.
850 * Error logged in poller modules only, all other just return error code
851 *
852 * @param string  $command  Used snmp command (ie: snmpget, snmpwalk)
853 * @param array   $device   Device array (device_id not allowed)
854 * @param string  $oid      SNMP oid string
855 * @param string  $options  SNMP options
856 * @param string  $mib      SNMP MIBs list
857 * @param string  $mibdir   SNMP MIB dirs list
858 * @return int              Numeric error code. Full list error codes see in definitions: $config['snmp']['errorcodes']
859 */
860function snmp_log_errors($command, $device, $oid, $options, $mib, $mibdir)
861{
862  $error_timestamp = time(); // current timestamp
863  $error_codes = $GLOBALS['config']['snmp']['errorcodes'];
864  $error_code = 0; // By default - OK
865
866  if ($GLOBALS['snmp_status'] === FALSE)
867  {
868    $error_code = 999; // Default Unknown error
869    if (is_array($oid))
870    {
871      $oid = implode(' ', $oid);
872    }
873    if ($mib == 'SNMPv2-MIB')
874    {
875      // Pre-check for net-snmp errors
876      if ($GLOBALS['exec_status']['exitcode'] === 1)
877      {
878        if      (strpos($GLOBALS['exec_status']['stderr'], '.index are too large') !== FALSE)
879        {
880          $error_code = 997;
881        }
882        elseif (preg_match('/(?:Cannot find module|Unknown Object Identifier)/', $GLOBALS['exec_status']['stderr']))
883        {
884          $error_code = 996;
885        }
886      }
887      if ($error_code === 999)
888      {
889        if ($oid == 'sysObjectID.0 sysUpTime.0')
890        {
891          // this is isSNMPable test, ignore
892          $error_code = 900;
893        }
894        elseif (isset($GLOBALS['config']['os'][$device['os']]['snmpable']) &&
895                 in_array($oid, $GLOBALS['config']['os'][$device['os']]['snmpable']))
896        {
897          $error_code = 900; // This is also isSNMPable, ignore
898        }
899      }
900    }
901
902    if ($error_code === 999 &&
903        strlen(trim($GLOBALS['exec_status']['stdout'], " \t\n\r\0\x0B\"")) === 0) // Empty or ""
904    {
905      $error_code = 1;  // Empty output non critical
906      if ($GLOBALS['exec_status']['exitcode'] === 1 || $GLOBALS['exec_status']['exitcode'] === -1)
907      {
908        $error_code = 1002;
909        if      (strpos($GLOBALS['exec_status']['stderr'], '.index are too large') !== FALSE)      { $error_code = 997; }
910        elseif (str_contains($GLOBALS['exec_status']['stderr'], array('Cannot find module', 'Unknown Object Identifier'))) { $error_code = 996; }
911        elseif (strpos($GLOBALS['exec_status']['stderr'], 'Empty command passed') !== FALSE)      { $error_code = 995; }
912        elseif (str_contains($GLOBALS['exec_status']['stderr'], array('Unknown user name', 'Error: passphrase', 'Unsupported security level')))
913        {
914           // SNMP v3 auth error
915          $error_code = 991;
916        }
917      }
918      elseif ($GLOBALS['exec_status']['exitcode'] === 2)
919      {
920        // Reason: (noSuchName) There is no such variable name in this MIB.
921        // This is incorrect snmp version used for MIB/oid (mostly snmp v1)
922        $error_code = 1000;
923      }
924    }
925    elseif ($error_code === 999)
926    {
927      if ($GLOBALS['exec_status']['exitcode'] === 2 && strpos($GLOBALS['exec_status']['stderr'], 'Response message would have been too large') !== FALSE)
928      {
929        // "Reason: (tooBig) Response message would have been too large."
930        // Too big max-rep definition used,
931        // this is not exactly device or net-snmp error, just need to set less max-rep in os definition
932        $error_code = 4;
933      }
934      // Non empty output, some errors can ignored
935      elseif (preg_match('/(?:No Such Instance|No Such Object|There is no such variable|No more variables left|Wrong Type)/i', $GLOBALS['exec_status']['stdout']) ||
936          $GLOBALS['exec_status']['stdout'] == 'NULL')
937      {
938        $error_code = 1000;
939      }
940      elseif (stripos($GLOBALS['exec_status']['stdout'], 'Authentication failure') !== FALSE)
941      {
942        $error_code = 991;
943      }
944      elseif ($GLOBALS['exec_status']['exitcode'] === 2 || stripos($GLOBALS['exec_status']['stderr'], 'Timeout') !== FALSE)
945      {
946        // non critical
947        $error_code = 2;
948      }
949      elseif ($GLOBALS['exec_status']['exitcode'] === 1)
950      {
951        // Calculate current snmp timeout
952        if (is_numeric($device['snmp_timeout']) && $device['snmp_timeout'] > 0)
953        {
954          $snmp_timeout = $device['snmp_timeout'];
955        }
956        elseif (isset($GLOBALS['config']['snmp']['timeout']))
957        {
958          $snmp_timeout = $GLOBALS['config']['snmp']['timeout'];
959        } else {
960          $snmp_timeout = 1;
961        }
962        if (is_numeric($device['snmp_retries']) && $device['snmp_retries'] >= 0)
963        {
964          $snmp_retries = $device['snmp_retries'];
965        }
966        elseif (isset($GLOBALS['config']['snmp']['retries']))
967        {
968          $snmp_retries = $GLOBALS['config']['snmp']['retries'];
969        } else {
970          $snmp_retries = 5;
971        }
972        $runtime_timeout = $snmp_timeout * (1 + $snmp_retries);
973
974        //$error_code = 2; // All other is incomplete request or timeout?
975        if (strpos($GLOBALS['exec_status']['stderr'], '.index are too large') !== FALSE) { $error_code = 997; }
976        elseif (preg_match('/(?:Cannot find module|Unknown Object Identifier)/', $GLOBALS['exec_status']['stderr'])) { $error_code = 996; }
977        elseif (preg_match('/ NULL\Z/', $GLOBALS['exec_status']['stdout']))             { $error_code = 1000; } // NULL as value at end of walk output
978        elseif ($GLOBALS['exec_status']['runtime'] >= $runtime_timeout)                 { $error_code = 3; }
979      }
980    }
981
982    // Count errors stats
983    $GLOBALS['snmp_stats']['errors'][$command]['count']++;
984    $GLOBALS['snmp_stats']['errors'][$command]['time'] += $GLOBALS['exec_status']['runtime'];
985
986    $msg = 'device: ' . $device['device_id'] . ', cmd: ' . $command . ', options: ' . $options;
987    $msg .= ', mib: \'' . $mib . '\', oid: \'' . $oid . '\'';
988    $msg .= ', cmd exitcode: ' . $GLOBALS['exec_status']['exitcode'] . ',' . PHP_EOL;
989    $msg .= '             snmp error code: #' . $error_code . ', reason: \'' . $error_codes[$error_code]['reason'] . '\', runtime: ' . $GLOBALS['exec_status']['runtime'];
990
991    if (OBS_DEBUG > 0)
992    {
993      if (OBS_DEBUG > 1)
994      {
995        // Show full error
996        print_debug('SNMP ERROR - '. $msg);
997      }
998      elseif ($error_code != 0 && $error_code != 900)
999      {
1000        // Show only common error info
1001        print_message('SNMP ERROR[%r#' . $error_code . ' - ' . $error_codes[$error_code]['reason'] . '%n]', 'color');
1002      }
1003    }
1004
1005    // Log error into DB, but only in poller modules, all other just return error code
1006    if (isset($GLOBALS['argv'][0]) && in_array(basename($GLOBALS['argv'][0]), array('poller.php')))
1007    {
1008      if ($error_code > 999 || $error_code < 900)
1009      {
1010        // Count critical errors into DB (only for poller)
1011        $sql  = 'SELECT * FROM `snmp_errors` ';
1012        // Note, snmp_options not in unique db index
1013        //$sql .= 'WHERE `device_id` = ? AND `error_code` = ? AND `snmp_cmd` = ? AND `snmp_options` = ? AND `mib` = ? AND `oid` = ?;';
1014        //$error_db = dbFetchRow($sql, array($device['device_id'], $error_code, $command, $options, $mib, $oid));
1015        $sql .= 'WHERE `device_id` = ? AND `error_code` = ? AND `snmp_cmd` = ? AND `mib` = ? AND `oid` = ?;';
1016        $error_db = dbFetchRow($sql, array($device['device_id'], $error_code, $command, $mib, $oid));
1017        if (isset($error_db['error_id']))
1018        {
1019          $error_db['error_count']++;
1020
1021          // DEBUG, error rate, if error rate >= 0.95, than error appears in each poll run
1022          //$poll_count = round(($error_timestamp - $error_db['added']) / $poll_period) + 1;
1023          //$error_db['error_rate'] = $error_db['error_count'] / $poll_count;
1024          //$msg .= ', rate: ' . $error_db['error_rate'] . ' err/poll';
1025          //logfile('snmp.log', $msg);
1026
1027          // Update count
1028          $update_array = array('error_count' => $error_db['error_count'],
1029                                'updated'     => $error_timestamp);
1030          if ($error_db['mib_dir']      != $mibdir)  { $update_array['mib_dir']      = $mibdir; }
1031          if ($error_db['snmp_options'] != $options) { $update_array['snmp_options'] = $options; }
1032          dbUpdate($update_array, 'snmp_errors', '`error_id` = ?', array($error_db['error_id']));
1033        } else {
1034          dbInsert(array('device_id'          => $device['device_id'],
1035                         'error_count'        => 1,
1036                         'error_code'         => $error_code,
1037                         'error_reason'       => $error_codes[$error_code]['reason'],
1038                         'snmp_cmd_exitcode'  => $GLOBALS['exec_status']['exitcode'],
1039                         'snmp_cmd'           => $command,
1040                         'snmp_options'       => $options,
1041                         'mib'                => $mib,
1042                         'mib_dir'            => $mibdir,
1043                         'oid'                => $oid,
1044                         'added'              => $error_timestamp,
1045                         'updated'            => $error_timestamp), 'snmp_errors');
1046        }
1047      } else {
1048        // DEBUG
1049        //logfile('snmp.log', $msg);
1050      }
1051    }
1052  }
1053
1054  $GLOBALS['snmp_error_code'] = $error_code; // Set global variable $snmp_error_code
1055
1056  return $error_code;
1057}
1058
1059/**
1060 * Return SNMP status for last snmp get/walk function
1061 *
1062 * @return boolean SNMP status
1063 */
1064function snmp_status()
1065{
1066  return $GLOBALS['snmp_status'];
1067}
1068
1069/**
1070 * Return SNMP error code for last snmp get/walk function
1071 *
1072 * @return integer SNMP error code
1073 */
1074function snmp_error_code()
1075{
1076  return $GLOBALS['snmp_error_code'];
1077}
1078
1079/**
1080 * Common SNMP get/walk functions
1081 */
1082
1083/**
1084 * Uses snmpget to fetch a single OID and returns a string.
1085 *
1086 * @param  array  $device
1087 * @param  string $oid
1088 * @param  string $options
1089 * @param  string $mib
1090 * @param  string $mibdir Optional, correct path should be set in the MIB definition
1091 * @param  integer $flags
1092 * @return string
1093 */
1094function snmp_get($device, $oid, $options = NULL, $mib = NULL, $mibdir = NULL, $flags = OBS_QUOTES_TRIM)
1095{
1096
1097  if (strpos($oid, ' '))
1098  {
1099    print_debug("WARNING: snmp_get called for multiple OIDs: $oid");
1100  }
1101  else if (empty($mib) && str_contains($oid, '::'))
1102  {
1103    // Split Oid names passed as full (ie SNMPv2-MIB::sysUpTime.0) into MIB name (SNMPv2-MIB) and Oid (sysUpTime.0)
1104    list($mib, $oid) = explode('::', $oid);
1105  }
1106
1107  $cmd = snmp_command('snmpget', $device, $oid, $options, $mib, $mibdir, $flags);
1108
1109  $data = external_exec($cmd);
1110
1111  $data = snmp_value_clean($data, $flags);
1112  $GLOBALS['snmp_status'] = ($GLOBALS['exec_status']['exitcode'] === 0 ? TRUE : FALSE);
1113
1114  $GLOBALS['snmp_stats']['snmpget']['count']++;
1115  $GLOBALS['snmp_stats']['snmpget']['time'] += $GLOBALS['exec_status']['runtime'];
1116
1117  if (isset($data[0])) // same as strlen($data) > 0
1118  {
1119    if (preg_match('/(?:No Such Instance|No Such Object|There is no such variable|No more variables left|Authentication failure)/i', $data) ||
1120        $data == 'NULL')
1121    {
1122      $data = '';
1123      $GLOBALS['snmp_status'] = FALSE;
1124    }
1125  } else {
1126    $GLOBALS['snmp_status'] = FALSE;
1127  }
1128  if (OBS_DEBUG)
1129  {
1130    print_message('SNMP STATUS['.($GLOBALS['snmp_status'] ? '%gTRUE': '%rFALSE').'%n]', 'color');
1131  }
1132  snmp_log_errors('snmpget', $device, $oid, $options, $mib, $mibdir);
1133
1134  return $data;
1135}
1136
1137// DOCME needs phpdoc block
1138// TESTME needs unit testing
1139// FIXME, why strip quotes is default? this removes all quotes also in index
1140function snmp_walk($device, $oid, $options = NULL, $mib = NULL, $mibdir = NULL, $flags = OBS_QUOTES_STRIP)
1141{
1142
1143  if (empty($mib) && str_contains($oid, '::'))
1144  {
1145    // Split Oid names passed as full (ie SNMPv2-MIB::sysUpTime) into MIB name (SNMPv2-MIB) and Oid (sysUpTime)
1146    list($mib, $oid) = explode('::', $oid);
1147  }
1148
1149  $cmd = snmp_command('snmpwalk', $device, $oid, $options, $mib, $mibdir, $flags);
1150
1151  $data = trim(external_exec($cmd));
1152
1153  $GLOBALS['snmp_status'] = ($GLOBALS['exec_status']['exitcode'] === 0 ? TRUE : FALSE);
1154
1155  if (is_string($data) && (preg_match("/No Such (Object|Instance)/i", $data)))
1156  {
1157    $data = '';
1158    $GLOBALS['snmp_status'] = FALSE;
1159  } else {
1160    if (preg_match('/No more variables left in this MIB View \(It is past the end of the MIB tree\)$/', $data)
1161     || preg_match('/End of MIB$/', $data))
1162    {
1163      # Bit ugly :-(
1164      $d_ex = explode("\n",$data);
1165      $d_ex_count = count($d_ex);
1166      if ($d_ex_count > 1)
1167      {
1168        // Remove last line
1169        unset($d_ex[$d_ex_count-1]);
1170        $data = implode("\n",$d_ex);
1171      } else {
1172        $data = '';
1173        $GLOBALS['snmp_status'] = FALSE;
1174      }
1175    }
1176
1177    // Concatenate multiline values if not set option -Oq
1178    if (is_flag_set(OBS_SNMP_CONCAT, $flags) && $data && strpos($options, 'q') === FALSE)
1179    {
1180      $old_data = $data;
1181      $data = array();
1182      foreach (explode("\n", $old_data) as $line)
1183      {
1184        $line = trim($line, " \r");
1185        if (strpos($line, ' =') !== FALSE)
1186        {
1187          $data[] = $line;
1188        } else {
1189          $key = count($data) - 1;                      // get previous entry key
1190          list(, $end) = explode(' =', $data[$key], 2);
1191          if ($line !== '' && $end !== '')              // add space if previous value not empty
1192          {
1193            $data[$key] .= ' ';
1194            //var_dump($line);
1195          }
1196          //$data[count($data)-1] .= '\n' . $line; // here NOT newline char, but two chars!
1197          $data[$key] .= $line;
1198        }
1199      }
1200      unset($old_data);
1201      $data = implode("\n", $data);
1202    }
1203  }
1204  $GLOBALS['snmp_stats']['snmpwalk']['count']++;
1205  $GLOBALS['snmp_stats']['snmpwalk']['time'] += $GLOBALS['exec_status']['runtime'];
1206
1207  if (OBS_DEBUG)
1208  {
1209    print_message('SNMP STATUS['.($GLOBALS['snmp_status'] ? '%gTRUE': '%rFALSE').'%n]', 'color');
1210  }
1211  snmp_log_errors('snmpwalk', $device, $oid, $options, $mib, $mibdir);
1212
1213  return $data;
1214}
1215
1216// Cache snmpEngineID
1217// DOCME needs phpdoc block
1218// TESTME needs unit testing
1219function snmp_cache_snmpEngineID($device)
1220{
1221  global $cache;
1222
1223  if ($device['snmp_version'] == 'v1') { return FALSE; } // snmpEngineID allowed only in v2c/v3
1224
1225  if (!isset($cache['snmp'][$device['device_id']]['snmpEngineID']))
1226  {
1227    //$snmpEngineID = snmp_get($device, 'snmpEngineID.0', '-OQv', 'SNMP-FRAMEWORK-MIB');
1228    $snmpEngineID = snmp_get_oid($device, 'snmpEngineID.0', 'SNMP-FRAMEWORK-MIB');
1229    $snmpEngineID = str_replace(array(' ', '"', "'", "\n", "\r"), '', $snmpEngineID);
1230
1231    if (isset($device['device_id']) && $device['device_id'] > 0)
1232    {
1233      $cache['snmp'][$device['device_id']]['snmpEngineID'] = $snmpEngineID;
1234    } else {
1235      // Correctly use this function, when device_id not set
1236      return $snmpEngineID;
1237    }
1238  }
1239
1240  return $cache['snmp'][$device['device_id']]['snmpEngineID'];
1241}
1242
1243// Cache sysObjectID
1244// DOCME needs phpdoc block
1245// TESTME needs unit testing
1246function snmp_cache_sysObjectID($device)
1247{
1248  global $cache;
1249
1250  if (!isset($cache['snmp'][$device['device_id']]['sysObjectID']))
1251  {
1252    //$sysObjectID  = snmp_get($device, 'sysObjectID.0', '-Ovqn', 'SNMPv2-MIB');
1253    $sysObjectID  = snmp_get_oid($device, 'sysObjectID.0', 'SNMPv2-MIB', NULL, OBS_SNMP_ALL_NUMERIC);
1254    if (strpos($sysObjectID, 'Wrong Type') !== FALSE)
1255    {
1256      // Wrong Type (should be OBJECT IDENTIFIER): "1.3.6.1.4.1.25651.1.2"
1257      list(, $sysObjectID) = explode(':', $sysObjectID);
1258      $sysObjectID = '.'.trim($sysObjectID, ' ."');
1259    }
1260
1261    if (isset($device['device_id']) && $device['device_id'] > 0)
1262    {
1263      $cache['snmp'][$device['device_id']]['sysObjectID'] = $sysObjectID;
1264    } else {
1265      // Correctly use this function, when device_id not set
1266      return $sysObjectID;
1267    }
1268  }
1269
1270  return $cache['snmp'][$device['device_id']]['sysObjectID'];
1271}
1272
1273// Return just an array of values without oids.
1274// DOCME needs phpdoc block
1275// TESTME needs unit testing
1276function snmpwalk_values($device, $oid, $array, $mib = NULL, $mibdir = NULL)
1277{
1278  $options = snmp_gen_options('snmpwalk'); // -OQUs
1279  $data = snmp_walk($device, $oid, $options, $mib, $mibdir);
1280  foreach (explode("\n", $data) as $line)
1281  {
1282    $entry = snmp_parse_line($line);
1283
1284    if (isset($entry['oid_name'][0]) && $entry['index_count'] > 0 && is_valid_snmp_value($entry['value']))
1285    {
1286      $array[] = $entry['value'];
1287    }
1288  }
1289
1290  return $array;
1291}
1292
1293// Return an array of values with numeric oids as keys
1294// DOCME needs phpdoc block
1295// TESTME needs unit testing
1296function snmpwalk_numericoids($device, $oid, $array, $mib = NULL, $mibdir = NULL)
1297{
1298  return snmpwalk_cache_oid($device, $oid, $array, $mib, $mibdir, OBS_SNMP_ALL_NUMERIC);
1299}
1300
1301/**
1302 * Uses snmpget to fetch single OID and return string value.
1303 * Differences from snmp_get:
1304 *  - not required raw $options, default is -OQv
1305 *
1306 * snmp_get() in-code (r8636) options:
1307 *    -Oqv    : 252
1308 *    -OQv    : 149
1309 *    -OQUsv  : 37
1310 *    -OUnqv  : 21
1311 *    -Onqv   : 19
1312 *    -Oqsv   : 17
1313 *    -OQUnv  : 14
1314 *    -OUqv   : 7
1315 *    -Onqsv  : 3
1316 *    -OQUs   : 2
1317 *    -OUqsv  : 1
1318 *    -OQnv   : 1
1319 *    -OQUnsv : 1
1320 *
1321 * snmp_get() cleaned options:
1322 *   'U', 's' has no effect with 'v', 'Q' better than 'q' (no more Wrong Type):
1323 *    -OQv    : 463
1324 *    -OQnv   : 59
1325 *    -OQUs   : 2
1326 *
1327 *    snmp_get() each option:
1328 *    v       : 522
1329 *    q       : 320
1330 *    Q       : 204
1331 *    U       : 83
1332 *    s       : 61
1333 *    n       : 59
1334 *
1335 * @param  array  $device
1336 * @param  string $oid
1337 * @param  string $mib
1338 * @param  string $mibdir Optional, correct path should be set in the MIB definition
1339 * @param  integer  $flags
1340 * @return string
1341 */
1342function snmp_get_oid($device, $oid, $mib = NULL, $mibdir = NULL, $flags = OBS_QUOTES_TRIM)
1343{
1344
1345  $options = snmp_gen_options('snmpget', $flags);
1346
1347  return snmp_get($device, $oid, $options, $mib, $mibdir, $flags);
1348}
1349
1350/**
1351 * Uses snmpgetnext to fetch single OID and return string value.
1352 *
1353 * @param  array  $device
1354 * @param  string $oid
1355 * @param  string $mib
1356 * @param  string $mibdir Optional, correct path should be set in the MIB definition
1357 * @param  integer  $flags
1358 * @return string
1359 */
1360function snmp_getnext_oid($device, $oid, $mib = NULL, $mibdir = NULL, $flags = OBS_QUOTES_TRIM)
1361{
1362
1363  $options = snmp_gen_options('snmpget', $flags);
1364
1365  if (strpos($oid, ' '))
1366  {
1367    print_debug("ERROR: snmp_getnext called for multiple OIDs: $oid");
1368    return NULL;
1369  }
1370  else if (empty($mib) && str_contains($oid, '::'))
1371  {
1372    // Split Oid names passed as full (ie SNMPv2-MIB::sysUpTime.0) into MIB name (SNMPv2-MIB) and Oid (sysUpTime.0)
1373    list($mib, $oid) = explode('::', $oid);
1374  }
1375
1376  $cmd = snmp_command('snmpgetnext', $device, $oid, $options, $mib, $mibdir, $flags);
1377
1378  $data = external_exec($cmd);
1379
1380  $data = snmp_value_clean($data, $flags);
1381  $GLOBALS['snmp_status'] = ($GLOBALS['exec_status']['exitcode'] === 0 ? TRUE : FALSE);
1382
1383  // For counts use just snmpget
1384  $GLOBALS['snmp_stats']['snmpget']['count']++;
1385  $GLOBALS['snmp_stats']['snmpget']['time'] += $GLOBALS['exec_status']['runtime'];
1386
1387  if (isset($data[0])) // same as strlen($data) > 0
1388  {
1389    if (preg_match('/(?:No Such Instance|No Such Object|There is no such variable|No more variables left|Authentication failure)/i', $data) ||
1390      $data == 'NULL')
1391    {
1392      $data = '';
1393      $GLOBALS['snmp_status'] = FALSE;
1394    }
1395  } else {
1396    $GLOBALS['snmp_status'] = FALSE;
1397  }
1398  if (OBS_DEBUG)
1399  {
1400    print_message('SNMP STATUS['.($GLOBALS['snmp_status'] ? '%gTRUE': '%rFALSE').'%n]', 'color');
1401  }
1402  snmp_log_errors('snmpgetnext', $device, $oid, $options, $mib, $mibdir);
1403
1404  return $data;
1405}
1406
1407/**
1408 * Uses snmpget to fetch multiple OIDs and returns a parsed array.
1409 * Differences from snmp_get_multi:
1410 *  - return same array as in snmpwalk_cache_oid()
1411 *  - array merges with passed array as in snmpwalk_cache_oid()
1412 *
1413 * @param  array  $device
1414 * @param  array  $oids
1415 * @param  array $array
1416 * @param  string $mib
1417 * @param  string $mibdir Optional, correct path should be set in the MIB definition
1418 * @param  integer $flags
1419 * @return array
1420 */
1421// TESTME needs unit testing
1422function snmp_get_multi_oid($device, $oids, $array = array(), $mib = NULL, $mibdir = NULL, $flags = OBS_QUOTES_TRIM)
1423{
1424  global $config, $cache;
1425
1426  $numeric_oids = is_flag_set(OBS_SNMP_NUMERIC, $flags); // Numeric oids, do not parse oid part
1427  $options = snmp_gen_options('snmpwalk', $flags); // yes, walk 'QUs'
1428
1429  // Oids passed as string and contain multiple Oids?
1430  $oids_multiple = is_string($oids) && strpos($oids, ' ') !== FALSE;
1431
1432  // Detect if snmp max-get param defined for os/model
1433  get_model_array($device); // Pre-cache model options (if required)
1434
1435  // Split Oids list by $max_get count
1436  if (isset($cache['devices']['model'][$device['device_id']]['snmp']['max-get']) &&
1437            $cache['devices']['model'][$device['device_id']]['snmp']['max-get'] >= 1)
1438  {
1439    // Device model specific
1440    $max_get = intval($cache['devices']['model'][$device['device_id']]['snmp']['max-get']);
1441
1442    // Convert Oids passed as string to array, for chunk it by defined max-get
1443    if ($oids_multiple)
1444    {
1445      $oids = preg_split('/\s+/', $oids);
1446    }
1447  }
1448  else if (isset($config['os'][$device['os']]['snmp']['max-get']) &&
1449                 $config['os'][$device['os']]['snmp']['max-get'] >= 1)
1450  {
1451    // OS specific
1452    $max_get = intval($config['os'][$device['os']]['snmp']['max-get']);
1453
1454    // Convert Oids passed as string to array, for chunk it by defined max-get
1455    if ($oids_multiple)
1456    {
1457      $oids = preg_split('/\s+/', $oids);
1458    }
1459  } else {
1460    // Default
1461    $max_get = $config['os_group']['default']['snmp']['max-get'];
1462    //$max_get = 16;
1463
1464    // NOTE. By default, do not convert Oids passed as string to array!
1465    // See notes below
1466  }
1467
1468  if (is_array($oids))
1469  {
1470
1471    if (OBS_DEBUG && count($oids) > $max_get)
1472    {
1473      print_warning("Passed to snmp_get_multi_oid() Oids count (".count($oids).") more than max-get ($max_get). Command snmpget splitted to multiple chunks.");
1474    }
1475
1476    $data = '';
1477    $oid_chunks = array_chunk($oids, $max_get);
1478    $GLOBALS['snmp_status'] = FALSE;
1479    foreach ($oid_chunks as $oid_chunk)
1480    {
1481      $oid_text  = implode($oid_chunk, ' ');
1482      $cmd       = snmp_command('snmpget', $device, $oid_text, $options, $mib, $mibdir, $flags);
1483      $this_data = trim(external_exec($cmd));
1484
1485      $GLOBALS['snmp_status'] = ($GLOBALS['exec_status']['exitcode'] === 0 ? TRUE : $GLOBALS['snmp_status']);
1486      snmp_log_errors('snmpget', $device, $oid_text, $options, $mib, $mibdir);
1487      $data .= $this_data."\n";
1488
1489      $GLOBALS['snmp_stats']['snmpget']['count']++;
1490      $GLOBALS['snmp_stats']['snmpget']['time'] += $GLOBALS['exec_status']['runtime'];
1491    }
1492  } else {
1493    // if Oids passed as string, do not split it by chunks,
1494    // ie ports use more than 16 Oids in list, split decrease total polling time
1495    //$oids = explode(' ', trim($oids)); // Convert to array
1496
1497    $cmd  = snmp_command('snmpget', $device, $oids, $options, $mib, $mibdir, $flags);
1498    $data = trim(external_exec($cmd));
1499
1500    $GLOBALS['snmp_status'] = ($GLOBALS['exec_status']['exitcode'] === 0 ? TRUE : FALSE);
1501    snmp_log_errors('snmpget', $device, $oids, $options, $mib, $mibdir);
1502    $GLOBALS['snmp_stats']['snmpget']['count']++;
1503    $GLOBALS['snmp_stats']['snmpget']['time'] += $GLOBALS['exec_status']['runtime'];
1504  }
1505
1506  foreach (explode("\n", $data) as $line)
1507  {
1508    $entry = snmp_parse_line($line, $flags);
1509
1510    // For numeric oids do not split oid and index part
1511    if ($numeric_oids && $entry['index_count'] > 0 && is_valid_snmp_value($entry['value']))
1512    {
1513      $array[$entry['index']] = $entry['value'];
1514      continue;
1515    }
1516
1517    //list($oid, $index) = explode('.', $oid, 2);
1518    if (isset($entry['oid_name'][0]) && $entry['index_count'] > 0 && is_valid_snmp_value($entry['value']))
1519    {
1520      $array[$entry['index']][$entry['oid_name']] = $entry['value'];
1521    }
1522  }
1523
1524  if (empty($array))
1525  {
1526    $GLOBALS['snmp_status'] = FALSE;
1527    snmp_log_errors('snmpget', $device, $oids, $options, $mib, $mibdir);
1528  }
1529
1530  if (OBS_DEBUG)
1531  {
1532    print_message('SNMP STATUS['.($GLOBALS['snmp_status'] ? '%gTRUE': '%rFALSE').'%n]', 'color');
1533  }
1534
1535  return $array;
1536}
1537
1538/**
1539 * Uses snmpwalk to fetch a single OID and returns a array.
1540 *
1541 * @param  array  $device
1542 * @param  string $oid
1543 * @param  array  $array
1544 * @param  string $mib
1545 * @param  string $mibdir Optional, correct path should be set in the MIB definition
1546 * @param  integer $flags
1547 * @return array
1548 */
1549function snmpwalk_cache_oid($device, $oid, $array, $mib = NULL, $mibdir = NULL, $flags = OBS_SNMP_ALL)
1550{
1551  $numeric_oids = is_flag_set(OBS_SNMP_NUMERIC, $flags); // Numeric oids, do not parse oid part
1552  $options = snmp_gen_options('snmpwalk', $flags);
1553
1554  $data = snmp_walk($device, $oid, $options, $mib, $mibdir, $flags);
1555  foreach (explode("\n", $data) as $line)
1556  {
1557    $entry = snmp_parse_line($line, $flags);
1558
1559    // For numeric oids do not split oid and index part
1560    if ($numeric_oids && $entry['index_count'] > 0 && is_valid_snmp_value($entry['value']))
1561    {
1562      $array[$entry['index']] = $entry['value'];
1563      continue;
1564    }
1565
1566    if (isset($entry['oid_name'][0]) && $entry['index_count'] > 0 && is_valid_snmp_value($entry['value']))
1567    {
1568      $array[$entry['index']][$entry['oid_name']] = $entry['value'];
1569    }
1570  }
1571
1572  return $array;
1573
1574}
1575
1576// just like snmpwalk_cache_oid except that it returns the numerical oid as the index
1577// this is useful when the oid is indexed by the mac address and snmpwalk would
1578// return periods (.) for non-printable numbers, thus making many different indexes appear
1579// to be the same.
1580// DOCME needs phpdoc block
1581// TESTME needs unit testing
1582// CLEANME (deprecated) not used anymore
1583function snmpwalk_cache_oid_num($device, $oid, $array, $mib = NULL, $mibdir = NULL)
1584{
1585  return snmpwalk_cache_oid($device, $oid, $array, $mib, $mibdir, OBS_SNMP_ALL_NUMERIC);
1586}
1587
1588// just like snmpwalk_cache_oid_num (it returns the numerical oid as the index),
1589// but use snmptranslate for cut mib part from index
1590// DOCME needs phpdoc block
1591// TESTME needs unit testing
1592// FIXME. maybe override function snmpwalk_cache_oid_num()?
1593function snmpwalk_cache_oid_num2($device, $oid, $array, $mib = NULL, $mibdir = NULL, $flags = OBS_SNMP_ALL_NUMERIC)
1594{
1595  $options = snmp_gen_options('snmpwalk', $flags | OBS_SNMP_NUMERIC); // This function always use OBS_SNMP_NUMERIC
1596
1597  $oid_num = snmp_translate($oid, $mib, $mibdir);
1598  //$data = snmp_walk($device, $oid, $options, $mib, $mibdir, $flags);
1599  $data = snmp_walk($device, $oid_num, $options, $mib, $mibdir, $flags);
1600
1601  $pattern = '/^' . str_replace('.', '\.', $oid_num) . '\./';
1602
1603  foreach (explode("\n", $data) as $entry)
1604  {
1605    list($oid_num, $value) = explode('=', $entry, 2);
1606    $oid_num = trim($oid_num);
1607    $value   = snmp_value_clean($value, $flags);
1608    $index   = preg_replace($pattern, '', $oid_num);
1609
1610    if (isset($oid) && isset($index[0]) && is_valid_snmp_value($value))
1611    {
1612      $array[$index][$oid] = $value;
1613    }
1614  }
1615
1616  return $array;
1617}
1618
1619// DOCME needs phpdoc block
1620// TESTME needs unit testing
1621// used only in discovery/processors/juniper-system-mib.inc.php
1622function snmpwalk_cache_bare_oid($device, $oid, $array, $mib = NULL, $mibdir = NULL, $flags = OBS_SNMP_ALL)
1623{
1624  // Always use snmpwalk_cache_oid() for numeric
1625  if (is_flag_set(OBS_SNMP_NUMERIC,       $flags))
1626  {
1627    return snmpwalk_cache_oid($device, $oid, $array, $mib, $mibdir, $flags);
1628  }
1629
1630  $options = snmp_gen_options('snmpwalk', $flags);
1631
1632  $data = snmp_walk($device, $oid, $options, $mib, $mibdir, $flags);
1633  foreach (explode("\n", $data) as $line)
1634  {
1635    $entry = snmp_parse_line($line, $flags);
1636
1637    // Not know why, but here removed index part more than 2, here old code:
1638    // list($r_oid, $first, $second) = explode('.', $r_oid);
1639    if (isset($entry['oid']) && is_valid_snmp_value($entry['value']))
1640    {
1641      $array[$entry['oid']] = $entry['value'];
1642    }
1643  }
1644
1645  return $array;
1646}
1647
1648
1649// DOCME needs phpdoc block
1650// TESTME needs unit testing
1651// used only in discovery/processors/juniper-system-mib.inc.php
1652function snmpwalk_cache_double_oid($device, $oid, $array, $mib = NULL, $mibdir = NULL, $flags = OBS_SNMP_ALL)
1653{
1654  // Always use snmpwalk_cache_oid() for numeric
1655  if (is_flag_set(OBS_SNMP_NUMERIC,       $flags))
1656  {
1657    return snmpwalk_cache_oid($device, $oid, $array, $mib, $mibdir, $flags);
1658  }
1659
1660  $options = snmp_gen_options('snmpwalk', $flags);
1661
1662  $index_count = 2;
1663  $data = snmp_walk($device, $oid, $options, $mib, $mibdir, $flags);
1664  foreach (explode("\n", $data) as $line)
1665  {
1666    $entry = snmp_parse_line($line, $flags);
1667
1668    // Not know why, but here removed index part more than 2, here old code:
1669    // list($r_oid, $first, $second) = explode('.', $r_oid);
1670    if (isset($entry['oid_name'][0]) && $entry['index_count'] >= $index_count && is_valid_snmp_value($entry['value']))
1671    {
1672      $index = implode('.', array_slice($entry['index_parts'], 0, $index_count));
1673      $array[$index][$entry['oid_name']] = $entry['value'];
1674    }
1675  }
1676
1677  return $array;
1678}
1679
1680// DOCME needs phpdoc block
1681// TESTME needs unit testing
1682function snmpwalk_cache_triple_oid($device, $oid, $array, $mib = NULL, $mibdir = NULL, $flags = OBS_SNMP_ALL)
1683{
1684  // Always use snmpwalk_cache_oid() for numeric
1685  if (is_flag_set(OBS_SNMP_NUMERIC,       $flags))
1686  {
1687    return snmpwalk_cache_oid($device, $oid, $array, $mib, $mibdir, $flags);
1688  }
1689
1690  $options = snmp_gen_options('snmpwalk', $flags);
1691
1692  $index_count = 3; // Not know why, but here removed index part more than 3
1693  $data = snmp_walk($device, $oid, $options, $mib, $mibdir, $flags);
1694  foreach (explode("\n", $data) as $line)
1695  {
1696    $entry = snmp_parse_line($line, $flags);
1697
1698    // Not know why, but here removed index part more than 3, here old code:
1699    // list($r_oid, $first, $second, $tried) = explode('.', $r_oid);
1700    if (isset($entry['oid_name'][0]) && $entry['index_count'] >= $index_count && is_valid_snmp_value($entry['value']))
1701    {
1702      $index = implode('.', array_slice($entry['index_parts'], 0, $index_count));
1703      $array[$index][$entry['oid_name']] = $entry['value'];
1704    }
1705  }
1706
1707  return $array;
1708}
1709
1710// DOCME needs phpdoc block
1711// TESTME needs unit testing
1712function snmpwalk_cache_twopart_oid($device, $oid, $array, $mib = NULL, $mibdir = NULL, $flags = OBS_SNMP_ALL)
1713{
1714  // Always use snmpwalk_cache_oid() for numeric
1715  if (is_flag_set(OBS_SNMP_NUMERIC,       $flags))
1716  {
1717    return snmpwalk_cache_oid($device, $oid, $array, $mib, $mibdir, $flags);
1718  }
1719
1720  $options = snmp_gen_options('snmpwalk', $flags);
1721
1722  $index_count = 2;
1723  $data = snmp_walk($device, $oid, $options, $mib, $mibdir, $flags);
1724  foreach (explode("\n", $data) as $line)
1725  {
1726    $entry = snmp_parse_line($line, $flags);
1727
1728    if (isset($entry['oid_name'][0]) && $entry['index_count'] >= $index_count && is_valid_snmp_value($entry['value']))
1729    {
1730      $first     = array_shift($entry['index_parts']);
1731      $second    = implode('.', $entry['index_parts']);
1732
1733      $array[$first][$second][$entry['oid_name']] = $entry['value'];
1734    }
1735  }
1736
1737  return $array;
1738}
1739
1740// DOCME needs phpdoc block
1741// TESTME needs unit testing
1742function snmpwalk_cache_threepart_oid($device, $oid, $array, $mib = NULL, $mibdir = NULL, $flags = OBS_SNMP_ALL)
1743{
1744  // Always use snmpwalk_cache_oid() for numeric
1745  if (is_flag_set(OBS_SNMP_NUMERIC,       $flags))
1746  {
1747    return snmpwalk_cache_oid($device, $oid, $array, $mib, $mibdir, $flags);
1748  }
1749
1750  $options = snmp_gen_options('snmpwalk', $flags);
1751
1752  $index_count = 3;
1753  $data = snmp_walk($device, $oid, $options, $mib, $mibdir, $flags);
1754
1755  foreach (explode("\n", $data) as $line)
1756  {
1757    $entry = snmp_parse_line($line, $flags);
1758    if (isset($entry['oid_name'][0]) && $entry['index_count'] >= $index_count && is_valid_snmp_value($entry['value']))
1759    {
1760      $first     = array_shift($entry['index_parts']);
1761      $second    = array_shift($entry['index_parts']);
1762      $third     = implode('.', $entry['index_parts']);
1763      $array[$first][$second][$third][$entry['oid_name']] = $entry['value'];
1764    }
1765  }
1766
1767  return $array;
1768}
1769
1770/**
1771 * SNMP walk and parse tables with any (not limited) count of index parts into multilevel array.
1772 * Array levels same as count index parts. Ie: someOid.1.2.3.4 -> 4 index parts, and result array also will have 4 levels
1773 *
1774 * @param array   $device Device array
1775 * @param string  $oid    Table OID name
1776 * @param array   $array  Array from previous snmpwalk for merge (or empty)
1777 * @param string  $mib    MIB name
1778 * @param mixed   $mibdir Array or string with MIB dirs list, by default used dir from MIB definitions
1779 * @param integer $flags  SNMP walk/parse flags
1780 *
1781 * @return array          Prsed array with content from requested Table
1782 */
1783function snmp_walk_multipart_oid($device, $oid, $array, $mib = NULL, $mibdir = NULL, $flags = OBS_QUOTES_TRIM)
1784{
1785  // Always use snmpwalk_cache_oid() for numeric
1786  if (is_flag_set(OBS_SNMP_NUMERIC,       $flags))
1787  {
1788    return snmpwalk_cache_oid($device, $oid, $array, $mib, $mibdir, $flags);
1789  }
1790
1791  $options = snmp_gen_options('snmpwalk', $flags);
1792
1793  $index_count = 1;
1794  $data = snmp_walk($device, $oid, $options, $mib, $mibdir, $flags);
1795  foreach (explode("\n", $data) as $line)
1796  {
1797    $entry = snmp_parse_line($line, $flags);
1798    //print_vars($entry);
1799
1800    if (isset($entry['oid_name'][0]) && $entry['index_count'] >= $index_count && is_valid_snmp_value($entry['value']))
1801    {
1802      $entry_array = array($entry['oid_name'] => $entry['value']);
1803      for ($i = $entry['index_count'] - 1; $i >= 0; $i--)
1804      {
1805        $entry_array = array($entry['index_parts'][$i] => $entry_array);
1806      }
1807      $array = array_replace_recursive((array)$array, $entry_array);
1808
1809      /* this seems retarded. need a way to just build this automatically.
1810      switch ($entry['index_count'])
1811      {
1812        case 1:
1813          $array[$entry['index_parts'][0]][$entry['oid_name']] = $entry['value'];
1814          break;
1815        case 2:
1816          $array[$entry['index_parts'][0]][$entry['index_parts'][1]][$entry['oid_name']] = $entry['value'];
1817          break;
1818        case 3:
1819          $array[$entry['index_parts'][0]][$entry['index_parts'][1]][$entry['index_parts'][2]][$entry['oid_name']] = $entry['value'];
1820          break;
1821        case 4:
1822          $array[$entry['index_parts'][0]][$entry['index_parts'][1]][$entry['index_parts'][2]][$entry['index_parts'][3]][$entry['oid_name']] = $entry['value'];
1823          break;
1824        case 5:
1825          $array[$entry['index_parts'][0]][$entry['index_parts'][1]][$entry['index_parts'][2]][$entry['index_parts'][3]][$entry['index_parts'][4]][$entry['oid_name']] = $entry['value'];
1826          break;
1827      }
1828      */
1829    }
1830  }
1831
1832  return $array;
1833}
1834
1835/**
1836 * Initialize (start) snmpsimd daemon, for tests or other purposes.
1837 *   Stop daemon not required, because here registered shutdown_function for kill daemon at end of run script(s)
1838 *
1839 * @param string $snmpsimd_data Data DIR, where *.snmprec placed
1840 * @param string $snmpsimd_ip   Local IP which used for daemon (default 127.0.0.1)
1841 * @param integer $snmpsimd_port Local Port which used for daemon (default 16111)
1842 */
1843function snmpsimd_init($snmpsimd_data, $snmpsimd_ip = '127.0.0.1', $snmpsimd_port = 16111)
1844{
1845  global $config;
1846
1847  if (str_contains($snmpsimd_ip, ':'))
1848  {
1849    // IPv6
1850    $ifconfig_cmd = "ifconfig | grep 'inet6 addr:$snmpsimd_ip' | cut -d: -f2 | awk '{print $1}'";
1851    $snmpsimd_end = 'udpv6';
1852  } else {
1853    $ifconfig_cmd = "ifconfig | grep 'inet addr:$snmpsimd_ip' | cut -d: -f2 | awk '{print $1}'";
1854    $snmpsimd_end = 'udpv4';
1855  }
1856  $snmpsimd_ip  = external_exec($ifconfig_cmd);
1857
1858  if ($snmpsimd_ip)
1859  {
1860    //$snmpsimd_port = 16111;
1861
1862    // Detect snmpsimd command path
1863    $snmpsimd_path = external_exec('which snmpsimd.py');
1864    if (empty($snmpsimd_path))
1865    {
1866      foreach (array('/usr/local/bin/', '/usr/bin/', '/usr/sbin/') as $path)
1867      {
1868        if (is_executable($path . 'snmpsimd.py'))
1869        {
1870          $snmpsimd_path = $path . 'snmpsimd.py';
1871          break;
1872        }
1873        else if (is_executable($path . 'snmpsimd'))
1874        {
1875          $snmpsimd_path = $path . 'snmpsimd';
1876          break;
1877        }
1878      }
1879    }
1880    //var_dump($snmpsimd_path);
1881
1882    if (empty($snmpsimd_path))
1883    {
1884      print_warning("snmpsimd not found, please install it first.");
1885    } else {
1886      //$snmpsimd_data = dirname(__FILE__) . '/data/os';
1887
1888      $tmp_path = empty($config['temp_dir']) ? '/tmp' : $config['temp_dir']; // GLOBALS empty in php units
1889
1890      $snmpsimd_pid  = $tmp_path.'/observium_snmpsimd.pid';
1891      $snmpsimd_log  = $tmp_path.'/observium_snmpsimd.log';
1892
1893      if (is_file($snmpsimd_pid))
1894      {
1895        // Kill stale snmpsimd process
1896        $pid  = file_get_contents($snmpsimd_pid);
1897        $info = get_pid_info($pid);
1898        //var_dump($info);
1899        if (str_contains($info['COMMAND'], 'snmpsimd'))
1900        {
1901          external_exec("kill -9 $pid");
1902        }
1903        unlink($snmpsimd_pid);
1904      }
1905
1906      $snmpsimd_cmd = "$snmpsimd_path --daemonize --data-dir=$snmpsimd_data --agent-$snmpsimd_end-endpoint=$snmpsimd_ip:$snmpsimd_port --pid-file=$snmpsimd_pid --logging-method=file:$snmpsimd_log";
1907      //var_dump($snmpsimd_cmd);
1908
1909      external_exec($snmpsimd_cmd);
1910      $pid = file_get_contents($snmpsimd_pid);
1911      if ($pid)
1912      {
1913        define('OBS_SNMPSIMD', TRUE);
1914        register_shutdown_function(function($snmpsimd_pid){
1915          $pid = file_get_contents($snmpsimd_pid);
1916          //echo "KILL'em all! PID: $pid\n";
1917          external_exec("kill -9 $pid");
1918          unlink($snmpsimd_pid);
1919        }, $snmpsimd_pid);
1920      }
1921    }
1922    //exit;
1923  } else {
1924    print_warning("Local IP $snmpsimd_ip unavailable. SNMP simulator not started.");
1925  }
1926  if (!defined('OBS_SNMPSIMD'))
1927  {
1928    define('OBS_SNMPSIMD', FALSE);
1929  }
1930}
1931
1932/**
1933 * Take -OXqs output and parse it into an array containing OID array and the value
1934 * Hopefully this is the beginning of more intelligent OID parsing!
1935 * Thanks to David Farrell <DavidPFarrell@gmail.com> for the parser solution.
1936 * This function is free for use by all with attribution to David.
1937 *
1938 * @return array
1939 * @param $string
1940 */
1941// TESTME needs unit testing
1942function parse_oid2($string)
1943{
1944  $result = array();
1945  $matches = array();
1946
1947  // Match OID - If wrapped in double-quotes ('"'), must escape '"', else must escape ' ' (space) or '[' - Other escaping is optional
1948  $match_count = preg_match('/^(?:((?!")(?:[^\\\\\\[ ]|(?:\\\\.))+)|(?:"((?:[^\\\\\"]|(?:\\\\.))+)"))/', $string, $matches);
1949  if (null !== $match_count && $match_count > 0)
1950  {
1951    // [1] = unquoted, [2] = quoted
1952    $value = strlen($matches[1]) > 0 ? $matches[1] : $matches[2];
1953    $result[] = stripslashes($value);
1954
1955    // I do this (vs keeping track of offset) to use ^ in regex
1956    $string = substr($string, strlen($matches[0]));
1957
1958    // Match indexes (optional) - If wrapped in double-quotes ('"'), must escape '"', else must escape ']' - Other escaping is optional
1959    while (true)
1960    {
1961      $match_count = preg_match('/^\\[(?:((?!")(?:[^\\\\\\]]|(?:\\\\.))+)|(?:"((?:[^\\\\\"]|(?:\\\\.))+)"))\\]/', $string, $matches);
1962      if (null !== $match_count && $match_count > 0)
1963      {
1964        // [1] = unquoted, [2] = quoted
1965        $value = strlen($matches[1]) > 0 ? $matches[1] : $matches[2];
1966        $result[] = stripslashes($value);
1967
1968        // I do this (vs keeping track of offset) to use ^ in regex
1969        $string = substr($string, strlen($matches[0]));
1970      }
1971      else
1972      {
1973        break;
1974      }
1975    } // while
1976
1977    // Match value - Skips leading ' ' characters - If remainder is wrapped in double-quotes ('"'), must escape '"', other escaping is optional
1978    $match_count = preg_match('/^\\s+(?:((?!")(?:[^\\\\]|(?:\\\\.))+)|(?:"((?:[^\\\\\"]|(?:\\\\.))+)"))$/', $string, $matches);
1979    if (null !== $match_count && $match_count > 0)
1980    {
1981      // [1] = unquoted, [2] = quoted
1982      $value = strlen($matches[1]) > 0 ? $matches[1] : $matches[2];
1983
1984      $result[] = stripslashes($value);
1985
1986      if (strlen($string) != strlen($matches[0])) { echo 'Length error!'; return null; }
1987
1988      return $result;
1989    }
1990  }
1991
1992  // All or nothing
1993  return null;
1994}
1995
1996/**
1997 * Take -Oqs output and parse it into an array containing OID array and the value
1998 * Hopefully this is the beginning of more intelligent OID parsing!
1999 * Thanks to David Farrell <DavidPFarrell@gmail.com> for the parser solution.
2000 * This function is free for use by all with attribution to David.
2001 *
2002 * @return array
2003 * @param $string
2004 */
2005// TESTME needs unit testing
2006// CLEANME (deprecated) not used anymore
2007function parse_oid($string)
2008{
2009  $result = array();
2010  while (true)
2011  {
2012    $matches = array();
2013    $match_count = preg_match('/^(?:((?:[^\\\\\\. "]|(?:\\\\.))+)|(?:"((?:[^\\\\"]|(?:\\\\.))+)"))((?:[\\. ])|$)/', $string, $matches);
2014    if (null !== $match_count && $match_count > 0)
2015    {
2016      // [1] = unquoted, [2] = quoted
2017      $value = strlen($matches[1]) > 0 ? $matches[1] : $matches[2];
2018      $result[] = stripslashes($value);
2019
2020      // Are we expecting any more parts?
2021      if (strlen($matches[3]) > 0)
2022      {
2023        // I do this (vs keeping track of offset) to use ^ in regex
2024        $string = substr($string, strlen($matches[0]));
2025      }
2026      else
2027      {
2028        $ret['value'] = array_pop($result);
2029        $ret['oid']   = $result;
2030        return $ret;
2031      }
2032    }
2033    else
2034    {
2035      // All or nothing
2036      return null;
2037    }
2038  } // while
2039}
2040
2041// DOCME needs phpdoc block
2042// TESTME needs unit testing
2043// CLEANME (deprecated) not used anymore
2044function snmp_walk_parser2($device, $oid, $oid_elements, $mib = NULL, $mibdir = NULL)
2045{
2046  $data = snmp_walk($device, $oid, '-Oqs', $mib, $mibdir, FALSE);
2047  foreach (explode("\n", $data) as $text)
2048  {
2049    $ret = parse_oid2($text);
2050    if (!empty($ret['value']))
2051    {
2052      // this seems retarded. need a way to just build this automatically.
2053      switch ($oid_elements)
2054      {
2055        case 1:
2056          $array[$ret['oid'][0]] = $ret['value'];
2057          break;
2058        case 2:
2059          $array[$ret['oid'][1]][$ret['oid'][0]] = $ret['value'];
2060          break;
2061        case 3:
2062          $array[$ret['oid'][1]][$ret['oid'][2]][$ret['oid'][0]] = $ret['value'];
2063          break;
2064        case 4:
2065          $array[$ret['oid'][1]][$ret['oid'][2]][$ret['oid'][3]][$ret['oid'][0]] = $ret['value'];
2066          break;
2067      }
2068    }
2069  }
2070  return $array;
2071}
2072
2073// DOCME needs phpdoc block
2074// TESTME needs unit testing
2075function snmp_walk_parser($device, $oid, $oid_elements, $mib = NULL, $mibdir = NULL)
2076{
2077  $data = snmp_walk($device, $oid, '-Oqs', $mib, $mibdir, FALSE);
2078  foreach (explode("\n", $data) as $text)
2079  {
2080    $ret = parse_oid($text);
2081    if (!empty($ret['value']))
2082    {
2083      // this seems retarded. need a way to just build this automatically.
2084      switch ($oid_elements)
2085      {
2086        case 1:
2087          $array[$ret['oid'][0]] = $ret['value'];
2088          break;
2089        case 2:
2090          $array[$ret['oid'][1]][$ret['oid'][0]] = $ret['value'];
2091          break;
2092        case 3:
2093          $array[$ret['oid'][1]][$ret['oid'][2]][$ret['oid'][0]] = $ret['value'];
2094          break;
2095        case 4:
2096          $array[$ret['oid'][1]][$ret['oid'][2]][$ret['oid'][3]][$ret['oid'][0]] = $ret['value'];
2097          break;
2098      }
2099    }
2100  }
2101
2102  return $array;
2103}
2104
2105
2106// CLEANME (deprecated) duplicate for snmpwalk_cache_oid
2107function snmpwalk_cache_multi_oid($device, $oid, $array, $mib = NULL, $mibdir = NULL, $flags = OBS_SNMP_ALL)
2108{
2109  return snmpwalk_cache_oid($device, $oid, $array, $mib, $mibdir, $flags);
2110}
2111
2112// CLEANME (deprecated) not used anymore
2113function snmp_parser_quote($m)
2114{
2115  return str_replace(array('.',' '),
2116    array('PLACEHOLDER-DOT', 'PLACEHOLDER-SPACE'), $m[1]);
2117}
2118
2119// CLEANME (deprecated) not used anymore
2120function snmp_parser_unquote($str)
2121{
2122  return str_replace(array('PLACEHOLDER-DOT', 'PLACEHOLDER-SPACE', 'PLACEHOLDER-ESCAPED-QUOTE'),
2123    array('.',' ','"'), $str);
2124}
2125
2126// CLEANME (deprecated) not used anymore
2127function ascii_to_oid($string)
2128{
2129  return snmp_string_to_oid($string);
2130}
2131
2132// CLEANME (deprecated) not used anymore
2133function string_to_oid($string)
2134{
2135  return snmp_string_to_oid($string);
2136}
2137
2138// Return table from array if already walked, else walk it.
2139// Currently overwrites arrays passed as $array, array_merge_indexed didn't like non-numeric indexes?
2140function snmp_cache_table($device, $table, $array, $mib, $mibdir = NULL, $flags = NULL)
2141{
2142
2143  // We seem to have been passed a MIB::oidName format. Split it.
2144  if (strpos($table, '::')) { list($mib, $table) = explode("::", $table); }
2145
2146  if (isset($GLOBALS['cache_snmp'][$device['device_id']][$mib][$table]) && is_array($GLOBALS['cache_snmp'][$device['device_id']][$mib][$table]))
2147  {
2148    print_debug("Get cached Table OID: $mib::$table");
2149    $array = array_merge_indexed($GLOBALS['cache_snmp'][$device['device_id']][$mib][$table], $array);
2150    //$array = $GLOBALS['cache_snmp'][$device['device_id']][$mib][$table];
2151  } else {
2152    $walk = snmpwalk_cache_oid($device, $table, array(), $mib, $mibdir, $flags);
2153    if (!isset($GLOBALS['cache_snmp'][$device['device_id']][$mib][$table]) && $walk)
2154    {
2155      print_debug("Store in cache Table OID: $mib::$table");
2156      $GLOBALS['cache_snmp'][$device['device_id']][$mib][$table] = $walk;
2157      $array = array_merge_indexed($walk, $array);
2158    }
2159    //$array = $walk;
2160  }
2161  return $array;
2162}
2163
2164// Return oid from cache if already fetched, else fetch it.
2165// Currently overwrites arrays passed as $array, array_merge_indexed didn't like non-numeric indexes?
2166// FIXME -- handle multiple OIDs (as individual cache entries)
2167function snmp_cache_oid($device, $oid, $mib = NULL, $mibdir = NULL, $flags = NULL)
2168{
2169
2170  // We seem to have been passed a MIB::oidName format. Split it.
2171  if (strpos($oid, '::')) { list($mib, $oid) = explode("::", $oid); }
2172
2173  if (isset($GLOBALS['cache_snmp'][$device['device_id']][$mib][$oid]))
2174  {
2175    print_debug("Get cached OID: $mib::$oid");
2176    $value = $GLOBALS['cache_snmp'][$device['device_id']][$mib][$oid];
2177  } else {
2178    $value = snmp_get_oid($device, $oid, $mib, $mibdir, $flags);
2179
2180    if (!isset($cache_snmp[$mib][$oid]))
2181    {
2182      print_debug("Store in cache OID: $mib::$oid");
2183      $GLOBALS['cache_snmp'][$device['device_id']][$mib][$oid] = $value;
2184    }
2185
2186  }
2187
2188  return $value;
2189}
2190
2191// EOF
2192