/* * '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)); } } } }