Packages
ejabberd
16.9.0
26.4.0
26.3.0
26.2.0
26.1.0
25.10.0
25.8.0
25.7.0
25.4.0
25.3.0
24.12.0
24.10.0
24.7.0
24.6.0
24.2.6
23.10.0
23.4.0
23.1.0
22.10.0
22.5.0
21.12.0
21.7.0
21.4.0
21.1.0
20.12.0
20.7.0
20.4.0
20.3.0
20.2.0
20.1.0
19.9.1
19.9.0
19.8.0
19.5.0
19.2.0
18.12.1
18.12.0
18.6.0
18.4.0
18.3.0
18.1.0
17.11.0
17.9.0
17.6.0
17.3.0
17.1.0
16.12.0-beta1
16.9.0
16.8.0
16.6.2
16.6.1
16.6.0
16.4.1
16.4.0
16.3.0
16.2.0
16.1.0-beta1
Robust, Ubiquitous and Massively Scalable Messaging Platform (XMPP, MQTT, SIP Server)
Current section
Files
Jump to
Current section
Files
src/mod_privilege.erl
%%%--------------------------------------------------------------------------------------
%%% 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.