File

plugins/mod_auth_ldap.lua @ 12894:0598d822614f 0.12 0.12.3

mod_websocket: Fire pre-session-close event (fixes #1800) This event was added in a7c183bb4e64 and is required to make mod_smacks know that a session was intentionally closed and shouldn't be hibernated (see fcea4d9e7502). Because this was missing from mod_websocket's session.close(), mod_smacks would always attempt to hibernate websocket sessions even if they closed cleanly. That mod_websocket has its own copy of session.close() is something to fix another day (probably not in the stable branch). So for now this commit makes the minimal change to get things working again. Thanks to Damian and the Jitsi team for reporting.
author Matthew Wild <mwild1@gmail.com>
date Mon, 20 Feb 2023 18:10:15 +0000
parent 11837:a37bf4497280
child 12642:9061f9621330
line wrap: on
line source

-- mod_auth_ldap

local jid_split = require "util.jid".split;
local new_sasl = require "util.sasl".new;
local lualdap = require "lualdap";

local function ldap_filter_escape(s)
	return (s:gsub("[*()\\%z]", function(c) return ("\\%02x"):format(c:byte()) end));
end

-- Config options
local ldap_server = module:get_option_string("ldap_server", "localhost");
local ldap_rootdn = module:get_option_string("ldap_rootdn", "");
local ldap_password = module:get_option_string("ldap_password", "");
local ldap_tls = module:get_option_boolean("ldap_tls");
local ldap_scope = module:get_option_string("ldap_scope", "subtree");
local ldap_filter = module:get_option_string("ldap_filter", "(uid=$user)"):gsub("%%s", "$user", 1);
local ldap_base = assert(module:get_option_string("ldap_base"), "ldap_base is a required option for ldap");
local ldap_mode = module:get_option_string("ldap_mode", "bind");
local ldap_admins = module:get_option_string("ldap_admin_filter",
	module:get_option_string("ldap_admins")); -- COMPAT with mistake in documentation
local host = ldap_filter_escape(module:get_option_string("realm", module.host));

-- Initiate connection
local ld = nil;
module.unload = function() if ld then pcall(ld, ld.close); end end

function ldap_do_once(method, ...)
	if ld == nil then
		local err;
		ld, err = lualdap.open_simple(ldap_server, ldap_rootdn, ldap_password, ldap_tls);
		if not ld then return nil, err, "reconnect"; end
	end

	-- luacheck: ignore 411/success
	local success, iterator, invariant, initial = pcall(ld[method], ld, ...);
	if not success then ld = nil; return nil, iterator, "search"; end

	local success, dn, attr = pcall(iterator, invariant, initial);
	if not success then ld = nil; return success, dn, "iter"; end

	return dn, attr, "return";
end

function ldap_do(method, retry_count, ...)
	local dn, attr, where;
	for _=1,1+retry_count do
		dn, attr, where = ldap_do_once(method, ...);
		if dn or not(attr) then break; end -- nothing or something found
		module:log("warn", "LDAP: %s %s (in %s)", tostring(dn), tostring(attr), where);
		-- otherwise retry
	end
	if not dn and attr then
		module:log("error", "LDAP: %s", tostring(attr));
	end
	return dn, attr;
end

function get_user(username)
	module:log("debug", "get_user(%q)", username);
	return ldap_do("search", 2, {
		base = ldap_base;
		scope = ldap_scope;
		sizelimit = 1;
		filter = ldap_filter:gsub("%$(%a+)", {
			user = ldap_filter_escape(username);
			host = host;
		});
	});
end

local provider = {};

function provider.create_user(username, password) -- luacheck: ignore 212
	return nil, "Account creation not available with LDAP.";
end

function provider.user_exists(username)
	return not not get_user(username);
end

function provider.set_password(username, password)
	local dn, attr = get_user(username);
	if not dn then return nil, attr end
	if attr.userPassword == password then return true end
	return ldap_do("modify", 2, dn, { '=', userPassword = password });
end

if ldap_mode == "getpasswd" then
	function provider.get_password(username)
		local dn, attr = get_user(username);
		if dn and attr then
			return attr.userPassword;
		end
	end

	function provider.test_password(username, password)
		return provider.get_password(username) == password;
	end

	function provider.get_sasl_handler()
		return new_sasl(module.host, {
			plain = function(sasl, username) -- luacheck: ignore 212/sasl
				local password = provider.get_password(username);
				if not password then return "", nil; end
				return password, true;
			end
		});
	end
elseif ldap_mode == "bind" then
	local function test_password(userdn, password)
		local ok, err = lualdap.open_simple(ldap_server, userdn, password, ldap_tls);
		if not ok then
			module:log("debug", "ldap open_simple error: %s", err);
		end
		return not not ok;
	end

	function provider.test_password(username, password)
		local dn = get_user(username);
		if not dn then return end
		return test_password(dn, password)
	end

	function provider.get_sasl_handler()
		return new_sasl(module.host, {
			plain_test = function(sasl, username, password) -- luacheck: ignore 212/sasl
				return provider.test_password(username, password), true;
			end
		});
	end
else
	module:log("error", "Unsupported ldap_mode %s", tostring(ldap_mode));
end

if ldap_admins then
	function provider.is_admin(jid)
		local username, user_host = jid_split(jid);
		if user_host ~= module.host then
			return false;
		end
		return ldap_do("search", 2, {
			base = ldap_base;
			scope = ldap_scope;
			sizelimit = 1;
			filter = ldap_admins:gsub("%$(%a+)", {
				user = ldap_filter_escape(username);
				host = host;
			});
		});
	end
end

module:provides("auth", provider);