1 |
|
%%%---------------------------------------------------------------------- |
2 |
|
%%% File : acl.erl |
3 |
|
%%% Author : Alexey Shchepin <alexey@process-one.net> |
4 |
|
%%% Purpose : ACL support |
5 |
|
%%% Created : 18 Jan 2003 by Alexey Shchepin <alexey@process-one.net> |
6 |
|
%%% |
7 |
|
%%% |
8 |
|
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne |
9 |
|
%%% |
10 |
|
%%% This program is free software; you can redistribute it and/or |
11 |
|
%%% modify it under the terms of the GNU General Public License as |
12 |
|
%%% published by the Free Software Foundation; either version 2 of the |
13 |
|
%%% License, or (at your option) any later version. |
14 |
|
%%% |
15 |
|
%%% This program is distributed in the hope that it will be useful, |
16 |
|
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of |
17 |
|
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
18 |
|
%%% General Public License for more details. |
19 |
|
%%% |
20 |
|
%%% You should have received a copy of the GNU General Public License |
21 |
|
%%% along with this program; if not, write to the Free Software |
22 |
|
%%% Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
23 |
|
%%% |
24 |
|
%%%---------------------------------------------------------------------- |
25 |
|
|
26 |
|
-module(acl). |
27 |
|
-author('alexey@process-one.net'). |
28 |
|
|
29 |
|
-export([match_rule/3, match_rule/4, match_rule/5, |
30 |
|
merge_access_rules/2]). |
31 |
|
|
32 |
|
-include("jlib.hrl"). |
33 |
|
-include("mongoose.hrl"). |
34 |
|
|
35 |
|
-export_type([rule_name/0]). |
36 |
|
|
37 |
|
%% A rule consists of clauses matched from top to bottom |
38 |
|
%% Each clause returns specific values (results) for specific acl's |
39 |
|
%% There are two predefined rules: 'all' (always allow) and 'none' (always deny) |
40 |
|
-type rule_name() :: all | none | atom(). |
41 |
|
-type rule_clause() :: #{acl := acl_name(), value := acl_result()}. |
42 |
|
|
43 |
|
%% Rules can allow/deny access or return other (e.g. numerical) values |
44 |
|
-type acl_result() :: allow | deny | term(). |
45 |
|
|
46 |
|
%% An acl is a union of specs that decide which users belong to it |
47 |
|
%% There are two predefined acl's: 'all' (match everyone) and 'none' (match nobody) |
48 |
|
-type acl_name() :: all | none | atom(). |
49 |
|
-type acl_spec() :: #{match := all | none | current_domain | any_hosted_domain, |
50 |
|
acl_spec_key() => binary()}. |
51 |
|
-type acl_spec_key() :: user | user_regexp | user_glob |
52 |
|
| server | server_regexp | server_glob |
53 |
|
| resource | resource_regexp | resource_glob. |
54 |
|
|
55 |
|
%% Skips the domain check for the 'match => current_domain' condition |
56 |
|
-spec match_rule(mongooseim:host_type_or_global(), rule_name(), jid:jid()) -> acl_result(). |
57 |
|
match_rule(HostType, RuleName, JID) -> |
58 |
389 |
match_rule(HostType, JID#jid.lserver, RuleName, JID). |
59 |
|
|
60 |
|
-spec match_rule(mongooseim:host_type_or_global(), jid:lserver(), rule_name(), jid:jid()) -> |
61 |
|
acl_result(). |
62 |
|
match_rule(_HostType, _Domain, all, _JID) -> |
63 |
178 |
allow; |
64 |
|
match_rule(_HostType, _Domain, none, _JID) -> |
65 |
2287 |
deny; |
66 |
|
match_rule(HostType, Domain, RuleName, JID) -> |
67 |
8465 |
match_rule(HostType, Domain, RuleName, JID, deny). |
68 |
|
|
69 |
|
-spec match_rule(mongooseim:host_type_or_global(), jid:lserver(), rule_name(), jid:jid(), |
70 |
|
Default :: acl_result()) -> |
71 |
|
acl_result(). |
72 |
|
match_rule(HostType, Domain, RuleName, JID, Default) -> |
73 |
8465 |
case mongoose_config:lookup_opt([{access, HostType}, RuleName]) of |
74 |
|
{error, not_found} -> |
75 |
344 |
Default; |
76 |
|
{ok, RuleSpec} -> |
77 |
8121 |
match_rule_clauses(RuleSpec, JID, HostType, Domain) |
78 |
|
end. |
79 |
|
|
80 |
|
%% Merge host-type rules with global ones on startup |
81 |
|
-spec merge_access_rules([rule_clause()], [rule_clause()]) -> [rule_clause()]. |
82 |
|
merge_access_rules(Global, HostLocal) -> |
83 |
:-( |
case lists:reverse(Global) of |
84 |
|
[#{acl := all, value := allow} = AllowAll | Rest] -> |
85 |
:-( |
lists:reverse(Rest) ++ HostLocal ++ [AllowAll]; |
86 |
|
_ -> |
87 |
:-( |
Global ++ HostLocal |
88 |
|
end. |
89 |
|
|
90 |
|
-spec match_rule_clauses([rule_clause()], jid:jid(), mongooseim:host_type_or_global(), |
91 |
|
jid:lserver()) -> |
92 |
|
acl_result(). |
93 |
|
match_rule_clauses([], _, _HostType, _Domain) -> |
94 |
:-( |
deny; |
95 |
|
match_rule_clauses([#{acl := ACLName, value := Value} | RemainingClauses], JID, HostType, Domain) -> |
96 |
11557 |
case match_acl(ACLName, JID, HostType, Domain) of |
97 |
|
true -> |
98 |
8121 |
Value; |
99 |
|
_ -> |
100 |
3436 |
match_rule_clauses(RemainingClauses, JID, HostType, Domain) |
101 |
|
end. |
102 |
|
|
103 |
|
-spec match_acl(acl_name(), jid:jid(), mongooseim:host_type_or_global(), jid:lserver()) -> |
104 |
|
boolean(). |
105 |
|
match_acl(all, _JID, _HostType, _Domain) -> |
106 |
8073 |
true; |
107 |
|
match_acl(none, _JID, _HostType, _Domain) -> |
108 |
:-( |
false; |
109 |
|
match_acl(ACLName, JID, HostType, Domain) -> |
110 |
3484 |
lists:any(fun(ACLSpec) -> match(ACLSpec, Domain, JID) end, get_acl_specs(ACLName, HostType)). |
111 |
|
|
112 |
|
-spec get_acl_specs(acl_name(), mongooseim:host_type_or_global()) -> [acl_spec()]. |
113 |
|
get_acl_specs(ACLName, HostType) -> |
114 |
3484 |
mongoose_config:get_opt([{acl, HostType}, ACLName], []). |
115 |
|
|
116 |
|
%% @doc Check if all conditions from ACLSpec are satisfied by JID |
117 |
|
-spec match(acl_spec(), jid:lserver(), jid:jid()) -> boolean(). |
118 |
|
match(ACLSpec, Domain, JID) when map_size(ACLSpec) > 0 -> |
119 |
48 |
match_step(maps:next(maps:iterator(ACLSpec)), Domain, JID). |
120 |
|
|
121 |
|
match_step({K, V, I}, Domain, JID) -> |
122 |
49 |
check(K, V, Domain, JID) andalso match_step(maps:next(I), Domain, JID); |
123 |
|
match_step(none, _Domain, _JID) -> |
124 |
48 |
true. |
125 |
|
|
126 |
|
-spec check(acl_spec_key(), binary(), jid:lserver(), jid:jid()) -> boolean(). |
127 |
:-( |
check(match, all, _, _) -> true; |
128 |
:-( |
check(match, none, _, _) -> false; |
129 |
|
check(match, any_hosted_domain, _, JID) -> |
130 |
:-( |
mongoose_domain_api:get_host_type(JID#jid.lserver) =/= {error, not_found}; |
131 |
48 |
check(match, current_domain, Domain, JID) -> JID#jid.lserver =:= Domain; |
132 |
1 |
check(user, User, _, JID) -> JID#jid.luser =:= User; |
133 |
:-( |
check(user_regexp, Regexp, _, JID) -> is_regexp_match(JID#jid.luser, Regexp); |
134 |
:-( |
check(user_glob, Glob, _, JID) -> is_glob_match(JID#jid.luser, Glob); |
135 |
:-( |
check(server, Server, _, JID) -> JID#jid.lserver =:= Server; |
136 |
:-( |
check(server_regexp, Regexp, _, JID) -> is_regexp_match(JID#jid.lserver, Regexp); |
137 |
:-( |
check(server_glob, Glob, _, JID) -> is_glob_match(JID#jid.lserver, Glob); |
138 |
:-( |
check(resource, Resource, _, JID) -> JID#jid.lresource =:= Resource; |
139 |
:-( |
check(resource_regexp, Regexp, _, JID) -> is_regexp_match(JID#jid.lresource, Regexp); |
140 |
:-( |
check(resource_glob, Glob, _, JID) -> is_glob_match(JID#jid.lresource, Glob). |
141 |
|
|
142 |
|
-spec is_regexp_match(binary(), RegExp :: iodata()) -> boolean(). |
143 |
|
is_regexp_match(String, RegExp) -> |
144 |
:-( |
try re:run(String, RegExp, [{capture, none}]) of |
145 |
|
nomatch -> |
146 |
:-( |
false; |
147 |
|
match -> |
148 |
:-( |
true |
149 |
|
catch _:ErrDesc -> |
150 |
:-( |
?LOG_ERROR(#{what => acl_regexp_match_failed, |
151 |
:-( |
string => String, regex => RegExp, reason => ErrDesc}), |
152 |
:-( |
false |
153 |
|
end. |
154 |
|
|
155 |
|
-spec is_glob_match(binary(), Glob :: binary()) -> boolean(). |
156 |
|
is_glob_match(String, Glob) -> |
157 |
:-( |
is_regexp_match(String, xmerl_regexp:sh_to_awk(binary_to_list(Glob))). |