view mod_client_proxy/mod_client_proxy.lua @ 5511:0860497152af

mod_http_oauth2: Record hash of client_id to allow future verification RFC 6819 section 5.2.2.2 states that refresh tokens MUST be bound to the client. In order to do that, we must record something that can definitely tie the client to the grant. Since the full client_id is so large (why we have this client_subset function), a hash is stored instead.
author Kim Alvefur <zash@zash.se>
date Fri, 02 Jun 2023 10:14:16 +0200
parents a14a573d43ff
children
line wrap: on
line source

if module:get_host_type() ~= "component" then
	error("proxy_component should be loaded as component", 0);
end

local jid_split = require "util.jid".split;
local jid_bare = require "util.jid".bare;
local jid_prep = require "util.jid".prep;
local st = require "util.stanza";
local array = require "util.array";

local target_address = module:get_option_string("target_address");

sessions = array{};
local sessions = sessions;

local function handle_target_presence(stanza)
	local type = stanza.attr.type;
	module:log("debug", "received presence from destination: %s", type)
	local _, _, resource = jid_split(stanza.attr.from);
	if type == "error" then
		-- drop all known sessions
		for k in pairs(sessions) do
			sessions[k] = nil
		end
		module:log(
			"debug",
			"received error presence, dropping all target sessions",
			resource
		)
	elseif type == "unavailable" then
		for k in pairs(sessions) do
			if sessions[k] == resource then
				sessions[k] = nil
				module:log(
					"debug",
					"dropped target session: %s",
					resource
				)
				break
			end
		end
	elseif not type then
		-- available
		local found = false;
		for k in pairs(sessions) do
			if sessions[k] == resource then
				found = true;
				break
			end
		end
		if not found then
			module:log(
				"debug",
				"registered new target session: %s",
				resource
			)
			sessions:push(resource)
		end
	end
end

local function handle_from_target(stanza)
	local type = stanza.attr.type
	module:log(
		"debug",
		"non-presence stanza from target: name = %s, type = %s",
		stanza.name,
		type
	)
	if stanza.name == "iq" then
		if type == "error" or type == "result" then
			-- de-NAT message
			local _, _, denatted_to_unprepped = jid_split(stanza.attr.to);
			local denatted_to = jid_prep(denatted_to_unprepped);
			if not denatted_to then
				module:log(
					"debug",
					"cannot de-NAT stanza, invalid to: %s",
					denatted_to_unprepped
				)
				return
			end
			local denatted_from = module:get_host();

			module:log(
				"debug",
				"de-NAT-ed stanza: from: %s -> %s, to: %s -> %s",
				stanza.attr.from,
				denatted_from,
				stanza.attr.to,
				denatted_to
			)

			stanza.attr.from = denatted_from
			stanza.attr.to = denatted_to

			module:send(stanza)
		else
			-- FIXME: we don’t support NATing outbund requests atm.
			module:send(st.error_reply(stanza, "cancel", "feature-not-implemented"))
		end
	elseif stanza.name == "message" then
		-- not implemented yet, we need a way to ensure that routing doesn’t
		-- break
		module:send(st.error_reply(stanza, "cancel", "feature-not-implemented"))
	end
end

local function handle_to_target(stanza)
	local type = stanza.attr.type;
	module:log(
		"debug",
		"stanza to target: name = %s, type = %s",
		stanza.name, type
	)
	if stanza.name == "presence" then
		if type ~= "error" then
			module:send(st.error_reply(stanza, "cancel", "bad-request"))
			return
		end
	elseif stanza.name == "iq" then
		if type == "get" or type == "set" then
			if #sessions == 0 then
				-- no sessions available to send to
				module:log("debug", "no sessions to send to!")
				module:send(st.error_reply(stanza, "cancel", "service-unavailable"))
				return
			end

			-- find a target session
			local target_session = sessions:random()
			local target = target_address .. "/" .. target_session

			-- encode sender JID in resource
			local natted_from = module:get_host() .. "/" .. stanza.attr.from;

			module:log(
				"debug",
				"NAT-ed stanza: from: %s -> %s, to: %s -> %s",
				stanza.attr.from,
				natted_from,
				stanza.attr.to,
				target
			)

			stanza.attr.from = natted_from
			stanza.attr.to = target

			module:send(stanza)
		end
		-- FIXME: handle and forward result/error correctly
	elseif stanza.name == "message" then
		-- not implemented yet, we need a way to ensure that routing doesn’t
		-- break
		module:send(st.error_reply(stanza, "cancel", "feature-not-implemented"))
	end
end

local function stanza_handler(event)
	local origin, stanza = event.origin, event.stanza
	module:log("debug", "received stanza from %s session", origin.type)

	local bare_from = jid_bare(stanza.attr.from);
	local _, _, to = jid_split(stanza.attr.to);
	if bare_from == target_address then
		-- from our target, to whom?
		if not to then
			-- directly to component
			if stanza.name == "presence" then
				handle_target_presence(stanza)
			else
				module:send(st.error_reply(stanza, "cancel", "bad-request"))
				return true
			end
		else
			-- to someone else
			handle_from_target(stanza)
		end
	else
		handle_to_target(stanza)
	end
	return true
end

module:hook("iq/bare", stanza_handler, -1);
module:hook("message/bare", stanza_handler, -1);
module:hook("presence/bare", stanza_handler, -1);
module:hook("iq/full", stanza_handler, -1);
module:hook("message/full", stanza_handler, -1);
module:hook("presence/full", stanza_handler, -1);
module:hook("iq/host", stanza_handler, -1);
module:hook("message/host", stanza_handler, -1);
module:hook("presence/host", stanza_handler, -1);

module:log("debug", "loaded proxy on %s", module:get_host())

subscription_request = st.presence({
	type = "subscribe",
	to = target_address,
	from = module:get_host()}
)
module:send(subscription_request)