]> granicus.if.org Git - ejabberd/commitdiff
New ACL infrastructure
authorPaweł Chmielowski <pchmielowski@process-one.net>
Wed, 25 May 2016 07:19:46 +0000 (09:19 +0200)
committerPaweł Chmielowski <pchmielowski@process-one.net>
Thu, 26 May 2016 09:08:53 +0000 (11:08 +0200)
src/acl.erl

index 06202c67e1af17f908c945f15706cf63648c64be..ff7872f336e8ec846898942be648d10dc4a74c84 100644 (file)
 
 -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]).
 
 -include("ejabberd.hrl").
 -include("logger.hrl").
@@ -92,12 +92,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 +182,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(
@@ -201,10 +199,21 @@ load_from_config() ->
                                   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, AccessRules)
+                end, ShaperRules)
       end, Hosts).
 
 %% Delete all previous set ACLs and Access rules
@@ -229,6 +238,7 @@ 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)};
         {shared_group, {G, H}} -> {shared_group, {b(G), nameprep(H)}};
@@ -255,130 +265,177 @@ 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.
+    access_matches(Access, #{usr => jid:tolower(JID)}, Host).
 
-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.
-
--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.
 
-get_aclspecs(ACL, Host) ->
-      ets:lookup(acl, {ACL, Host}) ++ ets:lookup(acl, {ACL, global}).
 
+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.
 
-match_user_spec(Spec, JID, Default) ->
-    case do_match_user_spec(Spec, jid:tolower(JID)) of
-        true -> Default;
-        false -> deny
-    end.
+-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 +507,18 @@ parse_ip_netmask(S) ->
       _ -> error
     end.
 
+transform_access_rules_config(Config) ->
+    lists:map(fun transform_access_rules_config2/1, lists:flatten(Config)).
+
+transform_access_rules_config2({Res, Rules}) when is_list(Rules) ->
+    {Res, lists:map(fun({Type, Args}) when is_list(Args) ->
+                            normalize_spec({Type, hd(lists:flatten(Args))});
+                       (V) -> normalize_spec(V)
+                    end, lists:flatten(Rules))};
+transform_access_rules_config2({Res, Rule}) ->
+    {Res, [Rule]}.
+
+
 transform_options(Opts) ->
     Opts1 = lists:foldl(fun transform_options/2, [], Opts),
     {ACLOpts, Opts2} = lists:mapfoldl(
@@ -464,6 +533,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 +558,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 +603,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, acces_rules, shaper_rules].