Mercurial > prosody-modules
view mod_auth_external/mod_auth_external.lua @ 3503:882180b459a0
mod_pubsub_post: Restructure authentication and authorization (BC)
This deprecates the default "superuser" actor model and makes the
default equivalent to the previous "request.id".
A single actor and secret per node is supported because HTTP and
WebHooks don't normally include any authorization identity.
Allowing authentication bypass when no secret is given should be
relatively safe when the actor is unprivileged, as will be unless
explicitly configured otherwise.
author | Kim Alvefur <zash@zash.se> |
---|---|
date | Sat, 30 Mar 2019 21:16:13 +0100 |
parents | c458f940b011 |
children | 11cd6e034fd3 |
line wrap: on
line source
-- -- Prosody IM -- Copyright (C) 2010 Waqas Hussain -- Copyright (C) 2010 Jeff Mitchell -- Copyright (C) 2013 Mikael Nordfeldth -- Copyright (C) 2013 Matthew Wild, finally came to fix it all -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local lpty = assert(require "lpty", "mod_auth_external requires lpty: https://modules.prosody.im/mod_auth_external.html#installation"); local usermanager = require "core.usermanager"; local new_sasl = require "util.sasl".new; local server = require "net.server"; local have_async, async = pcall(require, "util.async"); local log = module._log; local host = module.host; local script_type = module:get_option_string("external_auth_protocol", "generic"); local command = module:get_option_string("external_auth_command", ""); local read_timeout = module:get_option_number("external_auth_timeout", 5); local blocking = module:get_option_boolean("external_auth_blocking", true); -- non-blocking is very experimental local auth_processes = module:get_option_number("external_auth_processes", 1); assert(script_type == "ejabberd" or script_type == "generic", "Config error: external_auth_protocol must be 'ejabberd' or 'generic'"); assert(not host:find(":"), "Invalid hostname"); if not blocking then log("debug", "External auth in non-blocking mode, yay!") waiter, guard = async.waiter, async.guarder(); elseif auth_processes > 1 then log("warn", "external_auth_processes is greater than 1, but we are in blocking mode - reducing to 1"); auth_processes = 1; end local ptys = {}; local pty_options = { throw_errors = false, no_local_echo = true, use_path = false }; for i = 1, auth_processes do ptys[i] = lpty.new(pty_options); end function module.unload() for i = 1, auth_processes do ptys[i]:endproc(); end end module:hook_global("server-cleanup", module.unload); local curr_process = 0; function send_query(text) curr_process = (curr_process%auth_processes)+1; local pty = ptys[curr_process]; local finished_with_pty if not blocking then finished_with_pty = guard(pty); -- Prevent others from crossing this line while we're busy end if not pty:hasproc() then local status, ret = pty:exitstatus(); if status and (status ~= "exit" or ret ~= 0) then log("warn", "Auth process exited unexpectedly with %s %d, restarting", status, ret or 0); return nil; end local ok, err = pty:startproc(command); if not ok then log("error", "Failed to start auth process '%s': %s", command, err); return nil; end log("debug", "Started auth process"); end pty:send(text); if blocking then return pty:read(read_timeout); else local response; local wait, done = waiter(); server.addevent(pty:getfd(), server.event.EV_READ, function () response = pty:read(); done(); return -1; end); wait(); finished_with_pty(); return response; end end function do_query(kind, username, password) if not username then return nil, "not-acceptable"; end local query = (password and "%s:%s:%s:%s" or "%s:%s:%s"):format(kind, username, host, password); local len = #query if len > 1000 then return nil, "policy-violation"; end if script_type == "ejabberd" then local lo = len % 256; local hi = (len - lo) / 256; query = string.char(hi, lo)..query; elseif script_type == "generic" then query = query..'\n'; end local response, err = send_query(query); if not response then log("warn", "Error while waiting for result from auth process: %s", err or "unknown error"); elseif (script_type == "ejabberd" and response == "\0\2\0\0") or (script_type == "generic" and response:gsub("\r?\n$", "") == "0") then return nil, "not-authorized"; elseif (script_type == "ejabberd" and response == "\0\2\0\1") or (script_type == "generic" and response:gsub("\r?\n$", "") == "1") then return true; else log("warn", "Unable to interpret data from auth process, %s", (response:match("^error:") and response) or ("["..#response.." bytes]")); return nil, "internal-server-error"; end end local provider = {}; function provider.test_password(username, password) return do_query("auth", username, password); end function provider.set_password(username, password) return do_query("setpass", username, password); end function provider.user_exists(username) return do_query("isuser", username); end function provider.create_user(username, password) -- luacheck: ignore 212 return nil, "Account creation/modification not available."; end function provider.get_sasl_handler() local testpass_authentication_profile = { plain_test = function(sasl, username, password, realm) return usermanager.test_password(username, realm, password), true; end, }; return new_sasl(host, testpass_authentication_profile); end module:provides("auth", provider);