Files
cacti/lib/mib_parser.php
Thomas Vincent 08d0f94f24 Fix develop client behavior, popup isolation and PHP method visibility (#8292)
* fix(core): correct client state and declare public method contracts

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* fix(security): isolate Midwinter external homepage popup

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* fix(client): isolate menu state and simplify validated JavaScript guards

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* test(auth): use certificate expiry truth in rendering assertions

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* refactor(js): remove unused local storage while retaining initializer calls

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* fix(js): encode inline translated values through the shared JSON boundary

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* style(js): wrap encoded translation literals without changing messages

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* fix(client): remove unused checkbox reads from graph resizing

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* refactor(client): declare repeated function locals once

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* test(ui): run column width regression in browser CI

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
2026-10-11 21:15:39 -04:00

521 lines
15 KiB
PHP

<?php
/*
+-------------------------------------------------------------------------+
| Copyright (C) 2004-2026 The Cacti Group |
| |
| This program is free software; you can redistribute it and/or |
| modify it under the terms of the GNU General Public License |
| as published by the Free Software Foundation; either version 2 |
| of the License, or (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU General Public License for more details. |
+-------------------------------------------------------------------------+
| Cacti: The Complete RRDtool-based Graphing Solution |
+-------------------------------------------------------------------------+
| This code is designed, written, and maintained by the Cacti Group. See |
| about.php and/or the AUTHORS file for specific developer information. |
+-------------------------------------------------------------------------+
| http://www.cacti.net/ |
+-------------------------------------------------------------------------+
*/
/* This mib parser bases on the PHP mib_compiler script of David Eder for
phpsnmp which bases itself on libsnmp. Both scripts are licensed under the
GNU Lesser General Public License.
/**
* phpsnmp - a PHP SNMP library
* Copyright (C) 2004 David Eder <david@eder,us>
*
* Based on snmp - a Python SNMP library
* Copyright (C) 2003 Unicity Pty Ltd <libsnmp@unicity.com.au>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
class MibParser extends MibCache {
protected array $parsed = [];
public array $oids = [];
public mixed $mib = false;
/**
* Constructor
*/
public function __construct() {
set_time_limit(0);
ini_set('memory_limit', '-1');
error_reporting(E_ALL);
}
public function add_mib(string $filename, string $mib_name): void {
$mibtext = file_get_contents($filename);
if ($mibtext === false) {
return;
}
MibParser::parse_mib($mibtext, $mib_name, true);
}
/**
* Get Tokens
*
* @param string $text
* @return array
*/
public function get_tokens($text) {
$in_quote = false;
$in_comment = false;
$token = '';
$tokens = [];
$length = strlen($text);
for ($i = 0; $i < $length; $i++) {
if ($in_quote) {
if ($text[$i] == '"') {
$in_quote = false;
if ($token != '') {
// strip whitespaces from the end of the beginning of every object description row
$lines = preg_split('/\r\n|\r|\n/', $token) ?: [];
$token = '';
foreach ($lines as $line) {
$token .= trim($line) . "\r\n";
}
$tokens[] = $token;
$token = '';
}
} else {
$token .= $text[$i];
}
} elseif ($in_comment) {
if ($text[$i] == "\n" || $text[$i] == "\r") {
$in_comment = false;
}
} else {
switch($text[$i]) {
case ':':
if ($text[$i + 1] == ':' && $text[$i + 2] == '=') {
if ($token != '') {
$tokens[] = $token;
$token = '';
}
$tokens[] = '::=';
$i += 2;
} else {
$token .= $text[$i];
}
break;
case '.':
if ($text[$i + 1] == '.') {
if ($token != '') {
$tokens[] = $token;
$token = '';
}
$tokens[] = '..';
$i++;
} else {
$token .= $text[$i];
}
break;
case ',':
case ';':
case '{':
case '}':
case '(':
case ')':
case '|':
if ($token != '') {
$tokens[] = $token;
$token = '';
}
$tokens[] = $text[$i];
break;
case ' ':
case "\t":
case "\n":
case "\r":
if ($token != '') {
$tokens[] = $token;
$token = '';
}
break;
case '-':
if ($text[$i + 1] == '-') {
$in_comment = true;
} else {
$token .= $text[$i];
}
break;
case '"':
$in_quote = true;
break;
default:
$token .= $text[$i];
}
}
}
if ($token != '') {
$tokens[] = $token;
}
return $tokens;
}
/**
* Parse simple token
*
* @param array $tokens
* @param integer $index
* @param array $allowed
* @return array
*/
public function parse_simple_token($tokens, &$index, $allowed = null) {
$index++;
if (is_array($allowed)) {
if (in_array(strtolower($tokens[$index]), $allowed, true)) {
return $tokens[$index];
}
} elseif (is_null($allowed)) {
if ($tokens[$index] == '{') {
return MibParser::parse_bracket_token($tokens, $index, '{', '}');
} else {
return $tokens[$index];
}
}
trigger_error("unknown token {$tokens[$index]} {$tokens[$index]}", E_USER_ERROR);
}
/**
* Parse SYNTAX token
*
* @param array $tokens
* @param integer $index
* @return array
*/
public function parse_SYNTAX_token($tokens, &$index) {
$ret = null;
switch($tokens[$index + 1]) {
case 'SEQUENCE':
if ($tokens[$index + 2] == 'OF') {
$index += 3;
if ($tokens[$index] == '{') {
$ret = ['SEQUENCE OF'=>MibParser::parse_bracket_token($tokens, $index, '{', '}')];
} else {
$ret = ['SEQUENCE OF'=>$tokens[$index]];
}
}
break;
case 'OCTET':
if ($tokens[$index + 2] == 'STRING') {
$index += 3;
if ($tokens[$index] == '{') {
$ret = ['OCTET STRING'=>MibParser::parse_bracket_token($tokens, $index, '{', '}')];
} elseif ($tokens[$index] == '(') {
$ret = ['OCTET STRING'=>MibParser::parse_bracket_token($tokens, $index, '(', ')')];
} else {
$ret = 'OCTET STRING';
}
}
break;
case 'OBJECT':
if ($tokens[$index + 2] == 'IDENTIFIER') {
$index++;
$ret = $tokens[$index] . ' ' . $tokens[$index + 1];
$index++;
} else {
trigger_error("unknown token {$tokens[$index + 1]} {$tokens[$index + 2]}", E_USER_ERROR);
}
break;
case 'INTEGER':
case 'Counter':
case 'Counter32':
case 'Counter64':
case 'Integer32':
case 'Gauge':
case 'Gauge32':
case 'TimeStamp':
case 'TimeTicks':
case 'PhysAddress':
case 'IpAddress':
case 'DateAndTime':
case 'TimeInterval':
case 'Unsigned32':
case 'DisplayString':
$index++;
$ret = $tokens[$index];
if ($tokens[$index + 1] == '{') {
$index++;
$ret = [$ret=>MibParser::parse_bracket_token($tokens, $index, '{', '}')];
} elseif ($tokens[$index + 1] == '(') {
$index++;
$ret = [$ret=>MibParser::parse_bracket_token($tokens, $index, '(', ')')];
}
break;
default:
$index++;
$ret = $tokens[$index];
if ($tokens[$index + 1] == '{') {
$index++;
$ret = [$ret=>MibParser::parse_bracket_token($tokens, $index, '{', '}')];
} elseif ($tokens[$index + 1] == '(') {
$index++;
$ret = [$ret=>MibParser::parse_bracket_token($tokens, $index, '(', ')')];
}
break;
}
return $ret;
}
/**
* Parse bracket token
*
* @param array $tokens
* @param integer $index
* @param string $start
* @param string $end
* @return array
*/
public function parse_bracket_token($tokens, &$index, $start, $end) {
$begin = $index + 1;
while ($index + 1 < count($tokens) && $tokens[$index] != $end) {
$index++;
if ($tokens[$index] == $start) {
MibParser::parse_bracket_token($tokens, $index, $start, $end);
$index++;
}
}
return array_slice($tokens, $begin, $index - $begin);
}
/**
* Parse a MIB file
*
* @param string $mibtext
* @param boolean $full
* @param mixed $mib_name
*/
public function parse_mib($mibtext, $mib_name, $full = false): void {
$tokens = MibParser::get_tokens($mibtext);
$cnt = count($tokens);
$rec = [];
for ($index = 0; $index < $cnt; $index++) {
if ($tokens[$index] == 'DEFINITIONS' && $tokens[$index + 1] == '::=' && $tokens[$index + 2] == 'BEGIN') {
$mib_name = $tokens[$index - 1];
$this->mib = $mib_name;
} elseif (in_array($tokens[$index], ['OBJECT-IDENTITY', 'OBJECT-TYPE', 'OBJECT-GROUP', 'NOTIFICATION-GROUP', 'MODULE-IDENTITY', 'NOTIFICATION-TYPE'], true)) {
if ($tokens[$index - 1] != ',' && $tokens[$index + 1] != 'FROM' && $tokens[$index + 1] != 'MACRO') {
if (isset($rec['NAME']) && isset($rec['VALUE'])) {
$this->parsed[] = $rec;
}
$rec = [
'NAME' => $tokens[$index - 1],
'MIB' => $mib_name,
'TYPE' => $tokens[$index]
];
}
} elseif ($tokens[$index] == 'TEXTUAL-CONVENTION') {
if ($tokens[$index - 1] == '::=') {
if (isset($rec['NAME']) && isset($rec['VALUE'])) {
$this->parsed[] = $rec;
}
$rec = [
'NAME' => $tokens[$index - 2],
'MIB' => $mib_name,
'TYPE' => $tokens[$index],
'VALUE' => 'TEXTUAL-CONVENTION'
];
}
} elseif ($tokens[$index] == 'OBJECT') {
if ($tokens[$index + 1] == 'IDENTIFIER' && $tokens[$index - 1] != '(' && $tokens[$index - 1] != '::=' && $tokens[$index - 1] != 'SYNTAX' && $tokens[$index - 2] != '(') {
if (isset($rec['NAME']) && isset($rec['VALUE'])) {
$this->parsed[] = $rec;
}
$rec = [
'NAME' => $tokens[$index - 1],
'MIB' => $mib_name,
'TYPE' => $tokens[$index]
];
}
} elseif ($tokens[$index] == '{') {
MibParser::parse_bracket_token($tokens, $index, '{', '}');
} elseif (isset($rec['NAME'])) {
if ($tokens[$index] == '::=' && $tokens[$index + 1] != 'TEXTUAL-CONVENTION') {
$rec['VALUE'] = MibParser::parse_simple_token($tokens, $index);
$this->parsed[] = $rec;
$rec = [];
} elseif ($full) {
if ($tokens[$index] == 'ACCESS') {
$rec['ACCESS'] = MibParser::parse_simple_token($tokens, $index, ['read-only', 'not-accessible', 'read-write']);
} elseif ($tokens[$index] == 'OBJECTS') {
$rec['OBJECTS'] = MibParser::parse_simple_token($tokens, $index);
} elseif ($tokens[$index] == 'NOTIFICATIONS') {
$rec['NOTIFICATIONS'] = MibParser::parse_simple_token($tokens, $index);
} elseif ($tokens[$index] == 'DEFVAL') {
$rec['DEFVAL'] = MibParser::parse_simple_token($tokens, $index);
} elseif ($tokens[$index] == 'DESCRIPTION') {
$rec['DESCRIPTION'] = MibParser::parse_simple_token($tokens, $index);
} elseif ($tokens[$index] == 'INDEX') {
$rec['INDEX'] = MibParser::parse_simple_token($tokens, $index);
} elseif ($tokens[$index] == 'MAX-ACCESS') {
$rec['MAX-ACCESS'] = MibParser::parse_simple_token($tokens, $index, ['read-only', 'not-accessible', 'read-write', 'read-create', 'accessible-for-notify']);
} elseif ($tokens[$index] == 'REFERENCE') {
$rec['REFERENCE'] = MibParser::parse_simple_token($tokens, $index);
} elseif ($tokens[$index] == 'STATUS') {
$rec['STATUS'] = MibParser::parse_simple_token($tokens, $index, ['current', 'deprecated', 'obsolete', 'mandatory']);
} elseif ($tokens[$index] == 'SYNTAX') {
$rec['SYNTAX'] = MibParser::parse_SYNTAX_token($tokens, $index);
} elseif ($tokens[$index] == 'UNITS') {
$rec['UNITS'] = MibParser::parse_simple_token($tokens, $index);
}
}
}
}
if (isset($rec['NAME']) && isset($rec['VALUE'])) {
$this->parsed[] = $rec;
}
}
public function generate(): void {
$this->oids['enterprises'] = ['oid' => '.1.3.6.1.4.1'];
foreach ($this->parsed as $object) {
if (isset($object['VALUE'][0]) && !is_numeric($object['VALUE'][0])) {
if (isset($object['VALUE'][1]) && is_numeric($object['VALUE'][1])) {
if (isset($this->oids[$object['VALUE'][0]]['oid'])) {
$oid = $this->oids[$object['VALUE'][0]]['oid'] . '.' . $object['VALUE'][1];
} else {
$oid = db_fetch_cell_prepared('SELECT `oid`
FROM snmpagent_cache
WHERE name = ?
LIMIT 1',
[$object['VALUE'][0]]);
$oid .= '.' . $object['VALUE'][1];
}
$syntax = null;
if (isset($object['SYNTAX'])) {
$syntax = is_array($object['SYNTAX']) ? key($object['SYNTAX']) : $object['SYNTAX'];
}
$parent_otype = strtoupper(substr($object['VALUE'][0], -5));
$otype = $object['TYPE'];
if ($otype == 'OBJECT-TYPE' && $syntax !== null && !in_array(strtoupper(substr($object['NAME'], -5)), ['TABLE', 'ENTRY'], true) && !in_array($parent_otype, ['TABLE', 'ENTRY'], true)) {
$oid .= '.0';
$otype = 'DATA';
}
$kind = 'unknown';
if (in_array($otype, ['MODULE-IDENTITY', 'OBJECT-IDENTITY'], true)) {
$kind = 'Node';
} elseif (in_array($otype, ['NOTIFICATION-GROUP', 'OBJECT-GROUP'], true)) {
$kind = 'Group';
} elseif ($otype == 'OBJECT-TYPE' && strtoupper(substr($object['NAME'], -5)) == 'TABLE' && $syntax == 'SEQUENCE OF') {
$kind = 'Table';
} elseif ($otype == 'OBJECT-TYPE' && strtoupper(substr($object['NAME'], -5)) == 'ENTRY' && $parent_otype == 'TABLE') {
$kind = 'Row';
} elseif ($otype == 'OBJECT-TYPE' && $parent_otype == 'ENTRY') {
$kind = 'Column';
} elseif ($otype == 'DATA') {
$kind = 'Scalar';
} elseif ($otype == 'NOTIFICATION-TYPE') {
$kind = 'Notification';
}
$this->oids[$object['NAME']] = [
'oid' => $oid,
'max-access' => (isset($object['MAX-ACCESS']) ? $object['MAX-ACCESS'] : 'not-accessible'),
'syntax' => $syntax,
'otype' => $otype,
'kind' => $kind,
'mib' => $object['MIB'],
'description' => $object['DESCRIPTION']
];
if ($otype == 'OBJECT-GROUP' && isset($object['OBJECTS'])) {
$this->oids[$object['NAME']]['objects'] = array_diff($object['OBJECTS'], [',']);
} elseif ($otype == 'NOTIFICATION-GROUP' && isset($object['NOTIFICATIONS'])) {
$this->oids[$object['NAME']]['notifications'] = array_diff($object['NOTIFICATIONS'], [',']);
} elseif ($otype == 'NOTIFICATION-TYPE' && isset($object['OBJECTS'])) {
$this->oids[$object['NAME']]['objects'] = array_diff($object['OBJECTS'], [',']);
}
} elseif ($object['VALUE'] == 'TEXTUAL-CONVENTION') {
$syntax = null;
if (isset($object['SYNTAX'])) {
$syntax = is_array($object['SYNTAX']) ? key($object['SYNTAX']) : $object['SYNTAX'];
}
$kind = 'Textual-Convention';
$otype = 'TEXTUAL-CONVENTION';
$this->oids[$object['NAME']] = [
'oid' => '',
'max-access' => '',
'syntax' => $syntax,
'otype' => $otype,
'kind' => $kind,
'mib' => $object['MIB'],
'description' => $object['DESCRIPTION']
];
}
}
}
unset($this->oids['enterprises']);
unset($this->parsed);
}
}