comparison mod_delegation/mod_delegation.lua @ 1710:b68eed25b880

mod_delegation: iq forwarding to managing entity
author Goffi <goffi@goffi.org>
date Fri, 17 Apr 2015 21:05:08 +0200
parents 59ba224bf145
children 55b9ac807ac9
comparison
equal deleted inserted replaced
1709:59ba224bf145 1710:b68eed25b880
2 -- Copyright (C) 2015 Jérôme Poisson 2 -- Copyright (C) 2015 Jérôme Poisson
3 -- 3 --
4 -- This module is MIT/X11 licensed. Please see the 4 -- This module is MIT/X11 licensed. Please see the
5 -- COPYING file in the source package for more information. 5 -- COPYING file in the source package for more information.
6 6
7 -- This module manage namespace delegation, a way to delegate server features
8 -- to an external entity/component. Only the admin mode is implemented so far
9
10 -- TODO: client mode, managing entity error handling
7 11
8 local jid = require("util/jid") 12 local jid = require("util/jid")
9 local st = require("util/stanza") 13 local st = require("util/stanza")
10 local set = require("util/set") 14 local set = require("util/set")
11 15
18 delegation_session.connected_cb = set.new() 22 delegation_session.connected_cb = set.new()
19 end 23 end
20 local connected_cb = delegation_session.connected_cb 24 local connected_cb = delegation_session.connected_cb
21 25
22 local _DELEGATION_NS = 'urn:xmpp:delegation:1' 26 local _DELEGATION_NS = 'urn:xmpp:delegation:1'
23 -- local _FORWARDED_NS = 'urn:xmpp:forward:0' 27 local _FORWARDED_NS = 'urn:xmpp:forward:0'
24 28 local _ORI_ID_PREFIX = "IQ_RESULT_"
25 29
26 module:log("debug", "Loading namespace delegation module "); 30 module:log("debug", "Loading namespace delegation module ");
27 31
28 32
29 --> Configuration management <-- 33 --> Configuration management <--
30 34
31 local ns_delegations = module:get_option("delegations", {}) 35 local ns_delegations = module:get_option("delegations", {})
32 36
33 local jid2ns = {} 37 local jid2ns = {}
34 for namespace, config in pairs(ns_delegations) do 38 for namespace, ns_data in pairs(ns_delegations) do
35 -- "connected" contain the full jid of connected managing entity 39 -- "connected" contain the full jid of connected managing entity
36 config.connected = nil 40 ns_data.connected = nil
37 if config.jid then 41 if ns_data.jid then
38 if jid2ns[config.jid] == nil then 42 if jid2ns[ns_data.jid] == nil then
39 jid2ns[config.jid] = {} 43 jid2ns[ns_data.jid] = {}
40 end 44 end
41 jid2ns[config.jid][namespace] = config 45 jid2ns[ns_data.jid][namespace] = ns_data
46 module:log("debug", "Namespace %s is delegated%s to %s", namespace, ns_data.filtering and " (with filtering)" or "", ns_data.jid)
42 else 47 else
43 module:log("warn", "Ignoring delegation for %s: no jid specified", tostring(namespace)) 48 module:log("warn", "Ignoring delegation for %s: no jid specified", tostring(namespace))
44 ns_delegations[namespace] = nil 49 ns_delegations[namespace] = nil
45 end 50 end
46 end 51 end
55 -- we need to check if a delegation is granted because the configuration 60 -- we need to check if a delegation is granted because the configuration
56 -- can be complicated if some delegations are granted to bare jid 61 -- can be complicated if some delegations are granted to bare jid
57 -- and other to full jids, and several resources are connected. 62 -- and other to full jids, and several resources are connected.
58 local have_delegation = false 63 local have_delegation = false
59 64
60 for namespace, config in pairs(jid2ns[to_jid]) do 65 for namespace, ns_data in pairs(jid2ns[to_jid]) do
61 if config.connected == to_jid then 66 if ns_data.connected == to_jid then
62 have_delegation = true 67 have_delegation = true
63 message:tag("delegated", {namespace=namespace}) 68 message:tag("delegated", {namespace=namespace})
64 if type(config.filtering) == "table" then 69 if type(ns_data.filtering) == "table" then
65 for _, attribute in pairs(config.filtering) do 70 for _, attribute in pairs(ns_data.filtering) do
66 message:tag("attribute", {name=attribute}):up() 71 message:tag("attribute", {name=attribute}):up()
67 end 72 end
68 message:up() 73 message:up()
69 end 74 end
70 end 75 end
77 82
78 local function set_connected(entity_jid) 83 local function set_connected(entity_jid)
79 -- set the "connected" key for all namespace managed by entity_jid 84 -- set the "connected" key for all namespace managed by entity_jid
80 -- if the namespace has already a connected entity, ignore the new one 85 -- if the namespace has already a connected entity, ignore the new one
81 local function set_config(jid_) 86 local function set_config(jid_)
82 for _, config in pairs(jid2ns[jid_]) do 87 for _, ns_data in pairs(jid2ns[jid_]) do
83 if config.connected == nil then 88 if ns_data.connected == nil then
84 config.connected = entity_jid 89 ns_data.connected = entity_jid
85 end 90 end
86 end 91 end
87 end 92 end
88 local bare_jid = jid.bare(entity_jid) 93 local bare_jid = jid.bare(entity_jid)
89 set_config(bare_jid) 94 set_config(bare_jid)
130 end 135 end
131 136
132 connected_cb:add(on_component_connected) 137 connected_cb:add(on_component_connected)
133 module:hook('component-authenticated', on_component_auth) 138 module:hook('component-authenticated', on_component_auth)
134 module:hook('presence/initial', on_presence) 139 module:hook('presence/initial', on_presence)
140
141
142 --> delegated namespaces hook <--
143
144 local function managing_ent_result(event)
145 -- this function manage iq results from the managing entity
146 -- it do a couple of security check before sending the
147 -- result to the managed entity
148 local session, stanza = event.origin, event.stanza
149 if stanza.attr.to ~= module.host then
150 module:log("warn", 'forwarded stanza result has "to" attribute not addressed to current host, id conflict ?')
151 return
152 end
153 module:unhook("iq-result/host/"..stanza.attr.id, managing_ent_result)
154
155 -- lot of checks to do...
156 local delegation = stanza.tags[1]
157 if #stanza ~= 1 or delegation.name ~= "delegation" or
158 delegation.attr.xmlns ~= _DELEGATION_NS then
159 session.send(st.error_reply(stanza, 'modify', 'not-acceptable'))
160 return true
161 end
162
163 local forwarded = delegation.tags[1]
164 if #delegation ~= 1 or forwarded.name ~= "forwarded" or
165 forwarded.attr.xmlns ~= _FORWARDED_NS then
166 session.send(st.error_reply(stanza, 'modify', 'not-acceptable'))
167 return true
168 end
169
170 local iq = forwarded.tags[1]
171 if #forwarded ~= 1 or iq.name ~= "iq" or #iq ~= 1 then
172 session.send(st.error_reply(stanza, 'modify', 'not-acceptable'))
173 return true
174 end
175
176 local namespace = iq.tags[1].xmlns
177 local ns_data = ns_delegations[namespace]
178 local original = ns_data[_ORI_ID_PREFIX..stanza.attr.id]
179
180 if stanza.attr.from ~= ns_data.connected or iq.attr.type ~= "result" or
181 iq.attr.id ~= original.attr.id or iq.attr.to ~= original.attr.from then
182 session.send(st.error_reply(stanza, 'auth', 'forbidden'))
183 module:send(st.error_reply(original, 'cancel', 'service-unavailable'))
184 return true
185 end
186
187 -- at this point eveything is checked,
188 -- and we (hopefully) can send the the result safely
189 module:send(iq)
190 end
191
192 local function forward_iq(stanza, ns_data)
193 local to_jid = ns_data.connected
194 local iq_stanza = st.iq({ from=module.host, to=to_jid, type="set" })
195 :tag("delegation", { xmlns=_DELEGATION_NS })
196 :tag("forwarded", { xmlns=_FORWARDED_NS })
197 :add_child(stanza)
198 local iq_id = iq_stanza.attr.id
199 -- we save the original stanza to check the managing entity result
200 ns_data[_ORI_ID_PREFIX..iq_id] = stanza
201 module:log("debug", "stanza forwarded to "..to_jid..": "..tostring(iq_stanza))
202 module:hook("iq-result/host/"..iq_id, managing_ent_result)
203 module:send(iq_stanza)
204 end
205
206 local function iq_hook(event)
207 -- general hook for all the iq which forward delegated ones
208 -- and continue normal behaviour else. If a namespace is
209 -- delegated but managing entity is offline, a service-unavailable
210 -- error will be sent, as requested by the XEP
211 local session, stanza = event.origin, event.stanza
212 if #stanza == 1 and stanza.attr.type == 'get' or stanza.attr.type == 'set' then
213 local namespace = stanza.tags[1].attr.xmlns
214 local ns_data = ns_delegations[namespace]
215
216 if ns_data then
217 module:log("debug", "Namespace %s is delegated", namespace)
218 if ns_data.filtering then
219 local first_child = stanza.tags[1]
220 for _, attribute in ns_data.filtering do
221 -- if any filtered attribute if not present,
222 -- we must continue the normal bahaviour
223 if not first_child.attr[attribute] then
224 module:log("debug", "Filtered attribute %s not present, doing normal workflow", attribute)
225 return;
226 end
227 end
228 end
229 if not ns_data.connected then
230 module:log("warn", "No connected entity to manage "..namespace)
231 session.send(st.error_reply(stanza, 'cancel', 'service-unavailable'))
232 else
233 local managing_entity = ns_data.connected
234 module:log("debug", "Entity %s is managing %s", managing_entity, namespace)
235 forward_iq(stanza, ns_data)
236 end
237 return true
238 else
239 -- we have no delegation, we continue normal behaviour
240 return
241 end
242 end
243 end
244
245 module:hook("iq/self", iq_hook, 2^32)
246 module:hook("iq/host", iq_hook, 2^32)