Nmap Development mailing list archives
[NSE] WHOIS
From: jah <jah () zadkiel plus com>
Date: Sun, 03 Feb 2008 01:46:35 +0000
Hi,I'd like to share the attached whois.nse, which performs whois queries against the five Regional Internet Registries (ARIN, RIPE NCC, APNIC, LACNIC and AFRINIC) in order to return (a small number of) fields from the record pertaining to the range of IP address assignments in which the target IP address resides.
The results of a query, using the target IP address as the query term, will contain record 'objects' relating to the address range, an organisation responsible for the range and persons or a role within the responsible organisation. whois.nse will return selected fields from the address range and organisation objects. With verbosity, selected fields from the person or role objects will also be returned.
By default, ARIN is the first whois service to be queried because records there are most likely to point to the RIR to which the target IP address is assigned and the script will follow such redirects to find the desired record (much in the same way as the geektools.com whois proxy). Additionally, ARIN holds the most detailed records about assignments not covered by the five RIRs (i.e. assignments that pre-date the formation of the RIRs and other special cases).
The script will run against any target with a non-private IP address.The script is in the 'discovery' category (I refrained from placing it any category where it would be run without explicitly specifying either the category or the script).
Using --script-args, the number and query order of whois services may be defined on the commandline (overriding the default) e.g.
--script-args whodb=ripe
--script-args whodb=apnic+lacnic
--script-args whois={whodb=afrinic}
For a quick demo, may I suggest the following nmap command which will
perform minimal scanning against 10 random targets:
nmap -iR 10 -n -PN -sS -p80 --max-retries 0 --script whois Known issues:Records sometimes contain characters that can't be displayed and so appear as dots - I suspect this is either lua related or dependent on system setup. Occasionally a record is badly formed (esp. embedding one record within a bunch of 'remarks' fields in another record) and in these cases, whilst the correct RIR should have been identified, the result will be largely uninformative. Very occasionally, a query won't return all of the required fields from a record. It will, if that query is repeated. I'm still trying to work out why this happens, but it happens so infrequently (and is rarely reproducible on-demand) as to make this very difficult.
There is, however, a scenario I have identified as one best to avoid:Querying LACNIC for targets not assigned to LACNIC causes all manner of weirdness and so using
--script-args whodb=lacnic is ill advised except for a single target scan or where --max-hostgroup = 1Other than these few caveats I think the script works well and I have found it very useful. I hope someone else will find it so.
Regards, jah
id = "WHOIS"
description = [[
Attempts to retrieve WHOIS information about the range of IP addresses
that include the target IP address. If a record contains more than
one range of addresses, information about the smallest range should be
returned. With output verbosity, information about Persons or Roles
associated with the range will be returned.
]]
--[[
USAGE
nmap <target> --script whois
nmap <target> --script discovery
The default number and query order of Whois services may be overridden by
supplying the script argument "whodb" thus:
nmap <target> --script whois --script-args whodb=ripe
nmap <target> --script whois --script-args whodb=arin+ripe+apnic
nmap <target> --script whois --script-args whois={whodb=apnic*jpnic}
(N.B. commas or semi-colons should not be used to delimit arguments)
--]]
author = "jah <jah at zadkiel.plus.com>"
license = "As detailed in nmap COPYING"
categories = {"discovery"}
runlevel = 1.0
require "ipOps"
require "stdnse"
-- HOSTRULE
-- true for any non-private IP address
hostrule = function(host, port)
return not ipOps.isPrivate(host.ip)
end
-- ORDERDEFN
-- define the number and order of whois databases to query
local orderdefn = {"arin","ripe","apnic"}
-- FIELDS
-- define the fields from which to gather values
local fields_meta = {
rpsl = {
ob_exist = "\n%s*[Ii]netnum:%s*.-\n",
ob_netnum = {ob_start = "\n%s*[Ii]netnum:%s*.-\n",
ob_end = "\n%s*[Ss]ource:%s*.-\n\n",
inetnum = "\n%s*[Ii]netnum:%s*(.-)\n",
netname = "\n%s*[Nn]et[\-]-[Nn]ame:%s*(.-)\n",
nettype = "\n%s*[Nn]et[\-]-[Tt]ype:%s*(.-)\n",
descr = "[Dd]escr:[^\n][%s]*(.-)\n",
country = "\n%s*[Cc]ountry:%s*(.-)\n",
status = "\n%s*[Ss]tatus:%s*(.-)\n",
source = "\n%s*[Ss]ource:%s*(.-)\n"},
ob_org = { ob_start = "\n%s*[Oo]rgani[sz]ation:%s*.-\n",
ob_end = "\n%s*[Ss]ource:%s*.-\n\n",
organisation = "\n%s*[Oo]rgani[sz]ation:%s*(.-)\n",
orgname = "\n%s*[Oo]rg[\-]-[Nn]ame:%s*(.-)\n",
descr = "[Dd]escr:[^\n][%s]*(.-)\n",
email = "\n%s*[Ee][\-]-[Mm]ail:%s*(.-)\n"},
ob_role = { ob_start = "\n%s*[Rr]ole:%s*.-\n",
ob_end = "\n%s*[Ss]ource:%s*.-\n\n",
role = "\n%s*[Rr]ole:%s*(.-)\n",
email = "\n%s*[Ee][\-]-[Mm]ail:%s*(.-)\n"},
ob_persn = { ob_start = "\n%s*[Pp]erson:%s*.-\n",
ob_end = "\n%s*[Ss]ource:%s*.-\n\n",
person = "\n%s*[Pp]erson:%s*(.-)\n",
email = "\n%s*[Ee][\-]-[Mm]ail:%s*(.-)\n"} },
arin = {
ob_exist = "\n%s*[Nn]et[\-]-[Rr]ange:.-\n",
ob_netnum = {ob_start = "\n%s*[Nn]et[\-]-[Rr]ange:.-\n",
ob_end = "\n\n",
netrange = "\n%s*[Nn]et[\-]-[Rr]ange:(.-)\n",
netname = "\n%s*[Nn]et[\-]-[Nn]ame:(.-)\n",
nettype = "\n%s*[Nn]et[\-]-[Tt]ype:(.-)\n"},
ob_org = {ob_start = "\n%s*[Oo]rg[\-]-[Nn]ame:.-\n",
ob_end = "\n\n",
orgname = "\n%s*[Oo]rg[\-]-[Nn]ame:(.-)\n",
orgid = "\n%s*[Oo]rg[\-]-[Ii][Dd]:(.-)\n",
stateprov = "\n%s*[Ss]tate[\-]-[Pp]rov:(.-)\n",
country = "\n%s*[Cc]ountry:(.-)\n"},
ob_cust = {ob_start = "\n%s*[Cc]ust[\-]-[Nn]ame:.-\n",
ob_end = "\n\n",
custname = "\n%s*[Cc]ust[\-]-[Nn]ame:(.-)\n",
stateprov = "\n%s*[Ss]tate[\-]-[Pp]rov:(.-)\n",
country = "\n%s*[Cc]ountry:(.-)\n"},
ob_persn = {ob_start = "\n%s*[Oo]rg[\-]-[Tt]ech[\-]-[Nn]ame:.-\n",
ob_end = "\n\n",
orgtechname =
"\n%s*[Oo]rg[\-]-[Tt]ech[\-]-[Nn]ame:(.-)\n",
orgtechemail =
"\n%s*[Oo]rg[\-]-[Tt]ech[\-]-[Ee][\-]-[Mm]ail:(.-)\n"} },
lacnic = {
ob_exist = "\n%s*[Ii]netnum:%s*.-\n",
ob_netnum = {ob_start = "\n%s*[Ii]netnum:%s*.-\n",
ob_end = "\n\n",
inetnum = "\n%s*[Ii]netnum:%s*(.-)\n",
owner = "\n%s*[Oo]wner:%s*(.-)\n",
ownerid = "\n%s*[Oo]wner[\-]-[Ii][Dd]:%s*(.-)\n",
responsible = "\n%s*[Rr]esponsible:%s*(.-)\n",
country = "\n%s*[Cc]ountry:%s*(.-)\n",
source = "\n%s*[Ss]ource:%s*(.-)\n"},
ob_persn = {ob_start = "\n%s*[Pp]erson:%s*.-\n",
ob_end = "\n\n",
person = "\n%s*[Pp]erson:%s*(.-)\n",
email = "\n%s*[Ee][\-]-[Mm]ail:%s*(.-)\n"} },
jpnic = {
ob_exist = "\n%s*[Nn]etwork%s-[Ii]nformation:%s*.-\n",
ob_netnum = {ob_start = "\[[Nn]etwork%s*[Nn]umber\]%s*.-\n",
ob_end = "\n\n",
inetnum = "\[[Nn]etwork%s*[Nn]umber\]%s*(.-)\n",
netname = "\[[Nn]etwork%s*[Nn]ame\]%s*(.-)\n",
orgname = "\[[Oo]rganization\]%s*(.-)\n"} }
}
-- WHOISDB
-- there can be more dbs defined here than in orderdefn, those extra will
-- only be queried if redirects to them are found.
-- iana is not a whois db and does not need hostname
local whoisdb = {
arin = {
id = "arin",
hostname = "whois.arin.net", preflag = "+ ", postflag = "",
longname = {"american registry for internet numbers"},
fieldreq = fields_meta.arin,
smallnet_rule = fields_meta.arin.ob_netnum.netrange,
redirects = {
{"ob_org", "orgname", "longname"},
{"ob_org", "orgname", "id"},
{"ob_org", "orgid", "id"} },
output_short = {
{"ob_netnum", {"netrange", "netname"}},
{"ob_org", {"orgname", "orgid", {"country", "stateprov"}}} },
output_long = {
{"ob_netnum", {"netrange", "netname"}},
{"ob_org", {"orgname", "orgid", {"country", "stateprov"}}},
{"ob_cust", {"custname", {"country", "stateprov"}}},
{"ob_persn", {"orgtechname", "orgtechemail"}} } },
ripe = {
id = "ripe",
hostname = "whois.ripe.net", preflag = "-Bc ", postflag = "",
longname = {"ripe network coordination centre"},
fieldreq = fields_meta.rpsl,
smallnet_rule = fields_meta.rpsl.ob_netnum.inetnum,
redirects = {
{"ob_role", "role", "longname"},
{"ob_org", "orgname", "id"},
{"ob_org", "orgname", "longname"} },
output_short = {
{"ob_netnum", {"inetnum", "netname", "descr", "country"}},
{"ob_org", {"orgname", "organisation", "descr", "email"}} },
output_long = {
{"ob_netnum", {"inetnum", "netname", "descr", "country"}},
{"ob_org", {"orgname", "organisation", "descr", "email"}},
{"ob_role", {"role", "email"}},
{"ob_persn", {"person", "email"}} } },
apnic = {
id = "apnic",
hostname = "whois.apnic.net", preflag = "", postflag = "",
longname = {"asia pacific network information centre"},
fieldreq = fields_meta.rpsl,
smallnet_rule = fields_meta.rpsl.ob_netnum.inetnum,
redirects = {
{"ob_netnum", "netname", "id"},
{"ob_org", "orgname", "longname"},
{"ob_role", "role", "longname"},
{"ob_netnum", "source", "id"} },
output_short = {
{"ob_netnum", {"inetnum", "netname", "descr", "country"}},
{"ob_org", {"orgname", "organisation", "descr", "email"}} },
output_long = {
{"ob_netnum", {"inetnum", "netname", "descr", "country"}},
{"ob_org", {"orgname", "organisation", "descr", "email"}},
{"ob_role", {"role", "email"}},
{"ob_persn", {"person", "email"}} } },
lacnic = {
id = "lacnic",
hostname = "whois.lacnic.net", preflag = "", postflag = "",
longname =
{"latin american and caribbean ip address regional registry"},
fieldreq = fields_meta.lacnic,
smallnet_rule = fields_meta.lacnic.ob_netnum.inetnum,
redirects = {
{"ob_netnum", "ownerid", "id"},
{"ob_netnum", "source", "id"} },
output_short = {
{"ob_netnum",
{"inetnum", "owner", "ownerid", "responsible", "country"}} },
output_long = {
{"ob_netnum",
{"inetnum", "owner", "ownerid", "responsible", "country"}},
{"ob_persn", {"person", "email"}} } },
afrinic = {
id = "afrinic",
hostname = "whois.afrinic.net", preflag = "-Bc ", postflag = "",
longname = {"african internet numbers registry",
"african network information center"},
fieldreq = fields_meta.rpsl,
smallnet_rule = fields_meta.rpsl.ob_netnum.inetnum,
redirects = {
{"ob_org", "orgname", "longname"} },
output_short = {
{"ob_netnum", {"inetnum", "netname", "descr", "country"}},
{"ob_org", {"orgname", "organisation", "descr", "email"}} },
output_long = {
{"ob_netnum", {"inetnum", "netname", "descr", "country"}},
{"ob_org", {"orgname", "organisation", "descr", "email"}},
{"ob_role", {"role", "email"}},
{"ob_persn", {"person", "email"}} } },
jpnic = {
id = "jpnic",
hostname = "whois.nic.ad.jp", preflag = "", postflag = "/e",
longname = {"japan network information center"},
fieldreq = fields_meta.jpnic,
output_short = {
{"ob_netnum", {"inetnum", "netname", "orgname"}} } },
iana = { -- not actually a db but required here
id = "iana", longname = {"internet assigned numbers authority"} }
}
local addr
local m_dotted =
"(%d+%.%d+%.%d+%.%d+)[%s]*[-][%s]*(%d+%.%d+%.%d+%.%d+)"
local m_cidr = "(%d+)[.]*(%d*)[.]*(%d*)[.]*(%d*)[/]+(%d+)"
local m_redir = {
"\n%s*[%S]%s*(%w*)%s*[Rr]esource",
"%s*(%w*)%s*[Ww]hois"
}
local m_none = {
"\n%s*([Nn]o match found for[%s\+]*$addr)",
"\n%s*([Uu]nallocated resource:%s*$addr)",
"\n%s*([Rr]eserved:%s*$addr)",
"\n[^\n]*([Nn]ot%s[Aa]ssigned[^\n]*$addr)",
"\n%s*(No match!!)%s*\n"
}
local m_err = {
"\n%s*([Aa]n [Ee]rror [Oo]ccured)%s*\n",
"\n[^\n]*([Ee][Rr][Rr][Oo][Rr][^\n]*)\n"
}
--
-- FUNCTIONS
--
-- trim
-- trim, as well as turn empty string into nil
local function trim(s)
if not s then return nil end
local td = (string.gsub(s, "^%s*(.-)%s*$", "%1"))
if td == "" then td = nil end
return td
end
-- have_asked
-- matches a string to elements of a table and returns false only if both
-- string and table are present and if no match is found. used to find
-- out whether a whoisdb has been queried before
local have_asked = function(pattern, t, dbg)
local s = ""
if t then s = table.concat(t, " ") end
if pattern and s then
if not s:match(pattern) then return false end
stdnse.print_debug(3, dbg .. " " .. pattern ..
" is one of the following dbs to have been queried: " .. s)
end
return true
end
-- querydo
-- connect to db, send query (host.ip), receive and return any results
local querydo = function(db, host)
local status, line
local result = ""
local socket = nmap.new_socket()
local catch = function()
stdnse.print_debug(id .. " " .. host.ip ..
" connection aborted, check the details for whoisdb." .. db)
socket:close() end
local try = nmap.new_try(catch)
local v = whoisdb[db]
if not v then return false end
try(socket:connect(v.hostname, 43))
try(socket:send(v.preflag .. host.ip .. v.postflag .. "\n"))
while true do
local status, lines = socket:receive_lines(1)
if not status then
break
else
result = result .. lines
end
end
socket:close()
return result
end
-- two_dwords
-- returns the two ip addresses at either end of a net range, as dwords
local two_dwords = function(str, patt)
local a, b, c, d, e, lo_net, host
a, b, c, d, e = str:match(patt)
local ipt = {b, c, d}
local strip = ""
for _, cap in ipairs(ipt) do
if cap == "" then cap = "0" end
strip = strip .. "." .. cap
end
lo_net = a .. strip
if e ~= "" then e = tonumber(e)
if e and e <=32 then
host = 32 - e end
end
return ipOps.todword(lo_net), ipOps.todword(lo_net) + 2^host - 1
end
-- smallest_net
-- to sort a table of net ranges by range, ascending
-- return true if net_1 is smaller than net_2
local smallest_net = function(net_1, net_2)
local sorted = true -- return value defaulting true to avoid a loop
local n1_lo, n1_hi, n2_lo, n2_hi
if net_1:match(m_dotted) then
n1_lo, n1_hi = net_1:match(m_dotted)
n1_lo = ipOps.todword(n1_lo)
n1_hi = ipOps.todword(n1_hi)
elseif net_1:match(m_cidr) then
n1_lo, n1_hi = two_dwords(net_1, m_cidr)
end
if net_2:match(m_dotted) then
n2_lo, n2_hi = net_2:match(m_dotted)
n2_lo = ipOps.todword(n2_lo)
n2_hi = ipOps.todword(n2_hi)
elseif net_2:match(m_cidr) then
n2_lo, n2_hi = two_dwords(net_2, m_cidr)
end
if n1_lo <= n2_lo and n1_hi >= n2_hi then sorted = false end
return sorted
end
-- ip_in_net
-- to determine if a given ip addr falls inside a given net range
-- return true if net contains ip
local ip_in_net = function(ip, net)
local i, j, net_lo, net_hi, dw_ip
if net:match(m_dotted) then
net_lo, net_hi = net:match(m_dotted)
net_lo = ipOps.todword(net_lo)
net_hi = ipOps.todword(net_hi)
elseif net:match(m_cidr) then
net_lo, net_hi = two_dwords(net, m_cidr)
end
dw_ip = ipOps.todword(ip)
if net_lo <= dw_ip and dw_ip <= net_hi then return true end
return false
end
-- find the smallest inetnum that includes host.ip and constrain result
-- to any information after the start of the smallest inetnum and before
-- any further inetnums
local constrain_result = function(result, thisdb, host)
local strbgn = 1
local strend = 1
local mptr = {}
local mstr = {}
local ptr = 1
local bound = nil
local dbg = id .. " " .. host.ip .. " " .. thisdb
-- find all inetnums
-- build two tables
while strbgn and whoisdb[thisdb].fieldreq do
strbgn, strend =
result:find(whoisdb[thisdb].fieldreq.ob_exist, strend)
if strbgn then
-- one consisting the available inetnum pointers
table.insert(mptr, strbgn)
-- one consisting the available inetnum strings
table.insert(mstr,
trim(result:match(whoisdb[thisdb].smallnet_rule, strbgn)))
end
end
if # mstr > 1 then
-- find the closest one to host.ip and constrain the result to it
stdnse.print_debug(3, dbg .. " Focusing on the smallest of " ..
#mstr .. " address ranges")
-- sort the table mptr into nets ascending
table.sort(mstr, smallest_net)
-- select the first net that includes host.ip
local str_net
for _, strnet in ipairs(mstr) do
if ip_in_net(host.ip,strnet) then
str_net = strnet
break
end
end
stdnse.print_debug(3, dbg ..
" smallest range containing target IP addr. is " ..
trim(str_net))
-- get all pointers and sort in order
table.sort(mptr)
-- loop through pointers until our inetnum pointer matches
for i = #mptr,1,-1 do
if str_net ==
trim(result:match(whoisdb[thisdb].smallnet_rule, mptr[i]))
then
ptr = mptr[i]
-- check to see if there's a greater pointer for bounding
if mptr[i+1] then bound = mptr[i+1] end
break
end
end
local dbg =
dbg .. " smallest range is offset from " .. ptr .. " to"
-- isolate inetnum and associated objects
if bound then
stdnse.print_debug(3, dbg .. " " .. bound)
-- get from pointer to bound
return result:sub(ptr,bound), ptr
else
stdnse.print_debug(3, dbg .. " the end.")
-- or get the whole thing from the pointer onwards
return result:sub(ptr), ptr
end
end -- if # mstr
return result, 0
end -- function
-- EXTRACT_SOME_DATA
-- returns a table of objects or, a single object if that object is
-- passed as a fourth arg
local extract_some_data = function(rchunk, thisdb, host, obg)
local ret = {}
local dbg = id .. " " .. host.ip
local groups = {}
-- we either pass a table for one object group or for all groups
if obg then
groups[obg] = whoisdb[thisdb].fieldreq[obg]
stdnse.print_debug(3, dbg .. " " .. thisdb ..
" Extracting a single group of objects: " .. obg)
else
stdnse.print_debug(3, dbg .. " " .. thisdb ..
" Extracting all object groups.")
groups = whoisdb[thisdb].fieldreq
end
for object_group, ogt in pairs(groups) do
if object_group and object_group ~= "ob_exist" then
stdnse.print_debug(4, dbg .. " " .. thisdb ..
" matching object group " .. object_group)
ret[object_group] = {}
local pttn_grp = groups[object_group].ob_start
local pttn_grpend = groups[object_group].ob_end
local i, j, ob_start, ob_end
-- get a substr of rchunk that corresponds to an object group
ob_start, j = rchunk:find(pttn_grp)
i, ob_end = rchunk:find(pttn_grpend, j)
-- if we could not find the end, make the end EOF
ob_end = ob_end or -1
if ob_start and ob_end then
stdnse.print_debug(4, dbg .. " " .. thisdb ..
" capturing " .. object_group .. " with indices " ..
ob_start .. " " .. ob_end)
local obj_raw = rchunk:sub(ob_start,ob_end)
for fieldname, matchline in pairs(groups[object_group]) do
if fieldname ~= "ob_start"
and fieldname ~= "ob_end" then
ret[object_group][fieldname] =
trim(obj_raw:match(matchline))
end
end
end -- if ob_start and ob_end
end -- if object_group
end -- for object_group
if obg then ret = ret[obg] end -- returning one object
return ret
end -- function
-- RULESET
-- returns found, redirect, ianahits
-- found is true if no redirects detected, redirect is a member of whoisdb
-- ianahits is incremented here if a redirect to iana is detected
local ruleset = function(db, host, newwdata, ihits)
stdnse.print_debug(3, id .. " " .. host.ip .. " " .. db ..
" Testing for redirection.")
local redirect = nil -- the value of a recognised redirect
local found = false -- flag if we have found the record we seek
-- decide what action to take based on the presence of a redirect
-- returns three values akin to found, redirect, ihits
local decide_next_move = function(nom, i, db)
local iana = whoisdb.iana.id
local arin = whoisdb.arin.id
-- arin record points to iana so we won't follow and we assume we
-- have our record
if nom == iana and db == arin then
stdnse.print_debug(2, id .. " " .. host.ip .. " " .. db ..
" Accept arin record (matched IANA)")
return true, nil, (i + 1) end
-- other record points to iana so we query arin next
if nom == iana then
stdnse.print_debug(2, id .. " " .. host.ip .. " " .. db ..
" Redirecting to arin (matched IANA)")
return false, arin, (i + 1) end
-- redirect, but not to iana or to self, so we follow it.
if nom ~= whoisdb[db].id then
stdnse.print_debug(2, id .. " " .. host.ip .. " " .. db ..
" Redirecting to " .. nom)
return false, nom, i end
-- redirect to self
return true, nil, i
end
-- test for redirects
if whoisdb[db].redirects then
-- iterate over each table of redirect info for a specific field
for _, v in ipairs(whoisdb[db].redirects) do
local ob, fld, prop = unpack(v) -- three redirect elements
-- if a field has been captured for the given redirect info
if newwdata[db][ob] and newwdata[db][ob][fld] then
stdnse.print_debug(3, id .. " " .. host.ip .. " " .. db ..
" match redirect in object: " .. ob .."." .. fld ..
" for " .. prop)
-- iterate over the members of whoisdb to find a match
for member,mem_properties in pairs(whoisdb) do
-- check whether the field value captured contains the
-- string property of the current member of whoisdb.
-- if it does, we have a redirect and we have to
-- decide whether to follow the redirect
if type(mem_properties[prop]) == "string" then
if string.lower(newwdata[db][ob][fld])
:match(mem_properties[prop]) then
stdnse.print_debug(3, id .. " " .. host.ip ..
" " .. db .. " matched " .. prop ..
" in " .. ob .."." .. fld)
return decide_next_move(whoisdb[member].id,
ihits, db)
end
elseif type(mem_properties[prop]) == "table" then
-- tables are used for properties with more than
-- one value, iterate over each value which should
-- be of type string
for _, m in ipairs(mem_properties[prop]) do
if string.lower(newwdata[db][ob][fld])
:match(m) then
stdnse.print_debug(3, id .. " " ..
host.ip .. " " .. db .. " matched " ..
prop .. " in " .. ob .."." .. fld)
return
decide_next_move(whoisdb[member].id,
ihits, db)
end
end -- for _, m in ipairs
end
end -- for mem, mem_properties
end
end -- for _,v in ipairs
end -- if whoisdb
-- if redirects have not been found then assume that the record has
-- been found.
found = true
return found, redirect, ihits
end
--
--
-- ACTION
--
action = function(host, port)
-- Handle commandline args
if nmap.registry.args then
local cmd
local t = {}
if nmap.registry.args.whois and nmap.registry.args.whois.whodb
then
cmd = nmap.registry.args.whois.whodb end
if nmap.registry.args.whodb then
cmd = nmap.registry.args.whodb end
if cmd then
for db in string.gmatch(cmd, "%w+") do
if not whoisdb[db] then else
t[#t+1] = db
end
end
if #t > 0 then
orderdefn = t
stdnse.print_debug(2, id .. " " .. host.ip ..
" script args: " .. table.concat(t, " "))
end
end
end
local tmp -- local temp throw-away var
local trynum = 0 -- incr. for each qry to dbs in orderdefn
local numtotry = # orderdefn -- total number of dbs in orderdefn
local thisdb = "" -- the currently queried db
local nextdb -- the db id'd in a redirect from thisdb
local result -- thisdb response to our query
local found = false -- true when record has been found
local ianahits = 0 -- # times a record has pointed to IANA
local stuff_to_display = nil -- the output
local have_objects = false -- true when we recognise the response
local track = {} -- keep track of which dbs queried
local extrctd_data = {} -- table of extracted data
local verbos_level = nmap.verbosity() --Verbosity
local dbg = id .. " " .. host.ip
--
-- Send queries to whois db servers and analyse responses...
--
-- stop queries when record is found or we exhaust list of whois
-- servers defined in orderdefn
while trynum <= numtotry and not found do
-- try all whois dbs defined by orderdefn (in order defined)
-- except when specifically redirected elsewhere
if not nextdb then
-- thisdb is a db defined in orderdefn
if trynum >= numtotry then break end
trynum = trynum + 1
thisdb = orderdefn[trynum]
else
-- thisdb is a redirect
thisdb = nextdb
end
-- check whether we have already queried thisdb
stdnse.print_debug(3, dbg .. " Have we already queried " ..
thisdb .. "?" )
local prevqd = have_asked(thisdb, track, dbg)
if prevqd and nextdb then
-- We assume a redirect back to this db means record is found.
stdnse.print_debug(2, dbg .. " " .. thisdb ..
" has been queried previously!")
found = true
break
end
if not nextdb then
-- if we have asked orderdefn[trynum] already, try trynum+1
-- until we run out of dbs in orderdefn
while prevqd and (trynum < numtotry) do
trynum = trynum + 1
thisdb = orderdefn[trynum]
prevqd = have_asked(thisdb, track, dbg)
end
-- we've exhausted our list of whoisdbs to try
if prevqd then
stdnse.print_debug(2, dbg ..
" All whois dbs defined have been queried.")
break
end
stdnse.print_debug(2, dbg .. " Begin Query at " .. thisdb)
else
stdnse.print_debug(2, dbg .. " Begin Query at " .. thisdb ..
" (from redirect)")
end
-- send query to thisdb and receive result
result = querydo(thisdb, host)
-- keep track of which dbs we've queried - add thisdb
track[#track+1] = thisdb
-- fail completely if we have unknown issues...
if not result then
stdnse.print_debug(1, dbg .. " " .. thisdb ..
" RESULT IS FALSE! Eh?")
break
end
local result_chunk = result
-- do we recognise objects in the result?.
if whoisdb[thisdb].fieldreq then
have_objects = result:match(whoisdb[thisdb].fieldreq.ob_exist)
else
have_objects = false
stdnse.print_debug(2, "missing property: whoisdb." ..
thisdb .. ".fieldreq")
end
-- if we do not recognise objects check for an error/message
if not have_objects then
nextdb = nil
stdnse.print_debug(2, dbg .. " " .. thisdb ..
" has not responded with the expected objects")
local msg
-- We may have a redirect
for _, pttn in ipairs(m_redir) do
for tmp in result:gfind(pttn) do
if tmp and string.lower(tmp) ~= "iana" and
string.lower(tmp) ~= thisdb and
whoisdb[string.lower(tmp)] then
nextdb = string.lower(tmp)
break
end
end
if nextdb then break end
end
-- may have found our record saying something similar to
-- "No Record Found"
for _, pttn in ipairs(m_none) do
pttn = pttn:gsub("$addr", host.ip)
tmp = result:match(pttn)
if tmp then
msg = tmp
stdnse.print_debug(2, dbg .. " " .. thisdb ..
" responded with a message which is assumed to be authoritative.")
found = true
break
end
end
-- We may have an error
if not msg then
for _, pttn in ipairs(m_err) do
tmp = result:match(pttn)
if tmp then
msg = tmp
stdnse.print_debug(2, dbg .. " " .. thisdb ..
" responded with an ERROR message.")
break
end
end
end
-- if we've recognised a non-object message,
if msg then
stuff_to_display = "Message from " ..
whoisdb[thisdb].hostname .. "\n" .. msg
end
end
-- the query result may not contain the set of objects we were
-- expecting and we do not recognise the response message
-- it may contain a record mirrored from a different whois db
if not nextdb and not have_objects and not found then
local db_emu
for setname, set in pairs(fields_meta) do
if set ~= whoisdb[thisdb].fieldreq and
result:match(set.ob_exist) then
db_emu = setname
stdnse.print_debug(1, dbg .. " " .. thisdb ..
" seems to have responded using the set of objects named: " ..
setname)
break
end
end
if db_emu then
-- find a display to match the objects.
-- this probably needs to be less random
for some_db, db_props in pairs(whoisdb) do
if db_props.fieldreq and whoisdb[db_emu].fieldreq and
db_props.fieldreq == whoisdb[db_emu].fieldreq then
whoisdb[thisdb].redirects = nil
whoisdb[thisdb].fieldreq = whoisdb[some_db].fieldreq
whoisdb[thisdb].smallnet_rule = whoisdb[some_db].smallnet_rule
whoisdb[thisdb].output_short = whoisdb[some_db].output_short
whoisdb[thisdb].output_long = whoisdb[some_db].output_long
have_objects = true
stdnse.print_debug(1, dbg .. " " .. thisdb ..
" will use the display properties of " .. some_db)
break
end
end
end -- if db_emu
end
-- extract fields from the entire result, do redirect discovery.
if have_objects then
extrctd_data[thisdb] = extract_some_data(result, thisdb, host)
end
if have_objects and whoisdb[thisdb].redirects then
found, nextdb, ianahits =
ruleset(thisdb, host, extrctd_data, ianahits)
else
found, nextdb = true, nil
end
if have_objects and found then
-- optionally constrain result to a more focused area
-- discarding previous extraction
if whoisdb[thisdb].smallnet_rule then
local offset, ptr, strbgn, strend
result_chunk, offset = constrain_result(result, thisdb, host)
if offset > 0 then
extrctd_data[thisdb] = nil
extrctd_data[thisdb] =
extract_some_data(result_chunk, thisdb, host)
end
if offset > 1 then
-- fetch an object immediately in front of inetnum
stdnse.print_debug(2, dbg .. " " .. thisdb ..
" Searching for an object group immediately before this range.")
result_chunk = result:sub(1,offset)
-- avoid getting the first few objects of the result by starting
-- half-way between 1 and offset
ptr = (math.modf((result_chunk:len())/2))
for i, v in pairs(whoisdb[thisdb].fieldreq) do
if i ~= "ob_exist" then
strbgn, strend =
result_chunk:find(v.ob_start, ptr)
if strbgn then
if not next(extrctd_data[thisdb][i]) then
tmp, strend = result_chunk:find(v.ob_end, ptr + strbgn)
strend = strend or -1
extrctd_data[thisdb][i] = extract_some_data(
result_chunk:sub(strbgn, strend), thisdb, host, i)
end
end
end
end -- for i, v
end -- if offset
end -- if whoisdb[thisdb].smallnet_rule
-- DEBUG
stdnse.print_debug(4, dbg .. " " .. thisdb ..
" Fields captured :")
for obgroup, t in pairs(extrctd_data[thisdb]) do
for fieldname, fieldvalue in pairs(t) do
stdnse.print_debug(4, dbg .. " " .. thisdb .. " field " ..
obgroup .. "." ..fieldname .. " " .. fieldvalue)
end
end
end -- if have_objects and found
-- DEBUG
if nextdb then stdnse.print_debug(2, dbg .. " " .. thisdb ..
" redirects to " .. nextdb) end
local dbg_found =
dbg .. " " .. thisdb .. " query concluded with Record "
if found then
dbg_found = dbg_found .. "Found"
else
dbg_found = dbg_found .. "Not Found"
end
stdnse.print_debug(2, dbg_found)
end -- while trynum <= numtotry and not found
-- ALL QUERIES COMPLETED
-- CATCH IANA
-- if we have not found a good record but had more than one redirect
-- to iana then default to the arin record
if not found and ianahits > 1 then
stdnse.print_debug(1, dbg ..
" A Record not been found, but we have been directed to IANA " ..
ianahits .. " times.")
found = true
thisdb = whoisdb.arin.id
end
-- NO RECORD HAS BEEN FOUND/RECOGNISED
if not found then
return "script failed to find a record or " ..
"couldn't understand the query responses."
end
-- DISPLAY THE FOUND RECORD
-- ipairs over the table that dictates the order in which fields
-- should be output
local grpname = ""
local fieldname = ""
local display_rules = {}
if whoisdb[thisdb].output_long and
not whoisdb[thisdb].output_short then
whoisdb[thisdb].output_short = whoisdb[thisdb].output_long
end
if have_objects and whoisdb[thisdb].output_short then
if verbos_level > 0 and whoisdb[thisdb].output_long then
display_rules = whoisdb[thisdb].output_long
else
display_rules = whoisdb[thisdb].output_short
end
stuff_to_display = "Record found at " .. whoisdb[thisdb].hostname
for _, obtbl in ipairs(display_rules) do
for _, flds in ipairs(obtbl) do
if type(flds) == "string" then
grpname = flds
if not extrctd_data[thisdb][grpname] then break end
end
if type(flds) == "table" then
for _, fld in ipairs(flds) do
local fvalue
if type(fld) == "string" then
fvalue = extrctd_data[thisdb][grpname][fld]
if fvalue then
stuff_to_display = stuff_to_display .. " \n" .. fld ..
": " .. fvalue
end
end
if type(fld) == "table" then
local buildstr
local f
for _, fd in ipairs(fld) do
fvalue =
extrctd_data[thisdb][grpname][fd]
if fvalue then
if not buildstr then
buildstr = fd .. ": " .. fvalue
else
buildstr = buildstr .. " " .. fd .. ": " .. fvalue
end
end
end
if buildstr then
stuff_to_display = stuff_to_display ..
" \n" .. trim(buildstr)
end
end
end -- for _, fld
end
end -- for _, flds
end -- for _, obtbl
elseif not stuff_to_display then
stuff_to_display = " Record was not found."
end -- if have_objects and
return stuff_to_display
end -- ACTION_______________________________________________ Sent through the nmap-dev mailing list http://cgi.insecure.org/mailman/listinfo/nmap-dev Archived at http://SecLists.Org
Current thread:
- [NSE] WHOIS jah (Feb 02)
- Re: [NSE] WHOIS Fyodor (Feb 03)
- Re: [NSE] WHOIS jah (Feb 04)
- Re: [NSE] WHOIS Diman Todorov (Feb 04)
- Re: [NSE] WHOIS - Attempts to queue coroutines to limit the number of whois queries. jah (Feb 14)
- Re: [NSE] WHOIS jah (Feb 04)
- Re: [NSE] WHOIS Kris Katterjohn (Feb 04)
- <Possible follow-ups>
- Re: [NSE] WHOIS 4N9e Gutek (Feb 03)
- Re: [NSE] WHOIS Fyodor (Feb 03)
