# HG changeset patch # User Jonas Wielicki # Date 1528026910 -7200 # Node ID a81456a137972c5a3f3c7023c5f0f9d29680ffee # Parent c7f4e3987ed0443d4551214e54fd3797d346232a mod_client_proxy: a Jabber Address Translation implementation diff -r c7f4e3987ed0 -r a81456a13797 mod_auth_ldap/mod_auth_ldap.lua --- a/mod_auth_ldap/mod_auth_ldap.lua Sun Jun 03 01:34:23 2018 +0200 +++ b/mod_auth_ldap/mod_auth_ldap.lua Sun Jun 03 13:55:10 2018 +0200 @@ -35,9 +35,18 @@ local success, iterator, invariant, initial = pcall(ld[method], ld, ...); if not success then ld = nil; return nil, iterator, "search"; end + module:log("debug", "success = %s, invariant = %s", + tostring(success), + tostring(invariant)); + local success, dn, attr = pcall(iterator, invariant, initial); + module:log("debug", "success = %s, dn = %s, attr = %s", + tostring(success), + tostring(dn), + tostring(attr)); if not success then ld = nil; return success, dn, "iter"; end + return dn, attr, "return"; end diff -r c7f4e3987ed0 -r a81456a13797 mod_client_proxy/README.markdown --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_client_proxy/README.markdown Sun Jun 03 13:55:10 2018 +0200 @@ -0,0 +1,68 @@ +--- +labels: +- 'Stage-Alpha' +summary: 'Proxy multiple client resources behind a single component' +... + +What it does +============ + +This module must be used as a component. For example: + + Component "proxy.domain.example" "client_proxy" + target_address = "some-user@some-domain.example" + +All IQ requests against the proxy host (in the above example: +proxy.domain.example) are sent to a random resource of the target address (in +the above example: some-user@some-domain.example). The entity behind the +target address is called the "implementing client". + +The IQ requests are JAT-ed (JAT: Jabber Address Translation) so that when the +implementing client answers the IQ request, it is sent back to the component, +which reverts the translation and routes the reply back to the user. + +Let us assume that user@some-domain.exmaple sends a request. The +proxy.domain.example component has the client_proxy module loaded and proxies to +some-user@some-domain.example. some-user@some-domain.example has two resources, +/a and /b. + + user -> component: + + component -> implementing client: + + implementing client -> component: + + component -> user: + + +The encoded-from resource used in the exchange between the proxy component +and the implementing client is an implementation-defined string which allows +the proxy component to revert the JAT. + + +Use cases +========= + +* Implementation of services within clients instead of components, thus making + use of the more advanced authentication features. +* General evilness + + +Configuration +============= + +To use this module, it needs to be loaded on a component: + + Component "proxy.yourdomain.example" "client_proxy" + target_address = "implementation@yourdomain.example" + +It will then send a subscription request to implementation@yourdomain.example +which MUST be accepted: this is required so that the component can detect the +resources to which IQ requests can be dispatched. + + +Limitations +=========== + +* It does not handle presence or message stanzas. +* It does not allow the implementing client to initiate IQ requests diff -r c7f4e3987ed0 -r a81456a13797 mod_client_proxy/mod_client_proxy.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_client_proxy/mod_client_proxy.lua Sun Jun 03 13:55:10 2018 +0200 @@ -0,0 +1,208 @@ +if module:get_host_type() ~= "component" then + error("proxy_component should be loaded as component", 0); +end + +local comp_host = module:get_host(); +local comp_name = module:get_option_string("name", "Proxy Component"); + +local jid_split = require "util.jid".split; +local jid_bare = require "util.jid".bare; +local jid_prep = require "util.jid".prep; +local st = require "util.stanza"; +local array = require "util.array"; + +local target_address = module:get_option_string("target_address"); + +local hosts = prosody.hosts; + +sessions = array{}; +local sessions = sessions; + +local function handle_target_presence(origin, stanza) + local type = stanza.attr.type; + module:log("debug", "received presence from destination: %s", type) + local _, _, resource = jid_split(stanza.attr.from); + if type == "error" then + -- drop all known sessions + for k in pairs(sessions) do + sessions[k] = nil + end + module:log( + "debug", + "received error presence, dropping all target sessions", + resource + ) + elseif type == "unavailable" then + for k in pairs(sessions) do + if sessions[k] == resource then + sessions[k] = nil + module:log( + "debug", + "dropped target session: %s", + resource + ) + break + end + end + elseif not type then + -- available + local found = false; + for k in pairs(sessions) do + if sessions[k] == resource then + found = true; + break + end + end + if not found then + module:log( + "debug", + "registered new target session: %s", + resource + ) + sessions:push(resource) + end + end +end + +local function handle_from_target(origin, stanza) + local type = stanza.attr.type + module:log( + "debug", + "non-presence stanza from target: name = %s, type = %s", + stanza.name, + type + ) + if stanza.name == "iq" then + if type == "error" or type == "result" then + -- de-NAT message + local _, _, denatted_to_unprepped = jid_split(stanza.attr.to); + local denatted_to = jid_prep(denatted_to_unprepped); + if not denatted_to then + module:log( + "debug", + "cannot de-NAT stanza, invalid to: %s", + denatted_to_unprepped + ) + return + end + local denatted_from = module:get_host(); + + module:log( + "debug", + "de-NAT-ed stanza: from: %s -> %s, to: %s -> %s", + stanza.attr.from, + denatted_from, + stanza.attr.to, + denatted_to + ) + + stanza.attr.from = denatted_from + stanza.attr.to = denatted_to + + module:send(stanza) + else + -- FIXME: we don’t support NATing outbund requests atm. + module:send(st.error_reply(stanza, "cancel", "feature-not-implemented")) + end + elseif stanza.name == "message" then + -- not implemented yet, we need a way to ensure that routing doesn’t + -- break + module:send(st.error_reply(stanza, "cancel", "feature-not-implemented")) + end +end + +local function handle_to_target(origin, stanza) + local type = stanza.attr.type; + module:log( + "debug", + "stanza to target: name = %s, type = %s", + stanza.name, type + ) + if stanza.name == "presence" then + if type ~= "error" then + module:send(st.error_reply(stanza, "cancel", "bad-request")) + return + end + elseif stanza.name == "iq" then + if type == "get" or type == "set" then + if #sessions == 0 then + -- no sessions available to send to + module:log("debug", "no sessions to send to!") + module:send(st.error_reply(stanza, "cancel", "service-unavailable")) + return + end + + -- find a target session + local target_session = sessions:random() + local target = target_address .. "/" .. target_session + + -- encode sender JID in resource + local natted_from = module:get_host() .. "/" .. stanza.attr.from; + + module:log( + "debug", + "NAT-ed stanza: from: %s -> %s, to: %s -> %s", + stanza.attr.from, + natted_from, + stanza.attr.to, + target + ) + + stanza.attr.from = natted_from + stanza.attr.to = target + + module:send(stanza) + else + -- FIXME: handle and forward result/error correctly + end + elseif stanza.name == "message" then + -- not implemented yet, we need a way to ensure that routing doesn’t + -- break + module:send(st.error_reply(stanza, "cancel", "feature-not-implemented")) + end +end + +local function stanza_handler(event) + local origin, stanza = event.origin, event.stanza + module:log("debug", "received stanza from %s session", origin.type) + + local bare_from = jid_bare(stanza.attr.from); + local _, _, to = jid_split(stanza.attr.to); + if bare_from == target_address then + -- from our target, to whom? + if not to then + -- directly to component + if stanza.name == "presence" then + handle_target_presence(origin, stanza) + else + module:send(st.error_reply(stanza, "cancel", "bad-request")) + return true + end + else + -- to someone else + handle_from_target(origin, stanza) + end + else + handle_to_target(origin, stanza) + end + return true +end + +module:hook("iq/bare", stanza_handler, -1); +module:hook("message/bare", stanza_handler, -1); +module:hook("presence/bare", stanza_handler, -1); +module:hook("iq/full", stanza_handler, -1); +module:hook("message/full", stanza_handler, -1); +module:hook("presence/full", stanza_handler, -1); +module:hook("iq/host", stanza_handler, -1); +module:hook("message/host", stanza_handler, -1); +module:hook("presence/host", stanza_handler, -1); + +module:log("debug", "loaded proxy on %s", module:get_host()) + +subscription_request = st.presence({ + type = "subscribe", + to = target_address, + from = module:get_host()} +) +module:send(subscription_request) diff -r c7f4e3987ed0 -r a81456a13797 mod_muc_limits/mod_muc_limits.lua --- a/mod_muc_limits/mod_muc_limits.lua Sun Jun 03 01:34:23 2018 +0200 +++ b/mod_muc_limits/mod_muc_limits.lua Sun Jun 03 13:55:10 2018 +0200 @@ -25,6 +25,18 @@ dropped_jids = nil; end +local function get_non_outcast_affiliations(room) + local nmembers = 0; + -- this is an evil hack, we probably should not access _affiliations + -- directly ... + for _, aff in pairs(room._affiliations) do + if aff ~= "outcast" then + nmembers = nmembers + 1; + end + end + return nmembers; +end + local function handle_stanza(event) local origin, stanza = event.origin, event.stanza; if stanza.name == "presence" and stanza.attr.type == "unavailable" then -- Don't limit room leaving @@ -49,7 +61,15 @@ throttle = new_throttle(period*burst, burst); room.throttle = throttle; end - if not throttle:poll(1) then + + local cost = 1; + -- we scale the cost by the inverse of the square root of the number of + -- members; this should effectively raise the limit by a factor of + -- sqrt(nmembers) + local nmembers = math.max(get_non_outcast_affiliations(room), 1); + cost = cost / math.sqrt(nmembers); + + if not throttle:poll(cost) then module:log("debug", "Dropping stanza for %s@%s from %s, over rate limit", dest_room, dest_host, from_jid); if not dropped_jids then dropped_jids = { [from_jid] = true, from_jid };