// Copyright 2013 Stephen Vickers var ber = require ("asn1-ber").Ber; var smartbuffer = require ("smart-buffer"); var dgram = require ("dgram"); var net = require ("net"); var events = require ("events"); var util = require ("util"); var crypto = require ("crypto"); var mibparser = require ("./lib/mib"); var DEBUG = false; var MIN_SIGNED_INT32 = -2147483648; var MAX_SIGNED_INT32 = 2147483647; var MIN_UNSIGNED_INT32 = 0; var MAX_UNSIGNED_INT32 = 4294967295; function debug (line) { if ( DEBUG ) { console.debug (line); } } /***************************************************************************** ** Constants **/ function _expandConstantObject (object) { var keys = []; for (var key in object) keys.push (key); for (var i = 0; i < keys.length; i++) object[object[keys[i]]] = parseInt (keys[i]); } var ErrorStatus = { 0: "NoError", 1: "TooBig", 2: "NoSuchName", 3: "BadValue", 4: "ReadOnly", 5: "GeneralError", 6: "NoAccess", 7: "WrongType", 8: "WrongLength", 9: "WrongEncoding", 10: "WrongValue", 11: "NoCreation", 12: "InconsistentValue", 13: "ResourceUnavailable", 14: "CommitFailed", 15: "UndoFailed", 16: "AuthorizationError", 17: "NotWritable", 18: "InconsistentName" }; _expandConstantObject (ErrorStatus); var ObjectType = { 1: "Boolean", 2: "Integer", 3: "BitString", 4: "OctetString", 5: "Null", 6: "OID", 64: "IpAddress", 65: "Counter", 66: "Gauge", 67: "TimeTicks", 68: "Opaque", 70: "Counter64", 128: "NoSuchObject", 129: "NoSuchInstance", 130: "EndOfMibView" }; _expandConstantObject (ObjectType); // ASN.1 ObjectType.INTEGER = ObjectType.Integer; ObjectType["OCTET STRING"] = ObjectType.OctetString; ObjectType["OBJECT IDENTIFIER"] = ObjectType.OID; // SNMPv2-SMI ObjectType.Integer32 = ObjectType.Integer; ObjectType.Counter32 = ObjectType.Counter; ObjectType.Gauge32 = ObjectType.Gauge; ObjectType.Unsigned32 = ObjectType.Gauge32; // SNMPv2-TC ObjectType.AutonomousType = ObjectType["OBJECT IDENTIFIER"]; ObjectType.DateAndTime = ObjectType["OCTET STRING"]; ObjectType.DisplayString = ObjectType["OCTET STRING"]; ObjectType.InstancePointer = ObjectType["OBJECT IDENTIFIER"]; ObjectType.MacAddress = ObjectType["OCTET STRING"]; ObjectType.PhysAddress = ObjectType["OCTET STRING"]; ObjectType.RowPointer = ObjectType["OBJECT IDENTIFIER"]; ObjectType.RowStatus = ObjectType.INTEGER; ObjectType.StorageType = ObjectType.INTEGER; ObjectType.TestAndIncr = ObjectType.INTEGER; ObjectType.TimeStamp = ObjectType.TimeTicks; ObjectType.TruthValue = ObjectType.INTEGER; ObjectType.TAddress = ObjectType["OCTET STRING"]; ObjectType.TDomain = ObjectType["OBJECT IDENTIFIER"]; ObjectType.VariablePointer = ObjectType["OBJECT IDENTIFIER"]; var PduType = { 160: "GetRequest", 161: "GetNextRequest", 162: "GetResponse", 163: "SetRequest", 164: "Trap", 165: "GetBulkRequest", 166: "InformRequest", 167: "TrapV2", 168: "Report" }; _expandConstantObject (PduType); var TrapType = { 0: "ColdStart", 1: "WarmStart", 2: "LinkDown", 3: "LinkUp", 4: "AuthenticationFailure", 5: "EgpNeighborLoss", 6: "EnterpriseSpecific" }; _expandConstantObject (TrapType); var SecurityLevel = { 1: "noAuthNoPriv", 2: "authNoPriv", 3: "authPriv" }; _expandConstantObject (SecurityLevel); var AuthProtocols = { "1": "none", "2": "md5", "3": "sha", "4": "sha224", "5": "sha256", "6": "sha384", "7": "sha512" }; _expandConstantObject (AuthProtocols); var PrivProtocols = { "1": "none", "2": "des", "4": "aes", "6": "aes256b", "8": "aes256r" }; _expandConstantObject (PrivProtocols); var UsmStatsBase = "1.3.6.1.6.3.15.1.1"; var UsmStats = { "1": "Unsupported Security Level", "2": "Not In Time Window", "3": "Unknown User Name", "4": "Unknown Engine ID", "5": "Wrong Digest (incorrect password, community or key)", "6": "Decryption Error" }; _expandConstantObject (UsmStats); var MibProviderType = { "1": "Scalar", "2": "Table" }; _expandConstantObject (MibProviderType); var Version1 = 0; var Version2c = 1; var Version3 = 3; var Version = { "1": Version1, "2c": Version2c, "3": Version3 }; var AgentXPduType = { 1: "Open", 2: "Close", 3: "Register", 4: "Unregister", 5: "Get", 6: "GetNext", 7: "GetBulk", 8: "TestSet", 9: "CommitSet", 10: "UndoSet", 11: "CleanupSet", 12: "Notify", 13: "Ping", 14: "IndexAllocate", 15: "IndexDeallocate", 16: "AddAgentCaps", 17: "RemoveAgentCaps", 18: "Response" }; _expandConstantObject (AgentXPduType); var AccessControlModelType = { 0: "None", 1: "Simple" }; _expandConstantObject (AccessControlModelType); var AccessLevel = { 0: "None", 1: "ReadOnly", 2: "ReadWrite" }; _expandConstantObject (AccessLevel); // SMIv2 MAX-ACCESS values var MaxAccess = { 0: "not-accessible", 1: "accessible-for-notify", 2: "read-only", 3: "read-write", 4: "read-create" }; _expandConstantObject (MaxAccess); // SMIv1 ACCESS value mapping to SMIv2 MAX-ACCESS var AccessToMaxAccess = { "not-accessible": "not-accessible", "read-only": "read-only", "read-write": "read-write", "write-only": "read-write" }; var RowStatus = { // status values 1: "active", 2: "notInService", 3: "notReady", // actions 4: "createAndGo", 5: "createAndWait", 6: "destroy" }; _expandConstantObject (RowStatus); var ResponseInvalidCode = { 1: "EIp4AddressSize", 2: "EUnknownObjectType", 3: "EUnknownPduType", 4: "ECouldNotDecrypt", 5: "EAuthFailure", 6: "EReqResOidNoMatch", // 7: "ENonRepeaterCountMismatch", // no longer used 8: "EOutOfOrder", 9: "EVersionNoMatch", 10: "ECommunityNoMatch", 11: "EUnexpectedReport", 12: "EResponseNotHandled", 13: "EUnexpectedResponse" }; _expandConstantObject (ResponseInvalidCode); /***************************************************************************** ** Exception class definitions **/ function ResponseInvalidError (message, code, info) { this.name = "ResponseInvalidError"; this.message = message; this.code = code; this.info = info; Error.captureStackTrace(this, ResponseInvalidError); } util.inherits (ResponseInvalidError, Error); function RequestInvalidError (message) { this.name = "RequestInvalidError"; this.message = message; Error.captureStackTrace(this, RequestInvalidError); } util.inherits (RequestInvalidError, Error); function RequestFailedError (message, status) { this.name = "RequestFailedError"; this.message = message; this.status = status; Error.captureStackTrace(this, RequestFailedError); } util.inherits (RequestFailedError, Error); function RequestTimedOutError (message) { this.name = "RequestTimedOutError"; this.message = message; Error.captureStackTrace(this, RequestTimedOutError); } util.inherits (RequestTimedOutError, Error); /***************************************************************************** ** OID and varbind helper functions **/ function isVarbindError (varbind) { return !!(varbind.type == ObjectType.NoSuchObject || varbind.type == ObjectType.NoSuchInstance || varbind.type == ObjectType.EndOfMibView); } function varbindError (varbind) { return (ObjectType[varbind.type] || "NotAnError") + ": " + varbind.oid; } function oidFollowsOid (oidString, nextString) { var oid = {str: oidString, len: oidString.length, idx: 0}; var next = {str: nextString, len: nextString.length, idx: 0}; var dotCharCode = ".".charCodeAt (0); function getNumber (item) { var n = 0; if (item.idx >= item.len) return null; while (item.idx < item.len) { var charCode = item.str.charCodeAt (item.idx++); if (charCode == dotCharCode) return n; n = (n ? (n * 10) : n) + (charCode - 48); } return n; } while (1) { var oidNumber = getNumber (oid); var nextNumber = getNumber (next); if (oidNumber !== null) { if (nextNumber !== null) { if (nextNumber > oidNumber) { return true; } else if (nextNumber < oidNumber) { return false; } } else { return true; } } else { return true; } } } function oidInSubtree (oidString, nextString) { var oid = oidString.split ("."); var next = nextString.split ("."); if (oid.length > next.length) return false; for (var i = 0; i < oid.length; i++) { if (next[i] != oid[i]) return false; } return true; } function readInt32 (buffer) { var parsedInt = buffer.readInt (); if ( ! Number.isInteger(parsedInt) ) { throw new TypeError('Value read as integer ' + parsedInt + ' is not an integer'); } if ( parsedInt < MIN_SIGNED_INT32 || parsedInt > MAX_SIGNED_INT32 ) { throw new RangeError('Read integer ' + parsedInt + ' is outside the signed 32-bit range'); } return parsedInt; } function readUint32 (buffer) { var parsedInt = buffer.readInt (); if ( ! Number.isInteger(parsedInt) ) { throw new TypeError('Value read as integer ' + parsedInt + ' is not an integer'); } if ( parsedInt < MIN_UNSIGNED_INT32 || parsedInt > MAX_UNSIGNED_INT32 ) { throw new RangeError('Read integer ' + parsedInt + ' is outside the unsigned 32-bit range'); } return parsedInt; } function readUint64 (buffer) { var value = buffer.readString (ObjectType.Counter64, true); return value; } function readIpAddress (buffer) { var bytes = buffer.readString (ObjectType.IpAddress, true); if (bytes.length != 4) throw new ResponseInvalidError ("Length '" + bytes.length + "' of IP address '" + bytes.toString ("hex") + "' is not 4", ResponseInvalidCode.EIp4AddressSize); var value = bytes[0] + "." + bytes[1] + "." + bytes[2] + "." + bytes[3]; return value; } function readVarbindValue (buffer, type) { var value; if (type == ObjectType.Boolean) { value = buffer.readBoolean (); } else if (type == ObjectType.Integer) { value = readInt32 (buffer); } else if (type == ObjectType.BitString) { value = buffer.readBitString(); } else if (type == ObjectType.OctetString) { value = buffer.readString (null, true); } else if (type == ObjectType.Null) { buffer.readByte (); buffer.readByte (); value = null; } else if (type == ObjectType.OID) { value = buffer.readOID (); } else if (type == ObjectType.IpAddress) { value = readIpAddress (buffer); } else if (type == ObjectType.Counter) { value = readUint32 (buffer); } else if (type == ObjectType.Gauge) { value = readUint32 (buffer); } else if (type == ObjectType.TimeTicks) { value = readUint32 (buffer); } else if (type == ObjectType.Opaque) { value = buffer.readString (ObjectType.Opaque, true); } else if (type == ObjectType.Counter64) { value = readUint64 (buffer); } else if (type == ObjectType.NoSuchObject) { buffer.readByte (); buffer.readByte (); value = null; } else if (type == ObjectType.NoSuchInstance) { buffer.readByte (); buffer.readByte (); value = null; } else if (type == ObjectType.EndOfMibView) { buffer.readByte (); buffer.readByte (); value = null; } else { throw new ResponseInvalidError ("Unknown type '" + type + "' in response", ResponseInvalidCode.EUnknownObjectType); } return value; } function readVarbinds (buffer, varbinds) { buffer.readSequence (); while (1) { buffer.readSequence (); if ( buffer.peek () != ObjectType.OID ) break; var oid = buffer.readOID (); var type = buffer.peek (); if (type == null) break; var value = readVarbindValue (buffer, type); varbinds.push ({ oid: oid, type: type, value: value }); } } function writeInt32 (buffer, type, value) { if ( ! Number.isInteger(value) ) { throw new TypeError('Value to write as integer ' + value + ' is not an integer'); } if ( value < MIN_SIGNED_INT32 || value > MAX_SIGNED_INT32 ) { throw new RangeError('Integer to write ' + value + ' is outside the signed 32-bit range'); } buffer.writeInt(value, type); } function writeUint32 (buffer, type, value) { if ( ! Number.isInteger(value) ) { throw new TypeError('Value to write as integer ' + value + ' is not an integer'); } if ( value < MIN_UNSIGNED_INT32 || value > MAX_UNSIGNED_INT32 ) { throw new RangeError('Integer to write ' + value + ' is outside the unsigned 32-bit range'); } buffer.writeInt(value, type); } function writeUint64 (buffer, value) { buffer.writeBuffer (value, ObjectType.Counter64); } function writeVarbinds (buffer, varbinds) { buffer.startSequence (); for (var i = 0; i < varbinds.length; i++) { buffer.startSequence (); buffer.writeOID (varbinds[i].oid); if (varbinds[i].type && varbinds[i].hasOwnProperty("value")) { var type = varbinds[i].type; var value = varbinds[i].value; switch ( type ) { case ObjectType.Boolean: buffer.writeBoolean (value ? true : false); break; case ObjectType.Integer: // also Integer32 writeInt32 (buffer, ObjectType.Integer, value); break; case ObjectType.OctetString: if (typeof value == "string") buffer.writeString (value); else buffer.writeBuffer (value, ObjectType.OctetString); break; case ObjectType.Null: buffer.writeNull (); break; case ObjectType.OID: buffer.writeOID (value); break; case ObjectType.IpAddress: var bytes = value.split ("."); if (bytes.length != 4) throw new RequestInvalidError ("Invalid IP address '" + value + "'"); buffer.writeBuffer (Buffer.from (bytes), 64); break; case ObjectType.Counter: // also Counter32 writeUint32 (buffer, ObjectType.Counter, value); break; case ObjectType.Gauge: // also Gauge32 & Unsigned32 writeUint32 (buffer, ObjectType.Gauge, value); break; case ObjectType.TimeTicks: writeUint32 (buffer, ObjectType.TimeTicks, value); break; case ObjectType.Opaque: buffer.writeBuffer (value, ObjectType.Opaque); break; case ObjectType.Counter64: writeUint64 (buffer, value); break; case ObjectType.NoSuchObject: case ObjectType.NoSuchInstance: case ObjectType.EndOfMibView: buffer.writeByte (type); buffer.writeByte (0); break; default: throw new RequestInvalidError ("Unknown type '" + type + "' in request"); } } else { buffer.writeNull (); } buffer.endSequence (); } buffer.endSequence (); } /***************************************************************************** ** PDU class definitions **/ var SimplePdu = function () { }; SimplePdu.prototype.toBuffer = function (buffer) { buffer.startSequence (this.type); writeInt32 (buffer, ObjectType.Integer, this.id); writeInt32 (buffer, ObjectType.Integer, (this.type == PduType.GetBulkRequest) ? (this.options.nonRepeaters || 0) : 0); writeInt32 (buffer, ObjectType.Integer, (this.type == PduType.GetBulkRequest) ? (this.options.maxRepetitions || 0) : 0); writeVarbinds (buffer, this.varbinds); buffer.endSequence (); }; SimplePdu.prototype.initializeFromVariables = function (id, varbinds, options) { this.id = id; this.varbinds = varbinds; this.options = options || {}; this.contextName = (options && options.context) ? options.context : ""; }; SimplePdu.prototype.initializeFromBuffer = function (reader) { this.type = reader.peek (); reader.readSequence (); this.id = readInt32 (reader); this.nonRepeaters = readInt32 (reader); this.maxRepetitions = readInt32 (reader); this.varbinds = []; readVarbinds (reader, this.varbinds); }; SimplePdu.prototype.getResponsePduForRequest = function () { var responsePdu = GetResponsePdu.createFromVariables(this.id, [], {}); if ( this.contextEngineID ) { responsePdu.contextEngineID = this.contextEngineID; responsePdu.contextName = this.contextName; } return responsePdu; }; SimplePdu.createFromVariables = function (pduClass, id, varbinds, options) { var pdu = new pduClass (id, varbinds, options); pdu.id = id; pdu.varbinds = varbinds; pdu.options = options || {}; pdu.contextName = (options && options.context) ? options.context : ""; return pdu; }; var GetBulkRequestPdu = function () { this.type = PduType.GetBulkRequest; GetBulkRequestPdu.super_.apply (this, arguments); }; util.inherits (GetBulkRequestPdu, SimplePdu); GetBulkRequestPdu.createFromBuffer = function (reader) { var pdu = new GetBulkRequestPdu (); pdu.initializeFromBuffer (reader); return pdu; }; var GetNextRequestPdu = function () { this.type = PduType.GetNextRequest; GetNextRequestPdu.super_.apply (this, arguments); }; util.inherits (GetNextRequestPdu, SimplePdu); GetNextRequestPdu.createFromBuffer = function (reader) { var pdu = new GetNextRequestPdu (); pdu.initializeFromBuffer (reader); return pdu; }; var GetRequestPdu = function () { this.type = PduType.GetRequest; GetRequestPdu.super_.apply (this, arguments); }; util.inherits (GetRequestPdu, SimplePdu); GetRequestPdu.createFromBuffer = function (reader) { var pdu = new GetRequestPdu(); pdu.initializeFromBuffer (reader); return pdu; }; GetRequestPdu.createFromVariables = function (id, varbinds, options) { var pdu = new GetRequestPdu(); pdu.initializeFromVariables (id, varbinds, options); return pdu; }; var InformRequestPdu = function () { this.type = PduType.InformRequest; InformRequestPdu.super_.apply (this, arguments); }; util.inherits (InformRequestPdu, SimplePdu); InformRequestPdu.createFromBuffer = function (reader) { var pdu = new InformRequestPdu(); pdu.initializeFromBuffer (reader); return pdu; }; var SetRequestPdu = function () { this.type = PduType.SetRequest; SetRequestPdu.super_.apply (this, arguments); }; util.inherits (SetRequestPdu, SimplePdu); SetRequestPdu.createFromBuffer = function (reader) { var pdu = new SetRequestPdu (); pdu.initializeFromBuffer (reader); return pdu; }; var TrapPdu = function () { this.type = PduType.Trap; }; TrapPdu.prototype.toBuffer = function (buffer) { buffer.startSequence (this.type); buffer.writeOID (this.enterprise); buffer.writeBuffer (Buffer.from (this.agentAddr.split (".")), ObjectType.IpAddress); writeInt32 (buffer, ObjectType.Integer, this.generic); writeInt32 (buffer, ObjectType.Integer, this.specific); writeUint32 (buffer, ObjectType.TimeTicks, this.upTime || Math.floor (process.uptime () * 100)); writeVarbinds (buffer, this.varbinds); buffer.endSequence (); }; TrapPdu.createFromBuffer = function (reader) { var pdu = new TrapPdu(); reader.readSequence (); pdu.enterprise = reader.readOID (); pdu.agentAddr = readIpAddress (reader); pdu.generic = readInt32 (reader); pdu.specific = readInt32 (reader); pdu.upTime = readUint32 (reader); pdu.varbinds = []; readVarbinds (reader, pdu.varbinds); return pdu; }; TrapPdu.createFromVariables = function (typeOrOid, varbinds, options) { var pdu = new TrapPdu (); pdu.agentAddr = options.agentAddr || "127.0.0.1"; pdu.upTime = options.upTime; if (typeof typeOrOid == "string") { pdu.generic = TrapType.EnterpriseSpecific; pdu.specific = parseInt (typeOrOid.match (/\.(\d+)$/)[1]); pdu.enterprise = typeOrOid.replace (/\.(\d+)$/, ""); } else { pdu.generic = typeOrOid; pdu.specific = 0; pdu.enterprise = "1.3.6.1.4.1"; } pdu.varbinds = varbinds; return pdu; }; var TrapV2Pdu = function () { this.type = PduType.TrapV2; TrapV2Pdu.super_.apply (this, arguments); }; util.inherits (TrapV2Pdu, SimplePdu); TrapV2Pdu.createFromBuffer = function (reader) { var pdu = new TrapV2Pdu(); pdu.initializeFromBuffer (reader); return pdu; }; TrapV2Pdu.createFromVariables = function (id, varbinds, options) { var pdu = new TrapV2Pdu(); pdu.initializeFromVariables (id, varbinds, options); return pdu; }; var SimpleResponsePdu = function() { }; SimpleResponsePdu.prototype.toBuffer = function (writer) { writer.startSequence (this.type); writeInt32 (writer, ObjectType.Integer, this.id); writeInt32 (writer, ObjectType.Integer, this.errorStatus || 0); writeInt32 (writer, ObjectType.Integer, this.errorIndex || 0); writeVarbinds (writer, this.varbinds); writer.endSequence (); }; SimpleResponsePdu.prototype.initializeFromBuffer = function (reader) { reader.readSequence (this.type); this.id = readInt32 (reader); this.errorStatus = readInt32 (reader); this.errorIndex = readInt32 (reader); this.varbinds = []; readVarbinds (reader, this.varbinds); }; SimpleResponsePdu.prototype.initializeFromVariables = function (id, varbinds, options) { this.id = id; this.varbinds = varbinds; this.options = options || {}; }; var GetResponsePdu = function () { this.type = PduType.GetResponse; GetResponsePdu.super_.apply (this, arguments); }; util.inherits (GetResponsePdu, SimpleResponsePdu); GetResponsePdu.createFromBuffer = function (reader) { var pdu = new GetResponsePdu (); pdu.initializeFromBuffer (reader); return pdu; }; GetResponsePdu.createFromVariables = function (id, varbinds, options) { var pdu = new GetResponsePdu(); pdu.initializeFromVariables (id, varbinds, options); return pdu; }; var ReportPdu = function () { this.type = PduType.Report; ReportPdu.super_.apply (this, arguments); }; util.inherits (ReportPdu, SimpleResponsePdu); ReportPdu.createFromBuffer = function (reader) { var pdu = new ReportPdu (); pdu.initializeFromBuffer (reader); return pdu; }; ReportPdu.createFromVariables = function (id, varbinds, options) { var pdu = new ReportPdu(); pdu.initializeFromVariables (id, varbinds, options); return pdu; }; var readPdu = function (reader, scoped) { var pdu; var contextEngineID; var contextName; if ( scoped ) { reader = new ber.Reader (reader.readString (ber.Sequence | ber.Constructor, true)); contextEngineID = reader.readString (ber.OctetString, true); contextName = reader.readString (); } var type = reader.peek (); if (type == PduType.GetResponse) { pdu = GetResponsePdu.createFromBuffer (reader); } else if (type == PduType.Report ) { pdu = ReportPdu.createFromBuffer (reader); } else if (type == PduType.Trap ) { pdu = TrapPdu.createFromBuffer (reader); } else if (type == PduType.TrapV2 ) { pdu = TrapV2Pdu.createFromBuffer (reader); } else if (type == PduType.InformRequest ) { pdu = InformRequestPdu.createFromBuffer (reader); } else if (type == PduType.GetRequest ) { pdu = GetRequestPdu.createFromBuffer (reader); } else if (type == PduType.SetRequest ) { pdu = SetRequestPdu.createFromBuffer (reader); } else if (type == PduType.GetNextRequest ) { pdu = GetNextRequestPdu.createFromBuffer (reader); } else if (type == PduType.GetBulkRequest ) { pdu = GetBulkRequestPdu.createFromBuffer (reader); } else { throw new ResponseInvalidError ("Unknown PDU type '" + type + "' in response", ResponseInvalidCode.EUnknownPduType); } if ( scoped ) { pdu.contextEngineID = contextEngineID; pdu.contextName = contextName; } pdu.scoped = scoped; return pdu; }; var createDiscoveryPdu = function (context) { return GetRequestPdu.createFromVariables(_generateId(), [], {context: context}); }; var Authentication = {}; Authentication.HMAC_BUFFER_SIZE = 1024*1024; Authentication.algorithms = {}; Authentication.algorithms[AuthProtocols.md5] = { KEY_LENGTH: 16, AUTHENTICATION_CODE_LENGTH: 12, CRYPTO_ALGORITHM: 'md5' }; Authentication.algorithms[AuthProtocols.sha] = { KEY_LENGTH: 20, AUTHENTICATION_CODE_LENGTH: 12, CRYPTO_ALGORITHM: 'sha1' }; Authentication.algorithms[AuthProtocols.sha224] = { KEY_LENGTH: 28, AUTHENTICATION_CODE_LENGTH: 16, CRYPTO_ALGORITHM: 'sha224' }; Authentication.algorithms[AuthProtocols.sha256] = { KEY_LENGTH: 32, AUTHENTICATION_CODE_LENGTH: 24, CRYPTO_ALGORITHM: 'sha256' }; Authentication.algorithms[AuthProtocols.sha384] = { KEY_LENGTH: 48, AUTHENTICATION_CODE_LENGTH: 32, CRYPTO_ALGORITHM: 'sha384' }; Authentication.algorithms[AuthProtocols.sha512] = { KEY_LENGTH: 64, AUTHENTICATION_CODE_LENGTH: 48, CRYPTO_ALGORITHM: 'sha512' }; Authentication.authToKeyCache = {}; Authentication.computeCacheKey = function (authProtocol, authPasswordString, engineID) { var engineIDString = engineID.toString('base64'); return authProtocol + authPasswordString + engineIDString; }; // Adapted from RFC3414 Appendix A.2.1. Password to Key Sample Code for MD5 Authentication.passwordToKey = function (authProtocol, authPasswordString, engineID) { var hashAlgorithm; var firstDigest; var finalDigest; var buf; var cryptoAlgorithm = Authentication.algorithms[authProtocol].CRYPTO_ALGORITHM; var cacheKey = Authentication.computeCacheKey(authProtocol, authPasswordString, engineID); if (Authentication.authToKeyCache[cacheKey] !== undefined) { return Authentication.authToKeyCache[cacheKey]; } buf = Buffer.alloc (Authentication.HMAC_BUFFER_SIZE, authPasswordString); hashAlgorithm = crypto.createHash(cryptoAlgorithm); hashAlgorithm.update(buf); firstDigest = hashAlgorithm.digest(); // debug ("First digest: " + firstDigest.toString('hex')); hashAlgorithm = crypto.createHash(cryptoAlgorithm); hashAlgorithm.update(firstDigest); hashAlgorithm.update(engineID); hashAlgorithm.update(firstDigest); finalDigest = hashAlgorithm.digest(); // debug ("Localized key: " + finalDigest.toString('hex')); Authentication.authToKeyCache[cacheKey] = finalDigest; return finalDigest; }; Authentication.getParametersLength = function (authProtocol) { return Authentication.algorithms[authProtocol].AUTHENTICATION_CODE_LENGTH; }; Authentication.writeParameters = function (messageBuffer, authProtocol, authPassword, engineID, digestInMessage) { var digestToAdd; digestToAdd = Authentication.calculateDigest (messageBuffer, authProtocol, authPassword, engineID); digestToAdd.copy (digestInMessage); // debug ("Added Auth Parameters: " + digestToAdd.toString('hex')); }; Authentication.isAuthentic = function (messageBuffer, authProtocol, authPassword, engineID, digestInMessage) { var savedDigest; var calculatedDigest; if (digestInMessage.length !== Authentication.algorithms[authProtocol].AUTHENTICATION_CODE_LENGTH) return false; // save original authenticationParameters field in message savedDigest = Buffer.from (digestInMessage); // clear the authenticationParameters field in message digestInMessage.fill (0); calculatedDigest = Authentication.calculateDigest (messageBuffer, authProtocol, authPassword, engineID); // replace previously cleared authenticationParameters field in message savedDigest.copy (digestInMessage); // debug ("Digest in message: " + digestInMessage.toString('hex')); // debug ("Calculated digest: " + calculatedDigest.toString('hex')); return calculatedDigest.equals (digestInMessage); }; Authentication.calculateDigest = function (messageBuffer, authProtocol, authPassword, engineID) { var authKey = Authentication.passwordToKey (authProtocol, authPassword, engineID); var cryptoAlgorithm = Authentication.algorithms[authProtocol].CRYPTO_ALGORITHM; var hmacAlgorithm = crypto.createHmac (cryptoAlgorithm, authKey); hmacAlgorithm.update (messageBuffer); var digest = hmacAlgorithm.digest (); return digest.subarray (0, Authentication.algorithms[authProtocol].AUTHENTICATION_CODE_LENGTH); }; var Encryption = {}; Encryption.encryptPdu = function (privProtocol, scopedPdu, privPassword, authProtocol, engine) { var encryptFunction = Encryption.algorithms[privProtocol].encryptPdu; return encryptFunction (scopedPdu, privProtocol, privPassword, authProtocol, engine); }; Encryption.decryptPdu = function (privProtocol, encryptedPdu, privParameters, privPassword, authProtocol, engine) { var decryptFunction = Encryption.algorithms[privProtocol].decryptPdu; return decryptFunction (encryptedPdu, privProtocol, privParameters, privPassword, authProtocol, engine); }; Encryption.debugEncrypt = function (encryptionKey, iv, plainPdu, encryptedPdu) { debug ("Key: " + encryptionKey.toString ('hex')); debug ("IV: " + iv.toString ('hex')); debug ("Plain: " + plainPdu.toString ('hex')); debug ("Encrypted: " + encryptedPdu.toString ('hex')); }; Encryption.debugDecrypt = function (decryptionKey, iv, encryptedPdu, plainPdu) { debug ("Key: " + decryptionKey.toString ('hex')); debug ("IV: " + iv.toString ('hex')); debug ("Encrypted: " + encryptedPdu.toString ('hex')); debug ("Plain: " + plainPdu.toString ('hex')); }; Encryption.generateLocalizedKey = function (algorithm, authProtocol, privPassword, engineID) { var privLocalizedKey; var encryptionKey; privLocalizedKey = Authentication.passwordToKey (authProtocol, privPassword, engineID); encryptionKey = Buffer.alloc (algorithm.KEY_LENGTH); privLocalizedKey.copy (encryptionKey, 0, 0, algorithm.KEY_LENGTH); return encryptionKey; }; Encryption.generateLocalizedKeyBlumenthal = function (algorithm, authProtocol, privPassword, engineID) { let authKeyLength; let privLocalizedKey; let encryptionKey; let rounds; let hashInput; let nextHash; let hashAlgorithm; authKeyLength = Authentication.algorithms[authProtocol].KEY_LENGTH; rounds = Math.ceil (algorithm.KEY_LENGTH / authKeyLength ); encryptionKey = Buffer.alloc (algorithm.KEY_LENGTH); privLocalizedKey = Authentication.passwordToKey (authProtocol, privPassword, engineID); nextHash = privLocalizedKey; for ( let round = 0 ; round < rounds ; round++ ) { nextHash.copy (encryptionKey, round * authKeyLength, 0, authKeyLength); if ( round < rounds - 1 ) { hashAlgorithm = crypto.createHash (Authentication.algorithms[authProtocol].CRYPTO_ALGORITHM); hashInput = Buffer.alloc ( (round + 1) * authKeyLength); encryptionKey.copy (hashInput, round * authKeyLength, 0, (round + 1) * authKeyLength); hashAlgorithm.update (hashInput); nextHash = hashAlgorithm.digest (); } } return encryptionKey; }; Encryption.generateLocalizedKeyReeder = function (algorithm, authProtocol, privPassword, engineID) { let authKeyLength; let privLocalizedKey; let encryptionKey; let rounds; let nextPasswordInput; authKeyLength = Authentication.algorithms[authProtocol].KEY_LENGTH; rounds = Math.ceil (algorithm.KEY_LENGTH / authKeyLength ); encryptionKey = Buffer.alloc (algorithm.KEY_LENGTH); nextPasswordInput = privPassword; for ( let round = 0 ; round < rounds ; round++ ) { privLocalizedKey = Authentication.passwordToKey (authProtocol, nextPasswordInput, engineID); privLocalizedKey.copy (encryptionKey, round * authKeyLength, 0, authKeyLength); nextPasswordInput = privLocalizedKey; } return encryptionKey; }; Encryption.encryptPduDes = function (scopedPdu, privProtocol, privPassword, authProtocol, engine) { var des = Encryption.algorithms[PrivProtocols.des]; var privLocalizedKey; var encryptionKey; var preIv; var salt; var iv; var i; var paddedScopedPduLength; var paddedScopedPdu; var encryptedPdu; var cipher; encryptionKey = Encryption.generateLocalizedKey (des, authProtocol, privPassword, engine.engineID); privLocalizedKey = Authentication.passwordToKey (authProtocol, privPassword, engine.engineID); encryptionKey = Buffer.alloc (des.KEY_LENGTH); privLocalizedKey.copy (encryptionKey, 0, 0, des.KEY_LENGTH); preIv = Buffer.alloc (des.BLOCK_LENGTH); privLocalizedKey.copy (preIv, 0, des.KEY_LENGTH, des.KEY_LENGTH + des.BLOCK_LENGTH); salt = Buffer.alloc (des.BLOCK_LENGTH); // set local SNMP engine boots part of salt to 1, as we have no persistent engine state salt.fill ('00000001', 0, 4, 'hex'); // set local integer part of salt to random salt.fill (crypto.randomBytes (4), 4, 8); iv = Buffer.alloc (des.BLOCK_LENGTH); for (i = 0; i < iv.length; i++) { iv[i] = preIv[i] ^ salt[i]; } if (scopedPdu.length % des.BLOCK_LENGTH == 0) { paddedScopedPdu = scopedPdu; } else { paddedScopedPduLength = des.BLOCK_LENGTH * (Math.floor (scopedPdu.length / des.BLOCK_LENGTH) + 1); paddedScopedPdu = Buffer.alloc (paddedScopedPduLength); scopedPdu.copy (paddedScopedPdu, 0, 0, scopedPdu.length); } cipher = crypto.createCipheriv (des.CRYPTO_ALGORITHM, encryptionKey, iv); encryptedPdu = cipher.update (paddedScopedPdu); encryptedPdu = Buffer.concat ([encryptedPdu, cipher.final()]); // Encryption.debugEncrypt (encryptionKey, iv, paddedScopedPdu, encryptedPdu); return { encryptedPdu: encryptedPdu, msgPrivacyParameters: salt }; }; Encryption.decryptPduDes = function (encryptedPdu, privProtocol, privParameters, privPassword, authProtocol, engine) { var des = Encryption.algorithms[PrivProtocols.des]; var privLocalizedKey; var decryptionKey; var preIv; var salt; var iv; var i; var decryptedPdu; var decipher; privLocalizedKey = Authentication.passwordToKey (authProtocol, privPassword, engine.engineID); decryptionKey = Buffer.alloc (des.KEY_LENGTH); privLocalizedKey.copy (decryptionKey, 0, 0, des.KEY_LENGTH); preIv = Buffer.alloc (des.BLOCK_LENGTH); privLocalizedKey.copy (preIv, 0, des.KEY_LENGTH, des.KEY_LENGTH + des.BLOCK_LENGTH); salt = privParameters; iv = Buffer.alloc (des.BLOCK_LENGTH); for (i = 0; i < iv.length; i++) { iv[i] = preIv[i] ^ salt[i]; } decipher = crypto.createDecipheriv (des.CRYPTO_ALGORITHM, decryptionKey, iv); decipher.setAutoPadding(false); decryptedPdu = decipher.update (encryptedPdu); decryptedPdu = Buffer.concat ([decryptedPdu, decipher.final()]); // Encryption.debugDecrypt (decryptionKey, iv, encryptedPdu, decryptedPdu); return decryptedPdu; }; Encryption.generateIvAes = function (aes, engineBoots, engineTime, salt) { var iv; var engineBootsBuffer; var engineTimeBuffer; // iv = engineBoots(4) | engineTime(4) | salt(8) iv = Buffer.alloc (aes.BLOCK_LENGTH); engineBootsBuffer = Buffer.alloc (4); engineBootsBuffer.writeUInt32BE (engineBoots); engineTimeBuffer = Buffer.alloc (4); engineTimeBuffer.writeUInt32BE (engineTime); engineBootsBuffer.copy (iv, 0, 0, 4); engineTimeBuffer.copy (iv, 4, 0, 4); salt.copy (iv, 8, 0, 8); return iv; }; Encryption.encryptPduAes = function (scopedPdu, privProtocol, privPassword, authProtocol, engine) { var aes = Encryption.algorithms[privProtocol]; var localizationAlgorithm = aes.localizationAlgorithm; var encryptionKey; var salt; var iv; var cipher; var encryptedPdu; encryptionKey = localizationAlgorithm (aes, authProtocol, privPassword, engine.engineID); salt = Buffer.alloc (8).fill (crypto.randomBytes (8), 0, 8); iv = Encryption.generateIvAes (aes, engine.engineBoots, engine.engineTime, salt); cipher = crypto.createCipheriv (aes.CRYPTO_ALGORITHM, encryptionKey, iv); encryptedPdu = cipher.update (scopedPdu); encryptedPdu = Buffer.concat ([encryptedPdu, cipher.final()]); // Encryption.debugEncrypt (encryptionKey, iv, scopedPdu, encryptedPdu); return { encryptedPdu: encryptedPdu, msgPrivacyParameters: salt }; }; Encryption.decryptPduAes = function (encryptedPdu, privProtocol, privParameters, privPassword, authProtocol, engine) { var aes = Encryption.algorithms[privProtocol]; var localizationAlgorithm = aes.localizationAlgorithm; var decryptionKey; var iv; var decipher; var decryptedPdu; decryptionKey = localizationAlgorithm (aes, authProtocol, privPassword, engine.engineID); iv = Encryption.generateIvAes (aes, engine.engineBoots, engine.engineTime, privParameters); decipher = crypto.createDecipheriv (aes.CRYPTO_ALGORITHM, decryptionKey, iv); decryptedPdu = decipher.update (encryptedPdu); decryptedPdu = Buffer.concat ([decryptedPdu, decipher.final()]); // Encryption.debugDecrypt (decryptionKey, iv, encryptedPdu, decryptedPdu); return decryptedPdu; }; Encryption.algorithms = {}; Encryption.algorithms[PrivProtocols.des] = { CRYPTO_ALGORITHM: 'des-cbc', KEY_LENGTH: 8, BLOCK_LENGTH: 8, encryptPdu: Encryption.encryptPduDes, decryptPdu: Encryption.decryptPduDes, localizationAlgorithm: Encryption.generateLocalizedKey }; Encryption.algorithms[PrivProtocols.aes] = { CRYPTO_ALGORITHM: 'aes-128-cfb', KEY_LENGTH: 16, BLOCK_LENGTH: 16, encryptPdu: Encryption.encryptPduAes, decryptPdu: Encryption.decryptPduAes, localizationAlgorithm: Encryption.generateLocalizedKey }; Encryption.algorithms[PrivProtocols.aes256b] = { CRYPTO_ALGORITHM: 'aes-256-cfb', KEY_LENGTH: 32, BLOCK_LENGTH: 16, encryptPdu: Encryption.encryptPduAes, decryptPdu: Encryption.decryptPduAes, localizationAlgorithm: Encryption.generateLocalizedKeyBlumenthal }; Encryption.algorithms[PrivProtocols.aes256r] = { CRYPTO_ALGORITHM: 'aes-256-cfb', KEY_LENGTH: 32, BLOCK_LENGTH: 16, encryptPdu: Encryption.encryptPduAes, decryptPdu: Encryption.decryptPduAes, localizationAlgorithm: Encryption.generateLocalizedKeyReeder }; /***************************************************************************** ** Message class definition **/ var Message = function () { }; Message.prototype.getReqId = function () { return this.version == Version3 ? this.msgGlobalData.msgID : this.pdu.id; }; Message.prototype.toBuffer = function () { if ( this.version == Version3 ) { return this.toBufferV3(); } else { return this.toBufferCommunity(); } }; Message.prototype.toBufferCommunity = function () { if (this.buffer) return this.buffer; var writer = new ber.Writer (); writer.startSequence (); writeInt32 (writer, ObjectType.Integer, this.version); writer.writeString (this.community); this.pdu.toBuffer (writer); writer.endSequence (); this.buffer = writer.buffer; return this.buffer; }; Message.prototype.toBufferV3 = function () { var encryptionResult; if (this.buffer) return this.buffer; // ScopedPDU var scopedPduWriter = new ber.Writer (); scopedPduWriter.startSequence (); var contextEngineID = this.pdu.contextEngineID ? this.pdu.contextEngineID : this.msgSecurityParameters.msgAuthoritativeEngineID; if ( contextEngineID.length == 0 ) { scopedPduWriter.writeString (""); } else { scopedPduWriter.writeBuffer (contextEngineID, ber.OctetString); } scopedPduWriter.writeString (this.pdu.contextName); this.pdu.toBuffer (scopedPduWriter); scopedPduWriter.endSequence (); if ( this.hasPrivacy() ) { var authoritativeEngine = { engineID: this.msgSecurityParameters.msgAuthoritativeEngineID, engineBoots: this.msgSecurityParameters.msgAuthoritativeEngineBoots, engineTime: this.msgSecurityParameters.msgAuthoritativeEngineTime, }; encryptionResult = Encryption.encryptPdu (this.user.privProtocol, scopedPduWriter.buffer, this.user.privKey, this.user.authProtocol, authoritativeEngine); } var writer = new ber.Writer (); writer.startSequence (); writeInt32 (writer, ObjectType.Integer, this.version); // HeaderData writer.startSequence (); writeInt32 (writer, ObjectType.Integer, this.msgGlobalData.msgID); writeInt32 (writer, ObjectType.Integer, this.msgGlobalData.msgMaxSize); writer.writeByte (ber.OctetString); writer.writeByte (1); writer.writeByte (this.msgGlobalData.msgFlags); writeInt32 (writer, ObjectType.Integer, this.msgGlobalData.msgSecurityModel); writer.endSequence (); // msgSecurityParameters writer.startSequence (ber.OctetString); writer.startSequence (); //writer.writeString (this.msgSecurityParameters.msgAuthoritativeEngineID); // writing a zero-length buffer fails - should fix asn1-ber for this condition if ( this.msgSecurityParameters.msgAuthoritativeEngineID.length == 0 ) { writer.writeString (""); } else { writer.writeBuffer (this.msgSecurityParameters.msgAuthoritativeEngineID, ber.OctetString); } writeInt32 (writer, ObjectType.Integer, this.msgSecurityParameters.msgAuthoritativeEngineBoots); writeInt32 (writer, ObjectType.Integer, this.msgSecurityParameters.msgAuthoritativeEngineTime); writer.writeString (this.msgSecurityParameters.msgUserName); var msgAuthenticationParameters = ''; if ( this.hasAuthentication() ) { var authParametersLength = Authentication.getParametersLength (this.user.authProtocol); msgAuthenticationParameters = Buffer.alloc (authParametersLength); writer.writeBuffer (msgAuthenticationParameters, ber.OctetString); } else { writer.writeString (""); } var msgAuthenticationParametersOffset = writer._offset - msgAuthenticationParameters.length; if ( this.hasPrivacy() ) { writer.writeBuffer (encryptionResult.msgPrivacyParameters, ber.OctetString); } else { writer.writeString (""); } msgAuthenticationParametersOffset -= writer._offset; writer.endSequence (); writer.endSequence (); msgAuthenticationParametersOffset += writer._offset; if ( this.hasPrivacy() ) { writer.writeBuffer (encryptionResult.encryptedPdu, ber.OctetString); } else { writer.writeBuffer (scopedPduWriter.buffer); } msgAuthenticationParametersOffset -= writer._offset; writer.endSequence (); msgAuthenticationParametersOffset += writer._offset; this.buffer = writer.buffer; if ( this.hasAuthentication() ) { msgAuthenticationParameters = this.buffer.subarray (msgAuthenticationParametersOffset, msgAuthenticationParametersOffset + msgAuthenticationParameters.length); Authentication.writeParameters (this.buffer, this.user.authProtocol, this.user.authKey, this.msgSecurityParameters.msgAuthoritativeEngineID, msgAuthenticationParameters); } return this.buffer; }; Message.prototype.processIncomingSecurity = function (user, responseCb) { if ( this.hasPrivacy() ) { if ( ! this.decryptPdu(user, responseCb) ) { return false; } } if ( this.hasAuthentication() && ! this.isAuthenticationDisabled() ) { return this.checkAuthentication(user, responseCb); } else { return true; } }; Message.prototype.decryptPdu = function (user, responseCb) { var decryptedPdu; var decryptedPduReader; try { var authoratitiveEngine = { engineID: this.msgSecurityParameters.msgAuthoritativeEngineID, engineBoots: this.msgSecurityParameters.msgAuthoritativeEngineBoots, engineTime: this.msgSecurityParameters.msgAuthoritativeEngineTime }; decryptedPdu = Encryption.decryptPdu(user.privProtocol, this.encryptedPdu, this.msgSecurityParameters.msgPrivacyParameters, user.privKey, user.authProtocol, authoratitiveEngine); decryptedPduReader = new ber.Reader (decryptedPdu); this.pdu = readPdu(decryptedPduReader, true); return true; } catch (error) { responseCb (new ResponseInvalidError ("Failed to decrypt PDU: " + error, ResponseInvalidCode.ECouldNotDecrypt)); return false; } }; Message.prototype.checkAuthentication = function (user, responseCb) { if ( Authentication.isAuthentic(this.buffer, user.authProtocol, user.authKey, this.msgSecurityParameters.msgAuthoritativeEngineID, this.msgSecurityParameters.msgAuthenticationParameters) ) { return true; } else { responseCb (new ResponseInvalidError ("Authentication digest " + this.msgSecurityParameters.msgAuthenticationParameters.toString ('hex') + " received in message does not match digest " + Authentication.calculateDigest (this.buffer, user.authProtocol, user.authKey, this.msgSecurityParameters.msgAuthoritativeEngineID).toString ('hex') + " calculated for message", ResponseInvalidCode.EAuthFailure, { user })); return false; } }; Message.prototype.setMsgFlags = function (bitPosition, flag) { if ( this.msgGlobalData && this.msgGlobalData !== undefined && this.msgGlobalData !== null ) { if ( flag ) { this.msgGlobalData.msgFlags = this.msgGlobalData.msgFlags | ( 2 ** bitPosition ); } else { this.msgGlobalData.msgFlags = this.msgGlobalData.msgFlags & ( 255 - 2 ** bitPosition ); } } }; Message.prototype.hasAuthentication = function () { return this.msgGlobalData && this.msgGlobalData.msgFlags && this.msgGlobalData.msgFlags & 1; }; Message.prototype.setAuthentication = function (flag) { this.setMsgFlags (0, flag); }; Message.prototype.hasPrivacy = function () { return this.msgGlobalData && this.msgGlobalData.msgFlags && this.msgGlobalData.msgFlags & 2; }; Message.prototype.setPrivacy = function (flag) { this.setMsgFlags (1, flag); }; Message.prototype.isReportable = function () { return this.msgGlobalData && this.msgGlobalData.msgFlags && this.msgGlobalData.msgFlags & 4; }; Message.prototype.setReportable = function (flag) { this.setMsgFlags (2, flag); }; Message.prototype.isAuthenticationDisabled = function () { return this.disableAuthentication; }; Message.prototype.hasAuthoritativeEngineID = function () { return this.msgSecurityParameters && this.msgSecurityParameters.msgAuthoritativeEngineID && this.msgSecurityParameters.msgAuthoritativeEngineID != ""; }; Message.prototype.createReportResponseMessage = function (engine, context) { var user = { name: "", level: SecurityLevel.noAuthNoPriv }; var responseSecurityParameters = { msgAuthoritativeEngineID: engine.engineID, msgAuthoritativeEngineBoots: engine.engineBoots, msgAuthoritativeEngineTime: engine.engineTime, msgUserName: user.name, msgAuthenticationParameters: "", msgPrivacyParameters: "" }; var reportPdu = ReportPdu.createFromVariables (this.pdu.id, [], {}); reportPdu.contextName = context; var responseMessage = Message.createRequestV3 (user, responseSecurityParameters, reportPdu); responseMessage.msgGlobalData.msgID = this.msgGlobalData.msgID; return responseMessage; }; Message.prototype.createResponseForRequest = function (responsePdu) { if ( this.version == Version3 ) { return this.createV3ResponseFromRequest(responsePdu); } else { return this.createCommunityResponseFromRequest(responsePdu); } }; Message.prototype.createCommunityResponseFromRequest = function (responsePdu) { return Message.createCommunity(this.version, this.community, responsePdu); }; Message.prototype.createV3ResponseFromRequest = function (responsePdu) { var responseUser = { name: this.user.name, level: this.user.level, authProtocol: this.user.authProtocol, authKey: this.user.authKey, privProtocol: this.user.privProtocol, privKey: this.user.privKey }; var responseSecurityParameters = { msgAuthoritativeEngineID: this.msgSecurityParameters.msgAuthoritativeEngineID, msgAuthoritativeEngineBoots: this.msgSecurityParameters.msgAuthoritativeEngineBoots, msgAuthoritativeEngineTime: this.msgSecurityParameters.msgAuthoritativeEngineTime, msgUserName: this.msgSecurityParameters.msgUserName, msgAuthenticationParameters: "", msgPrivacyParameters: "" }; var responseGlobalData = { msgID: this.msgGlobalData.msgID, msgMaxSize: 65507, msgFlags: this.msgGlobalData.msgFlags & (255 - 4), msgSecurityModel: 3 }; return Message.createV3 (responseUser, responseGlobalData, responseSecurityParameters, responsePdu); }; Message.createCommunity = function (version, community, pdu) { var message = new Message (); message.version = version; message.community = community; message.pdu = pdu; return message; }; Message.createRequestV3 = function (user, msgSecurityParameters, pdu) { var authFlag = user.level == SecurityLevel.authNoPriv || user.level == SecurityLevel.authPriv ? 1 : 0; var privFlag = user.level == SecurityLevel.authPriv ? 1 : 0; var reportableFlag = ( pdu.type == PduType.GetResponse || pdu.type == PduType.TrapV2 ) ? 0 : 1; var msgGlobalData = { msgID: _generateId(), // random ID msgMaxSize: 65507, msgFlags: reportableFlag * 4 | privFlag * 2 | authFlag * 1, msgSecurityModel: 3 }; return Message.createV3 (user, msgGlobalData, msgSecurityParameters, pdu); }; Message.createV3 = function (user, msgGlobalData, msgSecurityParameters, pdu) { var message = new Message (); message.version = 3; message.user = user; message.msgGlobalData = msgGlobalData; message.msgSecurityParameters = { msgAuthoritativeEngineID: msgSecurityParameters.msgAuthoritativeEngineID || Buffer.from(""), msgAuthoritativeEngineBoots: msgSecurityParameters.msgAuthoritativeEngineBoots || 0, msgAuthoritativeEngineTime: msgSecurityParameters.msgAuthoritativeEngineTime || 0, msgUserName: user.name || "", msgAuthenticationParameters: "", msgPrivacyParameters: "" }; message.pdu = pdu; return message; }; Message.createDiscoveryV3 = function (pdu) { var msgSecurityParameters = { msgAuthoritativeEngineID: Buffer.from(""), msgAuthoritativeEngineBoots: 0, msgAuthoritativeEngineTime: 0 }; var emptyUser = { name: "", level: SecurityLevel.noAuthNoPriv }; return Message.createRequestV3 (emptyUser, msgSecurityParameters, pdu); }; Message.createFromBuffer = function (buffer, user) { var reader = new ber.Reader (buffer); var message = new Message(); reader.readSequence (); message.version = readInt32 (reader); if (message.version != 3) { message.community = reader.readString (); message.pdu = readPdu(reader, false); } else { // HeaderData message.msgGlobalData = {}; reader.readSequence (); message.msgGlobalData.msgID = readInt32 (reader); message.msgGlobalData.msgMaxSize = readInt32 (reader); message.msgGlobalData.msgFlags = reader.readString (ber.OctetString, true)[0]; message.msgGlobalData.msgSecurityModel = readInt32 (reader); // msgSecurityParameters message.msgSecurityParameters = {}; var msgSecurityParametersReader = new ber.Reader (reader.readString (ber.OctetString, true)); msgSecurityParametersReader.readSequence (); message.msgSecurityParameters.msgAuthoritativeEngineID = msgSecurityParametersReader.readString (ber.OctetString, true); message.msgSecurityParameters.msgAuthoritativeEngineBoots = readInt32 (msgSecurityParametersReader); message.msgSecurityParameters.msgAuthoritativeEngineTime = readInt32 (msgSecurityParametersReader); message.msgSecurityParameters.msgUserName = msgSecurityParametersReader.readString (); message.msgSecurityParameters.msgAuthenticationParameters = msgSecurityParametersReader.readString (ber.OctetString, true); message.msgSecurityParameters.msgPrivacyParameters = Buffer.from(msgSecurityParametersReader.readString (ber.OctetString, true)); if ( message.hasPrivacy() ) { message.encryptedPdu = reader.readString (ber.OctetString, true); message.pdu = null; } else { message.pdu = readPdu(reader, true); } } message.buffer = buffer; return message; }; var Req = function (session, message, feedCb, responseCb, options) { this.message = message; this.responseCb = responseCb; this.retries = session.retries; this.timeout = session.timeout; // Add timeout backoff this.backoff = session.backoff; this.onResponse = session.onSimpleGetResponse; this.feedCb = feedCb; this.port = (options && options.port) ? options.port : session.port; this.context = session.context; }; Req.prototype.getId = function() { return this.message.getReqId (); }; /***************************************************************************** ** Session class definition **/ var Session = function (target, authenticator, options) { this.target = target || "127.0.0.1"; options = options || {}; this.version = options.version ? options.version : Version1; if ( this.version == Version3 ) { this.user = authenticator; } else { this.community = authenticator || "public"; } this.transport = options.transport ? options.transport : "udp4"; this.port = options.port ? options.port : 161; this.trapPort = options.trapPort ? options.trapPort : 162; this.retries = (options.retries || options.retries == 0) ? options.retries : 1; this.timeout = options.timeout ? options.timeout : 5000; this.backoff = options.backoff >= 1.0 ? options.backoff : 1.0; this.sourceAddress = options.sourceAddress ? options.sourceAddress : undefined; this.sourcePort = options.sourcePort ? parseInt(options.sourcePort) : undefined; this.idBitsSize = options.idBitsSize ? parseInt(options.idBitsSize) : 32; this.context = options.context ? options.context : ""; this.backwardsGetNexts = (typeof options.backwardsGetNexts !== 'undefined') ? options.backwardsGetNexts : true; this.reportOidMismatchErrors = (typeof options.reportOidMismatchErrors !== 'undefined') ? options.reportOidMismatchErrors : false; DEBUG = options.debug; this.engine = new Engine (options.engineID); this.reqs = {}; this.reqCount = 0; this.dgram = dgram.createSocket (this.transport); this.dgram.unref(); var me = this; this.dgram.on ("message", me.onMsg.bind (me)); this.dgram.on ("close", me.onClose.bind (me)); this.dgram.on ("error", me.onError.bind (me)); if (this.sourceAddress || this.sourcePort) this.dgram.bind (this.sourcePort, this.sourceAddress); }; util.inherits (Session, events.EventEmitter); Session.prototype.close = function () { this.dgram.close (); return this; }; Session.prototype.cancelRequests = function (error) { var id; for (id in this.reqs) { var req = this.reqs[id]; this.unregisterRequest (req.getId ()); req.responseCb (error); } }; function _generateId (bitSize) { if (bitSize === 16) { return Math.floor(Math.random() * 10000) % 65535; } return Math.floor(Math.random() * 100000000) % 4294967295; } Session.prototype.get = function (oids, responseCb) { var reportOidMismatchErrors = this.reportOidMismatchErrors; function feedCb (req, message) { var pdu = message.pdu; var varbinds = []; if (req.message.pdu.varbinds.length != pdu.varbinds.length) { req.responseCb (new ResponseInvalidError ("Requested OIDs do not " + "match response OIDs", ResponseInvalidCode.EReqResOidNoMatch)); } else { for (var i = 0; i < req.message.pdu.varbinds.length; i++) { if ( reportOidMismatchErrors && req.message.pdu.varbinds[i].oid != pdu.varbinds[i].oid ) { req.responseCb (new ResponseInvalidError ("OID '" + req.message.pdu.varbinds[i].oid + "' in request at position '" + i + "' does not " + "match OID '" + pdu.varbinds[i].oid + "' in response " + "at position '" + i + "'", ResponseInvalidCode.EReqResOidNoMatch)); return; } else { varbinds.push (pdu.varbinds[i]); } } req.responseCb (null, varbinds); } } var pduVarbinds = []; for (var i = 0; i < oids.length; i++) { var varbind = { oid: oids[i] }; pduVarbinds.push (varbind); } this.simpleGet (GetRequestPdu, feedCb, pduVarbinds, responseCb); return this; }; Session.prototype.getBulk = function () { var oids, nonRepeaters, maxRepetitions, responseCb; var reportOidMismatchErrors = this.reportOidMismatchErrors; var backwardsGetNexts = this.backwardsGetNexts; if (arguments.length >= 4) { oids = arguments[0]; nonRepeaters = arguments[1]; maxRepetitions = arguments[2]; responseCb = arguments[3]; } else if (arguments.length >= 3) { oids = arguments[0]; nonRepeaters = arguments[1]; maxRepetitions = 10; responseCb = arguments[2]; } else { oids = arguments[0]; nonRepeaters = 0; maxRepetitions = 10; responseCb = arguments[1]; } function feedCb (req, message) { var pdu = message.pdu; var reqVarbinds = req.message.pdu.varbinds; var varbinds = []; var i = 0; for ( ; i < reqVarbinds.length && i < pdu.varbinds.length; i++) { if (isVarbindError (pdu.varbinds[i])) { if ( reportOidMismatchErrors && reqVarbinds[i].oid != pdu.varbinds[i].oid ) { req.responseCb (new ResponseInvalidError ("OID '" + reqVarbinds[i].oid + "' in request at position '" + i + "' does not " + "match OID '" + pdu.varbinds[i].oid + "' in response " + "at position '" + i + "'", ResponseInvalidCode.EReqResOidNoMatch)); return; } } else { if ( ! backwardsGetNexts && ! oidFollowsOid (reqVarbinds[i].oid, pdu.varbinds[i].oid)) { req.responseCb (new ResponseInvalidError ("OID '" + reqVarbinds[i].oid + "' in request at positiion '" + i + "' does not " + "precede OID '" + pdu.varbinds[i].oid + "' in response " + "at position '" + i + "'", ResponseInvalidCode.EOutOfOrder)); return; } } if (i < nonRepeaters) varbinds.push (pdu.varbinds[i]); else varbinds.push ([pdu.varbinds[i]]); } var repeaters = reqVarbinds.length - nonRepeaters; for ( ; i < pdu.varbinds.length; i++) { var reqIndex = (i - nonRepeaters) % repeaters + nonRepeaters; var prevIndex = i - repeaters; var prevOid = pdu.varbinds[prevIndex].oid; if (isVarbindError (pdu.varbinds[i])) { if ( reportOidMismatchErrors && prevOid != pdu.varbinds[i].oid ) { req.responseCb (new ResponseInvalidError ("OID '" + prevOid + "' in response at position '" + prevIndex + "' does not " + "match OID '" + pdu.varbinds[i].oid + "' in response " + "at position '" + i + "'", ResponseInvalidCode.EReqResOidNoMatch)); return; } } else { if ( ! backwardsGetNexts && ! oidFollowsOid (prevOid, pdu.varbinds[i].oid)) { req.responseCb (new ResponseInvalidError ("OID '" + prevOid + "' in response at positiion '" + prevIndex + "' does not " + "precede OID '" + pdu.varbinds[i].oid + "' in response " + "at position '" + i + "'", ResponseInvalidCode.EOutOfOrder)); return; } } varbinds[reqIndex].push (pdu.varbinds[i]); } req.responseCb (null, varbinds); } var pduVarbinds = []; for (var i = 0; i < oids.length; i++) { var varbind = { oid: oids[i] }; pduVarbinds.push (varbind); } var options = { nonRepeaters: nonRepeaters, maxRepetitions: maxRepetitions }; this.simpleGet (GetBulkRequestPdu, feedCb, pduVarbinds, responseCb, options); return this; }; Session.prototype.getNext = function (oids, responseCb) { var backwardsGetNexts = this.backwardsGetNexts; function feedCb (req, message) { var pdu = message.pdu; var varbinds = []; if (req.message.pdu.varbinds.length != pdu.varbinds.length) { req.responseCb (new ResponseInvalidError ("Requested OIDs do not " + "match response OIDs", ResponseInvalidCode.EReqResOidNoMatch)); } else { for (var i = 0; i < req.message.pdu.varbinds.length; i++) { if (isVarbindError (pdu.varbinds[i])) { varbinds.push (pdu.varbinds[i]); } else if ( ! backwardsGetNexts && ! oidFollowsOid (req.message.pdu.varbinds[i].oid, pdu.varbinds[i].oid)) { req.responseCb (new ResponseInvalidError ("OID '" + req.message.pdu.varbinds[i].oid + "' in request at " + "positiion '" + i + "' does not precede " + "OID '" + pdu.varbinds[i].oid + "' in response " + "at position '" + i + "'", ResponseInvalidCode.OutOfOrder)); return; } else { varbinds.push (pdu.varbinds[i]); } } req.responseCb (null, varbinds); } } var pduVarbinds = []; for (var i = 0; i < oids.length; i++) { var varbind = { oid: oids[i] }; pduVarbinds.push (varbind); } this.simpleGet (GetNextRequestPdu, feedCb, pduVarbinds, responseCb); return this; }; Session.prototype.inform = function () { var typeOrOid = arguments[0]; var varbinds, options = {}, responseCb; /** ** Support the following signatures: ** ** typeOrOid, varbinds, options, callback ** typeOrOid, varbinds, callback ** typeOrOid, options, callback ** typeOrOid, callback **/ if (arguments.length >= 4) { varbinds = arguments[1]; options = arguments[2]; responseCb = arguments[3]; } else if (arguments.length >= 3) { if (arguments[1].constructor != Array) { varbinds = []; options = arguments[1]; responseCb = arguments[2]; } else { varbinds = arguments[1]; responseCb = arguments[2]; } } else { varbinds = []; responseCb = arguments[1]; } if ( this.version == Version1 ) { responseCb (new RequestInvalidError ("Inform not allowed for SNMPv1")); return; } function feedCb (req, message) { var pdu = message.pdu; var varbinds = []; if (req.message.pdu.varbinds.length != pdu.varbinds.length) { req.responseCb (new ResponseInvalidError ("Inform OIDs do not " + "match response OIDs", ResponseInvalidCode.EReqResOidNoMatch)); } else { for (var i = 0; i < req.message.pdu.varbinds.length; i++) { if (req.message.pdu.varbinds[i].oid != pdu.varbinds[i].oid) { req.responseCb (new ResponseInvalidError ("OID '" + req.message.pdu.varbinds[i].oid + "' in inform at positiion '" + i + "' does not " + "match OID '" + pdu.varbinds[i].oid + "' in response " + "at position '" + i + "'", ResponseInvalidCode.EReqResOidNoMatch)); return; } else { varbinds.push (pdu.varbinds[i]); } } req.responseCb (null, varbinds); } } if (typeof typeOrOid != "string") typeOrOid = "1.3.6.1.6.3.1.1.5." + (typeOrOid + 1); var pduVarbinds = [ { oid: "1.3.6.1.2.1.1.3.0", type: ObjectType.TimeTicks, value: options.upTime || Math.floor (process.uptime () * 100) }, { oid: "1.3.6.1.6.3.1.1.4.1.0", type: ObjectType.OID, value: typeOrOid } ]; for (var i = 0; i < varbinds.length; i++) { var varbind = { oid: varbinds[i].oid, type: varbinds[i].type, value: varbinds[i].value }; pduVarbinds.push (varbind); } options.port = this.trapPort; this.simpleGet (InformRequestPdu, feedCb, pduVarbinds, responseCb, options); return this; }; Session.prototype.onClose = function () { this.cancelRequests (new Error ("Socket forcibly closed")); this.emit ("close"); }; Session.prototype.onError = function (error) { this.emit (error); }; Session.prototype.onMsg = function (buffer) { try { var message = Message.createFromBuffer (buffer); } catch (error) { this.emit("error", error); return; } var req = this.unregisterRequest (message.getReqId ()); if ( ! req ) return; if ( ! message.processIncomingSecurity (this.user, req.responseCb) ) return; if (message.version != req.message.version) { req.responseCb (new ResponseInvalidError ("Version in request '" + req.message.version + "' does not match version in " + "response '" + message.version + "'", ResponseInvalidCode.EVersionNoMatch)); } else if (message.community != req.message.community) { req.responseCb (new ResponseInvalidError ("Community '" + req.message.community + "' in request does not match " + "community '" + message.community + "' in response", ResponseInvalidCode.ECommunityNoMatch)); } else if (message.pdu.type == PduType.Report) { this.msgSecurityParameters = { msgAuthoritativeEngineID: message.msgSecurityParameters.msgAuthoritativeEngineID, msgAuthoritativeEngineBoots: message.msgSecurityParameters.msgAuthoritativeEngineBoots, msgAuthoritativeEngineTime: message.msgSecurityParameters.msgAuthoritativeEngineTime }; if ( this.proxy ) { this.msgSecurityParameters.msgUserName = this.proxy.user.name; this.msgSecurityParameters.msgAuthenticationParameters = ""; this.msgSecurityParameters.msgPrivacyParameters = ""; } else { if ( ! req.originalPdu || ! req.allowReport ) { if (Array.isArray(message.pdu.varbinds) && message.pdu.varbinds[0] && message.pdu.varbinds[0].oid.indexOf(UsmStatsBase) === 0) { this.userSecurityModelError (req, message.pdu.varbinds[0].oid); return; } req.responseCb (new ResponseInvalidError ("Unexpected Report PDU", ResponseInvalidCode.EUnexpectedReport) ); return; } req.originalPdu.contextName = this.context; var timeSyncNeeded = ! message.msgSecurityParameters.msgAuthoritativeEngineBoots && ! message.msgSecurityParameters.msgAuthoritativeEngineTime; this.sendV3Req (req.originalPdu, req.feedCb, req.responseCb, req.options, req.port, timeSyncNeeded); } } else if ( this.proxy ) { this.onProxyResponse (req, message); } else if (message.pdu.type == PduType.GetResponse) { req.onResponse (req, message); } else { req.responseCb (new ResponseInvalidError ("Unknown PDU type '" + message.pdu.type + "' in response", ResponseInvalidCode.EUnknownPduType)); } }; Session.prototype.onSimpleGetResponse = function (req, message) { var pdu = message.pdu; if (pdu.errorStatus > 0) { var statusString = ErrorStatus[pdu.errorStatus] || ErrorStatus.GeneralError; var statusCode = ErrorStatus[statusString] || ErrorStatus[ErrorStatus.GeneralError]; if (pdu.errorIndex <= 0 || pdu.errorIndex > pdu.varbinds.length) { req.responseCb (new RequestFailedError (statusString, statusCode)); } else { var oid = pdu.varbinds[pdu.errorIndex - 1].oid; var error = new RequestFailedError (statusString + ": " + oid, statusCode); req.responseCb (error); } } else { req.feedCb (req, message); } }; Session.prototype.registerRequest = function (req) { if (! this.reqs[req.getId ()]) { this.reqs[req.getId ()] = req; if (this.reqCount <= 0) this.dgram.ref(); this.reqCount++; } var me = this; req.timer = setTimeout (function () { if (req.retries-- > 0) { me.send (req); } else { me.unregisterRequest (req.getId ()); req.responseCb (new RequestTimedOutError ( "Request timed out")); } }, req.timeout); // Apply timeout backoff if (req.backoff && req.backoff >= 1) req.timeout *= req.backoff; }; Session.prototype.send = function (req, noWait) { try { var me = this; var buffer = req.message.toBuffer (); this.dgram.send (buffer, 0, buffer.length, req.port, this.target, function (error, bytes) { if (error) { req.responseCb (error); } else { if (noWait) { req.responseCb (null); } else { me.registerRequest (req); } } }); } catch (error) { req.responseCb (error); } return this; }; Session.prototype.set = function (varbinds, responseCb) { var reportOidMismatchErrors = this.reportOidMismatchErrors; function feedCb (req, message) { var pdu = message.pdu; var varbinds = []; if (req.message.pdu.varbinds.length != pdu.varbinds.length) { req.responseCb (new ResponseInvalidError ("Requested OIDs do not " + "match response OIDs", ResponseInvalidCode.EReqResOidNoMatch)); } else { for (var i = 0; i < req.message.pdu.varbinds.length; i++) { if ( reportOidMismatchErrors && req.message.pdu.varbinds[i].oid != pdu.varbinds[i].oid ) { req.responseCb (new ResponseInvalidError ("OID '" + req.message.pdu.varbinds[i].oid + "' in request at position '" + i + "' does not " + "match OID '" + pdu.varbinds[i].oid + "' in response " + "at position '" + i + "'", ResponseInvalidCode.EReqResOidNoMatch)); return; } else { varbinds.push (pdu.varbinds[i]); } } req.responseCb (null, varbinds); } } var pduVarbinds = []; for (var i = 0; i < varbinds.length; i++) { var varbind = { oid: varbinds[i].oid, type: varbinds[i].type, value: varbinds[i].value }; pduVarbinds.push (varbind); } this.simpleGet (SetRequestPdu, feedCb, pduVarbinds, responseCb); return this; }; Session.prototype.simpleGet = function (pduClass, feedCb, varbinds, responseCb, options) { var id = _generateId (this.idBitsSize); options = Object.assign({}, options, { context: this.context }); var pdu = SimplePdu.createFromVariables (pduClass, id, varbinds, options); var message; var req; if ( this.version == Version3 ) { if ( this.msgSecurityParameters ) { this.sendV3Req (pdu, feedCb, responseCb, options, this.port, true); } else { this.sendV3Discovery (pdu, feedCb, responseCb, options); } } else { message = Message.createCommunity (this.version, this.community, pdu); req = new Req (this, message, feedCb, responseCb, options); this.send (req); } }; function subtreeCb (req, varbinds) { var done = 0; for (var i = varbinds.length; i > 0; i--) { if (! oidInSubtree (req.baseOid, varbinds[i - 1].oid)) { done = 1; varbinds.pop (); } } if (varbinds.length > 0) req.feedCb (varbinds); if (done) return true; } Session.prototype.subtree = function () { var me = this; var oid = arguments[0]; var maxRepetitions, feedCb, doneCb; if (arguments.length < 4) { maxRepetitions = 20; feedCb = arguments[1]; doneCb = arguments[2]; } else { maxRepetitions = arguments[1]; feedCb = arguments[2]; doneCb = arguments[3]; } var req = { feedCb: feedCb, doneCb: doneCb, maxRepetitions: maxRepetitions, baseOid: oid }; this.walk (oid, maxRepetitions, subtreeCb.bind (me, req), doneCb); return this; }; function tableColumnsResponseCb (req, error) { if (error) { req.responseCb (error); } else if (req.error) { req.responseCb (req.error); } else { if (req.columns.length > 0) { var column = req.columns.pop (); var me = this; this.subtree (req.rowOid + column, req.maxRepetitions, tableColumnsFeedCb.bind (me, req), tableColumnsResponseCb.bind (me, req)); } else { req.responseCb (null, req.table); } } } function tableColumnsFeedCb (req, varbinds) { for (var i = 0; i < varbinds.length; i++) { if (isVarbindError (varbinds[i])) { req.error = new RequestFailedError (varbindError (varbinds[i])); return true; } var oid = varbinds[i].oid.replace (req.rowOid, ""); if (oid && oid != varbinds[i].oid) { var match = oid.match (/^(\d+)\.(.+)$/); if (match && match[1] > 0) { if (! req.table[match[2]]) req.table[match[2]] = {}; req.table[match[2]][match[1]] = varbinds[i].value; } } } } Session.prototype.tableColumns = function () { var me = this; var oid = arguments[0]; var columns = arguments[1]; var maxRepetitions, responseCb; if (arguments.length < 4) { responseCb = arguments[2]; maxRepetitions = 20; } else { maxRepetitions = arguments[2]; responseCb = arguments[3]; } var req = { responseCb: responseCb, maxRepetitions: maxRepetitions, baseOid: oid, rowOid: oid + ".1.", columns: columns.slice(0), table: {} }; if (req.columns.length > 0) { var column = req.columns.pop (); this.subtree (req.rowOid + column, maxRepetitions, tableColumnsFeedCb.bind (me, req), tableColumnsResponseCb.bind (me, req)); } return this; }; function tableResponseCb (req, error) { if (error) req.responseCb (error); else if (req.error) req.responseCb (req.error); else req.responseCb (null, req.table); } function tableFeedCb (req, varbinds) { for (var i = 0; i < varbinds.length; i++) { if (isVarbindError (varbinds[i])) { req.error = new RequestFailedError (varbindError (varbinds[i])); return true; } var oid = varbinds[i].oid.replace (req.rowOid, ""); if (oid && oid != varbinds[i].oid) { var match = oid.match (/^(\d+)\.(.+)$/); if (match && match[1] > 0) { if (! req.table[match[2]]) req.table[match[2]] = {}; req.table[match[2]][match[1]] = varbinds[i].value; } } } } Session.prototype.table = function () { var me = this; var oid = arguments[0]; var maxRepetitions, responseCb; if (arguments.length < 3) { responseCb = arguments[1]; maxRepetitions = 20; } else { maxRepetitions = arguments[1]; responseCb = arguments[2]; } var req = { responseCb: responseCb, maxRepetitions: maxRepetitions, baseOid: oid, rowOid: oid + ".1.", table: {} }; this.subtree (oid, maxRepetitions, tableFeedCb.bind (me, req), tableResponseCb.bind (me, req)); return this; }; Session.prototype.trap = function () { var req = {}; var typeOrOid = arguments[0]; var varbinds, options = {}, responseCb; var message; /** ** Support the following signatures: ** ** typeOrOid, varbinds, options, callback ** typeOrOid, varbinds, agentAddr, callback ** typeOrOid, varbinds, callback ** typeOrOid, agentAddr, callback ** typeOrOid, options, callback ** typeOrOid, callback **/ if (arguments.length >= 4) { varbinds = arguments[1]; if (typeof arguments[2] == "string") { options.agentAddr = arguments[2]; } else if (arguments[2].constructor != Array) { options = arguments[2]; } responseCb = arguments[3]; } else if (arguments.length >= 3) { if (typeof arguments[1] == "string") { varbinds = []; options.agentAddr = arguments[1]; } else if (arguments[1].constructor != Array) { varbinds = []; options = arguments[1]; } else { varbinds = arguments[1]; options.agentAddr = null; } responseCb = arguments[2]; } else { varbinds = []; responseCb = arguments[1]; } var pdu, pduVarbinds = []; for (var i = 0; i < varbinds.length; i++) { var varbind = { oid: varbinds[i].oid, type: varbinds[i].type, value: varbinds[i].value }; pduVarbinds.push (varbind); } var id = _generateId (this.idBitsSize); if (this.version == Version2c || this.version == Version3 ) { if (typeof typeOrOid != "string") typeOrOid = "1.3.6.1.6.3.1.1.5." + (typeOrOid + 1); pduVarbinds.unshift ( { oid: "1.3.6.1.2.1.1.3.0", type: ObjectType.TimeTicks, value: options.upTime || Math.floor (process.uptime () * 100) }, { oid: "1.3.6.1.6.3.1.1.4.1.0", type: ObjectType.OID, value: typeOrOid } ); pdu = TrapV2Pdu.createFromVariables (id, pduVarbinds, options); } else { pdu = TrapPdu.createFromVariables (typeOrOid, pduVarbinds, options); } if ( this.version == Version3 ) { var msgSecurityParameters = { msgAuthoritativeEngineID: this.engine.engineID, msgAuthoritativeEngineBoots: 0, msgAuthoritativeEngineTime: 0 }; message = Message.createRequestV3 (this.user, msgSecurityParameters, pdu); } else { message = Message.createCommunity (this.version, this.community, pdu); } req = { id: id, message: message, responseCb: responseCb, port: this.trapPort }; this.send (req, true); return this; }; Session.prototype.unregisterRequest = function (id) { var req = this.reqs[id]; if (req) { delete this.reqs[id]; clearTimeout (req.timer); delete req.timer; this.reqCount--; if (this.reqCount <= 0) this.dgram.unref(); return req; } else { return null; } }; function walkCb (req, error, varbinds) { var done = 0; var oid; if (error) { if (error instanceof RequestFailedError) { if (error.status != ErrorStatus.NoSuchName) { req.doneCb (error); return; } else { // signal the version 1 walk code below that it should stop done = 1; } } else { req.doneCb (error); return; } } if ( ! varbinds || ! varbinds.length ) { req.doneCb(null); return; } if (this.version == Version2c || this.version == Version3) { for (var i = varbinds[0].length; i > 0; i--) { if (varbinds[0][i - 1].type == ObjectType.EndOfMibView) { varbinds[0].pop (); done = 1; } } if (req.feedCb (varbinds[0])) done = 1; if (! done) oid = varbinds[0][varbinds[0].length - 1].oid; } else { if (! done) { if (req.feedCb (varbinds)) { done = 1; } else { oid = varbinds[0].oid; } } } if (done) req.doneCb (null); else this.walk (oid, req.maxRepetitions, req.feedCb, req.doneCb, req.baseOid); } Session.prototype.walk = function () { var me = this; var oid = arguments[0]; var maxRepetitions, feedCb, doneCb; if (arguments.length < 4) { maxRepetitions = 20; feedCb = arguments[1]; doneCb = arguments[2]; } else { maxRepetitions = arguments[1]; feedCb = arguments[2]; doneCb = arguments[3]; } var req = { maxRepetitions: maxRepetitions, feedCb: feedCb, doneCb: doneCb }; if (this.version == Version2c || this.version == Version3) this.getBulk ([oid], 0, maxRepetitions, walkCb.bind (me, req)); else this.getNext ([oid], walkCb.bind (me, req)); return this; }; Session.prototype.sendV3Req = function (pdu, feedCb, responseCb, options, port, allowReport) { var message = Message.createRequestV3 (this.user, this.msgSecurityParameters, pdu); var reqOptions = options || {}; var req = new Req (this, message, feedCb, responseCb, reqOptions); req.port = port; req.originalPdu = pdu; req.allowReport = allowReport; this.send (req); }; Session.prototype.sendV3Discovery = function (originalPdu, feedCb, responseCb, options) { var discoveryPdu = createDiscoveryPdu(this.context); var discoveryMessage = Message.createDiscoveryV3 (discoveryPdu); var discoveryReq = new Req (this, discoveryMessage, feedCb, responseCb, options); discoveryReq.originalPdu = originalPdu; discoveryReq.allowReport = true; this.send (discoveryReq); }; Session.prototype.userSecurityModelError = function (req, oid) { var oidSuffix = oid.replace (UsmStatsBase + '.', '').replace (/\.0$/, ''); var errorType = UsmStats[oidSuffix] || "Unexpected Report PDU"; req.responseCb (new ResponseInvalidError (errorType, ResponseInvalidCode.EAuthFailure) ); }; Session.prototype.onProxyResponse = function (req, message) { if ( message.version != Version3 ) { this.callback (new RequestFailedError ("Only SNMP version 3 contexts are supported")); return; } message.pdu.contextName = this.proxy.context; message.user = req.proxiedUser; message.setAuthentication ( ! (req.proxiedUser.level == SecurityLevel.noAuthNoPriv)); message.setPrivacy (req.proxiedUser.level == SecurityLevel.authPriv); message.msgSecurityParameters = { msgAuthoritativeEngineID: req.proxiedEngine.engineID, msgAuthoritativeEngineBoots: req.proxiedEngine.engineBoots, msgAuthoritativeEngineTime: req.proxiedEngine.engineTime, msgUserName: req.proxiedUser.name, msgAuthenticationParameters: "", msgPrivacyParameters: "" }; message.buffer = null; message.pdu.contextEngineID = message.msgSecurityParameters.msgAuthoritativeEngineID; message.pdu.contextName = this.proxy.context; message.pdu.id = req.proxiedPduId; this.proxy.listener.send (message, req.proxiedRinfo); }; Session.create = function (target, community, options) { // Ensure that options may be optional var version = (options && options.version) ? options.version : Version1; if (version != Version1 && version != Version2c) { throw new ResponseInvalidError ("SNMP community session requested but version '" + options.version + "' specified in options not valid", ResponseInvalidCode.EVersionNoMatch); } else { if (!options) options = {}; options.version = version; return new Session (target, community, options); } }; Session.createV3 = function (target, user, options) { // Ensure that options may be optional if ( options && options.version && options.version != Version3 ) { throw new ResponseInvalidError ("SNMPv3 session requested but version '" + options.version + "' specified in options", ResponseInvalidCode.EVersionNoMatch); } else { if (!options) options = {}; options.version = Version3; } return new Session (target, user, options); }; var Engine = function (engineID, engineBoots, engineTime) { if ( engineID ) { if ( ! (engineID instanceof Buffer) ) { engineID = engineID.replace('0x', ''); this.engineID = Buffer.from((engineID.toString().length % 2 == 1 ? '0' : '') + engineID.toString(), 'hex'); } else { this.engineID = engineID; } } else { this.generateEngineID (); } this.engineBoots = 0; this.engineTime = 10; }; Engine.prototype.generateEngineID = function() { // generate a 17-byte engine ID in the following format: // 0x80 | 0x00B983 (enterprise OID) | 0x80 (enterprise-specific format) | 12 bytes of random this.engineID = Buffer.alloc (17); this.engineID.fill ('8000B98380', 'hex', 0, 5); this.engineID.fill (crypto.randomBytes (12), 5, 17, 'hex'); }; var Listener = function (options, receiver) { this.receiver = receiver; this.callback = receiver.onMsg; this.family = options.transport || 'udp4'; this.port = options.port || 161; this.address = options.address; this.disableAuthorization = options.disableAuthorization || false; }; Listener.prototype.startListening = function () { var me = this; this.dgram = dgram.createSocket (this.family); this.dgram.on ("error", me.receiver.callback); this.dgram.bind (this.port, this.address); this.dgram.on ("message", me.callback.bind (me.receiver)); }; Listener.prototype.send = function (message, rinfo) { // var me = this; var buffer = message.toBuffer (); this.dgram.send (buffer, 0, buffer.length, rinfo.port, rinfo.address, function (error, bytes) { if (error) { // me.callback (error); console.error ("Error sending: " + error.message); } else { // debug ("Listener sent response message"); } }); }; Listener.formatCallbackData = function (pdu, rinfo) { if ( pdu.contextEngineID ) { pdu.contextEngineID = pdu.contextEngineID.toString('hex'); } delete pdu.nonRepeaters; delete pdu.maxRepetitions; return { pdu: pdu, rinfo: rinfo }; }; Listener.processIncoming = function (buffer, authorizer, callback) { var message = Message.createFromBuffer (buffer); var community; // Authorization if ( message.version == Version3 ) { message.user = authorizer.users.filter( localUser => localUser.name == message.msgSecurityParameters.msgUserName )[0]; message.disableAuthentication = authorizer.disableAuthorization; if ( ! message.user ) { if ( message.msgSecurityParameters.msgUserName != "" && ! authorizer.disableAuthorization ) { callback (new RequestFailedError ("Local user not found for message with user " + message.msgSecurityParameters.msgUserName)); return; } else if ( message.hasAuthentication () ) { callback (new RequestFailedError ("Local user not found and message requires authentication with user " + message.msgSecurityParameters.msgUserName)); return; } else { message.user = { name: "", level: SecurityLevel.noAuthNoPriv }; } } if ( (message.user.level == SecurityLevel.authNoPriv || message.user.level == SecurityLevel.authPriv) && ! message.hasAuthentication() ) { callback (new RequestFailedError ("Local user " + message.msgSecurityParameters.msgUserName + " requires authentication but message does not provide it")); return; } if ( message.user.level == SecurityLevel.authPriv && ! message.hasPrivacy() ) { callback (new RequestFailedError ("Local user " + message.msgSecurityParameters.msgUserName + " requires privacy but message does not provide it")); return; } if ( ! message.processIncomingSecurity (message.user, callback) ) { return; } } else { community = authorizer.communities.filter( localCommunity => localCommunity == message.community )[0]; if ( ! community && ! authorizer.disableAuthorization ) { callback (new RequestFailedError ("Local community not found for message with community " + message.community)); return; } } return message; }; Listener.prototype.close = function () { if ( this.dgram ) { this.dgram.close (); } }; var Authorizer = function (options) { this.communities = []; this.users = []; this.disableAuthorization = options.disableAuthorization; this.accessControlModelType = options.accessControlModelType || AccessControlModelType.None; if ( this.accessControlModelType == AccessControlModelType.None ) { this.accessControlModel = null; } else if ( this.accessControlModelType == AccessControlModelType.Simple ) { this.accessControlModel = new SimpleAccessControlModel (); } }; Authorizer.prototype.addCommunity = function (community) { if ( this.getCommunity (community) ) { return; } else { this.communities.push (community); if ( this.accessControlModelType == AccessControlModelType.Simple ) { this.accessControlModel.setCommunityAccess (community, AccessLevel.ReadOnly); } } }; Authorizer.prototype.getCommunity = function (community) { return this.communities.filter( localCommunity => localCommunity == community )[0] || null; }; Authorizer.prototype.getCommunities = function () { return this.communities; }; Authorizer.prototype.deleteCommunity = function (community) { var index = this.communities.indexOf(community); if ( index > -1 ) { this.communities.splice(index, 1); } }; Authorizer.prototype.addUser = function (user) { if ( this.getUser (user.name) ) { this.deleteUser (user.name); } this.users.push (user); if ( this.accessControlModelType == AccessControlModelType.Simple ) { this.accessControlModel.setUserAccess (user.name, AccessLevel.ReadOnly); } }; Authorizer.prototype.getUser = function (userName) { return this.users.filter( localUser => localUser.name == userName )[0] || null; }; Authorizer.prototype.getUsers = function () { return this.users; }; Authorizer.prototype.deleteUser = function (userName) { var index = this.users.findIndex(localUser => localUser.name == userName ); if ( index > -1 ) { this.users.splice(index, 1); } }; Authorizer.prototype.getAccessControlModelType = function () { return this.accessControlModelType; }; Authorizer.prototype.getAccessControlModel = function () { return this.accessControlModel; }; Authorizer.prototype.isAccessAllowed = function (securityModel, securityName, pduType) { if ( this.accessControlModel ) { return this.accessControlModel.isAccessAllowed (securityModel, securityName, pduType); } else { return true; } }; var SimpleAccessControlModel = function () { this.communitiesAccess = []; this.usersAccess = []; }; SimpleAccessControlModel.prototype.getCommunityAccess = function (community) { return this.communitiesAccess.find (entry => entry.community == community ); }; SimpleAccessControlModel.prototype.getCommunityAccessLevel = function (community) { var communityAccessEntry = this.getCommunityAccess (community); return communityAccessEntry ? communityAccessEntry.level : AccessLevel.None; }; SimpleAccessControlModel.prototype.getCommunitiesAccess = function () { return this.communitiesAccess; }; SimpleAccessControlModel.prototype.setCommunityAccess = function (community, accessLevel) { let accessEntry = this.getCommunityAccess (community); if ( accessEntry ) { accessEntry.level = accessLevel; } else { this.communitiesAccess.push ({ community: community, level: accessLevel }); this.communitiesAccess.sort ((a, b) => (a.community > b.community) ? 1 : -1); } }; SimpleAccessControlModel.prototype.removeCommunityAccess = function (community) { this.communitiesAccess.splice ( this.communitiesAccess.findIndex (entry => entry.community == community), 1); }; SimpleAccessControlModel.prototype.getUserAccess = function (userName) { return this.usersAccess.find (entry => entry.userName == userName ); }; SimpleAccessControlModel.prototype.getUserAccessLevel = function (user) { var userAccessEntry = this.getUserAccess (user); return userAccessEntry ? userAccessEntry.level : AccessLevel.None; }; SimpleAccessControlModel.prototype.getUsersAccess = function () { return this.usersAccess; }; SimpleAccessControlModel.prototype.setUserAccess = function (userName, accessLevel) { let accessEntry = this.getUserAccess (userName); if ( accessEntry ) { accessEntry.level = accessLevel; } else { this.usersAccess.push ({ userName: userName, level: accessLevel }); this.usersAccess.sort ((a, b) => (a.userName > b.userName) ? 1 : -1); } }; SimpleAccessControlModel.prototype.removeUserAccess = function (userName) { this.usersAccess.splice ( this.usersAccess.findIndex (entry => entry.userName == userName), 1); }; SimpleAccessControlModel.prototype.isAccessAllowed = function (securityModel, securityName, pduType) { var accessLevelConfigured; var accessLevelRequired; switch ( securityModel ) { case Version1: case Version2c: accessLevelConfigured = this.getCommunityAccessLevel (securityName); break; case Version3: accessLevelConfigured = this.getUserAccessLevel (securityName); break; } switch ( pduType ) { case PduType.SetRequest: accessLevelRequired = AccessLevel.ReadWrite; break; case PduType.GetRequest: case PduType.GetNextRequest: case PduType.GetBulkRequest: accessLevelRequired = AccessLevel.ReadOnly; break; default: accessLevelRequired = AccessLevel.None; break; } switch ( accessLevelRequired ) { case AccessLevel.ReadWrite: return accessLevelConfigured == AccessLevel.ReadWrite; case AccessLevel.ReadOnly: return accessLevelConfigured == AccessLevel.ReadWrite || accessLevelConfigured == AccessLevel.ReadOnly; case AccessLevel.None: return true; default: return false; } }; /***************************************************************************** ** Receiver class definition **/ var Receiver = function (options, callback) { DEBUG = options.debug; this.listener = new Listener (options, this); this.authorizer = new Authorizer (options); this.engine = new Engine (options.engineID); this.engineBoots = 0; this.engineTime = 10; this.disableAuthorization = false; this.callback = callback; this.family = options.transport || 'udp4'; this.port = options.port || 162; options.port = this.port; this.disableAuthorization = options.disableAuthorization || false; this.includeAuthentication = options.includeAuthentication || false; this.context = (options && options.context) ? options.context : ""; this.listener = new Listener (options, this); }; Receiver.prototype.getAuthorizer = function () { return this.authorizer; }; Receiver.prototype.onMsg = function (buffer, rinfo) { var message = Listener.processIncoming (buffer, this.authorizer, this.callback); var reportMessage; if ( ! message ) { return; } // The only GetRequest PDUs supported are those used for SNMPv3 discovery if ( message.pdu.type == PduType.GetRequest ) { if ( message.version != Version3 ) { this.callback (new RequestInvalidError ("Only SNMPv3 discovery GetRequests are supported")); return; } else if ( message.hasAuthentication() ) { this.callback (new RequestInvalidError ("Only discovery (noAuthNoPriv) GetRequests are supported but this message has authentication")); return; } else if ( ! message.isReportable () ) { this.callback (new RequestInvalidError ("Only discovery GetRequests are supported and this message does not have the reportable flag set")); return; } reportMessage = message.createReportResponseMessage (this.engine, this.context); this.listener.send (reportMessage, rinfo); return; } // Inform/trap processing // debug (JSON.stringify (message.pdu, null, 2)); if ( message.pdu.type == PduType.Trap || message.pdu.type == PduType.TrapV2 ) { this.callback (null, this.formatCallbackData (message, rinfo) ); } else if ( message.pdu.type == PduType.InformRequest ) { message.pdu.type = PduType.GetResponse; message.buffer = null; message.setReportable (false); this.listener.send (message, rinfo); message.pdu.type = PduType.InformRequest; this.callback (null, this.formatCallbackData (message, rinfo) ); } else { this.callback (new RequestInvalidError ("Unexpected PDU type " + message.pdu.type + " (" + PduType[message.pdu.type] + ")")); } }; Receiver.prototype.formatCallbackData = function (message, rinfo) { if ( message.pdu.contextEngineID ) { message.pdu.contextEngineID = message.pdu.contextEngineID.toString('hex'); } delete message.pdu.nonRepeaters; delete message.pdu.maxRepetitions; const formattedData = { pdu: message.pdu, rinfo: rinfo }; if (this.includeAuthentication) { if (message.community) { formattedData.pdu.community = message.community; } else if (message.user) { formattedData.pdu.user = message.user.name; } } return formattedData; }; Receiver.prototype.close = function() { this.listener.close (); }; Receiver.create = function (options, callback) { var receiver = new Receiver (options, callback); receiver.listener.startListening (); return receiver; }; var ModuleStore = function () { this.parser = mibparser (); }; ModuleStore.prototype.getSyntaxTypes = function () { var syntaxTypes = {}; Object.assign (syntaxTypes, ObjectType); var entryArray; for ( var mibModule of Object.values (this.parser.Modules) ) { entryArray = Object.values (mibModule); for ( var mibEntry of entryArray ) { if ( mibEntry.MACRO == "TEXTUAL-CONVENTION" ) { if ( mibEntry.SYNTAX && ! syntaxTypes[mibEntry.ObjectName] ) { if ( typeof mibEntry.SYNTAX == "object" ) { syntaxTypes[mibEntry.ObjectName] = syntaxTypes.Integer; } else { syntaxTypes[mibEntry.ObjectName] = syntaxTypes[mibEntry.SYNTAX]; } } } } } return syntaxTypes; }; ModuleStore.prototype.loadFromFile = function (fileName) { this.parser.Import (fileName); this.parser.Serialize (); }; ModuleStore.prototype.getModule = function (moduleName) { return this.parser.Modules[moduleName]; }; ModuleStore.prototype.getModules = function (includeBase) { var modules = {}; for ( var moduleName of Object.keys(this.parser.Modules) ) { if ( includeBase || ModuleStore.BASE_MODULES.indexOf (moduleName) == -1 ) { modules[moduleName] = this.parser.Modules[moduleName]; } } return modules; }; ModuleStore.prototype.getModuleNames = function (includeBase) { var modules = []; for ( var moduleName of Object.keys(this.parser.Modules) ) { if ( includeBase || ModuleStore.BASE_MODULES.indexOf (moduleName) == -1 ) { modules.push (moduleName); } } return modules; }; ModuleStore.prototype.getProvidersForModule = function (moduleName) { var mibModule = this.parser.Modules[moduleName]; var scalars = []; var tables = []; var mibEntry; var syntaxTypes; var entryArray; var currentTableProvider; var parentOid; var constraintsResults; var constraints; if ( ! mibModule ) { throw new ReferenceError ("MIB module " + moduleName + " not loaded"); } syntaxTypes = this.getSyntaxTypes (); entryArray = Object.values (mibModule); for ( var i = 0; i < entryArray.length ; i++ ) { mibEntry = entryArray[i]; var syntax = mibEntry.SYNTAX; var access = mibEntry["ACCESS"]; var maxAccess = (typeof mibEntry["MAX-ACCESS"] != "undefined" ? mibEntry["MAX-ACCESS"] : (access ? AccessToMaxAccess[access] : "not-accessible")); var defVal = mibEntry["DEFVAL"]; if ( syntax ) { constraintsResults = ModuleStore.getConstraintsFromSyntax (syntax); syntax = constraintsResults.syntax; constraints = constraintsResults.constraints; if ( syntax.startsWith ("SEQUENCE OF") ) { // start of table currentTableProvider = { tableName: mibEntry.ObjectName, type: MibProviderType.Table, //oid: mibEntry.OID, tableColumns: [], tableIndex: [1] // default - assume first column is index }; currentTableProvider.maxAccess = MaxAccess[maxAccess]; // read table to completion while ( currentTableProvider || i >= entryArray.length ) { i++; mibEntry = entryArray[i]; if ( ! mibEntry ) { tables.push (currentTableProvider); currentTableProvider = null; i--; break; } syntax = mibEntry.SYNTAX; access = mibEntry["ACCESS"]; maxAccess = (typeof mibEntry["MAX-ACCESS"] != "undefined" ? mibEntry["MAX-ACCESS"] : (access ? AccessToMaxAccess[access] : "not-accessible")); defVal = mibEntry["DEFVAL"]; constraintsResults = ModuleStore.getConstraintsFromSyntax (syntax); syntax = constraintsResults.syntax; constraints = constraintsResults.constraints; if ( mibEntry.MACRO == "SEQUENCE" ) { // table entry sequence - ignore } else if ( ! mibEntry["OBJECT IDENTIFIER"] ) { // unexpected } else { parentOid = mibEntry["OBJECT IDENTIFIER"].split (" ")[0]; if ( parentOid == currentTableProvider.tableName ) { // table entry currentTableProvider.name = mibEntry.ObjectName; currentTableProvider.oid = mibEntry.OID; if ( mibEntry.INDEX ) { currentTableProvider.tableIndex = []; for ( var indexEntry of mibEntry.INDEX ) { indexEntry = indexEntry.trim (); if ( indexEntry.includes(" ") ) { if ( indexEntry.split(" ")[0] == "IMPLIED" ) { currentTableProvider.tableIndex.push ({ columnName: indexEntry.split(" ")[1], implied: true }); } else { // unknown condition - guess that last token is name currentTableProvider.tableIndex.push ({ columnName: indexEntry.split(" ").slice(-1)[0], }); } } else { currentTableProvider.tableIndex.push ({ columnName: indexEntry }); } } } if ( mibEntry.AUGMENTS ) { currentTableProvider.tableAugments = mibEntry.AUGMENTS[0].trim(); currentTableProvider.tableIndex = null; } } else if ( parentOid == currentTableProvider.name ) { // table column var columnDefinition = { number: parseInt (mibEntry["OBJECT IDENTIFIER"].split (" ")[1]), name: mibEntry.ObjectName, type: syntaxTypes[syntax], maxAccess: MaxAccess[maxAccess] }; if ( constraints ) { columnDefinition.constraints = constraints; } if (defVal) { columnDefinition.defVal = defVal; } // If this column has syntax RowStatus and // the MIB module imports RowStatus from // SNMPv2-TC, mark this column as the // rowStatus column so we can act on it. // (See lib/mibs/SNMPv2-TC.mib#L186.) if ( syntax == "RowStatus" && "IMPORTS" in mibModule && Array.isArray(mibModule.IMPORTS["SNMPv2-TC"]) && mibModule.IMPORTS["SNMPv2-TC"].includes("RowStatus") ) { // Mark this column as being rowStatus columnDefinition.rowStatus = true; } currentTableProvider.tableColumns.push (columnDefinition); } else { // table finished tables.push (currentTableProvider); // console.log ("Table: " + currentTableProvider.name); currentTableProvider = null; i--; } } } } else if ( mibEntry.MACRO == "OBJECT-TYPE" ) { // OBJECT-TYPE entries not in a table are scalars var scalarDefinition = { name: mibEntry.ObjectName, type: MibProviderType.Scalar, oid: mibEntry.OID, scalarType: syntaxTypes[syntax], maxAccess: MaxAccess[maxAccess] }; if (defVal) { scalarDefinition.defVal = defVal; } if ( constraints ) { scalarDefinition.constraints = constraints; } scalars.push (scalarDefinition); // console.log ("Scalar: " + mibEntry.ObjectName); } } } return scalars.concat (tables); }; ModuleStore.prototype.loadBaseModules = function () { for ( var mibModule of ModuleStore.BASE_MODULES ) { this.parser.Import (__dirname + "/lib/mibs/" + mibModule + ".mib"); } this.parser.Serialize (); }; ModuleStore.getConstraintsFromSyntax = function (syntax) { let constraints; // detect INTEGER ranges, OCTET STRING sizes, and INTEGER enumerations if ( typeof syntax == "object" ) { let firstSyntaxKey = syntax[Object.keys(syntax)[0]]; if ( firstSyntaxKey.ranges ) { constraints = { ranges: firstSyntaxKey.ranges }; syntax = Object.keys(syntax)[0]; } else if ( firstSyntaxKey.sizes ) { constraints = { size: firstSyntaxKey.sizes }; syntax = Object.keys(syntax)[0]; } else { constraints = { enumeration: syntax.INTEGER }; syntax = "INTEGER"; } } else { constraints = null; } return { constraints: constraints, syntax: syntax }; }; ModuleStore.create = function () { var store = new ModuleStore (); store.loadBaseModules (); return store; }; ModuleStore.BASE_MODULES = [ "RFC1155-SMI", "RFC1158-MIB", "RFC-1212", "RFC1213-MIB", "SNMPv2-SMI", "SNMPv2-CONF", "SNMPv2-TC", "SNMPv2-MIB" ]; var MibNode = function(address, parent) { this.address = address; this.oid = this.address.join('.'); this.parent = parent; this.children = {}; }; MibNode.prototype.child = function (index) { return this.children[index]; }; MibNode.prototype.listChildren = function (lowest) { var sorted = []; lowest = lowest || 0; this.children.forEach (function (c, i) { if (i >= lowest) sorted.push (i); }); sorted.sort (function (a, b) { return (a - b); }); return sorted; }; MibNode.prototype.findChildImmediatelyBefore = function (index) { var sortedChildrenKeys = Object.keys(this.children).sort(function (a, b) { return (a - b); }); if ( sortedChildrenKeys.length === 0 ) { return null; } for ( var i = 0; i < sortedChildrenKeys.length; i++ ) { if ( index < sortedChildrenKeys[i] ) { if ( i === 0 ) { return null; } else { return this.children[sortedChildrenKeys[i - 1]]; } } } return this.children[sortedChildrenKeys[sortedChildrenKeys.length - 1]]; }; MibNode.prototype.isDescendant = function (address) { return MibNode.oidIsDescended(this.address, address); }; MibNode.prototype.isAncestor = function (address) { return MibNode.oidIsDescended (address, this.address); }; MibNode.prototype.getAncestorProvider = function () { if ( this.provider ) { return this; } else if ( ! this.parent ) { return null; } else { return this.parent.getAncestorProvider (); } }; MibNode.prototype.getTableColumnFromInstanceNode = function () { if ( this.parent && this.parent.provider ) { return this.address[this.address.length - 1]; } else if ( ! this.parent ) { return null; } else { return this.parent.getTableColumnFromInstanceNode (); } }; MibNode.prototype.getConstraintsFromProvider = function () { var providerNode = this.getAncestorProvider (); if ( ! providerNode ) { return null; } var provider = providerNode.provider; if ( provider.type == MibProviderType.Scalar ) { return provider.constraints; } else if ( provider.type == MibProviderType.Table ) { var columnNumber = this.getTableColumnFromInstanceNode (); if ( ! columnNumber ) { return null; } var columnDefinition = provider.tableColumns.filter (column => column.number == columnNumber)[0]; return columnDefinition ? columnDefinition.constraints : null; } else { return null; } }; MibNode.prototype.setValue = function (newValue) { var len; var min; var max; var range; var found = false; var constraints = this.getConstraintsFromProvider (); if ( ! constraints ) { this.value = newValue; return true; } if ( constraints.enumeration ) { if ( ! constraints.enumeration[newValue] ) { return false; } } else if ( constraints.ranges ) { for ( range of constraints.ranges ) { min = "min" in range ? range.min : Number.MIN_SAFE_INTEGER; max = "max" in range ? range.max : Number.MAX_SAFE_INTEGER; if ( newValue >= min && newValue <= max ) { found = true; break; } } if ( ! found ) { return false; } } else if ( constraints.sizes ) { // if size is constrained, value must have a length property if ( ! ( "length" in newValue ) ) { return false; } len = newValue.length; for ( range of constraints.sizes ) { min = "min" in range ? range.min : Number.MIN_SAFE_INTEGER; max = "max" in range ? range.max : Number.MAX_SAFE_INTEGER; if ( len >= min && len <= max ) { found = true; break; } } if ( ! found ) { return false; } } this.value = newValue; return true; }; MibNode.prototype.getInstanceNodeForTableRow = function () { var childCount = Object.keys (this.children).length; if ( childCount == 0 ) { if ( this.value != null ) { return this; } else { return null; } } else if ( childCount == 1 ) { return this.children[0].getInstanceNodeForTableRow(); } else if ( childCount > 1 ) { return null; } }; MibNode.prototype.getInstanceNodeForTableRowIndex = function (index) { var childCount = Object.keys (this.children).length; var remainingIndex; if ( childCount == 0 ) { if ( this.value != null ) { return this; } else { // not found return null; } } else { if ( index.length == 0 ) { return this.getInstanceNodeForTableRow(); } else { var nextChildIndexPart = index[0]; if ( nextChildIndexPart == null ) { return null; } remainingIndex = index.slice(1); if ( this.children[nextChildIndexPart] ) { return this.children[nextChildIndexPart].getInstanceNodeForTableRowIndex(remainingIndex); } else { return null; } } } }; MibNode.prototype.getInstanceNodesForColumn = function () { var columnNode = this; var instanceNode = this; var instanceNodes = []; while (instanceNode && ( instanceNode == columnNode || columnNode.isAncestor (instanceNode.address) ) ) { instanceNode = instanceNode.getNextInstanceNode (); if ( instanceNode && columnNode.isAncestor (instanceNode.address) ) { instanceNodes.push (instanceNode); } } return instanceNodes; }; MibNode.prototype.getNextInstanceNode = function () { var siblingIndex; var childrenAddresses; var node = this; if ( this.value != null ) { // Need upwards traversal first node = this; while ( node ) { siblingIndex = node.address.slice(-1)[0]; node = node.parent; if ( ! node ) { // end of MIB return null; } else { childrenAddresses = Object.keys (node.children).sort ( (a, b) => a - b); var siblingPosition = childrenAddresses.indexOf(siblingIndex.toString()); if ( siblingPosition + 1 < childrenAddresses.length ) { node = node.children[childrenAddresses[siblingPosition + 1]]; break; } } } } // Descent while ( node ) { if ( node.value != null ) { return node; } childrenAddresses = Object.keys (node.children).sort ( (a, b) => a - b); node = node.children[childrenAddresses[0]]; if ( ! node ) { // unexpected return null; } } }; MibNode.prototype.delete = function () { if ( Object.keys (this.children) > 0 ) { throw new Error ("Cannot delete non-leaf MIB node"); } var addressLastPart = this.address.slice(-1)[0]; delete this.parent.children[addressLastPart]; this.parent = null; }; MibNode.prototype.pruneUpwards = function () { if ( ! this.parent ) { return; } if ( Object.keys (this.children).length == 0 ) { var lastAddressPart = this.address.splice(-1)[0].toString(); delete this.parent.children[lastAddressPart]; this.parent.pruneUpwards(); this.parent = null; } }; MibNode.prototype.dump = function (options) { var valueString; if ( ( ! options.leavesOnly || options.showProviders ) && this.provider ) { console.log (this.oid + " [" + MibProviderType[this.provider.type] + ": " + this.provider.name + "]"); } else if ( ( ! options.leavesOnly ) || Object.keys (this.children).length == 0 ) { if ( this.value != null ) { valueString = " = "; valueString += options.showTypes ? ObjectType[this.valueType] + ": " : ""; valueString += options.showValues ? this.value : ""; } else { valueString = ""; } console.log (this.oid + valueString); } for ( var node of Object.keys (this.children).sort ((a, b) => a - b)) { this.children[node].dump (options); } }; MibNode.oidIsDescended = function (oid, ancestor) { var ancestorAddress = Mib.convertOidToAddress(ancestor); var address = Mib.convertOidToAddress(oid); var isAncestor = true; if (address.length <= ancestorAddress.length) { return false; } ancestorAddress.forEach (function (o, i) { if (address[i] !== ancestorAddress[i]) { isAncestor = false; } }); return isAncestor; }; var Mib = function () { var providersByOid; this.root = new MibNode ([], null); this.providerNodes = {}; // this.providers will be modified throughout this code. // Keep this.providersByOid in sync with it providersByOid = this.providersByOid = {}; this.providers = new Proxy({}, { set: function (target, key, value) { target[key] = value; providersByOid[value.oid] = value; }, deleteProperty: function (target, key) { delete providersByOid[target[key].oid]; delete target[key]; } }); }; Mib.prototype.addNodesForOid = function (oidString) { var address = Mib.convertOidToAddress (oidString); return this.addNodesForAddress (address); }; Mib.prototype.addNodesForAddress = function (address) { var node; var i; node = this.root; for (i = 0; i < address.length; i++) { if ( ! node.children.hasOwnProperty (address[i]) ) { node.children[address[i]] = new MibNode (address.slice(0, i + 1), node); } node = node.children[address[i]]; } return node; }; Mib.prototype.lookup = function (oid) { var address; address = Mib.convertOidToAddress (oid); return this.lookupAddress(address); }; Mib.prototype.lookupAddress = function (address) { var i; var node; node = this.root; for (i = 0; i < address.length; i++) { if ( ! node.children.hasOwnProperty (address[i])) { return null; } node = node.children[address[i]]; } return node; }; Mib.prototype.getTreeNode = function (oid) { var address = Mib.convertOidToAddress (oid); var node; node = this.lookupAddress (address); // OID already on tree if ( node ) { return node; } while ( address.length > 0 ) { var last = address.pop (); var parent = this.lookupAddress (address); if ( parent ) { node = parent.findChildImmediatelyBefore (last); if ( !node ) return parent; while ( true ) { // Find the last descendant var childrenAddresses = Object.keys (node.children).sort ( (a, b) => a - b); if ( childrenAddresses.length == 0 ) return node; node = node.children[childrenAddresses[childrenAddresses.length - 1]]; } } } return this.root; }; Mib.prototype.getProviderNodeForInstance = function (instanceNode) { if ( instanceNode.provider ) { // throw new ReferenceError ("Instance node has provider which should never happen"); return null; } return instanceNode.getAncestorProvider (); }; Mib.prototype.addProviderToNode = function (provider) { var node = this.addNodesForOid (provider.oid); node.provider = provider; if ( provider.type == MibProviderType.Table ) { if ( ! provider.tableIndex ) { provider.tableIndex = [1]; } } this.providerNodes[provider.name] = node; return node; }; Mib.prototype.getColumnFromProvider = function (provider, indexEntry) { var column = null; if ( indexEntry.columnName ) { column = provider.tableColumns.filter (column => column.name == indexEntry.columnName )[0]; } else if ( indexEntry.columnNumber !== undefined && indexEntry.columnNumber !== null ) { column = provider.tableColumns.filter (column => column.number == indexEntry.columnNumber )[0]; } return column; }; Mib.prototype.populateIndexEntryFromColumn = function (localProvider, indexEntry, i) { var column = null; var tableProviders; if ( ! indexEntry.columnName && ! indexEntry.columnNumber ) { throw new Error ("Index entry " + i + ": does not have either a columnName or columnNumber"); } if ( indexEntry.foreign ) { // Explicit foreign table is first to search column = this.getColumnFromProvider (this.providers[indexEntry.foreign], indexEntry); } else { // If foreign table isn't given, search the local table next column = this.getColumnFromProvider (localProvider, indexEntry); if ( ! column ) { // as a last resort, try to find the column in a foreign table tableProviders = Object.values(this.providers). filter ( prov => prov.type == MibProviderType.Table ); for ( var provider of tableProviders ) { column = this.getColumnFromProvider (provider, indexEntry); if ( column ) { indexEntry.foreign = provider.name; break; } } } } if ( ! column ) { throw new Error ("Could not find column for index entry with column " + indexEntry.columnName); } if ( indexEntry.columnName && indexEntry.columnName != column.name ) { throw new Error ("Index entry " + i + ": Calculated column name " + column.name + "does not match supplied column name " + indexEntry.columnName); } if ( indexEntry.columnNumber && indexEntry.columnNumber != column.number ) { throw new Error ("Index entry " + i + ": Calculated column number " + column.number + " does not match supplied column number " + indexEntry.columnNumber); } if ( ! indexEntry.columnName ) { indexEntry.columnName = column.name; } if ( ! indexEntry.columnNumber ) { indexEntry.columnNumber = column.number; } indexEntry.type = column.type; }; Mib.prototype.registerProvider = function (provider) { this.providers[provider.name] = provider; if ( provider.type == MibProviderType.Table ) { if ( provider.tableAugments ) { if ( provider.tableAugments == provider.name ) { throw new Error ("Table " + provider.name + " cannot augment itself"); } var augmentProvider = this.providers[provider.tableAugments]; if ( ! augmentProvider ) { throw new Error ("Cannot find base table " + provider.tableAugments + " to augment"); } provider.tableIndex = JSON.parse(JSON.stringify(augmentProvider.tableIndex)); provider.tableIndex.map (index => index.foreign = augmentProvider.name); } else { if ( ! provider.tableIndex ) { provider.tableIndex = [1]; // default to first column index } for ( var i = 0 ; i < provider.tableIndex.length ; i++ ) { var indexEntry = provider.tableIndex[i]; if ( typeof indexEntry == 'number' ) { provider.tableIndex[i] = { columnNumber: indexEntry }; } else if ( typeof indexEntry == 'string' ) { provider.tableIndex[i] = { columnName: indexEntry }; } indexEntry = provider.tableIndex[i]; this.populateIndexEntryFromColumn (provider, indexEntry, i); } } } }; Mib.prototype.setScalarDefaultValue = function (name, value) { let provider = this.getProvider(name); provider.defVal = value; }; Mib.prototype.setTableRowDefaultValues = function (name, values) { let provider = this.getProvider(name); let tc = provider.tableColumns; // We must be given an array of exactly the right number of columns if (values.length != tc.length) { throw new Error(`Incorrect values length: got ${values.length}; expected ${tc.length}`); } // Add defVal to each table column. tc.forEach((entry, i) => { if (typeof values[i] != "undefined") { entry.defVal = values[i]; } }); }; Mib.prototype.setScalarRanges = function (name, ranges ) { let provider = this.getProvider(name); provider.constraints = { ranges }; }; Mib.prototype.setTableColumnRanges = function(name, column, ranges ) { let provider = this.getProvider(name); let tc = provider.tableColumns; tc[column].constraints = { ranges }; }; Mib.prototype.setScalarSizes = function (name, sizes ) { let provider = this.getProvider(name); provider.constraints = { sizes }; }; Mib.prototype.setTableColumnSizes = function(name, column, sizes ) { let provider = this.getProvider(name); let tc = provider.tableColumns; tc[column].constraints = { sizes }; }; Mib.prototype.registerProviders = function (providers) { for ( var provider of providers ) { this.registerProvider (provider); } }; Mib.prototype.unregisterProvider = function (name) { var providerNode = this.providerNodes[name]; if ( providerNode ) { var providerNodeParent = providerNode.parent; providerNode.delete(); providerNodeParent.pruneUpwards(); delete this.providerNodes[name]; } delete this.providers[name]; }; Mib.prototype.getProvider = function (name) { return this.providers[name]; }; Mib.prototype.getProviders = function () { return this.providers; }; Mib.prototype.dumpProviders = function () { var extraInfo; for ( var provider of Object.values(this.providers) ) { extraInfo = provider.type == MibProviderType.Scalar ? ObjectType[provider.scalarType] : "Columns = " + provider.tableColumns.length; console.log(MibProviderType[provider.type] + ": " + provider.name + " (" + provider.oid + "): " + extraInfo); } }; Mib.prototype.getScalarValue = function (scalarName) { var providerNode = this.providerNodes[scalarName]; if ( ! providerNode || ! providerNode.provider || providerNode.provider.type != MibProviderType.Scalar ) { throw new ReferenceError ("Failed to get node for registered MIB provider " + scalarName); } var instanceAddress = providerNode.address.concat ([0]); if ( ! this.lookup (instanceAddress) ) { throw new Error ("Failed created instance node for registered MIB provider " + scalarName); } var instanceNode = this.lookup (instanceAddress); return instanceNode.value; }; Mib.prototype.setScalarValue = function (scalarName, newValue) { var providerNode; var instanceNode; var provider; if ( ! this.providers[scalarName] ) { throw new ReferenceError ("Provider " + scalarName + " not registered with this MIB"); } providerNode = this.providerNodes[scalarName]; if ( ! providerNode ) { providerNode = this.addProviderToNode (this.providers[scalarName]); } provider = providerNode.provider; if ( ! providerNode || ! provider || provider.type != MibProviderType.Scalar ) { throw new ReferenceError ("Could not find MIB node for registered provider " + scalarName); } var instanceAddress = providerNode.address.concat ([0]); instanceNode = this.lookup (instanceAddress); if ( ! instanceNode ) { this.addNodesForAddress (instanceAddress); instanceNode = this.lookup (instanceAddress); instanceNode.valueType = provider.scalarType; } instanceNode.value = newValue; // return instanceNode.setValue (newValue); }; Mib.prototype.getProviderNodeForTable = function (table) { var providerNode; var provider; providerNode = this.providerNodes[table]; if ( ! providerNode ) { throw new ReferenceError ("No MIB provider registered for " + table); } provider = providerNode.provider; if ( ! providerNode ) { throw new ReferenceError ("No MIB provider definition for registered provider " + table); } if ( provider.type != MibProviderType.Table ) { throw new TypeError ("Registered MIB provider " + table + " is not of the correct type (is type " + MibProviderType[provider.type] + ")"); } return providerNode; }; Mib.prototype.getOidAddressFromValue = function (value, indexPart) { var oidComponents; switch ( indexPart.type ) { case ObjectType.OID: oidComponents = value.split ("."); break; case ObjectType.OctetString: if ( value instanceof Buffer ) { // Buffer oidComponents = Array.prototype.slice.call (value); } else { // string oidComponents = [...value].map (c => c.charCodeAt()); } break; case ObjectType.IpAddress: return value.split ("."); default: return [value]; } if ( ! indexPart.implied && ! indexPart.length ) { oidComponents.unshift (oidComponents.length); } return oidComponents; }; /* What is this empty function here for? Mib.prototype.getValueFromOidAddress = function (oid, indexPart) { }; */ Mib.prototype.getTableRowInstanceFromRow = function (provider, row) { var rowIndex = []; var foreignColumnParts; var localColumnParts; var localColumnPosition; var oidArrayForValue; // foreign columns are first in row foreignColumnParts = provider.tableIndex.filter ( indexPart => indexPart.foreign ); for ( var i = 0; i < foreignColumnParts.length ; i++ ) { //rowIndex.push (row[i]); oidArrayForValue = this.getOidAddressFromValue (row[i], foreignColumnParts[i]); rowIndex = rowIndex.concat (oidArrayForValue); } // then local columns localColumnParts = provider.tableIndex.filter ( indexPart => ! indexPart.foreign ); for ( var localColumnPart of localColumnParts ) { localColumnPosition = provider.tableColumns.findIndex (column => column.number == localColumnPart.columnNumber); oidArrayForValue = this.getOidAddressFromValue (row[foreignColumnParts.length + localColumnPosition], localColumnPart); rowIndex = rowIndex.concat (oidArrayForValue); } return rowIndex; }; Mib.getRowIndexFromOid = function (oid, index) { var addressRemaining = oid.split ("."); var length = 0; var values = []; var value; for ( var indexPart of index ) { switch ( indexPart.type ) { case ObjectType.OID: if ( indexPart.implied ) { length = addressRemaining.length; } else { length = addressRemaining.shift (); } value = addressRemaining.splice (0, length); values.push (value.join (".")); break; case ObjectType.IpAddress: length = 4; value = addressRemaining.splice (0, length); values.push (value.join (".")); break; case ObjectType.OctetString: if ( indexPart.implied ) { length = addressRemaining.length; } else { length = addressRemaining.shift (); } value = addressRemaining.splice (0, length); value = value.map (c => String.fromCharCode(c)).join (""); values.push (value); break; default: values.push (parseInt (addressRemaining.shift ()) ); } } return values; }; Mib.prototype.getTableRowInstanceFromRowIndex = function (provider, rowIndex) { var rowIndexOid = []; var indexPart; var keyPart; for ( var i = 0; i < provider.tableIndex.length ; i++ ) { indexPart = provider.tableIndex[i]; keyPart = rowIndex[i]; rowIndexOid = rowIndexOid.concat (this.getOidAddressFromValue (keyPart, indexPart)); } return rowIndexOid; }; Mib.prototype.addTableRow = function (table, row) { var providerNode; var provider; var instance = []; var instanceAddress; var instanceNode; var rowValueOffset; if ( this.providers[table] && ! this.providerNodes[table] ) { this.addProviderToNode (this.providers[table]); } providerNode = this.getProviderNodeForTable (table); provider = providerNode.provider; rowValueOffset = provider.tableIndex.filter ( indexPart => indexPart.foreign ).length; instance = this.getTableRowInstanceFromRow (provider, row); for ( var i = 0; i < provider.tableColumns.length ; i++ ) { var column = provider.tableColumns[i]; var isColumnIndex = provider.tableIndex.some ( indexPart => indexPart.columnNumber == column.number ); // prevent not-accessible and accessible-for-notify index entries from being added as columns in the row if ( ! isColumnIndex || ! (column.maxAccess === MaxAccess['not-accessible'] || column.maxAccess === MaxAccess['accessible-for-notify']) ) { instanceAddress = providerNode.address.concat (column.number).concat (instance); this.addNodesForAddress (instanceAddress); instanceNode = this.lookup (instanceAddress); instanceNode.valueType = column.type; instanceNode.value = row[rowValueOffset + i]; } } }; Mib.prototype.getTableColumnDefinitions = function (table) { var providerNode; var provider; providerNode = this.getProviderNodeForTable (table); provider = providerNode.provider; return provider.tableColumns; }; Mib.prototype.getTableColumnCells = function (table, columnNumber, includeInstances) { var provider = this.providers[table]; var providerIndex = provider.tableIndex; var providerNode = this.getProviderNodeForTable (table); var columnNode = providerNode.children[columnNumber]; if ( ! columnNode ) { return null; } var instanceNodes = columnNode.getInstanceNodesForColumn (); var instanceOid; var indexValues = []; var columnValues = []; for ( var instanceNode of instanceNodes ) { instanceOid = Mib.getSubOidFromBaseOid (instanceNode.oid, columnNode.oid); indexValues.push (Mib.getRowIndexFromOid (instanceOid, providerIndex)); columnValues.push (instanceNode.value); } if ( includeInstances ) { return [ indexValues, columnValues ]; } else { return columnValues; } }; Mib.prototype.getTableRowCells = function (table, rowIndex) { var provider; var providerNode; var columnNode; var instanceAddress; var instanceNode; var row = []; var rowFound = false; provider = this.providers[table]; providerNode = this.getProviderNodeForTable (table); instanceAddress = this.getTableRowInstanceFromRowIndex (provider, rowIndex); for ( var columnNumber of Object.keys (providerNode.children) ) { columnNode = providerNode.children[columnNumber]; if ( columnNode ) { instanceNode = columnNode.getInstanceNodeForTableRowIndex (instanceAddress); if ( instanceNode ) { row.push (instanceNode.value); rowFound = true; } else { row.push (null); } } else { row.push (null); } } if ( rowFound ) { return row; } else { return null; } }; Mib.prototype.getTableCells = function (table, byRows, includeInstances) { var providerNode; var column; var data = []; providerNode = this.getProviderNodeForTable (table); for ( var columnNumber of Object.keys (providerNode.children) ) { column = this.getTableColumnCells (table, columnNumber, includeInstances); if ( includeInstances ) { data.push (...column); includeInstances = false; } else { data.push (column); } } if ( byRows ) { return Object.keys (data[0]).map (function (c) { return data.map (function (r) { return r[c]; }); }); } else { return data; } }; Mib.prototype.getTableSingleCell = function (table, columnNumber, rowIndex) { var provider; var providerNode; var instanceAddress; var columnNode; var instanceNode; provider = this.providers[table]; providerNode = this.getProviderNodeForTable (table); instanceAddress = this.getTableRowInstanceFromRowIndex (provider, rowIndex); columnNode = providerNode.children[columnNumber]; instanceNode = columnNode.getInstanceNodeForTableRowIndex (instanceAddress); return instanceNode.value; }; Mib.prototype.setTableSingleCell = function (table, columnNumber, rowIndex, value) { var provider; var providerNode; var columnNode; var instanceNode; var instanceAddress; provider = this.providers[table]; providerNode = this.getProviderNodeForTable (table); instanceAddress = this.getTableRowInstanceFromRowIndex (provider, rowIndex); columnNode = providerNode.children[columnNumber]; instanceNode = columnNode.getInstanceNodeForTableRowIndex (instanceAddress); instanceNode.value = value; // return instanceNode.setValue (value); }; Mib.prototype.deleteTableRow = function (table, rowIndex) { var provider; var providerNode; var instanceAddress; var columnNode; var instanceNode; var instanceParentNode; provider = this.providers[table]; providerNode = this.getProviderNodeForTable (table); instanceAddress = this.getTableRowInstanceFromRowIndex (provider, rowIndex); for ( var columnNumber of Object.keys (providerNode.children) ) { columnNode = providerNode.children[columnNumber]; instanceNode = columnNode.getInstanceNodeForTableRowIndex (instanceAddress); if ( instanceNode ) { instanceParentNode = instanceNode.parent; instanceNode.delete(); instanceParentNode.pruneUpwards(); } else { throw new ReferenceError ("Cannot find row for index " + rowIndex + " at registered provider " + table); } } if ( Object.keys (this.providerNodes[table].children).length === 0 ) { delete this.providerNodes[table]; } return true; }; Mib.prototype.dump = function (options) { if ( ! options ) { options = {}; } var completedOptions = { leavesOnly: options.leavesOnly === undefined ? true : options.leavesOnly, showProviders: options.showProviders === undefined ? true : options.showProviders, showValues: options.showValues === undefined ? true : options.showValues, showTypes: options.showTypes === undefined ? true : options.showTypes }; this.root.dump (completedOptions); }; Mib.convertOidToAddress = function (oid) { var address; var oidArray; var i; if (typeof (oid) === 'object' && util.isArray(oid)) { address = oid; } else if (typeof (oid) === 'string') { address = oid.split('.'); } else { throw new TypeError('oid (string or array) is required'); } if (address.length < 1) throw new RangeError('object identifier is too short'); oidArray = []; for (i = 0; i < address.length; i++) { var n; if (address[i] === '') continue; if (address[i] === true || address[i] === false) { throw new TypeError('object identifier component ' + address[i] + ' is malformed'); } n = Number(address[i]); if (isNaN(n)) { throw new TypeError('object identifier component ' + address[i] + ' is malformed'); } if (n % 1 !== 0) { throw new TypeError('object identifier component ' + address[i] + ' is not an integer'); } if (i === 0 && n > 2) { throw new RangeError('object identifier does not ' + 'begin with 0, 1, or 2'); } if (i === 1 && n > 39) { throw new RangeError('object identifier second ' + 'component ' + n + ' exceeds encoding limit of 39'); } if (n < 0) { throw new RangeError('object identifier component ' + address[i] + ' is negative'); } if (n > MAX_SIGNED_INT32) { throw new RangeError('object identifier component ' + address[i] + ' is too large'); } oidArray.push(n); } return oidArray; }; Mib.getSubOidFromBaseOid = function (oid, base) { return oid.substring (base.length + 1); }; Mib.create = function () { return new Mib (); }; var MibRequest = function (requestDefinition) { this.operation = requestDefinition.operation; this.address = Mib.convertOidToAddress (requestDefinition.oid); this.oid = this.address.join ('.'); this.providerNode = requestDefinition.providerNode; this.instanceNode = requestDefinition.instanceNode; }; MibRequest.prototype.isScalar = function () { return this.providerNode && this.providerNode.provider && this.providerNode.provider.type == MibProviderType.Scalar; }; MibRequest.prototype.isTabular = function () { return this.providerNode && this.providerNode.provider && this.providerNode.provider.type == MibProviderType.Table; }; var Agent = function (options, callback, mib) { DEBUG = options.debug; this.listener = new Listener (options, this); this.engine = new Engine (options.engineID); this.authorizer = new Authorizer (options); this.callback = callback || function () {}; this.mib = mib || new Mib (); this.context = ""; this.forwarder = new Forwarder (this.listener, this.callback); }; Agent.prototype.getMib = function () { return this.mib; }; Agent.prototype.setMib = function (mib) { this.mib = mib; }; Agent.prototype.getAuthorizer = function () { return this.authorizer; }; Agent.prototype.registerProvider = function (provider) { this.mib.registerProvider (provider); }; Agent.prototype.registerProviders = function (providers) { this.mib.registerProviders (providers); }; Agent.prototype.unregisterProvider = function (name) { this.mib.unregisterProvider (name); }; Agent.prototype.getProvider = function (name) { return this.mib.getProvider (name); }; Agent.prototype.getProviders = function () { return this.mib.getProviders (); }; Agent.prototype.scalarReadCreateHandlerInternal = function (createRequest) { let provider = createRequest.provider; // If there's a default value specified... if ( provider && typeof provider.defVal != "undefined" ) { // ... then use it return provider.defVal; } // We don't have enough information to auto-create the scalar return undefined; }; Agent.prototype.tableRowStatusHandlerInternal = function (createRequest) { let provider = createRequest.provider; let action = createRequest.action; let row = createRequest.row; let values = []; let missingDefVal = false; let rowIndexValues = Array.isArray( row ) ? row.slice(0) : [ row ]; const tc = provider.tableColumns; tc.forEach( (columnInfo) => { let entries; // Index columns get successive values from the rowIndexValues array. // RowStatus columns get either "active" or "notInService" values. // Every other column requires a defVal. entries = provider.tableIndex.filter( entry => columnInfo.number === entry.columnNumber ); if (entries.length > 0 ) { // It's an index column. Use the next index value values.push(rowIndexValues.shift()); } else if ( columnInfo.rowStatus ) { // It's the RowStatus column. Retain the action value for now; replaced later values.push( RowStatus[action] ); } else if ( "defVal" in columnInfo ) { // Neither index nor RowStatus column, so use the default value values.push( columnInfo.defVal ); } else { // Default value was required but not found console.log("No defVal defined for column:", columnInfo); missingDefVal = true; values.push( undefined ); // just for debugging; never gets returned } } ); // If a default value was missing, we can't auto-create the table row. // Otherwise, we're good to go: give 'em the column values. return missingDefVal ? undefined : values; }; Agent.prototype.onMsg = function (buffer, rinfo) { var message = Listener.processIncoming (buffer, this.authorizer, this.callback); var reportMessage; if ( ! message ) { return; } // SNMPv3 discovery if ( message.version == Version3 && message.pdu.type == PduType.GetRequest && ! message.hasAuthoritativeEngineID() && message.isReportable () ) { reportMessage = message.createReportResponseMessage (this.engine, this.context); this.listener.send (reportMessage, rinfo); return; } // Request processing // debug (JSON.stringify (message.pdu, null, 2)); if ( message.pdu.contextName && message.pdu.contextName != "" ) { this.onProxyRequest (message, rinfo); } else if ( message.pdu.type == PduType.GetRequest ) { this.getRequest (message, rinfo); } else if ( message.pdu.type == PduType.SetRequest ) { this.setRequest (message, rinfo); } else if ( message.pdu.type == PduType.GetNextRequest ) { this.getNextRequest (message, rinfo); } else if ( message.pdu.type == PduType.GetBulkRequest ) { this.getBulkRequest (message, rinfo); } else { this.callback (new RequestInvalidError ("Unexpected PDU type " + message.pdu.type + " (" + PduType[message.pdu.type] + ")")); } }; Agent.prototype.castSetValue = function ( type, value ) { switch (type) { case ObjectType.Boolean: return !! value; case ObjectType.Integer: if ( typeof value != "number" && typeof value != "string" ) { throw new Error("Invalid Integer", value); } return typeof value == "number" ? value : parseInt(value, 10); case ObjectType.OctetString: if ( value instanceof Buffer) { return value.toString(); } else if ( typeof value != "string" ) { throw new Error("Invalid OctetString", value); } else { return value; } case ObjectType.OID: if ( typeof value != "string" || ! value.match(/[0-9]+\([.][0-9]+\)+/) ) { throw new Error("Invalid OID", value); } return value; case ObjectType.Counter: case ObjectType.Counter64: // Counters should be initialized to 0 (RFC2578, end of section 7.9) // We'll do so. return 0; case ObjectType.IpAddress: // A 32-bit internet address represented as OCTET STRING of length 4 if ( typeof value != "string" || value.length != 4 ) { throw new Error("Invalid IpAddress", value); } return value; default : // Assume the caller knows what he's doing return value; } }; Agent.prototype.tryCreateInstance = function (varbind, requestType) { var row; var column; var value; var subOid; var subAddr; var address; var fullAddress; var rowStatusColumn; var provider; var providersByOid = this.mib.providersByOid; var oid = varbind.oid; var createRequest; // Look for the provider. fullAddress = Mib.convertOidToAddress (oid); for ( address = fullAddress.slice(0) ; address.length > 0; address.pop() ) { subOid = address.join("."); // create an oid from the current address // Does this oid have a provider? provider = providersByOid[subOid]; if (provider) { // Yup. Figure out what to do with it. // console.log(`FOUND MATCH TO ${oid}:\n`, providersByOid[subOid]); // // Scalar // if ( provider.type === MibProviderType.Scalar ) { // Does this provider support "read-create"? if ( provider.maxAccess != MaxAccess["read-create"] ) { // Nope. Nothing we can do to help 'em. return undefined; } // See if the provider says not to auto-create this scalar if ( provider.createHandler === null ) { return undefined; } // Call the provider-provided handler if available, or the default one if not createRequest = { provider: provider }; value = ( provider.createHandler || this.scalarReadCreateHandlerInternal ) ( createRequest ); if ( typeof value == "undefined" ) { // Handler said do not create instance return undefined; } // Ensure the value is of the correct type, and save it value = this.castSetValue ( provider.scalarType, value ); this.mib.setScalarValue ( provider.name, value ); // Now there should be an instanceNode available. return { instanceNode: this.mib.lookup (oid), providerType: MibProviderType.Scalar }; } // // Table // // This is where we would support "read-create" of table // columns. RFC2578 section 7.1.12.1, however, implies // that rows should be created only via use of the // RowStatus column. We'll therefore avoid creating rows // based solely on any other column's "read-create" // max-access value. // // RowStatus setter (actions) // subOid = Mib.getSubOidFromBaseOid (oid, provider.oid); subAddr = subOid.split("."); column = parseInt(subAddr.shift(), 10); row = Mib.getRowIndexFromOid(subAddr.join("."), provider.tableIndex); rowStatusColumn = provider.tableColumns.reduce( (acc, current) => current.rowStatus ? current.number : acc, null ); if ( requestType === PduType.SetRequest && typeof rowStatusColumn == "number" && column === rowStatusColumn ) { if ( (varbind.value === RowStatus["createAndGo"] || varbind.value === RowStatus["createAndWait"]) && provider.createHandler !== null ) { // The create handler will return an array // containing all table column values for the // table row to be added. createRequest = { provider: provider, action: RowStatus[varbind.value], row: row }; value = ( provider.createHandler || this.tableRowStatusHandlerInternal )( createRequest ); if ( typeof value == "undefined") { // Handler said do not create instance return undefined; } if (! Array.isArray( value ) ) { throw new Error("createHandler must return an array or undefined; got", value); } if ( value.length != provider.tableColumns.length ) { throw new Error("createHandler's returned array must contain a value for for each column" ); } // Map each column's value to the appropriate type value = value.map( (v, i) => this.castSetValue ( provider.tableColumns[i].type, v ) ); // Add the table row this.mib.addTableRow ( provider.name, value ); // Now there should be an instanceNode available. return { instanceNode: this.mib.lookup (oid), providerType: MibProviderType.Table, action: RowStatus[varbind.value], rowIndex: row, row: value }; } } return undefined; } } // console.log(`NO MATCH TO ${oid}`); return undefined; }; Agent.prototype.isAllowed = function (pduType, provider, instanceNode) { var column; var maxAccess; var columnEntry; if (provider.type === MibProviderType.Scalar) { // It's a scalar. We'll use the provider's maxAccess maxAccess = provider.maxAccess; } else { // It's a table column. Use that column's maxAccess. column = instanceNode.getTableColumnFromInstanceNode(); // In the typical case, we could use (column - 1) to index // into tableColumns to get to the correct entry. There is no // guarantee, however, that column numbers in the OID are // necessarily consecutive; theoretically some could be // missing. We'll therefore play it safe and search for the // specified column entry. columnEntry = provider.tableColumns.find(entry => entry.number === column); maxAccess = columnEntry ? columnEntry.maxAccess || MaxAccess['not-accessible'] : MaxAccess['not-accessible']; } switch ( PduType[pduType] ) { case "SetRequest": // SetRequest requires at least read-write access return maxAccess >= MaxAccess["read-write"]; case "GetRequest": case "GetNextRequest": case "GetBulkRequest": // GetRequests require at least read-only access return maxAccess >= MaxAccess["read-only"]; default: // Disallow other pdu types return false; } }; Agent.prototype.request = function (requestMessage, rinfo) { var me = this; var varbindsCompleted = 0; var requestPdu = requestMessage.pdu; var varbindsLength = requestPdu.varbinds.length; var responsePdu = requestPdu.getResponsePduForRequest (); var mibRequests = []; var handlers = []; var createResult = []; var oldValues = []; var securityName = requestMessage.version == Version3 ? requestMessage.user.name : requestMessage.community; for ( let i = 0; i < requestPdu.varbinds.length; i++ ) { let instanceNode = this.mib.lookup (requestPdu.varbinds[i].oid); let providerNode; let rowStatusColumn; let getIcsHandler; // If we didn't find an instance node, see if we can // automatically create it, either because it has // "read-create" MAX-ACCESS, or because it's a RowStatus SET // indicating create. if ( ! instanceNode ) { createResult[i] = this.tryCreateInstance(requestPdu.varbinds[i], requestPdu.type); if ( createResult[i] ) { instanceNode = createResult[i].instanceNode; } } // workaround re-write of OIDs less than 4 digits due to asn1-ber length limitation if ( requestPdu.varbinds[i].oid.split('.').length < 4 ) { requestPdu.varbinds[i].oid = "1.3.6.1"; } if ( ! instanceNode ) { mibRequests[i] = new MibRequest ({ operation: requestPdu.type, oid: requestPdu.varbinds[i].oid }); handlers[i] = function getNsoHandler (mibRequestForNso) { mibRequestForNso.done ({ errorStatus: ErrorStatus.NoError, type: ObjectType.NoSuchObject, value: null }); }; } else { providerNode = this.mib.getProviderNodeForInstance (instanceNode); if ( ! providerNode || instanceNode.value === undefined ) { mibRequests[i] = new MibRequest ({ operation: requestPdu.type, oid: requestPdu.varbinds[i].oid }); handlers[i] = function getNsiHandler (mibRequestForNsi) { mibRequestForNsi.done ({ errorStatus: ErrorStatus.NoError, type: ObjectType.NoSuchInstance, value: null }); }; } else if ( ! this.isAllowed(requestPdu.type, providerNode.provider, instanceNode ) ) { // requested access not allowed (by MAX-ACCESS) mibRequests[i] = new MibRequest ({ operation: requestPdu.type, oid: requestPdu.varbinds[i].oid }); handlers[i] = function getRanaHandler (mibRequestForRana) { mibRequestForRana.done ({ errorStatus: ErrorStatus.NoAccess, type: ObjectType.Null, value: null }); }; } else if ( this.authorizer.getAccessControlModelType () == AccessControlModelType.Simple && ! this.authorizer.getAccessControlModel ().isAccessAllowed (requestMessage.version, securityName, requestMessage.pdu.type) ) { // Access control check mibRequests[i] = new MibRequest ({ operation: requestPdu.type, oid: requestPdu.varbinds[i].oid }); handlers[i] = function getAccessDeniedHandler (mibRequestForAccessDenied) { mibRequestForAccessDenied.done ({ errorStatus: ErrorStatus.NoAccess, type: ObjectType.Null, value: null }); }; } else if ( requestPdu.type === PduType.SetRequest && providerNode.provider.type == MibProviderType.Table && typeof (rowStatusColumn = providerNode.provider.tableColumns.reduce( (acc, current) => current.rowStatus ? current.number : acc, null )) == "number" && instanceNode.getTableColumnFromInstanceNode() === rowStatusColumn) { getIcsHandler = function (mibRequestForIcs) { mibRequestForIcs.done ({ errorStatus: ErrorStatus.InconsistentValue, type: ObjectType.Null, value: null }); }; requestPdu.varbinds[i].requestValue = this.castSetValue (requestPdu.varbinds[i].type, requestPdu.varbinds[i].value); switch ( requestPdu.varbinds[i].value ) { case RowStatus["active"]: case RowStatus["notInService"]: // Setting either of these states, when the // row already exists, is fine break; case RowStatus["destroy"]: // This case is handled later break; case RowStatus["createAndGo"]: // Valid if this was a new row creation, but now set to active if ( instanceNode.value === RowStatus["createAndGo"] ) { requestPdu.varbinds[i].value = RowStatus["active"]; } else { // Row already existed mibRequests[i] = new MibRequest ({ operation: requestPdu.type, oid: requestPdu.varbinds[i].oid }); handlers[i] = getIcsHandler; } break; case RowStatus["createAndWait"]: // Valid if this was a new row creation, but now set to notInService if ( instanceNode.value === RowStatus["createAndWait"] ) { requestPdu.varbinds[i].value = RowStatus["notInService"]; } else { // Row already existed mibRequests[i] = new MibRequest ({ operation: requestPdu.type, oid: requestPdu.varbinds[i].oid }); handlers[i] = getIcsHandler; } break; case RowStatus["notReady"]: default: // It's not ever legal to set the RowStatus to // any value but the six that are defined, and // it's not legal to change the state to // "notReady". // // The row already exists, as determined by // the fact that we have an instanceNode, so // we can not apply a create action to the // RowStatus column, as dictated RFC-2579. // (See the summary state table on Page 8 // (inconsistent value) mibRequests[i] = new MibRequest ({ operation: requestPdu.type, oid: requestPdu.varbinds[i].oid }); handlers[i] = getIcsHandler; break; } } if ( requestPdu.type === PduType.SetRequest && ! createResult[i] ) { oldValues[i] = instanceNode.value; } if ( ! handlers[i] ) { mibRequests[i] = new MibRequest ({ operation: requestPdu.type, providerNode: providerNode, instanceNode: instanceNode, oid: requestPdu.varbinds[i].oid }); if ( requestPdu.type == PduType.SetRequest ) { mibRequests[i].setType = requestPdu.varbinds[i].type; mibRequests[i].setValue = requestPdu.varbinds[i].requestValue || requestPdu.varbinds[i].value; } handlers[i] = providerNode.provider.handler; } } (function (savedIndex) { let responseVarbind; mibRequests[savedIndex].done = function (error) { let rowIndex = null; let row = null; let deleted = false; let column = -1; responseVarbind = { oid: mibRequests[savedIndex].oid }; if ( error ) { if ( (typeof responsePdu.errorStatus == "undefined" || responsePdu.errorStatus == ErrorStatus.NoError) && error.errorStatus != ErrorStatus.NoError ) { responsePdu.errorStatus = error.errorStatus; responsePdu.errorIndex = savedIndex + 1; } responseVarbind.type = error.type || ObjectType.Null; responseVarbind.value = error.value || null; //responseVarbind.errorStatus: error.errorStatus if ( error.errorStatus != ErrorStatus.NoError ) { responseVarbind.errorStatus = error.errorStatus; } } else { let provider = providerNode ? providerNode.provider : null; let providerName = provider ? provider.name : null; let subOid; let subAddr; if ( providerNode && providerNode.provider && providerNode.provider.type == MibProviderType.Table ) { column = instanceNode.getTableColumnFromInstanceNode(); subOid = Mib.getSubOidFromBaseOid (instanceNode.oid, provider.oid); subAddr = subOid.split("."); subAddr.shift(); // shift off the column number, leaving the row index values rowIndex = Mib.getRowIndexFromOid( subAddr.join("."), provider.tableIndex ); row = me.mib.getTableRowCells ( providerName, rowIndex ); } if ( requestPdu.type == PduType.SetRequest ) { // Is this a RowStatus column with a value of 6 (delete)? let rowStatusColumn = provider.type == MibProviderType.Table ? provider.tableColumns.reduce( (acc, current) => current.rowStatus ? current.number : acc, null ) : null; if ( requestPdu.varbinds[savedIndex].value === RowStatus["destroy"] && typeof rowStatusColumn == "number" && column === rowStatusColumn ) { // Yup. Do the deletion. me.mib.deleteTableRow ( providerName, rowIndex ); deleted = true; // This is going to return the prior state of the RowStatus column, // i.e., either "active" or "notInService". That feels wrong, but there // is no value we can set it to to indicate just-deleted. One would // think we could set it to "notReady", but that is explicitly defined // in RFC-2579 as "the conceptual row exists in the agent", which is // no longer the case now that we've deleted the row. We're not allowed // to ever return "destroy" as a status, so that doesn't give us an // option either. } else { // No special handling required. Just save the new value. let setResult = mibRequests[savedIndex].instanceNode.setValue (me.castSetValue ( requestPdu.varbinds[savedIndex].type, requestPdu.varbinds[savedIndex].value )); if ( ! setResult ) { if ( typeof responsePdu.errorStatus == "undefined" || responsePdu.errorStatus == ErrorStatus.NoError ) { responsePdu.errorStatus = ErrorStatus.WrongValue; responsePdu.errorIndex = savedIndex + 1; } responseVarbind.errorStatus = ErrorStatus.WrongValue; } } } if ( ( requestPdu.type == PduType.GetNextRequest || requestPdu.type == PduType.GetBulkRequest ) && requestPdu.varbinds[savedIndex].type == ObjectType.EndOfMibView ) { responseVarbind.type = ObjectType.EndOfMibView; } else { responseVarbind.type = mibRequests[savedIndex].instanceNode.valueType; } responseVarbind.value = mibRequests[savedIndex].instanceNode.value; } if ( providerNode && providerNode.provider && providerNode.provider.name ) { responseVarbind.providerName = providerNode.provider.name; } if ( requestPdu.type == PduType.GetNextRequest || requestPdu.type == PduType.GetNextRequest ) { responseVarbind.previousOid = requestPdu.varbinds[savedIndex].previousOid; } if ( requestPdu.type == PduType.SetRequest ) { if ( oldValues[savedIndex] !== undefined ) { responseVarbind.oldValue = oldValues[savedIndex]; } responseVarbind.requestType = requestPdu.varbinds[savedIndex].type; if ( requestPdu.varbinds[savedIndex].requestValue ) { responseVarbind.requestValue = me.castSetValue (requestPdu.varbinds[savedIndex].type, requestPdu.varbinds[savedIndex].requestValue); } else { responseVarbind.requestValue = me.castSetValue (requestPdu.varbinds[savedIndex].type, requestPdu.varbinds[savedIndex].value); } } if ( createResult[savedIndex] ) { responseVarbind.autoCreated = true; } else if ( deleted ) { responseVarbind.deleted = true; } if ( providerNode && providerNode.provider.type == MibProviderType.Table ) { responseVarbind.column = column; responseVarbind.columnPosition = providerNode.provider.tableColumns.findIndex(tc => tc.number == column); responseVarbind.rowIndex = rowIndex; if ( ! deleted && rowIndex ) { row = me.mib.getTableRowCells ( providerNode.provider.name, rowIndex ); } responseVarbind.row = row; } me.setSingleVarbind (responsePdu, savedIndex, responseVarbind); if ( ++varbindsCompleted == varbindsLength) { me.sendResponse.call (me, rinfo, requestMessage, responsePdu); } }; })(i); if ( handlers[i] ) { handlers[i] (mibRequests[i]); } else { mibRequests[i].done (); } } }; Agent.prototype.getRequest = function (requestMessage, rinfo) { this.request (requestMessage, rinfo); }; Agent.prototype.setRequest = function (requestMessage, rinfo) { this.request (requestMessage, rinfo); }; Agent.prototype.addGetNextVarbind = function (targetVarbinds, startOid) { var startNode; var getNextNode; try { startNode = this.mib.lookup (startOid); } catch ( error ) { startOid = '1.3.6.1'; startNode = this.mib.lookup (startOid); } if ( ! startNode ) { // Off-tree start specified startNode = this.mib.getTreeNode (startOid); } getNextNode = startNode.getNextInstanceNode(); if ( ! getNextNode ) { // End of MIB targetVarbinds.push ({ previousOid: startOid, oid: startOid, type: ObjectType.EndOfMibView, value: null }); } else { // Normal response targetVarbinds.push ({ previousOid: startOid, oid: getNextNode.oid, type: getNextNode.valueType, value: getNextNode.value }); } return getNextNode; }; Agent.prototype.getNextRequest = function (requestMessage, rinfo) { var requestPdu = requestMessage.pdu; var varbindsLength = requestPdu.varbinds.length; var getNextVarbinds = []; for (var i = 0 ; i < varbindsLength ; i++ ) { this.addGetNextVarbind (getNextVarbinds, requestPdu.varbinds[i].oid); } requestMessage.pdu.varbinds = getNextVarbinds; this.request (requestMessage, rinfo); }; Agent.prototype.getBulkRequest = function (requestMessage, rinfo) { var requestPdu = requestMessage.pdu; var requestVarbinds = requestPdu.varbinds; var getBulkVarbinds = []; var startOid = []; var getNextNode; var endOfMib = false; for (var n = 0 ; n < Math.min (requestPdu.nonRepeaters, requestVarbinds.length) ; n++ ) { this.addGetNextVarbind (getBulkVarbinds, requestVarbinds[n].oid); } if ( requestPdu.nonRepeaters < requestVarbinds.length ) { for (var v = requestPdu.nonRepeaters ; v < requestVarbinds.length ; v++ ) { startOid.push (requestVarbinds[v].oid); } while ( getBulkVarbinds.length < requestPdu.maxRepetitions && ! endOfMib ) { for (var w = requestPdu.nonRepeaters ; w < requestVarbinds.length ; w++ ) { if (getBulkVarbinds.length < requestPdu.maxRepetitions ) { getNextNode = this.addGetNextVarbind (getBulkVarbinds, startOid[w - requestPdu.nonRepeaters]); if ( getNextNode ) { startOid[w - requestPdu.nonRepeaters] = getNextNode.oid; if ( getNextNode.type == ObjectType.EndOfMibView ) { endOfMib = true; } } } } } } requestMessage.pdu.varbinds = getBulkVarbinds; this.request (requestMessage, rinfo); }; Agent.prototype.setSingleVarbind = function (responsePdu, index, responseVarbind) { responsePdu.varbinds[index] = responseVarbind; }; Agent.prototype.sendResponse = function (rinfo, requestMessage, responsePdu) { var responseMessage = requestMessage.createResponseForRequest (responsePdu); this.listener.send (responseMessage, rinfo); this.callback (null, Listener.formatCallbackData (responseMessage.pdu, rinfo) ); }; Agent.prototype.onProxyRequest = function (message, rinfo) { var contextName = message.pdu.contextName; var proxy; var proxiedPduId; var proxiedUser; if ( message.version != Version3 ) { this.callback (new RequestFailedError ("Only SNMP version 3 contexts are supported")); return; } proxy = this.forwarder.getProxy (contextName); if ( ! proxy ) { this.callback (new RequestFailedError ("No proxy found for message received with context " + contextName)); return; } if ( ! proxy.session.msgSecurityParameters ) { // Discovery required - but chaining not implemented from here yet proxy.session.sendV3Discovery (null, null, this.callback, {}); } else { message.msgSecurityParameters = proxy.session.msgSecurityParameters; message.setAuthentication ( ! (proxy.user.level == SecurityLevel.noAuthNoPriv)); message.setPrivacy (proxy.user.level == SecurityLevel.authPriv); proxiedUser = message.user; message.user = proxy.user; message.buffer = null; message.pdu.contextEngineID = proxy.session.msgSecurityParameters.msgAuthoritativeEngineID; message.pdu.contextName = ""; proxiedPduId = message.pdu.id; message.pdu.id = _generateId (); var req = new Req (proxy.session, message, null, this.callback, {}, true); req.port = proxy.port; req.proxiedRinfo = rinfo; req.proxiedPduId = proxiedPduId; req.proxiedUser = proxiedUser; req.proxiedEngine = this.engine; proxy.session.send (req); } }; Agent.prototype.getForwarder = function () { return this.forwarder; }; Agent.prototype.close = function() { this.listener.close (); }; Agent.create = function (options, callback, mib) { var agent = new Agent (options, callback, mib); agent.listener.startListening (); return agent; }; var Forwarder = function (listener, callback) { this.proxies = {}; this.listener = listener; this.callback = callback; }; Forwarder.prototype.addProxy = function (proxy) { var options = { version: Version3, port: proxy.port, transport: proxy.transport }; proxy.session = Session.createV3 (proxy.target, proxy.user, options); proxy.session.proxy = proxy; proxy.session.proxy.listener = this.listener; this.proxies[proxy.context] = proxy; proxy.session.sendV3Discovery (null, null, this.callback); }; Forwarder.prototype.deleteProxy = function (proxyName) { var proxy = this.proxies[proxyName]; if ( proxy && proxy.session ) { proxy.session.close (); } delete this.proxies[proxyName]; }; Forwarder.prototype.getProxy = function (proxyName) { return this.proxies[proxyName]; }; Forwarder.prototype.getProxies = function () { return this.proxies; }; Forwarder.prototype.dumpProxies = function () { var dump = {}; for ( var proxy of Object.values (this.proxies) ) { dump[proxy.context] = { context: proxy.context, target: proxy.target, user: proxy.user, port: proxy.port }; } console.log (JSON.stringify (dump, null, 2)); }; var AgentXPdu = function () { }; AgentXPdu.prototype.toBuffer = function () { var buffer = new smartbuffer.SmartBuffer(); this.writeHeader (buffer); switch ( this.pduType ) { case AgentXPduType.Open: buffer.writeUInt32BE (this.timeout); AgentXPdu.writeOid (buffer, this.oid); AgentXPdu.writeOctetString (buffer, this.descr); break; case AgentXPduType.Close: buffer.writeUInt8 (5); // reasonShutdown == 5 buffer.writeUInt8 (0); // 3 x reserved bytes buffer.writeUInt8 (0); buffer.writeUInt8 (0); break; case AgentXPduType.Register: buffer.writeUInt8 (this.timeout); buffer.writeUInt8 (this.priority); buffer.writeUInt8 (this.rangeSubid); buffer.writeUInt8 (0); AgentXPdu.writeOid (buffer, this.oid); break; case AgentXPduType.Unregister: buffer.writeUInt8 (0); // reserved buffer.writeUInt8 (this.priority); buffer.writeUInt8 (this.rangeSubid); buffer.writeUInt8 (0); // reserved AgentXPdu.writeOid (buffer, this.oid); break; case AgentXPduType.AddAgentCaps: AgentXPdu.writeOid (buffer, this.oid); AgentXPdu.writeOctetString (buffer, this.descr); break; case AgentXPduType.RemoveAgentCaps: AgentXPdu.writeOid (buffer, this.oid); break; case AgentXPduType.Notify: AgentXPdu.writeVarbinds (buffer, this.varbinds); break; case AgentXPduType.Ping: break; case AgentXPduType.Response: buffer.writeUInt32BE (this.sysUpTime); buffer.writeUInt16BE (this.error); buffer.writeUInt16BE (this.index); AgentXPdu.writeVarbinds (buffer, this.varbinds); break; default: // unknown PDU type - should never happen as we control these } buffer.writeUInt32BE (buffer.length - 20, 16); return buffer.toBuffer (); }; AgentXPdu.prototype.writeHeader = function (buffer) { this.flags = this.flags | 0x10; // set NETWORK_BYTE_ORDER buffer.writeUInt8 (1); // h.version = 1 buffer.writeUInt8 (this.pduType); buffer.writeUInt8 (this.flags); buffer.writeUInt8 (0); // reserved byte buffer.writeUInt32BE (this.sessionID); buffer.writeUInt32BE (this.transactionID); buffer.writeUInt32BE (this.packetID); buffer.writeUInt32BE (0); return buffer; }; AgentXPdu.prototype.readHeader = function (buffer) { this.version = buffer.readUInt8 (); this.pduType = buffer.readUInt8 (); this.flags = buffer.readUInt8 (); buffer.readUInt8 (); // reserved byte this.sessionID = buffer.readUInt32BE (); this.transactionID = buffer.readUInt32BE (); this.packetID = buffer.readUInt32BE (); this.payloadLength = buffer.readUInt32BE (); }; AgentXPdu.createFromVariables = function (vars) { var pdu = new AgentXPdu (); pdu.flags = vars.flags ? vars.flags | 0x10 : 0x10; // set NETWORK_BYTE_ORDER to big endian pdu.pduType = vars.pduType || AgentXPduType.Open; pdu.sessionID = vars.sessionID || 0; pdu.transactionID = vars.transactionID || 0; pdu.packetID = vars.packetID || ++AgentXPdu.packetID; switch ( pdu.pduType ) { case AgentXPduType.Open: pdu.timeout = vars.timeout || 0; pdu.oid = vars.oid || null; pdu.descr = vars.descr || null; break; case AgentXPduType.Close: break; case AgentXPduType.Register: pdu.timeout = vars.timeout || 0; pdu.oid = vars.oid || null; pdu.priority = vars.priority || 127; pdu.rangeSubid = vars.rangeSubid || 0; break; case AgentXPduType.Unregister: pdu.oid = vars.oid || null; pdu.priority = vars.priority || 127; pdu.rangeSubid = vars.rangeSubid || 0; break; case AgentXPduType.AddAgentCaps: pdu.oid = vars.oid; pdu.descr = vars.descr; break; case AgentXPduType.RemoveAgentCaps: pdu.oid = vars.oid; break; case AgentXPduType.Notify: pdu.varbinds = vars.varbinds; break; case AgentXPduType.Ping: break; case AgentXPduType.Response: pdu.sysUpTime = vars.sysUpTime || 0; pdu.error = vars.error || 0; pdu.index = vars.index || 0; pdu.varbinds = vars.varbinds || null; break; default: // unsupported PDU type - should never happen as we control these throw new RequestInvalidError ("Unknown PDU type '" + pdu.pduType + "' in created PDU"); } return pdu; }; AgentXPdu.createFromBuffer = function (socketBuffer) { var pdu = new AgentXPdu (); var buffer = smartbuffer.SmartBuffer.fromBuffer (socketBuffer); pdu.readHeader (buffer); switch ( pdu.pduType ) { case AgentXPduType.Response: pdu.sysUpTime = buffer.readUInt32BE (); pdu.error = buffer.readUInt16BE (); pdu.index = buffer.readUInt16BE (); break; case AgentXPduType.Get: case AgentXPduType.GetNext: pdu.searchRangeList = AgentXPdu.readSearchRangeList (buffer, pdu.payloadLength); break; case AgentXPduType.GetBulk: pdu.nonRepeaters = buffer.readUInt16BE (); pdu.maxRepetitions = buffer.readUInt16BE (); pdu.searchRangeList = AgentXPdu.readSearchRangeList (buffer, pdu.payloadLength - 4); break; case AgentXPduType.TestSet: pdu.varbinds = AgentXPdu.readVarbinds (buffer, pdu.payloadLength); break; case AgentXPduType.CommitSet: case AgentXPduType.UndoSet: case AgentXPduType.CleanupSet: break; default: // Unknown PDU type - shouldn't happen as master agents shouldn't send administrative PDUs throw new RequestInvalidError ("Unknown PDU type '" + pdu.pduType + "' in request"); } return pdu; }; AgentXPdu.writeOid = function (buffer, oid, include = 0) { var prefix; if ( oid ) { var address = oid.split ('.').map ( Number ); if ( address.length >= 5 && address.slice (0, 4).join('.') == '1.3.6.1' ) { prefix = address[4]; address = address.slice(5); } else { prefix = 0; } buffer.writeUInt8 (address.length); buffer.writeUInt8 (prefix); buffer.writeUInt8 (include); buffer.writeUInt8 (0); // reserved for ( let addressPart of address ) { buffer.writeUInt32BE (addressPart); } } else { buffer.writeUInt32BE (0); // row of zeros for null OID } }; AgentXPdu.writeOctetString = function (buffer, octetString) { buffer.writeUInt32BE (octetString.length); buffer.writeString (octetString); var paddingOctets = ( 4 - octetString.length % 4 ) % 4; for ( let i = 0; i < paddingOctets ; i++ ) { buffer.writeUInt8 (0); } }; AgentXPdu.writeVarBind = function (buffer, varbind) { buffer.writeUInt16BE (varbind.type); buffer.writeUInt16BE (0); // reserved AgentXPdu.writeOid (buffer, varbind.oid); if (varbind.type && varbind.oid) { switch (varbind.type) { case ObjectType.Integer: // also Integer32 case ObjectType.Counter: // also Counter32 case ObjectType.Gauge: // also Gauge32 & Unsigned32 case ObjectType.TimeTicks: buffer.writeUInt32BE (varbind.value); break; case ObjectType.OctetString: case ObjectType.Opaque: AgentXPdu.writeOctetString (buffer, varbind.value); break; case ObjectType.OID: AgentXPdu.writeOid (buffer, varbind.value); break; case ObjectType.IpAddress: var bytes = varbind.value.split ("."); if (bytes.length != 4) throw new RequestInvalidError ("Invalid IP address '" + varbind.value + "'"); buffer.writeOctetString (buffer, Buffer.from (bytes)); break; case ObjectType.Counter64: buffer.writeUint64 (varbind.value); break; case ObjectType.Null: case ObjectType.EndOfMibView: case ObjectType.NoSuchObject: case ObjectType.NoSuchInstance: break; default: // Unknown data type - should never happen as the above covers all types in RFC 2741 Section 5.4 throw new RequestInvalidError ("Unknown type '" + varbind.type + "' in request"); } } }; AgentXPdu.writeVarbinds = function (buffer, varbinds) { if ( varbinds ) { for ( var i = 0; i < varbinds.length ; i++ ) { var varbind = varbinds[i]; AgentXPdu.writeVarBind(buffer, varbind); } } }; AgentXPdu.readOid = function (buffer) { var subidLength = buffer.readUInt8 (); var prefix = buffer.readUInt8 (); var include = buffer.readUInt8 (); buffer.readUInt8 (); // reserved // Null OID check if ( subidLength == 0 && prefix == 0 && include == 0) { return null; } var address = []; if ( prefix == 0 ) { address = []; } else { address = [1, 3, 6, 1, prefix]; } for ( let i = 0; i < subidLength; i++ ) { address.push (buffer.readUInt32BE ()); } var oid = address.join ('.'); return oid; }; AgentXPdu.readSearchRange = function (buffer) { return { start: AgentXPdu.readOid (buffer), end: AgentXPdu.readOid (buffer) }; }; AgentXPdu.readSearchRangeList = function (buffer, payloadLength) { var bytesLeft = payloadLength; var bufferPosition = (buffer.readOffset + 1); var searchRangeList = []; while (bytesLeft > 0) { searchRangeList.push (AgentXPdu.readSearchRange (buffer)); bytesLeft -= (buffer.readOffset + 1) - bufferPosition; bufferPosition = buffer.readOffset + 1; } return searchRangeList; }; AgentXPdu.readOctetString = function (buffer) { var octetStringLength = buffer.readUInt32BE (); var paddingOctets = ( 4 - octetStringLength % 4 ) % 4; var octetString = buffer.readString (octetStringLength); buffer.readString (paddingOctets); return octetString; }; AgentXPdu.readVarbind = function (buffer) { var vtype = buffer.readUInt16BE (); buffer.readUInt16BE (); // reserved var oid = AgentXPdu.readOid (buffer); var value; switch (vtype) { case ObjectType.Integer: case ObjectType.Counter: case ObjectType.Gauge: case ObjectType.TimeTicks: value = buffer.readUInt32BE (); break; case ObjectType.OctetString: case ObjectType.IpAddress: case ObjectType.Opaque: value = AgentXPdu.readOctetString (buffer); break; case ObjectType.OID: value = AgentXPdu.readOid (buffer); break; case ObjectType.Counter64: value = readUint64 (buffer); break; case ObjectType.Null: case ObjectType.NoSuchObject: case ObjectType.NoSuchInstance: case ObjectType.EndOfMibView: value = null; break; default: // Unknown data type - should never happen as the above covers all types in RFC 2741 Section 5.4 throw new RequestInvalidError ("Unknown type '" + vtype + "' in varbind"); } return { type: vtype, oid: oid, value: value }; }; AgentXPdu.readVarbinds = function (buffer, payloadLength) { var bytesLeft = payloadLength; var bufferPosition = (buffer.readOffset + 1); var varbindList = []; while (bytesLeft > 0) { varbindList.push (AgentXPdu.readVarbind (buffer)); bytesLeft -= (buffer.readOffset + 1) - bufferPosition; bufferPosition = buffer.readOffset + 1; } return varbindList; }; AgentXPdu.packetID = 1; var Subagent = function (options) { DEBUG = options.debug; this.mib = new Mib (); this.master = options.master || 'localhost'; this.masterPort = options.masterPort || 705; this.timeout = options.timeout || 0; this.descr = options.description || "Node net-snmp AgentX sub-agent"; this.sessionID = 0; this.transactionID = 0; this.packetID = _generateId(); this.requestPdus = {}; this.setTransactions = {}; }; Subagent.prototype.getMib = function () { return this.mib; }; Subagent.prototype.connectSocket = function () { var me = this; this.socket = new net.Socket (); this.socket.connect (this.masterPort, this.master, function () { debug ("Connected to '" + me.master + "' on port " + me.masterPort); }); this.socket.on ("data", me.onMsg.bind (me)); this.socket.on ("error", function (error) { console.error (error); }); }; Subagent.prototype.open = function (callback) { var pdu = AgentXPdu.createFromVariables ({ pduType: AgentXPduType.Open, timeout: this.timeout, oid: this.oid, descr: this.descr }); this.sendPdu (pdu, callback); }; Subagent.prototype.close = function (callback) { var pdu = AgentXPdu.createFromVariables ({ pduType: AgentXPduType.Close, sessionID: this.sessionID }); this.sendPdu (pdu, callback); }; Subagent.prototype.registerProvider = function (provider, callback) { var pdu = AgentXPdu.createFromVariables ({ pduType: AgentXPduType.Register, sessionID: this.sessionID, rangeSubid: 0, timeout: 5, priority: 127, oid: provider.oid }); this.mib.registerProvider (provider); this.sendPdu (pdu, callback); }; Subagent.prototype.unregisterProvider = function (name, callback) { var provider = this.getProvider (name); var pdu = AgentXPdu.createFromVariables ({ pduType: AgentXPduType.Unregister, sessionID: this.sessionID, rangeSubid: 0, priority: 127, oid: provider.oid }); this.mib.unregisterProvider (name); this.sendPdu (pdu, callback); }; Subagent.prototype.registerProviders = function (providers, callback) { for (var provider of providers) { this.registerProvider (provider, callback); } }; Subagent.prototype.getProvider = function (name) { return this.mib.getProvider (name); }; Subagent.prototype.getProviders = function () { return this.mib.getProviders (); }; Subagent.prototype.addAgentCaps = function (oid, descr, callback) { var pdu = AgentXPdu.createFromVariables ({ pduType: AgentXPduType.AddAgentCaps, sessionID: this.sessionID, oid: oid, descr: descr }); this.sendPdu (pdu, callback); }; Subagent.prototype.removeAgentCaps = function (oid, callback) { var pdu = AgentXPdu.createFromVariables ({ pduType: AgentXPduType.RemoveAgentCaps, sessionID: this.sessionID, oid: oid }); this.sendPdu (pdu, callback); }; Subagent.prototype.notify = function (typeOrOid, varbinds, callback) { varbinds = varbinds || []; if (typeof typeOrOid != "string") { typeOrOid = "1.3.6.1.6.3.1.1.5." + (typeOrOid + 1); } var pduVarbinds = [ { oid: "1.3.6.1.2.1.1.3.0", type: ObjectType.TimeTicks, value: Math.floor (process.uptime () * 100) }, { oid: "1.3.6.1.6.3.1.1.4.1.0", type: ObjectType.OID, value: typeOrOid } ]; pduVarbinds = pduVarbinds.concat (varbinds); var pdu = AgentXPdu.createFromVariables ({ pduType: AgentXPduType.Notify, sessionID: this.sessionID, varbinds: pduVarbinds }); this.sendPdu (pdu, callback); }; Subagent.prototype.ping = function (callback) { var pdu = AgentXPdu.createFromVariables ({ pduType: AgentXPduType.Ping, sessionID: this.sessionID }); this.sendPdu (pdu, callback); }; Subagent.prototype.sendPdu = function (pdu, callback) { debug ("Sending AgentX " + AgentXPduType[pdu.pduType] + " PDU"); debug (pdu); var buffer = pdu.toBuffer (); this.socket.write (buffer); if ( pdu.pduType != AgentXPduType.Response && ! this.requestPdus[pdu.packetID] ) { pdu.callback = callback; this.requestPdus[pdu.packetID] = pdu; } // Possible timeout / retry mechanism? // var me = this; // pdu.timer = setTimeout (function () { // if (pdu.retries-- > 0) { // this.sendPdu (pdu); // } else { // delete me.requestPdus[pdu.packetID]; // me.callback (new RequestTimedOutError ( // "Request timed out")); // } // }, this.timeout); }; Subagent.prototype.onMsg = function (buffer, rinfo) { var pdu = AgentXPdu.createFromBuffer (buffer); debug ("Received AgentX " + AgentXPduType[pdu.pduType] + " PDU"); debug (pdu); switch (pdu.pduType) { case AgentXPduType.Response: this.response (pdu); break; case AgentXPduType.Get: this.getRequest (pdu); break; case AgentXPduType.GetNext: this.getNextRequest (pdu); break; case AgentXPduType.GetBulk: this.getBulkRequest (pdu); break; case AgentXPduType.TestSet: this.testSet (pdu); break; case AgentXPduType.CommitSet: this.commitSet (pdu); break; case AgentXPduType.UndoSet: this.undoSet (pdu); break; case AgentXPduType.CleanupSet: this.cleanupSet (pdu); break; default: // Unknown PDU type - shouldn't happen as master agents shouldn't send administrative PDUs throw new RequestInvalidError ("Unknown PDU type '" + pdu.pduType + "' in request"); } }; Subagent.prototype.response = function (pdu) { var requestPdu = this.requestPdus[pdu.packetID]; if (requestPdu) { delete this.requestPdus[pdu.packetID]; // clearTimeout (pdu.timer); // delete pdu.timer; switch (requestPdu.pduType) { case AgentXPduType.Open: this.sessionID = pdu.sessionID; break; case AgentXPduType.Close: this.socket.end(); break; case AgentXPduType.Register: case AgentXPduType.Unregister: case AgentXPduType.AddAgentCaps: case AgentXPduType.RemoveAgentCaps: case AgentXPduType.Notify: case AgentXPduType.Ping: break; default: // Response PDU for request type not handled throw new ResponseInvalidError ("Response PDU for type '" + requestPdu.pduType + "' not handled", ResponseInvalidCode.EResponseNotHandled); } if (requestPdu.callback) { requestPdu.callback(null, pdu); } } else { // unexpected Response PDU - has no matching request throw new ResponseInvalidError ("Unexpected Response PDU with packetID " + pdu.packetID, ResponseInvalidCode.EUnexpectedResponse); } }; Subagent.prototype.request = function (pdu, requestVarbinds) { var me = this; var varbindsCompleted = 0; var varbindsLength = requestVarbinds.length; var responseVarbinds = []; for ( var i = 0; i < requestVarbinds.length; i++ ) { var requestVarbind = requestVarbinds[i]; var instanceNode = this.mib.lookup (requestVarbind.oid); var providerNode; var mibRequest; var handler; var responseVarbindType; if ( ! instanceNode ) { mibRequest = new MibRequest ({ operation: pdu.pduType, oid: requestVarbind.oid }); handler = function getNsoHandler (mibRequestForNso) { mibRequestForNso.done ({ errorStatus: ErrorStatus.NoError, errorIndex: 0, type: ObjectType.NoSuchObject, value: null }); }; } else { providerNode = this.mib.getProviderNodeForInstance (instanceNode); if ( ! providerNode ) { mibRequest = new MibRequest ({ operation: pdu.pduType, oid: requestVarbind.oid }); handler = function getNsiHandler (mibRequestForNsi) { mibRequestForNsi.done ({ errorStatus: ErrorStatus.NoError, errorIndex: 0, type: ObjectType.NoSuchInstance, value: null }); }; } else { mibRequest = new MibRequest ({ operation: pdu.pduType, providerNode: providerNode, instanceNode: instanceNode, oid: requestVarbind.oid }); if ( pdu.pduType == AgentXPduType.TestSet ) { mibRequest.setType = requestVarbind.type; mibRequest.setValue = requestVarbind.value; } handler = providerNode.provider.handler; } } (function (savedIndex) { var responseVarbind; mibRequest.done = function (error) { if ( error ) { responseVarbind = { oid: mibRequest.oid, type: error.type || ObjectType.Null, value: error.value || null }; } else { if ( pdu.pduType == AgentXPduType.TestSet ) { // more tests? } else if ( pdu.pduType == AgentXPduType.CommitSet ) { me.setTransactions[pdu.transactionID].originalValue = mibRequest.instanceNode.value; mibRequest.instanceNode.value = requestVarbind.value; } else if ( pdu.pduType == AgentXPduType.UndoSet ) { mibRequest.instanceNode.value = me.setTransactions[pdu.transactionID].originalValue; } if ( ( pdu.pduType == AgentXPduType.GetNext || pdu.pduType == AgentXPduType.GetBulk ) && requestVarbind.type == ObjectType.EndOfMibView ) { responseVarbindType = ObjectType.EndOfMibView; } else { responseVarbindType = mibRequest.instanceNode.valueType; } responseVarbind = { oid: mibRequest.oid, type: responseVarbindType, value: mibRequest.instanceNode.value }; } responseVarbinds[savedIndex] = responseVarbind; if ( ++varbindsCompleted == varbindsLength) { if ( pdu.pduType == AgentXPduType.TestSet || pdu.pduType == AgentXPduType.CommitSet || pdu.pduType == AgentXPduType.UndoSet) { me.sendSetResponse.call (me, pdu); } else { me.sendGetResponse.call (me, pdu, responseVarbinds); } } }; })(i); if ( handler ) { handler (mibRequest); } else { mibRequest.done (); } } }; Subagent.prototype.addGetNextVarbind = function (targetVarbinds, startOid) { var startNode; var getNextNode; try { startNode = this.mib.lookup (startOid); } catch ( error ) { startOid = '1.3.6.1'; startNode = this.mib.lookup (startOid); } if ( ! startNode ) { // Off-tree start specified startNode = this.mib.getTreeNode (startOid); } getNextNode = startNode.getNextInstanceNode(); if ( ! getNextNode ) { // End of MIB targetVarbinds.push ({ oid: startOid, type: ObjectType.EndOfMibView, value: null }); } else { // Normal response targetVarbinds.push ({ oid: getNextNode.oid, type: getNextNode.valueType, value: getNextNode.value }); } return getNextNode; }; Subagent.prototype.getRequest = function (pdu) { var requestVarbinds = []; for ( var i = 0; i < pdu.searchRangeList.length; i++ ) { requestVarbinds.push ({ oid: pdu.searchRangeList[i].start, value: null, type: null }); } this.request (pdu, requestVarbinds); }; Subagent.prototype.getNextRequest = function (pdu) { var getNextVarbinds = []; for (var i = 0 ; i < pdu.searchRangeList.length ; i++ ) { this.addGetNextVarbind (getNextVarbinds, pdu.searchRangeList[i].start); } this.request (pdu, getNextVarbinds); }; Subagent.prototype.getBulkRequest = function (pdu) { var getBulkVarbinds = []; var startOid = []; var getNextNode; var endOfMib = false; for (var n = 0 ; n < pdu.nonRepeaters ; n++ ) { this.addGetNextVarbind (getBulkVarbinds, pdu.searchRangeList[n].start); } for (var v = pdu.nonRepeaters ; v < pdu.searchRangeList.length ; v++ ) { startOid.push (pdu.searchRangeList[v].oid); } while ( getBulkVarbinds.length < pdu.maxRepetitions && ! endOfMib ) { for (var w = pdu.nonRepeaters ; w < pdu.searchRangeList.length ; w++ ) { if (getBulkVarbinds.length < pdu.maxRepetitions ) { getNextNode = this.addGetNextVarbind (getBulkVarbinds, startOid[w - pdu.nonRepeaters]); if ( getNextNode ) { startOid[w - pdu.nonRepeaters] = getNextNode.oid; if ( getNextNode.type == ObjectType.EndOfMibView ) { endOfMib = true; } } } } } this.request (pdu, getBulkVarbinds); }; Subagent.prototype.sendGetResponse = function (requestPdu, varbinds) { var pdu = AgentXPdu.createFromVariables ({ pduType: AgentXPduType.Response, sessionID: requestPdu.sessionID, transactionID: requestPdu.transactionID, packetID: requestPdu.packetID, sysUpTime: 0, error: 0, index: 0, varbinds: varbinds }); this.sendPdu (pdu, null); }; Subagent.prototype.sendSetResponse = function (setPdu) { var responsePdu = AgentXPdu.createFromVariables ({ pduType: AgentXPduType.Response, sessionID: setPdu.sessionID, transactionID: setPdu.transactionID, packetID: setPdu.packetID, sysUpTime: 0, error: 0, index: 0, }); this.sendPdu (responsePdu, null); }; Subagent.prototype.testSet = function (setPdu) { this.setTransactions[setPdu.transactionID] = setPdu; this.request (setPdu, setPdu.varbinds); }; Subagent.prototype.commitSet = function (setPdu) { if ( this.setTransactions[setPdu.transactionID] ) { this.request (setPdu, this.setTransactions[setPdu.transactionID].varbinds); } else { throw new RequestInvalidError ("Unexpected CommitSet PDU with transactionID " + setPdu.transactionID); } }; Subagent.prototype.undoSet = function (setPdu) { if ( this.setTransactions[setPdu.transactionID] ) { this.request (setPdu, this.setTransactions[setPdu.transactionID].varbinds); } else { throw new RequestInvalidError ("Unexpected UndoSet PDU with transactionID " + setPdu.transactionID); } }; Subagent.prototype.cleanupSet = function (setPdu) { if ( this.setTransactions[setPdu.transactionID] ) { delete this.setTransactions[setPdu.transactionID]; } else { throw new RequestInvalidError ("Unexpected CleanupSet PDU with transactionID " + setPdu.transactionID); } }; Subagent.create = function (options) { var subagent = new Subagent (options); subagent.connectSocket (); return subagent; }; /***************************************************************************** ** Exports **/ exports.Session = Session; exports.createSession = Session.create; exports.createV3Session = Session.createV3; exports.createReceiver = Receiver.create; exports.createAgent = Agent.create; exports.createModuleStore = ModuleStore.create; exports.createSubagent = Subagent.create; exports.createMib = Mib.create; exports.isVarbindError = isVarbindError; exports.varbindError = varbindError; exports.Version1 = Version1; exports.Version2c = Version2c; exports.Version3 = Version3; exports.Version = Version; exports.ErrorStatus = ErrorStatus; exports.TrapType = TrapType; exports.ObjectType = ObjectType; exports.PduType = PduType; exports.AgentXPduType = AgentXPduType; exports.MibProviderType = MibProviderType; exports.SecurityLevel = SecurityLevel; exports.AuthProtocols = AuthProtocols; exports.PrivProtocols = PrivProtocols; exports.AccessControlModelType = AccessControlModelType; exports.AccessLevel = AccessLevel; exports.MaxAccess = MaxAccess; exports.RowStatus = RowStatus; exports.ResponseInvalidCode = ResponseInvalidCode; exports.ResponseInvalidError = ResponseInvalidError; exports.RequestInvalidError = RequestInvalidError; exports.RequestFailedError = RequestFailedError; exports.RequestTimedOutError = RequestTimedOutError; /** ** Added for testing **/ exports.ObjectParser = { readInt32: readInt32, readUint32: readUint32, readVarbindValue: readVarbindValue }; exports.Authentication = Authentication; exports.Encryption = Encryption;