dnscache-model.lua 7.36 KB
Newer Older
1
local mymodule = {}
Mika Havela's avatar
Mika Havela committed
2 3

-- Load libraries
4 5
posix = require("posix")
modelfunctions = require("modelfunctions")
6 7 8
fs = require("acf.fs")
format = require("acf.format")
validator = require("acf.validator")
Mika Havela's avatar
Mika Havela committed
9 10

-- Set variables
11 12 13 14
local configfile = "/etc/conf.d/dnscache"
local processname = "dnscache"
local packagename = "dnscache"
local baseurl = "/etc/dnscache/"
Mika Havela's avatar
Mika Havela committed
15 16 17 18 19 20
local descr = {
}

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

Ted Trask's avatar
Ted Trask committed
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
local function validateconfig(config)
	local success = true
	if config.value.IPSEND and not validator.is_ipv4(config.value.IPSEND.value) then
		config.value.IPSEND.errtxt = "Invalid IP address"
		success = false
	end
	if config.value.CACHESIZE and not validator.is_integer(config.value.CACHESIZE.value) then
		config.value.CACHESIZE.errtxt = "Must be an integer"
		success = false
	end
	if config.value.IP and not validator.is_ipv4(config.value.IP.value) then
		config.value.IP.errtxt = "Invalid IP address"
		success = false
	end
	return success, config
end

local function validatedomain(domain)
	local success = false
40
	local domains = mymodule.getDomains()
Ted Trask's avatar
Ted Trask committed
41 42 43 44 45 46 47 48 49 50 51 52 53
	domain.value.domain.errtxt = "Invalid domain"
	for i,name in ipairs(domains.value) do
		if name == domain.value.domain.value then
			domain.value.domain.errtxt = nil
			success = true
			break
		end
	end
	for i,name in ipairs(domain.value.iplist.value) do
		if not validator.is_ipv4(name) then
			domain.value.iplist.errtxt = "Invalid IP address"
			success = false
			break
54 55
		end
	end
Ted Trask's avatar
Ted Trask committed
56
	return success, domain
57
end
Ted Trask's avatar
Ted Trask committed
58

Mika Havela's avatar
Mika Havela committed
59 60 61
-- ################################################################################
-- PUBLIC FUNCTIONS

62
function mymodule.get_startstop(self, clientdata)
63 64
        return modelfunctions.get_startstop(processname)
end
Ted Trask's avatar
Ted Trask committed
65

66
function mymodule.startstop_service(self, startstop, action)
67
        return modelfunctions.startstop_service(startstop, action)
Mika Havela's avatar
Mika Havela committed
68 69
end

70
function mymodule.getstatus()
71
	return modelfunctions.getstatus(processname, packagename, "DNS Cache Status")
Ted Trask's avatar
Ted Trask committed
72
end
Mika Havela's avatar
Mika Havela committed
73

74
function mymodule.getconfig()
75
	local conf = format.parse_ini_file(fs.read_file(configfile) or "", "") or {}
Ted Trask's avatar
Ted Trask committed
76 77 78 79 80 81 82 83 84
	local output = {}
	output.IPSEND = cfe({ value = conf.IPSEND or "", label="IP address for requests",
		descr="Use 0.0.0.0 for default address" })
	output.CACHESIZE = cfe({ value=conf.CACHESIZE or "0", label="Cache size" })
	output.IP = cfe({ value=conf.IP or "", label="IP address to listen on" })
	output.FORWARDONLY = cfe({ type="boolean", value=(conf.FORWARDONLY and conf.FORWARDONLY ~= ""),
		label="Forward only", descr="Servers are parent caches, not root servers" })
	return cfe({ type="group", value=output, label="DNS Cache Config" })
end
Mika Havela's avatar
Mika Havela committed
85

86
function mymodule.setconfig(self, config)
Ted Trask's avatar
Ted Trask committed
87
	local success, config = validateconfig(config)
Mika Havela's avatar
Mika Havela committed
88

Ted Trask's avatar
Ted Trask committed
89
	if success then
90
		local file = fs.read_file(configfile) or ""
91 92 93
		file = format.update_ini_file(file,"","IPSEND",config.value.IPSEND.value)
		file = format.update_ini_file(file,"","CACHESIZE",config.value.CACHESIZE.value)
		file = format.update_ini_file(file,"","IP",config.value.IP.value)
94
		if config.value.FORWARDONLY.value then
95
			file = format.update_ini_file(file,"","FORWARDONLY","true")
Ted Trask's avatar
Ted Trask committed
96
		else
97
			file = format.update_ini_file(file,"","FORWARDONLY","")
Ted Trask's avatar
Ted Trask committed
98
		end
Ted Trask's avatar
Ted Trask committed
99
		fs.write_file(configfile, file)
Ted Trask's avatar
Ted Trask committed
100 101 102
	else
		config.errtxt = "Failed to set config"
	end
103

Ted Trask's avatar
Ted Trask committed
104
	return config
Mika Havela's avatar
Mika Havela committed
105 106
end

107
function mymodule.getconfigfile()
108 109
	-- FIXME Validate
	return modelfunctions.getfiledetails(configfile)
Ted Trask's avatar
Ted Trask committed
110
end
Mika Havela's avatar
Mika Havela committed
111

112
function mymodule.setconfigfile(self, filedetails)
113
	-- FIXME Validate
114
	return modelfunctions.setfiledetails(self, filedetails, {configfile})
Ted Trask's avatar
Ted Trask committed
115 116
end

117
function mymodule.getIPs()
Ted Trask's avatar
Ted Trask committed
118
	local ipdir = baseurl.."ip"
Ted Trask's avatar
Ted Trask committed
119
	local iplist = cfe({ type="list", value={}, label="IP prefixes to respond to" })
Ted Trask's avatar
Ted Trask committed
120 121 122 123 124 125 126 127 128 129 130
	if fs.is_dir(ipdir) then
		for i,name in ipairs(posix.dir(ipdir)) do
			if not string.match(name, "^%.") then
				if (fs.is_file(ipdir.."/"..name)) then
					table.insert(iplist.value, name)
				end
			end
		end
	end
	return cfe({ type="group", value={iplist=iplist} })
end
Mika Havela's avatar
Mika Havela committed
131

132
function mymodule.setIPs(self, iplist)
Ted Trask's avatar
Ted Trask committed
133 134 135 136 137 138 139 140 141 142 143
	local reverseIPs = {}
	for i,name in ipairs(iplist.value.iplist.value) do
		-- check if a valid (or partial) ip
		if not validator.is_partial_ipv4(name) then
			iplist.value.iplist.errtxt = "Invalid IP address"
			iplist.errtxt = "Failed to set IP list"
			break
		end
		reverseIPs[name] = i
	end
	if not iplist.errtxt then
144
		local currentIPlist = mymodule.getIPs()
Ted Trask's avatar
Ted Trask committed
145
		for i,name in ipairs(currentIPlist.value.iplist.value) do
Ted Trask's avatar
Ted Trask committed
146 147 148 149
			if reverseIPs[name] then
				reverseIPs[name] = nil
			else
				-- need to delete the file
150
				os.remove(baseurl.."ip/"..name)
Ted Trask's avatar
Ted Trask committed
151 152 153 154
			end
		end
		for name in pairs(reverseIPs) do
			-- need to create the file
155
			fs.create_file(baseurl.."ip/"..name)
Ted Trask's avatar
Ted Trask committed
156 157 158
		end
	end
	return iplist
Mika Havela's avatar
Mika Havela committed
159 160
end

161
function mymodule.getDomains()
Ted Trask's avatar
Ted Trask committed
162 163 164 165 166 167 168 169 170 171
	local domaindir = baseurl.."servers"
	local domainlist = cfe({ type="list", value={}, label="DNS Server Domains" })
	if fs.is_dir(domaindir) then
		for i,name in ipairs(posix.dir(domaindir)) do
			if not string.match(name, "^%.") then
				if (fs.is_file(domaindir.."/"..name)) then
					table.insert(domainlist.value, name)
				end
			end
		end
172
	end
Ted Trask's avatar
Ted Trask committed
173
	return domainlist
Mika Havela's avatar
Mika Havela committed
174 175
end

176
function mymodule.getNewDomain()
Ted Trask's avatar
Ted Trask committed
177 178 179 180
	local domain = cfe({ label="Domain" })
	return cfe({ type="group", value={domain=domain} })
end

181
function mymodule.setNewDomain(self, domain)
Ted Trask's avatar
Ted Trask committed
182 183 184 185 186 187
	if "" ~= string.gsub(domain.value.domain.value..".", "%w+%.", "") then
		domain.value.domain.errtxt = "Invalid domain"
		domain.errtxt = "Failed to create domain"
	elseif fs.is_file(baseurl.."servers/"..domain.value.domain.value) then
		domain.value.domain.errtxt = "Domain already exists"
		domain.errtxt = "Failed to create domain"
Mika Havela's avatar
Mika Havela committed
188
	else
189
		fs.create_file(baseurl.."servers/"..domain.value.domain.value)
Ted Trask's avatar
Ted Trask committed
190
		domain.descr = "Created domain"
Mika Havela's avatar
Mika Havela committed
191
	end
Ted Trask's avatar
Ted Trask committed
192 193
	return domain
end
Mika Havela's avatar
Mika Havela committed
194

195
function mymodule.getDomain(self, clientdata)
196
	local getdomainname = clientdata.domain
197 198
	local domain = cfe({ value=getdomainname, label="Domain", errtxt="Invalid domain", readonly=true, seq=1 })
	local iplist = cfe({ type="list", value={}, label="List of DNS servers", seq=2 })
199
	local domains = mymodule.getDomains()
Ted Trask's avatar
Ted Trask committed
200 201 202 203
	for i,name in ipairs(domains.value) do
		if name == getdomainname then
			domain.errtxt = nil
			break
Mika Havela's avatar
Mika Havela committed
204 205
		end
	end
Ted Trask's avatar
Ted Trask committed
206
	if not domain.errtxt then
207
		local content = fs.read_file(baseurl.."servers/"..getdomainname) or ""
Ted Trask's avatar
Ted Trask committed
208 209 210
		for name in string.gmatch(content.."\n", "([^\n]+)\n") do
			table.insert(iplist.value, name)
		end
Mika Havela's avatar
Mika Havela committed
211
	end
Ted Trask's avatar
Ted Trask committed
212
	return cfe({ type="group", value={domain=domain, iplist=iplist} })
Mika Havela's avatar
Mika Havela committed
213
end
Ted Trask's avatar
Ted Trask committed
214

215
function mymodule.setDomain(self, domain)
Ted Trask's avatar
Ted Trask committed
216 217
	local success, domain = validatedomain(domain)
	if success then
Ted Trask's avatar
Ted Trask committed
218
		fs.write_file(baseurl.."servers/"..domain.value.domain.value,
Ted Trask's avatar
Ted Trask committed
219
			table.concat(domain.value.iplist.value, "\n") )
Ted Trask's avatar
Ted Trask committed
220 221
	else
		domain.errtxt = "Failed to save domain"
Ted Trask's avatar
Ted Trask committed
222 223
	end
	return domain
Mika Havela's avatar
Mika Havela committed
224 225
end

226
function mymodule.getDeleteDomain(self, clientdata)
227 228 229 230
	local domain = cfe({ value=clientdata.domain or "", label="Domain" })
	return cfe({ type="group", value={domain=domain}, label="Delete Domain" })
end

231
function mymodule.deleteDomain(self, domain)
232 233
	domain.errtxt = "Domain not deleted"
	domain.value.domain.errtxt = "Invalid domain"
234
	local domains = mymodule.getDomains()
235 236
	if domain.value.domain.value == "@" then
		domain.value.domain.errtxt = "Cannot delete root domain"
Ted Trask's avatar
Ted Trask committed
237 238
	else
		for i,name in ipairs(domains.value) do
239
			if name == domain.value.domain.value then
240
				os.remove(baseurl.."servers/"..name)
241 242
				domain.errtxt = nil
				domain.value.domain.errtxt = nil
Ted Trask's avatar
Ted Trask committed
243 244 245 246
				break
			end
		end
	end
247
	return domain
Ted Trask's avatar
Ted Trask committed
248
end
249 250

return mymodule