diff --git a/ejabberd.yml.example b/ejabberd.yml.example index 72439e5e1..dae839fdc 100644 --- a/ejabberd.yml.example +++ b/ejabberd.yml.example @@ -147,6 +147,15 @@ listen: ## access: all ## shaper_rule: fast ## 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: ## "icq.example.org": ## password: "secret" @@ -580,6 +589,7 @@ modules: mod_carboncopy: {} mod_client_state: {} mod_configure: {} # requires mod_adhoc + ##mod_delegation: {} # for xep0356 mod_disco: {} ## mod_echo: {} mod_irc: {} diff --git a/include/ejabberd_service.hrl b/include/ejabberd_service.hrl new file mode 100644 index 000000000..7cd3b6943 --- /dev/null +++ b/include/ejabberd_service.hrl @@ -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{} ). diff --git a/include/ns.hrl b/include/ns.hrl index a150746e7..3dbc765b0 100644 --- a/include/ns.hrl +++ b/include/ns.hrl @@ -164,6 +164,8 @@ -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">>). +-define(NS_PRIVILEGE, <<"urn:xmpp:privilege:1">>). +-define(NS_DELEGATION, <<"urn:xmpp:delegation:1">>). -define(NS_MUCSUB, <<"urn:xmpp:mucsub:0">>). -define(NS_MUCSUB_NODES_PRESENCE, <<"urn:xmpp:mucsub:nodes:presence">>). -define(NS_MUCSUB_NODES_MESSAGES, <<"urn:xmpp:mucsub:nodes:messages">>). diff --git a/src/ejabberd_app.erl b/src/ejabberd_app.erl index 6f0b97fa3..3b333b3b5 100644 --- a/src/ejabberd_app.erl +++ b/src/ejabberd_app.erl @@ -75,6 +75,7 @@ start(normal, _Args) -> ejabberd_oauth:start(), gen_mod:start_modules(), ejabberd_listener:start_listeners(), + ejabberd_service:start(), ?INFO_MSG("ejabberd ~s is started in the node ~p", [?VERSION, node()]), Sup; start(_, _) -> diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl index 270ef1dc5..cf7602441 100644 --- a/src/ejabberd_c2s.erl +++ b/src/ejabberd_c2s.erl @@ -32,6 +32,7 @@ -protocol({xep, 78, '2.5'}). -protocol({xep, 138, '2.0'}). -protocol({xep, 198, '1.3'}). +-protocol({xep, 356, '7.1'}). -update_info({update, 0}). @@ -48,6 +49,7 @@ send_element/2, socket_type/0, get_presence/1, + get_last_presence/1, get_aux_field/2, set_aux_field/3, del_aux_field/2, @@ -212,6 +214,9 @@ socket_type() -> xml_stream. get_presence(FsmRef) -> (?GEN_FSM):sync_send_all_state_event(FsmRef, {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}) -> case lists:keysearch(Key, 1, Opts) of @@ -1306,6 +1311,15 @@ handle_sync_event({get_presence}, _From, StateName, Resource = StateData#state.resource, Reply = {User, Resource, Show, Status}, 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, StateData) -> Subscribed = (?SETS):to_list(StateData#state.pres_f), diff --git a/src/ejabberd_service.erl b/src/ejabberd_service.erl index 9d72b17b4..9dd7c831e 100644 --- a/src/ejabberd_service.erl +++ b/src/ejabberd_service.erl @@ -36,7 +36,7 @@ -behaviour(?GEN_FSM). %% 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]). -export([init/1, wait_for_stream/2, @@ -44,19 +44,10 @@ handle_event/3, handle_sync_event/4, code_change/4, handle_info/3, terminate/3, print_state/1, opt_type/1]). --include("ejabberd.hrl"). --include("logger.hrl"). +-include("ejabberd_service.hrl"). +-include("mod_privacy.hrl"). --include("jlib.hrl"). - --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()}). +-export([get_delegated_ns/1]). %-define(DBGFSM, true). @@ -99,6 +90,15 @@ %%%---------------------------------------------------------------------- %%% 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) -> supervisor:start_child(ejabberd_service_sup, [SockData, Opts]). @@ -109,6 +109,9 @@ start_link(SockData, Opts) -> socket_type() -> xml_stream. +get_delegated_ns(FsmRef) -> + (?GEN_FSM):sync_send_all_state_event(FsmRef, {get_delegated_ns}). + %%%---------------------------------------------------------------------- %%% Callback functions from gen_fsm %%%---------------------------------------------------------------------- @@ -141,6 +144,21 @@ init([{SockMod, Socket}, Opts]) -> p1_sha:sha(crypto:rand_bytes(20))), dict:from_list([{global, Pass}]) 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 {value, {_, S}} -> S; _ -> none @@ -154,8 +172,9 @@ init([{SockMod, Socket}, Opts]) -> SockMod:change_shaper(Socket, Shaper), {ok, wait_for_stream, #state{socket = Socket, sockmod = SockMod, - streamid = new_id(), host_opts = HostOpts, - access = Access, check_from = CheckFrom}}. + streamid = new_id(), host_opts = HostOpts, access = Access, + check_from = CheckFrom, privilege_access = PrivAccess, + delegations = Delegations}}. %%---------------------------------------------------------------------- %% Func: StateName/2 @@ -227,8 +246,31 @@ wait_for_handshake({xmlstreamelement, El}, StateData) -> [H]), ejabberd_hooks:run(component_connected, [H]) - end, dict:fetch_keys(StateData#state.host_opts)), - {next_state, stream_established, StateData}; + end, dict:fetch_keys(StateData#state.host_opts)), + + 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), {stop, normal, StateData} @@ -276,11 +318,12 @@ stream_established({xmlstreamelement, El}, StateData) -> <<"">> -> error; _ -> jid:from_string(To) end, - if ((Name == <<"iq">>) or (Name == <<"message">>) or - (Name == <<"presence">>)) - and (ToJID /= error) - and (FromJID /= error) -> - ejabberd_router:route(FromJID, ToJID, NewEl); + if (Name == <<"iq">>) and (ToJID /= error) and (FromJID /= error) -> + mod_privilege:process_iq(StateData, FromJID, ToJID, NewEl); + (Name == <<"presence">>) and (ToJID /= error) and (FromJID /= error) -> + ejabberd_router:route(FromJID, ToJID, NewEl); + (Name == <<"message">>) and (ToJID /= error) and (FromJID /= error) -> + mod_privilege:process_message(StateData, FromJID, ToJID, NewEl); true -> Lang = fxml:get_tag_attr_s(<<"xml:lang">>, El), Txt = <<"Incorrect stanza name or from/to JID">>, @@ -330,8 +373,11 @@ handle_event(_Event, StateName, StateData) -> %% {stop, Reason, NewStateData} | %% {stop, Reason, Reply, NewStateData} %%---------------------------------------------------------------------- -handle_sync_event(_Event, _From, StateName, - StateData) -> +handle_sync_event({get_delegated_ns}, _From, StateName, 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}. 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) end, {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) -> ?ERROR_MSG("Unexpected info: ~p", [Info]), {next_state, StateName, StateData}. @@ -388,7 +464,26 @@ terminate(Reason, StateName, StateData) -> ejabberd_hooks:run(component_disconnected, [StateData#state.host, Reason]) 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 end, (StateData#state.sockmod):close(StateData#state.socket), @@ -448,3 +543,19 @@ fsm_limit_opts(Opts) -> opt_type(max_fsm_queue) -> fun (I) when is_integer(I), I > 0 -> I end; 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). diff --git a/src/jlib.erl b/src/jlib.erl index 4bc9b0055..3384e670e 100644 --- a/src/jlib.erl +++ b/src/jlib.erl @@ -371,15 +371,20 @@ iq_type_to_string(error) -> <<"error">>. -spec iq_to_xml(IQ :: iq()) -> xmlel(). iq_to_xml(#iq{id = ID, type = Type, sub_el = SubEl}) -> + Children = + if + is_list(SubEl) -> SubEl; + true -> [SubEl] + end, if ID /= <<"">> -> #xmlel{name = <<"iq">>, attrs = [{<<"id">>, ID}, {<<"type">>, iq_type_to_string(Type)}], - children = SubEl}; + children = Children}; true -> #xmlel{name = <<"iq">>, attrs = [{<<"type">>, iq_type_to_string(Type)}], - children = SubEl} + children = Children} end. -spec parse_xdata_submit(El :: xmlel()) -> diff --git a/src/mod_delegation.erl b/src/mod_delegation.erl new file mode 100644 index 000000000..f2d1a13b5 --- /dev/null +++ b/src/mod_delegation.erl @@ -0,0 +1,538 @@ +%%%-------------------------------------------------------------------------------------- +%%% File : mod_delegation.erl +%%% Author : Anna Mukharram +%%% 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, []). diff --git a/src/mod_privilege.erl b/src/mod_privilege.erl new file mode 100644 index 000000000..af6dacec4 --- /dev/null +++ b/src/mod_privilege.erl @@ -0,0 +1,363 @@ +%%%-------------------------------------------------------------------------------------- +%%% File : mod_privilege.erl +%%% Author : Anna Mukharram +%%% 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.