Changeset

275:9a35c7e2fee4

Merge with Zash
author Matthew Wild <mwild1@gmail.com>
date Sun, 07 Nov 2010 16:03:00 +0000
parents 266:e7296274f48c (current diff) 274:cda4855863af (diff)
children 276:27c652363874
files
diffstat 1 files changed, 155 insertions(+), 35 deletions(-) [+]
line wrap: on
line diff
--- 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));
-