shithub: scripts

ref: 3519f1d58ff78407227adb5a3a202d7a4e626a93
dir: /lib/lua/nseport/stdnse.lua/

View raw version
---
-- 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;