2016-11-12 11:27:15 +01:00
|
|
|
%%%-------------------------------------------------------------------
|
2016-11-13 14:46:04 +01:00
|
|
|
%%% File : mod_privilege.erl
|
|
|
|
%%% Author : Anna Mukharram <amuhar3@gmail.com>
|
|
|
|
%%% Purpose : XEP-0356: Privileged Entity
|
|
|
|
%%%
|
|
|
|
%%%
|
2024-01-22 16:40:01 +01:00
|
|
|
%%% ejabberd, Copyright (C) 2002-2024 ProcessOne
|
2016-11-13 14:46:04 +01:00
|
|
|
%%%
|
|
|
|
%%% This program is free software; you can redistribute it and/or
|
|
|
|
%%% modify it under the terms of the GNU General Public License as
|
|
|
|
%%% published by the Free Software Foundation; either version 2 of the
|
|
|
|
%%% License, or (at your option) any later version.
|
|
|
|
%%%
|
|
|
|
%%% This program is distributed in the hope that it will be useful,
|
|
|
|
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
%%% General Public License for more details.
|
|
|
|
%%%
|
|
|
|
%%% You should have received a copy of the GNU General Public License along
|
|
|
|
%%% with this program; if not, write to the Free Software Foundation, Inc.,
|
|
|
|
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
2016-11-12 11:27:15 +01:00
|
|
|
%%%
|
|
|
|
%%%-------------------------------------------------------------------
|
2016-09-07 14:34:31 +02:00
|
|
|
-module(mod_privilege).
|
|
|
|
|
2016-11-13 14:46:04 +01:00
|
|
|
-author('amuhar3@gmail.com').
|
|
|
|
|
2024-10-28 11:04:53 +01:00
|
|
|
-protocol({xep, 356, '0.4.1', '24.10', "", ""}).
|
2016-11-13 14:46:04 +01:00
|
|
|
|
2016-11-12 11:27:15 +01:00
|
|
|
-behaviour(gen_server).
|
|
|
|
-behaviour(gen_mod).
|
2016-09-07 14:34:31 +02:00
|
|
|
|
2016-11-12 11:27:15 +01:00
|
|
|
%% API
|
2018-01-23 08:54:52 +01:00
|
|
|
-export([start/2, stop/1, reload/3, mod_opt_type/1, mod_options/1, depends/2]).
|
2020-01-08 10:24:51 +01:00
|
|
|
-export([mod_doc/0]).
|
2016-11-12 11:27:15 +01:00
|
|
|
%% gen_server callbacks
|
|
|
|
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
|
|
|
terminate/2, code_change/3]).
|
|
|
|
-export([component_connected/1, component_disconnected/2,
|
2024-09-26 12:07:52 +02:00
|
|
|
component_send_packet/1,
|
2017-02-16 09:00:26 +01:00
|
|
|
roster_access/2, process_message/1,
|
2017-01-09 15:02:17 +01:00
|
|
|
process_presence_out/1, process_presence_in/1]).
|
2016-09-07 14:34:31 +02:00
|
|
|
|
2016-11-12 11:27:15 +01:00
|
|
|
-include("logger.hrl").
|
2020-09-03 13:45:57 +02:00
|
|
|
-include_lib("xmpp/include/xmpp.hrl").
|
2019-06-22 16:08:45 +02:00
|
|
|
-include("translate.hrl").
|
2016-09-07 14:34:31 +02:00
|
|
|
|
2019-07-08 08:46:50 +02:00
|
|
|
-type roster_permission() :: both | get | set.
|
2024-09-26 12:07:52 +02:00
|
|
|
-type iq_permission() :: both | get | set.
|
2019-07-08 08:46:50 +02:00
|
|
|
-type presence_permission() :: managed_entity | roster.
|
|
|
|
-type message_permission() :: outgoing.
|
|
|
|
-type roster_permissions() :: [{roster_permission(), acl:acl()}].
|
2024-09-26 12:07:52 +02:00
|
|
|
-type iq_permissions() :: [{iq_permission(), acl:acl()}].
|
2019-07-08 08:46:50 +02:00
|
|
|
-type presence_permissions() :: [{presence_permission(), acl:acl()}].
|
|
|
|
-type message_permissions() :: [{message_permission(), acl:acl()}].
|
2024-09-26 12:07:52 +02:00
|
|
|
-type access() :: [{roster, roster_permission()} |
|
|
|
|
{iq, [privilege_namespace()]} |
|
|
|
|
{presence, presence_permission()} |
|
|
|
|
{message, message_permission()}].
|
2019-07-08 08:46:50 +02:00
|
|
|
-type permissions() :: #{binary() => access()}.
|
|
|
|
-record(state, {server_host = <<"">> :: binary()}).
|
2016-09-07 14:34:31 +02:00
|
|
|
|
2016-11-12 11:27:15 +01:00
|
|
|
%%%===================================================================
|
|
|
|
%%% API
|
|
|
|
%%%===================================================================
|
|
|
|
start(Host, Opts) ->
|
2017-02-14 10:39:26 +01:00
|
|
|
gen_mod:start_child(?MODULE, Host, Opts).
|
2016-09-07 14:34:31 +02:00
|
|
|
|
2016-11-12 11:27:15 +01:00
|
|
|
stop(Host) ->
|
2017-02-14 10:39:26 +01:00
|
|
|
gen_mod:stop_child(?MODULE, Host).
|
2016-09-07 14:34:31 +02:00
|
|
|
|
2017-02-22 17:46:47 +01:00
|
|
|
reload(_Host, _NewOpts, _OldOpts) ->
|
|
|
|
ok.
|
|
|
|
|
2019-06-14 11:33:26 +02:00
|
|
|
mod_opt_type(roster) ->
|
|
|
|
econf:options(
|
|
|
|
#{both => econf:acl(), get => econf:acl(), set => econf:acl()});
|
2024-09-26 12:07:52 +02:00
|
|
|
mod_opt_type(iq) ->
|
|
|
|
econf:map(
|
|
|
|
econf:binary(),
|
|
|
|
econf:options(#{both => econf:acl(), get => econf:acl(), set => econf:acl()}));
|
2019-06-14 11:33:26 +02:00
|
|
|
mod_opt_type(message) ->
|
|
|
|
econf:options(
|
|
|
|
#{outgoing => econf:acl()});
|
|
|
|
mod_opt_type(presence) ->
|
|
|
|
econf:options(
|
|
|
|
#{managed_entity => econf:acl(), roster => econf:acl()}).
|
2018-01-23 08:54:52 +01:00
|
|
|
|
|
|
|
mod_options(_) ->
|
|
|
|
[{roster, [{both, none}, {get, none}, {set, none}]},
|
2024-09-26 12:07:52 +02:00
|
|
|
{iq, []},
|
2018-01-23 08:54:52 +01:00
|
|
|
{presence, [{managed_entity, none}, {roster, none}]},
|
|
|
|
{message, [{outgoing,none}]}].
|
2016-09-07 14:34:31 +02:00
|
|
|
|
2020-01-08 10:24:51 +01:00
|
|
|
mod_doc() ->
|
|
|
|
#{desc =>
|
|
|
|
[?T("This module is an implementation of "
|
|
|
|
"https://xmpp.org/extensions/xep-0356.html"
|
|
|
|
"[XEP-0356: Privileged Entity]. This extension "
|
|
|
|
"allows components to have privileged access to "
|
|
|
|
"other entity data (send messages on behalf of the "
|
|
|
|
"server or on behalf of a user, get/set user roster, "
|
|
|
|
"access presence information, etc.). This may be used "
|
|
|
|
"to write powerful external components, for example "
|
|
|
|
"implementing an external "
|
|
|
|
"https://xmpp.org/extensions/xep-0163.html[PEP] or "
|
|
|
|
"https://xmpp.org/extensions/xep-0313.html[MAM] service."), "",
|
|
|
|
?T("By default a component does not have any privileged access. "
|
|
|
|
"It is worth noting that the permissions grant access to "
|
|
|
|
"the component to a specific data type for all users of "
|
|
|
|
"the virtual host on which 'mod_privilege' is loaded."), "",
|
2020-04-08 18:49:41 +02:00
|
|
|
?T("Make sure you have a listener configured to connect your "
|
|
|
|
"component. Check the section about listening ports for more "
|
|
|
|
"information."), "",
|
|
|
|
?T("WARNING: Security issue: Privileged access gives components "
|
|
|
|
"access to sensitive data, so permission should be granted "
|
|
|
|
"carefully, only if you trust a component."), "",
|
2021-08-21 18:31:21 +02:00
|
|
|
?T("NOTE: This module is complementary to _`mod_delegation`_, "
|
2020-01-08 10:24:51 +01:00
|
|
|
"but can also be used separately.")],
|
2024-10-28 13:42:56 +01:00
|
|
|
note => "improved in 24.10",
|
2020-01-08 10:24:51 +01:00
|
|
|
opts =>
|
|
|
|
[{roster,
|
|
|
|
#{value => ?T("Options"),
|
|
|
|
desc =>
|
|
|
|
?T("This option defines roster permissions. "
|
|
|
|
"By default no permissions are given. "
|
|
|
|
"The 'Options' are:")},
|
|
|
|
[{both,
|
|
|
|
#{value => ?T("AccessName"),
|
|
|
|
desc =>
|
|
|
|
?T("Sets read/write access to a user's roster. "
|
|
|
|
"The default value is 'none'.")}},
|
|
|
|
{get,
|
|
|
|
#{value => ?T("AccessName"),
|
|
|
|
desc =>
|
|
|
|
?T("Sets read access to a user's roster. "
|
|
|
|
"The default value is 'none'.")}},
|
|
|
|
{set,
|
|
|
|
#{value => ?T("AccessName"),
|
|
|
|
desc =>
|
|
|
|
?T("Sets write access to a user's roster. "
|
|
|
|
"The default value is 'none'.")}}]},
|
2024-09-26 12:07:52 +02:00
|
|
|
{iq,
|
|
|
|
#{value => "{Namespace: Options}",
|
|
|
|
desc =>
|
|
|
|
?T("This option defines namespaces and their IQ permissions. "
|
|
|
|
"By default no permissions are given. "
|
|
|
|
"The 'Options' are:")},
|
|
|
|
[{both,
|
|
|
|
#{value => ?T("AccessName"),
|
|
|
|
desc =>
|
|
|
|
?T("Allows sending IQ stanzas of type 'get' and 'set'. "
|
|
|
|
"The default value is 'none'.")}},
|
|
|
|
{get,
|
|
|
|
#{value => ?T("AccessName"),
|
|
|
|
desc =>
|
|
|
|
?T("Allows sending IQ stanzas of type 'get'. "
|
|
|
|
"The default value is 'none'.")}},
|
|
|
|
{set,
|
|
|
|
#{value => ?T("AccessName"),
|
|
|
|
desc =>
|
|
|
|
?T("Allows sending IQ stanzas of type 'set'. "
|
|
|
|
"The default value is 'none'.")}}]},
|
2020-01-08 10:24:51 +01:00
|
|
|
{message,
|
|
|
|
#{value => ?T("Options"),
|
|
|
|
desc =>
|
|
|
|
?T("This option defines permissions for messages. "
|
|
|
|
"By default no permissions are given. "
|
|
|
|
"The 'Options' are:")},
|
|
|
|
[{outgoing,
|
|
|
|
#{value => ?T("AccessName"),
|
|
|
|
desc =>
|
|
|
|
?T("The option defines an access rule for sending "
|
|
|
|
"outgoing messages by the component. "
|
|
|
|
"The default value is 'none'.")}}]},
|
|
|
|
{presence,
|
|
|
|
#{value => ?T("Options"),
|
|
|
|
desc =>
|
|
|
|
?T("This option defines permissions for presences. "
|
|
|
|
"By default no permissions are given. "
|
|
|
|
"The 'Options' are:")},
|
|
|
|
[{managed_entity,
|
|
|
|
#{value => ?T("AccessName"),
|
|
|
|
desc =>
|
|
|
|
?T("An access rule that gives permissions to "
|
|
|
|
"the component to receive server presences. "
|
|
|
|
"The default value is 'none'.")}},
|
|
|
|
{roster,
|
|
|
|
#{value => ?T("AccessName"),
|
|
|
|
desc =>
|
|
|
|
?T("An access rule that gives permissions to "
|
|
|
|
"the component to receive the presence of both "
|
|
|
|
"the users and the contacts in their roster. "
|
|
|
|
"The default value is 'none'.")}}]}],
|
|
|
|
example =>
|
|
|
|
["modules:",
|
|
|
|
" mod_privilege:",
|
2024-09-26 12:07:52 +02:00
|
|
|
" iq:",
|
|
|
|
" http://jabber.org/protocol/pubsub:",
|
|
|
|
" get: all",
|
2020-01-08 10:24:51 +01:00
|
|
|
" roster:",
|
|
|
|
" get: all",
|
|
|
|
" presence:",
|
|
|
|
" managed_entity: all",
|
|
|
|
" message:",
|
2024-03-29 16:09:50 +01:00
|
|
|
" outgoing: all"]}.
|
2020-01-08 10:24:51 +01:00
|
|
|
|
2016-11-12 11:27:15 +01:00
|
|
|
depends(_, _) ->
|
|
|
|
[].
|
2016-09-07 14:34:31 +02:00
|
|
|
|
2016-11-12 11:27:15 +01:00
|
|
|
-spec component_connected(binary()) -> ok.
|
|
|
|
component_connected(Host) ->
|
|
|
|
lists:foreach(
|
|
|
|
fun(ServerHost) ->
|
|
|
|
Proc = gen_mod:get_module_proc(ServerHost, ?MODULE),
|
|
|
|
gen_server:cast(Proc, {component_connected, Host})
|
2019-06-14 11:33:26 +02:00
|
|
|
end, ejabberd_option:hosts()).
|
2016-09-07 14:34:31 +02:00
|
|
|
|
2016-11-12 11:27:15 +01:00
|
|
|
-spec component_disconnected(binary(), binary()) -> ok.
|
|
|
|
component_disconnected(Host, _Reason) ->
|
2016-09-07 14:34:31 +02:00
|
|
|
lists:foreach(
|
2016-11-12 11:27:15 +01:00
|
|
|
fun(ServerHost) ->
|
|
|
|
Proc = gen_mod:get_module_proc(ServerHost, ?MODULE),
|
|
|
|
gen_server:cast(Proc, {component_disconnected, Host})
|
2019-06-14 11:33:26 +02:00
|
|
|
end, ejabberd_option:hosts()).
|
2016-09-07 14:34:31 +02:00
|
|
|
|
2024-09-26 12:07:52 +02:00
|
|
|
%%
|
|
|
|
%% Message processing
|
|
|
|
%%
|
|
|
|
|
2017-02-16 09:00:26 +01:00
|
|
|
-spec process_message(stanza()) -> stop | ok.
|
|
|
|
process_message(#message{from = #jid{luser = <<"">>, lresource = <<"">>} = From,
|
|
|
|
to = #jid{lresource = <<"">>} = To,
|
|
|
|
lang = Lang, type = T} = Msg) when T /= error ->
|
2016-11-12 11:27:15 +01:00
|
|
|
Host = From#jid.lserver,
|
|
|
|
ServerHost = To#jid.lserver,
|
|
|
|
Permissions = get_permissions(ServerHost),
|
2019-07-08 08:46:50 +02:00
|
|
|
case maps:find(Host, Permissions) of
|
2016-11-12 11:27:15 +01:00
|
|
|
{ok, Access} ->
|
|
|
|
case proplists:get_value(message, Access, none) of
|
|
|
|
outgoing ->
|
2017-02-16 09:00:26 +01:00
|
|
|
forward_message(Msg);
|
2018-01-30 13:10:22 +01:00
|
|
|
_ ->
|
2019-06-22 16:08:45 +02:00
|
|
|
Txt = ?T("Insufficient privilege"),
|
2016-11-12 11:27:15 +01:00
|
|
|
Err = xmpp:err_forbidden(Txt, Lang),
|
2017-02-16 09:00:26 +01:00
|
|
|
ejabberd_router:route_error(Msg, Err)
|
2016-11-12 11:27:15 +01:00
|
|
|
end,
|
|
|
|
stop;
|
|
|
|
error ->
|
|
|
|
%% Component is disconnected
|
|
|
|
ok
|
|
|
|
end;
|
2024-09-26 12:07:52 +02:00
|
|
|
|
2017-02-16 09:00:26 +01:00
|
|
|
process_message(_Stanza) ->
|
2016-11-12 11:27:15 +01:00
|
|
|
ok.
|
|
|
|
|
2024-09-26 12:07:52 +02:00
|
|
|
%%
|
|
|
|
%% IQ processing
|
|
|
|
%%
|
|
|
|
|
|
|
|
%% @format-begin
|
|
|
|
|
|
|
|
component_send_packet({#iq{from = From,
|
|
|
|
to = #jid{lresource = <<"">>} = To,
|
|
|
|
id = Id,
|
|
|
|
type = Type} =
|
|
|
|
IQ,
|
|
|
|
State})
|
|
|
|
when Type /= error ->
|
|
|
|
Host = From#jid.lserver,
|
|
|
|
ServerHost = To#jid.lserver,
|
|
|
|
Permissions = get_permissions(ServerHost),
|
|
|
|
Result =
|
|
|
|
case {maps:find(Host, Permissions), get_iq_encapsulated_details(IQ)} of
|
|
|
|
{{ok, Access}, {ok, EncapType, EncapNs, EncapFrom, EncIq}}
|
|
|
|
when (EncapType == Type) and ((EncapFrom == undefined) or (EncapFrom == To)) ->
|
2024-11-05 16:55:47 +01:00
|
|
|
NsPermissions = proplists:get_value(iq, Access, []),
|
2024-09-26 12:07:52 +02:00
|
|
|
Permission =
|
|
|
|
case lists:keyfind(EncapNs, 2, NsPermissions) of
|
|
|
|
#privilege_namespace{type = AllowedType} ->
|
|
|
|
AllowedType;
|
|
|
|
_ ->
|
|
|
|
none
|
|
|
|
end,
|
|
|
|
case Permission == both
|
|
|
|
orelse Permission == get andalso Type == get
|
|
|
|
orelse Permission == set andalso Type == set
|
|
|
|
of
|
|
|
|
true ->
|
|
|
|
forward_iq(Host, To, Id, EncIq);
|
|
|
|
false ->
|
|
|
|
?INFO_MSG("IQ not forwarded: Permission not granted to ns=~s with type=~p",
|
|
|
|
[EncapNs, Type]),
|
|
|
|
drop
|
|
|
|
end;
|
|
|
|
{error, _} ->
|
|
|
|
%% Component is disconnected
|
|
|
|
?INFO_MSG("IQ not forwarded: Component seems disconnected", []),
|
|
|
|
drop;
|
|
|
|
{_, {ok, E, _, _, _}} when E /= Type ->
|
|
|
|
?INFO_MSG("IQ not forwarded: The encapsulated IQ stanza type=~p "
|
|
|
|
"does not match the top-level IQ stanza type=~p",
|
|
|
|
[E, Type]),
|
|
|
|
drop;
|
|
|
|
{_, {ok, _, _, EF, _}} when (EF /= undefined) and (EF /= To) ->
|
|
|
|
?INFO_MSG("IQ not forwarded: The FROM attribute in the encapsulated "
|
|
|
|
"IQ stanza and the TO in top-level IQ stanza do not match",
|
|
|
|
[]),
|
2024-11-05 16:55:47 +01:00
|
|
|
drop;
|
|
|
|
{_, {error, no_privileged_iq, _Err}} ->
|
|
|
|
?INFO_MSG("IQ not forwarded: Component tried to send not wrapped IQ stanza.",
|
|
|
|
[]),
|
|
|
|
drop;
|
|
|
|
{_, {error, roster_query, _Err}} ->
|
|
|
|
IQ;
|
|
|
|
{_, {error, Type, _Err}} ->
|
|
|
|
?INFO_MSG("IQ not forwarded: Component tried to send not valid IQ stanza: ~p.",
|
|
|
|
[Type]),
|
2024-09-26 12:07:52 +02:00
|
|
|
drop
|
|
|
|
end,
|
|
|
|
{Result, State};
|
|
|
|
component_send_packet(Acc) ->
|
|
|
|
Acc.
|
|
|
|
%% @format-end
|
|
|
|
|
|
|
|
%%
|
|
|
|
%% Roster processing
|
|
|
|
%%
|
|
|
|
|
2022-10-04 15:41:44 +02:00
|
|
|
-spec roster_access({true, iq()} | false, iq()) -> {true, iq()} | false.
|
|
|
|
roster_access({true, _IQ} = Acc, _) ->
|
|
|
|
Acc;
|
|
|
|
roster_access(false, #iq{from = From, to = To, type = Type} = IQ) ->
|
2016-11-12 11:27:15 +01:00
|
|
|
Host = From#jid.lserver,
|
|
|
|
ServerHost = To#jid.lserver,
|
|
|
|
Permissions = get_permissions(ServerHost),
|
2019-07-08 08:46:50 +02:00
|
|
|
case maps:find(Host, Permissions) of
|
2016-11-12 11:27:15 +01:00
|
|
|
{ok, Access} ->
|
|
|
|
Permission = proplists:get_value(roster, Access, none),
|
2022-10-04 15:41:44 +02:00
|
|
|
case (Permission == both)
|
|
|
|
orelse (Permission == get andalso Type == get)
|
|
|
|
orelse (Permission == set andalso Type == set) of
|
|
|
|
true ->
|
|
|
|
{true, xmpp:put_meta(IQ, privilege_from, To)};
|
|
|
|
false ->
|
|
|
|
false
|
|
|
|
end;
|
2016-11-12 11:27:15 +01:00
|
|
|
error ->
|
|
|
|
%% Component is disconnected
|
|
|
|
false
|
2016-09-07 14:34:31 +02:00
|
|
|
end.
|
|
|
|
|
2017-01-09 15:02:17 +01:00
|
|
|
-spec process_presence_out({stanza(), ejabberd_c2s:state()}) -> {stanza(), ejabberd_c2s:state()}.
|
|
|
|
process_presence_out({#presence{
|
|
|
|
from = #jid{luser = LUser, lserver = LServer} = From,
|
|
|
|
to = #jid{luser = LUser, lserver = LServer, lresource = <<"">>},
|
|
|
|
type = Type} = Pres, C2SState})
|
2016-11-12 11:27:15 +01:00
|
|
|
when Type == available; Type == unavailable ->
|
|
|
|
%% Self-presence processing
|
|
|
|
Permissions = get_permissions(LServer),
|
|
|
|
lists:foreach(
|
|
|
|
fun({Host, Access}) ->
|
|
|
|
Permission = proplists:get_value(presence, Access, none),
|
|
|
|
if Permission == roster; Permission == managed_entity ->
|
|
|
|
To = jid:make(Host),
|
|
|
|
ejabberd_router:route(
|
2017-02-16 09:00:26 +01:00
|
|
|
xmpp:set_from_to(Pres, From, To));
|
2016-11-12 11:27:15 +01:00
|
|
|
true ->
|
|
|
|
ok
|
|
|
|
end
|
2019-07-08 08:46:50 +02:00
|
|
|
end, maps:to_list(Permissions)),
|
2017-01-09 15:02:17 +01:00
|
|
|
{Pres, C2SState};
|
|
|
|
process_presence_out(Acc) ->
|
2016-11-12 11:27:15 +01:00
|
|
|
Acc.
|
2016-09-07 14:34:31 +02:00
|
|
|
|
2017-01-09 15:02:17 +01:00
|
|
|
-spec process_presence_in({stanza(), ejabberd_c2s:state()}) -> {stanza(), ejabberd_c2s:state()}.
|
|
|
|
process_presence_in({#presence{
|
|
|
|
from = #jid{luser = U, lserver = S} = From,
|
|
|
|
to = #jid{luser = LUser, lserver = LServer},
|
|
|
|
type = Type} = Pres, C2SState})
|
2016-11-12 11:27:15 +01:00
|
|
|
when {U, S} /= {LUser, LServer} andalso
|
|
|
|
(Type == available orelse Type == unavailable) ->
|
|
|
|
Permissions = get_permissions(LServer),
|
|
|
|
lists:foreach(
|
|
|
|
fun({Host, Access}) ->
|
|
|
|
case proplists:get_value(presence, Access, none) of
|
|
|
|
roster ->
|
|
|
|
Permission = proplists:get_value(roster, Access, none),
|
|
|
|
if Permission == both; Permission == get ->
|
|
|
|
To = jid:make(Host),
|
|
|
|
ejabberd_router:route(
|
2017-02-16 09:00:26 +01:00
|
|
|
xmpp:set_from_to(Pres, From, To));
|
2016-11-12 11:27:15 +01:00
|
|
|
true ->
|
|
|
|
ok
|
|
|
|
end;
|
2018-01-30 13:10:22 +01:00
|
|
|
_ ->
|
2016-11-12 11:27:15 +01:00
|
|
|
ok
|
|
|
|
end
|
2019-07-08 08:46:50 +02:00
|
|
|
end, maps:to_list(Permissions)),
|
2017-01-09 15:02:17 +01:00
|
|
|
{Pres, C2SState};
|
|
|
|
process_presence_in(Acc) ->
|
2016-11-12 11:27:15 +01:00
|
|
|
Acc.
|
2016-09-07 14:34:31 +02:00
|
|
|
|
2016-11-12 11:27:15 +01:00
|
|
|
%%%===================================================================
|
|
|
|
%%% gen_server callbacks
|
|
|
|
%%%===================================================================
|
2019-08-04 20:46:18 +02:00
|
|
|
init([Host|_]) ->
|
2017-02-14 08:25:08 +01:00
|
|
|
process_flag(trap_exit, true),
|
2019-07-08 08:46:50 +02:00
|
|
|
catch ets:new(?MODULE,
|
|
|
|
[named_table, public,
|
|
|
|
{heir, erlang:group_leader(), none}]),
|
2016-11-12 11:27:15 +01:00
|
|
|
ejabberd_hooks:add(component_connected, ?MODULE,
|
|
|
|
component_connected, 50),
|
|
|
|
ejabberd_hooks:add(component_disconnected, ?MODULE,
|
|
|
|
component_disconnected, 50),
|
|
|
|
ejabberd_hooks:add(local_send_to_resource_hook, Host, ?MODULE,
|
|
|
|
process_message, 50),
|
|
|
|
ejabberd_hooks:add(roster_remote_access, Host, ?MODULE,
|
|
|
|
roster_access, 50),
|
|
|
|
ejabberd_hooks:add(user_send_packet, Host, ?MODULE,
|
|
|
|
process_presence_out, 50),
|
|
|
|
ejabberd_hooks:add(user_receive_packet, Host, ?MODULE,
|
|
|
|
process_presence_in, 50),
|
2024-09-26 12:07:52 +02:00
|
|
|
ejabberd_hooks:add(component_send_packet, ?MODULE,
|
|
|
|
component_send_packet, 50),
|
2016-11-12 11:27:15 +01:00
|
|
|
{ok, #state{server_host = Host}}.
|
2016-09-07 14:34:31 +02:00
|
|
|
|
2019-07-08 08:46:50 +02:00
|
|
|
handle_call(Request, From, State) ->
|
|
|
|
?WARNING_MSG("Unexpected call from ~p: ~p", [From, Request]),
|
|
|
|
{noreply, State}.
|
2016-09-07 14:34:31 +02:00
|
|
|
|
2016-11-12 11:27:15 +01:00
|
|
|
handle_cast({component_connected, Host}, State) ->
|
|
|
|
ServerHost = State#state.server_host,
|
|
|
|
From = jid:make(ServerHost),
|
|
|
|
To = jid:make(Host),
|
|
|
|
RosterPerm = get_roster_permission(ServerHost, Host),
|
2024-09-26 12:07:52 +02:00
|
|
|
IqNamespaces = get_iq_namespaces(ServerHost, Host),
|
2016-11-12 11:27:15 +01:00
|
|
|
PresencePerm = get_presence_permission(ServerHost, Host),
|
|
|
|
MessagePerm = get_message_permission(ServerHost, Host),
|
2024-09-26 12:07:52 +02:00
|
|
|
if RosterPerm /= none; IqNamespaces /= []; PresencePerm /= none; MessagePerm /= none ->
|
2016-11-12 11:27:15 +01:00
|
|
|
Priv = #privilege{perms = [#privilege_perm{access = message,
|
|
|
|
type = MessagePerm},
|
|
|
|
#privilege_perm{access = roster,
|
|
|
|
type = RosterPerm},
|
2024-09-26 12:07:52 +02:00
|
|
|
#privilege_perm{access = iq,
|
|
|
|
namespaces = IqNamespaces},
|
2016-11-12 11:27:15 +01:00
|
|
|
#privilege_perm{access = presence,
|
|
|
|
type = PresencePerm}]},
|
|
|
|
?INFO_MSG("Granting permissions to external "
|
2019-09-23 14:17:20 +02:00
|
|
|
"component '~ts': roster = ~ts, presence = ~ts, "
|
2024-09-26 12:07:52 +02:00
|
|
|
"message = ~ts,~n iq = ~p",
|
|
|
|
[Host, RosterPerm, PresencePerm, MessagePerm, IqNamespaces]),
|
2016-11-12 11:27:15 +01:00
|
|
|
Msg = #message{from = From, to = To, sub_els = [Priv]},
|
2017-02-16 09:00:26 +01:00
|
|
|
ejabberd_router:route(Msg),
|
2019-07-08 08:46:50 +02:00
|
|
|
Permissions = maps:put(Host, [{roster, RosterPerm},
|
2024-09-26 12:07:52 +02:00
|
|
|
{iq, IqNamespaces},
|
2019-07-08 08:46:50 +02:00
|
|
|
{presence, PresencePerm},
|
|
|
|
{message, MessagePerm}],
|
|
|
|
get_permissions(ServerHost)),
|
|
|
|
ets:insert(?MODULE, {ServerHost, Permissions}),
|
|
|
|
{noreply, State};
|
2016-11-12 11:27:15 +01:00
|
|
|
true ->
|
2019-09-23 14:17:20 +02:00
|
|
|
?INFO_MSG("Granting no permissions to external component '~ts'",
|
2016-11-12 11:27:15 +01:00
|
|
|
[Host]),
|
|
|
|
{noreply, State}
|
|
|
|
end;
|
|
|
|
handle_cast({component_disconnected, Host}, State) ->
|
2019-07-08 08:46:50 +02:00
|
|
|
ServerHost = State#state.server_host,
|
|
|
|
Permissions = maps:remove(Host, get_permissions(ServerHost)),
|
|
|
|
case maps:size(Permissions) of
|
|
|
|
0 -> ets:delete(?MODULE, ServerHost);
|
|
|
|
_ -> ets:insert(?MODULE, {ServerHost, Permissions})
|
|
|
|
end,
|
|
|
|
{noreply, State};
|
|
|
|
handle_cast(Msg, State) ->
|
|
|
|
?WARNING_MSG("Unexpected cast: ~p", [Msg]),
|
2016-11-12 11:27:15 +01:00
|
|
|
{noreply, State}.
|
2016-09-07 14:34:31 +02:00
|
|
|
|
2019-07-08 08:46:50 +02:00
|
|
|
handle_info(Info, State) ->
|
|
|
|
?WARNING_MSG("Unexpected info: ~p", [Info]),
|
2016-11-12 11:27:15 +01:00
|
|
|
{noreply, State}.
|
2016-09-07 14:34:31 +02:00
|
|
|
|
2016-11-12 11:27:15 +01:00
|
|
|
terminate(_Reason, State) ->
|
|
|
|
Host = State#state.server_host,
|
2019-07-29 09:46:20 +02:00
|
|
|
case gen_mod:is_loaded_elsewhere(Host, ?MODULE) of
|
|
|
|
false ->
|
2024-09-26 12:07:52 +02:00
|
|
|
ejabberd_hooks:delete(component_send_packet, ?MODULE,
|
|
|
|
component_send_packet, 50),
|
2019-07-29 09:46:20 +02:00
|
|
|
ejabberd_hooks:delete(component_connected, ?MODULE,
|
|
|
|
component_connected, 50),
|
|
|
|
ejabberd_hooks:delete(component_disconnected, ?MODULE,
|
|
|
|
component_disconnected, 50);
|
|
|
|
true ->
|
|
|
|
ok
|
|
|
|
end,
|
2016-11-12 11:27:15 +01:00
|
|
|
ejabberd_hooks:delete(local_send_to_resource_hook, Host, ?MODULE,
|
|
|
|
process_message, 50),
|
|
|
|
ejabberd_hooks:delete(roster_remote_access, Host, ?MODULE,
|
|
|
|
roster_access, 50),
|
|
|
|
ejabberd_hooks:delete(user_send_packet, Host, ?MODULE,
|
|
|
|
process_presence_out, 50),
|
|
|
|
ejabberd_hooks:delete(user_receive_packet, Host, ?MODULE,
|
2019-07-08 08:46:50 +02:00
|
|
|
process_presence_in, 50),
|
|
|
|
ets:delete(?MODULE, Host).
|
2016-09-07 14:34:31 +02:00
|
|
|
|
2016-11-12 11:27:15 +01:00
|
|
|
code_change(_OldVsn, State, _Extra) ->
|
|
|
|
{ok, State}.
|
2016-09-07 14:34:31 +02:00
|
|
|
|
2016-11-12 11:27:15 +01:00
|
|
|
%%%===================================================================
|
|
|
|
%%% Internal functions
|
|
|
|
%%%===================================================================
|
2019-07-08 08:46:50 +02:00
|
|
|
-spec get_permissions(binary()) -> permissions().
|
2016-11-12 11:27:15 +01:00
|
|
|
get_permissions(ServerHost) ->
|
2024-10-01 13:23:53 +02:00
|
|
|
case ets:lookup(?MODULE, ServerHost) of
|
|
|
|
[] -> #{};
|
|
|
|
[{_, Permissions}] -> Permissions
|
2016-11-12 11:27:15 +01:00
|
|
|
end.
|
2016-09-07 14:34:31 +02:00
|
|
|
|
2024-09-26 12:07:52 +02:00
|
|
|
%%
|
|
|
|
%% Message
|
|
|
|
%%
|
|
|
|
|
2019-07-08 08:46:50 +02:00
|
|
|
-spec forward_message(message()) -> ok.
|
2017-02-16 09:00:26 +01:00
|
|
|
forward_message(#message{to = To} = Msg) ->
|
2016-11-12 11:27:15 +01:00
|
|
|
ServerHost = To#jid.lserver,
|
2016-11-13 11:41:04 +01:00
|
|
|
Lang = xmpp:get_lang(Msg),
|
2019-06-21 20:06:32 +02:00
|
|
|
CodecOpts = ejabberd_config:codec_options(),
|
2017-12-11 07:46:26 +01:00
|
|
|
try xmpp:try_subtag(Msg, #privilege{}) of
|
2018-01-25 18:02:47 +01:00
|
|
|
#privilege{forwarded = #forwarded{sub_els = [SubEl]}} ->
|
2018-02-09 16:12:50 +01:00
|
|
|
try xmpp:decode(SubEl, ?NS_CLIENT, CodecOpts) of
|
2016-11-13 11:41:04 +01:00
|
|
|
#message{} = NewMsg ->
|
|
|
|
case NewMsg#message.from of
|
|
|
|
#jid{lresource = <<"">>, lserver = ServerHost} ->
|
2023-06-23 16:47:20 +02:00
|
|
|
FromJID = NewMsg#message.from,
|
|
|
|
State = #{jid => FromJID},
|
|
|
|
ejabberd_hooks:run_fold(user_send_packet, FromJID#jid.lserver, {NewMsg, State}, []),
|
2017-02-16 09:00:26 +01:00
|
|
|
ejabberd_router:route(NewMsg);
|
2016-11-13 11:41:04 +01:00
|
|
|
_ ->
|
|
|
|
Lang = xmpp:get_lang(Msg),
|
2019-06-22 16:08:45 +02:00
|
|
|
Txt = ?T("Invalid 'from' attribute in forwarded message"),
|
2016-11-13 11:41:04 +01:00
|
|
|
Err = xmpp:err_forbidden(Txt, Lang),
|
2017-02-16 09:00:26 +01:00
|
|
|
ejabberd_router:route_error(Msg, Err)
|
2016-11-13 11:41:04 +01:00
|
|
|
end;
|
2016-11-12 11:27:15 +01:00
|
|
|
_ ->
|
2019-06-22 16:08:45 +02:00
|
|
|
Txt = ?T("Message not found in forwarded payload"),
|
2016-11-13 11:41:04 +01:00
|
|
|
Err = xmpp:err_bad_request(Txt, Lang),
|
2017-02-16 09:00:26 +01:00
|
|
|
ejabberd_router:route_error(Msg, Err)
|
2016-11-13 11:41:04 +01:00
|
|
|
catch _:{xmpp_codec, Why} ->
|
2017-11-14 07:02:43 +01:00
|
|
|
Txt = xmpp:io_format_error(Why),
|
2016-11-13 11:41:04 +01:00
|
|
|
Err = xmpp:err_bad_request(Txt, Lang),
|
2017-02-16 09:00:26 +01:00
|
|
|
ejabberd_router:route_error(Msg, Err)
|
2016-11-12 11:27:15 +01:00
|
|
|
end;
|
|
|
|
_ ->
|
2019-06-22 16:08:45 +02:00
|
|
|
Txt = ?T("No <forwarded/> element found"),
|
2017-12-11 07:46:26 +01:00
|
|
|
Err = xmpp:err_bad_request(Txt, Lang),
|
|
|
|
ejabberd_router:route_error(Msg, Err)
|
|
|
|
catch _:{xmpp_codec, Why} ->
|
|
|
|
Txt = xmpp:io_format_error(Why),
|
2016-11-12 11:27:15 +01:00
|
|
|
Err = xmpp:err_bad_request(Txt, Lang),
|
2017-02-16 09:00:26 +01:00
|
|
|
ejabberd_router:route_error(Msg, Err)
|
2016-09-07 14:34:31 +02:00
|
|
|
end.
|
|
|
|
|
2024-09-26 12:07:52 +02:00
|
|
|
%%
|
|
|
|
%% IQ
|
|
|
|
%%
|
|
|
|
|
|
|
|
%% @format-begin
|
|
|
|
|
|
|
|
-spec get_iq_encapsulated_details(iq()) ->
|
2024-11-05 16:55:47 +01:00
|
|
|
{ok, iq_type(), binary(), jid(), iq()} |
|
|
|
|
{error, Why :: atom(), stanza_error()}.
|
2024-09-26 12:07:52 +02:00
|
|
|
get_iq_encapsulated_details(#iq{sub_els = [IqSub]} = Msg) ->
|
|
|
|
Lang = xmpp:get_lang(Msg),
|
|
|
|
try xmpp:try_subtag(Msg, #privileged_iq{}) of
|
|
|
|
#privileged_iq{iq = #iq{type = EncapsulatedType, from = From} = EncIq} ->
|
|
|
|
[IqSubSub] = xmpp:get_els(IqSub),
|
|
|
|
[Element] = xmpp:get_els(IqSubSub),
|
|
|
|
Ns = xmpp:get_ns(Element),
|
|
|
|
{ok, EncapsulatedType, Ns, From, EncIq};
|
|
|
|
_ ->
|
2024-11-05 16:55:47 +01:00
|
|
|
try xmpp:try_subtag(Msg, #roster_query{}) of
|
|
|
|
#roster_query{} ->
|
|
|
|
{error, roster_query, xmpp:err_bad_request()};
|
|
|
|
_ ->
|
|
|
|
Txt = ?T("No <privileged_iq/> element found"),
|
|
|
|
Err = xmpp:err_bad_request(Txt, Lang),
|
|
|
|
{error, no_privileged_iq, Err}
|
|
|
|
catch
|
|
|
|
_:{xmpp_codec, Why} ->
|
|
|
|
Txt = xmpp:io_format_error(Why),
|
|
|
|
Err = xmpp:err_bad_request(Txt, Lang),
|
|
|
|
{error, codec_error, Err}
|
|
|
|
end
|
2024-09-26 12:07:52 +02:00
|
|
|
catch
|
|
|
|
_:{xmpp_codec, Why} ->
|
|
|
|
Txt = xmpp:io_format_error(Why),
|
|
|
|
Err = xmpp:err_bad_request(Txt, Lang),
|
|
|
|
{error, codec_error, Err}
|
|
|
|
end.
|
|
|
|
|
|
|
|
-spec forward_iq(binary(), jid(), binary(), iq()) -> iq().
|
|
|
|
forward_iq(Host, ToplevelTo, Id, Iq) ->
|
|
|
|
FromJID = ToplevelTo,
|
|
|
|
NewIq0 = Iq#iq{from = FromJID},
|
|
|
|
xmpp:put_meta(NewIq0, privilege_iq, {Id, Host, FromJID}).
|
|
|
|
%% @format-end
|
|
|
|
|
|
|
|
%%
|
|
|
|
%% Permissions
|
|
|
|
%%
|
|
|
|
|
2019-07-08 08:46:50 +02:00
|
|
|
-spec get_roster_permission(binary(), binary()) -> roster_permission() | none.
|
2016-11-12 11:27:15 +01:00
|
|
|
get_roster_permission(ServerHost, Host) ->
|
2019-06-14 11:33:26 +02:00
|
|
|
Perms = mod_privilege_opt:roster(ServerHost),
|
2016-11-12 11:27:15 +01:00
|
|
|
case match_rule(ServerHost, Host, Perms, both) of
|
|
|
|
allow ->
|
|
|
|
both;
|
|
|
|
deny ->
|
|
|
|
Get = match_rule(ServerHost, Host, Perms, get),
|
|
|
|
Set = match_rule(ServerHost, Host, Perms, set),
|
|
|
|
if Get == allow, Set == allow -> both;
|
|
|
|
Get == allow -> get;
|
|
|
|
Set == allow -> set;
|
|
|
|
true -> none
|
|
|
|
end
|
|
|
|
end.
|
2016-09-07 14:34:31 +02:00
|
|
|
|
2024-09-26 12:07:52 +02:00
|
|
|
-spec get_iq_namespaces(binary(), binary()) -> [privilege_namespace()].
|
|
|
|
get_iq_namespaces(ServerHost, Host) ->
|
|
|
|
NsPerms = mod_privilege_opt:iq(ServerHost),
|
|
|
|
[#privilege_namespace{ns = Ns, type = get_iq_permission(ServerHost, Host, Perms)} || {Ns, Perms} <- NsPerms].
|
|
|
|
|
|
|
|
-spec get_iq_permission(binary(), binary(), [iq_permission()]) -> iq_permission() | none.
|
|
|
|
get_iq_permission(ServerHost, Host, Perms) ->
|
|
|
|
case match_rule(ServerHost, Host, Perms, both) of
|
|
|
|
allow ->
|
|
|
|
both;
|
|
|
|
deny ->
|
|
|
|
Get = match_rule(ServerHost, Host, Perms, get),
|
|
|
|
Set = match_rule(ServerHost, Host, Perms, set),
|
|
|
|
if Get == allow, Set == allow -> both;
|
|
|
|
Get == allow -> get;
|
|
|
|
Set == allow -> set;
|
|
|
|
true -> none
|
|
|
|
end
|
|
|
|
end.
|
|
|
|
|
2019-07-08 08:46:50 +02:00
|
|
|
-spec get_message_permission(binary(), binary()) -> message_permission() | none.
|
2016-11-12 11:27:15 +01:00
|
|
|
get_message_permission(ServerHost, Host) ->
|
2019-06-14 11:33:26 +02:00
|
|
|
Perms = mod_privilege_opt:message(ServerHost),
|
2016-11-12 11:27:15 +01:00
|
|
|
case match_rule(ServerHost, Host, Perms, outgoing) of
|
|
|
|
allow -> outgoing;
|
|
|
|
deny -> none
|
|
|
|
end.
|
2016-09-07 14:34:31 +02:00
|
|
|
|
2019-07-08 08:46:50 +02:00
|
|
|
-spec get_presence_permission(binary(), binary()) -> presence_permission() | none.
|
2016-11-12 11:27:15 +01:00
|
|
|
get_presence_permission(ServerHost, Host) ->
|
2019-06-14 11:33:26 +02:00
|
|
|
Perms = mod_privilege_opt:presence(ServerHost),
|
2016-11-12 11:27:15 +01:00
|
|
|
case match_rule(ServerHost, Host, Perms, roster) of
|
|
|
|
allow ->
|
|
|
|
roster;
|
|
|
|
deny ->
|
|
|
|
case match_rule(ServerHost, Host, Perms, managed_entity) of
|
|
|
|
allow -> managed_entity;
|
|
|
|
deny -> none
|
|
|
|
end
|
2016-09-07 14:34:31 +02:00
|
|
|
end.
|
|
|
|
|
2024-09-26 12:07:52 +02:00
|
|
|
-ifdef(OTP_BELOW_26).
|
|
|
|
-dialyzer({no_contracts, match_rule/4}).
|
|
|
|
-endif.
|
|
|
|
|
2019-07-08 08:46:50 +02:00
|
|
|
-spec match_rule(binary(), binary(), roster_permissions(), roster_permission()) -> allow | deny;
|
2024-09-26 12:07:52 +02:00
|
|
|
(binary(), binary(), iq_permissions(), iq_permission()) -> allow | deny;
|
2019-07-08 08:46:50 +02:00
|
|
|
(binary(), binary(), presence_permissions(), presence_permission()) -> allow | deny;
|
|
|
|
(binary(), binary(), message_permissions(), message_permission()) -> allow | deny.
|
2016-11-12 11:27:15 +01:00
|
|
|
match_rule(ServerHost, Host, Perms, Type) ->
|
|
|
|
Access = proplists:get_value(Type, Perms, none),
|
|
|
|
acl:match_rule(ServerHost, Access, jid:make(Host)).
|