diff --git a/.gitignore b/.gitignore index 77f208ce7..976c4a50a 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,6 @@ docs/source/.templates/layout.html # Virtual envs venv* + +# Tox +.tox/ diff --git a/.travis.yml b/.travis.yml index eaf5f481e..497cf2a2e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,16 +1,41 @@ - language: python -python: - - "2.6" - - "2.7" - - "3.2" - - "3.3" - - "3.4" - - "3.5" - - "3.6" - - "nightly" -# - "pypy" -# - "pypy3" +cache: pip +matrix: + include: + - os: linux + dist: trusty + python: '2.6' + - os: linux + dist: trusty + python: '2.7' + - os: linux + dist: trusty + python: '3.2' + - os: linux + dist: trusty + python: '3.3' + - os: linux + dist: trusty + python: '3.4' + - os: linux + dist: trusty + python: '3.5' + - os: linux + dist: trusty + python: '3.6' + - os: linux + dist: xenial + sudo: true + python: '3.7' + - os: linux + dist: trusty + python: 'nightly' + - os: linux + dist: trusty + python: 'pypy' + - os: linux + dist: trusty + python: 'pypy3' install: - pip install -r requirements.txt -r devel-requirements.txt - pip install -e . diff --git a/CHANGES.txt b/CHANGES.txt index d81161a6b..30dc64863 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,108 @@ +Revision 4.4.12, released 2019-09-24 +------------------------------------ + +- Fixed broken SNMPv3 `msgFlag` initialization on authoritative SNMP + engine ID discovery. This bug causes secure communication with peer + SNMP engines to stall at SNMP engine ID discovery procedure. + +Revision 4.4.11, released 2019-08-10 +------------------------------------ + +- Added SNMPv3 USM master and localized keys support to LCD configuration +- Improved initial and runtime USM debugging +- Fixed a bug in USM configuration which did not allow the same user names + to be added under different security names + +Revision 4.4.10, released 2019-07-29 +------------------------------------ + +- 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 +- Received MIB objects resolution made more forgiving to errors, added + optional `ignoreErrors` parameter to `ObjectType.resolveWithMib()` to + control that behaviour. +- Fixed asyncore main loop to respect non-default timer resolution +- Fixed `.setTimerResolution()` behaviour of abstract main loop dispatcher + to update call intervals of the existing periodic dispatcher jobs +- Fixed `var-bindings` initialization to prevent pyasn1 encoder failures + with newer pyasn1 versions where `SequenceOf` type looses its default + initializer. +- Fixed crash on uninitialized component serialization left out in + SNMP v1 TRAP PDU to SNMPv2/3 TRAP PDU proxy translation routine. + +Revision 4.4.9, released 2019-02-09 +----------------------------------- + +- Made MIB loader ignoring file and directory access errors +- Added missing SNMP PDU error classes and their handling in Command Responder +- Fixed crash on MIB load failure in case of directory access error +- Fixed socket transparency option (IPV6_TRANSPARENT) to make IPv6 + transparent operation functional + +Revision 4.4.8, released 2018-12-30 +----------------------------------- + +- Fixed Pythonized MIB load (in the source form) - made sure to turn + it into a code object prior to its execution + +Revision 4.4.7, released 2018-12-29 +----------------------------------- + +- Copyright notice extended to the year 2019 +- Exposed ASN.1 `Null` type through `rfc1902` module for convenience. +- Use `compile()` before `exec`'ing MIB modules to attach filename to + the stack frames (ultimately shown in traceback/debugger) +- Fixed hlapi/v3arch transport target caching to ensure transport targets + are different even if just timeout/retries options differ +- Fixed hlapi LCD configurator to include `contextName`. Prior to this fix + sending SNMPv3 TRAP with non-default `contextName` would fail. +- Fixed possible duplicate key occurrence in the `OrderedDict` following + a race condition +- Fixed undefined name references in `inet_pton`/`inet_ntop` substitute + routines for IPv6 in `TRANSPORT-ADDRESS-MIB.py` + +Revision 4.4.6, released 2018-09-13 +----------------------------------- + +- Improved package build and dependency tracking +- Fixed missing LICENSE from the tarball distribution +- Fixed `CommandGeneratorLcdConfigurator.unconfigure()` to fully clean up + internal caches, otherwise repetitive attempts to configure the target + would fail. +- Fix to tolerate possible duplicate enumerations in `Bits` and `Integer` + SMI types. +- Fix to tolerate non-initialised entries in SNMP community table. Once a + bad entry sneaked into the SNMP community table, all the subsequent + SNMP v1/v2c operations failed. The fix ignores incomplete SNMP community + table entries in the course of building indices. + +Revision 4.4.5, released 2018-08-05 +----------------------------------- + +- Added PySnmpError.cause attribute holding parent exception tuple +- Fixed broken InetAddressType rendering caused by a pyasn1 regression +- Fixed typo in RFC1158 module +- Fixed possible infinite loop in GETBULK response PDU builder +- Fixed memory leak in the `config.delContext()` VACM management harness +- Fixed `Bits` class initialization when enumeration values are given +- Fixed crash caused by incoming SNMPv3 message requesting SNMPv1/v2c + security model +- Fixed out-of-scope OIDs leaking at the end of SNMP table at hlapi + `nextCmd` and `bulkCmd` calls when `lexicographicMode = False` + Revision 4.4.4, released 2018-01-03 ----------------------------------- @@ -557,7 +661,7 @@ Revision 4.2.4, released 2013-01-30 defaulted value changed from None to () meaning no var-binds. - Attempt to convert Windows style EOL into UNIX ones in MIB source modules appeared to be unnecessary and even destructive to modules - data in some cases. So the convertion code removed altogether. + data in some cases. So the conversion code removed altogether. - Fix to isAccessAllowed() error handling at NotificationOriginator. System used to crash on access denied condition. - Fix to NotificationOriginator to make it use system uptime and trap OID @@ -818,7 +922,7 @@ Revision 4.1.16a, released 2011-03-17 configuration (LCD). + default debug.logger is now just a zero value instead of an object what saves big on frequent calls - + SNMPv2-SMI columnar indices <-> index values convertion code optimized. + + SNMPv2-SMI columnar indices <-> index values conversion code optimized. + pre-compute and re-use some of ASN.1 structures. + avoid setting PDU defaults to save on unnecessary initialization. + skip ASN.1 types verification where possible. @@ -1008,7 +1112,7 @@ Revision 4.1.9a, released 2007-11-28 effects. - Fix to rfc2576.v1ToV2c() PDU converter to perform noSuchName error code translation. -- Fixes to Notification PDU convertion code at rfc2576 in part of +- Fixes to Notification PDU conversion code at rfc2576 in part of snmpTrapOID handling. - Fix to nonRepeaters object use as sequence slicer (must be int) at cmdrsp.CommandResponderApplication @@ -1030,7 +1134,7 @@ Revision 4.1.8a, released 2007-08-14 ------------------------------------ - UNSTABLE ALPHA RELEASE. -- SMI/dispatcher timeout convertion multiplier is actually 100 (1/100 sec) +- SMI/dispatcher timeout conversion multiplier is actually 100 (1/100 sec) rather than 1/1000. This fix affects timeouts specified through SMI. - __repr__() implemented for UdpTransportTarget, CommunityData, UsmUserData in oneliner module. @@ -1081,7 +1185,7 @@ Revision 4.1.7a, released 2007-02-19 - Fix to MibViewController.getNodeName() to take MIB module name into account (SF bug #1505847). - Do explicit check for Counter32,Unsigned32,TimeTicks,Counter64 value types - in MibTableRow index convertion and in TextualConvention.prettyPrint() + in MibTableRow index conversion and in TextualConvention.prettyPrint() methods (SF bug #1506341). Handle Bits in indices as RFC2578 suggests. - Apply read-create column status to libsmi2pysnmp-generated code whenever MIB text specifies that (SF bug #1508955). @@ -1105,7 +1209,7 @@ Revision 4.1.7a, released 2007-02-19 - LCD unconfiguration functions for oneliners implemented (SF bug #1635270). - unloadModules() and unexportSymbols() implemented at MibBuilder - Notification type PDU proxy code fixed to produce symmetrical - convertion. + conversion. - Various SNMP engine-internal caches expiration implemented. - SMI-level access control now takes effect only if AC object is passed to MIB instrumentation API. diff --git a/LICENSE.txt b/LICENSE.rst similarity index 95% rename from LICENSE.txt rename to LICENSE.rst index 12a18b46e..4770ec3db 100644 --- a/LICENSE.txt +++ b/LICENSE.rst @@ -1,4 +1,4 @@ -Copyright (c) 2005-2018, Ilya Etingof +Copyright (c) 2005-2019, Ilya Etingof All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/MANIFEST.in b/MANIFEST.in index c72432c29..21af931bd 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,4 @@ -include *.txt *.md *.sh +include *.rst *.txt *.md *.sh recursive-include examples *.py recursive-include docs/source *.rst *.svg *.py recursive-include docs/mibs *.txt diff --git a/README.md b/README.md index 9489ed1f2..1ccace838 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,8 @@ SNMP library for Python ----------------------- [![PyPI](https://img.shields.io/pypi/v/pysnmp.svg?maxAge=2592000)](https://pypi.python.org/pypi/pysnmp) [![Python Versions](https://img.shields.io/pypi/pyversions/pysnmp.svg)](https://pypi.python.org/pypi/pysnmp/) -[![Build status](https://travis-ci.org/etingof/pysnmp.svg?branch=master)](https://secure.travis-ci.org/etingof/pysnmp) -[![GitHub license](https://img.shields.io/badge/license-BSD-blue.svg)](https://raw.githubusercontent.com/etingof/pysnmp/master/LICENSE.txt) +[![Build status](https://travis-ci.org/etingof/pysnmp.svg?branch=master)](https://travis-ci.org/etingof/pysnmp) +[![GitHub license](https://img.shields.io/badge/license-BSD-blue.svg)](https://raw.githubusercontent.com/etingof/pysnmp/master/LICENSE.rst) This is a pure-Python, open source and free implementation of v1/v2c/v3 SNMP engine distributed under 2-clause [BSD license](http://snmplabs.com/pysnmp/license.html). @@ -27,7 +27,7 @@ Features * [PySMI](http://snmplabs.com/pysmi/) integration for dynamic MIB compilation * Built-in instrumentation exposing protocol engine operations * Python eggs and py2exe friendly -* 100% Python, works with Python 2.4 though 3.6 +* 100% Python, works with Python 2.4 though 3.7 * MT-safe (if SnmpEngine is thread-local) Features, specific to SNMPv3 model include: @@ -59,11 +59,11 @@ to download and install PySNMP along with its dependencies: * [PyCryptodomex](https://pycryptodome.readthedocs.io) (required only if SNMPv3 encryption is in use) * [PySMI](http://snmplabs.com/pysmi/) (required for MIB services only) -Besides the library, command-line [SNMP utilities](https://github.com/etingof/pysnmp-apps) +Besides the library, command-line [SNMP utilities](https://github.com/etingof/snmpclitools) written in pure-Python could be installed via: ```bash -$ pip install pysnmp-apps +$ pip install snmpclitools ``` and used in the very similar manner as conventional Net-SNMP tools: @@ -156,4 +156,4 @@ or try browsing pysnmp Bug reports and PRs are appreciated! ;-) -Copyright (c) 2005-2018, [Ilya Etingof](mailto:etingof@gmail.com). All rights reserved. +Copyright (c) 2005-2019, [Ilya Etingof](mailto:etingof@gmail.com). All rights reserved. diff --git a/devel-requirements.txt b/devel-requirements.txt index e78c2eb4b..02629f5e9 100644 --- a/devel-requirements.txt +++ b/devel-requirements.txt @@ -1,3 +1,16 @@ -sphinx -twisted +Sphinx <= 1.6; python_version < '2.7' +Sphinx > 1.6; python_version >= '2.7' trollius; python_version < '3.0' +# NOTE: Twisted < 19.2.1 has a security problem in URL handling. +# However, newer Twisted does not work on older Pythons. That's why +# we have to pin to older Twisted here. +# On the other hand, pysnmp does not use anything HTTP, however other +# dependencies can rely on that. +twisted < 15.4; python_version < '2.7' +twisted; python_version == '2.7' +twisted < 17.9; python_version == '3.0' +twisted < 17.9; python_version == '3.1' +twisted < 17.9; python_version == '3.2' +twisted <= 17.9; python_version == '3.3' +twisted <= 17.9; python_version == '3.4' +twisted; python_version >= '3.5' diff --git a/docs/mibs/PYSNMP-USM-MIB.txt b/docs/mibs/PYSNMP-USM-MIB.txt index 81e112c7b..739ca08e5 100644 --- a/docs/mibs/PYSNMP-USM-MIB.txt +++ b/docs/mibs/PYSNMP-USM-MIB.txt @@ -21,6 +21,8 @@ pysnmpUsmMIB MODULE-IDENTITY DESCRIPTION "This MIB module defines objects specific to User Security Model (USM) implementation at PySNMP." + REVISION "201908300000Z" + DESCRIPTION "Added USM key types" REVISION "201707300000Z" DESCRIPTION "Extended authentication key size" REVISION "201704140000Z" @@ -56,6 +58,19 @@ pysnmpUsmDiscovery OBJECT-TYPE DEFVAL { doDiscover } ::= { pysnmpUsmCfg 2 } +pysnmpUsmKeyType OBJECT-TYPE + SYNTAX INTEGER { passphrase (0), master(1), localized(2) } + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION "When configuring USM user, the value of this enumeration + determines how the keys should be treated. The default + value "passphrase" means that given keys are plain-text + pass-phrases, "master" indicates that the keys are pre-hashed + pass-phrases, while "localized" stands for pre-hashed + pass-phrases mixed with SNMP Security Engine ID value." + DEFVAL { passphrase } + ::= { pysnmpUsmCfg 3 } + -- The usmUser Group ************************************************ pysnmpUsmUser OBJECT IDENTIFIER ::= { pysnmpUsmMIBObjects 3 } diff --git a/docs/source/conf.py b/docs/source/conf.py index 5b4bb2879..10f97987e 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -51,7 +51,7 @@ # General information about the project. project = u'SNMP library for Python' -copyright = u'2005-2018, Ilya Etingof ' +copyright = u'2005-2019, Ilya Etingof ' author = u'Ilya Etingof ' # The version info for the project you're documenting, acts as replacement for diff --git a/docs/source/contents.rst b/docs/source/contents.rst index 2319bbfa3..c37c1a84c 100644 --- a/docs/source/contents.rst +++ b/docs/source/contents.rst @@ -17,14 +17,14 @@ multilingual capabilities, remote configuration and other features. PySNMP implementation closely follows intricate system details and features bringing most possible power and flexibility to its users. -Current PySNMP stable version is 4.4. It runs with Python 2.4 through 3.6 +Current PySNMP stable version is 4.4. It runs with Python 2.4 through 3.7 and is recommended for new applications as well as for migration from older, now obsolete, PySNMP releases. All site documentation and examples are written for the 4.4 and later versions in mind. Older materials are still available under the obsolete section. Besides the libraries, a set of pure-Python -`command-line tools `_ +`command-line tools `_ are shipped along with the system. Those tools mimic the interface and behaviour of popular Net-SNMP snmpget/snmpset/snmpwalk utilities. They may be useful in a cross-platform situations as well as a testing diff --git a/docs/source/development.rst b/docs/source/development.rst index 920b0ad90..0050f943b 100644 --- a/docs/source/development.rst +++ b/docs/source/development.rst @@ -93,7 +93,7 @@ sponsoring it. Please get back to us to discuss details. Contributions to the PySNMP source code is greatly appreciated as well. We require contributed code to run with Python 2.4 through the latest -Python version (which is 3.6 at the time of this writing). Contributed +Python version (which is 3.7 at the time of this writing). Contributed code will be redistributed under the terms of the same `license `_ as PySNMP is. diff --git a/docs/source/docs/api-reference.rst b/docs/source/docs/api-reference.rst index 8ff7d9584..b32f4dd00 100644 --- a/docs/source/docs/api-reference.rst +++ b/docs/source/docs/api-reference.rst @@ -217,10 +217,9 @@ User-based The :py:class:`~pysnmp.hlapi.UsmUserData` class provides SNMPv3 User-Based Security Model configuration for SNMP v3 systems. -.. autoclass:: pysnmp.hlapi.UsmUserData(userName, authKey=None, privKey=None, authProtocol=usmNoAuthProtocol, privProtocol=usmNoPrivProtocol, securityEngineId=None) +.. autoclass:: pysnmp.hlapi.UsmUserData(userName, authKey=None, privKey=None, authProtocol=usmNoAuthProtocol, privProtocol=usmNoPrivProtocol, securityEngineId=None, authKeyType=usmKeyTypePassphrase, privKeyType=usmKeyTypePassphrase) -Identification of Authentication and Privacy Protocols is done -via constant OIDs: +**Authentication protocol identifiers** .. autodata:: pysnmp.hlapi.usmNoAuthProtocol .. autodata:: pysnmp.hlapi.usmHMACMD5AuthProtocol @@ -230,6 +229,8 @@ via constant OIDs: .. autodata:: pysnmp.hlapi.usmHMAC256SHA384AuthProtocol .. autodata:: pysnmp.hlapi.usmHMAC384SHA512AuthProtocol +**Privacy (encryption) protocol identifiers** + .. autodata:: pysnmp.hlapi.usmNoPrivProtocol .. autodata:: pysnmp.hlapi.usmDESPrivProtocol .. autodata:: pysnmp.hlapi.usm3DESEDEPrivProtocol @@ -239,6 +240,17 @@ via constant OIDs: .. autodata:: pysnmp.hlapi.usmAesBlumenthalCfb192Protocol .. autodata:: pysnmp.hlapi.usmAesBlumenthalCfb256Protocol +**Key material types** + +.. autodata:: pysnmp.hlapi.usmKeyTypePassphrase +.. autodata:: pysnmp.hlapi.usmKeyTypeMaster +.. autodata:: pysnmp.hlapi.usmKeyTypeLocalized + +.. note:: + + SNMP authentication and encryption keys must be at least *8* + and at most *32* octets long. + Transport configuration is I/O framework specific and is described in respective sections. @@ -334,6 +346,20 @@ data description language. PySNMP types are derived from .. toctree:: :maxdepth: 2 +.. _null: + +Null type ++++++++++ + +.. autoclass:: pysnmp.proto.rfc1902.Null(initializer) + :members: + +.. note:: + + The `NULL` type actually belongs to the base ASN.1 types. It is not defined + in :RFC:`1902#section-2` as an SNMP type. The `Null` type is exposed through + `rfc1902` module just for convenience. + .. _integer32: Integer32 type diff --git a/docs/source/docs/pysnmp-hlapi-tutorial.rst b/docs/source/docs/pysnmp-hlapi-tutorial.rst index 1062a076d..22075810d 100644 --- a/docs/source/docs/pysnmp-hlapi-tutorial.rst +++ b/docs/source/docs/pysnmp-hlapi-tutorial.rst @@ -126,7 +126,7 @@ objects representing completely different instances of hardware or software being managed. This is where SNMP context could be used. -To indicate SNMP context at high-level API a preperly initialized +To indicate SNMP context at high-level API a properly initialized :py:class:`~pysnmp.hlapi.ContextData` object should be used. For this example we will use the 'empty' context (default). diff --git a/docs/source/docs/snmp-design.rst b/docs/source/docs/snmp-design.rst index 2752a9180..c0cde7b43 100644 --- a/docs/source/docs/snmp-design.rst +++ b/docs/source/docs/snmp-design.rst @@ -227,7 +227,7 @@ engine loads those modules at runtime on demand. PySNMP MIB modules are universal -- the same module can be consumed by both managed and managing entities. -MIB convertion is performed automatically by PySNMP, but technically, +MIB conversion is performed automatically by PySNMP, but technically, it is handled by PySNMP sister project called `PySMI `_. However you can also perform said conversion by hand with PySMI's *mibdump.py* tool. diff --git a/docs/source/examples/hlapi/asyncore/sync/manager/cmdgen/advanced-topics.rst b/docs/source/examples/hlapi/asyncore/sync/manager/cmdgen/advanced-topics.rst index 9854bc52e..0d64c7665 100644 --- a/docs/source/examples/hlapi/asyncore/sync/manager/cmdgen/advanced-topics.rst +++ b/docs/source/examples/hlapi/asyncore/sync/manager/cmdgen/advanced-topics.rst @@ -70,6 +70,28 @@ Advanced Command Generator :download:`Download` script. +.. include:: /../../examples/hlapi/asyncore/sync/manager/cmdgen/usm-master-keys.py + :start-after: """ + :end-before: """# + +.. literalinclude:: /../../examples/hlapi/asyncore/sync/manager/cmdgen/usm-master-keys.py + :start-after: """# + :language: python + +:download:`Download` script. + + +.. include:: /../../examples/hlapi/asyncore/sync/manager/cmdgen/usm-localized-keys.py + :start-after: """ + :end-before: """# + +.. literalinclude:: /../../examples/hlapi/asyncore/sync/manager/cmdgen/usm-localized-keys.py + :start-after: """# + :language: python + +:download:`Download` script. + + .. include:: /../../examples/hlapi/asyncore/sync/manager/cmdgen/query-agents-from-multuple-threads-over-ipv4-and-ipv6.py :start-after: """ :end-before: """# 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/docs/source/license.rst b/docs/source/license.rst index 829144f8a..ffc13bd70 100644 --- a/docs/source/license.rst +++ b/docs/source/license.rst @@ -2,4 +2,4 @@ License ======= -.. include:: ../../LICENSE.txt +.. include:: ../../LICENSE.rst diff --git a/examples/hlapi/asyncore/sync/manager/cmdgen/usm-localized-keys.py b/examples/hlapi/asyncore/sync/manager/cmdgen/usm-localized-keys.py new file mode 100644 index 000000000..4aba20d99 --- /dev/null +++ b/examples/hlapi/asyncore/sync/manager/cmdgen/usm-localized-keys.py @@ -0,0 +1,49 @@ +""" +SNMPv3: localized auth and privacy keys ++++++++++++++++++++++++++++++++++++++++ + +Send SNMP GET request using the following options: + +* with SNMPv3, user 'usr-md5-des', MD5 authentication, DES encryption +* use localized auth and privacy keys instead of pass-phrase or master keys +* configure authoritative SNMP engine ID (0x0000000000 can be used as well) +* over IPv4/UDP +* to an Agent at demo.snmplabs.com:161 +* for SNMPv2-MIB::sysDescr.0 MIB object instance + +Functionally similar to: + +| $ snmpget -v3 -l authPriv \ + -u usr-md5-des \ + -e 0x80004fb805636c6f75644dab22cc \ + -3k 0x6b99c475259ef7976cf8d028a3381eeb \ + -3K 0x92b5ef98f0a216885e73944e58c07345 \ + demo.snmplabs.com SNMPv2-MIB::sysDescr.0 + +"""# +from pysnmp.hlapi import * + +errorIndication, errorStatus, errorIndex, varBinds = next( + getCmd(SnmpEngine(), + UsmUserData('usr-md5-des', + authKey=OctetString( + hexValue='6b99c475259ef7976cf8d028a3381eeb'), + privKey=OctetString( + hexValue='92b5ef98f0a216885e73944e58c07345'), + authKeyType=usmKeyTypeLocalized, + privKeyType=usmKeyTypeLocalized, + securityEngineId=OctetString( + hexValue='80004fb805636c6f75644dab22cc')), + UdpTransportTarget(('demo.snmplabs.com', 161)), + ContextData(), + ObjectType(ObjectIdentity('SNMPv2-MIB', 'sysDescr', 0))) +) + +if errorIndication: + print(errorIndication) +elif errorStatus: + print('%s at %s' % (errorStatus.prettyPrint(), + errorIndex and varBinds[int(errorIndex) - 1][0] or '?')) +else: + for varBind in varBinds: + print(' = '.join([x.prettyPrint() for x in varBind])) diff --git a/examples/hlapi/asyncore/sync/manager/cmdgen/usm-master-keys.py b/examples/hlapi/asyncore/sync/manager/cmdgen/usm-master-keys.py new file mode 100644 index 000000000..a79c6d248 --- /dev/null +++ b/examples/hlapi/asyncore/sync/manager/cmdgen/usm-master-keys.py @@ -0,0 +1,45 @@ +""" +SNMPv3: master auth and privacy keys +++++++++++++++++++++++++++++++++++++ + +Send SNMP GET request using the following options: + +* with SNMPv3, user 'usr-md5-des', MD5 authentication, DES encryption +* use master auth and privacy keys instead of pass-phrase +* over IPv4/UDP +* to an Agent at demo.snmplabs.com:161 +* for SNMPv2-MIB::sysDescr.0 MIB object instance + +Functionally similar to: + +| $ snmpget -v3 -l authPriv \ + -u usr-md5-des \ + -3m 0x1dcf59e86553b3afa5d32fd5d61bf0cf \ + -3M 0xec5ab55e93e1d85cb6846d0f23e845e0 \ + demo.snmplabs.com SNMPv2-MIB::sysDescr.0 + +"""# +from pysnmp.hlapi import * + +errorIndication, errorStatus, errorIndex, varBinds = next( + getCmd(SnmpEngine(), + UsmUserData('usr-md5-des', + authKey=OctetString( + hexValue='1dcf59e86553b3afa5d32fd5d61bf0cf'), + privKey=OctetString( + hexValue='ec5ab55e93e1d85cb6846d0f23e845e0'), + authKeyType=usmKeyTypeMaster, + privKeyType=usmKeyTypeMaster), + UdpTransportTarget(('demo.snmplabs.com', 161)), + ContextData(), + ObjectType(ObjectIdentity('SNMPv2-MIB', 'sysDescr', 0))) +) + +if errorIndication: + print(errorIndication) +elif errorStatus: + print('%s at %s' % (errorStatus.prettyPrint(), + errorIndex and varBinds[int(errorIndex) - 1][0] or '?')) +else: + for varBind in varBinds: + print(' = '.join([x.prettyPrint() for x in varBind])) diff --git a/examples/smi/manager/mib-tree-inspection.py b/examples/smi/manager/mib-tree-inspection.py index cff34b00f..58b110a60 100644 --- a/examples/smi/manager/mib-tree-inspection.py +++ b/examples/smi/manager/mib-tree-inspection.py @@ -58,10 +58,10 @@ rowNode, = mibBuilder.importSymbols(modName, symName) print(rowNode) -print('Conceptual table index value to oid convertion: '), +print('Conceptual table index value to oid conversion: '), oid = rowNode.getInstIdFromIndices('router') print(oid) -print('Conceptual table index oid to value convertion: '), +print('Conceptual table index oid to value conversion: '), print(rowNode.getIndicesFromInstId(oid)) print('MIB tree traversal') 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..7f82dde83 --- /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', 161)) +) + +# 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/examples/v3arch/asyncore/agent/cmdrsp/multiple-usm-users.py b/examples/v3arch/asyncore/agent/cmdrsp/multiple-usm-users.py index 3b1119dd1..9d1454ce3 100644 --- a/examples/v3arch/asyncore/agent/cmdrsp/multiple-usm-users.py +++ b/examples/v3arch/asyncore/agent/cmdrsp/multiple-usm-users.py @@ -32,7 +32,7 @@ config.addTransport( snmpEngine, udp.domainName, - udp.UdpTransport().openServerMode(('127.0.0.1', 161)) + udp.UdpTransport().openServerMode(('127.0.0.1', 1161)) ) # SNMPv3/USM setup diff --git a/pysnmp/__init__.py b/pysnmp/__init__.py index 168d8d7cb..954ad4ed1 100644 --- a/pysnmp/__init__.py +++ b/pysnmp/__init__.py @@ -1,5 +1,5 @@ # http://www.python.org/dev/peps/pep-0396/ -__version__ = '4.4.4' +__version__ = '4.4.12' # backward compatibility version = tuple([int(x) for x in __version__.split('.')]) majorVersionId = version[0] diff --git a/pysnmp/cache.py b/pysnmp/cache.py index 3c8d7c108..f4b8796e7 100644 --- a/pysnmp/cache.py +++ b/pysnmp/cache.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # # Limited-size dictionary-like class to use for caches diff --git a/pysnmp/carrier/asyncio/base.py b/pysnmp/carrier/asyncio/base.py index 3b09b3af6..ce1b644f6 100644 --- a/pysnmp/carrier/asyncio/base.py +++ b/pysnmp/carrier/asyncio/base.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # # Copyright (C) 2014, Zebra Technologies diff --git a/pysnmp/carrier/asyncio/dgram/base.py b/pysnmp/carrier/asyncio/dgram/base.py index 886f8801c..a845fa1e9 100644 --- a/pysnmp/carrier/asyncio/dgram/base.py +++ b/pysnmp/carrier/asyncio/dgram/base.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # # Copyright (C) 2014, Zebra Technologies @@ -31,6 +31,7 @@ # THE POSSIBILITY OF SUCH DAMAGE. # import sys +import platform import traceback from pysnmp.carrier.asyncio.base import AbstractAsyncioTransport from pysnmp.carrier import error @@ -41,6 +42,8 @@ except ImportError: import trollius as asyncio +IS_PYTHON_344_PLUS = platform.python_version_tuple() >= ('3', '4', '4') + class DgramAsyncioProtocol(asyncio.DatagramProtocol, AbstractAsyncioTransport): """Base Asyncio datagram Transport, to be used with AsyncioDispatcher""" @@ -83,7 +86,12 @@ def openClientMode(self, iface=None): c = self.loop.create_datagram_endpoint( lambda: self, local_addr=iface, family=self.sockFamily ) - self._lport = asyncio.async(c) + # Avoid deprecation warning for asyncio.async() + if IS_PYTHON_344_PLUS: + self._lport = asyncio.ensure_future(c) + else: # pragma: no cover + self._lport = getattr(asyncio, 'async')(c) + except Exception: raise error.CarrierError(';'.join(traceback.format_exception(*sys.exc_info()))) return self @@ -93,7 +101,11 @@ def openServerMode(self, iface): c = self.loop.create_datagram_endpoint( lambda: self, local_addr=iface, family=self.sockFamily ) - self._lport = asyncio.async(c) + # Avoid deprecation warning for asyncio.async() + if IS_PYTHON_344_PLUS: + self._lport = asyncio.ensure_future(c) + else: # pragma: no cover + self._lport = getattr(asyncio, 'async')(c) except Exception: raise error.CarrierError(';'.join(traceback.format_exception(*sys.exc_info()))) return self diff --git a/pysnmp/carrier/asyncio/dgram/udp.py b/pysnmp/carrier/asyncio/dgram/udp.py index c407eb4a9..41ada7230 100644 --- a/pysnmp/carrier/asyncio/dgram/udp.py +++ b/pysnmp/carrier/asyncio/dgram/udp.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # # Copyright (C) 2014, Zebra Technologies diff --git a/pysnmp/carrier/asyncio/dgram/udp6.py b/pysnmp/carrier/asyncio/dgram/udp6.py index 12fba8424..bc03b4471 100644 --- a/pysnmp/carrier/asyncio/dgram/udp6.py +++ b/pysnmp/carrier/asyncio/dgram/udp6.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # import socket diff --git a/pysnmp/carrier/asyncio/dispatch.py b/pysnmp/carrier/asyncio/dispatch.py index d45fdb6b9..e92fe95e0 100644 --- a/pysnmp/carrier/asyncio/dispatch.py +++ b/pysnmp/carrier/asyncio/dispatch.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # # Copyright (C) 2014, Zebra Technologies @@ -31,6 +31,7 @@ # THE POSSIBILITY OF SUCH DAMAGE. # import sys +import platform import traceback from pysnmp.carrier.base import AbstractTransportDispatcher from pysnmp.error import PySnmpError @@ -40,6 +41,7 @@ except ImportError: import trollius as asyncio +IS_PYTHON_344_PLUS = platform.python_version_tuple() >= ('3', '4', '4') class AsyncioDispatcher(AbstractTransportDispatcher): """AsyncioDispatcher based on asyncio event loop""" @@ -66,10 +68,14 @@ def runDispatcher(self, timeout=0.0): raise except Exception: raise PySnmpError(';'.join(traceback.format_exception(*sys.exc_info()))) - + def registerTransport(self, tDomain, transport): if self.loopingcall is None and self.getTimerResolution() > 0: - self.loopingcall = asyncio.async(self.handle_timeout()) + # Avoid deprecation warning for asyncio.async() + if IS_PYTHON_344_PLUS: + self.loopingcall = asyncio.ensure_future(self.handle_timeout()) + else: # pragma: no cover + self.loopingcall = getattr(asyncio, 'async')(self.handle_timeout()) AbstractTransportDispatcher.registerTransport( self, tDomain, transport ) diff --git a/pysnmp/carrier/asyncore/base.py b/pysnmp/carrier/asyncore/base.py index a1424d7d2..61c552210 100644 --- a/pysnmp/carrier/asyncore/base.py +++ b/pysnmp/carrier/asyncore/base.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # import socket diff --git a/pysnmp/carrier/asyncore/dgram/base.py b/pysnmp/carrier/asyncore/dgram/base.py index 6524b8bb4..5b0faab2d 100644 --- a/pysnmp/carrier/asyncore/dgram/base.py +++ b/pysnmp/carrier/asyncore/dgram/base.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # import socket @@ -73,10 +73,12 @@ def enablePktInfo(self, flag=1): raise error.CarrierError('sendmsg()/recvmsg() interface is not supported by this OS and/or Python version') try: - if self.socket.family in (socket.AF_INET, socket.AF_INET6): + if self.socket.family == socket.AF_INET: self.socket.setsockopt(socket.SOL_IP, socket.IP_PKTINFO, flag) + if self.socket.family == socket.AF_INET6: self.socket.setsockopt(socket.SOL_IPV6, socket.IPV6_RECVPKTINFO, flag) + except socket.error: raise error.CarrierError('setsockopt() for %s failed: %s' % (self.socket.family == socket.AF_INET6 and "IPV6_RECVPKTINFO" or "IP_PKTINFO", sys.exc_info()[1])) @@ -94,10 +96,12 @@ def enableTransparent(self, flag=1): ) if self.socket.family == socket.AF_INET6: self.socket.setsockopt( - socket.SOL_IPV6, socket.IP_TRANSPARENT, flag + socket.SOL_IPV6, socket.IPV6_TRANSPARENT, flag ) + except socket.error: raise error.CarrierError('setsockopt() for IP_TRANSPARENT failed: %s' % sys.exc_info()[1]) + except OSError: raise error.CarrierError('IP_TRANSPARENT socket option requires superusre previleges') @@ -113,14 +117,17 @@ def sendMessage(self, outgoingMessage, transportAddress): def normalizeAddress(self, transportAddress): if not isinstance(transportAddress, self.addressType): transportAddress = self.addressType(transportAddress) + if not transportAddress.getLocalAddress(): transportAddress.setLocalAddress(self.getLocalAddress()) + return transportAddress def getLocalAddress(self): # one evil OS does not seem to support getsockname() for DGRAM sockets try: return self.socket.getsockname() + except Exception: return '0.0.0.0', 0 diff --git a/pysnmp/carrier/asyncore/dgram/udp.py b/pysnmp/carrier/asyncore/dgram/udp.py index ed9a70e51..08ae5ceba 100644 --- a/pysnmp/carrier/asyncore/dgram/udp.py +++ b/pysnmp/carrier/asyncore/dgram/udp.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # from socket import AF_INET diff --git a/pysnmp/carrier/asyncore/dgram/udp6.py b/pysnmp/carrier/asyncore/dgram/udp6.py index 944048b7a..900ef3c7d 100644 --- a/pysnmp/carrier/asyncore/dgram/udp6.py +++ b/pysnmp/carrier/asyncore/dgram/udp6.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # from pysnmp.carrier import sockfix diff --git a/pysnmp/carrier/asyncore/dgram/unix.py b/pysnmp/carrier/asyncore/dgram/unix.py index 6ab1e5fd4..15f68b2ba 100644 --- a/pysnmp/carrier/asyncore/dgram/unix.py +++ b/pysnmp/carrier/asyncore/dgram/unix.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # import os diff --git a/pysnmp/carrier/asyncore/dispatch.py b/pysnmp/carrier/asyncore/dispatch.py index a88bd6739..c271387bc 100644 --- a/pysnmp/carrier/asyncore/dispatch.py +++ b/pysnmp/carrier/asyncore/dispatch.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # from time import time @@ -42,7 +42,7 @@ def transportsAreWorking(self): def runDispatcher(self, timeout=0.0): while self.jobsArePending() or self.transportsAreWorking(): try: - loop(timeout and timeout or self.timeout, + loop(timeout or self.getTimerResolution(), use_poll=True, map=self.__sockMap, count=1) except KeyboardInterrupt: raise diff --git a/pysnmp/carrier/asynsock/dgram/udp.py b/pysnmp/carrier/asynsock/dgram/udp.py index c77184de9..ab44b28f3 100644 --- a/pysnmp/carrier/asynsock/dgram/udp.py +++ b/pysnmp/carrier/asynsock/dgram/udp.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # from pysnmp.carrier.asyncore.dgram.udp import * diff --git a/pysnmp/carrier/asynsock/dgram/udp6.py b/pysnmp/carrier/asynsock/dgram/udp6.py index 7ac4c5550..b3a8aec02 100644 --- a/pysnmp/carrier/asynsock/dgram/udp6.py +++ b/pysnmp/carrier/asynsock/dgram/udp6.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # from pysnmp.carrier.asyncore.dgram.udp6 import * diff --git a/pysnmp/carrier/asynsock/dgram/unix.py b/pysnmp/carrier/asynsock/dgram/unix.py index 6e75628a1..887b4edb6 100644 --- a/pysnmp/carrier/asynsock/dgram/unix.py +++ b/pysnmp/carrier/asynsock/dgram/unix.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # from pysnmp.carrier.asyncore.dgram.unix import * diff --git a/pysnmp/carrier/asynsock/dispatch.py b/pysnmp/carrier/asynsock/dispatch.py index 120a9a70f..478d9e49e 100644 --- a/pysnmp/carrier/asynsock/dispatch.py +++ b/pysnmp/carrier/asynsock/dispatch.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # from pysnmp.carrier.asyncore.dispatch import * diff --git a/pysnmp/carrier/base.py b/pysnmp/carrier/base.py index aedd42eb3..ce06b25bf 100644 --- a/pysnmp/carrier/base.py +++ b/pysnmp/carrier/base.py @@ -1,22 +1,28 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # +import sys + from pysnmp.carrier import error class TimerCallable(object): def __init__(self, cbFun, callInterval): self.__cbFun = cbFun - self.__callInterval = callInterval self.__nextCall = 0 + if sys.version_info > (2, 6): + self.__callInterval = callInterval + else: + self.interval = callInterval + def __call__(self, timeNow): if self.__nextCall <= timeNow: self.__cbFun(timeNow) - self.__nextCall = timeNow + self.__callInterval + self.__nextCall = timeNow + self.interval def __eq__(self, cbFun): return self.__cbFun == cbFun @@ -36,6 +42,15 @@ def __gt__(self, cbFun): def __ge__(self, cbFun): return self.__cbFun >= cbFun + if sys.version_info > (2, 6): + @property + def interval(self): + return self.__callInterval + + @interval.setter + def interval(self, callInterval): + self.__callInterval = callInterval + class AbstractTransportDispatcher(object): def __init__(self): @@ -151,6 +166,12 @@ def getTimerResolution(self): def setTimerResolution(self, timerResolution): if timerResolution < 0.01 or timerResolution > 10: raise error.CarrierError('Impossible timer resolution') + + for timerCallable in self.__timerCallables: + if timerCallable.interval == self.__timerResolution: + # Update periodics for default resolutions + timerCallable.interval = timerResolution + self.__timerResolution = timerResolution self.__timerDelta = timerResolution * 0.05 diff --git a/pysnmp/carrier/error.py b/pysnmp/carrier/error.py index 601fd0eea..34d1add28 100644 --- a/pysnmp/carrier/error.py +++ b/pysnmp/carrier/error.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # from pysnmp import error diff --git a/pysnmp/carrier/sockfix.py b/pysnmp/carrier/sockfix.py index 01b69223e..fab6d1aa8 100644 --- a/pysnmp/carrier/sockfix.py +++ b/pysnmp/carrier/sockfix.py @@ -1,19 +1,28 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # import socket -symbols = { +from pysnmp import debug + + +SYMBOLS = { 'IP_PKTINFO': 8, 'IP_TRANSPARENT': 19, 'SOL_IPV6': 41, 'IPV6_RECVPKTINFO': 49, - 'IPV6_PKTINFO': 50 + 'IPV6_PKTINFO': 50, + 'IPV6_TRANSPARENT': 75 } -for symbol in symbols: +for symbol, value in SYMBOLS.items(): if not hasattr(socket, symbol): - setattr(socket, symbol, symbols[symbol]) + setattr(socket, symbol, value) + + debug.logger & debug.flagIO and debug.logger( + 'WARNING: the socket module on this platform misses option %s. ' + 'Assuming its value is %d.' % (symbol, value) + ) diff --git a/pysnmp/carrier/sockmsg.py b/pysnmp/carrier/sockmsg.py index 634ea1878..4ff55a911 100644 --- a/pysnmp/carrier/sockmsg.py +++ b/pysnmp/carrier/sockmsg.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # # The following routines act like sendto()/recvfrom() calls but additionally @@ -17,6 +17,8 @@ # import sys +from pysnmp import debug + if sys.version_info[:2] < (3, 3): # noinspection PyUnusedLocal def getRecvFrom(addressType): @@ -29,7 +31,8 @@ def getSendTo(addressType): import ctypes import ipaddress import socket - from pysnmp.carrier import sockfix, error + from pysnmp.carrier import sockfix + from pysnmp.carrier import error uint32_t = ctypes.c_uint32 in_addr_t = uint32_t @@ -69,38 +72,58 @@ class in6_pktinfo(ctypes.Structure): def getRecvFrom(addressType): + def recvfrom(s, sz): _to = None + data, ancdata, msg_flags, _from = s.recvmsg(sz, socket.CMSG_LEN(sz)) + for anc in ancdata: if anc[0] == socket.SOL_IP and anc[1] == socket.IP_PKTINFO: addr = in_pktinfo.from_buffer_copy(anc[2]) addr = ipaddress.IPv4Address(memoryview(addr.ipi_addr).tobytes()) _to = (str(addr), s.getsockname()[1]) + break + elif anc[0] == socket.SOL_IPV6 and anc[1] == socket.IPV6_PKTINFO: addr = in6_pktinfo.from_buffer_copy(anc[2]) addr = ipaddress.ip_address(memoryview(addr.ipi6_addr).tobytes()) _to = (str(addr), s.getsockname()[1]) + break + + debug.logger & debug.flagIO and debug.logger( + 'recvfrom: received %d octets from %s to %s; ' + 'iov blob %r' % (len(data), _from, _to, ancdata)) + return data, addressType(_from).setLocalAddress(_to) return recvfrom def getSendTo(addressType): + def sendto(s, _data, _to): ancdata = [] if type(_to) == addressType: addr = ipaddress.ip_address(_to.getLocalAddress()[0]) + else: addr = ipaddress.ip_address(s.getsockname()[0]) + if type(addr) == ipaddress.IPv4Address: _f = in_pktinfo() _f.ipi_spec_dst = in_addr.from_buffer_copy(addr.packed) ancdata = [(socket.SOL_IP, socket.IP_PKTINFO, memoryview(_f).tobytes())] + elif s.family == socket.AF_INET6 and type(addr) == ipaddress.IPv6Address: _f = in6_pktinfo() _f.ipi6_addr = in6_addr.from_buffer_copy(addr.packed) ancdata = [(socket.SOL_IPV6, socket.IPV6_PKTINFO, memoryview(_f).tobytes())] + + debug.logger & debug.flagIO and debug.logger( + 'sendto: sending %d octets to %s; address %r; ' + 'iov blob %r' % (len(_data), _to, addr, ancdata)) + return s.sendmsg([_data], ancdata, 0, _to) return sendto diff --git a/pysnmp/carrier/twisted/base.py b/pysnmp/carrier/twisted/base.py index f7d0f3065..d8b75bc73 100644 --- a/pysnmp/carrier/twisted/base.py +++ b/pysnmp/carrier/twisted/base.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # # Copyright (C) 2008 Truelite Srl diff --git a/pysnmp/carrier/twisted/dgram/base.py b/pysnmp/carrier/twisted/dgram/base.py index 7df96d93a..4995aec44 100644 --- a/pysnmp/carrier/twisted/dgram/base.py +++ b/pysnmp/carrier/twisted/dgram/base.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # import sys diff --git a/pysnmp/carrier/twisted/dgram/udp.py b/pysnmp/carrier/twisted/dgram/udp.py index 1eaf86361..ac0f23f19 100644 --- a/pysnmp/carrier/twisted/dgram/udp.py +++ b/pysnmp/carrier/twisted/dgram/udp.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # import sys diff --git a/pysnmp/carrier/twisted/dgram/unix.py b/pysnmp/carrier/twisted/dgram/unix.py index 5695baadb..a256969ca 100644 --- a/pysnmp/carrier/twisted/dgram/unix.py +++ b/pysnmp/carrier/twisted/dgram/unix.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # import sys diff --git a/pysnmp/carrier/twisted/dispatch.py b/pysnmp/carrier/twisted/dispatch.py index 5113e7b24..def16f6f0 100644 --- a/pysnmp/carrier/twisted/dispatch.py +++ b/pysnmp/carrier/twisted/dispatch.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # # Copyright (C) 2008 Truelite Srl diff --git a/pysnmp/debug.py b/pysnmp/debug.py index 83502867c..715a38761 100644 --- a/pysnmp/debug.py +++ b/pysnmp/debug.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # import logging diff --git a/pysnmp/entity/config.py b/pysnmp/entity/config.py index 06d6c14db..4ff5895e8 100644 --- a/pysnmp/entity/config.py +++ b/pysnmp/entity/config.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # from pyasn1.compat.octets import null @@ -11,8 +11,10 @@ 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 +from pysnmp import debug # A shortcut to popular constants @@ -28,7 +30,9 @@ usmHMAC192SHA256AuthProtocol = hmacsha2.HmacSha2.sha256ServiceID usmHMAC256SHA384AuthProtocol = hmacsha2.HmacSha2.sha384ServiceID usmHMAC384SHA512AuthProtocol = hmacsha2.HmacSha2.sha512ServiceID + usmNoAuthProtocol = noauth.NoAuth.serviceID +"""No authentication service""" # Privacy protocol usmDESPrivProtocol = des.Des.serviceID @@ -40,6 +44,11 @@ usmAesCfb256Protocol = aes256.Aes256.serviceID # non-standard but used by many vendors usmNoPrivProtocol = nopriv.NoPriv.serviceID +# USM key types (PYSNMP-USM-MIB::pysnmpUsmKeyType) +usmKeyTypePassphrase = 0 +usmKeyTypeMaster = 1 +usmKeyTypeLocalized = 2 + # Auth services authServices = {hmacmd5.HmacMd5.serviceID: hmacmd5.HmacMd5(), hmacsha.HmacSha.serviceID: hmacsha.HmacSha(), @@ -83,13 +92,15 @@ def addV1System(snmpEngine, communityIndex, communityName, if contextName is None: contextName = null + securityName = securityName is not None and securityName or communityIndex + snmpEngine.msgAndPduDsp.mibInstrumController.writeVars( ((snmpCommunityEntry.name + (8,) + tblIdx, 'destroy'),) ) snmpEngine.msgAndPduDsp.mibInstrumController.writeVars( ((snmpCommunityEntry.name + (1,) + tblIdx, communityIndex), (snmpCommunityEntry.name + (2,) + tblIdx, communityName), - (snmpCommunityEntry.name + (3,) + tblIdx, securityName is not None and securityName or communityIndex), + (snmpCommunityEntry.name + (3,) + tblIdx, securityName), (snmpCommunityEntry.name + (4,) + tblIdx, contextEngineId), (snmpCommunityEntry.name + (5,) + tblIdx, contextName), (snmpCommunityEntry.name + (6,) + tblIdx, transportTag), @@ -97,6 +108,13 @@ def addV1System(snmpEngine, communityIndex, communityName, (snmpCommunityEntry.name + (8,) + tblIdx, 'createAndGo')) ) + debug.logger & debug.flagSM and debug.logger( + 'addV1System: added new table entry ' + 'communityIndex "%s" communityName "%s" securityName "%s" ' + 'contextEngineId "%s" contextName "%s" transportTag ' + '"%s"' % (communityIndex, communityName, securityName, + contextEngineId, contextName, transportTag)) + def delV1System(snmpEngine, communityIndex): (snmpCommunityEntry, tblIdx, @@ -105,6 +123,10 @@ def delV1System(snmpEngine, communityIndex): ((snmpCommunityEntry.name + (8,) + tblIdx, 'destroy'),) ) + debug.logger & debug.flagSM and debug.logger( + 'delV1System: deleted table entry by communityIndex ' + '"%s"' % (communityIndex,)) + def __cookV3UserInfo(snmpEngine, securityName, securityEngineId): mibBuilder = snmpEngine.msgAndPduDsp.mibInstrumController.mibBuilder @@ -112,17 +134,17 @@ def __cookV3UserInfo(snmpEngine, securityName, securityEngineId): snmpEngineID, = mibBuilder.importSymbols('__SNMP-FRAMEWORK-MIB', 'snmpEngineID') if securityEngineId is None: - snmpEngineID = snmpEngineID.syntax + securityEngineId = snmpEngineID.syntax else: - snmpEngineID = snmpEngineID.syntax.clone(securityEngineId) + securityEngineId = snmpEngineID.syntax.clone(securityEngineId) usmUserEntry, = mibBuilder.importSymbols('SNMP-USER-BASED-SM-MIB', 'usmUserEntry') - tblIdx1 = usmUserEntry.getInstIdFromIndices(snmpEngineID, securityName) + tblIdx1 = usmUserEntry.getInstIdFromIndices(securityEngineId, securityName) pysnmpUsmSecretEntry, = mibBuilder.importSymbols('PYSNMP-USM-MIB', 'pysnmpUsmSecretEntry') tblIdx2 = pysnmpUsmSecretEntry.getInstIdFromIndices(securityName) - return snmpEngineID, usmUserEntry, tblIdx1, pysnmpUsmSecretEntry, tblIdx2 + return securityEngineId, usmUserEntry, tblIdx1, pysnmpUsmSecretEntry, tblIdx2 def addV3User(snmpEngine, userName, @@ -130,16 +152,22 @@ def addV3User(snmpEngine, userName, privProtocol=usmNoPrivProtocol, privKey=None, securityEngineId=None, securityName=None, - # deprecated parameters follow + authKeyType=usmKeyTypePassphrase, + privKeyType=usmKeyTypePassphrase, + # deprecated parameter contextEngineId=None): + mibBuilder = snmpEngine.msgAndPduDsp.mibInstrumController.mibBuilder if securityName is None: securityName = userName + if securityEngineId is None: # backward compatibility securityEngineId = contextEngineId - (snmpEngineID, usmUserEntry, tblIdx1, - pysnmpUsmSecretEntry, tblIdx2) = __cookV3UserInfo(snmpEngine, userName, securityEngineId) + + (securityEngineId, usmUserEntry, tblIdx1, + pysnmpUsmSecretEntry, tblIdx2) = __cookV3UserInfo( + snmpEngine, securityName, securityEngineId) # Load augmenting table before creating new row in base one pysnmpUsmKeyEntry, = mibBuilder.importSymbols('PYSNMP-USM-MIB', 'pysnmpUsmKeyEntry') @@ -159,47 +187,109 @@ def addV3User(snmpEngine, userName, (usmUserEntry.name + (13,) + tblIdx1, 'createAndGo')) ) - # Localize keys - if authProtocol in authServices: - hashedAuthPassphrase = authServices[authProtocol].hashPassphrase( - authKey and authKey or null + if authProtocol not in authServices: + raise error.PySnmpError('Unknown auth protocol %s' % (authProtocol,)) + + if privProtocol not in privServices: + raise error.PySnmpError('Unknown privacy protocol %s' % (privProtocol,)) + + pysnmpUsmKeyType, = mibBuilder.importSymbols('__PYSNMP-USM-MIB', 'pysnmpUsmKeyType') + + authKeyType = pysnmpUsmKeyType.syntax.clone(authKeyType) + + # Localize authentication key unless given + + authKey = authKey and rfc1902.OctetString(authKey) + + masterAuthKey = localAuthKey = authKey + + if authKeyType < usmKeyTypeMaster: # pass phrase is given + masterAuthKey = authServices[authProtocol].hashPassphrase( + authKey or null ) + + if authKeyType < usmKeyTypeLocalized: # pass phrase or master key is given localAuthKey = authServices[authProtocol].localizeKey( - hashedAuthPassphrase, snmpEngineID + masterAuthKey, securityEngineId ) - else: - raise error.PySnmpError('Unknown auth protocol %s' % (authProtocol,)) - if privProtocol in privServices: - hashedPrivPassphrase = privServices[privProtocol].hashPassphrase( - authProtocol, privKey and privKey or null + # Localize privacy key unless given + + privKeyType = pysnmpUsmKeyType.syntax.clone(privKeyType) + + privKey = privKey and rfc1902.OctetString(privKey) + + masterPrivKey = localPrivKey = privKey + + if privKeyType < usmKeyTypeMaster: # pass phrase is given + masterPrivKey = privServices[privProtocol].hashPassphrase( + authProtocol, privKey or null ) + + if privKeyType < usmKeyTypeLocalized: # pass phrase or master key is given localPrivKey = privServices[privProtocol].localizeKey( - authProtocol, hashedPrivPassphrase, snmpEngineID + authProtocol, masterPrivKey, securityEngineId ) - else: - raise error.PySnmpError('Unknown priv protocol %s' % (privProtocol,)) - # Commit localized keys + # Commit only the keys we have + snmpEngine.msgAndPduDsp.mibInstrumController.writeVars( - ((pysnmpUsmKeyEntry.name + (1,) + tblIdx1, localAuthKey), - (pysnmpUsmKeyEntry.name + (2,) + tblIdx1, localPrivKey), - (pysnmpUsmKeyEntry.name + (3,) + tblIdx1, hashedAuthPassphrase), - (pysnmpUsmKeyEntry.name + (4,) + tblIdx1, hashedPrivPassphrase)) + ((pysnmpUsmKeyEntry.name + (1,) + tblIdx1, localAuthKey),) ) - # Commit passphrases + snmpEngine.msgAndPduDsp.mibInstrumController.writeVars( + ((pysnmpUsmKeyEntry.name + (2,) + tblIdx1, localPrivKey),) + ) + + if authKeyType < usmKeyTypeLocalized: + snmpEngine.msgAndPduDsp.mibInstrumController.writeVars( + ((pysnmpUsmKeyEntry.name + (3,) + tblIdx1, masterAuthKey),) + ) + + if privKeyType < usmKeyTypeLocalized: + snmpEngine.msgAndPduDsp.mibInstrumController.writeVars( + ((pysnmpUsmKeyEntry.name + (4,) + tblIdx1, masterPrivKey),) + ) snmpEngine.msgAndPduDsp.mibInstrumController.writeVars( ((pysnmpUsmSecretEntry.name + (4,) + tblIdx2, 'destroy'),) ) + + # Commit plain-text pass-phrases if we have them + snmpEngine.msgAndPduDsp.mibInstrumController.writeVars( - ((pysnmpUsmSecretEntry.name + (1,) + tblIdx2, userName), - (pysnmpUsmSecretEntry.name + (2,) + tblIdx2, authKey), - (pysnmpUsmSecretEntry.name + (3,) + tblIdx2, privKey), - (pysnmpUsmSecretEntry.name + (4,) + tblIdx2, 'createAndGo')) + ((pysnmpUsmSecretEntry.name + (4,) + tblIdx2, 'createAndGo'),) ) + if authKeyType < usmKeyTypeMaster: + snmpEngine.msgAndPduDsp.mibInstrumController.writeVars( + ((pysnmpUsmSecretEntry.name + (1,) + tblIdx2, userName), + (pysnmpUsmSecretEntry.name + (2,) + tblIdx2, authKey)) + ) + + if privKeyType < usmKeyTypeMaster: + snmpEngine.msgAndPduDsp.mibInstrumController.writeVars( + ((pysnmpUsmSecretEntry.name + (1,) + tblIdx2, userName), + (pysnmpUsmSecretEntry.name + (3,) + tblIdx2, privKey)) + ) + + debug.logger & debug.flagSM and debug.logger( + 'addV3User: added new table entries ' + 'userName "%s" securityName "%s" authProtocol %s ' + 'privProtocol %s localAuthKey "%s" localPrivKey "%s" ' + 'masterAuthKey "%s" masterPrivKey "%s" authKey "%s" ' + 'privKey "%s" by index securityName "%s" securityEngineId ' + '"%s"' % ( + userName, securityName, authProtocol, privProtocol, + localAuthKey and localAuthKey.prettyPrint(), + localPrivKey and localPrivKey.prettyPrint(), + masterAuthKey and masterAuthKey.prettyPrint(), + masterPrivKey and masterPrivKey.prettyPrint(), + authKey and authKey.prettyPrint(), + privKey and privKey.prettyPrint(), + securityName, + securityEngineId.prettyPrint())) + def delV3User(snmpEngine, userName, @@ -208,21 +298,31 @@ def delV3User(snmpEngine, contextEngineId=None): if securityEngineId is None: # backward compatibility securityEngineId = contextEngineId - (snmpEngineID, usmUserEntry, tblIdx1, pysnmpUsmSecretEntry, + (securityEngineId, usmUserEntry, tblIdx1, pysnmpUsmSecretEntry, tblIdx2) = __cookV3UserInfo(snmpEngine, userName, securityEngineId) + snmpEngine.msgAndPduDsp.mibInstrumController.writeVars( ((usmUserEntry.name + (13,) + tblIdx1, 'destroy'),) ) + snmpEngine.msgAndPduDsp.mibInstrumController.writeVars( ((pysnmpUsmSecretEntry.name + (4,) + tblIdx2, 'destroy'),) ) + debug.logger & debug.flagSM and debug.logger( + 'delV3User: deleted table entries by index ' + 'userName "%s" securityEngineId ' + '"%s"' % ( + userName, + securityEngineId.prettyPrint())) + # Drop all derived rows varBinds = initialVarBinds = ( (usmUserEntry.name + (1,), None), # usmUserEngineID (usmUserEntry.name + (2,), None), # usmUserName (usmUserEntry.name + (4,), None) # usmUserCloneFrom ) + while varBinds: varBinds = snmpEngine.msgAndPduDsp.mibInstrumController.readNextVars( varBinds @@ -389,13 +489,30 @@ def delTransport(snmpEngine, transportDomain): # VACM shortcuts -def addContext(snmpEngine, contextName): +def __cookVacmContextInfo(snmpEngine, contextName): mibBuilder = snmpEngine.msgAndPduDsp.mibInstrumController.mibBuilder - vacmContextEntry, = mibBuilder.importSymbols('SNMP-VIEW-BASED-ACM-MIB', 'vacmContextEntry') tblIdx = vacmContextEntry.getInstIdFromIndices(contextName) + return vacmContextEntry, tblIdx + + +def addContext(snmpEngine, contextName): + vacmContextEntry, tblIdx = __cookVacmContextInfo(snmpEngine, contextName) + + snmpEngine.msgAndPduDsp.mibInstrumController.writeVars( + ((vacmContextEntry.name + (2,) + tblIdx, 'destroy'),) + ) snmpEngine.msgAndPduDsp.mibInstrumController.writeVars( - ((vacmContextEntry.name + (1,) + tblIdx, contextName),) + ((vacmContextEntry.name + (1,) + tblIdx, contextName), + (vacmContextEntry.name + (2,) + tblIdx, 'createAndGo')) + ) + + +def delContext(snmpEngine, contextName): + vacmContextEntry, tblIdx = __cookVacmContextInfo(snmpEngine, contextName) + + snmpEngine.msgAndPduDsp.mibInstrumController.writeVars( + ((vacmContextEntry.name + (2,) + tblIdx, 'destroy'),) ) @@ -442,22 +559,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) # this is leaky +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), @@ -465,11 +580,11 @@ 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) + vacmAccessEntry, tblIdx = __cookVacmAccessInfo( + snmpEngine, groupName, contextPrefix, securityModel, securityLevel) + snmpEngine.msgAndPduDsp.mibInstrumController.writeVars( ((vacmAccessEntry.name + (9,) + tblIdx, 'destroy'),) ) @@ -485,16 +600,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 rfc1902.OctetString('.').asOctets() in rfc1902.OctetString(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')) ) @@ -526,24 +655,27 @@ 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, - readSubTree=(), writeSubTree=(), notifySubTree=()): + readSubTree=(), writeSubTree=(), notifySubTree=(), + contextName=null): (groupName, securityLevel, readView, writeView, notifyView) = __cookVacmUserInfo(snmpEngine, securityModel, securityName, securityLevel) + delContext(snmpEngine, contextName) delVacmGroup(snmpEngine, securityModel, securityName) - delVacmAccess(snmpEngine, groupName, null, securityModel, securityLevel) + delVacmAccess(snmpEngine, groupName, contextName, securityModel, securityLevel) if readSubTree: delVacmView( snmpEngine, readView, readSubTree @@ -560,36 +692,40 @@ def delVacmUser(snmpEngine, securityModel, securityName, securityLevel, # Obsolete shortcuts for add/delVacmUser() wrappers -def addRoUser(snmpEngine, securityModel, securityName, securityLevel, subTree): - addVacmUser(snmpEngine, securityModel, securityName, - securityLevel, subTree) +def addRoUser(snmpEngine, securityModel, securityName, securityLevel, + subTree, contextName=null): + addVacmUser(snmpEngine, securityModel, securityName, securityLevel, + subTree, contextName=contextName) -def delRoUser(snmpEngine, securityModel, securityName, securityLevel, subTree): +def delRoUser(snmpEngine, securityModel, securityName, securityLevel, + subTree, contextName=null): delVacmUser(snmpEngine, securityModel, securityName, securityLevel, - subTree) + subTree, contextName=contextName) -def addRwUser(snmpEngine, securityModel, securityName, securityLevel, subTree): +def addRwUser(snmpEngine, securityModel, securityName, securityLevel, + subTree, contextName=null): addVacmUser(snmpEngine, securityModel, securityName, securityLevel, - subTree, subTree) + subTree, subTree, contextName=contextName) -def delRwUser(snmpEngine, securityModel, securityName, securityLevel, subTree): +def delRwUser(snmpEngine, securityModel, securityName, securityLevel, + subTree, contextName=null): delVacmUser(snmpEngine, securityModel, securityName, securityLevel, - subTree, subTree) + subTree, subTree, contextName=contextName) def addTrapUser(snmpEngine, securityModel, securityName, - securityLevel, subTree): + securityLevel, subTree, contextName=null): addVacmUser(snmpEngine, securityModel, securityName, securityLevel, - (), (), subTree) + (), (), subTree, contextName=contextName) def delTrapUser(snmpEngine, securityModel, securityName, - securityLevel, subTree): + securityLevel, subTree, contextName=null): delVacmUser(snmpEngine, securityModel, securityName, securityLevel, - (), (), subTree) + (), (), subTree, contextName=contextName) # Notification target setup diff --git a/pysnmp/entity/engine.py b/pysnmp/entity/engine.py index 0550871b7..a299267cd 100644 --- a/pysnmp/entity/engine.py +++ b/pysnmp/entity/engine.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # import os diff --git a/pysnmp/entity/observer.py b/pysnmp/entity/observer.py index a52394711..3f7bd8457 100644 --- a/pysnmp/entity/observer.py +++ b/pysnmp/entity/observer.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # from pysnmp import error diff --git a/pysnmp/entity/rfc3413/cmdgen.py b/pysnmp/entity/rfc3413/cmdgen.py index be7eee0d1..79eba57f0 100644 --- a/pysnmp/entity/rfc3413/cmdgen.py +++ b/pysnmp/entity/rfc3413/cmdgen.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # import sys diff --git a/pysnmp/entity/rfc3413/cmdrsp.py b/pysnmp/entity/rfc3413/cmdrsp.py index b260918b9..5cef0a14c 100644 --- a/pysnmp/entity/rfc3413/cmdrsp.py +++ b/pysnmp/entity/rfc3413/cmdrsp.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # import sys @@ -138,6 +138,7 @@ def processPdu(self, snmpEngine, messageProcessingModel, securityModel, self.handleMgmtOperation(snmpEngine, stateReference, contextName, PDU, (self.__verifyAccess, snmpEngine)) + # SNMPv2 SMI exceptions except pysnmp.smi.error.GenError: errorIndication = sys.exc_info()[1] @@ -148,38 +149,72 @@ def processPdu(self, snmpEngine, messageProcessingModel, securityModel, statusInformation['oid'] = errorIndication['oid'] statusInformation['val'] = errorIndication['val'] - # PDU-level SMI errors + # Handle PDU-level SMI errors + + except pysnmp.smi.error.TooBigError: + errorStatus, errorIndex = 'tooBig', 0 + # rfc1905: 4.2.1.3 + varBinds = [] + + # this should never bubble up, SNMP exception objects should be passed as values + except pysnmp.smi.error.NoSuchNameError: + errorStatus, errorIndex = 'noSuchName', sys.exc_info()[1]['idx'] + 1 + + except pysnmp.smi.error.BadValueError: + errorStatus, errorIndex = 'badValue', sys.exc_info()[1]['idx'] + 1 + + except pysnmp.smi.error.ReadOnlyError: + errorStatus, errorIndex = 'readOnly', sys.exc_info()[1]['idx'] + 1 + + except pysnmp.smi.error.GenError: + errorStatus, errorIndex = 'genErr', sys.exc_info()[1]['idx'] + 1 + except pysnmp.smi.error.NoAccessError: errorStatus, errorIndex = 'noAccess', sys.exc_info()[1]['idx'] + 1 + except pysnmp.smi.error.WrongTypeError: errorStatus, errorIndex = 'wrongType', sys.exc_info()[1]['idx'] + 1 + except pysnmp.smi.error.WrongLengthError: errorStatus, errorIndex = 'wrongLength', sys.exc_info()[1]['idx'] + 1 + except pysnmp.smi.error.WrongEncodingError: errorStatus, errorIndex = 'wrongEncoding', sys.exc_info()[1]['idx'] + 1 + except pysnmp.smi.error.WrongValueError: errorStatus, errorIndex = 'wrongValue', sys.exc_info()[1]['idx'] + 1 + except pysnmp.smi.error.NoCreationError: errorStatus, errorIndex = 'noCreation', sys.exc_info()[1]['idx'] + 1 + except pysnmp.smi.error.InconsistentValueError: errorStatus, errorIndex = 'inconsistentValue', sys.exc_info()[1]['idx'] + 1 + except pysnmp.smi.error.ResourceUnavailableError: errorStatus, errorIndex = 'resourceUnavailable', sys.exc_info()[1]['idx'] + 1 + except pysnmp.smi.error.CommitFailedError: errorStatus, errorIndex = 'commitFailed', sys.exc_info()[1]['idx'] + 1 + except pysnmp.smi.error.UndoFailedError: errorStatus, errorIndex = 'undoFailed', sys.exc_info()[1]['idx'] + 1 + except pysnmp.smi.error.AuthorizationError: errorStatus, errorIndex = 'authorizationError', sys.exc_info()[1]['idx'] + 1 + except pysnmp.smi.error.NotWritableError: errorStatus, errorIndex = 'notWritable', sys.exc_info()[1]['idx'] + 1 + except pysnmp.smi.error.InconsistentNameError: errorStatus, errorIndex = 'inconsistentName', sys.exc_info()[1]['idx'] + 1 + except pysnmp.smi.error.SmiError: - errorStatus, errorIndex = 'genErr', len(varBinds) and 1 or 0 + errorStatus, errorIndex = 'genErr', len(varBinds) and 1 + except pysnmp.error.PySnmpError: self.releaseStateInformation(stateReference) return + else: # successful request processor must release state info return @@ -292,7 +327,7 @@ def handleMgmtOperation(self, snmpEngine, stateReference, R = max(len(reqVarBinds) - N, 0) if R: - M = min(M, self.maxVarBinds / R) + M = min(M, self.maxVarBinds // R) debug.logger & debug.flagApp and debug.logger('handleMgmtOperation: N %d, M %d, R %d' % (N, M, R)) diff --git a/pysnmp/entity/rfc3413/config.py b/pysnmp/entity/rfc3413/config.py index 5562784cd..144aa4862 100644 --- a/pysnmp/entity/rfc3413/config.py +++ b/pysnmp/entity/rfc3413/config.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # from pysnmp.smi.error import SmiError, NoSuchInstanceError diff --git a/pysnmp/entity/rfc3413/context.py b/pysnmp/entity/rfc3413/context.py index ec705a728..3975e6705 100644 --- a/pysnmp/entity/rfc3413/context.py +++ b/pysnmp/entity/rfc3413/context.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # from pyasn1.type import univ diff --git a/pysnmp/entity/rfc3413/mibvar.py b/pysnmp/entity/rfc3413/mibvar.py index 02051b5c1..bfc63a06b 100644 --- a/pysnmp/entity/rfc3413/mibvar.py +++ b/pysnmp/entity/rfc3413/mibvar.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # # THESE FUNCTIONS ARE OBSOLETE AND MUST NOT BE USED! diff --git a/pysnmp/entity/rfc3413/ntforg.py b/pysnmp/entity/rfc3413/ntforg.py index abf1a47f0..0065ffede 100644 --- a/pysnmp/entity/rfc3413/ntforg.py +++ b/pysnmp/entity/rfc3413/ntforg.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # import sys diff --git a/pysnmp/entity/rfc3413/ntfrcv.py b/pysnmp/entity/rfc3413/ntfrcv.py index 841ca793d..6066f0865 100644 --- a/pysnmp/entity/rfc3413/ntfrcv.py +++ b/pysnmp/entity/rfc3413/ntfrcv.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # import sys diff --git a/pysnmp/entity/rfc3413/oneliner/cmdgen.py b/pysnmp/entity/rfc3413/oneliner/cmdgen.py index ecbca4511..b74b7f135 100644 --- a/pysnmp/entity/rfc3413/oneliner/cmdgen.py +++ b/pysnmp/entity/rfc3413/oneliner/cmdgen.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # # All code in this file belongs to obsolete, compatibility wrappers. diff --git a/pysnmp/entity/rfc3413/oneliner/ntforg.py b/pysnmp/entity/rfc3413/oneliner/ntforg.py index 8f24d84a0..0f0d2d2e7 100644 --- a/pysnmp/entity/rfc3413/oneliner/ntforg.py +++ b/pysnmp/entity/rfc3413/oneliner/ntforg.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # # All code in this file belongs to obsolete, compatibility wrappers. diff --git a/pysnmp/error.py b/pysnmp/error.py index 3f546c79b..f80046387 100644 --- a/pysnmp/error.py +++ b/pysnmp/error.py @@ -1,10 +1,23 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # +import sys + class PySnmpError(Exception): - pass + def __init__(self, *args): + msg = args and str(args[0]) or '' + + self.cause = sys.exc_info() + + if self.cause[0]: + msg += 'caused by %s: %s' % (self.cause[0], self.cause[1]) + + if msg: + args = (msg,) + args[1:] + + Exception.__init__(self, *args) diff --git a/pysnmp/hlapi/__init__.py b/pysnmp/hlapi/__init__.py index f60b0ed48..8ae16aefb 100644 --- a/pysnmp/hlapi/__init__.py +++ b/pysnmp/hlapi/__init__.py @@ -1,15 +1,74 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # from pysnmp.proto.rfc1902 import * from pysnmp.proto.rfc1905 import NoSuchInstance, NoSuchObject, EndOfMibView from pysnmp.smi.rfc1902 import * -from pysnmp.hlapi.auth import * +from pysnmp.hlapi import auth from pysnmp.hlapi.context import * from pysnmp.entity.engine import * # default is synchronous asyncore-based API from pysnmp.hlapi.asyncore.sync import * + + +CommunityData = auth.CommunityData +UsmUserData = auth.UsmUserData + +usmNoAuthProtocol = auth.usmNoAuthProtocol +"""No Authentication Protocol""" + +usmHMACMD5AuthProtocol = auth.usmHMACMD5AuthProtocol +"""The HMAC-MD5-96 Digest Authentication Protocol (:RFC:`3414#section-6`)""" + +usmHMACSHAAuthProtocol = auth.usmHMACSHAAuthProtocol +"""The HMAC-SHA-96 Digest Authentication Protocol AKA SHA-1 (:RFC:`3414#section-7`)""" + +usmHMAC128SHA224AuthProtocol = auth.usmHMAC128SHA224AuthProtocol +"""The HMAC-SHA-2 Digest Authentication Protocols (:RFC:`7860`)""" + +usmHMAC192SHA256AuthProtocol = auth.usmHMAC192SHA256AuthProtocol +"""The HMAC-SHA-2 Digest Authentication Protocols (:RFC:`7860`)""" + +usmHMAC256SHA384AuthProtocol = auth.usmHMAC256SHA384AuthProtocol +"""The HMAC-SHA-2 Digest Authentication Protocols (:RFC:`7860`)""" + +usmHMAC384SHA512AuthProtocol = auth.usmHMAC384SHA512AuthProtocol +"""The HMAC-SHA-2 Digest Authentication Protocols (:RFC:`7860`)""" + +usmNoPrivProtocol = auth.usmNoPrivProtocol +"""No Privacy Protocol""" + +usmDESPrivProtocol = auth.usmDESPrivProtocol +"""The CBC-DES Symmetric Encryption Protocol (:RFC:`3414#section-8`)""" + +usm3DESEDEPrivProtocol = auth.usm3DESEDEPrivProtocol +"""The 3DES-EDE Symmetric Encryption Protocol (`draft-reeder-snmpv3-usm-3desede-00 `_)""" + +usmAesCfb128Protocol = auth.usmAesCfb128Protocol +"""The CFB128-AES-128 Symmetric Encryption Protocol (:RFC:`3826#section-3`)""" + +usmAesCfb192Protocol = auth.usmAesCfb192Protocol +"""The CFB128-AES-192 Symmetric Encryption Protocol (`draft-blumenthal-aes-usm-04 `_) with Reeder key localization""" + +usmAesCfb256Protocol = auth.usmAesCfb256Protocol +"""The CFB128-AES-256 Symmetric Encryption Protocol (`draft-blumenthal-aes-usm-04 `_) with Reeder key localization""" + +usmAesBlumenthalCfb192Protocol = auth.usmAesBlumenthalCfb192Protocol +"""The CFB128-AES-192 Symmetric Encryption Protocol (`draft-blumenthal-aes-usm-04 `_)""" + +usmAesBlumenthalCfb256Protocol = auth.usmAesBlumenthalCfb256Protocol +"""The CFB128-AES-256 Symmetric Encryption Protocol (`draft-blumenthal-aes-usm-04 `_)""" + +usmKeyTypePassphrase = auth.usmKeyTypePassphrase +"""USM key material type - plain-text pass phrase (:RFC:`3414#section-2.6`)""" + +usmKeyTypeMaster = auth.usmKeyTypeMaster +"""USM key material type - hashed pass-phrase AKA master key (:RFC:`3414#section-2.6`)""" + +usmKeyTypeLocalized = auth.usmKeyTypeLocalized +"""USM key material type - hashed pass-phrase hashed with Context SNMP Engine ID (:RFC:`3414#section-2.6`)""" + diff --git a/pysnmp/hlapi/asyncio/__init__.py b/pysnmp/hlapi/asyncio/__init__.py index 24e71f7c1..b136b0846 100644 --- a/pysnmp/hlapi/asyncio/__init__.py +++ b/pysnmp/hlapi/asyncio/__init__.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # from pysnmp.proto.rfc1902 import * diff --git a/pysnmp/hlapi/asyncio/cmdgen.py b/pysnmp/hlapi/asyncio/cmdgen.py index 0e9e88a5a..1bd5cdf47 100644 --- a/pysnmp/hlapi/asyncio/cmdgen.py +++ b/pysnmp/hlapi/asyncio/cmdgen.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # # Copyright (C) 2014, Zebra Technologies @@ -31,6 +31,8 @@ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF # THE POSSIBILITY OF SUCH DAMAGE. # +import sys + from pysnmp.smi.rfc1902 import * from pysnmp.hlapi.auth import * from pysnmp.hlapi.context import * @@ -41,6 +43,7 @@ try: import asyncio + except ImportError: import trollius as asyncio @@ -57,7 +60,7 @@ def getCmd(snmpEngine, authData, transportTarget, contextData, *varBinds, **options): """Creates a generator to perform SNMP GET query. - When itereator gets advanced by :py:mod:`asyncio` main loop, + When iterator gets advanced by :py:mod:`asyncio` main loop, SNMP GET request is send (:RFC:`1905#section-4.2.1`). The iterator yields :py:class:`asyncio.Future` which gets done whenever response arrives or error occurs. @@ -137,14 +140,16 @@ def __cbFun(snmpEngine, sendRequestHandle, try: varBindsUnmade = vbProcessor.unmakeVarBinds(snmpEngine, varBinds, lookupMib) - except Exception as e: - future.set_exception(e) + except Exception: + ex = sys.exc_info()[1] + future.set_exception(ex) else: future.set_result( (errorIndication, errorStatus, errorIndex, varBindsUnmade) ) - addrName, paramsName = lcd.configure(snmpEngine, authData, transportTarget) + addrName, paramsName = lcd.configure( + snmpEngine, authData, transportTarget, contextData.contextName) future = asyncio.Future() @@ -162,7 +167,7 @@ def setCmd(snmpEngine, authData, transportTarget, contextData, *varBinds, **options): """Creates a generator to perform SNMP SET query. - When itereator gets advanced by :py:mod:`asyncio` main loop, + When iterator gets advanced by :py:mod:`asyncio` main loop, SNMP SET request is send (:RFC:`1905#section-4.2.5`). The iterator yields :py:class:`asyncio.Future` which gets done whenever response arrives or error occurs. @@ -242,14 +247,16 @@ def __cbFun(snmpEngine, sendRequestHandle, try: varBindsUnmade = vbProcessor.unmakeVarBinds(snmpEngine, varBinds, lookupMib) - except Exception as e: - future.set_exception(e) + except Exception: + ex = sys.exc_info()[1] + future.set_exception(ex) else: future.set_result( (errorIndication, errorStatus, errorIndex, varBindsUnmade) ) - addrName, paramsName = lcd.configure(snmpEngine, authData, transportTarget) + addrName, paramsName = lcd.configure( + snmpEngine, authData, transportTarget, contextData.contextName) future = asyncio.Future() @@ -267,7 +274,7 @@ def nextCmd(snmpEngine, authData, transportTarget, contextData, *varBinds, **options): """Creates a generator to perform SNMP GETNEXT query. - When itereator gets advanced by :py:mod:`asyncio` main loop, + When iterator gets advanced by :py:mod:`asyncio` main loop, SNMP GETNEXT request is send (:RFC:`1905#section-4.2.2`). The iterator yields :py:class:`asyncio.Future` which gets done whenever response arrives or error occurs. @@ -353,14 +360,16 @@ def __cbFun(snmpEngine, sendRequestHandle, varBindTableRow, lookupMib) for varBindTableRow in varBindTable] - except Exception as e: - future.set_exception(e) + except Exception: + ex = sys.exc_info()[1] + future.set_exception(ex) else: future.set_result( (errorIndication, errorStatus, errorIndex, varBindsUnmade) ) - addrName, paramsName = lcd.configure(snmpEngine, authData, transportTarget) + addrName, paramsName = lcd.configure( + snmpEngine, authData, transportTarget, contextData.contextName) future = asyncio.Future() @@ -378,7 +387,7 @@ def bulkCmd(snmpEngine, authData, transportTarget, contextData, nonRepeaters, maxRepetitions, *varBinds, **options): """Creates a generator to perform SNMP GETBULK query. - When itereator gets advanced by :py:mod:`asyncio` main loop, + When iterator gets advanced by :py:mod:`asyncio` main loop, SNMP GETBULK request is send (:RFC:`1905#section-4.2.3`). The iterator yields :py:class:`asyncio.Future` which gets done whenever response arrives or error occurs. @@ -493,14 +502,16 @@ def __cbFun(snmpEngine, sendRequestHandle, varBindTableRow, lookupMib) for varBindTableRow in varBindTable] - except Exception as e: - future.set_exception(e) + except Exception: + ex = sys.exc_info()[1] + future.set_exception(ex) else: future.set_result( (errorIndication, errorStatus, errorIndex, varBindsUnmade) ) - addrName, paramsName = lcd.configure(snmpEngine, authData, transportTarget) + addrName, paramsName = lcd.configure( + snmpEngine, authData, transportTarget, contextData.contextName) future = asyncio.Future() diff --git a/pysnmp/hlapi/asyncio/ntforg.py b/pysnmp/hlapi/asyncio/ntforg.py index db7b89ad8..4481ef6f9 100644 --- a/pysnmp/hlapi/asyncio/ntforg.py +++ b/pysnmp/hlapi/asyncio/ntforg.py @@ -1,13 +1,15 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # # Copyright (C) 2014, Zebra Technologies # Authors: Matt Hooks # Zachary Lorusso # +import sys + from pysnmp.smi.rfc1902 import * from pysnmp.hlapi.auth import * from pysnmp.hlapi.context import * @@ -18,6 +20,7 @@ try: import asyncio + except ImportError: import trollius as asyncio @@ -32,7 +35,7 @@ def sendNotification(snmpEngine, authData, transportTarget, contextData, notifyType, varBinds, **options): """Creates a generator to send SNMP notification. - When itereator gets advanced by :py:mod:`asyncio` main loop, + When iterator gets advanced by :py:mod:`asyncio` main loop, SNMP TRAP or INFORM notification is send (:RFC:`1905#section-4.2.6`). The iterator yields :py:class:`asyncio.Future` which gets done whenever response arrives or error occurs. @@ -127,16 +130,17 @@ def __cbFun(snmpEngine, sendRequestHandle, try: varBindsUnmade = vbProcessor.unmakeVarBinds(snmpEngine, varBinds, lookupMib) - except Exception as e: - future.set_exception(e) + except Exception: + ex = sys.exc_info()[1] + future.set_exception(ex) else: future.set_result( (errorIndication, errorStatus, errorIndex, varBindsUnmade) ) notifyName = lcd.configure( - snmpEngine, authData, transportTarget, notifyType - ) + snmpEngine, authData, transportTarget, notifyType, + contextData.contextName) future = asyncio.Future() diff --git a/pysnmp/hlapi/asyncio/transport.py b/pysnmp/hlapi/asyncio/transport.py index 4e7b91106..0f78f82fd 100644 --- a/pysnmp/hlapi/asyncio/transport.py +++ b/pysnmp/hlapi/asyncio/transport.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # import socket diff --git a/pysnmp/hlapi/asyncore/__init__.py b/pysnmp/hlapi/asyncore/__init__.py index 303b4b842..f9bb3dd15 100644 --- a/pysnmp/hlapi/asyncore/__init__.py +++ b/pysnmp/hlapi/asyncore/__init__.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # from pysnmp.proto.rfc1902 import * diff --git a/pysnmp/hlapi/asyncore/cmdgen.py b/pysnmp/hlapi/asyncore/cmdgen.py index 4cedfa82e..284edd68c 100644 --- a/pysnmp/hlapi/asyncore/cmdgen.py +++ b/pysnmp/hlapi/asyncore/cmdgen.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # from pysnmp.entity.rfc3413 import cmdgen @@ -121,7 +121,8 @@ def __cbFun(snmpEngine, sendRequestHandle, snmpEngine, varBinds, lookupMib ), cbCtx) - addrName, paramsName = lcd.configure(snmpEngine, authData, transportTarget) + addrName, paramsName = lcd.configure( + snmpEngine, authData, transportTarget, contextData.contextName) return cmdgen.GetCommandGenerator().sendVarBinds( snmpEngine, addrName, contextData.contextEngineId, @@ -232,7 +233,8 @@ def __cbFun(snmpEngine, sendRequestHandle, snmpEngine, varBinds, lookupMib ), cbCtx) - addrName, paramsName = lcd.configure(snmpEngine, authData, transportTarget) + addrName, paramsName = lcd.configure( + snmpEngine, authData, transportTarget, contextData.contextName) return cmdgen.SetCommandGenerator().sendVarBinds( snmpEngine, addrName, contextData.contextEngineId, @@ -343,7 +345,9 @@ def __cbFun(snmpEngine, sendRequestHandle, errorIndication, varBindTable], cbCtx) - addrName, paramsName = lcd.configure(snmpEngine, authData, transportTarget) + addrName, paramsName = lcd.configure( + snmpEngine, authData, transportTarget, contextData.contextName) + return cmdgen.NextCommandGenerator().sendVarBinds( snmpEngine, addrName, contextData.contextEngineId, contextData.contextName, @@ -483,7 +487,9 @@ def __cbFun(snmpEngine, sendRequestHandle, [vbProcessor.unmakeVarBinds(snmpEngine, varBindTableRow, lookupMib) for varBindTableRow in varBindTable], cbCtx) - addrName, paramsName = lcd.configure(snmpEngine, authData, transportTarget) + addrName, paramsName = lcd.configure( + snmpEngine, authData, transportTarget, contextData.contextName) + return cmdgen.BulkCommandGenerator().sendVarBinds( snmpEngine, addrName, contextData.contextEngineId, contextData.contextName, nonRepeaters, maxRepetitions, diff --git a/pysnmp/hlapi/asyncore/ntforg.py b/pysnmp/hlapi/asyncore/ntforg.py index ecd418c1f..a27e6a883 100644 --- a/pysnmp/hlapi/asyncore/ntforg.py +++ b/pysnmp/hlapi/asyncore/ntforg.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # from pysnmp.smi.rfc1902 import * @@ -131,7 +131,7 @@ def __cbFun(snmpEngine, sendRequestHandle, errorIndication, ) notifyName = lcd.configure(snmpEngine, authData, transportTarget, - notifyType) + notifyType, contextData.contextName) return ntforg.NotificationOriginator().sendVarBinds( snmpEngine, notifyName, diff --git a/pysnmp/hlapi/asyncore/sync/__init__.py b/pysnmp/hlapi/asyncore/sync/__init__.py index f412713ac..14f6b926e 100644 --- a/pysnmp/hlapi/asyncore/sync/__init__.py +++ b/pysnmp/hlapi/asyncore/sync/__init__.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # from pysnmp.proto.rfc1902 import * diff --git a/pysnmp/hlapi/asyncore/sync/cmdgen.py b/pysnmp/hlapi/asyncore/sync/cmdgen.py index fb0723f4e..e60706130 100644 --- a/pysnmp/hlapi/asyncore/sync/cmdgen.py +++ b/pysnmp/hlapi/asyncore/sync/cmdgen.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # from sys import version_info @@ -351,6 +351,8 @@ def cbFun(snmpEngine, sendRequestHandle, totalRows = totalCalls = 0 while True: + previousVarBinds = varBinds + if varBinds: cmdgen.nextCmd(snmpEngine, authData, transportTarget, contextData, *[(x[0], Null('')) for x in varBinds], @@ -378,13 +380,22 @@ def cbFun(snmpEngine, sendRequestHandle, yield (errorIndication, errorStatus, errorIndex, varBinds) return else: + stopFlag = True + varBinds = cbCtx['varBindTable'] and cbCtx['varBindTable'][0] - for idx, varBind in enumerate(varBinds): + + for col, varBind in enumerate(varBinds): name, val = varBind - if not isinstance(val, Null): - if lexicographicMode or initialVars[idx].isPrefixOf(name): - break - else: + if isinstance(val, Null): + varBinds[col] = previousVarBinds[col][0], endOfMibView + + if not lexicographicMode and not initialVars[col].isPrefixOf(name): + varBinds[col] = previousVarBinds[col][0], endOfMibView + + if stopFlag and varBinds[col][1] is not endOfMibView: + stopFlag = False + + if stopFlag: return totalRows += 1 @@ -545,6 +556,8 @@ def cbFun(snmpEngine, sendRequestHandle, if maxRows and totalRows < maxRows: maxRepetitions = min(maxRepetitions, maxRows - totalRows) + previousVarBinds = varBinds + cmdgen.bulkCmd(snmpEngine, authData, transportTarget, contextData, nonRepeaters, maxRepetitions, *[(x[0], Null('')) for x in varBinds], @@ -583,14 +596,17 @@ def cbFun(snmpEngine, sendRequestHandle, break for col in range(len(varBindTable[row])): name, val = varBindTable[row][col] + if row: + previousVarBinds = varBindTable[row - 1] if nullVarBinds[col]: - varBindTable[row][col] = name, endOfMibView + varBindTable[row][col] = previousVarBinds[col][0], endOfMibView continue stopFlag = False if isinstance(val, Null): + varBindTable[row][col] = previousVarBinds[col][0], endOfMibView nullVarBinds[col] = True - elif not lexicographicMode and not initialVars[col].isPrefixOf(name): - varBindTable[row][col] = name, endOfMibView + if not lexicographicMode and not initialVars[col].isPrefixOf(name): + varBindTable[row][col] = previousVarBinds[col][0], endOfMibView nullVarBinds[col] = True if stopFlag: varBindTable = row and varBindTable[:row - 1] or [] @@ -607,9 +623,13 @@ def cbFun(snmpEngine, sendRequestHandle, if maxCalls and totalCalls >= maxCalls: stopFlag = True - for varBinds in varBindTable: - initialVarBinds = (yield errorIndication, errorStatus, errorIndex, varBinds) + varBinds = varBindTable and varBindTable[-1] or [] + + for varBindRow in varBindTable: + initialVarBinds = (yield errorIndication, errorStatus, errorIndex, varBindRow) if initialVarBinds: varBinds = initialVarBinds initialVars = [x[0] for x in vbProcessor.makeVarBinds(snmpEngine, varBinds)] + nullVarBinds = [False] * len(initialVars) + diff --git a/pysnmp/hlapi/asyncore/sync/compat/cmdgen.py b/pysnmp/hlapi/asyncore/sync/compat/cmdgen.py index 066d1d9ff..8624d8572 100644 --- a/pysnmp/hlapi/asyncore/sync/compat/cmdgen.py +++ b/pysnmp/hlapi/asyncore/sync/compat/cmdgen.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # # This is a Python 2.6- version of the same file at level up diff --git a/pysnmp/hlapi/asyncore/sync/compat/ntforg.py b/pysnmp/hlapi/asyncore/sync/compat/ntforg.py index 80221713c..a655b62f5 100644 --- a/pysnmp/hlapi/asyncore/sync/compat/ntforg.py +++ b/pysnmp/hlapi/asyncore/sync/compat/ntforg.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # # This is a Python 2.6- version of the same file at level up diff --git a/pysnmp/hlapi/asyncore/sync/ntforg.py b/pysnmp/hlapi/asyncore/sync/ntforg.py index 6f732d756..d8029564b 100644 --- a/pysnmp/hlapi/asyncore/sync/ntforg.py +++ b/pysnmp/hlapi/asyncore/sync/ntforg.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # from sys import version_info diff --git a/pysnmp/hlapi/asyncore/transport.py b/pysnmp/hlapi/asyncore/transport.py index a9b3a5736..a24f88f15 100644 --- a/pysnmp/hlapi/asyncore/transport.py +++ b/pysnmp/hlapi/asyncore/transport.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # import socket diff --git a/pysnmp/hlapi/auth.py b/pysnmp/hlapi/auth.py index e4650f4b8..f66eb49b6 100644 --- a/pysnmp/hlapi/auth.py +++ b/pysnmp/hlapi/auth.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # from pysnmp.entity import config @@ -34,21 +34,30 @@ class instance. Parameters ---------- - communityIndex: py:class:`str` + communityIndex: :py:class:`str`, :py:class:`~pysnmp.proto.rfc1902.OctetString` Unique index value of a row in snmpCommunityTable. If it is the only positional parameter, it is treated as a *communityName*. - communityName: py:class:`str` + + communityName: :py:class:`str`, :py:class:`~pysnmp.proto.rfc1902.OctetString` SNMP v1/v2c community string. - mpModel: py:class:`int` - SNMP version - 0 for SNMPv1 and 1 for SNMPv2c. - contextEngineId: py:class:`str` + + mpModel: :py:class:`int` + SNMP message processing model AKA SNMP version. Known SNMP versions are: + + * `0` - for SNMP v1 + * `1` - for SNMP v2c (default) + + + contextEngineId: :py:class:`str`, :py:class:`~pysnmp.proto.rfc1902.OctetString` Indicates the location of the context in which management information is accessed when using the community string specified by the above communityName. - contextName: py:class:`str` + + contextName: :py:class:`str`, :py:class:`~pysnmp.proto.rfc1902.OctetString` The context in which management information is accessed when using the above communityName. - tag: py:class:`str` + + tag: :py:class:`str` Arbitrary string that specifies a set of transport endpoints from which a command responder application will accept management requests with given *communityName* or to which @@ -151,35 +160,59 @@ def clone(self, communityIndex=None, communityName=None, securityName is None and self.securityName or securityName ) - -#: No Authentication Protocol. usmNoAuthProtocol = config.usmNoAuthProtocol -#: The HMAC-MD5-96 Digest Authentication Protocol (:RFC:`3414#section-6`) +"""No Authentication Protocol""" + usmHMACMD5AuthProtocol = config.usmHMACMD5AuthProtocol -#: The HMAC-SHA-96 Digest Authentication Protocol (:RFC:`3414#section-7`) +"""The HMAC-MD5-96 Digest Authentication Protocol (:RFC:`3414#section-6`)""" + usmHMACSHAAuthProtocol = config.usmHMACSHAAuthProtocol -#: The HMAC-SHA-2 Digest Authentication Protocols (:RFC:`7860`) +"""The HMAC-SHA-96 Digest Authentication Protocol AKA SHA-1 (:RFC:`3414#section-7`)""" + usmHMAC128SHA224AuthProtocol = config.usmHMAC128SHA224AuthProtocol +"""The HMAC-SHA-2 Digest Authentication Protocols (:RFC:`7860`)""" + usmHMAC192SHA256AuthProtocol = config.usmHMAC192SHA256AuthProtocol +"""The HMAC-SHA-2 Digest Authentication Protocols (:RFC:`7860`)""" + usmHMAC256SHA384AuthProtocol = config.usmHMAC256SHA384AuthProtocol +"""The HMAC-SHA-2 Digest Authentication Protocols (:RFC:`7860`)""" + usmHMAC384SHA512AuthProtocol = config.usmHMAC384SHA512AuthProtocol +"""The HMAC-SHA-2 Digest Authentication Protocols (:RFC:`7860`)""" -#: No Privacy Protocol. usmNoPrivProtocol = config.usmNoPrivProtocol -#: The CBC-DES Symmetric Encryption Protocol (:RFC:`3414#section-8`) +"""No Privacy Protocol""" + usmDESPrivProtocol = config.usmDESPrivProtocol -#: The 3DES-EDE Symmetric Encryption Protocol (`draft-reeder-snmpv3-usm-3desede-00 `_) +"""The CBC-DES Symmetric Encryption Protocol (:RFC:`3414#section-8`)""" + usm3DESEDEPrivProtocol = config.usm3DESEDEPrivProtocol -#: The CFB128-AES-128 Symmetric Encryption Protocol (:RFC:`3826#section-3`) +"""The 3DES-EDE Symmetric Encryption Protocol (`draft-reeder-snmpv3-usm-3desede-00 `_)""" + usmAesCfb128Protocol = config.usmAesCfb128Protocol -#: The CFB128-AES-192 Symmetric Encryption Protocol (`draft-blumenthal-aes-usm-04 `_) with Reeder key localization +"""The CFB128-AES-128 Symmetric Encryption Protocol (:RFC:`3826#section-3`)""" + usmAesCfb192Protocol = config.usmAesCfb192Protocol -#: The CFB128-AES-256 Symmetric Encryption Protocol (`draft-blumenthal-aes-usm-04 `_) with Reeder key localization +"""The CFB128-AES-192 Symmetric Encryption Protocol (`draft-blumenthal-aes-usm-04 `_) with Reeder key localization""" + usmAesCfb256Protocol = config.usmAesCfb256Protocol -#: The CFB128-AES-192 Symmetric Encryption Protocol (`draft-blumenthal-aes-usm-04 `_) +"""The CFB128-AES-256 Symmetric Encryption Protocol (`draft-blumenthal-aes-usm-04 `_) with Reeder key localization""" + usmAesBlumenthalCfb192Protocol = config.usmAesBlumenthalCfb192Protocol -#: The CFB128-AES-256 Symmetric Encryption Protocol (`draft-blumenthal-aes-usm-04 `_) +"""The CFB128-AES-192 Symmetric Encryption Protocol (`draft-blumenthal-aes-usm-04 `_)""" + usmAesBlumenthalCfb256Protocol = config.usmAesBlumenthalCfb256Protocol +"""The CFB128-AES-256 Symmetric Encryption Protocol (`draft-blumenthal-aes-usm-04 `_)""" + +usmKeyTypePassphrase = config.usmKeyTypePassphrase +"""USM key material type - plain-text pass phrase (:RFC:`3414#section-2.6`)""" + +usmKeyTypeMaster = config.usmKeyTypeMaster +"""USM key material type - hashed pass-phrase AKA master key (:RFC:`3414#section-2.6`)""" + +usmKeyTypeLocalized = config.usmKeyTypeLocalized +"""USM key material type - hashed pass-phrase hashed with Context SNMP Engine ID (:RFC:`3414#section-2.6`)""" class UsmUserData(object): @@ -197,21 +230,26 @@ class instance. Parameters ---------- - userName: py:class:`str` + userName: :py:class:`str`, :py:class:`~pysnmp.proto.rfc1902.OctetString` A human readable string representing the name of the SNMP USM user. - authKey: py:class:`str` + + Other Parameters + ---------------- + authKey: :py:class:`str`, :py:class:`~pysnmp.proto.rfc1902.OctetString` Initial value of the secret authentication key. If not set, :py:class:`~pysnmp.hlapi.usmNoAuthProtocol` is implied. If set and no *authProtocol* is specified, :py:class:`~pysnmp.hlapi.usmHMACMD5AuthProtocol` takes effect. - privKey: py:class:`str` + + privKey: :py:class:`str`, :py:class:`~pysnmp.proto.rfc1902.OctetString` Initial value of the secret encryption key. If not set, :py:class:`~pysnmp.hlapi.usmNoPrivProtocol` is implied. If set and no *privProtocol* is specified, :py:class:`~pysnmp.hlapi.usmDESPrivProtocol` takes effect. - authProtocol: py:class:`tuple` + + authProtocol: :py:class:`tuple`, :py:class:`~pysnmp.proto.rfc1902.ObjectIdentifier` An indication of whether messages sent on behalf of this USM user can be authenticated, and if so, the type of authentication protocol which is used. @@ -225,7 +263,25 @@ class instance. * :py:class:`~pysnmp.hlapi.usmHMAC192SHA256AuthProtocol` * :py:class:`~pysnmp.hlapi.usmHMAC256SHA384AuthProtocol` * :py:class:`~pysnmp.hlapi.usmHMAC384SHA512AuthProtocol` - privProtocol: py:class:`tuple` + + + securityEngineId: :py:class:`~pysnmp.proto.rfc1902.OctetString` + The snmpEngineID of the authoritative SNMP engine to which a + dateRequest message is to be sent. Will be automatically + discovered from peer if not given, unless localized keys + are used. In the latter case *securityEngineId* must be + specified. + + See :RFC:`3414#section-2.5.1` for technical explanation. + + securityName: :py:class:`str`, :py:class:`~pysnmp.proto.rfc1902.OctetString` + Together with the snmpEngineID it identifies a row in the + *SNMP-USER-BASED-SM-MIB::usmUserTable* that is to be used + for securing the message. + + See :RFC:`3414#section-2.5.1` for technical explanation. + + privProtocol: :py:class:`tuple`, :py:class:`~pysnmp.proto.rfc1902.ObjectIdentifier` An indication of whether messages sent on behalf of this USM user be encrypted, and if so, the type of encryption protocol which is used. @@ -238,6 +294,43 @@ class instance. * :py:class:`~pysnmp.hlapi.usmAesCfb192Protocol` * :py:class:`~pysnmp.hlapi.usmAesCfb256Protocol` + + authKeyType: :py:class:`int` + Type of `authKey` material. See :RFC:`3414#section-2.6` for + technical explanation. + + Supported key types are: + + * :py:class:`~pysnmp.hlapi.usmKeyTypePassphrase` (default) + * :py:class:`~pysnmp.hlapi.usmKeyTypeMaster` + * :py:class:`~pysnmp.hlapi.usmKeyTypeLocalized` + + privKeyType: :py:class:`int` + Type of `privKey` material. See :RFC:`3414#section-2.6` for + technical explanation. + + Supported key types are: + + * :py:class:`~pysnmp.hlapi.usmKeyTypePassphrase` (default) + * :py:class:`~pysnmp.hlapi.usmKeyTypeMaster` + * :py:class:`~pysnmp.hlapi.usmKeyTypeLocalized` + + Notes + ----- + If :py:class:`~pysnmp.hlapi.usmKeyTypeLocalized` is used when + running a non-authoritative SNMP engine, USM key localization + mechanism is not invoked. As a consequence, local SNMP engine + configuration won't get automatically populated with remote SNMP + engine's *securityEngineId*. + + Therefore peer SNMP engine's *securityEngineId* must be added + to local configuration and associated with its localized keys. + + Alternatively, the magic *securityEngineId* value of five zeros + (*0x0000000000*) can be used to refer to the localized keys that + should be used with any unknown remote SNMP engine. This feature + is specific to pysnmp. + Examples -------- >>> from pysnmp.hlapi import UsmUserData @@ -260,7 +353,9 @@ def __init__(self, userName, authKey=None, privKey=None, authProtocol=None, privProtocol=None, securityEngineId=None, - securityName=None): + securityName=None, + authKeyType=usmKeyTypePassphrase, + privKeyType=usmKeyTypePassphrase): self.userName = userName if securityName is None: self.securityName = userName @@ -287,24 +382,29 @@ def __init__(self, userName, self.privProtocol = privProtocol self.securityEngineId = securityEngineId + self.authKeyType = authKeyType + self.privKeyType = privKeyType def __hash__(self): raise TypeError('%s is not hashable' % self.__class__.__name__) def __repr__(self): - return '%s(userName=%r, authKey=, privKey=, authProtocol=%r, privProtocol=%r, securityEngineId=%r, securityName=%r)' % ( + return '%s(userName=%r, authKey=, privKey=, authProtocol=%r, privProtocol=%r, securityEngineId=%r, securityName=%r, authKeyType=%r, privKeyType=%r)' % ( self.__class__.__name__, self.userName, self.authProtocol, self.privProtocol, self.securityEngineId is None and '' or self.securityEngineId, - self.securityName + self.securityName, + self.authKeyType, + self.privKeyType ) def clone(self, userName=None, authKey=None, privKey=None, authProtocol=None, privProtocol=None, - securityEngineId=None, securityName=None): + securityEngineId=None, securityName=None, + authKeyType=None, privKeyType=None): return self.__class__( userName is None and self.userName or userName, authKey is None and self.authKey or authKey, @@ -312,5 +412,7 @@ def clone(self, userName=None, authProtocol is None and self.authProtocol or authProtocol, privProtocol is None and self.privProtocol or privProtocol, securityEngineId is None and self.securityEngineId or securityEngineId, - securityName=securityName is None and self.securityName or securityName + securityName is None and self.securityName or securityName, + authKeyType is None and self.authKeyType or usmKeyTypePassphrase, + privKeyType is None and self.privKeyType or usmKeyTypePassphrase ) diff --git a/pysnmp/hlapi/context.py b/pysnmp/hlapi/context.py index 754c43cc3..73e850b22 100644 --- a/pysnmp/hlapi/context.py +++ b/pysnmp/hlapi/context.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # from pyasn1.compat.octets import null diff --git a/pysnmp/hlapi/lcd.py b/pysnmp/hlapi/lcd.py index bcd69d87d..4ffee3fd9 100644 --- a/pysnmp/hlapi/lcd.py +++ b/pysnmp/hlapi/lcd.py @@ -1,13 +1,15 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # from pysnmp.entity import config from pysnmp import nextid, error from pysnmp.hlapi.auth import * +from pyasn1.compat.octets import null + __all__ = ['CommandGeneratorLcdConfigurator', 'NotificationOriginatorLcdConfigurator'] @@ -24,17 +26,17 @@ def _getCache(self, snmpEngine): snmpEngine.setUserContext(**{cacheId: cache}) return cache - def configure(self, snmpEngine, authData, transportTarget, *options): + def configure(self, snmpEngine, *args, **kwargs): pass - def unconfigure(self, snmpEngine, authData=None): + def unconfigure(self, snmpEngine, *args, **kwargs): pass class CommandGeneratorLcdConfigurator(AbstractLcdConfigurator): cacheKeys = ['auth', 'parm', 'tran', 'addr'] - def configure(self, snmpEngine, authData, transportTarget, *options): + def configure(self, snmpEngine, authData, transportTarget, contextName, **options): cache = self._getCache(snmpEngine) if isinstance(authData, CommunityData): if authData.communityIndex not in cache['auth']: @@ -56,8 +58,10 @@ def configure(self, snmpEngine, authData, transportTarget, *options): authData.userName, authData.authProtocol, authData.authKey, authData.privProtocol, authData.privKey, - authData.securityEngineId, - securityName=authData.securityName + securityEngineId=authData.securityEngineId, + securityName=authData.securityName, + authKeyType=authData.authKeyType, + privKeyType=authData.privKeyType ) cache['auth'][authDataKey] = authData else: @@ -94,7 +98,10 @@ def configure(self, snmpEngine, authData, transportTarget, *options): transportKey = (paramsName, transportTarget.transportDomain, transportTarget.transportAddr, - transportTarget.tagList) + transportTarget.timeout, + transportTarget.retries, + transportTarget.tagList, + transportTarget.iface) if transportKey in cache['addr']: addrName, useCount = cache['addr'][transportKey] @@ -114,7 +121,7 @@ def configure(self, snmpEngine, authData, transportTarget, *options): return addrName, paramsName - def unconfigure(self, snmpEngine, authData=None): + def unconfigure(self, snmpEngine, authData=None, contextName=null, **options): cache = self._getCache(snmpEngine) if authData: if isinstance(authData, CommunityData): @@ -175,7 +182,7 @@ def unconfigure(self, snmpEngine, authData=None): cache['addr'][addrKey] = addrName, useCount else: config.delTargetAddr(snmpEngine, addrName) - + del cache['addr'][addrKey] addrNames.add(addrKey) if addrKey[1] in cache['tran']: @@ -195,9 +202,9 @@ class NotificationOriginatorLcdConfigurator(AbstractLcdConfigurator): cacheKeys = ['auth', 'name'] _cmdGenLcdCfg = CommandGeneratorLcdConfigurator() - def configure(self, snmpEngine, authData, transportTarget, *options): + def configure(self, snmpEngine, authData, transportTarget, notifyType, + contextName, **options): cache = self._getCache(snmpEngine) - notifyType = options and options[0] or 'trap' notifyName = None # Create matching transport tags if not given by user. Not good! @@ -208,7 +215,8 @@ def configure(self, snmpEngine, authData, transportTarget, *options): if isinstance(authData, CommunityData) and not authData.tag: authData.tag = transportTarget.tagList.split()[0] - addrName, paramsName = self._cmdGenLcdCfg.configure(snmpEngine, authData, transportTarget) + addrName, paramsName = self._cmdGenLcdCfg.configure( + snmpEngine, authData, transportTarget, contextName, **options) tagList = transportTarget.tagList.split() if not tagList: tagList = [''] @@ -227,7 +235,7 @@ def configure(self, snmpEngine, authData, transportTarget, *options): notifyType ) cache['name'][notifyNameKey] = notifyName, paramsName, 1 - authDataKey = authData.securityName, authData.securityModel + authDataKey = authData.securityName, authData.securityModel, authData.securityLevel, contextName if authDataKey in cache['auth']: authDataX, subTree, useCount = cache['auth'][authDataKey] cache['auth'][authDataKey] = authDataX, subTree, useCount + 1 @@ -235,23 +243,24 @@ def configure(self, snmpEngine, authData, transportTarget, *options): subTree = (1, 3, 6) config.addTrapUser(snmpEngine, authData.securityModel, authData.securityName, authData.securityLevel, - subTree) + subTree, contextName=contextName) cache['auth'][authDataKey] = authData, subTree, 1 return notifyName - def unconfigure(self, snmpEngine, authData=None): + def unconfigure(self, snmpEngine, authData=None, contextName=null, **options): cache = self._getCache(snmpEngine) if authData: - authDataKey = authData.securityName, authData.securityModel + authDataKey = authData.securityName, authData.securityModel, authData.securityLevel, contextName if authDataKey in cache['auth']: authDataKeys = (authDataKey,) else: raise error.PySnmpError('Unknown authData %s' % (authData,)) else: - authDataKeys = tuple(cache['auth'].keys()) + authDataKeys = tuple(cache['auth']) - addrNames, paramsNames = self._cmdGenLcdCfg.unconfigure(snmpEngine, authData) + addrNames, paramsNames = self._cmdGenLcdCfg.unconfigure( + snmpEngine, authData, contextName, **options) notifyAndParamsNames = [(cache['name'][x], x) for x in cache['name'].keys() if x[0] in paramsNames] diff --git a/pysnmp/hlapi/transport.py b/pysnmp/hlapi/transport.py index 44a36504b..351f01f10 100644 --- a/pysnmp/hlapi/transport.py +++ b/pysnmp/hlapi/transport.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # from pyasn1.compat.octets import null diff --git a/pysnmp/hlapi/twisted/cmdgen.py b/pysnmp/hlapi/twisted/cmdgen.py index 944905275..c51d0684d 100644 --- a/pysnmp/hlapi/twisted/cmdgen.py +++ b/pysnmp/hlapi/twisted/cmdgen.py @@ -1,9 +1,11 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # +import sys + from pysnmp.smi.rfc1902 import * from pysnmp.hlapi.auth import * from pysnmp.hlapi.context import * @@ -120,13 +122,15 @@ def __cbFun(snmpEngine, sendRequestHandle, try: varBindsUnmade = vbProcessor.unmakeVarBinds(snmpEngine, varBinds, lookupMib) - except Exception as e: - deferred.errback(Failure(e)) + except Exception: + ex = sys.exc_info()[1] + deferred.errback(Failure(ex)) else: deferred.callback((errorStatus, errorIndex, varBindsUnmade)) - addrName, paramsName = lcd.configure(snmpEngine, authData, transportTarget) + addrName, paramsName = lcd.configure( + snmpEngine, authData, transportTarget, contextData.contextName) deferred = Deferred() @@ -236,13 +240,15 @@ def __cbFun(snmpEngine, sendRequestHandle, try: varBindsUnmade = vbProcessor.unmakeVarBinds(snmpEngine, varBinds, lookupMib) - except Exception as e: - deferred.errback(Failure(e)) + except Exception: + ex = sys.exc_info()[1] + deferred.errback(Failure(ex)) else: deferred.callback((errorStatus, errorIndex, varBindsUnmade)) - addrName, paramsName = lcd.configure(snmpEngine, authData, transportTarget) + addrName, paramsName = lcd.configure( + snmpEngine, authData, transportTarget, contextData.contextName) deferred = Deferred() @@ -366,13 +372,15 @@ def __cbFun(snmpEngine, sendRequestHandle, lookupMib) for varBindTableRow in varBindTable] - except Exception as e: - deferred.errback(Failure(e)) + except Exception: + ex = sys.exc_info()[1] + deferred.errback(Failure(ex)) else: deferred.callback((errorStatus, errorIndex, varBindsUnmade)) - addrName, paramsName = lcd.configure(snmpEngine, authData, transportTarget) + addrName, paramsName = lcd.configure( + snmpEngine, authData, transportTarget, contextData.contextName) deferred = Deferred() @@ -524,13 +532,15 @@ def __cbFun(snmpEngine, sendRequestHandle, lookupMib) for varBindTableRow in varBindTable] - except Exception as e: - deferred.errback(Failure(e)) + except Exception: + ex = sys.exc_info()[1] + deferred.errback(Failure(ex)) else: deferred.callback((errorStatus, errorIndex, varBindsUnmade)) - addrName, paramsName = lcd.configure(snmpEngine, authData, transportTarget) + addrName, paramsName = lcd.configure( + snmpEngine, authData, transportTarget, contextData.contextName) deferred = Deferred() diff --git a/pysnmp/hlapi/twisted/ntforg.py b/pysnmp/hlapi/twisted/ntforg.py index fbbdc0f1b..c8324deff 100644 --- a/pysnmp/hlapi/twisted/ntforg.py +++ b/pysnmp/hlapi/twisted/ntforg.py @@ -1,9 +1,11 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # +import sys + from pysnmp.smi.rfc1902 import * from pysnmp.hlapi.auth import * from pysnmp.hlapi.context import * @@ -128,15 +130,15 @@ def __cbFun(snmpEngine, sendRequestHandle, try: varBindsUnmade = vbProcessor.unmakeVarBinds(snmpEngine, varBinds, lookupMib) - except Exception as e: - deferred.errback(Failure(e)) + except Exception: + ex = sys.exc_info()[1] + deferred.errback(Failure(ex)) else: deferred.callback((errorStatus, errorIndex, varBindsUnmade)) - notifyName = lcd.configure( - snmpEngine, authData, transportTarget, notifyType - ) + notifyName = lcd.configure(snmpEngine, authData, transportTarget, + notifyType, contextData.contextName) def __trapFun(deferred): deferred.callback((0, 0, [])) diff --git a/pysnmp/hlapi/twisted/transport.py b/pysnmp/hlapi/twisted/transport.py index aeea22da8..2312d68d6 100644 --- a/pysnmp/hlapi/twisted/transport.py +++ b/pysnmp/hlapi/twisted/transport.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # import socket, sys diff --git a/pysnmp/hlapi/varbinds.py b/pysnmp/hlapi/varbinds.py index ac9151a88..fdffc1b33 100644 --- a/pysnmp/hlapi/varbinds.py +++ b/pysnmp/hlapi/varbinds.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # from pysnmp.smi import view @@ -36,14 +36,15 @@ def makeVarBinds(self, snmpEngine, varBinds): else: varBind = ObjectType(ObjectIdentity(varBind[0]), varBind[1]) - __varBinds.append(varBind.resolveWithMib(mibViewController)) + __varBinds.append(varBind.resolveWithMib(mibViewController, ignoreErrors=False)) return __varBinds def unmakeVarBinds(self, snmpEngine, varBinds, lookupMib=True): if lookupMib: mibViewController = self.getMibViewController(snmpEngine) - varBinds = [ObjectType(ObjectIdentity(x[0]), x[1]).resolveWithMib(mibViewController) for x in varBinds] + varBinds = [ObjectType(ObjectIdentity(x[0]), x[1]).resolveWithMib( + mibViewController) for x in varBinds] return varBinds @@ -52,7 +53,8 @@ class NotificationOriginatorVarBinds(AbstractVarBinds): def makeVarBinds(self, snmpEngine, varBinds): mibViewController = self.getMibViewController(snmpEngine) if isinstance(varBinds, NotificationType): - varBinds.resolveWithMib(mibViewController) + varBinds.resolveWithMib( + mibViewController, ignoreErrors=False) __varBinds = [] for varBind in varBinds: if isinstance(varBind, ObjectType): @@ -61,11 +63,13 @@ def makeVarBinds(self, snmpEngine, varBinds): varBind = ObjectType(*varBind) else: varBind = ObjectType(ObjectIdentity(varBind[0]), varBind[1]) - __varBinds.append(varBind.resolveWithMib(mibViewController)) + __varBinds.append(varBind.resolveWithMib( + mibViewController, ignoreErrors=False)) return __varBinds def unmakeVarBinds(self, snmpEngine, varBinds, lookupMib=False): if lookupMib: mibViewController = self.getMibViewController(snmpEngine) - varBinds = [ObjectType(ObjectIdentity(x[0]), x[1]).resolveWithMib(mibViewController) for x in varBinds] + varBinds = [ObjectType(ObjectIdentity(x[0]), x[1]).resolveWithMib( + mibViewController) for x in varBinds] return varBinds diff --git a/pysnmp/nextid.py b/pysnmp/nextid.py index 04e269d0d..f81bc9b61 100644 --- a/pysnmp/nextid.py +++ b/pysnmp/nextid.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # import random diff --git a/pysnmp/proto/acmod/rfc3415.py b/pysnmp/proto/acmod/rfc3415.py index 1e14ab34f..a1e9f6f7e 100644 --- a/pysnmp/proto/acmod/rfc3415.py +++ b/pysnmp/proto/acmod/rfc3415.py @@ -1,21 +1,132 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # from pysnmp.smi.error import NoSuchInstanceError from pysnmp.proto import errind, error from pysnmp import debug -__powOfTwoSeq = [128, 64, 32, 16, 8, 4, 2, 1] - # 3.2 class Vacm(object): """View-based Access Control Model""" accessModelID = 3 + _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,101 +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)) + + # Rebuild contextName map if changed + + 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 + + self._contextMap[nextMibNode.syntax] = True + + self._contextBranchId = vacmContextName.branchVersionId # 3.2.1 - vacmContextEntry, = mibInstrumController.mibBuilder.importSymbols('SNMP-VIEW-BASED-ACM-MIB', 'vacmContextEntry') - tblIdx = vacmContextEntry.getInstIdFromIndices(contextName) - try: - vacmContextName = vacmContextEntry.getNode( - vacmContextEntry.name + (1,) + tblIdx - ).syntax - except NoSuchInstanceError: + 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 - except NoSuchInstanceError: + groupName = self._groupNameMap[indices] + + 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 - except NoSuchInstanceError: - raise error.StatusInformation(errorIndication=errind.noAccessEntry) - if not len(viewName): - raise error.StatusInformation(errorIndication=errind.noSuchView) + vacmAccessStatus, = mibInstrumController.mibBuilder.importSymbols( + 'SNMP-VIEW-BASED-ACM-MIB', 'vacmAccessStatus') + + 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') - # XXX split onto object & instance ? + self._viewTreeMap.clear() + + powerOfTwo = [2 ** exp for exp in range(7, -1, -1)] + + 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 1: - 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 = mask + [b & c for b in __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) diff --git a/pysnmp/proto/acmod/void.py b/pysnmp/proto/acmod/void.py index 3d9533d40..ecc4c4b63 100644 --- a/pysnmp/proto/acmod/void.py +++ b/pysnmp/proto/acmod/void.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # from pysnmp.proto import errind, error diff --git a/pysnmp/proto/api/__init__.py b/pysnmp/proto/api/__init__.py index 85b37bb49..d742ecc76 100644 --- a/pysnmp/proto/api/__init__.py +++ b/pysnmp/proto/api/__init__.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # from pysnmp.proto.api import v1, v2c, verdec diff --git a/pysnmp/proto/api/v1.py b/pysnmp/proto/api/v1.py index 709a0b683..37882859f 100644 --- a/pysnmp/proto/api/v1.py +++ b/pysnmp/proto/api/v1.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # from pyasn1.type import univ @@ -66,7 +66,8 @@ def setDefaults(self, pdu): pdu.setComponentByPosition( 2, self._errorIndex, verifyConstraints=False, matchTags=False, matchConstraints=False ) - pdu.setComponentByPosition(3) + varBindList = pdu.setComponentByPosition(3).getComponentByPosition(3) + varBindList.clear() @staticmethod def getRequestID(pdu): @@ -170,7 +171,8 @@ def setDefaults(self, pdu): pdu.setComponentByPosition(2, self._genericTrap, verifyConstraints=False, matchTags=False, matchConstraints=False) pdu.setComponentByPosition(3, self._zeroInt, verifyConstraints=False, matchTags=False, matchConstraints=False) pdu.setComponentByPosition(4, self._zeroTime, verifyConstraints=False, matchTags=False, matchConstraints=False) - pdu.setComponentByPosition(5) + varBindList = pdu.setComponentByPosition(5).getComponentByPosition(5) + varBindList.clear() @staticmethod def getEnterprise(pdu): diff --git a/pysnmp/proto/api/v2c.py b/pysnmp/proto/api/v2c.py index eec78e797..b21ecf366 100644 --- a/pysnmp/proto/api/v2c.py +++ b/pysnmp/proto/api/v2c.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # from pysnmp.proto import rfc1901, rfc1902, rfc1905 @@ -91,7 +91,8 @@ def setDefaults(self, pdu): pdu.setComponentByPosition( 2, self._maxRepetitions, verifyConstraints=False, matchTags=False, matchConstraints=False ) - pdu.setComponentByPosition(3) + varBindList = pdu.setComponentByPosition(3).getComponentByPosition(3) + varBindList.clear() @staticmethod def getNonRepeaters(pdu): diff --git a/pysnmp/proto/api/verdec.py b/pysnmp/proto/api/verdec.py index 635c5a2d6..7076e63c1 100644 --- a/pysnmp/proto/api/verdec.py +++ b/pysnmp/proto/api/verdec.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # from pyasn1.type import univ diff --git a/pysnmp/proto/cache.py b/pysnmp/proto/cache.py index e9218416d..9eae19c90 100644 --- a/pysnmp/proto/cache.py +++ b/pysnmp/proto/cache.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # from pysnmp.proto import error diff --git a/pysnmp/proto/errind.py b/pysnmp/proto/errind.py index d660af54f..5683be8bc 100644 --- a/pysnmp/proto/errind.py +++ b/pysnmp/proto/errind.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # diff --git a/pysnmp/proto/error.py b/pysnmp/proto/error.py index eaa21f6b7..14e6b737c 100644 --- a/pysnmp/proto/error.py +++ b/pysnmp/proto/error.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # from pyasn1.error import PyAsn1Error diff --git a/pysnmp/proto/mpmod/base.py b/pysnmp/proto/mpmod/base.py index 2ed7834c2..620ccf894 100644 --- a/pysnmp/proto/mpmod/base.py +++ b/pysnmp/proto/mpmod/base.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # from pysnmp.proto.mpmod import cache diff --git a/pysnmp/proto/mpmod/cache.py b/pysnmp/proto/mpmod/cache.py index 6e9a886d3..4bc67169e 100644 --- a/pysnmp/proto/mpmod/cache.py +++ b/pysnmp/proto/mpmod/cache.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # from pysnmp.proto import error diff --git a/pysnmp/proto/mpmod/rfc2576.py b/pysnmp/proto/mpmod/rfc2576.py index 93ded6da0..b355868f6 100644 --- a/pysnmp/proto/mpmod/rfc2576.py +++ b/pysnmp/proto/mpmod/rfc2576.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # import sys @@ -271,7 +271,7 @@ def prepareDataElements(self, snmpEngine, transportDomain, try: try: - smHandler = snmpEngine.securityModels[int(securityModel)] + smHandler = snmpEngine.securityModels[securityModel] except KeyError: raise error.StatusInformation( diff --git a/pysnmp/proto/mpmod/rfc3412.py b/pysnmp/proto/mpmod/rfc3412.py index 5f9268a56..7c4bec98e 100644 --- a/pysnmp/proto/mpmod/rfc3412.py +++ b/pysnmp/proto/mpmod/rfc3412.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2016, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # import sys @@ -40,8 +40,11 @@ class HeaderData(univ.Sequence): namedtype.NamedType('msgMaxSize', univ.Integer().subtype(subtypeSpec=constraint.ValueRangeConstraint(484, 2147483647))), namedtype.NamedType('msgFlags', univ.OctetString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, 1))), + # NOTE (etingof): constrain SNMPv3 message to only USM+ security models + # because SNMPv1/v2c seems incompatible in pysnmp implementation, not sure + # if it's intended by the SNMP standard at all... namedtype.NamedType('msgSecurityModel', - univ.Integer().subtype(subtypeSpec=constraint.ValueRangeConstraint(1, 2147483647))) + univ.Integer().subtype(subtypeSpec=constraint.ValueRangeConstraint(3, 2147483647))) ) @@ -200,31 +203,14 @@ def prepareOutgoingMessage(self, snmpEngine, transportDomain, # 7.1.9.a if pdu.tagSet in rfc3411.unconfirmedClassPDUs: securityEngineId = snmpEngineID + else: if peerSnmpEngineData is None: - # Force engineID discovery (rfc3414, 4) - securityEngineId = securityName = self._emptyStr - securityLevel = 1 - # Clear possible auth&priv flags - headerData.setComponentByPosition( - 2, self._msgFlags[msgFlags & 0xfc], verifyConstraints=False, matchTags=False, matchConstraints=False - ) - # XXX - scopedPDU = self.__scopedPDU - scopedPDU.setComponentByPosition( - 0, self._emptyStr, verifyConstraints=False, matchTags=False, matchConstraints=False - ) - scopedPDU.setComponentByPosition(1, contextName) - scopedPDU.setComponentByPosition(2) + debug.logger & debug.flagMP and debug.logger( + 'prepareOutgoingMessage: peer SNMP engine is not known') - # Use dead-empty PDU for engine-discovery report - emptyPdu = pdu.clone() - pMod.apiPDU.setDefaults(emptyPdu) + securityEngineId = None - scopedPDU.getComponentByPosition(2).setComponentByType( - emptyPdu.tagSet, emptyPdu, verifyConstraints=False, matchTags=False, matchConstraints=False - ) - debug.logger & debug.flagMP and debug.logger('prepareOutgoingMessage: force engineID discovery') else: securityEngineId = peerSnmpEngineData['securityEngineId'] diff --git a/pysnmp/proto/proxy/rfc2576.py b/pysnmp/proto/proxy/rfc2576.py index 6ea99b596..7b20ddc9a 100644 --- a/pysnmp/proto/proxy/rfc2576.py +++ b/pysnmp/proto/proxy/rfc2576.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # from pysnmp.proto import rfc1905, rfc3411, error @@ -142,15 +142,17 @@ def v1ToV2(v1Pdu, origV2Pdu=None, snmpTrapCommunity=''): (oid, __v1ToV2ValueMap[v1Val.tagSet].clone(v1Val)) ) - if pduType in rfc3411.responseClassPDUs: - # 4.1.2.2.1&2 + if pduType not in rfc3411.notificationClassPDUs: errorStatus = int(v1.apiPDU.getErrorStatus(v1Pdu)) errorIndex = int(v1.apiPDU.getErrorIndex(v1Pdu, muteErrors=True)) - if errorStatus == 2: # noSuchName - if origV2Pdu.tagSet == v2c.GetNextRequestPDU.tagSet: - v2VarBinds = [(o, rfc1905.endOfMibView) for o, v in v2VarBinds] - else: - v2VarBinds = [(o, rfc1905.noSuchObject) for o, v in v2VarBinds] + + if pduType in rfc3411.responseClassPDUs: + # 4.1.2.2.1&2 + if errorStatus == 2: # noSuchName + if origV2Pdu.tagSet == v2c.GetNextRequestPDU.tagSet: + v2VarBinds = [(o, rfc1905.endOfMibView) for o, v in v2VarBinds] + else: + v2VarBinds = [(o, rfc1905.noSuchObject) for o, v in v2VarBinds] # partial one-to-one mapping - 4.2.1 v2c.apiPDU.setErrorStatus(v2Pdu, errorStatus) @@ -158,13 +160,11 @@ def v1ToV2(v1Pdu, origV2Pdu=None, snmpTrapCommunity=''): # 4.1.2.1 --> no-op - elif pduType in rfc3411.confirmedClassPDUs: - v2c.apiPDU.setErrorStatus(v2Pdu, 0) - v2c.apiPDU.setErrorIndex(v2Pdu, 0) - - if pduType not in rfc3411.notificationClassPDUs: v2c.apiPDU.setRequestID(v2Pdu, int(v1.apiPDU.getRequestID(v1Pdu))) + else: + v2c.apiPDU.setDefaults(v2Pdu) + v2c.apiPDU.setVarBinds(v2Pdu, v2VarBinds) debug.logger & debug.flagPrx and debug.logger('v1ToV2: v2Pdu %s' % v2Pdu.prettyPrint()) diff --git a/pysnmp/proto/rfc1155.py b/pysnmp/proto/rfc1155.py index 7bd2ea0f3..8fb48e8c1 100644 --- a/pysnmp/proto/rfc1155.py +++ b/pysnmp/proto/rfc1155.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # from pyasn1.type import univ, tag, constraint, namedtype diff --git a/pysnmp/proto/rfc1157.py b/pysnmp/proto/rfc1157.py index 603e80411..8cd354b8a 100644 --- a/pysnmp/proto/rfc1157.py +++ b/pysnmp/proto/rfc1157.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # from pyasn1.type import univ, tag, namedtype, namedval diff --git a/pysnmp/proto/rfc1901.py b/pysnmp/proto/rfc1901.py index c4d83ad49..de912395b 100644 --- a/pysnmp/proto/rfc1901.py +++ b/pysnmp/proto/rfc1901.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # from pyasn1.type import univ, namedtype, namedval diff --git a/pysnmp/proto/rfc1902.py b/pysnmp/proto/rfc1902.py index 12237482a..60dd6a844 100644 --- a/pysnmp/proto/rfc1902.py +++ b/pysnmp/proto/rfc1902.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # from sys import version_info @@ -10,7 +10,32 @@ __all__ = ['Opaque', 'TimeTicks', 'Bits', 'Integer', 'OctetString', 'IpAddress', 'Counter64', 'Unsigned32', 'Gauge32', 'Integer32', - 'ObjectIdentifier', 'Counter32'] + 'ObjectIdentifier', 'Counter32', 'Null'] + + +class Null(univ.Null): + """Creates an instance of SNMP Null class. + + :py:class:`~pysnmp.proto.rfc1902.Null` type represents the absence + of value. + + Parameters + ---------- + initializer: str + Python string object. Must be an empty string. + + Raises + ------ + PyAsn1Error : + On constraint violation or bad initializer. + + Examples + -------- + >>> from pysnmp.proto.rfc1902 import * + >>> Null('') + Null('') + >>> + """ class Integer32(univ.Integer): @@ -128,12 +153,17 @@ class Integer(Integer32): @classmethod def withNamedValues(cls, **values): - """Creates a subclass with discreet named values constraint. + """Create a subclass with discreet named values constraint. + + Reduce fully duplicate enumerations along the way. """ + enums = set(cls.namedValues.items()) + enums.update(values.items()) class X(cls): - namedValues = cls.namedValues + namedval.NamedValues(*values.items()) - subtypeSpec = cls.subtypeSpec + constraint.SingleValueConstraint(*values.values()) + namedValues = namedval.NamedValues(*enums) + subtypeSpec = cls.subtypeSpec + constraint.SingleValueConstraint( + *values.values()) X.__name__ = cls.__name__ return X @@ -598,11 +628,12 @@ class Bits(OctetString): """ namedValues = namedval.NamedValues() - def __init__(self, value=univ.noValue, **kwargs): - if 'namedValues' not in kwargs: - kwargs['namedValues'] = self.namedValues + def __new__(cls, *args, **kwargs): + if 'namedValues' in kwargs: + Bits = cls.withNamedBits(**dict(kwargs.pop('namedValues'))) + return Bits(*args, **kwargs) - OctetString.__init__(self, value, **kwargs) + return OctetString.__new__(cls) def prettyIn(self, bits): if not isinstance(bits, (tuple, list)): @@ -636,10 +667,14 @@ def prettyOut(self, value): @classmethod def withNamedBits(cls, **values): """Creates a subclass with discreet named bits constraint. + + Reduce fully duplicate enumerations along the way. """ + enums = set(cls.namedValues.items()) + enums.update(values.items()) class X(cls): - namedValues = cls.namedValues + namedval.NamedValues(*values.items()) + namedValues = namedval.NamedValues(*enums) X.__name__ = cls.__name__ return X diff --git a/pysnmp/proto/rfc1905.py b/pysnmp/proto/rfc1905.py index ac108617f..d5da39829 100644 --- a/pysnmp/proto/rfc1905.py +++ b/pysnmp/proto/rfc1905.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # from pyasn1.type import univ, tag, constraint, namedtype, namedval @@ -84,12 +84,13 @@ class VarBindList(univ.SequenceOf): errorStatus = univ.Integer( - namedValues=namedval.NamedValues(('noError', 0), ('tooBig', 1), ('noSuchName', 2), ('badValue', 3), ('readOnly', 4), - ('genErr', 5), ('noAccess', 6), ('wrongType', 7), ('wrongLength', 8), - ('wrongEncoding', 9), ('wrongValue', 10), ('noCreation', 11), - ('inconsistentValue', 12), ('resourceUnavailable', 13), ('commitFailed', 14), - ('undoFailed', 15), ('authorizationError', 16), ('notWritable', 17), - ('inconsistentName', 18)) + namedValues=namedval.NamedValues( + ('noError', 0), ('tooBig', 1), ('noSuchName', 2), ('badValue', 3), ('readOnly', 4), + ('genErr', 5), ('noAccess', 6), ('wrongType', 7), ('wrongLength', 8), + ('wrongEncoding', 9), ('wrongValue', 10), ('noCreation', 11), + ('inconsistentValue', 12), ('resourceUnavailable', 13), ('commitFailed', 14), + ('undoFailed', 15), ('authorizationError', 16), ('notWritable', 17), + ('inconsistentName', 18)) ) diff --git a/pysnmp/proto/rfc3411.py b/pysnmp/proto/rfc3411.py index d45353efd..bd4d40d3f 100644 --- a/pysnmp/proto/rfc3411.py +++ b/pysnmp/proto/rfc3411.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # from pysnmp.proto import rfc1157, rfc1905 diff --git a/pysnmp/proto/rfc3412.py b/pysnmp/proto/rfc3412.py index 386d1aaa6..100fff1c0 100644 --- a/pysnmp/proto/rfc3412.py +++ b/pysnmp/proto/rfc3412.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # import sys diff --git a/pysnmp/proto/secmod/base.py b/pysnmp/proto/secmod/base.py index 58fbca197..63df39c71 100644 --- a/pysnmp/proto/secmod/base.py +++ b/pysnmp/proto/secmod/base.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # from pysnmp.proto.secmod import cache diff --git a/pysnmp/proto/secmod/cache.py b/pysnmp/proto/secmod/cache.py index b6a0992f3..f5843c500 100644 --- a/pysnmp/proto/secmod/cache.py +++ b/pysnmp/proto/secmod/cache.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # from pysnmp import nextid diff --git a/pysnmp/proto/secmod/eso/priv/aes192.py b/pysnmp/proto/secmod/eso/priv/aes192.py index 33da51544..399a225ba 100644 --- a/pysnmp/proto/secmod/eso/priv/aes192.py +++ b/pysnmp/proto/secmod/eso/priv/aes192.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # from pysnmp.proto.secmod.eso.priv import aesbase diff --git a/pysnmp/proto/secmod/eso/priv/aes256.py b/pysnmp/proto/secmod/eso/priv/aes256.py index 08d120d61..360f26c33 100644 --- a/pysnmp/proto/secmod/eso/priv/aes256.py +++ b/pysnmp/proto/secmod/eso/priv/aes256.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # from pysnmp.proto.secmod.eso.priv import aesbase diff --git a/pysnmp/proto/secmod/eso/priv/aesbase.py b/pysnmp/proto/secmod/eso/priv/aesbase.py index 540ab0699..cb6287337 100644 --- a/pysnmp/proto/secmod/eso/priv/aesbase.py +++ b/pysnmp/proto/secmod/eso/priv/aesbase.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # from pysnmp.proto.secmod.rfc3826.priv import aes @@ -42,7 +42,8 @@ def localizeKey(self, authProtocol, privKey, snmpEngineID): # now extend this key if too short by repeating steps that includes the hashPassphrase step for count in range(1, int(ceil(self.keySize * 1.0 / len(localPrivKey)))): - localPrivKey += hashAlgo(localPrivKey).digest() + localPrivKey += localPrivKey.clone( + hashAlgo(localPrivKey.asOctets()).digest()) return localPrivKey[:self.keySize] diff --git a/pysnmp/proto/secmod/eso/priv/des3.py b/pysnmp/proto/secmod/eso/priv/des3.py index 426df6337..6976760c5 100644 --- a/pysnmp/proto/secmod/eso/priv/des3.py +++ b/pysnmp/proto/secmod/eso/priv/des3.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # import random diff --git a/pysnmp/proto/secmod/rfc2576.py b/pysnmp/proto/secmod/rfc2576.py index 69794272f..4da7d495d 100644 --- a/pysnmp/proto/secmod/rfc2576.py +++ b/pysnmp/proto/secmod/rfc2576.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # import sys @@ -38,7 +38,7 @@ def _sec2com(self, snmpEngine, securityName, contextEngineId, contextName): nextMibNode = snmpTargetParamsSecurityName - while 1: + while True: try: nextMibNode = snmpTargetParamsSecurityName.getNextNode(nextMibNode.name) @@ -49,10 +49,18 @@ def _sec2com(self, snmpEngine, securityName, contextEngineId, contextName): mibNode = snmpTargetParamsSecurityModel.getNode(snmpTargetParamsSecurityModel.name + instId) - if mibNode.syntax not in self.__nameToModelMap: - self.__nameToModelMap[nextMibNode.syntax] = set() + try: + if mibNode.syntax not in self.__nameToModelMap: + self.__nameToModelMap[nextMibNode.syntax] = set() + + self.__nameToModelMap[nextMibNode.syntax].add(mibNode.syntax) - self.__nameToModelMap[nextMibNode.syntax].add(mibNode.syntax) + except PyAsn1Error: + debug.logger & debug.flagSM and debug.logger( + '_sec2com: table entries %r/%r hashing failed' % ( + nextMibNode.syntax, mibNode.syntax) + ) + continue self.__paramsBranchId = snmpTargetParamsSecurityName.branchVersionId @@ -72,7 +80,8 @@ def _sec2com(self, snmpEngine, securityName, contextEngineId, contextName): self.__securityMap = {} nextMibNode = snmpCommunityName - while 1: + + while True: try: nextMibNode = snmpCommunityName.getNextNode(nextMibNode.name) @@ -88,9 +97,17 @@ def _sec2com(self, snmpEngine, securityName, contextEngineId, contextName): _contextName = snmpCommunityContextName.getNode(snmpCommunityContextName.name + instId).syntax - self.__securityMap[(_securityName, - _contextEngineId, - _contextName)] = nextMibNode.syntax + try: + self.__securityMap[(_securityName, + _contextEngineId, + _contextName)] = nextMibNode.syntax + + except PyAsn1Error: + debug.logger & debug.flagSM and debug.logger( + '_sec2com: table entries %r/%r/%r hashing failed' % ( + _securityName, _contextEngineId, _contextName) + ) + continue self.__securityBranchId = snmpCommunityName.branchVersionId @@ -123,11 +140,14 @@ def _com2sec(self, snmpEngine, communityName, transportInformation): self.__transportToTagMap = {} nextMibNode = snmpTargetAddrTagList + while True: try: nextMibNode = snmpTargetAddrTagList.getNextNode(nextMibNode.name) + except NoSuchInstanceError: break + instId = nextMibNode.name[len(snmpTargetAddrTagList.name):] targetAddrTDomain = snmpTargetAddrTDomain.getNode(snmpTargetAddrTDomain.name + instId).syntax targetAddrTAddress = snmpTargetAddrTAddress.getNode(snmpTargetAddrTAddress.name + instId).syntax @@ -144,17 +164,29 @@ def _com2sec(self, snmpEngine, communityName, transportInformation): targetAddrTAddress = tuple(TransportAddressIPv6(targetAddrTAddress)) elif targetAddrTDomain[:len(unix.snmpLocalDomain)] == unix.snmpLocalDomain: targetAddrTAddress = str(targetAddrTAddress) + targetAddr = targetAddrTDomain, targetAddrTAddress targetAddrTagList = snmpTargetAddrTagList.getNode(snmpTargetAddrTagList.name + instId).syntax + if targetAddr not in self.__transportToTagMap: self.__transportToTagMap[targetAddr] = set() - if targetAddrTagList: - self.__transportToTagMap[targetAddr].update( - [SnmpTagValue(x) - for x in targetAddrTagList.asOctets().split()] + + try: + if targetAddrTagList: + self.__transportToTagMap[targetAddr].update( + [SnmpTagValue(x) + for x in targetAddrTagList.asOctets().split()] + ) + + else: + self.__transportToTagMap[targetAddr].add(self.__emptyTag) + + except PyAsn1Error: + debug.logger & debug.flagSM and debug.logger( + '_com2sec: table entries %r/%r hashing failed' % ( + targetAddr, targetAddrTagList) ) - else: - self.__transportToTagMap[targetAddr].add(self.__emptyTag) + continue self.__transportBranchId = snmpTargetAddrTAddress.branchVersionId @@ -163,6 +195,7 @@ def _com2sec(self, snmpEngine, communityName, transportInformation): snmpTargetParamsSecurityName, = snmpEngine.msgAndPduDsp.mibInstrumController.mibBuilder.importSymbols( 'SNMP-TARGET-MIB', 'snmpTargetParamsSecurityName') + if self.__paramsBranchId != snmpTargetParamsSecurityName.branchVersionId: snmpTargetParamsSecurityModel, = snmpEngine.msgAndPduDsp.mibInstrumController.mibBuilder.importSymbols( 'SNMP-TARGET-MIB', 'snmpTargetParamsSecurityModel') @@ -182,10 +215,18 @@ def _com2sec(self, snmpEngine, communityName, transportInformation): mibNode = snmpTargetParamsSecurityModel.getNode(snmpTargetParamsSecurityModel.name + instId) - if nextMibNode.syntax not in self.__nameToModelMap: - self.__nameToModelMap[nextMibNode.syntax] = set() + try: + if nextMibNode.syntax not in self.__nameToModelMap: + self.__nameToModelMap[nextMibNode.syntax] = set() + + self.__nameToModelMap[nextMibNode.syntax].add(mibNode.syntax) - self.__nameToModelMap[nextMibNode.syntax].add(mibNode.syntax) + except PyAsn1Error: + debug.logger & debug.flagSM and debug.logger( + '_com2sec: table entries %r/%r hashing failed' % ( + nextMibNode.syntax, mibNode.syntax) + ) + continue self.__paramsBranchId = snmpTargetParamsSecurityName.branchVersionId @@ -211,6 +252,7 @@ def _com2sec(self, snmpEngine, communityName, transportInformation): self.__tagAndCommunityToSecurityMap = {} nextMibNode = snmpCommunityName + while True: try: nextMibNode = snmpCommunityName.getNextNode(nextMibNode.name) @@ -231,17 +273,25 @@ def _com2sec(self, snmpEngine, communityName, transportInformation): _tagAndCommunity = transportTag, nextMibNode.syntax - if _tagAndCommunity not in self.__tagAndCommunityToSecurityMap: - self.__tagAndCommunityToSecurityMap[_tagAndCommunity] = set() + try: + if _tagAndCommunity not in self.__tagAndCommunityToSecurityMap: + self.__tagAndCommunityToSecurityMap[_tagAndCommunity] = set() - self.__tagAndCommunityToSecurityMap[_tagAndCommunity].add( - (securityName, contextEngineId, contextName) - ) + self.__tagAndCommunityToSecurityMap[_tagAndCommunity].add( + (securityName, contextEngineId, contextName) + ) + + if nextMibNode.syntax not in self.__communityToTagMap: + self.__communityToTagMap[nextMibNode.syntax] = set() - if nextMibNode.syntax not in self.__communityToTagMap: - self.__communityToTagMap[nextMibNode.syntax] = set() + self.__communityToTagMap[nextMibNode.syntax].add(transportTag) - self.__communityToTagMap[nextMibNode.syntax].add(transportTag) + except PyAsn1Error: + debug.logger & debug.flagSM and debug.logger( + '_com2sec: table entries %r/%r hashing failed' % ( + _tagAndCommunity, nextMibNode.syntax) + ) + continue self.__communityBranchId = snmpCommunityName.branchVersionId diff --git a/pysnmp/proto/secmod/rfc3414/__init__.py b/pysnmp/proto/secmod/rfc3414/__init__.py index 9d06a20fb..cb2709fc7 100644 --- a/pysnmp/proto/secmod/rfc3414/__init__.py +++ b/pysnmp/proto/secmod/rfc3414/__init__.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # from pysnmp.proto.secmod.rfc3414 import service diff --git a/pysnmp/proto/secmod/rfc3414/auth/base.py b/pysnmp/proto/secmod/rfc3414/auth/base.py index 496f1bcdf..3ff8778f3 100644 --- a/pysnmp/proto/secmod/rfc3414/auth/base.py +++ b/pysnmp/proto/secmod/rfc3414/auth/base.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # from pysnmp.proto import errind, error diff --git a/pysnmp/proto/secmod/rfc3414/auth/hmacmd5.py b/pysnmp/proto/secmod/rfc3414/auth/hmacmd5.py index 387c8f395..761bf3d96 100644 --- a/pysnmp/proto/secmod/rfc3414/auth/hmacmd5.py +++ b/pysnmp/proto/secmod/rfc3414/auth/hmacmd5.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # try: diff --git a/pysnmp/proto/secmod/rfc3414/auth/hmacsha.py b/pysnmp/proto/secmod/rfc3414/auth/hmacsha.py index d4b900f39..3efe9c496 100644 --- a/pysnmp/proto/secmod/rfc3414/auth/hmacsha.py +++ b/pysnmp/proto/secmod/rfc3414/auth/hmacsha.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # try: diff --git a/pysnmp/proto/secmod/rfc3414/auth/noauth.py b/pysnmp/proto/secmod/rfc3414/auth/noauth.py index fbd4104ba..d1dea4f56 100644 --- a/pysnmp/proto/secmod/rfc3414/auth/noauth.py +++ b/pysnmp/proto/secmod/rfc3414/auth/noauth.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # from pysnmp.proto.secmod.rfc3414.auth import base diff --git a/pysnmp/proto/secmod/rfc3414/localkey.py b/pysnmp/proto/secmod/rfc3414/localkey.py index c7c730f2f..95d1deb0a 100644 --- a/pysnmp/proto/secmod/rfc3414/localkey.py +++ b/pysnmp/proto/secmod/rfc3414/localkey.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # try: @@ -37,7 +37,8 @@ def hashPassphrase(passphrase, hashFunc): ) mark = e - ringBufferLen count += 1 - return hasher.digest() + digest = hasher.digest() + return univ.OctetString(digest) def passwordToKey(passphrase, snmpEngineId, hashFunc): @@ -47,7 +48,8 @@ def passwordToKey(passphrase, snmpEngineId, hashFunc): def localizeKey(passKey, snmpEngineId, hashFunc): passKey = univ.OctetString(passKey).asOctets() # noinspection PyDeprecation,PyCallingNonCallable - return hashFunc(passKey + snmpEngineId.asOctets() + passKey).digest() + digest = hashFunc(passKey + snmpEngineId.asOctets() + passKey).digest() + return univ.OctetString(digest) # RFC3414: A.2.1 diff --git a/pysnmp/proto/secmod/rfc3414/priv/base.py b/pysnmp/proto/secmod/rfc3414/priv/base.py index c15c973fa..a7c68a11a 100644 --- a/pysnmp/proto/secmod/rfc3414/priv/base.py +++ b/pysnmp/proto/secmod/rfc3414/priv/base.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # from pysnmp.proto import error diff --git a/pysnmp/proto/secmod/rfc3414/priv/des.py b/pysnmp/proto/secmod/rfc3414/priv/des.py index b66889e21..b1d38b652 100644 --- a/pysnmp/proto/secmod/rfc3414/priv/des.py +++ b/pysnmp/proto/secmod/rfc3414/priv/des.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # import random diff --git a/pysnmp/proto/secmod/rfc3414/priv/nopriv.py b/pysnmp/proto/secmod/rfc3414/priv/nopriv.py index 473cd4fba..6721153b9 100644 --- a/pysnmp/proto/secmod/rfc3414/priv/nopriv.py +++ b/pysnmp/proto/secmod/rfc3414/priv/nopriv.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # from pysnmp.proto.secmod.rfc3414.priv import base diff --git a/pysnmp/proto/secmod/rfc3414/service.py b/pysnmp/proto/secmod/rfc3414/service.py index e8e363b98..90a90307a 100644 --- a/pysnmp/proto/secmod/rfc3414/service.py +++ b/pysnmp/proto/secmod/rfc3414/service.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # import time @@ -13,13 +13,16 @@ from pysnmp.proto.secmod.rfc7860.auth import hmacsha2 from pysnmp.proto.secmod.eso.priv import des3, aes192, aes256 from pysnmp.smi.error import NoSuchInstanceError -from pysnmp.proto import rfc1155, errind, error +from pysnmp.proto import api, rfc1155, errind, error from pysnmp import debug from pyasn1.type import univ, namedtype, constraint from pyasn1.codec.ber import encoder, decoder, eoo from pyasn1.error import PyAsn1Error from pyasn1.compat.octets import null +# API to rfc1905 protocol objects +pMod = api.protoModules[api.protoVersion2c] + # USM security params @@ -56,6 +59,11 @@ class SnmpUSMSecurityModel(AbstractSecurityModel): aes256.Aes256.serviceID: aes256.Aes256(), # non-standard nopriv.NoPriv.serviceID: nopriv.NoPriv()} + # If this, normally impossible, SNMP engine ID is present in LCD, we will use + # its master/localized keys when preparing SNMP message towards any unknown peer + # SNMP engine + wildcardSecurityEngineId = pMod.OctetString(hexValue='0000000000') + def __init__(self): AbstractSecurityModel.__init__(self) self.__securityParametersSpec = UsmSecurityParameters() @@ -224,6 +232,7 @@ def __generateRequestOrResponseMsg(self, snmpEngine, scopedPDU, securityStateReference): mibBuilder = snmpEngine.msgAndPduDsp.mibInstrumController.mibBuilder snmpEngineID = mibBuilder.importSymbols('__SNMP-FRAMEWORK-MIB', 'snmpEngineID')[0].syntax + msg = globalData # 3.1.1 if securityStateReference is not None: @@ -250,19 +259,66 @@ def __generateRequestOrResponseMsg(self, snmpEngine, usmUserPrivKeyLocalized = cachedSecurityData['usmUserPrivKeyLocalized'] else: usmUserPrivKeyLocalized = None + securityEngineID = snmpEngineID - debug.logger & debug.flagSM and debug.logger('__generateRequestOrResponseMsg: user info read from cache') - elif securityName: + + debug.logger & debug.flagSM and debug.logger( + '__generateRequestOrResponseMsg: using cached USM user entry ' + 'usmUserName "%s" ' + 'usmUserSecurityName "%s" ' + 'usmUserAuthProtocol "%s" ' + 'usmUserAuthKeyLocalized "%s" ' + 'usmUserPrivProtocol "%s" ' + 'usmUserPrivKeyLocalized "%s" for ' + 'securityEngineID "%s" and securityName "%s" found by ' + 'securityStateReference "%s" ' % ( + usmUserName, usmUserSecurityName, + usmUserAuthProtocol, + usmUserAuthKeyLocalized and usmUserAuthKeyLocalized.prettyPrint(), + usmUserPrivProtocol, + usmUserPrivKeyLocalized and usmUserPrivKeyLocalized.prettyPrint(), + securityEngineID.prettyPrint(), + securityName, securityStateReference)) + + elif securityEngineID: # 3.1.1b try: - (usmUserName, usmUserSecurityName, usmUserAuthProtocol, - usmUserAuthKeyLocalized, usmUserPrivProtocol, - usmUserPrivKeyLocalized) = self.__getUserInfo( - snmpEngine.msgAndPduDsp.mibInstrumController, - securityEngineID, - self.__sec2usr(snmpEngine, securityName, securityEngineID) - ) - debug.logger & debug.flagSM and debug.logger('__generateRequestOrResponseMsg: read user info') + try: + (usmUserName, usmUserSecurityName, usmUserAuthProtocol, + usmUserAuthKeyLocalized, usmUserPrivProtocol, + usmUserPrivKeyLocalized) = self.__getUserInfo( + snmpEngine.msgAndPduDsp.mibInstrumController, + securityEngineID, + self.__sec2usr(snmpEngine, securityName, + securityEngineID) + ) + + except NoSuchInstanceError: + (usmUserName, usmUserSecurityName, usmUserAuthProtocol, + usmUserAuthKeyLocalized, usmUserPrivProtocol, + usmUserPrivKeyLocalized) = self.__getUserInfo( + snmpEngine.msgAndPduDsp.mibInstrumController, + self.wildcardSecurityEngineId, + self.__sec2usr(snmpEngine, securityName, + self.wildcardSecurityEngineId) + ) + + debug.logger & debug.flagSM and debug.logger( + '__generateRequestOrResponseMsg: found USM user entry ' + 'usmUserName "%s" ' + 'usmUserSecurityName "%s" ' + 'usmUserAuthProtocol "%s" ' + 'usmUserAuthKeyLocalized "%s" ' + 'usmUserPrivProtocol "%s" ' + 'usmUserPrivKeyLocalized "%s" by ' + 'securityEngineID "%s" and securityName "%s"' % ( + usmUserName, usmUserSecurityName, + usmUserAuthProtocol, + usmUserAuthKeyLocalized and usmUserAuthKeyLocalized.prettyPrint(), + usmUserPrivProtocol, + usmUserPrivKeyLocalized and usmUserPrivKeyLocalized.prettyPrint(), + securityEngineID.prettyPrint(), + securityName)) except NoSuchInstanceError: pysnmpUsmDiscovery, = mibBuilder.importSymbols('__PYSNMP-USM-MIB', 'pysnmpUsmDiscovery') @@ -278,7 +334,28 @@ def __generateRequestOrResponseMsg(self, snmpEngine, self.__sec2usr(snmpEngine, securityName) ) + debug.logger & debug.flagSM and debug.logger( + '__generateRequestOrResponseMsg: cloned USM user entry ' + 'usmUserName "%s" ' + 'usmUserSecurityName "%s" ' + 'usmUserAuthProtocol "%s" ' + 'usmUserAuthKeyLocalized "%s" ' + 'usmUserPrivProtocol "%s" ' + 'usmUserPrivKeyLocalized "%s" for ' + 'securityEngineID "%s" and securityName "%s"' % ( + usmUserName, usmUserSecurityName, + usmUserAuthProtocol, + usmUserAuthKeyLocalized and usmUserAuthKeyLocalized.prettyPrint(), + usmUserPrivProtocol, + usmUserPrivKeyLocalized and usmUserPrivKeyLocalized.prettyPrint(), + securityEngineID.prettyPrint(), securityName)) + except NoSuchInstanceError: + debug.logger & debug.flagSM and debug.logger( + '__generateRequestOrResponseMsg: failed to clone ' + 'USM user for securityEngineID "%s" securityName ' + '"%s"' % (securityEngineID, securityName)) + reportUnknownName = True if reportUnknownName: @@ -286,8 +363,6 @@ def __generateRequestOrResponseMsg(self, snmpEngine, errorIndication=errind.unknownSecurityName ) - debug.logger & debug.flagSM and debug.logger('__generateRequestOrResponseMsg: clone user info') - except PyAsn1Error: debug.logger & debug.flagSM and debug.logger( '__generateRequestOrResponseMsg: %s' % (sys.exc_info()[1],)) @@ -296,20 +371,57 @@ def __generateRequestOrResponseMsg(self, snmpEngine, raise error.StatusInformation( errorIndication=errind.invalidMsg ) + else: - # empty username used for engineID discovery + # 4. (start SNMP engine ID discovery) + securityEngineID = securityName = null + securityLevel = 1 + + scopedPDU.setComponentByPosition( + 0, null, verifyConstraints=False, + matchTags=False, matchConstraints=False) + + headerData = msg.getComponentByPosition(1) + + # Clear possible auth&priv flags + headerData.setComponentByPosition( + 2, univ.OctetString(hexValue='04'), verifyConstraints=False, + matchTags=False, matchConstraints=False + ) + + emptyPdu = scopedPDU.getComponentByPosition(2).getComponent() + + # we edit the rest of the structures in-place because they + # are ours for as long as this stack lasts, however PDU + # is more persistent and should not be touched + + emptyPdu = emptyPdu.clone() + pMod.apiPDU.setDefaults(emptyPdu) + + scopedPDU.getComponentByPosition(2).setComponentByType( + emptyPdu.tagSet, emptyPdu, verifyConstraints=False, + matchTags=False, matchConstraints=False) + usmUserName = usmUserSecurityName = null usmUserAuthProtocol = noauth.NoAuth.serviceID usmUserPrivProtocol = nopriv.NoPriv.serviceID usmUserAuthKeyLocalized = usmUserPrivKeyLocalized = None - debug.logger & debug.flagSM and debug.logger('__generateRequestOrResponseMsg: use empty USM data') - - # noinspection PyUnboundLocalVariable - debug.logger & debug.flagSM and debug.logger( - '__generateRequestOrResponseMsg: local usmUserName %r usmUserSecurityName %r usmUserAuthProtocol %s usmUserPrivProtocol %s securityEngineID %r securityName %r' % ( - usmUserName, usmUserSecurityName, usmUserAuthProtocol, usmUserPrivProtocol, securityEngineID, securityName)) - msg = globalData + debug.logger & debug.flagSM and debug.logger( + '__generateRequestOrResponseMsg: using blank USM info for peer ' + 'SNMP engine ID discovery ' + 'usmUserName "%s" ' + 'usmUserSecurityName "%s" ' + 'usmUserAuthProtocol "%s" ' + 'usmUserAuthKeyLocalized "%s" ' + 'usmUserPrivProtocol "%s" ' + 'usmUserPrivKeyLocalized "%s" for ' + 'securityEngineID "%s" and securityName "%s"' % ( + usmUserName, usmUserSecurityName, + usmUserAuthProtocol, usmUserAuthKeyLocalized, + usmUserPrivProtocol, usmUserPrivKeyLocalized, + securityEngineID and securityEngineID.prettyPrint(), + securityName)) # 3.1.2 if securityLevel == 3: @@ -648,28 +760,44 @@ def processIncomingMsg(self, snmpEngine, messageProcessingModel, snmpEngine.msgAndPduDsp.mibInstrumController, msgAuthoritativeEngineId, msgUserName ) - debug.logger & debug.flagSM and debug.logger('processIncomingMsg: read user info from LCD') + debug.logger & debug.flagSM and debug.logger( + 'processIncomingMsg: read user info from LCD') except NoSuchInstanceError: - debug.logger & debug.flagSM and debug.logger( - 'processIncomingMsg: unknown securityEngineID %r msgUserName %r' % ( - msgAuthoritativeEngineId, msgUserName)) + try: + (usmUserName, + usmUserSecurityName, + usmUserAuthProtocol, + usmUserAuthKeyLocalized, + usmUserPrivProtocol, + usmUserPrivKeyLocalized) = self.__getUserInfo( + snmpEngine.msgAndPduDsp.mibInstrumController, + self.wildcardSecurityEngineId, msgUserName + ) + debug.logger & debug.flagSM and debug.logger( + 'processIncomingMsg: read wildcard user info from LCD') - usmStatsUnknownUserNames, = mibBuilder.importSymbols( - '__SNMP-USER-BASED-SM-MIB', 'usmStatsUnknownUserNames') - usmStatsUnknownUserNames.syntax += 1 + except NoSuchInstanceError: - raise error.StatusInformation( - errorIndication=errind.unknownSecurityName, - oid=usmStatsUnknownUserNames.name, - val=usmStatsUnknownUserNames.syntax, - securityStateReference=securityStateReference, - securityLevel=securityLevel, - contextEngineId=contextEngineId, - contextName=contextName, - msgUserName=msgUserName, - maxSizeResponseScopedPDU=maxSizeResponseScopedPDU - ) + debug.logger & debug.flagSM and debug.logger( + 'processIncomingMsg: unknown securityEngineID %r msgUserName %r' % ( + msgAuthoritativeEngineId, msgUserName)) + + usmStatsUnknownUserNames, = mibBuilder.importSymbols( + '__SNMP-USER-BASED-SM-MIB', 'usmStatsUnknownUserNames') + usmStatsUnknownUserNames.syntax += 1 + + raise error.StatusInformation( + errorIndication=errind.unknownSecurityName, + oid=usmStatsUnknownUserNames.name, + val=usmStatsUnknownUserNames.syntax, + securityStateReference=securityStateReference, + securityLevel=securityLevel, + contextEngineId=contextEngineId, + contextName=contextName, + msgUserName=msgUserName, + maxSizeResponseScopedPDU=maxSizeResponseScopedPDU + ) except PyAsn1Error: debug.logger & debug.flagSM and debug.logger('processIncomingMsg: %s' % (sys.exc_info()[1],)) diff --git a/pysnmp/proto/secmod/rfc3826/priv/aes.py b/pysnmp/proto/secmod/rfc3826/priv/aes.py index c702a4188..6f9893bcd 100644 --- a/pysnmp/proto/secmod/rfc3826/priv/aes.py +++ b/pysnmp/proto/secmod/rfc3826/priv/aes.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # import random diff --git a/pysnmp/smi/builder.py b/pysnmp/smi/builder.py index 8b81b06c6..a71c8a2c6 100644 --- a/pysnmp/smi/builder.py +++ b/pysnmp/smi/builder.py @@ -1,21 +1,43 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # import os import sys -import imp import struct import marshal import time import traceback +try: + import importlib + + try: + PY_MAGIC_NUMBER = importlib.util.MAGIC_NUMBER + SOURCE_SUFFIXES = importlib.machinery.SOURCE_SUFFIXES + BYTECODE_SUFFIXES = importlib.machinery.BYTECODE_SUFFIXES + + except Exception: + raise ImportError() + +except ImportError: + import imp + + PY_MAGIC_NUMBER = imp.get_magic() + SOURCE_SUFFIXES = [s[0] for s in imp.get_suffixes() + if s[2] == imp.PY_SOURCE] + BYTECODE_SUFFIXES = [s[0] for s in imp.get_suffixes() + if s[2] == imp.PY_COMPILED] + +PY_SUFFIXES = SOURCE_SUFFIXES + BYTECODE_SUFFIXES + try: from errno import ENOENT except ImportError: ENOENT = -1 + from pysnmp import version as pysnmp_version from pysnmp.smi import error from pysnmp import debug @@ -31,28 +53,22 @@ class __AbstractMibSource(object): def __init__(self, srcName): self._srcName = srcName - self.__magic = imp.get_magic() - self.__sfx = {} self.__inited = None - for sfx, mode, typ in imp.get_suffixes(): - if typ not in self.__sfx: - self.__sfx[typ] = [] - self.__sfx[typ].append((sfx, len(sfx), mode)) debug.logger & debug.flagBld and debug.logger('trying %s' % self) def __repr__(self): return '%s(%r)' % (self.__class__.__name__, self._srcName) def _uniqNames(self, files): - u = {} + u = set() + for f in files: - if f[:9] == '__init__.': + if f.startswith('__init__.'): continue - for typ in (imp.PY_SOURCE, imp.PY_COMPILED): - for sfx, sfxLen, mode in self.__sfx[typ]: - if f[-sfxLen:] == sfx: - u[f[:-sfxLen]] = None - return tuple(u.keys()) + + u.update(f[:-len(sfx)] for sfx in PY_SUFFIXES if f.endswith(sfx)) + + return tuple(u) # MibSource API follows @@ -66,6 +82,7 @@ def init(self): self.__inited = True if self.__inited is True: return self + else: return self.__inited @@ -75,54 +92,59 @@ def listdir(self): def read(self, f): pycTime = pyTime = -1 - for pycSfx, pycSfxLen, pycMode in self.__sfx[imp.PY_COMPILED]: + for pycSfx in BYTECODE_SUFFIXES: + try: - pycData = self._getData(f + pycSfx, pycMode) + pycData, pycPath = self._getData(f + pycSfx, 'rb') + except IOError: why = sys.exc_info()[1] if ENOENT == -1 or why.errno == ENOENT: debug.logger & debug.flagBld and debug.logger( 'file %s access error: %s' % (f + pycSfx, why) ) + else: raise error.MibLoadError('MIB file %s access error: %s' % (f + pycSfx, why)) + else: - if self.__magic == pycData[:4]: + if PY_MAGIC_NUMBER == pycData[:4]: pycData = pycData[4:] pycTime = struct.unpack('= pyTime: - # noinspection PyUnboundLocalVariable return marshal.loads(pycData), pycSfx + if pyTime != -1: - # noinspection PyUnboundLocalVariable - return self._getData(f + pySfx, pyMode), pySfx + modData, pyPath = self._getData(f + pySfx, 'r') + return compile(modData, pyPath, 'exec'), pyPath raise IOError(ENOENT, 'No suitable module found', f) @@ -194,9 +216,11 @@ def _getTimestamp(self, f): def _getData(self, f, mode=None): p = os.path.join(self._srcName, f) try: - return self.__loader.get_data(p) - except: # ZIP code seems to return all kinds of errors - raise IOError(ENOENT, 'No such file in ZIP archive: %s' % sys.exc_info()[1], p) + return self.__loader.get_data(p), p + + except Exception: # ZIP code seems to return all kinds of errors + why = sys.exc_info() + raise IOError(ENOENT, 'File or ZIP archive %s access error: %s' % (p, why[1])) class DirMibSource(__AbstractMibSource): @@ -208,8 +232,9 @@ def _listdir(self): try: return self._uniqNames(os.listdir(self._srcName)) except OSError: + why = sys.exc_info() debug.logger & debug.flagBld and debug.logger( - 'listdir() failed for %s: %s' % (self._srcName, sys.exc_info()[1])) + 'listdir() failed for %s: %s' % (self._srcName, why[1])) return () def _getTimestamp(self, f): @@ -220,20 +245,23 @@ def _getTimestamp(self, f): raise IOError(ENOENT, 'No such file: %s' % sys.exc_info()[1], p) def _getData(self, f, mode): - p = os.path.join(self._srcName, f) + p = os.path.join(self._srcName, '*') try: if f in os.listdir(self._srcName): # make FS case-sensitive + p = os.path.join(self._srcName, f) fp = open(p, mode) data = fp.read() fp.close() - return data + return data, p + except (IOError, OSError): - why = sys.exc_info()[1] - if why.errno != ENOENT and ENOENT != -1: - raise error.MibLoadError('MIB file %s access error: %s' % (p, why)) + why = sys.exc_info() + msg = 'File or directory %s access error: %s' % (p, why[1]) - raise IOError(ENOENT, 'No such file: %s' % sys.exc_info()[1], p) + else: + msg = 'No such file or directory: %s' % p + raise IOError(ENOENT, msg) class MibBuilder(object): defaultCoreMibs = os.pathsep.join( @@ -263,7 +291,7 @@ def __init__(self): self.mibSymbols = {} self.__mibSources = [] self.__modSeen = {} - self.__modPathsSeen = {} + self.__modPathsSeen = set() self.__mibCompiler = None self.setMibSources(*sources) @@ -306,10 +334,12 @@ def getMibPath(self): return paths def loadModule(self, modName, **userCtx): + """Load and execute MIB modules as Python code""" for mibSource in self.__mibSources: debug.logger & debug.flagBld and debug.logger('loadModule: trying %s at %s' % (modName, mibSource)) try: - modData, sfx = mibSource.read(modName) + codeObj, sfx = mibSource.read(modName) + except IOError: debug.logger & debug.flagBld and debug.logger( 'loadModule: read %s from %s failed: %s' % (modName, mibSource, sys.exc_info()[1])) @@ -320,20 +350,21 @@ def loadModule(self, modName, **userCtx): if modPath in self.__modPathsSeen: debug.logger & debug.flagBld and debug.logger('loadModule: seen %s' % modPath) break + else: - self.__modPathsSeen[modPath] = 1 + self.__modPathsSeen.add(modPath) debug.logger & debug.flagBld and debug.logger('loadModule: evaluating %s' % modPath) g = {'mibBuilder': self, 'userCtx': userCtx} try: - exec (modData, g) + exec(codeObj, g) except Exception: - del self.__modPathsSeen[modPath] + self.__modPathsSeen.remove(modPath) raise error.MibLoadError( - 'MIB module \"%s\" load error: %s' % (modPath, traceback.format_exception(*sys.exc_info())) + 'MIB module \'%s\' load error: %s' % (modPath, traceback.format_exception(*sys.exc_info())) ) self.__modSeen[modName] = modPath @@ -351,13 +382,15 @@ def loadModule(self, modName, **userCtx): return self def loadModules(self, *modNames, **userCtx): + """Load (optionally, compiling) pysnmp MIB modules""" # Build a list of available modules if not modNames: modNames = {} for mibSource in self.__mibSources: for modName in mibSource.listdir(): modNames[modName] = None - modNames = list(modNames.keys()) + modNames = list(modNames) + if not modNames: raise error.MibNotFoundError( 'No MIB module to load at %s' % (self,) @@ -366,6 +399,7 @@ def loadModules(self, *modNames, **userCtx): for modName in modNames: try: self.loadModule(modName, **userCtx) + except error.MibNotFoundError: if self.__mibCompiler: debug.logger & debug.flagBld and debug.logger('loadModules: calling MIB compiler for %s' % modName) @@ -374,7 +408,8 @@ def loadModules(self, *modNames, **userCtx): x in ('failed', 'missing')]) if errs: raise error.MibNotFoundError('%s compilation error(s): %s' % (modName, errs)) - # compilation suceeded, MIB might load now + + # compilation succeeded, MIB might load now self.loadModule(modName, **userCtx) return self @@ -388,7 +423,7 @@ def unloadModules(self, *modNames): 'No module %s at %s' % (modName, self) ) self.unexportSymbols(modName) - del self.__modPathsSeen[self.__modSeen[modName]] + self.__modPathsSeen.remove(self.__modSeen[modName]) del self.__modSeen[modName] debug.logger & debug.flagBld and debug.logger('unloadModules: %s' % modName) diff --git a/pysnmp/smi/compiler.py b/pysnmp/smi/compiler.py index 4e4ade156..57ba9bd71 100644 --- a/pysnmp/smi/compiler.py +++ b/pysnmp/smi/compiler.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # import os diff --git a/pysnmp/smi/error.py b/pysnmp/smi/error.py index 8f1c509da..a16dfe2b1 100644 --- a/pysnmp/smi/error.py +++ b/pysnmp/smi/error.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # from pyasn1.error import PyAsn1Error @@ -43,7 +43,24 @@ def update(self, d): self.__outArgs.update(d) -# Aligned with SNMPv2 PDU error-status +# Aligned with SNMPv2 PDU error-status values + +class TooBigError(MibOperationError): + pass + + +class NoSuchNameError(MibOperationError): + pass + + +class BadValueError(MibOperationError): + pass + + +class ReadOnlyError(MibOperationError): + pass + + class GenError(MibOperationError): pass @@ -100,20 +117,22 @@ class InconsistentNameError(MibOperationError): pass -# Aligned with SNMPv2 Var-Bind exceptions -class NoSuchObjectError(MibOperationError): +# Aligned with SNMPv2 PDU exceptions or error-status values + +class NoSuchObjectError(NoSuchNameError): pass -class NoSuchInstanceError(MibOperationError): +class NoSuchInstanceError(NoSuchNameError): pass -class EndOfMibViewError(MibOperationError): +class EndOfMibViewError(NoSuchNameError): pass -# Row management +# SNMP table management exceptions + class TableRowManagement(MibOperationError): pass diff --git a/pysnmp/smi/exval.py b/pysnmp/smi/exval.py index bc6e3d0f8..02e8cab84 100644 --- a/pysnmp/smi/exval.py +++ b/pysnmp/smi/exval.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # from pysnmp.proto import rfc1905 diff --git a/pysnmp/smi/indices.py b/pysnmp/smi/indices.py index 00d95a632..677ac97c1 100644 --- a/pysnmp/smi/indices.py +++ b/pysnmp/smi/indices.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # from bisect import bisect @@ -10,36 +10,26 @@ class OrderedDict(dict): """Ordered dictionary used for indices""" - def __init__(self, **kwargs): + def __init__(self, *args, **kwargs): self.__keys = [] self.__dirty = True super(OrderedDict, self).__init__() + if args: + self.update(*args) if kwargs: - self.update(kwargs) + self.update(**kwargs) def __setitem__(self, key, value): - if key not in self: - self.__keys.append(key) super(OrderedDict, self).__setitem__(key, value) - self.__dirty = True - - def __repr__(self): - if self.__dirty: - self.__order() - return super(OrderedDict, self).__repr__() - - def __str__(self): - if self.__dirty: - self.__order() - return super(OrderedDict, self).__str__() + if key not in self.__keys: + self.__keys.append(key) + self.__dirty = True def __delitem__(self, key): - if super(OrderedDict, self).__contains__(key): - self.__keys.remove(key) super(OrderedDict, self).__delitem__(key) - self.__dirty = True - - __delattr__ = __delitem__ + if key in self.__keys: + self.__keys.remove(key) + self.__dirty = True def clear(self): super(OrderedDict, self).clear() @@ -61,30 +51,43 @@ def items(self): self.__order() return [(k, self[k]) for k in self.__keys] - def update(self, d): - [self.__setitem__(k, v) for k, v in d.items()] + def update(self, *args, **kwargs): + if args: + iterable = args[0] + if hasattr(iterable, 'keys'): + for k in iterable: + self[k] = iterable[k] + else: + for k, v in iterable: + self[k] = v + + if kwargs: + for k in kwargs: + self[k] = kwargs[k] def sortingFun(self, keys): keys.sort() def __order(self): self.sortingFun(self.__keys) - d = {} - for k in self.__keys: - d[len(k)] = 1 - l = list(d.keys()) - l.sort(reverse=True) - self.__keysLens = tuple(l) + self.__keysLens = sorted(set(len(k) for k in self.__keys), reverse=True) self.__dirty = False def nextKey(self, key): - keys = list(self.keys()) - if key in self: + if self.__dirty: + self.__order() + + keys = self.__keys + + if key in keys: nextIdx = keys.index(key) + 1 + else: nextIdx = bisect(keys, key) + if nextIdx < len(keys): return keys[nextIdx] + else: raise KeyError(key) @@ -97,24 +100,22 @@ def getKeysLens(self): class OidOrderedDict(OrderedDict): """OID-ordered dictionary used for indices""" - def __init__(self, **kwargs): + def __init__(self, *args, **kwargs): self.__keysCache = {} - OrderedDict.__init__(self, **kwargs) + OrderedDict.__init__(self, *args, **kwargs) def __setitem__(self, key, value): + OrderedDict.__setitem__(self, key, value) if key not in self.__keysCache: if isinstance(key, tuple): self.__keysCache[key] = key else: self.__keysCache[key] = [int(x) for x in key.split('.') if x] - OrderedDict.__setitem__(self, key, value) def __delitem__(self, key): + OrderedDict.__delitem__(self, key) if key in self.__keysCache: del self.__keysCache[key] - OrderedDict.__delitem__(self, key) - - __delattr__ = __delitem__ def sortingFun(self, keys): keys.sort(key=lambda k, d=self.__keysCache: d[k]) diff --git a/pysnmp/smi/instrum.py b/pysnmp/smi/instrum.py index bd1444a9d..3c31b6313 100644 --- a/pysnmp/smi/instrum.py +++ b/pysnmp/smi/instrum.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # import sys diff --git a/pysnmp/smi/mibs/ASN1-ENUMERATION.py b/pysnmp/smi/mibs/ASN1-ENUMERATION.py index 5eb9f80e1..6b906def4 100644 --- a/pysnmp/smi/mibs/ASN1-ENUMERATION.py +++ b/pysnmp/smi/mibs/ASN1-ENUMERATION.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # from pyasn1.type import namedval diff --git a/pysnmp/smi/mibs/ASN1-REFINEMENT.py b/pysnmp/smi/mibs/ASN1-REFINEMENT.py index 64e20c2e3..1f5bce27d 100644 --- a/pysnmp/smi/mibs/ASN1-REFINEMENT.py +++ b/pysnmp/smi/mibs/ASN1-REFINEMENT.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # from pyasn1.type import constraint diff --git a/pysnmp/smi/mibs/ASN1.py b/pysnmp/smi/mibs/ASN1.py index e41d3b450..4a393d3b6 100644 --- a/pysnmp/smi/mibs/ASN1.py +++ b/pysnmp/smi/mibs/ASN1.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # from pyasn1.type import univ diff --git a/pysnmp/smi/mibs/INET-ADDRESS-MIB.py b/pysnmp/smi/mibs/INET-ADDRESS-MIB.py index 46684fbc7..b52f541c2 100644 --- a/pysnmp/smi/mibs/INET-ADDRESS-MIB.py +++ b/pysnmp/smi/mibs/INET-ADDRESS-MIB.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # # PySNMP MIB module INET-ADDRESS-MIB (http://snmplabs.com/pysnmp) @@ -80,7 +80,7 @@ def cloneFromName(cls, value, impliedFlag, parentRow, parentIndices): for parentIndex in reversed(parentIndices): if isinstance(parentIndex, InetAddressType): try: - return parentRow.setFromName(cls.typeMap[parentIndex], value, impliedFlag, parentIndices) + return parentRow.setFromName(cls.typeMap[int(parentIndex)], value, impliedFlag, parentIndices) except KeyError: pass @@ -90,7 +90,7 @@ def cloneAsName(self, impliedFlag, parentRow, parentIndices): for parentIndex in reversed(parentIndices): if isinstance(parentIndex, InetAddressType): try: - return parentRow.getAsName(self.typeMap[parentIndex].clone(self.asOctets()), impliedFlag, parentIndices) + return parentRow.getAsName(self.typeMap[int(parentIndex)].clone(self.asOctets().decode('ascii')), impliedFlag, parentIndices) except KeyError: pass diff --git a/pysnmp/smi/mibs/PYSNMP-MIB.py b/pysnmp/smi/mibs/PYSNMP-MIB.py index 64915f4b8..9e0df7214 100644 --- a/pysnmp/smi/mibs/PYSNMP-MIB.py +++ b/pysnmp/smi/mibs/PYSNMP-MIB.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # # PySNMP MIB module PYSNMP-MIB (http://snmplabs.com/pysnmp) diff --git a/pysnmp/smi/mibs/PYSNMP-SOURCE-MIB.py b/pysnmp/smi/mibs/PYSNMP-SOURCE-MIB.py index 216bcca1e..ba645a4b5 100644 --- a/pysnmp/smi/mibs/PYSNMP-SOURCE-MIB.py +++ b/pysnmp/smi/mibs/PYSNMP-SOURCE-MIB.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # # PySNMP MIB module PYSNMP-SOURCE-MIB (http://snmplabs.com/pysnmp) diff --git a/pysnmp/smi/mibs/PYSNMP-USM-MIB.py b/pysnmp/smi/mibs/PYSNMP-USM-MIB.py index 5dac372f3..b66802daf 100644 --- a/pysnmp/smi/mibs/PYSNMP-USM-MIB.py +++ b/pysnmp/smi/mibs/PYSNMP-USM-MIB.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # # PySNMP MIB module PYSNMP-USM-MIB (http://snmplabs.com/pysnmp) @@ -34,6 +34,9 @@ pysnmpUsmDiscovery = MibScalar((1, 3, 6, 1, 4, 1, 20408, 3, 1, 1, 1, 1, 2), Integer32().subtype(subtypeSpec=ConstraintsUnion(SingleValueConstraint(0, 1))).clone(namedValues=NamedValues(("doNotDiscover", 0), ("doDiscover", 1))).clone('doDiscover')).setMaxAccess("readwrite") if mibBuilder.loadTexts: pysnmpUsmDiscovery.setStatus('current') if mibBuilder.loadTexts: pysnmpUsmDiscovery.setDescription('Whether SNMP engine would try to figure out the EngineIDs of its peers by sending discover requests.') +pysnmpUsmKeyType = MibScalar((1, 3, 6, 1, 4, 1, 20408, 3, 1, 1, 1, 1, 3), Integer32().subtype(subtypeSpec=ConstraintsUnion(SingleValueConstraint(0, 1, 2))).clone(namedValues=NamedValues(("passphrase", 0), ("master", 1), ("localized", 2))).clone('passphrase')).setMaxAccess("not-accessible") +if mibBuilder.loadTexts: pysnmpUsmKeyType.setStatus('current') +if mibBuilder.loadTexts: pysnmpUsmKeyType.setDescription('When configuring USM user, the value of this enumeration determines how the keys should be treated. The default value "passphrase" means that given keys are plain-text pass-phrases, "master" indicates that the keys are pre-hashed pass-phrases, while "localized" stands for pre-hashed pass-phrases mixed with SNMP Security Engine ID value.') pysnmpUsmUser = MibIdentifier((1, 3, 6, 1, 4, 1, 20408, 3, 1, 1, 1, 3)) pysnmpUsmSecretTable = MibTable((1, 3, 6, 1, 4, 1, 20408, 3, 1, 1, 1, 2), ) if mibBuilder.loadTexts: pysnmpUsmSecretTable.setStatus('current') @@ -75,4 +78,4 @@ if mibBuilder.loadTexts: pysnmpUsmKeyPriv.setDescription("User's non-localized key used for encryption.") pysnmpUsmMIBCompliances = MibIdentifier((1, 3, 6, 1, 4, 1, 20408, 3, 1, 1, 2, 1)) pysnmpUsmMIBGroups = MibIdentifier((1, 3, 6, 1, 4, 1, 20408, 3, 1, 1, 2, 2)) -mibBuilder.exportSymbols("PYSNMP-USM-MIB", pysnmpUsmCfg=pysnmpUsmCfg, pysnmpUsmDiscoverable=pysnmpUsmDiscoverable, pysnmpUsmKeyEntry=pysnmpUsmKeyEntry, pysnmpUsmKeyTable=pysnmpUsmKeyTable, pysnmpUsmKeyPrivLocalized=pysnmpUsmKeyPrivLocalized, pysnmpUsmMIBCompliances=pysnmpUsmMIBCompliances, pysnmpUsmMIBObjects=pysnmpUsmMIBObjects, pysnmpUsmSecretTable=pysnmpUsmSecretTable, PYSNMP_MODULE_ID=pysnmpUsmMIB, pysnmpUsmSecretEntry=pysnmpUsmSecretEntry, pysnmpUsmMIBConformance=pysnmpUsmMIBConformance, pysnmpUsmUser=pysnmpUsmUser, pysnmpUsmKeyAuth=pysnmpUsmKeyAuth, pysnmpUsmSecretPrivKey=pysnmpUsmSecretPrivKey, pysnmpUsmKeyAuthLocalized=pysnmpUsmKeyAuthLocalized, pysnmpUsmMIB=pysnmpUsmMIB, pysnmpUsmDiscovery=pysnmpUsmDiscovery, pysnmpUsmSecretUserName=pysnmpUsmSecretUserName, pysnmpUsmKeyPriv=pysnmpUsmKeyPriv, pysnmpUsmSecretAuthKey=pysnmpUsmSecretAuthKey, pysnmpUsmSecretStatus=pysnmpUsmSecretStatus, pysnmpUsmMIBGroups=pysnmpUsmMIBGroups) +mibBuilder.exportSymbols("PYSNMP-USM-MIB", pysnmpUsmCfg=pysnmpUsmCfg, pysnmpUsmDiscoverable=pysnmpUsmDiscoverable, pysnmpUsmKeyType=pysnmpUsmKeyType, pysnmpUsmKeyEntry=pysnmpUsmKeyEntry, pysnmpUsmKeyTable=pysnmpUsmKeyTable, pysnmpUsmKeyPrivLocalized=pysnmpUsmKeyPrivLocalized, pysnmpUsmMIBCompliances=pysnmpUsmMIBCompliances, pysnmpUsmMIBObjects=pysnmpUsmMIBObjects, pysnmpUsmSecretTable=pysnmpUsmSecretTable, PYSNMP_MODULE_ID=pysnmpUsmMIB, pysnmpUsmSecretEntry=pysnmpUsmSecretEntry, pysnmpUsmMIBConformance=pysnmpUsmMIBConformance, pysnmpUsmUser=pysnmpUsmUser, pysnmpUsmKeyAuth=pysnmpUsmKeyAuth, pysnmpUsmSecretPrivKey=pysnmpUsmSecretPrivKey, pysnmpUsmKeyAuthLocalized=pysnmpUsmKeyAuthLocalized, pysnmpUsmMIB=pysnmpUsmMIB, pysnmpUsmDiscovery=pysnmpUsmDiscovery, pysnmpUsmSecretUserName=pysnmpUsmSecretUserName, pysnmpUsmKeyPriv=pysnmpUsmKeyPriv, pysnmpUsmSecretAuthKey=pysnmpUsmSecretAuthKey, pysnmpUsmSecretStatus=pysnmpUsmSecretStatus, pysnmpUsmMIBGroups=pysnmpUsmMIBGroups) diff --git a/pysnmp/smi/mibs/RFC1158-MIB.py b/pysnmp/smi/mibs/RFC1158-MIB.py index 2bcdb5c20..ef5c2c166 100644 --- a/pysnmp/smi/mibs/RFC1158-MIB.py +++ b/pysnmp/smi/mibs/RFC1158-MIB.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # # PySNMP MIB module RFC1158-MIB (http://snmplabs.com/pysnmp) @@ -17,5 +17,5 @@ snmpInBadTypes = MibScalar((1, 3, 6, 1, 2, 1, 11, 7), Counter32()).setMaxAccess("readonly") if mibBuilder.loadTexts: snmpInBadTypes.setStatus('mandatory') snmpOutReadOnlys = MibScalar((1, 3, 6, 1, 2, 1, 11, 23), Counter32()).setMaxAccess("readonly") -if mibBuilder.loadTexts: snmpInReadOnlys.setStatus('mandatory') +if mibBuilder.loadTexts: snmpOutReadOnlys.setStatus('mandatory') mibBuilder.exportSymbols("RFC1158-MIB", snmpOutReadOnlys=snmpOutReadOnlys, snmpInBadTypes=snmpInBadTypes) diff --git a/pysnmp/smi/mibs/RFC1213-MIB.py b/pysnmp/smi/mibs/RFC1213-MIB.py index 46ec17e07..01cd008ab 100644 --- a/pysnmp/smi/mibs/RFC1213-MIB.py +++ b/pysnmp/smi/mibs/RFC1213-MIB.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # # PySNMP MIB module RFC1213-MIB (http://snmplabs.com/pysnmp) diff --git a/pysnmp/smi/mibs/SNMP-COMMUNITY-MIB.py b/pysnmp/smi/mibs/SNMP-COMMUNITY-MIB.py index 4e9d0aea8..0f524053d 100644 --- a/pysnmp/smi/mibs/SNMP-COMMUNITY-MIB.py +++ b/pysnmp/smi/mibs/SNMP-COMMUNITY-MIB.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # # PySNMP MIB module SNMP-COMMUNITY-MIB (http://snmplabs.com/pysnmp) diff --git a/pysnmp/smi/mibs/SNMP-FRAMEWORK-MIB.py b/pysnmp/smi/mibs/SNMP-FRAMEWORK-MIB.py index fdaaf01a1..e7ff01f3a 100644 --- a/pysnmp/smi/mibs/SNMP-FRAMEWORK-MIB.py +++ b/pysnmp/smi/mibs/SNMP-FRAMEWORK-MIB.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # # PySNMP MIB module SNMP-FRAMEWORK-MIB (http://snmplabs.com/pysnmp) diff --git a/pysnmp/smi/mibs/SNMP-MPD-MIB.py b/pysnmp/smi/mibs/SNMP-MPD-MIB.py index 402e69130..147981004 100644 --- a/pysnmp/smi/mibs/SNMP-MPD-MIB.py +++ b/pysnmp/smi/mibs/SNMP-MPD-MIB.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # # diff --git a/pysnmp/smi/mibs/SNMP-NOTIFICATION-MIB.py b/pysnmp/smi/mibs/SNMP-NOTIFICATION-MIB.py index d71b9ae2e..87b110c51 100644 --- a/pysnmp/smi/mibs/SNMP-NOTIFICATION-MIB.py +++ b/pysnmp/smi/mibs/SNMP-NOTIFICATION-MIB.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # # PySNMP MIB module SNMP-NOTIFICATION-MIB (http://snmplabs.com/pysnmp) diff --git a/pysnmp/smi/mibs/SNMP-PROXY-MIB.py b/pysnmp/smi/mibs/SNMP-PROXY-MIB.py index a9f5b458d..b4ac1f2db 100644 --- a/pysnmp/smi/mibs/SNMP-PROXY-MIB.py +++ b/pysnmp/smi/mibs/SNMP-PROXY-MIB.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # # PySNMP MIB module SNMP-PROXY-MIB (http://snmplabs.com/pysnmp) diff --git a/pysnmp/smi/mibs/SNMP-TARGET-MIB.py b/pysnmp/smi/mibs/SNMP-TARGET-MIB.py index 14d0fdc45..af8439e0d 100644 --- a/pysnmp/smi/mibs/SNMP-TARGET-MIB.py +++ b/pysnmp/smi/mibs/SNMP-TARGET-MIB.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # # PySNMP MIB module SNMP-TARGET-MIB (http://snmplabs.com/pysnmp) diff --git a/pysnmp/smi/mibs/SNMP-USER-BASED-SM-3DES-MIB.py b/pysnmp/smi/mibs/SNMP-USER-BASED-SM-3DES-MIB.py index 0a368fc92..555dd1780 100644 --- a/pysnmp/smi/mibs/SNMP-USER-BASED-SM-3DES-MIB.py +++ b/pysnmp/smi/mibs/SNMP-USER-BASED-SM-3DES-MIB.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # # PySNMP MIB module SNMP-USER-BASED-SM-3DES-MIB (http://snmplabs.com/pysnmp) diff --git a/pysnmp/smi/mibs/SNMP-USER-BASED-SM-MIB.py b/pysnmp/smi/mibs/SNMP-USER-BASED-SM-MIB.py index 1e998f9f0..498ee9449 100644 --- a/pysnmp/smi/mibs/SNMP-USER-BASED-SM-MIB.py +++ b/pysnmp/smi/mibs/SNMP-USER-BASED-SM-MIB.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # # PySNMP MIB module SNMP-USER-BASED-SM-MIB (http://snmplabs.com/pysnmp) diff --git a/pysnmp/smi/mibs/SNMP-USM-AES-MIB.py b/pysnmp/smi/mibs/SNMP-USM-AES-MIB.py index 83d3bdaf8..7aee7bf6c 100644 --- a/pysnmp/smi/mibs/SNMP-USM-AES-MIB.py +++ b/pysnmp/smi/mibs/SNMP-USM-AES-MIB.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # # PySNMP MIB module SNMP-USM-AES-MIB (http://snmplabs.com/pysnmp) diff --git a/pysnmp/smi/mibs/SNMP-VIEW-BASED-ACM-MIB.py b/pysnmp/smi/mibs/SNMP-VIEW-BASED-ACM-MIB.py index d8eeffd4f..2b04e4d23 100644 --- a/pysnmp/smi/mibs/SNMP-VIEW-BASED-ACM-MIB.py +++ b/pysnmp/smi/mibs/SNMP-VIEW-BASED-ACM-MIB.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # # PySNMP MIB module SNMP-VIEW-BASED-ACM-MIB (http://snmplabs.com/pysnmp) @@ -34,6 +34,10 @@ vacmContextName = MibTableColumn((1, 3, 6, 1, 6, 3, 16, 1, 1, 1, 1), SnmpAdminString().subtype(subtypeSpec=ValueSizeConstraint(0, 32))).setMaxAccess("readonly") if mibBuilder.loadTexts: vacmContextName.setStatus('current') if mibBuilder.loadTexts: vacmContextName.setDescription('A human readable name identifying a particular context at a particular SNMP entity. The empty contextName (zero length) represents the default context. ') +# The RowStatus column is not present in the MIB +vacmContextStatus = MibTableColumn((1, 3, 6, 1, 6, 3, 16, 1, 1, 1, 2), RowStatus()).setMaxAccess("readcreate") +if mibBuilder.loadTexts: vacmContextStatus.setStatus('current') +if mibBuilder.loadTexts: vacmContextStatus.setDescription("The status of this conceptual row. Until instances of all corresponding columns are appropriately configured, the value of the corresponding instance of the vacmContextTableStatus column is 'notReady'. In particular, a newly created row cannot be made active until a value has been set for vacmContextName. The RowStatus TC [RFC2579] requires that this DESCRIPTION clause states under which circumstances other objects in this row can be modified: The value of this object has no effect on whether other objects in this conceptual row can be modified. ") vacmSecurityToGroupTable = MibTable((1, 3, 6, 1, 6, 3, 16, 1, 2), ) if mibBuilder.loadTexts: vacmSecurityToGroupTable.setStatus('current') if mibBuilder.loadTexts: vacmSecurityToGroupTable.setDescription('This table maps a combination of securityModel and securityName into a groupName which is used to define an access control policy for a group of principals. ') @@ -122,4 +126,4 @@ if mibBuilder.loadTexts: vacmMIBCompliance.setDescription('The compliance statement for SNMP engines which implement the SNMP View-based Access Control Model configuration MIB. ') vacmBasicGroup = ObjectGroup((1, 3, 6, 1, 6, 3, 16, 2, 2, 1)).setObjects(("SNMP-VIEW-BASED-ACM-MIB", "vacmContextName"), ("SNMP-VIEW-BASED-ACM-MIB", "vacmGroupName"), ("SNMP-VIEW-BASED-ACM-MIB", "vacmSecurityToGroupStorageType"), ("SNMP-VIEW-BASED-ACM-MIB", "vacmSecurityToGroupStatus"), ("SNMP-VIEW-BASED-ACM-MIB", "vacmAccessContextMatch"), ("SNMP-VIEW-BASED-ACM-MIB", "vacmAccessReadViewName"), ("SNMP-VIEW-BASED-ACM-MIB", "vacmAccessWriteViewName"), ("SNMP-VIEW-BASED-ACM-MIB", "vacmAccessNotifyViewName"), ("SNMP-VIEW-BASED-ACM-MIB", "vacmAccessStorageType"), ("SNMP-VIEW-BASED-ACM-MIB", "vacmAccessStatus"), ("SNMP-VIEW-BASED-ACM-MIB", "vacmViewSpinLock"), ("SNMP-VIEW-BASED-ACM-MIB", "vacmViewTreeFamilyMask"), ("SNMP-VIEW-BASED-ACM-MIB", "vacmViewTreeFamilyType"), ("SNMP-VIEW-BASED-ACM-MIB", "vacmViewTreeFamilyStorageType"), ("SNMP-VIEW-BASED-ACM-MIB", "vacmViewTreeFamilyStatus")) if mibBuilder.loadTexts: vacmBasicGroup.setDescription('A collection of objects providing for remote configuration of an SNMP engine which implements the SNMP View-based Access Control Model. ') -mibBuilder.exportSymbols("SNMP-VIEW-BASED-ACM-MIB", vacmSecurityModel=vacmSecurityModel, vacmSecurityToGroupEntry=vacmSecurityToGroupEntry, vacmMIBConformance=vacmMIBConformance, vacmAccessReadViewName=vacmAccessReadViewName, vacmViewTreeFamilyMask=vacmViewTreeFamilyMask, snmpVacmMIB=snmpVacmMIB, vacmViewTreeFamilyType=vacmViewTreeFamilyType, vacmMIBGroups=vacmMIBGroups, vacmAccessContextMatch=vacmAccessContextMatch, vacmAccessNotifyViewName=vacmAccessNotifyViewName, vacmViewSpinLock=vacmViewSpinLock, vacmAccessWriteViewName=vacmAccessWriteViewName, vacmContextTable=vacmContextTable, PYSNMP_MODULE_ID=snmpVacmMIB, vacmViewTreeFamilyViewName=vacmViewTreeFamilyViewName, vacmMIBObjects=vacmMIBObjects, vacmViewTreeFamilyStatus=vacmViewTreeFamilyStatus, vacmSecurityName=vacmSecurityName, vacmGroupName=vacmGroupName, vacmAccessSecurityLevel=vacmAccessSecurityLevel, vacmBasicGroup=vacmBasicGroup, vacmContextName=vacmContextName, vacmSecurityToGroupStatus=vacmSecurityToGroupStatus, vacmAccessEntry=vacmAccessEntry, vacmMIBViews=vacmMIBViews, vacmAccessStorageType=vacmAccessStorageType, vacmMIBCompliances=vacmMIBCompliances, vacmViewTreeFamilyEntry=vacmViewTreeFamilyEntry, vacmViewTreeFamilyTable=vacmViewTreeFamilyTable, vacmSecurityToGroupStorageType=vacmSecurityToGroupStorageType, vacmAccessTable=vacmAccessTable, vacmAccessContextPrefix=vacmAccessContextPrefix, vacmViewTreeFamilyStorageType=vacmViewTreeFamilyStorageType, vacmMIBCompliance=vacmMIBCompliance, vacmAccessSecurityModel=vacmAccessSecurityModel, vacmAccessStatus=vacmAccessStatus, vacmContextEntry=vacmContextEntry, vacmSecurityToGroupTable=vacmSecurityToGroupTable, vacmViewTreeFamilySubtree=vacmViewTreeFamilySubtree) +mibBuilder.exportSymbols("SNMP-VIEW-BASED-ACM-MIB", vacmSecurityModel=vacmSecurityModel, vacmSecurityToGroupEntry=vacmSecurityToGroupEntry, vacmMIBConformance=vacmMIBConformance, vacmAccessReadViewName=vacmAccessReadViewName, vacmViewTreeFamilyMask=vacmViewTreeFamilyMask, snmpVacmMIB=snmpVacmMIB, vacmViewTreeFamilyType=vacmViewTreeFamilyType, vacmMIBGroups=vacmMIBGroups, vacmAccessContextMatch=vacmAccessContextMatch, vacmAccessNotifyViewName=vacmAccessNotifyViewName, vacmViewSpinLock=vacmViewSpinLock, vacmAccessWriteViewName=vacmAccessWriteViewName, vacmContextTable=vacmContextTable, PYSNMP_MODULE_ID=snmpVacmMIB, vacmViewTreeFamilyViewName=vacmViewTreeFamilyViewName, vacmMIBObjects=vacmMIBObjects, vacmViewTreeFamilyStatus=vacmViewTreeFamilyStatus, vacmSecurityName=vacmSecurityName, vacmGroupName=vacmGroupName, vacmAccessSecurityLevel=vacmAccessSecurityLevel, vacmBasicGroup=vacmBasicGroup, vacmContextName=vacmContextName, vacmContextStatus=vacmContextStatus, vacmSecurityToGroupStatus=vacmSecurityToGroupStatus, vacmAccessEntry=vacmAccessEntry, vacmMIBViews=vacmMIBViews, vacmAccessStorageType=vacmAccessStorageType, vacmMIBCompliances=vacmMIBCompliances, vacmViewTreeFamilyEntry=vacmViewTreeFamilyEntry, vacmViewTreeFamilyTable=vacmViewTreeFamilyTable, vacmSecurityToGroupStorageType=vacmSecurityToGroupStorageType, vacmAccessTable=vacmAccessTable, vacmAccessContextPrefix=vacmAccessContextPrefix, vacmViewTreeFamilyStorageType=vacmViewTreeFamilyStorageType, vacmMIBCompliance=vacmMIBCompliance, vacmAccessSecurityModel=vacmAccessSecurityModel, vacmAccessStatus=vacmAccessStatus, vacmContextEntry=vacmContextEntry, vacmSecurityToGroupTable=vacmSecurityToGroupTable, vacmViewTreeFamilySubtree=vacmViewTreeFamilySubtree) diff --git a/pysnmp/smi/mibs/SNMPv2-CONF.py b/pysnmp/smi/mibs/SNMPv2-CONF.py index e333336d8..c41599d58 100644 --- a/pysnmp/smi/mibs/SNMPv2-CONF.py +++ b/pysnmp/smi/mibs/SNMPv2-CONF.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # # PySNMP MIB module SNMPv2-CONF (http://snmplabs.com/pysnmp) diff --git a/pysnmp/smi/mibs/SNMPv2-MIB.py b/pysnmp/smi/mibs/SNMPv2-MIB.py index e0d7001fa..a31d6871e 100644 --- a/pysnmp/smi/mibs/SNMPv2-MIB.py +++ b/pysnmp/smi/mibs/SNMPv2-MIB.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # # PySNMP MIB module SNMPv2-MIB (http://snmplabs.com/pysnmp) diff --git a/pysnmp/smi/mibs/SNMPv2-SMI.py b/pysnmp/smi/mibs/SNMPv2-SMI.py index b64089306..a34755408 100644 --- a/pysnmp/smi/mibs/SNMPv2-SMI.py +++ b/pysnmp/smi/mibs/SNMPv2-SMI.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # import sys diff --git a/pysnmp/smi/mibs/SNMPv2-TC.py b/pysnmp/smi/mibs/SNMPv2-TC.py index cad048b46..176e6533f 100644 --- a/pysnmp/smi/mibs/SNMPv2-TC.py +++ b/pysnmp/smi/mibs/SNMPv2-TC.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # import sys @@ -184,7 +184,7 @@ def prettyIn(self, value): # override asn1 type method """Implements DISPLAY-HINT parsing into base SNMP value Proper parsing seems impossible due to ambiguities. - Here we are truing to do our best, but be prepared + Here we are trying to do our best, but be prepared for failures on complicated DISPLAY-HINTs. Keep in mind that this parser only works with "text" @@ -259,6 +259,7 @@ def prettyIn(self, value): # override asn1 type method # how do we know if object is initialized with display-hint # formatted text? based on "text" input maybe? + # That boils down to `str` object on Py3 or `unicode` on Py2. if octets.isStringType(value) and not octets.isOctetsType(value): value = base.prettyIn(self, value) else: @@ -267,6 +268,7 @@ def prettyIn(self, value): # override asn1 type method outputValue = octets.str2octs('') runningValue = value displayHint = self.displayHint + while runningValue and displayHint: # 1 this information is totally lost, just fail explicitly if displayHint[0] == '*': diff --git a/pysnmp/smi/mibs/SNMPv2-TM.py b/pysnmp/smi/mibs/SNMPv2-TM.py index 3d04c2162..67c321371 100644 --- a/pysnmp/smi/mibs/SNMPv2-TM.py +++ b/pysnmp/smi/mibs/SNMPv2-TM.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # # PySNMP MIB module SNMPv2-TM (http://snmplabs.com/pysnmp) diff --git a/pysnmp/smi/mibs/TRANSPORT-ADDRESS-MIB.py b/pysnmp/smi/mibs/TRANSPORT-ADDRESS-MIB.py index e507358e9..213bb89c0 100644 --- a/pysnmp/smi/mibs/TRANSPORT-ADDRESS-MIB.py +++ b/pysnmp/smi/mibs/TRANSPORT-ADDRESS-MIB.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # # PySNMP MIB module TRANSPORT-ADDRESS-MIB (http://snmplabs.com/pysnmp) @@ -36,7 +36,7 @@ def inet_pton(address_family, ip_string): if address_family == socket.AF_INET: - return inet_aton(ip_string) + return socket.inet_aton(ip_string) elif address_family != socket.AF_INET6: raise socket.error( 'Unknown address family %s' % (address_family,) @@ -46,7 +46,7 @@ def inet_pton(address_family, ip_string): spaces = groups.count('') if '.' in groups[-1]: - groups[-1:] = ["%x" % x for x in struct.unpack("!HH", inet_aton(groups[-1]))] + groups[-1:] = ["%x" % x for x in struct.unpack("!HH", socket.inet_aton(groups[-1]))] if spaces == 1: idx = groups.index('') @@ -84,7 +84,7 @@ def inet_pton(address_family, ip_string): def inet_ntop(address_family, packed_ip): if address_family == socket.AF_INET: - return inet_ntop(packed_ip) + return socket.inet_ntop(packed_ip) elif address_family != socket.AF_INET6: raise socket.error( 'Unknown address family %s' % (address_family,) diff --git a/pysnmp/smi/mibs/instances/__PYSNMP-USM-MIB.py b/pysnmp/smi/mibs/instances/__PYSNMP-USM-MIB.py index 1b0f118a7..551e65c23 100644 --- a/pysnmp/smi/mibs/instances/__PYSNMP-USM-MIB.py +++ b/pysnmp/smi/mibs/instances/__PYSNMP-USM-MIB.py @@ -1,23 +1,27 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # MibScalarInstance, = mibBuilder.importSymbols('SNMPv2-SMI', 'MibScalarInstance') (pysnmpUsmDiscoverable, - pysnmpUsmDiscovery) = mibBuilder.importSymbols( + pysnmpUsmDiscovery, + pysnmpUsmKeyType) = mibBuilder.importSymbols( 'PYSNMP-USM-MIB', 'pysnmpUsmDiscoverable', - 'pysnmpUsmDiscovery' + 'pysnmpUsmDiscovery', + 'pysnmpUsmKeyType' ) __pysnmpUsmDiscoverable = MibScalarInstance(pysnmpUsmDiscoverable.name, (0,), pysnmpUsmDiscoverable.syntax) __pysnmpUsmDiscovery = MibScalarInstance(pysnmpUsmDiscovery.name, (0,), pysnmpUsmDiscovery.syntax) +__pysnmpUsmKeyType = MibScalarInstance(pysnmpUsmKeyType.name, (0,), pysnmpUsmKeyType.syntax) mibBuilder.exportSymbols( "__PYSNMP-USM-MIB", pysnmpUsmDiscoverable=__pysnmpUsmDiscoverable, - pysnmpUsmDiscovery=__pysnmpUsmDiscovery + pysnmpUsmDiscovery=__pysnmpUsmDiscovery, + pysnmpUsmKeyType=__pysnmpUsmKeyType ) diff --git a/pysnmp/smi/mibs/instances/__SNMP-FRAMEWORK-MIB.py b/pysnmp/smi/mibs/instances/__SNMP-FRAMEWORK-MIB.py index e20a1f5e0..cfbcb9ff2 100644 --- a/pysnmp/smi/mibs/instances/__SNMP-FRAMEWORK-MIB.py +++ b/pysnmp/smi/mibs/instances/__SNMP-FRAMEWORK-MIB.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # import time diff --git a/pysnmp/smi/mibs/instances/__SNMP-MPD-MIB.py b/pysnmp/smi/mibs/instances/__SNMP-MPD-MIB.py index 086dabcea..634398fe5 100644 --- a/pysnmp/smi/mibs/instances/__SNMP-MPD-MIB.py +++ b/pysnmp/smi/mibs/instances/__SNMP-MPD-MIB.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # MibScalarInstance, = mibBuilder.importSymbols('SNMPv2-SMI', 'MibScalarInstance') diff --git a/pysnmp/smi/mibs/instances/__SNMP-TARGET-MIB.py b/pysnmp/smi/mibs/instances/__SNMP-TARGET-MIB.py index fc7e0b0c7..fa9f4421c 100644 --- a/pysnmp/smi/mibs/instances/__SNMP-TARGET-MIB.py +++ b/pysnmp/smi/mibs/instances/__SNMP-TARGET-MIB.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # MibScalarInstance, = mibBuilder.importSymbols( diff --git a/pysnmp/smi/mibs/instances/__SNMP-USER-BASED-SM-MIB.py b/pysnmp/smi/mibs/instances/__SNMP-USER-BASED-SM-MIB.py index 82a1eb3b2..175cf0a0b 100644 --- a/pysnmp/smi/mibs/instances/__SNMP-USER-BASED-SM-MIB.py +++ b/pysnmp/smi/mibs/instances/__SNMP-USER-BASED-SM-MIB.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # MibScalarInstance, = mibBuilder.importSymbols('SNMPv2-SMI', 'MibScalarInstance') diff --git a/pysnmp/smi/mibs/instances/__SNMP-VIEW-BASED-ACM-MIB.py b/pysnmp/smi/mibs/instances/__SNMP-VIEW-BASED-ACM-MIB.py index ef0d6ecc4..740dcf913 100644 --- a/pysnmp/smi/mibs/instances/__SNMP-VIEW-BASED-ACM-MIB.py +++ b/pysnmp/smi/mibs/instances/__SNMP-VIEW-BASED-ACM-MIB.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # MibScalarInstance, = mibBuilder.importSymbols('SNMPv2-SMI', 'MibScalarInstance') diff --git a/pysnmp/smi/mibs/instances/__SNMPv2-MIB.py b/pysnmp/smi/mibs/instances/__SNMPv2-MIB.py index 7d9ce90fe..48d4bd991 100644 --- a/pysnmp/smi/mibs/instances/__SNMPv2-MIB.py +++ b/pysnmp/smi/mibs/instances/__SNMPv2-MIB.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # from sys import version diff --git a/pysnmp/smi/rfc1902.py b/pysnmp/smi/rfc1902.py index 07cea1e54..ad115e7ad 100644 --- a/pysnmp/smi/rfc1902.py +++ b/pysnmp/smi/rfc1902.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # import sys @@ -506,7 +506,7 @@ class instance self.__oid += instIds self.__indices = rowNode.getIndicesFromInstId(instIds) except PyAsn1Error: - raise SmiError('Instance index %r to OID convertion failure at object %r: %s' % ( + raise SmiError('Instance index %r to OID conversion failure at object %r: %s' % ( self.__args[2:], mibNode.getLabel(), sys.exc_info()[1])) elif self.__args[2:]: # any other kind of MIB node with indices if self.__args[2:]: @@ -805,7 +805,7 @@ def loadMibs(self, *modNames): self.__args[0].loadMibs(*modNames) return self - def resolveWithMib(self, mibViewController): + def resolveWithMib(self, mibViewController, ignoreErrors=True): """Perform MIB variable ID and associated value conversion. Parameters @@ -813,6 +813,12 @@ def resolveWithMib(self, mibViewController): mibViewController : :py:class:`~pysnmp.smi.view.MibViewController` class instance representing MIB browsing functionality. + Other Parameters + ---------------- + ignoreErrors: :py:class:`bool` + If `True` (default), ignore MIB object name or value casting + failures if possible. + Returns ------- : :py:class:`~pysnmp.smi.rfc1902.ObjectType` @@ -851,7 +857,8 @@ class instance representing MIB browsing functionality. if not isinstance(self.__args[0].getMibNode(), (MibScalar, MibTableColumn)): - if not isinstance(self.__args[1], AbstractSimpleAsn1Item): + if (ignoreErrors and + not isinstance(self.__args[1], AbstractSimpleAsn1Item)): raise SmiError('MIB object %r is not OBJECT-TYPE (MIB not loaded?)' % (self.__args[0],)) self.__state |= self.stClean return self @@ -866,9 +873,15 @@ class instance representing MIB browsing functionality. try: self.__args[1] = self.__args[0].getMibNode().getSyntax().clone(self.__args[1]) except PyAsn1Error: - raise SmiError('MIB object %r having type %r failed to cast value %r: %s' % ( - self.__args[0].prettyPrint(), self.__args[0].getMibNode().getSyntax().__class__.__name__, self.__args[1], - sys.exc_info()[1])) + err = ('MIB object %r having type %r failed to cast value ' + '%r: %s' % (self.__args[0].prettyPrint(), + self.__args[0].getMibNode().getSyntax().__class__.__name__, + self.__args[1], + sys.exc_info()[1])) + + if (not ignoreErrors or + not isinstance(self.__args[1], AbstractSimpleAsn1Item)): + raise SmiError(err) if rfc1902.ObjectIdentifier().isSuperTypeOf(self.__args[1], matchConstraints=False): self.__args[1] = ObjectIdentity(self.__args[1]).resolveWithMib(mibViewController) @@ -1100,7 +1113,7 @@ def loadMibs(self, *modNames): def isFullyResolved(self): return self.__state & self.stClean - def resolveWithMib(self, mibViewController): + def resolveWithMib(self, mibViewController, ignoreErrors=True): """Perform MIB variable ID conversion and notification objects expansion. Parameters @@ -1108,6 +1121,12 @@ def resolveWithMib(self, mibViewController): mibViewController : :py:class:`~pysnmp.smi.view.MibViewController` class instance representing MIB browsing functionality. + Other Parameters + ---------------- + ignoreErrors: :py:class:`bool` + If `True` (default), ignore MIB object name or value casting + failures if possible. + Returns ------- : :py:class:`~pysnmp.smi.rfc1902.NotificationType` @@ -1145,7 +1164,7 @@ class instance representing MIB browsing functionality. self.__varBinds.append( ObjectType(ObjectIdentity(v2c.apiTrapPDU.snmpTrapOID), - self.__objectIdentity).resolveWithMib(mibViewController) + self.__objectIdentity).resolveWithMib(mibViewController, ignoreErrors) ) SmiNotificationType, = mibViewController.mibBuilder.importSymbols('SNMPv2-SMI', 'NotificationType') @@ -1157,11 +1176,11 @@ class instance representing MIB browsing functionality. if isinstance(mibNode, SmiNotificationType): for notificationObject in mibNode.getObjects(): objectIdentity = ObjectIdentity(*notificationObject + self.__instanceIndex).resolveWithMib( - mibViewController) + mibViewController, ignoreErrors) self.__varBinds.append( ObjectType(objectIdentity, self.__objects.get(notificationObject, rfc1905.unSpecified)).resolveWithMib( - mibViewController) + mibViewController, ignoreErrors) ) varBindsLocation[objectIdentity] = len(self.__varBinds) - 1 else: @@ -1171,7 +1190,7 @@ class instance representing MIB browsing functionality. for varBinds in self.__additionalVarBinds: if not isinstance(varBinds, ObjectType): varBinds = ObjectType(ObjectIdentity(varBinds[0]), varBinds[1]) - varBinds.resolveWithMib(mibViewController) + varBinds.resolveWithMib(mibViewController, ignoreErrors) if varBinds[0] in varBindsLocation: self.__varBinds[varBindsLocation[varBinds[0]]] = varBinds else: diff --git a/pysnmp/smi/view.py b/pysnmp/smi/view.py index 8c5dd81c1..223e22d8d 100644 --- a/pysnmp/smi/view.py +++ b/pysnmp/smi/view.py @@ -1,7 +1,7 @@ # # This file is part of pysnmp software. # -# Copyright (c) 2005-2018, Ilya Etingof +# Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pysnmp/license.html # import sys diff --git a/runtests.sh b/runtests.sh index 22fab7e16..9143a769b 100755 --- a/runtests.sh +++ b/runtests.sh @@ -2,6 +2,8 @@ set -e +PYTHON=${1:-python} + for x in examples/hlapi/asyncore/sync/manager/cmdgen/*.py \ examples/hlapi/asyncore/sync/agent/ntforg/*.py \ examples/hlapi/asyncore/manager/cmdgen/*.py \ @@ -19,7 +21,7 @@ do continue ;; *) - python "${x}" + $PYTHON "${x}" | tail -50 ;; esac -done \ No newline at end of file +done diff --git a/setup.cfg b/setup.cfg index 2a9acf13d..13b523c63 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,2 +1,5 @@ [bdist_wheel] universal = 1 + +[metadata] +license_file = LICENSE.rst diff --git a/setup.py b/setup.py index 0fac0fca4..0d987d5c2 100644 --- a/setup.py +++ b/setup.py @@ -1,12 +1,13 @@ #!/usr/bin/env python """SNMP library for Python - SNMP v1/v2c/v3 engine and apps written in pure-Python. - Supports Manager/Agent/Proxy roles, scriptable MIBs, - asynchronous operation and multiple transports. +SNMP v1/v2c/v3 engine and Standard Applications suite written in pure-Python. +Supports Manager/Agent/Proxy roles, Manager/Agent-side MIBs, asynchronous +operation and multiple network transports. """ import sys import os +import re classifiers = """\ Development Status :: 5 - Production/Stable @@ -30,6 +31,7 @@ Programming Language :: Python :: 3.4 Programming Language :: Python :: 3.5 Programming Language :: Python :: 3.6 +Programming Language :: Python :: 3.7 Topic :: Communications Topic :: System :: Monitoring Topic :: System :: Networking :: Monitoring @@ -54,33 +56,52 @@ def howto_install_setuptools(): print("ERROR: this package requires Python 2.4 or later!") sys.exit(1) +requires = [ln.strip() for ln in open('requirements.txt').readlines()] + try: - from setuptools import setup + import setuptools + + setup, Command = setuptools.setup, setuptools.Command + + observed_version = [int(x) for x in setuptools.__version__.split('.')] + required_version = [36, 2, 0] + + # NOTE(etingof): require fresh setuptools to build proper wheels + # See also: https://hynek.me/articles/conditional-python-dependencies/ + if ('bdist_wheel' in sys.argv and + observed_version < required_version): + print("ERROR: your wheels won't come out round with setuptools %s! " + "Upgrade to %s and try again." % ( + '.'.join([str(x) for x in observed_version]), + '.'.join([str(x) for x in required_version]))) + sys.exit(1) params = { - 'install_requires': ['pyasn1>=0.2.3', 'pysmi', 'pycryptodomex'], + 'install_requires': requires, 'zip_safe': True } except ImportError: - for arg in sys.argv: - if 'egg' in arg: - howto_install_setuptools() - sys.exit(1) + if 'bdist_wheel' in sys.argv or 'bdist_egg' in sys.argv: + howto_install_setuptools() + sys.exit(1) from distutils.core import setup params = {} + if sys.version_info[:2] > (2, 4): - params['requires'] = ['pyasn1(>=0.2.3)', 'pysmi', 'pycryptodomex'] + params['requires'] = [ + re.sub(r'(.*?)([<>=!~]+)(.*)', r'\g<1>\g<2>(\g<3>)', r) for r in requires + ] -doclines = [x.strip() for x in (__doc__ or '').split('\n') if x] +doclines = [x.strip() for x in (__doc__ or '').split('\n')] params.update({ 'name': 'pysnmp', 'version': open(os.path.join('pysnmp', '__init__.py')).read().split('\'')[1], 'description': doclines[0], - 'long_description': ' '.join(doclines[1:]), + 'long_description': '\n'.join(doclines[1:]), 'maintainer': 'Ilya Etingof ', 'author': 'Ilya Etingof', 'author_email': 'etingof@gmail.com', diff --git a/tox.ini b/tox.ini new file mode 100644 index 000000000..88539239c --- /dev/null +++ b/tox.ini @@ -0,0 +1,5 @@ +[envlist] +envlist = py{26,27,33,34,35,36,37} + +[testenv] +commands = {toxinidir}/runtests.sh {envpython}