-- Module for blocking RRsets with owner names from given list
--
-- WARNING:
-- This module is experimental and WILL BE CHANGED IN INCOMPATIBLE WAYS
--
-- Usage:
--   1. copy module into /usr/lib/knot-resolver/kres_modules/
--   2. load module in your kresd config:
--      modules.load('experimental_filter')
--   3. specify list of blacklisted domains:
--      experimental_filter.config({'affex.org', 'criteo.com', 'eulerian.net'})
--

local ffi = require('ffi')

local M = {}
M.layer = {}

-- test if answer contains a RRset with owner name matching given domain suffix list
function policy_answer_owner_suffix(zone_list)
	local AC = require('ahocorasick')
	local tree = AC.create(zone_list)
	return function(req, _)
		local qry = req:resolved()
		local pkt = req.answer
		for _, section in ipairs({kres.section.ANSWER})
			-- do not check content of other sections
			-- (kres.section.AUTHORITY, kres.section.ADDITIONAL)
		do
			local records = pkt:section(section)
			local count = #records
			if count == 0 then
				return nil end
			for i = 1, count do
				local rr = records[i]
				-- check RR owner name but not rdata
				local match = AC.match(tree, rr.owner, false)
				if match ~= nil then
					if verbose() then
						ffi.C.kr_log_qverbose_impl(qry, 'poli',
						    'detected answer containing a RR from listed domain \'%s\'\n',
						    kres.rr2str(rr))
					end
					return true
				end
			end

		end
		-- no match, no action
		return nil
	end
end

-- replace response with empty packet with RCODE=REFUSED
function finish_refuse(req)
	-- we are deleting packet in consume() phase so other modules
	-- might have chosen some RRs from the original packet already
	-- *_selected arrays are in mempool
	-- so explicit deallocation is not necessary
	req.answ_selected.len = 0
	req.auth_selected.len = 0
	req.add_selected.len = 0

	req.options.RESOLVED = 1  -- stop iteration
	req.options.CACHED = 1  -- do not cache


	-- construct brand new answer packet
	local pkt = req.answer
	pkt:clear_payload()
	pkt:rcode(kres.rcode.REFUSED)
	pkt:ad(false)
	pkt:aa(false)
	-- pkt:begin(kres.section.ADDITIONAL)
	-- local msg = 'blocked by DNS filter'
	-- pkt:put('\11explanation\7invalid\0', 10800, pkt:qclass(), kres.type.TXT,
	-- string.char(#msg) .. msg)

	return kres.DONE
end

-- public API
--
function M.config(zone_list)
	M.filter = policy_answer_owner_suffix(policy.todnames(zone_list))
end

function M.layer.finish(state, req)
	local matched = M.filter and M.filter(req)
	if matched then
		return finish_refuse(req)
	else
		return state
	end
end

return M

