diff --git a/nselib/dns.lua b/nselib/dns.lua index a7de67e82..9f9d8df59 100644 --- a/nselib/dns.lua +++ b/nselib/dns.lua @@ -10,6 +10,10 @@ require("stdnse") get_servers = nmap.get_dns_servers +--- +-- Table of DNS resource types. +-- @name types +-- @class table types = { A = 1, AAAA = 28, @@ -27,16 +31,6 @@ types = { } ---- --- Table of error codes. --- @name err --- @class table -err = { - noSuchName = 3, - noServers = 9 -} - - --- -- Repeatedly sends UDP packets to host, waiting for an answer. -- @param data Data to be sent. @@ -139,9 +133,10 @@ end -- * retAll: Return all answers, not just the first. -- * retPkt: Return the packet instead of using the answer-fetching mechanism. -- * norecurse If true, do not set the recursion (RD) flag. --- @return Nice answer string by an answer fetcher on success or false on error. --- @return An error code on error. --- @see dns.err +-- @return True if a dns response was received and contained an answer of the requested type, +-- or the decoded dns response was requested (retPkt) and is being returned - or False otherwise. +-- @return String answer of the requested type, Table of answers or a String error message of one of the following: +-- "No Such Name", "No Servers", "No Answers", "Unable to handle response" function query(dname, options) if not options then options = {} end @@ -162,16 +157,11 @@ function query(dname, options) local srvI = 1 if not port then port = 53 end if not host then - -- IF PATCH NOT APPLIED! - if type(get_servers) == "function" then - srv = get_servers() - end - -- ! - + srv = get_servers() if srv and srv[1] then host = srv[1] else - return false, err.noServers + return false, "No Servers" end elseif type(host) == "table" then srv = host @@ -200,7 +190,7 @@ function query(dname, options) -- is it a real answer? if gotAnswer(rPkt) then if (options.retPkt) then - return rPkt + return true, rPkt else return findNiceAnswer(dtype, rPkt, options.retAll) end @@ -224,10 +214,12 @@ function query(dname, options) end end - -- nothing worked, maybe user finds decoded packet useful - return false, rPkt + -- nothing worked + stdnse.print_debug(1, "dns.query() failed to resolve the requested query%s%s", dname and ": " or ".", dname or "") + return false, "No Answers" else - return false + stdnse.print_debug(1, "dns.query() got zero responses attempting to resolve query%s%s", dname and ": " or ".", dname or "") + return false, "No Answers" end end @@ -277,37 +269,48 @@ local answerFetcher = {} -- Answer fetcher for TXT records. -- @param dec Decoded DNS response. -- @param retAll If true, return all entries, not just the first. --- @return First entry (or all of them), treated as TXT. +-- @return True if one or more answers of the required type were found - otherwise false. +-- @return String first dns TXT record or Table of TXT records or String Error message. answerFetcher[types.TXT] = function(dec, retAll) - if not retAll then + local answers = {} + if not retAll and dec.answers[1].data then return string.sub(dec.answers[1].data, 2) + elseif not retAll then + stdnse.print_debug(1, "dns.answerFetcher found no records of the required type: TXT") + return false, "No Answers" else - local answers = {} for _, v in ipairs(dec.answers) do if v.data then table.insert(answers, string.sub(v.data, 2)) end end - return answers end + if #answers == 0 then + stdnse.print_debug(1, "dns.answerFetcher found no records of the required type: TXT") + return false, "No Answers" + end + return true, answers end --- -- Answer fetcher for A records -- @param dec Decoded DNS response. -- @param retAll If true, return all entries, not just the first. --- @return First IP (or all of them) of response packet. +-- @return True if one or more answers of the required type were found - otherwise false. +-- @return String first dns A record or Table of A records or String Error message. answerFetcher[types.A] = function(dec, retAll) local answers = {} for _, ans in ipairs(dec.answers) do if ans.dtype == types.A then if not retAll then - return ans.ip - else - table.insert(answers, ans.ip) + return true, ans.ip end + table.insert(answers, ans.ip) end end - if retAll then return answers end - return dec + if not retAll or #answers == 0 then + stdnse.print_debug(1, "dns.answerFetcher found no records of the required type: A") + return false, "No Answers" + end + return true, answers end @@ -315,38 +318,59 @@ end -- Answer fetcher for CNAME records. -- @param dec Decoded DNS response. -- @param retAll If true, return all entries, not just the first. --- @return Domain entry of first answer RR (or all of them) in response packet. +-- @return True if one or more answers of the required type were found - otherwise false. +-- @return String first Domain entry or Table of domain entries or String Error message. answerFetcher[types.CNAME] = function(dec, retAll) - if not retAll then - return dec.answers[1].domain + local answers = {} + if not retAll and dec.answers[1].domain then + return true, dec.answers[1].domain + elseif not retAll then + stdnse.print_debug(1, "dns.answerFetcher found no records of the required type: NS, PTR or CNAME") + return false, "No Answers" else - local answers = {} for _, v in ipairs(dec.answers) do if v.domain then table.insert(answers, v.domain) end end - return answers end + if #answers == 0 then + stdnse.print_debug(1, "dns.answerFetcher found no records of the required type: NS, PTR or CNAME") + return false, "No Answers" + end + return true, answers end ---- -- Answer fetcher for MX records. -- @param dec Decoded DNS response. -- @param retAll If true, return all entries, not just the first. --- @return Domain entry of first answer RR (or all of them) in response packet. +-- @return True if one or more answers of the required type were found - otherwise false. +-- @return String first dns MX record or Table of MX records or String Error message. +-- Note that the format of a returned MX answer is "preference:hostname:IPaddress" where zero +-- or more IP addresses may be present. answerFetcher[types.MX] = function(dec, retAll) - if not retAll then - if dec.answers[1] then - return dec.answers[1].MX.pref .. ":" .. dec.answers[1].MX.server .. ":" .. dec.add[1].ip - else - return dec - end - else - local answers = {} - for _, v in ipairs(dec.answers) do - if v.MX then table.insert(answers, v.MX.pref .. ":" .. v.MX.server .. ":" .. v.MX.ip) end - end - return answers + local mx, ip, answers = {}, {}, {} + for _, ans in ipairs(dec.answers) do + if ans.MX then mx[#mx+1] = ans.MX end + if not retAll then break end end + if #mx == 0 then + stdnse.print_debug(1, "dns.answerFetcher found no records of the required type: MX") + return false, "No Answers" + end + for _, add in ipairs(dec.add) do + if ip[add.dname] then table.insert(ip[add.dname], add.ip) + else ip[add.dname] = {add.ip} end + end + for _, mxrec in ipairs(mx) do + if ip[mxrec.server] then + table.insert( answers, ("%s:%s:%s"):format(mxrec.pref or "-", mxrec.server or "-", table.concat(ip[mxrec.server], ":")) ) + if not retAll then return true, answers[1] end + else + -- no IP ? + table.insert( answers, ("%s:%s"):format(mxrec.pref or "-", mxrec.server or "-") ) + if not retAll then return true, answers[1] end + end + end + return true, answers end @@ -355,8 +379,8 @@ end -- @name answerFetcher[types.NS] -- @class function -- @param dec Decoded DNS response. --- @param retAll If true, return all entries, not just the first. --- @return Domain entry of first answer RR (or all of them) in response packet. +-- @return True if one or more answers of the required type were found - otherwise false. +-- @return String first Domain entry or Table of domain entries or String Error message. answerFetcher[types.NS] = answerFetcher[types.CNAME] --- @@ -365,27 +389,31 @@ answerFetcher[types.NS] = answerFetcher[types.CNAME] -- @class function -- @param dec Decoded DNS response. -- @param retAll If true, return all entries, not just the first. --- @return Domain entry of first answer RR (or all of them) in response packet. +-- @return True if one or more answers of the required type were found - otherwise false. +-- @return String first Domain entry or Table of domain entries or String Error message. answerFetcher[types.PTR] = answerFetcher[types.CNAME] --- -- Answer fetcher for AAAA records. -- @param dec Decoded DNS response. -- @param retAll If true, return all entries, not just the first. --- @return First IPv6 (or all of them) of response packet. +-- @return True if one or more answers of the required type were found - otherwise false. +-- @return String first dns AAAA record or Table of AAAA records or String Error message. answerFetcher[types.AAAA] = function(dec, retAll) local answers = {} for _, ans in ipairs(dec.answers) do if ans.dtype == types.AAAA then if not retAll then - return ans.ipv6 - else - table.insert(answers, ans.ipv6) + return true, ans.ipv6 end + table.insert(answers, ans.ipv6) end end - if retAll then return answers end - return dec + if not retAll or #answers == 0 then + stdnse.print_debug(1, "dns.answerFetcher found no records of the required type: AAAA") + return false, "No Answers" + end + return true, answers end @@ -395,20 +423,22 @@ end -- @param dtype DNS resource record type. -- @param dec Decoded DNS response. -- @param retAll If true, return all entries, not just the first. --- @return Answer according to the answer fetcher for dtype, --- dec if no answer fetcher is known for dtype, or --- false if flags indicate an error. --- @return Error code on error. +-- @return True if one or more answers of the required type were found - otherwise false. +-- @return Answer according to the answer fetcher for dtype or an Error message. function findNiceAnswer(dtype, dec, retAll) if (#dec.answers > 0) then if answerFetcher[dtype] then return answerFetcher[dtype](dec, retAll) else - return dec + stdnse.print_debug(1, "dns.findNiceAnswer() does not have an answerFetcher for dtype %s", + (type(dtype) == 'string' and dtype) or type(dtype) or "nil") + return false, "Unable to handle response" end + elseif (dec.flags.RC3 and dec.flags.RC4) then + return false, "No Such Name" else - if (dec.flags.RC3 and dec.flags.RC4) then return false, err.noSuchName - end + stdnse.print_debug(1, "dns.findNiceAnswer() found zero answers in a response, but got an unexpected flags.replycode") + return false, "No Answers" end end