mirror of
https://github.com/processone/ejabberd.git
synced 2024-11-20 16:15:59 +01:00
Merge pull request #1253 from Amuhar/xep0356
This commit is contained in:
parent
f304149615
commit
af0a493c66
@ -147,6 +147,15 @@ listen:
|
|||||||
## access: all
|
## access: all
|
||||||
## shaper_rule: fast
|
## shaper_rule: fast
|
||||||
## ip: "127.0.0.1"
|
## ip: "127.0.0.1"
|
||||||
|
## privilege_access:
|
||||||
|
## roster: "both"
|
||||||
|
## message: "outgoing"
|
||||||
|
## presence: "roster"
|
||||||
|
## delegations:
|
||||||
|
## "urn:xmpp:mam:1":
|
||||||
|
## filtering: ["node"]
|
||||||
|
## "http://jabber.org/protocol/pubsub":
|
||||||
|
## filtering: []
|
||||||
## hosts:
|
## hosts:
|
||||||
## "icq.example.org":
|
## "icq.example.org":
|
||||||
## password: "secret"
|
## password: "secret"
|
||||||
@ -580,6 +589,7 @@ modules:
|
|||||||
mod_carboncopy: {}
|
mod_carboncopy: {}
|
||||||
mod_client_state: {}
|
mod_client_state: {}
|
||||||
mod_configure: {} # requires mod_adhoc
|
mod_configure: {} # requires mod_adhoc
|
||||||
|
##mod_delegation: {} # for xep0356
|
||||||
mod_disco: {}
|
mod_disco: {}
|
||||||
## mod_echo: {}
|
## mod_echo: {}
|
||||||
mod_irc: {}
|
mod_irc: {}
|
||||||
|
20
include/ejabberd_service.hrl
Normal file
20
include/ejabberd_service.hrl
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
-include("ejabberd.hrl").
|
||||||
|
-include("logger.hrl").
|
||||||
|
-include("jlib.hrl").
|
||||||
|
|
||||||
|
-type filter_attr() :: {binary(), [binary()]}.
|
||||||
|
|
||||||
|
-record(state,
|
||||||
|
{socket :: ejabberd_socket:socket_state(),
|
||||||
|
sockmod = ejabberd_socket :: ejabberd_socket | ejabberd_frontend_socket,
|
||||||
|
streamid = <<"">> :: binary(),
|
||||||
|
host_opts = dict:new() :: ?TDICT,
|
||||||
|
host = <<"">> :: binary(),
|
||||||
|
access :: atom(),
|
||||||
|
check_from = true :: boolean(),
|
||||||
|
server_hosts = ?MYHOSTS :: [binary()],
|
||||||
|
privilege_access :: [attr()],
|
||||||
|
delegations :: [filter_attr()],
|
||||||
|
last_pres = dict:new() :: ?TDICT}).
|
||||||
|
|
||||||
|
-type(state() :: #state{} ).
|
@ -164,6 +164,8 @@
|
|||||||
-define(NS_MIX_NODES_PARTICIPANTS, <<"urn:xmpp:mix:nodes:participants">>).
|
-define(NS_MIX_NODES_PARTICIPANTS, <<"urn:xmpp:mix:nodes:participants">>).
|
||||||
-define(NS_MIX_NODES_SUBJECT, <<"urn:xmpp:mix:nodes:subject">>).
|
-define(NS_MIX_NODES_SUBJECT, <<"urn:xmpp:mix:nodes:subject">>).
|
||||||
-define(NS_MIX_NODES_CONFIG, <<"urn:xmpp:mix:nodes:config">>).
|
-define(NS_MIX_NODES_CONFIG, <<"urn:xmpp:mix:nodes:config">>).
|
||||||
|
-define(NS_PRIVILEGE, <<"urn:xmpp:privilege:1">>).
|
||||||
|
-define(NS_DELEGATION, <<"urn:xmpp:delegation:1">>).
|
||||||
-define(NS_MUCSUB, <<"urn:xmpp:mucsub:0">>).
|
-define(NS_MUCSUB, <<"urn:xmpp:mucsub:0">>).
|
||||||
-define(NS_MUCSUB_NODES_PRESENCE, <<"urn:xmpp:mucsub:nodes:presence">>).
|
-define(NS_MUCSUB_NODES_PRESENCE, <<"urn:xmpp:mucsub:nodes:presence">>).
|
||||||
-define(NS_MUCSUB_NODES_MESSAGES, <<"urn:xmpp:mucsub:nodes:messages">>).
|
-define(NS_MUCSUB_NODES_MESSAGES, <<"urn:xmpp:mucsub:nodes:messages">>).
|
||||||
|
@ -75,6 +75,7 @@ start(normal, _Args) ->
|
|||||||
ejabberd_oauth:start(),
|
ejabberd_oauth:start(),
|
||||||
gen_mod:start_modules(),
|
gen_mod:start_modules(),
|
||||||
ejabberd_listener:start_listeners(),
|
ejabberd_listener:start_listeners(),
|
||||||
|
ejabberd_service:start(),
|
||||||
?INFO_MSG("ejabberd ~s is started in the node ~p", [?VERSION, node()]),
|
?INFO_MSG("ejabberd ~s is started in the node ~p", [?VERSION, node()]),
|
||||||
Sup;
|
Sup;
|
||||||
start(_, _) ->
|
start(_, _) ->
|
||||||
|
@ -32,6 +32,7 @@
|
|||||||
-protocol({xep, 78, '2.5'}).
|
-protocol({xep, 78, '2.5'}).
|
||||||
-protocol({xep, 138, '2.0'}).
|
-protocol({xep, 138, '2.0'}).
|
||||||
-protocol({xep, 198, '1.3'}).
|
-protocol({xep, 198, '1.3'}).
|
||||||
|
-protocol({xep, 356, '7.1'}).
|
||||||
|
|
||||||
-update_info({update, 0}).
|
-update_info({update, 0}).
|
||||||
|
|
||||||
@ -48,6 +49,7 @@
|
|||||||
send_element/2,
|
send_element/2,
|
||||||
socket_type/0,
|
socket_type/0,
|
||||||
get_presence/1,
|
get_presence/1,
|
||||||
|
get_last_presence/1,
|
||||||
get_aux_field/2,
|
get_aux_field/2,
|
||||||
set_aux_field/3,
|
set_aux_field/3,
|
||||||
del_aux_field/2,
|
del_aux_field/2,
|
||||||
@ -212,6 +214,9 @@ socket_type() -> xml_stream.
|
|||||||
get_presence(FsmRef) ->
|
get_presence(FsmRef) ->
|
||||||
(?GEN_FSM):sync_send_all_state_event(FsmRef,
|
(?GEN_FSM):sync_send_all_state_event(FsmRef,
|
||||||
{get_presence}, 1000).
|
{get_presence}, 1000).
|
||||||
|
get_last_presence(FsmRef) ->
|
||||||
|
(?GEN_FSM):sync_send_all_state_event(FsmRef,
|
||||||
|
{get_last_presence}, 1000).
|
||||||
|
|
||||||
get_aux_field(Key, #state{aux_fields = Opts}) ->
|
get_aux_field(Key, #state{aux_fields = Opts}) ->
|
||||||
case lists:keysearch(Key, 1, Opts) of
|
case lists:keysearch(Key, 1, Opts) of
|
||||||
@ -1306,6 +1311,15 @@ handle_sync_event({get_presence}, _From, StateName,
|
|||||||
Resource = StateData#state.resource,
|
Resource = StateData#state.resource,
|
||||||
Reply = {User, Resource, Show, Status},
|
Reply = {User, Resource, Show, Status},
|
||||||
fsm_reply(Reply, StateName, StateData);
|
fsm_reply(Reply, StateName, StateData);
|
||||||
|
handle_sync_event({get_last_presence}, _From, StateName,
|
||||||
|
StateData) ->
|
||||||
|
User = StateData#state.user,
|
||||||
|
Server = StateData#state.server,
|
||||||
|
PresLast = StateData#state.pres_last,
|
||||||
|
Resource = StateData#state.resource,
|
||||||
|
Reply = {User, Server, Resource, PresLast},
|
||||||
|
fsm_reply(Reply, StateName, StateData);
|
||||||
|
|
||||||
handle_sync_event(get_subscribed, _From, StateName,
|
handle_sync_event(get_subscribed, _From, StateName,
|
||||||
StateData) ->
|
StateData) ->
|
||||||
Subscribed = (?SETS):to_list(StateData#state.pres_f),
|
Subscribed = (?SETS):to_list(StateData#state.pres_f),
|
||||||
|
@ -36,7 +36,7 @@
|
|||||||
-behaviour(?GEN_FSM).
|
-behaviour(?GEN_FSM).
|
||||||
|
|
||||||
%% External exports
|
%% External exports
|
||||||
-export([start/2, start_link/2, send_text/2,
|
-export([start/0, start/2, start_link/2, send_text/2,
|
||||||
send_element/2, socket_type/0, transform_listen_option/2]).
|
send_element/2, socket_type/0, transform_listen_option/2]).
|
||||||
|
|
||||||
-export([init/1, wait_for_stream/2,
|
-export([init/1, wait_for_stream/2,
|
||||||
@ -44,19 +44,10 @@
|
|||||||
handle_event/3, handle_sync_event/4, code_change/4,
|
handle_event/3, handle_sync_event/4, code_change/4,
|
||||||
handle_info/3, terminate/3, print_state/1, opt_type/1]).
|
handle_info/3, terminate/3, print_state/1, opt_type/1]).
|
||||||
|
|
||||||
-include("ejabberd.hrl").
|
-include("ejabberd_service.hrl").
|
||||||
-include("logger.hrl").
|
-include("mod_privacy.hrl").
|
||||||
|
|
||||||
-include("jlib.hrl").
|
-export([get_delegated_ns/1]).
|
||||||
|
|
||||||
-record(state,
|
|
||||||
{socket :: ejabberd_socket:socket_state(),
|
|
||||||
sockmod = ejabberd_socket :: ejabberd_socket | ejabberd_frontend_socket,
|
|
||||||
streamid = <<"">> :: binary(),
|
|
||||||
host_opts = dict:new() :: ?TDICT,
|
|
||||||
host = <<"">> :: binary(),
|
|
||||||
access :: atom(),
|
|
||||||
check_from = true :: boolean()}).
|
|
||||||
|
|
||||||
%-define(DBGFSM, true).
|
%-define(DBGFSM, true).
|
||||||
|
|
||||||
@ -99,6 +90,15 @@
|
|||||||
%%%----------------------------------------------------------------------
|
%%%----------------------------------------------------------------------
|
||||||
%%% API
|
%%% API
|
||||||
%%%----------------------------------------------------------------------
|
%%%----------------------------------------------------------------------
|
||||||
|
|
||||||
|
%% for xep-0355
|
||||||
|
%% table contans records like {namespace, fitering attributes, pid(),
|
||||||
|
%% host, disco info for general case, bare jid disco info }
|
||||||
|
|
||||||
|
start() ->
|
||||||
|
ets:new(delegated_namespaces, [named_table, public]),
|
||||||
|
ets:new(hooks_tmp, [named_table, public]).
|
||||||
|
|
||||||
start(SockData, Opts) ->
|
start(SockData, Opts) ->
|
||||||
supervisor:start_child(ejabberd_service_sup,
|
supervisor:start_child(ejabberd_service_sup,
|
||||||
[SockData, Opts]).
|
[SockData, Opts]).
|
||||||
@ -109,6 +109,9 @@ start_link(SockData, Opts) ->
|
|||||||
|
|
||||||
socket_type() -> xml_stream.
|
socket_type() -> xml_stream.
|
||||||
|
|
||||||
|
get_delegated_ns(FsmRef) ->
|
||||||
|
(?GEN_FSM):sync_send_all_state_event(FsmRef, {get_delegated_ns}).
|
||||||
|
|
||||||
%%%----------------------------------------------------------------------
|
%%%----------------------------------------------------------------------
|
||||||
%%% Callback functions from gen_fsm
|
%%% Callback functions from gen_fsm
|
||||||
%%%----------------------------------------------------------------------
|
%%%----------------------------------------------------------------------
|
||||||
@ -141,6 +144,21 @@ init([{SockMod, Socket}, Opts]) ->
|
|||||||
p1_sha:sha(crypto:rand_bytes(20))),
|
p1_sha:sha(crypto:rand_bytes(20))),
|
||||||
dict:from_list([{global, Pass}])
|
dict:from_list([{global, Pass}])
|
||||||
end,
|
end,
|
||||||
|
%% privilege access to entities data
|
||||||
|
PrivAccess = case lists:keysearch(privilege_access, 1, Opts) of
|
||||||
|
{value, {_, PrivAcc}} -> PrivAcc;
|
||||||
|
_ -> []
|
||||||
|
end,
|
||||||
|
Delegations = case lists:keyfind(delegations, 1, Opts) of
|
||||||
|
{delegations, Del} ->
|
||||||
|
lists:foldl(
|
||||||
|
fun({Ns, FiltAttr}, D) when Ns /= ?NS_DELEGATION ->
|
||||||
|
Attr = proplists:get_value(filtering, FiltAttr, []),
|
||||||
|
D ++ [{Ns, Attr}];
|
||||||
|
(_Deleg, D) -> D
|
||||||
|
end, [], Del);
|
||||||
|
false -> []
|
||||||
|
end,
|
||||||
Shaper = case lists:keysearch(shaper_rule, 1, Opts) of
|
Shaper = case lists:keysearch(shaper_rule, 1, Opts) of
|
||||||
{value, {_, S}} -> S;
|
{value, {_, S}} -> S;
|
||||||
_ -> none
|
_ -> none
|
||||||
@ -154,8 +172,9 @@ init([{SockMod, Socket}, Opts]) ->
|
|||||||
SockMod:change_shaper(Socket, Shaper),
|
SockMod:change_shaper(Socket, Shaper),
|
||||||
{ok, wait_for_stream,
|
{ok, wait_for_stream,
|
||||||
#state{socket = Socket, sockmod = SockMod,
|
#state{socket = Socket, sockmod = SockMod,
|
||||||
streamid = new_id(), host_opts = HostOpts,
|
streamid = new_id(), host_opts = HostOpts, access = Access,
|
||||||
access = Access, check_from = CheckFrom}}.
|
check_from = CheckFrom, privilege_access = PrivAccess,
|
||||||
|
delegations = Delegations}}.
|
||||||
|
|
||||||
%%----------------------------------------------------------------------
|
%%----------------------------------------------------------------------
|
||||||
%% Func: StateName/2
|
%% Func: StateName/2
|
||||||
@ -227,8 +246,31 @@ wait_for_handshake({xmlstreamelement, El}, StateData) ->
|
|||||||
[H]),
|
[H]),
|
||||||
ejabberd_hooks:run(component_connected,
|
ejabberd_hooks:run(component_connected,
|
||||||
[H])
|
[H])
|
||||||
end, dict:fetch_keys(StateData#state.host_opts)),
|
end, dict:fetch_keys(StateData#state.host_opts)),
|
||||||
{next_state, stream_established, StateData};
|
|
||||||
|
mod_privilege:advertise_permissions(StateData),
|
||||||
|
DelegatedNs = mod_delegation:advertise_delegations(StateData),
|
||||||
|
|
||||||
|
RosterAccess = proplists:get_value(roster,
|
||||||
|
StateData#state.privilege_access),
|
||||||
|
|
||||||
|
case proplists:get_value(presence,
|
||||||
|
StateData#state.privilege_access) of
|
||||||
|
<<"managed_entity">> ->
|
||||||
|
mod_privilege:initial_presences(StateData),
|
||||||
|
Fun = mod_privilege:process_presence(self()),
|
||||||
|
add_hooks(user_send_packet, Fun);
|
||||||
|
<<"roster">> when (RosterAccess == <<"both">>) or
|
||||||
|
(RosterAccess == <<"get">>) ->
|
||||||
|
mod_privilege:initial_presences(StateData),
|
||||||
|
Fun = mod_privilege:process_presence(self()),
|
||||||
|
add_hooks(user_send_packet, Fun),
|
||||||
|
Fun2 = mod_privilege:process_roster_presence(self()),
|
||||||
|
add_hooks(s2s_receive_packet, Fun2);
|
||||||
|
_ -> ok
|
||||||
|
end,
|
||||||
|
{next_state, stream_established,
|
||||||
|
StateData#state{delegations = DelegatedNs}};
|
||||||
_ ->
|
_ ->
|
||||||
send_text(StateData, ?INVALID_HANDSHAKE_ERR),
|
send_text(StateData, ?INVALID_HANDSHAKE_ERR),
|
||||||
{stop, normal, StateData}
|
{stop, normal, StateData}
|
||||||
@ -276,11 +318,12 @@ stream_established({xmlstreamelement, El}, StateData) ->
|
|||||||
<<"">> -> error;
|
<<"">> -> error;
|
||||||
_ -> jid:from_string(To)
|
_ -> jid:from_string(To)
|
||||||
end,
|
end,
|
||||||
if ((Name == <<"iq">>) or (Name == <<"message">>) or
|
if (Name == <<"iq">>) and (ToJID /= error) and (FromJID /= error) ->
|
||||||
(Name == <<"presence">>))
|
mod_privilege:process_iq(StateData, FromJID, ToJID, NewEl);
|
||||||
and (ToJID /= error)
|
(Name == <<"presence">>) and (ToJID /= error) and (FromJID /= error) ->
|
||||||
and (FromJID /= error) ->
|
ejabberd_router:route(FromJID, ToJID, NewEl);
|
||||||
ejabberd_router:route(FromJID, ToJID, NewEl);
|
(Name == <<"message">>) and (ToJID /= error) and (FromJID /= error) ->
|
||||||
|
mod_privilege:process_message(StateData, FromJID, ToJID, NewEl);
|
||||||
true ->
|
true ->
|
||||||
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, El),
|
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, El),
|
||||||
Txt = <<"Incorrect stanza name or from/to JID">>,
|
Txt = <<"Incorrect stanza name or from/to JID">>,
|
||||||
@ -330,8 +373,11 @@ handle_event(_Event, StateName, StateData) ->
|
|||||||
%% {stop, Reason, NewStateData} |
|
%% {stop, Reason, NewStateData} |
|
||||||
%% {stop, Reason, Reply, NewStateData}
|
%% {stop, Reason, Reply, NewStateData}
|
||||||
%%----------------------------------------------------------------------
|
%%----------------------------------------------------------------------
|
||||||
handle_sync_event(_Event, _From, StateName,
|
handle_sync_event({get_delegated_ns}, _From, StateName, StateData) ->
|
||||||
StateData) ->
|
Reply = {StateData#state.host, StateData#state.delegations},
|
||||||
|
{reply, Reply, StateName, StateData};
|
||||||
|
|
||||||
|
handle_sync_event(_Event, _From, StateName, StateData) ->
|
||||||
Reply = ok, {reply, Reply, StateName, StateData}.
|
Reply = ok, {reply, Reply, StateName, StateData}.
|
||||||
|
|
||||||
code_change(_OldVsn, StateName, StateData, _Extra) ->
|
code_change(_OldVsn, StateName, StateData, _Extra) ->
|
||||||
@ -370,6 +416,36 @@ handle_info({route, From, To, Packet}, StateName,
|
|||||||
ejabberd_router:route_error(To, From, Err, Packet)
|
ejabberd_router:route_error(To, From, Err, Packet)
|
||||||
end,
|
end,
|
||||||
{next_state, StateName, StateData};
|
{next_state, StateName, StateData};
|
||||||
|
|
||||||
|
handle_info({user_presence, Packet, From},
|
||||||
|
stream_established, StateData) ->
|
||||||
|
To = jid:from_string(StateData#state.host),
|
||||||
|
PacketNew = jlib:replace_from_to(From, To, Packet),
|
||||||
|
send_element(StateData, PacketNew),
|
||||||
|
{next_state, stream_established, StateData};
|
||||||
|
|
||||||
|
handle_info({roster_presence, Packet, From},
|
||||||
|
stream_established, StateData) ->
|
||||||
|
%% check that current presence stanza is equivalent to last
|
||||||
|
PresenceNew = jlib:remove_attr(<<"to">>, Packet),
|
||||||
|
Dict = StateData#state.last_pres,
|
||||||
|
LastPresence =
|
||||||
|
try dict:fetch(From, Dict)
|
||||||
|
catch _:_ ->
|
||||||
|
undefined
|
||||||
|
end,
|
||||||
|
case mod_privilege:compare_presences(LastPresence, PresenceNew) of
|
||||||
|
false ->
|
||||||
|
#xmlel{attrs = Attrs} = PresenceNew,
|
||||||
|
Presence = PresenceNew#xmlel{attrs = [{<<"to">>, StateData#state.host} | Attrs]},
|
||||||
|
send_element(StateData, Presence),
|
||||||
|
DictNew = dict:store(From, PresenceNew, Dict),
|
||||||
|
StateDataNew = StateData#state{last_pres = DictNew},
|
||||||
|
{next_state, stream_established, StateDataNew};
|
||||||
|
_ ->
|
||||||
|
{next_state, stream_established, StateData}
|
||||||
|
end;
|
||||||
|
|
||||||
handle_info(Info, StateName, StateData) ->
|
handle_info(Info, StateName, StateData) ->
|
||||||
?ERROR_MSG("Unexpected info: ~p", [Info]),
|
?ERROR_MSG("Unexpected info: ~p", [Info]),
|
||||||
{next_state, StateName, StateData}.
|
{next_state, StateName, StateData}.
|
||||||
@ -388,7 +464,26 @@ terminate(Reason, StateName, StateData) ->
|
|||||||
ejabberd_hooks:run(component_disconnected,
|
ejabberd_hooks:run(component_disconnected,
|
||||||
[StateData#state.host, Reason])
|
[StateData#state.host, Reason])
|
||||||
end,
|
end,
|
||||||
dict:fetch_keys(StateData#state.host_opts));
|
dict:fetch_keys(StateData#state.host_opts)),
|
||||||
|
|
||||||
|
lists:foreach(fun({Ns, _FilterAttr}) ->
|
||||||
|
ets:delete(delegated_namespaces, Ns),
|
||||||
|
remove_iq_handlers(Ns)
|
||||||
|
end, StateData#state.delegations),
|
||||||
|
|
||||||
|
RosterAccess = proplists:get_value(roster, StateData#state.privilege_access),
|
||||||
|
case proplists:get_value(presence, StateData#state.privilege_access) of
|
||||||
|
<<"managed_entity">> ->
|
||||||
|
Fun = mod_privilege:process_presence(self()),
|
||||||
|
remove_hooks(user_send_packet, Fun);
|
||||||
|
<<"roster">> when (RosterAccess == <<"both">>) or
|
||||||
|
(RosterAccess == <<"get">>) ->
|
||||||
|
Fun = mod_privilege:process_presence(self()),
|
||||||
|
remove_hooks(user_send_packet, Fun),
|
||||||
|
Fun2 = mod_privilege:process_roster_presence(self()),
|
||||||
|
remove_hooks(s2s_receive_packet, Fun2);
|
||||||
|
_ -> ok
|
||||||
|
end;
|
||||||
_ -> ok
|
_ -> ok
|
||||||
end,
|
end,
|
||||||
(StateData#state.sockmod):close(StateData#state.socket),
|
(StateData#state.sockmod):close(StateData#state.socket),
|
||||||
@ -448,3 +543,19 @@ fsm_limit_opts(Opts) ->
|
|||||||
opt_type(max_fsm_queue) ->
|
opt_type(max_fsm_queue) ->
|
||||||
fun (I) when is_integer(I), I > 0 -> I end;
|
fun (I) when is_integer(I), I > 0 -> I end;
|
||||||
opt_type(_) -> [max_fsm_queue].
|
opt_type(_) -> [max_fsm_queue].
|
||||||
|
|
||||||
|
remove_iq_handlers(Ns) ->
|
||||||
|
lists:foreach(fun(Host) ->
|
||||||
|
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, Ns),
|
||||||
|
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, Ns)
|
||||||
|
end, ?MYHOSTS).
|
||||||
|
|
||||||
|
add_hooks(Hook, Fun) ->
|
||||||
|
lists:foreach(fun(Host) ->
|
||||||
|
ejabberd_hooks:add(Hook, Host,Fun, 100)
|
||||||
|
end, ?MYHOSTS).
|
||||||
|
|
||||||
|
remove_hooks(Hook, Fun) ->
|
||||||
|
lists:foreach(fun(Host) ->
|
||||||
|
ejabberd_hooks:delete(Hook, Host, Fun, 100)
|
||||||
|
end, ?MYHOSTS).
|
||||||
|
@ -371,15 +371,20 @@ iq_type_to_string(error) -> <<"error">>.
|
|||||||
-spec iq_to_xml(IQ :: iq()) -> xmlel().
|
-spec iq_to_xml(IQ :: iq()) -> xmlel().
|
||||||
|
|
||||||
iq_to_xml(#iq{id = ID, type = Type, sub_el = SubEl}) ->
|
iq_to_xml(#iq{id = ID, type = Type, sub_el = SubEl}) ->
|
||||||
|
Children =
|
||||||
|
if
|
||||||
|
is_list(SubEl) -> SubEl;
|
||||||
|
true -> [SubEl]
|
||||||
|
end,
|
||||||
if ID /= <<"">> ->
|
if ID /= <<"">> ->
|
||||||
#xmlel{name = <<"iq">>,
|
#xmlel{name = <<"iq">>,
|
||||||
attrs =
|
attrs =
|
||||||
[{<<"id">>, ID}, {<<"type">>, iq_type_to_string(Type)}],
|
[{<<"id">>, ID}, {<<"type">>, iq_type_to_string(Type)}],
|
||||||
children = SubEl};
|
children = Children};
|
||||||
true ->
|
true ->
|
||||||
#xmlel{name = <<"iq">>,
|
#xmlel{name = <<"iq">>,
|
||||||
attrs = [{<<"type">>, iq_type_to_string(Type)}],
|
attrs = [{<<"type">>, iq_type_to_string(Type)}],
|
||||||
children = SubEl}
|
children = Children}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec parse_xdata_submit(El :: xmlel()) ->
|
-spec parse_xdata_submit(El :: xmlel()) ->
|
||||||
|
538
src/mod_delegation.erl
Normal file
538
src/mod_delegation.erl
Normal file
@ -0,0 +1,538 @@
|
|||||||
|
%%%--------------------------------------------------------------------------------------
|
||||||
|
%%% File : mod_delegation.erl
|
||||||
|
%%% Author : Anna Mukharram <amuhar3@gmail.com>
|
||||||
|
%%% Purpose : This module is an implementation for XEP-0355: Namespace Delegation
|
||||||
|
%%%--------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
-module(mod_delegation).
|
||||||
|
|
||||||
|
-author('amuhar3@gmail.com').
|
||||||
|
|
||||||
|
-behaviour(gen_mod).
|
||||||
|
|
||||||
|
-protocol({xep, 0355, '0.3'}).
|
||||||
|
|
||||||
|
-export([start/2, stop/1, depends/2, mod_opt_type/1]).
|
||||||
|
|
||||||
|
-export([advertise_delegations/1, process_iq/3,
|
||||||
|
disco_local_features/5, disco_sm_features/5,
|
||||||
|
disco_local_identity/5, disco_sm_identity/5, disco_info/5, clean/0]).
|
||||||
|
|
||||||
|
-include_lib("stdlib/include/ms_transform.hrl").
|
||||||
|
|
||||||
|
-include("ejabberd_service.hrl").
|
||||||
|
|
||||||
|
-define(CLEAN_INTERVAL, timer:minutes(10)).
|
||||||
|
|
||||||
|
%%%--------------------------------------------------------------------------------------
|
||||||
|
%%% API
|
||||||
|
%%%--------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
start(Host, _Opts) ->
|
||||||
|
mod_disco:register_feature(Host, ?NS_DELEGATION),
|
||||||
|
%% start timer for hooks_tmp table cleaning
|
||||||
|
timer:apply_after(?CLEAN_INTERVAL, ?MODULE, clean, []),
|
||||||
|
|
||||||
|
ejabberd_hooks:add(disco_local_features, Host, ?MODULE,
|
||||||
|
disco_local_features, 500), %% This hook should be the last
|
||||||
|
ejabberd_hooks:add(disco_local_identity, Host, ?MODULE,
|
||||||
|
disco_local_identity, 500),
|
||||||
|
ejabberd_hooks:add(disco_sm_identity, Host, ?MODULE,
|
||||||
|
disco_sm_identity, 500),
|
||||||
|
ejabberd_hooks:add(disco_sm_features, Host, ?MODULE,
|
||||||
|
disco_sm_features, 500),
|
||||||
|
ejabberd_hooks:add(disco_info, Host, ?MODULE,
|
||||||
|
disco_info, 500).
|
||||||
|
|
||||||
|
|
||||||
|
stop(Host) ->
|
||||||
|
mod_disco:unregister_feature(Host, ?NS_DELEGATION),
|
||||||
|
ejabberd_hooks:delete(disco_local_features, Host, ?MODULE,
|
||||||
|
disco_local_features, 500),
|
||||||
|
ejabberd_hooks:delete(disco_local_identity, Host, ?MODULE,
|
||||||
|
disco_local_identity, 500),
|
||||||
|
ejabberd_hooks:delete(disco_sm_identity, Host, ?MODULE,
|
||||||
|
disco_sm_identity, 500),
|
||||||
|
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE,
|
||||||
|
disco_sm_features, 500),
|
||||||
|
ejabberd_hooks:delete(disco_info, Host, ?MODULE,
|
||||||
|
disco_info, 500).
|
||||||
|
|
||||||
|
depends(_Host, _Opts) -> [].
|
||||||
|
|
||||||
|
mod_opt_type(_Opt) -> [].
|
||||||
|
|
||||||
|
%%%--------------------------------------------------------------------------------------
|
||||||
|
%%% 4.2 Functions to advertise service of delegated namespaces
|
||||||
|
%%%--------------------------------------------------------------------------------------
|
||||||
|
attribute_tag(Attrs) ->
|
||||||
|
lists:map(fun(Attr) ->
|
||||||
|
#xmlel{name = <<"attribute">>, attrs = [{<<"name">> , Attr}]}
|
||||||
|
end, Attrs).
|
||||||
|
|
||||||
|
delegations(From, To, Delegations) ->
|
||||||
|
{Elem0, DelegatedNs} =
|
||||||
|
lists:foldl(fun({Ns, FiltAttr}, {Acc, AccNs}) ->
|
||||||
|
case ets:insert_new(delegated_namespaces,
|
||||||
|
{Ns, FiltAttr, self(), To, {}, {}}) of
|
||||||
|
true ->
|
||||||
|
Attrs =
|
||||||
|
if
|
||||||
|
FiltAttr == [] ->
|
||||||
|
?DEBUG("namespace ~s is delegated to ~s with"
|
||||||
|
" no filtering attributes ~n",[Ns, To]),
|
||||||
|
[];
|
||||||
|
true ->
|
||||||
|
?DEBUG("namespace ~s is delegated to ~s with"
|
||||||
|
" ~p filtering attributes ~n",[Ns, To, FiltAttr]),
|
||||||
|
attribute_tag(FiltAttr)
|
||||||
|
end,
|
||||||
|
add_iq_handlers(Ns),
|
||||||
|
{[#xmlel{name = <<"delegated">>,
|
||||||
|
attrs = [{<<"namespace">>, Ns}],
|
||||||
|
children = Attrs}| Acc], [{Ns, FiltAttr}|AccNs]};
|
||||||
|
false -> {Acc, AccNs}
|
||||||
|
end
|
||||||
|
end, {[], []}, Delegations),
|
||||||
|
case Elem0 of
|
||||||
|
[] -> {ignore, DelegatedNs};
|
||||||
|
_ ->
|
||||||
|
Elem1 = #xmlel{name = <<"delegation">>,
|
||||||
|
attrs = [{<<"xmlns">>, ?NS_DELEGATION}],
|
||||||
|
children = Elem0},
|
||||||
|
Id = randoms:get_string(),
|
||||||
|
{#xmlel{name = <<"message">>,
|
||||||
|
attrs = [{<<"id">>, Id}, {<<"from">>, From}, {<<"to">>, To}],
|
||||||
|
children = [Elem1]}, DelegatedNs}
|
||||||
|
end.
|
||||||
|
|
||||||
|
add_iq_handlers(Ns) ->
|
||||||
|
lists:foreach(fun(Host) ->
|
||||||
|
IQDisc =
|
||||||
|
gen_mod:get_module_opt(Host, ?MODULE, iqdisc,
|
||||||
|
fun gen_iq_handler:check_type/1, one_queue),
|
||||||
|
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
|
||||||
|
Ns, ?MODULE,
|
||||||
|
process_iq, IQDisc),
|
||||||
|
gen_iq_handler:add_iq_handler(ejabberd_local, Host,
|
||||||
|
Ns, ?MODULE,
|
||||||
|
process_iq, IQDisc)
|
||||||
|
end, ?MYHOSTS).
|
||||||
|
|
||||||
|
advertise_delegations(#state{delegations = []}) -> [];
|
||||||
|
advertise_delegations(StateData) ->
|
||||||
|
{Delegated, DelegatedNs} =
|
||||||
|
delegations(?MYNAME, StateData#state.host, StateData#state.delegations),
|
||||||
|
if
|
||||||
|
Delegated /= ignore ->
|
||||||
|
ejabberd_service:send_element(StateData, Delegated),
|
||||||
|
% server asks available features for delegated namespaces
|
||||||
|
disco_info(StateData#state{delegations = DelegatedNs});
|
||||||
|
true -> ok
|
||||||
|
end,
|
||||||
|
DelegatedNs.
|
||||||
|
|
||||||
|
%%%--------------------------------------------------------------------------------------
|
||||||
|
%%% Delegated namespaces hook
|
||||||
|
%%%--------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
check_filter_attr([], _Children) -> true;
|
||||||
|
check_filter_attr(_FilterAttr, []) -> false;
|
||||||
|
check_filter_attr(FilterAttr, [#xmlel{} = Stanza|_]) ->
|
||||||
|
Attrs = proplists:get_keys(Stanza#xmlel.attrs),
|
||||||
|
lists:all(fun(Attr) ->
|
||||||
|
lists:member(Attr, Attrs)
|
||||||
|
end, FilterAttr);
|
||||||
|
check_filter_attr(_FilterAttr, _Children) -> false.
|
||||||
|
|
||||||
|
-spec get_client_server([attr()]) -> {jid(), jid()}.
|
||||||
|
|
||||||
|
get_client_server(Attrs) ->
|
||||||
|
Client = fxml:get_attr_s(<<"from">>, Attrs),
|
||||||
|
ClientJID = jid:from_string(Client),
|
||||||
|
ServerJID = jid:from_string(ClientJID#jid.lserver),
|
||||||
|
{ClientJID, ServerJID}.
|
||||||
|
|
||||||
|
decapsulate_result(#xmlel{children = []}) -> ok;
|
||||||
|
decapsulate_result(#xmlel{children = Children}) ->
|
||||||
|
decapsulate_result0(Children).
|
||||||
|
|
||||||
|
decapsulate_result0([]) -> ok;
|
||||||
|
decapsulate_result0([#xmlel{name = <<"delegation">>,
|
||||||
|
attrs = [{<<"xmlns">>, ?NS_DELEGATION}]} = Packet]) ->
|
||||||
|
decapsulate_result1(Packet#xmlel.children);
|
||||||
|
decapsulate_result0(_Children) -> ok.
|
||||||
|
|
||||||
|
decapsulate_result1([]) -> ok;
|
||||||
|
decapsulate_result1([#xmlel{name = <<"forwarded">>,
|
||||||
|
attrs = [{<<"xmlns">>, ?NS_FORWARD}]} = Packet]) ->
|
||||||
|
decapsulate_result2(Packet#xmlel.children);
|
||||||
|
decapsulate_result1(_Children) -> ok.
|
||||||
|
|
||||||
|
decapsulate_result2([]) -> ok;
|
||||||
|
decapsulate_result2([#xmlel{name = <<"iq">>, attrs = Attrs} = Packet]) ->
|
||||||
|
Ns = fxml:get_attr_s(<<"xmlns">>, Attrs),
|
||||||
|
if
|
||||||
|
Ns /= <<"jabber:client">> ->
|
||||||
|
ok;
|
||||||
|
true -> Packet
|
||||||
|
end;
|
||||||
|
decapsulate_result2(_Children) -> ok.
|
||||||
|
|
||||||
|
-spec check_iq(xmlel(), xmlel()) -> xmlel() | ignore.
|
||||||
|
|
||||||
|
check_iq(#xmlel{attrs = Attrs} = Packet,
|
||||||
|
#xmlel{attrs = AttrsOrigin} = OriginPacket) ->
|
||||||
|
% Id attribute of OriginPacket Must be equil to Packet Id attribute
|
||||||
|
Id1 = fxml:get_attr_s(<<"id">>, Attrs),
|
||||||
|
Id2 = fxml:get_attr_s(<<"id">>, AttrsOrigin),
|
||||||
|
% From attribute of OriginPacket Must be equil to Packet To attribute
|
||||||
|
From = fxml:get_attr_s(<<"from">>, AttrsOrigin),
|
||||||
|
To = fxml:get_attr_s(<<"to">>, Attrs),
|
||||||
|
% Type attribute Must be error or result
|
||||||
|
Type = fxml:get_attr_s(<<"type">>, Attrs),
|
||||||
|
if
|
||||||
|
((Type == <<"result">>) or (Type == <<"error">>)),
|
||||||
|
Id1 == Id2, To == From ->
|
||||||
|
NewPacket = jlib:remove_attr(<<"xmlns">>, Packet),
|
||||||
|
%% We can send the decapsulated stanza from Server to Client (To)
|
||||||
|
NewPacket;
|
||||||
|
true ->
|
||||||
|
%% service-unavailable
|
||||||
|
Err = jlib:make_error_reply(OriginPacket, ?ERR_SERVICE_UNAVAILABLE),
|
||||||
|
Err
|
||||||
|
end;
|
||||||
|
check_iq(_Packet, _OriginPacket) -> ignore.
|
||||||
|
|
||||||
|
-spec manage_service_result(atom(), atom(), binary(), xmlel()) -> ok.
|
||||||
|
|
||||||
|
manage_service_result(HookRes, HookErr, Service, OriginPacket) ->
|
||||||
|
fun(Packet) ->
|
||||||
|
{ClientJID, ServerJID} = get_client_server(OriginPacket#xmlel.attrs),
|
||||||
|
Server = ClientJID#jid.lserver,
|
||||||
|
|
||||||
|
ets:delete(hooks_tmp, {HookRes, Server}),
|
||||||
|
ets:delete(hooks_tmp, {HookErr, Server}),
|
||||||
|
% Check Packet "from" attribute
|
||||||
|
% It Must be equil to current service host
|
||||||
|
From = fxml:get_attr_s(<<"from">> , Packet#xmlel.attrs),
|
||||||
|
if
|
||||||
|
From == Service ->
|
||||||
|
% decapsulate iq result
|
||||||
|
ResultIQ = decapsulate_result(Packet),
|
||||||
|
ServResponse = check_iq(ResultIQ, OriginPacket),
|
||||||
|
if
|
||||||
|
ServResponse /= ignore ->
|
||||||
|
ejabberd_router:route(ServerJID, ClientJID, ServResponse);
|
||||||
|
true -> ok
|
||||||
|
end;
|
||||||
|
true ->
|
||||||
|
% service unavailable
|
||||||
|
Err = jlib:make_error_reply(OriginPacket, ?ERR_SERVICE_UNAVAILABLE),
|
||||||
|
ejabberd_router:route(ServerJID, ClientJID, Err)
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
|
-spec manage_service_error(atom(), atom(), xmlel()) -> ok.
|
||||||
|
|
||||||
|
manage_service_error(HookRes, HookErr, OriginPacket) ->
|
||||||
|
fun(_Packet) ->
|
||||||
|
{ClientJID, ServerJID} = get_client_server(OriginPacket#xmlel.attrs),
|
||||||
|
Server = ClientJID#jid.lserver,
|
||||||
|
ets:delete(hooks_tmp, {HookRes, Server}),
|
||||||
|
ets:delete(hooks_tmp, {HookErr, Server}),
|
||||||
|
Err = jlib:make_error_reply(OriginPacket, ?ERR_SERVICE_UNAVAILABLE),
|
||||||
|
ejabberd_router:route(ServerJID, ClientJID, Err)
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
-spec forward_iq(binary(), binary(), xmlel()) -> ok.
|
||||||
|
|
||||||
|
forward_iq(Server, Service, Packet) ->
|
||||||
|
Elem0 = #xmlel{name = <<"forwarded">>,
|
||||||
|
attrs = [{<<"xmlns">>, ?NS_FORWARD}], children = [Packet]},
|
||||||
|
Elem1 = #xmlel{name = <<"delegation">>,
|
||||||
|
attrs = [{<<"xmlns">>, ?NS_DELEGATION}], children = [Elem0]},
|
||||||
|
Id = randoms:get_string(),
|
||||||
|
Elem2 = #xmlel{name = <<"iq">>,
|
||||||
|
attrs = [{<<"from">>, Server}, {<<"to">>, Service},
|
||||||
|
{<<"type">>, <<"set">>}, {<<"id">>, Id}],
|
||||||
|
children = [Elem1]},
|
||||||
|
|
||||||
|
HookRes = {iq, result, Id},
|
||||||
|
HookErr = {iq, error, Id},
|
||||||
|
|
||||||
|
FunRes = manage_service_result(HookRes, HookErr, Service, Packet),
|
||||||
|
FunErr = manage_service_error(HookRes, HookErr, Packet),
|
||||||
|
|
||||||
|
Timestamp = p1_time_compat:system_time(seconds),
|
||||||
|
ets:insert(hooks_tmp, {{HookRes, Server}, FunRes, Timestamp}),
|
||||||
|
ets:insert(hooks_tmp, {{HookErr, Server}, FunErr, Timestamp}),
|
||||||
|
|
||||||
|
From = jid:make(<<"">>, Server, <<"">>),
|
||||||
|
To = jid:make(<<"">>, Service, <<"">>),
|
||||||
|
ejabberd_router:route(From, To, Elem2).
|
||||||
|
|
||||||
|
process_iq(From, #jid{lresource = <<"">>} = To,
|
||||||
|
#iq{type = Type, xmlns = XMLNS} = IQ) ->
|
||||||
|
%% check if stanza directed to server
|
||||||
|
%% or directed to the bare JID of the sender
|
||||||
|
case ((Type == get) or (Type == set)) of
|
||||||
|
true ->
|
||||||
|
Packet = jlib:iq_to_xml(IQ),
|
||||||
|
#xmlel{name = <<"iq">>, attrs = Attrs, children = Children} = Packet,
|
||||||
|
AttrsNew = [{<<"xmlns">>, <<"jabber:client">>} | Attrs],
|
||||||
|
AttrsNew2 = jlib:replace_from_to_attrs(jid:to_string(From),
|
||||||
|
jid:to_string(To), AttrsNew),
|
||||||
|
case ets:lookup(delegated_namespaces, XMLNS) of
|
||||||
|
[{XMLNS, FiltAttr, _Pid, ServiceHost, _, _}] ->
|
||||||
|
case check_filter_attr(FiltAttr, Children) of
|
||||||
|
true ->
|
||||||
|
forward_iq(From#jid.server, ServiceHost,
|
||||||
|
Packet#xmlel{attrs = AttrsNew2});
|
||||||
|
_ -> ok
|
||||||
|
end;
|
||||||
|
[] -> ok
|
||||||
|
end,
|
||||||
|
ignore;
|
||||||
|
_ ->
|
||||||
|
ignore
|
||||||
|
end;
|
||||||
|
process_iq(_From, _To, _IQ) -> ignore.
|
||||||
|
|
||||||
|
%%%--------------------------------------------------------------------------------------
|
||||||
|
%%% 7. Discovering Support
|
||||||
|
%%%--------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
decapsulate_features(#xmlel{attrs = Attrs} = Packet, Node) ->
|
||||||
|
case fxml:get_attr_s(<<"node">>, Attrs) of
|
||||||
|
Node ->
|
||||||
|
PREFIX = << ?NS_DELEGATION/binary, "::" >>,
|
||||||
|
Size = byte_size(PREFIX),
|
||||||
|
BARE_PREFIX = << ?NS_DELEGATION/binary, ":bare:" >>,
|
||||||
|
SizeBare = byte_size(BARE_PREFIX),
|
||||||
|
|
||||||
|
Features = [Feat || #xmlel{attrs = [{<<"var">>, Feat}]} <-
|
||||||
|
fxml:get_subtags(Packet, <<"feature">>)],
|
||||||
|
|
||||||
|
Identity = [I || I <- fxml:get_subtags(Packet, <<"identity">>)],
|
||||||
|
|
||||||
|
Exten = [I || I <- fxml:get_subtags_with_xmlns(Packet, <<"x">>, ?NS_XDATA)],
|
||||||
|
|
||||||
|
case Node of
|
||||||
|
<< PREFIX:Size/binary, NS/binary >> ->
|
||||||
|
ets:update_element(delegated_namespaces, NS,
|
||||||
|
{5, {Features, Identity, Exten}});
|
||||||
|
<< BARE_PREFIX:SizeBare/binary, NS/binary >> ->
|
||||||
|
ets:update_element(delegated_namespaces, NS,
|
||||||
|
{6, {Features, Identity, Exten}});
|
||||||
|
_ -> ok
|
||||||
|
end;
|
||||||
|
_ -> ok
|
||||||
|
end;
|
||||||
|
decapsulate_features(_Packet, _Node) -> ok.
|
||||||
|
|
||||||
|
-spec disco_result(atom(), atom(), binary()) -> ok.
|
||||||
|
|
||||||
|
disco_result(HookRes, HookErr, Node) ->
|
||||||
|
fun(Packet) ->
|
||||||
|
Tag = fxml:get_subtag_with_xmlns(Packet, <<"query">>, ?NS_DISCO_INFO),
|
||||||
|
decapsulate_features(Tag, Node),
|
||||||
|
|
||||||
|
ets:delete(hooks_tmp, {HookRes, ?MYNAME}),
|
||||||
|
ets:delete(hooks_tmp, {HookErr, ?MYNAME})
|
||||||
|
end.
|
||||||
|
|
||||||
|
-spec disco_error(atom(), atom()) -> ok.
|
||||||
|
|
||||||
|
disco_error(HookRes, HookErr) ->
|
||||||
|
fun(_Packet) ->
|
||||||
|
ets:delete(hooks_tmp, {HookRes, ?MYNAME}),
|
||||||
|
ets:delete(hooks_tmp, {HookErr, ?MYNAME})
|
||||||
|
end.
|
||||||
|
|
||||||
|
-spec disco_info(state()) -> ok.
|
||||||
|
|
||||||
|
disco_info(StateData) ->
|
||||||
|
disco_info(StateData, <<"::">>),
|
||||||
|
disco_info(StateData, <<":bare:">>).
|
||||||
|
|
||||||
|
-spec disco_info(state(), binary()) -> ok.
|
||||||
|
|
||||||
|
disco_info(StateData, Sep) ->
|
||||||
|
lists:foreach(fun({Ns, _FilterAttr}) ->
|
||||||
|
Id = randoms:get_string(),
|
||||||
|
Node = << ?NS_DELEGATION/binary, Sep/binary, Ns/binary >>,
|
||||||
|
|
||||||
|
HookRes = {iq, result, Id},
|
||||||
|
HookErr = {iq, error, Id},
|
||||||
|
|
||||||
|
FunRes = disco_result(HookRes, HookErr, Node),
|
||||||
|
FunErr = disco_error(HookRes, HookErr),
|
||||||
|
|
||||||
|
Timestamp = p1_time_compat:system_time(seconds),
|
||||||
|
ets:insert(hooks_tmp, {{HookRes, ?MYNAME}, FunRes, Timestamp}),
|
||||||
|
ets:insert(hooks_tmp, {{HookErr, ?MYNAME}, FunErr, Timestamp}),
|
||||||
|
|
||||||
|
Tag = #xmlel{name = <<"query">>,
|
||||||
|
attrs = [{<<"xmlns">>, ?NS_DISCO_INFO},
|
||||||
|
{<<"node">>, Node}],
|
||||||
|
children = []},
|
||||||
|
DiscoReq = #xmlel{name = <<"iq">>,
|
||||||
|
attrs = [{<<"type">>, <<"get">>}, {<<"id">>, Id},
|
||||||
|
{<<"from">>, ?MYNAME},
|
||||||
|
{<<"to">>, StateData#state.host }],
|
||||||
|
children = [Tag]},
|
||||||
|
ejabberd_service:send_element(StateData, DiscoReq)
|
||||||
|
|
||||||
|
end, StateData#state.delegations).
|
||||||
|
|
||||||
|
|
||||||
|
disco_features(Acc, Bare) ->
|
||||||
|
Fun = fun(Feat) ->
|
||||||
|
ets:foldl(fun({Ns, _, _, _, _, _}, A) ->
|
||||||
|
A or str:prefix(Ns, Feat)
|
||||||
|
end, false, delegated_namespaces)
|
||||||
|
end,
|
||||||
|
% delete feature namespace which is delegated to service
|
||||||
|
Features = lists:filter(fun ({{Feature, _Host}}) ->
|
||||||
|
not Fun(Feature);
|
||||||
|
(Feature) when is_binary(Feature) ->
|
||||||
|
not Fun(Feature)
|
||||||
|
end, Acc),
|
||||||
|
% add service features
|
||||||
|
FeaturesList =
|
||||||
|
ets:foldl(fun({_, _, _, _, {Feats, _, _}, {FeatsBare, _, _}}, A) ->
|
||||||
|
if
|
||||||
|
Bare -> A ++ FeatsBare;
|
||||||
|
true -> A ++ Feats
|
||||||
|
end;
|
||||||
|
(_, A) -> A
|
||||||
|
end, Features, delegated_namespaces),
|
||||||
|
{result, FeaturesList}.
|
||||||
|
|
||||||
|
disco_identity(Acc, Bare) ->
|
||||||
|
% filter delegated identites
|
||||||
|
Fun = fun(Ident) ->
|
||||||
|
ets:foldl(fun({_, _, _, _, {_ , I, _}, {_ , IBare, _}}, A) ->
|
||||||
|
Identity =
|
||||||
|
if
|
||||||
|
Bare -> IBare;
|
||||||
|
true -> I
|
||||||
|
end,
|
||||||
|
(fxml:get_attr_s(<<"category">> , Ident) ==
|
||||||
|
fxml:get_attr_s(<<"category">>, Identity)) and
|
||||||
|
(fxml:get_attr_s(<<"type">> , Ident) ==
|
||||||
|
fxml:get_attr_s(<<"type">>, Identity)) or A;
|
||||||
|
(_, A) -> A
|
||||||
|
end, false, delegated_namespaces)
|
||||||
|
end,
|
||||||
|
|
||||||
|
Identities =
|
||||||
|
lists:filter(fun (#xmlel{attrs = Attrs}) ->
|
||||||
|
not Fun(Attrs)
|
||||||
|
end, Acc),
|
||||||
|
% add service features
|
||||||
|
ets:foldl(fun({_, _, _, _, {_, I, _}, {_, IBare, _}}, A) ->
|
||||||
|
if
|
||||||
|
Bare -> A ++ IBare;
|
||||||
|
true -> A ++ I
|
||||||
|
end;
|
||||||
|
(_, A) -> A
|
||||||
|
end, Identities, delegated_namespaces).
|
||||||
|
|
||||||
|
%% xmlns from value element
|
||||||
|
|
||||||
|
-spec get_field_value([xmlel()]) -> binary().
|
||||||
|
|
||||||
|
get_field_value([]) -> <<"">>;
|
||||||
|
get_field_value([Elem| Elems]) ->
|
||||||
|
case (fxml:get_attr_s(<<"var">>, Elem#xmlel.attrs) == <<"FORM_TYPE">>) and
|
||||||
|
(fxml:get_attr_s(<<"type">>, Elem#xmlel.attrs) == <<"hidden">>) of
|
||||||
|
true ->
|
||||||
|
Ns = fxml:get_subtag_cdata(Elem, <<"value">>),
|
||||||
|
if
|
||||||
|
Ns /= <<"">> -> Ns;
|
||||||
|
true -> get_field_value(Elems)
|
||||||
|
end;
|
||||||
|
_ -> get_field_value(Elems)
|
||||||
|
end.
|
||||||
|
|
||||||
|
get_info(Acc, Bare) ->
|
||||||
|
Fun = fun(Feat) ->
|
||||||
|
ets:foldl(fun({Ns, _, _, _, _, _}, A) ->
|
||||||
|
(A or str:prefix(Ns, Feat))
|
||||||
|
end, false, delegated_namespaces)
|
||||||
|
end,
|
||||||
|
Exten = lists:filter(fun(Xmlel) ->
|
||||||
|
Tags = fxml:get_subtags(Xmlel, <<"field">>),
|
||||||
|
case get_field_value(Tags) of
|
||||||
|
<<"">> -> true;
|
||||||
|
Value -> not Fun(Value)
|
||||||
|
end
|
||||||
|
end, Acc),
|
||||||
|
ets:foldl(fun({_, _, _, _, {_, _, Ext}, {_, _, ExtBare}}, A) ->
|
||||||
|
if
|
||||||
|
Bare -> A ++ ExtBare;
|
||||||
|
true -> A ++ Ext
|
||||||
|
end;
|
||||||
|
(_, A) -> A
|
||||||
|
end, Exten, delegated_namespaces).
|
||||||
|
|
||||||
|
%% 7.2.1 General Case
|
||||||
|
|
||||||
|
disco_local_features({error, _Error} = Acc, _From, _To, _Node, _Lang) ->
|
||||||
|
Acc;
|
||||||
|
disco_local_features(Acc, _From, _To, <<>>, _Lang) ->
|
||||||
|
FeatsOld = case Acc of
|
||||||
|
{result, I} -> I;
|
||||||
|
_ -> []
|
||||||
|
end,
|
||||||
|
disco_features(FeatsOld, false);
|
||||||
|
disco_local_features(Acc, _From, _To, _Node, _Lang) ->
|
||||||
|
Acc.
|
||||||
|
|
||||||
|
disco_local_identity(Acc, _From, _To, <<>>, _Lang) ->
|
||||||
|
disco_identity(Acc, false);
|
||||||
|
disco_local_identity(Acc, _From, _To, _Node, _Lang) ->
|
||||||
|
Acc.
|
||||||
|
|
||||||
|
%% 7.2.2 Rediction Of Bare JID Disco Info
|
||||||
|
|
||||||
|
disco_sm_features({error, ?ERR_ITEM_NOT_FOUND}, _From,
|
||||||
|
#jid{lresource = <<"">>}, <<>>, _Lang) ->
|
||||||
|
disco_features([], true);
|
||||||
|
disco_sm_features({error, _Error} = Acc, _From, _To, _Node, _Lang) ->
|
||||||
|
Acc;
|
||||||
|
disco_sm_features(Acc, _From, #jid{lresource = <<"">>}, <<>>, _Lang) ->
|
||||||
|
FeatsOld = case Acc of
|
||||||
|
{result, I} -> I;
|
||||||
|
_ -> []
|
||||||
|
end,
|
||||||
|
disco_features(FeatsOld, true);
|
||||||
|
disco_sm_features(Acc, _From, _To, _Node, _Lang) ->
|
||||||
|
Acc.
|
||||||
|
|
||||||
|
disco_sm_identity(Acc, _From, #jid{lresource = <<"">>}, <<>>, _Lang) ->
|
||||||
|
disco_identity(Acc, true);
|
||||||
|
disco_sm_identity(Acc, _From, _To, _Node, _Lang) ->
|
||||||
|
Acc.
|
||||||
|
|
||||||
|
disco_info(Acc, #jid{}, #jid{lresource = <<"">>}, <<>>, _Lang) ->
|
||||||
|
get_info(Acc, true);
|
||||||
|
disco_info(Acc, _Host, _Mod, <<>>, _Lang) ->
|
||||||
|
get_info(Acc, false);
|
||||||
|
disco_info(Acc, _Host, _Mod, _Node, _Lang) ->
|
||||||
|
Acc.
|
||||||
|
|
||||||
|
%% clean hooks_tmp table
|
||||||
|
|
||||||
|
clean() ->
|
||||||
|
?DEBUG("cleaning ~p ETS table~n", [hooks_tmp]),
|
||||||
|
Now = p1_time_compat:system_time(seconds),
|
||||||
|
catch ets:select_delete(hooks_tmp,
|
||||||
|
ets:fun2ms(fun({_, _, Timestamp}) ->
|
||||||
|
Now - 300 >= Timestamp
|
||||||
|
end)),
|
||||||
|
%% start timer for table cleaning
|
||||||
|
timer:apply_after(?CLEAN_INTERVAL, ?MODULE, clean, []).
|
363
src/mod_privilege.erl
Normal file
363
src/mod_privilege.erl
Normal file
@ -0,0 +1,363 @@
|
|||||||
|
%%%--------------------------------------------------------------------------------------
|
||||||
|
%%% File : mod_privilege.erl
|
||||||
|
%%% Author : Anna Mukharram <amuhar3@gmail.com>
|
||||||
|
%%% Purpose : This module is an implementation for XEP-0356: Privileged Entity
|
||||||
|
%%%--------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
-module(mod_privilege).
|
||||||
|
|
||||||
|
-author('amuhar3@gmail.com').
|
||||||
|
|
||||||
|
-protocol({xep, 0356, '0.2.1'}).
|
||||||
|
|
||||||
|
-export([advertise_permissions/1, initial_presences/1, process_presence/1,
|
||||||
|
process_roster_presence/1, compare_presences/2,
|
||||||
|
process_message/4, process_iq/4]).
|
||||||
|
|
||||||
|
-include("ejabberd_service.hrl").
|
||||||
|
|
||||||
|
-include("mod_privacy.hrl").
|
||||||
|
|
||||||
|
%%%--------------------------------------------------------------------------------------
|
||||||
|
%%% Functions to advertise services of allowed permission
|
||||||
|
%%%--------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
-spec permissions(binary(), binary(), list()) -> xmlel().
|
||||||
|
|
||||||
|
permissions(From, To, PrivAccess) ->
|
||||||
|
Perms = lists:map(fun({Access, Type}) ->
|
||||||
|
?DEBUG("Advertise service ~s of allowed permission: ~s = ~s~n",
|
||||||
|
[To, Access, Type]),
|
||||||
|
#xmlel{name = <<"perm">>,
|
||||||
|
attrs = [{<<"access">>,
|
||||||
|
atom_to_binary(Access,latin1)},
|
||||||
|
{<<"type">>, Type}]}
|
||||||
|
end, PrivAccess),
|
||||||
|
Stanza = #xmlel{name = <<"privilege">>,
|
||||||
|
attrs = [{<<"xmlns">> ,?NS_PRIVILEGE}],
|
||||||
|
children = Perms},
|
||||||
|
Id = randoms:get_string(),
|
||||||
|
#xmlel{name = <<"message">>,
|
||||||
|
attrs = [{<<"id">>, Id}, {<<"from">>, From}, {<<"to">>, To}],
|
||||||
|
children = [Stanza]}.
|
||||||
|
|
||||||
|
advertise_permissions(#state{privilege_access = []}) -> ok;
|
||||||
|
advertise_permissions(StateData) ->
|
||||||
|
Stanza =
|
||||||
|
permissions(?MYNAME, StateData#state.host, StateData#state.privilege_access),
|
||||||
|
ejabberd_service:send_element(StateData, Stanza).
|
||||||
|
|
||||||
|
%%%--------------------------------------------------------------------------------------
|
||||||
|
%%% Process presences
|
||||||
|
%%%--------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
initial_presences(StateData) ->
|
||||||
|
Pids = ejabberd_sm:get_all_pids(),
|
||||||
|
lists:foreach(
|
||||||
|
fun(Pid) ->
|
||||||
|
{User, Server, Resource, PresenceLast} = ejabberd_c2s:get_last_presence(Pid),
|
||||||
|
From = #jid{user = User, server = Server, resource = Resource},
|
||||||
|
To = jid:from_string(StateData#state.host),
|
||||||
|
PacketNew = jlib:replace_from_to(From, To, PresenceLast),
|
||||||
|
ejabberd_service:send_element(StateData, PacketNew)
|
||||||
|
end, Pids).
|
||||||
|
|
||||||
|
%% hook user_send_packet(Packet, C2SState, From, To) -> Packet
|
||||||
|
%% for Managed Entity Presence
|
||||||
|
process_presence(Pid) ->
|
||||||
|
fun(#xmlel{name = <<"presence">>} = Packet, _C2SState, From, _To) ->
|
||||||
|
case fxml:get_attr_s(<<"type">>, Packet#xmlel.attrs) of
|
||||||
|
T when (T == <<"">>) or (T == <<"unavailable">>) ->
|
||||||
|
Pid ! {user_presence, Packet, From};
|
||||||
|
_ -> ok
|
||||||
|
end,
|
||||||
|
Packet;
|
||||||
|
(Packet, _C2SState, _From, _To) ->
|
||||||
|
Packet
|
||||||
|
end.
|
||||||
|
%% s2s_receive_packet(From, To, Packet) -> ok
|
||||||
|
%% for Roster Presence
|
||||||
|
%% From subscription "from" or "both"
|
||||||
|
process_roster_presence(Pid) ->
|
||||||
|
fun(From, To, #xmlel{name = <<"presence">>} = Packet) ->
|
||||||
|
case fxml:get_attr_s(<<"type">>, Packet#xmlel.attrs) of
|
||||||
|
T when (T == <<"">>) or (T == <<"unavailable">>) ->
|
||||||
|
Server = To#jid.server,
|
||||||
|
User = To#jid.user,
|
||||||
|
PrivList = ejabberd_hooks:run_fold(privacy_get_user_list,
|
||||||
|
Server, #userlist{}, [User, Server]),
|
||||||
|
case privacy_check_packet(Server, User, PrivList, From, To, Packet, in) of
|
||||||
|
allow ->
|
||||||
|
Pid ! {roster_presence, Packet, From};
|
||||||
|
_ -> ok
|
||||||
|
end,
|
||||||
|
ok;
|
||||||
|
_ -> ok
|
||||||
|
end;
|
||||||
|
(_From, _To, _Packet) -> ok
|
||||||
|
end.
|
||||||
|
|
||||||
|
%%%--------------------------------------------------------------------------------------
|
||||||
|
%%% Manage Roster
|
||||||
|
%%%--------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
process_iq(StateData, FromJID, ToJID, Packet) ->
|
||||||
|
IQ = jlib:iq_query_or_response_info(Packet),
|
||||||
|
case IQ of
|
||||||
|
#iq{xmlns = ?NS_ROSTER} ->
|
||||||
|
case (ToJID#jid.luser /= <<"">>) and
|
||||||
|
(FromJID#jid.luser == <<"">>) and
|
||||||
|
lists:member(ToJID#jid.lserver, ?MYHOSTS) of
|
||||||
|
true ->
|
||||||
|
AccessType =
|
||||||
|
proplists:get_value(roster, StateData#state.privilege_access, none),
|
||||||
|
case IQ#iq.type of
|
||||||
|
get when (AccessType == <<"both">>) or (AccessType == <<"get">>) ->
|
||||||
|
RosterIQ = roster_management(ToJID, FromJID, IQ),
|
||||||
|
ejabberd_service:send_element(StateData, RosterIQ);
|
||||||
|
set when (AccessType == <<"both">>) or (AccessType == <<"set">>) ->
|
||||||
|
%% check if user ToJID exist
|
||||||
|
#jid{lserver = Server, luser = User} = ToJID,
|
||||||
|
case ejabberd_auth:is_user_exists(User,Server) of
|
||||||
|
true ->
|
||||||
|
ResIQ = roster_management(ToJID, FromJID, IQ),
|
||||||
|
ejabberd_service:send_element(StateData, ResIQ);
|
||||||
|
_ -> ok
|
||||||
|
end;
|
||||||
|
_ ->
|
||||||
|
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
|
||||||
|
ejabberd_service:send_element(StateData, Err)
|
||||||
|
end;
|
||||||
|
_ ->
|
||||||
|
ejabberd_router:route(FromJID, ToJID, Packet)
|
||||||
|
end;
|
||||||
|
#iq{type = Type, id = Id} when (Type == error) or (Type == result) -> % for XEP-0355
|
||||||
|
Hook = {iq, Type, Id},
|
||||||
|
Host = ToJID#jid.lserver,
|
||||||
|
case (ToJID#jid.luser == <<"">>) and
|
||||||
|
(FromJID#jid.luser == <<"">>) and
|
||||||
|
lists:member(ToJID#jid.lserver, ?MYHOSTS) of
|
||||||
|
true ->
|
||||||
|
case ets:lookup(hooks_tmp, {Hook, Host}) of
|
||||||
|
[{_, Function, _Timestamp}] ->
|
||||||
|
catch apply(Function, [Packet]);
|
||||||
|
[] ->
|
||||||
|
ejabberd_router:route(FromJID, ToJID, Packet)
|
||||||
|
end;
|
||||||
|
_ ->
|
||||||
|
ejabberd_router:route(FromJID, ToJID, Packet)
|
||||||
|
end;
|
||||||
|
_ ->
|
||||||
|
ejabberd_router:route(FromJID, ToJID, Packet)
|
||||||
|
end.
|
||||||
|
|
||||||
|
roster_management(FromJID, ToJID, IQ) ->
|
||||||
|
ResIQ = mod_roster:process_iq(FromJID, FromJID, IQ),
|
||||||
|
ResXml = jlib:iq_to_xml(ResIQ),
|
||||||
|
jlib:replace_from_to(FromJID, ToJID, ResXml).
|
||||||
|
|
||||||
|
%%%--------------------------------------------------------------------------------------
|
||||||
|
%%% Message permission
|
||||||
|
%%%--------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
process_message(StateData, FromJID, ToJID, #xmlel{children = Children} = Packet) ->
|
||||||
|
%% if presence was send from service to server,
|
||||||
|
case lists:member(ToJID#jid.lserver, ?MYHOSTS) and
|
||||||
|
(ToJID#jid.luser == <<"">>) and
|
||||||
|
(FromJID#jid.luser == <<"">>) of %% service
|
||||||
|
true ->
|
||||||
|
%% if stanza contains privilege element
|
||||||
|
case Children of
|
||||||
|
[#xmlel{name = <<"privilege">>,
|
||||||
|
attrs = [{<<"xmlns">>, ?NS_PRIVILEGE}],
|
||||||
|
children = [#xmlel{name = <<"forwarded">>,
|
||||||
|
attrs = [{<<"xmlns">>, ?NS_FORWARD}],
|
||||||
|
children = Children2}]}] ->
|
||||||
|
%% 1 case : privilege service send subscription message
|
||||||
|
%% on behalf of the client
|
||||||
|
%% 2 case : privilege service send message on behalf
|
||||||
|
%% of the client
|
||||||
|
case Children2 of
|
||||||
|
%% it isn't case of 0356 extension
|
||||||
|
[#xmlel{name = <<"presence">>} = Child] ->
|
||||||
|
forward_subscribe(StateData, Child, Packet);
|
||||||
|
[#xmlel{name = <<"message">>} = Child] -> %% xep-0356
|
||||||
|
forward_message(StateData, Child, Packet);
|
||||||
|
_ ->
|
||||||
|
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
|
||||||
|
Txt = <<"invalid forwarded element">>,
|
||||||
|
Err = jlib:make_error_reply(Packet, ?ERRT_BAD_REQUEST(Lang, Txt)),
|
||||||
|
ejabberd_service:send_element(StateData, Err)
|
||||||
|
end;
|
||||||
|
_ ->
|
||||||
|
ejabberd_router:route(FromJID, ToJID, Packet)
|
||||||
|
end;
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
ejabberd_router:route(FromJID, ToJID, Packet)
|
||||||
|
end.
|
||||||
|
|
||||||
|
forward_subscribe(StateData, Presence, Packet) ->
|
||||||
|
PrivAccess = StateData#state.privilege_access,
|
||||||
|
T = proplists:get_value(roster, PrivAccess, none),
|
||||||
|
Type = fxml:get_attr_s(<<"type">>, Presence#xmlel.attrs),
|
||||||
|
if
|
||||||
|
((T == <<"both">>) or (T == <<"set">>)) and (Type == <<"subscribe">>) ->
|
||||||
|
From = fxml:get_attr_s(<<"from">>, Presence#xmlel.attrs),
|
||||||
|
FromJ = jid:from_string(From),
|
||||||
|
To = fxml:get_attr_s(<<"to">>, Presence#xmlel.attrs),
|
||||||
|
ToJ = case To of
|
||||||
|
<<"">> -> error;
|
||||||
|
_ -> jid:from_string(To)
|
||||||
|
end,
|
||||||
|
if
|
||||||
|
(ToJ /= error) and (FromJ /= error) ->
|
||||||
|
Server = FromJ#jid.lserver,
|
||||||
|
User = FromJ#jid.luser,
|
||||||
|
case (FromJ#jid.lresource == <<"">>) and
|
||||||
|
lists:member(Server, ?MYHOSTS) of
|
||||||
|
true ->
|
||||||
|
if
|
||||||
|
(Server /= ToJ#jid.lserver) or
|
||||||
|
(User /= ToJ#jid.luser) ->
|
||||||
|
%% 0356 server MUST NOT allow the privileged entity
|
||||||
|
%% to do anything that the managed entity could not do
|
||||||
|
try_roster_subscribe(Server,User, FromJ, ToJ, Presence);
|
||||||
|
true -> %% we don't want presence sent to self
|
||||||
|
ok
|
||||||
|
end;
|
||||||
|
_ ->
|
||||||
|
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
|
||||||
|
ejabberd_service:send_element(StateData, Err)
|
||||||
|
end;
|
||||||
|
true ->
|
||||||
|
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
|
||||||
|
Txt = <<"Incorrect stanza from/to JID">>,
|
||||||
|
Err = jlib:make_error_reply(Packet, ?ERRT_BAD_REQUEST(Lang, Txt)),
|
||||||
|
ejabberd_service:send_element(StateData, Err)
|
||||||
|
end;
|
||||||
|
true ->
|
||||||
|
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
|
||||||
|
ejabberd_service:send_element(StateData, Err)
|
||||||
|
end.
|
||||||
|
|
||||||
|
forward_message(StateData, Message, Packet) ->
|
||||||
|
PrivAccess = StateData#state.privilege_access,
|
||||||
|
T = proplists:get_value(message, PrivAccess, none),
|
||||||
|
if
|
||||||
|
(T == <<"outgoing">>) ->
|
||||||
|
From = fxml:get_attr_s(<<"from">>, Message#xmlel.attrs),
|
||||||
|
FromJ = jid:from_string(From),
|
||||||
|
To = fxml:get_attr_s(<<"to">>, Message#xmlel.attrs),
|
||||||
|
ToJ = case To of
|
||||||
|
<<"">> -> FromJ;
|
||||||
|
_ -> jid:from_string(To)
|
||||||
|
end,
|
||||||
|
if
|
||||||
|
(ToJ /= error) and (FromJ /= error) ->
|
||||||
|
Server = FromJ#jid.server,
|
||||||
|
User = FromJ#jid.user,
|
||||||
|
case (FromJ#jid.lresource == <<"">>) and
|
||||||
|
lists:member(Server, ?MYHOSTS) of
|
||||||
|
true ->
|
||||||
|
%% there are no restriction on to attribute
|
||||||
|
PrivList = ejabberd_hooks:run_fold(privacy_get_user_list,
|
||||||
|
Server, #userlist{},
|
||||||
|
[User, Server]),
|
||||||
|
check_privacy_route(Server, User, PrivList,
|
||||||
|
FromJ, ToJ, Message);
|
||||||
|
_ ->
|
||||||
|
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
|
||||||
|
ejabberd_service:send_element(StateData, Err)
|
||||||
|
end;
|
||||||
|
true ->
|
||||||
|
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
|
||||||
|
Txt = <<"Incorrect stanza from/to JID">>,
|
||||||
|
Err = jlib:make_error_reply(Packet, ?ERRT_BAD_REQUEST(Lang, Txt)),
|
||||||
|
ejabberd_service:send_element(StateData, Err)
|
||||||
|
end;
|
||||||
|
true ->
|
||||||
|
Err = jlib:make_error_reply(Packet,?ERR_FORBIDDEN),
|
||||||
|
ejabberd_service:send_element(StateData, Err)
|
||||||
|
end.
|
||||||
|
|
||||||
|
%%%--------------------------------------------------------------------------------------
|
||||||
|
%%% helper functions
|
||||||
|
%%%--------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
compare_presences(undefined, _Presence) -> false;
|
||||||
|
compare_presences(#xmlel{attrs = Attrs, children = Child},
|
||||||
|
#xmlel{attrs = Attrs2, children = Child2}) ->
|
||||||
|
Id1 = fxml:get_attr_s(<<"id">>, Attrs),
|
||||||
|
Id2 = fxml:get_attr_s(<<"id">>, Attrs2),
|
||||||
|
if
|
||||||
|
(Id1 /= Id2) ->
|
||||||
|
false;
|
||||||
|
(Id1 /= <<"">>) and (Id1 == Id2) ->
|
||||||
|
true;
|
||||||
|
true ->
|
||||||
|
case not compare_attrs(Attrs, Attrs2) of
|
||||||
|
true -> false;
|
||||||
|
_ ->
|
||||||
|
compare_elements(Child, Child2)
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
compare_elements([],[]) -> true;
|
||||||
|
compare_elements(Tags1, Tags2) when length(Tags1) == length(Tags2) ->
|
||||||
|
compare_tags(Tags1,Tags2);
|
||||||
|
compare_elements(_Tags1, _Tags2) -> false.
|
||||||
|
|
||||||
|
compare_tags([],[]) -> true;
|
||||||
|
compare_tags([{xmlcdata, CData}|Tags1], [{xmlcdata, CData}|Tags2]) ->
|
||||||
|
compare_tags(Tags1, Tags2);
|
||||||
|
compare_tags([{xmlcdata, _CData1}|_Tags1], [{xmlcdata, _CData2}|_Tags2]) ->
|
||||||
|
false;
|
||||||
|
compare_tags([#xmlel{} = Stanza1|Tags1], [#xmlel{} = Stanza2|Tags2]) ->
|
||||||
|
case (Stanza1#xmlel.name == Stanza2#xmlel.name) and
|
||||||
|
compare_attrs(Stanza1#xmlel.attrs, Stanza2#xmlel.attrs) and
|
||||||
|
compare_tags(Stanza1#xmlel.children, Stanza2#xmlel.children) of
|
||||||
|
true ->
|
||||||
|
compare_tags(Tags1,Tags2);
|
||||||
|
false ->
|
||||||
|
false
|
||||||
|
end.
|
||||||
|
|
||||||
|
%% attr() :: {Name, Value}
|
||||||
|
-spec compare_attrs([attr()], [attr()]) -> boolean().
|
||||||
|
compare_attrs([],[]) -> true;
|
||||||
|
compare_attrs(Attrs1, Attrs2) when length(Attrs1) == length(Attrs2) ->
|
||||||
|
lists:foldl(fun(Attr,Acc) -> lists:member(Attr, Attrs2) and Acc end, true, Attrs1);
|
||||||
|
compare_attrs(_Attrs1, _Attrs2) -> false.
|
||||||
|
|
||||||
|
%% Check if privacy rules allow this delivery
|
||||||
|
%% from ejabberd_c2s.erl
|
||||||
|
privacy_check_packet(Server, User, PrivList, From, To, Packet , Dir) ->
|
||||||
|
ejabberd_hooks:run_fold(privacy_check_packet,
|
||||||
|
Server, allow, [User, Server, PrivList,
|
||||||
|
{From, To, Packet}, Dir]).
|
||||||
|
|
||||||
|
check_privacy_route(Server, User, PrivList, From, To, Packet) ->
|
||||||
|
case privacy_check_packet(Server, User, PrivList, From, To, Packet, out) of
|
||||||
|
allow ->
|
||||||
|
ejabberd_router:route(From, To, Packet);
|
||||||
|
_ -> ok %% who should receive error : service or user?
|
||||||
|
end.
|
||||||
|
|
||||||
|
try_roster_subscribe(Server,User, From, To, Packet) ->
|
||||||
|
Access =
|
||||||
|
gen_mod:get_module_opt(Server, mod_roster, access,
|
||||||
|
fun(A) when is_atom(A) -> A end, all),
|
||||||
|
case acl:match_rule(Server, Access, From) of
|
||||||
|
deny ->
|
||||||
|
ok;
|
||||||
|
allow ->
|
||||||
|
ejabberd_hooks:run(roster_out_subscription, Server,
|
||||||
|
[User, Server, To, subscribe]),
|
||||||
|
PrivList = ejabberd_hooks:run_fold(privacy_get_user_list,
|
||||||
|
Server,
|
||||||
|
#userlist{},
|
||||||
|
[User, Server]),
|
||||||
|
check_privacy_route(Server, User, PrivList, From, To, Packet)
|
||||||
|
end.
|
Loading…
Reference in New Issue
Block a user