comparison mod_auth_dovecot/mod_auth_dovecot.lua @ 275:9a35c7e2fee4

Merge with Zash
author Matthew Wild <mwild1@gmail.com>
date Sun, 07 Nov 2010 16:03:00 +0000
parents cda4855863af
children 4c3abf1a9b5a
comparison
equal deleted inserted replaced
266:e7296274f48c 275:9a35c7e2fee4
5 -- Copyright (C) 2008-2010 Waqas Hussain 5 -- Copyright (C) 2008-2010 Waqas Hussain
6 -- 6 --
7 7
8 local socket_unix = require "socket.unix"; 8 local socket_unix = require "socket.unix";
9 local datamanager = require "util.datamanager"; 9 local datamanager = require "util.datamanager";
10 local log = require "util.logger".init("auth_internal_plain"); 10 local log = require "util.logger".init("auth_dovecot");
11 local new_sasl = require "util.sasl".new; 11 local new_sasl = require "util.sasl".new;
12 local nodeprep = require "util.encodings".stringprep.nodeprep; 12 local nodeprep = require "util.encodings".stringprep.nodeprep;
13 local base64 = require "util.encodings".base64; 13 local base64 = require "util.encodings".base64;
14 local pposix = require "util.pposix";
14 15
15 local prosody = _G.prosody; 16 local prosody = _G.prosody;
17 local socket_path = module:get_option_string("dovecot_auth_socket", "/var/run/dovecot/auth-login");
16 18
17 function new_default_provider(host) 19 function new_default_provider(host)
18 local provider = { name = "dovecot" }; 20 local provider = { name = "dovecot", c = nil, request_id = 0 };
19 log("debug", "initializing dovecot authentication provider for host '%s'", host); 21 log("debug", "initializing dovecot authentication provider for host '%s'", host);
20 22
21 function provider.test_password(username, password) 23 -- Closes the socket
22 log("debug", "test password '%s' for user %s at host %s", password, username, module.host); 24 function provider.close(self)
23 25 if (provider.c ~= nil) then
24 c = assert(socket.unix()); 26 provider.c:close();
25 assert(c:connect("/var/run/dovecot/auth-login")); -- FIXME: Hardcoded is bad 27 end
26 28 provider.c = nil;
27 local pid = "12345"; -- FIXME: this should be an unique number between processes, recommendation is PID 29 end
28 30
31 -- The following connects to a new socket and send the handshake
32 function provider.connect(self)
33 -- Destroy old socket
34 provider:close();
35
36 provider.c = socket.unix();
37
38 -- Create a connection to dovecot socket
39 log("debug", "connecting to dovecot socket at '%s'", socket_path);
40 local r, e = provider.c:connect(socket_path);
41 if (not r) then
42 log("warn", "error connecting to dovecot socket at '%s'. error was '%s'. check permissions", socket_path, e);
43 provider:close();
44 return false;
45 end
46
29 -- Send our handshake 47 -- Send our handshake
30 -- FIXME: Oh no! There are asserts everywhere 48 local pid = pposix.getpid();
31 assert(c:send("VERSION\t1\t1\n")); 49 log("debug", "sending handshake to dovecot. version 1.1, cpid '%d'", pid);
32 assert(c:send("CPID\t" .. pid .. "\n")); 50 if not provider:send("VERSION\t1\t1\n") then
33 51 return false
34 -- Check their handshake 52 end
53 if (not provider:send("CPID\t" .. pid .. "\n")) then
54 return false
55 end
56
57 -- Parse Dovecot's handshake
35 local done = false; 58 local done = false;
36 while (not done) do 59 while (not done) do
37 local l = assert(c:receive()); 60 local l = provider:receive();
61 if (not l) then
62 return false;
63 end
64
65 log("debug", "dovecot handshake: '%s'", l);
38 parts = string.gmatch(l, "[^\t]+"); 66 parts = string.gmatch(l, "[^\t]+");
39 first = parts(); 67 first = parts();
40 if (first == "VERSION") then 68 if (first == "VERSION") then
41 assert(parts() == "1"); 69 -- Version should be 1.1
42 assert(parts() == "1"); 70 local v1 = parts();
71 local v2 = parts();
72
73 if (not (v1 == "1" and v2 == "1")) then
74 log("warn", "server version is not 1.1. it is %s.%s", v1, v2);
75 provider:close();
76 return false;
77 end
43 elseif (first == "MECH") then 78 elseif (first == "MECH") then
79 -- Mechanisms should include PLAIN
44 local ok = false; 80 local ok = false;
45 for p in parts do 81 for p in parts do
46 if p == "PLAIN" then 82 if p == "PLAIN" then
47 ok = true; 83 ok = true;
48 end 84 end
49 end 85 end
50 assert(ok); 86 if (not ok) then
87 log("warn", "server doesn't support PLAIN mechanism. It supports '%s'", l);
88 provider:close();
89 return false;
90 end
51 elseif (first == "DONE") then 91 elseif (first == "DONE") then
52 done = true; 92 done = true;
53 end 93 end
54 end 94 end
55 95 return true;
96 end
97
98 -- Wrapper for send(). Handles errors
99 function provider.send(self, data)
100 local r, e = provider.c:send(data);
101 if (not r) then
102 log("warn", "error sending '%s' to dovecot. error was '%s'", data, e);
103 provider:close();
104 return false;
105 end
106 return true;
107 end
108
109 -- Wrapper for receive(). Handles errors
110 function provider.receive(self)
111 local r, e = provider.c:receive();
112 if (not r) then
113 log("warn", "error receiving data from dovecot. error was '%s'", socket, e);
114 provider:close();
115 return false;
116 end
117 return r;
118 end
119
120 function provider.send_auth_request(self, username, password)
121 if (provider.c == nil) then
122 if (not provider:connect()) then
123 return nil, "Auth failed. Dovecot communications error";
124 end
125 end
126
56 -- Send auth data 127 -- Send auth data
57 username = username .. "@" .. module.host; -- FIXME: this is actually a hack for my server 128 username = username .. "@" .. module.host; -- FIXME: this is actually a hack for my server
58 local b64 = base64.encode(username .. "\0" .. username .. "\0" .. password); 129 local b64 = base64.encode(username .. "\0" .. username .. "\0" .. password);
59 local id = "54321"; -- FIXME: probably can just be a fixed value if making one request per connection 130 provider.request_id = provider.request_id + 1 % 4294967296
60 assert(c:send("AUTH\t" .. id .. "\tPLAIN\tservice=XMPP\tresp=" .. b64 .. "\n")); 131
61 local l = assert(c:receive()); 132 local msg = "AUTH\t" .. provider.request_id .. "\tPLAIN\tservice=XMPP\tresp=" .. b64;
62 assert(c:close()); 133 log("debug", "sending auth request for '%s' with password '%s': '%s'", username, password, msg);
134 if (not provider:send(msg .. "\n")) then
135 return nil, "Auth failed. Dovecot communications error";
136 end
137
138
139 -- Get response
140 local l = provider:receive();
141 log("debug", "got auth response: '%s'", l);
142 if (not l) then
143 return nil, "Auth failed. Dovecot communications error";
144 end
63 local parts = string.gmatch(l, "[^\t]+"); 145 local parts = string.gmatch(l, "[^\t]+");
64 146
65 if (parts() == "OK") then 147 -- Check response
148 local status = parts();
149 local resp_id = tonumber(parts());
150
151 if (resp_id ~= provider.request_id) then
152 log("warn", "dovecot response_id(%s) doesn't match request_id(%s)", resp_id, provider.request_id);
153 provider:close();
154 return nil, "Auth failed. Dovecot communications error";
155 end
156
157 return status, parts;
158 end
159
160 function provider.test_password(username, password)
161 log("debug", "test password '%s' for user %s at host %s", password, username, module.host);
162
163 local status, extra = provider:send_auth_request(username, password);
164
165 if (status == "OK") then
166 log("info", "login ok for '%s'", username);
66 return true; 167 return true;
67 else 168 else
169 log("info", "login failed for '%s'", username);
68 return nil, "Auth failed. Invalid username or password."; 170 return nil, "Auth failed. Invalid username or password.";
69 end 171 end
70 end 172 end
71 173
72 function provider.get_password(username) 174 function provider.get_password(username)
76 function provider.set_password(username, password) 178 function provider.set_password(username, password)
77 return nil, "Cannot set_password in dovecot backend."; 179 return nil, "Cannot set_password in dovecot backend.";
78 end 180 end
79 181
80 function provider.user_exists(username) 182 function provider.user_exists(username)
81 --TODO: Send an auth request. If it returns FAIL <id> user=<user> then user exists. 183 log("debug", "user_exists for user %s at host %s", username, module.host);
82 return nil, "user_exists not yet implemented in dovecot backend."; 184
185 -- Send a request. If the response (FAIL) contains an extra
186 -- parameter like user=<username> then it exists.
187 local status, extra = provider:send_auth_request(username, "");
188
189 local param = extra();
190 while (param) do
191 parts = string.gmatch(param, "[^=]+");
192 name = parts();
193 value = parts();
194 if (name == "user") then
195 log("info", "user '%s' exists", username);
196 return true;
197 end
198
199 param = extra();
200 end
201
202 log("info", "user '%s' does not exists (or dovecot didn't send user=<username> parameter)", username);
203 return false;
83 end 204 end
84 205
85 function provider.create_user(username, password) 206 function provider.create_user(username, password)
86 return nil, "Cannot create_user in dovecot backend."; 207 return nil, "Cannot create_user in dovecot backend.";
87 end 208 end
88 209
89 function provider.get_sasl_handler() 210 function provider.get_sasl_handler()
90 local realm = module:get_option("sasl_realm") or module.host; 211 local realm = module:get_option("sasl_realm") or module.host;
91 local getpass_authentication_profile = { 212 local getpass_authentication_profile = {
92 plain_test = function(username, password, realm) 213 plain_test = function(username, password, realm)
93 local prepped_username = nodeprep(username); 214 local prepped_username = nodeprep(username);
94 if not prepped_username then 215 if not prepped_username then
95 log("debug", "NODEprep failed on username: %s", username); 216 log("debug", "NODEprep failed on username: %s", username);
96 return "", nil; 217 return "", nil;
97 end 218 end
98 return usermanager.test_password(prepped_username, realm, password), true; 219 return usermanager.test_password(prepped_username, realm, password), true;
99 end 220 end
100 }; 221 };
101 return new_sasl(realm, getpass_authentication_profile); 222 return new_sasl(realm, getpass_authentication_profile);
102 end 223 end
103 224
104 return provider; 225 return provider;
105 end 226 end
106 227
107 module:add_item("auth-provider", new_default_provider(module.host)); 228 module:add_item("auth-provider", new_default_provider(module.host));
108