local mymodule = {}

-- Load libraries
posix = require("posix")
modelfunctions = require("modelfunctions")
fs = require("acf.fs")
format = require("acf.format")
validator = require("acf.validator")

-- Set variables
local configfile = "/etc/racoon/racoon.conf"
local configfile2 = "/etc/ipsec.conf"
local processname = "racoon"
local packagename = "ipsec-tools"
local baseurl = "/etc/racoon/"

local descr = {
	state={
	['9']="Established",
	},
	side={
	['R']="We are 'Responder'.",
	['I']="We 'Initiated' this phase1",
	},
	exchange={
	['M']="Main mode",
	['A']="Agressive mode",
	['B']="Basic mode",
	},

}

-- ################################################################################
-- LOCAL FUNCTIONS

local function racoonctl_table()
	local output = {}
	local value = modelfunctions.run_executable({"ip", "xfrm", "state"})
	-- Get rid of all lines that don't start with "src"
	local phase2details = string.gsub(value, "\n[^s][^\n]*", "")
	
	value = modelfunctions.run_executable({"racoonctl", "-lll", "show-sa", "isakmp"})
 	for i,line in pairs(format.string_to_table(value,"\n")) do
		if not ((string.find(line,"^Source")) or (#line == 0)) then
			entry={}
			local variable=format.string_to_table(line,"%s+")
			entry['Source']=cfe({
				label="Source",
				value=variable[1],
				})
			entry['Destination']=cfe({
				label="Destination",
				value=variable[2],
				})
			entry['Cookies']=cfe({
				label="Cookies",
				value=variable[3],
				})
			entry['St']=cfe({
				label="State",
				value=variable[4],
				descr=descr.state[variable[4]],
				})
			entry['S']=cfe({
				label="Side",
				value=variable[5],
				descr=descr.side[variable[5]],
				})
			entry['V']=cfe({
				label="Version",
				value=variable[6],
				})
			entry['E']=cfe({
				label="Exchange",
				value=variable[7],
				descr=descr.exchange[variable[7]],
				})
			entry['Created']=cfe({
				label="Created",
				value=(variable[8] or "") .. " " .. (variable[9] or ""),
				})

			local dst = string.match(variable[2],"^(.*)%.")	-- Removes the portnumber
			local incoming = {}
			local outgoing = {}
			for l in string.gmatch(phase2details, "src [^\n]* "..dst.."\n") do
				incoming[#incoming+1] = l
			end
			for l in string.gmatch(phase2details, "src "..dst.." [^\n]*\n") do
				outgoing[#outgoing+1] = l
			end
			local phase2s = {{label="Outgoing", value=table.concat(outgoing)}, {label="Incoming", value=table.concat(incoming)}}
			entry['Phase2']=cfe({
				label="Phase2",
				value=variable[10],
				option=phase2s,
				})

			entry['Phase2details']=cfe({
				label="Phase2details",
				value=phase2s[1]['value'] .. phase2s[2]['value']
				})
			output[#output + 1] = entry
		end
	end
	return output
end

-- ################################################################################
-- PUBLIC FUNCTIONS

function mymodule.get_startstop(self, clientdata)
        return modelfunctions.get_startstop(processname)
end
	
function mymodule.startstop_service(self, startstop, action)
        return modelfunctions.startstop_service(startstop, action)
end

function mymodule.getstatus()
	return modelfunctions.getstatus(processname, packagename, "Racoon Status")
end

function mymodule.getstatusdetails()
	local status = {}
	status.show_isakmp = cfe({ type="list", value=racoonctl_table(), label="Tunnels" })
	status.ip_xfrm_policy = cfe({ type="longtext", label="ip xfrm policy" })
	status.ip_xfrm_policy.value, status.ip_xfrm_policy.errtxt = modelfunctions.run_executable({"ip", "xfrm", "policy"})

	return cfe({ type="group", value=status, label="Racoon Status Details" })
end

function mymodule.get_racoonfiledetails()
	return modelfunctions.getfiledetails(configfile)
end

function mymodule.update_racoonfiledetails(self, filedetails)
	return modelfunctions.setfiledetails(self, filedetails, {configfile})
end

function mymodule.get_ipsecfiledetails()
	return modelfunctions.getfiledetails(configfile2)
end

function mymodule.update_ipsecfiledetails(self, filedetails)
	return modelfunctions.setfiledetails(self, filedetails, {configfile2})
end

function mymodule.list_certs()
	local list = {}
	for file in fs.find(".*%.pem", baseurl) do
		list[#list+1] = posix.basename(file)
	end
	return cfe({ type="list", value=list, label="IPSEC Certificates" })
end

function mymodule.get_delete_cert(self, clientdata)
	local retval = {}
	retval.cert = cfe({ value=clientdata.cert or "", label="IPSEC Certificate" })
	return cfe({ type="group", value=retval, label="Delete Certificate" })
end

function mymodule.delete_cert(self, delcert)
	local list = mymodule.list_certs()
	delcert.value.cert.errtxt = "Invalid cert name"
	delcert.errtxt = "Failed to delete Certificate"
	for i,cert in ipairs(list.value) do
		if cert == delcert.value.cert.value then
			os.remove(baseurl..cert)
			delcert.value.cert.errtxt = nil
			delcert.errtxt = nil
			break
		end
	end
	return delcert
end

function mymodule.new_upload_cert()
	local value = {}
	value.cert = cfe({ type="raw", value=0, label="Certificate", descr='File must be a password protected ".pfx" file', seq=1 })
	value.password = cfe({ type="password", label="Certificate Password", seq=2 })
	value.name = cfe({ label="Certificate Local Name", seq=3 })
	return cfe({ type="group", value=value })
end

function mymodule.upload_cert(self, newcert)
	local success = true
	-- Trying to upload a cert/key
	-- The way haserl works, cert contains the temporary file name
	-- First, get the cert
	local cmd, f, cmdresult, errtxt
	if validator.is_valid_filename(newcert.value.cert.value, "/tmp/") and fs.is_file(newcert.value.cert.value) then
		cmdresult, errtxt = modelfunctions.run_executable({"openssl", "pkcs12", "-in", newcert.value.cert.value, "-out", newcert.value.cert.value.."cert.pem", "-password", "pass:"..newcert.value.password.value, "-nokeys", "-clcerts"}, true)
		local filestats = posix.stat(newcert.value.cert.value.."cert.pem")
		if errtxt or not filestats or filestats.size == 0 then
			newcert.value.cert.errtxt = "Could not open certificate\n"..(errtxt or cmdresult)
			success = false
		end
	else
		newcert.value.cert.errtxt = "Invalid certificate"
		success = false
	end

	-- Now, get the key and the ca certs
	if success then
		cmdresult, errtxt = modelfunctions.run_executable({"openssl", "pkcs12", "-in", newcert.value.cert.value, "-out", newcert.value.cert.value.."key.pem", "-password", "pass:"..newcert.value.password.value, "-nocerts", "-nodes"}, true)
		filestats = posix.stat(newcert.value.cert.value.."key.pem")
		if errtxt or not filestats or filestats.size == 0 then
			newcert.value.cert.errtxt = "Could not find key\n"..(errtxt or cmdresult)
			success = false
		end

		cmdresult, errtxt = modelfunctions.run_executable({"openssl", "pkcs12", "-in", newcert.value.cert.value, "-out", newcert.value.cert.value.."ca.pem", "-password", "pass:"..newcert.value.password.value, "-nokeys", "-cacerts"}, true)
		filestats = posix.stat(newcert.value.cert.value.."ca.pem")
		if errtxt or not filestats or filestats.size == 0 then
			newcert.value.cert.errtxt = "Could not find CA certs\n"..(errtxt or cmdresult)
			success = false
		end
	end

	if newcert.value.name.value == "" then
		newcert.value.name.errtxt = "Cannot be blank"
		success = false
	elseif posix.stat(baseurl..newcert.value.name.value.."-cert.pem") or posix.stat(baseurl..newcert.value.name.value.."-key.pem") or posix.stat(baseurl..newcert.value.name.value.."-ca.pem") then
		newcert.value.name.errtxt = "Certificate of this name already exists"
		success = false
	end

	if success then
		if not posix.stat(baseurl) then
			posix.mkdir(baseurl)
		end
		-- copy the keys
		fs.move_file(newcert.value.cert.value.."cert.pem", baseurl..newcert.value.name.value.."-cert.pem")
		fs.move_file(newcert.value.cert.value.."key.pem", baseurl..newcert.value.name.value.."-key.pem")
		fs.move_file(newcert.value.cert.value.."ca.pem", baseurl..newcert.value.name.value.."-ca.pem")
		posix.chmod(baseurl..newcert.value.name.value.."-key.pem", "rw-------")
	else
		newcert.errtxt = "Failed to upload certificate"
	end

	-- Delete the temporary files
	if validator.is_valid_filename(newcert.value.cert.value, "/tmp/") and fs.is_file(newcert.value.cert.value) then
		os.remove(newcert.value.cert.value.."cert.pem")
		os.remove(newcert.value.cert.value.."key.pem")
		os.remove(newcert.value.cert.value.."ca.pem")
	end

	return newcert
end

function mymodule.get_view_cert(self, clientdata)
	local retval = {}
	retval.cert = cfe({ value=clientdata.cert or "", label="IPSEC Certificate" })
	return cfe({ type="group", value=retval, label="View Certificate" })
end

mymodule.view_cert = function(self, viewcert)
	local list = mymodule.list_certs()
	viewcert.value.cert.errtxt = "Invalid cert name"
	viewcert.errtxt = "Failed to find cert"
	for i,cert in ipairs(list.value) do
		if cert == viewcert.value.cert.value then
			viewcert.value.result = cfe({ type="longtext", label="Certificate", readonly=true })
			viewcert.value.result.value, viewcert.value.result.errtxt = modelfunctions.run_executable({"openssl", "x509", "-in", baseurl..cert, "-noout", "-text"})
			viewcert.errtxt = viewcert.value.result.errtxt
			viewcert.value.cert.errtxt = nil
			break
		end
	end
	return viewcert
end

return mymodule
