# HG changeset patch # User Matthew Wild # Date 1678901936 0 # Node ID e46ac57fa60b69262129bee1e5900a1d315ec020 # Parent 3420808b8d3f499a67f155e93216126edb9e098e rtbl_guard: New plugin to subscribe to RTBLs and act on them in MUCs This allows applying an RTBL to a MUC that doesn't have built-in RTBL support. Note that it doesn't currently take any action when an entry is removed from an RTBL. diff -r 3420808b8d3f -r e46ac57fa60b plugins/rtbl_guard.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/rtbl_guard.lua Wed Mar 15 17:38:56 2023 +0000 @@ -0,0 +1,201 @@ +local jid_bare = require "util.jid".bare; +local jid_prep = require "util.jid".prep; +local sha256 = require "util.hashes".sha256; + +local function get_ban_reason(blocklist, entry) + local msg = { ("Banned by %s"):format(blocklist.name or "RTBL"), nil, nil }; + + local reason = entry.reason:match("^urn:xmpp:reporting:(%w+)$"); + if reason then + msg[2] = "for "..reason; + end + + if entry.text then + msg[#msg+1] = "("..entry.text..")"; + end + + return table.concat(msg, " "); +end + +function riddim.plugins.rtbl_guard(bot) + bot.stream:add_plugin("pubsub"); + + local config = bot.config.rtbl_guard; + if not config then return; end + + -- { + -- [rtbl_name] = { + -- entries = { + -- [jid/hash] = { + -- reason = + -- text = + -- } + -- } + -- } + local blocklists = {}; + + local function check_blocklists(jid, only_blocklist, only_entry_id) + jid = jid_bare(jid_prep(jid)); + local hash = sha256(jid, true); + + if only_blocklist and only_entry_id then + if only_entry_id ~= jid and only_entry_id ~= hash then + bot.stream:debug("No match for single entry %q == (%q | %q)", only_entry_id, jid, hash) + return; + end + return only_blocklist.entries[only_entry_id], only_blocklist; + end + + for rtbl_id, blocklist in pairs(only_blocklist and {only_blocklist} or blocklists) do + bot.stream:debug("Checking %s for (%q | %q)", rtbl_id, jid, hash); + for k in pairs(blocklist.entries) do + bot.stream:debug("[%q]", k); + end + local entry = blocklist.entries[hash] or blocklist.entries[jid]; + if entry then + return entry, blocklist; + end + end + end + + local function guard_room(room) + + local function process_occupant(occupant, only_blocklist, only_entry_id) + if not occupant.real_jid then + bot.stream:debug("Unable to determine real JID for %s - skipping RTBL checks", occupant.nick); + return; + end + + local matched_entry, matched_blocklist = check_blocklists(occupant.real_jid, only_blocklist, only_entry_id); + if not matched_entry then + bot.stream:debug("%s is not on any RTBLs", occupant.nick); + return; + end + + room:ban(occupant.nick, get_ban_reason(matched_blocklist, matched_entry)); + end + + -- Check future occupants when they join + room:hook("occupant-joined", process_occupant); + + local function process_all_occupants(blocklist, entry_id) + bot.stream:debug("Checking all occupants of %s against RTBL (%s)", room.jid, entry_id or "all entries"); + for _, occupant in pairs(room.occupants) do + process_occupant(occupant, blocklist, entry_id); + end + end + + -- Check existing occupants against all existing entries + process_all_occupants(); + + -- Check existing occupants when new entries are added + room:hook("rtbl-entry-added", function (event) + process_all_occupants(event.blocklist, event.entry_id); + end); + room:hook("rtbl-updated", function (event) + process_all_occupants(event.blocklist); + end); + end + + local function handle_entry(blocklist, item) + local report = item:get_child("report", "urn:xmpp:reporting:1"); + if not report then return; end + blocklist.entries[item.attr.id] = { + reason = report.attr.reason; + text = report:get_child_text("text"); + }; + return blocklist.entries[item.attr.id]; + end + + local function clear_entry(blocklist, retract) + local cleared_entry = blocklist.entries[retract.attr.id]; + if not cleared_entry then + return; + end + blocklist.entries[retract.attr.id] = nil; + return cleared_entry; + end + + bot.stream:hook("pubsub/event", function (event) + local blocklist_id = event.from.."::"..event.node; + local blocklist = blocklists[blocklist_id]; + if not blocklist then return; end + local entry = handle_entry(blocklist, event.item); + for _, room in pairs(bot.rooms) do + room:event("rtbl-entry-added", { + blocklist = blocklist; + entry = entry; + entry_id = event.item.attr.id; + }); + end + end); + + -- COMPAT: current verse does not emit an event for retraction + bot.stream:hook("message", function (message) + local m_from = message.attr.from; + for pubsub_event in message:childtags("event", "http://jabber.org/protocol/pubsub#event") do + local items = pubsub_event:get_child("items"); + if items then + local node = items.attr.node; + local blocklist_id = m_from.."::"..node; + local blocklist = blocklists[blocklist_id]; + if not blocklist then return; end + + for retract in items:childtags("retract") do + local entry = clear_entry(blocklist, retract); + if entry then + for _, room in pairs(bot.rooms) do + room:event("rtbl-entry-removed", { + blocklist = blocklist; + entry = entry; + }); + end + end + end + end + end + end); + + bot:hook("started", function () + for _, rtbl_config in ipairs(config) do + local host, node = rtbl_config.host, rtbl_config.node; + if host and node then + bot.stream.pubsub(host, node):subscribe(nil, nil, function (response) + if response.attr.type ~= "result" then + bot.stream:warn("Failed to subscribe to RTBL %s::%s", host, node); + return; + end + bot.stream:info("Subscribed to RTBL %s::%s", host, node); + + local blocklist = { name = rtbl_config.name, entries = {} }; + bot.stream.pubsub(host, node):items(true, function (items_response) + if items_response.attr.type ~= "result" then + bot.stream:warn("Failed to synchronize with RTBL: %s", tostring(items_response)); + return; + end + local items = items_response.tags[1].tags[1]; + bot.stream:debug("RTBL sync: %s", items); + for item in items:childtags("item") do + bot.stream:debug("RTBL item: %s", item); + handle_entry(blocklist, item); + end + + for _, room in pairs(bot.rooms) do + room:event("rtbl-updated", { + blocklist = blocklist; + }); + end + end); + local blocklist_id = host.."::"..node; + blocklists[blocklist_id] = blocklist; + end); + end + end + + for _, room in pairs(bot.rooms) do + guard_room(room); + end + + bot:hook("groupchat/joined", guard_room); + end); +end diff -r 3420808b8d3f -r e46ac57fa60b squishy --- a/squishy Wed Mar 15 12:19:39 2023 +0000 +++ b/squishy Wed Mar 15 17:38:56 2023 +0000 @@ -19,6 +19,7 @@ "opdown", "pubsub2room", "rtbl_admin", + "rtbl_guard", "simple_commands", "slap", "topic",