• 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.12
/src/mod_muc.erl
1
%%%----------------------------------------------------------------------
2
%%% File    : mod_muc.erl
3
%%% Author  : Alexey Shchepin <alexey@process-one.net>
4
%%% Purpose : MUC support (XEP-0045)
5
%%% Created : 19 Mar 2003 by Alexey Shchepin <alexey@process-one.net>
6
%%%
7
%%%
8
%%% ejabberd, Copyright (C) 2002-2025   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 along
21
%%% with this program; if not, write to the Free Software Foundation, Inc.,
22
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23
%%%
24
%%%----------------------------------------------------------------------
25
-module(mod_muc).
26
-author('alexey@process-one.net').
27
-protocol({xep, 45, '1.35.3', '0.5.0', "complete", ""}).
28
-protocol({xep, 249, '1.2', '0.5.0', "complete", ""}).
29
-protocol({xep, 486, '0.1.0', '24.07', "complete", ""}).
30
-ifndef(GEN_SERVER).
31
-define(GEN_SERVER, gen_server).
32
-endif.
33
-behaviour(?GEN_SERVER).
34
-behaviour(gen_mod).
35

36
%% API
37
-export([start/2,
38
         stop/1,
39
         start_link/2,
40
         reload/3,
41
         mod_doc/0,
42
         room_destroyed/4,
43
         store_room/4,
44
         store_room/5,
45
         store_changes/4,
46
         restore_room/3,
47
         forget_room/3,
48
         create_room/3,
49
         create_room/5,
50
         shutdown_rooms/1,
51
         process_disco_info/1,
52
         process_disco_items/1,
53
         process_vcard/1,
54
         process_register/1,
55
         process_iq_register/1,
56
         process_muc_unique/1,
57
         process_mucsub/1,
58
         broadcast_service_message/3,
59
         export/1,
60
         import_info/0,
61
         import/5,
62
         import_start/2,
63
         opts_to_binary/1,
64
         find_online_room/2,
65
         register_online_room/3,
66
         get_online_rooms/1,
67
         count_online_rooms/1,
68
         register_online_user/4,
69
         unregister_online_user/4,
70
         get_register_nick/3,
71
         get_register_nicks/2,
72
         iq_set_register_info/5,
73
         count_online_rooms_by_user/3,
74
         get_online_rooms_by_user/3,
75
         can_use_nick/4,
76
         get_subscribed_rooms/2,
77
         remove_user/2,
78
         procname/2,
79
         route/1, unhibernate_room/3]).
80

81
-export([init/1, handle_call/3, handle_cast/2,
82
         handle_info/2, terminate/2, code_change/3,
83
         mod_opt_type/1, mod_options/1, depends/2]).
84

85
-include("logger.hrl").
86
-include_lib("xmpp/include/xmpp.hrl").
87
-include("mod_muc.hrl").
88
-include("mod_muc_room.hrl").
89
-include("translate.hrl").
90

91

92
-type state() :: #{hosts := [binary()],
93
                   server_host := binary(),
94
                   worker := pos_integer()}.
95
-type access() :: {acl:acl(), acl:acl(), acl:acl(), acl:acl(), acl:acl()}.
96
-type muc_room_opts() :: [{atom(), any()}].
97
-export_type([access/0]).
98
-callback init(binary(), gen_mod:opts()) -> any().
99
-callback import(binary(), binary(), [binary()]) -> ok.
100
-callback store_room(binary(), binary(), binary(), list(), list()|undefined) -> {atomic, any()}.
101
-callback store_changes(binary(), binary(), binary(), list()) -> {atomic, any()}.
102
-callback restore_room(binary(), binary(), binary()) -> muc_room_opts() | error | {error, atom()}.
103
-callback forget_room(binary(), binary(), binary()) -> {atomic, any()}.
104
-callback can_use_nick(binary(), binary(), jid(), binary()) -> boolean().
105
-callback get_rooms(binary(), binary()) -> [#muc_room{}].
106
-callback get_nick(binary(), binary(), jid()) -> binary() | error.
107
-callback get_nicks(binary(), binary()) -> [{binary(), binary(), binary()}] | error.
108
-callback set_nick(binary(), binary(), jid(), binary()) -> {atomic, ok | false}.
109
-callback register_online_room(binary(), binary(), binary(), pid()) -> any().
110
-callback unregister_online_room(binary(), binary(), binary(), pid()) -> any().
111
-callback find_online_room(binary(), binary(), binary()) -> {ok, pid()} | error.
112
-callback find_online_room_by_pid(binary(), pid()) -> {ok, binary(), binary()} | error.
113
-callback get_online_rooms(binary(), binary(), undefined | rsm_set()) -> [{binary(), binary(), pid()}].
114
-callback count_online_rooms(binary(), binary()) -> non_neg_integer().
115
-callback rsm_supported() -> boolean().
116
-callback register_online_user(binary(), ljid(), binary(), binary()) -> any().
117
-callback unregister_online_user(binary(), ljid(), binary(), binary()) -> any().
118
-callback count_online_rooms_by_user(binary(), binary(), binary()) -> non_neg_integer().
119
-callback get_online_rooms_by_user(binary(), binary(), binary()) -> [{binary(), binary()}].
120
-callback get_subscribed_rooms(binary(), binary(), jid()) ->
121
          {ok, [{jid(), binary(), [binary()]}]} | {error, db_failure}.
122

123
-optional_callbacks([get_subscribed_rooms/3,
124
                     store_changes/4]).
125

126
%%====================================================================
127
%% API
128
%%====================================================================
129
start(Host, Opts) ->
130
    case mod_muc_sup:start(Host) of
109✔
131
        {ok, _} ->
132
            ejabberd_hooks:add(remove_user, Host, ?MODULE,
109✔
133
                               remove_user, 50),
134
            MyHosts = gen_mod:get_opt_hosts(Opts),
109✔
135
            Mod = gen_mod:db_mod(Opts, ?MODULE),
109✔
136
            RMod = gen_mod:ram_db_mod(Opts, ?MODULE),
109✔
137
            Mod:init(Host, gen_mod:set_opt(hosts, MyHosts, Opts)),
109✔
138
            RMod:init(Host, gen_mod:set_opt(hosts, MyHosts, Opts)),
109✔
139
            load_permanent_rooms(MyHosts, Host, Opts);
109✔
140
        Err ->
141
            Err
×
142
    end.
143

144
stop(Host) ->
145
    ejabberd_hooks:delete(remove_user, Host, ?MODULE,
109✔
146
                          remove_user, 50),
147
    Proc = mod_muc_sup:procname(Host),
109✔
148
    supervisor:terminate_child(ejabberd_gen_mod_sup, Proc),
109✔
149
    supervisor:delete_child(ejabberd_gen_mod_sup, Proc).
109✔
150

151
-spec reload(binary(), gen_mod:opts(), gen_mod:opts()) -> ok.
152
reload(ServerHost, NewOpts, OldOpts) ->
153
    NewMod = gen_mod:db_mod(NewOpts, ?MODULE),
×
154
    NewRMod = gen_mod:ram_db_mod(NewOpts, ?MODULE),
×
155
    OldMod = gen_mod:db_mod(OldOpts, ?MODULE),
×
156
    OldRMod = gen_mod:ram_db_mod(OldOpts, ?MODULE),
×
157
    NewHosts = gen_mod:get_opt_hosts(NewOpts),
×
158
    OldHosts = gen_mod:get_opt_hosts(OldOpts),
×
159
    AddHosts = NewHosts -- OldHosts,
×
160
    DelHosts = OldHosts -- NewHosts,
×
161
    if NewMod /= OldMod ->
×
162
            NewMod:init(ServerHost, gen_mod:set_opt(hosts, NewHosts, NewOpts));
×
163
       true ->
164
            ok
×
165
    end,
166
    if NewRMod /= OldRMod ->
×
167
            NewRMod:init(ServerHost, gen_mod:set_opt(hosts, NewHosts, NewOpts));
×
168
       true ->
169
            ok
×
170
    end,
171
    lists:foreach(
×
172
      fun(I) ->
173
              ?GEN_SERVER:cast(procname(ServerHost, I),
×
174
                               {reload, AddHosts, DelHosts, NewHosts})
175
      end, lists:seq(1, misc:logical_processors())),
176
    load_permanent_rooms(AddHosts, ServerHost, NewOpts),
×
177
    shutdown_rooms(ServerHost, DelHosts, OldRMod),
×
178
    lists:foreach(
×
179
      fun(Host) ->
180
              lists:foreach(
×
181
                fun({_, _, Pid}) when node(Pid) == node() ->
182
                        mod_muc_room:config_reloaded(Pid);
×
183
                   (_) ->
184
                        ok
×
185
                end, get_online_rooms(ServerHost, Host))
186
      end, misc:intersection(NewHosts, OldHosts)).
187

188
depends(_Host, _Opts) ->
189
    [{mod_mam, soft}].
129✔
190

191
start_link(Host, I) ->
192
    Proc = procname(Host, I),
436✔
193
    ?GEN_SERVER:start_link({local, Proc}, ?MODULE, [Host, I],
436✔
194
                           ejabberd_config:fsm_limit_opts([])).
195

196
-spec procname(binary(), pos_integer() | {binary(), binary()}) -> atom().
197
procname(Host, I) when is_integer(I) ->
198
    binary_to_atom(
1,305✔
199
      <<(atom_to_binary(?MODULE, latin1))/binary, "_", Host/binary,
200
        "_", (integer_to_binary(I))/binary>>, utf8);
201
procname(Host, RoomHost) ->
202
    Cores = misc:logical_processors(),
433✔
203
    I = erlang:phash2(RoomHost, Cores) + 1,
433✔
204
    procname(Host, I).
433✔
205

206
-spec route(stanza()) -> ok.
207
route(Pkt) ->
208
    To = xmpp:get_to(Pkt),
4,799✔
209
    ServerHost = ejabberd_router:host_of_route(To#jid.lserver),
4,799✔
210
    route(Pkt, ServerHost).
4,799✔
211

212
-spec route(stanza(), binary()) -> ok.
213
route(Pkt, ServerHost) ->
214
    From = xmpp:get_from(Pkt),
4,799✔
215
    To = xmpp:get_to(Pkt),
4,799✔
216
    Host = To#jid.lserver,
4,799✔
217
    Access = mod_muc_opt:access(ServerHost),
4,799✔
218
    case acl:match_rule(ServerHost, Access, From) of
4,799✔
219
        allow ->
220
            route(Pkt, Host, ServerHost);
4,799✔
221
        deny ->
222
            Lang = xmpp:get_lang(Pkt),
×
223
            ErrText = ?T("Access denied by service policy"),
×
224
            Err = xmpp:err_forbidden(ErrText, Lang),
×
225
            ejabberd_router:route_error(Pkt, Err)
×
226
    end.
227

228
-spec route(stanza(), binary(), binary()) -> ok.
229
route(#iq{to = #jid{luser = <<"">>, lresource = <<"">>}} = IQ, _, _) ->
230
    ejabberd_router:process_iq(IQ);
450✔
231
route(#message{lang = Lang, body = Body, type = Type, from = From,
232
               to = #jid{luser = <<"">>, lresource = <<"">>}} = Pkt,
233
      Host, ServerHost) ->
234
    if Type == error ->
45✔
235
            ok;
9✔
236
       true ->
237
            AccessAdmin = mod_muc_opt:access_admin(ServerHost),
36✔
238
            case acl:match_rule(ServerHost, AccessAdmin, From) of
36✔
239
                allow ->
240
                    Msg = xmpp:get_text(Body),
×
241
                    broadcast_service_message(ServerHost, Host, Msg);
×
242
                deny ->
243
                    ErrText = ?T("Only service administrators are allowed "
36✔
244
                                 "to send service messages"),
245
                    Err = xmpp:err_forbidden(ErrText, Lang),
36✔
246
                    ejabberd_router:route_error(Pkt, Err)
36✔
247
            end
248
    end;
249
route(Pkt, Host, ServerHost) ->
250
    {Room, _, _} = jid:tolower(xmpp:get_to(Pkt)),
4,304✔
251
    case Room of
4,304✔
252
        <<"">> ->
253
            Txt = ?T("No module is handling this query"),
135✔
254
            Err = xmpp:err_service_unavailable(Txt, xmpp:get_lang(Pkt)),
135✔
255
            ejabberd_router:route_error(Pkt, Err);
135✔
256
       _ ->
257
            RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
4,169✔
258
            case RMod:find_online_room(ServerHost, Room, Host) of
4,169✔
259
                error ->
260
                    Proc = procname(ServerHost, {Room, Host}),
433✔
261
                    case whereis(Proc) of
433✔
262
                        Pid when Pid == self() ->
263
                            route_to_room(Pkt, ServerHost);
×
264
                        Pid when is_pid(Pid) ->
265
                            ?DEBUG("Routing to MUC worker ~p:~n~ts", [Proc, xmpp:pp(Pkt)]),
433✔
266
                            ?GEN_SERVER:cast(Pid, {route_to_room, Pkt});
433✔
267
                        undefined ->
268
                            ?DEBUG("MUC worker ~p is dead", [Proc]),
×
269
                            Err = xmpp:err_internal_server_error(),
×
270
                            ejabberd_router:route_error(Pkt, Err)
×
271
                    end;
272
                {ok, Pid} ->
273
                    mod_muc_room:route(Pid, Pkt)
3,736✔
274
            end
275
    end.
276

277
-spec shutdown_rooms(binary()) -> [pid()].
278
shutdown_rooms(ServerHost) ->
279
    RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
8✔
280
    Hosts = gen_mod:get_module_opt_hosts(ServerHost, mod_muc),
8✔
281
    shutdown_rooms(ServerHost, Hosts, RMod).
8✔
282

283
-spec shutdown_rooms(binary(), [binary()], module()) -> [pid()].
284
shutdown_rooms(ServerHost, Hosts, RMod) ->
285
    Rooms = [RMod:get_online_rooms(ServerHost, Host, undefined)
8✔
286
             || Host <- Hosts],
8✔
287
    lists:flatmap(
8✔
288
      fun({_, _, Pid}) when node(Pid) == node() ->
289
              mod_muc_room:shutdown(Pid),
×
290
              [Pid];
×
291
         (_) ->
292
              []
×
293
      end, lists:flatten(Rooms)).
294

295
%% This function is called by a room in three situations:
296
%% A) The owner of the room destroyed it
297
%% B) The only participant of a temporary room leaves it
298
%% C) mod_muc:stop was called, and each room is being terminated
299
%%    In this case, the mod_muc process died before the room processes
300
%%    So the message sending must be caught
301
-spec room_destroyed(binary(), binary(), pid(), binary()) -> ok.
302
room_destroyed(Host, Room, Pid, ServerHost) ->
303
    Proc = procname(ServerHost, {Room, Host}),
×
304
    ?GEN_SERVER:cast(Proc, {room_destroyed, {Room, Host}, Pid}).
×
305

306
%% @doc Create a room.
307
%% If Opts = default, the default room options are used.
308
%% Else use the passed options as defined in mod_muc_room.
309
create_room(Host, Name, From, Nick, Opts) ->
310
    ServerHost = ejabberd_router:host_of_route(Host),
×
311
    Proc = procname(ServerHost, {Name, Host}),
×
312
    ?GEN_SERVER:call(Proc, {create, Name, Host, From, Nick, Opts}).
×
313

314
%% @doc Create a room.
315
%% If Opts = default, the default room options are used.
316
%% Else use the passed options as defined in mod_muc_room.
317
create_room(Host, Name, Opts) ->
318
    ServerHost = ejabberd_router:host_of_route(Host),
×
319
    Proc = procname(ServerHost, {Name, Host}),
×
320
    ?GEN_SERVER:call(Proc, {create, Name, Host, Opts}).
×
321

322
store_room(ServerHost, Host, Name, Opts) ->
323
    store_room(ServerHost, Host, Name, Opts, undefined).
×
324

325
maybe_store_new_room(ServerHost, Host, Name, Opts) ->
326
    case {proplists:get_bool(persistent, Opts), proplists:get_value(subscribers, Opts, [])} of
×
327
        {false, []} ->
328
            {atomic, ok};
×
329
        {_, Subs} ->
330
            Changes = [{add_subscription, JID, Nick, Nodes} || {JID, Nick, Nodes} <- Subs],
×
331
            store_room(ServerHost, Host, Name, Opts, Changes)
×
332
    end.
333

334
store_room(ServerHost, Host, Name, Opts, ChangesHints) ->
335
    LServer = jid:nameprep(ServerHost),
816✔
336
    Mod = gen_mod:db_mod(LServer, ?MODULE),
816✔
337
    Mod:store_room(LServer, Host, Name, Opts, ChangesHints).
816✔
338

339
store_changes(ServerHost, Host, Name, ChangesHints) ->
340
    LServer = jid:nameprep(ServerHost),
108✔
341
    Mod = gen_mod:db_mod(LServer, ?MODULE),
108✔
342
    Mod:store_changes(LServer, Host, Name, ChangesHints).
108✔
343

344
restore_room(ServerHost, Host, Name) ->
345
    LServer = jid:nameprep(ServerHost),
418✔
346
    Mod = gen_mod:db_mod(LServer, ?MODULE),
418✔
347
    Mod:restore_room(LServer, Host, Name).
418✔
348

349
forget_room(ServerHost, Host, Name) ->
350
    LServer = jid:nameprep(ServerHost),
762✔
351
    ejabberd_hooks:run(remove_room, LServer, [LServer, Name, Host]),
762✔
352
    Mod = gen_mod:db_mod(LServer, ?MODULE),
762✔
353
    Mod:forget_room(LServer, Host, Name).
762✔
354

355
can_use_nick(_ServerHost, _Host, _JID, <<"">>) -> false;
×
356
can_use_nick(ServerHost, Host, JID, Nick) ->
357
    LServer = jid:nameprep(ServerHost),
810✔
358
    Mod = gen_mod:db_mod(LServer, ?MODULE),
810✔
359
    Mod:can_use_nick(LServer, Host, JID, Nick).
810✔
360

361
-spec find_online_room(binary(), binary()) -> {ok, pid()} | error.
362
find_online_room(Room, Host) ->
363
    ServerHost = ejabberd_router:host_of_route(Host),
×
364
    RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
×
365
    RMod:find_online_room(ServerHost, Room, Host).
×
366

367
-spec register_online_room(binary(), binary(), pid()) -> any().
368
register_online_room(Room, Host, Pid) ->
369
    ServerHost = ejabberd_router:host_of_route(Host),
×
370
    RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
×
371
    RMod:register_online_room(ServerHost, Room, Host, Pid).
×
372

373
-spec get_online_rooms(binary()) -> [{binary(), binary(), pid()}].
374
get_online_rooms(Host) ->
375
    ServerHost = ejabberd_router:host_of_route(Host),
×
376
    get_online_rooms(ServerHost, Host).
×
377

378
-spec count_online_rooms(binary()) -> non_neg_integer().
379
count_online_rooms(Host) ->
380
    ServerHost = ejabberd_router:host_of_route(Host),
×
381
    count_online_rooms(ServerHost, Host).
×
382

383
-spec register_online_user(binary(), ljid(), binary(), binary()) -> any().
384
register_online_user(ServerHost, LJID, Name, Host) ->
385
    RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
679✔
386
    RMod:register_online_user(ServerHost, LJID, Name, Host).
679✔
387

388
-spec unregister_online_user(binary(), ljid(), binary(), binary()) -> any().
389
unregister_online_user(ServerHost, LJID, Name, Host) ->
390
    RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
679✔
391
    RMod:unregister_online_user(ServerHost, LJID, Name, Host).
679✔
392

393
-spec count_online_rooms_by_user(binary(), binary(), binary()) -> non_neg_integer().
394
count_online_rooms_by_user(ServerHost, LUser, LServer) ->
395
    RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
792✔
396
    RMod:count_online_rooms_by_user(ServerHost, LUser, LServer).
792✔
397

398
-spec get_online_rooms_by_user(binary(), binary(), binary()) -> [{binary(), binary()}].
399
get_online_rooms_by_user(ServerHost, LUser, LServer) ->
400
    RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
×
401
    RMod:get_online_rooms_by_user(ServerHost, LUser, LServer).
×
402

403
%%====================================================================
404
%% gen_server callbacks
405
%%====================================================================
406
-spec init(list()) -> {ok, state()}.
407
init([Host, Worker]) ->
408
    process_flag(trap_exit, true),
436✔
409
    Opts = gen_mod:get_module_opts(Host, ?MODULE),
436✔
410
    MyHosts = gen_mod:get_opt_hosts(Opts),
436✔
411
    register_routes(Host, MyHosts, Worker),
436✔
412
    register_iq_handlers(MyHosts, Worker),
436✔
413
    {ok, #{server_host => Host, hosts => MyHosts, worker => Worker}}.
436✔
414

415
-spec handle_call(term(), {pid(), term()}, state()) ->
416
                         {reply, ok | {ok, pid()} | {error, any()}, state()} |
417
                         {stop, normal, ok, state()}.
418
handle_call(stop, _From, State) ->
419
    {stop, normal, ok, State};
×
420
handle_call({unhibernate, Room, Host, ResetHibernationTime, Opts}, _From,
421
    #{server_host := ServerHost} = State) ->
422
    RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
×
423
    {reply, do_restore_room(RMod, Host, ServerHost, Room, ResetHibernationTime, Opts), State};
×
424
handle_call({create, Room, Host, Opts}, _From,
425
            #{server_host := ServerHost} = State) ->
426
    ?DEBUG("MUC: create new room '~ts'~n", [Room]),
×
427
    NewOpts = case Opts of
×
428
                  default -> mod_muc_opt:default_room_options(ServerHost);
×
429
                  _ -> Opts
×
430
              end,
431
    RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
×
432
    case start_room(RMod, Host, ServerHost, Room, NewOpts) of
×
433
        {ok, _} ->
434
            maybe_store_new_room(ServerHost, Host, Room, NewOpts),
×
435
            ejabberd_hooks:run(create_room, ServerHost, [ServerHost, Room, Host]),
×
436
            {reply, ok, State};
×
437
        Err ->
438
            {reply, Err, State}
×
439
    end;
440
handle_call({create, Room, Host, From, Nick, Opts}, _From,
441
            #{server_host := ServerHost} = State) ->
442
    ?DEBUG("MUC: create new room '~ts'~n", [Room]),
×
443
    NewOpts = case Opts of
×
444
                  default -> mod_muc_opt:default_room_options(ServerHost);
×
445
                  _ -> Opts
×
446
              end,
447
    RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
×
448
    case start_room(RMod, Host, ServerHost, Room, NewOpts, From, Nick) of
×
449
        {ok, _} ->
450
            maybe_store_new_room(ServerHost, Host, Room, NewOpts),
×
451
            ejabberd_hooks:run(create_room, ServerHost, [ServerHost, Room, Host]),
×
452
            {reply, ok, State};
×
453
        Err ->
454
            {reply, Err, State}
×
455
    end.
456

457
-spec handle_cast(term(), state()) -> {noreply, state()}.
458
handle_cast({route_to_room, Packet}, #{server_host := ServerHost} = State) ->
459
    try route_to_room(Packet, ServerHost)
433✔
460
    catch
461
        Class:Reason:StackTrace ->
462
            ?ERROR_MSG("Failed to route packet:~n~ts~n** ~ts",
×
463
                       [xmpp:pp(Packet),
464
                        misc:format_exception(2, Class, Reason, StackTrace)])
×
465
    end,
466
    {noreply, State};
433✔
467
handle_cast({room_destroyed, {Room, Host}, Pid},
468
            #{server_host := ServerHost} = State) ->
469
    RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
418✔
470
    RMod:unregister_online_room(ServerHost, Room, Host, Pid),
418✔
471
    {noreply, State};
418✔
472
handle_cast({reload, AddHosts, DelHosts, NewHosts},
473
            #{server_host := ServerHost, worker := Worker} = State) ->
474
    register_routes(ServerHost, AddHosts, Worker),
×
475
    register_iq_handlers(AddHosts, Worker),
×
476
    unregister_routes(DelHosts, Worker),
×
477
    unregister_iq_handlers(DelHosts, Worker),
×
478
    {noreply, State#{hosts => NewHosts}};
×
479
handle_cast(Msg, State) ->
480
    ?WARNING_MSG("Unexpected cast: ~p", [Msg]),
×
481
    {noreply, State}.
×
482

483
-spec handle_info(term(), state()) -> {noreply, state()}.
484
handle_info({route, Packet}, #{server_host := ServerHost} = State) ->
485
    %% We can only receive the packet here from other nodes
486
    %% where mod_muc is not loaded. Such configuration
487
    %% is *highly* discouraged
488
    try route(Packet, ServerHost)
×
489
    catch
490
        Class:Reason:StackTrace ->
491
            ?ERROR_MSG("Failed to route packet:~n~ts~n** ~ts",
×
492
                       [xmpp:pp(Packet),
493
                        misc:format_exception(2, Class, Reason, StackTrace)])
×
494
    end,
495
    {noreply, State};
×
496
handle_info({room_destroyed, {Room, Host}, Pid}, State) ->
497
    %% For backward compat
498
    handle_cast({room_destroyed, {Room, Host}, Pid}, State);
×
499
handle_info({'DOWN', _Ref, process, Pid, _Reason},
500
            #{server_host := ServerHost} = State) ->
501
    RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
418✔
502
    case RMod:find_online_room_by_pid(ServerHost, Pid) of
418✔
503
        {ok, Room, Host} ->
504
            handle_cast({room_destroyed, {Room, Host}, Pid}, State);
418✔
505
        _ ->
506
            {noreply, State}
×
507
    end;
508
handle_info(Info, State) ->
509
    ?ERROR_MSG("Unexpected info: ~p", [Info]),
×
510
    {noreply, State}.
×
511

512
-spec terminate(term(), state()) -> any().
513
terminate(_Reason, #{hosts := Hosts, worker := Worker}) ->
514
    unregister_routes(Hosts, Worker),
436✔
515
    unregister_iq_handlers(Hosts, Worker).
436✔
516

517
-spec code_change(term(), state(), term()) -> {ok, state()}.
518
code_change(_OldVsn, State, _Extra) -> {ok, State}.
×
519

520
%%--------------------------------------------------------------------
521
%%% Internal functions
522
%%--------------------------------------------------------------------
523
-spec register_iq_handlers([binary()], pos_integer()) -> ok.
524
register_iq_handlers(Hosts, 1) ->
525
    %% Only register handlers on first worker
526
    lists:foreach(
109✔
527
      fun(Host) ->
528
              gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_REGISTER,
109✔
529
                                            ?MODULE, process_register),
530
              gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_VCARD,
109✔
531
                                            ?MODULE, process_vcard),
532
              gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_MUCSUB,
109✔
533
                                            ?MODULE, process_mucsub),
534
              gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_MUC_UNIQUE,
109✔
535
                                            ?MODULE, process_muc_unique),
536
              gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO,
109✔
537
                                            ?MODULE, process_disco_info),
538
              gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS,
109✔
539
                                            ?MODULE, process_disco_items)
540
      end, Hosts);
541
register_iq_handlers(_, _) ->
542
    ok.
327✔
543

544
-spec unregister_iq_handlers([binary()], pos_integer()) -> ok.
545
unregister_iq_handlers(Hosts, 1) ->
546
    %% Only unregister handlers on first worker
547
    lists:foreach(
109✔
548
      fun(Host) ->
549
              gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_REGISTER),
109✔
550
              gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_VCARD),
109✔
551
              gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_MUCSUB),
109✔
552
              gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_MUC_UNIQUE),
109✔
553
              gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO),
109✔
554
              gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS)
109✔
555
      end, Hosts);
556
unregister_iq_handlers(_, _) ->
557
    ok.
327✔
558

559
-spec register_routes(binary(), [binary()], pos_integer()) -> ok.
560
register_routes(ServerHost, Hosts, 1) ->
561
    %% Only register routes on first worker
562
    lists:foreach(
109✔
563
      fun(Host) ->
564
              ejabberd_router:register_route(
109✔
565
                Host, ServerHost, {apply, ?MODULE, route})
566
      end, Hosts);
567
register_routes(_, _, _) ->
568
    ok.
327✔
569

570
-spec unregister_routes([binary()], pos_integer()) -> ok.
571
unregister_routes(Hosts, 1) ->
572
    %% Only unregister routes on first worker
573
    lists:foreach(
109✔
574
      fun(Host) ->
575
              ejabberd_router:unregister_route(Host)
109✔
576
      end, Hosts);
577
unregister_routes(_, _) ->
578
    ok.
327✔
579

580
%% Function copied from mod_muc_room.erl
581
-spec extract_password(presence() | iq()) -> binary() | false.
582
extract_password(#presence{} = Pres) ->
583
    case xmpp:get_subtag(Pres, #muc{}) of
400✔
584
        #muc{password = Password} when is_binary(Password) ->
585
            Password;
×
586
        _ ->
587
            false
400✔
588
    end;
589
extract_password(#iq{} = IQ) ->
590
    case xmpp:get_subtag(IQ, #muc_subscribe{}) of
18✔
591
        #muc_subscribe{password = Password} when Password /= <<"">> ->
592
            Password;
×
593
        _ ->
594
            false
18✔
595
    end.
596

597
-spec unhibernate_room(binary(), binary(), binary()) -> {ok, pid()} | {error, notfound | db_failure | term()}.
598
unhibernate_room(ServerHost, Host, Room) ->
599
    unhibernate_room(ServerHost, Host, Room, true).
18✔
600

601
-spec unhibernate_room(binary(), binary(), binary(), boolean()) -> {ok, pid()} | {error, notfound | db_failure | term()}.
602
unhibernate_room(ServerHost, Host, Room, ResetHibernationTime) ->
603
    RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
18✔
604
    case RMod:find_online_room(ServerHost, Room, Host) of
18✔
605
        error ->
606
            Mod = gen_mod:db_mod(ServerHost, ?MODULE),
×
607
            case Mod:restore_room(ServerHost, Host, Room) of
×
608
                    error ->
609
                        {error, notfound};
×
610
                {error, _} = Err ->
611
                    Err;
×
612
                Opts ->
613
                        Proc = procname(ServerHost, {Room, Host}),
×
614
                        ?GEN_SERVER:call(Proc, {unhibernate, Room, Host, ResetHibernationTime, Opts}, 20000)
×
615
            end;
616
        {ok, _} = R2 -> R2
18✔
617
    end.
618

619
-spec route_to_room(stanza(), binary()) -> ok.
620
route_to_room(Packet, ServerHost) ->
621
    From = xmpp:get_from(Packet),
433✔
622
    To = xmpp:get_to(Packet),
433✔
623
    {Room, Host, Nick} = jid:tolower(To),
433✔
624
    RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
433✔
625
    case RMod:find_online_room(ServerHost, Room, Host) of
433✔
626
        error ->
627
            case should_start_room(Packet) of
433✔
628
                false ->
629
                    Lang = xmpp:get_lang(Packet),
15✔
630
                    ErrText = ?T("Conference room does not exist"),
15✔
631
                    Err = xmpp:err_item_not_found(ErrText, Lang),
15✔
632
                    ejabberd_router:route_error(Packet, Err);
15✔
633
                StartType ->
634
                    case load_room(RMod, Host, ServerHost, Room, true) of
418✔
635
                        {error, notfound} when StartType == start ->
636
                            case check_create_room(ServerHost, Host, Room, From) of
418✔
637
                                true ->
638
                                    Pass = extract_password(Packet),
418✔
639
                                    case start_new_room(RMod, Host, ServerHost, Room, Pass, From, Nick) of
418✔
640
                                        {ok, Pid} ->
641
                                            mod_muc_room:route(Pid, Packet);
418✔
642
                                        _Err ->
643
                                            Err = xmpp:err_internal_server_error(),
×
644
                                            ejabberd_router:route_error(Packet, Err)
×
645
                                    end;
646
                                false ->
647
                                    Lang = xmpp:get_lang(Packet),
×
648
                                    ErrText = ?T("Room creation is denied by service policy"),
×
649
                                    Err = xmpp:err_forbidden(ErrText, Lang),
×
650
                                    ejabberd_router:route_error(Packet, Err)
×
651
                            end;
652
                        {error, notfound} ->
653
                            Lang = xmpp:get_lang(Packet),
×
654
                            ErrText = ?T("Conference room does not exist"),
×
655
                            Err = xmpp:err_item_not_found(ErrText, Lang),
×
656
                            ejabberd_router:route_error(Packet, Err);
×
657
                        {error, _} ->
658
                            Err = xmpp:err_internal_server_error(),
×
659
                            ejabberd_router:route_error(Packet, Err);
×
660
                        {ok, Pid2} ->
661
                            mod_muc_room:route(Pid2, Packet)
×
662
                    end
663
            end;
664
        {ok, Pid} ->
665
            mod_muc_room:route(Pid, Packet)
×
666
    end.
667

668
-spec process_vcard(iq()) -> iq().
669
process_vcard(#iq{type = get, to = To, lang = Lang, sub_els = [#vcard_temp{}]} = IQ) ->
670
    ServerHost = ejabberd_router:host_of_route(To#jid.lserver),
9✔
671
    VCard = case mod_muc_opt:vcard(ServerHost) of
9✔
672
                undefined ->
673
                    #vcard_temp{fn = <<"ejabberd/mod_muc">>,
×
674
                                url = ejabberd_config:get_uri(),
675
                                desc = misc:get_descr(Lang, ?T("ejabberd MUC module"))};
676
                V ->
677
                    V
9✔
678
            end,
679
    xmpp:make_iq_result(IQ, VCard);
9✔
680
process_vcard(#iq{type = set, lang = Lang} = IQ) ->
681
    Txt = ?T("Value 'set' of 'type' attribute is not allowed"),
18✔
682
    xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
18✔
683
process_vcard(#iq{lang = Lang} = IQ) ->
684
    Txt = ?T("No module is handling this query"),
9✔
685
    xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)).
9✔
686

687
-spec process_register(iq()) -> iq().
688
process_register(IQ) ->
689
    case process_iq_register(IQ) of
126✔
690
        {result, Result} ->
691
            xmpp:make_iq_result(IQ, Result);
117✔
692
        {error, Err} ->
693
            xmpp:make_error(IQ, Err)
9✔
694
    end.
695

696
-spec process_iq_register(iq()) -> {result, register()} | {error, stanza_error()}.
697
process_iq_register(#iq{type = Type, from = From, to = To, lang = Lang,
698
                     sub_els = [El = #register{}]}) ->
699
    Host = To#jid.lserver,
126✔
700
    RegisterDestination = jid:encode(To),
126✔
701
    ServerHost = ejabberd_router:host_of_route(Host),
126✔
702
    AccessRegister = mod_muc_opt:access_register(ServerHost),
126✔
703
    case acl:match_rule(ServerHost, AccessRegister, From) of
126✔
704
        allow ->
705
            case Type of
126✔
706
                get ->
707
                    {result, iq_get_register_info(ServerHost, RegisterDestination, From, Lang)};
72✔
708
                set ->
709
                    process_iq_register_set(ServerHost, RegisterDestination, From, El, Lang)
54✔
710
            end;
711
        deny ->
712
            ErrText = ?T("Access denied by service policy"),
×
713
            Err = xmpp:err_forbidden(ErrText, Lang),
×
714
            {error, Err}
×
715
    end.
716

717
-spec process_disco_info(iq()) -> iq().
718
process_disco_info(#iq{type = set, lang = Lang} = IQ) ->
719
    Txt = ?T("Value 'set' of 'type' attribute is not allowed"),
18✔
720
    xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
18✔
721
process_disco_info(#iq{type = get, from = From, to = To, lang = Lang,
722
                       sub_els = [#disco_info{node = <<"">>}]} = IQ) ->
723
    ServerHost = ejabberd_router:host_of_route(To#jid.lserver),
18✔
724
    RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
18✔
725
    AccessRegister = mod_muc_opt:access_register(ServerHost),
18✔
726
    X = ejabberd_hooks:run_fold(disco_info, ServerHost, [],
18✔
727
                                [ServerHost, ?MODULE, <<"">>, Lang]),
728
    MAMFeatures = case gen_mod:is_loaded(ServerHost, mod_mam) of
18✔
729
                      true -> [?NS_MAM_TMP, ?NS_MAM_0, ?NS_MAM_1, ?NS_MAM_2];
18✔
730
                      false -> []
×
731
                  end,
732
    OccupantIdFeatures = case gen_mod:is_loaded(ServerHost, mod_muc_occupantid) of
18✔
733
                      true -> [?NS_OCCUPANT_ID];
18✔
734
                      false -> []
×
735
                  end,
736
    RSMFeatures = case RMod:rsm_supported() of
18✔
737
                      true -> [?NS_RSM];
6✔
738
                      false -> []
12✔
739
                  end,
740
    RegisterFeatures = case acl:match_rule(ServerHost, AccessRegister, From) of
18✔
741
                           allow -> [?NS_REGISTER];
18✔
742
                           deny -> []
×
743
                       end,
744
    Features = [?NS_DISCO_INFO, ?NS_DISCO_ITEMS,
18✔
745
                ?NS_MUC, ?NS_VCARD, ?NS_MUCSUB, ?NS_MUC_UNIQUE,
746
                ?NS_MUC_STABLE_ID
747
                | RegisterFeatures ++ RSMFeatures ++ MAMFeatures ++ OccupantIdFeatures],
748
    Name = mod_muc_opt:name(ServerHost),
18✔
749
    Identity = #identity{category = <<"conference">>,
18✔
750
                         type = <<"text">>,
751
                         name = translate:translate(Lang, Name)},
752
    xmpp:make_iq_result(
18✔
753
      IQ, #disco_info{features = Features,
754
                      identities = [Identity],
755
                      xdata = X});
756
process_disco_info(#iq{type = get, lang = Lang,
757
                       sub_els = [#disco_info{}]} = IQ) ->
758
    xmpp:make_error(IQ, xmpp:err_item_not_found(?T("Node not found"), Lang));
9✔
759
process_disco_info(#iq{lang = Lang} = IQ) ->
760
    Txt = ?T("No module is handling this query"),
9✔
761
    xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)).
9✔
762

763
-spec process_disco_items(iq()) -> iq().
764
process_disco_items(#iq{type = set, lang = Lang} = IQ) ->
765
    Txt = ?T("Value 'set' of 'type' attribute is not allowed"),
18✔
766
    xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
18✔
767
process_disco_items(#iq{type = get, from = From, to = To, lang = Lang,
768
                        sub_els = [#disco_items{node = Node, rsm = RSM}]} = IQ) ->
769
    Host = To#jid.lserver,
36✔
770
    ServerHost = ejabberd_router:host_of_route(Host),
36✔
771
    MaxRoomsDiscoItems = mod_muc_opt:max_rooms_discoitems(ServerHost),
36✔
772
    case iq_disco_items(ServerHost, Host, From, Lang,
36✔
773
                        MaxRoomsDiscoItems, Node, RSM) of
774
        {error, Err} ->
775
            xmpp:make_error(IQ, Err);
×
776
        {result, Result} ->
777
            xmpp:make_iq_result(IQ, Result)
36✔
778
    end;
779
process_disco_items(#iq{lang = Lang} = IQ) ->
780
    Txt = ?T("No module is handling this query"),
9✔
781
    xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)).
9✔
782

783
-spec process_muc_unique(iq()) -> iq().
784
process_muc_unique(#iq{type = set, lang = Lang} = IQ) ->
785
    Txt = ?T("Value 'set' of 'type' attribute is not allowed"),
9✔
786
    xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
9✔
787
process_muc_unique(#iq{from = From, type = get,
788
                       sub_els = [#muc_unique{}]} = IQ) ->
789
    Name = str:sha(term_to_binary([From, erlang:timestamp(),
9✔
790
                                      p1_rand:get_string()])),
791
    xmpp:make_iq_result(IQ, #muc_unique{name = Name}).
9✔
792

793
-spec process_mucsub(iq()) -> iq().
794
process_mucsub(#iq{type = set, lang = Lang} = IQ) ->
795
    Txt = ?T("Value 'set' of 'type' attribute is not allowed"),
18✔
796
    xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
18✔
797
process_mucsub(#iq{type = get, from = From, to = To, lang = Lang,
798
                   sub_els = [#muc_subscriptions{}]} = IQ) ->
799
    Host = To#jid.lserver,
9✔
800
    ServerHost = ejabberd_router:host_of_route(Host),
9✔
801
    case get_subscribed_rooms(ServerHost, Host, From) of
9✔
802
        {ok, Subs} ->
803
            List = [#muc_subscription{jid = JID, nick = Nick, events = Nodes}
9✔
804
                    || {JID, Nick, Nodes} <- Subs],
9✔
805
            xmpp:make_iq_result(IQ, #muc_subscriptions{list = List});
9✔
806
        {error, _} ->
807
            Txt = ?T("Database failure"),
×
808
            xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang))
×
809
    end;
810
process_mucsub(#iq{lang = Lang} = IQ) ->
811
    Txt = ?T("No module is handling this query"),
9✔
812
    xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)).
9✔
813

814
-spec should_start_room(stanza()) -> start | load | false.
815
should_start_room(#presence{type = available}) ->
816
    start;
400✔
817
should_start_room(#iq{type = T} = IQ) when T == get; T == set ->
818
    case xmpp:has_subtag(IQ, #muc_subscribe{}) orelse
18✔
819
         xmpp:has_subtag(IQ, #muc_owner{}) of
18✔
820
        true ->
821
            start;
18✔
822
        _ ->
823
            load
×
824
    end;
825
should_start_room(#message{type = T, to = #jid{lresource = <<>>}})
826
    when T == groupchat; T == normal->
827
    load;
×
828
should_start_room(#message{type = T, to = #jid{lresource = Res}})
829
    when Res /= <<>> andalso T /= groupchat andalso T /= error ->
830
    load;
×
831
should_start_room(_) ->
832
    false.
15✔
833

834
-spec check_create_room(binary(), binary(), binary(), jid()) -> boolean().
835
check_create_room(ServerHost, Host, Room, From) ->
836
    AccessCreate = mod_muc_opt:access_create(ServerHost),
418✔
837
    case acl:match_rule(ServerHost, AccessCreate, From) of
418✔
838
        allow ->
839
            case mod_muc_opt:max_room_id(ServerHost) of
418✔
840
                Max when byte_size(Room) =< Max ->
841
                    Regexp = mod_muc_opt:regexp_room_id(ServerHost),
418✔
842
                    case re:run(Room, Regexp, [{capture, none}]) of
418✔
843
                        match ->
844
                            AccessAdmin = mod_muc_opt:access_admin(ServerHost),
418✔
845
                            case acl:match_rule(ServerHost, AccessAdmin, From) of
418✔
846
                                allow ->
847
                                    true;
×
848
                                _ ->
849
                                    ejabberd_hooks:run_fold(
418✔
850
                                      check_create_room, ServerHost, true,
851
                                      [ServerHost, Room, Host])
852
                            end;
853
                        _ ->
854
                            false
×
855
                    end;
856
                _ ->
857
                    false
×
858
            end;
859
        _ ->
860
            false
×
861
    end.
862

863
-spec get_access(binary() | gen_mod:opts()) -> access().
864
get_access(ServerHost) ->
865
    Access = mod_muc_opt:access(ServerHost),
418✔
866
    AccessCreate = mod_muc_opt:access_create(ServerHost),
418✔
867
    AccessAdmin = mod_muc_opt:access_admin(ServerHost),
418✔
868
    AccessPersistent = mod_muc_opt:access_persistent(ServerHost),
418✔
869
    AccessMam = mod_muc_opt:access_mam(ServerHost),
418✔
870
    {Access, AccessCreate, AccessAdmin, AccessPersistent, AccessMam}.
418✔
871

872
-spec get_rooms(binary(), binary()) -> [#muc_room{}].
873
get_rooms(ServerHost, Host) ->
874
    Mod = gen_mod:db_mod(ServerHost, ?MODULE),
109✔
875
    Mod:get_rooms(ServerHost, Host).
109✔
876

877
-spec load_permanent_rooms([binary()], binary(), gen_mod:opts()) -> ok.
878
load_permanent_rooms(Hosts, ServerHost, Opts) ->
879
    case mod_muc_opt:preload_rooms(Opts) of
109✔
880
        true ->
881
            lists:foreach(
109✔
882
                fun(Host) ->
883
                    ?DEBUG("Loading rooms at ~ts", [Host]),
109✔
884
                    lists:foreach(
109✔
885
                        fun(R) ->
886
                            {Room, _} = R#muc_room.name_host,
×
887
                            unhibernate_room(ServerHost, Host, Room, false)
×
888
                        end, get_rooms(ServerHost, Host))
889
                end, Hosts);
890
        false ->
891
            ok
×
892
    end.
893

894
-spec load_room(module(), binary(), binary(), binary(), boolean()) ->
895
    {ok, pid()} | {error, notfound | term()}.
896
load_room(RMod, Host, ServerHost, Room, ResetHibernationTime) ->
897
    case restore_room(ServerHost, Host, Room) of
418✔
898
            error ->
899
                {error, notfound};
418✔
900
        {error, _} = Err ->
901
            Err;
×
902
        Opts ->
903
            do_restore_room(RMod, Host, ServerHost, Room, ResetHibernationTime, Opts)
×
904
    end.
905

906
do_restore_room(RMod, Host, ServerHost, Room, ResetHibernationTime, Opts) ->
907
    Mod = gen_mod:db_mod(ServerHost, mod_muc),
×
908
    case proplists:get_bool(persistent, Opts) of
×
909
        true ->
910
            ?DEBUG("Restore room: ~ts", [Room]),
×
911
            Res2 = start_room(RMod, Host, ServerHost, Room, Opts),
×
912
            case {Res2, ResetHibernationTime} of
×
913
                {{ok, _}, true} ->
914
                    NewOpts = lists:keyreplace(hibernation_time, 1, Opts, {hibernation_time, undefined}),
×
915
                    store_room(ServerHost, Host, Room, NewOpts, []);
×
916
                _ ->
917
                    ok
×
918
            end,
919
            Res2;
×
920
        _ ->
921
            ?DEBUG("Restore hibernated non-persistent room: ~ts", [Room]),
×
922
            Res = start_room(RMod, Host, ServerHost, Room, Opts),
×
923
            case erlang:function_exported(Mod, get_subscribed_rooms, 3) of
×
924
                true ->
925
                    ok;
×
926
                _ ->
927
                    forget_room(ServerHost, Host, Room)
×
928
            end,
929
            Res
×
930
    end.
931

932
start_new_room(RMod, Host, ServerHost, Room, Pass, From, Nick) ->
933
    ?DEBUG("Open new room: ~ts", [Room]),
418✔
934
    DefRoomOpts = mod_muc_opt:default_room_options(ServerHost),
418✔
935
    DefRoomOpts2 = add_password_options(Pass, DefRoomOpts),
418✔
936
    start_room(RMod, Host, ServerHost, Room, DefRoomOpts2, From, Nick).
418✔
937

938
add_password_options(false, DefRoomOpts) ->
939
    DefRoomOpts;
418✔
940
add_password_options(<<>>, DefRoomOpts) ->
941
    DefRoomOpts;
×
942
add_password_options(Pass, DefRoomOpts) when is_binary(Pass) ->
943
    O2 = lists:keystore(password, 1, DefRoomOpts, {password, Pass}),
×
944
    lists:keystore(password_protected, 1, O2, {password_protected, true}).
×
945

946
start_room(Mod, Host, ServerHost, Room, DefOpts) ->
947
    Access = get_access(ServerHost),
×
948
    HistorySize = mod_muc_opt:history_size(ServerHost),
×
949
    QueueType = mod_muc_opt:queue_type(ServerHost),
×
950
    RoomShaper = mod_muc_opt:room_shaper(ServerHost),
×
951
    start_room(Mod, Host, ServerHost, Access, Room, HistorySize,
×
952
               RoomShaper, DefOpts, QueueType).
953

954
start_room(Mod, Host, ServerHost, Room, DefOpts, Creator, Nick) ->
955
    Access = get_access(ServerHost),
418✔
956
    HistorySize = mod_muc_opt:history_size(ServerHost),
418✔
957
    QueueType = mod_muc_opt:queue_type(ServerHost),
418✔
958
    RoomShaper = mod_muc_opt:room_shaper(ServerHost),
418✔
959
    start_room(Mod, Host, ServerHost, Access, Room,
418✔
960
               HistorySize, RoomShaper,
961
               Creator, Nick, DefOpts, QueueType).
962

963
start_room(Mod, Host, ServerHost, Access, Room,
964
           HistorySize, RoomShaper, DefOpts, QueueType) ->
965
    case mod_muc_room:start(Host, ServerHost, Access, Room,
×
966
                            HistorySize, RoomShaper, DefOpts, QueueType) of
967
        {ok, Pid} ->
968
            erlang:monitor(process, Pid),
×
969
            Mod:register_online_room(ServerHost, Room, Host, Pid),
×
970
            {ok, Pid};
×
971
        Err ->
972
            Err
×
973
    end.
974

975
start_room(Mod, Host, ServerHost, Access, Room, HistorySize,
976
           RoomShaper, Creator, Nick, DefOpts, QueueType) ->
977
    case mod_muc_room:start(Host, ServerHost, Access, Room,
418✔
978
                            HistorySize, RoomShaper,
979
                            Creator, Nick, DefOpts, QueueType) of
980
        {ok, Pid} ->
981
            erlang:monitor(process, Pid),
418✔
982
            Mod:register_online_room(ServerHost, Room, Host, Pid),
418✔
983
            {ok, Pid};
418✔
984
        Err ->
985
            Err
×
986
    end.
987

988
-spec iq_disco_items(binary(), binary(), jid(), binary(), integer(), binary(),
989
                     rsm_set() | undefined) ->
990
                            {result, disco_items()} | {error, stanza_error()}.
991
iq_disco_items(ServerHost, Host, From, Lang, MaxRoomsDiscoItems, Node, RSM)
992
  when Node == <<"">>; Node == <<"nonemptyrooms">>; Node == <<"emptyrooms">> ->
993
    Count = count_online_rooms(ServerHost, Host),
36✔
994
    Query = if Node == <<"">>, RSM == undefined, Count > MaxRoomsDiscoItems ->
36✔
995
                    {only_non_empty, From, Lang};
×
996
               Node == <<"nonemptyrooms">> ->
997
                    {only_non_empty, From, Lang};
×
998
               Node == <<"emptyrooms">> ->
999
                    {0, From, Lang};
×
1000
               true ->
1001
                    {all, From, Lang}
36✔
1002
            end,
1003
    MaxItems = case RSM of
36✔
1004
                   undefined ->
1005
                       MaxRoomsDiscoItems;
36✔
1006
                   #rsm_set{max = undefined} ->
1007
                       MaxRoomsDiscoItems;
×
1008
                   #rsm_set{max = Max} when Max > MaxRoomsDiscoItems ->
1009
                       MaxRoomsDiscoItems;
×
1010
                   #rsm_set{max = Max} ->
1011
                       Max
×
1012
               end,
1013
    RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
36✔
1014
    RsmSupported = RMod:rsm_supported(),
36✔
1015
    GetRooms =
36✔
1016
        fun GetRooms(AccInit, Rooms) ->
1017
            {Items, HitMax, DidSkip, Last, First} = lists:foldr(
39✔
1018
                fun(_, {Acc, _, Skip, F, L}) when length(Acc) >= MaxItems ->
1019
                    {Acc, true, Skip, F, L};
×
1020
                   ({RN, _, _} = R, {Acc, _, Skip, F, _}) ->
1021
                       F2 = if F == undefined -> RN; true -> F end,
69✔
1022
                       case get_room_disco_item(R, Query) of
69✔
1023
                           {ok, Item} -> {[Item | Acc], false, Skip, F2, RN};
54✔
1024
                           {error, _} -> {Acc, false, true, F2, RN}
15✔
1025
                       end
1026
                end, AccInit, Rooms),
1027
            if RsmSupported andalso not HitMax andalso DidSkip ->
39✔
1028
                RSM2 = case RSM of
3✔
1029
                           #rsm_set{'after' = undefined, before = undefined} ->
1030
                               #rsm_set{max = MaxItems - length(Items), 'after' = Last};
×
1031
                           #rsm_set{'after' = undefined} ->
1032
                               #rsm_set{max = MaxItems - length(Items), 'before' = First};
×
1033
                           _ ->
1034
                               #rsm_set{max = MaxItems - length(Items), 'after' = Last}
3✔
1035
                       end,
1036
                GetRooms({Items, false, false, undefined, undefined},
3✔
1037
                         get_online_rooms(ServerHost, Host, RSM2));
1038
                true -> {Items, HitMax}
36✔
1039
            end
1040
        end,
1041

1042
    {Items, HitMax} =
36✔
1043
        GetRooms({[], false, false, undefined, undefined},
1044
                 get_online_rooms(ServerHost, Host, RSM)),
1045
    ResRSM = case Items of
36✔
1046
                 [_|_] when RSM /= undefined; HitMax ->
1047
                     #disco_item{jid = #jid{luser = First}} = hd(Items),
×
1048
                     #disco_item{jid = #jid{luser = Last}} = lists:last(Items),
×
1049
                     #rsm_set{first = #rsm_first{data = First},
×
1050
                              last = Last,
1051
                              count = Count};
1052
                 [] when RSM /= undefined ->
1053
                     #rsm_set{count = Count};
×
1054
                 _ ->
1055
                     undefined
36✔
1056
             end,
1057
    {result, #disco_items{node = Node, items = Items, rsm = ResRSM}};
36✔
1058
iq_disco_items(_ServerHost, _Host, _From, Lang, _MaxRoomsDiscoItems, _Node, _RSM) ->
1059
    {error, xmpp:err_item_not_found(?T("Node not found"), Lang)}.
×
1060

1061
-spec get_room_disco_item({binary(), binary(), pid()},
1062
                          {mod_muc_room:disco_item_filter(),
1063
                           jid(), binary()}) -> {ok, disco_item()} |
1064
                                                {error, timeout | notfound}.
1065
get_room_disco_item({Name, Host, Pid}, {Filter, JID, Lang}) ->
1066
    case mod_muc_room:get_disco_item(Pid, Filter, JID, Lang) of
69✔
1067
        {ok, Desc} ->
1068
            RoomJID = jid:make(Name, Host),
54✔
1069
            {ok, #disco_item{jid = RoomJID, name = Desc}};
54✔
1070
        {error, _} = Err ->
1071
            Err
15✔
1072
    end.
1073

1074
-spec get_subscribed_rooms(binary(), jid()) -> {ok, [{jid(), binary(), [binary()]}]} | {error, any()}.
1075
get_subscribed_rooms(Host, User) ->
1076
    ServerHost = ejabberd_router:host_of_route(Host),
6✔
1077
    get_subscribed_rooms(ServerHost, Host, User).
6✔
1078

1079
-spec get_subscribed_rooms(binary(), binary(), jid()) ->
1080
                           {ok, [{jid(), binary(), [binary()]}]} | {error, any()}.
1081
get_subscribed_rooms(ServerHost, Host, From) ->
1082
    LServer = jid:nameprep(ServerHost),
15✔
1083
    Mod = gen_mod:db_mod(LServer, ?MODULE),
15✔
1084
    BareFrom = jid:remove_resource(From),
15✔
1085
    case erlang:function_exported(Mod, get_subscribed_rooms, 3) of
15✔
1086
        false ->
1087
            Rooms = get_online_rooms(ServerHost, Host),
9✔
1088
            {ok, lists:flatmap(
9✔
1089
                   fun({Name, _, Pid}) when Pid == self() ->
1090
                       USR = jid:split(BareFrom),
×
1091
                       case erlang:get(muc_subscribers) of
×
1092
                           #{USR := #subscriber{nodes = Nodes, nick = Nick}} ->
1093
                               [{jid:make(Name, Host), Nick, Nodes}];
×
1094
                           _ ->
1095
                               []
×
1096
                       end;
1097
                       ({Name, _, Pid}) ->
1098
                           case mod_muc_room:is_subscribed(Pid, BareFrom) of
21✔
1099
                               {true, Nick, Nodes} ->
1100
                                   [{jid:make(Name, Host), Nick, Nodes}];
21✔
1101
                               false -> []
×
1102
                           end;
1103
                      (_) ->
1104
                           []
×
1105
                   end, Rooms)};
1106
        true ->
1107
            Mod:get_subscribed_rooms(LServer, Host, BareFrom)
6✔
1108
    end.
1109

1110
get_nick(ServerHost, Host, From) ->
1111
    LServer = jid:nameprep(ServerHost),
126✔
1112
    Mod = gen_mod:db_mod(LServer, ?MODULE),
126✔
1113
    Mod:get_nick(LServer, Host, From).
126✔
1114

1115
-spec get_register_nick(binary(), binary(), jid()) -> binary() | error.
1116
get_register_nick(ServerHost, Host, From) ->
1117
    get_nick(ServerHost, Host, From).
54✔
1118

1119
-spec get_register_nicks(binary(), binary()) -> [{binary(), binary(), binary()}].
1120
get_register_nicks(ServerHost, Host) ->
1121
    LServer = jid:nameprep(ServerHost),
×
1122
    Mod = gen_mod:db_mod(LServer, ?MODULE),
×
1123
    Mod:get_nicks(LServer, Host).
×
1124

1125
iq_get_register_info(ServerHost, Host, From, Lang) ->
1126
    {Nick, Registered} = case get_nick(ServerHost, Host, From) of
72✔
1127
                             error -> {<<"">>, false};
27✔
1128
                             N -> {N, true}
45✔
1129
                         end,
1130
    Title = <<(translate:translate(
72✔
1131
                 Lang, ?T("Nickname Registration at ")))/binary, Host/binary>>,
1132
    Inst = translate:translate(Lang, ?T("Enter nickname you want to register")),
72✔
1133
    Fields = muc_register:encode([{roomnick, Nick}], Lang),
72✔
1134
    X = #xdata{type = form, title = Title,
72✔
1135
               instructions = [Inst], fields = Fields},
1136
    #register{nick = Nick,
72✔
1137
              registered = Registered,
1138
              instructions =
1139
                  translate:translate(
1140
                    Lang, ?T("You need a client that supports x:data "
1141
                             "to register the nickname")),
1142
              xdata = X}.
1143

1144
set_nick(ServerHost, Host, From, Nick) ->
1145
    case ejabberd_hooks:run_fold(registering_nickmuc,
224✔
1146
                                 ServerHost,
1147
                                 true,
1148
                                 [ServerHost, Host, From, Nick]) of
1149
        false ->
1150
            {atomic, false};
×
1151
        true ->
1152
            LServer = jid:nameprep(ServerHost),
224✔
1153
            Mod = gen_mod:db_mod(LServer, ?MODULE),
224✔
1154
            Mod:set_nick(LServer, Host, From, Nick)
224✔
1155
    end.
1156

1157
set_nick(ServerHost, From, Nick) ->
1158
    lists:foreach(
170✔
1159
      fun(MucHost) ->
1160
              set_nick(ServerHost, MucHost, From, Nick)
170✔
1161
      end,
1162
      gen_mod:get_module_opt_hosts(ServerHost, mod_muc)).
1163

1164
iq_set_register_info(ServerHost, Host, From, Nick,
1165
                     Lang) ->
1166
    OldNick = case mod_muc:get_register_nick(ServerHost, Host, From) of
54✔
1167
        error -> <<"">>;
27✔
1168
        ON when is_binary(ON) -> ON
27✔
1169
    end,
1170
    case set_nick(ServerHost, Host, From, Nick) of
54✔
1171
      {atomic, ok} ->
1172
            ejabberd_hooks:run(registered_nickmuc,
45✔
1173
                                 ServerHost,
1174
                                 [ServerHost, Host, From, Nick, OldNick]),
1175
            {result, undefined};
45✔
1176
      {atomic, false} ->
1177
          ErrText = ?T("That nickname is registered by another person"),
9✔
1178
          {error, xmpp:err_conflict(ErrText, Lang)};
9✔
1179
      _ ->
1180
          Txt = ?T("Database failure"),
×
1181
          {error, xmpp:err_internal_server_error(Txt, Lang)}
×
1182
    end.
1183

1184
process_iq_register_set(ServerHost, Host, From,
1185
                        #register{remove = true}, Lang) ->
1186
    iq_set_register_info(ServerHost, Host, From, <<"">>, Lang);
9✔
1187
process_iq_register_set(_ServerHost, _Host, _From,
1188
                        #register{xdata = #xdata{type = cancel}}, _Lang) ->
1189
    {result, undefined};
×
1190
process_iq_register_set(ServerHost, Host, From,
1191
                        #register{nick = Nick, xdata = XData}, Lang) ->
1192
    case XData of
45✔
1193
        #xdata{type = submit, fields = Fs} ->
1194
            try
45✔
1195
                Options = muc_register:decode(Fs),
45✔
1196
                N = proplists:get_value(roomnick, Options),
45✔
1197
                iq_set_register_info(ServerHost, Host, From, N, Lang)
45✔
1198
            catch _:{muc_register, Why} ->
1199
                    ErrText = muc_register:format_error(Why),
×
1200
                    {error, xmpp:err_bad_request(ErrText, Lang)}
×
1201
            end;
1202
        #xdata{} ->
1203
            Txt = ?T("Incorrect data form"),
×
1204
            {error, xmpp:err_bad_request(Txt, Lang)};
×
1205
        _ when is_binary(Nick), Nick /= <<"">> ->
1206
            iq_set_register_info(ServerHost, Host, From, Nick, Lang);
×
1207
        _ ->
1208
            ErrText = ?T("You must fill in field \"Nickname\" in the form"),
×
1209
            {error, xmpp:err_not_acceptable(ErrText, Lang)}
×
1210
    end.
1211

1212
-spec broadcast_service_message(binary(), binary(), binary()) -> ok.
1213
broadcast_service_message(ServerHost, Host, Msg) ->
1214
    lists:foreach(
×
1215
      fun({_, _, Pid}) ->
1216
              mod_muc_room:service_message(Pid, Msg)
×
1217
      end, get_online_rooms(ServerHost, Host)).
1218

1219
-spec get_online_rooms(binary(), binary()) -> [{binary(), binary(), pid()}].
1220
get_online_rooms(ServerHost, Host) ->
1221
    get_online_rooms(ServerHost, Host, undefined).
26✔
1222

1223
-spec get_online_rooms(binary(), binary(), undefined | rsm_set()) ->
1224
                          [{binary(), binary(), pid()}].
1225
get_online_rooms(ServerHost, Host, RSM) ->
1226
    RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
65✔
1227
    RMod:get_online_rooms(ServerHost, Host, RSM).
65✔
1228

1229
-spec count_online_rooms(binary(), binary()) -> non_neg_integer().
1230
count_online_rooms(ServerHost, Host) ->
1231
    RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
36✔
1232
    RMod:count_online_rooms(ServerHost, Host).
36✔
1233

1234
-spec remove_user(binary(), binary()) -> ok.
1235
remove_user(User, Server) ->
1236
    LUser = jid:nodeprep(User),
17✔
1237
    LServer = jid:nameprep(Server),
17✔
1238
    Mod = gen_mod:db_mod(LServer, ?MODULE),
17✔
1239
    case erlang:function_exported(Mod, remove_user, 2) of
17✔
1240
        true ->
1241
            Mod:remove_user(LUser, LServer);
12✔
1242
        false ->
1243
            ok
5✔
1244
    end,
1245
    JID = jid:make(User, Server),
17✔
1246
    lists:foreach(
17✔
1247
      fun(HostI) ->
1248
              catch set_nick(HostI, JID, <<"">>)
170✔
1249
      end,
1250
      ejabberd_option:hosts()),
1251
    lists:foreach(
17✔
1252
      fun(Host) ->
1253
              lists:foreach(
17✔
1254
                fun({_, _, Pid}) ->
1255
                        mod_muc_room:change_item_async(
×
1256
                          Pid, JID, affiliation, none, <<"User removed">>),
1257
                        mod_muc_room:change_item_async(
×
1258
                          Pid, JID, role, none, <<"User removed">>)
1259
                end,
1260
                get_online_rooms(LServer, Host))
1261
      end,
1262
      gen_mod:get_module_opt_hosts(LServer, mod_muc)),
1263
    ok.
17✔
1264

1265
opts_to_binary(Opts) ->
1266
    lists:flatmap(
×
1267
      fun({title, Title}) ->
1268
              [{title, iolist_to_binary(Title)}];
×
1269
         ({description, Desc}) ->
1270
              [{description, iolist_to_binary(Desc)}];
×
1271
         ({password, Pass}) ->
1272
              [{password, iolist_to_binary(Pass)}];
×
1273
         ({subject, [C|_] = Subj}) when is_integer(C), C >= 0, C =< 255 ->
1274
              [{subject, iolist_to_binary(Subj)}];
×
1275
         ({subject_author, {AuthorNick, AuthorJID}}) ->
1276
              [{subject_author, {iolist_to_binary(AuthorNick), AuthorJID}}];
×
1277
         ({subject_author, AuthorNick}) -> % ejabberd 23.04 or older
1278
              [{subject_author, {iolist_to_binary(AuthorNick), #jid{}}}];
×
1279
         ({allow_private_messages, Value}) -> % ejabberd 23.04 or older
1280
              Value2 = case Value of
×
1281
                           true -> anyone;
×
1282
                           false -> none;
×
1283
                           _ -> Value
×
1284
                       end,
1285
              [{allowpm, Value2}];
×
1286
         ({AffOrRole, Affs}) when (AffOrRole == affiliation) or (AffOrRole == role) ->
1287
              [{affiliations, lists:map(
×
1288
                               fun({{U, S, R}, Aff}) ->
1289
                                       NewAff =
×
1290
                                           case Aff of
1291
                                               {A, Reason} ->
1292
                                                   {A, iolist_to_binary(Reason)};
×
1293
                                               _ ->
1294
                                                   Aff
×
1295
                                           end,
1296
                                       {{iolist_to_binary(U),
×
1297
                                         iolist_to_binary(S),
1298
                                         iolist_to_binary(R)},
1299
                                        NewAff}
1300
                               end, Affs)}];
1301
         ({captcha_whitelist, CWList}) ->
1302
              [{captcha_whitelist, lists:map(
×
1303
                                    fun({U, S, R}) ->
1304
                                            {iolist_to_binary(U),
×
1305
                                             iolist_to_binary(S),
1306
                                             iolist_to_binary(R)}
1307
                                    end, CWList)}];
1308
         ({hats_users, HatsUsers}) ->  % Update hats definitions
1309
              case lists:keymember(hats_defs, 1, Opts) of
×
1310
                  true ->
1311
                      [{hats_users, HatsUsers}];
×
1312
                  _ ->
1313
                      {HatsDefs, HatsUsers2} =
×
1314
                          lists:foldl(fun({Jid, UriTitleList}, {Defs, Assigns}) ->
1315
                                              Defs2 =
×
1316
                                                  lists:foldl(fun({Uri, Title}, AccDef) ->
1317
                                                                      AccDef#{Uri => {Title, <<"">>}}
×
1318
                                                              end,
1319
                                                              Defs,
1320
                                                              UriTitleList),
1321
                                              Assigns2 =
×
1322
                                                  Assigns#{Jid => [ Uri || {Uri, _Title} <- UriTitleList ]},
×
1323
                                              {Defs2, Assigns2}
×
1324
                                      end,
1325
                                      {maps:new(), maps:new()},
1326
                                      HatsUsers),
1327
                      [{hats_users, maps:to_list(HatsUsers2)},
×
1328
                       {hats_defs, maps:to_list(HatsDefs)}]
1329
              end;
1330
         (Opt) ->
1331
              [Opt]
×
1332
      end, Opts).
1333

1334
export(LServer) ->
1335
    Mod = gen_mod:db_mod(LServer, ?MODULE),
×
1336
    Mod:export(LServer).
×
1337

1338
import_info() ->
1339
    [{<<"muc_room">>, 4}, {<<"muc_registered">>, 4}].
×
1340

1341
import_start(LServer, DBType) ->
1342
    Mod = gen_mod:db_mod(DBType, ?MODULE),
×
1343
    Mod:init(LServer, []).
×
1344

1345
import(LServer, {sql, _}, DBType, Tab, L) ->
1346
    Mod = gen_mod:db_mod(DBType, ?MODULE),
×
1347
    Mod:import(LServer, Tab, L).
×
1348

1349
mod_opt_type(access) ->
1350
    econf:acl();
129✔
1351
mod_opt_type(access_admin) ->
1352
    econf:acl();
129✔
1353
mod_opt_type(access_create) ->
1354
    econf:acl();
129✔
1355
mod_opt_type(access_persistent) ->
1356
    econf:acl();
129✔
1357
mod_opt_type(access_mam) ->
1358
    econf:acl();
129✔
1359
mod_opt_type(access_register) ->
1360
    econf:acl();
129✔
1361
mod_opt_type(history_size) ->
1362
    econf:non_neg_int();
129✔
1363
mod_opt_type(name) ->
1364
    econf:binary();
129✔
1365
mod_opt_type(max_room_desc) ->
1366
    econf:pos_int(infinity);
129✔
1367
mod_opt_type(max_room_id) ->
1368
    econf:pos_int(infinity);
129✔
1369
mod_opt_type(max_rooms_discoitems) ->
1370
    econf:non_neg_int();
129✔
1371
mod_opt_type(regexp_room_id) ->
1372
    econf:re([unicode]);
129✔
1373
mod_opt_type(max_room_name) ->
1374
    econf:pos_int(infinity);
129✔
1375
mod_opt_type(max_password) ->
1376
    econf:pos_int(infinity);
129✔
1377
mod_opt_type(max_captcha_whitelist) ->
1378
    econf:pos_int(infinity);
129✔
1379
mod_opt_type(max_user_conferences) ->
1380
    econf:pos_int();
129✔
1381
mod_opt_type(max_users) ->
1382
    econf:pos_int();
129✔
1383
mod_opt_type(max_users_admin_threshold) ->
1384
    econf:pos_int();
129✔
1385
mod_opt_type(max_users_presence) ->
1386
    econf:int();
129✔
1387
mod_opt_type(min_message_interval) ->
1388
    econf:number(0);
129✔
1389
mod_opt_type(min_presence_interval) ->
1390
    econf:number(0);
129✔
1391
mod_opt_type(preload_rooms) ->
1392
    econf:bool();
129✔
1393
mod_opt_type(room_shaper) ->
1394
    econf:atom();
129✔
1395
mod_opt_type(user_message_shaper) ->
1396
    econf:atom();
129✔
1397
mod_opt_type(user_presence_shaper) ->
1398
    econf:atom();
129✔
1399
mod_opt_type(cleanup_affiliations_on_start) ->
1400
    econf:bool();
129✔
1401
mod_opt_type(default_room_options) ->
1402
    econf:options(
129✔
1403
      #{allow_change_subj => econf:bool(),
1404
        allowpm =>
1405
            econf:enum([anyone, participants, moderators, none]),
1406
        allow_private_messages_from_visitors =>
1407
            econf:enum([anyone, moderators, nobody]),
1408
        allow_query_users => econf:bool(),
1409
        allow_subscription => econf:bool(),
1410
        allow_user_invites => econf:bool(),
1411
        allow_visitor_nickchange => econf:bool(),
1412
        allow_visitor_status => econf:bool(),
1413
        allow_voice_requests => econf:bool(),
1414
        anonymous => econf:bool(),
1415
        captcha_protected => econf:bool(),
1416
        description => econf:binary(),
1417
        enable_hats => econf:bool(),
1418
        lang => econf:lang(),
1419
        logging => econf:bool(),
1420
        mam => econf:bool(),
1421
        max_users => econf:pos_int(),
1422
        members_by_default => econf:bool(),
1423
        members_only => econf:bool(),
1424
        moderated => econf:bool(),
1425
        password => econf:binary(),
1426
        password_protected => econf:bool(),
1427
        persistent => econf:bool(),
1428
        presence_broadcast =>
1429
            econf:list(
1430
              econf:enum([moderator, participant, visitor])),
1431
        public => econf:bool(),
1432
        public_list => econf:bool(),
1433
        pubsub => econf:binary(),
1434
        title => econf:binary(),
1435
        vcard => econf:vcard_temp(),
1436
        vcard_xupdate => econf:binary(),
1437
        voice_request_min_interval => econf:pos_int()});
1438
mod_opt_type(db_type) ->
1439
    econf:db_type(?MODULE);
129✔
1440
mod_opt_type(ram_db_type) ->
1441
    econf:db_type(?MODULE);
129✔
1442
mod_opt_type(host) ->
1443
    econf:host();
129✔
1444
mod_opt_type(hosts) ->
1445
    econf:hosts();
129✔
1446
mod_opt_type(queue_type) ->
1447
    econf:queue_type();
129✔
1448
mod_opt_type(hibernation_timeout) ->
1449
    econf:timeout(second, infinity);
129✔
1450
mod_opt_type(vcard) ->
1451
    econf:vcard_temp().
129✔
1452

1453
mod_options(Host) ->
1454
    [{access, all},
129✔
1455
     {access_admin, none},
1456
     {access_create, all},
1457
     {access_persistent, all},
1458
     {access_mam, all},
1459
     {access_register, all},
1460
     {db_type, ejabberd_config:default_db(Host, ?MODULE)},
1461
     {ram_db_type, ejabberd_config:default_ram_db(Host, ?MODULE)},
1462
     {history_size, 20},
1463
     {host, <<"conference.", Host/binary>>},
1464
     {hosts, []},
1465
     {name, ?T("Chatrooms")},
1466
     {max_room_desc, infinity},
1467
     {max_room_id, infinity},
1468
     {max_room_name, infinity},
1469
     {max_password, infinity},
1470
     {max_captcha_whitelist, infinity},
1471
     {max_rooms_discoitems, 100},
1472
     {max_user_conferences, 100},
1473
     {max_users, 200},
1474
     {max_users_admin_threshold, 5},
1475
     {max_users_presence, 1000},
1476
     {min_message_interval, 0},
1477
     {min_presence_interval, 0},
1478
     {queue_type, ejabberd_option:queue_type(Host)},
1479
     {regexp_room_id, <<"">>},
1480
     {room_shaper, none},
1481
     {user_message_shaper, none},
1482
     {user_presence_shaper, none},
1483
     {preload_rooms, true},
1484
     {hibernation_timeout, infinity},
1485
     {vcard, undefined},
1486
     {cleanup_affiliations_on_start, false},
1487
     {default_room_options,
1488
      [{allow_change_subj,true},
1489
       {allowpm,anyone},
1490
       {allow_query_users,true},
1491
       {allow_user_invites,false},
1492
       {allow_visitor_nickchange,true},
1493
       {allow_visitor_status,true},
1494
       {anonymous,true},
1495
       {captcha_protected,false},
1496
       {lang,<<>>},
1497
       {logging,false},
1498
       {members_by_default,true},
1499
       {members_only,false},
1500
       {moderated,true},
1501
       {password_protected,false},
1502
       {persistent,false},
1503
       {public,true},
1504
       {public_list,true},
1505
       {mam,false},
1506
       {allow_subscription,false},
1507
       {password,<<>>},
1508
       {title,<<>>},
1509
       {allow_private_messages_from_visitors,anyone},
1510
       {max_users,200},
1511
       {presence_broadcast,[moderator,participant,visitor]}]}].
1512

1513
mod_doc() ->
1514
    #{desc =>
×
1515
          [?T("This module provides support for https://xmpp.org/extensions/xep-0045.html"
1516
             "[XEP-0045: Multi-User Chat]. Users can discover existing rooms, "
1517
             "join or create them. Occupants of a room can chat in public or have private chats."), "",
1518
           ?T("The MUC service allows any Jabber ID to register a nickname, so "
1519
              "nobody else can use that nickname in any room in the MUC "
1520
              "service. To register a nickname, open the Service Discovery in "
1521
              "your XMPP client and register in the MUC service."), "",
1522
           ?T("It is also possible to register a nickname in a room, so "
1523
              "nobody else can use that nickname in that room. If a nick is "
1524
              "registered in the MUC service, that nick cannot be registered in "
1525
              "any room, and vice versa: a nick that is registered in a room "
1526
              "cannot be registered at the MUC service."), "",
1527
           ?T("This module supports clustering and load balancing. One module "
1528
              "can be started per cluster node. Rooms are distributed at "
1529
              "creation time on all available MUC module instances. The "
1530
              "multi-user chat module is clustered but the rooms themselves "
1531
              "are not clustered nor fault-tolerant: if the node managing a "
1532
              "set of rooms goes down, the rooms disappear and they will be "
1533
              "recreated on an available node on first connection attempt.")],
1534
      opts =>
1535
          [{access,
1536
            #{value => ?T("AccessName"),
1537
              desc =>
1538
                  ?T("You can specify who is allowed to use the Multi-User Chat service. "
1539
                     "By default everyone is allowed to use it.")}},
1540
           {access_admin,
1541
            #{value => ?T("AccessName"),
1542
              desc =>
1543
                  ?T("This option specifies who is allowed to administrate "
1544
                     "the Multi-User Chat service. The default value is 'none', "
1545
                     "which means that only the room creator can administer "
1546
                     "their room. The administrators can send a normal message "
1547
                     "to the service JID, and it will be shown in all active "
1548
                     "rooms as a service message. The administrators can send a "
1549
                     "groupchat message to the JID of an active room, and the "
1550
                     "message will be shown in the room as a service message.")}},
1551
           {access_create,
1552
            #{value => ?T("AccessName"),
1553
              desc =>
1554
                  ?T("To configure who is allowed to create new rooms at the "
1555
                     "Multi-User Chat service, this option can be used. "
1556
                     "The default value is 'all', which means everyone is "
1557
                     "allowed to create rooms.")}},
1558
           {access_persistent,
1559
            #{value => ?T("AccessName"),
1560
              desc =>
1561
                  ?T("To configure who is allowed to modify the 'persistent' room option. "
1562
                     "The default value is 'all', which means everyone is allowed to "
1563
                     "modify that option.")}},
1564
           {access_mam,
1565
            #{value => ?T("AccessName"),
1566
              desc =>
1567
                  ?T("To configure who is allowed to modify the 'mam' room option. "
1568
                     "The default value is 'all', which means everyone is allowed to "
1569
                     "modify that option.")}},
1570
           {access_register,
1571
            #{value => ?T("AccessName"),
1572
              note => "improved in 23.10",
1573
              desc =>
1574
                  ?T("This option specifies who is allowed to register nickname "
1575
                     "within the Multi-User Chat service and rooms. The default is 'all' for "
1576
                     "backward compatibility, which means that any user is allowed "
1577
                     "to register any free nick in the MUC service and in the rooms.")}},
1578
           {db_type,
1579
            #{value => "mnesia | sql",
1580
              desc =>
1581
                  ?T("Same as top-level _`default_db`_ option, "
1582
                     "but applied to this module only.")}},
1583
           {ram_db_type,
1584
            #{value => "mnesia | sql",
1585
              desc =>
1586
                  ?T("Same as top-level _`default_ram_db`_ option, "
1587
                     "but applied to this module only.")}},
1588
           {hibernation_timeout,
1589
            #{value => "infinity | Seconds",
1590
              desc =>
1591
                  ?T("Timeout before hibernating the room process, expressed "
1592
                     "in seconds. The default value is 'infinity'.")}},
1593
           {history_size,
1594
            #{value => ?T("Size"),
1595
              desc =>
1596
                  ?T("A small history of the current discussion is sent to users "
1597
                     "when they enter the room. With this option you can define the "
1598
                     "number of history messages to keep and send to users joining the room. "
1599
                     "The value is a non-negative integer. Setting the value to '0' disables "
1600
                     "the history feature and, as a result, nothing is kept in memory. "
1601
                     "The default value is '20'. This value affects all rooms on the service. "
1602
                     "NOTE: modern XMPP clients rely on Message Archives (XEP-0313), so feel "
1603
                     "free to disable the history feature if you're only using modern clients "
1604
                     "and have _`mod_mam`_ module loaded.")}},
1605
           {host, #{desc => ?T("Deprecated. Use 'hosts' instead.")}},
1606
           {hosts,
1607
            #{value => ?T("[Host, ...]"),
1608
              desc =>
1609
                  ?T("This option defines the Jabber IDs of the service. "
1610
                     "If the 'hosts' option is not specified, the only Jabber ID will "
1611
                     "be the hostname of the virtual host with the prefix \"conference.\". "
1612
                     "The keyword '@HOST@' is replaced with the real virtual host name.")}},
1613
           {name,
1614
            #{value => "string()",
1615
              desc =>
1616
                  ?T("The value of the service name. This name is only visible in some "
1617
                     "clients that support https://xmpp.org/extensions/xep-0030.html"
1618
                     "[XEP-0030: Service Discovery]. The default is 'Chatrooms'.")}},
1619
           {max_room_desc,
1620
            #{value => ?T("Number"),
1621
              desc =>
1622
                  ?T("This option defines the maximum number of characters that "
1623
                     "Room Description can have when configuring the room. "
1624
                     "The default value is 'infinity'.")}},
1625
           {max_room_id,
1626
            #{value => ?T("Number"),
1627
              desc =>
1628
                  ?T("This option defines the maximum number of characters that "
1629
                     "Room ID can have when creating a new room. "
1630
                     "The default value is 'infinity'.")}},
1631
           {max_room_name,
1632
            #{value => ?T("Number"),
1633
              desc =>
1634
                  ?T("This option defines the maximum number of characters "
1635
                     "that Room Name can have when configuring the room. "
1636
                     "The default value is 'infinity'.")}},
1637
           {max_password,
1638
            #{value => ?T("Number"),
1639
              note => "added in 21.01",
1640
              desc =>
1641
                  ?T("This option defines the maximum number of characters "
1642
                     "that Password can have when configuring the room. "
1643
                     "The default value is 'infinity'.")}},
1644
           {max_captcha_whitelist,
1645
            #{value => ?T("Number"),
1646
              note => "added in 21.01",
1647
              desc =>
1648
                  ?T("This option defines the maximum number of characters "
1649
                     "that Captcha Whitelist can have when configuring the room. "
1650
                     "The default value is 'infinity'.")}},
1651
           {max_rooms_discoitems,
1652
            #{value => ?T("Number"),
1653
              desc =>
1654
                  ?T("When there are more rooms than this 'Number', "
1655
                     "only the non-empty ones are returned in a Service Discovery query. "
1656
                     "The default value is '100'.")}},
1657
           {max_user_conferences,
1658
            #{value => ?T("Number"),
1659
              desc =>
1660
                  ?T("This option defines the maximum number of rooms that any "
1661
                     "given user can join. The default value is '100'. This option "
1662
                     "is used to prevent possible abuses. Note that this is a soft "
1663
                     "limit: some users can sometimes join more conferences in "
1664
                     "cluster configurations.")}},
1665
           {max_users,
1666
            #{value => ?T("Number"),
1667
              desc =>
1668
                  ?T("This option defines at the service level, the maximum "
1669
                     "number of users allowed per room. It can be lowered in "
1670
                     "each room configuration but cannot be increased in "
1671
                     "individual room configuration. The default value is '200'.")}},
1672
           {max_users_admin_threshold,
1673
            #{value => ?T("Number"),
1674
              desc =>
1675
                  ?T("This option defines the number of service admins or room "
1676
                     "owners allowed to enter the room when the maximum number "
1677
                     "of allowed occupants was reached. The default limit is '5'.")}},
1678
           {max_users_presence,
1679
            #{value => ?T("Number"),
1680
              desc =>
1681
                  ?T("This option defines after how many users in the room, "
1682
                     "it is considered overcrowded. When a MUC room is considered "
1683
                     "overcrowded, presence broadcasts are limited to reduce load, "
1684
                     "traffic and excessive presence \"storm\" received by participants. "
1685
                     "The default value is '1000'.")}},
1686
           {min_message_interval,
1687
            #{value => ?T("Number"),
1688
              desc =>
1689
                  ?T("This option defines the minimum interval between two "
1690
                     "messages send by an occupant in seconds. This option "
1691
                     "is global and valid for all rooms. A decimal value can be used. "
1692
                     "When this option is not defined, message rate is not limited. "
1693
                     "This feature can be used to protect a MUC service from occupant "
1694
                     "abuses and limit number of messages that will be broadcasted by "
1695
                     "the service. A good value for this minimum message interval is '0.4' second. "
1696
                     "If an occupant tries to send messages faster, an error is send back "
1697
                     "explaining that the message has been discarded and describing the "
1698
                     "reason why the message is not acceptable.")}},
1699
           {min_presence_interval,
1700
            #{value => ?T("Number"),
1701
              desc =>
1702
                  ?T("This option defines the minimum of time between presence "
1703
                     "changes coming from a given occupant in seconds. "
1704
                     "This option is global and valid for all rooms. A decimal "
1705
                     "value can be used. When this option is not defined, no "
1706
                     "restriction is applied. This option can be used to protect "
1707
                     "a MUC service for occupants abuses. If an occupant tries "
1708
                     "to change its presence more often than the specified interval, "
1709
                     "the presence is cached by ejabberd and only the last presence "
1710
                     "is broadcasted to all occupants in the room after expiration "
1711
                     "of the interval delay. Intermediate presence packets are "
1712
                     "silently discarded. A good value for this option is '4' seconds.")}},
1713
           {queue_type,
1714
            #{value => "ram | file",
1715
              desc =>
1716
                  ?T("Same as top-level _`queue_type`_ option, but applied to this module only.")}},
1717
           {regexp_room_id,
1718
            #{value => "string()",
1719
              desc =>
1720
                  ?T("This option defines the regular expression that a Room ID "
1721
                     "must satisfy to allow the room creation. The default value "
1722
                     "is the empty string.")}},
1723
           {preload_rooms,
1724
            #{value => "true | false",
1725
              desc =>
1726
                  ?T("Whether to load all persistent rooms in memory on startup. "
1727
                     "If disabled, the room is only loaded on first participant join. "
1728
                     "The default is 'true'. It makes sense to disable room preloading "
1729
                     "when the number of rooms is high: this will improve server startup "
1730
                     "time and memory consumption.")}},
1731
           {room_shaper,
1732
            #{value => "none | ShaperName",
1733
              desc =>
1734
                  ?T("This option defines shaper for the MUC rooms. "
1735
                     "The default value is 'none'.")}},
1736
           {user_message_shaper,
1737
            #{value => "none | ShaperName",
1738
              desc =>
1739
                  ?T("This option defines shaper for the users messages. "
1740
                     "The default value is 'none'.")}},
1741
           {user_presence_shaper,
1742
            #{value => "none | ShaperName",
1743
              desc =>
1744
                  ?T("This option defines shaper for the users presences. "
1745
                     "The default value is 'none'.")}},
1746
           {vcard,
1747
            #{value => ?T("vCard"),
1748
              desc =>
1749
                  ?T("A custom vCard of the service that will be displayed "
1750
                     "by some XMPP clients in Service Discovery. The value of "
1751
                     "'vCard' is a YAML map constructed from an XML representation "
1752
                     "of vCard. Since the representation has no attributes, "
1753
                     "the mapping is straightforward."),
1754
              example =>
1755
                  ["# This XML representation of vCard:",
1756
                   "#   <vCard xmlns='vcard-temp'>",
1757
                   "#     <FN>Conferences</FN>",
1758
                   "#     <ADR>",
1759
                   "#       <WORK/>",
1760
                   "#       <STREET>Elm Street</STREET>",
1761
                   "#     </ADR>",
1762
                   "#   </vCard>",
1763
                   "# ",
1764
                   "# is translated to:",
1765
                   "vcard:",
1766
                   "  fn: Conferences",
1767
                   "  adr:",
1768
                   "    -",
1769
                   "      work: true",
1770
                   "      street: Elm Street"]}},
1771
           {cleanup_affiliations_on_start,
1772
            #{value => "true | false",
1773
              note => "added in 22.05",
1774
              desc =>
1775
                  ?T("Remove affiliations for non-existing local users on startup. "
1776
                     "The default value is 'false'.")}},
1777
           {default_room_options,
1778
            #{value => ?T("Options"),
1779
              note => "improved in 22.05",
1780
              desc =>
1781
                  ?T("Define the "
1782
                     "default room options. Note that the creator of a room "
1783
                     "can modify the options of his room at any time using an "
1784
                     "XMPP client with MUC capability. The 'Options' are:")},
1785
            [{allow_change_subj,
1786
              #{value => "true | false",
1787
                desc =>
1788
                    ?T("Allow occupants to change the subject. "
1789
                       "The default value is 'true'.")}},
1790
             {allowpm,
1791
              #{value => "anyone | participants | moderators | none",
1792
                desc =>
1793
                    ?T("Who can send private messages. "
1794
                       "The default value is 'anyone'.")}},
1795
             {allow_query_users,
1796
              #{value => "true | false",
1797
                desc =>
1798
                    ?T("Occupants can send IQ queries to other occupants. "
1799
                       "The default value is 'true'.")}},
1800
             {allow_user_invites,
1801
              #{value => "true | false",
1802
                desc =>
1803
                    ?T("Allow occupants to send invitations. "
1804
                       "The default value is 'false'.")}},
1805
             {allow_visitor_nickchange,
1806
              #{value => "true | false",
1807
                desc => ?T("Allow visitors to change nickname. "
1808
                           "The default value is 'true'.")}},
1809
             {allow_visitor_status,
1810
              #{value => "true | false",
1811
                desc =>
1812
                    ?T("Allow visitors to send status text in presence updates. "
1813
                       "If disallowed, the status text is stripped before broadcasting "
1814
                       "the presence update to all the room occupants. "
1815
                       "The default value is 'true'.")}},
1816
             {allow_voice_requests,
1817
              #{value => "true | false",
1818
                desc =>
1819
                    ?T("Allow visitors in a moderated room to request voice. "
1820
                       "The default value is 'true'.")}},
1821
             {anonymous,
1822
              #{value => "true | false",
1823
                desc =>
1824
                    ?T("The room is anonymous: occupants don't see the real "
1825
                       "JIDs of other occupants. Note that the room moderators "
1826
                       "can always see the real JIDs of the occupants. "
1827
                       "The default value is 'true'.")}},
1828
             {captcha_protected,
1829
              #{value => "true | false",
1830
                desc =>
1831
                    ?T("When a user tries to join a room where they have no "
1832
                       "affiliation (not owner, admin or member), the room "
1833
                       "requires them to fill a CAPTCHA challenge (see section "
1834
                       "_`basic.md#captcha|CAPTCHA`_ "
1835
                       "in order to accept their join in the room. "
1836
                       "The default value is 'false'.")}},
1837
             {description,
1838
              #{value => ?T("Room Description"),
1839
                desc =>
1840
                    ?T("Short description of the room. "
1841
                       "The default value is an empty string.")}},
1842
             {enable_hats,
1843
              #{value => "true | false",
1844
                note => "improved in 25.10",
1845
                desc =>
1846
                    ?T("Allow extended roles as defined in "
1847
                       "https://xmpp.org/extensions/xep-0317.html[XEP-0317: Hats]. "
1848
                       "For ejabberd older than 25.10 see the "
1849
                       "_`../../tutorials/muc-hats.md|MUC Hats`_ page. "
1850
                       "The default value is 'true'.")}},
1851
             {lang,
1852
              #{value => ?T("Language"),
1853
                desc =>
1854
                    ?T("Preferred language for the discussions in the room. "
1855
                       "The language format should conform to RFC 5646. "
1856
                       "There is no value by default.")}},
1857
             {logging,
1858
              #{value => "true | false",
1859
                desc =>
1860
                    ?T("The public messages are logged using _`mod_muc_log`_. "
1861
                       "The default value is 'false'.")}},
1862
             {members_by_default,
1863
              #{value => "true | false",
1864
                desc =>
1865
                    ?T("The occupants that enter the room are participants "
1866
                       "by default, so they have \"voice\". "
1867
                       "The default value is 'true'.")}},
1868
             {members_only,
1869
              #{value => "true | false",
1870
                desc =>
1871
                    ?T("Only members of the room can enter. "
1872
                       "The default value is 'false'.")}},
1873
             {moderated,
1874
              #{value => "true | false",
1875
                desc =>
1876
                    ?T("Only occupants with \"voice\" can send public messages. "
1877
                       "The default value is 'true'.")}},
1878
             {password_protected,
1879
              #{value => "true | false",
1880
                desc =>
1881
                    ?T("The password is required to enter the room. "
1882
                       "The default value is 'false'.")}},
1883
             {password,
1884
              #{value => ?T("Password"),
1885
                desc =>
1886
                    ?T("Password of the room. Implies option 'password_protected' "
1887
                       "set to 'true'. There is no default value.")}},
1888
             {persistent,
1889
              #{value => "true | false",
1890
                desc =>
1891
                    ?T("The room persists even if the last participant leaves. "
1892
                       "The default value is 'false'.")}},
1893
             {public,
1894
              #{value => "true | false",
1895
                desc =>
1896
                    ?T("The room is public in the list of the MUC service, "
1897
                       "so it can be discovered. MUC admins and room participants "
1898
                       "will see private rooms in Service Discovery if their XMPP "
1899
                       "client supports this feature. "
1900
                       "The default value is 'true'.")}},
1901
             {public_list,
1902
              #{value => "true | false",
1903
                desc =>
1904
                    ?T("The list of participants is public, without requiring "
1905
                       "to enter the room. The default value is 'true'.")}},
1906
             {pubsub,
1907
              #{value => ?T("PubSub Node"),
1908
                desc =>
1909
                    ?T("XMPP URI of associated Publish/Subscribe node. "
1910
                       "The default value is an empty string.")}},
1911
             {vcard,
1912
              #{value => ?T("vCard"),
1913
                desc =>
1914
                    ?T("A custom vCard for the room. See the equivalent mod_muc option."
1915
                       "The default value is an empty string.")}},
1916
             {vcard_xupdate,
1917
              #{value => "undefined | external | AvatarHash",
1918
                desc =>
1919
                    ?T("Set the hash of the avatar image. "
1920
                       "The default value is 'undefined'.")}},
1921
             {voice_request_min_interval,
1922
              #{value => ?T("Number"),
1923
                desc =>
1924
                    ?T("Minimum interval between voice requests, in seconds. "
1925
                       "The default value is '1800'.")}},
1926
             {mam,
1927
              #{value => "true | false",
1928
                desc =>
1929
                    ?T("Enable message archiving. Implies mod_mam is enabled. "
1930
                       "The default value is 'false'.")}},
1931
             {allow_subscription,
1932
              #{value => "true | false",
1933
                desc =>
1934
                    ?T("Allow users to subscribe to room events as described in "
1935
                       "_`../../developer/xmpp-clients-bots/extensions/muc-sub.md|Multi-User Chat Subscriptions`_. "
1936
                       "The default value is 'false'.")}},
1937
             {title,
1938
              #{value => ?T("Room Title"),
1939
                desc =>
1940
                    ?T("A human-readable title of the room. "
1941
                       "There is no default value")}},
1942
             {allow_private_messages_from_visitors,
1943
              #{value => "anyone | moderators | nobody",
1944
                desc =>
1945
                    ?T("Visitors can send private messages to other occupants. "
1946
                       "The default value is 'anyone' which means visitors "
1947
                       "can send private messages to any occupant.")}},
1948
             {max_users,
1949
              #{value => ?T("Number"),
1950
                desc =>
1951
                    ?T("Maximum number of occupants in the room. "
1952
                       "The default value is '200'.")}},
1953
             {presence_broadcast,
1954
              #{value => "[Role]",
1955
                desc =>
1956
                    ?T("List of roles for which presence is broadcasted. "
1957
                       "The list can contain one or several of: 'moderator', "
1958
                       "'participant', 'visitor'. The default value is shown "
1959
                       "in the example below:"),
1960
                example =>
1961
                    ["presence_broadcast:",
1962
                     "  - moderator",
1963
                     "  - participant",
1964
                     "  - visitor"]}}]}]}.
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