#!/usr/bin/lua

--! LibreMesh
--! Copyright (C) 2019  Gioacchino Mazzurco <gio@altermundi.net>
--!
--! This program is free software: you can redistribute it and/or modify
--! it under the terms of the GNU Affero General Public License as
--! published by the Free Software Foundation, either version 3 of the
--! License, or (at your option) any later version.
--!
--! This program is distributed in the hope that it will be useful,
--! but WITHOUT ANY WARRANTY; without even the implied warranty of
--! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
--! GNU Affero General Public License for more details.
--!
--! You should have received a copy of the GNU Affero General Public License
--! along with this program.  If not, see <http://www.gnu.org/licenses/>.

local JSON = require("luci.jsonc")
local uci = require("uci")
local fs = require("nixio.fs")

function string:split(sep)
	local ret = {}
	for token in self:gmatch("[^"..sep.."]+") do
		table.insert(ret, token)
	end
	return ret
end

function add_dhcp_leases_from_files()
	local leasesTable = {}
	local hostTable = {}

	for cFpath in fs.glob("/etc/dnsmasq.d/*.conf") do
		for line in io.lines(cFpath) do
			local lMac, lIp, lName =
					line:match("^dhcp%-host=(%x%x:%x%x:%x%x:%x%x:%x%x:%x%x),(%d+%.%d+%.%d+%.%d+),(.+),infinite$")
			if lMac ~= nil then
				leasesTable[lIp] = { hostname=lName, mac=lMac }
				hostTable[lIp.." "..lName] = true
			end
		end
	end

	for line in io.lines("/tmp/dhcp.leases") do
		_, client_mac, client_ip, client_hostname, client_id = unpack(line:split(" "))
		-- client_ip will be nil in duid lines
		-- https://dnsmasq-discuss.thekelleys.org.narkive.com/ia9YNLmE/dnsmasq-leases-file-format-specification
		if client_ip and type(client_hostname) == "string" then
			-- The dhcp.leases file can contain lines with hostname "*"
			-- avoiding to add these in hostTable
			if client_hostname:len() > 1 then
				hostTable[client_ip.." "..client_hostname] = true
			end
			-- avoid adding IPv6 as collisions are very unlikely
			-- if we were going to include it, we should remember that
			-- IPv6 lines have a number here instead of the MAC address
			-- https://github.com/libremesh/lime-packages/issues/969
			if client_ip:match('^%d+%.%d+%.%d+%.%d+$') then
				leasesTable[client_ip] = { hostname=client_hostname, mac=client_mac }
			end
		end
	end

	local cStdin = io.popen("shared-state insert dnsmasq-leases", "w")
	cStdin:write(JSON.stringify(leasesTable))
	cStdin:close()

	local cStdin = io.popen("shared-state insert dnsmasq-hosts", "w")
	cStdin:write(JSON.stringify(hostTable))
	cStdin:close()
end

function add_lease(client_mac, client_ip, client_hostname)
	if type(client_hostname) == "string" then
		-- avoid adding IPv6 as collisions are very unlikely
		-- if we were going to include it, we should prepend "id:" to the duid received as first argument
		-- see https://github.com/libremesh/lime-packages/pull/975#issuecomment-1413984349
		if client_ip:match('^%d+%.%d+%.%d+%.%d+$') then
			local leaseData = {}
			leaseData[client_ip] = { hostname=client_hostname, mac=client_mac }
			local cStdin = io.popen("shared-state insert dnsmasq-leases", "w")
			cStdin:write(JSON.stringify(leaseData))
			cStdin:close()
		end

		if client_hostname:len() > 1 then
			local hostTable = {}
			hostTable[client_ip.." "..client_hostname] = true
			local cStdin = io.popen("shared-state insert dnsmasq-hosts", "w")
			cStdin:write(JSON.stringify(hostTable))
			cStdin:close()
		end
	end
end

function add_own()
	local leasesTable = {}

	local uci_conf = uci.cursor()
	local own_ipv4 = uci_conf:get("network", "lan", "ipaddr")
	local own_ipv6 = uci_conf:get("network", "lan", "ip6addr")
	uci_conf = nil
	if own_ipv6 then own_ipv6 = own_ipv6:gsub("/.*$", "") end

	local own_hostname = io.input("/proc/sys/kernel/hostname"):read("*line")
	local own_mac = io.input("/sys/class/net/br-lan/address"):read("*line")

	add_lease(own_mac, own_ipv4, own_hostname, "*")
	add_lease(own_mac, own_ipv6, own_hostname, "*")
end

function del_lease(client_ip)
	local keysArr = {}
	table.insert(keysArr, client_ip)
	local cStdin = io.popen("shared-state remove dnsmasq-leases", "w")
	cStdin:write(JSON.stringify(keysArr))
	cStdin:close()

	local hostArr = {}
	table.insert(hostArr, client_ip.." "..client_hostname)
	local cStdin = io.popen("shared-state remove dnsmasq-hosts", "w")
	cStdin:write(JSON.stringify(hostArr))
	cStdin:close()
end

local command = arg[1]
local client_mac = arg[2]
local client_ip = arg[3]

local client_hostname
if (arg[4] and (arg[4]:len() > 0)) then client_hostname = arg[4]
else client_hostname = "" end

local client_id = os.getenv("DNSMASQ_CLIENT_ID")
if ((not client_id) or (client_id:len() <= 0)) then client_id = "*" end

if command == "add" then
	add_lease(client_mac, client_ip, client_hostname, client_id)
	add_own()

elseif command == "del" then
	del_lease(client_ip)
	add_own()

elseif command == nil or command:match("^%s*$") then
	add_dhcp_leases_from_files()
	add_own()
end
