module (..., package.seeall)
-- Load libraries
require("format")
require("fs")
require("procps")
require("getopts")
require("daemoncontrol")
require("processinfo")
-- Set variables
local configfile = "/etc/ntpd.conf"
local confdfile = "/etc/conf.d/ntpd"
local packagename = "openntpd"
local processname = "ntpd"
-- ################################################################################
-- LOCAL FUNCTIONS
-- This function is used to get config_content.
local function config_content( f )
local config = {}
config.name = f
local conf_file = fs.read_file_as_array ( config.name )
for i=1,table.maxn(conf_file) do
local l = conf_file[i]
-- Filter out commented lines
if not string.find ( l, "^[;#].*" ) then
local a,b,c = string.match ( l, "^%s*(%S+)%s*(%S*)%s*(%S*).*$" )
if (a) then
if not (config[string.lower(a)]) then
config[string.lower(a)] = {}
end
if (string.lower(a) == "listen") then
table.insert (config[string.lower(a)], {value=c} )
else
table.insert (config[string.lower(a)], {value=b} )
end
end
end
end
return config
end
local function last_time_change()
local cmdoutput = {}
local cmd, error = io.popen("cat /var/log/messages | grep ntpd | grep adjusting | tail -1" ,r)
local cmdoutput1,cmdoutput2 = string.match(cmd:read("*a"), "^%s*(%S+%s+%S+%s+%S+%s+).*: (.*)$")
cmd:close()
cmdoutput1 = cmdoutput1 or "(Have no data on updates)"
cmdoutput2 = cmdoutput2 or ""
return cmdoutput1 .. cmdoutput2
end
-- This function needs:
-- addremove = [add|remove]
-- file = Path to the file
-- variable = e.g. "NTPD_OPTS"
-- option = What value to look for to add or remove from the variable.
local function addremove_opts( addremove, file, variable, option )
if (string.lower(addremove) == "remove" ) then
cmdtxt = "/bin/sed -i 's/\\(" .. variable .. ".*\\)" .. option .. "/\\1/' " .. file
local cmd, error = io.popen ( cmdtxt )
local cmdoutput = cmd:read("*a")
cmd:close()
-- Cleanup the variable by removing unneccesary blanks
cmdtxt = "/bin/sed -i 's/\\\"\\ /\\\"/g' " .. file
cmdtxt = cmdtxt .. ";/bin/sed -i 's/\\ \\\"/\\\"/g' " .. file
local cmd, error = io.popen ( cmdtxt )
cmd:close()
elseif (string.lower(addremove) == "add" ) then
cmdtxt = "/bin/sed -i 's/\\(" .. variable .. ".*\\)\\\"/\\1" .. option .. " \\\"/' " .. file
local cmd, error = io.popen ( cmdtxt )
local cmdoutput = cmd:read("*a")
cmd:close()
end
return cmdtxt
end
local function addremove_config( addremove, variable, value )
local file = configfile
local cmdoutput
local filecontentarray = fs.read_file_as_array(file)
if (addremove == "delete" ) then
if not (value) then
return false, cfe({
name="openntpd.model.addremove_config(delete)",
errtxt="You need to choose a 'Timeserver host' to delete!",
})
end
local modifyresult
for k,v in pairs(filecontentarray) do
if (string.find(v,"^%s*" .. variable .. "%s*" .. value .. "%s*$" )) then
modifyresult = k
break
end
end
if (modifyresult) then
table.remove(filecontentarray,modifyresult)
fs.write_file(file, table.concat(filecontentarray, "\n"))
return true, cfe({
name="openntpd.model.addremove_config(delete)",
descr="* Record was successfully deleted!",
})
end
-- If nothing happened... then report error!
return false, cfe({
name="openntpd.model.addremove_config(delete)",
errtxt="Couldn't find the record to delete!",
})
elseif (addremove == "add" ) then
if not (value) then
return false, cfe({
name="openntpd.model.addremove_config(add)",
errtxt="You need to enter a value to add!",
})
end
if not (variable) then
return false, cfe({
name="openntpd.model.addremove_config(add)",
errtxt="You need to enter a variable for the value to add!",
})
end
local alreadyexists
for k,v in pairs(filecontentarray) do
-- if (string.find(v,"^.-%s+" .. value .. "%s*$" )) then
if (string.find(v,"^%s*" .. variable .. "%s*" .. value .. "%s*$" )) then
alreadyexists = k
break
end
end
if (alreadyexists) then
return false, cfe({
name="openntpd.model.addremove_config(add)",
errtxt="The entered record already exists in the config!",
})
end
table.insert(filecontentarray, variable .. " " .. value )
fs.write_file(file, table.concat(filecontentarray, "\n"))
return true, cfe({
name="openntpd.model.addremove_config(add)",
descr="* Record was successfully added!",
})
else
return false, cfe({
name="openntpd.model.addremove_config()",
errtxt="Wrong usage of this function!",
})
end
return false, cfe({
name="openntpd.model.addremove_config()",
errtxt="Something went wrong! You entered '" .. tostring(addremove) .. "' as function.",
})
end
local function process_status_text(procname)
local t = procps.pidof(procname)
if (t) and (#t > 0) then
return "Enabled"
else
return "Disabled"
end
end
-- ################################################################################
-- PUBLIC FUNCTIONS
-- action should be a CFE
function startstop_service ( self, action )
local cmd = action.value
local cmdresult,cmdmessage,cmderror,cmdaction = daemoncontrol.daemoncontrol("ntpd", cmd)
action.descr=cmdmessage
action.errtxt=cmderror
-- Reporting back (true|false, the original acition)
return cmdresult,action
end
function modify_config(self, addremove, variable, value)
return addremove_config(addremove, variable, value)
end
function modify_opts (self, addremove, file, variable, opts)
-- See to that only *my* configs are modyfied.
if (file == configfile) or (file == confdfile) then
-- See to that the variable/option only exists once
if (addremove == "add") then
local remove = addremove_opts( "remove", file, variable, opts )
end
local remove = addremove_opts( addremove, file, variable, opts )
end
end
function getstatus ()
local path = configfile
local status = {}
local config = getconfig()
if not (fs.is_file(path)) then
local file_result,err = fs.write_file(path, "")
end
local value, errtxt = processinfo.package_version(packagename)
status.version = cfe({ name = "version",
label="Program version",
value=value,
errtxt=errtxt,
})
status.status = cfe({ name="status",
label="Program status",
value=process_status_text(processname),
})
local autostart_sequense, autostart_errtxt = processinfo.process_botsequence(processname)
status.autostart = cfe({ name="autostart",
label="Autostart sequence",
value=autostart_sequense,
errtxt=autostart_errtxt,
})
status.date = config.date
status.setstimeonstartup = config.setstimeonstartup
status.timechanged = config.timechanged
return status
end
function getconfig ()
local path = configfile
local config = {}
local configopts = getopts.getoptsfromfile(confdfile)
configopts["variables"] = config_content(path)
config.setstimeonstartup = cfe({
name = "setstimeonstartup",
label="Sets time directly at startup",
value = "No",
type="checkbox",
})
if (getopts.getoptsfromfile(confdfile,"NTPD_OPTS", "-s")) then
config.setstimeonstartup.value = "Yes"
config.setstimeonstartup.checked = "Yes"
end
config.cmdsavesetstimeonstartup = cfe({
name = "cmdsavesetstimeonstartup",
label="Save the above settings",
value = "Save",
type="submit",
descr="See above checkbox",
})
config.timechanged = cfe({
name = "timechanged",
label="Previous time adjustment",
value = last_time_change(),
})
config.date = cfe({
name = "date",
label="Current time",
value=os.date(),
})
local options = {}
for k,v in pairs(configopts.variables) do
if (type(v) == "table") and ( (string.lower(k) == "servers") or (string.lower(k) == "server")) then
local srvtype = ""
if (string.lower(k) == "servers") then srvtype = " (pool)" end
for k1, v1 in pairs(v) do
if (v1.value) then
table.insert(options,v1.value..srvtype)
end
end
end
end
config.hosts_list = cfe({
name = "hosts_list",
label="Timeserver hosts",
type="select",
option=options,
size=#options + 1,
descr="In most cases you could use pool.ntp.org or [countryname].pool.ntp.org (if listed in http://www.pool.ntp.org/)."
})
if (#options < 2) then
config.hosts_list.size = 2
end
config.hosts_add = cfe({
name = "hosts_add",
label="Host to add",
})
local options = {
cfe({value="1", label="Val1",}),
cfe({value="2", label="Val1",}),
}
config.hosts_type = cfe({
name="hosts_type",
label="Type of server",
value="2",
type="radio",
option={
cfe({value="servers", label="Server pool",}),
cfe({value="server", label="Single server",}),
},
})
local options = {}
for k,v in pairs(configopts.variables) do
if (type(v) == "table") and (string.lower(k) == "listen") then
for k1, v1 in pairs(v) do
if (v1.value) then
table.insert(options,v1.value)
end
end
end
end
config.listen_list = cfe({
name = "listen_list",
label="Listen on address",
type="select",
option=options,
size=#options + 1,
descr="Empty list = Listening (acting as server) is disabled; '*' = Listen on all local addresses.",
})
if (#options < 2) then
config.listen_list.size = 2
end
config.listen_add = cfe({
name = "listen_add",
label="New listen address",
})
-- DEBUG INFORMATION
config.debug = cfe({
name = "debug",
label="Debug information",
value=configopts,
})
return config
end
function get_logfile ()
local file = {}
local cmdtxt = "cat /var/log/messages | grep ntpd"
local cmd, error = io.popen(cmdtxt ,r)
local cmdoutput = cmd:read("*a")
cmd:close()
file["filename"] = cfe({
name="filename",
label="File name",
value=cmdtxt,
})
file["filecontent"] = cfe({
type="longtext",
name="filecontent",
label="File content",
value=cmdoutput,
})
return file
end
function get_filedetails(self,num)
local path
if (num == "2") then
path = configfile2
else
path = configfile
end
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 '".. path .. "' is missing!"
end
file["filename" .. (num or "")] = cfe({
name="filename" .. (num or ""),
label="File name",
value=path,
errtxt=filenameerrtxt
})
file["filesize" .. (num or "")] = cfe({
name="filesize" .. (num or ""),
label="File size",
value=filedetails.size or 0,
})
file["mtime" .. (num or "")] = cfe({
name="mtime" .. (num or ""),
label="File date",
value=filedetails.mtime or "---",
})
file["filecontent" .. (num or "")] = 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
end
function update_filecontent (self, modifications)
local path = configfile
local file_result,err = fs.write_file(path, format.dostounix(modifications))
return file_result
end