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