summaryrefslogtreecommitdiffstats
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
downloadacf-nsd-master.tar.bz2
acf-nsd-master.tar.xz
Initial commitHEADv0.0.1master
Support for expert editing of files and user/role permissions
-rw-r--r--Makefile43
-rw-r--r--README1
-rw-r--r--config.mk10
-rw-r--r--nsd-controller.lua59
l---------nsd-editfile-html.lsp1
l---------nsd-editzonefile-html.lsp1
-rw-r--r--nsd-listfiles-html.lsp66
-rw-r--r--nsd-listpermissions-html.lsp75
-rw-r--r--nsd-listzonefiles-html.lsp65
-rw-r--r--nsd-model.lua396
l---------nsd-status-html.lsp1
-rw-r--r--nsd.menu5
-rw-r--r--nsd.roles4
13 files changed, 727 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..faeb6a1
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,43 @@
+APP_NAME=nsd
+PACKAGE=acf-$(APP_NAME)
+VERSION=0.0.1
+
+APP_DIST=\
+ nsd* \
+
+EXTRA_DIST=README Makefile config.mk
+
+DISTFILES=$(APP_DIST) $(EXTRA_DIST)
+
+TAR=tar
+
+P=$(PACKAGE)-$(VERSION)
+tarball=$(P).tar.bz2
+install_dir=$(DESTDIR)/$(appdir)/$(APP_NAME)
+
+all:
+clean:
+ rm -rf $(tarball) $(P)
+
+dist: $(tarball)
+
+install:
+ mkdir -p "$(install_dir)"
+ cp -a $(APP_DIST) "$(install_dir)"
+
+$(tarball): $(DISTFILES)
+ rm -rf $(P)
+ mkdir -p $(P)
+ cp -a $(DISTFILES) $(P)
+ $(TAR) -jcf $@ $(P)
+ rm -rf $(P)
+
+# target that creates a tar package, unpacks is and install from package
+dist-install: $(tarball)
+ $(TAR) -jxf $(tarball)
+ $(MAKE) -C $(P) install DESTDIR=$(DESTDIR)
+ rm -rf $(P)
+
+include config.mk
+
+.PHONY: all clean dist install dist-install
diff --git a/README b/README
new file mode 100644
index 0000000..55e2a08
--- /dev/null
+++ b/README
@@ -0,0 +1 @@
+ACF module for nsd
diff --git a/config.mk b/config.mk
new file mode 100644
index 0000000..45f4d21
--- /dev/null
+++ b/config.mk
@@ -0,0 +1,10 @@
+prefix=/usr
+datadir=${prefix}/share
+sysconfdir=${prefix}/etc
+localstatedir=${prefix}/var
+acfdir=${datadir}/acf
+wwwdir=${acfdir}/www
+cgibindir=${acfdir}/cgi-bin
+appdir=${acfdir}/app
+acflibdir=${acfdir}/lib
+sessionsdir=${localstatedir}/lib/acf/sessions
diff --git a/nsd-controller.lua b/nsd-controller.lua
new file mode 100644
index 0000000..846d14c
--- /dev/null
+++ b/nsd-controller.lua
@@ -0,0 +1,59 @@
+local mymodule = {}
+validator = require("acf.validator")
+
+mymodule.default_action = "status"
+
+function mymodule.status(self)
+ return self.model.getstatus()
+end
+
+function mymodule.startstop(self)
+ return self.handle_form(self, self.model.get_startstop, self.model.startstop_service, self.clientdata)
+end
+
+function mymodule.listfiles(self)
+ return self.model.list_files(self)
+end
+
+function mymodule.editfile(self)
+ return self.handle_form(self, self.model.get_file, self.model.update_file, self.clientdata, "Save", "Edit File", "File Saved")
+end
+
+function mymodule.createfile(self)
+ return self.handle_form(self, self.model.get_new_file, self.model.create_file, self.clientdata, "Create", "Create New NSD File", "Freeswitch File Created")
+end
+
+function mymodule.deletefile(self)
+ return self.handle_form(self, self.model.get_delete_file, self.model.delete_file, self.clientdata, "Delete", "Delete NSD File", "Freeswitch File Deleted")
+end
+
+function mymodule.listzonefiles(self)
+ return self.model.getzonefilelist(self, self.clientdata, self.sessiondata.userinfo.userid)
+end
+
+function mymodule.editzonefile(self)
+ config = self.handle_form(self, function()
+ return self.model.get_zonefile(self, self.clientdata, self.sessiondata.userinfo.userid)
+ end, function(self, value)
+ return self.model.set_zonefile(self, value, self.sessiondata.userinfo.userid)
+ end, self.clientdata, "Save", "Edit Zone File", "Zone File Saved")
+
+ if self.clientdata.linenumber and validator.is_integer(self.clientdata.linenumber) then
+ config.value.filecontent.linenumber = self.clientdata.linenumber
+ end
+ return config
+end
+
+function mymodule.listpermissions(self)
+ return self.model:getpermissionslist()
+end
+
+function mymodule.edituserpermissions(self)
+ return self.handle_form(self, self.model.getuserpermissions, self.model.setuserpermissions, self.clientdata, "Save", "Edit User Permissions", "User permissions set")
+end
+
+function mymodule.editrolepermissions(self)
+ return self.handle_form(self, self.model.getrolepermissions, self.model.setrolepermissions, self.clientdata, "Save", "Edit Role Permissions", "Role permissions set")
+end
+
+return mymodule
diff --git a/nsd-editfile-html.lsp b/nsd-editfile-html.lsp
new file mode 120000
index 0000000..15b1930
--- /dev/null
+++ b/nsd-editfile-html.lsp
@@ -0,0 +1 @@
+../filedetails-html.lsp \ No newline at end of file
diff --git a/nsd-editzonefile-html.lsp b/nsd-editzonefile-html.lsp
new file mode 120000
index 0000000..15b1930
--- /dev/null
+++ b/nsd-editzonefile-html.lsp
@@ -0,0 +1 @@
+../filedetails-html.lsp \ No newline at end of file
diff --git a/nsd-listfiles-html.lsp b/nsd-listfiles-html.lsp
new file mode 100644
index 0000000..5486a99
--- /dev/null
+++ b/nsd-listfiles-html.lsp
@@ -0,0 +1,66 @@
+<% local view, viewlibrary, page_info, session = ...
+htmlviewfunctions = require("htmlviewfunctions")
+html = require("acf.html")
+%>
+
+<script type="text/javascript">
+ if (typeof jQuery == 'undefined') {
+ document.write('<script type="text/javascript" src="<%= html.html_escape(page_info.wwwprefix) %>/js/jquery-latest.js"><\/script>');
+ }
+</script>
+
+<script type="text/javascript">
+ if (typeof $.tablesorter == 'undefined') {
+ document.write('<script type="text/javascript" src="<%= html.html_escape(page_info.wwwprefix) %>/js/jquery.tablesorter.js"><\/script>');
+ }
+</script>
+
+<script type="text/javascript">
+ $(document).ready(function() {
+ $("#list").tablesorter({headers: {0:{sorter: false}}, widgets: ['zebra']});
+ $(".deletefile").click(function(){ return confirm("Are you sure you want to delete this file?")});
+ });
+</script>
+
+<% htmlviewfunctions.displaycommandresults({"editfile", "deletefile"}, session) %>
+<% htmlviewfunctions.displaycommandresults({"createfile"}, session, true) %>
+
+<% if viewlibrary and viewlibrary.dispatch_component then
+ viewlibrary.dispatch_component("status")
+end %>
+
+<% local header_level = htmlviewfunctions.displaysectionstart(cfe({label="Configuration"}), page_info) %>
+<table id="list" class="tablesorter"><thead>
+ <tr>
+ <th>Action</th>
+ <th>File</th>
+ <th>Size</th>
+ <th>Last Modified</th>
+ </tr>
+</thead><tbody>
+<% local filename = cfe({ type="hidden", value="" }) %>
+<% local redir = cfe({ type="hidden", value=page_info.orig_action }) %>
+<% for i,file in ipairs( view.value ) do %>
+ <tr>
+ <td>
+ <% filename.value = file.filename %>
+ <% if viewlibrary.check_permission("editfile") then %>
+ <% htmlviewfunctions.displayitem(cfe({type="link", value={filename=filename, redir=redir}, label="", option="Edit", action="editfile"}), page_info, -1) %>
+ <% end %>
+ <% if viewlibrary.check_permission("deletefile") then %>
+ <% htmlviewfunctions.displayitem(cfe({type="form", value={filename=filename}, label="", option="Delete", action="deletefile", class="deletefile"}), page_info, -1) %>
+ <% end %>
+ </td>
+ <td><%= html.html_escape(file.filename) %></td>
+ <td><span class="hide"><%= html.html_escape(file.size or 0) %>b</span><%= format.formatfilesize(file.size) %></td>
+ <td><%= format.formattime(file.mtime) %></td>
+ </tr>
+<% end %>
+</tbody></table>
+
+<% if viewlibrary and viewlibrary.dispatch_component and viewlibrary.check_permission("createfile") then
+ local createform = viewlibrary.dispatch_component("createfile", nil, true)
+ createform.action = page_info.script .. page_info.prefix .. page_info.controller .. "/createfile"
+ htmlviewfunctions.displayitem(createform, page_info, htmlviewfunctions.incrementheader(header_level))
+end %>
+<% htmlviewfunctions.displaysectionend(header_level) %>
diff --git a/nsd-listpermissions-html.lsp b/nsd-listpermissions-html.lsp
new file mode 100644
index 0000000..eb7ecc6
--- /dev/null
+++ b/nsd-listpermissions-html.lsp
@@ -0,0 +1,75 @@
+<% local view, viewlibrary, page_info, session = ... %>
+<% htmlviewfunctions = require("htmlviewfunctions") %>
+<% html = require("acf.html") %>
+
+<script type="text/javascript">
+ if (typeof jQuery == 'undefined') {
+ document.write('<script type="text/javascript" src="<%= html.html_escape(page_info.wwwprefix) %>/js/jquery-latest.js"><\/script>');
+ }
+</script>
+
+<script type="text/javascript">
+ if (typeof $.tablesorter == 'undefined') {
+ document.write('<script type="text/javascript" src="<%= html.html_escape(page_info.wwwprefix) %>/js/jquery.tablesorter.js"><\/script>');
+ }
+</script>
+
+<script type="text/javascript">
+ $(document).ready(function() {
+ $("#userlist").tablesorter({headers: {0:{sorter: false}}, widgets: ['zebra']});
+ $("#rolelist").tablesorter({headers: {0:{sorter: false}}, widgets: ['zebra']});
+ });
+</script>
+
+<% htmlviewfunctions.displaycommandresults({"edituserpermissions", "editrolepermissions"}, session) %>
+
+<% local header_level = htmlviewfunctions.displaysectionstart(view, page_info) %>
+<% local header_level2 = htmlviewfunctions.displaysectionstart(cfe({label="User Permissions"}), page_info, htmlviewfunctions.incrementheader(header_level)) %>
+<table id="userlist" class="tablesorter"><thead>
+ <tr>
+ <th>Action</th>
+ <th>User</th>
+ <th>Permissions</th>
+ </tr>
+</thead><tbody>
+<% local userid = cfe({ type="hidden", value="" }) %>
+<% local redir = cfe({ type="hidden", value=page_info.orig_action }) %>
+<% for i,user in ipairs(view.value.user) do %>
+ <% userid.value = user.id %>
+ <tr>
+ <td><% htmlviewfunctions.displayitem(cfe({type="link", value={userid=userid, redir=redir}, label="", option="Edit", action="edituserpermissions"}), page_info, -1) %></td>
+ <td><%= html.html_escape(user.id) %></td>
+ <td>
+ <% for y,allowed in pairs(user.allowed) do
+ print(html.html_escape(allowed), "<br/>")
+ end %>
+ </td>
+ </tr>
+<% end %>
+</tbody></table>
+<% htmlviewfunctions.displaysectionend(header_level2) %>
+
+<% htmlviewfunctions.displaysectionstart(cfe({label="Role Permissions"}), page_info, header_level2) %>
+<table id="rolelist" class="tablesorter"><thead>
+ <tr>
+ <th>Action</th>
+ <th>Role</th>
+ <th>Permissions</th>
+ </tr>
+</thead><tbody>
+<% local rolecfe = cfe({ type="hidden", value="" }) %>
+<% for i,role in ipairs(view.value.role) do %>
+ <% rolecfe.value = role.id %>
+ <tr>
+ <td><% htmlviewfunctions.displayitem(cfe({type="link", value={role=rolecfe, redir=redir}, label="", option="Edit", action="editrolepermissions"}), page_info, -1) %></td>
+ <td><%= html.html_escape(role.id) %></td>
+ <td>
+ <% for y,allowed in pairs(role.allowed) do
+ print(html.html_escape(allowed), "<br/>")
+ end %>
+ </td>
+ </tr>
+<% end %>
+</tbody></table>
+<% htmlviewfunctions.displaysectionend(header_level2) %>
+<% htmlviewfunctions.displaysectionend(header_level) %>
diff --git a/nsd-listzonefiles-html.lsp b/nsd-listzonefiles-html.lsp
new file mode 100644
index 0000000..4acc759
--- /dev/null
+++ b/nsd-listzonefiles-html.lsp
@@ -0,0 +1,65 @@
+<% local form, viewlibrary, page_info, session = ...
+htmlviewfunctions = require("htmlviewfunctions")
+html = require("acf.html")
+%>
+
+<script type="text/javascript">
+ if (typeof jQuery == 'undefined') {
+ document.write('<script type="text/javascript" src="<%= html.html_escape(page_info.wwwprefix) %>/js/jquery-latest.js"><\/script>');
+ }
+</script>
+
+<script type="text/javascript">
+ if (typeof $.tablesorter == 'undefined') {
+ document.write('<script type="text/javascript" src="<%= html.html_escape(page_info.wwwprefix) %>/js/jquery.tablesorter.js"><\/script>');
+ }
+</script>
+
+<script type="text/javascript">
+ $(document).ready(function() {
+ $("#list").tablesorter({headers: {0:{sorter: false}}, widgets: ['zebra']});
+ });
+</script>
+
+<% htmlviewfunctions.displaycommandresults({"editzonefile"}, session) %>
+<% htmlviewfunctions.displaycommandresults({"startstop"}, session, true) %>
+
+<%
+local header_level = htmlviewfunctions.displaysectionstart(cfe({label="Configuration"}), page_info)
+local header_level2 = htmlviewfunctions.displaysectionstart(cfe({label="Edit/View Existing Domains"}), page_info, htmlviewfunctions.incrementheader(header_level))
+%>
+<table id="list" class="tablesorter"><thead>
+ <tr>
+ <th>Action</th>
+ <th>Size</th>
+ <th>Last Modified</th>
+ <th>File</th>
+ </tr>
+</thead><tbody>
+<% local filename = cfe({ type="hidden", value="" }) %>
+<% local redir = cfe({ type="hidden", value=page_info.orig_action }) %>
+<% for i,file in ipairs(form.value) do %>
+ <tr>
+ <td>
+ <%
+ filename.value = file.filename
+ if viewlibrary.check_permission("editzonefile") then
+ htmlviewfunctions.displayitem(cfe({type="link", value={filename=filename, redir=redir}, label="", option="Expert", action="editzonefile"}), page_info, -1)
+ end
+ %>
+ </td>
+ <td><span class="hide"><%= html.html_escape(file.size or 0) %>b</span><%= format.formatfilesize(file.size) %></td>
+ <td><%= format.formattime(file.mtime) %></td>
+ <td><%= html.html_escape(string.gsub(file.filename, "^.*/", "")) %></td>
+ </tr>
+<% end %>
+</tbody></table>
+<% if #form.value == 0 then %>
+ No domains defined
+<% end %>
+<% htmlviewfunctions.displaysectionend(header_level2) %>
+<% htmlviewfunctions.displaysectionend(header_level) %>
+
+<% if viewlibrary and viewlibrary.dispatch_component and viewlibrary.check_permission("startstop") then
+ viewlibrary.dispatch_component("startstop")
+end %>
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
diff --git a/nsd-status-html.lsp b/nsd-status-html.lsp
new file mode 120000
index 0000000..b2f8480
--- /dev/null
+++ b/nsd-status-html.lsp
@@ -0,0 +1 @@
+../status-html.lsp \ No newline at end of file
diff --git a/nsd.menu b/nsd.menu
new file mode 100644
index 0000000..ced0b2c
--- /dev/null
+++ b/nsd.menu
@@ -0,0 +1,5 @@
+#CAT GROUP/DESC TAB ACTION
+Networking 30NSD Status status
+Networking 30NSD List_Domains listzonefiles
+Networking 30NSD Expert listfiles
+Networking 30NSD Permissions listpermissions
diff --git a/nsd.roles b/nsd.roles
new file mode 100644
index 0000000..17ba60d
--- /dev/null
+++ b/nsd.roles
@@ -0,0 +1,4 @@
+USER=nsd:status,nsd:listzonefiles,nsd:startstop
+EDITOR=nsd:editzonefile,nsd:listzonefiles
+EXPERT=nsd:listfiles,nsd:editfile,nsd:createfile,nsd:deletefile
+ADMIN=nsd:status,nsd:listzonefiles,nsd:startstop,nsd:editzonefile,nsd:listfiles,nsd:editfile,nsd:createfile,nsd:deletefile,nsd:listpermissions,nsd:edituserpermissions,nsd:editrolepermissions