1# 2# This file is part of pysnmp software. 3# 4# Copyright (c) 2005-2019, Ilya Etingof <etingof@gmail.com> 5# License: http://snmplabs.com/pysnmp/license.html 6# 7import sys 8import traceback 9from pysnmp.smi.indices import OidOrderedDict 10from pysnmp.smi import exval, error 11from pysnmp.proto import rfc1902 12from pysnmp import cache, debug 13from pyasn1.type import univ 14from pyasn1.error import PyAsn1Error 15 16Integer, ObjectIdentifier = mibBuilder.importSymbols( 17 "ASN1", "Integer", "ObjectIdentifier" 18) 19 20(ConstraintsIntersection, ConstraintsUnion, SingleValueConstraint, 21 ValueRangeConstraint, ValueSizeConstraint) = mibBuilder.importSymbols( 22 "ASN1-REFINEMENT", "ConstraintsIntersection", "ConstraintsUnion", 23 "SingleValueConstraint", "ValueRangeConstraint", "ValueSizeConstraint" 24) 25 26# syntax of objects 27 28OctetString = rfc1902.OctetString 29Bits = rfc1902.Bits 30Integer32 = rfc1902.Integer32 31IpAddress = rfc1902.IpAddress 32Counter32 = rfc1902.Counter32 33Gauge32 = rfc1902.Gauge32 34Unsigned32 = rfc1902.Unsigned32 35TimeTicks = rfc1902.TimeTicks 36Opaque = rfc1902.Opaque 37Counter64 = rfc1902.Counter64 38 39 40class ExtUTCTime(OctetString): 41 subtypeSpec = OctetString.subtypeSpec + ConstraintsUnion(ValueSizeConstraint(11, 11), ValueSizeConstraint(13, 13)) 42 43 44# MIB tree foundation class 45 46class MibNode(object): 47 label = '' 48 49 def __init__(self, name): 50 self.name = name 51 52 def __repr__(self): 53 return '%s(%r)' % (self.__class__.__name__, self.name) 54 55 def getName(self): 56 return self.name 57 58 def getLabel(self): 59 return self.label 60 61 def setLabel(self, label): 62 self.label = label 63 return self 64 65 def clone(self, name=None): 66 myClone = self.__class__(self.name) 67 if name is not None: 68 myClone.name = name 69 if self.label is not None: 70 myClone.label = self.label 71 return myClone 72 73 74# definitions for information modules 75 76class ModuleIdentity(MibNode): 77 status = 'current' 78 lastUpdated = '' 79 organization = '' 80 contactInfo = '' 81 description = '' 82 revisions = () 83 revisionsDescriptions = () 84 85 def getStatus(self): 86 return self.status 87 88 def setStatus(self, v): 89 self.status = v 90 return self 91 92 def getLastUpdated(self): 93 return self.lastUpdated 94 95 def setLastUpdated(self, v): 96 self.lastUpdated = v 97 return self 98 99 def getOrganization(self): 100 return self.organization 101 102 def setOrganization(self, v): 103 self.organization = v 104 return self 105 106 def getContactInfo(self): 107 return self.contactInfo 108 109 def setContactInfo(self, v): 110 self.contactInfo = v 111 return self 112 113 def getDescription(self): 114 return self.description 115 116 def setDescription(self, v): 117 self.description = v 118 return self 119 120 def getRevisions(self): 121 return self.revisions 122 123 def setRevisions(self, args): 124 self.revisions = args 125 return self 126 127 def getRevisionsDescriptions(self): 128 return self.revisionsDescriptions 129 130 def setRevisionsDescriptions(self, args): 131 self.revisionsDescriptions = args 132 return self 133 134 def asn1Print(self): 135 return """\ 136MODULE-IDENTITY 137 LAST-UPDATED %s 138 ORGANIZATION "%s" 139 CONTACT-INFO "%s" 140 DESCRIPTION "%s" 141 %s""" % (self.getLastUpdated(), 142 self.getOrganization(), 143 self.getContactInfo(), 144 self.getDescription(), 145 ''.join(['REVISION "%s"\n' % x for x in self.getRevisions()])) 146 147 148class ObjectIdentity(MibNode): 149 status = 'current' 150 description = '' 151 reference = '' 152 153 def getStatus(self): 154 return self.status 155 156 def setStatus(self, v): 157 self.status = v 158 return self 159 160 def getDescription(self): 161 return self.description 162 163 def setDescription(self, v): 164 self.description = v 165 return self 166 167 def getReference(self): 168 return self.reference 169 170 def setReference(self, v): 171 self.reference = v 172 return self 173 174 def asn1Print(self): 175 return """\ 176OBJECT-IDENTITY 177 STATUS %s 178 DESCRIPTION "%s" 179 REFERENCE "%s" 180""" % (self.getStatus(), 181 self.getDescription(), 182 self.getReference()) 183 184 185# definition for objects 186 187class NotificationType(MibNode): 188 objects = () 189 status = 'current' 190 description = '' 191 reference = '' 192 # retained for compatibility 193 revisions = () 194 195 def getObjects(self): 196 return self.objects 197 198 def setObjects(self, *args, **kwargs): 199 if kwargs.get('append'): 200 self.objects += args 201 else: 202 self.objects = args 203 return self 204 205 def getStatus(self): 206 return self.status 207 208 def setStatus(self, v): 209 self.status = v 210 return self 211 212 def getDescription(self): 213 return self.description 214 215 def setDescription(self, v): 216 self.description = v 217 return self 218 219 def getReference(self): 220 return self.reference 221 222 def setReference(self, v): 223 self.reference = v 224 return self 225 226 # This should not be here. Retained for compatibility. 227 228 def getRevisions(self): 229 return self.revisions 230 231 def setRevisions(self, v): 232 self.revisions = v 233 return self 234 235 def asn1Print(self): 236 return """\ 237NOTIFICATION-TYPE 238 OBJECTS { %s } 239 STATUS %s 240 DESCRIPTION "%s" 241 REFERENCE "%s" 242""" % (', '.join([x for x in self.getObjects()]), 243 self.getStatus(), 244 self.getDescription(), 245 self.getReference()) 246 247 248class MibIdentifier(MibNode): 249 @staticmethod 250 def asn1Print(): 251 return 'OBJECT IDENTIFIER' 252 253 254class ObjectType(MibNode): 255 units = '' 256 maxAccess = 'not-accessible' 257 status = 'current' 258 description = '' 259 reference = '' 260 261 def __init__(self, name, syntax=None): 262 MibNode.__init__(self, name) 263 self.syntax = syntax 264 265 # XXX 266 def __eq__(self, other): 267 return self.syntax == other 268 269 def __ne__(self, other): 270 return self.syntax != other 271 272 def __lt__(self, other): 273 return self.syntax < other 274 275 def __le__(self, other): 276 return self.syntax <= other 277 278 def __gt__(self, other): 279 return self.syntax > other 280 281 def __ge__(self, other): 282 return self.syntax >= other 283 284 def __repr__(self): 285 return '%s(%r, %r)' % ( 286 self.__class__.__name__, self.name, self.syntax 287 ) 288 289 def getSyntax(self): 290 return self.syntax 291 292 def setSyntax(self, v): 293 self.syntax = v 294 return self 295 296 def getUnits(self): 297 return self.units 298 299 def setUnits(self, v): 300 self.units = v 301 return self 302 303 def getMaxAccess(self): 304 return self.maxAccess 305 306 def setMaxAccess(self, v): 307 self.maxAccess = v 308 return self 309 310 def getStatus(self): 311 return self.status 312 313 def setStatus(self, v): 314 self.status = v 315 return self 316 317 def getDescription(self): 318 return self.description 319 320 def setDescription(self, v): 321 self.description = v 322 return self 323 324 def getReference(self): 325 return self.reference 326 327 def setReference(self, v): 328 self.reference = v 329 return self 330 331 def asn1Print(self): 332 return """ 333OBJECT-TYPE 334 SYNTAX %s 335 UNITS "%s" 336 MAX-ACCESS %s 337 STATUS %s 338 DESCRIPTION "%s" 339 REFERENCE "%s" """ % (self.getSyntax().__class__.__name__, 340 self.getUnits(), 341 self.getMaxAccess(), 342 self.getStatus(), 343 self.getDescription(), 344 self.getReference()) 345 346 347class MibTree(ObjectType): 348 branchVersionId = 0 # cnanges on tree structure change 349 maxAccess = 'not-accessible' 350 351 def __init__(self, name, syntax=None): 352 ObjectType.__init__(self, name, syntax) 353 self._vars = OidOrderedDict() 354 355 # Subtrees registration 356 357 def registerSubtrees(self, *subTrees): 358 self.branchVersionId += 1 359 for subTree in subTrees: 360 if subTree.name in self._vars: 361 raise error.SmiError( 362 'MIB subtree %s already registered at %s' % (subTree.name, self) 363 ) 364 self._vars[subTree.name] = subTree 365 366 def unregisterSubtrees(self, *names): 367 self.branchVersionId += 1 368 for name in names: 369 # This may fail if you fill a table by exporting MibScalarInstances 370 # but later drop them through SNMP. 371 if name not in self._vars: 372 raise error.SmiError( 373 'MIB subtree %s not registered at %s' % (name, self) 374 ) 375 del self._vars[name] 376 377 # 378 # Tree traversal 379 # 380 # Missing branches are indicated by the NoSuchObjectError exception. 381 # Although subtrees may indicate their missing branches by the 382 # NoSuchInstanceError exception. 383 # 384 385 def getBranch(self, name, idx): 386 """Return a branch of this tree where the 'name' OID may reside""" 387 for keyLen in self._vars.getKeysLens(): 388 subName = name[:keyLen] 389 if subName in self._vars: 390 return self._vars[subName] 391 392 raise error.NoSuchObjectError(name=name, idx=idx) 393 394 def getNextBranch(self, name, idx=None): 395 # Start from the beginning 396 if self._vars: 397 first = list(self._vars.keys())[0] 398 else: 399 first = () 400 if self._vars and name < first: 401 return self._vars[first] 402 else: 403 try: 404 return self._vars[self._vars.nextKey(name)] 405 except KeyError: 406 raise error.NoSuchObjectError(idx=idx, name=name) 407 408 def getNode(self, name, idx=None): 409 """Return tree node found by name""" 410 if name == self.name: 411 return self 412 else: 413 return self.getBranch(name, idx).getNode(name, idx) 414 415 def getNextNode(self, name, idx=None): 416 """Return tree node next to name""" 417 try: 418 nextNode = self.getBranch(name, idx) 419 except (error.NoSuchInstanceError, error.NoSuchObjectError): 420 return self.getNextBranch(name, idx) 421 else: 422 try: 423 return nextNode.getNextNode(name, idx) 424 except (error.NoSuchInstanceError, error.NoSuchObjectError): 425 try: 426 return self._vars[self._vars.nextKey(nextNode.name)] 427 except KeyError: 428 raise error.NoSuchObjectError(idx=idx, name=name) 429 430 # MIB instrumentation 431 432 # Read operation 433 434 def readTest(self, name, val, idx, acInfo): 435 (acFun, acCtx) = acInfo 436 if name == self.name: 437 if acFun: 438 if self.maxAccess not in ('readonly', 439 'readwrite', 'readcreate') or \ 440 acFun(name, self.syntax, idx, 'read', acCtx): 441 raise error.NoAccessError(idx=idx, name=name) 442 else: 443 try: 444 node = self.getBranch(name, idx) 445 except (error.NoSuchInstanceError, error.NoSuchObjectError): 446 return # missing object is not an error here 447 else: 448 node.readTest(name, val, idx, acInfo) 449 450 def readGet(self, name, val, idx, acInfo): 451 try: 452 node = self.getBranch(name, idx) 453 except (error.NoSuchInstanceError, error.NoSuchObjectError): 454 return name, exval.noSuchObject 455 else: 456 return node.readGet(name, val, idx, acInfo) 457 458 # Read next operation is subtree-specific 459 460 depthFirst, breadthFirst = 0, 1 461 462 def readTestNext(self, name, val, idx, acInfo, oName=None): 463 if oName is None: 464 oName = name 465 topOfTheMib = True 466 else: 467 topOfTheMib = False 468 nextName = name 469 direction = self.depthFirst 470 while 1: # XXX linear search here 471 if direction == self.depthFirst: 472 direction = self.breadthFirst 473 try: 474 node = self.getBranch(nextName, idx) 475 except (error.NoSuchInstanceError, error.NoSuchObjectError): 476 continue 477 else: 478 try: 479 node = self.getNextBranch(nextName, idx) 480 except (error.NoSuchInstanceError, error.NoSuchObjectError): 481 if topOfTheMib: 482 return 483 raise 484 direction = self.depthFirst 485 nextName = node.name 486 try: 487 return node.readTestNext(nextName, val, idx, acInfo, oName) 488 except (error.NoAccessError, error.NoSuchInstanceError, error.NoSuchObjectError): 489 pass 490 491 def readGetNext(self, name, val, idx, acInfo, oName=None): 492 if oName is None: 493 oName = name 494 topOfTheMib = True 495 else: 496 topOfTheMib = False 497 nextName = name 498 direction = self.depthFirst 499 while True: # XXX linear search ahead! 500 if direction == self.depthFirst: 501 direction = self.breadthFirst 502 try: 503 node = self.getBranch(nextName, idx) 504 except (error.NoSuchInstanceError, error.NoSuchObjectError): 505 continue 506 else: 507 try: 508 node = self.getNextBranch(nextName, idx) 509 except (error.NoSuchInstanceError, error.NoSuchObjectError): 510 if topOfTheMib: 511 return name, exval.endOfMib 512 raise 513 direction = self.depthFirst 514 nextName = node.name 515 try: 516 return node.readGetNext(nextName, val, idx, acInfo, oName) 517 except (error.NoAccessError, error.NoSuchInstanceError, error.NoSuchObjectError): 518 pass 519 520 # Write operation 521 522 def writeTest(self, name, val, idx, acInfo): 523 acFun, acCtx = acInfo 524 if name == self.name: 525 # Make sure variable is writable 526 if acFun: 527 if self.maxAccess not in ('readwrite', 'readcreate') or \ 528 acFun(name, self.syntax, idx, 'write', acCtx): 529 raise error.NotWritableError(idx=idx, name=name) 530 else: 531 node = self.getBranch(name, idx) 532 node.writeTest(name, val, idx, acInfo) 533 534 def writeCommit(self, name, val, idx, acInfo): 535 self.getBranch(name, idx).writeCommit(name, val, idx, acInfo) 536 537 def writeCleanup(self, name, val, idx, acInfo): 538 self.branchVersionId += 1 539 self.getBranch(name, idx).writeCleanup(name, val, idx, acInfo) 540 541 def writeUndo(self, name, val, idx, acInfo): 542 self.getBranch(name, idx).writeUndo(name, val, idx, acInfo) 543 544 545class MibScalar(MibTree): 546 """Scalar MIB variable. Implements access control checking.""" 547 maxAccess = 'readonly' 548 549 # 550 # Subtree traversal 551 # 552 # Missing branches are indicated by the NoSuchInstanceError exception. 553 # 554 555 def getBranch(self, name, idx): 556 try: 557 return MibTree.getBranch(self, name, idx) 558 except (error.NoSuchInstanceError, error.NoSuchObjectError): 559 raise error.NoSuchInstanceError(idx=idx, name=name) 560 561 def getNextBranch(self, name, idx=None): 562 try: 563 return MibTree.getNextBranch(self, name, idx) 564 except (error.NoSuchInstanceError, error.NoSuchObjectError): 565 raise error.NoSuchInstanceError(idx=idx, name=name) 566 567 def getNode(self, name, idx=None): 568 try: 569 return MibTree.getNode(self, name, idx) 570 except (error.NoSuchInstanceError, error.NoSuchObjectError): 571 raise error.NoSuchInstanceError(idx=idx, name=name) 572 573 def getNextNode(self, name, idx=None): 574 try: 575 return MibTree.getNextNode(self, name, idx) 576 except (error.NoSuchInstanceError, error.NoSuchObjectError): 577 raise error.NoSuchInstanceError(idx=idx, name=name) 578 579 # MIB instrumentation methods 580 581 # Read operation 582 583 def readTest(self, name, val, idx, acInfo): 584 (acFun, acCtx) = acInfo 585 if name == self.name: 586 raise error.NoAccessError(idx=idx, name=name) 587 if acFun: 588 if self.maxAccess not in ('readonly', 'readwrite', 589 'readcreate') or \ 590 acFun(name, self.syntax, idx, 'read', acCtx): 591 raise error.NoAccessError(idx=idx, name=name) 592 MibTree.readTest(self, name, val, idx, acInfo) 593 594 def readGet(self, name, val, idx, acInfo): 595 try: 596 node = self.getBranch(name, idx) 597 except error.NoSuchInstanceError: 598 return name, exval.noSuchInstance 599 else: 600 return node.readGet(name, val, idx, acInfo) 601 602 def readTestNext(self, name, val, idx, acInfo, oName=None): 603 (acFun, acCtx) = acInfo 604 if acFun: 605 if self.maxAccess not in ('readonly', 'readwrite', 606 'readcreate') or \ 607 acFun(name, self.syntax, idx, 'read', acCtx): 608 raise error.NoAccessError(idx=idx, name=name) 609 MibTree.readTestNext(self, name, val, idx, acInfo, oName) 610 611 def readGetNext(self, name, val, idx, acInfo, oName=None): 612 (acFun, acCtx) = acInfo 613 # have to duplicate AC here as *Next code above treats 614 # noAccess as a noSuchObject at the Test stage, goes on 615 # to Reading 616 if acFun: 617 if self.maxAccess not in ('readonly', 'readwrite', 618 'readcreate') or \ 619 acFun(name, self.syntax, idx, 'read', acCtx): 620 raise error.NoAccessError(idx=idx, name=name) 621 return MibTree.readGetNext(self, name, val, idx, acInfo, oName) 622 623 # Two-phase commit implementation 624 625 def writeTest(self, name, val, idx, acInfo): 626 acFun, acCtx = acInfo 627 if name == self.name: 628 raise error.NoAccessError(idx=idx, name=name) 629 if acFun: 630 if self.maxAccess not in ('readwrite', 'readcreate') or \ 631 acFun(name, self.syntax, idx, 'write', acCtx): 632 raise error.NotWritableError(idx=idx, name=name) 633 MibTree.writeTest(self, name, val, idx, acInfo) 634 635 636class MibScalarInstance(MibTree): 637 """Scalar MIB variable instance. Implements read/write operations.""" 638 639 def __init__(self, typeName, instId, syntax): 640 MibTree.__init__(self, typeName + instId, syntax) 641 self.typeName = typeName 642 self.instId = instId 643 self.__oldSyntax = None 644 645 # 646 # Managed object value access methods 647 # 648 649 # noinspection PyUnusedLocal 650 def getValue(self, name, idx): 651 debug.logger & debug.flagIns and debug.logger('getValue: returning %r for %s' % (self.syntax, self.name)) 652 return self.syntax.clone() 653 654 def setValue(self, value, name, idx): 655 if value is None: 656 value = univ.noValue 657 try: 658 if hasattr(self.syntax, 'setValue'): 659 return self.syntax.setValue(value) 660 else: 661 return self.syntax.clone(value) 662 except PyAsn1Error: 663 exc_t, exc_v, exc_tb = sys.exc_info() 664 debug.logger & debug.flagIns and debug.logger('setValue: %s=%r failed with traceback %s' % ( 665 self.name, value, traceback.format_exception(exc_t, exc_v, exc_tb))) 666 if isinstance(exc_v, error.TableRowManagement): 667 raise exc_v 668 else: 669 raise error.WrongValueError(idx=idx, name=name, msg=exc_v) 670 671 # 672 # Subtree traversal 673 # 674 # Missing branches are indicated by the NoSuchInstanceError exception. 675 # 676 677 def getBranch(self, name, idx): 678 try: 679 return MibTree.getBranch(self, name, idx) 680 except (error.NoSuchInstanceError, error.NoSuchObjectError): 681 raise error.NoSuchInstanceError(idx=idx, name=name) 682 683 def getNextBranch(self, name, idx=None): 684 try: 685 return MibTree.getNextBranch(self, name, idx) 686 except (error.NoSuchInstanceError, error.NoSuchObjectError): 687 raise error.NoSuchInstanceError(idx=idx, name=name) 688 689 def getNode(self, name, idx=None): 690 # Recursion terminator 691 if name == self.name: 692 return self 693 raise error.NoSuchInstanceError(idx=idx, name=name) 694 695 def getNextNode(self, name, idx=None): 696 raise error.NoSuchInstanceError(idx=idx, name=name) 697 698 # MIB instrumentation methods 699 700 # Read operation 701 702 def readTest(self, name, val, idx, acInfo): 703 if name != self.name: 704 raise error.NoSuchInstanceError(idx=idx, name=name) 705 706 def readGet(self, name, val, idx, acInfo): 707 # Return current variable (name, value) 708 if name == self.name: 709 debug.logger & debug.flagIns and debug.logger('readGet: %s=%r' % (self.name, self.syntax)) 710 return self.name, self.getValue(name, idx) 711 else: 712 raise error.NoSuchInstanceError(idx=idx, name=name) 713 714 def readTestNext(self, name, val, idx, acInfo, oName=None): 715 if name != self.name or name <= oName: 716 raise error.NoSuchInstanceError(idx=idx, name=name) 717 718 def readGetNext(self, name, val, idx, acInfo, oName=None): 719 if name == self.name and name > oName: 720 debug.logger & debug.flagIns and debug.logger('readGetNext: %s=%r' % (self.name, self.syntax)) 721 return self.readGet(name, val, idx, acInfo) 722 else: 723 raise error.NoSuchInstanceError(idx=idx, name=name) 724 725 # Write operation: two-phase commit 726 727 # noinspection PyAttributeOutsideInit 728 def writeTest(self, name, val, idx, acInfo): 729 # Make sure write's allowed 730 if name == self.name: 731 try: 732 self.__newSyntax = self.setValue(val, name, idx) 733 except error.MibOperationError: 734 # SMI exceptions may carry additional content 735 why = sys.exc_info()[1] 736 if 'syntax' in why: 737 self.__newSyntax = why['syntax'] 738 raise why 739 else: 740 raise error.WrongValueError(idx=idx, name=name, msg=sys.exc_info()[1]) 741 else: 742 raise error.NoSuchInstanceError(idx=idx, name=name) 743 744 def writeCommit(self, name, val, idx, acInfo): 745 # Backup original value 746 if self.__oldSyntax is None: 747 self.__oldSyntax = self.syntax 748 # Commit new value 749 self.syntax = self.__newSyntax 750 751 # noinspection PyAttributeOutsideInit 752 def writeCleanup(self, name, val, idx, acInfo): 753 self.branchVersionId += 1 754 debug.logger & debug.flagIns and debug.logger('writeCleanup: %s=%r' % (name, val)) 755 # Drop previous value 756 self.__newSyntax = self.__oldSyntax = None 757 758 # noinspection PyAttributeOutsideInit 759 def writeUndo(self, name, val, idx, acInfo): 760 # Revive previous value 761 self.syntax = self.__oldSyntax 762 self.__newSyntax = self.__oldSyntax = None 763 764 # Table column instance specifics 765 766 # Create operation 767 768 # noinspection PyUnusedLocal,PyAttributeOutsideInit 769 def createTest(self, name, val, idx, acInfo): 770 if name == self.name: 771 try: 772 self.__newSyntax = self.setValue(val, name, idx) 773 except error.MibOperationError: 774 # SMI exceptions may carry additional content 775 why = sys.exc_info()[1] 776 if 'syntax' in why: 777 self.__newSyntax = why['syntax'] 778 else: 779 raise error.WrongValueError(idx=idx, name=name, msg=sys.exc_info()[1]) 780 else: 781 raise error.NoSuchInstanceError(idx=idx, name=name) 782 783 def createCommit(self, name, val, idx, acInfo): 784 if val is not None: 785 self.writeCommit(name, val, idx, acInfo) 786 787 def createCleanup(self, name, val, idx, acInfo): 788 self.branchVersionId += 1 789 debug.logger & debug.flagIns and debug.logger('createCleanup: %s=%r' % (name, val)) 790 if val is not None: 791 self.writeCleanup(name, val, idx, acInfo) 792 793 def createUndo(self, name, val, idx, acInfo): 794 if val is not None: 795 self.writeUndo(name, val, idx, acInfo) 796 797 # Destroy operation 798 799 # noinspection PyUnusedLocal,PyAttributeOutsideInit 800 def destroyTest(self, name, val, idx, acInfo): 801 if name == self.name: 802 try: 803 self.__newSyntax = self.setValue(val, name, idx) 804 except error.MibOperationError: 805 # SMI exceptions may carry additional content 806 why = sys.exc_info()[1] 807 if 'syntax' in why: 808 self.__newSyntax = why['syntax'] 809 else: 810 raise error.NoSuchInstanceError(idx=idx, name=name) 811 812 def destroyCommit(self, name, val, idx, acInfo): 813 pass 814 815 # noinspection PyUnusedLocal 816 def destroyCleanup(self, name, val, idx, acInfo): 817 self.branchVersionId += 1 818 819 def destroyUndo(self, name, val, idx, acInfo): 820 pass 821 822 823# Conceptual table classes 824 825class MibTableColumn(MibScalar): 826 """MIB table column. Manages a set of column instance variables""" 827 protoInstance = MibScalarInstance 828 829 def __init__(self, name, syntax): 830 MibScalar.__init__(self, name, syntax) 831 self.__createdInstances = {} 832 self.__destroyedInstances = {} 833 self.__rowOpWanted = {} 834 835 # 836 # Subtree traversal 837 # 838 # Missing leaves are indicated by the NoSuchInstanceError exception. 839 # 840 841 def getBranch(self, name, idx): 842 if name in self._vars: 843 return self._vars[name] 844 raise error.NoSuchInstanceError(name=name, idx=idx) 845 846 def setProtoInstance(self, protoInstance): 847 self.protoInstance = protoInstance 848 849 # Column creation (this should probably be converted into some state 850 # machine for clarity). Also, it might be a good idea to inidicate 851 # defaulted cols creation in a clearer way than just a val == None. 852 853 def createTest(self, name, val, idx, acInfo): 854 (acFun, acCtx) = acInfo 855 # Make sure creation allowed, create a new column instance but 856 # do not replace the old one 857 if name == self.name: 858 raise error.NoAccessError(idx=idx, name=name) 859 if acFun: 860 if val is not None and self.maxAccess != 'readcreate' or \ 861 acFun(name, self.syntax, idx, 'write', acCtx): 862 debug.logger & debug.flagACL and debug.logger( 863 'createTest: %s=%r %s at %s' % (name, val, self.maxAccess, self.name)) 864 raise error.NoCreationError(idx=idx, name=name) 865 # Create instances if either it does not yet exist (row creation) 866 # or a value is passed (multiple OIDs in SET PDU) 867 if val is None and name in self.__createdInstances: 868 return 869 self.__createdInstances[name] = self.protoInstance( 870 self.name, name[len(self.name):], self.syntax.clone() 871 ) 872 self.__createdInstances[name].createTest(name, val, idx, acInfo) 873 874 def createCommit(self, name, val, idx, acInfo): 875 # Commit new instance value 876 if name in self._vars: # XXX 877 if name in self.__createdInstances: 878 self._vars[name].createCommit(name, val, idx, acInfo) 879 return 880 self.__createdInstances[name].createCommit(name, val, idx, acInfo) 881 # ...commit new column instance 882 self._vars[name], self.__createdInstances[name] = \ 883 self.__createdInstances[name], self._vars.get(name) 884 885 def createCleanup(self, name, val, idx, acInfo): 886 # Drop previous column instance 887 self.branchVersionId += 1 888 if name in self.__createdInstances: 889 if self.__createdInstances[name] is not None: 890 self.__createdInstances[name].createCleanup(name, val, idx, 891 acInfo) 892 del self.__createdInstances[name] 893 elif name in self._vars: 894 self._vars[name].createCleanup(name, val, idx, acInfo) 895 896 def createUndo(self, name, val, idx, acInfo): 897 # Set back previous column instance, drop the new one 898 if name in self.__createdInstances: 899 self._vars[name] = self.__createdInstances[name] 900 del self.__createdInstances[name] 901 # Remove new instance on rollback 902 if self._vars[name] is None: 903 del self._vars[name] 904 else: 905 # Catch half-created instances (hackerish) 906 try: 907 self._vars[name] == 0 908 except PyAsn1Error: 909 del self._vars[name] 910 else: 911 self._vars[name].createUndo(name, val, idx, acInfo) 912 913 # Column destruction 914 915 def destroyTest(self, name, val, idx, acInfo): 916 (acFun, acCtx) = acInfo 917 # Make sure destruction is allowed 918 if name == self.name: 919 raise error.NoAccessError(idx=idx, name=name) 920 if name not in self._vars: 921 return 922 if acFun: 923 if val is not None and self.maxAccess != 'readcreate' or \ 924 acFun(name, self.syntax, idx, 'write', acCtx): 925 raise error.NoAccessError(idx=idx, name=name) 926 self._vars[name].destroyTest(name, val, idx, acInfo) 927 928 def destroyCommit(self, name, val, idx, acInfo): 929 # Make a copy of column instance and take it off the tree 930 if name in self._vars: 931 self._vars[name].destroyCommit(name, val, idx, acInfo) 932 self.__destroyedInstances[name] = self._vars[name] 933 del self._vars[name] 934 935 def destroyCleanup(self, name, val, idx, acInfo): 936 # Drop instance copy 937 self.branchVersionId += 1 938 if name in self.__destroyedInstances: 939 self.__destroyedInstances[name].destroyCleanup(name, val, 940 idx, acInfo) 941 debug.logger & debug.flagIns and debug.logger('destroyCleanup: %s=%r' % (name, val)) 942 del self.__destroyedInstances[name] 943 944 def destroyUndo(self, name, val, idx, acInfo): 945 # Set back column instance 946 if name in self.__destroyedInstances: 947 self._vars[name] = self.__destroyedInstances[name] 948 self._vars[name].destroyUndo(name, val, idx, acInfo) 949 del self.__destroyedInstances[name] 950 951 # Set/modify column 952 953 def writeTest(self, name, val, idx, acInfo): 954 # Besides common checks, request row creation on no-instance 955 try: 956 # First try the instance 957 MibScalar.writeTest(self, name, val, idx, acInfo) 958 # ...otherwise proceed with creating new column 959 except (error.NoSuchInstanceError, error.RowCreationWanted): 960 excValue = sys.exc_info()[1] 961 if isinstance(excValue, error.RowCreationWanted): 962 self.__rowOpWanted[name] = excValue 963 else: 964 self.__rowOpWanted[name] = error.RowCreationWanted() 965 self.createTest(name, val, idx, acInfo) 966 except error.RowDestructionWanted: 967 self.__rowOpWanted[name] = error.RowDestructionWanted() 968 self.destroyTest(name, val, idx, acInfo) 969 if name in self.__rowOpWanted: 970 debug.logger & debug.flagIns and debug.logger( 971 '%s flagged by %s=%r, exception %s' % (self.__rowOpWanted[name], name, val, sys.exc_info()[1])) 972 raise self.__rowOpWanted[name] 973 974 def __delegateWrite(self, subAction, name, val, idx, acInfo): 975 if name not in self.__rowOpWanted: 976 getattr(MibScalar, 'write' + subAction)(self, name, val, idx, acInfo) 977 return 978 if isinstance(self.__rowOpWanted[name], error.RowCreationWanted): 979 getattr(self, 'create' + subAction)(name, val, idx, acInfo) 980 if isinstance(self.__rowOpWanted[name], error.RowDestructionWanted): 981 getattr(self, 'destroy' + subAction)(name, val, idx, acInfo) 982 983 def writeCommit(self, name, val, idx, acInfo): 984 self.__delegateWrite('Commit', name, val, idx, acInfo) 985 if name in self.__rowOpWanted: 986 raise self.__rowOpWanted[name] 987 988 def writeCleanup(self, name, val, idx, acInfo): 989 self.branchVersionId += 1 990 self.__delegateWrite('Cleanup', name, val, idx, acInfo) 991 if name in self.__rowOpWanted: 992 e = self.__rowOpWanted[name] 993 del self.__rowOpWanted[name] 994 debug.logger & debug.flagIns and debug.logger('%s dropped by %s=%r' % (e, name, val)) 995 raise e 996 997 def writeUndo(self, name, val, idx, acInfo): 998 if name in self.__rowOpWanted: 999 self.__rowOpWanted[name] = error.RowDestructionWanted() 1000 self.__delegateWrite('Undo', name, val, idx, acInfo) 1001 if name in self.__rowOpWanted: 1002 e = self.__rowOpWanted[name] 1003 del self.__rowOpWanted[name] 1004 debug.logger & debug.flagIns and debug.logger('%s dropped by %s=%r' % (e, name, val)) 1005 raise e 1006 1007 1008class MibTableRow(MibTree): 1009 """MIB table row (SMI 'Entry'). Manages a set of table columns. 1010 Implements row creation/destruction. 1011 """ 1012 1013 def __init__(self, name): 1014 MibTree.__init__(self, name) 1015 self.__idToIdxCache = cache.Cache() 1016 self.__idxToIdCache = cache.Cache() 1017 self.indexNames = () 1018 self.augmentingRows = {} 1019 1020 # Table indices resolution. Handle almost all possible rfc1902 types 1021 # explicitly rather than by means of isSuperTypeOf() method because 1022 # some subtypes may be implicitly tagged what renders base tag 1023 # unavailable. 1024 1025 __intBaseTag = Integer.tagSet.getBaseTag() 1026 __strBaseTag = OctetString.tagSet.getBaseTag() 1027 __oidBaseTag = ObjectIdentifier.tagSet.getBaseTag() 1028 __ipaddrTagSet = IpAddress.tagSet 1029 __bitsBaseTag = Bits.tagSet.getBaseTag() 1030 1031 def setFromName(self, obj, value, impliedFlag=None, parentIndices=None): 1032 if not value: 1033 raise error.SmiError('Short OID for index %r' % (obj,)) 1034 if hasattr(obj, 'cloneFromName'): 1035 return obj.cloneFromName(value, impliedFlag, parentRow=self, parentIndices=parentIndices) 1036 baseTag = obj.getTagSet().getBaseTag() 1037 if baseTag == self.__intBaseTag: 1038 return obj.clone(value[0]), value[1:] 1039 elif self.__ipaddrTagSet.isSuperTagSetOf(obj.getTagSet()): 1040 return obj.clone('.'.join([str(x) for x in value[:4]])), value[4:] 1041 elif baseTag == self.__strBaseTag: 1042 # rfc1902, 7.7 1043 if impliedFlag: 1044 return obj.clone(tuple(value)), () 1045 elif obj.isFixedLength(): 1046 l = obj.getFixedLength() 1047 return obj.clone(tuple(value[:l])), value[l:] 1048 else: 1049 return obj.clone(tuple(value[1:value[0] + 1])), value[value[0] + 1:] 1050 elif baseTag == self.__oidBaseTag: 1051 if impliedFlag: 1052 return obj.clone(value), () 1053 else: 1054 return obj.clone(value[1:value[0] + 1]), value[value[0] + 1:] 1055 # rfc2578, 7.1 1056 elif baseTag == self.__bitsBaseTag: 1057 return obj.clone(tuple(value[1:value[0] + 1])), value[value[0] + 1:] 1058 else: 1059 raise error.SmiError('Unknown value type for index %r' % (obj,)) 1060 1061 def getAsName(self, obj, impliedFlag=None, parentIndices=None): 1062 if hasattr(obj, 'cloneAsName'): 1063 return obj.cloneAsName(impliedFlag, parentRow=self, parentIndices=parentIndices) 1064 baseTag = obj.getTagSet().getBaseTag() 1065 if baseTag == self.__intBaseTag: 1066 # noinspection PyRedundantParentheses 1067 return (int(obj),) 1068 elif self.__ipaddrTagSet.isSuperTagSetOf(obj.getTagSet()): 1069 return obj.asNumbers() 1070 elif baseTag == self.__strBaseTag: 1071 if impliedFlag or obj.isFixedLength(): 1072 initial = () 1073 else: 1074 initial = (len(obj),) 1075 return initial + obj.asNumbers() 1076 elif baseTag == self.__oidBaseTag: 1077 if impliedFlag: 1078 return tuple(obj) 1079 else: 1080 return (len(obj),) + tuple(obj) 1081 # rfc2578, 7.1 1082 elif baseTag == self.__bitsBaseTag: 1083 return (len(obj),) + obj.asNumbers() 1084 else: 1085 raise error.SmiError('Unknown value type for index %r' % (obj,)) 1086 1087 # Fate sharing mechanics 1088 1089 def announceManagementEvent(self, action, name, val, idx, acInfo): 1090 # Convert OID suffix into index vals 1091 instId = name[len(self.name) + 1:] 1092 baseIndices = [] 1093 indices = [] 1094 for impliedFlag, modName, symName in self.indexNames: 1095 mibObj, = mibBuilder.importSymbols(modName, symName) 1096 syntax, instId = self.setFromName(mibObj.syntax, instId, 1097 impliedFlag, indices) 1098 1099 if self.name == mibObj.name[:-1]: 1100 baseIndices.append((mibObj.name, syntax)) 1101 1102 indices.append(syntax) 1103 1104 if instId: 1105 raise error.SmiError('Excessive instance identifier sub-OIDs left at %s: %s' % (self, instId)) 1106 1107 if not baseIndices: 1108 return 1109 1110 for modName, mibSym in self.augmentingRows.keys(): 1111 mibObj, = mibBuilder.importSymbols(modName, mibSym) 1112 debug.logger & debug.flagIns and debug.logger('announceManagementEvent %s to %s' % (action, mibObj)) 1113 mibObj.receiveManagementEvent( 1114 action, baseIndices, val, idx, acInfo 1115 ) 1116 1117 def receiveManagementEvent(self, action, baseIndices, val, idx, acInfo): 1118 # The default implementation supports one-to-one rows dependency 1119 newSuffix = () 1120 # Resolve indices intersection 1121 for impliedFlag, modName, symName in self.indexNames: 1122 mibObj, = mibBuilder.importSymbols(modName, symName) 1123 parentIndices = [] 1124 for name, syntax in baseIndices: 1125 if name == mibObj.name: 1126 newSuffix += self.getAsName(syntax, impliedFlag, parentIndices) 1127 parentIndices.append(syntax) 1128 1129 if newSuffix: 1130 debug.logger & debug.flagIns and debug.logger( 1131 'receiveManagementEvent %s for suffix %s' % (action, newSuffix)) 1132 self.__manageColumns(action, (), newSuffix, val, idx, acInfo) 1133 1134 def registerAugmentions(self, *names): 1135 for modName, symName in names: 1136 if (modName, symName) in self.augmentingRows: 1137 raise error.SmiError( 1138 'Row %s already augmented by %s::%s' % (self.name, modName, symName) 1139 ) 1140 self.augmentingRows[(modName, symName)] = 1 1141 return self 1142 1143 def setIndexNames(self, *names): 1144 for name in names: 1145 self.indexNames += (name,) 1146 return self 1147 1148 def getIndexNames(self): 1149 return self.indexNames 1150 1151 def __manageColumns(self, action, excludeName, nameSuffix, 1152 val, idx, acInfo): 1153 # Build a map of index names and values for automatic initialization 1154 indexVals = {} 1155 instId = nameSuffix 1156 indices = [] 1157 for impliedFlag, modName, symName in self.indexNames: 1158 mibObj, = mibBuilder.importSymbols(modName, symName) 1159 syntax, instId = self.setFromName(mibObj.syntax, instId, 1160 impliedFlag, indices) 1161 indexVals[mibObj.name] = syntax 1162 indices.append(syntax) 1163 1164 for name, var in self._vars.items(): 1165 if name == excludeName: 1166 continue 1167 1168 if name in indexVals: 1169 getattr(var, action)(name + nameSuffix, indexVals[name], idx, 1170 (None, None)) 1171 else: 1172 getattr(var, action)(name + nameSuffix, val, idx, acInfo) 1173 1174 debug.logger & debug.flagIns and debug.logger('__manageColumns: action %s name %s suffix %s %svalue %r' % ( 1175 action, name, nameSuffix, name in indexVals and "index " or "", indexVals.get(name, val))) 1176 1177 def __delegate(self, subAction, name, val, idx, acInfo): 1178 # Relay operation request to column, expect row operation request. 1179 rowIsActive = False 1180 try: 1181 getattr(self.getBranch(name, idx), 'write' + subAction)( 1182 name, val, idx, acInfo 1183 ) 1184 1185 except error.RowCreationWanted: 1186 self.__manageColumns( 1187 'create' + subAction, name[:len(self.name) + 1], 1188 name[len(self.name) + 1:], None, idx, acInfo 1189 ) 1190 1191 self.announceManagementEvent( 1192 'create' + subAction, name, None, idx, acInfo 1193 ) 1194 1195 # watch for RowStatus == 'stActive' 1196 rowIsActive = sys.exc_info()[1].get('syntax', 0) == 1 1197 1198 except error.RowDestructionWanted: 1199 self.__manageColumns( 1200 'destroy' + subAction, name[:len(self.name) + 1], 1201 name[len(self.name) + 1:], None, idx, acInfo 1202 ) 1203 1204 self.announceManagementEvent( 1205 'destroy' + subAction, name, None, idx, acInfo 1206 ) 1207 1208 return rowIsActive 1209 1210 def writeTest(self, name, val, idx, acInfo): 1211 self.__delegate('Test', name, val, idx, acInfo) 1212 1213 def writeCommit(self, name, val, idx, acInfo): 1214 rowIsActive = self.__delegate('Commit', name, val, idx, acInfo) 1215 if rowIsActive: 1216 for mibNode in self._vars.values(): 1217 colNode = mibNode.getNode(mibNode.name + name[len(self.name) + 1:]) 1218 if not colNode.syntax.hasValue(): 1219 raise error.InconsistentValueError(msg='Row consistency check failed for %r' % colNode) 1220 1221 def writeCleanup(self, name, val, idx, acInfo): 1222 self.branchVersionId += 1 1223 self.__delegate('Cleanup', name, val, idx, acInfo) 1224 1225 def writeUndo(self, name, val, idx, acInfo): 1226 self.__delegate('Undo', name, val, idx, acInfo) 1227 1228 # Table row management 1229 1230 # Table row access by instance name 1231 1232 def getInstName(self, colId, instId): 1233 return self.name + (colId,) + instId 1234 1235 # Table index management 1236 1237 def getIndicesFromInstId(self, instId): 1238 """Return index values for instance identification""" 1239 if instId in self.__idToIdxCache: 1240 return self.__idToIdxCache[instId] 1241 1242 indices = [] 1243 for impliedFlag, modName, symName in self.indexNames: 1244 mibObj, = mibBuilder.importSymbols(modName, symName) 1245 try: 1246 syntax, instId = self.setFromName(mibObj.syntax, instId, impliedFlag, indices) 1247 except PyAsn1Error: 1248 debug.logger & debug.flagIns and debug.logger('error resolving table indices at %s, %s: %s' % (self.__class__.__name__, instId, sys.exc_info()[1])) 1249 indices = [instId] 1250 instId = () 1251 break 1252 1253 indices.append(syntax) # to avoid cyclic refs 1254 1255 if instId: 1256 raise error.SmiError( 1257 'Excessive instance identifier sub-OIDs left at %s: %s' % 1258 (self, instId) 1259 ) 1260 1261 indices = tuple(indices) 1262 self.__idToIdxCache[instId] = indices 1263 1264 return indices 1265 1266 def getInstIdFromIndices(self, *indices): 1267 """Return column instance identification from indices""" 1268 try: 1269 return self.__idxToIdCache[indices] 1270 except TypeError: 1271 cacheable = False 1272 except KeyError: 1273 cacheable = True 1274 idx = 0 1275 instId = () 1276 parentIndices = [] 1277 for impliedFlag, modName, symName in self.indexNames: 1278 if idx >= len(indices): 1279 break 1280 mibObj, = mibBuilder.importSymbols(modName, symName) 1281 syntax = mibObj.syntax.clone(indices[idx]) 1282 instId += self.getAsName(syntax, impliedFlag, parentIndices) 1283 parentIndices.append(syntax) 1284 idx += 1 1285 if cacheable: 1286 self.__idxToIdCache[indices] = instId 1287 return instId 1288 1289 # Table access by index 1290 1291 def getInstNameByIndex(self, colId, *indices): 1292 """Build column instance name from components""" 1293 return self.name + (colId,) + self.getInstIdFromIndices(*indices) 1294 1295 def getInstNamesByIndex(self, *indices): 1296 """Build column instance names from indices""" 1297 instNames = [] 1298 for columnName in self._vars.keys(): 1299 instNames.append( 1300 self.getInstNameByIndex(*(columnName[-1],) + indices) 1301 ) 1302 1303 return tuple(instNames) 1304 1305 1306class MibTable(MibTree): 1307 """MIB table. Manages a set of TableRow's""" 1308 1309 def __init__(self, name): 1310 MibTree.__init__(self, name) 1311 1312 1313zeroDotZero = ObjectIdentity((0, 0)) 1314 1315# OID tree 1316itu_t = MibTree((0,)).setLabel('itu-t') 1317iso = MibTree((1,)) 1318joint_iso_itu_t = MibTree((2,)).setLabel('joint-iso-itu-t') 1319org = MibIdentifier(iso.name + (3,)) 1320dod = MibIdentifier(org.name + (6,)) 1321internet = MibIdentifier(dod.name + (1,)) 1322directory = MibIdentifier(internet.name + (1,)) 1323mgmt = MibIdentifier(internet.name + (2,)) 1324mib_2 = MibIdentifier(mgmt.name + (1,)).setLabel('mib-2') 1325transmission = MibIdentifier(mib_2.name + (10,)) 1326experimental = MibIdentifier(internet.name + (3,)) 1327private = MibIdentifier(internet.name + (4,)) 1328enterprises = MibIdentifier(private.name + (1,)) 1329security = MibIdentifier(internet.name + (5,)) 1330snmpV2 = MibIdentifier(internet.name + (6,)) 1331 1332snmpDomains = MibIdentifier(snmpV2.name + (1,)) 1333snmpProxys = MibIdentifier(snmpV2.name + (2,)) 1334snmpModules = MibIdentifier(snmpV2.name + (3,)) 1335 1336mibBuilder.exportSymbols( 1337 'SNMPv2-SMI', MibNode=MibNode, 1338 Integer32=Integer32, Bits=Bits, IpAddress=IpAddress, 1339 Counter32=Counter32, Gauge32=Gauge32, Unsigned32=Unsigned32, 1340 TimeTicks=TimeTicks, Opaque=Opaque, Counter64=Counter64, 1341 ExtUTCTime=ExtUTCTime, 1342 ModuleIdentity=ModuleIdentity, ObjectIdentity=ObjectIdentity, 1343 NotificationType=NotificationType, MibScalar=MibScalar, 1344 MibScalarInstance=MibScalarInstance, 1345 MibIdentifier=MibIdentifier, MibTree=MibTree, 1346 MibTableColumn=MibTableColumn, MibTableRow=MibTableRow, 1347 MibTable=MibTable, zeroDotZero=zeroDotZero, 1348 itu_t=itu_t, iso=iso, joint_iso_itu_t=joint_iso_itu_t, org=org, dod=dod, 1349 internet=internet, directory=directory, mgmt=mgmt, mib_2=mib_2, 1350 transmission=transmission, experimental=experimental, private=private, 1351 enterprises=enterprises, security=security, snmpV2=snmpV2, 1352 snmpDomains=snmpDomains, snmpProxys=snmpProxys, snmpModules=snmpModules 1353) 1354 1355# XXX 1356# getAsName/setFromName goes out of MibRow? 1357# revisit getNextNode() -- needs optimization 1358