aboutsummaryrefslogtreecommitdiff
path: root/src/acl.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/acl.erl')
-rw-r--r--src/acl.erl432
1 files changed, 301 insertions, 131 deletions
diff --git a/src/acl.erl b/src/acl.erl
index 06202c67e..95c9ebbd4 100644
--- a/src/acl.erl
+++ b/src/acl.erl
@@ -29,12 +29,13 @@
-author('alexey@process-one.net').
--export([start/0, to_record/3, add/3, add_list/3,
- add_local/3, add_list_local/3, load_from_config/0,
- match_rule/3, match_access/4, match_acl/3, transform_options/1,
- opt_type/1]).
-
-export([add_access/3, clear/0]).
+-export([start/0, add/3, add_list/3, add_local/3, add_list_local/3,
+ load_from_config/0, match_rule/3,
+ transform_options/1, opt_type/1, acl_rule_matches/3,
+ acl_rule_verify/1, access_matches/3,
+ transform_access_rules_config/1,
+ access_rules_validator/1, shaper_rules_validator/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -92,12 +93,6 @@ start() ->
load_from_config(),
ok.
--spec to_record(binary(), atom(), aclspec()) -> acl().
-
-to_record(Host, ACLName, ACLSpec) ->
- #acl{aclname = {ACLName, Host},
- aclspec = normalize_spec(ACLSpec)}.
-
-spec add(binary(), aclname(), aclspec()) -> ok | {error, any()}.
add(Host, ACLName, ACLSpec) ->
@@ -188,6 +183,10 @@ load_from_config() ->
{acl, Host}, fun(V) -> V end, []),
AccessRules = ejabberd_config:get_option(
{access, Host}, fun(V) -> V end, []),
+ AccessRulesNew = ejabberd_config:get_option(
+ {access_rules, Host}, fun(V) -> V end, []),
+ ShaperRules = ejabberd_config:get_option(
+ {shaper_rules, Host}, fun(V) -> V end, []),
lists:foreach(
fun({ACLName, SpecList}) ->
lists:foreach(
@@ -203,8 +202,19 @@ load_from_config() ->
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, AccessRules)
+ end, AccessRulesNew),
+ lists:foreach(
+ fun({Access, Rules}) ->
+ add_access(Host, Access, Rules)
+ end, ShaperRules)
end, Hosts).
%% Delete all previous set ACLs and Access rules
@@ -225,19 +235,28 @@ nameprep(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} -> {acl, N};
{user, {U, S}} -> {user, {nodeprep(U), nameprep(S)}};
- {user, U} -> {user, nodeprep(U)};
+ {user, U} -> {user, split_user_server(U, fun nodeprep/1, fun nameprep/1)};
{shared_group, {G, H}} -> {shared_group, {b(G), nameprep(H)}};
- {shared_group, G} -> {shared_group, b(G)};
+ {shared_group, G} -> {shared_group, split_user_server(G, fun b/1, fun nameprep/1)};
{user_regexp, {UR, S}} -> {user_regexp, {b(UR), nameprep(S)}};
- {user_regexp, UR} -> {user_regexp, b(UR)};
+ {user_regexp, UR} -> {user_regexp, split_user_server(UR, fun b/1, fun nameprep/1)};
{node_regexp, {UR, SR}} -> {node_regexp, {b(UR), b(SR)}};
{user_glob, {UR, S}} -> {user_glob, {b(UR), nameprep(S)}};
- {user_glob, UR} -> {user_glob, b(UR)};
+ {user_glob, UR} -> {user_glob, split_user_server(UR, fun b/1, fun nameprep/1)};
{node_glob, {UR, SR}} -> {node_glob, {b(UR), b(SR)}};
{server, S} -> {server, nameprep(S)};
{resource, R} -> {resource, resourceprep(R)};
@@ -255,130 +274,196 @@ normalize_spec(Spec) ->
end
end.
--spec match_access(global | binary(), access_name(),
- jid() | ljid() | inet:ip_address(),
- atom()) -> any().
-
-match_access(_Host, all, _JID, _Default) ->
- allow;
-match_access(_Host, none, _JID, _Default) ->
- deny;
-match_access(_Host, {user, UserPattern}, JID, Default) ->
- match_user_spec({user, UserPattern}, JID, Default);
-match_access(Host, AccessRule, JID, _Default) ->
- match_rule(Host, AccessRule, JID).
-
-spec match_rule(global | binary(), access_name(),
jid() | ljid() | inet:ip_address()) -> any().
-match_rule(_Host, all, _JID) ->
- allow;
-match_rule(_Host, none, _JID) ->
- deny;
+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) ->
- GAccess = ets:lookup(access, {Access, global}),
- LAccess = if Host /= global ->
- ets:lookup(access, {Access, Host});
- true ->
- []
- end,
- case GAccess ++ LAccess of
- [] ->
- deny;
- AccessList ->
- Rules = lists:flatmap(
- fun(#access{rules = Rs}) ->
- Rs
- end, AccessList),
- match_acls(Rules, JID, Host)
- end.
-
-match_acls([], _, _Host) -> deny;
-match_acls([{ACL, Access} | ACLs], JID, Host) ->
- case match_acl(ACL, JID, Host) of
- true -> Access;
- _ -> match_acls(ACLs, JID, Host)
- end.
+ access_matches(Access, #{usr => jid:tolower(JID)}, Host).
--spec match_acl(atom(),
- jid() | ljid() | inet:ip_address(),
- binary()) -> boolean().
+-spec acl_rule_verify(aclspec()) -> boolean().
-match_acl(all, _JID, _Host) ->
+acl_rule_verify(all) ->
true;
-match_acl(none, _JID, _Host) ->
- false;
-match_acl(ACL, IP, Host) when tuple_size(IP) == 4;
- tuple_size(IP) == 8 ->
- lists:any(
- fun(#acl{aclspec = {ip, {Net, Mask}}}) ->
- is_ip_match(IP, Net, Mask);
- (_) ->
- false
- end, get_aclspecs(ACL, Host));
-match_acl(ACL, JID, Host) ->
- {User, Server, Resource} = jid:tolower(JID),
- lists:any(
- fun(#acl{aclspec = Spec}) ->
- case Spec of
- all -> true;
- {user, {U, S}} -> U == User andalso S == Server;
- {user, U} ->
- U == User andalso
- lists:member(Server, ?MYHOSTS);
- {server, S} -> S == Server;
- {resource, R} -> R == Resource;
- {shared_group, {G, H}} ->
- Mod = loaded_shared_roster_module(H),
- Mod:is_user_in_group({User, Server}, G, H);
- {shared_group, G} ->
- Mod = loaded_shared_roster_module(Host),
- Mod:is_user_in_group({User, Server}, G, Host);
- {user_regexp, {UR, S}} ->
- S == Server andalso is_regexp_match(User, UR);
- {user_regexp, UR} ->
- lists:member(Server, ?MYHOSTS)
- andalso is_regexp_match(User, UR);
- {server_regexp, SR} ->
- is_regexp_match(Server, SR);
- {resource_regexp, RR} ->
- is_regexp_match(Resource, RR);
- {node_regexp, {UR, SR}} ->
- is_regexp_match(Server, SR) andalso
- is_regexp_match(User, UR);
- {user_glob, {UR, S}} ->
- S == Server andalso is_glob_match(User, UR);
- {user_glob, UR} ->
- lists:member(Server, ?MYHOSTS)
- andalso is_glob_match(User, UR);
- {server_glob, SR} -> is_glob_match(Server, SR);
- {resource_glob, RR} ->
- is_glob_match(Resource, RR);
- {node_glob, {UR, SR}} ->
- is_glob_match(Server, SR) andalso
- is_glob_match(User, UR);
- WrongSpec ->
- ?ERROR_MSG("Wrong ACL expression: ~p~nCheck your "
- "config file and reload it with the override_a"
- "cls option enabled",
- [WrongSpec]),
- false
- end
- end,
- get_aclspecs(ACL, Host)).
+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, iolist_to_binary(io_lib: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]).
-get_aclspecs(ACL, Host) ->
- ets:lookup(acl, {ACL, Host}) ++ ets:lookup(acl, {ACL, global}).
-match_user_spec(Spec, JID, Default) ->
- case do_match_user_spec(Spec, jid:tolower(JID)) of
- true -> Default;
- false -> deny
- end.
+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.
+
+-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),
+ all_acl_rules_matches(RawACLs, Data, Host);
+acl_rule_matches({ip, {Net, Mask}}, #{ip := {IP, _Port}}, _Host) ->
+ is_ip_match(IP, Net, Mask);
+acl_rule_matches({ip, {Net, Mask}}, #{ip := IP}, _Host) ->
+ is_ip_match(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, ?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, ?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, ?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.
+
+-spec access_matches(atom()|list(), any(), global|binary()) -> any().
+access_matches(all, _Data, _Host) ->
+ allow;
+access_matches(none, _Data, _Host) ->
+ deny;
+access_matches(Name, Data, Host) when is_atom(Name) ->
+ GAccess = ets:lookup(access, {Name, global}),
+ LAccess =
+ if Host /= global -> ets:lookup(access, {Name, Host});
+ true -> []
+ end,
+ case GAccess ++ LAccess of
+ [] ->
+ deny;
+ AccessList ->
+ Rules = lists:flatmap(
+ fun(#access{rules = Rs}) ->
+ Rs
+ end, AccessList),
+ access_rules_matches(Rules, Data, Host)
+ end;
+access_matches(Rules, Data, Host) when is_list(Rules) ->
+ access_rules_matches(Rules, Data, Host).
+
+
+-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.
-do_match_user_spec({user, {U, S}}, {User, Server, _Resource}) ->
- U == User andalso S == Server.
+get_aclspecs(ACL, Host) ->
+ ets:lookup(acl, {ACL, Host}) ++ ets:lookup(acl, {ACL, global}).
is_regexp_match(String, RegExp) ->
case ejabberd_regexp:run(String, RegExp) of
@@ -450,6 +535,63 @@ parse_ip_netmask(S) ->
_ -> 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),
+ throw({replace_with, 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),
+ throw({replace_with, 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(
@@ -464,6 +606,18 @@ transform_options(Opts) ->
(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
@@ -477,7 +631,21 @@ transform_options(Opts) ->
[] -> [];
L2 -> [{acl, L2}]
end,
- ACLOpts2 ++ AccessOpts1 ++ Opts3.
+ 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
@@ -508,5 +676,7 @@ 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].
+opt_type(_) -> [access, acl, access_rules, shaper_rules].