Mercurial > prosody-modules
view mod_net_dovecotauth/mod_net_dovecotauth.lua @ 5424:b45d9a81b3da
mod_http_oauth2: Revert role selector, going to try something else
Back out f2c7bb3af600
Allowing only a single role to be encoded into the grant takes away the
possibility of having multiple roles in the grant, one of which is
selected when issuing an access token. It also takes away the ability to
have zero roles granted, which could be useful e.g. when you only need
OIDC scopes.
author | Kim Alvefur <zash@zash.se> |
---|---|
date | Sun, 07 May 2023 19:40:57 +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; });