mirror of
https://github.com/nmap/nmap.git
synced 2025-12-07 13:11:28 +00:00
579 lines
16 KiB
Lua
579 lines
16 KiB
Lua
--- SNMP functions
|
|
--@copyright See nmaps COPYING for licence
|
|
|
|
|
|
module(... or "snmp",package.seeall)
|
|
|
|
|
|
---
|
|
-- Encodes an Integer according to ASN.1 basic encoding rules
|
|
--@param val Value to be encoded
|
|
--@return encoded integer
|
|
local function encodeInt(val)
|
|
local lsb = 0
|
|
if val > 0 then
|
|
local valStr = ""
|
|
while (val > 0) do
|
|
lsb = math.mod(val, 256)
|
|
valStr = valStr .. bin.pack("C", lsb)
|
|
val = math.floor(val/256)
|
|
end
|
|
if lsb > 127 then -- two's complement collision
|
|
valStr = valStr .. bin.pack("H", "00")
|
|
end
|
|
|
|
return string.reverse(valStr)
|
|
elseif val < 0 then
|
|
local i = 1
|
|
local tcval = val + 256 -- two's complement
|
|
while tcval <= 127 do
|
|
tcval = tcval + (math.pow(256, i) * 255)
|
|
i = i+1
|
|
end
|
|
local valStr = ""
|
|
while (tcval > 0) do
|
|
lsb = math.mod(tcval, 256)
|
|
valStr = valStr .. bin.pack("C", lsb)
|
|
tcval = math.floor(tcval/256)
|
|
end
|
|
return string.reverse(valStr)
|
|
else -- val == 0
|
|
return bin.pack("x")
|
|
end
|
|
end
|
|
|
|
|
|
---
|
|
-- Encodes the length part of a ASN.1 encoding triplet
|
|
--@param val Value to be encoded
|
|
--@return encoded length value
|
|
local function encodeLength(val)
|
|
if (val >= 128) then
|
|
local valStr = ""
|
|
while (val > 0) do
|
|
local lsb = math.mod(val, 256)
|
|
valStr = valStr .. bin.pack("C", lsb)
|
|
val = math.floor(val/256)
|
|
end
|
|
return bin.pack("CA", string.len(valStr) + 0x80, string.reverse(valStr))
|
|
-- count down
|
|
else
|
|
return bin.pack("C", val)
|
|
end
|
|
end
|
|
|
|
|
|
---
|
|
-- Encodes a given value according to ASN.1 basic encoding
|
|
-- rules for SNMP packet creation
|
|
--@param val Value to be encoded
|
|
--@return encoded value
|
|
function encode(val)
|
|
local vtype = type(val)
|
|
if (vtype == 'number') then
|
|
local ival = encodeInt(val)
|
|
local len = encodeLength(string.len(ival))
|
|
return bin.pack('HAA', '02', len, ival)
|
|
end
|
|
if (vtype == 'string') then
|
|
local len = encodeLength(string.len(val))
|
|
return bin.pack('HAA', '04', len, val)
|
|
end
|
|
if (vtype == 'nil' or vtype == 'boolean') then
|
|
return bin.pack('H', '05 00')
|
|
end
|
|
if (vtype == 'table') then -- complex data types
|
|
if val._snmp == '06' then -- OID
|
|
local oidStr = bin.pack("C", val[1]*40 + val[2])
|
|
for i = 3, #val do
|
|
oidStr = oidStr .. bin.pack("C", val[i])
|
|
end
|
|
return bin.pack("HCA", '06', #val - 1, oidStr)
|
|
elseif (val._snmp == '40') then -- ipAddress
|
|
return bin.pack("HC4", '40 04', unpack(val))
|
|
elseif (val._snmp == '41') then -- counter
|
|
local cnt = encodeInt(val[1])
|
|
return bin.pack("HAA", val._snmp, encodeLength(string.len(cnt)), cnt)
|
|
elseif (val._snmp == '42') then -- gauge
|
|
local gauge = encodeInt(val[1])
|
|
return bin.pack("HAA", val._snmp, encodeLength(string.len(gauge)), gauge)
|
|
elseif (val._snmp == '43') then -- timeticks
|
|
local ticks = encodeInt(val[1])
|
|
return bin.pack("HAA", val._snmp, encodeLength(string.len(ticks)), ticks)
|
|
elseif (val._snmp == '44') then -- opaque
|
|
return bin.pack("HAA", val._snmp, encodeLength(string.len(val[1])), val[1])
|
|
end
|
|
local encVal = ""
|
|
for _, v in ipairs(val) do
|
|
encVal = encVal .. encode(v) -- todo: buffer?
|
|
end
|
|
local tableType = bin.pack("H", "30")
|
|
if (val["_snmp"]) then
|
|
tableType = bin.pack("H", val["_snmp"])
|
|
end
|
|
return bin.pack('AAA', tableType, encodeLength(string.len(encVal)), encVal)
|
|
end
|
|
return ''
|
|
end
|
|
|
|
|
|
---
|
|
-- Decodes length part of encoded value according to
|
|
-- ASN.1 basic encoding rules
|
|
--@param encStr Encoded string
|
|
--@param pos Current position in the string
|
|
--@return The position after decoding and the length of the following value
|
|
local function decodeLength(encStr, pos)
|
|
local elen
|
|
pos, elen = bin.unpack('C', encStr, pos)
|
|
if (elen > 128) then
|
|
elen = elen - 128
|
|
local elenCalc = 0
|
|
local elenNext
|
|
for i = 1, elen do
|
|
elenCalc = elenCalc * 256
|
|
pos, elenNext = bin.unpack("C", encStr, pos)
|
|
elenCalc = elenCalc + elenNext
|
|
end
|
|
elen = elenCalc
|
|
end
|
|
return pos, elen
|
|
end
|
|
|
|
|
|
---
|
|
-- Decodes an Integer according to ASN.1 basic
|
|
-- encoding rules
|
|
--@param encStr Encoded string
|
|
--@param len Length of integer in bytes
|
|
--@param pos Current position in the string
|
|
--@return The position after decoding and the decoded integer
|
|
local function decodeInt(encStr, len, pos)
|
|
local hexStr
|
|
pos, hexStr = bin.unpack("H" .. len, encStr, pos)
|
|
local value = tonumber(hexStr, 16)
|
|
if (value >= math.pow(256, len)/2) then
|
|
value = value - math.pow(256, len)
|
|
end
|
|
return pos, value
|
|
end
|
|
|
|
---
|
|
-- Decodes a sequence according to ASN.1 basic
|
|
-- encoding rules
|
|
--@param encStr Encoded string
|
|
--@param len Length of sequence in bytes
|
|
--@param pos Current position in the string
|
|
--@return The position after decoding and the decoded sequence as a table
|
|
local function decodeSeq(encStr, len, pos)
|
|
local seq = {}
|
|
local sPos = 1
|
|
local i = 1
|
|
local sStr
|
|
pos, sStr = bin.unpack("A" .. len, encStr, pos)
|
|
while (sPos < len) do
|
|
sPos, newSeq = decode(sStr, sPos)
|
|
table.insert(seq, newSeq)
|
|
i = i + 1
|
|
end
|
|
return pos, seq
|
|
end
|
|
|
|
---
|
|
-- Decodes an SNMP packet or a part of it according
|
|
-- to ASN.1 basic encoding rules
|
|
--@param encStr Encoded string
|
|
--@param pos Current position in the string
|
|
--@return The position after decoding and the decoded value(s)
|
|
function decode(encStr, pos)
|
|
local etype, elen
|
|
pos, etype = bin.unpack("H1", encStr, pos)
|
|
pos, elen = decodeLength(encStr, pos)
|
|
if (etype == "02") then -- INTEGER
|
|
return decodeInt(encStr, elen, pos)
|
|
|
|
elseif (etype == "04") then -- STRING
|
|
return bin.unpack("A" .. elen, encStr, pos)
|
|
|
|
elseif (etype == "05") then -- NULL
|
|
return pos, false
|
|
|
|
elseif (etype == "06") then -- OID
|
|
local oid = {}
|
|
oid._snmp = '06'
|
|
pos, octet = bin.unpack("C", encStr, pos)
|
|
oid[2] = math.mod(octet, 40)
|
|
octet = octet - oid[2]
|
|
oid[1] = octet/40
|
|
for i = 2, elen do
|
|
pos, oid[i+1] = bin.unpack("C", encStr, pos)
|
|
end
|
|
return pos, oid
|
|
elseif (etype == "30") then -- sequence
|
|
local seq
|
|
pos, seq = decodeSeq(encStr, elen, pos)
|
|
return pos, seq
|
|
|
|
elseif (etype == "A0") then -- getReq
|
|
local seq
|
|
pos, seq = decodeSeq(encStr, elen, pos)
|
|
seq._snmp = etype
|
|
return pos, seq
|
|
|
|
elseif (etype == "A1") then -- getNextReq
|
|
local seq
|
|
pos, seq = decodeSeq(encStr, elen, pos)
|
|
seq._snmp = etype
|
|
return pos, seq
|
|
|
|
elseif (etype == "A2") then -- getResponse
|
|
local seq
|
|
pos, seq = decodeSeq(encStr, elen, pos)
|
|
seq._snmp = etype
|
|
return pos, seq
|
|
|
|
elseif (etype == "A3") then -- setReq
|
|
local seq
|
|
pos, seq = decodeSeq(encStr, elen, pos)
|
|
seq._snmp = etype
|
|
return pos, seq
|
|
elseif (etype == "A4") then -- Trap
|
|
local seq
|
|
pos, seq = decodeSeq(encStr, elen, pos)
|
|
seq._snmp = etype
|
|
return pos, seq
|
|
elseif (etype == '40') then -- App: IP-Address
|
|
local ip = {}
|
|
pos, ip[1], ip[2], ip[3], ip[4] = bin.unpack("C4", encStr, pos)
|
|
ip._snmp = '40'
|
|
return pos, ip
|
|
elseif (etype == '41') then -- App: counter
|
|
local cnt = {}
|
|
pos, cnt[1] = decodeInt(encStr, elen, pos)
|
|
cnt._snmp = '41'
|
|
return pos, cnt
|
|
elseif (etype == '42') then -- App: gauge
|
|
local gauge = {}
|
|
pos, gauge[1] = decodeInt(encStr, elen, pos)
|
|
gauge._snmp = '42'
|
|
return pos, gauge
|
|
elseif (etype == '43') then -- App: TimeTicks
|
|
local ticks = {}
|
|
pos, ticks[1] = decodeInt(encStr, elen, pos)
|
|
ticks._snmp = '43'
|
|
return pos, ticks
|
|
elseif (etype == '44') then -- App: opaque
|
|
local opaque = {}
|
|
pos, opaque[1] = bin.unpack("A" .. elen, encStr, pos)
|
|
opaque._snmp = '44'
|
|
return pos, opaque
|
|
end
|
|
return pos, nil
|
|
end
|
|
|
|
---
|
|
-- Decodes an SNMP packet or a part of it according
|
|
-- to ASN.1 basic encoding rules
|
|
--@param encStr Encoded string
|
|
--@param pos Current position in the string
|
|
--@return The decoded value(s)
|
|
function dec(encStr, pos)
|
|
local result
|
|
local _
|
|
_, result = decode(encStr, pos)
|
|
return result
|
|
end
|
|
|
|
---
|
|
-- Create SNMP packet
|
|
--@param PDU SNMP Protocol Data Unit to be encapsulated in the packet
|
|
--@param version SNMP version, default 0 (SNMP V1)
|
|
--@param commStr community string, if not already supplied in registry or as script argument
|
|
function buildPacket(PDU, version, commStr)
|
|
local comm = nmap.registry.args.snmpcommunity
|
|
if (not comm) then comm = nmap.registry.snmpcommunity end
|
|
if (not comm) then comm = commStr end
|
|
if (not comm) then comm = "public" end
|
|
|
|
if (not version) then version = 0 end
|
|
local packet = {}
|
|
packet[1] = version
|
|
packet[2] = comm
|
|
packet[3] = PDU
|
|
return packet
|
|
end
|
|
|
|
|
|
---
|
|
-- Create SNMP Get Request PDU
|
|
--@param options Configure PDU: request ID (reqId), error and error index (err, errIdx)
|
|
--@param OIDs Object identifiers to be queried
|
|
--@return Table representing PDU
|
|
function buildGetRequest(options, ...)
|
|
if not options then options = {} end
|
|
|
|
if not options.reqId then options.reqId = math.mod(nmap.clock_ms(), 65000) end
|
|
if not options.err then options.err = 0 end
|
|
if not options.errIdx then options.errIdx = 0 end
|
|
|
|
local req = {}
|
|
req._snmp = 'A0'
|
|
req[1] = options.reqId
|
|
req[2] = options.err
|
|
req[3] = options.errIdx
|
|
|
|
local payload = {}
|
|
for i=1, select('#', ...) do
|
|
payload[i] = {}
|
|
payload[i][1] = select(i, ...)
|
|
if type(payload[i][1]) == "string" then
|
|
payload[i][1] = str2oid(payload[i][1])
|
|
end
|
|
payload[i][2] = false
|
|
end
|
|
req[4] = payload
|
|
return req
|
|
end
|
|
|
|
|
|
---
|
|
-- Create SNMP Get Next Request PDU
|
|
--@param options Configure PDU: request ID (reqId), error and error index (err, errIdx)
|
|
--@param OIDs Object identifiers to be queried
|
|
--@return Table representing PDU
|
|
function buildGetNextRequest(options, ...)
|
|
if not options then options = {} end
|
|
|
|
if not options.reqId then options.reqId = math.mod(nmap.clock_ms(), 65000) end
|
|
if not options.err then options.err = 0 end
|
|
if not options.errIdx then options.errIdx = 0 end
|
|
|
|
local req = {}
|
|
req._snmp = 'A1'
|
|
req[1] = options.reqId
|
|
req[2] = options.err
|
|
req[3] = options.errIdx
|
|
|
|
local payload = {}
|
|
for i=1, select('#', ...) do
|
|
payload[i] = {}
|
|
payload[i][1] = select(i, ...)
|
|
if type(payload[i][1]) == "string" then
|
|
payload[i][1] = str2oid(payload[i][1])
|
|
end
|
|
payload[i][2] = false
|
|
end
|
|
req[4] = payload
|
|
return req
|
|
end
|
|
|
|
---
|
|
-- Create SNMP Set Request PDU
|
|
-- Takes one OID/value pair or an already prepared table
|
|
--@param options Configure PDU: request ID (reqId), error and error index (err, errIdx)
|
|
--@param OIDs Object identifiers of object to be set
|
|
--@param value To which value object should be set. If given a table, use table instead of OID/value pair
|
|
--@return Table representing PDU
|
|
function buildSetRequest(options, oid, value)
|
|
if not options then options = {} end
|
|
|
|
if not options.reqId then options.reqId = math.mod(nmap.clock_ms(), 65000) end
|
|
if not options.err then options.err = 0 end
|
|
if not options.errIdx then options.errIdx = 0 end
|
|
|
|
local req = {}
|
|
req._snmp = 'A3'
|
|
req[1] = options.reqId
|
|
req[2] = options.err
|
|
req[3] = options.errIdx
|
|
|
|
if (type(value) == "table") then
|
|
req[4] = value
|
|
else
|
|
local payload = {}
|
|
if (type(oid) == "string") then
|
|
payload[1] = str2oid(oid)
|
|
else
|
|
payload[1] = oid
|
|
end
|
|
payload[2] = value
|
|
req[4] = {}
|
|
req[4][1] = payload
|
|
end
|
|
return req
|
|
end
|
|
|
|
---
|
|
-- Create SNMP Trap PDU
|
|
--@param enterpriseOid
|
|
--@param agentIp
|
|
--@param genTrap
|
|
--@param specTrap
|
|
--@param timeStamp
|
|
--@return Table representing PDU
|
|
function buildTrap(enterpriseOid, agentIp, genTrap, specTrap, timeStamp)
|
|
local req = {}
|
|
req._snmp = 'A4'
|
|
if (type(enterpriseOid) == "string") then
|
|
req[1] = str2oid(enterpriseOid)
|
|
else
|
|
req[1] = enterpriseOid
|
|
end
|
|
req[2] = {}
|
|
req[2]._snmp = '40'
|
|
for n in string.gmatch(agentIp, "%d+") do
|
|
table.insert(req[2], tonumber(n))
|
|
end
|
|
req[3] = genTrap
|
|
req[4] = specTrap
|
|
|
|
req[5] = {}
|
|
req[5]._snmp = '43'
|
|
req[5][1] = timeStamp
|
|
|
|
req[6] = {}
|
|
|
|
return req
|
|
end
|
|
|
|
---
|
|
-- Create SNMP Get Response PDU
|
|
-- Takes one OID/value pair or an already prepared table
|
|
--@param options Configure PDU: request ID (reqId), error and error index (err, errIdx)
|
|
--@param OIDs Object identifiers of object to be sent back
|
|
--@param value To which value object or returned object. If given a table, use table instead of OID/value pair
|
|
--@return Table representing PDU
|
|
function buildGetResponse(options, oid, value)
|
|
if not options then options = {} end
|
|
|
|
-- if really a response, should use reqId of request!
|
|
if not options.reqId then options.reqId = math.mod(nmap.clock_ms(), 65000) end
|
|
if not options.err then options.err = 0 end
|
|
if not options.errIdx then options.errIdx = 0 end
|
|
|
|
local resp = {}
|
|
resp._snmp = 'A2'
|
|
resp[1] = options.reqId
|
|
resp[2] = options.err
|
|
resp[3] = options.errIdx
|
|
|
|
if (type(value) == "table") then
|
|
resp[4] = value
|
|
else
|
|
|
|
local payload = {}
|
|
if (type(oid) == "string") then
|
|
payload[1] = str2oid(oid)
|
|
else
|
|
payload[1] = oid
|
|
end
|
|
payload[2] = value
|
|
resp[4] = {}
|
|
resp[4][1] = payload
|
|
end
|
|
return resp
|
|
end
|
|
|
|
---
|
|
-- Transforms a string into an object identifier table
|
|
--@param oidStr Object identifier as string, for example "1.3.6.1.2.1.1.1.0"
|
|
--@return Table representing OID
|
|
function str2oid(oidStr)
|
|
local oid = {}
|
|
for n in string.gmatch(oidStr, "%d+") do
|
|
table.insert(oid, tonumber(n))
|
|
end
|
|
oid._snmp = '06'
|
|
return oid
|
|
end
|
|
|
|
---
|
|
-- Transforms a table representing an object identifier to a string
|
|
--@param oid Object identifier table
|
|
--@return OID string
|
|
function oid2str(oid)
|
|
if (type(oid) ~= "table") then return 'invalid oid' end
|
|
return table.concat(oid, '.')
|
|
end
|
|
|
|
---
|
|
-- Transforms a table representing an IP to a string
|
|
--@param ip IP table
|
|
--@return IP string
|
|
function ip2str(ip)
|
|
if (type(ip) ~= "table") then return 'invalid ip' end
|
|
return table.concat(ip, '.')
|
|
end
|
|
|
|
|
|
---
|
|
-- Transforms a string into an IP table
|
|
--@param ipStr IP as string
|
|
--@return Table representing IP
|
|
function str2ip(ipStr)
|
|
local ip = {}
|
|
for n in string.gmatch(ipStr, "%d+") do
|
|
table.insert(ip, tonumber(n))
|
|
end
|
|
ip._snmp = '40'
|
|
return ip
|
|
end
|
|
|
|
|
|
---
|
|
-- Fetches values from a SNMP response
|
|
--@param resp SNMP Response (will be decoded if necessary)
|
|
--@result Table with all decoded responses and their OIDs
|
|
function fetchResponseValues(resp)
|
|
if (type(resp) == "string") then
|
|
local _
|
|
_, resp = decode(resp)
|
|
end
|
|
|
|
if (type(resp) ~= "table") then
|
|
return {}
|
|
end
|
|
|
|
local varBind
|
|
if (resp._snmp and resp._snmp == 'A2') then
|
|
varBind = resp[4]
|
|
elseif (resp[3]._snmp and resp[3]._snmp == 'A2') then
|
|
varBind = resp[3][4]
|
|
end
|
|
|
|
if (varBind and type(varBind) == "table") then
|
|
local result = {}
|
|
for k, v in ipairs(varBind) do
|
|
local val = v[2]
|
|
if (type(v[2]) == "table") then
|
|
if (v[2]._snmp == '40') then
|
|
val = v[2][1] .. '.' .. v[2][2] .. '.' .. v[2][3] .. '.' .. v[2][4]
|
|
elseif (v[2]._snmp == '41') then
|
|
val = v[2][1]
|
|
elseif (v[2]._snmp == '42') then
|
|
val = v[2][1]
|
|
elseif (v[2]._snmp == '43') then
|
|
val = v[2][1]
|
|
elseif (v[2]._snmp == '44') then
|
|
val = v[2][1]
|
|
end
|
|
end
|
|
table.insert(result, {val, oid2str(v[1]), v[1]})
|
|
end
|
|
return result
|
|
end
|
|
return {}
|
|
end
|
|
|
|
|
|
---
|
|
-- Fetches first value from a SNMP response.
|
|
--@param response SNMP Response (will be decoded if necessary)
|
|
--@return First decoded value of the response
|
|
function fetchFirst(response)
|
|
local result = fetchResponseValues(response)
|
|
if type(result) == "table" and result[1] and result[1][1] then return result[1][1]
|
|
else return nil
|
|
end
|
|
end
|