From ee9e897b20b150c54444b0c1bac69f7f922e6a5d Mon Sep 17 00:00:00 2001 From: Ilya Etingof Date: Fri, 26 Jul 2019 08:33:32 +0200 Subject: [PATCH] Rework VACM access control function Most important changes include: * Added subtree match negation support (vacmViewTreeFamilyType) * Added subtree family mask support (vacmViewTreeFamilyMask) * Added prefix content name matching support (vacmAccessContextMatch) * Added key VACM tables caching for better lookup performance --- CHANGES.txt | 14 +- .../asyncore/agent/cmdrsp/advanced-topics.rst | 10 + .../cmdrsp/detailed-vacm-configuration.py | 117 +++++ pysnmp/entity/config.py | 58 ++- pysnmp/proto/acmod/rfc3415.py | 428 ++++++++++++++---- 5 files changed, 508 insertions(+), 119 deletions(-) create mode 100644 examples/v3arch/asyncore/agent/cmdrsp/detailed-vacm-configuration.py diff --git a/CHANGES.txt b/CHANGES.txt index 3a1efbb60..37765b7b1 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,7 +1,19 @@ -Revision 4.4.10, released 2019-04-XX +Revision 4.4.10, released 2019-07-XX ------------------------------------ +- Reworked VACM access control function. Most important changes include: + + * Added subtree match negation support (vacmViewTreeFamilyType) + * Added subtree family mask support (vacmViewTreeFamilyMask) + * Added prefix content name matching support (vacmAccessContextMatch) + * Added key VACM tables caching for better `isAccessAllowed` lookup + performance + + One potential incompatibility may be caused by the `addContext()` call + which now needs to be made explicitly during low-level VACM configuration + rather than be a side effect of `addVacmAccess()` call. + - Rebased MIB importing code onto `importlib` because `imp` is long deprecated - Fixed asyncore main loop to respect non-default timer resolution diff --git a/docs/source/examples/v3arch/asyncore/agent/cmdrsp/advanced-topics.rst b/docs/source/examples/v3arch/asyncore/agent/cmdrsp/advanced-topics.rst index 3b1d95dbe..38af9cb49 100644 --- a/docs/source/examples/v3arch/asyncore/agent/cmdrsp/advanced-topics.rst +++ b/docs/source/examples/v3arch/asyncore/agent/cmdrsp/advanced-topics.rst @@ -37,4 +37,14 @@ Advanced topics :download:`Download` script. +.. include:: /../../examples/v3arch/asyncore/agent/cmdrsp/detailed-vacm-configuration.py + :start-after: """ + :end-before: """# + +.. literalinclude:: /../../examples/v3arch/asyncore/agent/cmdrsp/detailed-vacm-configuration.py + :start-after: """# + :language: python + +:download:`Download` script. + See also: :doc:`library reference `. diff --git a/examples/v3arch/asyncore/agent/cmdrsp/detailed-vacm-configuration.py b/examples/v3arch/asyncore/agent/cmdrsp/detailed-vacm-configuration.py new file mode 100644 index 000000000..374d4a826 --- /dev/null +++ b/examples/v3arch/asyncore/agent/cmdrsp/detailed-vacm-configuration.py @@ -0,0 +1,117 @@ +""" +Detailed VACM configuration ++++++++++++++++++++++++++++ + +Serves MIB subtrees under different conditions: + +* Respond to SNMPv2c commands +* with SNMP community "public" +* over IPv4/UDP, listening at 127.0.0.1:161 +* Serve MIB under non-default contextName `abcd` +* Allow access to `SNMPv2-MIB::system` subtree +* Although deny access to `SNMPv2-MIB::sysUpTime` by a bit mask +* Use partial context name matching (`a`) + +This example demonstrates detailed VACM configuration performed via +low-level VACM calls: `addContext`, `addVacmGroup`, `addVacmAccess` +and `addVacmView`. Each function populates one of the tables +defined in `SNMP-VIEW-BASED-ACM-MIB` and used strictly as described +in the above mentioned MIB. + +The following Net-SNMP's commands will GET a value at this Agent: + +| $ snmpget -v2c -c public 127.0.0.1 SNMPv2-MIB::sysLocation.0 + +However this command will fail: + +| $ snmpget -v2c -c public 127.0.0.1 SNMPv2-MIB::sysUpTime.0 + +This command will not reveal `SNMPv2-MIB::sysUpTime.0` among other objects: + +| $ snmpwalk -v2c -c public 127.0.0.1 SNMPv2-MIB::system +"""# +from pysnmp.entity import engine, config +from pysnmp.entity.rfc3413 import cmdrsp, context +from pysnmp.carrier.asyncore.dgram import udp + +# Create SNMP engine with autogenernated engineID and pre-bound +# to socket transport dispatcher +snmpEngine = engine.SnmpEngine() + +# Transport setup + +# UDP over IPv4 +config.addTransport( + snmpEngine, + udp.domainName, + udp.UdpTransport().openServerMode(('127.0.0.1', 1161)) +) + +# Register default MIB instrumentation controller with a new SNMP context + +contextName = 'abcd' + +snmpContext = context.SnmpContext(snmpEngine) + +snmpContext.registerContextName( + contextName, snmpEngine.msgAndPduDsp.mibInstrumController) + +# Add new SNMP community name, map it to a new security name and +# SNMP context + +securityName = 'my-area' +communityName = 'public' + +config.addV1System( + snmpEngine, securityName, communityName, + contextEngineId=snmpContext.contextEngineId, + contextName=contextName) + +# VACM configuration settings + +securityModel = 2 # SNMPv2c +securityLevel = 1 # noAuthNoPriv + +vacmGroup = 'my-group' +readViewName = 'my-read-view' + +# We will match by context name prefix +contextPrefix = contextName[:1] + +# Populate SNMP-VIEW-BASED-ACM-MIB::vacmContextTable +config.addContext(snmpEngine, contextName) + +# Populate SNMP-VIEW-BASED-ACM-MIB::vacmSecurityToGroupTable +config.addVacmGroup( + snmpEngine, vacmGroup, securityModel, securityName) + +# Populate SNMP-VIEW-BASED-ACM-MIB::vacmAccessTable +config.addVacmAccess( + snmpEngine, vacmGroup, contextPrefix, securityModel, securityLevel, + 'prefix', readViewName, '', '') + +# Populate SNMP-VIEW-BASED-ACM-MIB::vacmViewTreeFamilyTable + +# Allow the whole system subtree +config.addVacmView( + snmpEngine, readViewName, 'included', '1.3.6.1.2.1.1.1', '1.1.1.1.1.1.1.0') + +# ...but exclude one sub-branch (just one scalar OID) +config.addVacmView( + snmpEngine, readViewName, 'excluded', '1.3.6.1.2.1.1.3', '1.1.1.1.1.1.1.1') + +# Register SNMP Applications at the SNMP engine for particular SNMP context +cmdrsp.GetCommandResponder(snmpEngine, snmpContext) +cmdrsp.SetCommandResponder(snmpEngine, snmpContext) +cmdrsp.NextCommandResponder(snmpEngine, snmpContext) + +# Register an imaginary never-ending job to keep I/O dispatcher running forever +snmpEngine.transportDispatcher.jobStarted(1) + +# Run I/O dispatcher which would receive queries and send responses +try: + snmpEngine.transportDispatcher.runDispatcher() + +except Exception: + snmpEngine.transportDispatcher.closeDispatcher() + raise diff --git a/pysnmp/entity/config.py b/pysnmp/entity/config.py index 20fe63a0c..8e76bf3e6 100644 --- a/pysnmp/entity/config.py +++ b/pysnmp/entity/config.py @@ -11,6 +11,7 @@ from pysnmp.proto.secmod.rfc3826.priv import aes from pysnmp.proto.secmod.rfc7860.auth import hmacsha2 from pysnmp.proto.secmod.eso.priv import des3, aes192, aes256 +from pysnmp.proto import rfc1902 from pysnmp.proto import rfc1905 from pysnmp import error @@ -461,22 +462,20 @@ def __cookVacmAccessInfo(snmpEngine, groupName, contextName, securityModel, return vacmAccessEntry, tblIdx -def addVacmAccess(snmpEngine, groupName, contextName, securityModel, - securityLevel, prefix, readView, writeView, notifyView): - vacmAccessEntry, tblIdx = __cookVacmAccessInfo(snmpEngine, groupName, - contextName, securityModel, - securityLevel) - - addContext(snmpEngine, contextName) +def addVacmAccess(snmpEngine, groupName, contextPrefix, securityModel, + securityLevel, contextMatch, readView, writeView, notifyView): + vacmAccessEntry, tblIdx = __cookVacmAccessInfo( + snmpEngine, groupName, contextPrefix, securityModel, + securityLevel) snmpEngine.msgAndPduDsp.mibInstrumController.writeVars( ((vacmAccessEntry.name + (9,) + tblIdx, 'destroy'),) ) snmpEngine.msgAndPduDsp.mibInstrumController.writeVars( - ((vacmAccessEntry.name + (1,) + tblIdx, contextName), + ((vacmAccessEntry.name + (1,) + tblIdx, contextPrefix), (vacmAccessEntry.name + (2,) + tblIdx, securityModel), (vacmAccessEntry.name + (3,) + tblIdx, securityLevel), - (vacmAccessEntry.name + (4,) + tblIdx, prefix), + (vacmAccessEntry.name + (4,) + tblIdx, contextMatch), (vacmAccessEntry.name + (5,) + tblIdx, readView), (vacmAccessEntry.name + (6,) + tblIdx, writeView), (vacmAccessEntry.name + (7,) + tblIdx, notifyView), @@ -484,13 +483,10 @@ def addVacmAccess(snmpEngine, groupName, contextName, securityModel, ) -def delVacmAccess(snmpEngine, groupName, contextName, securityModel, +def delVacmAccess(snmpEngine, groupName, contextPrefix, securityModel, securityLevel): - vacmAccessEntry, tblIdx = __cookVacmAccessInfo(snmpEngine, groupName, - contextName, securityModel, - securityLevel) - - delContext(snmpEngine, contextName) + vacmAccessEntry, tblIdx = __cookVacmAccessInfo( + snmpEngine, groupName, contextPrefix, securityModel, securityLevel) snmpEngine.msgAndPduDsp.mibInstrumController.writeVars( ((vacmAccessEntry.name + (9,) + tblIdx, 'destroy'),) @@ -507,16 +503,30 @@ def __cookVacmViewInfo(snmpEngine, viewName, subTree): return vacmViewTreeFamilyEntry, tblIdx -def addVacmView(snmpEngine, viewName, viewType, subTree, mask): - vacmViewTreeFamilyEntry, tblIdx = __cookVacmViewInfo(snmpEngine, viewName, - subTree) +def addVacmView(snmpEngine, viewName, viewType, subTree, subTreeMask): + vacmViewTreeFamilyEntry, tblIdx = __cookVacmViewInfo( + snmpEngine, viewName, subTree) + + # Allow bitmask specification in form of an OID + if '.' in subTreeMask: + subTreeMask = rfc1902.ObjectIdentifier(subTreeMask) + + if isinstance(subTreeMask, rfc1902.ObjectIdentifier): + subTreeMask = tuple(subTreeMask) + if len(subTreeMask) < len(subTree): + subTreeMask += (1,) * (len(subTree) - len(subTreeMask)) + + subTreeMask = rfc1902.OctetString.fromBinaryString( + ''.join(str(x) for x in subTreeMask)) + snmpEngine.msgAndPduDsp.mibInstrumController.writeVars( ((vacmViewTreeFamilyEntry.name + (6,) + tblIdx, 'destroy'),) ) + snmpEngine.msgAndPduDsp.mibInstrumController.writeVars( ((vacmViewTreeFamilyEntry.name + (1,) + tblIdx, viewName), (vacmViewTreeFamilyEntry.name + (2,) + tblIdx, subTree), - (vacmViewTreeFamilyEntry.name + (3,) + tblIdx, mask), + (vacmViewTreeFamilyEntry.name + (3,) + tblIdx, subTreeMask), (vacmViewTreeFamilyEntry.name + (4,) + tblIdx, viewType), (vacmViewTreeFamilyEntry.name + (6,) + tblIdx, 'createAndGo')) ) @@ -548,15 +558,16 @@ def addVacmUser(snmpEngine, securityModel, securityName, securityLevel, (groupName, securityLevel, readView, writeView, notifyView) = __cookVacmUserInfo(snmpEngine, securityModel, securityName, securityLevel) + addContext(snmpEngine, contextName) addVacmGroup(snmpEngine, groupName, securityModel, securityName) addVacmAccess(snmpEngine, groupName, contextName, securityModel, - securityLevel, 1, readView, writeView, notifyView) + securityLevel, 'exact', readView, writeView, notifyView) if readSubTree: - addVacmView(snmpEngine, readView, "included", readSubTree, null) + addVacmView(snmpEngine, readView, 'included', readSubTree, null) if writeSubTree: - addVacmView(snmpEngine, writeView, "included", writeSubTree, null) + addVacmView(snmpEngine, writeView, 'included', writeSubTree, null) if notifySubTree: - addVacmView(snmpEngine, notifyView, "included", notifySubTree, null) + addVacmView(snmpEngine, notifyView, 'included', notifySubTree, null) def delVacmUser(snmpEngine, securityModel, securityName, securityLevel, @@ -565,6 +576,7 @@ def delVacmUser(snmpEngine, securityModel, securityName, securityLevel, (groupName, securityLevel, readView, writeView, notifyView) = __cookVacmUserInfo(snmpEngine, securityModel, securityName, securityLevel) + delContext(snmpEngine, contextName) delVacmGroup(snmpEngine, securityModel, securityName) delVacmAccess(snmpEngine, groupName, contextName, securityModel, securityLevel) if readSubTree: diff --git a/pysnmp/proto/acmod/rfc3415.py b/pysnmp/proto/acmod/rfc3415.py index cf020d9f8..a1e9f6f7e 100644 --- a/pysnmp/proto/acmod/rfc3415.py +++ b/pysnmp/proto/acmod/rfc3415.py @@ -16,6 +16,117 @@ class Vacm(object): _powOfTwoSeq = (128, 64, 32, 16, 8, 4, 2, 1) + def __init__(self): + self._contextBranchId = -1 + self._groupNameBranchId = -1 + self._accessBranchId = -1 + self._viewTreeBranchId = -1 + + self._contextMap = {} + self._groupNameMap = {} + self._accessMap = {} + self._viewTreeMap = {} + + def _addAccessEntry(self, groupName, contextPrefix, securityModel, + securityLevel, prefixMatch, readView, writeView, + notifyView): + if not groupName: + return + + groups = self._accessMap + + try: + views = groups[groupName] + + except KeyError: + views = groups[groupName] = {} + + for viewType, viewName in ( + ('read', readView), ('write', writeView), + ('notify', notifyView)): + + try: + matches = views[viewType] + + except KeyError: + matches = views[viewType] = {} + + try: + contexts = matches[prefixMatch] + + except KeyError: + contexts = matches[prefixMatch] = {} + + try: + models = contexts[contextPrefix] + + except KeyError: + models = contexts[contextPrefix] = {} + + try: + levels = models[securityModel] + + except KeyError: + levels = models[securityModel] = {} + + levels[securityLevel] = viewName + + def _getFamilyViewName(self, groupName, contextName, securityModel, securityLevel, viewType): + groups = self._accessMap + + try: + views = groups[groupName] + + except KeyError: + raise error.StatusInformation(errorIndication=errind.noGroupName) + + try: + matches = views[viewType] + + except KeyError: + raise error.StatusInformation(errorIndication=errind.noAccessEntry) + + try: + # vacmAccessTable #2: exact match shortcut + return matches[1][contextName][securityModel][securityLevel] + + except KeyError: + pass + + # vacmAccessTable #2: fuzzy look-up + + candidates = [] + + for match, names in matches.items(): + + for context, models in names.items(): + + if match == 1 and contextName != context: + continue + + if match == 2 and contextName[:len(context)] != context: + continue + + for model, levels in models.items(): + for level, viewName in levels.items(): + + # priorities: + # - matching securityModel + # - exact context name match + # - longer partial match + # - highest securityLevel + rating = securityModel == model, match == 1, len(context), level + + candidates.append((rating, viewName)) + + if not candidates: + raise error.StatusInformation(errorIndication=errind.notInView) + + candidates.sort() + + rating, viewName = candidates[0] + return viewName + def isAccessAllowed(self, snmpEngine, securityModel, @@ -24,118 +135,245 @@ def isAccessAllowed(self, viewType, contextName, variableName): + mibInstrumController = snmpEngine.msgAndPduDsp.mibInstrumController debug.logger & debug.flagACL and debug.logger( - 'isAccessAllowed: securityModel %s, securityName %s, securityLevel %s, viewType %s, contextName %s for variableName %s' % ( - securityModel, securityName, securityLevel, viewType, contextName, variableName)) + 'isAccessAllowed: securityModel %s, securityName %s, ' + 'securityLevel %s, viewType %s, contextName %s for ' + 'variableName %s' % (securityModel, securityName, + securityLevel, viewType, contextName, + variableName)) - # 3.2.1 - vacmContextEntry, = mibInstrumController.mibBuilder.importSymbols( - 'SNMP-VIEW-BASED-ACM-MIB', 'vacmContextEntry') + # Rebuild contextName map if changed - tblIdx = vacmContextEntry.getInstIdFromIndices(contextName) - try: - vacmContextEntry.getNode( - vacmContextEntry.name + (1,) + tblIdx - ).syntax + vacmContextName, = mibInstrumController.mibBuilder.importSymbols( + 'SNMP-VIEW-BASED-ACM-MIB', 'vacmContextName') + + if self._contextBranchId != vacmContextName.branchVersionId: + + self._contextMap.clear() + + nextMibNode = vacmContextName + + while True: + try: + nextMibNode = vacmContextName.getNextNode(nextMibNode.name) + + except NoSuchInstanceError: + break - except NoSuchInstanceError: + self._contextMap[nextMibNode.syntax] = True + + self._contextBranchId = vacmContextName.branchVersionId + + # 3.2.1 + if contextName not in self._contextMap: raise error.StatusInformation(errorIndication=errind.noSuchContext) + # Rebuild groupName map if changed + + vacmGroupName, = mibInstrumController.mibBuilder.importSymbols( + 'SNMP-VIEW-BASED-ACM-MIB', 'vacmGroupName') + + if self._groupNameBranchId != vacmGroupName.branchVersionId: + + vacmSecurityToGroupEntry, = mibInstrumController.mibBuilder.importSymbols( + 'SNMP-VIEW-BASED-ACM-MIB', 'vacmSecurityToGroupEntry') + + self._groupNameMap.clear() + + nextMibNode = vacmGroupName + + while True: + try: + nextMibNode = vacmGroupName.getNextNode(nextMibNode.name) + + except NoSuchInstanceError: + break + + instId = nextMibNode.name[len(vacmGroupName.name):] + + indices = vacmSecurityToGroupEntry.getIndicesFromInstId(instId) + + self._groupNameMap[indices] = nextMibNode.syntax + + self._groupNameBranchId = vacmGroupName.branchVersionId + # 3.2.2 - vacmSecurityToGroupEntry, = mibInstrumController.mibBuilder.importSymbols( - 'SNMP-VIEW-BASED-ACM-MIB', 'vacmSecurityToGroupEntry') - tblIdx = vacmSecurityToGroupEntry.getInstIdFromIndices( - securityModel, securityName - ) + indices = securityModel, securityName try: - vacmGroupName = vacmSecurityToGroupEntry.getNode( - vacmSecurityToGroupEntry.name + (3,) + tblIdx - ).syntax + groupName = self._groupNameMap[indices] - except NoSuchInstanceError: + except KeyError: raise error.StatusInformation(errorIndication=errind.noGroupName) - # 3.2.3 - vacmAccessEntry, = mibInstrumController.mibBuilder.importSymbols( - 'SNMP-VIEW-BASED-ACM-MIB', 'vacmAccessEntry' - ) - - # XXX partial context name match - tblIdx = vacmAccessEntry.getInstIdFromIndices( - vacmGroupName, contextName, securityModel, securityLevel - ) - - # 3.2.4 - if viewType == 'read': - entryIdx = vacmAccessEntry.name + (5,) + tblIdx - elif viewType == 'write': - entryIdx = vacmAccessEntry.name + (6,) + tblIdx - elif viewType == 'notify': - entryIdx = vacmAccessEntry.name + (7,) + tblIdx - else: - raise error.ProtocolError('Unknown view type %s' % viewType) + # Rebuild access map if changed - try: - viewName = vacmAccessEntry.getNode(entryIdx).syntax + vacmAccessStatus, = mibInstrumController.mibBuilder.importSymbols( + 'SNMP-VIEW-BASED-ACM-MIB', 'vacmAccessStatus') - except NoSuchInstanceError: - raise error.StatusInformation(errorIndication=errind.noAccessEntry) + if self._accessBranchId != vacmAccessStatus.branchVersionId: + + (vacmAccessEntry, + vacmAccessContextPrefix, + vacmAccessSecurityModel, + vacmAccessSecurityLevel, + vacmAccessContextMatch, + vacmAccessReadViewName, + vacmAccessWriteViewName, + vacmAccessNotifyViewName) = mibInstrumController.mibBuilder.importSymbols( + 'SNMP-VIEW-BASED-ACM-MIB', + 'vacmAccessEntry', + 'vacmAccessContextPrefix', + 'vacmAccessSecurityModel', + 'vacmAccessSecurityLevel', + 'vacmAccessContextMatch', + 'vacmAccessReadViewName', + 'vacmAccessWriteViewName', + 'vacmAccessNotifyViewName') + + self._accessMap.clear() + + nextMibNode = vacmAccessStatus + + while True: + try: + nextMibNode = vacmAccessStatus.getNextNode(nextMibNode.name) + + except NoSuchInstanceError: + break + + if nextMibNode.syntax != 1: # active row + continue + + instId = nextMibNode.name[len(vacmAccessStatus.name):] + + indices = vacmAccessEntry.getIndicesFromInstId(instId) + + vacmGroupName = indices[0] + + self._addAccessEntry( + vacmGroupName, + vacmAccessContextPrefix.getNode( + vacmAccessContextPrefix.name + instId).syntax, + vacmAccessSecurityModel.getNode( + vacmAccessSecurityModel.name + instId).syntax, + vacmAccessSecurityLevel.getNode( + vacmAccessSecurityLevel.name + instId).syntax, + vacmAccessContextMatch.getNode( + vacmAccessContextMatch.name + instId).syntax, + vacmAccessReadViewName.getNode( + vacmAccessReadViewName.name + instId).syntax, + vacmAccessWriteViewName.getNode( + vacmAccessWriteViewName.name + instId).syntax, + vacmAccessNotifyViewName.getNode( + vacmAccessNotifyViewName.name + instId).syntax + ) + + self._accessBranchId = vacmAccessStatus.branchVersionId + + viewName = self._getFamilyViewName( + groupName, contextName, securityModel, securityLevel, viewType) + + # Rebuild family subtree map if changed + + vacmViewTreeFamilyViewName, = mibInstrumController.mibBuilder.importSymbols( + 'SNMP-VIEW-BASED-ACM-MIB', 'vacmViewTreeFamilyViewName') + + if self._viewTreeBranchId != vacmViewTreeFamilyViewName.branchVersionId: + + (vacmViewTreeFamilySubtree, + vacmViewTreeFamilyMask, + vacmViewTreeFamilyType) = mibInstrumController.mibBuilder.importSymbols( + 'SNMP-VIEW-BASED-ACM-MIB', + 'vacmViewTreeFamilySubtree', + 'vacmViewTreeFamilyMask', + 'vacmViewTreeFamilyType') + + self._viewTreeMap.clear() - if not viewName: - raise error.StatusInformation(errorIndication=errind.noSuchView) + powerOfTwo = [2 ** exp for exp in range(7, -1, -1)] - # XXX split onto object & instance ? + nextMibNode = vacmViewTreeFamilyViewName + + while True: + try: + nextMibNode = vacmViewTreeFamilyViewName.getNextNode( + nextMibNode.name) + + except NoSuchInstanceError: + break + + if nextMibNode.syntax not in self._viewTreeMap: + self._viewTreeMap[nextMibNode.syntax] = [] + + instId = nextMibNode.name[len(vacmViewTreeFamilyViewName.name):] + + subtree = vacmViewTreeFamilySubtree.getNode( + vacmViewTreeFamilySubtree.name + instId).syntax + + mask = vacmViewTreeFamilyMask.getNode( + vacmViewTreeFamilyMask.name + instId).syntax + + mode = vacmViewTreeFamilyType.getNode( + vacmViewTreeFamilyType.name + instId).syntax + + mask = mask.asNumbers() + maskLength = min(len(mask) * 8, len(subtree)) + + ignoredSubOids = [ + i * 8 + j for i, octet in enumerate(mask) + for j, bit in enumerate(powerOfTwo) + if not (bit & octet) and i * 8 + j < maskLength + ] + + if ignoredSubOids: + pattern = list(subtree) + + for ignoredSubOid in ignoredSubOids: + pattern[ignoredSubOid] = 0 + + subtree = subtree.clone(pattern) + + entry = subtree, ignoredSubOids, mode == 1 + + self._viewTreeMap[nextMibNode.syntax].append(entry) + + for entries in self._viewTreeMap.values(): + entries.sort(key=lambda x: (len(x[0]), x[0])) + + self._viewTreeBranchId = vacmViewTreeFamilyViewName.branchVersionId # 3.2.5a - vacmViewTreeFamilyEntry, = mibInstrumController.mibBuilder.importSymbols( - 'SNMP-VIEW-BASED-ACM-MIB', 'vacmViewTreeFamilyEntry') - tblIdx = vacmViewTreeFamilyEntry.getInstIdFromIndices(viewName) - - # Walk over entries - initialTreeName = treeName = vacmViewTreeFamilyEntry.name + (2,) + tblIdx - maskName = vacmViewTreeFamilyEntry.name + (3,) + tblIdx - - while True: - vacmViewTreeFamilySubtree = vacmViewTreeFamilyEntry.getNextNode( - treeName - ) - vacmViewTreeFamilyMask = vacmViewTreeFamilyEntry.getNextNode( - maskName - ) - - treeName = vacmViewTreeFamilySubtree.name - maskName = vacmViewTreeFamilyMask.name - - if initialTreeName != treeName[:len(initialTreeName)]: - # 3.2.5b - raise error.StatusInformation(errorIndication=errind.notInView) - - l = len(vacmViewTreeFamilySubtree.syntax) - if l > len(variableName): - continue - - if vacmViewTreeFamilyMask.syntax: - mask = [] - for c in vacmViewTreeFamilyMask.syntax.asNumbers(): - mask.extend([b & c for b in self._powOfTwoSeq]) - - m = len(mask) - 1 - idx = l - 1 - while idx: - if (idx > m or mask[idx] and - vacmViewTreeFamilySubtree.syntax[idx] != variableName[idx]): - break - idx -= 1 - - if idx: - continue # no match - - else: # no mask - if vacmViewTreeFamilySubtree.syntax != variableName[:l]: - continue # no match - - # 3.2.5c - return error.StatusInformation(errorIndication=errind.accessAllowed) + indices = viewName + + try: + entries = self._viewTreeMap[indices] + + except KeyError: + return error.StatusInformation(errorIndication=errind.notInView) + + accessAllowed = False + + for entry in entries: + subtree, ignoredSubOids, included = entry + + if ignoredSubOids: + subOids = list(variableName) + + for ignoredSubOid in ignoredSubOids: + subOids[ignoredSubOid] = 0 + + normalizedVariableName = subtree.clone(subOids) + + else: + normalizedVariableName = variableName + + if subtree.isPrefixOf(normalizedVariableName): + accessAllowed = included + + # 3.2.5c + if not accessAllowed: + raise error.StatusInformation(errorIndication=errind.notInView)