24
1
mirror of https://github.com/processone/ejabberd.git synced 2024-06-02 21:17:12 +02:00
xmpp.chapril.org-ejabberd/src/acl.erl
Evgeny Khramtsov 08f3d066b1 Switch more log message to warning level
The commit is supposed to improve logging at loglevel 3, which
is the recommended level for high loaded ejabberd servers
2018-09-19 23:12:14 +03:00

765 lines
26 KiB
Erlang

%%%----------------------------------------------------------------------
%%% File : acl.erl
%%% Author : Alexey Shchepin <alexey@process-one.net>
%%% Purpose : ACL support
%%% Created : 18 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2018 ProcessOne
%%%
%%% 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.
%%%
%%%----------------------------------------------------------------------
-module(acl).
-behaviour(gen_server).
-behaviour(ejabberd_config).
-author('alexey@process-one.net').
-export([add_access/3, clear/0]).
-export([start_link/0, add/3, add_list/3, add_local/3, add_list_local/3,
load_from_config/0, reload_from_config/0, match_rule/3,
any_rules_allowed/3, transform_options/1, opt_type/1,
acl_rule_matches/3, acl_rule_verify/1, access_matches/3,
transform_access_rules_config/1,
parse_ip_netmask/1, ip_matches_mask/3,
access_rules_validator/1, shaper_rules_validator/1,
normalize_spec/1, resolve_access/2]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-include("logger.hrl").
-include("jid.hrl").
-record(acl, {aclname, aclspec}).
-record(access, {name :: aclname(),
rules = [] :: [access_rule()]}).
-record(state, {}).
-type regexp() :: binary().
-type iprange() :: {inet:ip_address(), integer()} | binary().
-type glob() :: binary().
-type access_name() :: atom().
-type access_rule() :: {atom(), any()}.
-type host() :: binary().
-type aclname() :: {atom(), binary() | global}.
-type aclspec() :: all | none |
{user, {binary(), host()} | binary()} |
{server, binary()} |
{resource, binary()} |
{user_regexp, {regexp(), host()} | regexp()} |
{shared_group, {binary(), host()} | binary()} |
{user_regexp, {regexp(), host()} | regexp()} |
{server_regexp, regexp()} |
{resource_regexp, regexp()} |
{node_regexp, {regexp(), regexp()}} |
{user_glob, {glob(), host()} | glob()} |
{server_glob, glob()} |
{resource_glob, glob()} |
{ip, iprange()} |
{node_glob, {glob(), glob()}}.
-type acl() :: #acl{aclname :: aclname(),
aclspec :: aclspec()}.
-export_type([acl/0]).
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
init([]) ->
ejabberd_mnesia:create(?MODULE, acl,
[{ram_copies, [node()]}, {type, bag},
{local_content, true},
{attributes, record_info(fields, acl)}]),
ejabberd_mnesia:create(?MODULE, access,
[{ram_copies, [node()]},
{local_content, true},
{attributes, record_info(fields, access)}]),
ejabberd_hooks:add(config_reloaded, ?MODULE, reload_from_config, 20),
load_from_config(),
{ok, #state{}}.
handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
-spec add(binary(), aclname(), aclspec()) -> ok | {error, any()}.
add(Host, ACLName, ACLSpec) ->
{ResL, BadNodes} = ejabberd_cluster:multicall(
?MODULE, add_local,
[Host, ACLName, ACLSpec]),
case lists:keyfind(aborted, 1, ResL) of
false when BadNodes == [] ->
ok;
false ->
{error, {failed_nodes, BadNodes}};
Err ->
{error, Err}
end.
add_local(Host, ACLName, ACLSpec) ->
F = fun () ->
mnesia:write(#acl{aclname = {ACLName, Host},
aclspec = normalize_spec(ACLSpec)})
end,
case mnesia:transaction(F) of
{atomic, ok} ->
ok;
Err ->
Err
end.
-spec add_list(binary(), [acl()], boolean()) -> ok | {error, any()}.
add_list(Host, ACLs, Clear) ->
{ResL, BadNodes} = ejabberd_cluster:multicall(
?MODULE, add_list_local,
[Host, ACLs, Clear]),
case lists:keyfind(aborted, 1, ResL) of
false when BadNodes == [] ->
ok;
false ->
{error, {failed_nodes, BadNodes}};
Err ->
{error, Err}
end.
add_list_local(Host, ACLs, Clear) ->
F = fun () ->
if Clear ->
Ks = mnesia:select(acl,
[{{acl, {'$1', Host}, '$2'}, [],
['$1']}]),
lists:foreach(fun (K) -> mnesia:delete({acl, {K, Host}})
end,
Ks);
true -> ok
end,
lists:foreach(fun (ACL) ->
case ACL of
#acl{aclname = ACLName,
aclspec = ACLSpec} ->
mnesia:write(#acl{aclname =
{ACLName,
Host},
aclspec =
normalize_spec(ACLSpec)})
end
end,
ACLs)
end,
mnesia:transaction(F).
-spec add_access(binary() | global,
access_name(), [access_rule()]) -> ok | {error, any()}.
add_access(Host, Access, Rules) ->
Obj = #access{name = {Access, Host}, rules = Rules},
case mnesia:transaction(fun() -> mnesia:write(Obj) end) of
{atomic, ok} ->
ok;
Err ->
{error, Err}
end.
-spec load_from_config() -> ok.
load_from_config() ->
Hosts = [global|ejabberd_config:get_myhosts()],
lists:foreach(
fun(Host) ->
ACLs = ejabberd_config:get_option(
{acl, Host}, []),
AccessRules = ejabberd_config:get_option(
{access, Host}, []),
AccessRulesNew = ejabberd_config:get_option(
{access_rules, Host}, []),
ShaperRules = ejabberd_config:get_option(
{shaper_rules, Host}, []),
lists:foreach(
fun({ACLName, SpecList}) ->
lists:foreach(
fun({ACLType, ACLSpecs}) when is_list(ACLSpecs) ->
lists:foreach(
fun(ACLSpec) ->
add(Host, ACLName,
{ACLType, ACLSpec})
end, lists:flatten(ACLSpecs));
({ACLType, ACLSpecs}) ->
add(Host, ACLName, {ACLType, ACLSpecs})
end, lists:flatten(SpecList))
end, ACLs),
lists:foreach(
fun({Access, Rules}) ->
NRules = lists:map(fun({ACL, Type}) ->
{Type, [{acl, ACL}]}
end, Rules),
add_access(Host, Access, NRules ++ [{deny, [all]}])
end, AccessRules),
lists:foreach(
fun({Access, Rules}) ->
add_access(Host, Access, Rules)
end, AccessRulesNew),
lists:foreach(
fun({Access, Rules}) ->
add_access(Host, Access, Rules)
end, ShaperRules)
end, Hosts).
-spec reload_from_config() -> ok.
reload_from_config() ->
clear(),
load_from_config().
%% Delete all previous set ACLs and Access rules
clear() ->
mnesia:clear_table(acl),
mnesia:clear_table(access),
ok.
b(S) ->
iolist_to_binary(S).
nodeprep(S) ->
jid:nodeprep(b(S)).
nameprep(S) ->
jid:nameprep(b(S)).
resourceprep(S) ->
jid:resourceprep(b(S)).
split_user_server(Str, NormFunUsr, NormFunSrv) ->
case binary:split(Str, <<"@">>) of
[U, S] ->
{NormFunUsr(U), NormFunSrv(S)};
_ ->
NormFunUsr(Str)
end.
normalize_spec(Spec) ->
case Spec of
all -> all;
none -> none;
{acl, N} when is_atom(N) ->
{acl, N};
{user, {U, S}} when is_binary(U), is_binary(S) ->
{user, {nodeprep(U), nameprep(S)}};
{user, U} when is_binary(U) ->
{user, split_user_server(U, fun nodeprep/1, fun nameprep/1)};
{shared_group, {G, H}} when is_binary(G), is_binary(H) ->
{shared_group, {b(G), nameprep(H)}};
{shared_group, G} when is_binary(G) ->
{shared_group, split_user_server(G, fun b/1, fun nameprep/1)};
{user_regexp, {UR, S}} when is_binary(UR), is_binary(S) ->
{user_regexp, {b(UR), nameprep(S)}};
{user_regexp, UR} when is_binary(UR) ->
{user_regexp, split_user_server(UR, fun b/1, fun nameprep/1)};
{node_regexp, {UR, SR}} when is_binary(UR), is_binary(SR) ->
{node_regexp, {b(UR), b(SR)}};
{user_glob, {UR, S}} when is_binary(UR), is_binary(S) ->
{user_glob, {b(UR), nameprep(S)}};
{user_glob, UR} when is_binary(UR) ->
{user_glob, split_user_server(UR, fun b/1, fun nameprep/1)};
{node_glob, {UR, SR}} when is_binary(UR), is_binary(SR) ->
{node_glob, {b(UR), b(SR)}};
{server, S} when is_binary(S) ->
{server, nameprep(S)};
{resource, R} when is_binary(R) ->
{resource, resourceprep(R)};
{server_regexp, SR} when is_binary(SR) ->
{server_regexp, b(SR)};
{resource_regexp, R} when is_binary(R) ->
{resource_regexp, b(R)};
{server_glob, S} when is_binary(S) ->
{server_glob, b(S)};
{resource_glob, R} when is_binary(R) ->
{resource_glob, b(R)};
{ip, {Net, Mask}} when is_binary(Net), is_integer(Mask) ->
{ip, {Net, Mask}};
{ip, S} ->
case parse_ip_netmask(b(S)) of
{ok, Net, Mask} ->
{ip, {Net, Mask}};
error ->
?WARNING_MSG("Invalid network address: ~p", [S]),
none
end;
BadVal ->
throw({<<"Invalid acl value">>, BadVal})
end.
-spec any_rules_allowed(global | binary(), [access_name()],
jid() | ljid() | inet:ip_address()) -> boolean().
any_rules_allowed(Host, Access, Entity) ->
lists:any(fun (Rule) ->
allow == acl:match_rule(Host, Rule, Entity)
end,
Access).
-spec match_rule(global | binary(), access_name(),
jid() | ljid() | inet:ip_address()) -> any().
match_rule(Host, Access, IP) when tuple_size(IP) == 4;
tuple_size(IP) == 8 ->
access_matches(Access, #{ip => IP}, Host);
match_rule(Host, Access, JID) ->
access_matches(Access, #{usr => jid:tolower(JID)}, Host).
-spec acl_rule_verify(aclspec()) -> boolean().
acl_rule_verify(all) ->
true;
acl_rule_verify(none) ->
true;
acl_rule_verify({ip, {{A,B,C,D}, Mask}})
when is_integer(A), is_integer(B), is_integer(C), is_integer(D),
A >= 0, A =< 255, B >= 0, B =< 255, C >= 0, C =< 255, D >= 0, D =< 255,
is_integer(Mask), Mask >= 0, Mask =< 32 ->
true;
acl_rule_verify({ip, {{A,B,C,D,E,F,G,H}, Mask}}) when
is_integer(A), is_integer(B), is_integer(C), is_integer(D),
is_integer(E), is_integer(F), is_integer(G), is_integer(H),
A >= 0, A =< 65535, B >= 0, B =< 65535, C >= 0, C =< 65535, D >= 0, D =< 65535,
E >= 0, E =< 65535, F >= 0, F =< 65535, G >= 0, G =< 65535, H >= 0, H =< 65535,
is_integer(Mask), Mask >= 0, Mask =< 64 ->
true;
acl_rule_verify({user, {U, S}}) when is_binary(U), is_binary(S) ->
true;
acl_rule_verify({user, U}) when is_binary(U) ->
true;
acl_rule_verify({server, S}) when is_binary(S) ->
true;
acl_rule_verify({resource, R}) when is_binary(R) ->
true;
acl_rule_verify({shared_group, {G, H}}) when is_binary(G), is_binary(H) ->
true;
acl_rule_verify({shared_group, G}) when is_binary(G) ->
true;
acl_rule_verify({user_regexp, {UR, S}}) when is_binary(UR), is_binary(S) ->
true;
acl_rule_verify({user_regexp, UR}) when is_binary(UR) ->
true;
acl_rule_verify({server_regexp, SR}) when is_binary(SR) ->
true;
acl_rule_verify({resource_regexp, RR}) when is_binary(RR) ->
true;
acl_rule_verify({node_regexp, {UR, SR}}) when is_binary(UR), is_binary(SR) ->
true;
acl_rule_verify({user_glob, {UR, S}}) when is_binary(UR), is_binary(S) ->
true;
acl_rule_verify({user_glob, UR}) when is_binary(UR) ->
true;
acl_rule_verify({server_glob, SR}) when is_binary(SR) ->
true;
acl_rule_verify({resource_glob, RR}) when is_binary(RR) ->
true;
acl_rule_verify({node_glob, {UR, SR}}) when is_binary(UR), is_binary(SR) ->
true;
acl_rule_verify(_Spec) ->
false.
invalid_syntax(Msg, Data) ->
throw({invalid_syntax, (str:format(Msg, Data))}).
acl_rules_verify([{acl, Name} | Rest], true) when is_atom(Name) ->
acl_rules_verify(Rest, true);
acl_rules_verify([{acl, Name} = Rule | _Rest], false) when is_atom(Name) ->
invalid_syntax(<<"Using acl: rules not allowed: ~p">>, [Rule]);
acl_rules_verify([Rule | Rest], AllowAcl) ->
case acl_rule_verify(Rule) of
false ->
invalid_syntax(<<"Invalid rule: ~p">>, [Rule]);
true ->
acl_rules_verify(Rest, AllowAcl)
end;
acl_rules_verify([], _AllowAcl) ->
true;
acl_rules_verify(Rules, _AllowAcl) ->
invalid_syntax(<<"Not a acl rules list: ~p">>, [Rules]).
all_acl_rules_matches([], _Data, _Host) ->
false;
all_acl_rules_matches(Rules, Data, Host) ->
all_acl_rules_matches2(Rules, Data, Host).
all_acl_rules_matches2([Rule | Tail], Data, Host) ->
case acl_rule_matches(Rule, Data, Host) of
true ->
all_acl_rules_matches2(Tail, Data, Host);
false ->
false
end;
all_acl_rules_matches2([], _Data, _Host) ->
true.
any_acl_rules_matches([], _Data, _Host) ->
false;
any_acl_rules_matches([Rule|Tail], Data, Host) ->
case acl_rule_matches(Rule, Data, Host) of
true ->
true;
false ->
any_acl_rules_matches(Tail, Data, Host)
end.
-spec acl_rule_matches(aclspec(), any(), global|binary()) -> boolean().
acl_rule_matches(all, _Data, _Host) ->
true;
acl_rule_matches({acl, all}, _Data, _Host) ->
true;
acl_rule_matches({acl, Name}, Data, Host) ->
ACLs = get_aclspecs(Name, Host),
RawACLs = lists:map(fun(#acl{aclspec = R}) -> R end, ACLs),
any_acl_rules_matches(RawACLs, Data, Host);
acl_rule_matches({ip, {Net, Mask}}, #{ip := {IP, _Port}}, _Host) ->
ip_matches_mask(IP, Net, Mask);
acl_rule_matches({ip, {Net, Mask}}, #{ip := IP}, _Host) ->
ip_matches_mask(IP, Net, Mask);
acl_rule_matches({user, {U, S}}, #{usr := {U, S, _}}, _Host) ->
true;
acl_rule_matches({user, U}, #{usr := {U, S, _}}, _Host) ->
lists:member(S, ejabberd_config:get_myhosts());
acl_rule_matches({server, S}, #{usr := {_, S, _}}, _Host) ->
true;
acl_rule_matches({resource, R}, #{usr := {_, _, R}}, _Host) ->
true;
acl_rule_matches({shared_group, {G, H}}, #{usr := {U, S, _}}, _Host) ->
Mod = loaded_shared_roster_module(H),
Mod:is_user_in_group({U, S}, G, H);
acl_rule_matches({shared_group, G}, #{usr := {U, S, _}}, Host) ->
Mod = loaded_shared_roster_module(Host),
Mod:is_user_in_group({U, S}, G, Host);
acl_rule_matches({user_regexp, {UR, S}}, #{usr := {U, S, _}}, _Host) ->
is_regexp_match(U, UR);
acl_rule_matches({user_regexp, UR}, #{usr := {U, S, _}}, _Host) ->
lists:member(S, ejabberd_config:get_myhosts()) andalso is_regexp_match(U, UR);
acl_rule_matches({server_regexp, SR}, #{usr := {_, S, _}}, _Host) ->
is_regexp_match(S, SR);
acl_rule_matches({resource_regexp, RR}, #{usr := {_, _, R}}, _Host) ->
is_regexp_match(R, RR);
acl_rule_matches({node_regexp, {UR, SR}}, #{usr := {U, S, _}}, _Host) ->
is_regexp_match(U, UR) andalso is_regexp_match(S, SR);
acl_rule_matches({user_glob, {UR, S}}, #{usr := {U, S, _}}, _Host) ->
is_glob_match(U, UR);
acl_rule_matches({user_glob, UR}, #{usr := {U, S, _}}, _Host) ->
lists:member(S, ejabberd_config:get_myhosts()) andalso is_glob_match(U, UR);
acl_rule_matches({server_glob, SR}, #{usr := {_, S, _}}, _Host) ->
is_glob_match(S, SR);
acl_rule_matches({resource_glob, RR}, #{usr := {_, _, R}}, _Host) ->
is_glob_match(R, RR);
acl_rule_matches({node_glob, {UR, SR}}, #{usr := {U, S, _}}, _Host) ->
is_glob_match(U, UR) andalso is_glob_match(S, SR);
acl_rule_matches(_ACL, _Data, _Host) ->
false.
resolve_access(all, _Host) ->
all;
resolve_access(none, _Host) ->
none;
resolve_access(Name, Host) when is_atom(Name) ->
GAccess = mnesia:dirty_read(access, {Name, global}),
LAccess =
if Host /= global -> mnesia:dirty_read(access, {Name, Host});
true -> []
end,
case GAccess ++ LAccess of
[] ->
[];
AccessList ->
lists:flatmap(
fun(#access{rules = Rs}) ->
Rs
end, AccessList)
end;
resolve_access(Rules, _Host) when is_list(Rules) ->
Rules.
-spec access_matches(atom()|list(), any(), global|binary()) -> allow|deny|atom()|integer().
access_matches(Rules, Data, Host) ->
case resolve_access(Rules, Host) of
all -> allow;
none -> deny;
RRules -> access_rules_matches(RRules, Data, Host)
end.
-spec access_rules_matches(list(), any(), global|binary()) -> any().
access_rules_matches(AR, Data, Host) ->
access_rules_matches(AR, Data, Host, deny).
access_rules_matches([{Type, Acls} | Rest], Data, Host, Default) ->
case all_acl_rules_matches(Acls, Data, Host) of
false ->
access_rules_matches(Rest, Data, Host, Default);
true ->
Type
end;
access_rules_matches([], _Data, _Host, Default) ->
Default.
get_aclspecs(ACL, Host) ->
mnesia:dirty_read(acl, {ACL, Host}) ++ mnesia:dirty_read(acl, {ACL, global}).
is_regexp_match(String, RegExp) ->
case ejabberd_regexp:run(String, RegExp) of
nomatch -> false;
match -> true;
{error, ErrDesc} ->
?ERROR_MSG("Wrong regexp ~p in ACL: ~p",
[RegExp, ErrDesc]),
false
end.
is_glob_match(String, Glob) ->
is_regexp_match(String,
ejabberd_regexp:sh_to_awk(Glob)).
ip_matches_mask({_, _, _, _} = IP, {_, _, _, _} = Net, Mask) ->
IPInt = ip_to_integer(IP),
NetInt = ip_to_integer(Net),
M = bnot (1 bsl (32 - Mask) - 1),
IPInt band M =:= NetInt band M;
ip_matches_mask({_, _, _, _, _, _, _, _} = IP,
{_, _, _, _, _, _, _, _} = Net, Mask) ->
IPInt = ip_to_integer(IP),
NetInt = ip_to_integer(Net),
M = bnot (1 bsl (128 - Mask) - 1),
IPInt band M =:= NetInt band M;
ip_matches_mask({_, _, _, _} = IP,
{0, 0, 0, 0, 0, 16#FFFF, _, _} = Net, Mask) ->
IPInt = ip_to_integer({0, 0, 0, 0, 0, 16#FFFF, 0, 0}) + ip_to_integer(IP),
NetInt = ip_to_integer(Net),
M = bnot (1 bsl (128 - Mask) - 1),
IPInt band M =:= NetInt band M;
ip_matches_mask({0, 0, 0, 0, 0, 16#FFFF, _, _} = IP,
{_, _, _, _} = Net, Mask) ->
IPInt = ip_to_integer(IP) - ip_to_integer({0, 0, 0, 0, 0, 16#FFFF, 0, 0}),
NetInt = ip_to_integer(Net),
M = bnot (1 bsl (32 - Mask) - 1),
IPInt band M =:= NetInt band M;
ip_matches_mask(_, _, _) ->
false.
ip_to_integer({IP1, IP2, IP3, IP4}) ->
IP1 bsl 8 bor IP2 bsl 8 bor IP3 bsl 8 bor IP4;
ip_to_integer({IP1, IP2, IP3, IP4, IP5, IP6, IP7,
IP8}) ->
IP1 bsl 16 bor IP2 bsl 16 bor IP3 bsl 16 bor IP4 bsl 16
bor IP5
bsl 16
bor IP6
bsl 16
bor IP7
bsl 16
bor IP8.
loaded_shared_roster_module(Host) ->
case gen_mod:is_loaded(Host, mod_shared_roster_ldap) of
true -> mod_shared_roster_ldap;
false -> mod_shared_roster
end.
parse_ip_netmask(S) ->
case str:tokens(S, <<"/">>) of
[IPStr] ->
case inet_parse:address(binary_to_list(IPStr)) of
{ok, {_, _, _, _} = IP} -> {ok, IP, 32};
{ok, {_, _, _, _, _, _, _, _} = IP} -> {ok, IP, 128};
_ -> error
end;
[IPStr, MaskStr] ->
case catch binary_to_integer(MaskStr) of
Mask when is_integer(Mask), Mask >= 0 ->
case inet_parse:address(binary_to_list(IPStr)) of
{ok, {_, _, _, _} = IP} when Mask =< 32 ->
{ok, IP, Mask};
{ok, {_, _, _, _, _, _, _, _} = IP} when Mask =< 128 ->
{ok, IP, Mask};
_ -> error
end;
_ -> error
end;
_ -> error
end.
transform_access_rules_config(Config) when is_list(Config) ->
lists:map(fun transform_access_rules_config2/1, lists:flatten(Config));
transform_access_rules_config(Config) ->
transform_access_rules_config([Config]).
transform_access_rules_config2(Type) when is_integer(Type); is_atom(Type) ->
{Type, [all]};
transform_access_rules_config2({Type, ACL}) when is_atom(ACL) ->
{Type, [{acl, ACL}]};
transform_access_rules_config2({Res, Rules}) when is_list(Rules) ->
T = lists:map(fun({Type, Args}) when is_list(Args) ->
normalize_spec({Type, hd(lists:flatten(Args))});
(V) -> normalize_spec(V)
end, lists:flatten(Rules)),
{Res, T};
transform_access_rules_config2({Res, Rule}) ->
{Res, [Rule]}.
access_rules_validator(Name) when is_atom(Name) ->
Name;
access_rules_validator(Rules0) ->
Rules = transform_access_rules_config(Rules0),
access_shaper_rules_validator(Rules, fun(allow) -> true;
(deny) -> true;
(_) -> false
end),
Rules.
shaper_rules_validator(Name) when is_atom(Name) ->
Name;
shaper_rules_validator(Rules0) ->
Rules = transform_access_rules_config(Rules0),
access_shaper_rules_validator(Rules, fun(V) when is_atom(V) -> true;
(V2) when is_integer(V2) -> true;
(_) -> false
end),
Rules.
access_shaper_rules_validator([{Type, Acls} = Rule | Rest], RuleTypeCheck) ->
case RuleTypeCheck(Type) of
true ->
case acl_rules_verify(Acls, true) of
true ->
access_shaper_rules_validator(Rest, RuleTypeCheck);
Err ->
Err
end;
false ->
invalid_syntax(<<"Invalid rule type: ~p in rule ~p">>, [Type, Rule])
end;
access_shaper_rules_validator([], _RuleTypeCheck) ->
true;
access_shaper_rules_validator(Value, _RuleTypeCheck) ->
invalid_syntax(<<"Not a rule definition: ~p">>, [Value]).
transform_options(Opts) ->
Opts1 = lists:foldl(fun transform_options/2, [], Opts),
{ACLOpts, Opts2} = lists:mapfoldl(
fun({acl, Os}, Acc) ->
{Os, Acc};
(O, Acc) ->
{[], [O|Acc]}
end, [], Opts1),
{AccessOpts, Opts3} = lists:mapfoldl(
fun({access, Os}, Acc) ->
{Os, Acc};
(O, Acc) ->
{[], [O|Acc]}
end, [], Opts2),
{NewAccessOpts, Opts4} = lists:mapfoldl(
fun({access_rules, Os}, Acc) ->
{Os, Acc};
(O, Acc) ->
{[], [O|Acc]}
end, [], Opts3),
{ShaperOpts, Opts5} = lists:mapfoldl(
fun({shaper_rules, Os}, Acc) ->
{Os, Acc};
(O, Acc) ->
{[], [O|Acc]}
end, [], Opts4),
ACLOpts1 = ejabberd_config:collect_options(lists:flatten(ACLOpts)),
AccessOpts1 = case ejabberd_config:collect_options(
lists:flatten(AccessOpts)) of
[] -> [];
L1 -> [{access, L1}]
end,
ACLOpts2 = case lists:map(
fun({ACLName, Os}) ->
{ACLName, ejabberd_config:collect_options(Os)}
end, ACLOpts1) of
[] -> [];
L2 -> [{acl, L2}]
end,
NewAccessOpts1 = case lists:map(
fun({NAName, Os}) ->
{NAName, transform_access_rules_config(Os)}
end, lists:flatten(NewAccessOpts)) of
[] -> [];
L3 -> [{access_rules, L3}]
end,
ShaperOpts1 = case lists:map(
fun({SName, Ss}) ->
{SName, transform_access_rules_config(Ss)}
end, lists:flatten(ShaperOpts)) of
[] -> [];
L4 -> [{shaper_rules, L4}]
end,
ACLOpts2 ++ AccessOpts1 ++ NewAccessOpts1 ++ ShaperOpts1 ++ Opts5.
transform_options({acl, Name, Type}, Opts) ->
T = case Type of
all -> all;
none -> none;
{user, U} -> {user, [b(U)]};
{user, U, S} -> {user, [[{b(U), b(S)}]]};
{shared_group, G} -> {shared_group, [b(G)]};
{shared_group, G, H} -> {shared_group, [[{b(G), b(H)}]]};
{user_regexp, UR} -> {user_regexp, [b(UR)]};
{user_regexp, UR, S} -> {user_regexp, [[{b(UR), b(S)}]]};
{node_regexp, UR, SR} -> {node_regexp, [[{b(UR), b(SR)}]]};
{user_glob, UR} -> {user_glob, [b(UR)]};
{user_glob, UR, S} -> {user_glob, [[{b(UR), b(S)}]]};
{node_glob, UR, SR} -> {node_glob, [[{b(UR), b(SR)}]]};
{server, S} -> {server, [b(S)]};
{resource, R} -> {resource, [b(R)]};
{server_regexp, SR} -> {server_regexp, [b(SR)]};
{server_glob, S} -> {server_glob, [b(S)]};
{ip, S} -> {ip, [b(S)]};
{resource_glob, R} -> {resource_glob, [b(R)]};
{resource_regexp, R} -> {resource_regexp, [b(R)]}
end,
[{acl, [{Name, [T]}]}|Opts];
transform_options({access, Name, Rules}, Opts) ->
NewRules = [{ACL, Action} || {Action, ACL} <- Rules],
[{access, [{Name, NewRules}]}|Opts];
transform_options(Opt, Opts) ->
[Opt|Opts].
opt_type(access) -> fun (V) -> V end;
opt_type(access_rules) -> fun (V) -> V end;
opt_type(shaper_rules) -> fun (V) -> V end;
opt_type(acl) -> fun (V) -> V end;
opt_type(_) -> [access, acl, access_rules, shaper_rules].