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.compat.octets import null 9from pysnmp.entity.rfc3413 import config 10from pysnmp.proto.proxy import rfc2576 11from pysnmp.proto import rfc3411 12from pysnmp.proto.api import v2c 13from pysnmp.proto import errind, error 14from pysnmp.smi import view, rfc1902 15from pysnmp import nextid 16from pysnmp import debug 17 18getNextHandle = nextid.Integer(0x7fffffff) 19 20 21class NotificationOriginator(object): 22 acmID = 3 # default MIB access control method to use 23 24 def __init__(self, **options): 25 self.__pendingReqs = {} 26 self.__pendingNotifications = {} 27 self.snmpContext = options.pop('snmpContext', None) # this is deprecated 28 self.__options = options 29 30 def processResponsePdu(self, snmpEngine, messageProcessingModel, 31 securityModel, securityName, securityLevel, 32 contextEngineId, contextName, pduVersion, 33 PDU, statusInformation, sendPduHandle, cbInfo): 34 sendRequestHandle, cbFun, cbCtx = cbInfo 35 36 # 3.3.6d 37 if sendPduHandle not in self.__pendingReqs: 38 raise error.ProtocolError('Missing sendPduHandle %s' % sendPduHandle) 39 40 (origTransportDomain, origTransportAddress, 41 origMessageProcessingModel, origSecurityModel, 42 origSecurityName, origSecurityLevel, origContextEngineId, 43 origContextName, origPdu, origTimeout, 44 origRetryCount, origRetries, origDiscoveryRetries) = self.__pendingReqs.pop(sendPduHandle) 45 46 snmpEngine.transportDispatcher.jobFinished(id(self)) 47 48 if statusInformation: 49 debug.logger & debug.flagApp and debug.logger( 50 'processResponsePdu: sendRequestHandle %s, sendPduHandle %s statusInformation %s' % ( 51 sendRequestHandle, sendPduHandle, statusInformation)) 52 53 errorIndication = statusInformation['errorIndication'] 54 55 if errorIndication in (errind.notInTimeWindow, errind.unknownEngineID): 56 origDiscoveryRetries += 1 57 origRetries = 0 58 else: 59 origDiscoveryRetries = 0 60 origRetries += 1 61 62 if origRetries > origRetryCount or origDiscoveryRetries > self.__options.get('discoveryRetries', 4): 63 debug.logger & debug.flagApp and debug.logger( 64 'processResponsePdu: sendRequestHandle %s, sendPduHandle %s retry count %d exceeded' % ( 65 sendRequestHandle, sendPduHandle, origRetries)) 66 cbFun(snmpEngine, sendRequestHandle, errorIndication, None, cbCtx) 67 return 68 69 # Convert timeout in seconds into timeout in timer ticks 70 timeoutInTicks = float(origTimeout) / 100 / snmpEngine.transportDispatcher.getTimerResolution() 71 72 # User-side API assumes SMIv2 73 if messageProcessingModel == 0: 74 reqPDU = rfc2576.v2ToV1(origPdu) 75 pduVersion = 0 76 else: 77 reqPDU = origPdu 78 pduVersion = 1 79 80 # 3.3.6a 81 try: 82 sendPduHandle = snmpEngine.msgAndPduDsp.sendPdu( 83 snmpEngine, origTransportDomain, origTransportAddress, 84 origMessageProcessingModel, origSecurityModel, 85 origSecurityName, origSecurityLevel, 86 origContextEngineId, origContextName, pduVersion, 87 reqPDU, True, timeoutInTicks, self.processResponsePdu, 88 (sendRequestHandle, cbFun, cbCtx) 89 ) 90 except error.StatusInformation: 91 statusInformation = sys.exc_info()[1] 92 debug.logger & debug.flagApp and debug.logger( 93 'processResponsePdu: sendRequestHandle %s: sendPdu() failed with %r ' % ( 94 sendRequestHandle, statusInformation)) 95 cbFun(snmpEngine, sendRequestHandle, 96 statusInformation['errorIndication'], None, cbCtx) 97 return 98 99 snmpEngine.transportDispatcher.jobStarted(id(self)) 100 101 debug.logger & debug.flagApp and debug.logger( 102 'processResponsePdu: sendRequestHandle %s, sendPduHandle %s, timeout %d, retry %d of %d' % ( 103 sendRequestHandle, sendPduHandle, origTimeout, origRetries, origRetryCount)) 104 105 # 3.3.6b 106 self.__pendingReqs[sendPduHandle] = ( 107 origTransportDomain, origTransportAddress, 108 origMessageProcessingModel, origSecurityModel, 109 origSecurityName, origSecurityLevel, 110 origContextEngineId, origContextName, origPdu, 111 origTimeout, origRetryCount, origRetries, origDiscoveryRetries 112 ) 113 return 114 115 # 3.3.6c 116 # User-side API assumes SMIv2 117 if messageProcessingModel == 0: 118 PDU = rfc2576.v1ToV2(PDU, origPdu) 119 120 cbFun(snmpEngine, sendRequestHandle, None, PDU, cbCtx) 121 122 def sendPdu(self, snmpEngine, targetName, contextEngineId, 123 contextName, pdu, cbFun=None, cbCtx=None): 124 (transportDomain, transportAddress, timeout, 125 retryCount, params) = config.getTargetAddr(snmpEngine, targetName) 126 127 (messageProcessingModel, securityModel, securityName, 128 securityLevel) = config.getTargetParams(snmpEngine, params) 129 130 # User-side API assumes SMIv2 131 if messageProcessingModel == 0: 132 reqPDU = rfc2576.v2ToV1(pdu) 133 pduVersion = 0 134 else: 135 reqPDU = pdu 136 pduVersion = 1 137 138 # 3.3.5 139 if reqPDU.tagSet in rfc3411.confirmedClassPDUs: 140 # Convert timeout in seconds into timeout in timer ticks 141 timeoutInTicks = float(timeout) / 100 / snmpEngine.transportDispatcher.getTimerResolution() 142 143 sendRequestHandle = getNextHandle() 144 145 # 3.3.6a 146 sendPduHandle = snmpEngine.msgAndPduDsp.sendPdu( 147 snmpEngine, transportDomain, transportAddress, 148 messageProcessingModel, securityModel, securityName, 149 securityLevel, contextEngineId, contextName, 150 pduVersion, reqPDU, True, timeoutInTicks, 151 self.processResponsePdu, (sendRequestHandle, cbFun, cbCtx) 152 ) 153 154 debug.logger & debug.flagApp and debug.logger( 155 'sendPdu: sendPduHandle %s, timeout %d' % (sendPduHandle, timeout)) 156 157 # 3.3.6b 158 self.__pendingReqs[sendPduHandle] = ( 159 transportDomain, transportAddress, messageProcessingModel, 160 securityModel, securityName, securityLevel, contextEngineId, 161 contextName, pdu, timeout, retryCount, 0, 0 162 ) 163 snmpEngine.transportDispatcher.jobStarted(id(self)) 164 else: 165 snmpEngine.msgAndPduDsp.sendPdu( 166 snmpEngine, transportDomain, transportAddress, 167 messageProcessingModel, securityModel, 168 securityName, securityLevel, contextEngineId, 169 contextName, pduVersion, reqPDU, False 170 ) 171 172 sendRequestHandle = None 173 174 debug.logger & debug.flagApp and debug.logger('sendPdu: message sent') 175 176 return sendRequestHandle 177 178 def processResponseVarBinds(self, snmpEngine, sendRequestHandle, 179 errorIndication, pdu, cbCtx): 180 notificationHandle, cbFun, cbCtx = cbCtx 181 182 self.__pendingNotifications[notificationHandle].remove(sendRequestHandle) 183 184 debug.logger & debug.flagApp and debug.logger( 185 'processResponseVarBinds: notificationHandle %s, sendRequestHandle %s, errorIndication %s, pending requests %s' % ( 186 notificationHandle, sendRequestHandle, errorIndication, self.__pendingNotifications[notificationHandle])) 187 188 if not self.__pendingNotifications[notificationHandle]: 189 debug.logger & debug.flagApp and debug.logger( 190 'processResponseVarBinds: notificationHandle %s, sendRequestHandle %s -- completed' % ( 191 notificationHandle, sendRequestHandle)) 192 del self.__pendingNotifications[notificationHandle] 193 cbFun(snmpEngine, sendRequestHandle, errorIndication, 194 pdu and v2c.apiPDU.getErrorStatus(pdu) or 0, 195 pdu and v2c.apiPDU.getErrorIndex(pdu, muteErrors=True) or 0, 196 pdu and v2c.apiPDU.getVarBinds(pdu) or (), 197 cbCtx) 198 199 # 200 # Higher-level API to Notification Originator. Supports multiple 201 # targets, automatic var-binding formation and is fully LCD-driven. 202 # 203 def sendVarBinds(self, snmpEngine, notificationTarget, contextEngineId, 204 contextName, varBinds=(), cbFun=None, cbCtx=None): 205 debug.logger & debug.flagApp and debug.logger( 206 'sendVarBinds: notificationTarget %s, contextEngineId %s, contextName "%s", varBinds %s' % ( 207 notificationTarget, contextEngineId or '<default>', contextName, varBinds)) 208 209 if contextName: 210 __SnmpAdminString, = snmpEngine.msgAndPduDsp.mibInstrumController.mibBuilder.importSymbols( 211 'SNMP-FRAMEWORK-MIB', 'SnmpAdminString') 212 contextName = __SnmpAdminString(contextName) 213 214 # 3.3 215 (notifyTag, notifyType) = config.getNotificationInfo(snmpEngine, notificationTarget) 216 217 notificationHandle = getNextHandle() 218 219 debug.logger & debug.flagApp and debug.logger( 220 'sendVarBinds: notificationHandle %s, notifyTag %s, notifyType %s' % ( 221 notificationHandle, notifyTag, notifyType)) 222 223 varBinds = [(v2c.ObjectIdentifier(x), y) for x, y in varBinds] 224 225 # 3.3.2 & 3.3.3 226 snmpTrapOID, sysUpTime = snmpEngine.msgAndPduDsp.mibInstrumController.mibBuilder.importSymbols('__SNMPv2-MIB', 227 'snmpTrapOID', 228 'sysUpTime') 229 230 for idx in range(len(varBinds)): 231 if idx and varBinds[idx][0] == sysUpTime.getName(): 232 if varBinds[0][0] == sysUpTime.getName(): 233 varBinds[0] = varBinds[idx] 234 else: 235 varBinds.insert(0, varBinds[idx]) 236 del varBinds[idx] 237 238 if varBinds[0][0] != sysUpTime.getName(): 239 varBinds.insert(0, (v2c.ObjectIdentifier(sysUpTime.getName()), 240 sysUpTime.getSyntax().clone())) 241 242 if len(varBinds) < 2 or varBinds[1][0] != snmpTrapOID.getName(): 243 varBinds.insert(1, (v2c.ObjectIdentifier(snmpTrapOID.getName()), 244 snmpTrapOID.getSyntax())) 245 246 sendRequestHandle = -1 247 248 debug.logger & debug.flagApp and debug.logger('sendVarBinds: final varBinds %s' % (varBinds,)) 249 250 for targetAddrName in config.getTargetNames(snmpEngine, notifyTag): 251 (transportDomain, transportAddress, timeout, 252 retryCount, params) = config.getTargetAddr(snmpEngine, 253 targetAddrName) 254 (messageProcessingModel, securityModel, securityName, 255 securityLevel) = config.getTargetParams(snmpEngine, params) 256 257 # 3.3.1 XXX 258 # XXX filtering's yet to be implemented 259 # filterProfileName = config.getNotifyFilterProfile(params) 260 261 # (filterSubtree, filterMask, 262 # filterType) = config.getNotifyFilter(filterProfileName) 263 264 debug.logger & debug.flagApp and debug.logger( 265 'sendVarBinds: notificationHandle %s, notifyTag %s yields: transportDomain %s, transportAddress %r, securityModel %s, securityName %s, securityLevel %s' % ( 266 notificationHandle, notifyTag, transportDomain, transportAddress, securityModel, 267 securityName, securityLevel)) 268 269 for varName, varVal in varBinds: 270 if varName in (sysUpTime.name, snmpTrapOID.name): 271 continue 272 try: 273 snmpEngine.accessControlModel[self.acmID].isAccessAllowed( 274 snmpEngine, securityModel, securityName, 275 securityLevel, 'notify', contextName, varName 276 ) 277 278 debug.logger & debug.flagApp and debug.logger( 279 'sendVarBinds: ACL succeeded for OID %s securityName %s' % (varName, securityName)) 280 281 except error.StatusInformation: 282 debug.logger & debug.flagApp and debug.logger( 283 'sendVarBinds: ACL denied access for OID %s securityName %s, droppping notification' % ( 284 varName, securityName)) 285 return 286 287 # 3.3.4 288 if notifyType == 1: 289 pdu = v2c.SNMPv2TrapPDU() 290 elif notifyType == 2: 291 pdu = v2c.InformRequestPDU() 292 else: 293 raise error.ProtocolError('Unknown notify-type %r', notifyType) 294 295 v2c.apiPDU.setDefaults(pdu) 296 v2c.apiPDU.setVarBinds(pdu, varBinds) 297 298 # 3.3.5 299 try: 300 sendRequestHandle = self.sendPdu( 301 snmpEngine, targetAddrName, contextEngineId, 302 contextName, pdu, self.processResponseVarBinds, 303 (notificationHandle, cbFun, cbCtx) 304 ) 305 306 except error.StatusInformation: 307 statusInformation = sys.exc_info()[1] 308 debug.logger & debug.flagApp and debug.logger( 309 'sendVarBinds: sendRequestHandle %s: sendPdu() failed with %r' % ( 310 sendRequestHandle, statusInformation)) 311 if notificationHandle not in self.__pendingNotifications or \ 312 not self.__pendingNotifications[notificationHandle]: 313 if notificationHandle in self.__pendingNotifications: 314 del self.__pendingNotifications[notificationHandle] 315 if cbFun: 316 cbFun(snmpEngine, notificationHandle, 317 statusInformation['errorIndication'], 0, 0, (), 318 cbCtx) 319 return notificationHandle 320 321 debug.logger & debug.flagApp and debug.logger( 322 'sendVarBinds: notificationHandle %s, sendRequestHandle %s, timeout %d' % ( 323 notificationHandle, sendRequestHandle, timeout)) 324 325 if notifyType == 2: 326 if notificationHandle not in self.__pendingNotifications: 327 self.__pendingNotifications[notificationHandle] = set() 328 self.__pendingNotifications[notificationHandle].add(sendRequestHandle) 329 330 debug.logger & debug.flagApp and debug.logger( 331 'sendVarBinds: notificationHandle %s, sendRequestHandle %s, notification(s) sent' % ( 332 notificationHandle, sendRequestHandle)) 333 334 return notificationHandle 335 336 337# 338# Obsolete, compatibility interfaces. 339# 340 341def _sendNotificationCbFun(snmpEngine, sendRequestHandle, errorIndication, 342 errorStatus, errorIndex, varBinds, cbCtx): 343 cbFun, cbCtx = cbCtx 344 345 try: 346 # we need to pass response PDU information to user for INFORMs 347 cbFun(sendRequestHandle, errorIndication, 348 errorStatus, errorIndex, varBinds, cbCtx) 349 except TypeError: 350 # a backward compatible way of calling user function 351 cbFun(sendRequestHandle, errorIndication, cbCtx) 352 353 354def _sendNotification(self, snmpEngine, notificationTarget, notificationName, 355 additionalVarBinds=(), cbFun=None, cbCtx=None, 356 contextName=null, instanceIndex=None): 357 if self.snmpContext is None: 358 raise error.ProtocolError('SNMP context not specified') 359 360 # 361 # Here we first expand trap OID into associated OBJECTS 362 # and then look them up at context-specific MIB 363 # 364 365 mibViewController = snmpEngine.getUserContext('mibViewController') 366 if not mibViewController: 367 mibViewController = view.MibViewController(snmpEngine.getMibBuilder()) 368 snmpEngine.setUserContext(mibViewController=mibViewController) 369 370 # Support the following syntax: 371 # '1.2.3.4' 372 # (1,2,3,4) 373 # ('MIB', 'symbol') 374 if isinstance(notificationName, (tuple, list)) and \ 375 notificationName and isinstance(notificationName[0], str): 376 notificationName = rfc1902.ObjectIdentity(*notificationName) 377 else: 378 notificationName = rfc1902.ObjectIdentity(notificationName) 379 380 varBinds = rfc1902.NotificationType(notificationName, 381 instanceIndex=instanceIndex) 382 varBinds.resolveWithMib(mibViewController) 383 384 mibInstrumController = self.snmpContext.getMibInstrum(contextName) 385 386 varBinds = varBinds[:1] + mibInstrumController.readVars(varBinds[1:]) 387 388 return self.sendVarBinds(snmpEngine, notificationTarget, 389 self.snmpContext.contextEngineId, 390 contextName, varBinds + list(additionalVarBinds), 391 _sendNotificationCbFun, (cbFun, cbCtx)) 392 393 394# install compatibility wrapper 395NotificationOriginator.sendNotification = _sendNotification 396 397# XXX 398# move/group/implement config setting/retrieval at a stand-alone module 399