ref: 3519f1d58ff78407227adb5a3a202d7a4e626a93
dir: /lib/lua/nseport/stdnse.lua/
--- -- Standard Nmap Scripting Engine functions. This module contains various handy -- functions that are too small to justify modules of their own. -- -- @copyright Same as Nmap--See https://nmap.org/book/man-legal.html -- @class module -- @name stdnse local _G = require "_G" local p9 = require "p9" local coroutine = require "coroutine" local math = require "math" local string = require "string" local table = require "table" local assert = assert; local error = error; local getmetatable = getmetatable; local ipairs = ipairs local pairs = pairs local next = next local rawset = rawset local require = require; local select = select local setmetatable = setmetatable; local tonumber = tonumber; local tostring = tostring; local print = print; local type = type local pcall = pcall local ceil = math.ceil local max = math.max local format = string.format; local rep = string.rep local match = string.match local find = string.find local sub = string.sub local gsub = string.gsub local char = string.char local byte = string.byte local gmatch = string.gmatch local concat = table.concat; local insert = table.insert; local remove = table.remove; local pack = table.pack; local unpack = table.unpack; local EMPTY = require "strict" {}; -- Empty constant table _ENV = {}; --- Sleeps for a given amount of time. -- -- This causes the program to yield control and not regain it until the time -- period has elapsed. The time may have a fractional part. Internally, the -- timer provides millisecond resolution. -- @name sleep -- @class function -- @param t Time to sleep, in seconds. -- @usage stdnse.sleep(1.5) local function sleep (seconds) p9.sleep(seconds * 1000) end _ENV.sleep = sleep; -- These stub functions get overwritten by the script run loop in nse_main.lua -- These empty stubs will be used if a library calls stdnse.debug while loading _ENV.getid = function () return end _ENV.getinfo = function () return end _ENV.gethostport = function () return end do local t = { ["0"] = "0000", ["1"] = "0001", ["2"] = "0010", ["3"] = "0011", ["4"] = "0100", ["5"] = "0101", ["6"] = "0110", ["7"] = "0111", ["8"] = "1000", ["9"] = "1001", a = "1010", b = "1011", c = "1100", d = "1101", e = "1110", f = "1111" }; --- Converts the given number, n, to a string in a binary number format (12 -- becomes "1100"). Leading 0s not stripped. -- @param n Number to convert. -- @return String in binary format. function tobinary(n) -- enforced by string.format: assert(tonumber(n), "number expected"); return gsub(format("%x", n), "%w", t) end end --- Converts the given number, n, to a string in an octal number format (12 -- becomes "14"). -- @param n Number to convert. -- @return String in octal format. function tooctal(n) -- enforced by string.format: assert(tonumber(n), "number expected"); return format("%o", n) end local tohex_helper = function(b) return format("%02x", byte(b)) end --- Encode a string or integer in hexadecimal (12 becomes "c", "AB" becomes -- "4142"). -- -- An optional second argument is a table with formatting options. The possible -- fields in this table are -- * <code>separator</code>: A string to use to separate groups of digits. -- * <code>group</code>: The size of each group of digits between separators. Defaults to 2, but has no effect if <code>separator</code> is not also given. -- @usage -- stdnse.tohex("abc") --> "616263" -- stdnse.tohex("abc", {separator = ":"}) --> "61:62:63" -- stdnse.tohex("abc", {separator = ":", group = 4}) --> "61:6263" -- stdnse.tohex(123456) --> "1e240" -- stdnse.tohex(123456, {separator = ":"}) --> "1:e2:40" -- stdnse.tohex(123456, {separator = ":", group = 4}) --> "1:e240" -- @param s String or number to be encoded. -- @param options Table specifying formatting options. -- @return String in hexadecimal format. function tohex( s, options ) options = options or EMPTY local separator = options.separator local hex if type( s ) == "number" then hex = format("%x", s) elseif type( s ) == 'string' then hex = gsub(s, ".", tohex_helper) else error( "Type not supported in tohex(): " .. type(s), 2 ) end -- format hex if we got a separator if separator then local group = options.group or 2 local subs = 0 local pat = "(%x)(" .. rep("[^:]", group) .. ")%f[\0:]" repeat hex, subs = gsub(hex, pat, "%1:%2") until subs == 0 end return hex end local fromhex_helper = function (h) return char(tonumber(h, 16)) end ---Decode a hexadecimal string to raw bytes -- -- The string can contain any amount of whitespace and capital or lowercase -- hexadecimal digits. There must be an even number of hex digits, since it -- takes 2 hex digits to make a byte. -- -- @param hex A string in hexadecimal representation -- @return A string of bytes or nil if string could not be decoded -- @return Error message if string could not be decoded function fromhex (hex) local p = find(hex, "[^%x%s]") if p then return nil, "Invalid hexadecimal digits at position " .. p end hex = gsub(hex, "%s+", "") if #hex % 2 ~= 0 then return nil, "Odd number of hexadecimal digits" end return gsub(hex, "..", fromhex_helper) end local colonsep = {separator=":"} ---Format a MAC address as colon-separated hex bytes. --@param mac The MAC address in binary, such as <code>host.mac_addr</code> --@return The MAC address in XX:XX:XX:XX:XX:XX format function format_mac(mac) return tohex(mac, colonsep) end ---Either return the string itself, or return "<blank>" (or the value of the second parameter) if the string -- was blank or nil. -- --@param string The base string. --@param blank The string to return if <code>string</code> was blank --@return Either <code>string</code> or, if it was blank, <code>blank</code> function string_or_blank(string, blank) if(string == nil or string == "") then if(blank == nil) then return "<blank>" else return blank end else return string end end local timespec_multipliers = {[""] = 1, s = 1, m = 60, h = 60 * 60, ms = 0.001} --- -- Parses a time duration specification, which is a number followed by a -- unit, and returns a number of seconds. -- -- The unit is optional and defaults to seconds. The possible units -- (case-insensitive) are -- * <code>ms</code>: milliseconds, -- * <code>s</code>: seconds, -- * <code>m</code>: minutes, -- * <code>h</code>: hours. -- In case of a parsing error, the function returns <code>nil</code> -- followed by an error message. -- -- @usage -- parse_timespec("10") --> 10 -- parse_timespec("10ms") --> 0.01 -- parse_timespec("10s") --> 10 -- parse_timespec("10m") --> 600 -- parse_timespec("10h") --> 36000 -- parse_timespec("10z") --> nil, "Can't parse time specification \"10z\" (bad unit \"z\")" -- -- @param timespec A time specification string. -- @return A number of seconds, or <code>nil</code> followed by an error -- message. function parse_timespec(timespec) if timespec == nil then return nil, "Can't parse nil timespec" end local n, unit, t, m n, unit = match(timespec, "^([%d.]+)(.*)$") if not n then return nil, format("Can't parse time specification \"%s\"", timespec) end t = tonumber(n) if not t then return nil, format("Can't parse time specification \"%s\" (bad number \"%s\")", timespec, n) end m = timespec_multipliers[unit] if not m then return nil, format("Can't parse time specification \"%s\" (bad unit \"%s\")", timespec, unit) end return t * m end --- Returns the current time in milliseconds since the epoch -- @return The current time in milliseconds since the epoch function clock_ms() return p9.clock() * 1000 end --- Returns the current time in microseconds since the epoch -- @return The current time in microseconds since the epoch function clock_us() return p9.clock() * 1000000 end ---Get the indentation symbols at a given level. local function format_get_indent(indent) return rep(" ", #indent) end -- A helper for format_output (see below). local function format_output_sub(status, data, indent) if (#data == 0) then return "" end -- Used to put 'ERROR: ' in front of all lines on error messages local prefix = "" -- Initialize the output string to blank (or, if we're at the top, add a newline) local output = {} if(not(indent)) then insert(output, '\n') end if(not(status)) then if(nmap.debugging() < 1) then return nil end prefix = "ERROR: " end -- If a string was passed, turn it into a table if(type(data) == 'string') then data = {data} end -- Make sure we have an indent value indent = indent or {} if(data['name']) then if(data['warning'] and nmap.debugging() > 0) then insert(output, format("%s%s%s (WARNING: %s)\n", format_get_indent(indent), prefix, data['name'], data['warning'])) else insert(output, format("%s%s%s\n", format_get_indent(indent), prefix, data['name'])) end elseif(data['warning'] and nmap.debugging() > 0) then insert(output, format("%s%s(WARNING: %s)\n", format_get_indent(indent), prefix, data['warning'])) end for i, value in ipairs(data) do if(type(value) == 'table') then -- Do a shallow copy of indent local new_indent = {} for _, v in ipairs(indent) do insert(new_indent, v) end if(i ~= #data) then insert(new_indent, false) else insert(new_indent, true) end insert(output, format_output_sub(status, value, new_indent)) elseif(type(value) == 'string') then -- ensure it ends with a newline if sub(value, -1) ~= "\n" then value = value .. "\n" end for line in gmatch(value, "([^\r\n]-)\n") do insert(output, format("%s %s%s\n", format_get_indent(indent), prefix, line)) end end end return concat(output) end ---This function is deprecated. -- -- Please use structured NSE output instead: https://nmap.org/book/nse-api.html#nse-structured-output -- -- Takes a table of output on the commandline and formats it for display to the -- user. -- -- This is basically done by converting an array of nested tables into a -- string. In addition to numbered array elements, each table can have a 'name' -- and a 'warning' value. The 'name' will be displayed above the table, and -- 'warning' will be displayed, with a 'WARNING' tag, if and only if debugging -- is enabled. -- -- Here's an example of a table: -- <code> -- local domains = {} -- domains['name'] = "DOMAINS" -- table.insert(domains, 'Domain 1') -- table.insert(domains, 'Domain 2') -- -- local names = {} -- names['name'] = "NAMES" -- names['warning'] = "Not all names could be determined!" -- table.insert(names, "Name 1") -- -- local response = {} -- table.insert(response, "Apple pie") -- table.insert(response, domains) -- table.insert(response, names) -- -- return stdnse.format_output(true, response) -- </code> -- -- With debugging enabled, this is the output: -- <code> -- Host script results: -- | smb-enum-domains: -- | Apple pie -- | DOMAINS -- | Domain 1 -- | Domain 2 -- | NAMES (WARNING: Not all names could be determined!) -- |_ Name 1 -- </code> -- --@param status A boolean value dictating whether or not the script succeeded. -- If status is false, and debugging is enabled, 'ERROR' is prepended -- to every line. If status is false and debugging is disabled, no output -- occurs. --@param data The table of output. --@param indent Used for indentation on recursive calls; should generally be set to -- nil when calling from a script. -- @return <code>nil</code>, if <code>data</code> is empty, otherwise a -- multiline string. function format_output(status, data, indent) -- If data is nil, die with an error (I keep doing that by accident) assert(data, "No data was passed to format_output()") -- Don't bother if we don't have any data if (#data == 0) then return nil end local result = format_output_sub(status, data, indent) -- Check for an empty result if(result == nil or #result == "" or result == "\n" or result == "\n") then return nil end return result end --- Module function that mimics some behavior of Lua 5.1 module function. -- -- This convenience function returns a module environment to set the _ENV -- upvalue. The _NAME, _PACKAGE, and _M fields are set as in the Lua 5.1 -- version of this function. Each option function (e.g. stdnse.seeall) -- passed is run with the new environment, in order. -- -- @see stdnse.seeall -- @see strict -- @usage -- _ENV = stdnse.module(name, stdnse.seeall, require "strict"); -- @param name The module name. -- @param ... Option functions which modify the environment of the module. function module (name, ...) local env = {}; env._NAME = name; env._PACKAGE = match(name, "(.+)%.[^.]+$"); env._M = env; local mods = pack(...); for i = 1, mods.n do mods[i](env); end return env; end --- Change environment to load global variables. -- -- Option function for use with stdnse.module. It is the same -- as package.seeall from Lua 5.1. -- -- @see stdnse.module -- @usage -- _ENV = stdnse.module(name, stdnse.seeall); -- @param env Environment to change. function seeall (env) local m = getmetatable(env) or {}; m.__index = _G; setmetatable(env, m); end --- Return a table that keeps elements in order of insertion. -- -- The pairs function, called on a table returned by this function, will yield -- elements in the order they were inserted. This function is meant to be used -- to construct output tables returned by scripts. -- -- Reinserting a key that is already in the table does not change its position -- in the order. However, removing a key by assigning to <code>nil</code> and -- then doing another assignment will move the key to the end of the order. -- -- @return An ordered table. function output_table () local t = {} local order = {} local function iterator () for i, key in ipairs(order) do coroutine.yield(key, t[key]) end end local mt = { __newindex = function (_, k, v) if t[k] == nil and v ~= nil then -- New key? insert(order, k) elseif v == nil then -- Deleting an existing key? for i, key in ipairs(order) do if key == k then remove(order, i) break end end end rawset(t, k, v) end, __index = t, __pairs = function (_) return coroutine.wrap(iterator) end, __call = function (_) -- hack to mean "not_empty?" return not not next(order) end, __len = function (_) return #order end } return setmetatable({}, mt) end --- A pretty printer for Lua objects. -- -- Takes an object (usually a table) and prints it using the -- printer function. The printer function takes a sole string -- argument and will be called repeatedly. -- -- @param obj The object to pretty print. -- @param printer The printer function. function pretty_printer (obj, printer) if printer == nil then printer = print end local function aux (obj, spacing) local t = type(obj) if t == "table" then printer "{\n" for k, v in pairs(obj) do local spacing = spacing.."\t" printer(spacing) printer "[" aux(k, spacing) printer "] = " aux(v, spacing) printer ",\n" end printer(spacing.."}") elseif t == "string" then printer(format("%q", obj)) else printer(tostring(obj)) end end return aux(obj, "") end --- Returns a conservative timeout for a host -- -- If the host parameter is a NSE host table with a <code>times.timeout</code> -- attribute, then the return value is the host timeout scaled according to the -- max_timeout. The scaling factor is defined by a linear formula such that -- (max_timeout=8000, scale=2) and (max_timeout=1000, scale=1) -- -- @param host The host object to base the timeout on. If this is anything but -- a host table, the max_timeout is returned. -- @param max_timeout The maximum timeout in milliseconds. This is the default -- timeout used if there is no host.times.timeout. Default: 8000 -- @param min_timeout The minimum timeout in milliseconds that will be -- returned. Default: 1000 -- @return The timeout in milliseconds, suitable for passing to set_timeout -- @usage -- assert(host.times.timeout == 1.3) -- assert(get_timeout() == 8000) -- assert(get_timeout(nil, 5000) == 5000) -- assert(get_timeout(host) == 2600) -- assert(get_timeout(host, 10000, 3000) == 3000) function get_timeout(host, max_timeout, min_timeout) max_timeout = max_timeout or 8000 local t = type(host) == "table" and host.times and host.times.timeout if not t then return max_timeout end t = t * (max_timeout + 6000) / 7 min_timeout = min_timeout or 1000 if t < min_timeout then return min_timeout elseif t > max_timeout then return max_timeout end return t end return _ENV;