diff --git a/CHANGELOG b/CHANGELOG
index 9110efd2a..42ff0eb86 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,7 +1,11 @@
# Nmap Changelog ($Id$); -*-text-*-
+o [NSE] Added the Netware Core Protocol (NCP) library and the scripts
+ ncp-serverinfo and ncp-enum-users. [Patrik]
+
o [NSE] Added ldap-novell-getpass, a script that provides support for
retrieving Universal Passwords in plain-text from Novell eDirectory.
+ [Patrik]
o [ZenMmap] Fixed issue with ports closed in newer scan not being removed
from the ports list [Colin Rice]
diff --git a/nselib/ncp.lua b/nselib/ncp.lua
new file mode 100644
index 000000000..8619e1944
--- /dev/null
+++ b/nselib/ncp.lua
@@ -0,0 +1,1199 @@
+--- A tiny implementation of the Netware Core Protocol (NCP)
+-- While NCP was originally a Netware only protocol it's now present on
+-- both Linux and Windows platforms running Novell eDirectory.
+--
+-- The library implements a small amount of NCP functions based on various
+-- packet dumps generated by Novell software, such as the Novell Client and
+-- Console One. The functions are mainly used for enumeration and discovery
+--
+-- The library implements a number of different classes where the Helper is
+-- the one that should be the easiest to use from scripts.
+--
+-- The following classes exist:
+--
+-- * Packet
+-- - Implements functions for creating and serializing a NCP packet
+--
+-- * ResponseParser
+-- - A static class containing a bunch of functions to decode server
+-- responses
+--
+-- * Response
+-- - Class responsible for decoding NCP responses
+--
+-- * NCP
+-- - Contains the "native" NCP functions sending the actual request to the
+-- server.
+--
+-- * Socket
+-- - A buffered socket implementation
+--
+-- * Helper
+-- - The prefered script interface to the library containing functions
+-- that wrap functions from the NCP class using more descriptive names
+-- and easier interface.
+--
+-- * Util
+-- - A class containing mostly decoding and helper functions
+--
+-- The following example code illustrates how to use the Helper class from a
+-- script. The example queries the server for all User objects from the root.
+--
+--
+-- local helper = ncp.Helper:new(host,port)
+-- local status, resp = helper:connect()
+-- status, resp = helper:search("[Root]", "User", "*")
+-- status = helper:close()
+--
+--
+
+--@author Patrik Karlsson
+--@copyright Same as Nmap--See http://nmap.org/book/man-legal.html
+
+-- Version 0.1
+-- Created 24/04/2011 - v0.1 - created by Patrik Karlsson
+
+module(... or "ncp", package.seeall)
+
+require 'ipops'
+
+NCPType = {
+ CreateConnection = 0x1111,
+ ServiceRequest = 0x2222,
+ ServiceReply = 0x3333,
+ DestroyConnection = 0x5555,
+}
+
+Status = {
+ CONNECTION_OK = 0,
+ COMPLETION_OK = 0,
+}
+
+NCPFunction = {
+ GetMountVolumeList = 0x16,
+ GetFileServerInfo = 0x17,
+ Ping = 0x68,
+ EnumerateNetworkAddress = 0x7b,
+ SendFragmentedRequest = 0x68,
+}
+
+NCPVerb = {
+ Resolve = 1,
+ List = 5,
+ Search = 6,
+}
+
+-- The NCP Packet
+Packet = {
+
+ --- Creates a new instance of Packet
+ -- @return o instance of Packet
+ new = function(self)
+ local o = {}
+ setmetatable(o, self)
+ self.__index = self
+ o.ncp_ip = { signature = "DmdT", replybuf = 0, version = 1 }
+ o.task = 1
+ o.func = 0
+ return o
+ end,
+
+ --- Sets the NCP Reply buffer size
+ -- @param n number containing the buffer size
+ setNCPReplyBuf = function(self, n) self.ncp_ip.replybuf = n end,
+
+ --- Sets the NCP packet length
+ -- @param n number containing the length
+ setNCPLength = function(self, n) self.ncp_ip.length = n end,
+
+ --- Gets the NCP packet length
+ -- @return n number containing the NCP length
+ getNCPLength = function(self) return self.ncp_ip.length end,
+
+ --- Sets the NCP packet type
+ -- @param t number containing the NCP packet type
+ setType = function(self, t) self.type = t end,
+
+ --- Gets the NCP packet type
+ -- @return type number containing the NCP packet type
+ getType = function(self) return self.type end,
+
+ --- Sets the NCP packet function
+ -- @param t number containing the NCP function
+ setFunc = function(self, f) self.func = f end,
+
+ --- Gets the NCP packet function
+ -- @return func number containing the NCP packet function
+ getFunc = function(self) return self.func end,
+
+ --- Sets the NCP sequence number
+ -- @param seqno number containing the sequence number
+ setSeqNo = function(self, n) self.seqno = n end,
+
+ --- Sets the NCP connection number
+ -- @param conn number containing the connection number
+ setConnNo = function(self, n) self.conn = n end,
+
+ --- Gets the NCP connection number
+ -- @return conn number containing the connection number
+ getConnNo = function(self) return self.conn end,
+
+ --- Sets the NCP sub function
+ -- @param subfunc number containing the subfunction
+ setSubFunc = function(self, n) self.subfunc = n end,
+
+ --- Gets the NCP sub function
+ -- @return subfunc number containing the subfunction
+ getSubFunc = function(self) return self.subfunc end,
+
+ --- Gets the Sequence number
+ -- @return seqno number containing the sequence number
+ getSeqNo = function(self) return self.seqno end,
+
+ --- Sets the packet length
+ -- @param len number containing the packet length
+ setLength = function(self, n) self.length = n end,
+
+ --- Sets the packet data
+ -- @param data string containing the packet data
+ setData = function(self, data) self.data = data end,
+
+ --- Gets the packet data
+ -- @return data string containing the packet data
+ getData = function(self) return self.data end,
+
+ --- Sets the packet task
+ -- @param task number containing the packet number
+ setTask = function(self, task) self.task = task end,
+
+ --- "Serializes" the packet to a string
+ __tostring = function(self)
+ local UNKNOWN = 0
+ local data = bin.pack(">AIIISCCCCC", self.ncp_ip.signature,
+ self.ncp_ip.length or 0, self.ncp_ip.version,
+ self.ncp_ip.replybuf, self.type, self.seqno,
+ self.conn, self.task, UNKNOWN, self.func )
+
+ if ( self.length ) then data = data .. bin.pack(">S", self.length) end
+ if ( self.subfunc ) then data = data .. bin.pack("C", self.subfunc) end
+ if ( self.data ) then data = data .. bin.pack("A", self.data) end
+
+ return data
+ end,
+
+}
+
+-- Parses different responses into suitable tables
+ResponseParser = {
+
+ --- Determines what parser to call based on the contents of the client
+ -- request and server response.
+ -- @param req instance of Packet containing the request to the server
+ -- @param resp instance of Response containing the server response
+ -- @return status true on success, false on failure
+ -- @return resp table (on success) containing the decoded response
+ -- @return err string (on failure) containing the error message
+ parse = function(req, resp)
+ local func, subfunc, typ = req:getFunc(), req:getSubFunc(), req:getType()
+
+ if ( ResponseParser[func] ) then
+ return ResponseParser[func](resp)
+ elseif ( NCPFunction.SendFragmentedRequest == func ) then
+ if ( 1 == subfunc ) then
+ return ResponseParser.Ping(resp)
+ elseif ( 2 == subfunc ) then
+ local data = req:getData()
+ if ( #data < 21 ) then
+ return false, "Invalid NCP request, could not parse"
+ end
+ local pos, verb = bin.unpack("srvname
+ -- os_major
+ -- os_minor
+ -- conns_supported
+ -- conns_inuse
+ -- vols_supported
+ -- os_rev
+ -- sft_support
+ -- tts_level
+ -- conns_max_use
+ -- acct_version
+ -- vap_version
+ -- qms_version
+ -- print_version
+ -- internet_bridge_ver
+ -- mixed_mode_path
+ -- local_login_info
+ -- product_major
+ -- product_minor
+ -- product_rev
+ -- os_lang_id
+ -- support_64_bit
+ -- @return error message (if status is false)
+ [NCPFunction.GetFileServerInfo] = function(resp)
+ local data = resp:getData()
+ local len = #data
+
+ if ( len < 78 ) then
+ return false, "Failed to decode GetFileServerInfo"
+ end
+
+ local result = {}
+ local pos
+
+ pos, result.srvname, result.os_major, result.os_minor,
+ result.conns_supported, result.conns_inuse,
+ result.vols_supported, result.os_rev, result.sft_support,
+ result.tts_level, result.conns_max_use, result.acct_version,
+ result.vap_version, result.qms_version, result.print_version,
+ result.virt_console_ver, result.sec_restrict_ver,
+ result.internet_bridge_ver, result.mixed_mode_path,
+ result.local_login_info, result.product_major,
+ result.product_minor, result.product_rev, result.os_lang_id,
+ result.support_64_bit = bin.unpack(">A48CCSSSCCCSCCCCCCCCCSSSCC", data)
+
+ return true, result
+ end,
+
+ --- Decodes a GetMountVolumeList response
+ -- @param resp string containing the response as received from the server
+ -- @return status true on success, false on failure
+ -- @return response table of vol entries (if status is true)
+ -- Each vol entry is a table containing the following fields:
+ -- vol_no and vol_name
+ -- @return error message (if status is false)
+ [NCPFunction.GetMountVolumeList] = function(resp)
+ local data = resp:getData()
+ local len = #data
+
+ local pos, items, next_vol_no = bin.unpack("tree_name
+ -- @return error message (if status is false)
+ Ping = function(resp)
+ local data = resp:getData()
+ local len = #data
+ local pos
+ local result = {}
+
+ if ( len < 40 ) then return false, "NCP Ping result too short" end
+
+ pos, result.nds_version = bin.unpack("C", data)
+ -- move to the offset of the
+ pos = pos + 7
+ pos, result.tree_name = bin.unpack("A32", data, pos)
+
+ result.tree_name = (result.tree_name:match("^([^_]*)_*$"))
+
+ return true, result
+ end,
+
+ --- Decodes a EnumerateNetworkAddress response
+ -- @param resp string containing the response as received from the server
+ -- @return status true on success, false on failure
+ -- @return response table (if status is true) containing:
+ -- ip, port and proto
+ -- @return error message (if status is false)
+ [NCPFunction.EnumerateNetworkAddress] = function(resp)
+ local pos, result = 1, {}
+ local items
+ local data = resp:getData()
+ local len = #data
+
+ pos, result.time_since_boot, result.console_version, result.console_revision,
+ result.srvinfo_flags, result.guid, result.next_search,
+ items = bin.unpack("CCISS len ) then
+ return false, "EnumerateNetworkAddress packet too short"
+ end
+
+ result.addr = {}
+ for i=1, items do
+ local addr = {}
+ pos, addr = DecodeAddress(data, pos)
+ table.insert(result.addr, addr )
+ end
+ return true, result
+ end,
+
+
+ --- Decodes a Resolve response
+ -- @param resp string containing the response as received from the server
+ -- @return status true on success, false on failure
+ -- @return response table (if status is true) containing:
+ -- tag and id
+ -- @return error message (if status is false)
+ Resolve = function(resp)
+ local data = resp:getData()
+ local len = #data
+
+ if ( len < 12 ) then
+ return false, "ResponseParser: NCP Resolve, packet too short"
+ end
+
+ local pos, frag_size, frag_handle, comp_code = bin.unpack("EntryDecoder
+ -- @return error message (if status is false)
+ Search = function(resp)
+ local data = resp:getData()
+ local len = #data
+ local entries = {}
+
+ if ( len < 12 ) then
+ return false, "ResponseParser: NCP Resolve, packet too short"
+ end
+
+ local pos, frag_size, frag_handle, comp_code, iter_handle = bin.unpack("flags
+ -- mod_time
+ -- sub_count
+ -- baseclass
+ -- rdn
+ -- name
+ EntryDecoder = function(data, pos)
+
+ -- The InfoFlags class takes a numeric value and facilitates
+ -- bit decoding into InfoFlag fields, the current supported fields
+ -- are:
+ -- Output
+ -- Entry
+ -- Count
+ -- ModTime
+ -- BaseClass
+ -- RelDN
+ -- DN
+ local InfoFlags = {
+ -- Creates a new instance
+ -- @param val number containing the numeric representation of flags
+ -- @return a new instance of InfoFlags
+ new = function(self, val)
+ local o = {}
+ setmetatable(o, self)
+ self.__index = self
+ o.val = val
+ o:parse()
+ return o
+ end,
+
+ -- Parses the numeric value and creates a number of class fields
+ parse = function(self)
+ local fields = { "Output", "_u1", "Entry", "Count", "ModTime",
+ "_u2", "_u3", "_u4", "_u5", "_u6", "_u7", "BaseClass",
+ "RelDN", "DN" }
+ local bits = 1
+ for _, field in ipairs(fields) do
+ self[field] = ( bit.band(self.val, bits) == bits )
+ bits = bits * 2
+ end
+ end
+ }
+
+ local entry = {}
+ local f, len
+ pos, f = bin.unpack("EntryDecoder
+ -- @return error message (if status is false)
+ List = function(resp)
+ local data = resp:getData()
+ local len = #data
+
+ if ( len < 12 ) then
+ return false, "ResponseParser: NCP Resolve, packet too short"
+ end
+
+ local pos, frag_size, frag_handle, comp_code, iter_handle = bin.unpack("IISCCSCC", self.header)
+
+ if ( self.data ) then
+ local len = #self.data - pos
+ if ( ( #self.data - pos ) ~= ( self.length - 33 ) ) then
+ stdnse.print_debug("NCP packet length mismatched")
+ return
+ end
+ end
+ end,
+
+ --- Gets the sequence number
+ -- @return seqno number
+ getSeqNo = function(self) return self.seqno end,
+
+ --- Gets the connection number
+ -- @return conn number
+ getConnNo = function(self) return self.conn end,
+
+ --- Gets the data portion of the response
+ -- @return data string
+ getData = function(self) return self.data end,
+
+ --- Gets the header portion of the response
+ getHeader = function(self) return self.header end,
+
+ --- Returns true if there are any errors
+ -- @return error true if the response error code is anything else than OK
+ hasErrors = function(self)
+ return not( ( self.compl_code == Status.COMPLETION_OK ) and
+ ( self.status_code == Status.CONNECTION_OK ) )
+
+ end,
+
+ --- Creates a Response instance from the data read of the socket
+ -- @param socket socket connected to server and ready to receive data
+ -- @return Response containing a new Response instance
+ fromSocket = function(socket)
+ local status, header = socket:recv(16)
+ if ( not(status) ) then return false, "Failed to receive data" end
+
+ local pos, sig, len = bin.unpack(">II", header)
+ if ( len < 8 ) then return false, "NCP packet too short" end
+
+ local data
+
+ if ( 0 < len - 16 ) then
+ status, data = socket:recv(len - 16)
+ if ( not(status) ) then return false, "Failed to receive data" end
+ end
+ return true, Response:new(header, data)
+ end,
+
+ --- "Serializes" the Response instance to a string
+ __tostring = function(self)
+ return bin.pack("AA", self.header, self.data)
+ end,
+
+}
+
+-- The NCP class
+NCP = {
+
+ --- Creates a new NCP instance
+ -- @param socket containing a socket connected to the NCP server
+ -- @return o instance of NCP
+ new = function(self, socket)
+ local o = {}
+ setmetatable(o, self)
+ self.__index = self
+ o.socket = socket
+ o.seqno = -1
+ o.conn = 0
+ return o
+ end,
+
+ --- Handles sending and receiving a NCP message
+ -- @param p Packet containing the request to send to the server
+ -- @return status true on success false on failure
+ -- @return response table (if status is true) containing the parsed
+ -- response
+ -- @return error string (if status is false) containing the error
+ Exch = function(self, p)
+ local status, err = self:SendPacket(p)
+ if ( not(status) ) then return status, err end
+
+ local status, resp = Response.fromSocket(self.socket)
+ if ( not(status) or resp:hasErrors() ) then return false, resp end
+
+ self.seqno = resp:getSeqNo()
+ self.conn = resp:getConnNo()
+
+ return ResponseParser.parse(p, resp)
+ end,
+
+ --- Sends a packet to the server
+ -- @param p Packet to be sent to the server
+ -- @return status true on success, false on failure
+ -- @return err string containing the error message on failure
+ SendPacket = function(self, p)
+ if ( not(p:getSeqNo() ) ) then p:setSeqNo(self.seqno + 1) end
+ if ( not(p:getConnNo() ) ) then p:setConnNo(self.conn) end
+
+ if ( not(p:getNCPLength()) ) then
+ local len = #(tostring(p))
+ p:setNCPLength(len)
+ end
+
+ local status, err = self.socket:send(tostring(p))
+ if ( not(status) ) then return status, "Failed to send data" end
+
+ return true
+ end,
+
+ --- Creates a connection to the NCP server
+ -- @return status true on success, false on failure
+ CreateConnect = function(self)
+ local p = Packet:new()
+ p:setType(NCPType.CreateConnection)
+
+ local resp = self:Exch( p )
+ return true
+ end,
+
+ --- Destroys a connection established with the NCP server
+ -- @return status true on success, false on failure
+ DestroyConnect = function(self)
+ local p = Packet:new()
+ p:setType(NCPType.DestroyConnection)
+
+ local resp = self:Exch( p )
+ return true
+ end,
+
+ --- Gets file server information
+ -- @return status true on success, false on failure
+ -- @return response table (if status is true) containing:
+ -- srvname
+ -- os_major
+ -- os_minor
+ -- conns_supported
+ -- conns_inuse
+ -- vols_supported
+ -- os_rev
+ -- sft_support
+ -- tts_level
+ -- conns_max_use
+ -- acct_version
+ -- vap_version
+ -- qms_version
+ -- print_version
+ -- internet_bridge_ver
+ -- mixed_mode_path
+ -- local_login_info
+ -- product_major
+ -- product_minor
+ -- product_rev
+ -- os_lang_id
+ -- support_64_bit
+ -- @return error message (if status is false)
+ GetFileServerInfo = function(self)
+ local p = Packet:new()
+ p:setType(NCPType.ServiceRequest)
+ p:setFunc(NCPFunction.GetFileServerInfo)
+ p:setNCPReplyBuf(128)
+ p:setLength(1)
+ p:setSubFunc(17)
+ return self:Exch( p )
+ end,
+
+
+ -- NEEDS authentication, disabled for now
+ --
+ -- Get the logged on user for the specified connection
+ -- @param conn_no number containing the connection number
+ -- GetStationLoggedInfo = function(self, conn_no)
+ -- local p = Packet:new()
+ -- p:setType(NCPType.ServiceRequest)
+ -- p:setFunc(NCPFunction.GetFileServerInfo)
+ -- p:setNCPReplyBuf(62)
+ -- p:setLength(5)
+ -- p:setSubFunc(28)
+ -- p:setTask(4)
+ --
+ -- local data = bin.pack("tree_name
+ -- @return error message (if status is false)
+ Ping = function(self)
+ local p = Packet:new()
+ p:setType(NCPType.ServiceRequest)
+ p:setFunc(NCPFunction.Ping)
+ p:setSubFunc(1)
+ p:setNCPReplyBuf(45)
+ p:setData("\0\0\0")
+
+ return self:Exch( p )
+ end,
+
+ --- Enumerates the IP addresses associated with the server
+ -- @return status true on success, false on failure
+ -- @return response table (if status is true) containing:
+ -- ip, port and proto
+ -- @return error message (if status is false)
+ EnumerateNetworkAddress = function(self)
+ local p = Packet:new()
+ p:setType(NCPType.ServiceRequest)
+ p:setFunc(NCPFunction.EnumerateNetworkAddress)
+ p:setSubFunc(17)
+ p:setNCPReplyBuf(4096)
+ p:setData("\0\0\0\0")
+ p:setLength(5)
+ return self:Exch( p )
+ end,
+
+ --- Resolves an directory entry id from a name
+ -- @param name string containing the name to resolve
+ -- @return status true on success, false on failure
+ -- @return response table (if status is true) containing:
+ -- tag and id
+ -- @return error message (if status is false)
+ ResolveName = function(self, name)
+ local p = Packet:new()
+ p:setType(NCPType.ServiceRequest)
+ p:setFunc(NCPFunction.SendFragmentedRequest)
+ p:setSubFunc(2)
+ p:setNCPReplyBuf(4108)
+
+ local pad = (4 - ( #name % 4 ) )
+ name = Util.ZeroPad(name, #name + pad)
+
+ local w_name = Util.ToWideChar(name)
+ local frag_handle, frag_size = 0xffffffff, 64176
+ local msg_size, unknown, proto_flags, nds_verb = 44 + #w_name, 0, 0, 1
+ local nds_reply_buf, version, flags, scope = 4096, 1, 0x2062, 0
+ local unknown2 = 0x0e
+ local ZERO = 0
+
+ local data = bin.pack("vol_no and vol_name
+ -- @return error message (if status is false)
+ GetMountVolumeList = function(self)
+ local p = Packet:new()
+ p:setType(NCPType.ServiceRequest)
+ p:setFunc(NCPFunction.GetMountVolumeList)
+ p:setSubFunc(52)
+ p:setNCPReplyBuf(538)
+ p:setTask(4)
+ p:setLength(12)
+
+ local start_vol = 0
+ local vol_req_flags = 1
+ local src_name_space = 0
+
+ local data = bin.pack("Resolve
+ -- @param class string containing a class name (or * wildcard)
+ -- @param name string containing a entry name (or * wildcard)
+ -- @param options table containing one or more of the following
+ -- numobjs
+ -- @return status true on success false on failure
+ -- @return entries table (if status is true) as return by:
+ -- ResponseDecoder.EntryDecoder
+ -- @return error string (if status is false) containing the error
+ Search = function(self, base, class, name, options)
+ assert( ( base and base.id ), "No base entry was specified")
+
+ local class = class and class .. '\0' or '*\0'
+ local name = name and name .. '\0' or '*\0'
+ local w_name = Util.ToWideChar(name)
+ local w_class = Util.ToWideChar(class)
+ local options = options or {}
+ local p = Packet:new()
+ p:setType(NCPType.ServiceRequest)
+ p:setFunc(NCPFunction.SendFragmentedRequest)
+ p:setSubFunc(2)
+ p:setNCPReplyBuf(64520)
+ p:setTask(5)
+
+ local frag_handle, frag_size, msg_size = 0xffffffff, 64176, 98
+ local unknown, proto_flags, nds_verb, version, flags = 0, 0, 6, 3, 0
+ local nds_reply_buf = 64520
+ local iter_handle = 0xffffffff
+ local repl_type = 2 -- base and all subordinates
+ local numobjs = options.numobjs or 0
+ local info_types = 1 -- Names
+ local info_flags = 0x0000381d
+ -- a bunch of unknowns
+ local u2, u3, u4, u5, u6, u7, u8, u9 = 0, 0, 2, 2, 0, 0x10, 0, 0x11
+
+ local data = bin.pack("Resolve
+ -- @return status true on success false on failure
+ -- @return entries table (if status is true) as return by:
+ -- ResponseDecoder.EntryDecoder
+ -- @return error string (if status is false) containing the error
+ List = function(self, entry)
+ local p = Packet:new()
+ p:setType(NCPType.ServiceRequest)
+ p:setFunc(NCPFunction.SendFragmentedRequest)
+ p:setSubFunc(2)
+ p:setNCPReplyBuf(4112)
+ p:setTask(2)
+
+ local frag_handle, frag_size = 0xffffffff, 64176
+ local msg_size, unknown, proto_flags, nds_verb = 40, 0, 0, 5
+ local nds_reply_buf, version, flags = 4100, 1, 0x0001
+ local iter_handle = 0xffffffff
+ local unknown2 = 0x0e
+ local ZERO = 0
+ local info_flags = 0x0000381d
+
+ local data = bin.pack("numobjs - number of objects to limit the search to
+ search = function(self, base, class, name, options)
+ local base = base or "[Root]"
+ local status, entry = self.ncp:ResolveName(base)
+
+ if ( not(status) ) then
+ return false, "Search failed, base could not be resolved"
+ end
+
+ local status, result = self.ncp:Search(entry, class, name, options)
+ if (not(status)) then return false, result end
+
+ return status, result
+ end,
+
+ --- Retrieves some information from the server using the following NCP
+ -- functions:
+ -- GetFileServerInfo
+ -- Ping
+ -- EnumerateNetworkAddress
+ -- GetMountVolumeList
+ -- The result contains the Tree name, product versions and mounts
+ getServerInfo = function(self)
+ local status, srv_info = self.ncp:GetFileServerInfo()
+ if ( not(status) ) then return false, srv_info end
+
+ local status, ping_info = self.ncp:Ping()
+ if ( not(status) ) then return false, ping_info end
+
+ local status, net_info = self.ncp:EnumerateNetworkAddress()
+ if ( not(status) ) then return false, net_info end
+
+ local status, mnt_list = self.ncp:GetMountVolumeList()
+ if ( not(status) ) then return false, mnt_list end
+
+ local output = {}
+ table.insert(output, ("Server name: %s"):format(srv_info.srvname))
+ table.insert(output, ("Tree Name: %s"):format(ping_info.tree_name))
+ table.insert(output,
+ ("OS Version: %d.%d (rev %d)"):format(srv_info.os_major,
+ srv_info.os_minor, srv_info.os_rev))
+ table.insert(output,
+ ("Product version: %d.%d (rev %d)"):format(srv_info.product_major,
+ srv_info.product_minor, srv_info.product_rev))
+ table.insert(output, ("OS Language ID: %d"):format(srv_info.os_lang_id))
+
+ local niceaddr = {}
+ for _, addr in ipairs(net_info.addr) do
+ table.insert(niceaddr, ("%s %d/%s"):format(addr.ip,addr.port,
+ addr.proto))
+ end
+
+ niceaddr.name = "Addresses"
+ table.insert(output, niceaddr)
+
+ local mounts = {}
+ for _, mount in ipairs(mnt_list) do
+ table.insert(mounts, mount.vol_name)
+ end
+
+ mounts.name = "Mounts"
+ table.insert(output, mounts)
+
+ if ( nmap.debugging() > 0 ) then
+ table.insert(output, ("Acct version: %d"):format(srv_info.acct_version))
+ table.insert(output, ("VAP version: %d"):format(srv_info.vap_version))
+ table.insert(output, ("QMS version: %d"):format(srv_info.qms_version))
+ table.insert(output,
+ ("Print server version: %d"):format(srv_info.print_version))
+ table.insert(output,
+ ("Virtual console version: %d"):format(srv_info.virt_console_ver))
+ table.insert(output,
+ ("Security Restriction Version: %d"):format(srv_info.sec_restrict_ver))
+ table.insert(output,
+ ("Internet Bridge Version: %d"):format(srv_info.internet_bridge_ver))
+ end
+
+ return true, output
+ end,
+}
+
+Socket =
+{
+ new = function(self)
+ local o = {}
+ setmetatable(o, self)
+ self.__index = self
+ o.Socket = nmap.new_socket()
+ o.Buffer = nil
+ return o
+ end,
+
+ --- Sets the socket timeout (@see nmap.set_timeout)
+ -- @param tm number containing the socket timeout in ms
+ set_timeout = function(self, tm) self.Socket:set_timeout(tm) end,
+
+ --- Establishes a connection.
+ --
+ -- @param hostid Hostname or IP address.
+ -- @param port Port number.
+ -- @param protocol "tcp", "udp", or
+ -- @return Status (true or false).
+ -- @return Error code (if status is false).
+ connect = function( self, hostid, port, protocol )
+ self.Socket:set_timeout(5000)
+ return self.Socket:connect( hostid, port, protocol )
+ end,
+
+ --- Closes an open connection.
+ --
+ -- @return Status (true or false).
+ -- @return Error code (if status is false).
+ close = function( self )
+ return self.Socket:close()
+ end,
+
+ --- Opposed to the socket:receive_bytes function, that returns
+ -- at least x bytes, this function returns the amount of bytes requested.
+ --
+ -- @param count of bytes to read
+ -- @return true on success, false on failure
+ -- @return data containing bytes read from the socket
+ -- err containing error message if status is false
+ recv = function( self, count )
+ local status, data
+
+ self.Buffer = self.Buffer or ""
+
+ if ( #self.Buffer < count ) then
+ status, data = self.Socket:receive_bytes( count - #self.Buffer )
+ if ( not(status) or #data < count - #self.Buffer ) then
+ return false, data
+ end
+ self.Buffer = self.Buffer .. data
+ end
+
+ data = self.Buffer:sub( 1, count )
+ self.Buffer = self.Buffer:sub( count + 1)
+
+ return true, data
+ end,
+
+ --- Sends data over the socket
+ --
+ -- @return Status (true or false).
+ -- @return Error code (if status is false).
+ send = function( self, data )
+ return self.Socket:send( data )
+ end,
+}
+
+--- "static" Utility class containing mostly conversion functions
+Util =
+{
+ --- Converts a string to a wide string
+ --
+ -- @param str string to be converted
+ -- @return string containing a two byte representation of str where a zero
+ -- byte character has been tagged on to each character.
+ ToWideChar = function( str )
+ return str:gsub("(.)", "%1" .. string.char(0x00) )
+ end,
+
+
+ --- Concerts a wide string to string
+ --
+ -- @param wstr containing the wide string to convert
+ -- @return string with every other character removed
+ FromWideChar = function( wstr )
+ local str = ""
+ if ( nil == wstr ) then return nil end
+ for i=1, wstr:len(), 2 do str = str .. wstr:sub(i, i) end
+ return str
+ end,
+
+ --- Pads a string with zeroes
+ --
+ -- @param str string containing the string to be padded
+ -- @param len number containing the length of the new string
+ -- @return str string containing the new string
+ ZeroPad = function( str, len )
+ if len < str:len() then return end
+ for i=1, len - str:len() do str = str .. string.char(0) end
+ return str
+ end,
+
+ -- Removes trailing nulls
+ --
+ -- @param str containing the string
+ -- @return ret the string with any trailing nulls removed
+ CToLuaString = function( str )
+ local ret
+
+ if ( not(str) ) then return "" end
+ if ( str:sub(-1, -1 ) ~= "\0" ) then return str end
+
+ for i=1, #str do
+ if ( str:sub(-i,-i) == "\0" ) then
+ ret = str:sub(1, -i - 1)
+ else
+ break
+ end
+ end
+ return ret
+ end,
+
+}
\ No newline at end of file
diff --git a/scripts/ncp-enum-users.nse b/scripts/ncp-enum-users.nse
new file mode 100644
index 000000000..d07dfe7d8
--- /dev/null
+++ b/scripts/ncp-enum-users.nse
@@ -0,0 +1,51 @@
+description = [[
+Retrieves a list of all eDirectory users from the NCP service
+]]
+
+---
+--
+--@output
+-- PORT STATE SERVICE REASON
+-- 524/tcp open ncp syn-ack
+-- | ncp-enum-users:
+-- | CN=admin.O=cqure
+-- | CN=cawi.OU=finance.O=cqure
+-- | CN=linux-l84tadmin.O=cqure
+-- | CN=nist.OU=hr.O=cqure
+-- | CN=novlxregd.O=cqure
+-- | CN=novlxsrvd.O=cqure
+-- | CN=OESCommonProxy_linux-l84t.O=cqure
+-- | CN=sasi.OU=hr.O=cqure
+-- |_ CN=wwwrun.O=cqure
+--
+
+-- Version 0.1
+-- Created 04/26/2011 - v0.1 - created by Patrik Karlsson
+
+author = "Patrik Karlsson"
+license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
+categories = {"discovery", "safe"}
+
+require 'shortport'
+require 'ncp'
+
+portrule = shortport.port_or_service(524, "ncp", "tcp")
+
+action = function(host, port)
+ local helper = ncp.Helper:new(host,port)
+
+ local status, resp = helper:connect()
+ if ( not(status) ) then return stdnse.format_output(false, resp) end
+
+ status, resp = helper:search("[Root]", "User", "*")
+ if ( not(status) ) then return stdnse.format_output(false, resp) end
+
+ local output = {}
+
+ for _, entry in ipairs(resp) do
+ table.insert(output, entry.name)
+ end
+
+ return stdnse.format_output(true, output)
+end
+
diff --git a/scripts/ncp-serverinfo.nse b/scripts/ncp-serverinfo.nse
new file mode 100644
index 000000000..f299eb3ee
--- /dev/null
+++ b/scripts/ncp-serverinfo.nse
@@ -0,0 +1,48 @@
+description = [[
+Gets NCP Server Information
+]]
+
+---
+--
+--@output
+-- PORT STATE SERVICE
+-- 524/tcp open ncp
+-- | ncp-serverinfo:
+-- | Server name: LINUX-L84T
+-- | Tree Name: IIT-LABTREE
+-- | OS Version: 5.70 (rev 7)
+-- | Product version: 6.50 (rev 7)
+-- | OS Language ID: 4
+-- | Addresses
+-- | 10.0.200.33 524/udp
+-- | 10.0.200.33 524/tcp
+-- | Mounts
+-- | SYS
+-- | ADMIN
+-- |_ _ADMIN
+
+-- Version 0.1
+-- Created 04/26/2011 - v0.1 - created by Patrik Karlsson
+
+author = "Patrik Karlsson"
+license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
+categories = {"discovery", "safe"}
+
+require "shortport"
+require "ncp"
+
+portrule = shortport.port_or_service(524, "ncp", "tcp")
+
+action = function(host, port)
+ local helper = ncp.Helper:new(host,port)
+
+ local status, resp = helper:connect()
+ if ( not(status) ) then return stdnse.format_output(false, resp) end
+
+ status, resp = helper:getServerInfo()
+ if ( not(status) ) then return stdnse.format_output(false, resp) end
+
+ helper:close()
+
+ return stdnse.format_output(true, resp)
+end
diff --git a/scripts/script.db b/scripts/script.db
index a5ed856db..df78684c5 100644
--- a/scripts/script.db
+++ b/scripts/script.db
@@ -113,6 +113,8 @@ Entry { filename = "mysql-users.nse", categories = { "discovery", "intrusive", }
Entry { filename = "mysql-variables.nse", categories = { "discovery", "intrusive", } }
Entry { filename = "nat-pmp-info.nse", categories = { "default", "discovery", "safe", } }
Entry { filename = "nbstat.nse", categories = { "default", "discovery", "safe", } }
+Entry { filename = "ncp-enum-users.nse", categories = { "discovery", "safe", } }
+Entry { filename = "ncp-serverinfo.nse", categories = { "discovery", "safe", } }
Entry { filename = "netbus-auth-bypass.nse", categories = { "auth", "safe", "vuln", } }
Entry { filename = "netbus-brute.nse", categories = { "auth", "intrusive", } }
Entry { filename = "netbus-info.nse", categories = { "default", "discovery", "safe", } }