Mercurial > prosody-modules
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 |
