• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

processone / ejabberd / 1258

12 Dec 2025 03:57PM UTC coverage: 33.638% (-0.006%) from 33.644%
1258

push

github

badlop
Container: Apply commit a22c88a

ejabberdctl.template: Show meaningful error when ERL_DIST_PORT is in use

15554 of 46240 relevant lines covered (33.64%)

1078.28 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

59.13
/src/acl.erl
1
%%%----------------------------------------------------------------------
2
%%% ejabberd, Copyright (C) 2002-2025   ProcessOne
3
%%%
4
%%% This program is free software; you can redistribute it and/or
5
%%% modify it under the terms of the GNU General Public License as
6
%%% published by the Free Software Foundation; either version 2 of the
7
%%% License, or (at your option) any later version.
8
%%%
9
%%% This program is distributed in the hope that it will be useful,
10
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
11
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12
%%% General Public License for more details.
13
%%%
14
%%% You should have received a copy of the GNU General Public License along
15
%%% with this program; if not, write to the Free Software Foundation, Inc.,
16
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
%%%
18
%%%----------------------------------------------------------------------
19
-module(acl).
20
-behaviour(gen_server).
21

22
-export([start_link/0]).
23
-export([reload_from_config/0]).
24
-export([match_rule/3, match_acl/3]).
25
-export([match_rules/4, match_acls/3]).
26
-export([access_rules_validator/0, access_validator/0]).
27
-export([validator/1, validators/0]).
28
-export([loaded_shared_roster_module/1]).
29
%% gen_server callbacks
30
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
31
         terminate/2, code_change/3]).
32

33
-include("logger.hrl").
34

35
-type state() :: #{hosts := [binary()]}.
36
-type action() :: allow | deny.
37
-type ip_mask() :: {inet:ip4_address(), 0..32} | {inet:ip6_address(), 0..128}.
38
-type access_rule() :: {acl, atom()} | acl_rule().
39
-type acl_rule() :: {user, {binary(), binary()} | binary()} |
40
                    {server, binary()} |
41
                    {resource, binary()} |
42
                    {user_regexp, {misc:re_mp(), binary()} | misc:re_mp()} |
43
                    {server_regexp, misc:re_mp()} |
44
                    {resource_regexp, misc:re_mp()} |
45
                    {node_regexp, {misc:re_mp(), misc:re_mp()}} |
46
                    {user_glob, {misc:re_mp(), binary()} | misc:re_mp()} |
47
                    {server_glob, misc:re_mp()} |
48
                    {resource_glob, misc:re_mp()} |
49
                    {node_glob, {misc:re_mp(), misc:re_mp()}} |
50
                    {shared_group, {binary(), binary()} | binary()} |
51
                    {ip, ip_mask()}.
52
-type access() :: [{action(), [access_rule()]}].
53
-type acl() :: atom() | access().
54
-type match() :: #{ip => inet:ip_address(),
55
                   usr => jid:ljid(),
56
                   atom() => term()}.
57

58
-export_type([acl/0, acl_rule/0, access/0, access_rule/0, match/0]).
59

60
%%%===================================================================
61
%%% API
62
%%%===================================================================
63
start_link() ->
64
    gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
11✔
65

66
-spec match_rule(global | binary(), atom() | access(),
67
                 jid:jid() | jid:ljid() | inet:ip_address() | match()) -> action().
68
match_rule(_, all, _) ->
69
    allow;
11,545✔
70
match_rule(_, none, _) ->
71
    deny;
13,716✔
72
match_rule(Host, Access, Match) when is_map(Match) ->
73
    Rules = if is_atom(Access) -> read_access(Access, Host);
7,255✔
74
               true -> Access
×
75
            end,
76
    match_rules(Host, Rules, Match, deny);
7,255✔
77
match_rule(Host, Access, IP) when tuple_size(IP) == 4; tuple_size(IP) == 8 ->
78
    match_rule(Host, Access, #{ip => IP});
×
79
match_rule(Host, Access, JID) ->
80
    match_rule(Host, Access, #{usr => jid:tolower(JID)}).
5,220✔
81

82
-spec match_acl(global | binary(), access_rule(), match()) -> boolean().
83
match_acl(_Host, {acl, all}, _) ->
84
    true;
12,349✔
85
match_acl(_Host, {acl, none}, _) ->
86
    false;
×
87
match_acl(Host, {acl, ACLName}, Match) ->
88
    lists:any(
13,383✔
89
      fun(ACL) ->
90
              match_acl(Host, ACL, Match)
11,348✔
91
      end, read_acl(ACLName, Host));
92
match_acl(_Host, {ip, {Net, Mask}}, #{ip := {IP, _Port}}) ->
93
    misc:match_ip_mask(IP, Net, Mask);
×
94
match_acl(_Host, {ip, {Net, Mask}}, #{ip := IP}) ->
95
    misc:match_ip_mask(IP, Net, Mask);
×
96
match_acl(_Host, {user, {U, S}}, #{usr := {U, S, _}}) ->
97
    true;
×
98
match_acl(_Host, {user, U}, #{usr := {U, S, _}}) ->
99
    ejabberd_router:is_my_host(S);
1,116✔
100
match_acl(_Host, {server, S}, #{usr := {_, S, _}}) ->
101
    true;
×
102
match_acl(_Host, {resource, R}, #{usr := {_, _, R}}) ->
103
    true;
×
104
match_acl(_Host, {shared_group, {G, H}}, #{usr := {U, S, _}}) ->
105
    case loaded_shared_roster_module(H) of
×
106
        undefined -> false;
×
107
        Mod -> Mod:is_user_in_group({U, S}, G, H)
×
108
    end;
109
match_acl(Host, {shared_group, G}, #{usr := {_, S, _}} = Map) ->
110
    match_acl(Host, {shared_group, {G, S}}, Map);
×
111
match_acl(_Host, {user_regexp, {UR, S1}}, #{usr := {U, S2, _}}) ->
112
    S1 == S2 andalso match_regexp(U, UR);
×
113
match_acl(_Host, {user_regexp, UR}, #{usr := {U, S, _}}) ->
114
    ejabberd_router:is_my_host(S) andalso match_regexp(U, UR);
2,153✔
115
match_acl(_Host, {server_regexp, SR}, #{usr := {_, S, _}}) ->
116
    match_regexp(S, SR);
×
117
match_acl(_Host, {resource_regexp, RR}, #{usr := {_, _, R}}) ->
118
    match_regexp(R, RR);
×
119
match_acl(_Host, {node_regexp, {UR, SR}}, #{usr := {U, S, _}}) ->
120
    match_regexp(U, UR) andalso match_regexp(S, SR);
×
121
match_acl(_Host, {user_glob, {UR, S1}}, #{usr := {U, S2, _}}) ->
122
    S1 == S2 andalso match_regexp(U, UR);
×
123
match_acl(_Host, {user_glob, UR}, #{usr := {U, S, _}}) ->
124
    ejabberd_router:is_my_host(S) andalso match_regexp(U, UR);
×
125
match_acl(_Host, {server_glob, SR}, #{usr := {_, S, _}}) ->
126
    match_regexp(S, SR);
×
127
match_acl(_Host, {resource_glob, RR}, #{usr := {_, _, R}}) ->
128
    match_regexp(R, RR);
×
129
match_acl(_Host, {node_glob, {UR, SR}}, #{usr := {U, S, _}}) ->
130
    match_regexp(U, UR) andalso match_regexp(S, SR);
×
131
match_acl(_, _, _) ->
132
    false.
8,079✔
133

134
-spec match_rules(global | binary(), [{T, [access_rule()]}], match(), T) -> T.
135
match_rules(Host, [{Return, Rules} | Rest], Match, Default) ->
136
    case match_acls(Host, Rules, Match) of
24,608✔
137
        false ->
138
            match_rules(Host, Rest, Match, Default);
10,114✔
139
        true ->
140
            Return
14,494✔
141
    end;
142
match_rules(_Host, [], _Match, Default) ->
143
    Default.
1,969✔
144

145
-spec match_acls(global | binary(), [access_rule()], match()) -> boolean().
146
match_acls(_Host, [], _Match) ->
147
    false;
×
148
match_acls(Host, Rules, Match) ->
149
    lists:all(
24,608✔
150
      fun(Rule) ->
151
              match_acl(Host, Rule, Match)
24,608✔
152
      end, Rules).
153

154
-spec reload_from_config() -> ok.
155
reload_from_config() ->
156
    gen_server:call(?MODULE, reload_from_config, timer:minutes(1)).
2✔
157

158
-spec validator(access_rules | acl) -> econf:validator().
159
validator(access_rules) ->
160
    econf:options(
39✔
161
      #{'_' => access_rules_validator()},
162
      [{disallowed, [all, none]}, unique]);
163
validator(acl) ->
164
    econf:options(
39✔
165
      #{'_' => acl_validator()},
166
      [{disallowed, [all, none]}, unique]).
167

168
%%%===================================================================
169
%%% gen_server callbacks
170
%%%===================================================================
171
-spec init([]) -> {ok, state()}.
172
init([]) ->
173
    create_tab(acl),
11✔
174
    create_tab(access),
11✔
175
    Hosts = ejabberd_option:hosts(),
11✔
176
    load_from_config(Hosts),
11✔
177
    ejabberd_hooks:add(config_reloaded, ?MODULE, reload_from_config, 20),
11✔
178
    {ok, #{hosts => Hosts}}.
11✔
179

180
-spec handle_call(term(), term(), state()) -> {reply, ok, state()} | {noreply, state()}.
181
handle_call(reload_from_config, _, State) ->
182
    NewHosts = ejabberd_option:hosts(),
2✔
183
    load_from_config(NewHosts),
2✔
184
    {reply, ok, State#{hosts => NewHosts}};
2✔
185
handle_call(Request, From, State) ->
186
    ?WARNING_MSG("Unexpected call from ~p: ~p", [From, Request]),
×
187
    {noreply, State}.
×
188

189
-spec handle_cast(term(), state()) -> {noreply, state()}.
190
handle_cast(Msg, State) ->
191
    ?WARNING_MSG("Unexpected cast: ~p", [Msg]),
×
192
    {noreply, State}.
×
193

194
-spec handle_info(term(), state()) -> {noreply, state()}.
195
handle_info(Info, State) ->
196
    ?WARNING_MSG("Unexpected info: ~p", [Info]),
×
197
    {noreply, State}.
×
198

199
-spec terminate(any(), state()) -> ok.
200
terminate(_Reason, _State) ->
201
    ejabberd_hooks:delete(config_reloaded, ?MODULE, reload_from_config, 20).
×
202

203
-spec code_change(term(), state(), term()) -> {ok, state()}.
204
code_change(_OldVsn, State, _Extra) ->
205
    {ok, State}.
×
206

207
%%%===================================================================
208
%%% Internal functions
209
%%%===================================================================
210
%%%===================================================================
211
%%% Table management
212
%%%===================================================================
213
-spec load_from_config([binary()]) -> ok.
214
load_from_config(NewHosts) ->
215
    ?DEBUG("Loading access rules from config", []),
13✔
216
    load_tab(acl, NewHosts, fun ejabberd_option:acl/1),
13✔
217
    load_tab(access, NewHosts, fun ejabberd_option:access_rules/1),
13✔
218
    ?DEBUG("Access rules loaded successfully", []).
13✔
219

220
-spec create_tab(atom()) -> atom().
221
create_tab(Tab) ->
222
    _ = mnesia:delete_table(Tab),
22✔
223
    ets:new(Tab, [named_table, set, {read_concurrency, true}]).
22✔
224

225
-spec load_tab(atom(), [binary()], fun((global | binary()) -> {atom(), list()})) -> ok.
226
load_tab(Tab, Hosts, Fun) ->
227
    Old = ets:tab2list(Tab),
26✔
228
    New = lists:flatmap(
26✔
229
            fun(Host) ->
230
                    [{{Name, Host}, List} || {Name, List} <- Fun(Host)]
286✔
231
            end, [global|Hosts]),
232
    ets:insert(Tab, New),
26✔
233
    lists:foreach(
26✔
234
      fun({Key, _}) ->
235
              case lists:keymember(Key, 1, New) of
242✔
236
                  false -> ets:delete(Tab, Key);
×
237
                  true -> ok
242✔
238
              end
239
      end, Old).
240

241
-spec read_access(atom(), global | binary()) -> access().
242
read_access(Name, Host) ->
243
    case ets:lookup(access, {Name, Host}) of
7,255✔
244
        [{_, Access}] -> Access;
7,253✔
245
        [] -> []
2✔
246
    end.
247

248
-spec read_acl(atom(), global | binary()) -> [acl_rule()].
249
read_acl(Name, Host) ->
250
    case ets:lookup(acl, {Name, Host}) of
13,383✔
251
        [{_, ACL}] -> ACL;
11,348✔
252
        [] -> []
2,035✔
253
    end.
254

255
%%%===================================================================
256
%%% Validators
257
%%%===================================================================
258
validators() ->
259
    #{ip => econf:list_or_single(econf:ip_mask()),
4,852✔
260
      user => user_validator(econf:user(), econf:domain()),
261
      user_regexp => user_validator(econf:re([unicode]), econf:domain()),
262
      user_glob => user_validator(econf:glob([unicode]), econf:domain()),
263
      server => econf:list_or_single(econf:domain()),
264
      server_regexp => econf:list_or_single(econf:re([unicode])),
265
      server_glob => econf:list_or_single(econf:glob([unicode])),
266
      resource => econf:list_or_single(econf:resource()),
267
      resource_regexp => econf:list_or_single(econf:re([unicode])),
268
      resource_glob => econf:list_or_single(econf:glob([unicode])),
269
      node_regexp => node_validator(econf:re([unicode]), econf:re([unicode])),
270
      node_glob => node_validator(econf:glob([unicode]), econf:glob([unicode])),
271
      shared_group => user_validator(econf:binary(), econf:domain()),
272
      acl => econf:atom()}.
273

274
rule_validator() ->
275
    rule_validator(validators()).
4,787✔
276

277
rule_validator(RVs) ->
278
    econf:and_then(
4,826✔
279
      econf:non_empty(econf:options(RVs, [])),
280
      fun(Rules) ->
281
              lists:flatmap(
221✔
282
                fun({Type, Rs}) when is_list(Rs) ->
283
                        [{Type, R} || R <- Rs];
26✔
284
                   (Other) ->
285
                        [Other]
195✔
286
                end, Rules)
287
      end).
288

289
access_validator() ->
290
    econf:and_then(
4,787✔
291
      fun(L) when is_list(L) ->
292
              lists:map(
×
293
                fun({K, V}) -> {(econf:atom())(K), V};
×
294
                   (A) -> {acl, (econf:atom())(A)}
×
295
                end, lists:flatten(L));
296
         (A) ->
297
              [{acl, (econf:atom())(A)}]
195✔
298
      end,
299
      rule_validator()).
300

301
access_rules_validator() ->
302
    econf:and_then(
2,361✔
303
      fun(L) when is_list(L) ->
304
              lists:map(
117✔
305
                fun({K, V}) -> {(econf:atom())(K), V};
130✔
306
                   (A) -> {(econf:atom())(A), [{acl, all}]}
×
307
                end, lists:flatten(L));
308
         (Bad) ->
309
              Bad
×
310
      end,
311
      econf:non_empty(
312
        econf:options(
313
          #{allow => access_validator(),
314
            deny => access_validator()},
315
          []))).
316

317
acl_validator() ->
318
    econf:and_then(
39✔
319
      fun(L) when is_list(L) -> lists:flatten(L);
26✔
320
         (Bad) -> Bad
×
321
      end,
322
      rule_validator(maps:remove(acl, validators()))).
323

324
user_validator(UV, SV) ->
325
    econf:and_then(
19,408✔
326
      econf:list_or_single(
327
        fun({U, S}) ->
328
                {UV(U), SV(S)};
×
329
           (M) when is_list(M) ->
330
                (econf:map(UV, SV))(M);
×
331
           (Val) ->
332
                US = (econf:binary())(Val),
26✔
333
                case binary:split(US, <<"@">>, [global]) of
26✔
334
                    [U, S] -> {UV(U), SV(S)};
×
335
                    [U] -> UV(U);
26✔
336
                    _ -> econf:fail({bad_user, Val})
×
337
                end
338
        end),
339
      fun lists:flatten/1).
340

341
node_validator(UV, SV) ->
342
    econf:and_then(
9,704✔
343
      econf:and_then(
344
        econf:list(econf:any()),
345
        fun lists:flatten/1),
346
      econf:map(UV, SV)).
347

348
%%%===================================================================
349
%%% Aux
350
%%%===================================================================
351
-spec match_regexp(iodata(), misc:re_mp()) -> boolean().
352
match_regexp(Data, RegExp) ->
353
    re:run(Data, RegExp) /= nomatch.
2,153✔
354

355
-spec loaded_shared_roster_module(global | binary()) -> atom().
356
loaded_shared_roster_module(global) ->
357
    loaded_shared_roster_module(ejabberd_config:get_myname());
×
358
loaded_shared_roster_module(Host) ->
359
    case gen_mod:is_loaded(Host, mod_shared_roster_ldap) of
×
360
        true -> mod_shared_roster_ldap;
×
361
        false ->
362
            case gen_mod:is_loaded(Host, mod_shared_roster) of
×
363
                true -> mod_shared_roster;
×
364
                false -> undefined
×
365
            end
366
    end.
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc