summaryrefslogtreecommitdiffstats
path: root/scripts/nfs-statfs.nse
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/nfs-statfs.nse')
-rw-r--r--scripts/nfs-statfs.nse359
1 files changed, 359 insertions, 0 deletions
diff --git a/scripts/nfs-statfs.nse b/scripts/nfs-statfs.nse
new file mode 100644
index 0000000..2086978
--- /dev/null
+++ b/scripts/nfs-statfs.nse
@@ -0,0 +1,359 @@
+local rpc = require "rpc"
+local shortport = require "shortport"
+local stdnse = require "stdnse"
+local string = require "string"
+local tab = require "tab"
+local table = require "table"
+local nmap = require "nmap"
+
+description = [[
+Retrieves disk space statistics and information from a remote NFS share.
+The output is intended to resemble the output of <code>df</code>.
+
+The script will provide pathconf information of the remote NFS if
+the version used is NFSv3.
+]]
+
+---
+-- @usage
+-- nmap -p 111 --script=nfs-statfs <target>
+-- nmap -sV --script=nfs-statfs <target>
+-- @output
+-- PORT STATE SERVICE
+-- | nfs-statfs:
+-- | Filesystem 1K-blocks Used Available Use% Blocksize
+-- | /mnt/nfs/files 5542276 2732012 2528728 52% 4096
+-- |_ /mnt/nfs/opensource 5534416 620640 4632644 12% 4096
+--
+-- @args nfs-statfs.human If set to <code>1</code> or <code>true</code>,
+-- shows file sizes in a human readable format with suffixes like
+-- <code>KB</code> and <code>MB</code>.
+
+-- Version 0.3
+
+-- Created 01/25/2010 - v0.1 - created by Patrik Karlsson <patrik@cqure.net>
+-- Revised 02/22/2010 - v0.2 - adapted to support new RPC library
+-- Revised 03/13/2010 - v0.3 - converted host to port rule
+-- Revised 06/28/2010 - v0.4 - added NFSv3 support and doc
+
+
+author = {"Patrik Karlsson", "Djalal Harouni"}
+license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
+categories = {"discovery", "safe"}
+dependencies = {"rpc-grind"}
+
+
+portrule = shortport.port_or_service(111, "rpcbind", {"tcp", "udp"} )
+
+hostrule = function(host)
+ local mountport, nfsport
+ if host.registry.nfs then
+ mountport = host.registry.nfs.mountport
+ nfsport = host.registry.nfs.nfsport
+ else
+ host.registry.nfs = {}
+ end
+ for _,proto in ipairs({"tcp","udp"}) do
+ local port = nmap.get_ports(host, nil, proto, "open")
+ while port do
+ if port.version then
+ if port.service == "mountd" then
+ mountport = port
+ elseif port.service == "nfs" then
+ nfsport = port
+ end
+ end
+ if mountport and nfsport then break end
+ port = nmap.get_ports(host, port, proto, "open")
+ end
+ if mountport and nfsport then break end
+ end
+ -- Run when nfs and mount ports were scanned and their versions numbers known
+ if not (nfsport and (host.registry.nfs.nfsver or nfsport.version.version)) then
+ return false
+ end
+ if not (mountport and (host.registry.nfs.mountver or mountport.version.version)) then
+ return false
+ end
+ if host.registry.nfs.nfsver == nil then
+ local low, high = string.match(nfsport.version.version, "(%d)%-(%d)")
+ if high == nil then
+ high = tonumber(nfsport.version.version)
+ if high == 4 then
+ return false --Can't support version 4
+ else
+ host.registry.nfs.nfsver = high
+ end
+ else
+ if high == "4" then
+ host.registry.nfs.nfsver = 3
+ else
+ host.registry.nfs.nfsver = tonumber(low)
+ end
+ end
+ end
+ if host.registry.nfs.mountver == nil then
+ local low, high = string.match(mountport.version.version, "(%d)%-(%d)")
+ if high == nil then
+ host.registry.nfs.mountver = tonumber(mountport.version.version)
+ else
+ host.registry.nfs.mountver = tonumber(high)
+ end
+ end
+ host.registry.nfs.mountport = mountport
+ host.registry.nfs.nfsport = nfsport
+ return (mountport and nfsport)
+end
+
+local procedures = { }
+
+local function table_fsstat(nfs, mount, stats)
+ local fs, err = rpc.Util.calc_fsstat_table(stats, nfs.version, nfs.human)
+ if fs == nil then
+ return false, err
+ end
+ fs.filesystem = string.format("%s", mount)
+ return true, fs
+end
+
+local function table_fsinfo(nfs, fsinfo)
+ local ret = {}
+ local fs, err = rpc.Util.calc_fsinfo_table(fsinfo, nfs.version, nfs.human)
+ if fs == nil then
+ return false, err
+ end
+
+ ret.maxfilesize = fs.maxfilesize
+ return true, ret
+end
+
+local function table_pathconf(nfs, pconf)
+ local ret = {}
+ local fs, err = rpc.Util.calc_pathconf_table(pconf, nfs.version)
+ if fs == nil then
+ return false, err
+ end
+
+ ret.linkmax = fs.linkmax
+ return true, ret
+end
+
+local function report(nfs, tables)
+ local outtab, tab_size, tab_avail
+ local tab_filesys, tab_used, tab_use,
+ tab_bs, tab_maxfs, tab_linkmax = "Filesystem",
+ "Used", "Use%", "Blocksize", "Maxfilesize", "Maxlink"
+
+ if nfs.human then
+ tab_size = "Size"
+ tab_avail = "Avail"
+ else
+ tab_size = "1K-blocks"
+ tab_avail = "Available"
+ end
+
+ if nfs.version == 2 then
+ outtab = tab.new()
+ tab.addrow(outtab, tab_filesys, tab_size, tab_used,
+ tab_avail, tab_use, tab_bs)
+ for _, t in ipairs(tables) do
+ tab.addrow(outtab, t.filesystem, t.size,
+ t.used, t.available, t.use, t.bsize)
+ end
+ elseif nfs.version == 3 then
+ outtab = tab.new()
+ tab.addrow(outtab, tab_filesys, tab_size, tab_used,
+ tab_avail, tab_use, tab_maxfs, tab_linkmax)
+ for _, t in ipairs(tables) do
+ tab.addrow(outtab, t.filesystem, t.size, t.used,
+ t.available, t.use, t.maxfilesize, t.linkmax)
+ end
+ end
+
+ return tab.dump(outtab)
+end
+
+local function nfs_filesystem_info(nfs, mount, filesystem)
+ local results, res, status = {}, {}
+ local nfsobj = rpc.NFS:new()
+ local mnt_comm, nfs_comm, fhandle
+
+ mnt_comm, fhandle = procedures.MountPath(nfs.host, mount)
+ if mnt_comm == nil then
+ return false, fhandle
+ end
+
+ local nfs_comm, status = procedures.NfsOpen(nfs.host)
+ if nfs_comm == nil then
+ rpc.Helper.UnmountPath(mnt_comm, mount)
+ return false, status
+ end
+
+ nfs.version = nfs_comm.version
+
+ -- use simple check since NFSv1 is not used anymore, and NFSv4 not supported
+ if (nfs_comm.version <= 2 and mnt_comm.version > 2) then
+ rpc.Helper.UnmountPath(mnt_comm, mount)
+ return false, string.format("versions mismatch, nfs v%d - mount v%d",
+ nfs_comm.version, mnt_comm.version)
+ end
+
+ if nfs_comm.version < 3 then
+ status, res = nfsobj:StatFs(nfs_comm, fhandle)
+ elseif nfs_comm.version == 3 then
+ status, res = nfsobj:FsStat(nfs_comm, fhandle)
+ end
+
+ if status then
+ status, res = table_fsstat(nfs, mount, res)
+ if status then
+ for k, v in pairs(res) do
+ results[k] = v
+ end
+ end
+
+ if nfs_comm.version == 3 then
+ status, res = nfsobj:FsInfo(nfs_comm, fhandle)
+ if status then
+ status, res = table_fsinfo(nfs, res)
+ if status then
+ for k, v in pairs(res) do
+ results[k] = v
+ end
+ end
+ end
+
+ status, res = nfsobj:PathConf(nfs_comm, fhandle)
+ if status then
+ status, res = table_pathconf(nfs, res)
+ if status then
+ for k, v in pairs(res) do
+ results[k] = v
+ end
+ end
+ end
+
+ end
+ end
+
+ rpc.Helper.NfsClose(nfs_comm)
+ rpc.Helper.UnmountPath(mnt_comm, mount)
+ if (not(status)) then
+ return status, res
+ end
+
+ table.insert(filesystem, results)
+ return true, nil
+end
+
+mainaction = function(host)
+ local fs_info, mounts, status = {}, {}, {}
+ local nfs_info =
+ {
+ host = host,
+ }
+ nfs_info.human = stdnse.get_script_args('nfs-statfs.human')
+
+ status, mounts = procedures.ShowMounts( host )
+ if (not(status)) then
+ return stdnse.format_output(false, mounts)
+ end
+
+ if #mounts < 1 then
+ stdnse.debug1("No NFS mounts available")
+ return nil
+ end
+
+ for _, v in ipairs(mounts) do
+ local err
+ status, err = nfs_filesystem_info(nfs_info, v.name, fs_info)
+ if (not(status)) then
+ return stdnse.format_output(false,
+ string.format("%s: %s", v.name, err))
+ end
+ end
+
+ return stdnse.format_output(true, report(nfs_info, fs_info))
+end
+
+hostaction = function(host)
+ procedures = {
+ ShowMounts = function(ahost)
+ local mnt_comm, status, result, mounts
+ local mnt = rpc.Mount:new()
+ mnt_comm = rpc.Comm:new('mountd', host.registry.nfs.mountver)
+ status, result = mnt_comm:Connect(ahost, host.registry.nfs.mountport)
+ if ( not(status) ) then
+ stdnse.debug1("ShowMounts: %s", result)
+ return false, result
+ end
+ status, mounts = mnt:Export(mnt_comm)
+ mnt_comm:Disconnect()
+ if ( not(status) ) then
+ stdnse.debug1("ShowMounts: %s", mounts)
+ end
+ return status, mounts
+ end,
+
+ MountPath = function(ahost, path)
+ local fhandle, status, err
+ local mountd, mnt_comm
+ local mnt = rpc.Mount:new()
+
+ mnt_comm = rpc.Comm:new("mountd", host.registry.nfs.mountver)
+
+ status, err = mnt_comm:Connect(host, host.registry.nfs.mountport)
+ if not status then
+ stdnse.debug1("MountPath: %s", err)
+ return nil, err
+ end
+
+ status, fhandle = mnt:Mount(mnt_comm, path)
+ if not status then
+ mnt_comm:Disconnect()
+ stdnse.debug1("MountPath: %s", fhandle)
+ return nil, fhandle
+ end
+
+ return mnt_comm, fhandle
+ end,
+
+ NfsOpen = function(ahost)
+ local nfs_comm, status, err
+
+ nfs_comm = rpc.Comm:new('nfs', host.registry.nfs.nfsver)
+ status, err = nfs_comm:Connect(host, host.registry.nfs.nfsport)
+ if not status then
+ stdnse.debug1("NfsOpen: %s", err)
+ return nil, err
+ end
+
+ return nfs_comm, nil
+ end,
+ }
+ return mainaction(host)
+end
+
+portaction = function(host, port)
+ procedures = {
+ ShowMounts = function(ahost)
+ return rpc.Helper.ShowMounts(ahost, port)
+ end,
+ MountPath = function(ahost, path)
+ return rpc.Helper.MountPath(ahost, port, path)
+ end,
+ NfsOpen = function(ahost)
+ return rpc.Helper.NfsOpen(ahost, port)
+ end,
+ }
+ return mainaction(host)
+end
+
+local ActionsTable = {
+ -- portrule: use rpcbind service
+ portrule = portaction,
+ -- hostrule: Talk to services directly
+ hostrule = hostaction
+}
+
+action = function(...) return ActionsTable[SCRIPT_TYPE](...) end