CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/lib/snmp/varbind.rb
Views: 1904
1
#
2
# Copyright (c) 2004 David R. Halliday
3
# All rights reserved.
4
#
5
# This SNMP library is free software. Redistribution is permitted under the
6
# same terms and conditions as the standard Ruby distribution. See the
7
# COPYING file in the Ruby distribution for details.
8
#
9
10
require 'snmp/ber'
11
12
include SNMP::BER
13
14
module SNMP
15
16
class UnsupportedValueTag < RuntimeError; end
17
class InvalidIpAddress < ArgumentError; end
18
19
class VarBindList < Array
20
def self.decode(data)
21
list = VarBindList.new
22
varbind_data, remainder = decode_sequence(data)
23
while varbind_data != ""
24
varbind, varbind_data = VarBind.decode(varbind_data)
25
list << varbind
26
end
27
return list, remainder
28
end
29
30
def initialize(varbind_list=[])
31
super()
32
if varbind_list.respond_to? :to_str
33
self << ObjectId.new(varbind_list.to_str).to_varbind
34
elsif varbind_list.respond_to? :to_varbind
35
self << varbind_list.to_varbind
36
else
37
varbind_list.each do |item|
38
if item.respond_to? :to_str
39
self << ObjectId.new(item.to_str).to_varbind
40
else
41
self << item.to_varbind
42
end
43
end
44
end
45
end
46
47
def asn1_type
48
"VarBindList"
49
end
50
51
def encode
52
varbind_data = ""
53
self.each do |varbind|
54
varbind_data << varbind.encode
55
end
56
encode_sequence(varbind_data)
57
end
58
end
59
60
class Integer
61
include Comparable
62
63
def self.decode(value_data)
64
Integer.new(decode_integer_value(value_data))
65
end
66
67
def asn1_type
68
"INTEGER"
69
end
70
71
def initialize(value)
72
@value = value.to_i
73
end
74
75
def <=>(other)
76
@value <=> other.to_i
77
end
78
79
def coerce(other)
80
if other.kind_of? Integer
81
return [other, @value]
82
else
83
return [other.to_f, self.to_f]
84
end
85
end
86
87
def to_s
88
@value.to_s
89
end
90
91
def to_i
92
@value
93
end
94
95
def to_f
96
@value.to_f
97
end
98
99
def encode
100
encode_integer(@value)
101
end
102
103
def to_oid
104
raise RangeError, "@{value} cannot be an OID (must be >0)" if @value < 0
105
ObjectId.new([@value])
106
end
107
end
108
109
class Integer32 < Integer
110
def initialize(value)
111
super(value)
112
raise ArgumentError, "Out of range: #{value}" if value < -2147483648
113
raise ArgumentError, "Out of range: #{value}" if value > 2147483647
114
end
115
end
116
117
class OctetString < String
118
def self.decode(value_data)
119
OctetString.new(value_data)
120
end
121
122
def asn1_type
123
"OCTET STRING"
124
end
125
126
def encode
127
encode_octet_string(self)
128
end
129
130
def to_oid
131
oid = ObjectId.new
132
each_byte { |b| oid << b }
133
oid
134
end
135
end
136
137
class ObjectId < Array
138
include Comparable
139
140
def self.decode(value_data)
141
ObjectId.new(decode_object_id_value(value_data))
142
end
143
144
def asn1_type
145
"OBJECT IDENTIFIER"
146
end
147
148
##
149
# Create an object id. The input is expected to be either a string
150
# in the format "n.n.n.n.n.n" or an array of integers.
151
#
152
def initialize(id=[])
153
if id.nil?
154
raise ArgumentError
155
elsif id.respond_to? :to_str
156
super(make_integers(id.to_str.split(".")))
157
else
158
super(make_integers(id.to_ary))
159
end
160
rescue ArgumentError
161
raise ArgumentError, "#{id.inspect}:#{id.class} not a valid object ID"
162
end
163
164
def to_varbind
165
VarBind.new(self, Null)
166
end
167
168
def to_oid
169
self
170
end
171
172
def to_s
173
self.join('.')
174
end
175
176
def inspect
177
"[#{self.to_s}]"
178
end
179
180
def encode
181
encode_object_id(self)
182
end
183
184
##
185
# Returns true if this ObjectId is a subtree of the provided parent tree
186
# ObjectId. For example, "1.3.6.1.5" is a subtree of "1.3.6.1".
187
#
188
def subtree_of?(parent_tree)
189
parent_tree = make_object_id(parent_tree)
190
if parent_tree.length > self.length
191
false
192
else
193
parent_tree.each_index do |i|
194
return false if parent_tree[i] != self[i]
195
end
196
true
197
end
198
end
199
200
##
201
# Returns an index based on the difference between this ObjectId
202
# and the provided parent ObjectId.
203
#
204
# For example, ObjectId.new("1.3.6.1.5").index("1.3.6.1") returns an
205
# ObjectId of "5".
206
#
207
def index(parent_tree)
208
parent_tree = make_object_id(parent_tree)
209
if not subtree_of?(parent_tree)
210
raise ArgumentError, "#{self.to_s} not a subtree of #{parent_tree.to_s}"
211
elsif self.length == parent_tree.length
212
raise ArgumentError, "OIDs are the same"
213
else
214
ObjectId.new(self[parent_tree.length..-1])
215
end
216
end
217
218
private
219
220
def make_integers(list)
221
list.collect{|n| Integer(n)}
222
end
223
224
def make_object_id(oid)
225
oid.kind_of?(ObjectId) ? oid : ObjectId.new(oid)
226
end
227
228
end
229
230
class IpAddress
231
class << self
232
def decode(value_data)
233
IpAddress.new(value_data)
234
end
235
end
236
237
def asn1_type
238
"IpAddress"
239
end
240
241
##
242
# Create an IpAddress object. The constructor accepts either a raw
243
# four-octet string or a formatted string of integers separated by dots
244
# (i.e. "10.1.2.3").
245
#
246
def initialize(value_data)
247
ip = value_data.to_str
248
if ip.length > 4
249
ip = parse_string(ip)
250
elsif ip.length != 4
251
raise InvalidIpAddress, "Expected 4 octets or formatted string, got #{value_data.inspect}"
252
end
253
@value = ip
254
end
255
256
##
257
# Returns a raw four-octet string representing this IpAddress.
258
#
259
def to_str
260
@value.dup
261
end
262
263
##
264
# Returns a formatted, dot-separated string representing this IpAddress.
265
#
266
def to_s
267
octets = []
268
@value.each_byte { |b| octets << b.to_s }
269
octets.join('.')
270
end
271
272
def to_oid
273
oid = ObjectId.new
274
@value.each_byte { |b| oid << b }
275
oid
276
end
277
278
def ==(other)
279
if other.respond_to? :to_str
280
return @value.eql?(other.to_str)
281
else
282
return false
283
end
284
end
285
286
def eql?(other)
287
self == other
288
end
289
290
def hash
291
@value.hash
292
end
293
294
def encode
295
encode_tlv(IpAddress_TAG, @value)
296
end
297
298
private
299
def parse_string(ip_string)
300
parts = ip_string.split(".")
301
raise InvalidIpAddress, ip_string.inspect if parts.length != 4
302
value_data = ""
303
parts.each do |s|
304
octet = s.to_i
305
raise InvalidIpAddress, ip_string.inspect if octet > 255
306
raise InvalidIpAddress, ip_string.inspect if octet < 0
307
value_data << octet.chr
308
end
309
value_data
310
end
311
312
end
313
314
class UnsignedInteger < Integer
315
def initialize(value)
316
super(value)
317
raise ArgumentError, "Negative integer invalid: #{value}" if value < 0
318
raise ArgumentError, "Out of range: #{value}" if value > 4294967295
319
end
320
321
def self.decode(value_data)
322
self.new(decode_uinteger_value(value_data))
323
end
324
end
325
326
class Counter32 < UnsignedInteger
327
def asn1_type
328
"Counter32"
329
end
330
331
def encode
332
encode_tagged_integer(Counter32_TAG, @value)
333
end
334
end
335
336
class Gauge32 < UnsignedInteger
337
def asn1_type
338
"Gauge32"
339
end
340
341
def encode
342
encode_tagged_integer(Gauge32_TAG, @value)
343
end
344
end
345
346
class Unsigned32 < UnsignedInteger
347
def asn1_type
348
"Unsigned32"
349
end
350
351
def encode
352
encode_tagged_integer(Unsigned32_TAG, @value)
353
end
354
end
355
356
class TimeTicks < UnsignedInteger
357
def asn1_type
358
"TimeTicks"
359
end
360
361
def encode
362
encode_tagged_integer(TimeTicks_TAG, @value)
363
end
364
365
def to_s
366
days, remainder = @value.divmod(8640000)
367
hours, remainder = remainder.divmod(360000)
368
minutes, remainder = remainder.divmod(6000)
369
seconds, hundredths = remainder.divmod(100)
370
case
371
when days < 1
372
sprintf('%02d:%02d:%02d.%02d',
373
hours, minutes, seconds, hundredths)
374
when days == 1
375
sprintf('1 day, %02d:%02d:%02d.%02d',
376
hours, minutes, seconds, hundredths)
377
when days > 1
378
sprintf('%d days, %02d:%02d:%02d.%02d',
379
days, hours, minutes, seconds, hundredths)
380
end
381
end
382
end
383
384
class Opaque < OctetString
385
def self.decode(value_data)
386
Opaque.new(value_data)
387
end
388
389
def asn1_type
390
"Opaque"
391
end
392
393
def encode
394
encode_tlv(Opaque_TAG, self)
395
end
396
end
397
398
class Counter64 < Integer
399
def self.decode(value_data)
400
Counter64.new(decode_integer_value(value_data))
401
end
402
403
def asn1_type
404
"Counter64"
405
end
406
407
def initialize(value)
408
super(value)
409
raise ArgumentError, "Negative integer invalid: #{value}" if value < 0
410
raise ArgumentError, "Out of range: #{value}" if value > 18446744073709551615
411
end
412
413
def encode
414
encode_tagged_integer(Counter64_TAG, @value)
415
end
416
end
417
418
class Null
419
class << self
420
def decode(value_data)
421
Null
422
end
423
424
def encode
425
encode_null
426
end
427
428
def asn1_type
429
'Null'
430
end
431
432
def to_s
433
asn1_type
434
end
435
end
436
end
437
438
class NoSuchObject
439
class << self
440
def decode(value_data)
441
NoSuchObject
442
end
443
444
def encode
445
encode_exception(NoSuchObject_TAG)
446
end
447
448
def asn1_type
449
'noSuchObject'
450
end
451
452
def to_s
453
asn1_type
454
end
455
end
456
end
457
458
class NoSuchInstance
459
class << self
460
def decode(value_data)
461
NoSuchInstance
462
end
463
464
def encode
465
encode_exception(NoSuchInstance_TAG)
466
end
467
468
def asn1_type
469
'noSuchInstance'
470
end
471
472
def to_s
473
asn1_type
474
end
475
end
476
end
477
478
class EndOfMibView
479
class << self
480
def decode(value_data)
481
EndOfMibView
482
end
483
484
def encode
485
encode_exception(EndOfMibView_TAG)
486
end
487
488
def asn1_type
489
'endOfMibView'
490
end
491
492
def to_s
493
asn1_type
494
end
495
end
496
end
497
498
class VarBind
499
attr_accessor :name
500
attr_accessor :value
501
502
alias :oid :name
503
504
class << self
505
def decode(data)
506
varbind_data, remaining_varbind_data = decode_sequence(data)
507
name, remainder = decode_object_id(varbind_data)
508
value, remainder = decode_value(remainder)
509
assert_no_remainder(remainder)
510
return VarBind.new(name, value), remaining_varbind_data
511
end
512
513
ValueDecoderMap = {
514
INTEGER_TAG => Integer,
515
OCTET_STRING_TAG => OctetString,
516
NULL_TAG => Null,
517
OBJECT_IDENTIFIER_TAG => ObjectId,
518
IpAddress_TAG => IpAddress,
519
Counter32_TAG => Counter32,
520
Gauge32_TAG => Gauge32,
521
# note Gauge32 tag same as Unsigned32
522
TimeTicks_TAG => TimeTicks,
523
Opaque_TAG => Opaque,
524
Counter64_TAG => Counter64,
525
NoSuchObject_TAG => NoSuchObject,
526
NoSuchInstance_TAG => NoSuchInstance,
527
EndOfMibView_TAG => EndOfMibView
528
}
529
530
def decode_value(data)
531
value_tag, value_data, remainder = decode_tlv(data)
532
decoder_class = ValueDecoderMap[value_tag]
533
if decoder_class
534
value = decoder_class.decode(value_data)
535
else
536
raise UnsupportedValueTag, value_tag.to_s
537
end
538
return value, remainder
539
end
540
end
541
542
def initialize(name, value=Null)
543
if name.kind_of? ObjectId
544
@name = name
545
else
546
@name = ObjectName.new(name)
547
end
548
@value = value
549
end
550
551
def asn1_type
552
"VarBind"
553
end
554
555
def to_varbind
556
self
557
end
558
559
def to_s
560
"[name=#{@name.to_s}, value=#{@value.to_s} (#{@value.asn1_type})]"
561
end
562
563
def each
564
yield self
565
end
566
567
def encode
568
data = encode_object_id(@name) << value.encode
569
encode_sequence(data)
570
end
571
end
572
573
class ObjectName < ObjectId
574
def asn1_type
575
"ObjectName"
576
end
577
end
578
579
end
580
581