This file is indexed.

/usr/share/check_mk/modules/snmp.py is in check-mk-server 1.1.12-1ubuntu1.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
#!/usr/bin/python
# -*- encoding: utf-8; py-indent-offset: 4 -*-
# +------------------------------------------------------------------+
# |             ____ _               _        __  __ _  __           |
# |            / ___| |__   ___  ___| | __   |  \/  | |/ /           |
# |           | |   | '_ \ / _ \/ __| |/ /   | |\/| | ' /            |
# |           | |___| | | |  __/ (__|   <    | |  | | . \            |
# |            \____|_| |_|\___|\___|_|\_\___|_|  |_|_|\_\           |
# |                                                                  |
# | Copyright Mathias Kettner 2010             mk@mathias-kettner.de |
# +------------------------------------------------------------------+
#
# This file is part of Check_MK.
# The official homepage is at http://mathias-kettner.de/check_mk.
#
# check_mk is free software;  you can redistribute it and/or modify it
# under the  terms of the  GNU General Public License  as published by
# the Free Software Foundation in version 2.  check_mk is  distributed
# in the hope that it will be useful, but WITHOUT ANY WARRANTY;  with-
# out even the implied warranty of  MERCHANTABILITY  or  FITNESS FOR A
# PARTICULAR PURPOSE. See the  GNU General Public License for more de-
# ails.  You should have  received  a copy of the  GNU  General Public
# License along with GNU Make; see the file  COPYING.  If  not,  write
# to the Free Software Foundation, Inc., 51 Franklin St,  Fifth Floor,
# Boston, MA 02110-1301 USA.

# This module is needed only for SNMP based checks

OID_END    =  0
OID_STRING = -1
OID_BIN    = -2

def strip_snmp_value(value):
    v = value.strip()
    if v.startswith('"'):
        v = v[1:-1]
        if len(v) > 2 and is_hex_string(v):
            return convert_from_hex(v)
        else:
            return v.strip()
    else:
        return v.strip()

def is_hex_string(value):
    # as far as I remember, snmpwalk puts a trailing space within
    # the quotes in case of hex strings. So we require that space
    # to be present in order make sure, we really deal with a hex string.
    if value[-1] != ' ':
        return False
    hexdigits = "0123456789abcdefABCDEF"
    n = 0
    for x in value:
        if n % 3 == 2:
            if x != ' ':
                return False
        else:
            if x not in hexdigits:
                return False
        n += 1
    return True

def convert_from_hex(value):
    hexparts = value.split()
    r = ""
    for hx in hexparts:
        r += chr(int(hx, 16))
    return r


def oid_to_bin(oid):
    return u"".join([ unichr(int(p)) for p in oid.strip(".").split(".") ])


def snmpwalk_on_suboid(hostname, ip, oid):
    portspec = snmp_port_spec(hostname)
    command = snmp_walk_command(hostname) + \
             " -OQ -OU -On -Ot %s%s %s 2>/dev/null" % (ip, portspec, oid)
    if opt_debug:
        sys.stderr.write('   Running %s\n' % (command,))
    snmp_process = os.popen(command, "r").xreadlines()

    # Ugly(1): in some cases snmpwalk inserts line feed within one
    # dataset. This happens for example on hexdump outputs longer
    # than a few bytes. Those dumps are enclosed in double quotes.
    # So if the value begins with a double quote, but the line
    # does not end with a double quote, we take the next line(s) as
    # a continuation line.
    rowinfo = []
    try:
        while True: # walk through all lines
            line = snmp_process.next().strip()
            parts = line.split('=', 1)
            if len(parts) < 2:
                continue # broken line, must contain =
            oid = parts[0].strip()
            value = parts[1].strip()
            # Filter out silly error messages from snmpwalk >:-P
            if value.startswith('No more variables') or value.startswith('End of MIB') \
               or value.startswith('No Such Object available') or value.startswith('No Such Instance currently exists'):
                continue

            if len(value) > 0 and value[0] == '"' and value[-1] != '"': # to be continued
                while True: # scan for end of this dataset
                    nextline = snmp_process.next().strip()
                    value += " " + nextline
                    if value[-1] == '"':
                        break
            rowinfo.append((oid, strip_snmp_value(value)))

    except StopIteration:
        pass

    exitstatus = snmp_process.close()
    if exitstatus:
        if opt_verbose:
            sys.stderr.write(tty_red + tty_bold + "ERROR: " + tty_normal + "SNMP error\n")
	raise MKSNMPError("SNMP Error on %s" % ip)
    return rowinfo

def extract_end_oid(prefix, complete):
    return complete[len(prefix):].lstrip('.')

# sort OID strings numerically
def cmp_oids(o1, o2):
    i1 = map(int, o1.split('.')) 
    i2 = map(int, o2.split('.')) 
    return cmp(i1, i2)

def get_snmp_table(hostname, ip, oid_info):
    # oid_info is either ( oid, columns ) or
    # ( oid, suboids, columns )
    # suboids is a list if OID-infixes that are put between baseoid
    # and the columns and also prefixed to the index column. This
    # allows to merge distinct SNMP subtrees with a similar structure
    # to one virtual new tree (look into cmctc_temp for an example)
    if len(oid_info) == 2:
        oid, targetcolumns = oid_info
        suboids = [None]
    else:
        oid, suboids, targetcolumns = oid_info

    if not oid.startswith("."):
        raise MKGeneralException("OID definition '%s' does not begin with ." % oid)

    all_values = []
    index_column = -1
    index_format = None
    number_rows = -1
    info = []
    for suboid in suboids:
        colno = -1
        columns = []
        # Detect missing (empty columns)
        max_len = 0
        max_len_col = -1

        for column in targetcolumns:
            fetchoid = oid
            if suboid:
                fetchoid += "." + str(suboid)
            if column != "":
                fetchoid += "." + str(column)

            # column may be integer or string like "1.5.4.2.3"
            colno += 1
            # if column is 0, we do not fetch any data from snmp, but use
            # a running counter as index. If the index column is the first one,
            # we do not know the number of entries right now. We need to fill
            # in later. If the column in OID_STRING or OID_BIN we do something
            # similar: we fill in the complete OID of the entry, either as
            # string or as binary UTF-8 encoded number string
            if column in [ OID_END, OID_STRING, OID_BIN ]:
                index_column = colno
                columns.append((fetchoid, []))
                index_format = column
                continue


            if opt_use_snmp_walk or is_usewalk_host(hostname):
                rowinfo = get_stored_snmpwalk(hostname, fetchoid)
            else:
                rowinfo = snmpwalk_on_suboid(hostname, ip, fetchoid)

            columns.append((fetchoid, rowinfo))
            number_rows = len(rowinfo)
            if len(rowinfo) > max_len:
                max_len = len(rowinfo)
                max_len_col = colno

        if index_column != -1:
            index_rows = []
            # Take end-oids of non-index columns as indices
            fetchoid, max_column  = columns[max_len_col]
            for o, value in max_column:
                if index_format == OID_END:
		    eo = extract_end_oid(fetchoid, o)
                    index_rows.append((o, eo))
                elif index_format == OID_STRING:
                    index_rows.append((o, o))
                else:
                    index_rows.append((o, oid_to_bin(o)))
            columns[index_column] = fetchoid, index_rows


        # prepend suboid to first column
        if suboid and len(columns) > 0:
            fetchoid, first_column = columns[0]
            new_first_column = []
            for o, val in first_column:
                new_first_column.append((o, str(suboid) + "." + str(val)))
            columns[0] = fetchoid, new_first_column

        # Swap X and Y axis of table (we want one list of columns per item)
        # Here we have to deal with a nasty problem: Some brain-dead devices
        # omit entries in some sub OIDs. This happens e.g. for CISCO 3650
        # in the interfaces MIB with 64 bit counters. So we need to look at
        # the OIDs and watch out for gaps we need to fill with dummy values.

        # First compute the complete list of end-oids appearing in the output
        # by looping all results and putting the endoids to a flat list
        endoids = []
        for fetchoid, column in columns:
            for o, value in column:
                endoid = extract_end_oid(fetchoid, o)
                if endoid not in endoids:
                    endoids.append(endoid)

        # The list needs to be sorted to prevent problems when the first
        # column has missing values in the middle of the tree. Since we
        # work with strings of numerical components, a simple string sort
        # is not correct. 1.14 must come after 1.2!
        endoids.sort(cmp = cmp_oids)

        # Now fill gaps in columns where some endois are missing
        new_columns = []
        for fetchoid, column in columns:
            i = 0
            new_column = []
            # Loop all lines to fill holes in the middle of the list. All
            # columns check the following lines for the correct endoid. If
            # an endoid differs empty values are added until the hole is filled
            for o, value in column:
                eo = extract_end_oid(fetchoid, o)
                while i < len(endoids) and endoids[i] != eo:
                    new_column.append("") # (beginoid + '.' +endoids[i], "" ) )
                    i += 1
                new_column.append(value)
                i += 1

            # At the end check if trailing OIDs are missing
            while i < len(endoids):
                new_column.append("") # (beginoid + '.' +endoids[i], "") )
                i += 1
            new_columns.append(new_column)
        columns = new_columns

        # Now construct table by swapping X and Y
        new_info = []
        index = 0
        if len(columns) > 0:
            for item in columns[0]:
                new_info.append([ c[index] for c in columns ])
                index += 1
            info += new_info

    return info

# SNMP-Helper functions used in various checks

def check_snmp_misc(item, params, info):
    for line in info:
        if item == line[0]:
            value = savefloat(line[1])
            text = line[2]
            crit_low, warn_low, warn_high, crit_high = params
            # if value is negative, we have to swap >= and <=!
            perfdata=[ (item, line[1]) ]
            if not within_range(value, crit_low, crit_high):
                return (2, "CRIT - %.2f value out of crit range (%.2f .. %.2f)" % \
                        (value, crit_low, crit_high), perfdata)
            elif not within_range(value, warn_low, warn_high):
                return (2, "WARNING - %.2f value out of warning range (%.2f .. %.2f)" % \
                        (value, warn_low, warn_high), perfdata)
            else:
                return (0, "OK = %s (OK within %.2f .. %.2f)" % (text, warn_low, warn_high), perfdata)
    return (3, "Missing item %s in SNMP data" % item)

def inventory_snmp_misc(checkname, info):
    inventory = []
    for line in info:
        value = savefloat(line[1])
        params = "(%.1f, %.1f, %.1f, %.1f)" % (value*.8, value*.9, value*1.1, value*1.2)
        inventory.append( (line[0], line[2], params ) )
    return inventory

# Version with simple handling of target parameters: only
# the current value is OK, all other values are CRIT
def inventory_snmp_fixed(checkname, info):
    inventory = []
    for line in info:
        value = line[1]
        params = '"%s"' % (value,)
        inventory.append( (line[0], line[2], params ) )
    return inventory

def check_snmp_fixed(item, targetvalue, info):
    for line in info:
        if item == line[0]:
            value = line[1]
            text = line[2]
            if value != targetvalue:
                return (2, "CRIT - %s (should be %s)" % (value, targetvalue))
            else:
                return (0, "OK - %s" % (value,))
    return (3, "Missing item %s in SNMP data" % item)

g_walk_cache = {}
def get_stored_snmpwalk(hostname, oid):
    if oid.startswith("."):
        oid = oid[1:]

    if oid.endswith(".*"):
        oid_prefix = oid[:-2]
        dot_star = True
    else:
        oid_prefix = oid
        dot_star = False

    path = snmpwalks_dir + "/" + hostname

    if opt_debug:
        sys.stderr.write("Getting %s from %s\n" % (oid, path))
    if not os.path.exists(path):
        raise MKGeneralException("No snmpwalk file %s\n" % path)

    rowinfo = []

    use_new = True
    if use_new:
        # New implementation: use binary search
        def to_bin_string(oid):
            try:
                return tuple(map(int, oid.strip(".").split(".")))
            except:
                raise MKGeneralException("Invalid OID %s" % oid)

        def compare_oids(a, b):
            aa = to_bin_string(a)
            bb = to_bin_string(b)
            if len(aa) <= len(bb) and bb[:len(aa)] == aa:
                result = 0
            else:
                result = cmp(aa, bb)
            return result
            
        if hostname in g_walk_cache:
            lines = g_walk_cache[hostname]
        else:
            lines = file(path).readlines()
            g_walk_cache[hostname] = lines

        begin = 0
        end = len(lines)
        hit = None
        while end - begin > 0:
            current = (begin + end) / 2
            parts = lines[current].split(None, 1)
            comp = parts[0]
            hit = compare_oids(oid_prefix, comp)
            if hit == 0:
                break
            elif hit == 1: # we are too low
                begin = current + 1
            else:
                end = current

        if hit != 0:
            return [] # not found

        
        def collect_until(index, direction):
            rows = []
            # Handle case, where we run after the end of the lines list
            if index >= len(lines):
                if direction > 0:
                    return []
                else:
                    index -= 1
            while True: 
                line = lines[index]
                parts = line.split(None, 1)
                o = parts[0]
                if o.startswith('.'):
                    o = o[1:]
                if o == oid or o.startswith(oid_prefix + "."):
                    if len(parts) > 1:
                        value = parts[1]
                        if agent_simulator:
                            value = agent_simulator_process(value)
                    else:
                        value = ""
                    # Fix for missing starting oids
                    rows.append(('.'+o, strip_snmp_value(value)))
                    index += direction
                    if index < 0 or index >= len(lines):
                        break
                else:
                    break
            return rows


        rowinfo = collect_until(current, -1)
        rowinfo.reverse()
        rowinfo += collect_until(current + 1, 1)
        # import pprint ; pprint.pprint(rowinfo)

        if dot_star:
            return [ rowinfo[0] ]
        else:
            return rowinfo



    # Old implementation
    hot = False
    for line in file(path):
        parts = line.split(None, 1)
        o = parts[0]
        if o.startswith('.'):
            o = o[1:]
        if o == oid or o.startswith(oid_prefix + "."):
            hot = True
            if len(parts) > 1:
                value = parts[1]
                if agent_simulator:
                    value = agent_simulator_process(value)
            else:
                value = ""
            rowinfo.append((o, strip_snmp_value(value))) # return pair of OID and value
            if dot_star:
                break
        elif hot: # end of interesting part, no point in further search
            break
    # import pprint ; pprint.pprint(rowinfo)
    return rowinfo

# Helper function to be used in checks.  It applies a user-specified
# character encoding in order to tranlate e.g. latin1 to utf8
def snmp_decode_string(text):
    encoding = get_snmp_character_encoding(g_hostname)
    if encoding:
        return text.decode(encoding).encode("utf-8")
    else:
        return text