2021-02-20 19:55:07 +00:00
|
|
|
local module_host = module:get_host();
|
2020-10-30 16:45:00 +00:00
|
|
|
|
|
|
|
local jid = require("util.jid");
|
|
|
|
local st = require("util.stanza");
|
2021-05-24 18:19:55 +00:00
|
|
|
local send_to_available_resources = require("core.sessionmanager").send_to_available_resources;
|
2020-11-02 16:07:20 +00:00
|
|
|
local rm_remove_from_roster = require("core.rostermanager").remove_from_roster;
|
|
|
|
local rm_add_to_roster = require("core.rostermanager").add_to_roster;
|
|
|
|
local rm_roster_push = require("core.rostermanager").roster_push;
|
2021-02-21 11:29:33 +00:00
|
|
|
local rm_load_roster = require("core.rostermanager").load_roster;
|
2020-10-30 16:45:00 +00:00
|
|
|
|
|
|
|
-- Persistent storage
|
|
|
|
local mix_pam = module:open_store("mix_pam", "keyval");
|
|
|
|
|
|
|
|
-- Runtime data
|
2021-02-21 11:29:33 +00:00
|
|
|
local mix_hosts = {}; -- MIX host's JID -> Reference Counter
|
2020-10-30 16:45:00 +00:00
|
|
|
|
|
|
|
-- Namespaceing
|
|
|
|
local mix_pam_xmlns = "urn:xmpp:mix:pam:2";
|
2020-11-02 16:07:20 +00:00
|
|
|
local mix_roster_xmlns = "urn:xmpp:mix:roster:0";
|
2020-10-30 16:45:00 +00:00
|
|
|
|
|
|
|
module:add_feature(mix_pam_xmlns);
|
|
|
|
-- NOTE: To show that we archive messages
|
|
|
|
-- module:add_feature(mix_pam_xmlns.."#archive");
|
|
|
|
|
2021-02-20 19:55:07 +00:00
|
|
|
local function add_mix_host(host)
|
2020-10-30 16:45:00 +00:00
|
|
|
if mix_hosts[host] ~= nil then
|
|
|
|
mix_hosts[host] = mix_hosts[host] + 1;
|
|
|
|
module:log("debug", "Known MIX host has a new user");
|
|
|
|
else
|
|
|
|
module:log("debug", "Added %s as a new MIX host", host);
|
|
|
|
mix_hosts[host] = 1;
|
|
|
|
end
|
2020-11-02 16:07:20 +00:00
|
|
|
|
|
|
|
mix_pam:set("hosts", mix_hosts);
|
2020-10-30 16:45:00 +00:00
|
|
|
end
|
2021-02-20 19:55:07 +00:00
|
|
|
local function remove_mix_host(host)
|
|
|
|
if mix_hosts[host] ~= nil then
|
2020-10-30 16:45:00 +00:00
|
|
|
local count = mix_hosts[host];
|
|
|
|
if count == 1 then
|
|
|
|
mix_hosts[host] = nil;
|
|
|
|
module:log("debug", "Removing %s as a mix host", host);
|
|
|
|
else
|
|
|
|
mix_hosts[host] = count - 1;
|
|
|
|
module:log("debug", "Decrementing %s's user counter", host);
|
|
|
|
end
|
|
|
|
else
|
|
|
|
module:log("debug", "Attempt to remove unknown MIX host");
|
|
|
|
end
|
2020-11-02 16:07:20 +00:00
|
|
|
|
|
|
|
mix_pam:set("hosts", mix_hosts);
|
2020-10-30 16:45:00 +00:00
|
|
|
end
|
2021-02-20 19:55:07 +00:00
|
|
|
local function is_mix_host(host)
|
2020-10-30 16:45:00 +00:00
|
|
|
return mix_hosts[host] ~= nil;
|
|
|
|
end
|
|
|
|
|
2021-02-20 19:55:07 +00:00
|
|
|
local function is_mix_message(stanza)
|
2020-10-30 16:45:00 +00:00
|
|
|
return stanza:get_child("mix", "urn:xmpp:mix:core:1") ~= nil;
|
|
|
|
end
|
|
|
|
|
|
|
|
function module.load()
|
|
|
|
mix_hosts = mix_pam:get("hosts");
|
|
|
|
module:log("info", "Loaded known MIX hosts");
|
2021-02-20 19:55:07 +00:00
|
|
|
|
2020-10-30 16:45:00 +00:00
|
|
|
if mix_hosts == nil then
|
|
|
|
module:log("info", "No known MIX hosts loaded");
|
|
|
|
mix_hosts = {};
|
|
|
|
end
|
2020-11-02 16:07:20 +00:00
|
|
|
for host, _ in pairs(mix_hosts) do
|
2020-10-30 16:45:00 +00:00
|
|
|
module:log("debug", "Known host: %s", host);
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-02-20 19:55:07 +00:00
|
|
|
local function handle_client_join(event)
|
2020-10-30 16:45:00 +00:00
|
|
|
-- Client requests to join
|
|
|
|
module:log("debug", "client-join received");
|
|
|
|
local stanza, origin = event.stanza, event.origin;
|
2021-02-20 19:55:07 +00:00
|
|
|
|
2020-10-30 16:45:00 +00:00
|
|
|
local client_join = stanza:get_child("client-join", mix_pam_xmlns);
|
|
|
|
if client_join.attr.channel == nil then
|
|
|
|
origin.send(st.error_reply(stanza, "cancel", "bad-request", "No channel specified"));
|
|
|
|
return true;
|
|
|
|
end
|
|
|
|
local join = client_join:get_child("join", "urn:xmpp:mix:core:1");
|
|
|
|
if join == nil then
|
|
|
|
origin.send(st.error_reply(stanza, "cancel", "bad-request", "No join stanza"));
|
|
|
|
return true;
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Transform the client-join into a join
|
2021-02-20 19:55:07 +00:00
|
|
|
local join_iq = st.iq({
|
|
|
|
type = "set";
|
|
|
|
from = jid.bare(stanza.attr.from);
|
|
|
|
to = client_join.attr.channel;
|
|
|
|
id = stanza.attr.id, xmlns = "jabber:client"
|
|
|
|
});
|
2020-10-30 16:45:00 +00:00
|
|
|
join_iq:add_child(join);
|
2021-02-20 19:55:07 +00:00
|
|
|
|
2021-05-28 14:25:35 +00:00
|
|
|
module:send_iq(join_iq)
|
|
|
|
:next(function(resp)
|
|
|
|
-- Success
|
|
|
|
handle_mix_join(resp, origin);
|
|
|
|
end, function(resp)
|
|
|
|
-- Error
|
|
|
|
-- TODO
|
|
|
|
local error_stanza = resp.stanza;
|
|
|
|
error_stanza.attr.to = origin.full_jid;
|
|
|
|
module:send(error_stanza);
|
|
|
|
end);
|
2020-10-30 16:45:00 +00:00
|
|
|
return true;
|
|
|
|
end
|
|
|
|
|
2021-02-20 19:55:07 +00:00
|
|
|
local function handle_client_leave(event)
|
2020-10-30 16:45:00 +00:00
|
|
|
-- Client requests to leave
|
|
|
|
module:log("debug", "client-leave received");
|
|
|
|
local stanza, origin = event.stanza, event.origin;
|
2021-02-20 19:55:07 +00:00
|
|
|
|
2020-10-30 16:45:00 +00:00
|
|
|
local client_leave = stanza:get_child("client-leave", mix_pam_xmlns);
|
|
|
|
if client_leave.attr.channel == nil then
|
|
|
|
origin.send(st.error_reply(stanza, "cancel", "bad-request", "No channel specified"));
|
|
|
|
return true;
|
|
|
|
end
|
|
|
|
local leave = client_leave:get_child("leave", "urn:xmpp:mix:core:1");
|
|
|
|
if leave == nil then
|
|
|
|
origin.send(st.error_reply(stanza, "cancel", "bad-request", "No leave stanza"));
|
|
|
|
return true;
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Transform the client-join into a join
|
2021-02-20 19:55:07 +00:00
|
|
|
local leave_iq = st.iq({
|
|
|
|
type = "set";
|
|
|
|
from = jid.bare(stanza.attr.from);
|
|
|
|
to = client_leave.attr.channel;
|
|
|
|
id = stanza.attr.id
|
|
|
|
});
|
2020-10-30 16:45:00 +00:00
|
|
|
leave_iq:add_child(leave);
|
|
|
|
|
2021-05-28 14:25:35 +00:00
|
|
|
module:send_iq(leave_iq)
|
|
|
|
:next(function(resp)
|
|
|
|
handle_mix_leave(resp, origin);
|
|
|
|
end, function(resp)
|
|
|
|
-- Error
|
|
|
|
-- TODO
|
|
|
|
local error_stanza = resp.stanza;
|
|
|
|
error_stanza.attr.to = origin.full_jid;
|
|
|
|
module:send(error_stanza);
|
|
|
|
end);
|
2020-10-30 16:45:00 +00:00
|
|
|
return true;
|
|
|
|
end
|
|
|
|
|
2021-02-20 19:55:07 +00:00
|
|
|
module:hook("iq/self", function(event)
|
|
|
|
local stanza = event.stanza;
|
|
|
|
if #stanza.tags == 0 then return; end
|
|
|
|
|
|
|
|
if stanza:get_child("client-join", mix_pam_xmlns) ~= nil then
|
|
|
|
return handle_client_join(event);
|
|
|
|
elseif stanza:get_child("client-leave", mix_pam_xmlns) ~= nil then
|
|
|
|
return handle_client_leave(event);
|
2020-10-30 16:45:00 +00:00
|
|
|
end
|
|
|
|
end);
|
|
|
|
|
2021-05-28 14:25:35 +00:00
|
|
|
local function handle_mix_join(event, origin)
|
2020-10-30 16:45:00 +00:00
|
|
|
-- The MIX server responded
|
|
|
|
module:log("debug", "Received MIX-JOIN result");
|
2021-02-20 19:55:07 +00:00
|
|
|
|
2020-10-30 16:45:00 +00:00
|
|
|
local stanza = event.stanza;
|
2020-11-02 16:07:20 +00:00
|
|
|
local spid = stanza:get_child("join", "urn:xmpp:mix:core:1").attr.id;
|
|
|
|
local channel_jid = spid.."#"..stanza.attr.from;
|
|
|
|
local resource = origin.resource;
|
2021-02-20 19:55:07 +00:00
|
|
|
|
|
|
|
local client_join = st.iq({
|
|
|
|
type = "result";
|
|
|
|
id = stanza.attr.id;
|
|
|
|
from = jid.bare(stanza.attr.to);
|
|
|
|
to = stanza.attr.to.."/"..resource
|
|
|
|
}):tag("client-join", { xmlns = mix_pam_xmlns, jid = channel_jid });
|
|
|
|
|
2020-10-30 16:45:00 +00:00
|
|
|
client_join:add_child(stanza:get_child("join", "urn:xmpp:mix:core:1"));
|
2020-11-02 16:07:20 +00:00
|
|
|
module:send(client_join);
|
2021-02-20 19:55:07 +00:00
|
|
|
|
2020-11-02 16:07:20 +00:00
|
|
|
-- TODO: Error handling?
|
|
|
|
rm_add_to_roster(origin, stanza.attr.from, {
|
2021-05-18 09:22:00 +00:00
|
|
|
subscription = "none", -- TODO: This depends on MIX-ANON
|
2020-11-02 16:07:20 +00:00
|
|
|
groups = {},
|
2021-02-21 11:29:33 +00:00
|
|
|
mix_spid = spid,
|
2020-11-02 16:07:20 +00:00
|
|
|
});
|
2021-02-20 19:55:07 +00:00
|
|
|
rm_roster_push(jid.node(stanza.attr.to), module_host, stanza.attr.from);
|
2021-02-28 10:31:40 +00:00
|
|
|
add_mix_host(jid.host(stanza.attr.from));
|
2020-11-02 16:07:20 +00:00
|
|
|
|
2020-10-30 16:45:00 +00:00
|
|
|
return true;
|
|
|
|
end
|
|
|
|
|
2021-05-28 14:25:35 +00:00
|
|
|
local function handle_mix_leave(event, origin)
|
2020-10-30 16:45:00 +00:00
|
|
|
-- The MIX server responded
|
|
|
|
module:log("debug", "Received MIX-LEAVE result");
|
2021-02-20 19:55:07 +00:00
|
|
|
|
2020-10-30 16:45:00 +00:00
|
|
|
local stanza = event.stanza;
|
2020-11-02 16:07:20 +00:00
|
|
|
local resource = origin.resource;
|
2020-10-30 16:45:00 +00:00
|
|
|
|
2021-02-20 19:55:07 +00:00
|
|
|
local client_leave = st.iq({
|
|
|
|
type = "result";
|
|
|
|
id = stanza.attr.id;
|
|
|
|
from = jid.bare(stanza.attr.to);
|
|
|
|
to = stanza.attr.to.."/"..resource
|
|
|
|
}):tag("client-leave", { xmlns = mix_pam_xmlns });
|
|
|
|
|
2020-10-30 16:45:00 +00:00
|
|
|
client_leave:add_child(stanza:get_child("leave", "urn:xmpp:mix:core:1"));
|
2021-02-20 19:55:07 +00:00
|
|
|
module:send(client_leave);
|
|
|
|
|
2020-11-02 16:07:20 +00:00
|
|
|
-- Remove from roster
|
|
|
|
-- TODO: Error handling
|
|
|
|
rm_remove_from_roster(origin, jid.bare(stanza.attr.from));
|
2021-02-20 19:55:07 +00:00
|
|
|
rm_roster_push(jid.node(stanza.attr.to), module_host, jid.bare(stanza.attr.from));
|
2021-02-21 11:29:33 +00:00
|
|
|
remove_mix_host(jid.bare(stanza.attr.from));
|
2020-11-02 16:07:20 +00:00
|
|
|
|
2020-10-30 16:45:00 +00:00
|
|
|
return true;
|
|
|
|
end
|
|
|
|
|
2020-11-02 16:07:20 +00:00
|
|
|
module:hook("roster-get", function(event)
|
|
|
|
-- NOTE: Currently this requires a patch to make mod_roster emit
|
|
|
|
-- the roster-get event
|
2021-04-25 14:43:47 +00:00
|
|
|
local reply, stanza = event.reply, event.stanza;
|
|
|
|
local client_query = stanza:get_child("query", "jabber:iq:roster");
|
|
|
|
if not client_query then return; end
|
|
|
|
|
|
|
|
local annotate = client_query:get_child("annotate", mix_roster_xmlns);
|
2020-11-02 16:07:20 +00:00
|
|
|
if not annotate then return; end
|
|
|
|
|
|
|
|
module:log("debug", "Annotated roster request received");
|
|
|
|
|
|
|
|
-- User requested the roster with an <annotate/>
|
2021-04-25 14:43:47 +00:00
|
|
|
local roster = rm_load_roster(jid.node(stanza.attr.from), jid.host(stanza.attr.from));
|
|
|
|
local query = reply:get_child("query", "jabber:iq:roster");
|
|
|
|
query:maptags(function (item)
|
|
|
|
-- Bail early, just in case
|
|
|
|
if item.name ~= "item" then return item; end
|
|
|
|
|
|
|
|
local spid = roster[item.attr.jid]["mix_spid"];
|
|
|
|
if spid ~= nil then
|
|
|
|
item:tag("channel", {
|
|
|
|
xmlns = mix_roster_xmlns,
|
|
|
|
["participant-id"] = spid,
|
|
|
|
});
|
2020-11-02 16:07:20 +00:00
|
|
|
end
|
2021-04-25 14:43:47 +00:00
|
|
|
|
|
|
|
return item;
|
|
|
|
end);
|
2020-11-02 16:07:20 +00:00
|
|
|
end);
|
|
|
|
|
2020-10-30 16:45:00 +00:00
|
|
|
module:hook("message/bare", function(event)
|
|
|
|
local stanza = event.stanza;
|
2021-02-20 19:55:07 +00:00
|
|
|
local jid_host = jid.host(stanza.attr.from);
|
|
|
|
if not is_mix_host(jid_host) then return; end
|
2020-10-30 16:45:00 +00:00
|
|
|
if not is_mix_message(stanza) then return; end
|
2021-02-20 19:55:07 +00:00
|
|
|
|
2020-10-30 16:45:00 +00:00
|
|
|
-- Per XEP we know that stanza.attr.to is the user's bare JID
|
2021-05-24 18:19:55 +00:00
|
|
|
-- TODO: Only send to resources that advertise support for MIX (When MIX clients are available for testing)
|
|
|
|
local to = stanza.attr.to;
|
|
|
|
send_to_available_resources(jid.node(to), jid.host(to), stanza);
|
2020-10-30 16:45:00 +00:00
|
|
|
return true;
|
|
|
|
end);
|