module(..., package.seeall) -- Load libraries require("modelfunctions") 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 path = "PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin " -- ################################################################################ -- 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 get_startstop(self, clientdata) return modelfunctions.get_startstop(processname) end function startstop_service(self, startstop, action) return modelfunctions.startstop_service(startstop, action) end function getstatus() return modelfunctions.getstatus(processname, packagename, "Postfix Status") end function 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 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 getfiledetails(filename) return modelfunctions.getfiledetails(filename, geteditablefilelist()) end function updatefiledetails(self, filedetails) return modelfunctions.setfiledetails(self, filedetails, geteditablefilelist()) end function getnewfile() local options = {} options.filename = cfe({ label="File Name" }) return cfe({ type="group", value=options, label="New File" }) end function 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 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 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 get_rebuild_databases() local retval = {} return cfe({ type="group", value=retval, label="Rebuild Databases" }) end function rebuild_databases(self, rebuild) local result = {"Rebuilding databases"} table.insert(result, " See logfile for possible errors") local cmd,f,cmdresult -- 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 cmd = "postmap "..filename table.insert(result, "Running: "..cmd) f = io.popen(format.escapespecialcharacters(path..cmd)) table.insert(result, f:read("*a")) f:close() end end end -- finally, run newaliases cmd = "newaliases" table.insert(result, "Running: "..cmd) f = io.popen(format.escapespecialcharacters(path..cmd)) table.insert(result, f:read("*a")) f:close() rebuild.descr = table.concat(result, "\n") return rebuild end function getmailqueue() local f = io.popen(path.."mailq") local result = f:read("*a") f:close() return cfe({ type="longtext", value=result, label="Postfix Mail Queue" }) end function getflushqueue() local retval = {} return cfe({ type="group", value=retval, label="Flush Queue" }) end function flushqueue(self, flush) local f = io.popen(path.."postqueue -f") local result = f:read("*a") f:close() if not result or result == "" then result = "Queue Flushed" end flush.descr = result return flush end