Nmap Security Scanner
*Intro
*Ref Guide
*Install Guide
*Download
*Changelog
*Book
*Docs
Security Lists
*Nmap Hackers
*Nmap Dev
*Bugtraq
*Full Disclosure
*Pen Test
*Basics
*More
Security Tools
*Pass crackers
*Sniffers
*Vuln Scanners
*Web scanners
*Wireless
*Exploitation
*Packet crafters
*More
Site News
Site Search:
Exploit World
Advertising
About/Contact
Credits
Sponsors:
edgeos



Nmap Development: Re: [NSE] WHOIS - Now with Queuing and Cached Results.

Re: [NSE] WHOIS - Now with Queuing and Cached Results.

From: jah <jah_at_zadkiel.plus.com>
Date: Wed, 23 Apr 2008 03:51:03 +0100

On 09/04/2008 02:32, majek04 wrote:
> On Fri, Feb 15, 2008 at 3:54 AM, jah <jah_at_zadkiel.plus.com> wrote:
>
>> My problem is; how to make a coroutine wait?
>>
>
> In os.nse [1] I use this solution, based on pcap (raw sockets):
>
> -- hack used only to go back to event loop
> local pcap = nmap.new_socket()
> function pcap_callback() return 'xxx' end
> pcap:pcap_open(interface, 64, 0, pcap_callback,
> "tcp[0] == 0xDE and ((tcp[tcpflags] & tcp-fin) != 0) " )
>
>
> -- go back to the event loop for a while
> pcap:set_timeout(XXX) -- XXXms
> pcap:pcap_register('aaa')
> _, _, _, _, _ = pcap:pcap_receive()
>
>
> But we really should create something more standard, like nmap.sleep().
> Maybe it's a good idea for SoC?
>
nmap.sleep() sounds like a good idea to me. Until then, I've used
Marek's pcap hack for whois.nse to enforce a strict queue for each whois
service. This enables caching of results and means that a) for a given
net-range only one query per whois service is required for any targets
within that range and b) a given whois service will have only one query
outstanding at any time - queries to different whois services can still
happen concurrently.
Once a record has been found for a target, any further targets within
that range will point to the same result, rather than print it again.

I've attached the new and improved whois.nse and welcome any feedback -
especially if you manage to break it.

Cheers,

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 = "Same as Nmap--See http://nmap.org/book/man-legal.html"
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"}} },
                reg = "netrange"},
        ripe = {
                id = "ripe",
                hostname = "whois.ripe.net", preflag = "-B ", 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"}} },
                reg = "inetnum" },
        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"}} },
                reg = "inetnum" },
        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"}} },
                reg = "inetnum" },
        afrinic = {
                id = "afrinic",
                hostname = "whois.afrinic.net", preflag = "-c ", 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"}} },
                reg = "inetnum" },
        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"}} },
                reg = "inetnum" },
        iana = { -- not actually a db but required here
                id = "iana", longname = {"internet assigned numbers authority"} }
        }

-- cached results
nmap.registry.whois = {}

-- queue for queries
local queue = {}
for i,v in pairs(whoisdb) do
        if i ~= "iana" then queue[i]={} end
end

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)
		pop_queue(host.ip)
		socket:close()
	end
	local try = nmap.new_try(catch)
	local v = whoisdb[db]
	if not v then return false end
	socket:set_timeout(10000)
	connect_attempts = connect_attempts + 1
	try(socket:connect(v.hostname, 43))
	if check_registry(host.ip) then socket:close(); return end
	send_attempts = send_attempts + 1
	try(socket:send(v.preflag .. host.ip .. v.postflag .. "\n"))
	if check_registry(host.ip) then socket:close(); return end
	while true do
		local status, lines = socket:receive_lines(1)
		if not status then
			break
		else
			result = result .. lines
			if check_registry(host.ip) then socket:close(); return end
		end
	end
	socket:close()
	results_returned = results_returned + 1
	return result
end
-- todotted
-- convert dword into dotted IPv4 notation (returns a string)
todotted = function(dword)
	-- perhaps ought to check dword is legal
	local a,b,c,_
	a, _ = math.modf(dword/256^3)
	dword = dword - ipOps.todword(a .. ".0.0.0")
	b, _ = math.modf(dword/256^2)
	dword = dword - ipOps.todword("0." .. b .. ".0.0")
	c, _ = math.modf(dword/256)
	dword = dword - ipOps.todword("0.0." .. c .. ".0")
	return a .. "." .. b .. "." .. c .. "." .. dword
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
		-- this will be unpredictable if we have two equal ranges!
		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
function check_registry(ip, db)
	if not next(nmap.registry.whois) then return nil end
	local h_ip = ip
	for cached_ip, t in pairs(nmap.registry.whois) do
		if  ip ~= cached_ip and t.range and ip_in_net(ip,t.range) and
		math.abs(ipOps.todword(h_ip) - ipOps.todword(cached_ip))	< 256 then
			if t.next_thisdb then
				-- pop_queue(ip,db)
				return cached_ip, t.next_thisdb
			else
				pop_queue(ip)
				return cached_ip, nil
			end
		end
	end
end
function pop_queue(ip, db)
	-- remove host.ip from db queue or, all queues if db is nil
	if not db then
		for db,_ in pairs(queue) do
			for i,_ in ipairs(queue[db]) do
				if queue[db][i] == ip then
					table.remove(queue[db],i)
					break
				end
			end
		end
	else
		for i,_ in ipairs(queue[db]) do
			if queue[db][i] == ip then
				table.remove(queue[db],i)
				break
			end
		end
	end
end
-- create a single socket object per instance for sleep() hack
local pcap = nmap.new_socket()
function pcap_callback() return 'xxx' end
--
--
-- ACTION
--
action = function(host, port)
	-- open pcap socket for sleep() hack (if not open already)
	if not pcap:get_info() then
		pcap:pcap_open(host.interface, 64, 0, pcap_callback,
			"tcp[0:2] == 0xFFFF and tcp[2:2] == 0xFFFF" )
	end
-- add host to the queues for each whoisdb
	for i,v in pairs(queue) do
		table.insert(queue[i], host.ip)
	end
	-- expect liberal checking for cached results!
	local cached_ip, next_thisdb = check_registry(host.ip)
	if cached_ip and not next_thisdb then return "see the result for " ..
		cached_ip end
	local nextdb = next_thisdb or nil
	-- 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 vars
	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 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
		local alt = ''
		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
		else
			-- debug
			alt = " (from redirect)"
		end
		-- Check the registry
		cached_ip, next_thisdb = check_registry(host.ip,thisdb)
		if cached_ip and not next_thisdb then return "see the result for " ..
			cached_ip end
		thisdb = next_thisdb or thisdb
		-- Make this coroutine wait until it is first in the queue for thisdb
		local count_loops = 1
		local time_out = 500
		local first_in_queue = queue[thisdb][1]
		while queue[thisdb][1] ~= host.ip do
 			-- reset count if the first_in_queue has changed during this loop
 			if queue[thisdb][1] and queue[thisdb][1] ~= first_in_queue then
 				for cached_ip, t in pairs(nmap.registry.whois) do
 					if cached_ip and t.range and not
 						ip_in_net(queue[thisdb][1],t.range) then
 						first_in_queue = queue[thisdb][1]
 						count_loops = 1
 						break
 					end
 				end
 			end
			-- yield() this coroutine for time_out ms
			pcap:set_timeout(time_out)
			pcap:pcap_register('aaa')
			_, _, _, _, _ = pcap:pcap_receive()
			count_loops = count_loops + 1
			cached_ip, next_thisdb = check_registry(host.ip,thisdb)
			-- if a result for the range containing host.ip has been found,
			-- return immediately from action().
			if cached_ip and not next_thisdb then
				return "see the result for " .. cached_ip end
			-- if a redirect has been found for the range containing host.ip,
			-- queue instead for next_thisdb.
			if next_thisdb and next_thisdb ~= thisdb then
				thisdb = next_thisdb; count_loops = 1 end
		end
		--
		-- SEND QUERY TO THISDB
		--
		stdnse.print_debug(2, dbg .. " Begin Query at " .. thisdb .. alt)
		result = querydo(thisdb, host)
		-- Check the registry
		local cached_ip, this_nextdb = check_registry(host.ip)
		if cached_ip and not this_nextdb then return "see the result for " ..
			cached_ip end
		-- keep track of which dbs we've queried - add thisdb
		track[#track+1] = thisdb
		-- break out of the main loop if we have unknown issues...
		if not result and not this_nextdb 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 and not next_thisdb then
			have_objects = result:match(whoisdb[thisdb].fieldreq.ob_exist)
		elseif not whoisdb[thisdb].fieldreq then
			have_objects = false
			stdnse.print_debug(2, "missing property: whoisdb." ..
				thisdb .. ".fieldreq")
		elseif next_thisdb then
			nextdb = next_thisdb
			have_objects = false
			found = false
		end
		-- if we do not recognise objects check for an error/message
		if not have_objects and not next_thisdb 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_mirrored
			for setname, set in pairs(fields_meta) do
				if set ~= whoisdb[thisdb].fieldreq and
				result:match(set.ob_exist) then
					db_mirrored = setname
					stdnse.print_debug(1, dbg .. " " .. thisdb ..
						" seems to have responded using the set of objects named: " ..
						setname)
					break
				end
			end
			if db_mirrored 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_mirrored].fieldreq and
					db_props.fieldreq == whoisdb[db_mirrored].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_mirrored
		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)
			if not found and nextdb then
				-- cache this wide range and redirect
				if extrctd_data[thisdb] and extrctd_data[thisdb].ob_netnum then
					nmap.registry.whois[host.ip] = {}
					nmap.registry.whois[host.ip].range =
						extrctd_data[thisdb].ob_netnum[whoisdb[thisdb].reg]
					nmap.registry.whois[host.ip].next_thisdb = nextdb
					-- remove all ips in cached range from the queue
					for cached_ip, t in pairs(nmap.registry.whois) do
						if host.ip == cached_ip then
							local h_ip = queue[thisdb][1]
							while h_ip and t.range and ip_in_net(h_ip,t.range) and
							math.abs(ipOps.todword(cached_ip) - ipOps.todword(h_ip))
							< 256 do
								pop_queue(queue[thisdb][1],thisdb)
								h_ip = queue[thisdb][1]
							end
						end
					end
				end
			end
		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 which should be enough...
					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)
		-- if cache exists we can remove host.ip from the queue for thisdb
		pop_queue(host.ip, thisdb)
	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
		-- remove host.ip from all queues
		pop_queue(host.ip)
		return "script failed to find a record or " ..
		"couldn't understand the query responses."
	end
	-- CACHE A RESULT IN THE REGISTRY
	-- store the target IP address and net range
	if found and have_objects then
		if extrctd_data[thisdb] and extrctd_data[thisdb].ob_netnum then
			nmap.registry.whois[host.ip] = {}
			nmap.registry.whois[host.ip].range =
				extrctd_data[thisdb].ob_netnum[whoisdb[thisdb].reg]
		end
	end -- if found and have_objects
	if found and not have_objects and stuff_to_display then
		-- We don't have a net range to cache so we create one using the
		-- (poss. inacurate) assumption that any target within 32 addresses
		-- will share the same non-record message
		local fake_range = host.ip .. " - " ..
		todotted(ipOps.todword(host.ip) + 31)
		nmap.registry.whois[host.ip] = {}
		nmap.registry.whois[host.ip].range = fake_range
		for cached_ip, t in pairs(nmap.registry.whois) do
				if host.ip == cached_ip then
					local h_ip = queue[thisdb][1]
					while h_ip and t.range and ip_in_net(h_ip,t.range) and
					math.abs(ipOps.todword(cached_ip) - ipOps.todword(h_ip))
					< 256 do
						pop_queue(queue[thisdb][1],thisdb)
						h_ip = queue[thisdb][1]
					end
				end
		end
	end
	-- We can now remove host.ip from each queue it's in
	pop_queue(host.ip)
	-- 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
	-- alles klar
	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
Received on Apr 22 2008

[ Nmap | Sec Tools | Mailing Lists | Site News | About/Contact | Advertising | Privacy ]
edgeos