Mercurial > prosody-modules
changeset 6304:6e2458b69e33
mod_muc_rtbl: Use mod_rtbl, adds support for multiple list subscriptions
| author | Matthew Wild <mwild1@gmail.com> |
|---|---|
| date | Wed, 30 Jul 2025 14:16:55 +0100 |
| parents | d1d774f79ea8 |
| children | 1c0e9486ba23 |
| files | mod_muc_rtbl/README.md mod_muc_rtbl/mod_muc_rtbl.lua |
| diffstat | 2 files changed, 63 insertions(+), 108 deletions(-) [+] |
line wrap: on
line diff
--- a/mod_muc_rtbl/README.md Thu Jul 31 15:07:30 2025 +0100 +++ b/mod_muc_rtbl/README.md Wed Jul 30 14:16:55 2025 +0100 @@ -2,7 +2,7 @@ summary: rockspec: dependencies: - - mod_pubsub_subscription + - mod_rtbl labels: - Stage-Alpha ... @@ -23,17 +23,24 @@ } ``` -Then there are two options, which must be set under the component or in the -global section of your config: +Then you should configure one or more lists you want to subscribe to: +```lua +muc_rtbls = { + "xmpp:rtbl.example?;node=muc_bans_sha256"; +} ``` -muc_rtbl_jid = "rtbl.example" -muc_rtbl_node = "muc_bans_sha256" -``` + +If an unaffiliated JID matches an entry found in any of the configured lists, +this module will block: + +- Joins +- Group chat messages +- Private messages # Compatibility -Should work with Prosody >= 0.11.x +Should work with Prosody >= 0.12.x # Developers
--- a/mod_muc_rtbl/mod_muc_rtbl.lua Thu Jul 31 15:07:30 2025 +0100 +++ b/mod_muc_rtbl/mod_muc_rtbl.lua Wed Jul 30 14:16:55 2025 +0100 @@ -1,104 +1,52 @@ -local array = require "util.array"; -local it = require "util.iterators"; local jid = require "util.jid"; local sha256 = require "util.hashes".sha256; -local set = require "util.set"; local st = require "util.stanza"; -local rtbl_service_jid = assert(module:get_option_string("muc_rtbl_jid"), "No RTBL JID supplied"); +local url = require "socket.url"; + +-- Legacy config (single list only) +local rtbl_service_jid = module:get_option_string("muc_rtbl_jid"); local rtbl_node = module:get_option_string("muc_rtbl_node", "muc_bans_sha256"); -local banned_hashes = module:shared("banned_hashes"); - -module:depends("pubsub_subscription"); - -module:add_item("pubsub-subscription", { - service = rtbl_service_jid; - node = rtbl_node; - - -- Callbacks: - on_subscribed = function() - module:log("info", "RTBL active"); - end; - - on_error = function(err) - module:log("error", "Failed to subscribe to RTBL: %s::%s: %s", err.type, err.condition, err.text); - end; - - on_item = function(event) - local hash = event.item.attr.id; - if not hash then return; end - module:log("debug", "Received new hash: %s", hash); - banned_hashes[hash] = true; - end; - - on_retract = function (event) - local hash = event.item.attr.id; - if not hash then return; end - module:log("debug", "Retracted hash: %s", hash); - banned_hashes[hash] = nil; - end; - - purge = function() - module:log("debug", "Purge all hashes"); - for hash in pairs(banned_hashes) do - banned_hashes[hash] = nil; - end - end; +-- Multi-list config +local rtbl_config = module:get_option_array("muc_rtbls", { + rtbl_service_jid and rtbl_node and ("xmpp:"..rtbl_service_jid.."?;node="..rtbl_node) or nil; }); -function request_list() - local items_request = st.iq({ to = rtbl_service_jid, from = module.host, type = "get", id = "rtbl-request" }) - :tag("pubsub", { xmlns = "http://jabber.org/protocol/pubsub" }) - :tag("items", { node = rtbl_node }):up() - :up(); - - module:send(items_request); +if #rtbl_config == 0 then + return error("No RTBLs configured"); end -function update_list(event) - local from_jid = event.stanza.attr.from; - if from_jid ~= rtbl_service_jid then - module:log("debug", "Ignoring RTBL response from unknown sender"); - return; - end - local items_el = event.stanza:find("{http://jabber.org/protocol/pubsub}pubsub/items"); - if not items_el then - module:log("warn", "Invalid items response from RTBL service"); - return; +local mod_rtbl = module:depends("rtbl"); + +local lists = {}; + +local function parse_xmpp_uri(uri) + local parsed = url.parse(uri); + if parsed.scheme ~= "xmpp" then + return nil, "unexpected-scheme"; end - local old_entries = set.new(array.collect(it.keys(banned_hashes))); - - local n_added, n_removed, n_total = 0, 0, 0; - for item in items_el:childtags("item") do - local hash = item.attr.id; - if hash then - n_total = n_total + 1; - if not old_entries:contains(hash) then - -- New entry - n_added = n_added + 1; - banned_hashes[hash] = true; - else - -- Entry already existed - old_entries:remove(hash); - end - end + local parsed_query = {}; + for kv in (parsed.query or ""):gmatch("[^?;]+") do + local k, v = kv:match("^([^=]+)=?(.*)$"); + parsed_query[k] = v; end - -- Remove old entries that weren't in the received list - for hash in old_entries do - n_removed = n_removed + 1; - banned_hashes[hash] = nil; - end - - module:log("info", "%d RTBL entries received from %s (%d added, %d removed)", n_total, from_jid, n_added, n_removed); - return true; + return { + jid = jid.prep(parsed.path); + params = parsed_query; + }; end -module:hook("iq-result/host/rtbl-request", update_list); +for _, rtbl_uri in ipairs(rtbl_config) do + local uri = parse_xmpp_uri(rtbl_uri); + module:log("debug", "Subscribing to %q node %q", uri.jid, uri.params.node); + local rtbl = mod_rtbl.new_rtbl_subscription(uri.jid, uri.params.node, {}); + table.insert(lists, rtbl); +end -function update_hashes(occupant) +local function update_occupant_hashes(occupant) local bare_hash, host_hash; if not occupant.mod_muc_rtbl_bare_hash then bare_hash = sha256(jid.bare(occupant.bare_jid), true); @@ -112,12 +60,20 @@ else host_hash = occupant.mod_muc_rtbl_host_hash; end - return bare_hash, host_hash + return bare_hash, host_hash; +end + +local function is_banned_occupant(occupant) + local bare_hash, host_hash = update_occupant_hashes(occupant); + for _, list in ipairs(lists) do + local items = list.items; + if items[bare_hash] or items[host_hash] then + return true; + end + end end module:hook("muc-occupant-pre-join", function (event) - if next(banned_hashes) == nil then return end - local from_bare = jid.bare(event.stanza.attr.from); local affiliation = event.room:get_affiliation(from_bare); @@ -126,8 +82,7 @@ return; end - local bare_hash, host_hash = update_hashes(event.occupant); - if banned_hashes[bare_hash] or banned_hashes[host_hash] then + if is_banned_occupant(event.occupant) then module:log("info", "Blocked user <%s> from room <%s> due to RTBL match", from_bare, event.stanza.attr.to); local error_reply = st.error_reply(event.stanza, "cancel", "forbidden", "You are banned from this service", event.room.jid); event.origin.send(error_reply); @@ -136,14 +91,14 @@ end); module:hook("muc-occupant-groupchat", function(event) - local affiliation = event.room:get_affiliation(event.occupant.bare_jid); + local occupant = event.occupant; + local affiliation = event.room:get_affiliation(occupant.bare_jid); if affiliation and affiliation ~= "none" then -- Skip check for affiliated users return; end - local bare_hash, host_hash = update_hashes(event.occupant); - if banned_hashes[bare_hash] or banned_hashes[host_hash] then + if is_banned_occupant(occupant) then module:log("debug", "Blocked message from user <%s> to room <%s> due to RTBL match", event.stanza.attr.from, event.stanza.attr.to); local error_reply = st.error_reply(event.stanza, "cancel", "forbidden", "You are banned from this service", event.room.jid); event.origin.send(error_reply); @@ -159,8 +114,7 @@ return; end - local bare_hash, host_hash = update_hashes(occupant); - if banned_hashes[bare_hash] or banned_hashes[host_hash] then + if is_banned_occupant(occupant) then module:log("debug", "Blocked private message from user <%s> from room <%s> due to RTBL match", occupant.bare_jid, event.stanza.attr.to); local error_reply = st.error_reply(event.stanza, "cancel", "forbidden", "You are banned from this service", event.room.jid); event.origin.send(error_reply); @@ -168,10 +122,4 @@ end end); -if prosody.start_time then - request_list(); -else - module:hook_global("server-started", function () - request_list(); - end); -end +module.environment.lists = lists
