2016-03-08 18:04:29 +01:00
|
|
|
%%%-------------------------------------------------------------------
|
2016-12-27 10:44:07 +01:00
|
|
|
%%% File : mod_mix.erl
|
|
|
|
%%% Author : Evgeny Khramtsov <ekhramtsov@process-one.net>
|
2016-03-08 18:04:29 +01:00
|
|
|
%%% Created : 2 Mar 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
|
2016-12-27 10:44:07 +01:00
|
|
|
%%%
|
|
|
|
%%%
|
2017-01-02 21:41:53 +01:00
|
|
|
%%% ejabberd, Copyright (C) 2002-2017 ProcessOne
|
2016-12-27 10:44:07 +01:00
|
|
|
%%%
|
|
|
|
%%% This program is free software; you can redistribute it and/or
|
|
|
|
%%% modify it under the terms of the GNU General Public License as
|
|
|
|
%%% published by the Free Software Foundation; either version 2 of the
|
|
|
|
%%% License, or (at your option) any later version.
|
|
|
|
%%%
|
|
|
|
%%% This program is distributed in the hope that it will be useful,
|
|
|
|
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
%%% General Public License for more details.
|
|
|
|
%%%
|
|
|
|
%%% You should have received a copy of the GNU General Public License along
|
|
|
|
%%% with this program; if not, write to the Free Software Foundation, Inc.,
|
|
|
|
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
|
|
%%%
|
|
|
|
%%%----------------------------------------------------------------------
|
|
|
|
|
2016-03-08 18:04:29 +01:00
|
|
|
-module(mod_mix).
|
|
|
|
|
|
|
|
-behaviour(gen_server).
|
|
|
|
-behaviour(gen_mod).
|
|
|
|
|
|
|
|
%% API
|
2017-02-14 10:39:26 +01:00
|
|
|
-export([start/2, stop/1, process_iq/1,
|
2016-03-08 18:04:29 +01:00
|
|
|
disco_items/5, disco_identity/5, disco_info/5,
|
2016-07-06 13:58:48 +02:00
|
|
|
disco_features/5, mod_opt_type/1, depends/2]).
|
2016-03-08 18:04:29 +01:00
|
|
|
|
|
|
|
%% gen_server callbacks
|
|
|
|
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
|
|
|
terminate/2, code_change/3]).
|
|
|
|
|
|
|
|
-include("logger.hrl").
|
2016-07-31 07:51:47 +02:00
|
|
|
-include("xmpp.hrl").
|
2016-03-08 18:04:29 +01:00
|
|
|
|
|
|
|
-define(NODES, [?NS_MIX_NODES_MESSAGES,
|
|
|
|
?NS_MIX_NODES_PRESENCE,
|
|
|
|
?NS_MIX_NODES_PARTICIPANTS,
|
|
|
|
?NS_MIX_NODES_SUBJECT,
|
|
|
|
?NS_MIX_NODES_CONFIG]).
|
|
|
|
|
|
|
|
-record(state, {server_host :: binary(),
|
|
|
|
host :: binary()}).
|
|
|
|
|
|
|
|
%%%===================================================================
|
|
|
|
%%% API
|
|
|
|
%%%===================================================================
|
|
|
|
start(Host, Opts) ->
|
2017-02-14 10:39:26 +01:00
|
|
|
gen_mod:start_child(?MODULE, Host, Opts).
|
2016-03-08 18:04:29 +01:00
|
|
|
|
|
|
|
stop(Host) ->
|
2017-02-14 10:39:26 +01:00
|
|
|
gen_mod:stop_child(?MODULE, Host).
|
2016-03-08 18:04:29 +01:00
|
|
|
|
2016-09-08 16:08:48 +02:00
|
|
|
-spec disco_features({error, stanza_error()} | {result, [binary()]} | empty,
|
2016-08-09 09:56:32 +02:00
|
|
|
jid(), jid(), binary(), binary()) -> {result, [binary()]}.
|
2016-03-08 18:04:29 +01:00
|
|
|
disco_features(_Acc, _From, _To, _Node, _Lang) ->
|
|
|
|
{result, [?NS_MIX_0]}.
|
|
|
|
|
|
|
|
disco_items(_Acc, _From, To, _Node, _Lang) when To#jid.luser /= <<"">> ->
|
2016-07-31 07:51:47 +02:00
|
|
|
BareTo = jid:remove_resource(To),
|
|
|
|
{result, [#disco_item{jid = BareTo, node = Node} || Node <- ?NODES]};
|
2016-03-08 18:04:29 +01:00
|
|
|
disco_items(_Acc, _From, _To, _Node, _Lang) ->
|
|
|
|
{result, []}.
|
|
|
|
|
|
|
|
disco_identity(Acc, _From, To, _Node, _Lang) when To#jid.luser == <<"">> ->
|
2016-07-31 07:51:47 +02:00
|
|
|
Acc ++ [#identity{category = <<"conference">>,
|
|
|
|
name = <<"MIX service">>,
|
|
|
|
type = <<"text">>}];
|
2016-03-08 18:04:29 +01:00
|
|
|
disco_identity(Acc, _From, _To, _Node, _Lang) ->
|
2016-07-31 07:51:47 +02:00
|
|
|
Acc ++ [#identity{category = <<"conference">>,
|
|
|
|
type = <<"mix">>}].
|
2016-03-08 18:04:29 +01:00
|
|
|
|
2016-08-09 09:56:32 +02:00
|
|
|
-spec disco_info([xdata()], binary(), module(), binary(), binary()) -> [xdata()];
|
|
|
|
([xdata()], jid(), jid(), binary(), binary()) -> [xdata()].
|
2016-03-08 18:04:29 +01:00
|
|
|
disco_info(_Acc, _From, To, _Node, _Lang) when is_atom(To) ->
|
2016-07-31 07:51:47 +02:00
|
|
|
[#xdata{type = result,
|
|
|
|
fields = [#xdata_field{var = <<"FORM_TYPE">>,
|
|
|
|
type = hidden,
|
|
|
|
values = [?NS_MIX_SERVICEINFO_0]}]}];
|
2016-03-08 18:04:29 +01:00
|
|
|
disco_info(Acc, _From, _To, _Node, _Lang) ->
|
|
|
|
Acc.
|
|
|
|
|
2016-07-31 07:51:47 +02:00
|
|
|
process_iq(#iq{type = set, from = From, to = To,
|
|
|
|
sub_els = [#mix_join{subscribe = SubNodes}]} = IQ) ->
|
|
|
|
Nodes = [Node || Node <- SubNodes, lists:member(Node, ?NODES)],
|
2016-03-08 18:04:29 +01:00
|
|
|
case subscribe_nodes(From, To, Nodes) of
|
|
|
|
{result, _} ->
|
|
|
|
case publish_participant(From, To) of
|
|
|
|
{result, _} ->
|
2016-07-31 07:51:47 +02:00
|
|
|
BareFrom = jid:remove_resource(From),
|
|
|
|
xmpp:make_iq_result(
|
|
|
|
IQ, #mix_join{jid = BareFrom, subscribe = Nodes});
|
2016-03-08 18:04:29 +01:00
|
|
|
{error, Err} ->
|
2016-07-31 07:51:47 +02:00
|
|
|
xmpp:make_error(IQ, Err)
|
2016-03-08 18:04:29 +01:00
|
|
|
end;
|
|
|
|
{error, Err} ->
|
2016-07-31 07:51:47 +02:00
|
|
|
xmpp:make_error(IQ, Err)
|
2016-03-08 18:04:29 +01:00
|
|
|
end;
|
2016-07-31 07:51:47 +02:00
|
|
|
process_iq(#iq{type = set, from = From, to = To,
|
|
|
|
sub_els = [#mix_leave{}]} = IQ) ->
|
2016-03-08 18:04:29 +01:00
|
|
|
case delete_participant(From, To) of
|
|
|
|
{result, _} ->
|
|
|
|
case unsubscribe_nodes(From, To, ?NODES) of
|
|
|
|
{result, _} ->
|
2016-07-31 07:51:47 +02:00
|
|
|
xmpp:make_iq_result(IQ);
|
2016-03-08 18:04:29 +01:00
|
|
|
{error, Err} ->
|
2016-07-31 07:51:47 +02:00
|
|
|
xmpp:make_error(IQ, Err)
|
2016-03-08 18:04:29 +01:00
|
|
|
end;
|
|
|
|
{error, Err} ->
|
2016-07-31 07:51:47 +02:00
|
|
|
xmpp:make_error(IQ, Err)
|
2016-03-08 18:04:29 +01:00
|
|
|
end;
|
2016-07-31 07:51:47 +02:00
|
|
|
process_iq(#iq{lang = Lang} = IQ) ->
|
2016-03-31 10:00:29 +02:00
|
|
|
Txt = <<"Unsupported MIX query">>,
|
2016-07-31 07:51:47 +02:00
|
|
|
xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang)).
|
2016-03-08 18:04:29 +01:00
|
|
|
|
|
|
|
%%%===================================================================
|
|
|
|
%%% gen_server callbacks
|
|
|
|
%%%===================================================================
|
|
|
|
init([ServerHost, Opts]) ->
|
2017-02-14 08:25:08 +01:00
|
|
|
process_flag(trap_exit, true),
|
2016-03-08 18:04:29 +01:00
|
|
|
Host = gen_mod:get_opt_host(ServerHost, Opts, <<"mix.@HOST@">>),
|
|
|
|
IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
|
|
|
|
one_queue),
|
|
|
|
ConfigTab = gen_mod:get_module_proc(Host, config),
|
|
|
|
ets:new(ConfigTab, [named_table]),
|
|
|
|
ets:insert(ConfigTab, {plugins, [<<"mix">>]}),
|
|
|
|
ejabberd_hooks:add(disco_local_items, Host, ?MODULE, disco_items, 100),
|
|
|
|
ejabberd_hooks:add(disco_local_features, Host, ?MODULE, disco_features, 100),
|
|
|
|
ejabberd_hooks:add(disco_local_identity, Host, ?MODULE, disco_identity, 100),
|
|
|
|
ejabberd_hooks:add(disco_sm_items, Host, ?MODULE, disco_items, 100),
|
|
|
|
ejabberd_hooks:add(disco_sm_features, Host, ?MODULE, disco_features, 100),
|
|
|
|
ejabberd_hooks:add(disco_sm_identity, Host, ?MODULE, disco_identity, 100),
|
|
|
|
ejabberd_hooks:add(disco_info, Host, ?MODULE, disco_info, 100),
|
|
|
|
gen_iq_handler:add_iq_handler(ejabberd_local, Host,
|
|
|
|
?NS_DISCO_ITEMS, mod_disco,
|
|
|
|
process_local_iq_items, IQDisc),
|
|
|
|
gen_iq_handler:add_iq_handler(ejabberd_local, Host,
|
|
|
|
?NS_DISCO_INFO, mod_disco,
|
|
|
|
process_local_iq_info, IQDisc),
|
|
|
|
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
|
|
|
|
?NS_DISCO_ITEMS, mod_disco,
|
|
|
|
process_local_iq_items, IQDisc),
|
|
|
|
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
|
|
|
|
?NS_DISCO_INFO, mod_disco,
|
|
|
|
process_local_iq_info, IQDisc),
|
|
|
|
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
|
|
|
|
?NS_PUBSUB, mod_pubsub, iq_sm, IQDisc),
|
|
|
|
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
|
|
|
|
?NS_MIX_0, ?MODULE, process_iq, IQDisc),
|
2016-03-13 09:38:40 +01:00
|
|
|
ejabberd_router:register_route(Host, ServerHost),
|
2016-03-08 18:04:29 +01:00
|
|
|
{ok, #state{server_host = ServerHost, host = Host}}.
|
|
|
|
|
|
|
|
handle_call(_Request, _From, State) ->
|
|
|
|
Reply = ok,
|
|
|
|
{reply, Reply, State}.
|
|
|
|
|
|
|
|
handle_cast(_Msg, State) ->
|
|
|
|
{noreply, State}.
|
|
|
|
|
2017-02-16 09:00:26 +01:00
|
|
|
handle_info({route, Packet}, State) ->
|
|
|
|
case catch do_route(State, Packet) of
|
2016-03-08 18:04:29 +01:00
|
|
|
{'EXIT', _} = Err ->
|
|
|
|
try
|
2017-02-16 09:00:26 +01:00
|
|
|
?ERROR_MSG("failed to route packet:~n~s~nReason: ~p",
|
|
|
|
[xmpp:pp(Packet), Err]),
|
2016-07-31 07:51:47 +02:00
|
|
|
Error = xmpp:err_internal_server_error(),
|
2017-02-16 09:00:26 +01:00
|
|
|
ejabberd_router:route_error(Packet, Error)
|
2016-03-08 18:04:29 +01:00
|
|
|
catch _:_ ->
|
|
|
|
ok
|
|
|
|
end;
|
|
|
|
_ ->
|
|
|
|
ok
|
|
|
|
end,
|
|
|
|
{noreply, State};
|
|
|
|
handle_info(_Info, State) ->
|
|
|
|
{noreply, State}.
|
|
|
|
|
2016-03-09 09:14:45 +01:00
|
|
|
terminate(_Reason, #state{host = Host}) ->
|
|
|
|
ejabberd_hooks:delete(disco_local_items, Host, ?MODULE, disco_items, 100),
|
|
|
|
ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, disco_features, 100),
|
|
|
|
ejabberd_hooks:delete(disco_local_identity, Host, ?MODULE, disco_identity, 100),
|
|
|
|
ejabberd_hooks:delete(disco_sm_items, Host, ?MODULE, disco_items, 100),
|
|
|
|
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, disco_features, 100),
|
|
|
|
ejabberd_hooks:delete(disco_sm_identity, Host, ?MODULE, disco_identity, 100),
|
|
|
|
ejabberd_hooks:delete(disco_info, Host, ?MODULE, disco_info, 100),
|
|
|
|
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS),
|
|
|
|
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO),
|
|
|
|
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_DISCO_ITEMS),
|
|
|
|
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_DISCO_INFO),
|
|
|
|
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_PUBSUB),
|
|
|
|
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_MIX_0),
|
|
|
|
ejabberd_router:unregister_route(Host),
|
2016-03-08 18:04:29 +01:00
|
|
|
ok.
|
|
|
|
|
|
|
|
code_change(_OldVsn, State, _Extra) ->
|
|
|
|
{ok, State}.
|
|
|
|
|
|
|
|
%%%===================================================================
|
|
|
|
%%% Internal functions
|
|
|
|
%%%===================================================================
|
2017-02-16 09:00:26 +01:00
|
|
|
do_route(_State, #iq{} = Packet) ->
|
|
|
|
ejabberd_router:process_iq(Packet);
|
|
|
|
do_route(_State, #presence{from = From, to = To, type = unavailable})
|
2016-03-08 18:04:29 +01:00
|
|
|
when To#jid.luser /= <<"">> ->
|
2016-07-31 07:51:47 +02:00
|
|
|
delete_presence(From, To);
|
2017-02-16 09:00:26 +01:00
|
|
|
do_route(_State, _Packet) ->
|
2016-03-08 18:04:29 +01:00
|
|
|
ok.
|
|
|
|
|
|
|
|
subscribe_nodes(From, To, Nodes) ->
|
|
|
|
LTo = jid:tolower(jid:remove_resource(To)),
|
|
|
|
LFrom = jid:tolower(jid:remove_resource(From)),
|
|
|
|
lists:foldl(
|
|
|
|
fun(_Node, {error, _} = Err) ->
|
|
|
|
Err;
|
|
|
|
(Node, {result, _}) ->
|
2016-09-13 11:30:05 +02:00
|
|
|
case mod_pubsub:subscribe_node(LTo, Node, From, From, []) of
|
2016-03-08 18:04:29 +01:00
|
|
|
{error, _} = Err ->
|
|
|
|
case is_item_not_found(Err) of
|
|
|
|
true ->
|
|
|
|
case mod_pubsub:create_node(
|
|
|
|
LTo, To#jid.lserver, Node, LFrom, <<"mix">>) of
|
|
|
|
{result, _} ->
|
2016-09-13 11:30:05 +02:00
|
|
|
mod_pubsub:subscribe_node(LTo, Node, From, From, []);
|
2016-03-08 18:04:29 +01:00
|
|
|
Error ->
|
|
|
|
Error
|
|
|
|
end;
|
|
|
|
false ->
|
|
|
|
Err
|
|
|
|
end;
|
|
|
|
{result, _} = Result ->
|
|
|
|
Result
|
|
|
|
end
|
|
|
|
end, {result, []}, Nodes).
|
|
|
|
|
|
|
|
unsubscribe_nodes(From, To, Nodes) ->
|
|
|
|
LTo = jid:tolower(jid:remove_resource(To)),
|
2016-09-13 11:30:05 +02:00
|
|
|
BareFrom = jid:remove_resource(From),
|
2016-03-08 18:04:29 +01:00
|
|
|
lists:foldl(
|
|
|
|
fun(_Node, {error, _} = Err) ->
|
|
|
|
Err;
|
|
|
|
(Node, {result, _} = Result) ->
|
2016-09-13 11:30:05 +02:00
|
|
|
case mod_pubsub:unsubscribe_node(LTo, Node, From, BareFrom, <<"">>) of
|
2016-03-08 18:04:29 +01:00
|
|
|
{error, _} = Err ->
|
|
|
|
case is_not_subscribed(Err) of
|
|
|
|
true -> Result;
|
|
|
|
_ -> Err
|
|
|
|
end;
|
|
|
|
{result, _} = Res ->
|
|
|
|
Res
|
|
|
|
end
|
|
|
|
end, {result, []}, Nodes).
|
|
|
|
|
|
|
|
publish_participant(From, To) ->
|
2016-07-31 07:51:47 +02:00
|
|
|
BareFrom = jid:remove_resource(From),
|
|
|
|
LFrom = jid:tolower(BareFrom),
|
2016-03-08 18:04:29 +01:00
|
|
|
LTo = jid:tolower(jid:remove_resource(To)),
|
2016-07-31 07:51:47 +02:00
|
|
|
Participant = #mix_participant{jid = BareFrom},
|
2017-03-14 00:31:51 +01:00
|
|
|
ItemID = str:sha(jid:encode(LFrom)),
|
2016-03-08 18:04:29 +01:00
|
|
|
mod_pubsub:publish_item(
|
|
|
|
LTo, To#jid.lserver, ?NS_MIX_NODES_PARTICIPANTS,
|
2016-07-31 07:51:47 +02:00
|
|
|
From, ItemID, [xmpp:encode(Participant)]).
|
2016-03-08 18:04:29 +01:00
|
|
|
|
|
|
|
delete_presence(From, To) ->
|
|
|
|
LFrom = jid:tolower(From),
|
|
|
|
LTo = jid:tolower(jid:remove_resource(To)),
|
|
|
|
case mod_pubsub:get_items(LTo, ?NS_MIX_NODES_PRESENCE) of
|
|
|
|
Items when is_list(Items) ->
|
|
|
|
lists:foreach(
|
2016-07-31 07:51:47 +02:00
|
|
|
fun({pubsub_item, {ItemID, _}, _, {_, LJID}, _})
|
|
|
|
when LJID == LFrom ->
|
2016-03-08 18:04:29 +01:00
|
|
|
delete_item(From, To, ?NS_MIX_NODES_PRESENCE, ItemID);
|
|
|
|
(_) ->
|
|
|
|
ok
|
|
|
|
end, Items);
|
|
|
|
_ ->
|
|
|
|
ok
|
|
|
|
end.
|
|
|
|
|
|
|
|
delete_participant(From, To) ->
|
|
|
|
LFrom = jid:tolower(jid:remove_resource(From)),
|
2017-03-14 00:31:51 +01:00
|
|
|
ItemID = str:sha(jid:encode(LFrom)),
|
2016-03-08 18:04:29 +01:00
|
|
|
delete_presence(From, To),
|
|
|
|
delete_item(From, To, ?NS_MIX_NODES_PARTICIPANTS, ItemID).
|
|
|
|
|
|
|
|
delete_item(From, To, Node, ItemID) ->
|
|
|
|
LTo = jid:tolower(jid:remove_resource(To)),
|
|
|
|
case mod_pubsub:delete_item(
|
|
|
|
LTo, Node, From, ItemID, true) of
|
|
|
|
{result, _} = Res ->
|
|
|
|
Res;
|
|
|
|
{error, _} = Err ->
|
|
|
|
case is_item_not_found(Err) of
|
|
|
|
true -> {result, []};
|
|
|
|
false -> Err
|
|
|
|
end
|
|
|
|
end.
|
|
|
|
|
2016-09-13 11:30:05 +02:00
|
|
|
-spec is_item_not_found({error, stanza_error()}) -> boolean().
|
|
|
|
is_item_not_found({error, #stanza_error{reason = 'item-not-found'}}) -> true;
|
|
|
|
is_item_not_found({error, _}) -> false.
|
2016-03-08 18:04:29 +01:00
|
|
|
|
2016-09-13 11:30:05 +02:00
|
|
|
-spec is_not_subscribed({error, stanza_error()}) -> boolean().
|
|
|
|
is_not_subscribed({error, #stanza_error{sub_els = Els}}) ->
|
|
|
|
%% TODO: make xmpp:get_els function working for any XMPP element
|
|
|
|
%% with sub_els field
|
|
|
|
xmpp:has_subtag(#message{sub_els = Els},
|
|
|
|
#ps_error{type = 'not-subscribed'}).
|
2016-03-09 09:19:15 +01:00
|
|
|
|
2016-07-06 13:58:48 +02:00
|
|
|
depends(_Host, _Opts) ->
|
|
|
|
[{mod_pubsub, hard}].
|
|
|
|
|
2016-03-09 09:19:15 +01:00
|
|
|
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
|
|
|
|
mod_opt_type(host) -> fun iolist_to_binary/1;
|
|
|
|
mod_opt_type(_) -> [host, iqdisc].
|