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)