diff --git a/CHANGELOG b/CHANGELOG
index 5f740f2fc..1c25b8301 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -8,6 +8,12 @@ o Reformat Nmap COPYING file (e.g. remove C comment markers, reduce
when presented by the Windows executable (NSIS) installer. Thanks
to Jah for the patch (which was modified slightly by Fyodor).
+o Added NSE Datafiles library which reads and parses Nmap's nmap-*
+ data files for scripts. The functions (parse_protocols(),
+ parse_rpc() and parse_services()) return tables with numbers
+ (e.g. port numbers) indexing names (e.g. service names). The
+ rpcinfo.nse script was also updated to use this library. [Kris]
+
o Changed the NSE function nmap.set_port_state() so that it checks to
see if the requested port is already in the requested state. This
prevents "Duplicate port" messages during the script scan and the
diff --git a/docs/scripting.xml b/docs/scripting.xml
index 89e8da2a0..8055b6aca 100644
--- a/docs/scripting.xml
+++ b/docs/scripting.xml
@@ -1399,6 +1399,68 @@ if(s) code_to_be_done_on_match end
+
+
+ Data File Parsing Functions
+
+ The datafiles module provides functions for reading and parsing
+ Nmap's data files (e.g. nmap-protocol, nmap-rpc,
+ etc.). These functions' return values are setup for use with exception handling via
+ nmap.new_try().
+
+
+
+
+
+ parse_protocols
+
+
+ This function reads and parses Nmap's nmap-protocols
+ file. bool is a boolen value indicating success.
+ If bool is true, then the second returned
+ value is a table with protocol numbers indexing the protocol
+ names. If bool is false, an error message
+ is returned as the second value instead of the table.
+
+
+
+
+
+
+ parse_rpc
+
+
+ This function reads and parses Nmap's nmap-rpc
+ file. bool is a boolen value indicating success.
+ If bool is true, then the second returned
+ value is a table with RPC numbers indexing the RPC names. If
+ bool is false, an error message is returned
+ as the second value instead of the table.
+
+
+
+
+
+
+ parse_services
+
+
+ This function reads and parses Nmap's nmap-services
+ file. bool is a boolen value indicating success.
+ If bool is true, then the second returned
+ value is a table containing two other tables:
+ tcp{} and udp{}.
+ tcp{} contains services indexed by TCP port
+ numbers. udp{} is the same, but for UDP.
+ You can pass "tcp" or "udp" as an argument to
+ parse_services() to only get the corresponding
+ table. If bool is false, an error message is
+ returned as the second value instead of the table.
+
+
+
+
+
Various Utility Functions
diff --git a/nselib/datafiles.lua b/nselib/datafiles.lua
new file mode 100644
index 000000000..b45d0d59d
--- /dev/null
+++ b/nselib/datafiles.lua
@@ -0,0 +1,128 @@
+-- Kris Katterjohn 03/2008
+
+module(..., package.seeall)
+
+require 'stdnse'
+
+-- These tables are filled by the following fill* functions
+local protocols_table = {}
+local rpc_table = {}
+local services_table = {tcp={}, udp={}}
+
+-- Fills protocols or rpc table with values read from the nmap-* files
+local filltable = function(filename, table)
+ if #table ~= 0 then
+ return true
+ end
+
+ local path = nmap.fetchfile(filename)
+
+ if path == nil then
+ return false
+ end
+
+ local file = io.open(path, "r")
+
+ -- Loops through file line-by-line
+ while true do
+ local l = file:read()
+
+ if not l then
+ break
+ end
+
+ l = l:gsub("%s*#.*", "")
+
+ if l:len() ~= 0 then
+ local m = l:gsub("^([%a%d_-]+)%s+(%d+).*", "%2=%1")
+
+ if m:match("=") then
+ local t = stdnse.strsplit("=", m)
+ table[tonumber(t[1])] = t[2]
+ end
+ end
+ end
+
+ file:close()
+
+ return true
+end
+
+-- Fills services_table{} with values read from nmap-services
+local fillservices = function()
+ if #services_table["tcp"] ~= 0 or
+ #services_table["udp"] ~= 0 then
+ return true
+ end
+
+ local path = nmap.fetchfile("nmap-services")
+
+ if path == nil then
+ return false
+ end
+
+ local file = io.open(path, "r")
+
+ -- Loops through nmap-services line-by-line
+ while true do
+ local l = file:read()
+
+ if not l then
+ break
+ end
+
+ l = l:gsub("%s*#.*", "")
+
+ if l:len() ~= 0 then
+ local m = l:gsub("^([%a%d_-]+)%s+([%a%d/]+).*", "%2=%1")
+
+ if m:match("=") and m:match("/") then
+ local t = stdnse.strsplit("=", m)
+ local s = stdnse.strsplit("/", t[1])
+
+ if s[2] ~= "tcp" and s[2] ~= "udp" then
+ services_table = {tcp={}, udp={}}
+ return false
+ end
+
+ services_table[s[2]][tonumber(s[1])] = t[2]
+ end
+ end
+ end
+
+ file:close()
+
+ return true
+end
+
+parse_protocols = function()
+ if not filltable("nmap-protocols", protocols_table) then
+ return false, "Error parsing nmap-protocols"
+ end
+
+ return true, protocols_table
+end
+
+parse_rpc = function()
+ if not filltable("nmap-rpc", rpc_table) then
+ return false, "Error parsing nmap-rpc"
+ end
+
+ return true, rpc_table
+end
+
+parse_services = function(protocol)
+ if protocol and protocol ~= "tcp" and protocol ~= "udp" then
+ return false, "Bad protocol for nmap-services: use tcp or udp"
+ end
+
+ if not fillservices() then
+ return false, "Error parsing nmap-services"
+ end
+
+ if protocol then
+ return true, services_table[protocol]
+ end
+ return true, services_table
+end
+
diff --git a/scripts/rpcinfo.nse b/scripts/rpcinfo.nse
index ecb8bc465..d1375c506 100644
--- a/scripts/rpcinfo.nse
+++ b/scripts/rpcinfo.nse
@@ -7,44 +7,7 @@ categories = {"safe","discovery"}
require "shortport"
require "packet"
-require "stdnse"
-
-local rpc_numbers = {}
-
--- Fills rpc_numbers with values read from RPC file - Kris Katterjohn
-local fillrpc = function()
- local path = nmap.fetchfile("nmap-rpc")
-
- if path == nil then
- return false, "Can't read from RPC file!"
- end
-
- local file = io.open(path, "r")
-
- -- Loops through RPC file line-by-line
- while true do
- local l = file:read()
-
- if not l then
- break
- end
-
- l = l:gsub("%s*#.*", "")
-
- if l:len() ~= 0 then
- local m = l:gsub("^([%a%d_]+)%s+(%d+).*", "%2=%1")
-
- if m:match("=") then
- local t = stdnse.strsplit("=", m)
- rpc_numbers[tonumber(t[1])] = t[2]
- end
- end
- end
-
- file:close()
-
- return true
-end
+require "datafiles"
portrule = shortport.port_or_service(111, "rpcbind")
@@ -53,10 +16,11 @@ action = function(host, port)
local transaction_id = "nmap"
local socket = nmap.new_socket()
local result = " \n"
+ local rpc_numbers
catch = function() socket:close() end
try = nmap.new_try( catch )
- try( fillrpc() )
+ rpc_numbers = try(datafiles.parse_rpc())
local request = string.char(0x80,0,0,40) -- fragment header
request = request .. transaction_id -- transaction id