xmpp.chapril.org-ejabberd/src/mod_privilege.erl

364 lines
15 KiB
Erlang

%%%--------------------------------------------------------------------------------------
%%% 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.