Software /
code /
prosody-modules
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); |