# HG changeset patch # User Goffi # Date 1429297508 -7200 # Node ID b68eed25b880f4d2c94ef02377dd54fc8d5e5d24 # Parent 59ba224bf1459a6db1b175cda2bf69923986086c mod_delegation: iq forwarding to managing entity diff -r 59ba224bf145 -r b68eed25b880 mod_delegation/mod_delegation.lua --- a/mod_delegation/mod_delegation.lua Fri Apr 17 21:02:22 2015 +0200 +++ b/mod_delegation/mod_delegation.lua Fri Apr 17 21:05:08 2015 +0200 @@ -4,6 +4,10 @@ -- This module is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. +-- This module manage namespace delegation, a way to delegate server features +-- to an external entity/component. Only the admin mode is implemented so far + +-- TODO: client mode, managing entity error handling local jid = require("util/jid") local st = require("util/stanza") @@ -20,8 +24,8 @@ local connected_cb = delegation_session.connected_cb local _DELEGATION_NS = 'urn:xmpp:delegation:1' --- local _FORWARDED_NS = 'urn:xmpp:forward:0' - +local _FORWARDED_NS = 'urn:xmpp:forward:0' +local _ORI_ID_PREFIX = "IQ_RESULT_" module:log("debug", "Loading namespace delegation module "); @@ -31,14 +35,15 @@ local ns_delegations = module:get_option("delegations", {}) local jid2ns = {} -for namespace, config in pairs(ns_delegations) do +for namespace, ns_data in pairs(ns_delegations) do -- "connected" contain the full jid of connected managing entity - config.connected = nil - if config.jid then - if jid2ns[config.jid] == nil then - jid2ns[config.jid] = {} + ns_data.connected = nil + if ns_data.jid then + if jid2ns[ns_data.jid] == nil then + jid2ns[ns_data.jid] = {} end - jid2ns[config.jid][namespace] = config + jid2ns[ns_data.jid][namespace] = ns_data + module:log("debug", "Namespace %s is delegated%s to %s", namespace, ns_data.filtering and " (with filtering)" or "", ns_data.jid) else module:log("warn", "Ignoring delegation for %s: no jid specified", tostring(namespace)) ns_delegations[namespace] = nil @@ -57,12 +62,12 @@ -- and other to full jids, and several resources are connected. local have_delegation = false - for namespace, config in pairs(jid2ns[to_jid]) do - if config.connected == to_jid then + for namespace, ns_data in pairs(jid2ns[to_jid]) do + if ns_data.connected == to_jid then have_delegation = true message:tag("delegated", {namespace=namespace}) - if type(config.filtering) == "table" then - for _, attribute in pairs(config.filtering) do + if type(ns_data.filtering) == "table" then + for _, attribute in pairs(ns_data.filtering) do message:tag("attribute", {name=attribute}):up() end message:up() @@ -79,9 +84,9 @@ -- set the "connected" key for all namespace managed by entity_jid -- if the namespace has already a connected entity, ignore the new one local function set_config(jid_) - for _, config in pairs(jid2ns[jid_]) do - if config.connected == nil then - config.connected = entity_jid + for _, ns_data in pairs(jid2ns[jid_]) do + if ns_data.connected == nil then + ns_data.connected = entity_jid end end end @@ -132,3 +137,110 @@ connected_cb:add(on_component_connected) module:hook('component-authenticated', on_component_auth) module:hook('presence/initial', on_presence) + + +--> delegated namespaces hook <-- + +local function managing_ent_result(event) + -- this function manage iq results from the managing entity + -- it do a couple of security check before sending the + -- result to the managed entity + local session, stanza = event.origin, event.stanza + if stanza.attr.to ~= module.host then + module:log("warn", 'forwarded stanza result has "to" attribute not addressed to current host, id conflict ?') + return + end + module:unhook("iq-result/host/"..stanza.attr.id, managing_ent_result) + + -- lot of checks to do... + local delegation = stanza.tags[1] + if #stanza ~= 1 or delegation.name ~= "delegation" or + delegation.attr.xmlns ~= _DELEGATION_NS then + session.send(st.error_reply(stanza, 'modify', 'not-acceptable')) + return true + end + + local forwarded = delegation.tags[1] + if #delegation ~= 1 or forwarded.name ~= "forwarded" or + forwarded.attr.xmlns ~= _FORWARDED_NS then + session.send(st.error_reply(stanza, 'modify', 'not-acceptable')) + return true + end + + local iq = forwarded.tags[1] + if #forwarded ~= 1 or iq.name ~= "iq" or #iq ~= 1 then + session.send(st.error_reply(stanza, 'modify', 'not-acceptable')) + return true + end + + local namespace = iq.tags[1].xmlns + local ns_data = ns_delegations[namespace] + local original = ns_data[_ORI_ID_PREFIX..stanza.attr.id] + + if stanza.attr.from ~= ns_data.connected or iq.attr.type ~= "result" or + iq.attr.id ~= original.attr.id or iq.attr.to ~= original.attr.from then + session.send(st.error_reply(stanza, 'auth', 'forbidden')) + module:send(st.error_reply(original, 'cancel', 'service-unavailable')) + return true + end + + -- at this point eveything is checked, + -- and we (hopefully) can send the the result safely + module:send(iq) +end + +local function forward_iq(stanza, ns_data) + local to_jid = ns_data.connected + local iq_stanza = st.iq({ from=module.host, to=to_jid, type="set" }) + :tag("delegation", { xmlns=_DELEGATION_NS }) + :tag("forwarded", { xmlns=_FORWARDED_NS }) + :add_child(stanza) + local iq_id = iq_stanza.attr.id + -- we save the original stanza to check the managing entity result + ns_data[_ORI_ID_PREFIX..iq_id] = stanza + module:log("debug", "stanza forwarded to "..to_jid..": "..tostring(iq_stanza)) + module:hook("iq-result/host/"..iq_id, managing_ent_result) + module:send(iq_stanza) +end + +local function iq_hook(event) + -- general hook for all the iq which forward delegated ones + -- and continue normal behaviour else. If a namespace is + -- delegated but managing entity is offline, a service-unavailable + -- error will be sent, as requested by the XEP + local session, stanza = event.origin, event.stanza + if #stanza == 1 and stanza.attr.type == 'get' or stanza.attr.type == 'set' then + local namespace = stanza.tags[1].attr.xmlns + local ns_data = ns_delegations[namespace] + + if ns_data then + module:log("debug", "Namespace %s is delegated", namespace) + if ns_data.filtering then + local first_child = stanza.tags[1] + for _, attribute in ns_data.filtering do + -- if any filtered attribute if not present, + -- we must continue the normal bahaviour + if not first_child.attr[attribute] then + module:log("debug", "Filtered attribute %s not present, doing normal workflow", attribute) + return; + end + end + end + if not ns_data.connected then + module:log("warn", "No connected entity to manage "..namespace) + session.send(st.error_reply(stanza, 'cancel', 'service-unavailable')) + else + local managing_entity = ns_data.connected + module:log("debug", "Entity %s is managing %s", managing_entity, namespace) + forward_iq(stanza, ns_data) + end + return true + else + -- we have no delegation, we continue normal behaviour + return + end + end +end + +module:hook("iq/self", iq_hook, 2^32) +module:hook("iq/host", iq_hook, 2^32)