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 = 1


Other 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: