local mymodule = {} -- Load libraries modelfunctions = require("modelfunctions") posix = require("posix") fs = require("acf.fs") format = require("acf.format") validator = require("acf.validator") -- Set variables local processname = "postfix" local packagename = "postfix" local baseurl = "/etc/postfix/" local aliasesfile = "/etc/postfix/aliases" -- ################################################################################ -- LOCAL FUNCTIONS local getconfig = function() -- the postfix config is like an ini file, but it allows multi-line entries -- so, we convert and then parse like ini file local content = fs.read_file(baseurl.."main.cf") or "" -- lines that begin with whitespace and first non-whitespace character is not '#' -- are continuations, so delete the carriage return -- have to delete blank and comment lines first, because could be before continuation line -- I've seen no documentation on inline comments, so user beware content = string.gsub(content, "\n+", "\n") content = string.gsub(content, "\n%s*#[^\n]*", "") content = string.gsub(content, "\n[^%S\n]+([^#])", " %1") return format.parse_ini_file(content, "") or {}, content 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, "Postfix Status") end function mymodule.getstatusdetails() return cfe({ type="longtext", value="", label="Postfix Status Details" }) end local function geteditablefilelist() local listed_files = fs.find_files_as_array("[^%.].*", baseurl) -- remove .db files local result = {} for i,name in ipairs(listed_files) do if not string.find(name, "%.db$") then result[#result+1] = name end end return result end function mymodule.getfilelist() local listed_files = {} for i,name in ipairs(geteditablefilelist()) do local filedetails = fs.stat(name) or {} table.insert ( listed_files , {filename=name, mtime=filedetails.mtime or "---", filesize=filedetails.size or "0"} ) end table.sort(listed_files, function (a,b) return (a.filename < b.filename) end ) return cfe({ type="list", value=listed_files, label="Postfix File List" }) end function mymodule.getfiledetails(filename) return modelfunctions.getfiledetails(filename, geteditablefilelist()) end function mymodule.updatefiledetails(self, filedetails) return modelfunctions.setfiledetails(self, filedetails, geteditablefilelist()) end function mymodule.getnewfile() local options = {} options.filename = cfe({ label="File Name" }) return cfe({ type="group", value=options, label="New File" }) end function mymodule.createfile(self, newfile) newfile.errtxt = "Failed to create file" local path = string.match(newfile.value.filename.value, "^%s*(.*%S)%s*$") or "" if not string.find(path, "/") then path = baseurl..path end if validator.is_valid_filename(path, baseurl) then if posix.stat(path) then newfile.value.filename.errtxt = "File already exists" elseif string.find(path, "%.db$") then newfile.value.filename.errtxt = "Cannot create .db files" else fs.create_file(path) newfile.errtxt = nil end else newfile.value.filename.errtxt = "Invalid filename" end return newfile end function mymodule.getdeletefile(self, clientdata) local retval = {} retval.filename = cfe({ value=clientdata.filename or "", label="File Name" }) return cfe({ type="group", value=retval, label="Delete File" }) end function mymodule.deletefile(self, delfile) local filename = delfile.value.filename.value delfile.errtxt = "Failed to delete file" if not validator.is_valid_filename(filename, baseurl) then delfile.value.filename.errtxt = "Not a valid filename!" elseif not fs.is_file(filename) then delfile.value.filename.errtxt = "File doesn't exist!" else os.remove(filename) delfile.errtxt = nil end return delfile end function mymodule.get_rebuild_databases() local retval = {} return cfe({ type="group", value=retval, label="Rebuild Databases" }) end function mymodule.rebuild_databases(self, rebuild) local result = {"Rebuilding databases"} local errresult = false local cmd,f,cmdresult,errtxt -- parse main.cf looking for hash files local config, content = getconfig() for i,db in ipairs({"btree", "cdb", "dbm", "hash", "sdbm"}) do for filename in string.gmatch(content, "[%s=]("..db..":%S+)") do filename = string.gsub(filename, ",$", "") -- run postmap on file if filename and not string.find(filename, aliasesfile) then table.insert(result, "Running: postmap"..filename) cmdresult, errtxt = modelfunctions.run_executable({"postmap", filename}, true) if errtxt then errresult = true table.insert(result, errtxt) end table.insert(result, cmdresult) end end end -- finally, run newaliases table.insert(result, "Running: newaliases") cmdresult, errtxt = modelfunctions.run_executable({"newaliases"}, true) if errtxt then errresult = true table.insert(result, errtxt) end table.insert(result, cmdresult) if errresult then rebuild.errtxt = table.concat(result, "\n") else rebuild.descr = table.concat(result, "\n") end return rebuild end function mymodule.getmailqueue() local result, errtxt = modelfunctions.run_executable({"mailq"}) return cfe({ type="longtext", value=result, label="Postfix Mail Queue", errtxt=errtxt }) end function mymodule.getflushqueue() local retval = {} return cfe({ type="group", value=retval, label="Flush Queue" }) end function mymodule.flushqueue(self, flush) flush.descr, flush.errtxt = modelfunctions.run_executable({"postqueue", "-f"}) if not flush.errtxt and flush.descr == "" then flush.descr = "Queue Flushed" end return flush end return mymodule