mirror of
https://github.com/processone/ejabberd
synced 2025-10-06 03:50:15 +02:00
Merge branch 'master' into xml-ng
Conflicts: src/adhoc.erl src/cyrsasl_oauth.erl src/ejabberd_c2s.erl src/ejabberd_config.erl src/ejabberd_service.erl src/gen_mod.erl src/mod_admin_extra.erl src/mod_announce.erl src/mod_carboncopy.erl src/mod_client_state.erl src/mod_configure.erl src/mod_echo.erl src/mod_mam.erl src/mod_muc.erl src/mod_muc_room.erl src/mod_offline.erl src/mod_pubsub.erl src/mod_stats.erl src/node_flat_sql.erl src/randoms.erl
This commit is contained in:
commit
78a44e0176
124 changed files with 7141 additions and 1335 deletions
348
src/mod_privilege.erl
Normal file
348
src/mod_privilege.erl
Normal file
|
@ -0,0 +1,348 @@
|
|||
%%%-------------------------------------------------------------------
|
||||
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%% @copyright (C) 2016, Evgeny Khramtsov
|
||||
%%% @doc
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 11 Nov 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(mod_privilege).
|
||||
|
||||
-behaviour(gen_server).
|
||||
-behaviour(gen_mod).
|
||||
|
||||
%% API
|
||||
-export([start_link/2]).
|
||||
-export([start/2, stop/1, mod_opt_type/1, depends/2]).
|
||||
%% 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,
|
||||
roster_access/2, process_message/3,
|
||||
process_presence_out/4, process_presence_in/5]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("xmpp.hrl").
|
||||
|
||||
-record(state, {server_host = <<"">> :: binary(),
|
||||
permissions = dict:new() :: ?TDICT}).
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
start_link(Host, Opts) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?MODULE),
|
||||
gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
|
||||
|
||||
start(Host, Opts) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?MODULE),
|
||||
PingSpec = {Proc, {?MODULE, start_link, [Host, Opts]},
|
||||
transient, 2000, worker, [?MODULE]},
|
||||
supervisor:start_child(ejabberd_sup, PingSpec).
|
||||
|
||||
stop(Host) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?MODULE),
|
||||
gen_server:call(Proc, stop),
|
||||
supervisor:delete_child(ejabberd_sup, Proc).
|
||||
|
||||
mod_opt_type(roster) -> v_roster();
|
||||
mod_opt_type(message) -> v_message();
|
||||
mod_opt_type(presence) -> v_presence();
|
||||
mod_opt_type(_) ->
|
||||
[roster, message, presence].
|
||||
|
||||
depends(_, _) ->
|
||||
[].
|
||||
|
||||
-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})
|
||||
end, ?MYHOSTS).
|
||||
|
||||
-spec component_disconnected(binary(), binary()) -> ok.
|
||||
component_disconnected(Host, _Reason) ->
|
||||
lists:foreach(
|
||||
fun(ServerHost) ->
|
||||
Proc = gen_mod:get_module_proc(ServerHost, ?MODULE),
|
||||
gen_server:cast(Proc, {component_disconnected, Host})
|
||||
end, ?MYHOSTS).
|
||||
|
||||
-spec process_message(jid(), jid(), stanza()) -> stop | ok.
|
||||
process_message(#jid{luser = <<"">>, lresource = <<"">>} = From,
|
||||
#jid{lresource = <<"">>} = To,
|
||||
#message{lang = Lang, type = T} = Msg) when T /= error ->
|
||||
Host = From#jid.lserver,
|
||||
ServerHost = To#jid.lserver,
|
||||
Permissions = get_permissions(ServerHost),
|
||||
case dict:find(Host, Permissions) of
|
||||
{ok, Access} ->
|
||||
case proplists:get_value(message, Access, none) of
|
||||
outgoing ->
|
||||
forward_message(From, To, Msg);
|
||||
none ->
|
||||
Txt = <<"Insufficient privilege">>,
|
||||
Err = xmpp:err_forbidden(Txt, Lang),
|
||||
ejabberd_router:route_error(To, From, Msg, Err)
|
||||
end,
|
||||
stop;
|
||||
error ->
|
||||
%% Component is disconnected
|
||||
ok
|
||||
end;
|
||||
process_message(_From, _To, _Stanza) ->
|
||||
ok.
|
||||
|
||||
-spec roster_access(boolean(), iq()) -> boolean().
|
||||
roster_access(true, _) ->
|
||||
true;
|
||||
roster_access(false, #iq{from = From, to = To, type = Type}) ->
|
||||
Host = From#jid.lserver,
|
||||
ServerHost = To#jid.lserver,
|
||||
Permissions = get_permissions(ServerHost),
|
||||
case dict:find(Host, Permissions) of
|
||||
{ok, Access} ->
|
||||
Permission = proplists:get_value(roster, Access, none),
|
||||
(Permission == both)
|
||||
orelse (Permission == get andalso Type == get)
|
||||
orelse (Permission == set andalso Type == set);
|
||||
error ->
|
||||
%% Component is disconnected
|
||||
false
|
||||
end.
|
||||
|
||||
-spec process_presence_out(stanza(), ejabberd_c2s:state(), jid(), jid()) -> stanza().
|
||||
process_presence_out(#presence{type = Type} = Pres, _C2SState,
|
||||
#jid{luser = LUser, lserver = LServer} = From,
|
||||
#jid{luser = LUser, lserver = LServer, lresource = <<"">>})
|
||||
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(
|
||||
From, To, xmpp:set_from_to(Pres, From, To));
|
||||
true ->
|
||||
ok
|
||||
end
|
||||
end, dict:to_list(Permissions)),
|
||||
Pres;
|
||||
process_presence_out(Acc, _, _, _) ->
|
||||
Acc.
|
||||
|
||||
-spec process_presence_in(stanza(), ejabberd_c2s:state(),
|
||||
jid(), jid(), jid()) -> stanza().
|
||||
process_presence_in(#presence{type = Type} = Pres, _C2SState, _,
|
||||
#jid{luser = U, lserver = S} = From,
|
||||
#jid{luser = LUser, lserver = LServer})
|
||||
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(
|
||||
From, To, xmpp:set_from_to(Pres, From, To));
|
||||
true ->
|
||||
ok
|
||||
end;
|
||||
true ->
|
||||
ok
|
||||
end
|
||||
end, dict:to_list(Permissions)),
|
||||
Pres;
|
||||
process_presence_in(Acc, _, _, _, _) ->
|
||||
Acc.
|
||||
|
||||
%%%===================================================================
|
||||
%%% gen_server callbacks
|
||||
%%%===================================================================
|
||||
init([Host, _Opts]) ->
|
||||
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),
|
||||
{ok, #state{server_host = Host}}.
|
||||
|
||||
handle_call(get_permissions, _From, State) ->
|
||||
{reply, {ok, State#state.permissions}, State};
|
||||
handle_call(_Request, _From, State) ->
|
||||
Reply = ok,
|
||||
{reply, Reply, State}.
|
||||
|
||||
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),
|
||||
PresencePerm = get_presence_permission(ServerHost, Host),
|
||||
MessagePerm = get_message_permission(ServerHost, Host),
|
||||
if RosterPerm /= none, PresencePerm /= none, MessagePerm /= none ->
|
||||
Priv = #privilege{perms = [#privilege_perm{access = message,
|
||||
type = MessagePerm},
|
||||
#privilege_perm{access = roster,
|
||||
type = RosterPerm},
|
||||
#privilege_perm{access = presence,
|
||||
type = PresencePerm}]},
|
||||
?INFO_MSG("Granting permissions to external "
|
||||
"component '~s': roster = ~s, presence = ~s, "
|
||||
"message = ~s",
|
||||
[Host, RosterPerm, PresencePerm, MessagePerm]),
|
||||
Msg = #message{from = From, to = To, sub_els = [Priv]},
|
||||
ejabberd_router:route(From, To, Msg),
|
||||
Permissions = dict:store(Host, [{roster, RosterPerm},
|
||||
{presence, PresencePerm},
|
||||
{message, MessagePerm}],
|
||||
State#state.permissions),
|
||||
{noreply, State#state{permissions = Permissions}};
|
||||
true ->
|
||||
?INFO_MSG("Granting no permissions to external component '~s'",
|
||||
[Host]),
|
||||
{noreply, State}
|
||||
end;
|
||||
handle_cast({component_disconnected, Host}, State) ->
|
||||
Permissions = dict:erase(Host, State#state.permissions),
|
||||
{noreply, State#state{permissions = Permissions}};
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
terminate(_Reason, State) ->
|
||||
%% Note: we don't remove component_* hooks because they are global
|
||||
%% and might be registered within a module on another virtual host
|
||||
Host = State#state.server_host,
|
||||
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,
|
||||
process_presence_in, 50).
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
get_permissions(ServerHost) ->
|
||||
Proc = gen_mod:get_module_proc(ServerHost, ?MODULE),
|
||||
try gen_server:call(Proc, get_permissions) of
|
||||
{ok, Permissions} ->
|
||||
Permissions
|
||||
catch exit:{noproc, _} ->
|
||||
%% No module is loaded for this virtual host
|
||||
dict:new()
|
||||
end.
|
||||
|
||||
forward_message(From, To, Msg) ->
|
||||
Host = From#jid.lserver,
|
||||
ServerHost = To#jid.lserver,
|
||||
case xmpp:get_subtag(Msg, #privilege{}) of
|
||||
#privilege{forwarded = #forwarded{sub_els = [#message{} = SubEl]}} ->
|
||||
case SubEl#message.from of
|
||||
#jid{lresource = <<"">>, lserver = ServerHost} ->
|
||||
ejabberd_router:route(
|
||||
xmpp:get_from(SubEl), xmpp:get_to(SubEl), SubEl);
|
||||
_ ->
|
||||
Lang = xmpp:get_lang(Msg),
|
||||
Txt = <<"Invalid 'from' attribute">>,
|
||||
Err = xmpp:err_forbidden(Txt, Lang),
|
||||
ejabberd_router:route_error(To, From, Msg, Err)
|
||||
end;
|
||||
_ ->
|
||||
?ERROR_MSG("got invalid forwarded payload from external "
|
||||
"component '~s':~n~s", [Host, xmpp:pp(Msg)]),
|
||||
Lang = xmpp:get_lang(Msg),
|
||||
Txt = <<"Invalid forwarded payload">>,
|
||||
Err = xmpp:err_bad_request(Txt, Lang),
|
||||
ejabberd_router:route_error(To, From, Msg, Err)
|
||||
end.
|
||||
|
||||
get_roster_permission(ServerHost, Host) ->
|
||||
Perms = gen_mod:get_module_opt(ServerHost, ?MODULE, roster,
|
||||
v_roster(), []),
|
||||
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.
|
||||
|
||||
get_message_permission(ServerHost, Host) ->
|
||||
Perms = gen_mod:get_module_opt(ServerHost, ?MODULE, message,
|
||||
v_message(), []),
|
||||
case match_rule(ServerHost, Host, Perms, outgoing) of
|
||||
allow -> outgoing;
|
||||
deny -> none
|
||||
end.
|
||||
|
||||
get_presence_permission(ServerHost, Host) ->
|
||||
Perms = gen_mod:get_module_opt(ServerHost, ?MODULE, presence,
|
||||
v_presence(), []),
|
||||
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
|
||||
end.
|
||||
|
||||
match_rule(ServerHost, Host, Perms, Type) ->
|
||||
Access = proplists:get_value(Type, Perms, none),
|
||||
acl:match_rule(ServerHost, Access, jid:make(Host)).
|
||||
|
||||
v_roster() ->
|
||||
fun(Props) ->
|
||||
lists:map(
|
||||
fun({both, ACL}) -> {both, acl:access_rules_validator(ACL)};
|
||||
({get, ACL}) -> {get, acl:access_rules_validator(ACL)};
|
||||
({set, ACL}) -> {set, acl:access_rules_validator(ACL)}
|
||||
end, Props)
|
||||
end.
|
||||
|
||||
v_message() ->
|
||||
fun(Props) ->
|
||||
lists:map(
|
||||
fun({outgoing, ACL}) -> {outgoing, acl:access_rules_validator(ACL)}
|
||||
end, Props)
|
||||
end.
|
||||
|
||||
v_presence() ->
|
||||
fun(Props) ->
|
||||
lists:map(
|
||||
fun({managed_entity, ACL}) ->
|
||||
{managed_entity, acl:access_rules_validator(ACL)};
|
||||
({roster, ACL}) ->
|
||||
{roster, acl:access_rules_validator(ACL)}
|
||||
end, Props)
|
||||
end.
|
Loading…
Add table
Add a link
Reference in a new issue