view mod_auth_ha1/mod_auth_ha1.lua @ 5401:c8d04ac200fc

mod_http_oauth2: Reject loopback URIs as client_uri This really should be a proper website with info, https://localhost is not good enough. Ideally we'd validate that it's got proper DNS and is actually reachable, but triggering HTTP or even DNS lookups seems like it would carry abuse potential that would best to avoid.
author Kim Alvefur <zash@zash.se>
date Tue, 02 May 2023 16:20:55 +0200
parents 31c4d92a81e5
children
line wrap: on
line source

-- Prosody IM
-- Copyright (C) 2014 Matthew Wild
--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--

local usermanager = require "core.usermanager";
local new_sasl = require "util.sasl".new;

local nodeprep = require "util.encodings".stringprep.nodeprep;
local nameprep = require "util.encodings".stringprep.nameprep;
local md5 = require "util.hashes".md5;

local host = module.host;

local auth_filename = module:get_option_string("auth_ha1_file", "auth.txt");
local auth_data = {};

function reload_auth_data()
	local f, err = io.open(auth_filename);
	if not f then
		module:log("error", "Failed to read from auth file: %s", err);
		return;
	end
	auth_data = {};
	local line_number, imported_count, not_authorized_count = 0, 0, 0;
	for line in f:lines() do
		line_number = line_number + 1;
		local username, hash, realm, state = line:match("^([^:]+):(%x+):([^:]+):(.+)$");
		if not username then
			if line:sub(1,1) ~= "#" then
				module:log("error", "Unable to parse line %d of auth file, skipping", line_number);
			end
		else
			username, realm = nodeprep(username), nameprep(realm);
			if not username then
				module:log("error", "Invalid username on line %d of auth file, skipping", line_number);
			elseif not realm then
				module:log("error", "Invalid hostname/realm on line %d of auth file, skipping", line_number);
			elseif state ~= "authorized" then
				not_authorized_count = not_authorized_count + 1;
			elseif realm == host then
				auth_data[username] = hash;
				imported_count = imported_count + 1;
			end
		end
	end
	f:close();
	module:log("debug", "Loaded %d accounts from auth file (%d authorized)", imported_count, imported_count-not_authorized_count);
end

function module.load()
	reload_auth_data();
end

module:hook_global("config-reloaded", reload_auth_data);

-- define auth provider
local provider = {};

function provider.test_password(username, password)
	module:log("debug", "test password for user %s at host %s, %s", username, host, password);

	local test_hash = md5(username..":"..host..":"..password, true);

	if test_hash == auth_data[username] then
		return true;
	else
		return nil, "Auth failed. Invalid username or password.";
	end
end

function provider.set_password(username, password)
	return nil, "Changing passwords not supported";
end

function provider.user_exists(username)
	if not auth_data[username] then
		module:log("debug", "account not found for username '%s' at host '%s'", username, host);
		return nil, "Auth failed. Invalid username";
	end
	return true;
end

function provider.create_user(username, password)
	return nil, "User creation not supported";
end

function provider.delete_user(username)
	return nil , "User deletion not supported";
end

function provider.get_sasl_handler()
	return new_sasl(host, {
		plain_test = function(sasl, username, password, realm)
			return usermanager.test_password(username, realm, password), true;
		end
	});
end

module:provides("auth", provider);