module(..., package.seeall)
require("procps")
require("getopts")
require("fs")
require("format")
local configdir
local datafile
local configfiles = {}
local configitems = {}
local processname = "tinydns"
local configfile = "/etc/conf.d/" .. processname
local initdoptions = getopts.getoptsfromfile_onperline("/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
--configdir = "hidden for the moment - This row is here only for debug purpose"
-- ################################################################################
-- LOCAL FUNCTIONS
local function get_version()
local cmd_output_result, cmd_output_error
local cmd = "/sbin/apk_version -vs " .. processname .." 2>/dev/null"
local f = io.popen( cmd )
local cmdresult = f:read("*l")
if (cmdresult) and (#cmdresult > 0) then
cmd_output_result = string.match(cmdresult,"^%S*") or "Unknown"
else
cmd_output_error = "Program not installed"
end
f:close()
return cmd_output_result,cmd_output_error
end
-- Return a table with the config-content of a file
-- Commented/Blank lines are ignored
local function get_value_from_file(file)
local output = {}
local filecontent = fs.read_file_as_array(file)
for i=1,table.maxn(filecontent) do
local l = filecontent[i]
if not (string.find ( l, "^[;#].*" )) and not (string.find (l, "^%s*$")) then
table.insert(output, string.match(l,"(.-)%s*$"))
end
end
if (#output > 0) then
return true, output
else
return false, output
end
end
-- Function to recursively inserts all filenames in a dir into an array
local function recursedir(path, filearray)
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
-- If subfolder exists, list files in this subfolder
if (posix.stat(f).type == "directory") then
recursedir(f, filearray)
else
table.insert(filearray, f)
end
end
end
end
-- Functin to split items into a table
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 = check_signs("prefix")
output.type = output.type[string.sub(orgitem,1,1)] or "unknown"
return output
end
-- Feed the configfiles table with list of all availage configfiles
local function searchforconfigfiles()
local cnffile = {}
recursedir(configdir, cnffile)
for k,v in pairs(cnffile) do
local configcontent = get_value_from_file(v)
if (configcontent) then
table.insert(configfiles, v)
end
end
-- Debug option (adds the sampleconfig content)
table.insert(configfiles, "/usr/share/acf/app/tinydns/sampleconfig.conf")
end
searchforconfigfiles()
local function recurseoutput(table,cnt)
if not (cnt) then cnt=0 end
cnt = cnt + 1
for k,v in pairs(table or {}) do
if (type(v) == "string") then
io.write(" "..
tostring(v) .. "
")
else
io.write(" "..
tostring(k) .. "
")
recurseoutput(v,cnt)
end
end
end
-- 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
-- ################################################################################
-- PUBLIC FUNCTIONS
-- This function could be used to check that valid parameters are used in different places
function check_signs(sign)
local output = {}
local output = {prefix={
['.']="Name server for your domain",
['&']="Name server",
['=']="Host",
['+']="Alias",
['@']="Mail exchanger",
['=']="Host",
['^']="PTR record",
['C']="Canonical Name",
['Z']="SOA record",
[':']="Generic record",
['%']="Client location",
}}
output = output[sign]
return output
end
-- Present some general status
function getstatus()
local status = {}
local version,versionerrtxt = get_version()
status.version = cfe({ name = "version",
label="Program version",
value=version,
errtxt=versionerrtxt,
})
status.status = cfe({ name="status",
label="Program status",
value=procps.pidof(processname),
})
return status
end
-- Return config-information
function getlocations(self,filter_type)
local config = {}
local configobjects = {}
local locations = {}
local debug
local version,versionerrtxt = get_version()
local listenaddr = getopts.getoptsfromfile_onperline(configfile,"IP") or {}
-- 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
end
function getconfig(self,filter_type)
local config = {}
local listenaddr = getopts.getoptsfromfile_onperline(configfile,"IP") or {}
config.listen = cfe({
name = "listen",
label="IP address to listen on",
value=listenaddr.IP or "",
})
return config
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)
local domains = {}
--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)
-- Create domain information tables
local domain, level1, level2, level3, level4, level5, level6, levels
-- * START * COMMONT SETTINGS ***************************************************************************************
local descr=check_signs("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
-- This is the main information on each object
domaindetails = cfe ({
name=filecontent_table[2],
label=filecontent_table[2],
})
-- Add details to the previous object
table.insert(domaindetails, 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(domaindetails, 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(domaindetails, cfe ({
name=name,
label=label,
value=filecontent_table[4],
}))
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(domaindetails, 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"
end
if (filecontent_table[6]) and (#filecontent_table[6]> 0) and (name) then
table.insert(domaindetails, 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(domaindetails, cfe ({
name=name,
label=label,
value=filecontent_table[7],
}))
end
-- 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(domaindetails, cfe ({
name=name,
label=label,
value=filecontent_table[8],
}))
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(domaindetails, cfe ({
name="min",
label="Minimum time",
value=filecontent_table[9],
}))
end
if (filecontent_table[10]) and (#filecontent_table[10]> 0) then
table.insert(domaindetails, cfe ({
name="ttl",
label="Time to live",
value=filecontent_table[10],
}))
end
if (filecontent_table[11]) and (#filecontent_table[11]> 0) then
table.insert(domaindetails, cfe ({
name="timestamp",
label="Time stamp",
value=filecontent_table[11],
}))
end
if (filecontent_table[12]) and (#filecontent_table[12]> 0) then
table.insert(domaindetails, cfe ({
name="location",
label="Location",
value=filecontent_table[12],
}))
end
end
--[[
table.insert(domaindetails, cfe ({
name="debug",
label="Debug",
value=filter_type,
}))
--]]
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))
end
if (domaindetails.value) then
table.insert (currenttable , domaindetails)
end
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], ...
return domains
end
-- ################################################################################
-- DEBUG INFORMATION (Everything below will be deleted in the future)
function getdebug()
local debug = {}
--[[
local signs = get_available_signs("prefix") or {}
debug.debugprefixes = cfe({
name = "debugprefixes",
label="Available prefixes",
option=signs,
type="select",
size=table.maxn(signs)+1,
})
local signs = get_available_signs("suffix") or {}
debug.debugsuffixes = cfe({
name = "debugsuffixes",
label="Available suffixes",
option=signs,
type="select",
size=table.maxn(signs)+1,
})
--]]
debug.configdir = cfe({
name = "configdir",
label="configdir",
value=configdir,
})
debug.datafile = cfe({
name = "datafile",
label="datafile",
value=datafile,
})
for k,v in pairs(configfiles) do
local cnfcontent
fake, cnfcontent = get_value_from_file(v)
for kk,vv in pairs(cnfcontent) do
table.insert(configitems,vv)
end
end
---[[
debug.configitems = cfe({
name = "configitems",
label="configitems",
option=configitems,
type="select",
})
--]]
debug.configfiles = cfe({
name = "configfiles",
label="configfiles",
option=configfiles,
type="select",
})
return debug
end