369 lines
11 KiB
Lua
369 lines
11 KiB
Lua
---
|
|
-- 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
|