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/pdu.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
require 'snmp/varbind'
12
13
include SNMP::BER
14
15
module SNMP
16
17
# Exceptions thrown during message/pdu decoding
18
class UnsupportedVersion < RuntimeError; end
19
class UnsupportedPduTag < RuntimeError; end
20
class InvalidPduTag < RuntimeError; end
21
class ParseError < RuntimeError; end
22
class InvalidErrorStatus < RuntimeError; end
23
class InvalidTrapVarbind < RuntimeError; end
24
class InvalidGenericTrap < RuntimeError; end
25
26
SYS_UP_TIME_OID = ObjectId.new("1.3.6.1.2.1.1.3.0")
27
SNMP_TRAP_OID_OID = ObjectId.new("1.3.6.1.6.3.1.1.4.1.0")
28
29
class Message
30
attr_reader :version
31
attr_reader :community
32
attr_reader :pdu
33
34
class << self
35
def decode(data)
36
message_data, remainder = decode_sequence(data)
37
assert_no_remainder(remainder)
38
version, remainder = decode_version(message_data)
39
community, remainder = decode_octet_string(remainder)
40
pdu, remainder = decode_pdu(version, remainder)
41
assert_no_remainder(remainder)
42
Message.new(version, community, pdu)
43
end
44
45
def decode_version(data)
46
version_data, remainder = decode_integer(data)
47
if version_data == SNMP_V1
48
version = :SNMPv1
49
elsif version_data == SNMP_V2C
50
version = :SNMPv2c
51
else
52
raise UnsupportedVersion, version_data.to_s
53
end
54
return version, remainder
55
end
56
57
def decode_pdu(version, data)
58
pdu_tag, pdu_data, remainder = decode_tlv(data)
59
case pdu_tag
60
when GetRequest_PDU_TAG
61
pdu = PDU.decode(GetRequest, pdu_data)
62
when GetNextRequest_PDU_TAG
63
pdu = PDU.decode(GetNextRequest, pdu_data)
64
when Response_PDU_TAG
65
pdu = PDU.decode(Response, pdu_data)
66
when SetRequest_PDU_TAG
67
pdu = PDU.decode(SetRequest, pdu_data)
68
when SNMPv1_Trap_PDU_TAG
69
raise InvalidPduTag, "SNMPv1-trap not valid for #{version.to_s}" if version != :SNMPv1
70
pdu = SNMPv1_Trap.decode(pdu_data)
71
when GetBulkRequest_PDU_TAG
72
raise InvalidPduTag, "get-bulk not valid for #{version.to_s}" if version != :SNMPv2c
73
pdu = PDU.decode(GetBulkRequest, pdu_data)
74
when InformRequest_PDU_TAG
75
raise InvalidPduTag, "inform not valid for #{version.to_s}" if version != :SNMPv2c
76
pdu = PDU.decode(InformRequest, pdu_data)
77
when SNMPv2_Trap_PDU_TAG
78
raise InvalidPduTag, "SNMPv2c-trap not valid for #{version.to_s}" if version != :SNMPv2c
79
pdu = PDU.decode(SNMPv2_Trap, pdu_data)
80
else
81
raise UnsupportedPduTag, pdu_tag.to_s
82
end
83
return pdu, remainder
84
end
85
end
86
87
def initialize(version, community, pdu)
88
@version = version
89
@community = community
90
@pdu = pdu
91
end
92
93
def response
94
Message.new(@version, @community, Response.from_pdu(@pdu))
95
end
96
97
def encode_version(version)
98
if version == :SNMPv1
99
encode_integer(SNMP_V1)
100
elsif version == :SNMPv2c
101
encode_integer(SNMP_V2C)
102
else
103
raise UnsupportedVersion, version.to_s
104
end
105
end
106
107
def encode
108
data = encode_version(@version)
109
data << encode_octet_string(@community)
110
data << @pdu.encode
111
encode_sequence(data)
112
end
113
end
114
115
class PDU
116
attr_accessor :request_id
117
attr_accessor :error_index
118
attr_accessor :varbind_list
119
120
alias vb_list varbind_list
121
122
def self.decode(pdu_class, pdu_data)
123
request_id, remainder = decode_integer(pdu_data)
124
error_status, remainder = decode_integer(remainder)
125
error_index, remainder = decode_integer(remainder)
126
varbind_list, remainder = VarBindList.decode(remainder)
127
assert_no_remainder(remainder)
128
pdu_class.new(request_id, varbind_list, error_status, error_index)
129
end
130
131
ERROR_STATUS_NAME = {
132
0 => :noError,
133
1 => :tooBig,
134
2 => :noSuchName,
135
3 => :badValue,
136
4 => :readOnly,
137
5 => :genErr,
138
6 => :noAccess,
139
7 => :wrongType,
140
8 => :wrongLength,
141
9 => :wrongEncoding,
142
10 => :wrongValue,
143
11 => :noCreation,
144
12 => :inconsistentValue,
145
13 => :resourceUnavailable,
146
14 => :commitFailed,
147
15 => :undoFailed,
148
16 => :authorizationError,
149
17 => :notWritable,
150
18 => :inconsistentName
151
}
152
153
ERROR_STATUS_CODE = ERROR_STATUS_NAME.invert
154
155
def initialize(request_id, varbind_list, error_status=0, error_index=0)
156
@request_id = request_id
157
self.error_status = error_status
158
@error_index = error_index.to_int
159
@varbind_list = varbind_list
160
end
161
162
def error_status=(status)
163
@error_status = ERROR_STATUS_CODE[status]
164
unless @error_status
165
if status.respond_to?(:to_int) && ERROR_STATUS_NAME[status.to_int]
166
@error_status = status
167
else
168
raise InvalidErrorStatus, status.to_s
169
end
170
end
171
end
172
173
def error_status
174
ERROR_STATUS_NAME[@error_status]
175
end
176
177
def encode_pdu(pdu_tag)
178
pdu_data = encode_integer(@request_id)
179
pdu_data << encode_integer(@error_status)
180
pdu_data << encode_integer(@error_index)
181
pdu_data << @varbind_list.encode
182
encode_tlv(pdu_tag, pdu_data)
183
end
184
185
def each_varbind(&block)
186
@varbind_list.each(&block)
187
end
188
end
189
190
class GetRequest < PDU
191
def encode
192
encode_pdu(GetRequest_PDU_TAG)
193
end
194
end
195
196
class GetNextRequest < PDU
197
def encode
198
encode_pdu(GetNextRequest_PDU_TAG)
199
end
200
end
201
202
class SetRequest < PDU
203
def encode
204
encode_pdu(SetRequest_PDU_TAG)
205
end
206
end
207
208
class GetBulkRequest < PDU
209
alias max_repetitions error_index
210
alias max_repetitions= error_index=
211
212
def encode
213
encode_pdu(GetBulkRequest_PDU_TAG)
214
end
215
216
def non_repeaters=(number)
217
@error_status = number
218
end
219
220
def non_repeaters
221
@error_status
222
end
223
end
224
225
class Response < PDU
226
class << self
227
def from_pdu(request)
228
Response.new(request.request_id, request.varbind_list,
229
:noError, 0)
230
end
231
end
232
233
def encode
234
encode_pdu(Response_PDU_TAG)
235
end
236
end
237
238
##
239
# The PDU class for traps in SNMPv2c. Methods are provided for retrieving
240
# the values of the mandatory varbinds: the system uptime and the OID of the
241
# trap. The complete varbind list is available through the usual varbind_list
242
# method. The first two varbinds in this list will always be the uptime
243
# and trap OID varbinds.
244
#
245
class SNMPv2_Trap < PDU
246
def encode
247
encode_pdu(SNMPv2_Trap_PDU_TAG)
248
end
249
250
##
251
# Returns the source IP address for the trap, usually derived from the
252
# source IP address of the packet that delivered the trap.
253
#
254
attr_accessor :source_ip
255
256
##
257
# Returns the value of the mandatory sysUpTime varbind for this trap.
258
#
259
# Throws InvalidTrapVarbind if the sysUpTime varbind is not present.
260
#
261
def sys_up_time
262
varbind = @varbind_list[0]
263
if varbind && (varbind.name == SYS_UP_TIME_OID)
264
return varbind.value
265
else
266
raise InvalidTrapVarbind, "Expected sysUpTime.0, found " + varbind.to_s
267
end
268
end
269
270
##
271
# Returns the value of the mandatory snmpTrapOID varbind for this trap.
272
#
273
# Throws InvalidTrapVarbind if the snmpTrapOID varbind is not present.
274
#
275
def trap_oid
276
varbind = @varbind_list[1]
277
if varbind && (varbind.name == SNMP_TRAP_OID_OID)
278
return varbind.value
279
else
280
raise InvalidTrapVarbind, "Expected snmpTrapOID.0, found " + varbind.to_s
281
end
282
end
283
end
284
285
##
286
# The PDU class for SNMPv2 Inform notifications. This class is identical
287
# to SNMPv2_Trap.
288
#
289
class InformRequest < SNMPv2_Trap
290
def encode
291
encode_pdu(InformRequest_PDU_TAG)
292
end
293
end
294
295
##
296
# The PDU class for traps in SNMPv1.
297
#
298
class SNMPv1_Trap
299
##
300
# Returns the source IP address for the trap, usually derived from the
301
# source IP address of the packet that delivered the trap.
302
#
303
attr_accessor :source_ip
304
305
attr_accessor :enterprise
306
attr_accessor :agent_addr
307
attr_accessor :specific_trap
308
attr_accessor :timestamp
309
attr_accessor :varbind_list
310
311
alias :vb_list :varbind_list
312
313
def self.decode(pdu_data)
314
oid_data, remainder = decode_object_id(pdu_data)
315
enterprise = ObjectId.new(oid_data)
316
ip_data, remainder = decode_ip_address(remainder)
317
agent_addr = IpAddress.new(ip_data)
318
generic_trap, remainder = decode_integer(remainder)
319
specific_trap, remainder = decode_integer(remainder)
320
time_data, remainder = decode_timeticks(remainder)
321
timestamp = TimeTicks.new(time_data)
322
varbind_list, remainder = VarBindList.decode(remainder)
323
assert_no_remainder(remainder)
324
SNMPv1_Trap.new(enterprise, agent_addr, generic_trap, specific_trap,
325
timestamp, varbind_list)
326
end
327
328
def initialize(enterprise, agent_addr, generic_trap, specific_trap, timestamp, varbind_list)
329
@enterprise = enterprise
330
@agent_addr = agent_addr
331
self.generic_trap = generic_trap
332
@specific_trap = specific_trap
333
@timestamp = timestamp
334
@varbind_list = varbind_list
335
end
336
337
# Name map for all of the generic traps defined in RFC 1157.
338
GENERIC_TRAP_NAME = {
339
0 => :coldStart,
340
1 => :warmStart,
341
2 => :linkDown,
342
3 => :linkUp,
343
4 => :authenticationFailure,
344
5 => :egpNeighborLoss,
345
6 => :enterpriseSpecific
346
}
347
348
# Code map for all of the generic traps defined in RFC 1157.
349
GENERIC_TRAP_CODE = GENERIC_TRAP_NAME.invert
350
351
def generic_trap=(trap)
352
@generic_trap = GENERIC_TRAP_CODE[trap]
353
unless @generic_trap
354
if trap.respond_to?(:to_i) && GENERIC_TRAP_NAME[trap.to_i]
355
@generic_trap = trap
356
else
357
raise InvalidGenericTrap, trap.to_s
358
end
359
end
360
end
361
362
def generic_trap
363
GENERIC_TRAP_NAME[@generic_trap]
364
end
365
366
def encode
367
pdu_data = @enterprise.encode <<
368
@agent_addr.encode <<
369
encode_integer(@generic_trap) <<
370
encode_integer(@specific_trap) <<
371
@timestamp.encode <<
372
@varbind_list.encode
373
encode_tlv(SNMPv1_Trap_PDU_TAG, pdu_data)
374
end
375
376
def each_varbind(&block)
377
@varbind_list.each(&block)
378
end
379
end
380
381
end
382
383