Comparison

mod_auth_external_insecure/mod_auth_external_insecure.lua @ 3884:f84ede3e9e3b

mod_auth_external->mod_auth_external_insecure: Unmaintained and almost certainly insecure, discourage its use
author Matthew Wild <mwild1@gmail.com>
date Thu, 06 Feb 2020 21:03:17 +0000
parent 3660:mod_auth_external/mod_auth_external.lua@11cd6e034fd3
comparison
equal deleted inserted replaced
3883:571249f69577 3884:f84ede3e9e3b
1 --
2 -- Prosody IM
3 -- Copyright (C) 2010 Waqas Hussain
4 -- Copyright (C) 2010 Jeff Mitchell
5 -- Copyright (C) 2013 Mikael Nordfeldth
6 -- Copyright (C) 2013 Matthew Wild, finally came to fix it all
7 --
8 -- This project is MIT/X11 licensed. Please see the
9 -- COPYING file in the source package for more information.
10 --
11
12 local lpty = assert(require "lpty", "mod_auth_external requires lpty: https://modules.prosody.im/mod_auth_external.html#installation");
13 local usermanager = require "core.usermanager";
14 local new_sasl = require "util.sasl".new;
15 local server = require "net.server";
16 local have_async, async = pcall(require, "util.async");
17
18 local log = module._log;
19 local host = module.host;
20
21 local script_type = module:get_option_string("external_auth_protocol", "generic");
22 local command = module:get_option_string("external_auth_command", "");
23 local read_timeout = module:get_option_number("external_auth_timeout", 5);
24 local blocking = module:get_option_boolean("external_auth_blocking", true); -- non-blocking is very experimental
25 local auth_processes = module:get_option_number("external_auth_processes", 1);
26
27 assert(script_type == "ejabberd" or script_type == "generic",
28 "Config error: external_auth_protocol must be 'ejabberd' or 'generic'");
29 assert(not host:find(":"), "Invalid hostname");
30
31
32 if not blocking then
33 assert(server.event, "External auth non-blocking mode requires libevent installed and enabled");
34 log("debug", "External auth in non-blocking mode, yay!")
35 waiter, guard = async.waiter, async.guarder();
36 elseif auth_processes > 1 then
37 log("warn", "external_auth_processes is greater than 1, but we are in blocking mode - reducing to 1");
38 auth_processes = 1;
39 end
40
41 local ptys = {};
42
43 local pty_options = { throw_errors = false, no_local_echo = true, use_path = false };
44 for i = 1, auth_processes do
45 ptys[i] = lpty.new(pty_options);
46 end
47
48 function module.unload()
49 for i = 1, auth_processes do
50 ptys[i]:endproc();
51 end
52 end
53
54 module:hook_global("server-cleanup", module.unload);
55
56 local curr_process = 0;
57 function send_query(text)
58 curr_process = (curr_process%auth_processes)+1;
59 local pty = ptys[curr_process];
60
61 local finished_with_pty
62 if not blocking then
63 finished_with_pty = guard(pty); -- Prevent others from crossing this line while we're busy
64 end
65 if not pty:hasproc() then
66 local status, ret = pty:exitstatus();
67 if status and (status ~= "exit" or ret ~= 0) then
68 log("warn", "Auth process exited unexpectedly with %s %d, restarting", status, ret or 0);
69 return nil;
70 end
71 local ok, err = pty:startproc(command);
72 if not ok then
73 log("error", "Failed to start auth process '%s': %s", command, err);
74 return nil;
75 end
76 log("debug", "Started auth process");
77 end
78
79 pty:send(text);
80 if blocking then
81 return pty:read(read_timeout);
82 else
83 local response;
84 local wait, done = waiter();
85 server.addevent(pty:getfd(), server.event.EV_READ, function ()
86 response = pty:read();
87 done();
88 return -1;
89 end);
90 wait();
91 finished_with_pty();
92 return response;
93 end
94 end
95
96 function do_query(kind, username, password)
97 if not username then return nil, "not-acceptable"; end
98
99 local query = (password and "%s:%s:%s:%s" or "%s:%s:%s"):format(kind, username, host, password);
100 local len = #query
101 if len > 1000 then return nil, "policy-violation"; end
102
103 if script_type == "ejabberd" then
104 local lo = len % 256;
105 local hi = (len - lo) / 256;
106 query = string.char(hi, lo)..query;
107 elseif script_type == "generic" then
108 query = query..'\n';
109 end
110
111 local response, err = send_query(query);
112 if not response then
113 log("warn", "Error while waiting for result from auth process: %s", err or "unknown error");
114 elseif (script_type == "ejabberd" and response == "\0\2\0\0") or
115 (script_type == "generic" and response:gsub("\r?\n$", "") == "0") then
116 return nil, "not-authorized";
117 elseif (script_type == "ejabberd" and response == "\0\2\0\1") or
118 (script_type == "generic" and response:gsub("\r?\n$", "") == "1") then
119 return true;
120 else
121 log("warn", "Unable to interpret data from auth process, %s",
122 (response:match("^error:") and response) or ("["..#response.." bytes]"));
123 return nil, "internal-server-error";
124 end
125 end
126
127 local provider = {};
128
129 function provider.test_password(username, password)
130 return do_query("auth", username, password);
131 end
132
133 function provider.set_password(username, password)
134 return do_query("setpass", username, password);
135 end
136
137 function provider.user_exists(username)
138 return do_query("isuser", username);
139 end
140
141 function provider.create_user(username, password) -- luacheck: ignore 212
142 return nil, "Account creation/modification not available.";
143 end
144
145 function provider.get_sasl_handler()
146 local testpass_authentication_profile = {
147 plain_test = function(sasl, username, password, realm)
148 return usermanager.test_password(username, realm, password), true;
149 end,
150 };
151 return new_sasl(host, testpass_authentication_profile);
152 end
153
154 module:provides("auth", provider);