diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 11:08:07 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 11:08:07 +0000 |
commit | c69cb8cc094cc916adbc516b09e944cd3d137c01 (patch) | |
tree | f2878ec41fb6d0e3613906c6722fc02b934eeb80 /collectors/node.d.plugin/node_modules | |
parent | Initial commit. (diff) | |
download | netdata-c69cb8cc094cc916adbc516b09e944cd3d137c01.tar.xz netdata-c69cb8cc094cc916adbc516b09e944cd3d137c01.zip |
Adding upstream version 1.29.3.upstream/1.29.3upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'collectors/node.d.plugin/node_modules')
-rw-r--r-- | collectors/node.d.plugin/node_modules/asn1-ber.js | 7 | ||||
-rw-r--r-- | collectors/node.d.plugin/node_modules/extend.js | 88 | ||||
-rw-r--r-- | collectors/node.d.plugin/node_modules/lib/ber/errors.js | 10 | ||||
-rw-r--r-- | collectors/node.d.plugin/node_modules/lib/ber/index.js | 18 | ||||
-rw-r--r-- | collectors/node.d.plugin/node_modules/lib/ber/reader.js | 270 | ||||
-rw-r--r-- | collectors/node.d.plugin/node_modules/lib/ber/types.js | 35 | ||||
-rw-r--r-- | collectors/node.d.plugin/node_modules/lib/ber/writer.js | 318 | ||||
-rw-r--r-- | collectors/node.d.plugin/node_modules/net-snmp.js | 3452 | ||||
-rw-r--r-- | collectors/node.d.plugin/node_modules/netdata.js | 654 | ||||
-rw-r--r-- | collectors/node.d.plugin/node_modules/pixl-xml.js | 607 |
10 files changed, 5459 insertions, 0 deletions
diff --git a/collectors/node.d.plugin/node_modules/asn1-ber.js b/collectors/node.d.plugin/node_modules/asn1-ber.js new file mode 100644 index 0000000..55c8f68 --- /dev/null +++ b/collectors/node.d.plugin/node_modules/asn1-ber.js @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT + +var Ber = require('./lib/ber/index') + +exports.Ber = Ber +exports.BerReader = Ber.Reader +exports.BerWriter = Ber.Writer diff --git a/collectors/node.d.plugin/node_modules/extend.js b/collectors/node.d.plugin/node_modules/extend.js new file mode 100644 index 0000000..3cd2e91 --- /dev/null +++ b/collectors/node.d.plugin/node_modules/extend.js @@ -0,0 +1,88 @@ +// https://github.com/justmoon/node-extend +// SPDX-License-Identifier: MIT + +'use strict'; + +var hasOwn = Object.prototype.hasOwnProperty; +var toStr = Object.prototype.toString; + +var isArray = function isArray(arr) { + if (typeof Array.isArray === 'function') { + return Array.isArray(arr); + } + + return toStr.call(arr) === '[object Array]'; +}; + +var isPlainObject = function isPlainObject(obj) { + if (!obj || toStr.call(obj) !== '[object Object]') { + return false; + } + + var hasOwnConstructor = hasOwn.call(obj, 'constructor'); + var hasIsPrototypeOf = obj.constructor && obj.constructor.prototype && hasOwn.call(obj.constructor.prototype, 'isPrototypeOf'); + // Not own constructor property must be Object + if (obj.constructor && !hasOwnConstructor && !hasIsPrototypeOf) { + return false; + } + + // Own properties are enumerated firstly, so to speed up, + // if last one is own, then all properties are own. + var key; + for (key in obj) { /**/ } + + return typeof key === 'undefined' || hasOwn.call(obj, key); +}; + +module.exports = function extend() { + var options, name, src, copy, copyIsArray, clone; + var target = arguments[0]; + var i = 1; + var length = arguments.length; + var deep = false; + + // Handle a deep copy situation + if (typeof target === 'boolean') { + deep = target; + target = arguments[1] || {}; + // skip the boolean and the target + i = 2; + } else if ((typeof target !== 'object' && typeof target !== 'function') || target == null) { + target = {}; + } + + for (; i < length; ++i) { + options = arguments[i]; + // Only deal with non-null/undefined values + if (options != null) { + // Extend the base object + for (name in options) { + src = target[name]; + copy = options[name]; + + // Prevent never-ending loop + if (target !== copy) { + // Recurse if we're merging plain objects or arrays + if (deep && copy && (isPlainObject(copy) || (copyIsArray = isArray(copy)))) { + if (copyIsArray) { + copyIsArray = false; + clone = src && isArray(src) ? src : []; + } else { + clone = src && isPlainObject(src) ? src : {}; + } + + // Never move original objects, clone them + target[name] = extend(deep, clone, copy); + + // Don't bring in undefined values + } else if (typeof copy !== 'undefined') { + target[name] = copy; + } + } + } + } + } + + // Return the modified object + return target; +}; diff --git a/collectors/node.d.plugin/node_modules/lib/ber/errors.js b/collectors/node.d.plugin/node_modules/lib/ber/errors.js new file mode 100644 index 0000000..1c0df7b --- /dev/null +++ b/collectors/node.d.plugin/node_modules/lib/ber/errors.js @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT + +module.exports = { + InvalidAsn1Error: function(msg) { + var e = new Error() + e.name = 'InvalidAsn1Error' + e.message = msg || '' + return e + } +} diff --git a/collectors/node.d.plugin/node_modules/lib/ber/index.js b/collectors/node.d.plugin/node_modules/lib/ber/index.js new file mode 100644 index 0000000..eb69ec5 --- /dev/null +++ b/collectors/node.d.plugin/node_modules/lib/ber/index.js @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT + +var errors = require('./errors') +var types = require('./types') + +var Reader = require('./reader') +var Writer = require('./writer') + +for (var t in types) + if (types.hasOwnProperty(t)) + exports[t] = types[t] + +for (var e in errors) + if (errors.hasOwnProperty(e)) + exports[e] = errors[e] + +exports.Reader = Reader +exports.Writer = Writer diff --git a/collectors/node.d.plugin/node_modules/lib/ber/reader.js b/collectors/node.d.plugin/node_modules/lib/ber/reader.js new file mode 100644 index 0000000..06decf4 --- /dev/null +++ b/collectors/node.d.plugin/node_modules/lib/ber/reader.js @@ -0,0 +1,270 @@ +// SPDX-License-Identifier: MIT + +var assert = require('assert'); + +var ASN1 = require('./types'); +var errors = require('./errors'); + + +///--- Globals + +var InvalidAsn1Error = errors.InvalidAsn1Error; + + + +///--- API + +function Reader(data) { + if (!data || !Buffer.isBuffer(data)) + throw new TypeError('data must be a node Buffer'); + + this._buf = data; + this._size = data.length; + + // These hold the "current" state + this._len = 0; + this._offset = 0; +} + +Object.defineProperty(Reader.prototype, 'length', { + enumerable: true, + get: function () { return (this._len); } +}); + +Object.defineProperty(Reader.prototype, 'offset', { + enumerable: true, + get: function () { return (this._offset); } +}); + +Object.defineProperty(Reader.prototype, 'remain', { + get: function () { return (this._size - this._offset); } +}); + +Object.defineProperty(Reader.prototype, 'buffer', { + get: function () { return (this._buf.slice(this._offset)); } +}); + + +/** + * Reads a single byte and advances offset; you can pass in `true` to make this + * a "peek" operation (i.e., get the byte, but don't advance the offset). + * + * @param {Boolean} peek true means don't move offset. + * @return {Number} the next byte, null if not enough data. + */ +Reader.prototype.readByte = function(peek) { + if (this._size - this._offset < 1) + return null; + + var b = this._buf[this._offset] & 0xff; + + if (!peek) + this._offset += 1; + + return b; +}; + + +Reader.prototype.peek = function() { + return this.readByte(true); +}; + + +/** + * Reads a (potentially) variable length off the BER buffer. This call is + * not really meant to be called directly, as callers have to manipulate + * the internal buffer afterwards. + * + * As a result of this call, you can call `Reader.length`, until the + * next thing called that does a readLength. + * + * @return {Number} the amount of offset to advance the buffer. + * @throws {InvalidAsn1Error} on bad ASN.1 + */ +Reader.prototype.readLength = function(offset) { + if (offset === undefined) + offset = this._offset; + + if (offset >= this._size) + return null; + + var lenB = this._buf[offset++] & 0xff; + if (lenB === null) + return null; + + if ((lenB & 0x80) == 0x80) { + lenB &= 0x7f; + + if (lenB == 0) + throw InvalidAsn1Error('Indefinite length not supported'); + + if (lenB > 4) + throw InvalidAsn1Error('encoding too long'); + + if (this._size - offset < lenB) + return null; + + this._len = 0; + for (var i = 0; i < lenB; i++) + this._len = (this._len << 8) + (this._buf[offset++] & 0xff); + + } else { + // Wasn't a variable length + this._len = lenB; + } + + return offset; +}; + + +/** + * Parses the next sequence in this BER buffer. + * + * To get the length of the sequence, call `Reader.length`. + * + * @return {Number} the sequence's tag. + */ +Reader.prototype.readSequence = function(tag) { + var seq = this.peek(); + if (seq === null) + return null; + if (tag !== undefined && tag !== seq) + throw InvalidAsn1Error('Expected 0x' + tag.toString(16) + + ': got 0x' + seq.toString(16)); + + var o = this.readLength(this._offset + 1); // stored in `length` + if (o === null) + return null; + + this._offset = o; + return seq; +}; + + +Reader.prototype.readInt = function(tag) { + if (typeof(tag) !== 'number') + tag = ASN1.Integer; + + return this._readTag(ASN1.Integer); +}; + + +Reader.prototype.readBoolean = function(tag) { + if (typeof(tag) !== 'number') + tag = ASN1.Boolean; + + return (this._readTag(tag) === 0 ? false : true); +}; + + +Reader.prototype.readEnumeration = function(tag) { + if (typeof(tag) !== 'number') + tag = ASN1.Enumeration; + + return this._readTag(ASN1.Enumeration); +}; + + +Reader.prototype.readString = function(tag, retbuf) { + if (!tag) + tag = ASN1.OctetString; + + var b = this.peek(); + if (b === null) + return null; + + if (b !== tag) + throw InvalidAsn1Error('Expected 0x' + tag.toString(16) + + ': got 0x' + b.toString(16)); + + var o = this.readLength(this._offset + 1); // stored in `length` + + if (o === null) + return null; + + if (this.length > this._size - o) + return null; + + this._offset = o; + + if (this.length === 0) + return retbuf ? new Buffer(0) : ''; + + var str = this._buf.slice(this._offset, this._offset + this.length); + this._offset += this.length; + + return retbuf ? str : str.toString('utf8'); +}; + +Reader.prototype.readOID = function(tag) { + if (!tag) + tag = ASN1.OID; + + var b = this.readString(tag, true); + if (b === null) + return null; + + var values = []; + var value = 0; + + for (var i = 0; i < b.length; i++) { + var byte = b[i] & 0xff; + + value <<= 7; + value += byte & 0x7f; + if ((byte & 0x80) == 0) { + values.push(value >>> 0); + value = 0; + } + } + + value = values.shift(); + values.unshift(value % 40); + values.unshift((value / 40) >> 0); + + return values.join('.'); +}; + + +Reader.prototype._readTag = function(tag) { + assert.ok(tag !== undefined); + + var b = this.peek(); + + if (b === null) + return null; + + if (b !== tag) + throw InvalidAsn1Error('Expected 0x' + tag.toString(16) + + ': got 0x' + b.toString(16)); + + var o = this.readLength(this._offset + 1); // stored in `length` + if (o === null) + return null; + + if (this.length > 4) + throw InvalidAsn1Error('Integer too long: ' + this.length); + + if (this.length > this._size - o) + return null; + this._offset = o; + + var fb = this._buf[this._offset]; + var value = 0; + + for (var i = 0; i < this.length; i++) { + value <<= 8; + value |= (this._buf[this._offset++] & 0xff); + } + + if ((fb & 0x80) == 0x80 && i !== 4) + value -= (1 << (i * 8)); + + return value >> 0; +}; + + + +///--- Exported API + +module.exports = Reader; diff --git a/collectors/node.d.plugin/node_modules/lib/ber/types.js b/collectors/node.d.plugin/node_modules/lib/ber/types.js new file mode 100644 index 0000000..7519ddc --- /dev/null +++ b/collectors/node.d.plugin/node_modules/lib/ber/types.js @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT + +module.exports = { + EOC: 0, + Boolean: 1, + Integer: 2, + BitString: 3, + OctetString: 4, + Null: 5, + OID: 6, + ObjectDescriptor: 7, + External: 8, + Real: 9, + Enumeration: 10, + PDV: 11, + Utf8String: 12, + RelativeOID: 13, + Sequence: 16, + Set: 17, + NumericString: 18, + PrintableString: 19, + T61String: 20, + VideotexString: 21, + IA5String: 22, + UTCTime: 23, + GeneralizedTime: 24, + GraphicString: 25, + VisibleString: 26, + GeneralString: 28, + UniversalString: 29, + CharacterString: 30, + BMPString: 31, + Constructor: 32, + Context: 128 +} diff --git a/collectors/node.d.plugin/node_modules/lib/ber/writer.js b/collectors/node.d.plugin/node_modules/lib/ber/writer.js new file mode 100644 index 0000000..d3a718f --- /dev/null +++ b/collectors/node.d.plugin/node_modules/lib/ber/writer.js @@ -0,0 +1,318 @@ +// SPDX-License-Identifier: MIT + +var assert = require('assert'); +var ASN1 = require('./types'); +var errors = require('./errors'); + + +///--- Globals + +var InvalidAsn1Error = errors.InvalidAsn1Error; + +var DEFAULT_OPTS = { + size: 1024, + growthFactor: 8 +}; + + +///--- Helpers + +function merge(from, to) { + assert.ok(from); + assert.equal(typeof(from), 'object'); + assert.ok(to); + assert.equal(typeof(to), 'object'); + + var keys = Object.getOwnPropertyNames(from); + keys.forEach(function(key) { + if (to[key]) + return; + + var value = Object.getOwnPropertyDescriptor(from, key); + Object.defineProperty(to, key, value); + }); + + return to; +} + + + +///--- API + +function Writer(options) { + options = merge(DEFAULT_OPTS, options || {}); + + this._buf = new Buffer(options.size || 1024); + this._size = this._buf.length; + this._offset = 0; + this._options = options; + + // A list of offsets in the buffer where we need to insert + // sequence tag/len pairs. + this._seq = []; +} + +Object.defineProperty(Writer.prototype, 'buffer', { + get: function () { + if (this._seq.length) + throw new InvalidAsn1Error(this._seq.length + ' unended sequence(s)'); + + return (this._buf.slice(0, this._offset)); + } +}); + +Writer.prototype.writeByte = function(b) { + if (typeof(b) !== 'number') + throw new TypeError('argument must be a Number'); + + this._ensure(1); + this._buf[this._offset++] = b; +}; + + +Writer.prototype.writeInt = function(i, tag) { + if (typeof(i) !== 'number') + throw new TypeError('argument must be a Number'); + if (typeof(tag) !== 'number') + tag = ASN1.Integer; + + var sz = 4; + + while ((((i & 0xff800000) === 0) || ((i & 0xff800000) === 0xff800000 >> 0)) && + (sz > 1)) { + sz--; + i <<= 8; + } + + if (sz > 4) + throw new InvalidAsn1Error('BER ints cannot be > 0xffffffff'); + + this._ensure(2 + sz); + this._buf[this._offset++] = tag; + this._buf[this._offset++] = sz; + + while (sz-- > 0) { + this._buf[this._offset++] = ((i & 0xff000000) >>> 24); + i <<= 8; + } + +}; + + +Writer.prototype.writeNull = function() { + this.writeByte(ASN1.Null); + this.writeByte(0x00); +}; + + +Writer.prototype.writeEnumeration = function(i, tag) { + if (typeof(i) !== 'number') + throw new TypeError('argument must be a Number'); + if (typeof(tag) !== 'number') + tag = ASN1.Enumeration; + + return this.writeInt(i, tag); +}; + + +Writer.prototype.writeBoolean = function(b, tag) { + if (typeof(b) !== 'boolean') + throw new TypeError('argument must be a Boolean'); + if (typeof(tag) !== 'number') + tag = ASN1.Boolean; + + this._ensure(3); + this._buf[this._offset++] = tag; + this._buf[this._offset++] = 0x01; + this._buf[this._offset++] = b ? 0xff : 0x00; +}; + + +Writer.prototype.writeString = function(s, tag) { + if (typeof(s) !== 'string') + throw new TypeError('argument must be a string (was: ' + typeof(s) + ')'); + if (typeof(tag) !== 'number') + tag = ASN1.OctetString; + + var len = Buffer.byteLength(s); + this.writeByte(tag); + this.writeLength(len); + if (len) { + this._ensure(len); + this._buf.write(s, this._offset); + this._offset += len; + } +}; + + +Writer.prototype.writeBuffer = function(buf, tag) { + if (!Buffer.isBuffer(buf)) + throw new TypeError('argument must be a buffer'); + + // If no tag is specified we will assume `buf` already contains tag and length + if (typeof(tag) === 'number') { + this.writeByte(tag); + this.writeLength(buf.length); + } + + this._ensure(buf.length); + buf.copy(this._buf, this._offset, 0, buf.length); + this._offset += buf.length; +}; + + +Writer.prototype.writeStringArray = function(strings, tag) { + if (! (strings instanceof Array)) + throw new TypeError('argument must be an Array[String]'); + + var self = this; + strings.forEach(function(s) { + self.writeString(s, tag); + }); +}; + +// This is really to solve DER cases, but whatever for now +Writer.prototype.writeOID = function(s, tag) { + if (typeof(s) !== 'string') + throw new TypeError('argument must be a string'); + if (typeof(tag) !== 'number') + tag = ASN1.OID; + + if (!/^([0-9]+\.){3,}[0-9]+$/.test(s)) + throw new Error('argument is not a valid OID string'); + + function encodeOctet(bytes, octet) { + if (octet < 128) { + bytes.push(octet); + } else if (octet < 16384) { + bytes.push((octet >>> 7) | 0x80); + bytes.push(octet & 0x7F); + } else if (octet < 2097152) { + bytes.push((octet >>> 14) | 0x80); + bytes.push(((octet >>> 7) | 0x80) & 0xFF); + bytes.push(octet & 0x7F); + } else if (octet < 268435456) { + bytes.push((octet >>> 21) | 0x80); + bytes.push(((octet >>> 14) | 0x80) & 0xFF); + bytes.push(((octet >>> 7) | 0x80) & 0xFF); + bytes.push(octet & 0x7F); + } else { + bytes.push(((octet >>> 28) | 0x80) & 0xFF); + bytes.push(((octet >>> 21) | 0x80) & 0xFF); + bytes.push(((octet >>> 14) | 0x80) & 0xFF); + bytes.push(((octet >>> 7) | 0x80) & 0xFF); + bytes.push(octet & 0x7F); + } + } + + var tmp = s.split('.'); + var bytes = []; + bytes.push(parseInt(tmp[0], 10) * 40 + parseInt(tmp[1], 10)); + tmp.slice(2).forEach(function(b) { + encodeOctet(bytes, parseInt(b, 10)); + }); + + var self = this; + this._ensure(2 + bytes.length); + this.writeByte(tag); + this.writeLength(bytes.length); + bytes.forEach(function(b) { + self.writeByte(b); + }); +}; + + +Writer.prototype.writeLength = function(len) { + if (typeof(len) !== 'number') + throw new TypeError('argument must be a Number'); + + this._ensure(4); + + if (len <= 0x7f) { + this._buf[this._offset++] = len; + } else if (len <= 0xff) { + this._buf[this._offset++] = 0x81; + this._buf[this._offset++] = len; + } else if (len <= 0xffff) { + this._buf[this._offset++] = 0x82; + this._buf[this._offset++] = len >> 8; + this._buf[this._offset++] = len; + } else if (len <= 0xffffff) { + this._buf[this._offset++] = 0x83; + this._buf[this._offset++] = len >> 16; + this._buf[this._offset++] = len >> 8; + this._buf[this._offset++] = len; + } else { + throw new InvalidAsn1Error('Length too long (> 4 bytes)'); + } +}; + +Writer.prototype.startSequence = function(tag) { + if (typeof(tag) !== 'number') + tag = ASN1.Sequence | ASN1.Constructor; + + this.writeByte(tag); + this._seq.push(this._offset); + this._ensure(3); + this._offset += 3; +}; + + +Writer.prototype.endSequence = function() { + var seq = this._seq.pop(); + var start = seq + 3; + var len = this._offset - start; + + if (len <= 0x7f) { + this._shift(start, len, -2); + this._buf[seq] = len; + } else if (len <= 0xff) { + this._shift(start, len, -1); + this._buf[seq] = 0x81; + this._buf[seq + 1] = len; + } else if (len <= 0xffff) { + this._buf[seq] = 0x82; + this._buf[seq + 1] = len >> 8; + this._buf[seq + 2] = len; + } else if (len <= 0xffffff) { + this._shift(start, len, 1); + this._buf[seq] = 0x83; + this._buf[seq + 1] = len >> 16; + this._buf[seq + 2] = len >> 8; + this._buf[seq + 3] = len; + } else { + throw new InvalidAsn1Error('Sequence too long'); + } +}; + + +Writer.prototype._shift = function(start, len, shift) { + assert.ok(start !== undefined); + assert.ok(len !== undefined); + assert.ok(shift); + + this._buf.copy(this._buf, start + shift, start, start + len); + this._offset += shift; +}; + +Writer.prototype._ensure = function(len) { + assert.ok(len); + + if (this._size - this._offset < len) { + var sz = this._size * this._options.growthFactor; + if (sz - this._offset < len) + sz += len; + + var buf = new Buffer(sz); + + this._buf.copy(buf, 0, 0, this._offset); + this._buf = buf; + this._size = sz; + } +}; + + + +///--- Exported API + +module.exports = Writer; diff --git a/collectors/node.d.plugin/node_modules/net-snmp.js b/collectors/node.d.plugin/node_modules/net-snmp.js new file mode 100644 index 0000000..6b5b754 --- /dev/null +++ b/collectors/node.d.plugin/node_modules/net-snmp.js @@ -0,0 +1,3452 @@ +// Copyright 2013 Stephen Vickers <stephen.vickers.sv@gmail.com> +// SPDX-License-Identifier: MIT + +var ber = require("asn1-ber").Ber; +var dgram = require("dgram"); +var events = require("events"); +var util = require("util"); +var crypto = require("crypto"); + +var DEBUG = false; + +var MAX_INT32 = 2147483647; + +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", + 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); + +ObjectType.Integer32 = ObjectType.Integer; +ObjectType.Counter32 = ObjectType.Counter; +ObjectType.Gauge32 = ObjectType.Gauge; +ObjectType.Unsigned32 = ObjectType.Gauge32; + +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" +}; + +_expandConstantObject(AuthProtocols); + +var PrivProtocols = { + "1": "none", + "2": "des" +}; + +_expandConstantObject(PrivProtocols); + +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 +}; + +/***************************************************************************** + ** Exception class definitions + **/ + +function ResponseInvalidError(message) { + this.name = "ResponseInvalidError"; + this.message = message; + 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; +} + +/** + ** Some SNMP agents produce integers on the wire such as 00 ff ff ff ff. + ** The ASN.1 BER parser we use throws an error when parsing this, which we + ** believe is correct. So, we decided not to bother the "asn1" developer(s) + ** with this, instead opting to work around it here. + ** + ** If an integer is 5 bytes in length we check if the first byte is 0, and if so + ** simply drop it and parse it like it was a 4 byte integer, otherwise throw + ** an error since the integer is too large. + **/ + +function readInt(buffer) { + return readUint(buffer, true); +} + +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"); + var value = bytes[0] + "." + bytes[1] + "." + bytes[2] + "." + bytes[3]; + return value; +} + +function readUint(buffer, isSigned) { + buffer.readByte(); + var length = buffer.readByte(); + var value = 0; + var signedBitSet = false; + + if (length > 5) { + throw new RangeError("Integer too long '" + length + "'"); + } else if (length == 5) { + if (buffer.readByte() !== 0) + throw new RangeError("Integer too long '" + length + "'"); + length = 4; + } + + for (var i = 0; i < length; i++) { + value *= 256; + value += buffer.readByte(); + + if (isSigned && i <= 0) { + if ((value & 0x80) == 0x80) + signedBitSet = true; + } + } + + if (signedBitSet) + value -= (1 << (i * 8)); + + return value; +} + +function readUint64(buffer) { + var value = buffer.readString(ObjectType.Counter64, true); + + 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; + + if (type == ObjectType.Boolean) { + value = buffer.readBoolean(); + } else if (type == ObjectType.Integer) { + value = readInt(buffer); + } 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) { + 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"); + value = bytes[0] + "." + bytes[1] + "." + bytes[2] + "." + bytes[3]; + } else if (type == ObjectType.Counter) { + value = readUint(buffer); + } else if (type == ObjectType.Gauge) { + value = readUint(buffer); + } else if (type == ObjectType.TimeTicks) { + value = readUint(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"); + } + + varbinds.push({ + oid: oid, + type: type, + value: value + }); + } +} + +function writeUint(buffer, type, value) { + var b = Buffer.alloc(4); + b.writeUInt32BE(value, 0); + buffer.writeBuffer(b, 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; + + if (type == ObjectType.Boolean) { + buffer.writeBoolean(value ? true : false); + } else if (type == ObjectType.Integer) { // also Integer32 + buffer.writeInt(value); + } else if (type == ObjectType.OctetString) { + if (typeof value == "string") + buffer.writeString(value); + else + buffer.writeBuffer(value, ObjectType.OctetString); + } else if (type == ObjectType.Null) { + buffer.writeNull(); + } else if (type == ObjectType.OID) { + buffer.writeOID(value); + } else if (type == ObjectType.IpAddress) { + var bytes = value.split("."); + if (bytes.length != 4) + throw new RequestInvalidError("Invalid IP address '" + + value + "'"); + buffer.writeBuffer(Buffer.from(bytes), 64); + } else if (type == ObjectType.Counter) { // also Counter32 + writeUint(buffer, ObjectType.Counter, value); + } else if (type == ObjectType.Gauge) { // also Gauge32 & Unsigned32 + writeUint(buffer, ObjectType.Gauge, value); + } else if (type == ObjectType.TimeTicks) { + writeUint(buffer, ObjectType.TimeTicks, value); + } else if (type == ObjectType.Opaque) { + buffer.writeBuffer(value, ObjectType.Opaque); + } else if (type == ObjectType.Counter64) { + writeUint64(buffer, value); + } else if (type == ObjectType.EndOfMibView) { + buffer.writeByte(130); + buffer.writeByte(0); + } else { + 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); + + buffer.writeInt(this.id); + buffer.writeInt((this.type == PduType.GetBulkRequest) + ? (this.options.nonRepeaters || 0) + : 0); + buffer.writeInt((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 = reader.readInt(); + this.nonRepeaters = reader.readInt(); + this.maxRepetitions = reader.readInt(); + + 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); + buffer.writeInt(this.generic); + buffer.writeInt(this.specific); + writeUint(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 = reader.readInt(); + pdu.specific = reader.readInt(); + pdu.upTime = readUint(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); + + writer.writeInt(this.id); + writer.writeInt(this.errorStatus || 0); + writer.writeInt(this.errorIndex || 0); + writeVarbinds(writer, this.varbinds); + writer.endSequence(); + +}; + +SimpleResponsePdu.prototype.initializeFromBuffer = function (reader) { + reader.readSequence(this.type); + + this.id = reader.readInt(); + this.errorStatus = reader.readInt(); + this.errorIndex = reader.readInt(); + + 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.readSequence(); + 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"); + } + 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.HMAC_BLOCK_SIZE = 64; +Authentication.AUTHENTICATION_CODE_LENGTH = 12; +Authentication.AUTH_PARAMETERS_PLACEHOLDER = Buffer.from('8182838485868788898a8b8c', 'hex'); + +Authentication.algorithms = {}; + +Authentication.algorithms[AuthProtocols.md5] = { + // KEY_LENGTH: 16, + CRYPTO_ALGORITHM: 'md5' +}; + +Authentication.algorithms[AuthProtocols.sha] = { + // KEY_LENGTH: 20, + CRYPTO_ALGORITHM: 'sha1' +}; + +// 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 = Buffer.alloc(Authentication.HMAC_BUFFER_SIZE); + var bufOffset = 0; + var passwordIndex = 0; + var count = 0; + var password = Buffer.from(authPasswordString); + var cryptoAlgorithm = Authentication.algorithms[authProtocol].CRYPTO_ALGORITHM; + + while (count < Authentication.HMAC_BUFFER_SIZE) { + for (var i = 0; i < Authentication.HMAC_BLOCK_SIZE; i++) { + buf.writeUInt8(password[passwordIndex++ % password.length], bufOffset++); + } + count += Authentication.HMAC_BLOCK_SIZE; + } + 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')); + + return finalDigest; +}; + +Authentication.addParametersToMessageBuffer = function (messageBuffer, authProtocol, authPassword, engineID) { + var authenticationParametersOffset; + var digestToAdd; + + // clear the authenticationParameters field in message + authenticationParametersOffset = messageBuffer.indexOf(Authentication.AUTH_PARAMETERS_PLACEHOLDER); + messageBuffer.fill(0, authenticationParametersOffset, authenticationParametersOffset + Authentication.AUTHENTICATION_CODE_LENGTH); + + digestToAdd = Authentication.calculateDigest(messageBuffer, authProtocol, authPassword, engineID); + digestToAdd.copy(messageBuffer, authenticationParametersOffset, 0, Authentication.AUTHENTICATION_CODE_LENGTH); + debug("Added Auth Parameters: " + digestToAdd.toString('hex')); +}; + +Authentication.isAuthentic = function (messageBuffer, authProtocol, authPassword, engineID, digestInMessage) { + var authenticationParametersOffset; + var calculatedDigest; + + // clear the authenticationParameters field in message + authenticationParametersOffset = messageBuffer.indexOf(digestInMessage); + messageBuffer.fill(0, authenticationParametersOffset, authenticationParametersOffset + Authentication.AUTHENTICATION_CODE_LENGTH); + + calculatedDigest = Authentication.calculateDigest(messageBuffer, authProtocol, authPassword, engineID); + + // replace previously cleared authenticationParameters field in message + digestInMessage.copy(messageBuffer, authenticationParametersOffset, 0, Authentication.AUTHENTICATION_CODE_LENGTH); + + debug("Digest in message: " + digestInMessage.toString('hex')); + debug("Calculated digest: " + calculatedDigest.toString('hex')); + return calculatedDigest.equals(digestInMessage, Authentication.AUTHENTICATION_CODE_LENGTH); +}; + +Authentication.calculateDigest = function (messageBuffer, authProtocol, authPassword, engineID) { + var authKey = Authentication.passwordToKey(authProtocol, authPassword, engineID); + + // Adapted from RFC3147 Section 6.3.1. Processing an Outgoing Message + var hashAlgorithm; + var kIpad; + var kOpad; + var firstDigest; + var finalDigest; + var truncatedDigest; + var i; + var cryptoAlgorithm = Authentication.algorithms[authProtocol].CRYPTO_ALGORITHM; + + if (authKey.length > Authentication.HMAC_BLOCK_SIZE) { + hashAlgorithm = crypto.createHash(cryptoAlgorithm); + hashAlgorithm.update(authKey); + authKey = hashAlgorithm.digest(); + } + + // MD(K XOR opad, MD(K XOR ipad, msg)) + kIpad = Buffer.alloc(Authentication.HMAC_BLOCK_SIZE); + kOpad = Buffer.alloc(Authentication.HMAC_BLOCK_SIZE); + for (i = 0; i < authKey.length; i++) { + kIpad[i] = authKey[i] ^ 0x36; + kOpad[i] = authKey[i] ^ 0x5c; + } + kIpad.fill(0x36, authKey.length); + kOpad.fill(0x5c, authKey.length); + + // inner MD + hashAlgorithm = crypto.createHash(cryptoAlgorithm); + hashAlgorithm.update(kIpad); + hashAlgorithm.update(messageBuffer); + firstDigest = hashAlgorithm.digest(); + // outer MD + hashAlgorithm = crypto.createHash(cryptoAlgorithm); + hashAlgorithm.update(kOpad); + hashAlgorithm.update(firstDigest); + finalDigest = hashAlgorithm.digest(); + + truncatedDigest = Buffer.alloc(Authentication.AUTHENTICATION_CODE_LENGTH); + finalDigest.copy(truncatedDigest, 0, 0, Authentication.AUTHENTICATION_CODE_LENGTH); + return truncatedDigest; +}; + +var Encryption = {}; + +Encryption.INPUT_KEY_LENGTH = 16; +Encryption.DES_KEY_LENGTH = 8; +Encryption.DES_BLOCK_LENGTH = 8; +Encryption.CRYPTO_DES_ALGORITHM = 'des-cbc'; +Encryption.PRIV_PARAMETERS_PLACEHOLDER = Buffer.from('9192939495969798', 'hex'); + +Encryption.encryptPdu = function (scopedPdu, privProtocol, privPassword, authProtocol, engineID) { + var privLocalizedKey; + var encryptionKey; + var preIv; + var salt; + var iv; + var i; + var paddedScopedPduLength; + var paddedScopedPdu; + var encryptedPdu; + var cbcProtocol = Encryption.CRYPTO_DES_ALGORITHM; + + privLocalizedKey = Authentication.passwordToKey(authProtocol, privPassword, engineID); + encryptionKey = Buffer.alloc(Encryption.DES_KEY_LENGTH); + privLocalizedKey.copy(encryptionKey, 0, 0, Encryption.DES_KEY_LENGTH); + preIv = Buffer.alloc(Encryption.DES_BLOCK_LENGTH); + privLocalizedKey.copy(preIv, 0, Encryption.DES_KEY_LENGTH, Encryption.DES_KEY_LENGTH + Encryption.DES_BLOCK_LENGTH); + + salt = Buffer.alloc(Encryption.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(Encryption.DES_BLOCK_LENGTH); + for (i = 0; i < iv.length; i++) { + iv[i] = preIv[i] ^ salt[i]; + } + + if (scopedPdu.length % Encryption.DES_BLOCK_LENGTH == 0) { + paddedScopedPdu = scopedPdu; + } else { + paddedScopedPduLength = Encryption.DES_BLOCK_LENGTH * (Math.floor(scopedPdu.length / Encryption.DES_BLOCK_LENGTH) + 1); + paddedScopedPdu = Buffer.alloc(paddedScopedPduLength); + scopedPdu.copy(paddedScopedPdu, 0, 0, scopedPdu.length); + } + cipher = crypto.createCipheriv(cbcProtocol, encryptionKey, iv); + encryptedPdu = cipher.update(paddedScopedPdu); + encryptedPdu = Buffer.concat([encryptedPdu, cipher.final()]); + debug("Key: " + encryptionKey.toString('hex')); + debug("IV: " + iv.toString('hex')); + debug("Plain: " + paddedScopedPdu.toString('hex')); + debug("Encrypted: " + encryptedPdu.toString('hex')); + + return { + encryptedPdu: encryptedPdu, + msgPrivacyParameters: salt + }; +}; + +Encryption.decryptPdu = function (encryptedPdu, privProtocol, privParameters, privPassword, authProtocol, engineID, forceAutoPaddingDisable) { + var privLocalizedKey; + var decryptionKey; + var preIv; + var salt; + var iv; + var i; + var decryptedPdu; + var cbcProtocol = Encryption.CRYPTO_DES_ALGORITHM; + ; + + privLocalizedKey = Authentication.passwordToKey(authProtocol, privPassword, engineID); + decryptionKey = Buffer.alloc(Encryption.DES_KEY_LENGTH); + privLocalizedKey.copy(decryptionKey, 0, 0, Encryption.DES_KEY_LENGTH); + preIv = Buffer.alloc(Encryption.DES_BLOCK_LENGTH); + privLocalizedKey.copy(preIv, 0, Encryption.DES_KEY_LENGTH, Encryption.DES_KEY_LENGTH + Encryption.DES_BLOCK_LENGTH); + + salt = privParameters; + iv = Buffer.alloc(Encryption.DES_BLOCK_LENGTH); + for (i = 0; i < iv.length; i++) { + iv[i] = preIv[i] ^ salt[i]; + } + + decipher = crypto.createDecipheriv(cbcProtocol, decryptionKey, iv); + if (forceAutoPaddingDisable) { + decipher.setAutoPadding(false); + } + decryptedPdu = decipher.update(encryptedPdu); + // This try-catch is a workaround for a seemingly incorrect error condition + // - where sometimes a decrypt error is thrown with decipher.final() + // It replaces this line which should have been sufficient: + // decryptedPdu = Buffer.concat ([decryptedPdu, decipher.final()]); + try { + decryptedPdu = Buffer.concat([decryptedPdu, decipher.final()]); + } catch (error) { + // debug("Decrypt error: " + error); + decipher = crypto.createDecipheriv(cbcProtocol, decryptionKey, iv); + decipher.setAutoPadding(false); + decryptedPdu = decipher.update(encryptedPdu); + decryptedPdu = Buffer.concat([decryptedPdu, decipher.final()]); + } + debug("Key: " + decryptionKey.toString('hex')); + debug("IV: " + iv.toString('hex')); + debug("Encrypted: " + encryptedPdu.toString('hex')); + debug("Plain: " + decryptedPdu.toString('hex')); + + return decryptedPdu; + +}; + +Encryption.addParametersToMessageBuffer = function (messageBuffer, msgPrivacyParameters) { + privacyParametersOffset = messageBuffer.indexOf(Encryption.PRIV_PARAMETERS_PLACEHOLDER); + msgPrivacyParameters.copy(messageBuffer, privacyParametersOffset, 0, Encryption.DES_IV_LENGTH); +}; + +/***************************************************************************** + ** 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(); + + writer.writeInt(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; + + var writer = new ber.Writer(); + + writer.startSequence(); + + writer.writeInt(this.version); + + // HeaderData + writer.startSequence(); + writer.writeInt(this.msgGlobalData.msgID); + writer.writeInt(this.msgGlobalData.msgMaxSize); + writer.writeByte(ber.OctetString); + writer.writeByte(1); + writer.writeByte(this.msgGlobalData.msgFlags); + writer.writeInt(this.msgGlobalData.msgSecurityModel); + writer.endSequence(); + + // msgSecurityParameters + var msgSecurityParametersWriter = new ber.Writer(); + msgSecurityParametersWriter.startSequence(); + //msgSecurityParametersWriter.writeString (this.msgSecurityParameters.msgAuthoritativeEngineID); + // writing a zero-length buffer fails - should fix asn1-ber for this condition + if (this.msgSecurityParameters.msgAuthoritativeEngineID.length == 0) { + msgSecurityParametersWriter.writeString(""); + } else { + msgSecurityParametersWriter.writeBuffer(this.msgSecurityParameters.msgAuthoritativeEngineID, ber.OctetString); + } + msgSecurityParametersWriter.writeInt(this.msgSecurityParameters.msgAuthoritativeEngineBoots); + msgSecurityParametersWriter.writeInt(this.msgSecurityParameters.msgAuthoritativeEngineTime); + msgSecurityParametersWriter.writeString(this.msgSecurityParameters.msgUserName); + + if (this.hasAuthentication()) { + msgSecurityParametersWriter.writeBuffer(Authentication.AUTH_PARAMETERS_PLACEHOLDER, ber.OctetString); + // should never happen where msgFlags has no authentication but authentication parameters still present + } else if (this.msgSecurityParameters.msgAuthenticationParameters.length > 0) { + msgSecurityParametersWriter.writeBuffer(this.msgSecurityParameters.msgAuthenticationParameters, ber.OctetString); + } else { + msgSecurityParametersWriter.writeString(""); + } + + if (this.hasPrivacy()) { + msgSecurityParametersWriter.writeBuffer(Encryption.PRIV_PARAMETERS_PLACEHOLDER, ber.OctetString); + // should never happen where msgFlags has no privacy but privacy parameters still present + } else if (this.msgSecurityParameters.msgPrivacyParameters.length > 0) { + msgSecurityParametersWriter.writeBuffer(this.msgSecurityParameters.msgPrivacyParameters, ber.OctetString); + } else { + msgSecurityParametersWriter.writeString(""); + } + msgSecurityParametersWriter.endSequence(); + + writer.writeBuffer(msgSecurityParametersWriter.buffer, ber.OctetString); + + // 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()) { + encryptionResult = Encryption.encryptPdu(scopedPduWriter.buffer, this.user.privProtocol, this.user.privKey, this.user.authProtocol, this.msgSecurityParameters.msgAuthoritativeEngineID); + writer.writeBuffer(encryptionResult.encryptedPdu, ber.OctetString); + } else { + writer.writeBuffer(scopedPduWriter.buffer); + } + + writer.endSequence(); + + this.buffer = writer.buffer; + + if (this.hasPrivacy()) { + Encryption.addParametersToMessageBuffer(this.buffer, encryptionResult.msgPrivacyParameters); + } + + if (this.hasAuthentication()) { + Authentication.addParametersToMessageBuffer(this.buffer, this.user.authProtocol, this.user.authKey, + this.msgSecurityParameters.msgAuthoritativeEngineID); + } + + 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 { + decryptedPdu = Encryption.decryptPdu(this.encryptedPdu, user.privProtocol, + this.msgSecurityParameters.msgPrivacyParameters, user.privKey, user.authProtocol, + this.msgSecurityParameters.msgAuthoritativeEngineID); + decryptedPduReader = new ber.Reader(decryptedPdu); + this.pdu = readPdu(decryptedPduReader, true); + return true; + // really really occasionally the decrypt truncates a single byte + // causing an ASN read failure in readPdu() + // in this case, disabling auto padding decrypts the PDU correctly + // this try-catch provides the workaround for this condition + } catch (possibleTruncationError) { + try { + decryptedPdu = Encryption.decryptPdu(this.encryptedPdu, user.privProtocol, + this.msgSecurityParameters.msgPrivacyParameters, user.privKey, user.authProtocol, + this.msgSecurityParameters.msgAuthoritativeEngineID, true); + decryptedPduReader = new ber.Reader(decryptedPdu); + this.pdu = readPdu(decryptedPduReader, true); + return true; + } catch (error) { + responseCb(new ResponseInvalidError("Failed to decrypt PDU: " + error)); + 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(buffer, user.authProtocol, user.authKey, + this.msgSecurityParameters.msgAuthoritativeEngineID).toString('hex') + + " calculated for message")); + return false; + } + +}; + +Message.prototype.hasAuthentication = function () { + return this.msgGlobalData && this.msgGlobalData.msgFlags && this.msgGlobalData.msgFlags & 1; +}; + +Message.prototype.hasPrivacy = function () { + return this.msgGlobalData && this.msgGlobalData.msgFlags && this.msgGlobalData.msgFlags & 2; +}; + +Message.prototype.isReportable = function () { + return this.msgGlobalData && this.msgGlobalData.msgFlags && this.msgGlobalData.msgFlags & 4; +}; + +Message.prototype.setReportable = function (flag) { + if (this.msgGlobalData && this.msgGlobalData.msgFlags) { + if (flag) { + this.msgGlobalData.msgFlags = this.msgGlobalData.msgFlags | 4; + } else { + this.msgGlobalData.msgFlags = this.msgGlobalData.msgFlags & (255 - 4); + } + } +}; + +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.name, + 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 = reader.readInt(); + + if (message.version != 3) { + message.community = reader.readString(); + message.pdu = readPdu(reader, false); + } else { + // HeaderData + message.msgGlobalData = {}; + reader.readSequence(); + message.msgGlobalData.msgID = reader.readInt(); + message.msgGlobalData.msgMaxSize = reader.readInt(); + message.msgGlobalData.msgFlags = reader.readString(ber.OctetString, true)[0]; + message.msgGlobalData.msgSecurityModel = reader.readInt(); + + // 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 = msgSecurityParametersReader.readInt(); + message.msgSecurityParameters.msgAuthoritativeEngineTime = msgSecurityParametersReader.readInt(); + message.msgSecurityParameters.msgUserName = msgSecurityParametersReader.readString(); + message.msgSecurityParameters.msgAuthenticationParameters = Buffer.from(msgSecurityParametersReader.readString(ber.OctetString, true)); + message.msgSecurityParameters.msgPrivacyParameters = Buffer.from(msgSecurityParametersReader.readString(ber.OctetString, true)); + scopedPdu = 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; + 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"; + + this.version = (options && options.version) + ? options.version + : Version1; + + if (this.version == Version3) { + this.user = authenticator; + } else { + this.community = authenticator || "public"; + } + + this.transport = (options && options.transport) + ? options.transport + : "udp4"; + this.port = (options && options.port) + ? options.port + : 161; + this.trapPort = (options && options.trapPort) + ? options.trapPort + : 162; + + this.retries = (options && (options.retries || options.retries == 0)) + ? options.retries + : 1; + this.timeout = (options && options.timeout) + ? options.timeout + : 5000; + + this.sourceAddress = (options && options.sourceAddress) + ? options.sourceAddress + : undefined; + this.sourcePort = (options && options.sourcePort) + ? parseInt(options.sourcePort) + : undefined; + + this.idBitsSize = (options && options.idBitsSize) + ? parseInt(options.idBitsSize) + : 32; + + this.context = (options && options.context) ? options.context : ""; + + DEBUG = options.debug; + + 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) { + 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")); + } 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 request at positiion '" + i + "' does not " + + "match OID '" + pdu.varbinds[i].oid + "' in response " + + "at position '" + i + "'")); + 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; + + 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 varbinds = []; + var i = 0; + + // first walk through and grab non-repeaters + if (pdu.varbinds.length < nonRepeaters) { + req.responseCb(new ResponseInvalidError("Varbind count in " + + "response '" + pdu.varbinds.length + "' is less than " + + "non-repeaters '" + nonRepeaters + "' in request")); + } else { + for (; i < nonRepeaters; i++) { + if (isVarbindError(pdu.varbinds[i])) { + varbinds.push(pdu.varbinds[i]); + } else if (!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 + "'")); + return; + } else { + varbinds.push(pdu.varbinds[i]); + } + } + } + + var repeaters = req.message.pdu.varbinds.length - nonRepeaters; + + // secondly walk through and grab repeaters + if (pdu.varbinds.length % (repeaters)) { + req.responseCb(new ResponseInvalidError("Varbind count in " + + "response '" + pdu.varbinds.length + "' is not a " + + "multiple of repeaters '" + repeaters + + "' plus non-repeaters '" + nonRepeaters + "' in request")); + } else { + while (i < pdu.varbinds.length) { + for (var j = 0; j < repeaters; j++, i++) { + var reqIndex = nonRepeaters + j; + var respIndex = i; + + if (isVarbindError(pdu.varbinds[respIndex])) { + if (!varbinds[reqIndex]) + varbinds[reqIndex] = []; + varbinds[reqIndex].push(pdu.varbinds[respIndex]); + } else if (!oidFollowsOid( + req.message.pdu.varbinds[reqIndex].oid, + pdu.varbinds[respIndex].oid)) { + req.responseCb(new ResponseInvalidError("OID '" + + req.message.pdu.varbinds[reqIndex].oid + + "' in request at positiion '" + (reqIndex) + + "' does not precede OID '" + + pdu.varbinds[respIndex].oid + + "' in response at position '" + (respIndex) + "'")); + return; + } else { + if (!varbinds[reqIndex]) + varbinds[reqIndex] = []; + varbinds[reqIndex].push(pdu.varbinds[respIndex]); + } + } + } + } + + 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) { + 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")); + } else { + for (var i = 0; i < req.message.pdu.varbinds.length; i++) { + if (isVarbindError(pdu.varbinds[i])) { + varbinds.push(pdu.varbinds[i]); + } else if (!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 + "'")); + 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")); + } 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 + "'")); + 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); + + var req = this.unregisterRequest(message.getReqId()); + if (!req) + return; + + if (!message.processIncomingSecurity(this.user, req.responseCb)) + return; + + try { + if (message.version != req.message.version) { + req.responseCb(new ResponseInvalidError("Version in request '" + + req.message.version + "' does not match version in " + + "response '" + message.version + "'")); + } 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")); + } else if (message.pdu.type == PduType.GetResponse) { + req.onResponse(req, message); + } else if (message.pdu.type == PduType.Report) { + if (!req.originalPdu) { + req.responseCb(new ResponseInvalidError("Unexpected Report PDU")); + return; + } + this.msgSecurityParameters = { + msgAuthoritativeEngineID: message.msgSecurityParameters.msgAuthoritativeEngineID, + msgAuthoritativeEngineBoots: message.msgSecurityParameters.msgAuthoritativeEngineBoots, + msgAuthoritativeEngineTime: message.msgSecurityParameters.msgAuthoritativeEngineTime + }; + req.originalPdu.contextName = this.context; + this.sendV3Req(req.originalPdu, req.feedCb, req.responseCb, req.options, req.port); + } else { + req.responseCb(new ResponseInvalidError("Unknown PDU type '" + + message.pdu.type + "' in response")); + } + } catch (error) { + req.responseCb(error); + } + } catch (error) { + this.emit("error", error); + } +}; + +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); +}; + +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) { + 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")); + } 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 request at positiion '" + i + "' does not " + + "match OID '" + pdu.varbinds[i].oid + "' in response " + + "at position '" + i + "'")); + 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) { + try { + var id = _generateId(this.idBitsSize); + 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); + } else { + // SNMPv3 discovery + var discoveryPdu = createDiscoveryPdu(this.context); + var discoveryMessage = Message.createDiscoveryV3(discoveryPdu); + var discoveryReq = new Req(this, discoveryMessage, feedCb, responseCb, options); + discoveryReq.originalPdu = pdu; + this.send(discoveryReq); + } + } else { + message = Message.createCommunity(this.version, this.community, pdu); + req = new Req(this, message, feedCb, responseCb, options); + this.send(req); + } + } catch (error) { + if (responseCb) + responseCb(error); + } +} + +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(varbind[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(varbind[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 = {}; + + try { + 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]; + 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.user.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); + } catch (error) { + if (req.responseCb) + req.responseCb(error); + } + + 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 (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, baseOid; + + 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) { + var message = Message.createRequestV3(this.user, this.msgSecurityParameters, pdu); + var reqOptions = options || {}; + var req = new Req(this, message, feedCb, responseCb, reqOptions); + req.port = port; + this.send(req); +}; + +var Engine = function (engineID, engineBoots, engineTime) { + if (engineID) { + this.engineID = Buffer.from(engineID, 'hex'); + } 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.disableAuthorization = options.disableAuthorization || false; +}; + +Listener.prototype.startListening = function (receiver) { + var me = this; + this.dgram = dgram.createSocket(this.family); + this.dgram.bind(this.port); + 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.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; +}; + +var Authorizer = function () { + this.communities = []; + this.users = []; +} + +Authorizer.prototype.addCommunity = function (community) { + if (this.getCommunity(community)) { + return; + } else { + this.communities.push(community); + } +}; + +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); +}; + +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); + } +}; + + +/***************************************************************************** + ** Receiver class definition + **/ + +var Receiver = function (options, callback) { + DEBUG = options.debug; + this.listener = new Listener(options, this); + this.authorizer = new Authorizer(); + 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.context = (options && options.context) ? options.context : ""; + this.listener = new Listener(options, this); +}; + +Receiver.prototype.addCommunity = function (community) { + this.authorizer.addCommunity(community); +}; + +Receiver.prototype.getCommunity = function (community) { + return this.authorizer.getCommunity(community); +}; + +Receiver.prototype.getCommunities = function () { + return this.authorizer.getCommunities(); +}; + +Receiver.prototype.deleteCommunity = function (community) { + this.authorizer.deleteCommunities(community); +}; + +Receiver.prototype.addUser = function (user) { + this.authorizer.addUser(user); +}; + +Receiver.prototype.getUser = function (userName) { + return this.authorizer.getUser(userName); +}; + +Receiver.prototype.getUsers = function () { + return this.authorizer.getUsers(); +}; + +Receiver.prototype.deleteUser = function (userName) { + this.authorizer.deleteUser(userName); +}; + +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; + } + var 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.pdu, 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.pdu, rinfo)); + } else { + this.callback(new RequestInvalidError("Unexpected PDU type " + message.pdu.type + " (" + PduType[message.pdu.type] + ")")); + } +} + +Receiver.prototype.formatCallbackData = function (pdu, rinfo) { + if (pdu.contextEngineID) { + pdu.contextEngineID = pdu.contextEngineID.toString('hex'); + } + delete pdu.nonRepeaters; + delete pdu.maxRepetitions; + return { + pdu: pdu, + rinfo: rinfo + }; +}; + +Receiver.prototype.close = function () { + this.listener.close(); +}; + +Receiver.create = function (options, callback) { + var receiver = new Receiver(options, callback); + receiver.listener.startListening(); + return receiver; +}; + +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.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.getInstanceNodeForTableRow = function () { + var childCount = Object.keys(this.children).length; + if (childCount == 0) { + if (this.value) { + 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; + if (childCount == 0) { + if (this.value) { + return this; + } else { + // not found + return null; + } + } else { + if (index.length == 0) { + return this.getInstanceNodeForTableRow(); + } else { + var nextChildIndexPart = index[0]; + if (!nextChildIndexPart) { + return null; + } + remainingIndex = index.slice(1); + return this.children[nextChildIndexPart].getInstanceNodeForTableRowIndex(remainingIndex); + } + } +}; + +MibNode.prototype.getNextInstanceNode = function () { + + node = this; + if (this.value) { + // 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); + siblingPosition = childrenAddresses.indexOf(siblingIndex.toString()); + if (siblingPosition + 1 < childrenAddresses.length) { + node = node.children[childrenAddresses[siblingPosition + 1]]; + break; + } + } + } + } + // Descent + while (node) { + if (node.value) { + 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"); + } + 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) { + valueString = " = "; + valueString += options.showTypes ? ObjectType[this.valueType] + ": " : ""; + valueString += options.showValues ? this.value : ""; + } else { + valueString = ""; + } + console.log(this.oid + valueString); + } + for (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 () { + this.root = new MibNode([], null); + this.providers = {}; + this.providerNodes = {}; +}; + +Mib.prototype.addNodesForOid = function (oidString) { + var address = Mib.convertOidToAddress(oidString); + return this.addNodesForAddress(address); +}; + +Mib.prototype.addNodesForAddress = function (address) { + var 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; + var i; + var node; + + address = Mib.convertOidToAddress(oid); + 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.getProviderNodeForInstance = function (instanceNode) { + if (instanceNode.provider) { + throw new ReferenceError("Instance node has provider which should never happen"); + } + return instanceNode.getAncestorProvider(); +}; + +Mib.prototype.addProviderToNode = function (provider) { + var node = this.addNodesForOid(provider.oid); + + node.provider = provider; + if (provider.type == MibProviderType.Table) { + if (!provider.index) { + provider.index = [1]; + } + } + this.providerNodes[provider.name] = node; + return node; +}; + +Mib.prototype.registerProvider = function (provider) { + this.providers[provider.name] = provider; +}; + +Mib.prototype.unregisterProvider = function (name) { + var providerNode = this.providerNodes[name]; + if (providerNode) { + 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.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; + + 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]); + } + if (!providerNode || !providerNode.provider || providerNode.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 = providerNode.provider.scalarType; + } + instanceNode.value = 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.addTableRow = function (table, row) { + var providerNode; + var provider; + var instance = []; + var instanceAddress; + var instanceNode; + + if (this.providers[table] && !this.providerNodes[table]) { + this.addProviderToNode(this.providers[table]); + } + providerNode = this.getProviderNodeForTable(table); + provider = providerNode.provider; + for (var indexPart of provider.index) { + columnPosition = provider.columns.findIndex(column => column.number == indexPart); + instance.push(row[columnPosition]); + } + for (var i = 0; i < providerNode.provider.columns.length; i++) { + var column = providerNode.provider.columns[i]; + instanceAddress = providerNode.address.concat(column.number).concat(instance); + this.addNodesForAddress(instanceAddress); + instanceNode = this.lookup(instanceAddress); + instanceNode.valueType = column.type; + instanceNode.value = row[i]; + } +}; + +Mib.prototype.getTableColumnDefinitions = function (table) { + var providerNode; + var provider; + + providerNode = this.getProviderNodeForTable(table); + provider = providerNode.provider; + return provider.columns; +}; + +Mib.prototype.getTableColumnCells = function (table, columnNumber) { + providerNode = this.getProviderNodeForTable(table); + columnNode = providerNode.children[columnNumber]; + column = [] + for (var row of Object.keys(columnNode.children)) { + instanceNode = columnNode.children[row].getInstanceNodeForTableRow(); + column.push(instanceNode.value); + } + return column; +}; + +Mib.prototype.getTableRowCells = function (table, rowIndex) { + var providerNode; + var columnNode; + var instanceNode; + var row = []; + + providerNode = this.getProviderNodeForTable(table); + for (var columnNumber of Object.keys(providerNode.children)) { + columnNode = providerNode.children[columnNumber]; + instanceNode = columnNode.getInstanceNodeForTableRowIndex(rowIndex); + row.push(instanceNode.value); + } + return row; +}; + +Mib.prototype.getTableCells = function (table, byRows) { + var providerNode; + var columnNode; + var data = []; + + providerNode = this.getProviderNodeForTable(table); + for (var columnNumber of Object.keys(providerNode.children)) { + columnNode = providerNode.children[columnNumber]; + column = []; + data.push(column); + for (var row of Object.keys(columnNode.children)) { + instanceNode = columnNode.children[row].getInstanceNodeForTableRow(); + column.push(instanceNode.value); + } + } + + 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 providerNode; + var columnNode; + var instanceNode; + + providerNode = this.getProviderNodeForTable(table); + columnNode = providerNode.children[columnNumber]; + instanceNode = columnNode.getInstanceNodeForTableRowIndex(rowIndex); + return instanceNode.value; +}; + +Mib.prototype.setTableSingleCell = function (table, columnNumber, rowIndex, value) { + var providerNode; + var columnNode; + var instanceNode; + + providerNode = this.getProviderNodeForTable(table); + columnNode = providerNode.children[columnNumber]; + instanceNode = columnNode.getInstanceNodeForTableRowIndex(rowIndex); + instanceNode.value = value; +}; + +Mib.prototype.deleteTableRow = function (table, rowIndex) { + var providerNode; + var columnNode; + var instanceNode; + var row = []; + + providerNode = this.getProviderNodeForTable(table); + for (var columnNumber of Object.keys(providerNode.children)) { + columnNode = providerNode.children[columnNumber]; + instanceNode = columnNode.getInstanceNodeForTableRowIndex(rowIndex); + if (instanceNode) { + instanceParentNode = instanceNode.parent; + instanceNode.delete(); + instanceParentNode.pruneUpwards(); + } else { + throw new ReferenceError("Cannot find row for index " + rowIndex + " at registered provider " + table); + } + } + return row; +}; + +Mib.prototype.dump = function (options) { + if (!options) { + options = {}; + } + var completedOptions = { + leavesOnly: options.leavesOnly || true, + showProviders: options.leavesOnly || true, + showValues: options.leavesOnly || true, + showTypes: options.leavesOnly || true + }; + 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 < 3) + 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_INT32) { + throw new RangeError('object identifier component ' + + address[i] + ' is too large'); + } + oidArray.push(n); + } + + return oidArray; + +}; + +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) { + DEBUG = options.debug; + this.listener = new Listener(options, this); + this.engine = new Engine(options.engineID); + this.authorizer = new Authorizer(); + this.mib = new Mib(); + this.callback = callback || function () { + }; + this.context = ""; +}; + +Agent.prototype.getMib = function () { + return this.mib; +}; + +Agent.prototype.getAuthorizer = function () { + return this.authorizer; +}; + +Agent.prototype.registerProvider = function (provider) { + this.mib.registerProvider(provider); +}; + +Agent.prototype.unregisterProvider = function (provider) { + this.mib.unregisterProvider(provider); +}; + +Agent.prototype.getProvider = function (provider) { + return this.mib.getProvider(provider); +}; + +Agent.prototype.getProviders = function () { + return this.mib.getProviders(); +}; + +Agent.prototype.onMsg = function (buffer, rinfo) { + var message = Listener.processIncoming(buffer, this.authorizer, this.callback); + var reportMessage; + var responseMessage; + + 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.type == PduType.GetRequest) { + responseMessage = this.request(message, rinfo); + } else if (message.pdu.type == PduType.SetRequest) { + responseMessage = this.request(message, rinfo); + } else if (message.pdu.type == PduType.GetNextRequest) { + responseMessage = this.getNextRequest(message, rinfo); + } else if (message.pdu.type == PduType.GetBulkRequest) { + responseMessage = this.getBulkRequest(message, rinfo); + } else { + this.callback(new RequestInvalidError("Unexpected PDU type " + + message.pdu.type + " (" + PduType[message.pdu.type] + ")")); + } + +}; + +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(); + + for (var i = 0; i < requestPdu.varbinds.length; i++) { + var requestVarbind = requestPdu.varbinds[i]; + var instanceNode = this.mib.lookup(requestVarbind.oid); + var providerNode; + var mibRequest; + var handler; + var responseVarbindType; + + if (!instanceNode) { + mibRequest = new MibRequest({ + operation: requestPdu.type, + oid: requestVarbind.oid + }); + handler = function getNsoHandler(mibRequestForNso) { + mibRequestForNso.done({ + errorStatus: ErrorStatus.NoSuchName, + errorIndex: i + }); + }; + } else { + providerNode = this.mib.getProviderNodeForInstance(instanceNode); + mibRequest = new MibRequest({ + operation: requestPdu.type, + providerNode: providerNode, + instanceNode: instanceNode, + oid: requestVarbind.oid + }); + handler = providerNode.provider.handler; + } + + mibRequest.done = function (error) { + if (error) { + responsePdu.errorStatus = error.errorStatus; + responsePdu.errorIndex = error.errorIndex; + responseVarbind = { + oid: mibRequest.oid, + type: ObjectType.Null, + value: null + }; + } else { + if (requestPdu.type == PduType.SetRequest) { + mibRequest.instanceNode.value = requestVarbind.value; + } + if (requestPdu.type == PduType.GetNextRequest && requestVarbind.type == ObjectType.EndOfMibView) { + responseVarbindType = ObjectType.EndOfMibView; + } else { + responseVarbindType = mibRequest.instanceNode.valueType; + } + responseVarbind = { + oid: mibRequest.oid, + type: responseVarbindType, + value: mibRequest.instanceNode.value + }; + } + me.setSingleVarbind(responsePdu, i, responseVarbind); + if (++varbindsCompleted == varbindsLength) { + me.sendResponse.call(me, rinfo, requestMessage, responsePdu); + } + }; + if (handler) { + handler(mibRequest); + } else { + mibRequest.done(); + } + } + ; +}; + +Agent.prototype.addGetNextVarbind = function (targetVarbinds, startOid) { + var startNode = this.mib.lookup(startOid); + var getNextNode; + + if (!startNode) { + // Off-tree start specified + targetVarbinds.push({ + oid: requestVarbind.oid, + type: ObjectType.Null, + value: null + }); + } else { + getNextNode = startNode.getNextInstanceNode(); + if (!getNextNode) { + // End of MIB + targetVarbinds.push({ + oid: requestVarbind.oid, + type: ObjectType.EndOfMibView, + value: null + }); + } else { + // Normal response + targetVarbinds.push({ + 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; + + for (var n = 0; n < requestPdu.nonRepeaters; n++) { + this.addGetNextVarbind(getBulkVarbinds, requestVarbinds[n].oid); + } + + for (var v = requestPdu.nonRepeaters; v < requestVarbinds.length; v++) { + startOid.push(requestVarbinds[v].oid); + } + + for (var r = 0; r < requestPdu.maxRepetitions; r++) { + for (var v = requestPdu.nonRepeaters; v < requestVarbinds.length; v++) { + getNextNode = this.addGetNextVarbind(getBulkVarbinds, startOid[v - requestPdu.nonRepeaters]); + if (getNextNode) { + startOid[v - requestPdu.nonRepeaters] = getNextNode.oid; + } + } + } + + 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.create = function (options, callback) { + var agent = new Agent(options, callback); + agent.listener.startListening(); + return agent; +}; + +/***************************************************************************** + ** Exports + **/ + +exports.Session = Session; + +exports.createSession = function (target, community, options) { + if (options.version && !(options.version == Version1 || options.version == Version2c)) { + throw new ResponseInvalidError("SNMP community session requested but version '" + options.version + "' specified in options not valid"); + } else { + return new Session(target, community, options); + } +}; + +exports.createV3Session = function (target, user, options) { + if (options.version && options.version != Version3) { + throw new ResponseInvalidError("SNMPv3 session requested but version '" + options.version + "' specified in options"); + } else { + options.version = Version3; + } + return new Session(target, user, options); +}; + +exports.createReceiver = Receiver.create; +exports.createAgent = Agent.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.MibProviderType = MibProviderType; +exports.SecurityLevel = SecurityLevel; +exports.AuthProtocols = AuthProtocols; +exports.PrivProtocols = PrivProtocols; + +exports.ResponseInvalidError = ResponseInvalidError; +exports.RequestInvalidError = RequestInvalidError; +exports.RequestFailedError = RequestFailedError; +exports.RequestTimedOutError = RequestTimedOutError; + +/** + ** We've added this for testing. + **/ +exports.ObjectParser = { + readInt: readInt, + readUint: readUint +}; +exports.Authentication = Authentication; +exports.Encryption = Encryption; diff --git a/collectors/node.d.plugin/node_modules/netdata.js b/collectors/node.d.plugin/node_modules/netdata.js new file mode 100644 index 0000000..603922c --- /dev/null +++ b/collectors/node.d.plugin/node_modules/netdata.js @@ -0,0 +1,654 @@ +'use strict'; + +// netdata +// real-time performance and health monitoring, done right! +// (C) 2016 Costa Tsaousis <costa@tsaousis.gr> +// SPDX-License-Identifier: GPL-3.0-or-later + +var url = require('url'); +var http = require('http'); +var util = require('util'); + +/* +var netdata = require('netdata'); + +var example_chart = { + id: 'id', // the unique id of the chart + name: 'name', // the name of the chart + title: 'title', // the title of the chart + units: 'units', // the units of the chart dimensions + family: 'family', // the family of the chart + context: 'context', // the context of the chart + type: netdata.chartTypes.line, // the type of the chart + priority: 0, // the priority relative to others in the same family + update_every: 1, // the expected update frequency of the chart + dimensions: { + 'dim1': { + id: 'dim1', // the unique id of the dimension + name: 'name', // the name of the dimension + algorithm: netdata.chartAlgorithms.absolute, // the id of the netdata algorithm + multiplier: 1, // the multiplier + divisor: 1, // the divisor + hidden: false, // is hidden (boolean) + }, + 'dim2': { + id: 'dim2', // the unique id of the dimension + name: 'name', // the name of the dimension + algorithm: 'absolute', // the id of the netdata algorithm + multiplier: 1, // the multiplier + divisor: 1, // the divisor + hidden: false, // is hidden (boolean) + } + // add as many dimensions as needed + } +}; +*/ + +var netdata = { + options: { + filename: __filename, + DEBUG: false, + update_every: 1 + }, + + chartAlgorithms: { + incremental: 'incremental', + absolute: 'absolute', + percentage_of_absolute_row: 'percentage-of-absolute-row', + percentage_of_incremental_row: 'percentage-of-incremental-row' + }, + + chartTypes: { + line: 'line', + area: 'area', + stacked: 'stacked' + }, + + services: new Array(), + modules_configuring: 0, + charts: {}, + + processors: { + http: { + name: 'http', + + process: function(service, callback) { + var __DEBUG = netdata.options.DEBUG; + + if(__DEBUG === true) + netdata.debug(service.module.name + ': ' + service.name + ': making ' + this.name + ' request: ' + netdata.stringify(service.request)); + + var req = http.request(service.request, function(response) { + if(__DEBUG === true) netdata.debug(service.module.name + ': ' + service.name + ': got server response...'); + + var end = false; + var data = ''; + response.setEncoding('utf8'); + + if(response.statusCode !== 200) { + if(end === false) { + service.error('Got HTTP code ' + response.statusCode + ', failed to get data.'); + end = true; + return callback(null); + } + } + + response.on('data', function(chunk) { + if(end === false) data += chunk; + }); + + response.on('error', function() { + if(end === false) { + service.error(': Read error, failed to get data.'); + end = true; + return callback(null); + } + }); + + response.on('end', function() { + if(end === false) { + if(__DEBUG === true) netdata.debug(service.module.name + ': ' + service.name + ': read completed.'); + end = true; + return callback(data); + } + }); + }); + + req.on('error', function(e) { + if(__DEBUG === true) netdata.debug('Failed to make request: ' + netdata.stringify(service.request) + ', message: ' + e.message); + service.error('Failed to make request, message: ' + e.message); + return callback(null); + }); + + // write data to request body + if(typeof service.postData !== 'undefined' && service.request.method === 'POST') { + if(__DEBUG === true) netdata.debug(service.module.name + ': ' + service.name + ': posting data: ' + service.postData); + req.write(service.postData); + } + + req.end(); + } + } + }, + + stringify: function(obj) { + return util.inspect(obj, {depth: 10}); + }, + + zeropad2: function(s) { + return ("00" + s).slice(-2); + }, + + logdate: function(d) { + if(typeof d === 'undefined') d = new Date(); + return d.getFullYear().toString() + '-' + this.zeropad2(d.getMonth() + 1) + '-' + this.zeropad2(d.getDate()) + + ' ' + this.zeropad2(d.getHours()) + ':' + this.zeropad2(d.getMinutes()) + ':' + this.zeropad2(d.getSeconds()); + }, + + // show debug info, if debug is enabled + debug: function(msg) { + if(this.options.DEBUG === true) { + console.error(this.logdate() + ': ' + netdata.options.filename + ': DEBUG: ' + ((typeof(msg) === 'object')?netdata.stringify(msg):msg).toString()); + } + }, + + // log an error + error: function(msg) { + console.error(this.logdate() + ': ' + netdata.options.filename + ': ERROR: ' + ((typeof(msg) === 'object')?netdata.stringify(msg):msg).toString()); + }, + + // send data to netdata + send: function(msg) { + console.log(msg.toString()); + }, + + service: function(service) { + if(typeof service === 'undefined') + service = {}; + + var now = Date.now(); + + service._current_chart = null; // the current chart we work on + service._queue = ''; // data to be sent to netdata + + service.error_reported = false; // error log flood control + + service.added = false; // added to netdata.services + service.enabled = true; + service.updates = 0; + service.running = false; + service.started = 0; + service.ended = 0; + + if(typeof service.module === 'undefined') { + service.module = { name: 'not-defined-module' }; + service.error('Attempted to create service without a module.'); + service.enabled = false; + } + + if(typeof service.name === 'undefined') { + service.name = 'unnamed@' + service.module.name + '/' + now; + } + + if(typeof service.processor === 'undefined') + service.processor = netdata.processors.http; + + if(typeof service.update_every === 'undefined') + service.update_every = service.module.update_every; + + if(typeof service.update_every === 'undefined') + service.update_every = netdata.options.update_every; + + if(service.update_every < netdata.options.update_every) + service.update_every = netdata.options.update_every; + + // align the runs + service.next_run = now - (now % (service.update_every * 1000)) + (service.update_every * 1000); + + service.commit = function() { + if(this.added !== true) { + this.added = true; + + var now = Date.now(); + this.next_run = now - (now % (service.update_every * 1000)) + (service.update_every * 1000); + + netdata.services.push(this); + if(netdata.options.DEBUG === true) netdata.debug(this.module.name + ': ' + this.name + ': service committed.'); + } + }; + + service.execute = function(responseProcessor) { + var __DEBUG = netdata.options.DEBUG; + + if(service.enabled === false) + return responseProcessor(null); + + this.module.active++; + this.running = true; + this.started = Date.now(); + this.updates++; + + if(__DEBUG === true) + netdata.debug(this.module.name + ': ' + this.name + ': making ' + this.processor.name + ' request: ' + netdata.stringify(this)); + + this.processor.process(this, function(response) { + service.ended = Date.now(); + service.duration = service.ended - service.started; + + if(typeof response === 'undefined') + response = null; + + if(response !== null) + service.errorClear(); + + if(__DEBUG === true) + netdata.debug(service.module.name + ': ' + service.name + ': processing ' + service.processor.name + ' response (received in ' + (service.ended - service.started).toString() + ' ms)'); + + try { + responseProcessor(service, response); + } + catch(e) { + netdata.error(e); + service.error("responseProcessor failed process response data."); + } + + service.running = false; + service.module.active--; + if(service.module.active < 0) { + service.module.active = 0; + if(__DEBUG === true) + netdata.debug(service.module.name + ': active module counter below zero.'); + } + + if(service.module.active === 0) { + // check if we run under configure + if(service.module.configure_callback !== null) { + if(__DEBUG === true) + netdata.debug(service.module.name + ': configuration finish callback called from processResponse().'); + + var configure_callback = service.module.configure_callback; + service.module.configure_callback = null; + configure_callback(); + } + } + }); + }; + + service.update = function() { + if(netdata.options.DEBUG === true) + netdata.debug(this.module.name + ': ' + this.name + ': starting data collection...'); + + this.module.update(this, function() { + if(netdata.options.DEBUG === true) + netdata.debug(service.module.name + ': ' + service.name + ': data collection ended in ' + service.duration.toString() + ' ms.'); + }); + }; + + service.error = function(message) { + if(this.error_reported === false) { + netdata.error(this.module.name + ': ' + this.name + ': ' + message); + this.error_reported = true; + } + else if(netdata.options.DEBUG === true) + netdata.debug(this.module.name + ': ' + this.name + ': ' + message); + }; + + service.errorClear = function() { + this.error_reported = false; + }; + + service.queue = function(txt) { + this._queue += txt + '\n'; + }; + + service._send_chart_to_netdata = function(chart) { + // internal function to send a chart to netdata + this.queue('CHART "' + chart.id + '" "' + chart.name + '" "' + chart.title + '" "' + chart.units + '" "' + chart.family + '" "' + chart.context + '" "' + chart.type + '" ' + chart.priority.toString() + ' ' + chart.update_every.toString()); + + if(typeof(chart.dimensions) !== 'undefined') { + var dims = Object.keys(chart.dimensions); + var len = dims.length; + while(len--) { + var d = chart.dimensions[dims[len]]; + + this.queue('DIMENSION "' + d.id + '" "' + d.name + '" "' + d.algorithm + '" ' + d.multiplier.toString() + ' ' + d.divisor.toString() + ' ' + ((d.hidden === true) ? 'hidden' : '').toString()); + d._created = true; + d._updated = false; + } + } + + chart._created = true; + chart._updated = false; + }; + + // begin data collection for a chart + service.begin = function(chart) { + if(this._current_chart !== null && this._current_chart !== chart) { + this.error('Called begin() for chart ' + chart.id + ' while chart ' + this._current_chart.id + ' is still open. Closing it.'); + this.end(); + } + + if(typeof(chart.id) === 'undefined' || netdata.charts[chart.id] !== chart) { + this.error('Called begin() for chart ' + chart.id + ' that is not mine. Where did you find it? Ignoring it.'); + return false; + } + + if(netdata.options.DEBUG === true) netdata.debug('setting current chart to ' + chart.id); + this._current_chart = chart; + this._current_chart._began = true; + + if(this._current_chart._dimensions_count !== 0) { + if(this._current_chart._created === false || this._current_chart._updated === true) + this._send_chart_to_netdata(this._current_chart); + + var now = this.ended; + this.queue('BEGIN ' + this._current_chart.id + ' ' + ((this._current_chart._last_updated > 0)?((now - this._current_chart._last_updated) * 1000):'').toString()); + } + // else this.error('Called begin() for chart ' + chart.id + ' which is empty.'); + + this._current_chart._last_updated = now; + this._current_chart._began = true; + this._current_chart._counter++; + + return true; + }; + + // set a collected value for a chart + // we do most things on the first value we attempt to set + service.set = function(dimension, value) { + if(this._current_chart === null) { + this.error('Called set(' + dimension + ', ' + value + ') without an open chart.'); + return false; + } + + if(typeof(this._current_chart.dimensions[dimension]) === 'undefined') { + this.error('Called set(' + dimension + ', ' + value + ') but dimension "' + dimension + '" does not exist in chart "' + this._current_chart.id + '".'); + return false; + } + + if(typeof value === 'undefined' || value === null) + return false; + + if(this._current_chart._dimensions_count !== 0) + this.queue('SET ' + dimension + ' = ' + value.toString()); + + return true; + }; + + // end data collection for the current chart - after calling begin() + service.end = function() { + if(this._current_chart !== null && this._current_chart._began === false) { + this.error('Called end() without an open chart.'); + return false; + } + + if(this._current_chart._dimensions_count !== 0) { + this.queue('END'); + netdata.send(this._queue); + } + + this._queue = ''; + this._current_chart._began = false; + if(netdata.options.DEBUG === true) netdata.debug('sent chart ' + this._current_chart.id); + this._current_chart = null; + return true; + }; + + // discard the collected values for the current chart - after calling begin() + service.flush = function() { + if(this._current_chart === null || this._current_chart._began === false) { + this.error('Called flush() without an open chart.'); + return false; + } + + this._queue = ''; + this._current_chart._began = false; + this._current_chart = null; + return true; + }; + + // create a netdata chart + service.chart = function(id, chart) { + var __DEBUG = netdata.options.DEBUG; + + if(typeof(netdata.charts[id]) === 'undefined') { + netdata.charts[id] = { + _created: false, + _updated: true, + _began: false, + _counter: 0, + _last_updated: 0, + _dimensions_count: 0, + id: id, + name: id, + title: 'untitled chart', + units: 'a unit', + family: '', + context: '', + type: netdata.chartTypes.line, + priority: 50000, + update_every: netdata.options.update_every, + dimensions: {} + }; + } + + var c = netdata.charts[id]; + + if(typeof(chart.name) !== 'undefined' && chart.name !== c.name) { + if(__DEBUG === true) netdata.debug('chart ' + id + ' updated its name'); + c.name = chart.name; + c._updated = true; + } + + if(typeof(chart.title) !== 'undefined' && chart.title !== c.title) { + if(__DEBUG === true) netdata.debug('chart ' + id + ' updated its title'); + c.title = chart.title; + c._updated = true; + } + + if(typeof(chart.units) !== 'undefined' && chart.units !== c.units) { + if(__DEBUG === true) netdata.debug('chart ' + id + ' updated its units'); + c.units = chart.units; + c._updated = true; + } + + if(typeof(chart.family) !== 'undefined' && chart.family !== c.family) { + if(__DEBUG === true) netdata.debug('chart ' + id + ' updated its family'); + c.family = chart.family; + c._updated = true; + } + + if(typeof(chart.context) !== 'undefined' && chart.context !== c.context) { + if(__DEBUG === true) netdata.debug('chart ' + id + ' updated its context'); + c.context = chart.context; + c._updated = true; + } + + if(typeof(chart.type) !== 'undefined' && chart.type !== c.type) { + if(__DEBUG === true) netdata.debug('chart ' + id + ' updated its type'); + c.type = chart.type; + c._updated = true; + } + + if(typeof(chart.priority) !== 'undefined' && chart.priority !== c.priority) { + if(__DEBUG === true) netdata.debug('chart ' + id + ' updated its priority'); + c.priority = chart.priority; + c._updated = true; + } + + if(typeof(chart.update_every) !== 'undefined' && chart.update_every !== c.update_every) { + if(__DEBUG === true) netdata.debug('chart ' + id + ' updated its update_every from ' + c.update_every + ' to ' + chart.update_every); + c.update_every = chart.update_every; + c._updated = true; + } + + if(typeof(chart.dimensions) !== 'undefined') { + var dims = Object.keys(chart.dimensions); + var len = dims.length; + while(len--) { + var x = dims[len]; + + if(typeof(c.dimensions[x]) === 'undefined') { + c._dimensions_count++; + + c.dimensions[x] = { + _created: false, + _updated: false, + id: x, // the unique id of the dimension + name: x, // the name of the dimension + algorithm: netdata.chartAlgorithms.absolute, // the id of the netdata algorithm + multiplier: 1, // the multiplier + divisor: 1, // the divisor + hidden: false // is hidden (boolean) + }; + + if(__DEBUG === true) netdata.debug('chart ' + id + ' created dimension ' + x); + c._updated = true; + } + + var dim = chart.dimensions[x]; + var d = c.dimensions[x]; + + if(typeof(dim.name) !== 'undefined' && d.name !== dim.name) { + if(__DEBUG === true) netdata.debug('chart ' + id + ', dimension ' + x + ' updated its name'); + d.name = dim.name; + d._updated = true; + } + + if(typeof(dim.algorithm) !== 'undefined' && d.algorithm !== dim.algorithm) { + if(__DEBUG === true) netdata.debug('chart ' + id + ', dimension ' + x + ' updated its algorithm from ' + d.algorithm + ' to ' + dim.algorithm); + d.algorithm = dim.algorithm; + d._updated = true; + } + + if(typeof(dim.multiplier) !== 'undefined' && d.multiplier !== dim.multiplier) { + if(__DEBUG === true) netdata.debug('chart ' + id + ', dimension ' + x + ' updated its multiplier'); + d.multiplier = dim.multiplier; + d._updated = true; + } + + if(typeof(dim.divisor) !== 'undefined' && d.divisor !== dim.divisor) { + if(__DEBUG === true) netdata.debug('chart ' + id + ', dimension ' + x + ' updated its divisor'); + d.divisor = dim.divisor; + d._updated = true; + } + + if(typeof(dim.hidden) !== 'undefined' && d.hidden !== dim.hidden) { + if(__DEBUG === true) netdata.debug('chart ' + id + ', dimension ' + x + ' updated its hidden status'); + d.hidden = dim.hidden; + d._updated = true; + } + + if(d._updated) c._updated = true; + } + } + + //if(netdata.options.DEBUG === true) netdata.debug(netdata.charts); + return netdata.charts[id]; + }; + + return service; + }, + + runAllServices: function() { + if(netdata.options.DEBUG === true) netdata.debug('runAllServices()'); + + var now = Date.now(); + var len = netdata.services.length; + while(len--) { + var service = netdata.services[len]; + + if(service.enabled === false || service.running === true) continue; + if(now <= service.next_run) continue; + + service.update(); + + now = Date.now(); + service.next_run = now - (now % (service.update_every * 1000)) + (service.update_every * 1000); + } + + // 1/10th of update_every in pause + setTimeout(netdata.runAllServices, netdata.options.update_every * 100); + }, + + start: function() { + if(netdata.options.DEBUG === true) this.debug('started, services: ' + netdata.stringify(this.services)); + + if(this.services.length === 0) { + this.disableNodePlugin(); + + // eslint suggested way to exit + var exit = process.exit; + exit(1); + } + else this.runAllServices(); + }, + + // disable the whole node.js plugin + disableNodePlugin: function() { + this.send('DISABLE'); + + // eslint suggested way to exit + var exit = process.exit; + exit(1); + }, + + requestFromParams: function(protocol, hostname, port, path, method) { + return { + protocol: protocol, + hostname: hostname, + port: port, + path: path, + //family: 4, + method: method, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Connection': 'keep-alive' + }, + agent: new http.Agent({ + keepAlive: true, + keepAliveMsecs: netdata.options.update_every * 1000, + maxSockets: 2, // it must be 2 to work + maxFreeSockets: 1 + }) + }; + }, + + requestFromURL: function(a_url) { + var u = url.parse(a_url); + return netdata.requestFromParams(u.protocol, u.hostname, u.port, u.path, 'GET'); + }, + + configure: function(module, config, callback) { + if(netdata.options.DEBUG === true) this.debug(module.name + ': configuring (update_every: ' + this.options.update_every + ')...'); + + module.active = 0; + module.update_every = this.options.update_every; + + if(typeof config.update_every !== 'undefined') + module.update_every = config.update_every; + + module.enable_autodetect = (config.enable_autodetect)?true:false; + + if(typeof(callback) === 'function') + module.configure_callback = callback; + else + module.configure_callback = null; + + var added = module.configure(config); + + if(netdata.options.DEBUG === true) this.debug(module.name + ': configured, reporting ' + added + ' eligible services.'); + + if(module.configure_callback !== null && added === 0) { + if(netdata.options.DEBUG === true) this.debug(module.name + ': configuration finish callback called from configure().'); + var configure_callback = module.configure_callback; + module.configure_callback = null; + configure_callback(); + } + + return added; + } +}; + +if(netdata.options.DEBUG === true) netdata.debug('loaded netdata from:', __filename); +module.exports = netdata; diff --git a/collectors/node.d.plugin/node_modules/pixl-xml.js b/collectors/node.d.plugin/node_modules/pixl-xml.js new file mode 100644 index 0000000..48de89e --- /dev/null +++ b/collectors/node.d.plugin/node_modules/pixl-xml.js @@ -0,0 +1,607 @@ +// SPDX-License-Identifier: MIT +/* + JavaScript XML Library + Plus a bunch of object utility functions + + Usage: + var XML = require('pixl-xml'); + var myxmlstring = '<?xml version="1.0"?><Document>' + + '<Simple>Hello</Simple>' + + '<Node Key="Value">Content</Node>' + + '</Document>'; + + var tree = XML.parse( myxmlstring, { preserveAttributes: true }); + console.log( tree ); + + tree.Simple = "Hello2"; + tree.Node._Attribs.Key = "Value2"; + tree.Node._Data = "Content2"; + tree.New = "I added this"; + + console.log( XML.stringify( tree, 'Document' ) ); + + Copyright (c) 2004 - 2015 Joseph Huckaby + Released under the MIT License + This version is for Node.JS, converted in 2012. +*/ + +var fs = require('fs'); + +var indent_string = "\t"; +var xml_header = '<?xml version="1.0"?>'; +var sort_args = null; +var re_valid_tag_name = /^\w[\w\-\:]*$/; + +var XML = exports.XML = function XML(args) { + // class constructor for XML parser class + // pass in args hash or text to parse + if (!args) args = ''; + if (isa_hash(args)) { + for (var key in args) this[key] = args[key]; + } + else this.text = args || ''; + + // stringify buffers + if (this.text instanceof Buffer) { + this.text = this.text.toString(); + } + + if (!this.text.match(/^\s*</)) { + // try as file path + var file = this.text; + this.text = fs.readFileSync(file, { encoding: 'utf8' }); + if (!this.text) throw new Error("File not found: " + file); + } + + this.tree = {}; + this.errors = []; + this.piNodeList = []; + this.dtdNodeList = []; + this.documentNodeName = ''; + + if (this.lowerCase) { + this.attribsKey = this.attribsKey.toLowerCase(); + this.dataKey = this.dataKey.toLowerCase(); + } + + this.patTag.lastIndex = 0; + if (this.text) this.parse(); +} + +XML.prototype.preserveAttributes = false; +XML.prototype.lowerCase = false; + +XML.prototype.patTag = /([^<]*?)<([^>]+)>/g; +XML.prototype.patSpecialTag = /^\s*([\!\?])/; +XML.prototype.patPITag = /^\s*\?/; +XML.prototype.patCommentTag = /^\s*\!--/; +XML.prototype.patDTDTag = /^\s*\!DOCTYPE/; +XML.prototype.patCDATATag = /^\s*\!\s*\[\s*CDATA/; +XML.prototype.patStandardTag = /^\s*(\/?)([\w\-\:\.]+)\s*(.*)$/; +XML.prototype.patSelfClosing = /\/\s*$/; +XML.prototype.patAttrib = new RegExp("([\\w\\-\\:\\.]+)\\s*=\\s*([\\\"\\'])([^\\2]*?)\\2", "g"); +XML.prototype.patPINode = /^\s*\?\s*([\w\-\:]+)\s*(.*)$/; +XML.prototype.patEndComment = /--$/; +XML.prototype.patNextClose = /([^>]*?)>/g; +XML.prototype.patExternalDTDNode = new RegExp("^\\s*\\!DOCTYPE\\s+([\\w\\-\\:]+)\\s+(SYSTEM|PUBLIC)\\s+\\\"([^\\\"]+)\\\""); +XML.prototype.patInlineDTDNode = /^\s*\!DOCTYPE\s+([\w\-\:]+)\s+\[/; +XML.prototype.patEndDTD = /\]$/; +XML.prototype.patDTDNode = /^\s*\!DOCTYPE\s+([\w\-\:]+)\s+\[(.*)\]/; +XML.prototype.patEndCDATA = /\]\]$/; +XML.prototype.patCDATANode = /^\s*\!\s*\[\s*CDATA\s*\[([^]*)\]\]/; + +XML.prototype.attribsKey = '_Attribs'; +XML.prototype.dataKey = '_Data'; + +XML.prototype.parse = function(branch, name) { + // parse text into XML tree, recurse for nested nodes + if (!branch) branch = this.tree; + if (!name) name = null; + var foundClosing = false; + var matches = null; + + // match each tag, plus preceding text + while ( matches = this.patTag.exec(this.text) ) { + var before = matches[1]; + var tag = matches[2]; + + // text leading up to tag = content of parent node + if (before.match(/\S/)) { + if (typeof(branch[this.dataKey]) != 'undefined') branch[this.dataKey] += ' '; else branch[this.dataKey] = ''; + branch[this.dataKey] += trim(decode_entities(before)); + } + + // parse based on tag type + if (tag.match(this.patSpecialTag)) { + // special tag + if (tag.match(this.patPITag)) tag = this.parsePINode(tag); + else if (tag.match(this.patCommentTag)) tag = this.parseCommentNode(tag); + else if (tag.match(this.patDTDTag)) tag = this.parseDTDNode(tag); + else if (tag.match(this.patCDATATag)) { + tag = this.parseCDATANode(tag); + if (typeof(branch[this.dataKey]) != 'undefined') branch[this.dataKey] += ' '; else branch[this.dataKey] = ''; + branch[this.dataKey] += trim(decode_entities(tag)); + } // cdata + else { + this.throwParseError( "Malformed special tag", tag ); + break; + } // error + + if (tag == null) break; + continue; + } // special tag + else { + // Tag is standard, so parse name and attributes (if any) + var matches = tag.match(this.patStandardTag); + if (!matches) { + this.throwParseError( "Malformed tag", tag ); + break; + } + + var closing = matches[1]; + var nodeName = this.lowerCase ? matches[2].toLowerCase() : matches[2]; + var attribsRaw = matches[3]; + + // If this is a closing tag, make sure it matches its opening tag + if (closing) { + if (nodeName == (name || '')) { + foundClosing = 1; + break; + } + else { + this.throwParseError( "Mismatched closing tag (expected </" + name + ">)", tag ); + break; + } + } // closing tag + else { + // Not a closing tag, so parse attributes into hash. If tag + // is self-closing, no recursive parsing is needed. + var selfClosing = !!attribsRaw.match(this.patSelfClosing); + var leaf = {}; + var attribs = leaf; + + // preserve attributes means they go into a sub-hash named "_Attribs" + // the XML composer honors this for restoring the tree back into XML + if (this.preserveAttributes) { + leaf[this.attribsKey] = {}; + attribs = leaf[this.attribsKey]; + } + + // parse attributes + this.patAttrib.lastIndex = 0; + while ( matches = this.patAttrib.exec(attribsRaw) ) { + var key = this.lowerCase ? matches[1].toLowerCase() : matches[1]; + attribs[ key ] = decode_entities( matches[3] ); + } // foreach attrib + + // if no attribs found, but we created the _Attribs subhash, clean it up now + if (this.preserveAttributes && !num_keys(attribs)) { + delete leaf[this.attribsKey]; + } + + // Recurse for nested nodes + if (!selfClosing) { + this.parse( leaf, nodeName ); + if (this.error()) break; + } + + // Compress into simple node if text only + var num_leaf_keys = num_keys(leaf); + if ((typeof(leaf[this.dataKey]) != 'undefined') && (num_leaf_keys == 1)) { + leaf = leaf[this.dataKey]; + } + else if (!num_leaf_keys) { + leaf = ''; + } + + // Add leaf to parent branch + if (typeof(branch[nodeName]) != 'undefined') { + if (isa_array(branch[nodeName])) { + branch[nodeName].push( leaf ); + } + else { + var temp = branch[nodeName]; + branch[nodeName] = [ temp, leaf ]; + } + } + else { + branch[nodeName] = leaf; + } + + if (this.error() || (branch == this.tree)) break; + } // not closing + } // standard tag + } // main reg exp + + // Make sure we found the closing tag + if (name && !foundClosing) { + this.throwParseError( "Missing closing tag (expected </" + name + ">)", name ); + } + + // If we are the master node, finish parsing and setup our doc node + if (branch == this.tree) { + if (typeof(this.tree[this.dataKey]) != 'undefined') delete this.tree[this.dataKey]; + + if (num_keys(this.tree) > 1) { + this.throwParseError( 'Only one top-level node is allowed in document', first_key(this.tree) ); + return; + } + + this.documentNodeName = first_key(this.tree); + if (this.documentNodeName) { + this.tree = this.tree[this.documentNodeName]; + } + } +}; + +XML.prototype.throwParseError = function(key, tag) { + // log error and locate current line number in source XML document + var parsedSource = this.text.substring(0, this.patTag.lastIndex); + var eolMatch = parsedSource.match(/\n/g); + var lineNum = (eolMatch ? eolMatch.length : 0) + 1; + lineNum -= tag.match(/\n/) ? tag.match(/\n/g).length : 0; + + this.errors.push({ + type: 'Parse', + key: key, + text: '<' + tag + '>', + line: lineNum + }); + + // Throw actual error (must wrap parse in try/catch) + throw new Error( this.getLastError() ); +}; + +XML.prototype.error = function() { + // return number of errors + return this.errors.length; +}; + +XML.prototype.getError = function(error) { + // get formatted error + var text = ''; + if (!error) return ''; + + text = (error.type || 'General') + ' Error'; + if (error.code) text += ' ' + error.code; + text += ': ' + error.key; + + if (error.line) text += ' on line ' + error.line; + if (error.text) text += ': ' + error.text; + + return text; +}; + +XML.prototype.getLastError = function() { + // Get most recently thrown error in plain text format + if (!this.error()) return ''; + return this.getError( this.errors[this.errors.length - 1] ); +}; + +XML.prototype.parsePINode = function(tag) { + // Parse Processor Instruction Node, e.g. <?xml version="1.0"?> + if (!tag.match(this.patPINode)) { + this.throwParseError( "Malformed processor instruction", tag ); + return null; + } + + this.piNodeList.push( tag ); + return tag; +}; + +XML.prototype.parseCommentNode = function(tag) { + // Parse Comment Node, e.g. <!-- hello --> + var matches = null; + this.patNextClose.lastIndex = this.patTag.lastIndex; + + while (!tag.match(this.patEndComment)) { + if (matches = this.patNextClose.exec(this.text)) { + tag += '>' + matches[1]; + } + else { + this.throwParseError( "Unclosed comment tag", tag ); + return null; + } + } + + this.patTag.lastIndex = this.patNextClose.lastIndex; + return tag; +}; + +XML.prototype.parseDTDNode = function(tag) { + // Parse Document Type Descriptor Node, e.g. <!DOCTYPE ... > + var matches = null; + + if (tag.match(this.patExternalDTDNode)) { + // tag is external, and thus self-closing + this.dtdNodeList.push( tag ); + } + else if (tag.match(this.patInlineDTDNode)) { + // Tag is inline, so check for nested nodes. + this.patNextClose.lastIndex = this.patTag.lastIndex; + + while (!tag.match(this.patEndDTD)) { + if (matches = this.patNextClose.exec(this.text)) { + tag += '>' + matches[1]; + } + else { + this.throwParseError( "Unclosed DTD tag", tag ); + return null; + } + } + + this.patTag.lastIndex = this.patNextClose.lastIndex; + + // Make sure complete tag is well-formed, and push onto DTD stack. + if (tag.match(this.patDTDNode)) { + this.dtdNodeList.push( tag ); + } + else { + this.throwParseError( "Malformed DTD tag", tag ); + return null; + } + } + else { + this.throwParseError( "Malformed DTD tag", tag ); + return null; + } + + return tag; +}; + +XML.prototype.parseCDATANode = function(tag) { + // Parse CDATA Node, e.g. <![CDATA[Brooks & Shields]]> + var matches = null; + this.patNextClose.lastIndex = this.patTag.lastIndex; + + while (!tag.match(this.patEndCDATA)) { + if (matches = this.patNextClose.exec(this.text)) { + tag += '>' + matches[1]; + } + else { + this.throwParseError( "Unclosed CDATA tag", tag ); + return null; + } + } + + this.patTag.lastIndex = this.patNextClose.lastIndex; + + if (matches = tag.match(this.patCDATANode)) { + return matches[1]; + } + else { + this.throwParseError( "Malformed CDATA tag", tag ); + return null; + } +}; + +XML.prototype.getTree = function() { + // get reference to parsed XML tree + return this.tree; +}; + +XML.prototype.compose = function() { + // compose tree back into XML + var raw = compose_xml( this.tree, this.documentNodeName ); + var body = raw.substring( raw.indexOf("\n") + 1, raw.length ); + var xml = ''; + + if (this.piNodeList.length) { + for (var idx = 0, len = this.piNodeList.length; idx < len; idx++) { + xml += '<' + this.piNodeList[idx] + '>' + "\n"; + } + } + else { + xml += xml_header + "\n"; + } + + if (this.dtdNodeList.length) { + for (var idx = 0, len = this.dtdNodeList.length; idx < len; idx++) { + xml += '<' + this.dtdNodeList[idx] + '>' + "\n"; + } + } + + xml += body; + return xml; +}; + +// +// Static Utility Functions: +// + +var parse_xml = exports.parse = function parse_xml(text, opts) { + // turn text into XML tree quickly + if (!opts) opts = {}; + opts.text = text; + var parser = new XML(opts); + return parser.error() ? parser.getLastError() : parser.getTree(); +}; + +var trim = exports.trim = function trim(text) { + // strip whitespace from beginning and end of string + if (text == null) return ''; + + if (text && text.replace) { + text = text.replace(/^\s+/, ""); + text = text.replace(/\s+$/, ""); + } + + return text; +}; + +var encode_entities = exports.encodeEntities = function encode_entities(text) { + // Simple entitize exports.for = function for composing XML + if (text == null) return ''; + + if (text && text.replace) { + text = text.replace(/\&/g, "&"); // MUST BE FIRST + text = text.replace(/</g, "<"); + text = text.replace(/>/g, ">"); + } + + return text; +}; + +var encode_attrib_entities = exports.encodeAttribEntities = function encode_attrib_entities(text) { + // Simple entitize exports.for = function for composing XML attributes + if (text == null) return ''; + + if (text && text.replace) { + text = text.replace(/\&/g, "&"); // MUST BE FIRST + text = text.replace(/</g, "<"); + text = text.replace(/>/g, ">"); + text = text.replace(/\"/g, """); + text = text.replace(/\'/g, "'"); + } + + return text; +}; + +var decode_entities = exports.decodeEntities = function decode_entities(text) { + // Decode XML entities into raw ASCII + if (text == null) return ''; + + if (text && text.replace && text.match(/\&/)) { + text = text.replace(/\<\;/g, "<"); + text = text.replace(/\>\;/g, ">"); + text = text.replace(/\"\;/g, '"'); + text = text.replace(/\&apos\;/g, "'"); + text = text.replace(/\&\;/g, "&"); // MUST BE LAST + } + + return text; +}; + +var compose_xml = exports.stringify = function compose_xml(node, name, indent) { + // Compose node into XML including attributes + // Recurse for child nodes + var xml = ""; + + // If this is the root node, set the indent to 0 + // and setup the XML header (PI node) + if (!indent) { + indent = 0; + xml = xml_header + "\n"; + + if (!name) { + // no name provided, assume content is wrapped in it + name = first_key(node); + node = node[name]; + } + } + + // Setup the indent text + var indent_text = ""; + for (var k = 0; k < indent; k++) indent_text += indent_string; + + if ((typeof(node) == 'object') && (node != null)) { + // node is object -- now see if it is an array or hash + if (!node.length) { // what about zero-length array? + // node is hash + xml += indent_text + "<" + name; + + var num_keys = 0; + var has_attribs = 0; + for (var key in node) num_keys++; // there must be a better way... + + if (node["_Attribs"]) { + has_attribs = 1; + var sorted_keys = hash_keys_to_array(node["_Attribs"]).sort(); + for (var idx = 0, len = sorted_keys.length; idx < len; idx++) { + var key = sorted_keys[idx]; + xml += " " + key + "=\"" + encode_attrib_entities(node["_Attribs"][key]) + "\""; + } + } // has attribs + + if (num_keys > has_attribs) { + // has child elements + xml += ">"; + + if (node["_Data"]) { + // simple text child node + xml += encode_entities(node["_Data"]) + "</" + name + ">\n"; + } // just text + else { + xml += "\n"; + + var sorted_keys = hash_keys_to_array(node).sort(); + for (var idx = 0, len = sorted_keys.length; idx < len; idx++) { + var key = sorted_keys[idx]; + if ((key != "_Attribs") && key.match(re_valid_tag_name)) { + // recurse for node, with incremented indent value + xml += compose_xml( node[key], key, indent + 1 ); + } // not _Attribs key + } // foreach key + + xml += indent_text + "</" + name + ">\n"; + } // real children + } + else { + // no child elements, so self-close + xml += "/>\n"; + } + } // standard node + else { + // node is array + for (var idx = 0; idx < node.length; idx++) { + // recurse for node in array with same indent + xml += compose_xml( node[idx], name, indent ); + } + } // array of nodes + } // complex node + else { + // node is simple string + xml += indent_text + "<" + name + ">" + encode_entities(node) + "</" + name + ">\n"; + } // simple text node + + return xml; +}; + +var always_array = exports.alwaysArray = function always_array(obj, key) { + // if object is not array, return array containing object + // if key is passed, work like XMLalwaysarray() instead + if (key) { + if ((typeof(obj[key]) != 'object') || (typeof(obj[key].length) == 'undefined')) { + var temp = obj[key]; + delete obj[key]; + obj[key] = new Array(); + obj[key][0] = temp; + } + return null; + } + else { + if ((typeof(obj) != 'object') || (typeof(obj.length) == 'undefined')) { return [ obj ]; } + else return obj; + } +}; + +var hash_keys_to_array = exports.hashKeysToArray = function hash_keys_to_array(hash) { + // convert hash keys to array (discard values) + var array = []; + for (var key in hash) array.push(key); + return array; +}; + +var isa_hash = exports.isaHash = function isa_hash(arg) { + // determine if arg is a hash + return( !!arg && (typeof(arg) == 'object') && (typeof(arg.length) == 'undefined') ); +}; + +var isa_array = exports.isaArray = function isa_array(arg) { + // determine if arg is an array or is array-like + if (typeof(arg) == 'array') return true; + return( !!arg && (typeof(arg) == 'object') && (typeof(arg.length) != 'undefined') ); +}; + +var first_key = exports.firstKey = function first_key(hash) { + // return first key from hash (unordered) + for (var key in hash) return key; + return null; // no keys in hash +}; + +var num_keys = exports.numKeys = function num_keys(hash) { + // count the number of keys in a hash + var count = 0; + for (var a in hash) count++; + return count; +}; |