local mymodule = {} -- Load libraries modelfunctions = require("modelfunctions") fs = require("acf.fs") format = require("acf.format") -- Set variables local configfile = "/etc/ntpd.conf" local confdfile = "/etc/conf.d/ntpd" local packagename = "openntpd" local processname = "ntpd" -- ################################################################################ -- LOCAL FUNCTIONS local function last_time_change() local cmdoutput = "(Have no data on updates)" -- This works for busybox syslog, which always logs to /var/log/messages -- FIXME to work with other syslog options local mess = fs.read_file_as_array("/var/log/messages") for i=table.maxn(mess),1,-1 do if string.find(mess[i], "ntpd.*adjusting") then local cmd1,cmd2 = string.match(mess[i], "^%s*(%S+%s+%S+%s+%S+%s+).*: (.*)$") cmdoutput = cmd1 .. cmd2 break end end return cmdoutput end local function validate_config(config) local success = true -- Three of the fields are just lists of IP addresses / hostnames local tags = {"server", "servers", "listen"} for i,tag in ipairs(tags) do local field = config.value[tag] for j,entry in ipairs(field.value) do if string.find(entry, "[^%w.-]") then if not (tag=="listen" and entry=="*") then field.errtxt = "Invalid IP address/hostname" success = false break end end end end return success, config 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, "OpenNTPD Status") end function mymodule.getstatusdetails() local status = {} status.timechanged = cfe({ value=last_time_change(), label="Previous time adjustment" }) status.date = cfe({ value=os.date(), label="Current time" }) return cfe({ type="group", value=status, label="OpenNTPD Detailed Status" }) end function mymodule.read_config () local config = {} config.server = cfe({ type="list", value={}, label="Single servers", descr="List of server IP addresses/hostnames. OpenNTPD will attempt to synchronize to one resolved address for each hostname entry.", seq=3 }) config.servers = cfe({ type="list", value={}, label="Multiple servers", descr="List of server IP addresses/hostnames. OpenNTPD will attempt to synchronize to all resolved addresses for each hostname entry.", seq=4 }) config.listen = cfe({ type="list", value={}, label="Addresses to listen on", descr="List of IP addresses/hostnames to listen on. '*' means listen on all local addresses.", seq=2 }) config.setstimeonstartup = cfe({ type="boolean", value=false, label="Set time on startup", seq=1 }) local conf = format.parse_linesandwords(fs.read_file(configfile) or "") for i,line in ipairs(conf) do if line[1] == "server" then table.insert(config.server.value, line[2]) elseif line[1] == "servers" then table.insert(config.servers.value, line[2]) elseif line[1] == "listen" and line[2] == "on" then table.insert(config.listen.value, line[3]) end end local opts = string.sub(format.parse_ini_file(fs.read_file(confdfile) or "", "", "NTPD_OPTS") or "", 2, -2) if format.opts_to_table(opts, "-s") then config.setstimeonstartup.value = true end return cfe({ type="group", value=config, label="OpenNTPD Config" }) end function mymodule.update_config(self, config) local success, config = validate_config(config) if success then local reverseserver = {} for i,val in ipairs(config.value.server.value) do reverseserver[val] = i end local reverseservers = {} for i,val in ipairs(config.value.servers.value) do reverseservers[val] = i end local reverselisten = {} for i,val in ipairs(config.value.listen.value) do reverselisten[val] = i end local configcontent = string.gsub(fs.read_file(configfile) or "", "\n+$", "") local configlines = format.parse_linesandwords(configcontent) for i=#configlines,1,-1 do if configlines[i][1] == "server" then if reverseserver[configlines[i][2]] then reverseserver[configlines[i][2]] = nil else configcontent = format.replace_line(configcontent, configlines[i].linenum) end elseif configlines[i][1] == "servers" then if reverseservers[configlines[i][2]] then reverseservers[configlines[i][2]] = nil else configcontent = format.replace_line(configcontent, configlines[i].linenum) end elseif configlines[i][1] == "listen" and configlines[i][2] == "on" then if reverselisten[configlines[i][3]] then reverselisten[configlines[i][3]] = nil else configcontent = format.replace_line(configcontent, configlines[i].linenum) end end end -- Then add the missing ones lines = {configcontent} for entry in pairs(reverselisten) do lines[#lines+1] = "listen on "..entry end for entry in pairs(reverseserver) do lines[#lines+1] = "server "..entry end for entry in pairs(reverseservers) do lines[#lines+1] = "servers "..entry end fs.write_file(configfile, table.concat(lines, "\n")) -- Finally, handle setstimeonstartup local opts = {} if config.value.setstimeonstartup.value then opts["-s"] = "" end fs.write_file(confdfile, format.update_ini_file(fs.read_file(confdfile) or "", "", "NTPD_OPTS", '"'..format.table_to_opts(opts)..'"')) else config.errtxt = "Failed to save config" end return config end function mymodule.get_filedetails() return modelfunctions.getfiledetails(configfile) end function mymodule.update_filedetails(self, filedetails) return modelfunctions.setfiledetails(self, filedetails, {configfile}) end function mymodule.get_logfile(self, clientdata) local retval = cfe({ type="group", value={}, label="Log File Configuration" }) retval.value.facility = cfe({value="daemon", label="Syslog Facility"}) retval.value.grep = cfe({ value="ntpd", label="Grep" }) return retval end return mymodule