# HG changeset patch # User Matthew Wild # Date 1289145780 0 # Node ID 9a35c7e2fee48df03526e572943e1f57893752cc # Parent e7296274f48c8d46d79ba463c062e261b0b22357# Parent cda4855863afd2236a5ae68c33729130cd94dc79 Merge with Zash diff -r e7296274f48c -r 9a35c7e2fee4 mod_auth_dovecot/mod_auth_dovecot.lua --- a/mod_auth_dovecot/mod_auth_dovecot.lua Sun Nov 07 16:58:13 2010 +0100 +++ b/mod_auth_dovecot/mod_auth_dovecot.lua Sun Nov 07 16:03:00 2010 +0000 @@ -7,64 +7,166 @@ local socket_unix = require "socket.unix"; local datamanager = require "util.datamanager"; -local log = require "util.logger".init("auth_internal_plain"); +local log = require "util.logger".init("auth_dovecot"); local new_sasl = require "util.sasl".new; local nodeprep = require "util.encodings".stringprep.nodeprep; local base64 = require "util.encodings".base64; +local pposix = require "util.pposix"; local prosody = _G.prosody; +local socket_path = module:get_option_string("dovecot_auth_socket", "/var/run/dovecot/auth-login"); function new_default_provider(host) - local provider = { name = "dovecot" }; + local provider = { name = "dovecot", c = nil, request_id = 0 }; log("debug", "initializing dovecot authentication provider for host '%s'", host); - - function provider.test_password(username, password) - log("debug", "test password '%s' for user %s at host %s", password, username, module.host); + + -- Closes the socket + function provider.close(self) + if (provider.c ~= nil) then + provider.c:close(); + end + provider.c = nil; + end + + -- The following connects to a new socket and send the handshake + function provider.connect(self) + -- Destroy old socket + provider:close(); - c = assert(socket.unix()); - assert(c:connect("/var/run/dovecot/auth-login")); -- FIXME: Hardcoded is bad + provider.c = socket.unix(); - local pid = "12345"; -- FIXME: this should be an unique number between processes, recommendation is PID - + -- Create a connection to dovecot socket + log("debug", "connecting to dovecot socket at '%s'", socket_path); + local r, e = provider.c:connect(socket_path); + if (not r) then + log("warn", "error connecting to dovecot socket at '%s'. error was '%s'. check permissions", socket_path, e); + provider:close(); + return false; + end + -- Send our handshake - -- FIXME: Oh no! There are asserts everywhere - assert(c:send("VERSION\t1\t1\n")); - assert(c:send("CPID\t" .. pid .. "\n")); - - -- Check their handshake + local pid = pposix.getpid(); + log("debug", "sending handshake to dovecot. version 1.1, cpid '%d'", pid); + if not provider:send("VERSION\t1\t1\n") then + return false + end + if (not provider:send("CPID\t" .. pid .. "\n")) then + return false + end + + -- Parse Dovecot's handshake local done = false; while (not done) do - local l = assert(c:receive()); + local l = provider:receive(); + if (not l) then + return false; + end + + log("debug", "dovecot handshake: '%s'", l); parts = string.gmatch(l, "[^\t]+"); first = parts(); if (first == "VERSION") then - assert(parts() == "1"); - assert(parts() == "1"); + -- Version should be 1.1 + local v1 = parts(); + local v2 = parts(); + + if (not (v1 == "1" and v2 == "1")) then + log("warn", "server version is not 1.1. it is %s.%s", v1, v2); + provider:close(); + return false; + end elseif (first == "MECH") then + -- Mechanisms should include PLAIN local ok = false; for p in parts do if p == "PLAIN" then ok = true; end end - assert(ok); + if (not ok) then + log("warn", "server doesn't support PLAIN mechanism. It supports '%s'", l); + provider:close(); + return false; + end elseif (first == "DONE") then done = true; end end - + return true; + end + + -- Wrapper for send(). Handles errors + function provider.send(self, data) + local r, e = provider.c:send(data); + if (not r) then + log("warn", "error sending '%s' to dovecot. error was '%s'", data, e); + provider:close(); + return false; + end + return true; + end + + -- Wrapper for receive(). Handles errors + function provider.receive(self) + local r, e = provider.c:receive(); + if (not r) then + log("warn", "error receiving data from dovecot. error was '%s'", socket, e); + provider:close(); + return false; + end + return r; + end + + function provider.send_auth_request(self, username, password) + if (provider.c == nil) then + if (not provider:connect()) then + return nil, "Auth failed. Dovecot communications error"; + end + end + -- Send auth data username = username .. "@" .. module.host; -- FIXME: this is actually a hack for my server local b64 = base64.encode(username .. "\0" .. username .. "\0" .. password); - local id = "54321"; -- FIXME: probably can just be a fixed value if making one request per connection - assert(c:send("AUTH\t" .. id .. "\tPLAIN\tservice=XMPP\tresp=" .. b64 .. "\n")); - local l = assert(c:receive()); - assert(c:close()); + provider.request_id = provider.request_id + 1 % 4294967296 + + local msg = "AUTH\t" .. provider.request_id .. "\tPLAIN\tservice=XMPP\tresp=" .. b64; + log("debug", "sending auth request for '%s' with password '%s': '%s'", username, password, msg); + if (not provider:send(msg .. "\n")) then + return nil, "Auth failed. Dovecot communications error"; + end + + + -- Get response + local l = provider:receive(); + log("debug", "got auth response: '%s'", l); + if (not l) then + return nil, "Auth failed. Dovecot communications error"; + end local parts = string.gmatch(l, "[^\t]+"); - - if (parts() == "OK") then + + -- Check response + local status = parts(); + local resp_id = tonumber(parts()); + + if (resp_id ~= provider.request_id) then + log("warn", "dovecot response_id(%s) doesn't match request_id(%s)", resp_id, provider.request_id); + provider:close(); + return nil, "Auth failed. Dovecot communications error"; + end + + return status, parts; + end + + function provider.test_password(username, password) + log("debug", "test password '%s' for user %s at host %s", password, username, module.host); + + local status, extra = provider:send_auth_request(username, password); + + if (status == "OK") then + log("info", "login ok for '%s'", username); return true; else + log("info", "login failed for '%s'", username); return nil, "Auth failed. Invalid username or password."; end end @@ -78,8 +180,27 @@ end function provider.user_exists(username) - --TODO: Send an auth request. If it returns FAIL user= then user exists. - return nil, "user_exists not yet implemented in dovecot backend."; + log("debug", "user_exists for user %s at host %s", username, module.host); + + -- Send a request. If the response (FAIL) contains an extra + -- parameter like user= then it exists. + local status, extra = provider:send_auth_request(username, ""); + + local param = extra(); + while (param) do + parts = string.gmatch(param, "[^=]+"); + name = parts(); + value = parts(); + if (name == "user") then + log("info", "user '%s' exists", username); + return true; + end + + param = extra(); + end + + log("info", "user '%s' does not exists (or dovecot didn't send user= parameter)", username); + return false; end function provider.create_user(username, password) @@ -90,13 +211,13 @@ local realm = module:get_option("sasl_realm") or module.host; local getpass_authentication_profile = { plain_test = function(username, password, realm) - local prepped_username = nodeprep(username); - if not prepped_username then - log("debug", "NODEprep failed on username: %s", username); - return "", nil; - end - return usermanager.test_password(prepped_username, realm, password), true; - end + local prepped_username = nodeprep(username); + if not prepped_username then + log("debug", "NODEprep failed on username: %s", username); + return "", nil; + end + return usermanager.test_password(prepped_username, realm, password), true; + end }; return new_sasl(realm, getpass_authentication_profile); end @@ -105,4 +226,3 @@ end module:add_item("auth-provider", new_default_provider(module.host)); -