aboutsummaryrefslogtreecommitdiff
path: root/src/acl.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/acl.erl')
-rw-r--r--src/acl.erl469
1 files changed, 315 insertions, 154 deletions
diff --git a/src/acl.erl b/src/acl.erl
index 1338e55b6..4c4523617 100644
--- a/src/acl.erl
+++ b/src/acl.erl
@@ -29,35 +29,38 @@
-author('alexey@process-one.net').
-export([start/0, to_record/3, add/3, add_list/3,
- add_local/3, add_list_local/3,
- match_rule/3, match_acl/3]).
+ add_local/3, add_list_local/3, load_from_config/0,
+ match_rule/3, match_acl/3, transform_options/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
-include("jlib.hrl").
-record(acl, {aclname, aclspec}).
+-record(access, {name :: access_name(),
+ rules = [] :: [access_rule()]}).
-type regexp() :: 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()} |
- {user, binary(), binary()} |
+ {user, {binary(), host()} | binary()} |
{server, binary()} |
{resource, binary()} |
- {user_regexp, regexp()} |
- {shared_group, binary()} |
- {shared_group, binary(), binary()} |
- {user_regexp, regexp(), 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()} |
- {user_glob, glob(), binary()} |
+ {node_regexp, {regexp(), regexp()}} |
+ {user_glob, {glob(), host()} | glob()} |
{server_glob, glob()} |
{resource_glob, glob()} |
- {node_glob, glob(), glob()}.
+ {ip, {inet:ip_address(), integer()}} |
+ {node_glob, {glob(), glob()}}.
-type acl() :: #acl{aclname :: aclname(),
aclspec :: aclspec()}.
@@ -65,12 +68,23 @@
-export_type([acl/0]).
start() ->
+ case catch mnesia:table_info(acl, storage_type) of
+ disc_copies ->
+ mnesia:delete_table(acl);
+ _ ->
+ ok
+ end,
mnesia:create_table(acl,
- [{disc_copies, [node()]}, {type, bag},
+ [{ram_copies, [node()]}, {type, bag},
{local_content, true},
{attributes, record_info(fields, acl)}]),
+ mnesia:create_table(access,
+ [{ram_copies, [node()]},
+ {local_content, true},
+ {attributes, record_info(fields, access)}]),
mnesia:add_table_copy(acl, node(), ram_copies),
- update_table(),
+ mnesia:add_table_copy(access, node(), ram_copies),
+ load_from_config(),
ok.
-spec to_record(binary(), atom(), aclspec()) -> acl().
@@ -82,7 +96,7 @@ to_record(Host, ACLName, ACLSpec) ->
-spec add(binary(), aclname(), aclspec()) -> ok | {error, any()}.
add(Host, ACLName, ACLSpec) ->
- {ResL, BadNodes} = rpc:multicall(ejabberd_cluster:get_nodes(),
+ {ResL, BadNodes} = rpc:multicall(mnesia:system_info(running_db_nodes),
?MODULE, add_local,
[Host, ACLName, ACLSpec]),
case lists:keyfind(aborted, 1, ResL) of
@@ -109,7 +123,7 @@ add_local(Host, ACLName, ACLSpec) ->
-spec add_list(binary(), [acl()], boolean()) -> ok | {error, any()}.
add_list(Host, ACLs, Clear) ->
- {ResL, BadNodes} = rpc:multicall(ejabberd_cluster:get_nodes(),
+ {ResL, BadNodes} = rpc:multicall(mnesia:system_info(running_db_nodes),
?MODULE, add_list_local,
[Host, ACLs, Clear]),
case lists:keyfind(aborted, 1, ResL) of
@@ -147,130 +161,196 @@ add_list_local(Host, ACLs, Clear) ->
end,
mnesia:transaction(F).
-normalize(A) -> jlib:nodeprep(iolist_to_binary(A)).
-
-normalize_spec({A, B}) -> {A, normalize(B)};
-normalize_spec({A, B, C}) ->
- {A, normalize(B), normalize(C)};
-normalize_spec(all) -> all;
-normalize_spec(none) -> none.
-
--spec match_rule(global | binary(), atom(), jid() | ljid()) -> any().
-
-match_rule(global, Rule, JID) ->
- case Rule of
- all -> allow;
- none -> deny;
- _ ->
- case ejabberd_config:get_global_option(
- {access, Rule, global}, fun(V) -> V end)
- of
- undefined -> deny;
- GACLs -> match_acls(GACLs, JID, global)
- end
- end;
-match_rule(Host, Rule, JID) ->
- case Rule of
- all -> allow;
- none -> deny;
- _ ->
- case ejabberd_config:get_global_option(
- {access, Rule, global}, fun(V) -> V end)
- of
- undefined ->
- case ejabberd_config:get_global_option(
- {access, Rule, Host}, fun(V) -> V end)
- of
- undefined -> deny;
- ACLs -> match_acls(ACLs, JID, Host)
- end;
- GACLs ->
- case ejabberd_config:get_global_option(
- {access, Rule, Host}, fun(V) -> V end)
- of
- undefined -> match_acls(GACLs, JID, Host);
- ACLs ->
- case lists:reverse(GACLs) of
- [{allow, all} | Rest] ->
- match_acls(lists:reverse(Rest) ++
- ACLs ++ [{allow, all}],
- JID, Host);
- _ -> match_acls(GACLs ++ ACLs, JID, Host)
- end
- end
- end
+-spec add_access(binary() | global,
+ access_name(), [access_rule()]) -> ok | {error, any()}.
+
+add_access(Host, Access, Rules) ->
+ case mnesia:transaction(
+ fun() ->
+ mnesia:write(
+ #access{name = {Access, Host},
+ rules = Rules})
+ end) of
+ {atomic, ok} ->
+ ok;
+ Err ->
+ {error, Err}
+ end.
+
+-spec load_from_config() -> ok.
+
+load_from_config() ->
+ Hosts = [global|?MYHOSTS],
+ lists:foreach(
+ fun(Host) ->
+ ACLs = ejabberd_config:get_option(
+ {acl, Host}, fun(V) -> V end, []),
+ AccessRules = ejabberd_config:get_option(
+ {access, Host}, fun(V) -> V end, []),
+ 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}) ->
+ add_access(Host, Access, Rules)
+ end, AccessRules)
+ end, Hosts).
+
+b(S) ->
+ iolist_to_binary(S).
+
+nodeprep(S) ->
+ jlib:nodeprep(b(S)).
+
+nameprep(S) ->
+ jlib:nameprep(b(S)).
+
+resourceprep(S) ->
+ jlib:resourceprep(b(S)).
+
+normalize_spec(Spec) ->
+ case Spec of
+ all -> all;
+ none -> none;
+ {user, {U, S}} -> {user, {nodeprep(U), nameprep(S)}};
+ {user, U} -> {user, nodeprep(U)};
+ {shared_group, {G, H}} -> {shared_group, {b(G), nameprep(H)}};
+ {shared_group, G} -> {shared_group, b(G)};
+ {user_regexp, {UR, S}} -> {user_regexp, {b(UR), nameprep(S)}};
+ {user_regexp, UR} -> {user_regexp, b(UR)};
+ {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)};
+ {node_glob, {UR, SR}} -> {node_glob, {b(UR), b(SR)}};
+ {server, S} -> {server, nameprep(S)};
+ {resource, R} -> {resource, resourceprep(R)};
+ {server_regexp, SR} -> {server_regexp, b(SR)};
+ {server_glob, S} -> {server_glob, b(S)};
+ {resource_glob, R} -> {resource_glob, b(R)};
+ {ip, S} ->
+ case parse_ip_netmask(b(S)) of
+ {ok, Net, Mask} ->
+ {ip, {Net, Mask}};
+ error ->
+ ?INFO_MSG("Invalid network address: ~p", [S]),
+ none
+ end
+ end.
+
+-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, JID) ->
+ GAccess = ets:lookup(access, {Access, global}),
+ LAccess = if Host /= global ->
+ ets:lookup(access, {Access, Host});
+ true ->
+ []
+ end,
+ case GAccess ++ LAccess of
+ [] ->
+ ?WARNING_MSG("Attempt to match against unspecified "
+ "access rule '~s' (scope: ~s)",
+ [Access, Host]),
+ deny;
+ AccessList ->
+ Rules = lists:flatmap(
+ fun(#access{rules = Rs}) ->
+ Rs
+ end, AccessList),
+ match_acls(Rules, JID, Host)
end.
match_acls([], _, _Host) -> deny;
-match_acls([{Access, ACL} | ACLs], JID, Host) ->
+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(), binary()) -> boolean().
+-spec match_acl(atom(),
+ jid() | ljid() | inet:ip_address(),
+ binary()) -> boolean().
+match_acl(all, _JID, _Host) ->
+ 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,
+ ets:lookup(acl, {ACL, Host}) ++
+ ets:lookup(acl, {ACL, global}));
match_acl(ACL, JID, Host) ->
- case ACL of
- all -> true;
- none -> false;
- _ ->
- {User, Server, Resource} = jlib:jid_tolower(JID),
- lists:any(fun (#acl{aclspec = Spec}) ->
- case Spec of
- all -> true;
- {user, U} ->
- U == User andalso
- (Host == Server orelse
- Host == global andalso
- lists:member(Server, ?MYHOSTS));
- {user, U, S} -> U == User andalso S == Server;
- {server, S} -> S == Server;
- {resource, R} -> R == Resource;
- {user_regexp, UR} ->
- (Host == Server orelse
- Host == global andalso
- lists:member(Server, ?MYHOSTS))
- andalso is_regexp_match(User, UR);
- {shared_group, G} ->
- Mod = loaded_shared_roster_module(Host),
- Mod:is_user_in_group({User, Server}, G, Host);
- {shared_group, G, H} ->
- Mod = loaded_shared_roster_module(H),
- Mod:is_user_in_group({User, Server}, G, H);
- {user_regexp, UR, S} ->
- S == Server 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} ->
- (Host == Server orelse
- Host == global andalso
- lists:member(Server, ?MYHOSTS))
- andalso is_glob_match(User, UR);
- {user_glob, UR, S} ->
- S == Server 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,
- ets:lookup(acl, {ACL, global}) ++
- ets:lookup(acl, {ACL, Host}))
- end.
+ {User, Server, Resource} = jlib: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,
+ ets:lookup(acl, {ACL, Host}) ++
+ ets:lookup(acl, {ACL, global})).
is_regexp_match(String, RegExp) ->
case ejabberd_regexp:run(String, RegExp) of
@@ -286,34 +366,115 @@ is_glob_match(String, Glob) ->
is_regexp_match(String,
ejabberd_regexp:sh_to_awk(Glob)).
+is_ip_match({_, _, _, _} = 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;
+is_ip_match({_, _, _, _, _, _, _, _} = 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;
+is_ip_match(_, _, _) ->
+ 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.
-update_table() ->
- Fields = record_info(fields, acl),
- case mnesia:table_info(acl, attributes) of
- Fields ->
- ejabberd_config:convert_table_to_binary(
- acl, Fields, bag,
- fun(#acl{aclspec = Spec}) when is_tuple(Spec) ->
- element(2, Spec);
- (_) ->
- '$next'
- end,
- fun(#acl{aclname = {ACLName, Host},
- aclspec = Spec} = R) ->
- NewHost = if Host == global ->
- Host;
- true ->
- iolist_to_binary(Host)
- end,
- R#acl{aclname = {ACLName, NewHost},
- aclspec = normalize_spec(Spec)}
- end);
- _ ->
- ?INFO_MSG("Recreating acl table", []),
- mnesia:transform_table(acl, ignore, Fields)
+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 jlib: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_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),
+ 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,
+ ACLOpts2 ++ AccessOpts1 ++ Opts3.
+
+transform_options({acl, Name, Type}, Opts) ->
+ T = case Type of
+ all -> all;
+ none -> none;
+ {user, U} -> {user, [U]};
+ {user, U, S} -> {user, [[{U, S}]]};
+ {shared_group, G} -> {shared_group, [G]};
+ {shared_group, G, H} -> {shared_group, [[{G, H}]]};
+ {user_regexp, UR} -> {user_regexp, [UR]};
+ {user_regexp, UR, S} -> {user_regexp, [[{UR, S}]]};
+ {node_regexp, UR, SR} -> {node_regexp, [[{UR, SR}]]};
+ {user_glob, UR} -> {user_glob, [UR]};
+ {user_glob, UR, S} -> {user_glob, [[{UR, S}]]};
+ {node_glob, UR, SR} -> {node_glob, [[{UR, SR}]]};
+ {server, S} -> {server, [S]};
+ {resource, R} -> {resource, [R]};
+ {server_regexp, SR} -> {server_regexp, [SR]};
+ {server_glob, S} -> {server_glob, [S]};
+ {ip, S} -> {ip, [S]};
+ {resource_glob, R} -> {resource_glob, [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].