local st = require "prosody.util.stanza"; local jid_bare = require "prosody.util.jid".bare; local xmlns_privilege = "urn:xmpp:privilege:2"; local xmlns_forward = "urn:xmpp:forward:0"; local xmlns_carbons = "urn:xmpp:carbons:2"; local bare_sessions = prosody.bare_sessions; local allowed_component_jid = module:get_option_string("privileged_entity_jid"); module:log("info", "mod_privileged_carbons loaded for host=%s privileged_entity_jid=%s", module.host or "?", allowed_component_jid or ""); local function iter_sessions(user_sessions) if not user_sessions or not user_sessions.sessions then return function() return nil end; end return pairs(user_sessions.sessions); end local function unwrap_forwarded_message(stanza) local privilege = stanza:get_child("privilege", xmlns_privilege); if not privilege then return nil; end local forwarded = privilege:get_child("forwarded", xmlns_forward); if not forwarded then return nil; end for _, tag in ipairs(forwarded.tags or {}) do if tag.name == "message" then return tag; end end return nil; end local function is_carbon_delivery(inner) if not inner then return false; end return inner:get_child("sent", xmlns_carbons) ~= nil or inner:get_child("received", xmlns_carbons) ~= nil; end local function build_sent_carbon(inner, user_bare) local function rebuild_stanza(node) if type(node) ~= "table" or not node.name then return node; end local attr = {}; for k, v in pairs(node.attr or {}) do attr[k] = v; end local rebuilt = st.stanza(node.name, attr); for _, child in ipairs(node) do if type(child) == "table" and child.name then rebuilt:add_direct_child(rebuild_stanza(child)); elseif type(child) == "string" then rebuilt:add_direct_child(child); end end return rebuilt; end local copy = rebuild_stanza(inner); local function normalize_client_ns(node) if not node then return; end if node.attr then if node.attr.xmlns == nil or node.attr.xmlns == "jabber:component:accept" then node.attr.xmlns = "jabber:client"; end end if node.tags then for _, child in ipairs(node.tags) do normalize_client_ns(child); end end end normalize_client_ns(copy); return st.message({ from = user_bare, type = inner.attr.type or "chat", xmlns = "jabber:client" }) :tag("sent", { xmlns = xmlns_carbons }) :tag("forwarded", { xmlns = xmlns_forward }) :add_child(copy):reset(); end local function deliver_carbon_to_user_sessions(bare_jid, inner) local user_sessions = bare_sessions[bare_jid]; if not user_sessions then module:log("debug", "Privileged carbon skipped for offline user %s", bare_jid); return true; end local carbon = build_sent_carbon(inner, bare_jid); local delivered = false; for _, session in iter_sessions(user_sessions) do if session.full_jid and session.send then local copy = st.clone(carbon); copy.attr.xmlns = "jabber:client"; copy.attr.to = session.full_jid; session.rawsend(tostring(copy)); delivered = true; end end module:log("info", "Privileged carbon delivered user=%s delivered=%s", bare_jid, tostring(delivered)); return true; end local function handle_privileged_carbon(event) local origin, stanza = event.origin, event.stanza; if not origin or origin.type ~= "component" then return nil; end if allowed_component_jid and allowed_component_jid ~= "" and origin.host ~= allowed_component_jid then module:log("debug", "Ignoring privileged message from unexpected component %s", origin.host or "?"); return nil; end local inner = unwrap_forwarded_message(stanza); if not inner then module:log("debug", "Ignoring privileged message without forwarded payload from %s", origin.host or "?"); return nil; end local bare_from = jid_bare(inner.attr.from); if not bare_from or bare_from == "" then module:log("warn", "Rejected malformed privileged carbon from %s", origin.host or "?"); return true; end if bare_from:match("@(.+)$") ~= module.host then module:log("debug", "Ignoring privileged carbon for remote host %s", bare_from); return nil; end return deliver_carbon_to_user_sessions(bare_from, inner); end module:hook("pre-message/host", handle_privileged_carbon, 10); module:hook("message/host", handle_privileged_carbon, 10);