view mod_muc_adhoc_bots/mod_muc_adhoc_bots.lua @ 6029:cc665f343690

mod_firewall: SUBSCRIBED: Flip subscription check to match documentation The documentation claims that this condition checks whether the recipient is subscribed to the sender. However, it was using the wrong method, and actually checking whether the sender was subscribed to the recipient. A quick poll of folk suggested that the documentation's approach is the right one, so this should fix the code to match the documentation. This should also fix the bundled anti-spam rules from blocking presence from JIDs that you subscribe do (but don't have a mutual subscription with).
author Matthew Wild <mwild1@gmail.com>
date Fri, 22 Nov 2024 13:50:48 +0000
parents 8b868c00e38e
children
line wrap: on
line source

local jid = require "util.jid";
local json = require "util.json";
local promise = require "util.promise";
local st = require "util.stanza";
local uuid = require "util.uuid";

local xmlns_cmd = "http://jabber.org/protocol/commands";

module:hook("muc-disco#info", function(event)
	event.reply:tag("feature", {var = xmlns_cmd}):up();
end);

module:hook("iq-get/bare/http://jabber.org/protocol/disco#items:query", function (event)
	local room = prosody.hosts[module:get_host()].modules.muc.get_room_from_jid(event.stanza.attr.to);
	local occupant = room:get_occupant_by_real_jid(event.stanza.attr.from)
	if event.stanza.tags[1].attr.node ~= xmlns_cmd or not occupant then
		return
	end

	local bots = module:get_option_array("adhoc_bots", {})
	bots:map(function(bot)
		return module:send_iq(
			st.iq({ type = "get", id = uuid.generate(), to = bot, from = room:get_occupant_jid(event.stanza.attr.from) })
				:tag("query", { xmlns = "http://jabber.org/protocol/disco#items", node = xmlns_cmd }):up(),
			nil,
			5
		)
	end)

	promise.all_settled(bots):next(function (bot_commands)
		local reply = st.reply(event.stanza):query("http://jabber.org/protocol/disco#items")
		for i, one_bot_reply in ipairs(bot_commands) do
			if one_bot_reply.status == "fulfilled"	then
			local query = one_bot_reply.value.stanza:get_child("query", "http://jabber.org/protocol/disco#items")
				if query then
					-- Should use query:childtags("item") but it doesn't work
					for j,item in ipairs(query.tags) do
						item.attr.node = json.encode({ jid = item.attr.jid, node = item.attr.node })
						item.attr.jid = event.stanza.attr.to
						reply:add_child(item)
					end
				end
			end
		end
		event.origin.send(reply:up())
	end):catch(function (e)
		module:log("error", e)
	end)

	return true;
end, 500);

local function is_adhoc_bot(jid)
	for i, bot_jid in ipairs(module:get_option_array("adhoc_bots", {})) do
		if jid == bot_jid then
			return true
		end
	end

	return false
end

module:hook("iq-set/bare/"..xmlns_cmd..":command", function (event)
	local origin, stanza = event.origin, event.stanza;
	local node = stanza.tags[1].attr.node
	local meta = json.decode(node)
	local room = prosody.hosts[module:get_host()].modules.muc.get_room_from_jid(stanza.attr.to);
	local occupant = room:get_occupant_by_real_jid(event.stanza.attr.from)
	if meta and occupant and is_adhoc_bot(meta.jid) then
		local fwd = st.clone(stanza)
		fwd.attr.to = meta.jid
		fwd.attr.from = room:get_occupant_jid(event.stanza.attr.from)
		local command = fwd:get_child("command", "http://jabber.org/protocol/commands")
		command.attr.node = meta.node
		module:send_iq(fwd):next(function(response)
			local response_command = response.stanza:get_child("command", "http://jabber.org/protocol/commands")
			response.stanza.attr.from = stanza.attr.to
			response.stanza.attr.to = stanza.attr.from
			response_command.attr.node = node
			origin.send(response.stanza)
		end):catch(function (e)
			module:log("error", e)
		end)

		return true
	end

	return
end, 500);

local function clean_xmlns(node)
		-- Recursively remove "jabber:client" attribute from node.
		-- In Prosody internal routing, xmlns should not be set.
		-- Keeping xmlns would lead to issues like mod_smacks ignoring the outgoing stanza,
		-- so we remove all xmlns attributes with a value of "jabber:client"
		if node.attr.xmlns == 'jabber:client' then
				for childnode in node:childtags() do
						clean_xmlns(childnode)
				end
				node.attr.xmlns = nil
		end
end

module:hook("message/bare", function (event)
	local origin, stanza = event.origin, event.stanza;
	if not is_adhoc_bot(stanza.attr.from) then return; end
	local room = prosody.hosts[module:get_host()].modules.muc.get_room_from_jid(stanza.attr.to);
	if room == nil then return; end
	local privilege = stanza:get_child("privilege", "urn:xmpp:privilege:2")
	if privilege == nil then return; end
	local fwd = privilege:get_child("forwarded", "urn:xmpp:forward:0")
	if fwd == nil then return; end
	local message = fwd:get_child("message", "jabber:client")
	if message == nil then return; end
	if message.attr.to ~= stanza.attr.to or jid.bare(message.attr.from) ~= stanza.attr.to then
		return
	end

	clean_xmlns(message)
	room:broadcast_message(message)
	return true
end)