diff --git a/nselib/rpc.lua b/nselib/rpc.lua index 4f8ef6e13..884cdbdc4 100644 --- a/nselib/rpc.lua +++ b/nselib/rpc.lua @@ -1795,7 +1795,7 @@ NFS = { FsStat = function(self, comm, file_handle) local status, packet local pos, data = 1, "" - local reponse = {} + local header, response = {}, {} if (comm.version < 3) then return false, string.format("NFS version: %d does not support FSSTAT", @@ -1867,7 +1867,7 @@ NFS = { pos, fsinfo.rtmax, fsinfo.rtpref, fsinfo.rtmult, fsinfo.wtmax, fsinfo.wtpref, fsinfo.wtmult, fsinfo.dtpref = Util.unmarshall_uint32(data, pos, 7) - pos, fsinfo.maxfilesize = Util.unmarshall_size3(data, pos) + pos, fsinfo.maxfilesize = Util.unmarshall_nfssize3(data, pos) pos, fsinfo.time_delta = Util.unmarshall_nfstime(data, pos) pos, fsinfo.properties = Util.unmarshall_uint32(data, pos) @@ -1877,7 +1877,7 @@ NFS = { FsInfo = function(self, comm, file_handle) local status, packet local pos, data = 1, "" - local reponse = {} + local header, response = {} if (comm.version < 3) then return false, string.format("NFS version: %d does not support FSINFO", @@ -1913,7 +1913,7 @@ NFS = { return true, response end, - PathConfDecode = function(self, comm, file_handle) + PathConfDecode = function(self, comm, data, pos) local pconf, status, value_follows = {} status, data = comm:GetAdditionalBytes(data, pos, 4) @@ -1956,7 +1956,7 @@ NFS = { PathConf = function(self, comm, file_handle) local status, packet local pos, data = 1, "" - local reponse = {} + local header, response = {} if (comm.version < 3) then return false, string.format("NFS version: %d does not support PATHCONF", @@ -3071,12 +3071,12 @@ Util = -- readable format SizeToHuman = function(size, blocksize) local bs, idx = 1024, 1 - local unit = { "B", "K", "M", "G" } + local unit = { "B", "K", "M", "G" , "T"} if blocksize and blocksize == 1000 then bs = blocksize end for i=1, #unit do - if (size > bs) then + if (size > bs and idx < #unit) then size = size / bs idx = idx + 1 end @@ -3126,6 +3126,111 @@ Util = return ret end, + --- Return the pathconf filesystem table + -- + -- @param pconf table returned by the NFSv3 PATHCONF call + -- @param nfsversion the version of the remote NFS server + -- @return fs table that contains the remote filesystem + -- pathconf information. + calc_pathconf_table = function(pconf, nfsversion) + local fs = {} + if nfsversion ~= 3 then + return nil, "ERROR: unsupported NFS version." + end + + fs.linkmax = pconf.linkmax + fs.name_max = pconf.name_max + + if pconf.chown_restricted then + fs.chown_restricted = "True" + else + fs.chown_restricted = "False" + end + + return fs, nil + end, + + --- Calculate and return the fsinfo filesystem table + -- + -- @param fsinfo table returned by the NFSv3 FSINFO call + -- @param nfsversion the version of the remote NFS server + -- @param human if set show the size in the human + -- readable format. + -- @return fs table that contains the remote filesystem + -- information. + calc_fsinfo_table = function(fsinfo, nfsversion, human) + local fs = {} + local nfsobj = NFS:new() + if nfsversion ~= 3 then + return nil, "ERROR: unsupported NFS version." + end + + fs.maxfilesize = Util.SizeToHuman(fsinfo.maxfilesize) + + if nfsobj:FSinfoLink(fsinfo.properties, nfsversion) ~= 0 then + fs.link = "True" + else + fs.link = "False" + end + + if nfsobj:FSinfoSymlink(fsinfo.properties, nfsversion) ~= 0 then + fs.symlink = "True" + else + fs.symlink = "False" + end + + return fs, nil + end, + + --- Calculate and return the fsstat filesystem table + -- + -- @param stats table returned by the NFSv3 FSSTAT or + -- NFSv2 STATFS calls + -- @param nfsversion the version of the remote NFS server + -- @param human if set show the size in the human + -- readable format. + -- @return df table that contains the remote filesystem + -- attributes. + calc_fsstat_table = function(stats, nfsversion, human) + local df, base = {}, 1024 + local size, free, total, avail, used, use + if (nfsversion == 3) then + free = stats.fbytes + size = stats.tbytes + avail = stats.abytes + elseif (nfsversion == 2) then + df.bsize = stats.block_size + free = stats.free_blocks * df.bsize + size = stats.total_blocks * df.bsize + avail = stats.available_blocks * df.bsize + else + return nil, "ERROR: unsupported NFS version." + end + + if (human) then + if (df.bsize) then + df.bsize = Util.SizeToHuman(df.bsize) + end + df.size = Util.SizeToHuman(size) + df.available = Util.SizeToHuman(avail) + used = size - free + avail = avail + df.used = Util.SizeToHuman(used) + total = used + avail + else + free = free / base + df.size = size / base + df.available = avail / base + used = df.size - free + df.used = used + total = df.used + df.available + end + + use = math.ceil(used * 100 / total) + df.use = string.format("%.0f%%", use) + return df, nil + end, + --- Converts a RPC program name to it's equivalent number -- -- @param prog_name string containing the name of the RPC program diff --git a/scripts/nfs-statfs.nse b/scripts/nfs-statfs.nse index 73bd9d247..58682e702 100644 --- a/scripts/nfs-statfs.nse +++ b/scripts/nfs-statfs.nse @@ -1,21 +1,22 @@ description = [[ -Retrieves disk space statistics from the remote NFS share +Retrieves disk space statistics and information from the remote NFS +share. This script will try to emulate the behaviour of the "df" tool. + +The script will provide pathconf information of the remote NFS if +the version used is NFSv3. ]] --- -- @output -- PORT STATE SERVICE --- | nfs-statfs: --- | /home/storage/backup --- | Block size: 512 --- | Total blocks: 1901338728 --- | Free blocks: 729769328 --- | Available blocks: 633186880 --- | /home --- | Block size: 512 --- | Total blocks: 1901338728 --- | Free blocks: 729769328 --- |_ Available blocks: 633186880 +-- | 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 '1' or 'true' shows the filesystem +-- size in the human readable format. -- -- Version 0.3 @@ -23,43 +24,185 @@ Retrieves disk space statistics from the remote NFS share -- Created 01/25/2010 - v0.1 - created by Patrik Karlsson -- 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" +author = "Patrik Karlsson, Djalal Harouni" license = "Same as Nmap--See http://nmap.org/book/man-legal.html" categories = {"discovery", "safe"} require("shortport") require("rpc") +require("tab") portrule = shortport.port_or_service(111, "rpcbind", {"tcp", "udp"} ) -action = function(host, port) - - local result, entry = {}, {} - local status, mounts = rpc.Helper.ShowMounts( host, port ) - - if ( not(status) ) then - return stdnse.format_output(false, mounts) - end - - for _, v in ipairs( mounts ) do - local entry = {} - local status, stats = rpc.Helper.ExportStats(host, port, v.name) - - entry.name = v.name - - if (not(status)) then - table.insert(entry, string.format("ERROR: %s", stats)) - else - table.insert( entry, string.format("Block size: %d", stats.block_size) ) - table.insert( entry, string.format("Total blocks: %d", stats.total_blocks) ) - table.insert( entry, string.format("Free blocks: %d", stats.free_blocks) ) - table.insert( entry, string.format("Available blocks: %d", stats.available_blocks) ) - end - table.insert( result, entry ) - end - - return stdnse.format_output( true, result ) - +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(6) + tab.nextrow(outtab) + tab.addrow(outtab, tab_filesys, tab_size, tab_used, + tab_avail, tab_use, tab_bs) + for _, t in ipairs(tables) do + tab.nextrow(outtab) + tab.addrow(outtab, (" %s"):format(t.filesystem), t.size, + t.used, t.available, t.use, t.bsize) + end + elseif nfs.version == 3 then + outtab = tab.new(7) + tab.nextrow(outtab) + tab.addrow(outtab, tab_filesys, tab_size, tab_used, + tab_avail, tab_use, tab_maxfs, tab_linkmax) + for _, t in ipairs(tables) do + tab.nextrow(outtab) + tab.addrow(outtab, (" %s"):format(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 = rpc.Helper.MountPath(nfs.host, nfs.port, mount) + if mnt_comm == nil then + return false, fhandle + end + + local nfs_comm, status = rpc.Helper.NfsOpen(nfs.host, nfs.port) + 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. + if (mnt_comm.version ~= nfs_comm.version) 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 + +action = function(host, port) + local o, fs_info, mounts, status = {}, {}, {}, {} + local nfs_info = + { + host = host, + port = port, + human = nmap.registry.args['nfs-statfs.human'] or nil, + } + + status, mounts = rpc.Helper.ShowMounts( host, port ) + if (not(status)) then + return stdnse.format_output(false, mounts) + 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, err) + end + end + table.insert(o, report(nfs_info, fs_info)) + + return stdnse.format_output(true, o) end