/*
* 'MIBTree.java' NOTE: This copyright does *not* cover user programs that use
* HQ program services by normal system calls through the application program
* interfaces provided as part of the Hyperic Plug-in Development Kit or the
* Hyperic Client Development Kit - this is merely considered normal use of the
* program, and does *not* fall under the heading of "derived work". Copyright
* (C) [2004, 2005, 2006, 2007, 2008, 2009], Hyperic, Inc. This file is part of
* HQ. HQ is free software; you can redistribute it and/or modify it under the
* terms version 2 of the GNU General Public License as published by the Free
* Software Foundation. This program is distributed in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details. You should have received a copy of the GNU
* General Public License along with this program; if not, write to the Free
* Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA.
*/
package org.hyperic.snmp;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.StringTokenizer;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.snmp4j.smi.OctetString;
/*
* MIB file parser intended ONLY for OBJECT-TYPE name -> OID conversion For
* example, converting: "wwwServiceDescription" to: "1.3.6.1.2.1.65.1.1.1.1.2"
* IMPORTS are ignored and parse() order of MIBs does not matter, provided all
* MIB files required to lookup a given name have been parsed prior to calling
* getOID(name).
*/
public class MIBTree {
public static final String PROP_MIBS_DIR = "snmp.mibs.dir";
private static Log log = LogFactory.getLog(MIBTree.class.getName());
private static final int INDEX = 1;
private static final int IDENTIFIER = 2;
private static final int NO_ACCESS = 4;
private static final int MAX_OID_LEN = 127;
private static final String QUOTE = "\"";
private static final String ASSIGN = "::=";
private static MIBTree instance = null;
private HashMap parsedFiles = new HashMap();
private HashMap table = new HashMap(); // parsed MIBs
private HashMap oids = new HashMap(); // cache OID conversion
private LineNumberReader reader;
private List tokens = new ArrayList(); // lexx / yakk / hakk
private List previous = new ArrayList();
private StringTokenizer tokenizer;
private String currentMIB;
private String lastLookupFailure;
private boolean inDescription = false;
private static final int T_NAME = 0;
private static final int T_PARENT = 1;
private static final int T_OID = 2;
// Every MIB depends on SNMPv2-SMI...
private static final String[][] SNMPv2_SMI = { { "org", "iso", "3" },
{ "dod", "org", "6" },
{ "internet", "dod", "1" },
{ "directory", "internet", "1" },
{ "mgmt", "internet", "2" },
{ "mib-2", "mgmt", "1" },
{ "transmission", "mib-2", "10" },
{ "experimental", "internet", "3" },
{ "private", "internet", "4" },
{ "enterprises", "private", "1" },
{ "security", "internet", "5" },
{ "snmpV2", "internet", "6" },
{ "snmpDomains", "snmpV2", "1" },
{ "snmpProxys", "snmpV2", "2" },
{ "snmpModules", "snmpV2", "3" }, };
// Commonly used objects from SNMPv2-MIB...
private static final String[][] SNMPv2_MIB = { { "sysDescr", "system", "1" },
{ "sysObjectID", "system", "2" },
{ "sysUpTime", "system", "3" },
{ "sysContact", "system", "4" },
{ "sysName", "system", "5" },
{ "sysLocation", "system", "6" },
{ "sysServices", "system", "7" }, };
class MIBNode {
String parent;
String oid;
int flags = 0;
MIBNode(String oid, String parent) {
this.oid = oid.intern();
this.parent = parent;
}
MIBNode getParent() {
return lookup(this.parent);
}
String getMIB() {
return "unknown";
}
boolean hasFlag(int flag) {
return (this.flags & flag) != 0;
}
// To keep the MIBNode objects as small as possible,
// we work backwards here to compose the full oid
// only when they are asked for. Parser will cache
// so this is a one-time expense...
int[] getOID(String name) {
int[] scratch = new int[MAX_OID_LEN];
int ix = scratch.length;
MIBNode node = this;
boolean indexApplies = !hasFlag(IDENTIFIER) && !hasFlag(NO_ACCESS);
boolean hasIndex = false;
boolean isDebug = log.isDebugEnabled();
while ((node != null) && (ix > 0)) {
scratch[--ix] = Integer.parseInt(node.oid);
MIBNode parent = node.getParent();
if (parent == null) {
if (node.getClass() != ISONode.class) {
lastLookupFailure = node.parent;
return null;
} else {
break;
}
}
if (parent.hasFlag(INDEX)) {
hasIndex = true;
}
node = parent;
}
boolean addIndex = indexApplies && !hasIndex;
int len = scratch.length - ix;
int alloc = addIndex ? len + 1 : len;
int[] oid = new int[alloc];
System.arraycopy(scratch, ix, oid, 0, len);
// sysUpTime.0...
if (addIndex) {
oid[len] = 0;
if (isDebug) {
log.debug(getMIB() + "." + name + " has no index, appending .0");
}
}
return oid;
}
}
class ISONode
extends MIBNode
{
ISONode() {
super("1", null);
this.flags = IDENTIFIER;
}
}
class DebugMIBNode
extends MIBNode
{
String mib; // only useful if log.isDebugEnabled
DebugMIBNode(String oid, String parent) {
super(oid, parent);
if (instance != null) {
this.mib = instance.currentMIB;
}
}
String getMIB() {
return this.mib;
}
}
public MIBTree() {
this.table.put("iso", new ISONode());
}
public void init() {
this.currentMIB = "SNMPv2-SMI";
add(SNMPv2_SMI, IDENTIFIER);
this.currentMIB = "SNMPv2-MIB";
add("system", "mib-2", "1", IDENTIFIER);
add(SNMPv2_MIB, 0);
this.currentMIB = null;
String dir = System.getProperty(PROP_MIBS_DIR);
if (dir != null) {
File mibs = new File(dir);
if (!mibs.exists()) {
log.debug(mibs + " MIB dir does not exist");
return;
}
try {
parse(mibs);
} catch (IOException e) {
log.error(e.getMessage(), e);
}
}
}
public synchronized static MIBTree getInstance() {
if (instance == null) {
instance = new MIBTree();
instance.init();
}
return instance;
}
MIBNode lookup(String name) {
return (MIBNode) this.table.get(name);
}
public static void setMibDir(String dir) {
System.setProperty(PROP_MIBS_DIR, dir);
}
public static String toString(int[] oid) {
StringBuffer buffer = new StringBuffer(oid.length * 2);
buffer.append(oid[0]);
for (int i = 1; i < oid.length; i++) {
buffer.append('.').append(oid[i]);
}
return buffer.toString();
}
public int[] getOID(String name) {
int[] oid = (int[]) this.oids.get(name);
if (oid != null) {
return oid;
}
if (name.indexOf('.') != -1) {
// Handle "cpmCPUTotal5sec.1"...
StringTokenizer tok = new StringTokenizer(name, ".");
int[] scratch = new int[MAX_OID_LEN];
int ix = 0;
while (tok.hasMoreTokens()) {
String node = tok.nextToken();
if (Character.isDigit(node.charAt(0))) {
scratch[ix++] = Integer.parseInt(node);
} else {
int[] subnode;
int len = node.length() - 1;
// See:
// http://www.snmp4j.org/doc/org/snmp4j/smi/OID.html#OID(java.lang.String)...
final char quote = '\'';
if ((node.charAt(0) == quote) && (node.charAt(len) == quote)) {
node = node.substring(1, len);
subnode = new OctetString(node).toSubIndex(false).getValue();
} else {
subnode = getOID(node);
}
if (subnode == null) {
return null;
}
System.arraycopy(subnode, 0, scratch, ix, subnode.length);
ix += subnode.length;
}
}
oid = new int[ix];
System.arraycopy(scratch, 0, oid, 0, ix);
} else {
MIBNode mibnode = lookup(name);
if (mibnode == null) {
return null;
}
oid = mibnode.getOID(name);
if (oid == null) {
log.warn(name + " found in tree but unable to resolve OID." + " lastLookupFailure=" +
this.lastLookupFailure);
return null;
}
}
if (log.isDebugEnabled()) {
log.debug(name + " resolved to: " + toString(oid));
}
this.oids.put(name, oid); // Cache result...
return oid;
}
private void add(String[][] table, int flags) {
for (int i = 0; i < table.length; i++) {
String[] entry = table[i];
add(entry[T_NAME], entry[T_PARENT], entry[T_OID], flags);
}
}
private void add(String name, String parent, String oid, int flags) {
boolean isDebug = log.isDebugEnabled();
MIBNode node = (MIBNode) this.table.get(name);
if (node != null) {
if (isDebug) {
log.debug(this.currentMIB + "." + name + " already added by " + node.getMIB());
}
return;
}
node = isDebug ? new DebugMIBNode(oid, parent) : new MIBNode(oid, parent);
node.flags = flags;
this.table.put(name, node);
}
// On-demand StringTokenizer.nextToken ( )...
private String token(int ix) {
if (ix < this.tokens.size()) {
return (String) this.tokens.get(ix);
}
while (this.tokenizer.hasMoreTokens()) {
String token = this.tokenizer.nextToken();
this.tokens.add(token);
if (ix + 1 == this.tokens.size()) {
return token;
}
}
return ""; // Avoid NPE and goofy "CONST".equals ( val )...
}
private String where(int start) {
return " at " + this.currentMIB + ":" + ((start == 0) ? "" : start + "..") + this.reader.getLineNumber();
}
private void tokenize(String line) {
this.previous.clear();
this.previous.addAll(this.tokens);
this.tokens.clear();
if (line == null) {
line = "";
}
this.tokenizer = new StringTokenizer(line);
}
private String readToLine(String contains) throws IOException {
int start = this.reader.getLineNumber();
String line;
while ((line = readLine()) != null) {
if (line.indexOf(contains) != -1) {
return line;
}
}
throw new IOException("Expecting '" + contains + "'" + " not found" + where(start));
}
// Skip all text within DESCRIPTION "..."
// since certain MIBs have text which we would
// otherwise get parsed, which we dont want...
private String skipDescription(String line) throws IOException {
// Flag to prevent recursing on ourselves...
this.inDescription = true;
try {
if (line.indexOf(QUOTE) == -1) {
line = readToLine(QUOTE);
}
if (!line.endsWith(QUOTE)) {
line = readToLine(QUOTE);
}
return readLine();
} finally {
this.inDescription = false;
}
}
private String readLine() throws IOException {
String line;
while ((line = this.reader.readLine()) != null) {
line = line.trim();
if ((line.length() == 0) || line.startsWith("--")) // Skip
// comments...
{
continue;
}
int ix = line.indexOf("--");
if (ix != -1) {
line = line.substring(0, ix).trim();
}
if (line.length() != 0) {
if (!this.inDescription && line.startsWith("DESCRIPTION")) {
// This will recurse...
return skipDescription(line);
} else {
return line;
}
}
}
return null;
}
private void parseId(String name, String line, int flags) throws IOException {
if (line.endsWith(ASSIGN)) {
line = readLine();
}
int start = line.indexOf('{');
int end = line.indexOf('}');
if ((start != -1) && (end == -1)) {
// e.g. cisco LAN-EMULATION-CLIENT-MIB.my
// atmfLanEmulation OBJECT IDENTIFIER ::= {
// enterprises
// atmForum(353)
// atmForumNetworkManagement(5)
// 3 }
String nextLine;
do {
nextLine = readLine();
line += " " + nextLine;
} while ((end = line.indexOf('}')) == -1);
}
if ((start == -1) || (end == -1)) {
throw new IOException("Expecting ::= {...} " + " in " + line + where(0));
}
line = line.substring(start + 1, end).trim();
StringTokenizer tok = new StringTokenizer(line);
int numTokens = tok.countTokens();
if (numTokens < 2) {
throw new IOException("Invalid ID in " + line + where(0));
}
if (numTokens == 2) {
// Common case ::= { wwwServiceEntry 4 }...
String parent = tok.nextToken();
String number = tok.nextToken();
add(name, parent, number, flags);
} else {
// ::= { iso org(3) dod(6) 1 }
// atmfLanEmulation ... ::= (above)
String parent = tok.nextToken();
while (tok.hasMoreTokens()) {
String next = tok.nextToken();
int openParen = next.indexOf('(');
if (openParen != -1) {
int closeParen = next.indexOf(')');
if (closeParen == -1) {
throw new IOException("Expecting ')' in " + line + where(0));
}
String subName = next.substring(0, openParen);
String subNum = next.substring(openParen + 1, closeParen);
add(subName, parent, subNum, IDENTIFIER);
parent = subName;
} else {
add(name, parent, next, flags);
}
}
}
}
private void parseObjectType() throws IOException {
// :: = { wwwService 65 }
String name = token(0);
String line;
int flags = NO_ACCESS;
while ((line = readLine()) != null) {
if (line.indexOf(ASSIGN) != -1) {
break;
}
if (line.startsWith("INDEX")) {
flags |= INDEX;
} else if (line.startsWith("SYNTAX")) {
if (line.indexOf("SEQUENCE") != -1) {
flags |= INDEX;
}
} else if (line.startsWith("ACCESS") || line.startsWith("MAX-ACCESS")) {
if (line.indexOf("not-accessible") == -1) {
flags &= ~NO_ACCESS;
}
}
}
parseId(name, line, flags);
}
private void parseObjectIdentifier(String line) throws IOException {
// wwwMIBObjects OBJECT IDENTIFIER ::= { wwwMIB 1 }
String name = token(0);
if (line.indexOf(ASSIGN) == -1) {
line = readToLine(ASSIGN);
}
parseId(name, line, IDENTIFIER);
}
private boolean hasParsedFile(File file) {
String name = file.getName();
if (this.parsedFiles.get(name) != null) {
return true;
} else {
this.parsedFiles.put(name, Boolean.TRUE);
return false;
}
}
private boolean parseFile(File file) throws IOException {
return parse(file.toString(), new FileInputStream(file));
}
private class AcceptFilter {
List filter = null;
AcceptFilter(String[] accept) {
if ((accept != null) && (accept.length != 0)) {
filter = Arrays.asList(accept);
}
}
boolean accept(String name) {
if (filter != null) {
return filter.contains(name);
} else {
return true;
}
}
}
public boolean parse(JarFile jar) throws IOException {
return parse(jar, null);
}
public boolean parse(JarFile jar, String[] accept) throws IOException {
AcceptFilter filter = new AcceptFilter(accept);
for (Enumeration e = jar.entries(); e.hasMoreElements();) {
JarEntry entry = (JarEntry) e.nextElement();
if (entry.isDirectory()) {
continue;
}
if (!entry.getName().startsWith("mibs/")) {
continue;
}
String name = entry.getName().substring(5);
if (!filter.accept(name)) {
continue;
}
if (hasParsedFile(new File(name))) {
continue;
}
String where = jar.getName() + "!" + entry.getName();
parse(where, jar.getInputStream(entry));
}
return true;
}
public boolean parse(File file) throws IOException {
return parse(file, null);
}
public boolean parse(File file, String[] accept) throws IOException {
if (hasParsedFile(file)) {
return true;
}
if (file.isDirectory()) {
File[] mibs = file.listFiles();
if ((mibs == null) || (mibs.length == 0)) {
log.debug("No MIBs in directory: " + file);
return false;
}
AcceptFilter filter = new AcceptFilter(accept);
log.debug("Loading MIBs in directory: " + file);
for (int i = 0; i < mibs.length; i++) {
File mib = mibs[i];
if (mib.isDirectory()) {
continue;
}
if (!filter.accept(mib.getName())) {
continue;
}
parseFile(mib);
}
return true;
} else if (file.getName().endsWith(".jar")) {
JarFile jar = new JarFile(file);
try {
return parse(jar, accept);
} finally {
jar.close();
}
} else {
return parseFile(file);
}
}
public boolean parse(URL url) throws IOException {
if (hasParsedFile(new File(url.getFile()))) {
return true;
}
return parse(url.toString(), url.openStream());
}
public boolean parse(String name, InputStream is) throws IOException {
boolean isSuccess = false;
try {
isSuccess = parse(is);
} catch (IOException e) {
throw new IOException("Failed to load MIB: '" + name + "': " + e);
}
log.debug("Loading MIB: '" + name + "': " + (isSuccess ? "success" : "skipped"));
return isSuccess;
}
public boolean parse(InputStream is) throws IOException {
this.lastLookupFailure = null;
try {
return parseMIB(is);
} catch (IOException e) {
throw e;
} catch (Exception e) {
throw new IOException(e + where(0));
} finally {
this.tokens.clear();
this.previous.clear();
try {
is.close();
} catch (IOException e) {
}
}
}
public String getLastLookupFailure() {
return this.lastLookupFailure;
}
private boolean parseMIB(InputStream is) throws IOException {
String line;
this.reader = new LineNumberReader(new InputStreamReader(is));
this.currentMIB = "";
tokenize(readLine());
if (!token(1).equals("DEFINITIONS")) {
return false;
}
this.currentMIB = token(0);
int size = this.table.size();
while ((line = readLine()) != null) {
tokenize(line);
String first = token(0);
if (first.equals("IMPORTS") || first.equals("EXPORTS")) {
readToLine(";");
continue;
}
if (first.equals("SYNTAX")) {
continue;
}
String second = token(1);
if (second == null) {
continue;
}
if ((line.indexOf("SEQUENCE {") != -1) || (line.indexOf("CHOICE {") != -1)) {
readToLine("}");
} else if (second.equals("OBJECT") && token(2).equals("IDENTIFIER")) {
parseObjectIdentifier(line);
} else if ((this.previous.size() == 1) && first.equals("OBJECT") && second.equals("IDENTIFIER")) {
// snmpFrameworkAdmin
// OBJECT IDENTIFIER ::= { snmpFrameworkMIB 1 }
Object name = this.previous.get(0);
line = name + " " + line;
this.tokens.add(0, name);
parseObjectIdentifier(line);
} else if (second.equals("OBJECT-TYPE") || second.equals("MODULE-IDENTITY") ||
second.equals("OBJECT-IDENTITY"))
{
parseObjectType();
}
}
if (log.isDebugEnabled()) {
log.debug(this.currentMIB + " added " + (this.table.size() - size) + " entries");
}
return true;
}
public static void main(String[] args) throws Exception {
ArrayList names = new ArrayList();
MIBTree tree = MIBTree.getInstance();
for (int i = 0; i < args.length; i++) {
File file = new File(args[i]);
if (file.exists()) {
if (!tree.parse(file)) {
System.out.println(args[i] + " is not valid MIB");
} else {
System.out.println(args[i] + " parsed");
}
} else {
names.add(args[i]);
}
}
if (names.size() == 0) {
names.addAll(tree.table.keySet());
}
for (int i = 0; i < names.size(); i++) {
String name = (String) names.get(i);
int[] oid = tree.getOID(name);
if (oid == null) {
System.out.println("Failed to get oid for: " + name + " (lastLookupFailure=" + tree.lastLookupFailure +
")");
} else {
System.out.println(name + "=" + toString(oid));
}
}
}
}