diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 07:42:04 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 07:42:04 +0000 |
commit | 0d47952611198ef6b1163f366dc03922d20b1475 (patch) | |
tree | 3d840a3b8c0daef0754707bfb9f5e873b6b1ac13 /nselib/stdnse.lua | |
parent | Initial commit. (diff) | |
download | nmap-0d47952611198ef6b1163f366dc03922d20b1475.tar.xz nmap-0d47952611198ef6b1163f366dc03922d20b1475.zip |
Adding upstream version 7.94+git20230807.3be01efb1+dfsg.upstream/7.94+git20230807.3be01efb1+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'nselib/stdnse.lua')
-rw-r--r-- | nselib/stdnse.lua | 1047 |
1 files changed, 1047 insertions, 0 deletions
diff --git a/nselib/stdnse.lua b/nselib/stdnse.lua new file mode 100644 index 0000000..d16339d --- /dev/null +++ b/nselib/stdnse.lua @@ -0,0 +1,1047 @@ +--- +-- 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 coroutine = require "coroutine" +local math = require "math" +local nmap = require "nmap" +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 = {}; -- Empty constant table + +_ENV = require "strict" {}; + +--- 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) +_ENV.sleep = nmap.socket.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 + +local function debug (level, ...) + if type(level) ~= "number" then + return debug(1, level, ...) + end + local current = nmap.debugging() + if level <= current then + local host, port = gethostport() + local prefix = ( (current >= 2 and getinfo or getid)() or "") + .. (host and " "..host.ip .. (port and ":"..port.number or "") or "") + if prefix ~= "" then + nmap.log_write("stdout", "[" .. prefix .. "] " .. format(...)) + else + nmap.log_write("stdout", format(...)) + end + end +end + +--- +-- Prints a formatted debug message if the current debugging level is greater +-- than or equal to a given level. +-- +-- This is a convenience wrapper around <code>nmap.log_write</code>. The first +-- optional numeric argument, <code>level</code>, is used as the debugging level +-- necessary to print the message (it defaults to 1 if omitted). All remaining +-- arguments are processed with Lua's <code>string.format</code> function. +-- +-- If known, the output includes some context based information: the script +-- identifier and the target ip/port (if there is one). If the debug level is +-- at least 2, it also prints the base thread identifier and whether it is a +-- worker thread or the controller thread. +-- +-- @class function +-- @name debug +-- @param level Optional debugging level. +-- @param fmt Format string. +-- @param ... Arguments to format. +_ENV.debug = debug + +--Aliases for particular debug levels +function debug1 (...) return debug(1, ...) end +function debug2 (...) return debug(2, ...) end +function debug3 (...) return debug(3, ...) end +function debug4 (...) return debug(4, ...) end +function debug5 (...) return debug(5, ...) end + +--- +-- Deprecated version of debug(), kept for now to prevent the script id from being +-- printed twice. Scripts should use debug() and not pass SCRIPT_NAME +print_debug = function(level, fmt, ...) + local l, d = tonumber(level), nmap.debugging(); + if l and l <= d then + nmap.log_write("stdout", format(fmt, ...)); + elseif not l and 1 <= d then + nmap.log_write("stdout", format(level, fmt, ...)); + end +end + +local function verbose (level, ...) + if type(level) ~= "number" then + return verbose(1, level, ...) + end + local current = nmap.verbosity() + if level <= current then + local prefix + if current >= 2 then + local host, port = gethostport() + prefix = (getid() or "") + .. (host and " "..host.ip .. (port and ":"..port.number or "") or "") + else + prefix = getid() or "" + end + if prefix ~= "" then + nmap.log_write("stdout", "[" .. prefix .. "] " .. format(...)) + else + nmap.log_write("stdout", format(...)) + end + end +end + +--- +-- Prints a formatted verbosity message if the current verbosity level is greater +-- than or equal to a given level. +-- +-- This is a convenience wrapper around <code>nmap.log_write</code>. The first +-- optional numeric argument, <code>level</code>, is used as the verbosity level +-- necessary to print the message (it defaults to 1 if omitted). All remaining +-- arguments are processed with Lua's <code>string.format</code> function. +-- +-- If known, the output includes some context based information: the script +-- identifier. If the verbosity level is at least 2, it also prints the target +-- ip/port (if there is one) +-- +-- @class function +-- @name verbose +-- @param level Optional verbosity level. +-- @param fmt Format string. +-- @param ... Arguments to format. +_ENV.verbose = verbose + +--Aliases for particular verbosity levels +function verbose1 (...) return verbose(1, ...) end +function verbose2 (...) return verbose(2, ...) end +function verbose3 (...) return verbose(3, ...) end +function verbose4 (...) return verbose(4, ...) end +function verbose5 (...) return verbose(5, ...) end + +--- +-- Deprecated version of verbose(), kept for now to prevent the script id from being +-- printed twice. Scripts should use verbose() and not pass SCRIPT_NAME +print_verbose = function(level, fmt, ...) + local l, d = tonumber(level), nmap.verbosity(); + if l and l <= d then + nmap.log_write("stdout", format(fmt, ...)); + elseif not l and 1 <= d then + nmap.log_write("stdout", format(level, fmt, ...)); + end +end + +--- Return a wrapper closure around a socket that buffers socket reads into +-- chunks separated by a pattern. +-- +-- This function operates on a socket attempting to read data. It separates the +-- data by <code>sep</code> and, for each invocation, returns a piece of the +-- separated data. Typically this is used to iterate over the lines of data +-- received from a socket (<code>sep = "\r?\n"</code>). The returned string +-- does not include the separator. It will return the final data even if it is +-- not followed by the separator. Once an error or EOF is reached, it returns +-- <code>nil, msg</code>. <code>msg</code> is what is returned by +-- <code>nmap.receive_lines</code>. +-- @param socket Socket for the buffer. +-- @param sep Separator for the buffered reads. +-- @return Data from socket reads or <code>nil</code> on EOF or error. +-- @return Error message, as with <code>receive_lines</code>. +function make_buffer(socket, sep) + local point, left, buffer, done, msg = 1, ""; + local function self() + if done then + return nil, msg; -- must be nil for stdnse.lines (below) + elseif not buffer then + local status, str = socket:receive(); + if not status then + if #left > 0 then + done, msg = not status, str; + return left; + else + return status, str; + end + else + buffer = left..str; + return self(); + end + else + local i, j = find(buffer, sep, point); + if i then + local ret = sub(buffer, point, i-1); + point = j + 1; + return ret; + else + point, left, buffer = 1, sub(buffer, point), nil; + return self(); + end + end + end + return self; +end + +--[[ This function may be usable in Lua 5.2 +function lines(socket) + return make_buffer(socket, "\r?\n"), nil, nil; +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 nmap.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 nmap.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 + +-- Get the value of a script argument, or nil if the script argument was not +-- given. This works also for arguments given as top-level array values, like +-- --script-args=unsafe; for these it returns the value 1. +local function arg_value(argname) + -- First look for the literal script-arg name + -- as a key/value pair + if nmap.registry.args[argname] then + return nmap.registry.args[argname] + end + -- and alone, as a boolean flag + for _, v in ipairs(nmap.registry.args) do + if v == argname then + return 1 + end + end + + -- if scriptname.arg is not there, check "arg" + local shortname = match(argname, "%.([^.]*)$") + if shortname then + -- as a key/value pair + if nmap.registry.args[shortname] then + return nmap.registry.args[shortname] + end + -- and alone, as a boolean flag + for _, v in ipairs(nmap.registry.args) do + if v == shortname then + return 1 + end + end + end + return nil +end + +--- Parses the script arguments passed to the --script-args option. +-- +-- @usage +-- --script-args 'script.arg1=value,script.arg3,script-x.arg=value' +-- local arg1, arg2, arg3 = get_script_args('script.arg1','script.arg2','script.arg3') +-- => arg1 = "value" +-- => arg2 = nil +-- => arg3 = 1 +-- +-- --script-args 'displayall,unsafe,script-x.arg=value,script-y.arg=value' +-- local displayall, unsafe = get_script_args('displayall','unsafe') +-- => displayall = 1 +-- => unsafe = 1 +-- +-- --script-args 'dns-cache-snoop.mode=timed,dns-cache-snoop.domains={host1,host2}' +-- local mode, domains = get_script_args('dns-cache-snoop.mode', +-- 'dns-cache-snoop.domains') +-- => mode = "timed" +-- => domains = {"host1","host2"} +-- +-- @param Arguments Script arguments to check. +-- @return Arguments values. +function get_script_args (...) + local args = {} + + for i, set in ipairs({...}) do + if type(set) == "string" then + set = {set} + end + for _, test in ipairs(set) do + local v = arg_value(test) + if v then + args[i] = v + break + end + end + end + + return unpack(args, 1, select("#", ...)) +end + +---Get the best possible hostname for the given host. This can be the target as given on +-- the commandline, the reverse dns name, or simply the ip address. +--@param host The host table (or a string that'll simply be returned). +--@return The best possible hostname, as a string. +function get_hostname(host) + if type(host) == "table" then + return host.targetname or ( host.name ~= '' and host.name ) or host.ip + else + return host + end +end + +---Retrieve an item from the registry, checking if each sub-key exists. If any key doesn't +-- exist, return nil. +function registry_get(subkeys) + local registry = nmap.registry + local i = 1 + + while(subkeys[i]) do + if(not(registry[subkeys[i]])) then + return nil + end + + registry = registry[subkeys[i]] + + i = i + 1 + end + + return registry +end + +--Check if the given element exists in the registry. If 'key' is nil, it isn't checked. +function registry_exists(subkeys, key, value) + local subkey = registry_get(subkeys) + + if(not(subkey)) then + return false + end + + for k, v in pairs(subkey) do + if((key == nil or key == k) and (v == value)) then -- TODO: if 'value' is a table, this fails + return true + end + end + + return false +end + +---Add an item to an array in the registry, creating all sub-keys if necessary. +-- +-- For example, calling: +-- <code>registry_add_array({'192.168.1.100', 'www', '80', 'pages'}, 'index.html')</code> +-- Will create nmap.registry['192.168.1.100'] as a table, if necessary, then add a table +-- under the 'www' key, and so on. 'pages', finally, is treated as an array and the value +-- given is added to the end. +function registry_add_array(subkeys, value, allow_duplicates) + local registry = nmap.registry + local i = 1 + + -- Unless the user wants duplicates, make sure there aren't any + if(allow_duplicates ~= true) then + if(registry_exists(subkeys, nil, value)) then + return + end + end + + while(subkeys[i]) do + if(not(registry[subkeys[i]])) then + registry[subkeys[i]] = {} + end + registry = registry[subkeys[i]] + i = i + 1 + end + + -- Make sure the value isn't already in the table + for _, v in pairs(registry) do + if(v == value) then + return + end + end + insert(registry, value) +end + +---Similar to <code>registry_add_array</code>, except instead of adding a value to the +-- end of an array, it adds a key:value pair to the table. +function registry_add_table(subkeys, key, value, allow_duplicates) + local registry = nmap.registry + local i = 1 + + -- Unless the user wants duplicates, make sure there aren't any + if(allow_duplicates ~= true) then + if(registry_exists(subkeys, key, value)) then + return + end + end + + while(subkeys[i]) do + if(not(registry[subkeys[i]])) then + registry[subkeys[i]] = {} + end + registry = registry[subkeys[i]] + i = i + 1 + end + + registry[key] = value +end + + +--- This function allows you to create worker threads that may perform +-- network tasks in parallel with your script thread. +-- +-- Any network task (e.g. <code>socket:connect(...)</code>) will cause the +-- running thread to yield to NSE. This allows network tasks to appear to be +-- blocking while being able to run multiple network tasks at once. +-- While this is useful for running multiple separate scripts, it is +-- unfortunately difficult for a script itself to perform network tasks in +-- parallel. In order to allow scripts to also have network tasks running in +-- parallel, we provide this function, <code>stdnse.new_thread</code>, to +-- create a new thread that can perform its own network related tasks +-- in parallel with the script. +-- +-- The script launches the worker thread by calling the <code>new_thread</code> +-- function with the parameters: +-- * The main Lua function for the script to execute, similar to the script action function. +-- * The variable number of arguments to be passed to the worker's main function. +-- +-- The <code>stdnse.new_thread</code> function will return two results: +-- * The worker thread's base (main) coroutine (useful for tracking status). +-- * A status query function (described below). +-- +-- The status query function shall return two values: +-- * The result of coroutine.status using the worker thread base coroutine. +-- * The error object thrown that ended the worker thread or <code>nil</code> if no error was thrown. This is typically a string, like most Lua errors. +-- +-- Note that NSE discards all return values of the worker's main function. You +-- must use function parameters, upvalues or environments to communicate +-- results. +-- +-- You should use the condition variable (<code>nmap.condvar</code>) +-- and mutex (<code>nmap.mutex</code>) facilities to coordinate with your +-- worker threads. Keep in mind that Nmap is single threaded so there are +-- no (memory) issues in synchronization to worry about; however, there +-- is resource contention. Your resources are usually network +-- bandwidth, network sockets, etc. Condition variables are also useful if the +-- work for any single thread is dynamic. For example, a web server spider +-- script with a pool of workers will initially have a single root html +-- document. Following the retrieval of the root document, the set of +-- resources to be retrieved (the worker's work) will become very large +-- (an html document adds many new hyperlinks (resources) to fetch). +--@name new_thread +--@class function +--@param main The main function of the worker thread. +--@param ... The arguments passed to the main worker thread. +--@return co The base coroutine of the worker thread. +--@return info A query function used to obtain status information of the worker. +--@usage +--local requests = {"/", "/index.html", --[[ long list of objects ]]} +-- +--function thread_main (host, port, responses, ...) +-- local condvar = nmap.condvar(responses); +-- local what = {n = select("#", ...), ...}; +-- local allReqs = nil; +-- for i = 1, what.n do +-- allReqs = http.pGet(host, port, what[i], nil, nil, allReqs); +-- end +-- local p = assert(http.pipeline(host, port, allReqs)); +-- for i, response in ipairs(p) do responses[#responses+1] = response end +-- condvar "signal"; +--end +-- +--function many_requests (host, port) +-- local threads = {}; +-- local responses = {}; +-- local condvar = nmap.condvar(responses); +-- local i = 1; +-- repeat +-- local j = math.min(i+10, #requests); +-- local co = stdnse.new_thread(thread_main, host, port, responses, +-- table.unpack(requests, i, j)); +-- threads[co] = true; +-- i = j+1; +-- until i > #requests; +-- repeat +-- condvar "wait"; +-- for thread in pairs(threads) do +-- if coroutine.status(thread) == "dead" then threads[thread] = nil end +-- end +-- until next(threads) == nil; +-- return responses; +--end +do end -- no function here, see nse_main.lua + +--- Returns the base coroutine of the running script. +-- +-- A script may be resuming multiple coroutines to facilitate its own +-- collaborative multithreading design. Because there is a "root" or "base" +-- coroutine that lets us determine whether the script is still active +-- (that is, the script did not end, possibly due to an error), we provide +-- this <code>stdnse.base</code> function that will retrieve the base +-- coroutine of the script. This base coroutine is the coroutine that runs +-- the action function. +-- +-- The base coroutine is useful for many reasons but here are some common +-- uses: +-- * We want to attribute the ownership of an object (perhaps a network socket) to a script. +-- * We want to identify if the script is still alive. +--@name base +--@class function +--@return coroutine Returns the base coroutine of the running script. +do end -- no function here, see nse_main.lua + +--- The Lua Require Function with errors silenced. +-- +-- See the Lua manual for description of the require function. This modified +-- version allows the script to quietly fail at loading if a required +-- library does not exist. +-- +--@name silent_require +--@class function +--@usage stdnse.silent_require "openssl" +do end -- no function here, see nse_main.lua + + +--- 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; |