1#
2# This file is part of pysnmp software.
3#
4# Copyright (c) 2005-2019, Ilya Etingof <etingof@gmail.com>
5# License: http://snmplabs.com/pysnmp/license.html
6#
7import sys
8from pyasn1.codec.ber import decoder, eoo
9from pyasn1.type import univ
10from pyasn1.compat.octets import null
11from pyasn1.error import PyAsn1Error
12from pysnmp.proto.mpmod.base import AbstractMessageProcessingModel
13from pysnmp.proto import rfc3411, errind, error
14from pysnmp.proto.api import v1, v2c
15from pysnmp import debug
16
17
18# Since I have not found a detailed reference to v1MP/v2cMP
19# inner workings, the following has been patterned from v3MP. Most
20# references here goes to RFC3412.
21
22class SnmpV1MessageProcessingModel(AbstractMessageProcessingModel):
23    messageProcessingModelID = univ.Integer(0)  # SNMPv1
24    snmpMsgSpec = v1.Message
25
26    # rfc3412: 7.1
27    def prepareOutgoingMessage(self, snmpEngine, transportDomain,
28                               transportAddress, messageProcessingModel,
29                               securityModel, securityName, securityLevel,
30                               contextEngineId, contextName, pduVersion,
31                               pdu, expectResponse, sendPduHandle):
32        mibBuilder = snmpEngine.msgAndPduDsp.mibInstrumController.mibBuilder
33
34        snmpEngineId, = mibBuilder.importSymbols('__SNMP-FRAMEWORK-MIB', 'snmpEngineID')
35        snmpEngineId = snmpEngineId.syntax
36
37        # rfc3412: 7.1.1b
38        if pdu.tagSet in rfc3411.confirmedClassPDUs:
39            # serve unique PDU request-id
40            msgID = self._cache.newMsgID()
41            reqID = pdu.getComponentByPosition(0)
42            debug.logger & debug.flagMP and debug.logger(
43                'prepareOutgoingMessage: PDU request-id %s replaced with unique ID %s' % (reqID, msgID))
44
45        # rfc3412: 7.1.4
46        # Since there's no SNMP engine identification in v1/2c,
47        # set destination contextEngineId to ours
48        if not contextEngineId:
49            contextEngineId = snmpEngineId
50
51        # rfc3412: 7.1.5
52        if not contextName:
53            contextName = null
54
55        debug.logger & debug.flagMP and debug.logger(
56            'prepareOutgoingMessage: using contextEngineId %r contextName %r' % (contextEngineId, contextName))
57
58        # rfc3412: 7.1.6
59        scopedPDU = (contextEngineId, contextName, pdu)
60
61        msg = self._snmpMsgSpec
62        msg.setComponentByPosition(0, self.messageProcessingModelID)
63        msg.setComponentByPosition(2)
64        msg.getComponentByPosition(2).setComponentByType(
65            pdu.tagSet, pdu, verifyConstraints=False, matchTags=False, matchConstraints=False
66        )
67
68        # rfc3412: 7.1.7
69        globalData = (msg,)
70
71        k = int(securityModel)
72        if k in snmpEngine.securityModels:
73            smHandler = snmpEngine.securityModels[k]
74        else:
75            raise error.StatusInformation(
76                errorIndication=errind.unsupportedSecurityModel
77            )
78
79        # rfc3412: 7.1.9.a & rfc2576: 5.2.1 --> no-op
80
81        snmpEngineMaxMessageSize, = mibBuilder.importSymbols('__SNMP-FRAMEWORK-MIB',
82                                                             'snmpEngineMaxMessageSize')
83
84        # fix unique request-id right prior PDU serialization
85        if pdu.tagSet in rfc3411.confirmedClassPDUs:
86            # noinspection PyUnboundLocalVariable
87            pdu.setComponentByPosition(0, msgID)
88
89        # rfc3412: 7.1.9.b
90        (securityParameters,
91         wholeMsg) = smHandler.generateRequestMsg(
92            snmpEngine, self.messageProcessingModelID, globalData,
93            snmpEngineMaxMessageSize.syntax, securityModel,
94            snmpEngineId, securityName, securityLevel, scopedPDU
95        )
96
97        # return original request-id right after PDU serialization
98        if pdu.tagSet in rfc3411.confirmedClassPDUs:
99            # noinspection PyUnboundLocalVariable
100            pdu.setComponentByPosition(0, reqID)
101
102        # rfc3412: 7.1.9.c
103        if pdu.tagSet in rfc3411.confirmedClassPDUs:
104            # XXX rfc bug? why stateReference should be created?
105            self._cache.pushByMsgId(int(msgID), sendPduHandle=sendPduHandle,
106                                    reqID=reqID, snmpEngineId=snmpEngineId,
107                                    securityModel=securityModel,
108                                    securityName=securityName,
109                                    securityLevel=securityLevel,
110                                    contextEngineId=contextEngineId,
111                                    contextName=contextName,
112                                    transportDomain=transportDomain,
113                                    transportAddress=transportAddress)
114
115        communityName = msg.getComponentByPosition(1)  # for observer
116
117        snmpEngine.observer.storeExecutionContext(
118            snmpEngine, 'rfc2576.prepareOutgoingMessage',
119            dict(transportDomain=transportDomain,
120                 transportAddress=transportAddress,
121                 wholeMsg=wholeMsg,
122                 securityModel=securityModel,
123                 securityName=securityName,
124                 securityLevel=securityLevel,
125                 contextEngineId=contextEngineId,
126                 contextName=contextName,
127                 communityName=communityName,
128                 pdu=pdu)
129        )
130        snmpEngine.observer.clearExecutionContext(
131            snmpEngine, 'rfc2576.prepareOutgoingMessage'
132        )
133
134        return transportDomain, transportAddress, wholeMsg
135
136    # rfc3412: 7.1
137    def prepareResponseMessage(self, snmpEngine, messageProcessingModel,
138                               securityModel, securityName, securityLevel,
139                               contextEngineId, contextName, pduVersion,
140                               pdu, maxSizeResponseScopedPDU, stateReference,
141                               statusInformation):
142        mibBuilder = snmpEngine.msgAndPduDsp.mibInstrumController.mibBuilder
143
144        snmpEngineId, = mibBuilder.importSymbols('__SNMP-FRAMEWORK-MIB', 'snmpEngineID')
145        snmpEngineId = snmpEngineId.syntax
146
147        # rfc3412: 7.1.2.b
148        if stateReference is None:
149            raise error.StatusInformation(errorIndication=errind.nonReportable)
150
151        cachedParams = self._cache.popByStateRef(stateReference)
152        msgID = cachedParams['msgID']
153        reqID = cachedParams['reqID']
154        contextEngineId = cachedParams['contextEngineId']
155        contextName = cachedParams['contextName']
156        securityModel = cachedParams['securityModel']
157        securityName = cachedParams['securityName']
158        securityLevel = cachedParams['securityLevel']
159        securityStateReference = cachedParams['securityStateReference']
160        maxMessageSize = cachedParams['msgMaxSize']
161        transportDomain = cachedParams['transportDomain']
162        transportAddress = cachedParams['transportAddress']
163
164        debug.logger & debug.flagMP and debug.logger(
165            'prepareResponseMessage: cache read msgID %s transportDomain %s transportAddress %s by stateReference %s' % (
166                msgID, transportDomain, transportAddress, stateReference))
167
168        # rfc3412: 7.1.3
169        if statusInformation:
170            # rfc3412: 7.1.3a (N/A)
171
172            # rfc3412: 7.1.3b (always discard)
173            raise error.StatusInformation(errorIndication=errind.nonReportable)
174
175        # rfc3412: 7.1.4
176        # Since there's no SNMP engine identification in v1/2c,
177        # set destination contextEngineId to ours
178        if not contextEngineId:
179            contextEngineId = snmpEngineId
180
181        # rfc3412: 7.1.5
182        if not contextName:
183            contextName = null
184
185        # rfc3412: 7.1.6
186        scopedPDU = (contextEngineId, contextName, pdu)
187
188        debug.logger & debug.flagMP and debug.logger(
189            'prepareResponseMessage: using contextEngineId %r contextName %r' % (contextEngineId, contextName))
190
191        msg = self._snmpMsgSpec
192        msg.setComponentByPosition(0, messageProcessingModel)
193        msg.setComponentByPosition(2)
194        msg.getComponentByPosition(2).setComponentByType(
195            pdu.tagSet, pdu, verifyConstraints=False, matchTags=False, matchConstraints=False
196        )
197
198        # att: msgId not set back to PDU as it's up to responder app
199
200        # rfc3412: 7.1.7
201        globalData = (msg,)
202
203        k = int(securityModel)
204        if k in snmpEngine.securityModels:
205            smHandler = snmpEngine.securityModels[k]
206        else:
207            raise error.StatusInformation(
208                errorIndication=errind.unsupportedSecurityModel
209            )
210
211        # set original request-id right prior to PDU serialization
212        pdu.setComponentByPosition(0, reqID)
213
214        # rfc3412: 7.1.8.a
215        (securityParameters, wholeMsg) = smHandler.generateResponseMsg(
216            snmpEngine, self.messageProcessingModelID, globalData,
217            maxMessageSize, securityModel, snmpEngineId, securityName,
218            securityLevel, scopedPDU, securityStateReference
219        )
220
221        # recover unique request-id right after PDU serialization
222        pdu.setComponentByPosition(0, msgID)
223
224        snmpEngine.observer.storeExecutionContext(
225            snmpEngine, 'rfc2576.prepareResponseMessage',
226            dict(transportDomain=transportDomain,
227                 transportAddress=transportAddress,
228                 securityModel=securityModel,
229                 securityName=securityName,
230                 securityLevel=securityLevel,
231                 contextEngineId=contextEngineId,
232                 contextName=contextName,
233                 securityEngineId=snmpEngineId,
234                 communityName=msg.getComponentByPosition(1),
235                 pdu=pdu)
236        )
237        snmpEngine.observer.clearExecutionContext(
238            snmpEngine, 'rfc2576.prepareResponseMessage'
239        )
240
241        return transportDomain, transportAddress, wholeMsg
242
243    # rfc3412: 7.2.1
244
245    def prepareDataElements(self, snmpEngine, transportDomain,
246                            transportAddress, wholeMsg):
247        mibBuilder = snmpEngine.msgAndPduDsp.mibInstrumController.mibBuilder
248
249        # rfc3412: 7.2.2
250        msg, restOfWholeMsg = decoder.decode(wholeMsg, asn1Spec=self._snmpMsgSpec)
251
252        debug.logger & debug.flagMP and debug.logger('prepareDataElements: %s' % (msg.prettyPrint(),))
253
254        if eoo.endOfOctets.isSameTypeWith(msg):
255            raise error.StatusInformation(errorIndication=errind.parseError)
256
257        # rfc3412: 7.2.3
258        msgVersion = msg.getComponentByPosition(0)
259
260        # rfc2576: 5.2.1
261        snmpEngineMaxMessageSize, = mibBuilder.importSymbols('__SNMP-FRAMEWORK-MIB', 'snmpEngineMaxMessageSize')
262        communityName = msg.getComponentByPosition(1)
263        # transportDomain identifies local endpoint
264        securityParameters = (communityName, (transportDomain, transportAddress))
265        messageProcessingModel = int(msg.getComponentByPosition(0))
266        securityModel = messageProcessingModel + 1
267        securityLevel = 1
268
269        # rfc3412: 7.2.4 -- 7.2.5 -> no-op
270
271        try:
272
273            try:
274                smHandler = snmpEngine.securityModels[securityModel]
275
276            except KeyError:
277                raise error.StatusInformation(
278                    errorIndication=errind.unsupportedSecurityModel
279                )
280
281            # rfc3412: 7.2.6
282            (securityEngineId,
283             securityName,
284             scopedPDU,
285             maxSizeResponseScopedPDU,
286             securityStateReference) = smHandler.processIncomingMsg(
287                snmpEngine, messageProcessingModel,
288                snmpEngineMaxMessageSize.syntax, securityParameters,
289                securityModel, securityLevel, wholeMsg, msg
290            )
291
292            debug.logger & debug.flagMP and debug.logger(
293                'prepareDataElements: SM returned securityEngineId %r securityName %r' % (securityEngineId, securityName))
294
295        except error.StatusInformation:
296            statusInformation = sys.exc_info()[1]
297
298            snmpEngine.observer.storeExecutionContext(
299                snmpEngine, 'rfc2576.prepareDataElements:sm-failure',
300                dict(transportDomain=transportDomain,
301                     transportAddress=transportAddress,
302                     securityModel=securityModel,
303                     securityLevel=securityLevel,
304                     securityParameters=securityParameters,
305                     statusInformation=statusInformation)
306            )
307            snmpEngine.observer.clearExecutionContext(
308                snmpEngine, 'rfc2576.prepareDataElements:sm-failure'
309            )
310
311            raise
312
313        # rfc3412: 7.2.6a --> no-op
314
315        # rfc3412: 7.2.7
316        contextEngineId, contextName, pdu = scopedPDU
317
318        # rfc2576: 5.2.1
319        pduVersion = msgVersion
320        pduType = pdu.tagSet
321
322        # rfc3412: 7.2.8, 7.2.9 -> no-op
323
324        # rfc3412: 7.2.10
325        if pduType in rfc3411.responseClassPDUs:
326            # get unique PDU request-id
327            msgID = pdu.getComponentByPosition(0)
328
329            # 7.2.10a
330            try:
331                cachedReqParams = self._cache.popByMsgId(int(msgID))
332            except error.ProtocolError:
333                smHandler.releaseStateInformation(securityStateReference)
334                raise error.StatusInformation(errorIndication=errind.dataMismatch)
335
336            # recover original PDU request-id to return to app
337            pdu.setComponentByPosition(0, cachedReqParams['reqID'])
338
339            debug.logger & debug.flagMP and debug.logger(
340                'prepareDataElements: unique PDU request-id %s replaced with original ID %s' % (
341                    msgID, cachedReqParams['reqID']))
342
343            # 7.2.10b
344            sendPduHandle = cachedReqParams['sendPduHandle']
345        else:
346            sendPduHandle = None
347
348        # no error by default
349        statusInformation = None
350
351        # rfc3412: 7.2.11 -> no-op
352
353        # rfc3412: 7.2.12
354        if pduType in rfc3411.responseClassPDUs:
355            # rfc3412: 7.2.12a -> no-op
356            # rfc3412: 7.2.12b
357            # noinspection PyUnboundLocalVariable
358            if (securityModel != cachedReqParams['securityModel'] or
359                    securityName != cachedReqParams['securityName'] or
360                    securityLevel != cachedReqParams['securityLevel'] or
361                    contextEngineId != cachedReqParams['contextEngineId'] or
362                    contextName != cachedReqParams['contextName']):
363                smHandler.releaseStateInformation(securityStateReference)
364                raise error.StatusInformation(errorIndication=errind.dataMismatch)
365
366            stateReference = None
367
368            snmpEngine.observer.storeExecutionContext(
369                snmpEngine, 'rfc2576.prepareDataElements:response',
370                dict(transportDomain=transportDomain,
371                     transportAddress=transportAddress,
372                     securityModel=securityModel,
373                     securityName=securityName,
374                     securityLevel=securityLevel,
375                     contextEngineId=contextEngineId,
376                     contextName=contextName,
377                     securityEngineId=securityEngineId,
378                     communityName=communityName,
379                     pdu=pdu)
380            )
381            snmpEngine.observer.clearExecutionContext(
382                snmpEngine, 'rfc2576.prepareDataElements:response'
383            )
384
385            # rfc3412: 7.2.12c
386            smHandler.releaseStateInformation(securityStateReference)
387
388            # rfc3412: 7.2.12d
389            return (messageProcessingModel, securityModel,
390                    securityName, securityLevel, contextEngineId,
391                    contextName, pduVersion, pdu, pduType, sendPduHandle,
392                    maxSizeResponseScopedPDU, statusInformation,
393                    stateReference)
394
395        # rfc3412: 7.2.13
396        if pduType in rfc3411.confirmedClassPDUs:
397            # store original PDU request-id and replace it with a unique one
398            reqID = pdu.getComponentByPosition(0)
399            msgID = self._cache.newMsgID()
400            pdu.setComponentByPosition(0, msgID)
401            debug.logger & debug.flagMP and debug.logger(
402                'prepareDataElements: received PDU request-id %s replaced with unique ID %s' % (reqID, msgID))
403
404            # rfc3412: 7.2.13a
405            snmpEngineId, = mibBuilder.importSymbols('__SNMP-FRAMEWORK-MIB', 'snmpEngineID')
406            if securityEngineId != snmpEngineId.syntax:
407                smHandler.releaseStateInformation(securityStateReference)
408                raise error.StatusInformation(
409                    errorIndication=errind.engineIDMismatch
410                )
411
412            # rfc3412: 7.2.13b
413            stateReference = self._cache.newStateReference()
414            self._cache.pushByStateRef(
415                stateReference, msgVersion=messageProcessingModel,
416                msgID=msgID, reqID=reqID, contextEngineId=contextEngineId,
417                contextName=contextName, securityModel=securityModel,
418                securityName=securityName, securityLevel=securityLevel,
419                securityStateReference=securityStateReference,
420                msgMaxSize=snmpEngineMaxMessageSize.syntax,
421                maxSizeResponseScopedPDU=maxSizeResponseScopedPDU,
422                transportDomain=transportDomain,
423                transportAddress=transportAddress
424            )
425
426            snmpEngine.observer.storeExecutionContext(
427                snmpEngine, 'rfc2576.prepareDataElements:confirmed',
428                dict(transportDomain=transportDomain,
429                     transportAddress=transportAddress,
430                     securityModel=securityModel,
431                     securityName=securityName,
432                     securityLevel=securityLevel,
433                     contextEngineId=contextEngineId,
434                     contextName=contextName,
435                     securityEngineId=securityEngineId,
436                     communityName=communityName,
437                     pdu=pdu)
438            )
439            snmpEngine.observer.clearExecutionContext(
440                snmpEngine, 'rfc2576.prepareDataElements:confirmed'
441            )
442
443            debug.logger & debug.flagMP and debug.logger(
444                'prepareDataElements: cached by new stateReference %s' % stateReference)
445
446            # rfc3412: 7.2.13c
447            return (messageProcessingModel, securityModel, securityName,
448                    securityLevel, contextEngineId, contextName,
449                    pduVersion, pdu, pduType, sendPduHandle,
450                    maxSizeResponseScopedPDU, statusInformation,
451                    stateReference)
452
453        # rfc3412: 7.2.14
454        if pduType in rfc3411.unconfirmedClassPDUs:
455            # Pass new stateReference to let app browse request details
456            stateReference = self._cache.newStateReference()
457
458            snmpEngine.observer.storeExecutionContext(
459                snmpEngine, 'rfc2576.prepareDataElements:unconfirmed',
460                dict(transportDomain=transportDomain,
461                     transportAddress=transportAddress,
462                     securityModel=securityModel,
463                     securityName=securityName,
464                     securityLevel=securityLevel,
465                     contextEngineId=contextEngineId,
466                     contextName=contextName,
467                     securityEngineId=securityEngineId,
468                     communityName=communityName,
469                     pdu=pdu)
470            )
471            snmpEngine.observer.clearExecutionContext(
472                snmpEngine, 'rfc2576.prepareDataElements:unconfirmed'
473            )
474
475            # This is not specified explicitly in RFC
476            smHandler.releaseStateInformation(securityStateReference)
477
478            return (messageProcessingModel, securityModel, securityName,
479                    securityLevel, contextEngineId, contextName,
480                    pduVersion, pdu, pduType, sendPduHandle,
481                    maxSizeResponseScopedPDU, statusInformation,
482                    stateReference)
483
484        smHandler.releaseStateInformation(securityStateReference)
485        raise error.StatusInformation(errorIndication=errind.unsupportedPDUtype)
486
487
488class SnmpV2cMessageProcessingModel(SnmpV1MessageProcessingModel):
489    messageProcessingModelID = univ.Integer(1)  # SNMPv2c
490    snmpMsgSpec = v2c.Message
491