local mymodule = {} -- Load libraries modelfunctions = require("modelfunctions") format = require("acf.format") fs = require("acf.fs") validator = require("acf.validator") processinfo = require("acf.processinfo") posix = require("posix") -- Set variables local configfile = "/etc/chrony/chrony.conf" local processname = "chronyd" local packagename = "chrony" local keyfile = "/etc/chrony/chrony.keys" -- ################################################################################ -- LOCAL FUNCTIONS local function validate_config(config) local success = true for i,val in ipairs(config.value.server.value) do if string.find(val, "[^%w%s.-]") then config.value.server.errtxt = "Invalid entry on line "..i success = false break end end for i,val in ipairs(config.value.allow.value) do if string.find(val, "[^%w%s.-/]") then config.value.allow.errtxt = "Invalid entry on line "..i success = false break end end if not validator.is_valid_filename(config.value.driftfile.value) then config.value.driftfile.errtxt = "Invalid file name" success = false end if not validator.is_valid_filename(config.value.keyfile.value) then config.value.keyfile.errtxt = "Invalid file name" success = false end if config.value.commandkey.value ~= "" and not validator.is_integer(config.value.commandkey.value) then config.value.commandkey.errtxt = "Must be an integer" success = false end return success, config end local function get_keyfilestatus(filedetails) filedetails.value.filecontent.descr = "List of password numbers and passwords (ie. '10 cronpass')" -- check to see if the file is being used filedetails.value.status = cfe({ value="Key file in use", label="Key File Status" }) local config = mymodule.get_config() if config.value.keyfile.value ~= keyfile then filedetails.value.status.value = "" filedetails.value.status.errtxt = "Key file is not in use" end return filedetails end -- ################################################################################ -- PUBLIC FUNCTIONS function mymodule.get_startstop(self, clientdata) local retval = modelfunctions.get_startstop(processname) retval.option[#retval.option+1] = "Online" retval.option[#retval.option+1] = "Offline" return retval end function mymodule.startstop_service(self, startstop, action) local lower = action:lower() if lower == "online" or lower == "offline" then -- try to find the password local config = mymodule.get_config() if config.value.keyfile.value == "" then startstop.errtxt = "No key file defined" elseif config.value.commandkey.value == "" then startstop.errtxt = "No command key defined" else local content = fs.read_file(config.value.keyfile.value) or "" local password = string.match("\n"..content, "\n"..config.value.commandkey.value.."%s+(%S+)") if not password then startstop.errtxt = "Could not find password in key file" else startstop.descr, startstop.errtxt = modelfunctions.run_executable({"chronyc", "-m", "password", lower}, false, password.."\n") end end else startstop = modelfunctions.startstop_service(startstop, action) end return startstop end function mymodule.getstatus() return modelfunctions.getstatus(processname, packagename, "Chrony Status") end function mymodule.getdetails() local details = {} details.time = cfe({ value=os.date(), label="Current Time" }) details.sources = cfe({ type="longtext", value="Unavailable", label="Sources" }) details.sourcestats = cfe({ type="longtext", value="Unavailable", label="Source Stats" }) details.tracking = cfe({ type="longtext", value="Unavailable", label="Tracking" }) local pid = processinfo.pidof(processname) if pid and #pid > 0 then details.sources.value, details.sources.errtxt = modelfunctions.run_executable({"chronyc", "sources"}) details.sourcestats.value, details.sourcestats.errtxt = modelfunctions.run_executable({"chronyc", "sourcestats"}) details.tracking.value, details.tracking.errtxt = modelfunctions.run_executable({"chronyc", "tracking"}) end return cfe({ type="group", value=details, label="Chrony Status Details" }) end function mymodule.get_config() local output = {} output.server = cfe({ type="list", value={}, label="Servers", descr="List of NTP servers by name or IP (ie. 0.pool.ntp.org). If infrequent Internet connection, follow name/IP with 'offline'.", seq=1 }) output.allow = cfe({ type="list", value={}, label="Allow", descr="List of allowed clients by name/subnet/IP or 'all'.", seq=2 }) output.driftfile = cfe({ label="Drift File", descr="Name of drift file (ie. /var/log/chrony/chrony.drift)", seq=3 }) output.keyfile = cfe({ label="Key File", descr="Name of key file (ie. /etc/chrony/chrony.keys)", seq=4 }) output.commandkey = cfe({ label="Command Key", descr="Number of key in Key File for commands.", seq=5 }) local config = format.parse_linesandwords(fs.read_file(configfile) or "", "[!;#%%]") if config then for i,entry in ipairs(config) do if output[entry[1]] then if type(output[entry[1]].value) == "table" then table.insert(output[entry[1]].value, table.concat(entry, " ", 2)) else output[entry[1]].value = table.concat(entry, " ", 2) end end end end return cfe({ type="group", value=output, label="Chrony Config" }) end function mymodule.update_config(self, config) local success, config = validate_config(config) if success then for name,val in pairs(config.value) do if type(val.value) == "table" then if #val.value > 0 then val.line = name.." "..table.concat(val.value, "\n"..name.." ") end else if val.value ~= "" then val.line = name .. " " .. val.value end end end local lines = fs.read_file_as_array(configfile) or {} local conf = format.parse_linesandwords(lines, "[!;#%%]") for i,entry in ipairs(conf) do if config.value[entry[1]] then if config.value[entry[1]].line then lines[entry.linenum] = config.value[entry[1]].line else lines[entry.linenum] = nil end config.value[entry[1]].line = nil end end -- remove the holes in the lines array (sparse array due to removing entries) local newlines = {} for i=1,table.maxn(lines) do table.insert(newlines, lines[i]) end -- add in missing entries to end for name,val in pairs(config.value) do if val.line then newlines[#newlines+1] = val.line val.line = nil end end fs.write_file(configfile, table.concat(newlines, "\n")) else config.errtxt = "Failed to save config" end return config end function mymodule.get_keyfiledetails() return get_keyfilestatus(modelfunctions.getfiledetails(keyfile)) end function mymodule.update_keyfiledetails(self, filedetails) return get_keyfilestatus(modelfunctions.setfiledetails(self, filedetails, {keyfile})) end function mymodule.get_enable_keyfile() local result = {} return cfe({ type="group", value=result, label="Enable Key File" }) end function mymodule.enable_keyfile(self, enablerequest) local config = mymodule.get_config() config.value.keyfile.value = keyfile config = mymodule.update_config(self, config) if config.errtxt then enablerequest.errtxt = {config.errtxt} for name,val in pairs(config.value) do if val.errtxt then table.insert(enablerequest.errtxt, name.." - "..val.errtxt) end end enablerequest.errtxt = table.concat(enablerequest.errtxt, "\n") end return enablerequest end function mymodule.get_filedetails() -- FIXME validate return modelfunctions.getfiledetails(configfile) end function mymodule.update_filedetails(self, filedetails) -- FIXME validate return modelfunctions.setfiledetails(self, filedetails, {configfile}) end function mymodule.get_logfile(self, clientdata) local retval = cfe({ type="structure", value={{facility="daemon", grep="chronyd"}}, label="Log File Configuration" }) local logdir = "/var/log/chrony" local config = format.parse_linesandwords(fs.read_file(configfile) or "", "[!;#%%]") if config then for i,entry in ipairs(config) do if entry[1] == "logdir" then logdir = entry[2] end end end -- Add in files in the logdir for f in fs.find(nil, logdir) do retval.value[#retval.value+1] = {filename=f} end return retval end return mymodule