Software / code / prosody-modules
Comparison
mod_auth_external/mod_auth_external.lua @ 1154:61f95bf51b35
mod_auth_external: Switch to lpty, remove file-based fallback, improve error messages and handling. Should greatly increase compatibility with scripts.
| author | Matthew Wild <mwild1@gmail.com> |
|---|---|
| date | Tue, 13 Aug 2013 18:56:50 +0100 |
| parent | 1086:50ee38e95e75 |
| child | 1155:40f7a8d152eb |
comparison
equal
deleted
inserted
replaced
| 1153:572b1ad46182 | 1154:61f95bf51b35 |
|---|---|
| 1 -- | |
| 2 -- NOTE: currently this uses lpc; when waqas fixes process, it can go back to that | |
| 3 -- | 1 -- |
| 4 -- Prosody IM | 2 -- Prosody IM |
| 5 -- Copyright (C) 2010 Waqas Hussain | 3 -- Copyright (C) 2010 Waqas Hussain |
| 6 -- Copyright (C) 2010 Jeff Mitchell | 4 -- Copyright (C) 2010 Jeff Mitchell |
| 7 -- Copyright (C) 2013 Mikael Nordfeldth | 5 -- Copyright (C) 2013 Mikael Nordfeldth |
| 6 -- Copyright (C) 2013 Matthew Wild, finally came to fix it all | |
| 8 -- | 7 -- |
| 9 -- This project is MIT/X11 licensed. Please see the | 8 -- This project is MIT/X11 licensed. Please see the |
| 10 -- COPYING file in the source package for more information. | 9 -- COPYING file in the source package for more information. |
| 11 -- | 10 -- |
| 12 | 11 |
| 12 local lpty = assert(require "lpty", "mod_auth_external requires lpty: https://code.google.com/p/prosody-modules/wiki/mod_auth_external#Installation"); | |
| 13 | 13 |
| 14 --local process = require "process"; | |
| 15 local lpc; pcall(function() lpc = require "lpc"; end); | |
| 16 | |
| 17 local config = require "core.configmanager"; | |
| 18 local log = module._log; | 14 local log = module._log; |
| 19 local host = module.host; | 15 local host = module.host; |
| 20 local script_type = config.get(host, "core", "external_auth_protocol") or "generic"; | 16 local script_type = module:get_option_string("external_auth_protocol", "generic"); |
| 21 assert(script_type == "ejabberd" or script_type == "generic"); | 17 assert(script_type == "ejabberd" or script_type == "generic", "Config error: external_auth_protocol must be 'ejabberd' or 'generic'"); |
| 22 local command = config.get(host, "core", "external_auth_command") or ""; | 18 local command = module:get_option_string("external_auth_command", ""); |
| 23 assert(type(command) == "string"); | 19 local read_timeout = module:get_option_number("external_auth_timeout", 5); |
| 24 assert(not host:find(":")); | 20 assert(not host:find(":"), "Invalid hostname"); |
| 25 local usermanager = require "core.usermanager"; | 21 local usermanager = require "core.usermanager"; |
| 26 local jid_bare = require "util.jid".bare; | 22 local jid_bare = require "util.jid".bare; |
| 27 local new_sasl = require "util.sasl".new; | 23 local new_sasl = require "util.sasl".new; |
| 28 | 24 |
| 29 local function send_query(text) | 25 local pty = lpty.new({ throw_errors = false, no_local_echo = true, use_path = false }); |
| 30 local tmpname = os.tmpname(); | |
| 31 local p = io.popen(command.." > "..tmpname, "w"); -- dump result to file | |
| 32 p:write(text); -- push colon-separated args through pipe to above command | |
| 33 p:close(); | |
| 34 local tmpfile = io.open(tmpname, "r"); -- open file to read auth result | |
| 35 local result; | |
| 36 if script_type == "ejabberd" then | |
| 37 result = tmpfile:read(4); | |
| 38 elseif script_type == "generic" then | |
| 39 result = tmpfile:read(); | |
| 40 end | |
| 41 tmpfile:close(); | |
| 42 os.remove(tmpname); -- clean up after us | |
| 43 return result; | |
| 44 end | |
| 45 | 26 |
| 46 if lpc then | 27 function send_query(text) |
| 47 --local proc; | 28 if not pty:hasproc() then |
| 48 local pid; | 29 local status, ret = pty:exitstatus(); |
| 49 local readfile; | 30 if status and (status ~= "exit" or ret ~= 0) then |
| 50 local writefile; | 31 log("warn", "Auth process exited unexpectedly with %s %d, restarting", status, ret or 0); |
| 51 | |
| 52 function send_query(text) | |
| 53 if pid and lpc.wait(pid,1) ~= nil then | |
| 54 log("debug","error, process died, force reopen"); | |
| 55 pid=nil; | |
| 56 end | |
| 57 if not pid then | |
| 58 log("debug", "Opening process " .. command); | |
| 59 -- proc = process.popen(command); | |
| 60 pid, writefile, readfile = lpc.run(command); | |
| 61 end | |
| 62 -- if not proc then | |
| 63 if not pid then | |
| 64 log("debug", "Process failed to open"); | |
| 65 return nil; | 32 return nil; |
| 66 end | 33 end |
| 67 -- proc:write(text); | 34 local ok, err = pty:startproc(command); |
| 68 -- proc:flush(); | 35 if not ok then |
| 36 log("error", "Failed to start auth process '%s': %s", command, err); | |
| 37 return nil; | |
| 38 end | |
| 39 log("debug", "Started auth process"); | |
| 40 end | |
| 69 | 41 |
| 70 writefile:write(text); | 42 pty:send(text); |
| 71 writefile:flush(); | 43 return pty:read(read_timeout); |
| 72 if script_type == "ejabberd" then | |
| 73 -- return proc:read(4); -- FIXME do properly | |
| 74 return readfile:read(4); -- FIXME do properly | |
| 75 elseif script_type == "generic" then | |
| 76 -- return proc:read(1); | |
| 77 return readfile:read(); | |
| 78 end | |
| 79 end | |
| 80 end | 44 end |
| 81 | 45 |
| 82 function do_query(kind, username, password) | 46 function do_query(kind, username, password) |
| 83 if not username then return nil, "not-acceptable"; end | 47 if not username then return nil, "not-acceptable"; end |
| 84 | 48 |
| 95 query = query..'\n'; | 59 query = query..'\n'; |
| 96 end | 60 end |
| 97 | 61 |
| 98 local response = send_query(query); | 62 local response = send_query(query); |
| 99 if (script_type == "ejabberd" and response == "\0\2\0\0") or | 63 if (script_type == "ejabberd" and response == "\0\2\0\0") or |
| 100 (script_type == "generic" and response == "0") then | 64 (script_type == "generic" and response:gsub("\r?\n$", "") == "0") then |
| 101 return nil, "not-authorized"; | 65 return nil, "not-authorized"; |
| 102 elseif (script_type == "ejabberd" and response == "\0\2\0\1") or | 66 elseif (script_type == "ejabberd" and response == "\0\2\0\1") or |
| 103 (script_type == "generic" and response == "1") then | 67 (script_type == "generic" and response:gsub("\r?\n$", "") == "1") then |
| 104 return true; | 68 return true; |
| 105 else | 69 else |
| 106 log("debug", "Nonsense back"); | 70 if response then |
| 107 --proc:close(); | 71 log("warn", "Unable to interpret data from auth process, %d bytes beginning with: %s", #response, (response:sub(1,4):gsub(".", function (c) |
| 108 --proc = nil; | 72 return ("%02X "):format(c:byte()); |
| 73 end))); | |
| 74 else | |
| 75 log("warn", "Error while waiting for result from auth process: %s", response or "unknown error"); | |
| 76 end | |
| 109 return nil, "internal-server-error"; | 77 return nil, "internal-server-error"; |
| 110 end | 78 end |
| 111 end | 79 end |
| 112 | 80 |
| 113 local host = module.host; | 81 local host = module.host; |