#!/usr/bin/lua

require("luci.http")
require("luci.util")
require("luci.model.uci")

-- Init state session
local uci = luci.model.uci.cursor_state()


function main(argv)
	local cmd = argv[1]
	local arg = argv[2]
	
	if cmd == "status" then
		if not arg then
			os.exit(1)
		end
		
		if iswhitelisted(arg) then
			print("whitelisted")
			os.exit(0)
		end
		
		if haslease(arg) then
			print("lease")
			os.exit(0)
		end		
		
		print("unknown")
		os.exit(0)
	elseif cmd == "add" then
		if not arg then
			os.exit(1)
		end
		
		if not haslease(arg) then
			add_lease(arg)
		else
			print("already leased!")
			os.exit(2)
		end
		os.exit(0)
	elseif cmd == "remove" then
		if not arg then
			os.exit(1)
		end
		
		remove_lease(arg)
		os.exit(0)		
	elseif cmd == "sync" then
		sync()
		os.exit(0)
	else
		print("Usage: " .. argv[0] .. " <status|add|remove|sync> [MAC]")
		os.exit(1)	
	end
end

-- Add a lease to state and invoke add_rule
function add_lease(mac)
	uci:section("luci_splash", "lease", nil, {
		mac = mac,
		start = os.time()
	})
	add_rule(mac)
	
	uci:save("luci_splash")
end


-- Remove a lease from state and invoke remove_rule
function remove_lease(mac)
	mac = mac:lower()
	local del = {}

	uci:foreach("luci_splash", "lease",
		function (section)
			if section.mac:lower() == mac then
				table.insert(del, section[".name"])
			end
		end)
		
	for i,j in ipairs(del) do
		remove_rule(j)
		uci:delete("luci_splash", j)
	end
		
	uci:save("luci_splash")
end


-- Add an iptables rule
function add_rule(mac)
	return os.execute("iptables -t nat -I luci_splash_leases -m mac --mac-source '"..mac.."' -j RETURN")
end


-- Remove an iptables rule
function remove_rule(mac)
	return os.execute("iptables -t nat -D luci_splash_leases -m mac --mac-source '"..mac.."' -j RETURN")
end


-- Check whether a MAC-Address is listed in the lease state list
function haslease(mac)
	mac = mac:lower()
	local stat = false
	
	uci:foreach("luci_splash", "lease",
		function (section)
			if section.mac:lower() == mac then
				stat = true
				return
			end
		end)
	
	return stat
end


-- Check whether a MAC-Address is whitelisted
function iswhitelisted(mac)
	mac = mac:lower()
	
	uci:foreach("luci_splash", "whitelist",
		function (section)
			if section.mac:lower() == mac then
				stat = true
				return
			end
		end)
	
	return false
end


-- Returns a list of MAC-Addresses for which a rule is existing
function listrules()
	local cmd = "iptables -t nat -L luci_splash_leases | grep RETURN |"
	cmd = cmd .. "egrep -io [0-9a-f]+:[0-9a-f]+:[0-9a-f]+:[0-9a-f]+:[0-9a-f]+:[0-9a-f]+"
	return luci.util.split(luci.util.exec(cmd))
end


-- Synchronise leases, remove abandoned rules
function sync()
	local written = {}
	local time = os.time()
	
	-- Current leases in state files
	local leases = uci:get_all("luci_splash")
	
	-- Convert leasetime to seconds
	local leasetime = tonumber(uci:get("luci_splash", "general", "leasetime")) * 3600
	
	-- Clean state file
	uci:load("luci_splash")
	uci:revert("luci_splash")
	
	
	-- For all leases
	for k, v in pairs(leases) do
		if v[".type"] == "lease" then
			if os.difftime(time, tonumber(v.start)) > leasetime then
				-- Remove expired
				remove_rule(v.mac)
			else
				-- Rewrite state
				uci:section("luci_splash", "lease", nil, {		
					mac = v.mac,
					start = v.start
				})
				written[v.mac:lower()] = 1
			end
		end
	end
	
	
	-- Delete rules without state
	for i, r in ipairs(listrules()) do
		if #r > 0 and not written[r:lower()] then
			remove_rule(r)
		end
	end
	
	uci:save("luci_splash")
end

main(arg)