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
parents 50ee38e95e75
children 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;