summaryrefslogtreecommitdiffstats
path: root/nsd-model.lua
diff options
context:
space:
mode:
authorTed Trask <ttrask01@yahoo.com>2017-04-11 15:07:33 +0000
committerTed Trask <ttrask01@yahoo.com>2017-04-11 15:07:33 +0000
commitf653fac935d599c5453e86c094502f233abe9180 (patch)
treee58db38aef9899d5359b4b69a6419408d95c564c /nsd-model.lua
downloadacf-nsd-master.tar.bz2
acf-nsd-master.tar.xz
Initial commitHEADv0.0.1master
Support for expert editing of files and user/role permissions
Diffstat (limited to 'nsd-model.lua')
-rw-r--r--nsd-model.lua396
1 files changed, 396 insertions, 0 deletions
diff --git a/nsd-model.lua b/nsd-model.lua
new file mode 100644
index 0000000..79b5d8d
--- /dev/null
+++ b/nsd-model.lua
@@ -0,0 +1,396 @@
+local mymodule = {}
+
+-- Load libraries
+modelfunctions = require("modelfunctions")
+fs = require("acf.fs")
+format = require("acf.format")
+validator = require("acf.validator")
+authenticator = require("authenticator")
+roles = require("roles")
+posix = require("posix")
+
+-- Set variables
+local allzonefiles = {}
+local cachedzonefiles = {}
+local cached_user
+local packagename = "nsd"
+local processname = "nsd"
+local configfile = "/etc/nsd/nsd.conf"
+local configdir = "/etc/nsd/"
+local descr = {
+ prefix={
+ ['.']="Name server for your domain (NS + A + SOA)",
+ ['&']="Delegate subdomain (NS + A)",
+ ['=']="Host and reverse record (A + PTR)",
+ ['+']="Host record (A, no PTR)",
+ ['@']="Mail exchanger (MX)",
+ ["'"]="Text record (TXT)",
+ ['^']="Reverse record (PTR)",
+ ['C']="Canonical name (CNAME)",
+ ['Z']="SOA record (SOA)",
+ [':']="Generic record",
+ ['%']="Client location",
+ ['S']="Service location (SRV)",
+ ['N']="Naming authority pointer (NAPTR)",
+ },
+ fieldlabels={
+ ['.']={"Domain", "IP address", "Name server", "Time to live", "Timestamp", "Location", },
+ ['&']={"Domain", "IP address", "Name server", "Time to live", "Timestamp", "Location", },
+ ['=']={"Host", "IP address", "Time to live", "Timestamp", "Location", },
+ ['+']={"Host", "IP address", "Time to live", "Timestamp", "Location", },
+ ['@']={"Domain", "IP address", "Mail exchanger", "Distance", "Time to live", "Timestamp", "Location", },
+ ['\'']={"Domain", "Text Record", "Time to live", "Timestamp", "Location", },
+ ['^']={"PTR", "Domain name", "Time to live", "Timestamp", "Location", },
+ ['C']={"Domain", "Canonical name", "Time to live", "Timestamp", "Location", },
+ ['Z']={"Domain", "Primary name server", "Contact address", "Serial number", "Refresh time", "Retry time", "Expire time", "Minimum time", "Time to live", "Timestamp", "Location",},
+ [':']={"Domain", "Record type", "Record data", "Time to live", "Timestamp", "Location", },
+ ['%']={"Location", "IP prefix", },
+ ['S']={"Domain Service", "IP address", "Server", "Port", "Priority", "Weight", "Time to live", "Timestamp", },
+ ['N']={"Domain", "Order", "Preference", "Flags", "Service", "Regular expression", "Replacement", "Time to live", "Timestamp", },
+ },
+}
+
+-- ################################################################################
+-- LOCAL FUNCTIONS
+
+-- Function to recursively inserts all file details in a dir into an array
+local function recursedir(path, filearray)
+ filearray = filearray or {}
+ local k,v
+ for k,v in pairs(posix.dir(path) or {}) do
+ -- Ignore files that begins with a '.'
+ if not string.match(v, "^%.") then
+ local f = path .. v
+ local details = posix.stat(f)
+ -- If subfolder exists, list files in this subfolder
+ if details.type == "directory" then
+ recursedir(f.."/", filearray)
+ elseif details.type == "regular" then
+ details.filename = f
+ table.insert(filearray, details)
+ end
+ end
+ end
+ return filearray
+end
+
+local is_valid_filename = function(filename)
+ local dirname = posix.dirname(filename or "").."/"
+ return validator.is_valid_filename(filename) and string.match(dirname, "^"..format.escapemagiccharacters(configdir)) and not string.match(dirname, "%.%.")
+end
+
+local function getallowedlist(self, userid)
+ local allowedlist = {}
+ local auth = authenticator.get_subauth(self)
+ local entry = auth.read_entry(self, authenticator.usertable, self.conf.prefix..self.conf.controller, userid) or ""
+ for x in string.gmatch(entry, "([^,]+),?") do allowedlist[#allowedlist + 1] = x end
+
+ -- also check to see if there are allowed files for this user's roles
+ local userinfo = authenticator.get_userinfo(self, userid)
+ -- add in the guest role
+ userinfo.roles[#userinfo.roles + 1] = roles.guest_role
+ for i,role in ipairs(userinfo.roles) do
+ local entry = auth.read_entry(self, authenticator.roletable, self.conf.prefix..self.conf.controller, role) or ""
+ for x in string.gmatch(entry, "([^,]+),?") do allowedlist[#allowedlist + 1] = x end
+ end
+ return allowedlist
+end
+
+-- Feed the allzonefiles table with list of all zonefiles in the config
+local function listzonefiles()
+ if #allzonefiles > 0 then
+ return allzonefiles
+ end
+
+ -- Parse the config
+ -- TODO - handle include statements
+ -- TODO - should properly parse the whole file, not just look for lines
+ local file = io.open(configfile)
+ local zonesdir
+ local zonefiles = {}
+ for line in file:lines() do
+ if string.find(line, "^%s*zonesdir:") then
+ zonesdir = string.match(line, "^%s*zonesdir:%s*\"(.*)\"")
+ elseif string.find(line, "^%s*zonefile:") then
+ local z = string.match(line, "^%s*zonefile:%s*\"(.*)\"")
+ zonefiles[#zonefiles+1] = z
+ end
+ end
+ file:close()
+ if not zonesdir or zonesdir == "" then
+ zonesdir = configdir
+ elseif not string.find(zonesdir, "/$") then
+ zonesdir = zonesdir.."/"
+ end
+ for i,z in ipairs(zonefiles) do
+ allzonefiles[#allzonefiles+1] = zonesdir..z
+ end
+ return allzonefiles
+end
+
+-- Feed the cachedzonefiles table with list of all zonefiles that are available and allowed
+-- Default to allowing all files if no userid or allowed list
+local function searchforzonefiles(self, userid)
+ if #cachedzonefiles > 0 and cached_user == userid then
+ return cachedzonefiles
+ end
+
+ allzonefiles = listzonefiles(configdir)
+ local allowedlist = getallowedlist(self, userid)
+ if allowedlist and #allowedlist > 0 then
+ local reverseallowed = {}
+ for x,name in ipairs(allowedlist) do reverseallowed[name] = x end
+ for k,v in pairs(allzonefiles) do
+ if reverseallowed[v] then
+ table.insert(cachedzonefiles, v)
+ end
+ end
+ else
+ cachedzonefiles = allzonefiles
+ end
+
+ cached_user = userid
+ table.sort(cachedzonefiles)
+ return cachedzonefiles
+end
+
+local function is_valid_zonefile(path)
+ for k,v in pairs(cachedzonefiles) do
+ if (v == path) then
+ return true
+ end
+ end
+ return false, "Not a valid filename!"
+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, "NSD Status")
+end
+
+function mymodule.list_files(self)
+ local filearray = recursedir(configdir)
+ table.sort(filearray, function(a,b) return a.filename < b.filename end)
+ return cfe({ type="structure", value=filearray, label="List of NSD files" })
+end
+
+function mymodule.get_file(self, clientdata)
+ local retval = cfe({ type="group", value={ filename=cfe({}) } })
+ self.handle_clientdata(retval, clientdata)
+ return modelfunctions.getfiledetails(retval.value.filename.value, is_valid_filename)
+end
+
+function mymodule.update_file(self, filedetails)
+ return modelfunctions.setfiledetails(self, filedetails, is_valid_filename)
+end
+
+function mymodule.get_new_file(self, clientdata)
+ local retval = cfe({ type="group", value={}, label="NSD File" })
+ retval.value.filename = cfe({ label="File Name", descr="Must be in "..configdir })
+ self.handle_clientdata(retval, clientdata)
+ return retval
+end
+
+function mymodule.create_file(self, filedetails)
+ local success = true
+ local path = string.match(filedetails.value.filename.value, "^%s*(.*%S)%s*$") or ""
+ if not string.find(path, "/") then
+ path = configdir..path
+ end
+
+ if not is_valid_filename(path) then
+ success = false
+ filedetails.value.filename.errtxt = "Invalid filename"
+ else
+ if not fs.is_dir(configdir) then fs.create_directory(configdir) end
+ if posix.stat(path) then
+ success = false
+ filedetails.value.filename.errtxt = "Filename already exists"
+ end
+ end
+
+ if success then
+ fs.create_file(path)
+ else
+ filedetails.errtxt = "Failed to Create File"
+ end
+
+ return filedetails
+end
+
+function mymodule.get_delete_file(self, clientdata)
+ local retval = cfe({ type="group", value={}, label="Delete NSD File" })
+ retval.value.filename = cfe({ label="File Name" })
+ self.handle_clientdata(retval, clientdata)
+ return retval
+end
+
+function mymodule.delete_file(self, delfile)
+ delfile.errtxt = "Failed to delete NSD File - invalid filename"
+ for i,file in ipairs(mymodule.list_files().value) do
+ if delfile.value.filename.value == file.filename then
+ delfile.errtxt = nil
+ os.remove(delfile.value.filename.value)
+ break
+ end
+ end
+
+ return delfile
+end
+
+function mymodule.getzonefilelist(self, clientdata, userid)
+ cachedzonefiles = searchforzonefiles(self, userid)
+ local listed_files = {}
+ for i,name in pairs(cachedzonefiles) do
+ local filedetails = posix.stat(name) or {}
+ filedetails.filename = name
+ table.insert(listed_files, filedetails)
+ end
+
+ return cfe({ type="structure", value=listed_files, label="Zone files" })
+end
+
+function mymodule.get_zonefile(self, clientdata, userid)
+ cachedzonefiles = searchforzonefiles(self, userid)
+ local retval = cfe({ type="group", value={ filename=cfe({}) } })
+ self.handle_clientdata(retval, clientdata)
+ return modelfunctions.getfiledetails(retval.value.filename.value, is_valid_zonefile)
+end
+
+function mymodule.set_zonefile (self, filedetails, userid)
+ cachedzonefiles = searchforzonefiles(self, userid)
+ return modelfunctions.setfiledetails(self, filedetails, is_valid_zonefile)
+end
+
+function mymodule.getpermissionslist(self)
+ local auth = authenticator.get_subauth(self)
+ local users = authenticator.list_users(self)
+ local userlist = {}
+ for i,user in ipairs(users) do
+ local allowedlist = {}
+ local entry = auth.read_entry(self, authenticator.usertable, self.conf.prefix..self.conf.controller, user) or ""
+ for x in string.gmatch(entry, "([^,]+),?") do allowedlist[#allowedlist + 1] = x end
+ userlist[#userlist + 1] = {id=user, allowed=allowedlist}
+ end
+ -- Need to check for roles as well as users
+ local rolelist = {}
+ local rols = roles.list_all_roles(self)
+ for i,role in ipairs(rols) do
+ local allowedlist = {}
+ local entry = auth.read_entry(self, authenticator.roletable, self.conf.prefix..self.conf.controller, role) or ""
+ for x in string.gmatch(entry, "([^,]+),?") do allowedlist[#allowedlist + 1] = x end
+ rolelist[#rolelist + 1] = {id=role, allowed=allowedlist}
+ end
+ table.sort(userlist, function(a,b) return a.id < b.id end)
+ return cfe({ type="structure", value={user=userlist, role=rolelist}, label="NSD Permissions" })
+end
+
+local function validateuserpermissions(self, userpermissions)
+ local success = true
+ local userinfo = authenticator.get_userinfo(self, userpermissions.value.userid.value)
+ if not userinfo then
+ userpermissions.value.userid.errtxt = "Invalid user"
+ success = false
+ end
+ success = modelfunctions.validatemulti(userpermissions.value.allowed) and success
+ return success, userpermissions
+end
+
+local function validaterolepermissions(self, rolepermissions)
+ local success = false
+ rolepermissions.value.role.errtxt = "Invalid role"
+ local rols = roles.list_all_roles(self)
+ for i,role in ipairs(rols) do
+ if rolepermissions.value.role.value == role then
+ rolepermissions.value.role.errtxt = nil
+ success = true
+ break
+ end
+ end
+ success = modelfunctions.validatemulti(rolepermissions.value.allowed) and success
+ return success, rolepermissions
+end
+
+function mymodule.getuserpermissions(self, clientdata)
+ local retval = cfe({ type="group", value={}, label="NSD User Permissions" })
+ retval.value.userid = cfe({ label="User Name", seq=0 })
+ self.handle_clientdata(retval, clientdata)
+
+ local allzonefiles = listzonefiles(configdir)
+ table.sort(allzonefiles)
+ retval.value.allowed = cfe({ type="multi", value={}, label="NSD Permissions", option=allzonefiles, descr="If no permissions are defined, then all are allowed", seq=1 })
+ if #allzonefiles == 0 then
+ retval.value.allowed.errtxt = "No zones defined"
+ end
+
+ local userid = retval.value.userid.value
+ if userid ~= "" then
+ retval.value.userid.readonly = true
+
+ local auth = authenticator.get_subauth(self)
+ local entry = auth.read_entry(self, authenticator.usertable, self.conf.prefix..self.conf.controller, userid) or ""
+ for x in string.gmatch(entry, "([^,]+),?") do retval.value.allowed.value[#retval.value.allowed.value + 1] = x end
+ validateuserpermissions(self, retval)
+ end
+ return retval
+end
+
+function mymodule.setuserpermissions(self, userpermissions)
+ local success, userpermissions = validateuserpermissions(self, userpermissions)
+
+ if success then
+ local auth = authenticator.get_subauth(self)
+ auth.write_entry(self, authenticator.usertable, self.conf.prefix..self.conf.controller, userpermissions.value.userid.value, table.concat(userpermissions.value.allowed.value, ","))
+ else
+ userpermissions.errtxt = "Failed to set user permissions"
+ end
+ return userpermissions
+end
+
+function mymodule.getrolepermissions(self, clientdata)
+ local retval = cfe({ type="group", value={}, label="NSD Role Permissions" })
+ retval.value.role = cfe({ label="Role", seq=0 })
+ self.handle_clientdata(retval, clientdata)
+
+ local allzonefiles = listzonefiles(configdir)
+ table.sort(allzonefiles)
+ retval.value.allowed = cfe({ type="multi", value={}, label="NSD Permissions", option=allzonefiles, descr="If no permissions are defined, then all are allowed", seq=1 })
+ if #allzonefiles == 0 then
+ retval.value.allowed.errtxt = "No zones defined"
+ end
+
+ local role = retval.value.role.value
+ if role ~= "" then
+ retval.value.role.readonly = true
+
+ local auth = authenticator.get_subauth(self)
+ local entry = auth.read_entry(self, authenticator.roletable, self.conf.prefix..self.conf.controller, role) or ""
+ for x in string.gmatch(entry, "([^,]+),?") do retval.value.allowed.value[#retval.value.allowed.value + 1] = x end
+ validaterolepermissions(self, retval)
+ end
+ return retval
+end
+
+function mymodule.setrolepermissions(self, rolepermissions)
+ local success, rolepermissions = validaterolepermissions(self, rolepermissions)
+
+ if success then
+ local auth = authenticator.get_subauth(self)
+ auth.write_entry(self, authenticator.roletable, self.conf.prefix..self.conf.controller, rolepermissions.value.role.value, table.concat(rolepermissions.value.allowed.value, ","))
+ else
+ rolepermissions.errtxt = "Failed to set role permissions"
+ end
+ return rolepermissions
+end
+
+return mymodule