local dns = require "dns"
local nmap = require "nmap"
local packet = require "packet"
local stdnse = require "stdnse"
local string = require "string"
local ipOps = require "ipOps"

description = [[
Detects whether the remote device has ip forwarding or "Internet connection
sharing" enabled, by sending an ICMP echo request to a given target using
the scanned host as default gateway.

The given target can be a routed or a LAN host and needs to be able to respond
to ICMP requests (ping) in order for the test to be successful. In addition,
if the given target is a routed host, the scanned host needs to have the proper
routing to reach it.

In order to use the scanned host as default gateway Nmap needs to discover
the MAC address. This requires Nmap to be run in privileged mode and the host
to be on the LAN.
]]

---
-- @usage
-- sudo nmap -sn <target> --script ip-forwarding --script-args='target=www.example.com'
--
-- @output
-- | ip-forwarding: 
-- |_  The host has ip forwarding enabled, tried ping against (www.example.com)
--
-- @args ip-forwarding.target a LAN or routed target responding to ICMP echo
--        requests (ping).
--

author = "Patrik Karlsson"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"safe", "discovery"}

local arg_target = stdnse.get_script_args(SCRIPT_NAME .. ".target")

hostrule = function(host)
	if ( not(host.mac_addr) ) then
		stdnse.print_debug( "%s: Failed to determine hosts remote MAC address", SCRIPT_NAME )
	end
	return (arg_target ~= nil and host.mac_addr ~= nil)
end


local function format_mac(mac)
	local octets

	octets = {}
	for _, v in ipairs({ string.byte(mac, 1, #mac) }) do
		octets[#octets + 1] = string.format("%02x", v)
	end

	return stdnse.strjoin(":", octets)
end

icmpEchoRequest = function(ifname, host, addr)
	local iface = nmap.get_interface_info(ifname)
	local dnet, pcap = nmap.new_dnet(), nmap.new_socket()

	pcap:set_timeout(5000)
	pcap:pcap_open(iface.device, 128, false, ("ether src %s and icmp and ( icmp[0] = 0 or icmp[0] = 5 ) and dst %s"):format(format_mac(host.mac_addr), iface.address))
	dnet:ethernet_open(iface.device)
	
	local probe = packet.Frame:new()
	probe.mac_src = iface.mac
	probe.mac_dst = host.mac_addr
	probe.ip_bin_src = packet.iptobin(iface.address)
	probe.ip_bin_dst = packet.iptobin(addr)
	probe.echo_id = 0x1234
	probe.echo_seq = 6
	probe.echo_data = "Nmap host discovery."
	probe:build_icmp_echo_request()
	probe:build_icmp_header()
	probe:build_ip_packet()
	probe:build_ether_frame()
	
	dnet:ethernet_send(probe.frame_buf)
	local status = pcap:pcap_receive()	
	dnet:ethernet_close()
	return status
end

local function fail(err) return ("\n  ERROR: %s"):format(err or "") end

action = function(host)

	local ifname = nmap.get_interface() or host.interface
	if ( not(ifname) ) then
		return fail("Failed to determine the network interface name")
	end

	local target = ipOps.ip_to_bin(arg_target)
	if ( not(target) ) then
		local status
		status, target = dns.query(arg_target, { dtype='A' })
		if ( not(status) ) then
			return fail(("Failed to lookup hostname: %s"):format(arg_target))
		end
	else
		target = arg_target
	end
	
	if ( target == host.ip ) then
		return ("\n  ERROR: Target can not be the same as the scanned host")
	end
	
	if (icmpEchoRequest(ifname, host, target)) then
		return ("\n  The host has ip forwarding enabled, tried ping against (%s)"):format(arg_target)
	end

end

