Mercurial > prosody-modules
view mod_net_dovecotauth/mod_net_dovecotauth.lua @ 5648:c217f4edfc4f
misc/mtail: Start of an mtail config
Stashing it here in case anyone wants to continue working on it.
Currently it's only counting log messages by level.
Due to the permissions set by systemd on Prosody logs, mtail never
managed to start correctly until permissions were manually relaxed.
author | Kim Alvefur <zash@zash.se> |
---|---|
date | Sun, 17 Sep 2023 13:36:30 +0200 |
parents | 8e686bf63441 |
children |
line wrap: on
line source
-- mod_net_dovecotauth.lua -- -- Protocol spec: -- http://dovecot.org/doc/auth-protocol.txt -- -- Example postfix config: -- sudo postconf smtpd_sasl_path=inet:127.0.0.1:28484 -- sudo postconf smtpd_sasl_type=dovecot -- sudo postconf smtpd_sasl_auth_enable=yes module:set_global(); -- Imports local new_sasl = require "core.usermanager".get_sasl_handler; local user_exists = require "core.usermanager".user_exists; local base64 = require"util.encodings".base64; local dump = require"util.serialization".serialize; local pposix = require "util.pposix"; -- Config local default_vhost = module:get_option_string("dovecotauth_host", (next(hosts))); -- TODO Is there a better solution? local allow_master = module:get_option_boolean("dovecotauth_allow_master", false); -- Active sessions local sessions = {}; -- Session methods local new_session; do local sess = { }; local sess_mt = { __index = sess }; function new_session(conn) local s = { type = "?", conn = conn, buf = "", sasl = {} } function s:log(l, m, ...) return module:log(l, self.type..tonumber(tostring(self):match("%x+$"), 16)..": "..m, ...); end return setmetatable(s, sess_mt); end function sess:send(...) local data = table.concat({...}, "\t") .. "\n" -- self:log("debug", "SEND: %s", dump(ret)); return self.conn:write(data); end local mech_params = { ANONYMOUS = "anonymous"; PLAIN = "plaintext"; ["DIGEST-MD5"] = "mutual-auth"; ["SCRAM-SHA-1"] = "mutual-auth"; ["SCRAM-SHA-1-PLUS"] = "mutual-auth"; } function sess:handshake() self:send("VERSION", 1, 1); self:send("SPID", pposix.getpid()); self:send("CUID", tonumber(tostring(self):match"%x+$", 16)); for mech in pairs(self.g_sasl:mechanisms()) do self:send("MECH", mech, mech_params[mech]); end self:send("DONE"); end function sess:feed(data) -- TODO break this up a bit -- module:log("debug", "sess = %s", dump(self)); local buf = self.buf; buf = buf .. data; local line, eol = buf:match("(.-)\r?\n()") while line and line ~= "" do buf = buf:sub(eol); self.buf = buf; local part = line:gmatch("[^\t]+"); local command = part(); if command == "VERSION" then local major = tonumber(part()); local minor = tonumber(part()); if major ~= 1 then self:log("warn", "Wrong version, expected 1.1, got %s.%s", tostring(major), tostring(minor)); self.conn:close(); break; end elseif command == "CPID" then self.type = "C"; self.pid = part(); elseif command == "SPID" and allow_master then self.type = "M"; self.pid = part(); elseif command == "AUTH" and self.type ~= "?" then -- C: "AUTH" TAB <id> TAB <mechanism> TAB service=<service> [TAB <parameters>] local id = part() -- <id> local sasl = self.sasl[id]; local mech = part(); if not sasl then -- TODO Should maybe initialize SASL handler after parsing the line? sasl = self.g_sasl:clean_clone(); self.sasl[id] = sasl; if not sasl:select(mech) then self:send("FAIL", id, "reason=invalid-mechanism"); self.sasl[id] = nil; sasl = false end end if sasl then local params = {}; -- Not used for anything yet for p in part do local k,v = p:match("^([^=]*)=(.*)$"); if k == "resp" then self:log("debug", "params = %s", dump(params)); v = base64.decode(v); local status, ret, err = sasl:process(v); self:log("debug", status); if status == "challenge" then self:send("CONT", id, base64.encode(ret)); elseif status == "failure" then self.sasl[id] = nil; self:send("FAIL", id, "reason="..tostring(err)); elseif status == "success" then self.sasl[id] = nil; self:send("OK", id, "user="..sasl.username, ret and "resp="..base64.encode(ret)); end break; -- resp MUST be the last param else params[k or p] = v or true; end end end elseif command == "USER" and self.type == "M" then -- FIXME Should this be on a separate listener? local id = part(); local user = part(); if user and user_exists(user, default_vhost) then self:send("USER", id); else self:send("NOTFOUND", id); end else self:log("warn", "Unhandled command %s", tostring(command)); self.conn:close(); break; end line, eol = buf:match("(.-)\r?\n()") end end end local listener = {} function listener.onconnect(conn) local s = new_session(conn); sessions[conn] = s; local g_sasl = new_sasl(default_vhost, s); s.g_sasl = g_sasl; s:handshake(); end function listener.onincoming(conn, data) local s = sessions[conn]; -- s:log("debug", "RECV %s", dump(data)); return s:feed(data); end function listener.ondisconnect(conn) sessions[conn] = nil; end function module.unload() for c in pairs(sessions) do c:close(); end end module:provides("net", { default_port = 28484; listener = listener; });