]> granicus.if.org Git - ejabberd/commitdiff
Merge pull request #1253 from Amuhar/xep0356
authorChristophe Romain <christophe.romain@process-one.net>
Wed, 7 Sep 2016 12:34:31 +0000 (14:34 +0200)
committerChristophe Romain <christophe.romain@process-one.net>
Wed, 7 Sep 2016 12:34:31 +0000 (14:34 +0200)
ejabberd.yml.example
include/ejabberd_service.hrl [new file with mode: 0644]
include/ns.hrl
src/ejabberd_app.erl
src/ejabberd_c2s.erl
src/ejabberd_service.erl
src/jlib.erl
src/mod_delegation.erl [new file with mode: 0644]
src/mod_privilege.erl [new file with mode: 0644]

index 72439e5e1889fc3461d7cb13c47b3f7ee89f213a..dae839fdcd0237889c8227ccded39010f8fb6faa 100644 (file)
@@ -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 (file)
index 0000000..7cd3b69
--- /dev/null
@@ -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{} ).
index a150746e73db63f6a6f3900a313f58664dd57405..3dbc765b0d9ce80aab92305e35e2b3ad5f98823f 100644 (file)
 -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">>).
index 6f0b97fa30e5666fb62b7aa5a3190535ad17eb4e..3b333b3b5d44327ff45fe9859bf67234b119b9fd 100644 (file)
@@ -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(_, _) ->
index 270ef1dc5fa12bbcfd8f0d83ff438d0aec0d4e57..cf7602441b7dec6b4c5ef8baeab34f62f40feae9 100644 (file)
@@ -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),
index 9d72b17b4b89df0ef04fa096c06caf32bc5fa924..9dd7c831e68e9350336ea0ff645d32a53d95e83e 100644 (file)
@@ -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,
         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).
 
 %%%----------------------------------------------------------------------
 %%% 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).
index 4bc9b0055c1d2b319255bf86298ddf8d02c06c28..3384e670ecbb6de30d43416ad2551222b1b4835b 100644 (file)
@@ -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 (file)
index 0000000..f2d1a13
--- /dev/null
@@ -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, []).
diff --git a/src/mod_privilege.erl b/src/mod_privilege.erl
new file mode 100644 (file)
index 0000000..af6dace
--- /dev/null
@@ -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.