module(..., package.seeall) require("fs") require("procps") require("getopts") require("format") require("daemoncontrol") require("validator") local processname = "fetchmail" local configfile = "/root/.fetchmailrc" local config = {} local function get_version() local cmd = "/usr/bin/fetchmail --version 2>/dev/null" local f = io.popen( cmd ) local cmd_output_result = f:read("*l") f:close() return cmd_output_result end local function getloglevels() local loglevels = {} for i=1,8 do table.insert(loglevels,i) end return loglevels end local function getmethods() local methods = {"pop3","imap","pop3domain", } return methods end local function getmailboxes(t) local objects = cfe({}) objects.label = "Mailbox " .. tostring(t["RBOX"]) objects.method = cfe({ name="method", label = "Method", type = "select", value = t["METHOD"], option = getmethods(), }) objects.disabled = cfe({ name="disabled", type="checkbox", label = "Disabled", checked = t["DISABLED"], }) objects.remotehost = cfe({ name="remotehost", label = "RemoteHost", value = t["RHOST"], }) objects.remotemailbox = cfe({ name="remotemailbox", label = "Mailbox", value = t["RBOX"], }) objects.remotepassword = cfe({ name="remotepassword", label = "Password", type = "passwd", value = t["PASSWD"], }) objects.localhost = cfe({ name="localhost", label = "LocalHost", value = t["LHOST"], }) objects.localmailbox = cfe({ name="localmailbox", label = "LocalMailbox", value = t["LBOX"], }) objects.localdomain = cfe({ name="localdomain", label = "LocalDomain", value = t["DOMAIN"], }) return objects end local function read_config() --[[ imap/pop3 lines are: 1 2 3 4 5 6 7 8 9 10 poll protocol no dns username password 11 12 13 14 15 16 17 is smtphost no rewrite fetchall pop3domain lines are: 1 2 3 4 5 6 7 8 9 10 11 12 poll localdomainst protocol pop3 no dns username password 13 14 15 16 17 18 19 20 21 22 to * here smtphost smtpaddress no rewrite fetchall etrn is: 1 2 3 4 5 6 pool protocol etrn smtpdomain --]] local mailboxes = {} local configcontent_postmaster = {} local configcontent_etrn = {} local path = configfile local valid = nil configcontenttable = fs.read_file_as_array(path) or {} for k,v in pairs(configcontenttable) do if (string.match(v, "^%s*#Begin Fetchmail")) then valid=1 end if (valid) then -- local DISABLED = string.match(v, "^%s*(#)") -- Set parameters for POP3 or IMAP local val = {RHOST=2, CHECK=9 ,METHOD=4, RBOX=8, PASSWD=10, LBOX=12, LHOST=14} local configcontent = {} configcontent[k] = {} configcontent[k]["DISABLED"] = DISABLED for kk,vv in pairs(val) do configcontent[k][kk] = tostring(string.match(v,"^%s*" .. string.rep("%S*%s*",vv-1) .. "(%S*)%s*")) end -- Remove quotes from passwords if (configcontent[k]["PASSWD"]) then configcontent[k]["PASSWD"] = string.match(configcontent[k]["PASSWD"], "^\"(.-)\"") end -- Check if row is valid config if (configcontent[k]["CHECK"]) and (string.lower(configcontent[k]["CHECK"]) == "password") then table.insert(mailboxes,getmailboxes(configcontent[k])) end -- Set parameters for POP3domain local val = {RHOST=2, METHOD=6, RBOX=10, CHECK=11, PASSWD=12, LHOST=17, DOMAIN=19,} local configcontent = {} configcontent[k] = {} configcontent[k]["DISABLED"] = DISABLED for kk,vv in pairs(val) do configcontent[k][kk] = tostring(string.match(v,"^%s*" .. string.rep("%S*%s*",vv-1) .. "(%S*)%s*")) end -- Display this config as Method=pop3domain (as the current options in the view) configcontent[k]["METHOD"] = "pop3domain" -- Remove quotes from passwords if (configcontent[k]["PASSWD"]) then configcontent[k]["PASSWD"] = string.match(configcontent[k]["PASSWD"], "^\"(.-)\"") end -- Check if row is valid config if (configcontent[k]["CHECK"]) and (string.lower(configcontent[k]["CHECK"]) == "password") then table.insert(mailboxes,getmailboxes(configcontent[k])) end -- Set parameters for etrn local val = {ETRNSMTPHOST=2, CHECK=4, ETRNDOMAIN=6} local configcontent = {} configcontent[k] = {} configcontent[k]["DISABLED"] = DISABLED for kk,vv in pairs(val) do configcontent[k][kk] = tostring(string.match(v,"^%s*" .. string.rep("%S*%s*",vv-1) .. "(%S*)%s*")) end -- Check if row is valid config if (configcontent[k]["CHECK"]) and (string.lower(configcontent[k]["CHECK"]) == "etrn") then configcontent_etrn=configcontent[k] end -- Set parameters for postmaster local val = {CHECK=2, POSTMASTER=3} local configcontent = {} configcontent[k] = {} configcontent[k]["DISABLED"] = DISABLED for kk,vv in pairs(val) do configcontent[k][kk] = tostring(string.match(v,"^%s*" .. string.rep("%S*%s*",vv-1) .. "(%S*)%s*")) end -- Check if row is valid config if (configcontent[k]["CHECK"]) and (string.lower(configcontent[k]["CHECK"]) == "postmaster") then configcontent_postmaster=configcontent[k] end end if (string.match(v, "^%s*#End Fetchmail")) then valid=nil end end -- Create one empty record so that user can add settings table.insert(mailboxes,getmailboxes({})) return mailboxes,configcontent_postmaster,configcontent_etrn end -- ################################################################################ -- PUBLIC FUNCTIONS -- action should be a CFE function startstop_service ( self, action ) local cmd = action.value local cmdresult,cmdmessage,cmderror,cmdaction = daemoncontrol.daemoncontrol(processname, cmd) action.descr=cmdmessage action.errtxt=cmderror -- Reporting back (true|false, the original acition) return cmdresult,action end function getstatus() local opts = getconfig() local status = {} status.version = cfe({ name = "version", label="Program version", value=get_version(), }) status.status = cfe({ name="status", label="Program status", value=procps.pidof(processname), }) status.configfile = cfe({ name="configfile", label="Config file", value=configfile, }) --[[ if (opts["remotelogging"]) and not ((opts["remotelogging"]["value"] ~= "") and not (opts["localandnetworklog"]["value"])) then status.logfile = cfe({ name="logfile", label="Locally logging to", value=opts["logfile"]["value"], }) end if (opts["SYSLOGD_OPTS"]) and (opts["SYSLOGD_OPTS"]["-R"]) and (opts["SYSLOGD_OPTS"]["-R"] ~= "") then status.remote = cfe({ name="remotelogging", label="Remote logging to", value=opts["SYSLOGD_OPTS"]["-R"], }) end --]] return status end function get_filedetails() local path = configfile local file = {} local filedetails = {} local config = {} local filenameerrtxt if (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"] = cfe({ name="filename", label="File name", value=path, errtxt=filenameerrtxt }) file["filesize"] = cfe({ name="filesize", label="File size", value=filedetails.size or 0, }) file["mtime"] = cfe({ name="mtime", label="File date", value=filedetails.mtime or "---", }) file["filecontent"] = cfe({ type="longtext", name="filecontent", 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]["errtxt"] ~= "") then sumerrors = sumerrors .. config[k]["errtxt"] .. "\n" end end if (sumerrors ~= "") then file["sumerrors"] = cfe ({ name="sumerrors", label = "Configuration errors", errtxt = string.match(sumerrors, "(.-)\n$"), }) end return file end function getconfig() local config = {} local mailboxes,configcontent_postmaster,configcontent_etrn = read_config() if not (fs.is_file(configfile)) then config["configfile"] = "Config file '".. configfile .. "' is missing!" end config["debug"] = cfe({ name="debug", label = "Debug info", type = "longtext", value = configcontent, }) -- Next section selects which configurations we should show to the user config["freq"] = cfe({ name="freq", label = "Check mail once every", type = "select", value = "123", option = {"15min", "hour","day",}, }) config["mailboxes"] = cfe({ name="mailboxes", label = "Mailboxes", value = mailboxes, }) config["postmaster"] = cfe({ name="postmaster", label = "Postmaster", value = configcontent_postmaster["POSTMASTER"], }) config["etrnremote"] = cfe({ name="etrnremote", label = "Remote server", value = configcontent_etrn["ETRNSMTPHOST"], }) config["etrnquedomain"] = cfe({ name="etrnquedomain", label = "Queued domain", value = configcontent_etrn["ETRNDOMAIN"], }) --[[ -- Next section is to print errormessages when configs are wrong if (configcontent["SYSLOGD_OPTS"]["-l"]) and ((tonumber(configcontent["SYSLOGD_OPTS"]["-l"]) == nil) or (tonumber(configcontent["SYSLOGD_OPTS"]["-l"]) > 8)) then config["loglevel"]["errtxt"] = "Log value is out of range!\nCurrent value in config is '" .. configcontent["SYSLOGD_OPTS"]["-l"] .. "' - This is invalid!\nPlease select one of the above and save your changes." table.insert(config["loglevel"]["option"], tonumber(configcontent["SYSLOGD_OPTS"]["-l"])) end if (configcontent["SYSLOGD_OPTS"]["-L"] ~= nil) and ((configcontent["SYSLOGD_OPTS"]["-R"] == nil) or (configcontent["SYSLOGD_OPTS"]["-R"] == "")) then config["localandnetworklog"]["errtxt"] = "Logging to local and network (-L) is not possible unless you define a host (-R) for remote logging or remove this option." end -- Sum all errors into one cfe local sumerrors = "" for k,v in pairs(config) do if (config[k]["errtxt"] ~= "") then sumerrors = sumerrors .. config[k]["errtxt"] .. "\n" end end if (sumerrors ~= "") then config["sumerrors"] = cfe ({ name="sumerrors", label = "Configuration errors", errtxt = sumerrors, }) end --]] return config end -- IMPORTANT! This function is a exception! It's not fed with CFE's -- Parameter should be one of the ones defined in the variable 'variabletranslator'. -- value should be whatever the new value should be. function setconfigs(self,parameter,value) -- Set variables --[[ local variable = "SYSLOGD_OPTS" local variabletranslator = ({ logfile = "-O", loglevel = "-l", smallerlogs = "-S", maxsize = "-s", numrotate = "-b", localandnetworklog = "-L", remotelogging = "-R", }) cmdparameter = variabletranslator[parameter] -- Report a error if someone tryes to use a invalid parameter if not (cmdparameter) then local availablevariables = "" for k,v in pairs(variabletranslator) do availablevariables = k .. ", " .. availablevariables end parameter = parameter or "" return false, cfe({ name="syslog.model.setconfigs()", errtxt="'" .. parameter .. "' is not a valid parameter!\nValid options are: " .. availablevariables, }) end --TODO: Validate so that user cant add values with '-' (could cause major breakage next time you do getopts) -- This config-file only accepts one type of parameters (report error if someone uses wrong parameter) if not (string.find(cmdparameter, "-%a$")) then return false, cfe({ name="syslog.model.setconfigs()", errtxt="Parameter must be formated '-a' (where a is one upper/lowercase letter [a-z])", }) end -- Validate userinput (if valid path/filename) if (value) and (cmdparameter == "-O") then local cmdresult, cmdmessage = validator.is_valid_filename(value, "/var/log" ) if not (cmdresult) then return false, cfe({ name="syslog.model.setconfigs()", errtxt=cmdmessage, }) end end -- Validate userinput (Has the user entered a valid hostname and/or port) if (value) and (cmdparameter == "-R") then local hostport = format.string_to_table(value, ":") local host = hostport[1] local port = hostport[2] if (port) and not (validator.is_port(port)) then return false, cfe({ name="syslog.model.setconfigs.getopts.setoptsinfile()", errtxt="You entered '" .. tostring(port) .. "' as port - This is not valid!", }) end end -- Set/Unset checkbox variables if (value) and ((cmdparameter == "-S") or (cmdparameter == "-L")) then value = "" end local cmdresult, cmdmessage, cmderror = getopts.setoptsinfile(configfile,variable,cmdparameter,value) if (cmderror) then return false, cfe({ name="syslog.model.setconfigs.getopts.setoptsinfile()", errtxt=cmderror, }) end return true, cfe({ name="syslog.model.setconfigs()", value=cmdmessage, }) --]] end -- modifications should be a CFE function update_filecontent (self, modifications) local path = configfile local file_result,err = fs.write_file(path, format.dostounix(modifications)) return file_result, err end