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