diff options
Diffstat (limited to 'tinydns-model.lua')
-rw-r--r-- | tinydns-model.lua | 668 |
1 files changed, 147 insertions, 521 deletions
diff --git a/tinydns-model.lua b/tinydns-model.lua index 1ace06f..fa0632f 100644 --- a/tinydns-model.lua +++ b/tinydns-model.lua @@ -10,23 +10,12 @@ require("daemoncontrol") require("validator") -- Set variables -local configdir -local datafile local configfiles = {} -local configitems = {} local packagename = "tinydns" local processname = "tinydns" local configfile = "/etc/conf.d/" .. processname -local baseurl = "/etc/tinydns" -local initdoptions = getopts.getoptsfromfile("/etc/init.d/" .. processname, "") -if (initdoptions) then - configdir = initdoptions.DATADIR - datafile = initdoptions.ROOT .. "/data" or "/var/cache/data" -else - configdir = "/etc/" .. processname - datafile = "/var/cache/data" -end -descr = { +local configdir = "/etc/"..processname +local descr = { prefix={ ['.']="Name server for your domain (NS + A + SOA)", ['&']="Deletegate subdomain (NS + A)", @@ -40,32 +29,20 @@ descr = { [':']="Generic record", ['%']="Client location", }, - reverse={ - ['nsourdomain']=".", - ['nsdomain']="&", - ['host']="=", - ['alias']="+", - ['mx']="@", - ['ptr']="^", - ['cname']="C", - ['soa']="Z", - [':']=":", - ['locations']="%", - }, fieldlabels={ - ['.']={"Prefix", "Domain", "IP address", "Name server", "Time to live", "Timestamp", "Location", }, - ['&']={"Prefix", "Domain", "IP address", "Name server", "Time to live", "Timestamp", "Location", }, - ['=']={"Prefix", "Host", "IP address", "Time to live", "Timestamp", "Location", }, - ['+']={"Prefix", "Alias", "IP address", "Time to live", "Timestamp", "Location", }, - ['@']={"Prefix", "Domain", "IP address", "Mail exchanger", "Distance", "Time to live", "Timestamp", "Location", }, - ['^']={"Prefix", "PTR", "Domain name", "Time to live", "Timestamp", "Location", }, - ['C']={"Prefix", "Domain name", "Canonical name", "Time to live", "Timestamp", "Location", }, - ['Z']={"Prefix", "Unknown", "Primary name server", "Contact address", "Serial number", "Refresh time", "Retry time", "Expire time", "Minimum time", "Time to live", "Timestamp", "Location",}, - [':']={"Prefix", }, - ['%']={"Prefix", }, + ['.']={"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", }, + ['+']={"Alias", "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", }, }, } ---configdir = "hidden for the moment - This row is here only for debug purpose" -- ################################################################################ -- LOCAL FUNCTIONS @@ -110,9 +87,9 @@ end local function split_config_items(orgitem) local delimiter = ":" local output = {} - output = format.string_to_table(string.sub(orgitem,1,1) .. ":" .. string.sub(orgitem,2),delimiter) - output.type = descr['prefix'] - output.type = output.type[string.sub(orgitem,1,1)] or "unknown" + output = format.string_to_table(string.sub(orgitem,2),delimiter) + output.type = string.sub(orgitem,1,1) + output.label = descr['prefix'][output.type] or "unknown" return output end @@ -129,68 +106,8 @@ local function searchforconfigfiles() end searchforconfigfiles() --- Create table with doman levels -local function recursedomains(t,array,maxn,currnum) - if not (currnum) then currnum = maxn + 1 end - currnum = currnum - 1 - if not (currnum == 0) then - - if not (array[t[currnum]]) then - array[t[currnum]] = {} - end - recursedomains(t,array[t[currnum]],maxn,currnum) - end - - -- FIXME: This is a /really uggly/ hack to return the current table - -- If it's fixed nicely... it would be wonderful! - if (array[t[maxn]]) and - (array[t[maxn]][t[maxn-1]]) and - (array[t[maxn]][t[maxn-1]][t[maxn-2]]) and - (array[t[maxn]][t[maxn-1]][t[maxn-2]][t[maxn-3]]) and - (array[t[maxn]][t[maxn-1]][t[maxn-2]][t[maxn-3]][t[maxn-4]]) and - (array[t[maxn]][t[maxn-1]][t[maxn-2]][t[maxn-3]][t[maxn-4]][t[maxn-5]]) then - return array[t[maxn]][t[maxn-1]][t[maxn-2]][t[maxn-3]][t[maxn-4]][t[maxn-5]] - end - - if (array[t[maxn]]) and - (array[t[maxn]][t[maxn-1]]) and - (array[t[maxn]][t[maxn-1]][t[maxn-2]]) and - (array[t[maxn]][t[maxn-1]][t[maxn-2]][t[maxn-3]]) and - (array[t[maxn]][t[maxn-1]][t[maxn-2]][t[maxn-3]][t[maxn-4]]) then - return array[t[maxn]][t[maxn-1]][t[maxn-2]][t[maxn-3]][t[maxn-4]] - end - - if (array[t[maxn]]) and - (array[t[maxn]][t[maxn-1]]) and - (array[t[maxn]][t[maxn-1]][t[maxn-2]]) and - (array[t[maxn]][t[maxn-1]][t[maxn-2]][t[maxn-3]]) then - return array[t[maxn]][t[maxn-1]][t[maxn-2]][t[maxn-3]] - end - - if (array[t[maxn]]) and - (array[t[maxn]][t[maxn-1]]) and - (array[t[maxn]][t[maxn-1]][t[maxn-2]]) and - (array[t[maxn]][t[maxn-1]][t[maxn-2]][t[maxn-3]]) then - return array[t[maxn]][t[maxn-1]][t[maxn-2]][t[maxn-3]] - end - - if (array[t[maxn]]) and - (array[t[maxn]][t[maxn-1]]) and - (array[t[maxn]][t[maxn-1]][t[maxn-2]]) then - return array[t[maxn]][t[maxn-1]][t[maxn-2]] - end - - if (array[t[maxn]]) and (array[t[maxn]][t[maxn-1]]) then - return array[t[maxn]][t[maxn-1]] - end - - if (array[t[maxn]]) then - return array[t[maxn]] - end -end - local function validfilename(path) - for k,v in pairs(getfilelist()) do + for k,v in pairs(getfilelist().value) do if (v == path) then return true end @@ -198,76 +115,13 @@ local function validfilename(path) return false, "Not a valid filename!" end --- Example taken from PIL --- Sort by Keys -local function pairsByKeys(t,f) - local a = {} - - for n in pairs(t) do - -- This is to fix some bug when next table is indexnumber instead of name - if (tonumber(n) == nil) then - a[#a + 1] = n - end - end - table.sort(a,f) - local i = 0 -- iterator variable - return function () --iterator function - i = i + 1 - return a[i], t[a[i]] - end -end - -local function rebuild_table(t,domains_rebuilt) - if not (type(t) == "string") then - for k,v in pairs(t) do - if (tonumber(k)) then - table.insert(domains_rebuilt, v) - else - table.insert(domains_rebuilt, {label=k}) - rebuild_table(v,domains_rebuilt[#domains_rebuilt]) - end - end - table.sort(domains_rebuilt, function(a,b) return (a.label < b.label) end) - end -end - --- This function removes all records that doesn't have the filter-value -local function filter_table(t1,domains_filtered,filter) - if not (type(t1) == "string") then - for k1,v1 in pairs(t1) do - for k2,v2 in pairs(v1) do - if (v2.label) then - if ( string.find(filter,v2.label) ) then - table.insert(domains_filtered, v2) - end - end - end - end - end -end - -- ################################################################################ -- PUBLIC FUNCTIONS function startstop_service ( self, action ) - local cmd = action.value - local cmdresult,cmdmessage,cmderror,cmdaction = daemoncontrol.daemoncontrol(processname, cmd) - action.descr=cmdmessage - action.errtxt=cmderror - return cmdresult,action -end - -function valid_filename(self,path) - return validfilename(path) -end - --- This function could be used to check that valid parameters are used in different places -function check_signs(sign) - local output - if (sign) and (descr[sign]) then - output = descr[sign] - end - return output + -- action is validated in daemoncontrol + local cmdresult,cmdmessage,cmderror,cmdaction = daemoncontrol.daemoncontrol(processname, action) + return cfe({ type="boolean", value=cmdresult, descr=cmdmessage, errtxt=cmderror, label="Start/Stop result" }) end -- Present some general status @@ -275,460 +129,232 @@ function getstatus() local status = {} local value, errtxt = processinfo.package_version(packagename) - status.version = cfe({ name = "version", + status.version = cfe({ label="Program version", value=value, errtxt=errtxt, }) - status.status = cfe({ name="status", + status.status = cfe({ label="Program status", value=procps.pidof(processname), }) + if (#status.status.value > 0) then + status.status.value = "Enabled" + else + status.status.value = "Disabled" + end - status.configdir = cfe({ name="configdir", + status.configdir = cfe({ label="Config directory", value=configdir, }) - status.configfiles = cfe({ name="configfiles", + status.configfiles = cfe({ + type="list", label="Config files", value=configfiles, }) local autostart_sequense, autostart_errtxt = processinfo.process_botsequence(processname) - status.autostart = cfe({ name="autostart", + status.autostart = cfe({ label="Autostart sequence", value=autostart_sequense, errtxt=autostart_errtxt, }) - return status -end - + local config = getconfig() + status.listen = config.value.listen --- Return config-information -function getlocations(self,filter_type) - local config = {} - local configobjects = {} - local locations = {} - - -- Loop through all available configfiles - for k,v in pairs(configfiles) do - local filecontent, fileresult - fileresult, filecontent = get_value_from_file(v) - for kk,vv in pairs(filecontent) do - local domaindetails = {} - local filecontent_table = split_config_items(vv) - - -- This is mostly for debugging - -- This table contains all available configs - table.insert(configobjects, cfe({ - name=vv, - value=vv, - option=filecontent_table, - })) - - -- Create a table with location items - -- Containing all objects that start with % - if (filecontent_table[1] == "%") then - if not (locations[filecontent_table[2]]) then - locations[filecontent_table[2]] = {} - end - table.insert(locations[filecontent_table[2]], filecontent_table[3]) - end - end - end - - return locations + return cfe({ type="group", value=status, label="DNS Status" }) end -function getconfig(self,filter_type) +function getconfig() local config = {} - local listenaddr = getopts.getoptsfromfile(configfile,"","IP") or "" - - config.listen = cfe({ - name = "listen", + local listenaddr = getopts.getoptsfromfile(configfile,"","IP") or "" + config.listen = cfe({ label="IP address to listen on", value=listenaddr, }) + local test, errtxt = validator.is_ipv4(config.listen.value) + if not test then + config.listen.errtxt = errtxt + end - config.baseurl = cfe({ - name = "baseurl", - label="Baseurl for configfiles", - value=baseurl, - }) + return cfe({ type="group", value=config, label="TinyDNS Configuration" }) +end - return config +function setconfig(conf) + local test, errtxt = validator.is_ipv4(conf.value.listen.value) + if not test then + conf.value.listen.errtxt = errtxt + conf.errtxt = "Failed to set configuration" + else + getopts.setoptsinfile(configfile,"","IP",conf.value.listen.value) + end + + return conf end -- If you enter 'filter_type' (this should be one of the options found in local function check_signs() ) then -- the output will be filtered to only contain this type of data. -function getconfigobjects(self,filter_type, filter_levels) - local domains = {} - +function getconfigobjects(self, filter_type) + local configobjects = {} --Loop through all available configfiles - for k,v in pairs(configfiles) do + for i,filename in pairs(configfiles) do local filecontent, fileresult - fileresult, filecontent = get_value_from_file(v) - for kk,vv in pairs(filecontent) do + fileresult, filecontent = get_value_from_file(filename) + for j,configline in pairs(filecontent) do local domaindetails = {} - local filecontent_table = split_config_items(vv) - filecontent_table["orgrecord"] = vv - - -- Create domain information tables - local domain + local filecontent_table = split_config_items(configline) + filecontent_table.configline = configline - -- * START * COMMONT SETTINGS **************************************** - local descr=descr['prefix'] -- Use only configs that has a valid prefix - -- We filter away location-definitions -- If function is called with some filter options... then show only the filtered values - if ( not (filter_type) or - ( (filter_type) and (filter_type == filecontent_table[1]))) and - (descr[filecontent_table[1]]) and not - (filecontent_table[1] == "%") then - - domain = format.string_to_table(filecontent_table[2], "%.") - -- We rebuild the table and add previous level-information to the current level - for i = table.maxn(domain),2,-1 do - domain[i-1] = domain[i-1] .. "." .. domain[i] - end - - local domainoptions = {} - -- Add details to the previous object - table.insert(domainoptions, cfe ({ - name="type", - label="Type", - value=descr[filecontent_table[1]], - })) - - -- Set values and labels for field #3 - local name = "ip" - local label = "IP address" - -- Some configs uses third column in some other way - if (filecontent_table[1] == "^") or (filecontent_table[1] == "C") then - name = "pointdomain" - label = "Domain" - end - if (filecontent_table[1] == "Z") then - name = "mname" - label = "Primary nameserver" - end - if (filecontent_table[1] == ":") then - name = "rectype" - label = "Type of record" - end - if (filecontent_table[3]) and (#filecontent_table[3]> 0) and (name) then - table.insert(domainoptions, cfe ({ - name=name, - label=label, - value=filecontent_table[3], - })) - end - - -- Set values and labels for field #4 - name = "ttl" - label = "Time to live" - -- Some configs uses third column in some other way - if (filecontent_table[1] == ".") or (filecontent_table[1] == "&") then - name = "ns" - label = "Name server" - end - if (filecontent_table[1] == "@") then - name = "mx" - label = "Mail exchanger" - end - if (filecontent_table[1] == "Z") then - name = "rname" - label = "Contact address" - end - if (filecontent_table[4]) and (#filecontent_table[4]> 0) and (name) then - table.insert(domainoptions, cfe ({ - name=name, - label=label, - value=filecontent_table[4], - })) + if ( not (filter_type) or ((filter_type) and (filter_type == filecontent_table.type)) ) + and (filecontent_table.label) + then + local entry = {} + for i,value in ipairs(filecontent_table) do + entry[i] = value end - - -- Set values and labels for field #5 - name = "timestamp" - label = "Time stamp" - -- Some configs uses third column in some other way - if (filecontent_table[1] == ".") or (filecontent_table[1] == "&") then - name = "ttl" - label = "Time to live" - end - if (filecontent_table[1] == "@") then - name = "dist" - label = "Distance" - end - if (filecontent_table[1] == "Z") then - name = "ser" - label = "Serial number" - end - if (filecontent_table[5]) and (#filecontent_table[5]> 0) and (name) then - table.insert(domainoptions, cfe ({ - name=name, - label=label, - value=filecontent_table[5], - })) - end - - -- Set values and labels for field #6 - name = "lo" - label = "Location" - -- Some configs uses third column in some other way - if (filecontent_table[1] == ".") or (filecontent_table[1] == "&") then - name = "timestamp" - label = "Time stamp" - end - if (filecontent_table[1] == "@") then - name = "ttl" - label = "Time to live" - end - if (filecontent_table[1] == "Z") then - name = "ref" - label = "Refresh time" + -- we're gonna add a reverse domain name to make it easier to sort + local domain = {} + for mt in string.gmatch(entry[1], "([^.]+)") do + table.insert(domain, mt) end - - if (filecontent_table[6]) and (#filecontent_table[6]> 0) and (name) then - table.insert(domainoptions, cfe ({ - name=name, - label=label, - value=filecontent_table[6], - })) - end - - -- Set values and labels for field #7 - local name = nil - local label = nil - -- Some configs uses third column in some other way - if (filecontent_table[1] == ".") or (filecontent_table[1] == "&") then - name = "lo" - label = "Location" - end - if (filecontent_table[1] == "@") then - name = "timestamp" - label = "Timestamp" - end - if (filecontent_table[1] == "Z") then - name = "ret" - label = "Retry time" - end - if (filecontent_table[7]) and (#filecontent_table[7]> 0) and (name) then - table.insert(domainoptions, cfe ({ - name=name, - label=label, - value=filecontent_table[7], - })) + local reversedomain = {} + for i=#domain,1,-1 do + table.insert(reversedomain, domain[i]) end + entry.sort = table.concat(reversedomain, ".") - -- Set values and labels for field #8 - local name = nil - local label = nil - -- Some configs uses third column in some other way - if (filecontent_table[1] == "@") then - name = "lo" - label = "Location" - end - if (filecontent_table[1] == "Z") then - name = "exp" - label = "Expire time" - end - if (filecontent_table[8]) and (#filecontent_table[8]> 0) and (name) then - table.insert(domainoptions, cfe ({ - name=name, - label=label, - value=filecontent_table[8], - })) + -- add it to the table + if not configobjects[filecontent_table.type] then + configobjects[filecontent_table.type] = {label=filecontent_table.label, fieldlabels=descr.fieldlabels[filecontent_table.type]} end - - -- Set values and labels for field #9-12 - if (filecontent_table[1] == "Z") then - if (filecontent_table[9]) and (#filecontent_table[9]> 0) then - table.insert(domainoptions, cfe ({ - name="min", - label="Minimum time", - value=filecontent_table[9], - })) - end - if (filecontent_table[10]) and (#filecontent_table[10]> 0) then - table.insert(domainoptions, cfe ({ - name="ttl", - label="Time to live", - value=filecontent_table[10], - })) - end - if (filecontent_table[11]) and (#filecontent_table[11]> 0) then - table.insert(domainoptions, cfe ({ - name="timestamp", - label="Time stamp", - value=filecontent_table[11], - })) - end - if (filecontent_table[12]) and (#filecontent_table[12]> 0) then - table.insert(domainoptions, cfe ({ - name="location", - label="Location", - value=filecontent_table[12], - })) - end - end - - - -- This is the main information on each object - domaindetails = cfe ({ - name=filecontent_table[2], - label=filecontent_table[2], - option=domainoptions, - orgrecordtable=filecontent_table, - }) - - end - -- * END * COMMONT SETTINGS ******************************************** - -- Inject the previous data into the right table - local value = filecontent_table[2] - local currenttable - if (type(domain) == "table") then - currenttable = recursedomains(domain, domains, table.maxn(domain)) + table.insert(configobjects[filecontent_table.type], entry) end + end + end ----[[ - if (domaindetails.value) then - table.insert (currenttable , domaindetails) + -- Sort each of the tables by domain name (entry 1) + for type,entries in pairs(configobjects) do + table.sort(entries, function(a,b) + if a == b then + return false; + elseif a.sort ~= b.sort then + return a.sort < b.sort end ---]] - + for i in ipairs(a) do + if a[i] ~= b[i] then + return a[i] < b[i] + end + end + a.errtxt = "Duplicate entry" + b.errtxt = "Duplicate entry" + return false + end) + for i,entry in ipairs(entries) do + entry.sort = nil end end - -- TODO: Sort the domains table! - -- Sorting is not possible when things is done as they are (se above) - -- problem comese when we use keynames instead of [1], [2], ... - -- Next we rebuild the domains table and do it so it can be sorted - local domains_sorted = {} - - local domains_rebuilt = {} - rebuild_table(domains,domains_rebuilt) - - -- Filter away not wanted records - local domains_filtered = {} --- local filter_level2 = "" -- < This is DEBUG!!! REMOVE WHEN DONE! --- filter_table(domains_rebuilt,domains_filtered,filter_level2) - domains_filtered = domains_rebuilt - - return domains_filtered + return configobjects end function getfilelist () local listed_files = {} - for k,v in pairs{baseurl} do - recursedir(v, listed_files) - end --- table.sort(listed_files, function (a,b) return (a.name < b.name) end ) + recursedir(configdir, listed_files) - return listed_files + return cfe({ type="list", value=listed_files, label="List of config files" }) end -function get_filedetails(self,path) - if not (validfilename(path)) then - return false, "Not a valid filename!" - end +function get_filedetails(path) local file = {} local filedetails = {} - local config = {} local filenameerrtxt if (path) and (fs.is_file(path)) then filedetails = fs.stat(path) - config = getconfig(path) else - config = {} - config.filename = {} - config["filename"]["errtxt"]="Config file '".. tostring(path) .. "' is missing!" + filenameerrtxt="Config file '".. tostring(path) .. "' is missing!" end - file["filename" .. (num or "")] = cfe({ - name="filename" .. (num or ""), + file["filename"] = cfe({ label="File name", value=path, errtxt=filenameerrtxt }) - file["filesize" .. (num or "")] = cfe({ - name="filesize" .. (num or ""), + file["filesize"] = cfe({ label="File size", - value=filedetails.size or 0, + value=filedetails.size or "0", }) - file["mtime" .. (num or "")] = cfe({ - name="mtime" .. (num or ""), + file["mtime"] = cfe({ label="File date", value=filedetails.mtime or "---", }) - file["filecontent" .. (num or "")] = cfe({ + file["filecontent"] = cfe({ type="longtext", - name="filecontent" .. (num or ""), label="File content", value=fs.read_file(path), }) - -- Sum all errors into one cfe - local sumerrors = "" - for k,v in pairs(config) do - if (config[k]) and (config[k]["errtxt"]) and (config[k]["errtxt"] ~= "") then - sumerrors = sumerrors .. config[k]["errtxt"] .. "\n" - end - end - if (sumerrors ~= "") then - file["sumerrors" .. (num or "")] = cfe ({ - name="sumerrors" .. (num or ""), - label = "Configuration errors", - errtxt = string.match(sumerrors, "(.-)\n$"), - }) - end - - return file + return cfe({ type="group", value=file, label="Config file details" }) end -function updatefilecontent (self, filetochange) - local path = filetochange.name - local modifications = filetochange.value +function updatefilecontent (path, modifications) + local success = false + local errtxt if not (fs.is_file(path)) then - return false, "Not a filename" - end - if (validfilename(path)) then + errtxt = "Not a filename" + elseif (validfilename(path)) then fs.write_file(path, format.dostounix(modifications)) - return true + success = true else - return false, "Not a valid filename!" + errtxt = "Not a valid filename!" end - return false, "Something went wrong!" + + return cfe({ type="boolean", value=success, label="Update file result", errtxt=errtxt }) end -function createconfigfile (self, path) - local validfilepath, filepatherror = validator.is_valid_filename(path,baseurl) - if (fs.is_file(path)) then - return false,"File already exists" - end +function getnewconfigfile() + local options = {} + options.filename = cfe({ value=configdir.."/", label="File Name" }) + return cfe({ type="group", value=options, label="New config file" }) +end + +function createconfigfile(configfile) + configfile.errtxt = "Failed to create file" + local path = configfile.value.filename.value + local validfilepath, filepatherror = validator.is_valid_filename(path,configdir) if (validfilepath) then - fs.write_file(path, "") - return true,nil + if (fs.is_file(path)) then + configfile.value.filename.errtxt = "File already exists" + else + local file = io.open(path, "w") + file:close() + configfile.errtxt = nil + end else - return false, filepatherror + configfile.value.filename.errtxt = filepatherror end - return false, "Something went wrong!" + + return configfile end -function remove_file(self, path) + +function remove_file(path) + local success = false + local errtxt if not (fs.is_file(path)) then - return false,"File doesn't exist!" - end - if (validfilename(path)) then + errtxt = "File doesn't exist!" + elseif (validfilename(path)) then local cmd, errors = io.popen( "/bin/rm " .. path, r ) local cmdoutput = cmd:read("*a") cmd:close() - return true, cmdoutput + success = true else - return false, "Not a valid filename!" + errtxt = "Not a valid filename!" end - return false, "Something went wrong!" + return cfe({ type="boolean", value=success, label="Delete config file result", errtxt=errtxt }) end |