local nmap = require "nmap"
local shortport = require "shortport"
local stdnse = require "stdnse"
local table = require "table"

description = [[
Tests a list of known ICAP service names and prints information about
any it detects. The Internet Content Adaptation Protocol (ICAP) is
used to extend transparent proxy servers and is generally used for
content filtering and antivirus scanning.
]]

---
-- @usage
-- nmap -p 1344 <ip> --script icap-info
--
-- @output
-- PORT     STATE SERVICE
-- 1344/tcp open  unknown
-- | icap-info: 
-- |   /avscan
-- |     Service: C-ICAP/0.1.6 server - Clamav/Antivirus service
-- |     ISTag: CI0001-000-0973-6314940
-- |   /echo
-- |     Service: C-ICAP/0.1.6 server - Echo demo service
-- |     ISTag: CI0001-XXXXXXXXX
-- |   /srv_clamav
-- |     Service: C-ICAP/0.1.6 server - Clamav/Antivirus service
-- |     ISTag: CI0001-000-0973-6314940
-- |   /url_check
-- |     Service: C-ICAP/0.1.6 server - Url_Check demo service
-- |_    ISTag: CI0001-XXXXXXXXX
--
--

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


portrule = shortport.port_or_service(1344, "icap")

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

local function parseResponse(resp)
	if ( not(resp) ) then
		return
	end
	
	local resp_p = { header = {}, rawheader = {} }
	local resp_tbl = stdnse.strsplit("\r?\n", resp)

	if ( not(resp_tbl) or #resp_tbl == 0 ) then
		stdnse.print_debug(2, "Received an invalid response from server")
		return
	end

	resp_p.status = tonumber(resp_tbl[1]:match("^ICAP/1%.0 (%d*) .*$"))
	resp_p['status-line'] = resp_tbl[1]
	
	for i=2, #resp_tbl do
		local key, val = resp_tbl[i]:match("^([^:]*):%s*(.*)$")
		if ( not(key) or not(val) ) then
			stdnse.print_debug(2, "Failed to parse header: %s", resp_tbl[i])
		else
			resp_p.header[key:lower()] = val
		end
		table.insert(resp_p.rawheader, resp_tbl[i])
	end
	return resp_p
end

action = function(host, port)
	
	local services = {"/avscan", "/echo", "/srv_clamav", "/url_check", "/nmap" }
	local headers = {"Service", "ISTag"}
	local probe = {
		"OPTIONS icap://%s%s ICAP/1.0",
		"Host: %s",
		"User-Agent: nmap icap-client/0.01",
		"Encapsulated: null-body=0"
	}
	local hostname = stdnse.get_hostname(host)
	local result = {}
	
	for _, service in ipairs(services) do
		local socket = nmap.new_socket()
		socket:set_timeout(5000)
		if ( not(socket:connect(host, port)) ) then
			return fail("Failed to connect to server")
		end
		
		local request = (stdnse.strjoin("\r\n", probe) .. "\r\n\r\n"):format(hostname, service, hostname)
	
		if ( not(socket:send(request)) ) then
			socket:close()
			return fail("Failed to send request to server")
		end
		
		local status, resp = socket:receive_buf("\r\n\r\n", false)
		if ( not(status) ) then
			return fail("Failed to receive response from server")
		end

		local resp_p = parseResponse(resp)
		if ( resp_p and resp_p.status == 200 ) then
			local result_part = { name = service }
			for _, h in ipairs(headers) do
				if ( resp_p.header[h:lower()] ) then
					table.insert(result_part, ("%s: %s"):format(h, resp_p.header[h:lower()]))
				end
			end
			table.insert(result, result_part)
		end
	end
	return stdnse.format_output(true, result)
end
