net-snmp 3.28.0 → 3.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +30 -1
  2. package/index.js +98 -19
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -157,6 +157,18 @@ constants are defined in this object:
157
157
  * `NotWritable`
158
158
  * `InconsistentName`
159
159
 
160
+ Only the first six of these - `NoError` through `GeneralError` - are defined by
161
+ SNMPv1. The agent produces error statuses using the full SNMPv2 set regardless
162
+ of the version of the request, then translates them when responding to an
163
+ SNMPv1 request, using the mapping given in RFC 2089 section 2.1 (repeated in
164
+ RFC 3584 section 4.4). So an SNMPv1 manager sees `NoSuchName` where an SNMPv2c
165
+ or SNMPv3 manager would see `NoAccess`, `NoCreation`, `NotWritable`,
166
+ `InconsistentName` or `AuthorizationError`; `BadValue` in place of `WrongType`,
167
+ `WrongLength`, `WrongEncoding`, `WrongValue` or `InconsistentValue`; and
168
+ `GeneralError` in place of `ResourceUnavailable`, `CommitFailed` or
169
+ `UndoFailed`. `ReadOnly` is listed for completeness, since SNMPv1 defines it,
170
+ but no conformant agent generates it - see RFC 1908 section 3.1.2.
171
+
160
172
  ## snmp.ObjectType
161
173
 
162
174
  This object contains constants used to specify syntax for varbind objects,
@@ -2171,7 +2183,12 @@ level provided by this provider. The allowable values are the
2171
2183
  numeric values from the MaxAccess export. If a `maxAccess` value is
2172
2184
  specified, a `get` request to the agent will return a `noAccess`
2173
2185
  error if `maxAccess` is not at least "read-only" (2). `maxAccess`
2174
- must be at least "read-write" (3) for a `set` request to succeed.
2186
+ must be at least "read-write" (3) for a `set` request to succeed;
2187
+ a `set` below that level returns a `notWritable` error, which is the
2188
+ code RFC 3416 section 4.2.5 reserves for a variable that exists but
2189
+ cannot be modified whatever value is supplied. Note that `noAccess`
2190
+ there means something narrower - a variable denied because it is not
2191
+ in the requester's MIB view.
2175
2192
  * `defVal` *(optional)* - the default value to assign for scalar
2176
2193
  objects automatically created, when `maxAccess` is set to
2177
2194
  "read-create" (4). Note that table columns can specify such `defVal`
@@ -3779,6 +3796,18 @@ Example programs are included under the module's `example` directory.
3779
3796
 
3780
3797
  * Replace `.npmignore` with a `files` allowlist in `package.json`, so the published package ships only the module, its libraries, examples and reference files
3781
3798
 
3799
+ # Version 3.28.1 - 12/09/2026
3800
+
3801
+ * Reject table instance OIDs whose row index encoding is malformed, instead of silently decoding them to a different row - a SetRequest naming a non-implied `OctetString` or OID index that claimed a different length than it supplied, or that carried unexpected trailing components, would create a row at the decoded index while answering `NoSuchInstance` for the OID that was requested
3802
+
3803
+ * Decode index parts declared with a fixed `length` without consuming a leading length component, matching the way they are encoded - an index value of `AB` in such a column previously read back as `B`
3804
+
3805
+ # Version 3.29.0 - 13/09/2026
3806
+
3807
+ * Answer a SetRequest for an object whose `maxAccess` is below "read-write" with a `notWritable` error instead of `noAccess`, for both the agent and the AgentX subagent - RFC 3416 section 4.2.5 reserves `noAccess` for a variable denied because it is not in the requester's MIB view, and gives `notWritable` for one that exists but cannot be modified whatever value is supplied
3808
+
3809
+ * Translate error statuses to their SNMPv1 equivalents when the agent responds to a version 1 request, using the mapping in RFC 2089 section 2.1 - the agent produces statuses from the SNMPv2 set whatever version it is answering, and previously sent codes such as `noAccess` and `notWritable` to SNMPv1 managers unchanged, even though SNMPv1 defines no such values. A SetRequest for a read-only object now returns `noSuchName` to an SNMPv1 manager
3810
+
3782
3811
  # License
3783
3812
 
3784
3813
  Copyright (c) 2020 Mark Abrahams <mark@abrahams.co.nz>
package/index.js CHANGED
@@ -71,6 +71,27 @@ var ErrorStatus = {
71
71
 
72
72
  _expandConstantObject (ErrorStatus);
73
73
 
74
+ // SNMPv2 introduced error-status values that carry no meaning for an SNMPv1
75
+ // manager, whose ErrorStatus ASN.1 stops at genErr(5). A multi-lingual agent
76
+ // must therefore translate before responding to a version 1 request. Table
77
+ // from RFC 2089 section 2.1, repeated in RFC 3584 section 4.4. Values absent
78
+ // from this table - noError, tooBig, noSuchName, badValue, readOnly and
79
+ // genErr - are already valid in SNMPv1 and pass through untouched.
80
+ var ErrorStatusV2ToV1 = {};
81
+ ErrorStatusV2ToV1[ErrorStatus.NoAccess] = ErrorStatus.NoSuchName;
82
+ ErrorStatusV2ToV1[ErrorStatus.WrongType] = ErrorStatus.BadValue;
83
+ ErrorStatusV2ToV1[ErrorStatus.WrongLength] = ErrorStatus.BadValue;
84
+ ErrorStatusV2ToV1[ErrorStatus.WrongEncoding] = ErrorStatus.BadValue;
85
+ ErrorStatusV2ToV1[ErrorStatus.WrongValue] = ErrorStatus.BadValue;
86
+ ErrorStatusV2ToV1[ErrorStatus.NoCreation] = ErrorStatus.NoSuchName;
87
+ ErrorStatusV2ToV1[ErrorStatus.InconsistentValue] = ErrorStatus.BadValue;
88
+ ErrorStatusV2ToV1[ErrorStatus.ResourceUnavailable] = ErrorStatus.GeneralError;
89
+ ErrorStatusV2ToV1[ErrorStatus.CommitFailed] = ErrorStatus.GeneralError;
90
+ ErrorStatusV2ToV1[ErrorStatus.UndoFailed] = ErrorStatus.GeneralError;
91
+ ErrorStatusV2ToV1[ErrorStatus.AuthorizationError] = ErrorStatus.NoSuchName;
92
+ ErrorStatusV2ToV1[ErrorStatus.NotWritable] = ErrorStatus.NoSuchName;
93
+ ErrorStatusV2ToV1[ErrorStatus.InconsistentName] = ErrorStatus.NoSuchName;
94
+
74
95
  var ObjectType = {
75
96
  1: "Boolean",
76
97
  2: "Integer",
@@ -4707,41 +4728,73 @@ Mib.prototype.getTableRowInstanceFromRow = function (provider, row) {
4707
4728
  return rowIndex;
4708
4729
  };
4709
4730
 
4731
+ // Splices exactly `length` components off the front of an index address,
4732
+ // rejecting encodings that claim more components than they supply.
4733
+ Mib.spliceIndexValue = function (addressRemaining, length, oid) {
4734
+ var value = addressRemaining.splice (0, length);
4735
+ if ( value.length !== length ) {
4736
+ throw new RangeError ("Index encoding in \"" + oid + "\" claims " + length +
4737
+ " components but supplies " + value.length);
4738
+ }
4739
+ return value;
4740
+ };
4741
+
4742
+ // Determines how many OID components an index part occupies, consuming a
4743
+ // leading length component for variable-length parts that are neither
4744
+ // implied nor of a fixed length.
4745
+ Mib.getIndexPartLength = function (indexPart, addressRemaining, oid) {
4746
+ var length;
4747
+ if ( indexPart.implied ) {
4748
+ return addressRemaining.length;
4749
+ }
4750
+ if ( indexPart.length ) {
4751
+ return indexPart.length;
4752
+ }
4753
+ length = Number (addressRemaining.shift ());
4754
+ if ( ! Number.isInteger (length) || length < 0 ) {
4755
+ throw new RangeError ("Index encoding in \"" + oid + "\" has a missing or invalid length component");
4756
+ }
4757
+ return length;
4758
+ };
4759
+
4710
4760
  Mib.getRowIndexFromOid = function (oid, index) {
4711
- var addressRemaining = oid.split (".");
4761
+ var addressRemaining = oid === "" ? [] : oid.split (".");
4712
4762
  var length = 0;
4713
4763
  var values = [];
4714
4764
  var value;
4715
4765
  for ( var indexPart of index ) {
4716
4766
  switch ( indexPart.type ) {
4717
4767
  case ObjectType.OID:
4718
- if ( indexPart.implied ) {
4719
- length = addressRemaining.length;
4720
- } else {
4721
- length = addressRemaining.shift ();
4722
- }
4723
- value = addressRemaining.splice (0, length);
4768
+ length = Mib.getIndexPartLength (indexPart, addressRemaining, oid);
4769
+ value = Mib.spliceIndexValue (addressRemaining, length, oid);
4724
4770
  values.push (value.join ("."));
4725
4771
  break;
4726
4772
  case ObjectType.IpAddress:
4727
4773
  length = 4;
4728
- value = addressRemaining.splice (0, length);
4774
+ value = Mib.spliceIndexValue (addressRemaining, length, oid);
4729
4775
  values.push (value.join ("."));
4730
4776
  break;
4731
4777
  case ObjectType.OctetString:
4732
- if ( indexPart.implied ) {
4733
- length = addressRemaining.length;
4734
- } else {
4735
- length = addressRemaining.shift ();
4736
- }
4737
- value = addressRemaining.splice (0, length);
4778
+ length = Mib.getIndexPartLength (indexPart, addressRemaining, oid);
4779
+ value = Mib.spliceIndexValue (addressRemaining, length, oid);
4738
4780
  value = value.map (c => String.fromCharCode(c)).join ("");
4739
4781
  values.push (value);
4740
4782
  break;
4741
4783
  default:
4742
- values.push (parseInt (addressRemaining.shift ()) );
4784
+ if ( addressRemaining.length === 0 ) {
4785
+ throw new RangeError ("Index encoding in \"" + oid + "\" is missing a component");
4786
+ }
4787
+ value = parseInt (addressRemaining.shift ());
4788
+ if ( Number.isNaN (value) ) {
4789
+ throw new RangeError ("Index encoding in \"" + oid + "\" has a non-numeric component");
4790
+ }
4791
+ values.push (value);
4743
4792
  }
4744
4793
  }
4794
+ if ( addressRemaining.length > 0 ) {
4795
+ throw new RangeError ("Index encoding in \"" + oid + "\" has " + addressRemaining.length +
4796
+ " unexpected trailing component(s)");
4797
+ }
4745
4798
  return values;
4746
4799
  };
4747
4800
 
@@ -5297,7 +5350,15 @@ Agent.prototype.tryCreateInstance = function (varbind, requestType) {
5297
5350
  subOid = Mib.getSubOidFromBaseOid (oid, provider.oid);
5298
5351
  subAddr = subOid.split(".");
5299
5352
  column = parseInt(subAddr.shift(), 10);
5300
- row = Mib.getRowIndexFromOid(subAddr.join("."), provider.tableIndex);
5353
+ try {
5354
+ row = Mib.getRowIndexFromOid(subAddr.join("."), provider.tableIndex);
5355
+ } catch (error) {
5356
+ // The OID is not a valid index encoding for this table, so it names no
5357
+ // row and there is nothing to create. Leave the varbind to be answered
5358
+ // as NoSuchInstance.
5359
+ debug (error);
5360
+ return undefined;
5361
+ }
5301
5362
  rowStatusColumn = provider.tableColumns.reduce( (acc, current) => current.rowStatus ? current.number : acc, null );
5302
5363
 
5303
5364
  if ( requestType === PduType.SetRequest &&
@@ -5468,14 +5529,19 @@ Agent.prototype.request = function (socket, requestMessage, rinfo) {
5468
5529
  });
5469
5530
  };
5470
5531
  } else if ( ! this.isAllowed(requestPdu.type, providerNode.provider, instanceNode ) ) {
5471
- // requested access not allowed (by MAX-ACCESS)
5532
+ // Requested access not allowed (by MAX-ACCESS). RFC 3416 section
5533
+ // 4.2.5 reserves noAccess for a variable denied because it is not
5534
+ // in the appropriate MIB view (rule 1); a variable that exists but
5535
+ // can not be modified no matter what value is supplied is
5536
+ // notWritable (rule 2). MAX-ACCESS below read-write is the latter.
5537
+ const accessErrorStatus = isSetRequest ? ErrorStatus.NotWritable : ErrorStatus.NoAccess;
5472
5538
  mibRequests[i] = new MibRequest ({
5473
5539
  operation: requestPdu.type,
5474
5540
  oid: requestPdu.varbinds[i].oid
5475
5541
  });
5476
5542
  mibRequests[i].handler = function getRanaHandler (mibRequestForRana) {
5477
5543
  mibRequestForRana.done ({
5478
- errorStatus: ErrorStatus.NoAccess,
5544
+ errorStatus: accessErrorStatus,
5479
5545
  type: ObjectType.Null,
5480
5546
  value: null
5481
5547
  });
@@ -5880,6 +5946,16 @@ Agent.prototype.setSingleVarbind = function (responsePdu, index, responseVarbind
5880
5946
  };
5881
5947
 
5882
5948
  Agent.prototype.sendResponse = function (socket, rinfo, requestMessage, responsePdu) {
5949
+ // The agent is multi-lingual: it answers each request in the version that
5950
+ // request arrived with. Error statuses are produced internally using the
5951
+ // SNMPv2 set, so a response to a version 1 request must be translated down
5952
+ // to something an SNMPv1 manager understands - RFC 2089 section 2.1.
5953
+ if ( requestMessage.version == Version1 ) {
5954
+ const version1ErrorStatus = ErrorStatusV2ToV1[responsePdu.errorStatus];
5955
+ if ( version1ErrorStatus !== undefined ) {
5956
+ responsePdu.errorStatus = version1ErrorStatus;
5957
+ }
5958
+ }
5883
5959
  var responseMessage = requestMessage.createResponseForRequest (responsePdu);
5884
5960
  this.listener.send (responseMessage, rinfo, socket);
5885
5961
  this.callback (null, Listener.formatCallbackData (responseMessage.pdu, rinfo) );
@@ -6709,8 +6785,11 @@ Subagent.prototype.request = function (pdu, requestVarbinds) {
6709
6785
  });
6710
6786
  mibRequests[i].handler = providerNode.provider.handler;
6711
6787
  if ( ! me.isAllowed(pdu.pduType, mibRequests[i].providerNode?.provider, mibRequests[i].instanceNode) ) {
6788
+ // A set below read-write MAX-ACCESS is notWritable, not
6789
+ // noAccess - RFC 3416 section 4.2.5 rule 2, and RFC 2741
6790
+ // section 7.2.1.4 for the AgentX TestSet phase.
6712
6791
  mibRequests[i].error = {
6713
- errorStatus: ErrorStatus.NoAccess,
6792
+ errorStatus: isSetRequest ? ErrorStatus.NotWritable : ErrorStatus.NoAccess,
6714
6793
  errorIndex: i + 1,
6715
6794
  type: ObjectType.Null,
6716
6795
  value: null
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "net-snmp",
3
- "version": "3.28.0",
3
+ "version": "3.29.0",
4
4
  "description": "JavaScript implementation of the Simple Network Management Protocol (SNMP)",
5
5
  "author": "Mark Abrahams <mark@abrahams.co.nz>",
6
6
  "license": "MIT",