1
0

6226 Zeilen
178 KiB
JavaScript

// Copyright 2013 Stephen Vickers <stephen.vickers.sv@gmail.com>
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;