1
0
mirror of https://github.com/nmap/nmap.git synced 2025-12-18 05:29:02 +00:00
Files
nmap/nselib/ls.lua
dmiller 829fbef715 Fix human-readable sizes in ls.lua
First, enforce significant digits when converting, e.g. 1.1K to bytes.
Next, use the server-returned human-readable format instead of
converting to bytes by default. The conversion to bytes is still done to
get total byte count.

Also changed how boolean options work to better match existing
convention: --script-args ls.human or --script-args ls.human=1 now work.
You must explicitly say "false", "no", or "0" to make a boolean flag
false (or just leave it out).
2015-09-04 14:23:14 +00:00

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 http://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, 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