2021-02-28 10:29:21 +00:00
|
|
|
-- TODO: Handle creation and deletion of avatar nodes when publishing to :config
|
2021-06-02 14:44:17 +00:00
|
|
|
-- TODO: Somehow make the hosts aware of our "presence"
|
2021-02-19 16:03:17 +00:00
|
|
|
|
2020-10-27 17:05:41 +00:00
|
|
|
local host = module:get_host();
|
|
|
|
if module:get_host_type() ~= "component" then
|
2020-11-02 16:07:50 +00:00
|
|
|
error("MIX should be loaded as a component", 0);
|
2020-10-27 17:05:41 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
local st = require("util.stanza");
|
|
|
|
local jid = require("util.jid");
|
|
|
|
local uuid = require("util.uuid");
|
|
|
|
local id = require("util.id");
|
|
|
|
local datetime = require("util.datetime");
|
|
|
|
local time = require("util.time");
|
2020-11-02 16:07:50 +00:00
|
|
|
local dataforms = require("util.dataforms");
|
2021-02-28 10:29:21 +00:00
|
|
|
local array = require("util.array");
|
|
|
|
local set = require("util.set");
|
2021-06-02 14:44:17 +00:00
|
|
|
local lib_pubsub = module:require("pubsub");
|
2021-02-28 10:29:21 +00:00
|
|
|
|
2020-11-02 16:07:50 +00:00
|
|
|
local helpers = module:require("mix/helpers");
|
2021-02-28 10:29:21 +00:00
|
|
|
local namespaces = module:require("mix/namespaces");
|
2021-06-02 14:44:17 +00:00
|
|
|
local lib_forms = module:require("mix/forms");
|
|
|
|
local lib_mix = module:require("mix/mix");
|
2020-11-02 16:07:50 +00:00
|
|
|
|
2021-06-02 14:44:17 +00:00
|
|
|
local Channel = lib_mix.Channel;
|
|
|
|
local Participant = lib_mix.Participant;
|
2020-10-29 16:58:11 +00:00
|
|
|
|
|
|
|
-- Persistent data
|
2020-10-27 17:05:41 +00:00
|
|
|
local persistent_channels = module:open_store("mix_channels", "keyval");
|
|
|
|
local persistent_channel_data = module:open_store("mix_data", "keyval");
|
2020-11-02 16:07:50 +00:00
|
|
|
local message_archive = module:open_store("mix_log", "archive");
|
2020-10-27 17:05:41 +00:00
|
|
|
|
2020-10-29 16:58:11 +00:00
|
|
|
-- Configuration
|
2020-10-27 17:05:41 +00:00
|
|
|
local default_channel_description = module:get_option("default_description", "A MIX channel for chatting");
|
|
|
|
local default_channel_name = module:get_option("default_name", "MIX channel");
|
2020-10-29 17:10:08 +00:00
|
|
|
local restrict_channel_creation = module:get_option("restrict_local_channels", "local");
|
2021-02-28 10:29:21 +00:00
|
|
|
local service_name = module:get_option("service_name", "Prosody MIX service");
|
|
|
|
|
|
|
|
-- MIX configuration
|
2021-06-02 14:44:17 +00:00
|
|
|
local default_mix_nodes = array { namespaces.info, namespaces.participants, namespaces.messages };
|
2020-10-27 17:05:41 +00:00
|
|
|
|
|
|
|
local channels = {};
|
|
|
|
|
2021-06-02 14:44:17 +00:00
|
|
|
local function find_channel(channel_jid)
|
2020-10-29 16:58:11 +00:00
|
|
|
-- Return the channel object from the channels array for which the
|
|
|
|
-- JID matches. If none is found, returns -1, nil
|
2021-06-02 14:44:17 +00:00
|
|
|
local _, channel = helpers.find(channels, function(c) return c.jid == channel_jid; end);
|
|
|
|
return channel;
|
2020-10-27 17:05:41 +00:00
|
|
|
end
|
|
|
|
|
2021-02-21 17:38:21 +00:00
|
|
|
local function save_channels()
|
2020-10-27 17:05:41 +00:00
|
|
|
module:log("debug", "Saving channel list...");
|
|
|
|
local channel_list = {};
|
|
|
|
for _, channel in pairs(channels) do
|
|
|
|
table.insert(channel_list, channel.jid);
|
|
|
|
|
|
|
|
persistent_channel_data:set(channel.jid, channel);
|
|
|
|
end
|
|
|
|
|
|
|
|
persistent_channels:set("channels", channel_list);
|
|
|
|
module:log("debug", "Saving channel list done.");
|
|
|
|
end
|
|
|
|
|
|
|
|
function Channel:save_state()
|
2021-02-28 10:29:21 +00:00
|
|
|
-- Store the channel in the persistent channel store
|
|
|
|
module:log("debug", "Saving channel %s...", self.jid);
|
2020-10-27 17:05:41 +00:00
|
|
|
persistent_channel_data:set(self.jid, self);
|
2021-02-28 10:29:21 +00:00
|
|
|
module:log("debug", "Saving done.", self.jid);
|
2020-11-02 16:07:50 +00:00
|
|
|
end
|
|
|
|
|
2020-10-27 17:05:41 +00:00
|
|
|
function module.load()
|
|
|
|
module:log("info", "Loading MIX channels...");
|
2021-02-21 17:38:21 +00:00
|
|
|
|
2020-10-27 17:05:41 +00:00
|
|
|
local channel_list = persistent_channels:get("channels");
|
|
|
|
if channel_list then
|
2021-02-21 17:38:21 +00:00
|
|
|
for _, channel_data in pairs(channel_list) do
|
|
|
|
local channel = Channel:from(persistent_channel_data:get(channel_data));
|
2020-11-02 16:07:50 +00:00
|
|
|
table.insert(channels, channel);
|
|
|
|
module:log("debug", "MIX channel %s loaded", channel.jid);
|
2020-10-27 17:05:41 +00:00
|
|
|
end
|
|
|
|
else
|
|
|
|
module:log("debug", "No MIX channels found.");
|
|
|
|
end
|
2020-11-02 16:07:50 +00:00
|
|
|
module:log("info", "Loading MIX channels done.");
|
2020-10-27 17:05:41 +00:00
|
|
|
end
|
|
|
|
|
2021-06-02 14:44:17 +00:00
|
|
|
-- PubSub logic
|
|
|
|
local function pubsub_not_implemented(stanza, feature)
|
|
|
|
local reply = st.error_reply(stanza, "cancel", "feature-not-implemented");
|
|
|
|
reply:tag("unsupported", {
|
|
|
|
xmlns = namespaces.pubsub_error;
|
|
|
|
feature = feature;
|
|
|
|
});
|
|
|
|
|
|
|
|
return reply;
|
|
|
|
end
|
|
|
|
|
|
|
|
local function handle_pubsub_iq(event)
|
|
|
|
local stanza, origin = event.stanza, event.origin;
|
|
|
|
local from = jid.bare(stanza.attr.from);
|
|
|
|
|
|
|
|
local channel = find_channel(stanza.attr.to);
|
|
|
|
if not channel then
|
|
|
|
module:log("error", "PubSub was used for unknown channel");
|
|
|
|
origin:send(st.error_reply(stanza, "cancel", "item-not-found"));
|
|
|
|
return;
|
|
|
|
end;
|
|
|
|
|
|
|
|
-- Certain actions we do not want the user to perform, so we need to
|
|
|
|
-- catch them here.
|
|
|
|
local pubsub = stanza:get_child("pubsub", namespaces.pubsub);
|
|
|
|
if pubsub then
|
|
|
|
local items = pubsub:get_child("items");
|
|
|
|
if items then
|
|
|
|
if not channel:may_retrieve_items(from, items.attr.node) then
|
|
|
|
origin:send(pubsub_not_implemented(stanza, "retrieve-items"));
|
|
|
|
return true;
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local publish = pubsub:get_child("publish");
|
|
|
|
if publish then
|
|
|
|
if not channel:may_publish(from, publish.attr.node) then
|
|
|
|
origin:send(pubsub_not_implemented(stanza, "publish"));
|
|
|
|
return true;
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local retract = pubsub:get_child("retract");
|
|
|
|
if retract then
|
|
|
|
if not channel:may_retract(from, retract.attr.node) then
|
|
|
|
origin:send(pubsub_not_implemented(stanza, "delete-items"));
|
|
|
|
return true;
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- We generally do not allow deletion, creation or configuration of
|
|
|
|
-- nodes. (Un)Subscribing is not allowed as this is managed via
|
|
|
|
-- interaction with the MIX host.
|
|
|
|
-- NOTE: Checking for <delete> is not needed as no user is ever set as
|
|
|
|
-- owner
|
|
|
|
if pubsub:get_child("configure") then
|
|
|
|
origin:send(pubsub_not_implemented(stanza, "config-node"));
|
|
|
|
return true;
|
|
|
|
end
|
|
|
|
if pubsub:get_child("unsubscibe") or pubsub:get_child("subscribe") then
|
|
|
|
origin:send(st.error_reply(stanza, "auth", "forbidden"));
|
|
|
|
return true;
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- TODO: Process publishing to :config
|
|
|
|
|
|
|
|
local service = channel:get_pubsub_service();
|
|
|
|
return lib_pubsub.handle_pubsub_iq(event, service);
|
2020-10-29 16:08:04 +00:00
|
|
|
end
|
2021-06-02 14:44:17 +00:00
|
|
|
module:hook("iq/bare/"..namespaces.pubsub..":pubsub", handle_pubsub_iq, 1000);
|
|
|
|
|
|
|
|
local function can_create_channels(user)
|
|
|
|
-- Returns true when the jid is allowed to create MIX channels. False otherwise.
|
|
|
|
-- NOTE: Taken from plugins/muc/mod_muc.lua
|
|
|
|
local host_suffix = host:gsub("^[^%.]+%.", "");
|
|
|
|
|
|
|
|
if restrict_channel_creation == "local" then
|
|
|
|
module:log("debug", "Comparing %s (Sender) to %s (Host)", jid.host(user), host_suffix);
|
2020-10-29 16:08:04 +00:00
|
|
|
|
2021-06-02 14:44:17 +00:00
|
|
|
if jid.host(user) == host_suffix then
|
|
|
|
return true;
|
|
|
|
else
|
|
|
|
return false;
|
|
|
|
end
|
|
|
|
elseif type(restrict_channel_creation) == "table" then
|
|
|
|
if helpers.find_str(restrict_channel_creation, user) ~= -1 then
|
|
|
|
-- User was specifically listed
|
|
|
|
return true;
|
|
|
|
elseif helpers.find_str(restrict_channel_creation, jid.host(user)) then
|
|
|
|
-- User's host was allowed
|
|
|
|
return true;
|
|
|
|
end
|
|
|
|
|
|
|
|
return false;
|
|
|
|
end
|
|
|
|
|
|
|
|
-- TODO: Handle also true/"admin" (See mod_muc)
|
|
|
|
return true;
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
-- Disco related functionality
|
2020-10-27 17:05:41 +00:00
|
|
|
module:hook("host-disco-items", function(event)
|
|
|
|
module:log("debug", "host-disco-items called");
|
|
|
|
local reply = event.reply;
|
|
|
|
for _, channel in pairs(channels) do
|
|
|
|
-- Adhoc channels are supposed to be invisible
|
|
|
|
if not channel.adhoc then
|
|
|
|
reply:tag("item", { jid = channel.jid }):up();
|
|
|
|
end
|
2021-02-21 17:38:21 +00:00
|
|
|
end
|
2020-10-27 17:05:41 +00:00
|
|
|
end);
|
|
|
|
|
2021-06-02 14:44:17 +00:00
|
|
|
local function handle_channel_disco_items(event)
|
2020-10-27 17:05:41 +00:00
|
|
|
module:log("debug", "IQ-GET disco#items");
|
|
|
|
|
|
|
|
local origin, stanza = event.origin, event.stanza;
|
2020-11-02 16:07:50 +00:00
|
|
|
if stanza:get_child("query", "http://jabber.org/protocol/disco#items").attr.node ~= "mix" then
|
|
|
|
origin.send(st.error_reply(stanza, "modify", "bad-request"));
|
|
|
|
return true;
|
|
|
|
end
|
|
|
|
|
|
|
|
-- TODO: Maybe here we should check if the user has permissions to get infos
|
|
|
|
-- about the channel before saying that it doesn't exist to prevent creating
|
|
|
|
-- an oracle.
|
2021-06-02 14:44:17 +00:00
|
|
|
local channel = find_channel(stanza.attr.to);
|
2020-10-27 17:05:41 +00:00
|
|
|
if not channel then
|
2021-06-02 14:44:17 +00:00
|
|
|
origin.send(lib_mix.channel_not_found(stanza));
|
2020-10-30 16:45:22 +00:00
|
|
|
return true;
|
2020-10-27 17:05:41 +00:00
|
|
|
end
|
2020-11-02 16:07:50 +00:00
|
|
|
|
|
|
|
if not channel:is_participant(jid.bare(stanza.attr.from)) then
|
|
|
|
origin.send(st.error_reply(stanza, "cancel", "forbidden"));
|
|
|
|
return true;
|
|
|
|
end
|
2020-10-29 16:08:04 +00:00
|
|
|
|
2020-10-27 17:05:41 +00:00
|
|
|
local reply = st.reply(stanza):tag("query", { xmlns = "http://jabber.org/protocol/disco#items", node = "mix" });
|
2021-02-28 10:29:21 +00:00
|
|
|
for _, node in pairs(channel.nodes) do
|
2020-10-27 17:05:41 +00:00
|
|
|
reply:tag("item", { jid = channel.jid, node = node }):up();
|
|
|
|
end
|
|
|
|
|
|
|
|
origin.send(reply);
|
|
|
|
return true;
|
2020-10-29 17:10:08 +00:00
|
|
|
end
|
2021-06-02 14:44:17 +00:00
|
|
|
module:hook("iq/bare/http://jabber.org/protocol/disco#items:query", handle_channel_disco_items);
|
2020-10-29 17:10:08 +00:00
|
|
|
|
2020-10-29 16:58:11 +00:00
|
|
|
module:hook("iq-get/host/http://jabber.org/protocol/disco#info:query", function(event)
|
|
|
|
module:log("debug", "IQ-GET host disco#info");
|
2021-02-21 17:38:21 +00:00
|
|
|
|
2020-10-29 16:58:11 +00:00
|
|
|
local origin, stanza = event.origin, event.stanza;
|
|
|
|
local reply = st.reply(stanza)
|
|
|
|
:tag("query", { xmlns = "http://jabber.org/protocol/disco#info" })
|
2021-02-28 10:29:21 +00:00
|
|
|
:tag("identity", { category = "conference", type = "mix", name = service_name }):up()
|
2020-10-29 16:58:11 +00:00
|
|
|
:tag("feature", { var = "http://jabber.org/protocol/disco#info" }):up()
|
2020-11-02 16:07:50 +00:00
|
|
|
:tag("feature", { var = "http://jabber.org/protocol/disco#items" }):up()
|
2021-02-28 10:29:21 +00:00
|
|
|
:tag("feature", { var = namespaces.mix_core }):up();
|
2020-10-29 16:58:11 +00:00
|
|
|
|
2020-10-29 17:10:08 +00:00
|
|
|
if can_create_channels(stanza.attr.from) then
|
2021-02-28 10:29:21 +00:00
|
|
|
reply:tag("feature", { var = namespaces.mix_core.."#create-channel" }):up();
|
2020-10-29 16:58:11 +00:00
|
|
|
end
|
2020-11-02 16:07:50 +00:00
|
|
|
origin.send(reply);
|
|
|
|
return true;
|
2021-06-02 14:44:17 +00:00
|
|
|
end, 1000);
|
2020-10-29 16:58:11 +00:00
|
|
|
|
2021-06-02 14:44:17 +00:00
|
|
|
local function handle_channel_disco_info(event)
|
2020-10-27 17:05:41 +00:00
|
|
|
module:log("debug", "IQ-GET disco#info");
|
|
|
|
|
|
|
|
local origin, stanza = event.origin, event.stanza;
|
2021-06-02 14:44:17 +00:00
|
|
|
local channel = find_channel(stanza.attr.to);
|
2020-10-27 17:05:41 +00:00
|
|
|
if not channel then
|
2021-06-02 14:44:17 +00:00
|
|
|
origin.send(lib_mix.channel_not_found(stanza));
|
2020-10-30 16:45:22 +00:00
|
|
|
return true;
|
2020-10-27 17:05:41 +00:00
|
|
|
end
|
|
|
|
local reply = st.reply(stanza):tag("query", { xmlns = "http://jabber.org/protocol/disco#info" });
|
2020-10-29 16:08:04 +00:00
|
|
|
reply:tag("feature", { var = "http://jabber.org/protocol/disco#info" }):up();
|
2020-10-27 17:05:41 +00:00
|
|
|
reply:tag("identity", { category = "conference", name = channel.name, type = "mix" }):up();
|
2020-10-29 16:08:04 +00:00
|
|
|
|
2021-02-28 10:29:21 +00:00
|
|
|
reply:tag("feature", { var = namespaces.mix_core }):up();
|
2020-11-02 16:07:50 +00:00
|
|
|
reply:tag("feature", { var = "urn:xmpp:mam:2" }):up();
|
2020-10-29 16:58:11 +00:00
|
|
|
|
2020-10-27 17:05:41 +00:00
|
|
|
origin.send(reply);
|
|
|
|
return true;
|
2021-06-02 14:44:17 +00:00
|
|
|
end
|
|
|
|
module:hook("iq-get/bare/http://jabber.org/protocol/disco#info:query", handle_channel_disco_info);
|
2020-10-27 17:05:41 +00:00
|
|
|
|
2021-02-28 10:29:21 +00:00
|
|
|
module:hook("iq-set/bare/"..namespaces.mam..":query", function(event)
|
2020-11-02 16:07:50 +00:00
|
|
|
local stanza, origin = event.stanza, event.origin;
|
|
|
|
local channel_jid = stanza.attr.to;
|
2021-06-02 14:44:17 +00:00
|
|
|
local channel = find_channel(channel_jid);
|
|
|
|
if not channel then
|
2020-11-02 16:07:50 +00:00
|
|
|
-- TODO: Is this correct?
|
2021-06-02 14:44:17 +00:00
|
|
|
origin.send(lib_mix.channel_not_found(stanza));
|
2020-11-02 16:07:50 +00:00
|
|
|
return true;
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Check if the user is subscribed to the messages node
|
2021-02-28 10:29:21 +00:00
|
|
|
if not channel:is_subscribed(stanza.attr.from, namespaces.messages) then
|
2020-11-02 16:07:50 +00:00
|
|
|
origin.send(st.error_reply(stanza, "cancel", "forbidden"));
|
|
|
|
return true;
|
|
|
|
end
|
2021-02-21 17:38:21 +00:00
|
|
|
|
2021-02-28 10:29:21 +00:00
|
|
|
local query = stanza:get_child("query", namespaces.mam);
|
2020-11-02 16:07:50 +00:00
|
|
|
local filter = {};
|
|
|
|
local query_id = query.attr.queryid;
|
|
|
|
local x = query:get_child("x", "jabber:x:data");
|
|
|
|
if x ~= nil then
|
2021-02-21 17:38:21 +00:00
|
|
|
-- TODO: Error handling
|
2021-06-02 14:44:17 +00:00
|
|
|
local form, err = lib_forms.mam_query:data(x);
|
2021-02-21 17:38:21 +00:00
|
|
|
|
2020-11-02 16:07:50 +00:00
|
|
|
-- Validate
|
|
|
|
if (form["start"] and not form["end"]) or (not form["start"] and form["end"]) then
|
|
|
|
origin.send(st.error_reply(stanza, "modify", "bad-request", "Invalid timestamp"));
|
|
|
|
return true;
|
2020-10-27 17:05:41 +00:00
|
|
|
end
|
2020-11-02 16:07:50 +00:00
|
|
|
|
|
|
|
module:log("debug", "Got a MAM query between %s and %s", form["start"], form["end"]);
|
|
|
|
filter = {
|
|
|
|
start = datetime.parse(form["start"]); ["end"] = datetime.parse(form["end"])
|
|
|
|
};
|
2020-10-27 17:05:41 +00:00
|
|
|
end
|
2020-11-02 16:07:50 +00:00
|
|
|
local data, err = message_archive:find(channel_jid, filter);
|
|
|
|
if not data then
|
|
|
|
module:log("debug", "MAM error: %s", err);
|
2020-10-27 17:05:41 +00:00
|
|
|
|
2020-11-02 16:07:50 +00:00
|
|
|
if err == "item-not-found" then
|
|
|
|
origin.send(st.error_reply(stanza, "modify", "item-not-found"));
|
|
|
|
else
|
|
|
|
origin.send(st.error_reply(stanza, "cancel", "internal-server-error"));
|
|
|
|
end
|
|
|
|
return true;
|
|
|
|
end
|
2021-02-21 17:38:21 +00:00
|
|
|
for message_id, item, when in data do
|
2020-11-02 16:07:50 +00:00
|
|
|
local msg = st.stanza("message", { from = channel_jid, to = stanza.attr.from, type = "groupchat" })
|
2021-02-28 10:29:21 +00:00
|
|
|
:tag("result", { xmlns = namespaces.mam, queryid = query_id, id = message_id })
|
2020-11-02 16:07:50 +00:00
|
|
|
:tag("forwarded", { xmlns = "urn:xmpp:forward:0" })
|
|
|
|
:tag("delay", { xmlns = "urn:xmpp:delay", stamp = datetime.datetime(when) }):up();
|
|
|
|
msg:add_child(item);
|
|
|
|
origin.send(msg);
|
|
|
|
end
|
|
|
|
return true;
|
|
|
|
end);
|
|
|
|
|
2021-02-28 10:29:21 +00:00
|
|
|
module:hook("iq-get/bare/"..namespaces.mam..":query", function(event)
|
2020-11-02 16:07:50 +00:00
|
|
|
if event.stanza.attr.id ~= "form1" then return; end
|
2021-02-21 17:38:21 +00:00
|
|
|
|
2020-11-02 16:07:50 +00:00
|
|
|
module:log("debug", "Got a MAM query for supported fields");
|
|
|
|
|
2021-02-28 10:29:21 +00:00
|
|
|
-- TODO: Use dataforms:...
|
2020-11-02 16:07:50 +00:00
|
|
|
local ret = st.reply(event.stanza)
|
2021-02-28 10:29:21 +00:00
|
|
|
:tag("query", { xmlns = namespaces.mam })
|
2020-11-02 16:07:50 +00:00
|
|
|
:tag("x", { xmlns = "jabber:x:data", type = "form"})
|
|
|
|
:tag("field", { type = "hidden", var = "FORM_TYPE" })
|
2021-02-28 10:29:21 +00:00
|
|
|
:tag("value"):text(namespaces.mam):up():up()
|
2020-11-02 16:07:50 +00:00
|
|
|
:tag("field", { type = "jid-single", var = "with" }):up()
|
|
|
|
:tag("field", { type = "text-single", var = "start" }):up()
|
|
|
|
:tag("field", { type = "text-single", var = "end" });
|
|
|
|
module:send(ret);
|
|
|
|
return true;
|
|
|
|
end);
|
2020-10-27 17:05:41 +00:00
|
|
|
|
2021-02-28 10:29:21 +00:00
|
|
|
module:hook("iq-set/bare/"..namespaces.mix_core..":leave", function(event)
|
2020-10-27 17:05:41 +00:00
|
|
|
module:log("debug", "MIX leave received");
|
|
|
|
local origin, stanza = event.origin, event.stanza;
|
|
|
|
local from = jid.bare(stanza.attr.from);
|
2021-06-02 14:44:17 +00:00
|
|
|
local channel = find_channel(stanza.attr.to);
|
2020-10-27 17:05:41 +00:00
|
|
|
if not channel then
|
2021-06-02 14:44:17 +00:00
|
|
|
origin.send(lib_mix.channel_not_found(stanza));
|
2020-10-30 16:45:22 +00:00
|
|
|
return true;
|
2020-10-27 17:05:41 +00:00
|
|
|
end
|
|
|
|
|
2021-06-02 14:44:17 +00:00
|
|
|
local participant = channel:find_participant(from);
|
|
|
|
if not participant then
|
2020-10-29 16:08:04 +00:00
|
|
|
origin.send(st.error_reply(stanza,
|
|
|
|
"cancel",
|
|
|
|
"forbidden",
|
|
|
|
"Not a participant"));
|
2020-10-27 17:05:41 +00:00
|
|
|
channel:debug_print();
|
|
|
|
module:log("debug", "%s is not a participant in %s", from, channel.jid);
|
2020-10-30 16:45:22 +00:00
|
|
|
return true;
|
2020-10-27 17:05:41 +00:00
|
|
|
end
|
|
|
|
|
2021-02-28 10:29:21 +00:00
|
|
|
channel:remove_participant(from);
|
2020-10-27 17:05:41 +00:00
|
|
|
|
2021-02-22 07:45:35 +00:00
|
|
|
module:fire_event("mix-channel-leave", { channel = channel, participant = participant });
|
|
|
|
|
2021-02-28 10:29:21 +00:00
|
|
|
origin.send(st.reply(stanza):tag("leave", { xmlns = namespaces.mix_core }));
|
2020-10-27 17:05:41 +00:00
|
|
|
return true;
|
|
|
|
end);
|
|
|
|
|
2021-02-28 10:29:21 +00:00
|
|
|
module:hook("iq-set/bare/"..namespaces.mix_core..":join", function(event)
|
2020-10-27 17:05:41 +00:00
|
|
|
module:log("debug", "MIX join received");
|
|
|
|
|
|
|
|
local origin, stanza = event.origin, event.stanza;
|
2020-10-29 16:58:11 +00:00
|
|
|
local from = jid.bare(stanza.attr.from);
|
2021-06-02 14:44:17 +00:00
|
|
|
local channel = find_channel(stanza.attr.to);
|
2020-10-27 17:05:41 +00:00
|
|
|
if not channel then
|
2021-06-02 14:44:17 +00:00
|
|
|
origin:send(lib_mix.channel_not_found(stanza));
|
2020-10-30 16:45:22 +00:00
|
|
|
return true;
|
2020-10-27 17:05:41 +00:00
|
|
|
end
|
2021-02-21 17:38:21 +00:00
|
|
|
|
2020-10-29 16:58:11 +00:00
|
|
|
-- Prevent the user from joining multiple times
|
2021-06-02 14:44:17 +00:00
|
|
|
local participant = channel:find_participant(from);
|
|
|
|
if participant then
|
2020-10-30 16:45:22 +00:00
|
|
|
module:send(st.error_reply(stanza, "cancel", "bad-request", "User already joined"));
|
|
|
|
return true;
|
2020-10-29 16:58:11 +00:00
|
|
|
end
|
|
|
|
|
2021-02-28 10:29:21 +00:00
|
|
|
-- Is the user allowed to join?
|
2021-06-02 14:44:17 +00:00
|
|
|
if not channel:may_join(from) then
|
2021-02-28 10:29:21 +00:00
|
|
|
origin:send(st.error_reply(stanza, "cancel", "forbidden", "User or host is banned"));
|
|
|
|
return true;
|
|
|
|
end
|
|
|
|
|
2020-10-29 16:58:11 +00:00
|
|
|
local spid = channel:get_spid(from) or uuid.generate(); -- Stable Participant ID
|
2020-10-27 17:05:41 +00:00
|
|
|
local reply = st.reply(stanza)
|
2021-02-28 10:29:21 +00:00
|
|
|
:tag("join", { xmlns = namespaces.mix_core, id = spid });
|
|
|
|
local join = stanza:get_child("join", namespaces.mix_core);
|
2021-02-19 16:03:17 +00:00
|
|
|
local nick_tag = join:get_child("nick");
|
2021-02-28 10:29:21 +00:00
|
|
|
|
2021-06-02 14:44:17 +00:00
|
|
|
-- TODO: Check if the channel has mandatory nicks
|
2021-02-28 10:29:21 +00:00
|
|
|
|
2021-02-19 16:03:17 +00:00
|
|
|
local nick;
|
2021-02-21 17:38:21 +00:00
|
|
|
if not nick_tag then
|
2021-02-19 16:03:17 +00:00
|
|
|
nick = jid.node(from);
|
|
|
|
else
|
2021-02-21 17:38:21 +00:00
|
|
|
nick = nick_tag:get_text();
|
2021-02-19 16:03:17 +00:00
|
|
|
end
|
|
|
|
module:log("debug", "User joining as nick %s", nick);
|
2021-02-21 17:38:21 +00:00
|
|
|
|
2021-06-02 14:44:17 +00:00
|
|
|
local srv = channel:get_pubsub_service(jid.node(channel.jid));
|
2020-10-27 17:05:41 +00:00
|
|
|
local nodes = {};
|
2020-10-29 16:58:11 +00:00
|
|
|
local has_subscribed_once = false;
|
|
|
|
local first_error = nil;
|
2021-02-28 10:29:21 +00:00
|
|
|
local owner_or_admin = channel:is_admin(from) or channel:is_owner(from);
|
2020-10-27 17:05:41 +00:00
|
|
|
for subscribe in join:childtags("subscribe") do
|
2021-02-28 10:29:21 +00:00
|
|
|
-- May the user subscribe to the node?
|
2020-10-27 17:05:41 +00:00
|
|
|
module:log("debug", "Subscribing user to node %s", subscribe.attr.node);
|
2021-02-28 10:29:21 +00:00
|
|
|
if channel:may_subscribe(from, subscribe.attr.node, true) then
|
|
|
|
local ok, err = srv:add_subscription(subscribe.attr.node, true, from);
|
|
|
|
if not ok then
|
|
|
|
module:log("debug", "Error during subscription: %s", err);
|
|
|
|
|
|
|
|
-- MIX-CORE says that the first error should be returned when
|
|
|
|
-- no of the requested nodes could be subscribed to
|
|
|
|
if first_error ~= nil then
|
|
|
|
first_error = err;
|
|
|
|
end
|
|
|
|
else
|
|
|
|
table.insert(nodes, subscribe.attr.node);
|
|
|
|
reply:tag("subscribe", { node = subscribe.attr.node }):up();
|
|
|
|
has_subscribed_once = true;
|
2020-10-29 16:58:11 +00:00
|
|
|
end
|
2021-06-02 14:44:17 +00:00
|
|
|
|
|
|
|
-- Set the correct affiliation
|
|
|
|
channel:set_affiliation(subscribe.attr.node, from, "member");
|
2020-10-27 17:05:41 +00:00
|
|
|
else
|
2021-02-28 10:29:21 +00:00
|
|
|
module:log("debug", "Error during subscription: may_subscribe returned false");
|
|
|
|
if first_error ~= nil then
|
|
|
|
first_error = "Channel does not allow subscribing";
|
|
|
|
end
|
2020-10-27 17:05:41 +00:00
|
|
|
end
|
|
|
|
end
|
2020-10-29 16:58:11 +00:00
|
|
|
|
|
|
|
if not has_subscribed_once then
|
2021-02-28 10:29:21 +00:00
|
|
|
-- TODO: This does not work
|
|
|
|
origin:send(st.error_reply(stanza, "cancel", first_error));
|
2020-10-30 16:45:22 +00:00
|
|
|
return true;
|
2020-10-29 16:58:11 +00:00
|
|
|
end
|
|
|
|
|
2021-06-02 14:44:17 +00:00
|
|
|
-- TODO: Participant configuration
|
2021-02-20 19:33:46 +00:00
|
|
|
|
2021-06-02 14:44:17 +00:00
|
|
|
local participant = Participant:new(jid.bare(from), nick, {});
|
2021-02-20 19:33:46 +00:00
|
|
|
channel.subscriptions[from] = nodes;
|
|
|
|
table.insert(channel.participants, participant)
|
|
|
|
channel:set_spid(jid.bare(stanza.attr.from), spid);
|
2021-02-28 10:29:21 +00:00
|
|
|
channel:publish_participant(spid, participant);
|
2021-02-20 19:33:46 +00:00
|
|
|
channel:save_state();
|
2020-10-27 17:05:41 +00:00
|
|
|
|
2021-02-22 07:45:35 +00:00
|
|
|
module:fire_event("mix-channel-join", { channel = channel, participant = participant });
|
|
|
|
|
2021-02-28 10:29:21 +00:00
|
|
|
-- We do not reuse nick_tag as it might be nil
|
|
|
|
reply:tag("nick"):text(nick):up();
|
|
|
|
reply:add_child(anon.form:form(config, "result"));
|
2020-10-27 17:05:41 +00:00
|
|
|
origin.send(reply);
|
|
|
|
return true
|
|
|
|
end);
|
|
|
|
|
2021-02-28 10:29:21 +00:00
|
|
|
module:hook("iq-set/bare/"..namespaces.mix_core..":setnick", function(event)
|
2020-10-27 17:05:41 +00:00
|
|
|
module:log("debug", "MIX setnick received");
|
|
|
|
local origin, stanza = event.origin, event.stanza;
|
|
|
|
local from = jid.bare(stanza.attr.from);
|
2021-06-02 14:44:17 +00:00
|
|
|
local channel = find_channel(stanza.attr.to);
|
2020-10-27 17:05:41 +00:00
|
|
|
if not channel then
|
2021-06-02 14:44:17 +00:00
|
|
|
origin.send(lib_mix.channel_not_found(stanza));
|
2020-10-30 16:45:22 +00:00
|
|
|
return true;
|
2020-10-27 17:05:41 +00:00
|
|
|
end
|
|
|
|
|
2021-06-02 14:44:17 +00:00
|
|
|
local participant = channel:find_participant(from);
|
|
|
|
if not participant then
|
2020-10-27 17:05:41 +00:00
|
|
|
channel:debug_print();
|
|
|
|
module:log("debug", "%s is not a participant in %s", from, channel.jid);
|
2020-10-30 16:45:22 +00:00
|
|
|
return true;
|
2020-10-27 17:05:41 +00:00
|
|
|
end
|
2021-02-21 17:38:21 +00:00
|
|
|
|
2020-10-29 16:58:11 +00:00
|
|
|
-- NOTE: Prosody should guarantee us that the setnick stanza exists
|
2021-02-28 10:29:21 +00:00
|
|
|
local setnick = stanza:get_child("setnick", namespaces.mix_core);
|
2020-10-29 16:58:11 +00:00
|
|
|
local nick = setnick:get_child("nick");
|
2021-02-22 07:33:43 +00:00
|
|
|
if nick == nil then
|
2020-10-29 16:58:11 +00:00
|
|
|
origin.send(st.error_reply(stanza, "cancel", "bad-request", "Missing <nick>"));
|
2020-10-30 16:45:22 +00:00
|
|
|
return true;
|
2020-10-29 16:58:11 +00:00
|
|
|
end
|
|
|
|
|
2020-10-27 17:05:41 +00:00
|
|
|
-- Change the nick
|
2021-06-02 14:44:17 +00:00
|
|
|
participant.nick = nick:get_text();
|
2020-10-27 17:05:41 +00:00
|
|
|
-- Inform all other members
|
2021-02-28 10:29:21 +00:00
|
|
|
channel:publish_participant(channel:get_spid(participant.jid), participant);
|
2020-10-27 17:05:41 +00:00
|
|
|
|
2021-02-22 07:45:35 +00:00
|
|
|
module:fire_event("mix-change-nick", { channel = channel, participant = participant });
|
|
|
|
|
2020-10-27 17:05:41 +00:00
|
|
|
origin.send(st.reply(stanza)
|
2021-02-28 10:29:21 +00:00
|
|
|
:tag("setnick", { xmlns = namespaces.mix_core })
|
2020-10-29 16:58:11 +00:00
|
|
|
:tag("nick"):text(nick:get_text()));
|
2020-10-27 17:05:41 +00:00
|
|
|
channel:save_state();
|
|
|
|
return true;
|
|
|
|
end);
|
|
|
|
|
2021-02-21 17:38:21 +00:00
|
|
|
local function create_channel(node, creator, adhoc)
|
2021-02-28 10:29:21 +00:00
|
|
|
-- TODO: Now all properties from the admin dataform are covered
|
|
|
|
local channel = Channel:new(string.format("%s@%s", node, host), -- Channel JID
|
2020-10-27 17:05:41 +00:00
|
|
|
default_channel_name,
|
|
|
|
default_channel_description,
|
2021-02-28 10:29:21 +00:00
|
|
|
{}, -- Participants
|
|
|
|
{}, -- Administrators
|
|
|
|
{ creator }, -- Owners
|
|
|
|
{}, -- Subscriptions
|
|
|
|
{}, -- SPID mapping
|
|
|
|
{ creator }, -- Contacts
|
|
|
|
adhoc, -- Is channel an AdHoc channel
|
|
|
|
{}, -- Allowed
|
|
|
|
{}, -- Banned
|
2021-06-02 14:44:17 +00:00
|
|
|
lib_mix.default_channel_configuration, -- Channel config
|
2021-02-28 10:29:21 +00:00
|
|
|
{}); -- Present nodes
|
|
|
|
|
2021-06-02 14:44:17 +00:00
|
|
|
-- Create the PubSub nodes
|
|
|
|
local srv = channel:get_pubsub_service();
|
2021-02-28 10:29:21 +00:00
|
|
|
for _, psnode in ipairs(default_mix_nodes) do
|
2021-02-19 16:03:17 +00:00
|
|
|
srv:create(psnode, true, {
|
2021-06-02 14:44:17 +00:00
|
|
|
-- NOTE: Our custom PubSub service is persistent only, so we don't
|
|
|
|
-- need to explicitly set it
|
|
|
|
["access_model"] = lib_mix.get_node_access_model(psnode, adhoc);
|
|
|
|
["max_items"] = lib_mix.get_node_max_items(psnode);
|
2021-02-19 16:03:17 +00:00
|
|
|
});
|
2021-06-02 14:44:17 +00:00
|
|
|
channel:set_affiliation(psnode, creator, "creator");
|
|
|
|
table.insert(channel.nodes, psnode);
|
2020-10-30 16:45:22 +00:00
|
|
|
end
|
2021-02-21 17:38:21 +00:00
|
|
|
|
2021-06-02 14:44:17 +00:00
|
|
|
channel:publish_info(srv);
|
2020-10-27 17:05:41 +00:00
|
|
|
table.insert(channels, channel);
|
|
|
|
end
|
|
|
|
|
2021-02-28 10:29:21 +00:00
|
|
|
module:hook("iq-set/host/"..namespaces.mix_core..":create", function(event)
|
2020-10-27 17:05:41 +00:00
|
|
|
module:log("debug", "MIX create received");
|
|
|
|
local origin, stanza = event.origin, event.stanza;
|
|
|
|
local from = jid.bare(stanza.attr.from);
|
|
|
|
|
2020-10-29 17:10:08 +00:00
|
|
|
-- Check permissions
|
|
|
|
if not can_create_channels(from) then
|
|
|
|
origin.send(st.error_reply(stanza, "cancel", "forbidden", "Not authorized to create channels"));
|
2020-10-30 16:45:22 +00:00
|
|
|
return true;
|
2020-10-29 17:10:08 +00:00
|
|
|
end
|
|
|
|
|
2021-02-28 10:29:21 +00:00
|
|
|
local create = stanza:get_child("create", namespaces.mix_core);
|
2020-10-27 17:05:41 +00:00
|
|
|
local node;
|
|
|
|
if create.attr.channel ~= nil then
|
|
|
|
-- Create non-adhoc channel
|
2020-11-02 16:07:50 +00:00
|
|
|
module:log("debug", "Attempting to create channel %s", create.attr.channel);
|
2020-10-27 17:05:41 +00:00
|
|
|
node = create.attr.channel;
|
2021-06-02 14:44:17 +00:00
|
|
|
local channel = find_channel(create.attr.channel.."@"..stanza.attr.to);
|
|
|
|
if channel then
|
2020-10-29 16:08:04 +00:00
|
|
|
origin.send(st.error_reply(stanza,
|
|
|
|
"cancel",
|
|
|
|
"conflict",
|
|
|
|
"Channel already exists"));
|
2020-10-30 16:45:22 +00:00
|
|
|
return true;
|
2020-10-27 17:05:41 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
create_channel(create.attr.channel, from, false);
|
|
|
|
else
|
|
|
|
-- Create adhoc channel
|
2020-10-29 16:58:11 +00:00
|
|
|
while (true) do
|
|
|
|
node = id.short();
|
2021-06-02 14:44:17 +00:00
|
|
|
local ch = find_channel(string.format("%s@%s", node, host));
|
|
|
|
if not ch then
|
2020-10-29 16:58:11 +00:00
|
|
|
break;
|
|
|
|
end
|
|
|
|
end
|
2021-02-21 17:38:21 +00:00
|
|
|
|
2020-10-27 17:05:41 +00:00
|
|
|
create_channel(node, from, true);
|
|
|
|
end
|
|
|
|
module:log("debug", "Channel %s created with %s as owner", node, from);
|
2021-02-22 07:45:35 +00:00
|
|
|
-- TODO: Add an event
|
2020-10-27 17:05:41 +00:00
|
|
|
|
|
|
|
origin.send(st.reply(stanza)
|
2021-02-28 10:29:21 +00:00
|
|
|
:tag("create", { xmlns = namespaces.mix_core, channel = node }));
|
2020-10-27 17:05:41 +00:00
|
|
|
save_channels();
|
|
|
|
return true;
|
|
|
|
end);
|
|
|
|
|
2021-02-28 10:29:21 +00:00
|
|
|
module:hook("iq-set/host/"..namespaces.mix_core..":destroy", function(event)
|
2020-10-27 17:05:41 +00:00
|
|
|
module:log("debug", "MIX destroy received");
|
|
|
|
local origin, stanza = event.origin, event.stanza;
|
|
|
|
local from = jid.bare(stanza.attr.from);
|
|
|
|
|
2021-02-28 10:29:21 +00:00
|
|
|
local destroy = stanza:get_child("destroy", namespaces.mix_core);
|
2021-02-21 17:38:21 +00:00
|
|
|
local node = destroy.attr.channel;
|
2020-10-27 17:05:41 +00:00
|
|
|
local node_jid = string.format("%s@%s", node, host);
|
2021-06-02 14:44:17 +00:00
|
|
|
local channel = find_channel(node_jid);
|
2020-10-27 17:05:41 +00:00
|
|
|
if not channel then
|
2021-06-02 14:44:17 +00:00
|
|
|
origin.send(lib_mix.channel_not_found(stanza));
|
2020-10-30 16:45:22 +00:00
|
|
|
return true;
|
2020-10-27 17:05:41 +00:00
|
|
|
end
|
2020-11-02 16:07:50 +00:00
|
|
|
|
2021-06-02 14:44:17 +00:00
|
|
|
-- TODO(MIX-ADMIN): Check if the user is the owner of the channel
|
|
|
|
-- Until then, we just check if the user is in the contacts
|
|
|
|
if helpers.find_str(channel.contacts, from) == -1 then
|
2020-11-02 16:07:50 +00:00
|
|
|
origin.send(st.error_reply(stanza, "cancel", "forbidden"));
|
|
|
|
return true;
|
|
|
|
end
|
2021-02-28 10:29:21 +00:00
|
|
|
|
2021-06-02 14:44:17 +00:00
|
|
|
if module:fire_event("mix-destroy-channel", { channel = channel }) then
|
|
|
|
return true;
|
|
|
|
end
|
|
|
|
|
2020-10-27 17:05:41 +00:00
|
|
|
-- Remove all registered nodes
|
2021-06-02 14:44:17 +00:00
|
|
|
local srv = channel:get_pubsub_service();
|
|
|
|
for _, psnode in pairs(channel.nodes) do
|
|
|
|
srv:delete(psnode, true);
|
2020-10-27 17:05:41 +00:00
|
|
|
end
|
2021-06-02 14:44:17 +00:00
|
|
|
channels = array.filter(channels, function (c) return c.jid ~= node_jid end);
|
2021-02-21 17:38:21 +00:00
|
|
|
|
2021-06-02 14:44:17 +00:00
|
|
|
module:fire_event("mix-channel-destroyed", { channel = channel });
|
2020-10-27 17:05:41 +00:00
|
|
|
module:log("debug", "Channel %s destroyed", node);
|
|
|
|
|
|
|
|
origin.send(st.reply(stanza));
|
|
|
|
save_channels();
|
|
|
|
return true;
|
|
|
|
end);
|
|
|
|
|
|
|
|
module:hook("message/bare", function(event)
|
2020-11-02 16:07:50 +00:00
|
|
|
module:log("debug", "MIX message received");
|
2020-10-29 16:08:04 +00:00
|
|
|
local stanza, origin = event.stanza, event.origin;
|
2020-10-27 17:05:41 +00:00
|
|
|
if stanza.attr.type ~= "groupchat" then
|
2020-11-02 16:07:50 +00:00
|
|
|
origin.send(st.error_reply(stanza, "modify", "bad-request", "Non-groupchat message"));
|
2020-10-30 16:45:22 +00:00
|
|
|
return true;
|
2020-10-27 17:05:41 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
local from = jid.bare(stanza.attr.from);
|
2021-06-02 14:44:17 +00:00
|
|
|
local channel = find_channel(stanza.attr.to);
|
2020-10-27 17:05:41 +00:00
|
|
|
if not channel then
|
2021-06-02 14:44:17 +00:00
|
|
|
origin.send(lib_mix.channel_not_found(stanza));
|
2020-10-30 16:45:22 +00:00
|
|
|
return true;
|
2020-10-27 17:05:41 +00:00
|
|
|
end
|
2020-11-02 16:07:50 +00:00
|
|
|
|
2021-06-02 14:44:17 +00:00
|
|
|
local participant = channel:find_participant(from);
|
|
|
|
if not participant then
|
2020-10-29 16:08:04 +00:00
|
|
|
origin.send(st.error_reply(stanza, "cancel", "forbidden", "Not a participant"));
|
2020-10-30 16:45:22 +00:00
|
|
|
return true;
|
2020-10-27 17:05:41 +00:00
|
|
|
end
|
|
|
|
|
2021-02-28 10:29:21 +00:00
|
|
|
-- Handles sending the message accordingly, firing an event and
|
|
|
|
-- even doing nothing if an event handler for "mix-broadcast-message"
|
|
|
|
-- returns true.
|
|
|
|
channel:broadcast_message(stanza, participant, message_archive);
|
|
|
|
return true;
|
|
|
|
end);
|