mirror of
https://github.com/nmap/nmap.git
synced 2025-12-09 22:21:29 +00:00
This is a maintenance fix for the NSE Nsock library binding. The patch focuses on code correctness and simplicity. The patch also brings some initial updates with an eye towards the upcoming Lua 5.2 release. See [1] for a post concerning this branch. [1] http://seclists.org/nmap-dev/2010/q3/710
622 lines
27 KiB
Lua
622 lines
27 KiB
Lua
---Implement a Dynamic Host Configuration Protocol (DHCP) client.
|
|
--
|
|
-- DHCP, defined in rfc2132 and rfc2131, is a protocol for hosts to automatically
|
|
-- configure themselves on a network (that is, obtain an ip address). This library,
|
|
-- which have a trivial one-function interface, can send out DHCP packets of many
|
|
-- types and parse the responses.
|
|
--
|
|
|
|
module(... or "dhcp", package.seeall)
|
|
|
|
require 'bin'
|
|
require 'bit'
|
|
require 'ipOps'
|
|
require 'stdnse'
|
|
|
|
request_types =
|
|
{
|
|
DHCPDISCOVER = 1,
|
|
DHCPOFFER = 2,
|
|
DHCPREQUEST = 3,
|
|
DHCPDECLINE = 4,
|
|
DHCPACK = 5,
|
|
DHCPNAK = 6,
|
|
DHCPRELEASE = 7,
|
|
DHCPINFORM = 8
|
|
}
|
|
|
|
request_types_str = {}
|
|
request_types_str[1] = "DHCPDISCOVER"
|
|
request_types_str[2] = "DHCPOFFER"
|
|
request_types_str[3] = "DHCPREQUEST"
|
|
request_types_str[4] = "DHCPDECLINE"
|
|
request_types_str[5] = "DHCPACK"
|
|
request_types_str[6] = "DHCPNAK"
|
|
request_types_str[7] = "DHCPRELEASE"
|
|
request_types_str[8] = "DHCPINFORM"
|
|
|
|
---Read an IP address or a list of IP addresses. Print an error if the length isn't a multiple of 4.
|
|
--
|
|
--@param data The packet.
|
|
--@param pos The position in the packet.
|
|
--@param length The length that the server claims the field is.
|
|
--@return The new position (will always be pos + length, no matter what we think it should be)
|
|
--@return The value of the field, or nil if the field length was wrong.
|
|
local function read_ip(data, pos, length)
|
|
if(length ~= 4) then
|
|
if((length % 4) ~= 0) then
|
|
stdnse.print_debug(1, "dhcp-discover: Invalid length for an ip address (%d)", length)
|
|
pos = pos + length
|
|
|
|
return pos, nil
|
|
else
|
|
local results = {}
|
|
for i=1, length, 4 do
|
|
local value
|
|
pos, value = bin.unpack("<I", data, pos)
|
|
table.insert(results, ipOps.fromdword(value))
|
|
end
|
|
|
|
return pos, results
|
|
end
|
|
else
|
|
local value
|
|
pos, value = bin.unpack("<I", data, pos)
|
|
|
|
return pos, ipOps.fromdword(value)
|
|
end
|
|
end
|
|
|
|
---Read a string. The length of the string is given by the length field.
|
|
--
|
|
--@param data The packet.
|
|
--@param pos The position in the packet.
|
|
--@param length The length that the server claims the field is.
|
|
--@return The new position (will always be pos + length, no matter what we think it should be)
|
|
--@return The value of the field, or nil if the field length was wrong.
|
|
local function read_string(data, pos, length)
|
|
return bin.unpack(string.format("A%d", length), data, pos)
|
|
end
|
|
|
|
---Read a single byte. Print an error if the length isn't 1.
|
|
--
|
|
--@param data The packet.
|
|
--@param pos The position in the packet.
|
|
--@param length The length that the server claims the field is.
|
|
--@return The new position (will always be pos + length, no matter what we think it should be)
|
|
--@return The value of the field, or nil if the field length was wrong.
|
|
local function read_1_byte(data, pos, length)
|
|
if(length ~= 1) then
|
|
stdnse.print_debug(1, "dhcp-discover: Invalid length for data (%d; should be %d)", length, 1)
|
|
pos = pos + length
|
|
return pos, nil
|
|
end
|
|
return bin.unpack("C", data, pos)
|
|
end
|
|
|
|
---Read a message type. This is a single-byte value that's looked up in the <code>request_types_str</code>
|
|
-- table. Print an error if the length isn't 1.
|
|
--
|
|
--@param data The packet.
|
|
--@param pos The position in the packet.
|
|
--@param length The length that the server claims the field is.
|
|
--@return The new position (will always be pos + length, no matter what we think it should be)
|
|
--@return The value of the field, or nil if the field length was wrong.
|
|
local function read_message_type(data, pos, length)
|
|
local value
|
|
|
|
pos, value = read_1_byte(data, pos, length)
|
|
if(value == nil) then
|
|
stdnse.print_debug(1, "dhcp-discover: Couldn't read the 1-byte message type")
|
|
return pos, nil
|
|
end
|
|
|
|
return pos, request_types_str[value]
|
|
end
|
|
|
|
---Read a single byte, and return 'false' if it's 0, or 'true' if it's non-zero. Print an error if the
|
|
-- length isn't 1.
|
|
--
|
|
--@param data The packet.
|
|
--@param pos The position in the packet.
|
|
--@param length The length that the server claims the field is.
|
|
--@return The new position (will always be pos + length, no matter what we think it should be)
|
|
--@return The value of the field, or nil if the field length was wrong.
|
|
local function read_boolean(data, pos, length)
|
|
local result
|
|
pos, result = read_1_byte(data, pos, length)
|
|
|
|
if(result == nil) then
|
|
stdnse.print_debug(1, "dhcp-discover: Couldn't read the 1-byte boolean")
|
|
return pos, nil
|
|
elseif(result == 0) then
|
|
return pos, "false"
|
|
else
|
|
return pos, "true"
|
|
end
|
|
end
|
|
|
|
---Read a 2-byte unsigned little endian value. Print an error if the length isn't 2.
|
|
--
|
|
--@param data The packet.
|
|
--@param pos The position in the packet.
|
|
--@param length The length that the server claims the field is.
|
|
--@return The new position (will always be pos + length, no matter what we think it should be)
|
|
--@return The value of the field, or nil if the field length was wrong.
|
|
local function read_2_bytes(data, pos, length)
|
|
if(length ~= 2) then
|
|
stdnse.print_debug(1, "dhcp-discover: Invalid length for data (%d; should be %d)", length, 2)
|
|
pos = pos + length
|
|
return pos, nil
|
|
end
|
|
return bin.unpack(">S", data, pos)
|
|
end
|
|
|
|
|
|
---Read a list of 2-byte unsigned little endian values. Print an error if the length isn't a multiple
|
|
-- of 2.
|
|
--
|
|
--@param data The packet.
|
|
--@param pos The position in the packet.
|
|
--@param length The length that the server claims the field is.
|
|
--@return The new position (will always be pos + length, no matter what we think it should be)
|
|
--@return The value of the field, or nil if the field length was wrong.
|
|
local function read_2_bytes_list(data, pos, length)
|
|
if((length % 2) ~= 0) then
|
|
stdnse.print_debug(1, "dhcp-discover: Invalid length for data (%d; should be multiple of %d)", length, 2)
|
|
pos = pos + length
|
|
|
|
return pos, nil
|
|
else
|
|
local results = {}
|
|
for i=1, length, 2 do
|
|
local value
|
|
pos, value = bin.unpack(">S", data, pos)
|
|
table.insert(results, value)
|
|
end
|
|
|
|
return pos, results
|
|
end
|
|
end
|
|
|
|
|
|
---Read a 4-byte unsigned little endian value. Print an error if the length isn't 4.
|
|
--
|
|
--@param data The packet.
|
|
--@param pos The position in the packet.
|
|
--@param length The length that the server claims the field is.
|
|
--@return The new position (will always be pos + length, no matter what we think it should be)
|
|
--@return The value of the field, or nil if the field length was wrong.
|
|
local function read_4_bytes(data, pos, length)
|
|
if(length ~= 4) then
|
|
stdnse.print_debug(1, "dhcp-discover: Invalid length for data (%d; should be %d)", length, 4)
|
|
pos = pos + length
|
|
return pos, nil
|
|
end
|
|
return bin.unpack(">I", data, pos)
|
|
end
|
|
|
|
---Read a 4-byte unsigned little endian value, and interpret it as a time offset value. Print an
|
|
-- error if the length isn't 4.
|
|
--
|
|
--@param data The packet.
|
|
--@param pos The position in the packet.
|
|
--@param length The length that the server claims the field is.
|
|
--@return The new position (will always be pos + length, no matter what we think it should be)
|
|
--@return The value of the field, or nil if the field length was wrong.
|
|
local function read_time(data, pos, length)
|
|
local result
|
|
if(length ~= 4) then
|
|
stdnse.print_debug(1, "dhcp-discover: Invalid length for data (%d; should be %d)", length, 4)
|
|
pos = pos + length
|
|
return pos, nil
|
|
end
|
|
pos, result = bin.unpack(">I", data, pos)
|
|
|
|
-- This code was mostly taken from snmp-sysdescr.nse. It should probably be abstracted into stdnse.lua [TODO]
|
|
local days, hours, minutes, seconds, htime, mtime, stime
|
|
days = math.floor(result / 86400)
|
|
htime = math.fmod(result, 86400)
|
|
hours = math.floor(htime / 3600)
|
|
mtime = math.fmod(htime, 3600)
|
|
minutes = math.floor(mtime / 60)
|
|
seconds = math.fmod(mtime, 60)
|
|
|
|
local dayLabel
|
|
|
|
if days == 1 then
|
|
dayLabel = "day"
|
|
else
|
|
dayLabel = "days"
|
|
end
|
|
|
|
return pos, string.format("%d %s, %d:%02d:%02d", days, dayLabel, hours, minutes, seconds)
|
|
end
|
|
|
|
---Read a list of static routes. Each of them are a pair of IP addresses, a destination and a
|
|
-- router. Print an error if the length isn't a multiple of 8.
|
|
--
|
|
--@param data The packet.
|
|
--@param pos The position in the packet.
|
|
--@param length The length that the server claims the field is.
|
|
--@return The new position (will always be pos + length, no matter what we think it should be)
|
|
--@return The value of the field, or nil if the field length was wrong.
|
|
local function read_static_route(data, pos, length)
|
|
if((length % 8) ~= 0) then
|
|
stdnse.print_debug(1, "dhcp-discover: Invalid length for data (%d; should be multiple of %d)", length, 8)
|
|
pos = pos + length
|
|
|
|
return pos, nil
|
|
else
|
|
local results = {}
|
|
for i=1, length, 8 do
|
|
local destination, router
|
|
pos, destination = read_ip(data, pos, 4)
|
|
pos, router = read_ip(data, pos, 4)
|
|
table.insert(results, {destination=destination, router=router})
|
|
end
|
|
|
|
return pos, results
|
|
end
|
|
end
|
|
|
|
---Read a list of policy filters. Each of them are a pair of IP addresses, an address and a
|
|
-- mask. Print an error if the length isn't a multiple of 8.
|
|
--
|
|
--@param data The packet.
|
|
--@param pos The position in the packet.
|
|
--@param length The length that the server claims the field is.
|
|
--@return The new position (will always be pos + length, no matter what we think it should be)
|
|
--@return The value of the field, or nil if the field length was wrong.
|
|
local function read_policy_filter(data, pos, length)
|
|
if((length % 8) ~= 0) then
|
|
stdnse.print_debug(1, "dhcp-discover: Invalid length for data (%d; should be multiple of %d)", length, 8)
|
|
pos = pos + length
|
|
|
|
return pos, nil
|
|
else
|
|
local results = {}
|
|
for i=1, length, 8 do
|
|
local address, router, mask
|
|
pos, address = read_ip(data, pos, 4)
|
|
pos, mask = read_ip(data, pos, 4)
|
|
table.insert(results, {address=address, mask=mask})
|
|
end
|
|
|
|
return pos, results
|
|
end
|
|
end
|
|
|
|
---These are the different fields for DHCP. These have to come after the read_* function
|
|
-- definitions.
|
|
local actions = {}
|
|
actions[1] = {name="Subnet Mask", func=read_ip, default=true}
|
|
actions[2] = {name="Time Offset", func=read_4_bytes, default=false}
|
|
actions[3] = {name="Router", func=read_ip, default=true}
|
|
actions[4] = {name="Time Server", func=read_ip, default=true}
|
|
actions[5] = {name="Name Server", func=read_ip, default=true}
|
|
actions[6] = {name="Domain Name Server", func=read_ip, default=true}
|
|
actions[7] = {name="Log Server", func=read_ip, default=true}
|
|
actions[8] = {name="Cookie Server", func=read_ip, default=true}
|
|
actions[9] = {name="LPR Server", func=read_ip, default=true}
|
|
actions[10] = {name="Impress Server", func=read_ip, default=true}
|
|
actions[11] = {name="Resource Location Server", func=read_ip, default=true}
|
|
actions[12] = {name="Hostname", func=read_string, default=true}
|
|
actions[13] = {name="Boot File Size", func=read_2_bytes, default=false}
|
|
actions[14] = {name="Merit Dump File", func=read_string, default=false}
|
|
actions[15] = {name="Domain Name", func=read_string, default=true}
|
|
actions[16] = {name="Swap Server", func=read_ip, default=true}
|
|
actions[17] = {name="Root Path", func=read_string, default=false}
|
|
actions[18] = {name="Extensions Path", func=read_string, default=false}
|
|
actions[19] = {name="IP Forwarding", func=read_boolean, default=false}
|
|
actions[20] = {name="Non-local Source Routing", func=read_boolean, default=true}
|
|
actions[21] = {name="Policy Filter", func=read_policy_filter, default=false}
|
|
actions[22] = {name="Maximum Datagram Reassembly Size",func=read_2_bytes, default=false}
|
|
actions[23] = {name="Default IP TTL", func=read_1_byte, default=false}
|
|
actions[24] = {name="Path MTU Aging Timeout", func=read_time, default=false}
|
|
actions[25] = {name="Path MTU Plateau", func=read_2_bytes_list, default=false}
|
|
actions[26] = {name="Interface MTU", func=read_2_bytes, default=false}
|
|
actions[27] = {name="All Subnets are Local", func=read_boolean, default=false}
|
|
actions[28] = {name="Broadcast Address", func=read_ip, default=true}
|
|
actions[29] = {name="Perform Mask Discovery", func=read_boolean, default=false}
|
|
actions[30] = {name="Mask Supplier", func=read_boolean, default=false}
|
|
actions[31] = {name="Perform Router Discovery", func=read_boolean, default=false}
|
|
actions[32] = {name="Router Solicitation Address", func=read_ip, default=true}
|
|
actions[33] = {name="Static Route", func=read_static_route, default=true}
|
|
actions[34] = {name="Trailer Encapsulation", func=read_boolean, default=false}
|
|
actions[35] = {name="ARP Cache Timeout", func=read_time, default=false}
|
|
actions[36] = {name="Ethernet Encapsulation", func=read_boolean, default=false}
|
|
actions[37] = {name="TCP Default TTL", func=read_1_byte, default=false}
|
|
actions[38] = {name="TCP Keepalive Interval", func=read_4_bytes, default=false}
|
|
actions[39] = {name="TCP Keepalive Garbage", func=read_boolean, default=false}
|
|
actions[40] = {name="NIS Domain", func=read_string, default=true}
|
|
actions[41] = {name="NIS Servers", func=read_ip, default=true}
|
|
actions[42] = {name="NTP Servers", func=read_ip, default=true}
|
|
actions[43] = {name="Vendor Specific Information", func=read_string, default=false}
|
|
actions[44] = {name="NetBIOS Name Server", func=read_ip, default=true}
|
|
actions[45] = {name="NetBIOS Datagram Server", func=read_ip, default=true}
|
|
actions[46] = {name="NetBIOS Node Type", func=read_1_byte, default=false}
|
|
actions[47] = {name="NetBIOS Scope", func=read_string, default=false}
|
|
actions[48] = {name="X Window Font Server", func=read_ip, default=true}
|
|
actions[49] = {name="X Window Display Manager", func=read_ip, default=true}
|
|
actions[50] = {name="Requested IP Address (client)", func=read_ip, default=false}
|
|
actions[51] = {name="IP Address Lease Time", func=read_time, default=false}
|
|
actions[52] = {name="Option Overload", func=read_1_byte, default=false}
|
|
actions[53] = {name="DHCP Message Type", func=read_message_type, default=false}
|
|
actions[54] = {name="Server Identifier", func=read_ip, default=true}
|
|
actions[55] = {name="Parameter Request List (client)", func=read_string, default=false}
|
|
actions[56] = {name="Error Message", func=read_string, default=true}
|
|
actions[57] = {name="Maximum DHCP Message Size", func=read_2_bytes, default=false}
|
|
actions[58] = {name="Renewal Time Value", func=read_time, default=false}
|
|
actions[59] = {name="Rebinding Time Value", func=read_time, default=false}
|
|
actions[60] = {name="Class Identifier", func=read_string, default=false}
|
|
actions[61] = {name="Client Identifier (client)", func=read_string, default=false}
|
|
|
|
--- Does the send/receive, doesn't build/parse anything.
|
|
local function dhcp_send(interface, host, packet, transaction_id)
|
|
local socket
|
|
local status, err, data
|
|
local result
|
|
local results = {}
|
|
|
|
|
|
-- Create a pcap socket to listen for the response. I used to consider this a hack, but
|
|
-- it really isn't -- it's kinda how this has to be done.
|
|
local pcap = nmap.new_socket()
|
|
pcap:pcap_open(interface, 590, false, "udp port 68")
|
|
pcap:set_timeout(5000)
|
|
stdnse.print_debug(1, "dhcp: Starting listener")
|
|
|
|
-- Create the UDP socket (TODO: enable SO_BROADCAST if we need to)
|
|
socket = nmap.new_socket()
|
|
status, err = socket:connect(host, 67, "udp")
|
|
if(status == false) then
|
|
return false, "Couldn't create socket: " .. err
|
|
end
|
|
stdnse.print_debug(1, "dhcp: Created UDP socket")
|
|
|
|
-- Send out the packet
|
|
socket:send(packet)
|
|
|
|
-- Read the response
|
|
local status, length, layer2, layer3 = pcap:pcap_receive();
|
|
-- This pulls back 4 bytes in the packet that correspond to the transaction id. This should be randomly
|
|
-- generated and different for every instance of a script (to prevent collisions)
|
|
while status and layer3:sub(33, 36) ~= transaction_id do
|
|
status, length, layer2, layer3 = pcap:pcap_receive();
|
|
end
|
|
if(status == false) then
|
|
stdnse.print_debug(1, "dhcp: Error calling pcap_receive(): %s", err)
|
|
return false, "Error calling pcap_receive(): " .. err
|
|
end
|
|
|
|
-- If no data was captured (ie, a timeout), return an error
|
|
if(data == nil) then
|
|
stdnse.print_debug(1, "dhcp: Error calling pcap_receive(): TIMEOUT")
|
|
return false, "TIMEOUT"
|
|
end
|
|
|
|
-- Cut off the address/transport headers
|
|
data = string.sub(data, 29) -- I doubt this is the right way to do this, but since we're only supporting IPv4 + UDP, maybe it'll work
|
|
|
|
-- Close our sockets
|
|
socket:close()
|
|
pcap:close()
|
|
|
|
-- Finally, return the data
|
|
return true, data
|
|
end
|
|
|
|
local function dhcp_build(request_type, ip_address, mac_address, request_options, overrides, lease_time, transaction_id)
|
|
local packet = ''
|
|
|
|
-- Set up the default overrides
|
|
if(overrides == nil) then
|
|
overrides = {}
|
|
end
|
|
|
|
if(request_options == nil) then
|
|
-- Request the defaults, or there's no verbosity; otherwise, request everything!
|
|
request_options = ''
|
|
for i = 1, 61, 1 do
|
|
if(nmap.verbosity() > 0) then
|
|
request_options = request_options .. string.char(i)
|
|
else
|
|
if(actions[i] and actions[i].default) then
|
|
request_options = request_options .. string.char(i)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Header
|
|
packet = packet .. bin.pack(">CCCC", overrides['op'] or 1, overrides['htype'] or 1, overrides['hlen'] or 6, overrides['hops'] or 0) -- BOOTREQUEST, 10mb ethernet, 6 bytes long, 0 hops
|
|
packet = packet .. transaction_id -- Transaction ID
|
|
packet = packet .. bin.pack(">SS", overrides['secs'] or 0, overrides['flags'] or 0x0000) -- Secs, flags
|
|
packet = packet .. bin.pack("A", ip_address) -- Client address
|
|
packet = packet .. bin.pack("<I", overrides['yiaddr'] or 0) -- yiaddr
|
|
packet = packet .. bin.pack("<I", overrides['siaddr'] or 0) -- siaddr
|
|
packet = packet .. bin.pack("<I", overrides['giaddr'] or 0) -- giaddr
|
|
packet = packet .. mac_address .. string.rep(string.char(0), 16 - #mac_address) -- chaddr (MAC address)
|
|
packet = packet .. (overrides['sname'] or string.rep(string.char(0), 64)) -- sname
|
|
packet = packet .. (overrides['file'] or string.rep(string.char(0), 128)) -- file
|
|
packet = packet .. bin.pack(">I", overrides['cookie'] or 0x63825363) -- Magic cookie
|
|
|
|
-- Options
|
|
packet = packet .. bin.pack(">CCC", 0x35, 1, request_type) -- Request type
|
|
packet = packet .. bin.pack(">CCA", 0x37, #request_options, request_options) -- Request options
|
|
packet = packet .. bin.pack(">CCI", 0x33, 4, lease_time or 1) -- Lease time
|
|
|
|
packet = packet .. bin.pack(">C", 0xFF) -- Termination
|
|
|
|
return true, packet
|
|
end
|
|
|
|
---Parse a DHCP packet (either a request or a response) and return the results as a table. The
|
|
-- table at the top of this function (<code>actions</code>) defines the name of each field, as
|
|
-- laid out in rfc2132, and the function that parses it.
|
|
--
|
|
-- In theory, this should be able to parse any valid DHCP packet.
|
|
--
|
|
--@param data The DHCP packet data. Any padding at the end of the packet will be ignored (by default,
|
|
-- DHCP packets are padded with \x00 bytes).
|
|
local function dhcp_parse(data, transaction_id)
|
|
local pos = 1
|
|
local result = {}
|
|
|
|
-- Receive the first bit and make sure we got the correct operation back
|
|
pos, result['op'], result['htype'], result['hlen'], result['hops'] = bin.unpack(">CCCC", data, pos)
|
|
if(result['op'] ~= 2) then
|
|
return false, string.format("DHCP server returned invalid reply ('op' wasn't BOOTREPLY (it was 0x%02x))", result['op'])
|
|
end
|
|
|
|
-- Confirm the transaction id
|
|
pos, result['xid'] = bin.unpack("A4", data, pos)
|
|
if(result['xid'] ~= transaction_id) then
|
|
return false, string.format("DHCP server returned invalid reply (transaction id didn't match (%s != %s))", result['xid'], transaction_id)
|
|
end
|
|
|
|
-- Unpack the secs, flags, addresses, sname, and file
|
|
pos, result['secs'], result['flags'] = bin.unpack(">SS", data, pos)
|
|
pos, result['ciaddr'] = bin.unpack("<I", data, pos)
|
|
pos, result['yiaddr'] = bin.unpack("<I", data, pos)
|
|
pos, result['siaddr'] = bin.unpack("<I", data, pos)
|
|
pos, result['giaddr'] = bin.unpack("<I", data, pos)
|
|
pos, result['chaddr'] = bin.unpack("A16", data, pos)
|
|
pos, result['sname'] = bin.unpack("A64", data, pos)
|
|
pos, result['file'] = bin.unpack("A128", data, pos)
|
|
|
|
-- Convert the addresses to strings
|
|
result['ciaddr_str'] = ipOps.fromdword(result['ciaddr'])
|
|
result['yiaddr_str'] = ipOps.fromdword(result['yiaddr'])
|
|
result['siaddr_str'] = ipOps.fromdword(result['siaddr'])
|
|
result['giaddr_str'] = ipOps.fromdword(result['giaddr'])
|
|
|
|
-- Confirm the cookie
|
|
pos, result['cookie'] = bin.unpack(">I", data, pos)
|
|
if(result['cookie'] ~= 0x63825363) then
|
|
return false, "DHCP server returned invalid reply (the magic cookie was invalid)"
|
|
end
|
|
|
|
-- Parse the options
|
|
result['options'] = {}
|
|
while true do
|
|
local option, length
|
|
pos, option, length = bin.unpack(">CC", data, pos)
|
|
|
|
-- Check for termination condition
|
|
if(option == 0xFF) then
|
|
break;
|
|
end
|
|
|
|
-- Get the action from the array, based on the code
|
|
local action = actions[option]
|
|
|
|
-- Verify we got a valid code (if we didn't, we're probably in big trouble)
|
|
local value
|
|
if(action == nil) then
|
|
stdnse.print_debug(1, "dhcp-discover: Unknown option: %d", option)
|
|
pos = pos + length
|
|
else
|
|
-- Call the function to parse the option, and insert the result into our results table
|
|
|
|
stdnse.print_debug(2, "dhcp-discover: Attempting to parse %s", action['name'])
|
|
pos, value = action['func'](data, pos, length)
|
|
|
|
if(nmap.verbosity() == 0 and action.default == false) then
|
|
stdnse.print_debug(1, "dhcp-discover: Server returned unrequested option (%s => %s)", action['name'], value)
|
|
|
|
else
|
|
if(value) then
|
|
table.insert(result['options'], {name=action['name'], value=value})
|
|
else
|
|
stdnse.print_debug(1, "dhcp-discover: Couldn't determine value for %s", action['name']);
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Handle the 'Option Overload' option specially -- if it's set, it tells us to use the file and/or sname values after we
|
|
-- run out of data.
|
|
if(option == 52) then
|
|
if(value == 1) then
|
|
data = data .. result['file']
|
|
elseif(value == 2) then
|
|
data = data .. result['sname']
|
|
elseif(value == 3) then
|
|
data = data .. result['file'] .. result['sname']
|
|
else
|
|
stdnse.print_debug(1, "dhcp-discover: Warning: 'Option Overload' gave an unsupported value: %d", value)
|
|
end
|
|
end
|
|
end
|
|
|
|
return true, result
|
|
end
|
|
|
|
---Build and send any kind of DHCP packet, and parse the response. This is the only interface
|
|
-- to the DHCP library, and should be the only one necessary.
|
|
--
|
|
-- All DHCP packet have the same structure, but different fields. It is therefore easy to build
|
|
-- any of the possible request types:
|
|
-- * DHCPDISCOVER
|
|
-- * DHCPOFFER
|
|
-- * DHCPREQUEST
|
|
-- * DHCPDECLINE
|
|
-- * DHCPACK
|
|
-- * DHCPNAK
|
|
-- * DHCPRELEASE
|
|
-- * DHCPINFORM
|
|
--
|
|
-- Although these will all build a valid packet with any option, and the default options (that can be
|
|
-- overridden with the <code>overrides</code> argument) won't necessarily work with every request
|
|
-- type. If you're going to build some DHCP code on your own, I recommend reading rfc2131.
|
|
--
|
|
--@param request_type The type of request as an integer (use the <code>request_types</code> table at the
|
|
-- top of this file).
|
|
--@param ip_address Your ip address (as a dotted-decimal string). This tells the DHCP server where to
|
|
-- send the response. Setting it to "255.255.255.255" or "0.0.0.0" is generally acceptable (if not,
|
|
-- host.ip_src can work).
|
|
--@param mac_address Your mac address (as a string up to 16 bytes) where the server will send the response. Like
|
|
-- <code>ip_address</code>, setting to the broadcast address (FF:FF:FF:FF:FF:FF) is
|
|
-- common (host.mac_addr_src works).
|
|
--@param request_options [optional] The options to request from the server, as an array of integers. For the
|
|
-- acceptable options, see the <code>actions</code> table above or have a look at rfc2132.
|
|
-- Some DHCP servers (such as my Linksys WRT54g) will ignore this list and send whichever
|
|
-- information it wants. Default: all options marked as 'default' in the <code>actions</code>
|
|
-- table above are requested (the typical interesting ones) if no verbosity is given.
|
|
-- If any level of verbosity is on, get all types.
|
|
--@param overrides [optional] A table of overrides. If a field in the table matches a field in the DHCP
|
|
-- packet (see rfc2131 section 2 for a list of possible fields), the value in the table
|
|
-- will be sent instead of the default value.
|
|
--@param lease_time [optional] The lease time used when requestint an IP. Default: 1 second.
|
|
--@return status (true or false)
|
|
--@return The parsed response, as a table.
|
|
function make_request(target, interface, request_type, ip_address, mac_address, request_options, overrides, lease_time)
|
|
-- A unique id that identifies this particular session (and lets us filter out what we don't want to see)
|
|
local transaction_id = bin.pack("<I", math.random(0, 0x7FFFFFFF))
|
|
|
|
-- Generate the packet
|
|
local status, packet = dhcp_build(request_type, bin.pack(">I", ipOps.todword(ip_address)), mac_address, request_options, overrides, lease_time, transaction_id)
|
|
if(not(status)) then
|
|
stdnse.print_debug(1, "dhcp: Couldn't build packet: " .. packet)
|
|
return false, "Couldn't build packet: " .. packet
|
|
end
|
|
|
|
-- Send the packet and get the response
|
|
local status, response = dhcp_send(interface, target, packet, transaction_id)
|
|
if(not(status)) then
|
|
stdnse.print_debug(1, "dhcp: Couldn't send packet: " .. response)
|
|
return false, "Couldn't send packet: " .. response
|
|
end
|
|
|
|
-- Parse the response
|
|
local status, parsed = dhcp_parse(response, transaction_id)
|
|
if(not(status)) then
|
|
stdnse.print_debug(1, "dhcp: Couldn't parse response: " .. parsed)
|
|
return false, "Couldn't parse response: " .. parsed
|
|
end
|
|
|
|
return true, parsed
|
|
end
|
|
|