Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/processone/ejabberd.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/ns.hrl7
-rw-r--r--src/ejabberd_local.erl2
-rw-r--r--src/ejabberd_sm.erl1
-rw-r--r--src/mod_mix.erl329
-rw-r--r--src/mod_pubsub.erl2
-rw-r--r--src/node_mix.erl167
6 files changed, 506 insertions, 2 deletions
diff --git a/include/ns.hrl b/include/ns.hrl
index 6934195d9..c7f556372 100644
--- a/include/ns.hrl
+++ b/include/ns.hrl
@@ -157,3 +157,10 @@
-define(NS_HTTP_UPLOAD_OLD, <<"eu:siacs:conversations:http:upload">>).
-define(NS_THUMBS_1, <<"urn:xmpp:thumbs:1">>).
-define(NS_NICK, <<"http://jabber.org/protocol/nick">>).
+-define(NS_MIX_0, <<"urn:xmpp:mix:0">>).
+-define(NS_MIX_SERVICEINFO_0, <<"urn:xmpp:mix:0#serviceinfo">>).
+-define(NS_MIX_NODES_MESSAGES, <<"urn:xmpp:mix:nodes:messages">>).
+-define(NS_MIX_NODES_PRESENCE, <<"urn:xmpp:mix:nodes:presence">>).
+-define(NS_MIX_NODES_PARTICIPANTS, <<"urn:xmpp:mix:nodes:participants">>).
+-define(NS_MIX_NODES_SUBJECT, <<"urn:xmpp:mix:nodes:subject">>).
+-define(NS_MIX_NODES_CONFIG, <<"urn:xmpp:mix:nodes:config">>).
diff --git a/src/ejabberd_local.erl b/src/ejabberd_local.erl
index 1b7f93c77..292288a37 100644
--- a/src/ejabberd_local.erl
+++ b/src/ejabberd_local.erl
@@ -32,7 +32,7 @@
%% API
-export([start_link/0]).
--export([route/3, route_iq/4, route_iq/5,
+-export([route/3, route_iq/4, route_iq/5, process_iq/3,
process_iq_reply/3, register_iq_handler/4,
register_iq_handler/5, register_iq_response_handler/4,
register_iq_response_handler/5, unregister_iq_handler/2,
diff --git a/src/ejabberd_sm.erl b/src/ejabberd_sm.erl
index b2e5a21f3..218e657f3 100644
--- a/src/ejabberd_sm.erl
+++ b/src/ejabberd_sm.erl
@@ -35,6 +35,7 @@
-export([start/0,
start_link/0,
route/3,
+ process_iq/3,
open_session/5,
open_session/6,
close_session/4,
diff --git a/src/mod_mix.erl b/src/mod_mix.erl
new file mode 100644
index 000000000..047ac8da1
--- /dev/null
+++ b/src/mod_mix.erl
@@ -0,0 +1,329 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 2 Mar 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_mix).
+
+-behaviour(gen_server).
+-behaviour(gen_mod).
+
+%% API
+-export([start_link/2, start/2, stop/1, process_iq/3,
+ disco_items/5, disco_identity/5, disco_info/5,
+ disco_features/5]).
+
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+ terminate/2, code_change/3]).
+
+-include("logger.hrl").
+-include("jlib.hrl").
+-include("pubsub.hrl").
+
+-define(PROCNAME, ejabberd_mod_mix).
+-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_link(Host, Opts) ->
+ Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
+ gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
+
+start(Host, Opts) ->
+ Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
+ ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]},
+ temporary, 5000, worker, [?MODULE]},
+ supervisor:start_child(ejabberd_sup, ChildSpec).
+
+stop(Host) ->
+ Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
+ supervisor:terminate_child(ejabberd_sup, Proc),
+ supervisor:delete_child(ejabberd_sup, Proc),
+ ok.
+
+disco_features(_Acc, _From, _To, _Node, _Lang) ->
+ {result, [?NS_MIX_0]}.
+
+disco_items(_Acc, _From, To, _Node, _Lang) when To#jid.luser /= <<"">> ->
+ To_s = jid:to_string(jid:remove_resource(To)),
+ {result, [#xmlel{name = <<"item">>,
+ attrs = [{<<"jid">>, To_s},
+ {<<"node">>, Node}]} || Node <- ?NODES]};
+disco_items(_Acc, _From, _To, _Node, _Lang) ->
+ {result, []}.
+
+disco_identity(Acc, _From, To, _Node, _Lang) when To#jid.luser == <<"">> ->
+ Acc ++ [#xmlel{name = <<"identity">>,
+ attrs =
+ [{<<"category">>, <<"conference">>},
+ {<<"name">>, <<"MIX service">>},
+ {<<"type">>, <<"text">>}]}];
+disco_identity(Acc, _From, _To, _Node, _Lang) ->
+ Acc ++ [#xmlel{name = <<"identity">>,
+ attrs =
+ [{<<"category">>, <<"conference">>},
+ {<<"type">>, <<"mix">>}]}].
+
+disco_info(_Acc, _From, To, _Node, _Lang) when is_atom(To) ->
+ [#xmlel{name = <<"x">>,
+ attrs = [{<<"xmlns">>, ?NS_XDATA},
+ {<<"type">>, <<"result">>}],
+ children = [#xmlel{name = <<"field">>,
+ attrs = [{<<"var">>, <<"FORM_TYPE">>},
+ {<<"type">>, <<"hidden">>}],
+ children = [#xmlel{name = <<"value">>,
+ children = [{xmlcdata,
+ ?NS_MIX_SERVICEINFO_0}]}]}]}];
+disco_info(Acc, _From, _To, _Node, _Lang) ->
+ Acc.
+
+process_iq(From, To,
+ #iq{type = set, sub_el = #xmlel{name = <<"join">>} = SubEl} = IQ) ->
+ Nodes = lists:flatmap(
+ fun(#xmlel{name = <<"subscribe">>, attrs = Attrs}) ->
+ Node = fxml:get_attr_s(<<"node">>, Attrs),
+ case lists:member(Node, ?NODES) of
+ true -> [Node];
+ false -> []
+ end;
+ (_) ->
+ []
+ end, SubEl#xmlel.children),
+ case subscribe_nodes(From, To, Nodes) of
+ {result, _} ->
+ case publish_participant(From, To) of
+ {result, _} ->
+ LFrom_s = jid:to_string(jid:tolower(jid:remove_resource(From))),
+ Subscribe = [#xmlel{name = <<"subscribe">>,
+ attrs = [{<<"node">>, Node}]} || Node <- Nodes],
+ IQ#iq{type = result,
+ sub_el = [#xmlel{name = <<"join">>,
+ attrs = [{<<"jid">>, LFrom_s},
+ {<<"xmlns">>, ?NS_MIX_0}],
+ children = Subscribe}]};
+ {error, Err} ->
+ IQ#iq{type = error, sub_el = [SubEl, Err]}
+ end;
+ {error, Err} ->
+ IQ#iq{type = error, sub_el = [SubEl, Err]}
+ end;
+process_iq(From, To,
+ #iq{type = set, sub_el = #xmlel{name = <<"leave">>} = SubEl} = IQ) ->
+ case delete_participant(From, To) of
+ {result, _} ->
+ case unsubscribe_nodes(From, To, ?NODES) of
+ {result, _} ->
+ IQ#iq{type = result, sub_el = []};
+ {error, Err} ->
+ IQ#iq{type = error, sub_el = [SubEl, Err]}
+ end;
+ {error, Err} ->
+ IQ#iq{type = error, sub_el = [SubEl, Err]}
+ end;
+process_iq(_From, _To, #iq{sub_el = SubEl} = IQ) ->
+ IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]}.
+
+%%%===================================================================
+%%% gen_server callbacks
+%%%===================================================================
+init([ServerHost, Opts]) ->
+ 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),
+ ejabberd_router:register_route(Host),
+ {ok, #state{server_host = ServerHost, host = Host}}.
+
+handle_call(_Request, _From, State) ->
+ Reply = ok,
+ {reply, Reply, State}.
+
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+
+handle_info({route, From, To, Packet}, State) ->
+ case catch do_route(State, From, To, Packet) of
+ {'EXIT', _} = Err ->
+ try
+ ?ERROR_MSG("failed to route packet ~p from '~s' to '~s': ~p",
+ [Packet, jid:to_string(From), jid:to_string(To), Err]),
+ ErrPkt = jlib:make_error_reply(Packet, ?ERR_INTERNAL_SERVER_ERROR),
+ ejabberd_router:route_error(To, From, ErrPkt, Packet)
+ catch _:_ ->
+ ok
+ end;
+ _ ->
+ ok
+ end,
+ {noreply, State};
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+terminate(_Reason, _State) ->
+ ok.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+do_route(_State, From, To, #xmlel{name = <<"iq">>} = Packet) ->
+ if To#jid.luser == <<"">> ->
+ ejabberd_local:process_iq(From, To, Packet);
+ true ->
+ ejabberd_sm:process_iq(From, To, Packet)
+ end;
+do_route(_State, From, To, #xmlel{name = <<"presence">>} = Packet)
+ when To#jid.luser /= <<"">> ->
+ case fxml:get_tag_attr_s(<<"type">>, Packet) of
+ <<"unavailable">> ->
+ delete_presence(From, To);
+ _ ->
+ ok
+ end;
+do_route(_State, _From, _To, _Packet) ->
+ ok.
+
+subscribe_nodes(From, To, Nodes) ->
+ LTo = jid:tolower(jid:remove_resource(To)),
+ LFrom = jid:tolower(jid:remove_resource(From)),
+ From_s = jid:to_string(LFrom),
+ lists:foldl(
+ fun(_Node, {error, _} = Err) ->
+ Err;
+ (Node, {result, _}) ->
+ case mod_pubsub:subscribe_node(LTo, Node, From, From_s, []) of
+ {error, _} = Err ->
+ case is_item_not_found(Err) of
+ true ->
+ case mod_pubsub:create_node(
+ LTo, To#jid.lserver, Node, LFrom, <<"mix">>) of
+ {result, _} ->
+ mod_pubsub:subscribe_node(LTo, Node, From, From_s, []);
+ 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)),
+ LFrom = jid:tolower(jid:remove_resource(From)),
+ From_s = jid:to_string(LFrom),
+ lists:foldl(
+ fun(_Node, {error, _} = Err) ->
+ Err;
+ (Node, {result, _} = Result) ->
+ case mod_pubsub:unsubscribe_node(LTo, Node, From, From_s, <<"">>) of
+ {error, _} = Err ->
+ case is_not_subscribed(Err) of
+ true -> Result;
+ _ -> Err
+ end;
+ {result, _} = Res ->
+ Res
+ end
+ end, {result, []}, Nodes).
+
+publish_participant(From, To) ->
+ LFrom = jid:tolower(jid:remove_resource(From)),
+ LTo = jid:tolower(jid:remove_resource(To)),
+ Participant = #xmlel{name = <<"participant">>,
+ attrs = [{<<"xmlns">>, ?NS_MIX_0},
+ {<<"jid">>, jid:to_string(LFrom)}]},
+ ItemID = p1_sha:sha(jid:to_string(LFrom)),
+ mod_pubsub:publish_item(
+ LTo, To#jid.lserver, ?NS_MIX_NODES_PARTICIPANTS,
+ From, ItemID, [Participant]).
+
+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(
+ fun(#pubsub_item{modification = {_, LJID},
+ itemid = {ItemID, _}}) when LJID == LFrom ->
+ 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)),
+ ItemID = p1_sha:sha(jid:to_string(LFrom)),
+ 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.
+
+is_item_not_found({error, ErrEl}) ->
+ case fxml:get_subtag_with_xmlns(
+ ErrEl, <<"item-not-found">>, ?NS_STANZAS) of
+ #xmlel{} -> true;
+ _ -> false
+ end.
+
+is_not_subscribed({error, ErrEl}) ->
+ case fxml:get_subtag_with_xmlns(
+ ErrEl, <<"not-subscribed">>, ?NS_PUBSUB_ERRORS) of
+ #xmlel{} -> true;
+ _ -> false
+ end.
diff --git a/src/mod_pubsub.erl b/src/mod_pubsub.erl
index 61546353f..ed3debbf1 100644
--- a/src/mod_pubsub.erl
+++ b/src/mod_pubsub.erl
@@ -63,7 +63,7 @@
%% exports for console debug manual use
-export([create_node/5, create_node/7, delete_node/3,
subscribe_node/5, unsubscribe_node/5, publish_item/6,
- delete_item/4, send_items/7, get_items/2, get_item/3,
+ delete_item/4, delete_item/5, send_items/7, get_items/2, get_item/3,
get_cached_item/2, get_configure/5, set_configure/5,
tree_action/3, node_action/4, node_call/4]).
diff --git a/src/node_mix.erl b/src/node_mix.erl
new file mode 100644
index 000000000..b0410a8c3
--- /dev/null
+++ b/src/node_mix.erl
@@ -0,0 +1,167 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 8 Mar 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(node_mix).
+
+-behaviour(gen_pubsub_node).
+
+%% API
+-export([init/3, terminate/2, options/0, features/0,
+ create_node_permission/6, create_node/2, delete_node/1,
+ purge_node/2, subscribe_node/8, unsubscribe_node/4,
+ publish_item/6, delete_item/4, remove_extra_items/3,
+ get_entity_affiliations/2, get_node_affiliations/1,
+ get_affiliation/2, set_affiliation/3,
+ get_entity_subscriptions/2, get_node_subscriptions/1,
+ get_subscriptions/2, set_subscriptions/4,
+ get_pending_nodes/2, get_states/1, get_state/2,
+ set_state/1, get_items/7, get_items/3, get_item/7,
+ get_item/2, set_item/1, get_item_name/3, node_to_path/1,
+ path_to_node/1]).
+
+-include("pubsub.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(Host, ServerHost, Opts) ->
+ node_flat:init(Host, ServerHost, Opts).
+
+terminate(Host, ServerHost) ->
+ node_flat:terminate(Host, ServerHost).
+
+options() ->
+ [{deliver_payloads, true},
+ {notify_config, false},
+ {notify_delete, false},
+ {notify_retract, true},
+ {purge_offline, false},
+ {persist_items, true},
+ {max_items, ?MAXITEMS},
+ {subscribe, true},
+ {access_model, open},
+ {roster_groups_allowed, []},
+ {publish_model, open},
+ {notification_type, headline},
+ {max_payload_size, ?MAX_PAYLOAD_SIZE},
+ {send_last_published_item, never},
+ {deliver_notifications, true},
+ {broadcast_all_resources, true},
+ {presence_based_delivery, false}].
+
+features() ->
+ [<<"create-nodes">>,
+ <<"delete-nodes">>,
+ <<"delete-items">>,
+ <<"instant-nodes">>,
+ <<"item-ids">>,
+ <<"outcast-affiliation">>,
+ <<"persistent-items">>,
+ <<"publish">>,
+ <<"purge-nodes">>,
+ <<"retract-items">>,
+ <<"retrieve-affiliations">>,
+ <<"retrieve-items">>,
+ <<"retrieve-subscriptions">>,
+ <<"subscribe">>,
+ <<"subscription-notifications">>].
+
+create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) ->
+ node_flat:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access).
+
+create_node(Nidx, Owner) ->
+ node_flat:create_node(Nidx, Owner).
+
+delete_node(Removed) ->
+ node_flat:delete_node(Removed).
+
+subscribe_node(Nidx, Sender, Subscriber, AccessModel,
+ SendLast, PresenceSubscription, RosterGroup, Options) ->
+ node_flat:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast,
+ PresenceSubscription, RosterGroup, Options).
+
+unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
+ node_flat:unsubscribe_node(Nidx, Sender, Subscriber, SubId).
+
+publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload) ->
+ node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload).
+
+remove_extra_items(Nidx, MaxItems, ItemIds) ->
+ node_flat:remove_extra_items(Nidx, MaxItems, ItemIds).
+
+delete_item(Nidx, Publisher, PublishModel, ItemId) ->
+ node_flat:delete_item(Nidx, Publisher, PublishModel, ItemId).
+
+purge_node(Nidx, Owner) ->
+ node_flat:purge_node(Nidx, Owner).
+
+get_entity_affiliations(Host, Owner) ->
+ node_flat:get_entity_affiliations(Host, Owner).
+
+get_node_affiliations(Nidx) ->
+ node_flat:get_node_affiliations(Nidx).
+
+get_affiliation(Nidx, Owner) ->
+ node_flat:get_affiliation(Nidx, Owner).
+
+set_affiliation(Nidx, Owner, Affiliation) ->
+ node_flat:set_affiliation(Nidx, Owner, Affiliation).
+
+get_entity_subscriptions(Host, Owner) ->
+ node_flat:get_entity_subscriptions(Host, Owner).
+
+get_node_subscriptions(Nidx) ->
+ node_flat:get_node_subscriptions(Nidx).
+
+get_subscriptions(Nidx, Owner) ->
+ node_flat:get_subscriptions(Nidx, Owner).
+
+set_subscriptions(Nidx, Owner, Subscription, SubId) ->
+ node_flat:set_subscriptions(Nidx, Owner, Subscription, SubId).
+
+get_pending_nodes(Host, Owner) ->
+ node_flat:get_pending_nodes(Host, Owner).
+
+get_states(Nidx) ->
+ node_flat:get_states(Nidx).
+
+get_state(Nidx, JID) ->
+ node_flat:get_state(Nidx, JID).
+
+set_state(State) ->
+ node_flat:set_state(State).
+
+get_items(Nidx, From, RSM) ->
+ node_flat:get_items(Nidx, From, RSM).
+
+get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) ->
+ node_flat:get_items(Nidx, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId, RSM).
+
+get_item(Nidx, ItemId) ->
+ node_flat:get_item(Nidx, ItemId).
+
+get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
+ node_flat:get_item(Nidx, ItemId, JID, AccessModel,
+ PresenceSubscription, RosterGroup, SubId).
+
+set_item(Item) ->
+ node_flat:set_item(Item).
+
+get_item_name(Host, Node, Id) ->
+ node_flat:get_item_name(Host, Node, Id).
+
+node_to_path(Node) ->
+ node_flat:node_to_path(Node).
+
+path_to_node(Path) ->
+ node_flat:path_to_node(Path).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================