1#
2# This file is part of snmpclitools software.
3#
4# Copyright (c) 2005-2018, Ilya Etingof <etingof@gmail.com>
5# License: http://snmplabs.com/snmpclitools/license.html
6#
7# C/L interface to MIB variables. Mimics Net-SNMP CLI.
8#
9import os
10from pyasn1.type import univ
11from pyasn1.type import namedval
12from snmpclitools.cli import base
13from pysnmp.proto import rfc1902
14from pysnmp.smi import builder, compiler
15from pysnmp import error
16
17defaultMibSourceUrl = 'http://mibs.snmplabs.com/asn1/@mib@'
18defaultMibBorrowerUrl = 'http://mibs.snmplabs.com/pysnmp/fulltexts/@mib@'
19
20
21def getUsage():
22    return """\
23MIB options:
24   -m MIB[:...]   load given list of MIBs (ALL loads all compiled MIBs)
25   -M DIR[:...]   look in given list of directories for MIBs
26   -P MIBOPTS     Toggle various defaults controlling MIB parsing:
27              XS: search for ASN.1 MIBs in remote directories specified
28                  in URL form. The @mib@ token in the URL is substituted
29                  with actual MIB to be downloaded. Default repository
30                  address is %s
31              XB: search for pysnmp MIBs in remote directories specified
32                  in URL form. The @mib@ token in the URL is substituted
33                  with actual MIB to be downloaded. Default repository
34                  address is %s
35   -O OUTOPTS     Toggle various defaults controlling output display:
36              q:  removes the equal sign and type information
37              Q:  removes the type information
38              f:  print full OIDs on output
39              s:  print only last symbolic element of OID
40              S:  print MIB module-id plus last element
41              u:  print OIDs using UCD-style prefix suppression
42              n:  print OIDs numerically
43              e:  print enums numerically
44              b:  do not break OID indexes down
45              E:  include a " to escape the quotes in indices
46              X:  place square brackets around each index
47              T:  print value in hex
48              v:  print values only (not OID = value)
49              U:  don't print units
50              t:  output timeticks values as raw numbers
51   -I INOPTS      Toggle various defaults controlling input parsing:
52              h:  don't apply DISPLAY-HINTs
53              u:  top-level OIDs must have '.' prefix (UCD-style)
54""" % (defaultMibSourceUrl, defaultMibBorrowerUrl)
55
56# Scanner
57
58
59class MibViewScannerMixIn:
60    def t_mibfiles(self, s):
61        r' -m '
62        self.rv.append(base.ConfigToken('mibfiles'))
63
64    def t_mibdirs(self, s):
65        r' -M '
66        self.rv.append(base.ConfigToken('mibdirs'))
67
68    def t_parseropts(self, s):
69        r' -P '
70        self.rv.append(base.ConfigToken('parseropts'))
71
72    def t_outputopts(self, s):
73        r' -O '
74        self.rv.append(base.ConfigToken('outputopts'))
75
76    def t_inputopts(self, s):
77        r' -I '
78        self.rv.append(base.ConfigToken('inputopts'))
79
80# Parser
81
82
83class MibViewParserMixIn:
84    def p_mibView(self, args):
85        '''
86        Option ::= GeneralOption
87        Option ::= ParserOption
88        Option ::= OutputOption
89        Option ::= InputOption
90
91        GeneralOption ::= MibDirList
92        MibDirList ::= mibdirs MibDirs
93        MibDirList ::= mibdirs whitespace MibDirs
94        MibDirs ::= MibDir semicolon MibDirs
95        MibDirs ::= MibDir
96        MibDir ::= string
97        GeneralOption ::= MibFileList
98        MibFileList ::= mibfiles MibFiles
99        MibFileList ::= mibfiles whitespace MibFiles
100        MibFiles ::= MibFile semicolon MibFiles
101        MibFiles ::= MibFile
102        MibFile ::= string
103
104        ParserOption ::= parseropts string
105        ParserOption ::= parseropts whitespace string
106        ParserOption ::= parseropts string whitespace Url
107        ParserOption ::= parseropts whitespace string whitespace Url
108        Url ::= string semicolon string
109
110        OutputOption ::= outputopts string
111        OutputOption ::= outputopts whitespace string
112
113        InputOption ::= inputopts string
114        InputOption ::= inputopts whitespace string
115        '''
116
117# Generator
118
119
120class __MibViewGenerator(base.GeneratorTemplate):
121    # Load MIB modules
122    def n_MibFile(self, cbCtx, node):
123        snmpEngine, ctx = cbCtx
124        mibBuilder = snmpEngine.getMibBuilder()
125        if node[0].attr.lower() == 'all':
126            mibBuilder.loadModules()
127        else:
128            mibBuilder.loadModules(node[0].attr)
129
130    def n_MibDir(self, cbCtx, node):
131        snmpEngine, ctx = cbCtx
132        if 'MibDir' not in ctx:
133            ctx['MibDir'] = []
134        ctx['MibDir'].append(node[0].attr)
135
136    def n_Url(self, cbCtx, node):
137        snmpEngine, ctx = cbCtx
138        ctx['Url'] = node[0].attr + ':' + node[2].attr
139
140    def n_ParserOption_exit(self, cbCtx, node):
141        snmpEngine, ctx = cbCtx
142        opt = node[1].attr or node[2].attr
143        for c in opt:
144            if c == 'XS':
145                if 'MibDir' not in ctx:
146                    ctx['MibDir'] = []
147                if 'Url' not in ctx:
148                    raise error.PySnmpError('Missing URL for option')
149                ctx['MibDir'].append(ctx['Url'])
150                del ctx['Url']
151            elif c == 'XB':
152                if 'MibBorrowers' not in ctx:
153                    ctx['MibBorrowers'] = []
154                if 'Url' not in ctx:
155                    raise error.PySnmpError('Missing URL for option')
156                ctx['MibBorrowers'].append(ctx['Url'])
157                del ctx['Url']
158
159    def n_OutputOption(self, cbCtx, node):
160        snmpEngine, ctx = cbCtx
161        mibViewProxy = ctx['mibViewProxy']
162        opt = node[1].attr or node[2].attr
163        for c in opt:
164            if c == 'q':
165                mibViewProxy.buildEqualSign = False
166                mibViewProxy.buildTypeInfo = False
167            elif c == 'Q':
168                mibViewProxy.buildTypeInfo = False
169            elif c == 'f':
170                mibViewProxy.buildModInfo = False
171                mibViewProxy.buildObjectDesc = False
172                mibViewProxy.buildAbsoluteName = True
173            elif c == 's':
174                mibViewProxy.buildModInfo = False
175                mibViewProxy.buildObjectDesc = True
176            elif c == 'S':
177                mibViewProxy.buildObjectDesc = True
178            elif c == 'n':
179                mibViewProxy.buildObjectDesc = False
180                mibViewProxy.buildModInfo = False
181                mibViewProxy.buildNumericName = True
182                mibViewProxy.buildNumericIndices = True
183                mibViewProxy.buildAbsoluteName = True
184            elif c == 'e':
185                mibViewProxy.buildEnums = False
186            elif c == 'b':
187                mibViewProxy.buildNumericIndices = True
188            elif c == 'E':
189                mibViewProxy.buildEscQuotes = True
190            elif c == 'X':
191                mibViewProxy.buildSquareBrackets = True
192            elif c == 'T':
193                mibViewProxy.buildHexVals = True
194            elif c == 'v':
195                mibViewProxy.buildObjectName = False
196            elif c == 'U':
197                mibViewProxy.buildUnits = False
198            elif c == 't':
199                mibViewProxy.buildRawTimeTicks = True
200                pass
201            elif c == 'R':
202                mibViewProxy.buildRawVals = True
203            else:
204                raise error.PySnmpError(
205                    'Unknown output option %s at %s' % (c, self)
206                )
207
208    def n_InputOption(self, cbCtx, node):
209        snmpEngine, ctx = cbCtx
210        mibViewProxy = ctx['mibViewProxy']
211        opt = node[1].attr or node[2].attr
212        for c in opt:
213            if c == 'R':
214                pass
215            elif c == 'b':
216                pass
217            elif c == 'u':
218                mibViewProxy.defaultOidPrefix = (
219                    'iso', 'org', 'dod', 'internet', 'mgmt', 'mib-2'
220                )
221            elif c == 'r':
222                pass
223            elif c == 'h':
224                pass
225            else:
226                raise error.PySnmpError(
227                    'Unknown input option %s at %s' % (c, self)
228                )
229
230
231def generator(cbCtx, ast):
232    snmpEngine, ctx = cbCtx
233    if 'mibViewProxy' not in ctx:
234        ctx['mibViewProxy'] = MibViewProxy(ctx['mibViewController'])
235
236    compiler.addMibCompiler(snmpEngine.getMibBuilder())
237
238    snmpEngine, ctx = __MibViewGenerator().preorder((snmpEngine, ctx), ast)
239
240    if 'MibDir' not in ctx:
241        ctx['MibDir'] = [defaultMibSourceUrl]
242    if 'MibBorrowers' not in ctx:
243        ctx['MibBorrowers'] = [defaultMibBorrowerUrl]
244
245    compiler.addMibCompiler(snmpEngine.getMibBuilder(),
246                            sources=ctx['MibDir'],
247                            borrowers=ctx['MibBorrowers'])
248    return snmpEngine, ctx
249
250
251class UnknownSyntax:
252    def prettyOut(self, val):
253        return str(val)
254unknownSyntax = UnknownSyntax()
255
256#  Proxy MIB view
257
258
259class MibViewProxy:
260    # Defaults
261    defaultOidPrefix = (
262        'iso', 'org', 'dod', 'internet', 'mgmt', 'mib-2', 'system'
263    )
264    defaultMibs = ('SNMPv2-MIB',)
265    defaultMibDirs = ()
266
267    # MIB parsing options
268    # currently N/A
269
270    # MIB output options
271    buildObjectName = True
272    buildValue = True
273    buildModInfo = True
274    buildObjectDesc = True
275    buildNumericName = False
276    buildAbsoluteName = False
277    buildEnums = True
278    buildNumericIndices = False
279    buildEqualSign = True
280    buildTypeInfo = True
281    buildEscQuotes = False
282    buildSquareBrackets = False
283    buildHexVals = False
284    buildRawVals = False
285    buildRawTimeTicks = False
286    buildGuessedStringVals = True
287    buildUnits = True
288
289    # MIB input options
290    parseAsRandomAccessMib = True
291    parseAsRegExp = False
292    parseAsRelativeOid = True
293    parseAndCheckIndices = True
294    parseAsDisplayHint = True
295
296    def __init__(self, mibViewController):
297        if 'PYSNMPOIDPREFIX' in os.environ:
298            self.defaultOidPrefix = os.environ['PYSNMPOIDPREFIX']
299        if 'PYSNMPMIBS' in os.environ:
300            self.defaultMibs = os.environ['PYSNMPMIBS'].split(':')
301        if 'PYSNMPMIBDIRS' in os.environ:
302            self.defaultMibDirs = os.environ['PYSNMPMIBDIRS'].split(':')
303        if self.defaultMibDirs:
304            mibViewController.mibBuilder.setMibSources(
305                *(mibViewController.mibBuilder.getMibSources() +
306                  tuple([builder.ZipMibSource(m).init() for m in self.defaultMibDirs]))
307            )
308        if self.defaultMibs:
309            mibViewController.mibBuilder.loadModules(*self.defaultMibs)
310        self.__oidValue = univ.ObjectIdentifier()
311        self.__intValue = univ.Integer()
312        self.__timeValue = rfc1902.TimeTicks()
313        self.__bitsValue = rfc1902.Bits()
314
315    def getPrettyOidVal(self, mibViewController, oid, val):
316        prefix, label, suffix = mibViewController.getNodeName(oid)
317        modName, nodeDesc, _suffix = mibViewController.getNodeLocation(prefix)
318        out = ''
319        # object name
320        if self.buildObjectName:
321            if self.buildModInfo:
322                out = '%s::' % modName
323            if self.buildObjectDesc:
324                out += nodeDesc
325            else:
326                if self.buildNumericName:
327                    name = prefix
328                else:
329                    name = label
330                if not self.buildAbsoluteName:
331                    name = name[len(self.defaultOidPrefix):]
332                out += '.'.join([str(x) for x in name])
333
334            if suffix:
335                if suffix == (0,):
336                    out += '.0'
337                else:
338                    m, n, s = mibViewController.getNodeLocation(prefix[:-1])
339                    rowNode, = mibViewController.mibBuilder.importSymbols(
340                        m, n
341                    )
342                    if self.buildNumericIndices:
343                        out += '.' + '.'.join([str(x) for x in suffix])
344                    else:
345                        try:
346                            for i in rowNode.getIndicesFromInstId(suffix):
347                                if self.buildEscQuotes:
348                                    out += '.\\\"%s\\\"' % i.prettyOut(i)
349                                elif self.buildSquareBrackets:
350                                    out += '.[%s]' % i.prettyOut(i)
351                                else:
352                                    out += '.\"%s\"' % i.prettyOut(i)
353                        except Exception:
354                            out += '.' + '.'.join(
355                                [str(x) for x in suffix]
356                            )
357
358        if self.buildObjectName and self.buildValue:
359            if self.buildEqualSign:
360                out += ' = '
361            else:
362                out += ' '
363
364        # Value
365        if self.buildValue:
366            if isinstance(val, univ.Null):
367                return out + val.prettyPrint()
368            mibNode, = mibViewController.mibBuilder.importSymbols(
369                modName, nodeDesc
370            )
371            if hasattr(mibNode, 'syntax'):
372                syntax = mibNode.syntax
373            else:
374                syntax = val
375            if syntax is None:  # lame Agent may return a non-instance OID
376                syntax = unknownSyntax
377            if self.buildTypeInfo:
378                out += '%s: ' % syntax.__class__.__name__
379            if self.buildRawVals:
380                out += str(val)
381            elif self.buildHexVals:  # XXX make it always in hex?
382                if self.__intValue.isSuperTypeOf(val):
383                    out += '%x' % int(val)
384                elif self.__timeValue.isSameTypeWith(val):
385                    out += '%x' % int(val)
386                elif self.__oidValue.isSuperTypeOf(val):
387                    out += ' '.join(['%x' % x for x in tuple(val)])
388                else:
389                    out += ' '.join(['%.2x' % x for x in val.asNumbers()])
390            elif self.__timeValue.isSameTypeWith(val):
391                if self.buildRawTimeTicks:
392                    out += str(int(val))
393                else:  # TimeTicks is not a TC
394                    val = int(val)
395                    d, m = divmod(val, 8640000)
396                    out += '%d days ' % d
397                    d, m = divmod(m, 360000)
398                    out += '%d:' % d
399                    d, m = divmod(m, 6000)
400                    out += '%d:' % d
401                    d, m = divmod(m, 100)
402                    out += '%d.%d' % (d, m)
403            elif self.__oidValue.isSuperTypeOf(val):
404                oid, label, suffix = mibViewController.getNodeName(val)
405                out += '.'.join(
406                    label + tuple([str(x) for x in suffix])
407                )
408            elif (not self.buildEnums and
409                      (self.__intValue.isSuperTypeOf(val) or
410                       self.__bitsValue.isSuperTypeOf(val))):
411                out += syntax.clone(val, namedValues=namedval.NamedValues()).prettyPrint()
412            else:
413                out += syntax.clone(val).prettyPrint()
414
415            if self.buildUnits:
416                if hasattr(mibNode, 'getUnits'):
417                    out += ' %s' % mibNode.getUnits()
418
419        return out
420
421    def setPrettyOidValue(self, oid, val, t):
422        return oid, val
423