--- /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)