diff options
Diffstat (limited to 'nsd-model.lua')
-rw-r--r-- | nsd-model.lua | 396 |
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 |