--- 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 <id> user=<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=<username> 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=<username> 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));
-