summaryrefslogtreecommitdiffstats
path: root/nselib/ls.lua
diff options
context:
space:
mode:
Diffstat (limited to 'nselib/ls.lua')
-rw-r--r--nselib/ls.lua368
1 files changed, 368 insertions, 0 deletions
diff --git a/nselib/ls.lua b/nselib/ls.lua
new file mode 100644
index 0000000..9d190ff
--- /dev/null
+++ b/nselib/ls.lua
@@ -0,0 +1,368 @@
+---
+-- Report file and directory listings.
+--
+-- For scripts that gather and report directory listings, this script provides
+-- a common output format and helpful script arguments.
+--
+-- The arguments can either be set for all the scripts using this
+-- module (--script-args ls.arg=value) or for one particular script
+-- (--script-args afp-ls.arg=value). If both are specified for the
+-- same argument, the script-specific value is used.
+--
+-- @args ls.maxdepth The maximum depth to recurse into a directory. If less
+-- than 0 (e.g. -1) then recursion is unlimited.
+-- (default: 0, no recursion).
+-- @args ls.maxfiles The maximum number of files to return. Set to 0 or less to
+-- disable this limit. (default: 10).
+-- @args ls.checksum (boolean) Download each file and calculate a
+-- SHA1 checksum. Although this is a module
+-- argument, the implementation is done in each
+-- script and is currently only supported by smb-ls
+-- and http-ls
+-- @args ls.errors (boolean) Report errors
+-- @args ls.empty (boolean) Report empty volumes (with no information
+-- or error)
+-- @args ls.human (boolean) Show file sizes in human-readable format with K,
+-- M, G, T, P suffixes. Some services return human-readable
+-- sizes natively; in these cases, the size is reported as
+-- given.
+--
+-- @author Pierre Lalet <pierre@droids-corp.org>
+-- @copyright Same as Nmap--See https://nmap.org/book/man-legal.html
+-----------------------------------------------------------------------
+
+local LIBRARY_NAME = "ls"
+
+local math = require "math"
+local stdnse = require "stdnse"
+local string = require "string"
+local tab = require "tab"
+local table = require "table"
+
+_ENV = stdnse.module("ls", stdnse.seeall)
+
+local config_values = {
+ ["maxdepth"] = 1,
+ ["maxfiles"] = 10,
+ ["checksum"] = false,
+ ["errors"] = false,
+ ["empty"] = false,
+ ["human"] = false,
+}
+
+--- Convert an argument to its expected type
+local function convert_arg(argval, argtype)
+ if argtype == "number" then
+ return tonumber(argval)
+ elseif argtype == "boolean" then
+ if argval == "false" or argval == "no" or argval == "0" then
+ return false
+ else
+ return true
+ end
+ end
+ return argval
+end
+
+--- Update config_values using module arguments ("ls.argname", as
+-- opposed to script-specific arguments, "http-ls.argname")
+for argname, argvalue in pairs(config_values) do
+ local argval = stdnse.get_script_args(LIBRARY_NAME .. "." .. argname)
+ if argval ~= nil then
+ config_values[argname] = convert_arg(argval, type(argvalue))
+ end
+end
+
+--- Get a config value from (by order or priority):
+-- 1. a script-specific argument (e.g., http-ls.argname)
+-- 2. a module argument (ls.argname)
+-- 3. the default value
+-- @param argname The name of the configuration parameter
+-- @return The configuration value
+function config(argname)
+ local argval = stdnse.get_script_args(stdnse.getid() .. "." .. argname)
+ if argval == nil then
+ return config_values[argname]
+ else
+ return convert_arg(argval, type(config_values[argname]))
+ end
+end
+
+--- Create a new script output.
+-- @return The ls output object to be passed to other functions
+function new_listing()
+ local output = stdnse.output_table()
+ output['curvol'] = nil
+ output['volumes'] = {}
+ output['errors'] = {}
+ output['info'] = {}
+ output['total'] = {
+ ['files'] = 0,
+ ['bytes'] = 0,
+ }
+ return output
+end
+
+--- Create a new volume within the provided output
+-- @param output The ls output object, from new_listing()
+-- @param name The name of the volume
+-- @param hasperms Boolean true if the volume listing will include permissions
+function new_vol(output, name, hasperms)
+ local curvol = stdnse.output_table()
+ local files = tab.new()
+ local i = 1
+ if hasperms then
+ tab.add(files, 1, "PERMISSION")
+ tab.add(files, 2, "UID")
+ tab.add(files, 3, "GID")
+ i = 4
+ end
+ tab.add(files, i, "SIZE")
+ tab.add(files, i + 1, "TIME")
+ tab.add(files, i + 2, "FILENAME")
+ if config("checksum") then
+ tab.add(files, i + 3, "CHECKSUM")
+ end
+ tab.nextrow(files)
+ curvol['name'] = name
+ curvol['files'] = files
+ curvol['count'] = 0
+ curvol['bytes'] = 0
+ curvol['errors'] = {}
+ curvol['info'] = {}
+ curvol['hasperms'] = hasperms
+ output['curvol'] = curvol
+end
+
+--- Report an error, using stdnse.debug() and (depending on the
+-- configuration settings) adding the error message to the output.
+-- @param output The ls output object, from new_listing()
+-- @param err The error message to report
+-- @param level The debug level (default: 1)
+function report_error(output, err, level)
+ level = level or 1
+ if output["curvol"] == nil then
+ stdnse.debug(level, string.format("error: %s", err))
+ else
+ stdnse.debug(level, string.format("error [%s]: %s",
+ output["curvol"]["name"], err))
+ end
+ if config('errors') then
+ if output["curvol"] == nil then
+ table.insert(output["errors"], err)
+ else
+ table.insert(output["curvol"]["errors"], err)
+ end
+ end
+end
+
+--- Report information, using stdnse.debug() and adding the message
+-- to the output.
+-- @param output The ls output object, from new_listing()
+-- @param info The info message to report
+-- @param level The debug level (default: 1)
+function report_info(output, info, level)
+ level = level or 1
+ if output["curvol"] == nil then
+ stdnse.debug(level, string.format("info: %s", info))
+ table.insert(output["info"], info)
+ else
+ stdnse.debug(level, string.format("info [%s]: %s",
+ output["curvol"]["name"], info))
+ table.insert(output["curvol"]["info"], info)
+ end
+end
+
+local units = {
+ ["k"] = 1024,
+ ["m"] = 1024^2,
+ ["g"] = 1024^3,
+ ["t"] = 1024^4,
+ ["p"] = 1024^5,
+}
+
+--- Get a size as an integer from a (possibly) human readable input.
+local function get_size(size)
+ local bsize = tonumber(size)
+ if bsize == nil then
+ local unit = string.lower(string.sub(size, -1, -1))
+ bsize = tonumber(string.sub(size, 0, -2))
+ if units[unit] ~= nil and bsize ~= nil then
+ bsize = bsize * units[unit]
+ local sigfigs = #(string.match(size, "[0-9.]+"))
+ if string.find(size, "%.") then
+ sigfigs = sigfigs - 1
+ end
+ local d = math.ceil(math.log(bsize, 10))
+ local power = sigfigs - d
+ local magnitude = 10^power
+ local shifted = math.floor(bsize * magnitude)
+ bsize = math.floor(shifted / magnitude)
+ else
+ bsize = nil
+ end
+ end
+ return bsize
+end
+
+--- Add a new file to the current volume.
+-- @param output The ls output object, from new_listing()
+-- @param file A table containing the information about the file
+-- @return Boolean true if the script may continue adding files, false if
+-- maxfiles has been reached.
+function add_file(output, file)
+ local curvol = output.curvol
+ local files = curvol["files"]
+ for i, info in ipairs(file) do
+ tab.add(files, i, tostring(info))
+ end
+ local size = get_size(file[curvol.hasperms and 4 or 1])
+ if size then
+ curvol["bytes"] = curvol["bytes"] + size
+ end
+ tab.nextrow(files)
+ curvol["count"] = curvol["count"] + 1
+ return (config("maxfiles") == 0 or config("maxfiles") == nil
+ or config("maxfiles") > curvol["count"])
+end
+
+--- Close the current volume. It is mandatory to call this function
+-- before calling new_vol() again or before calling end_listing().
+-- @param output The ls output object, from new_listing()
+function end_vol(output)
+ local curvol = output.curvol
+ local vol = {["volume"] = curvol["name"]}
+ local empty = true
+ -- "files" is a tab.lua table, so row 1 is the table heading
+ if #curvol["files"] ~= 1 then
+ vol["files"] = curvol["files"]
+ empty = false
+ end
+ if #curvol["errors"] ~= 0 then
+ vol["errors"] = curvol["errors"]
+ empty = false
+ end
+ if #curvol["info"] ~= 0 then
+ vol["info"] = curvol["info"]
+ empty = false
+ end
+ if not empty or config("empty") then
+ table.insert(output["volumes"], vol)
+ end
+ output["total"]["files"] = output["total"]["files"] + curvol["count"]
+ output["total"]["bytes"] = output["total"]["bytes"] + curvol["bytes"]
+ output["curvol"] = nil
+end
+
+--- Convert a files table to structured data.
+local function files_to_structured(files)
+ local result = {}
+ local fields = table.remove(files, 1)
+ for i=1, #fields do
+ fields[i] = string.lower(fields[i])
+ end
+ for i, file in ipairs(files) do
+ result[i] = {}
+ for j, value in ipairs(file) do
+ result[i][fields[j]] = value
+ end
+ end
+ return result
+end
+
+--- Convert a files table to human readable data.
+local function files_to_readable(files)
+ local outtab = tab.new()
+ local fields = files[1]
+ local isize
+ tab.addrow(outtab, table.unpack(fields))
+ for i, field in ipairs(fields) do
+ if string.lower(field) == "size" then
+ isize = i
+ break
+ end
+ end
+ local units = {"K", "M", "G", "T", "P"}
+ for i = 2, #files do
+ local outfile = {}
+ for j, value in ipairs(files[i]) do
+ outfile[j] = value
+ end
+ if config("human") then
+ local size = tonumber(outfile[isize])
+ -- If tonumber didn't work, it's already in human-readable format
+ if size ~= nil then
+ local iunit = 0
+ while size > 1024 and units[iunit+1] do
+ size = size / 1024
+ iunit = iunit + 1
+ end
+ if units[iunit] then
+ outfile[isize] = string.format("%.1f %s", size, units[iunit])
+ else
+ outfile[isize] = tostring(size)
+ end
+ end
+ end
+ tab.addrow(outtab, table.unpack(outfile))
+ end
+ return tab.dump(outtab)
+end
+
+--- Close current listing. Return both the structured and the human
+-- readable outputs.
+-- @param output The ls output object, from new_listing()
+-- @return Structured output
+-- @return Human readable output
+function end_listing(output)
+ assert(output["curvol"] == nil)
+ local text = {}
+ local empty = true
+ if #output["info"] == 0 then
+ output["info"] = nil
+ else
+ for _, line in ipairs(output["info"]) do
+ text[#text + 1] = line
+ end
+ empty = false
+ end
+ if #output["errors"] == 0 then
+ output["errors"] = nil
+ else
+ for _, line in ipairs(output["errors"]) do
+ text[#text + 1] = string.format("ERROR: %s", line)
+ end
+ empty = false
+ end
+ if #output["volumes"] == 0 then
+ output["volumes"] = nil
+ output["total"] = nil
+ else
+ for _, volume in ipairs(output["volumes"]) do
+ text[#text + 1] = string.format("Volume %s", volume["volume"])
+ if volume["info"] then
+ for _, line in ipairs(volume["info"]) do
+ text[#text + 1] = string.format(" %s", line)
+ end
+ end
+ if volume["errors"] then
+ for _, line in ipairs(volume["errors"]) do
+ text[#text + 1] = string.format(" ERROR: %s", line)
+ end
+ end
+ if volume["files"] then
+ text[#text + 1] = files_to_readable(volume["files"])
+ volume["files"] = files_to_structured(volume["files"])
+ end
+ text[#text + 1] = ""
+ end
+ empty = false
+ end
+ if empty then
+ return nil
+ else
+ return output, table.concat(text, "\n")
+ end
+end
+
+return _ENV