• 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

56.49
/src/mod_muc_room.erl
1
%%%----------------------------------------------------------------------
2
%%% File    : mod_muc_room.erl
3
%%% Author  : Alexey Shchepin <alexey@process-one.net>
4
%%% Purpose : MUC room stuff
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

26
-module(mod_muc_room).
27

28
-author('alexey@process-one.net').
29

30
-protocol({xep, 317, '0.3.1', '21.12', "complete", "0.3.1 since 25.10"}).
31
-protocol({xep, 410, '1.1.0', '18.12', "complete", ""}).
32

33
-behaviour(p1_fsm).
34

35
%% External exports
36
-export([start_link/10,
37
         start_link/8,
38
         start/10,
39
         start/8,
40
         supervisor/1,
41
         get_role/2,
42
         get_affiliation/2,
43
         is_occupant_or_admin/2,
44
         route/2,
45
         expand_opts/1,
46
         config_fields/0,
47
         destroy/1,
48
         destroy/2,
49
         shutdown/1,
50
         get_config/1,
51
         set_config/2,
52
         get_state/1,
53
         get_info/1,
54
         change_item/5,
55
         change_item_async/5,
56
         config_reloaded/1,
57
         subscribe/4,
58
         unsubscribe/2,
59
         is_subscribed/2,
60
         get_subscribers/1,
61
         service_message/2,
62
         get_disco_item/4]).
63

64
%% gen_fsm callbacks
65
-export([init/1,
66
         normal_state/2,
67
         handle_event/3,
68
         handle_sync_event/4,
69
         handle_info/3,
70
         terminate/3,
71
         code_change/4]).
72

73
-include("logger.hrl").
74
-include_lib("xmpp/include/xmpp.hrl").
75
-include("translate.hrl").
76
-include("mod_muc_room.hrl").
77

78

79
-define(MAX_USERS_DEFAULT_LIST,
80
        [5, 10, 20, 30, 50, 100, 200, 500, 1000, 2000, 5000]).
81

82
-define(MUC_HAT_CREATE_CMD,   <<"urn:xmpp:hats:commands:create">>).
83
-define(MUC_HAT_DESTROY_CMD,  <<"urn:xmpp:hats:commands:destroy">>).
84
-define(MUC_HAT_LISTHATS_CMD, <<"urn:xmpp:hats:commands:list">>).
85

86
-define(MUC_HAT_ASSIGN_CMD,   <<"urn:xmpp:hats:commands:assign">>).
87
-define(MUC_HAT_UNASSIGN_CMD, <<"urn:xmpp:hats:commands:unassign">>).
88
-define(MUC_HAT_LISTUSERS_CMD,<<"urn:xmpp:hats:commands:list-assigned">>).
89

90
-define(MAX_HATS_USERS, 100).
91
-define(MAX_HATS_PER_USER, 10).
92
-define(CLEAN_ROOM_TIMEOUT, 30000).
93

94
%-define(DBGFSM, true).
95

96
-ifdef(DBGFSM).
97

98
-define(FSMOPTS, [{debug, [trace]}]).
99

100
-else.
101

102
-define(FSMOPTS, []).
103

104
-endif.
105

106
-type state() :: #state{}.
107
-type fsm_stop() :: {stop, normal, state()}.
108
-type fsm_next() :: {next_state, normal_state, state()}.
109
-type fsm_transition() :: fsm_stop() | fsm_next().
110
-type disco_item_filter() ::  only_non_empty | all | non_neg_integer().
111
-type admin_action() :: {jid(), affiliation | role, affiliation() | role(), binary()}.
112
-export_type([state/0, disco_item_filter/0]).
113

114
-callback set_affiliation(binary(), binary(), binary(), jid(), affiliation(),
115
                          binary()) -> ok | {error, any()}.
116
-callback set_affiliations(binary(), binary(), binary(),
117
                           affiliations()) -> ok | {error, any()}.
118
-callback get_affiliation(binary(), binary(), binary(),
119
                          binary(), binary()) -> {ok, affiliation()} | {error, any()}.
120
-callback get_affiliations(binary(), binary(), binary()) -> {ok, affiliations()} | {error, any()}.
121
-callback search_affiliation(binary(), binary(), binary(), affiliation()) ->
122
    {ok, [{ljid(), {affiliation(), binary()}}]} | {error, any()}.
123

124
-ifndef(OTP_BELOW_28).
125
-dialyzer([no_opaque_union]).
126
-endif.
127

128
%%%----------------------------------------------------------------------
129
%%% API
130
%%%----------------------------------------------------------------------
131
-spec start(binary(), binary(), mod_muc:access(), binary(), non_neg_integer(),
132
            atom(), jid(), binary(), [{atom(), term()}], ram | file) ->
133
                   {ok, pid()} | {error, any()}.
134
start(Host, ServerHost, Access, Room, HistorySize, RoomShaper,
135
      Creator, Nick, DefRoomOpts, QueueType) ->
136
    supervisor:start_child(
418✔
137
      supervisor(ServerHost),
138
      [Host, ServerHost, Access, Room, HistorySize,
139
       RoomShaper, Creator, Nick, DefRoomOpts, QueueType]).
140

141
-spec start(binary(), binary(), mod_muc:access(), binary(), non_neg_integer(),
142
            atom(), [{atom(), term()}], ram | file) ->
143
                   {ok, pid()} | {error, any()}.
144
start(Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts, QueueType) ->
145
    supervisor:start_child(
×
146
      supervisor(ServerHost),
147
      [Host, ServerHost, Access, Room, HistorySize,
148
       RoomShaper, Opts, QueueType]).
149

150
-spec start_link(binary(), binary(), mod_muc:access(), binary(), non_neg_integer(),
151
                 atom(), jid(), binary(), [{atom(), term()}], ram | file) ->
152
                        {ok, pid()} | {error, any()}.
153
start_link(Host, ServerHost, Access, Room, HistorySize, RoomShaper,
154
           Creator, Nick, DefRoomOpts, QueueType) ->
155
    p1_fsm:start_link(?MODULE, [Host, ServerHost, Access, Room, HistorySize,
418✔
156
                                 RoomShaper, Creator, Nick, DefRoomOpts, QueueType],
157
                       ?FSMOPTS).
158

159
-spec start_link(binary(), binary(), mod_muc:access(), binary(), non_neg_integer(),
160
                 atom(), [{atom(), term()}], ram | file) ->
161
                        {ok, pid()} | {error, any()}.
162
start_link(Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts, QueueType) ->
163
    p1_fsm:start_link(?MODULE, [Host, ServerHost, Access, Room, HistorySize,
×
164
                                 RoomShaper, Opts, QueueType],
165
                       ?FSMOPTS).
166

167
-spec supervisor(binary()) -> atom().
168
supervisor(Host) ->
169
    gen_mod:get_module_proc(Host, mod_muc_room_sup).
527✔
170

171
-spec destroy(pid()) -> ok.
172
destroy(Pid) ->
173
    p1_fsm:send_all_state_event(Pid, destroy).
×
174

175
-spec destroy(pid(), binary()) -> ok.
176
destroy(Pid, Reason) ->
177
    p1_fsm:send_all_state_event(Pid, {destroy, Reason}).
×
178

179
-spec shutdown(pid()) -> boolean().
180
shutdown(Pid) ->
181
    ejabberd_cluster:send(Pid, shutdown).
×
182

183
-spec config_reloaded(pid()) -> boolean().
184
config_reloaded(Pid) ->
185
    ejabberd_cluster:send(Pid, config_reloaded).
×
186

187
-spec get_config(pid()) -> {ok, config()} | {error, notfound | timeout}.
188
get_config(Pid) ->
189
    try p1_fsm:sync_send_all_state_event(Pid, get_config)
×
190
    catch _:{timeout, {p1_fsm, _, _}} ->
191
            {error, timeout};
×
192
          _:{_, {p1_fsm, _, _}} ->
193
            {error, notfound}
×
194
    end.
195

196
-spec set_config(pid(), config()) -> {ok, config()} | {error, notfound | timeout}.
197
set_config(Pid, Config) ->
198
    try p1_fsm:sync_send_all_state_event(Pid, {change_config, Config})
×
199
    catch _:{timeout, {p1_fsm, _, _}} ->
200
            {error, timeout};
×
201
          _:{_, {p1_fsm, _, _}} ->
202
            {error, notfound}
×
203
    end.
204

205
-spec change_item(pid(), jid(), affiliation | role, affiliation() | role(), binary()) ->
206
                         {ok, state()} | {error, notfound | timeout}.
207
change_item(Pid, JID, Type, AffiliationOrRole, Reason) ->
208
    try p1_fsm:sync_send_all_state_event(
9✔
209
          Pid, {process_item_change, {JID, Type, AffiliationOrRole, Reason}, undefined})
210
    catch _:{timeout, {p1_fsm, _, _}} ->
211
            {error, timeout};
×
212
          _:{_, {p1_fsm, _, _}} ->
213
            {error, notfound}
×
214
    end.
215

216
-spec change_item_async(pid(), jid(), affiliation | role, affiliation() | role(), binary()) -> ok.
217
change_item_async(Pid, JID, Type, AffiliationOrRole, Reason) ->
218
    p1_fsm:send_all_state_event(
×
219
      Pid, {process_item_change, {JID, Type, AffiliationOrRole, Reason}, undefined}).
220

221
-spec get_state(pid()) -> {ok, state()} | {error, notfound | timeout}.
222
get_state(Pid) ->
223
    try p1_fsm:sync_send_all_state_event(Pid, get_state)
×
224
    catch _:{timeout, {p1_fsm, _, _}} ->
225
            {error, timeout};
×
226
          _:{_, {p1_fsm, _, _}} ->
227
            {error, notfound}
×
228
    end.
229

230
-spec get_info(pid()) -> {ok, #{occupants_number => integer()}} |
231
                         {error, notfound | timeout}.
232
get_info(Pid) ->
233
    try
×
234
        {ok, p1_fsm:sync_send_all_state_event(Pid, get_info)}
×
235
    catch _:{timeout, {p1_fsm, _, _}} ->
236
            {error, timeout};
×
237
          _:{_, {p1_fsm, _, _}} ->
238
            {error, notfound}
×
239
    end.
240

241
-spec subscribe(pid(), jid(), binary(), [binary()]) -> {ok, [binary()]} | {error, binary()}.
242
subscribe(Pid, JID, Nick, Nodes) ->
243
    try p1_fsm:sync_send_all_state_event(Pid, {muc_subscribe, JID, Nick, Nodes})
×
244
    catch _:{timeout, {p1_fsm, _, _}} ->
245
            {error, ?T("Request has timed out")};
×
246
          _:{_, {p1_fsm, _, _}} ->
247
            {error, ?T("Conference room does not exist")}
×
248
    end.
249

250
-spec unsubscribe(pid(), jid()) -> ok | {error, binary()}.
251
unsubscribe(Pid, JID) ->
252
    try p1_fsm:sync_send_all_state_event(Pid, {muc_unsubscribe, JID})
9✔
253
    catch _:{timeout, {p1_fsm, _, _}} ->
254
            {error, ?T("Request has timed out")};
×
255
          exit:{normal, {p1_fsm, _, _}} ->
256
            ok;
×
257
          _:{_, {p1_fsm, _, _}} ->
258
            {error, ?T("Conference room does not exist")}
×
259
    end.
260

261
-spec is_subscribed(pid(), jid()) -> {true, binary(), [binary()]} | false.
262
is_subscribed(Pid, JID) ->
263
    try p1_fsm:sync_send_all_state_event(Pid, {is_subscribed, JID})
21✔
264
    catch _:{_, {p1_fsm, _, _}} -> false
×
265
    end.
266

267
-spec get_subscribers(pid()) -> {ok, [jid()]} | {error, notfound | timeout}.
268
get_subscribers(Pid) ->
269
    try p1_fsm:sync_send_all_state_event(Pid, get_subscribers)
×
270
    catch _:{timeout, {p1_fsm, _, _}} ->
271
            {error, timeout};
×
272
          _:{_, {p1_fsm, _, _}} ->
273
            {error, notfound}
×
274
    end.
275

276
-spec service_message(pid(), binary()) -> ok.
277
service_message(Pid, Text) ->
278
    p1_fsm:send_all_state_event(Pid, {service_message, Text}).
×
279

280
-spec get_disco_item(pid(), disco_item_filter(), jid(), binary()) ->
281
                            {ok, binary()} | {error, notfound | timeout}.
282
get_disco_item(Pid, Filter, JID, Lang) ->
283
    Timeout = 100,
69✔
284
    Time = erlang:system_time(millisecond),
69✔
285
    Query = {get_disco_item, Filter, JID, Lang, Time+Timeout},
69✔
286
    try p1_fsm:sync_send_all_state_event(Pid, Query, Timeout) of
69✔
287
        {item, Desc} ->
288
            {ok, Desc};
54✔
289
        false ->
290
            {error, notfound}
9✔
291
    catch _:{timeout, {p1_fsm, _, _}} ->
292
            {error, timeout};
×
293
          _:{_, {p1_fsm, _, _}} ->
294
            {error, notfound}
6✔
295
    end.
296

297
%%%----------------------------------------------------------------------
298
%%% Callback functions from gen_fsm
299
%%%----------------------------------------------------------------------
300

301
init([Host, ServerHost, Access, Room, HistorySize,
302
      RoomShaper, Creator, _Nick, DefRoomOpts, QueueType]) ->
303
    process_flag(trap_exit, true),
418✔
304
    misc:set_proc_label({?MODULE, Room, Host}),
418✔
305
    Shaper = ejabberd_shaper:new(RoomShaper),
418✔
306
    RoomQueue = room_queue_new(ServerHost, Shaper, QueueType),
418✔
307
    State = set_opts(DefRoomOpts,
418✔
308
                     #state{host = Host, server_host = ServerHost,
309
                            access = Access, room = Room,
310
                            history = lqueue_new(HistorySize, QueueType),
311
                            jid = jid:make(Room, Host),
312
                            just_created = true,
313
                            room_queue = RoomQueue,
314
                            room_shaper = Shaper}),
315
    State1 = set_affiliation(Creator, owner, State),
418✔
316
    store_room(State1),
418✔
317
    ?INFO_MSG("Created MUC room ~ts@~ts by ~ts",
418✔
318
              [Room, Host, jid:encode(Creator)]),
418✔
319
    add_to_log(room_existence, created, State1),
418✔
320
    add_to_log(room_existence, started, State1),
418✔
321
    ejabberd_hooks:run(start_room, ServerHost, [ServerHost, Room, Host]),
418✔
322
    erlang:send_after(?CLEAN_ROOM_TIMEOUT, self(),
418✔
323
                      close_room_if_temporary_and_empty),
324
    {ok, normal_state, reset_hibernate_timer(State1)};
418✔
325
init([Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts, QueueType]) ->
326
    process_flag(trap_exit, true),
×
327
    misc:set_proc_label({?MODULE, Room, Host}),
×
328
    Shaper = ejabberd_shaper:new(RoomShaper),
×
329
    RoomQueue = room_queue_new(ServerHost, Shaper, QueueType),
×
330
    Jid = jid:make(Room, Host),
×
331
    State = set_opts(Opts, #state{host = Host,
×
332
                                  server_host = ServerHost,
333
                                  access = Access,
334
                                  room = Room,
335
                                  history = lqueue_new(HistorySize, QueueType),
336
                                  jid = Jid,
337
                                  room_queue = RoomQueue,
338
                                  room_shaper = Shaper}),
339
    add_to_log(room_existence, started, State),
×
340
    ejabberd_hooks:run(start_room, ServerHost, [ServerHost, Room, Host]),
×
341
    State1 = cleanup_affiliations(State),
×
342
    State2 =
×
343
    case {lists:keyfind(hibernation_time, 1, Opts),
344
          (State1#state.config)#config.mam,
345
          (State1#state.history)#lqueue.max} of
346
        {{_, V}, true, L} when is_integer(V), L > 0 ->
347
            {Msgs, _, _} = mod_mam:select(ServerHost, Jid, Jid, [],
×
348
                                         #rsm_set{max = L, before = <<"9999999999999999">>},
349
                                         groupchat, only_messages),
350
            Hist2 =
×
351
            lists:foldl(
352
                fun({_, TS, #forwarded{sub_els = [#message{meta = #{archive_nick := Nick}} = Msg]}}, Hist) ->
353
                    Pkt = xmpp:set_from_to(Msg, jid:replace_resource(Jid, Nick), Jid),
×
354
                    Size = element_size(Pkt),
×
355
                    lqueue_in({Nick, Pkt, false, misc:usec_to_now(TS), Size}, Hist)
×
356
                end, State1#state.history, Msgs),
357
            State1#state{history = Hist2};
×
358
        _ ->
359
            State1
×
360
    end,
361
    erlang:send_after(?CLEAN_ROOM_TIMEOUT, self(),
×
362
                      close_room_if_temporary_and_empty),
363
    {ok, normal_state, reset_hibernate_timer(State2)}.
×
364

365
normal_state({route, <<"">>,
366
              #message{from = From, type = Type, lang = Lang} = Packet},
367
             StateData) ->
368
    case is_user_online(From, StateData) orelse
783✔
369
        is_subscriber(From, StateData) orelse
17✔
370
        is_user_allowed_message_nonparticipant(From, StateData) of
9✔
371
        true when Type == groupchat ->
372
            Activity = get_user_activity(From, StateData),
649✔
373
            Now = erlang:system_time(microsecond),
649✔
374
            MinMessageInterval = trunc(mod_muc_opt:min_message_interval(StateData#state.server_host) * 1000000),
649✔
375
            Size = element_size(Packet),
649✔
376
            {MessageShaper, MessageShaperInterval} =
649✔
377
                ejabberd_shaper:update(Activity#activity.message_shaper, Size),
378
            if Activity#activity.message /= undefined ->
649✔
379
                    ErrText = ?T("Traffic rate limit is exceeded"),
×
380
                    Err = xmpp:err_resource_constraint(ErrText, Lang),
×
381
                    ejabberd_router:route_error(Packet, Err),
×
382
                    {next_state, normal_state, StateData};
×
383
               Now >= Activity#activity.message_time + MinMessageInterval,
384
               MessageShaperInterval == 0 ->
385
                    {RoomShaper, RoomShaperInterval} =
649✔
386
                        ejabberd_shaper:update(StateData#state.room_shaper, Size),
387
                    RoomQueueEmpty = case StateData#state.room_queue of
649✔
388
                                         undefined -> true;
649✔
389
                                         RQ -> p1_queue:is_empty(RQ)
×
390
                                     end,
391
                    if RoomShaperInterval == 0, RoomQueueEmpty ->
649✔
392
                            NewActivity = Activity#activity{
649✔
393
                                            message_time = Now,
394
                                            message_shaper = MessageShaper},
395
                            StateData1 = store_user_activity(From,
649✔
396
                                                             NewActivity,
397
                                                             StateData),
398
                            StateData2 = StateData1#state{room_shaper =
649✔
399
                                                              RoomShaper},
400
                            process_groupchat_message(Packet,
649✔
401
                                                      StateData2);
402
                       true ->
403
                            StateData1 = if RoomQueueEmpty ->
×
404
                                                 erlang:send_after(RoomShaperInterval,
×
405
                                                                   self(),
406
                                                                   process_room_queue),
407
                                                 StateData#state{room_shaper =
×
408
                                                                     RoomShaper};
409
                                            true -> StateData
×
410
                                         end,
411
                            NewActivity = Activity#activity{
×
412
                                            message_time = Now,
413
                                            message_shaper = MessageShaper,
414
                                            message = Packet},
415
                            RoomQueue = p1_queue:in({message, From},
×
416
                                                    StateData#state.room_queue),
417
                            StateData2 = store_user_activity(From,
×
418
                                                             NewActivity,
419
                                                             StateData1),
420
                            StateData3 = StateData2#state{room_queue = RoomQueue},
×
421
                            {next_state, normal_state, StateData3}
×
422
                    end;
423
               true ->
424
                    MessageInterval = (Activity#activity.message_time +
×
425
                                           MinMessageInterval - Now) div 1000,
426
                    Interval = lists:max([MessageInterval,
×
427
                                          MessageShaperInterval]),
428
                    erlang:send_after(Interval, self(),
×
429
                                      {process_user_message, From}),
430
                    NewActivity = Activity#activity{
×
431
                                    message = Packet,
432
                                    message_shaper = MessageShaper},
433
                    StateData1 = store_user_activity(From, NewActivity,        StateData),
×
434
                    {next_state, normal_state, StateData1}
×
435
            end;
436
        true when Type == error ->
437
            case is_user_online(From, StateData) of
8✔
438
                true ->
439
                    ErrorText = ?T("It is not allowed to send error messages to the"
×
440
                                   " room. The participant (~s) has sent an error "
441
                                   "message (~s) and got kicked from the room"),
442
                    NewState = expulse_participant(Packet, From, StateData,
×
443
                                                   translate:translate(Lang,
444
                                                                       ErrorText)),
445
                    close_room_if_temporary_and_empty(NewState);
×
446
                _ ->
447
                    {next_state, normal_state, StateData}
8✔
448
            end;
449
        true when Type == chat ->
450
            ErrText = ?T("It is not allowed to send private messages "
×
451
                         "to the conference"),
452
            Err = xmpp:err_not_acceptable(ErrText, Lang),
×
453
            ejabberd_router:route_error(Packet, Err),
×
454
            {next_state, normal_state, StateData};
×
455
        true when Type == normal ->
456
            {next_state, normal_state,
117✔
457
             try xmpp:decode_els(Packet) of
117✔
458
                 Pkt -> process_normal_message(From, Pkt, StateData)
117✔
459
             catch _:{xmpp_codec, Why} ->
460
                     Txt = xmpp:io_format_error(Why),
×
461
                     Err = xmpp:err_bad_request(Txt, Lang),
×
462
                     ejabberd_router:route_error(Packet, Err),
×
463
                     StateData
×
464
             end};
465
        true ->
466
            ErrText = ?T("Improper message type"),
×
467
            Err = xmpp:err_not_acceptable(ErrText, Lang),
×
468
            ejabberd_router:route_error(Packet, Err),
×
469
            {next_state, normal_state, StateData};
×
470
        false when Type /= error ->
471
            handle_roommessage_from_nonparticipant(Packet, StateData, From),
9✔
472
            {next_state, normal_state, StateData};
9✔
473
        false ->
474
            {next_state, normal_state, StateData}
×
475
    end;
476
normal_state({route, <<"">>,
477
              #iq{from = From, type = Type, lang = Lang, sub_els = [_]} = IQ0},
478
             StateData) when Type == get; Type == set ->
479
    try
1,767✔
480
        case ejabberd_hooks:run_fold(
1,767✔
481
               muc_process_iq,
482
               StateData#state.server_host,
483
               xmpp:set_from_to(xmpp:decode_els(IQ0),
484
                                From, StateData#state.jid),
485
               [StateData]) of
486
            ignore ->
487
                {next_state, normal_state, StateData};
×
488
            {ignore, StateData2} ->
489
                {next_state, normal_state, StateData2};
×
490
            #iq{type = T} = IQRes when T == error; T == result ->
491
                ejabberd_router:route(IQRes),
40✔
492
                {next_state, normal_state, StateData};
40✔
493
            #iq{sub_els = [SubEl]} = IQ ->
494
                Res1 = case SubEl of
1,727✔
495
                           #muc_admin{} ->
496
                               process_iq_admin(From, IQ, StateData);
288✔
497
                           #muc_owner{} ->
498
                               process_iq_owner(From, IQ, StateData);
1,091✔
499
                           #disco_info{} ->
500
                               process_iq_disco_info(From, IQ, StateData);
122✔
501
                           #disco_items{} ->
502
                               process_iq_disco_items(From, IQ, StateData);
18✔
503
                           #vcard_temp{} ->
504
                               process_iq_vcard(From, IQ, StateData);
54✔
505
                           #muc_subscribe{} ->
506
                               process_iq_mucsub(From, IQ, StateData);
77✔
507
                           #muc_unsubscribe{} ->
508
                               process_iq_mucsub(From, IQ, StateData);
77✔
509
                           #muc_subscriptions{} ->
510
                               process_iq_mucsub(From, IQ, StateData);
×
511
                           #xcaptcha{} ->
512
                               process_iq_captcha(From, IQ, StateData);
×
513
                           #adhoc_command{} ->
514
                               process_iq_adhoc(From, IQ, StateData);
×
515
                           #register{} ->
516
                               mod_muc:process_iq_register(IQ);
×
517
                           #message_moderate{id = Id, reason = Reason} -> % moderate:1
518
                               process_iq_moderate(From, IQ, Id, Reason, StateData);
×
519
                           #fasten_apply_to{id = ModerateId} = ApplyTo ->
520
                               case xmpp:get_subtag(ApplyTo, #message_moderate_21{}) of
×
521
                                   #message_moderate_21{reason = Reason} -> % moderate:0
522
                                       process_iq_moderate(From, IQ, ModerateId, Reason, StateData);
×
523
                                   _ ->
524
                                       Txt = ?T("The feature requested is not "
×
525
                                                "supported by the conference"),
526
                                       {error, xmpp:err_service_unavailable(Txt, Lang)}
×
527
                               end;
528
                           _ ->
529
                               Txt = ?T("The feature requested is not "
×
530
                                        "supported by the conference"),
531
                               {error, xmpp:err_service_unavailable(Txt, Lang)}
×
532
                       end,
533
                {IQRes, NewStateData} =
1,727✔
534
                    case Res1 of
535
                        {result, Res, SD} ->
536
                            {xmpp:make_iq_result(IQ, Res), SD};
1,304✔
537
                        {result, Res} ->
538
                            {xmpp:make_iq_result(IQ, Res), StateData};
396✔
539
                        {ignore, SD} ->
540
                            {ignore, SD};
×
541
                        {error, Error} ->
542
                            {xmpp:make_error(IQ0, Error), StateData}
27✔
543
                    end,
544
                if IQRes /= ignore ->
1,727✔
545
                        ejabberd_router:route(IQRes);
1,727✔
546
                   true ->
547
                        ok
×
548
                end,
549
                case NewStateData of
1,727✔
550
                    stop ->
551
                        Conf = StateData#state.config,
9✔
552
                        {stop, normal, StateData#state{config = Conf#config{persistent = {destroying, Conf#config.persistent}}}};
9✔
553
                    _ when NewStateData#state.just_created ->
554
                        close_room_if_temporary_and_empty(NewStateData);
18✔
555
                    _ ->
556
                        {next_state, normal_state, NewStateData}
1,700✔
557
                end
558
        end
559
    catch _:{xmpp_codec, Why} ->
560
            ErrTxt = xmpp:io_format_error(Why),
×
561
            Err = xmpp:err_bad_request(ErrTxt, Lang),
×
562
            ejabberd_router:route_error(IQ0, Err),
×
563
            {next_state, normal_state, StateData}
×
564
    end;
565
normal_state({route, <<"">>, #iq{} = IQ}, StateData) ->
566
    Err = xmpp:err_bad_request(),
×
567
    ejabberd_router:route_error(IQ, Err),
×
568
    case StateData#state.just_created of
×
569
        true -> {stop, normal, StateData};
×
570
        _ -> {next_state, normal_state, StateData}
×
571
    end;
572
normal_state({route, Nick, #presence{from = From} = Packet}, StateData) ->
573
    Activity = get_user_activity(From, StateData),
1,443✔
574
    Now = erlang:system_time(microsecond),
1,443✔
575
    MinPresenceInterval =
1,443✔
576
        trunc(mod_muc_opt:min_presence_interval(StateData#state.server_host) * 1000000),
577
    if (Now >= Activity#activity.presence_time + MinPresenceInterval)
1,443✔
578
       and (Activity#activity.presence == undefined) ->
579
            NewActivity = Activity#activity{presence_time = Now},
1,443✔
580
            StateData1 = store_user_activity(From, NewActivity,
1,443✔
581
                                             StateData),
582
            process_presence(Nick, Packet, StateData1);
1,443✔
583
       true ->
584
            if Activity#activity.presence == undefined ->
×
585
                    Interval = (Activity#activity.presence_time +
×
586
                                    MinPresenceInterval - Now) div 1000,
587
                    erlang:send_after(Interval, self(),
×
588
                                      {process_user_presence, From});
589
               true -> ok
×
590
            end,
591
            NewActivity = Activity#activity{presence = {Nick, Packet}},
×
592
            StateData1 = store_user_activity(From, NewActivity,
×
593
                                             StateData),
594
            {next_state, normal_state, StateData1}
×
595
    end;
596
normal_state({route, ToNick,
597
              #message{from = From, type = Type, lang = Lang} = Packet},
598
             StateData) ->
599
    case decide_fate_message(Packet, From, StateData) of
108✔
600
        {expulse_sender, Reason} ->
601
            ?DEBUG(Reason, []),
×
602
            ErrorText = ?T("It is not allowed to send error messages to the"
×
603
                           " room. The participant (~s) has sent an error "
604
                           "message (~s) and got kicked from the room"),
605
            NewState = expulse_participant(Packet, From, StateData,
×
606
                                           translate:translate(Lang, ErrorText)),
607
            {next_state, normal_state, NewState};
×
608
        forget_message ->
609
            {next_state, normal_state, StateData};
×
610
        continue_delivery ->
611
            case {is_user_allowed_private_message(From, StateData),
108✔
612
                  is_user_online(From, StateData) orelse
108✔
613
                  is_subscriber(From, StateData) orelse
×
614
                  is_user_allowed_message_nonparticipant(From, StateData)} of
×
615
                {true, true} when Type == groupchat ->
616
                    ErrText = ?T("It is not allowed to send private messages "
×
617
                                 "of type \"groupchat\""),
618
                    Err = xmpp:err_bad_request(ErrText, Lang),
×
619
                    ejabberd_router:route_error(Packet, Err);
×
620
                {true, true} ->
621
                    case find_jids_by_nick(ToNick, StateData) of
90✔
622
                        [] ->
623
                            ErrText = ?T("Recipient is not in the conference room"),
9✔
624
                            Err = xmpp:err_item_not_found(ErrText, Lang),
9✔
625
                            ejabberd_router:route_error(Packet, Err);
9✔
626
                        ToJIDs ->
627
                            SrcIsVisitor = is_visitor(From, StateData),
81✔
628
                            DstIsModerator = is_moderator(hd(ToJIDs), StateData),
81✔
629
                            PmFromVisitors =
81✔
630
                                (StateData#state.config)#config.allow_private_messages_from_visitors,
631
                            if SrcIsVisitor == false;
81✔
632
                               PmFromVisitors == anyone;
633
                               (PmFromVisitors == moderators) and
634
                               DstIsModerator ->
635
                                   {FromNick, _} = get_participant_data(From, StateData),
72✔
636
                                    FromNickJID =
72✔
637
                                        jid:replace_resource(StateData#state.jid,
638
                                                             FromNick),
639
                                    X = #muc_user{},
72✔
640
                                    Packet2 = xmpp:set_subtag(Packet, X),
72✔
641
                                    case ejabberd_hooks:run_fold(muc_filter_message,
72✔
642
                                                                 StateData#state.server_host,
643
                                                                 xmpp:put_meta(Packet2, mam_ignore, true),
644
                                                                 [StateData, FromNick]) of
645
                                        drop ->
646
                                            ok;
×
647
                                        Packet3 ->
648
                                            PrivMsg = xmpp:set_from(xmpp:del_meta(Packet3, mam_ignore), FromNickJID),
72✔
649
                                            lists:foreach(
72✔
650
                                              fun(ToJID) ->
651
                                                      ejabberd_router:route(xmpp:set_to(PrivMsg, ToJID))
72✔
652
                                              end, ToJIDs)
653
                                    end;
654
                               true ->
655
                                    ErrText = ?T("You are not allowed to send private messages"),
9✔
656
                                    Err = xmpp:err_forbidden(ErrText, Lang),
9✔
657
                                    ejabberd_router:route_error(Packet, Err)
9✔
658
                            end
659
                    end;
660
                {true, false} ->
661
                    ErrText = ?T("Only occupants are allowed to send messages "
×
662
                                 "to the conference"),
663
                    Err = xmpp:err_not_acceptable(ErrText, Lang),
×
664
                    ejabberd_router:route_error(Packet, Err);
×
665
                {false, _} ->
666
                    ErrText = ?T("You are not allowed to send private messages"),
18✔
667
                    Err = xmpp:err_forbidden(ErrText, Lang),
18✔
668
                    ejabberd_router:route_error(Packet, Err)
18✔
669
            end,
670
          {next_state, normal_state, StateData}
108✔
671
    end;
672
normal_state({route, ToNick,
673
              #iq{from = From, lang = Lang} = Packet},
674
             #state{config = #config{allow_query_users = AllowQuery}} = StateData) ->
675
    DirectIqType = direct_iq_type(Packet),
36✔
676
    try maps:get(jid:tolower(From), StateData#state.users) of
36✔
677
        #user{nick = FromNick} when AllowQuery
678
                                    orelse DirectIqType == vcard
679
                                    orelse ToNick == FromNick ->
680
            case find_jid_by_nick(ToNick, StateData) of
9✔
681
                false ->
682
                    ErrText = ?T("Recipient is not in the conference room"),
×
683
                    Err = xmpp:err_item_not_found(ErrText, Lang),
×
684
                    ejabberd_router:route_error(Packet, Err);
×
685
                To ->
686
                    FromJID = jid:replace_resource(StateData#state.jid, FromNick),
9✔
687
                    case DirectIqType of
9✔
688
                        vcard ->
689
                            ejabberd_router:route_iq(
×
690
                              xmpp:set_from_to(Packet, FromJID, jid:remove_resource(To)),
691
                              Packet, self());
692
                        pubsub ->
693
                            ejabberd_router:route_iq(
×
694
                                xmpp:set_from_to(Packet, FromJID, jid:remove_resource(To)),
695
                                Packet, self());
696
                        ping when ToNick == FromNick ->
697
                            %% Self-ping optimization from XEP-0410
698
                            ejabberd_router:route(xmpp:make_iq_result(Packet));
×
699
                        response ->
700
                            ejabberd_router:route(xmpp:set_from_to(Packet, FromJID, To));
×
701
                        #stanza_error{} = Err ->
702
                            ejabberd_router:route_error(Packet, Err);
×
703
                        _OtherRequest ->
704
                            ejabberd_router:route_iq(
9✔
705
                              xmpp:set_from_to(Packet, FromJID, To), Packet, self())
706
                    end
707
            end;
708
        _ ->
709
            ErrText = ?T("Queries to the conference members are "
18✔
710
                         "not allowed in this room"),
711
            Err = xmpp:err_not_allowed(ErrText, Lang),
18✔
712
            ejabberd_router:route_error(Packet, Err)
18✔
713
    catch _:{badkey, _} ->
714
            ErrText = ?T("Only occupants are allowed to send queries "
9✔
715
                         "to the conference"),
716
            Err = xmpp:err_not_acceptable(ErrText, Lang),
9✔
717
            ejabberd_router:route_error(Packet, Err)
9✔
718
    end,
719
    {next_state, normal_state, StateData};
36✔
720
normal_state(hibernate, StateData) ->
721
    case maps:size(StateData#state.users) of
×
722
        0 ->
723
            store_room_no_checks(StateData, [], true),
×
724
            ?INFO_MSG("Hibernating room ~ts@~ts", [StateData#state.room, StateData#state.host]),
×
725
            {stop, normal, StateData#state{hibernate_timer = hibernating}};
×
726
        _ ->
727
            {next_state, normal_state, StateData}
×
728
    end;
729
normal_state(_Event, StateData) ->
730
    {next_state, normal_state, StateData}.
×
731

732
handle_event({service_message, Msg}, _StateName,
733
             StateData) ->
734
    MessagePkt = #message{type = groupchat, body = xmpp:mk_text(Msg)},
×
735
    send_wrapped_multiple(
×
736
      StateData#state.jid,
737
      get_users_and_subscribers_with_node(?NS_MUCSUB_NODES_MESSAGES, StateData),
738
      MessagePkt,
739
      ?NS_MUCSUB_NODES_MESSAGES,
740
      StateData),
741
    NSD = add_message_to_history(<<"">>,
×
742
                                 StateData#state.jid, MessagePkt, StateData),
743
    {next_state, normal_state, NSD};
×
744
handle_event({destroy, Reason}, _StateName,
745
             StateData) ->
746
    _ = destroy_room(#muc_destroy{xmlns = ?NS_MUC_OWNER, reason = Reason}, StateData),
×
747
    ?INFO_MSG("Destroyed MUC room ~ts with reason: ~p",
×
748
              [jid:encode(StateData#state.jid), Reason]),
×
749
    add_to_log(room_existence, destroyed, StateData),
×
750
    Conf = StateData#state.config,
×
751
    {stop, shutdown, StateData#state{config = Conf#config{persistent = {destroying, Conf#config.persistent}}}};
×
752
handle_event(destroy, StateName, StateData) ->
753
    ?INFO_MSG("Destroyed MUC room ~ts",
×
754
              [jid:encode(StateData#state.jid)]),
×
755
    handle_event({destroy, <<"">>}, StateName, StateData);
×
756
handle_event({set_affiliations, Affiliations},
757
             StateName, StateData) ->
758
    NewStateData = set_affiliations(Affiliations, StateData),
×
759
    {next_state, StateName, NewStateData};
×
760
handle_event({process_item_change, Item, UJID}, StateName, StateData) ->
761
    case process_item_change(Item, StateData, UJID) of
×
762
        {error, _} ->
763
            {next_state, StateName, StateData};
×
764
        StateData ->
765
            {next_state, StateName, StateData};
×
766
        NSD ->
767
            store_room(NSD),
×
768
            {next_state, StateName, NSD}
×
769
    end;
770
handle_event(_Event, StateName, StateData) ->
771
    {next_state, StateName, StateData}.
×
772

773
handle_sync_event({get_disco_item, Filter, JID, Lang, Time}, _From, StateName, StateData) ->
774
    Len = maps:size(StateData#state.nicks),
63✔
775
    Reply = case (Filter == all) or (Filter == Len) or ((Filter /= 0) and (Len /= 0)) of
63✔
776
        true ->
777
            get_roomdesc_reply(JID, StateData,
63✔
778
                               get_roomdesc_tail(StateData, Lang));
779
        false ->
780
            false
×
781
    end,
782
    CurrentTime = erlang:system_time(millisecond),
63✔
783
    if CurrentTime < Time ->
63✔
784
            {reply, Reply, StateName, StateData};
63✔
785
       true ->
786
            {next_state, StateName, StateData}
×
787
    end;
788
%% These two clauses are only for backward compatibility with nodes running old code
789
handle_sync_event({get_disco_item, JID, Lang}, From, StateName, StateData) ->
790
    handle_sync_event({get_disco_item, any, JID, Lang}, From, StateName, StateData);
×
791
handle_sync_event({get_disco_item, Filter, JID, Lang}, From, StateName, StateData) ->
792
    handle_sync_event({get_disco_item, Filter, JID, Lang, infinity}, From, StateName, StateData);
×
793
handle_sync_event(get_config, _From, StateName,
794
                  StateData) ->
795
    {reply, {ok, StateData#state.config}, StateName,
×
796
     StateData};
797
handle_sync_event(get_state, _From, StateName,
798
                  StateData) ->
799
    {reply, {ok, StateData}, StateName, StateData};
×
800
handle_sync_event(get_info, _From, StateName,
801
                  StateData) ->
802
    Result = #{occupants_number => maps:size(StateData#state.users)},
×
803
    {reply, Result, StateName, StateData};
×
804
handle_sync_event({change_config, Config}, _From,
805
                  StateName, StateData) ->
806
    {result, undefined, NSD} = change_config(Config, StateData),
×
807
    {reply, {ok, NSD#state.config}, StateName, NSD};
×
808
handle_sync_event({change_state, NewStateData}, _From,
809
                  StateName, _StateData) ->
810
    Mod = gen_mod:db_mod(NewStateData#state.server_host, mod_muc),
×
811
    case erlang:function_exported(Mod, get_subscribed_rooms, 3) of
×
812
        true ->
813
            ok;
×
814
        _ ->
815
            erlang:put(muc_subscribers, NewStateData#state.muc_subscribers#muc_subscribers.subscribers)
×
816
    end,
817
    {reply, {ok, NewStateData}, StateName, NewStateData};
×
818
handle_sync_event({process_item_change, Item, UJID}, _From, StateName, StateData) ->
819
    case process_item_change(Item, StateData, UJID) of
9✔
820
        {error, _} = Err ->
821
            {reply, Err, StateName, StateData};
×
822
        StateData ->
823
            {reply, {ok, StateData}, StateName, StateData};
×
824
        NSD ->
825
            store_room(NSD),
9✔
826
            {reply, {ok, NSD}, StateName, NSD}
9✔
827
    end;
828
handle_sync_event(get_subscribers, _From, StateName, StateData) ->
829
    JIDs = muc_subscribers_fold(
×
830
             fun(_LBareJID, #subscriber{jid = JID}, Acc) ->
831
                     [JID | Acc]
×
832
             end, [], StateData#state.muc_subscribers),
833
    {reply, {ok, JIDs}, StateName, StateData};
×
834
handle_sync_event({muc_subscribe, From, Nick, Nodes}, _From,
835
                  StateName, StateData) ->
836
    IQ = #iq{type = set, id = p1_rand:get_string(),
×
837
             from = From, sub_els = [#muc_subscribe{nick = Nick,
838
                                                    events = Nodes}]},
839
    Config = StateData#state.config,
×
840
    CaptchaRequired = Config#config.captcha_protected,
×
841
    PasswordProtected = Config#config.password_protected,
×
842
    MembersOnly = Config#config.members_only,
×
843
    TmpConfig = Config#config{captcha_protected = false,
×
844
                              password_protected = false,
845
                              members_only = false},
846
    TmpState = StateData#state{config = TmpConfig},
×
847
    case process_iq_mucsub(From, IQ, TmpState) of
×
848
        {result, #muc_subscribe{events = NewNodes}, NewState} ->
849
            NewConfig = (NewState#state.config)#config{
×
850
                          captcha_protected = CaptchaRequired,
851
                          password_protected = PasswordProtected,
852
                          members_only = MembersOnly},
853
            {reply, {ok, NewNodes}, StateName,
×
854
             NewState#state{config = NewConfig}};
855
        {ignore, NewState} ->
856
            NewConfig = (NewState#state.config)#config{
×
857
                          captcha_protected = CaptchaRequired,
858
                          password_protected = PasswordProtected,
859
                          members_only = MembersOnly},
860
            {reply, {error, ?T("Request is ignored")},
×
861
             NewState#state{config = NewConfig}};
862
        {error, Err} ->
863
            {reply, {error, get_error_text(Err)}, StateName, StateData}
×
864
    end;
865
handle_sync_event({muc_unsubscribe, From}, _From, StateName,
866
                  #state{config = Conf} = StateData) ->
867
    IQ = #iq{type = set, id = p1_rand:get_string(),
9✔
868
             from = From, sub_els = [#muc_unsubscribe{}]},
869
    case process_iq_mucsub(From, IQ, StateData) of
9✔
870
        {result, _, stop} ->
871
            {stop, normal, StateData#state{config = Conf#config{persistent = {destroying, Conf#config.persistent}}}};
×
872
        {result, _, NewState} ->
873
            {reply, ok, StateName, NewState};
9✔
874
        {ignore, NewState} ->
875
            {reply, {error, ?T("Request is ignored")}, NewState};
×
876
        {error, Err} ->
877
            {reply, {error, get_error_text(Err)}, StateName, StateData}
×
878
    end;
879
handle_sync_event({is_subscribed, From}, _From, StateName, StateData) ->
880
    IsSubs = try muc_subscribers_get(
21✔
881
                   jid:split(From), StateData#state.muc_subscribers) of
882
                 #subscriber{nick = Nick, nodes = Nodes} -> {true, Nick, Nodes}
21✔
883
             catch _:{badkey, _} -> false
×
884
             end,
885
    {reply, IsSubs, StateName, StateData};
21✔
886
handle_sync_event(_Event, _From, StateName,
887
                  StateData) ->
888
    Reply = ok, {reply, Reply, StateName, StateData}.
×
889

890
code_change(_OldVsn, StateName, StateData, _Extra) ->
891
    {ok, StateName, StateData}.
×
892

893
handle_info({process_user_presence, From}, normal_state = _StateName, StateData) ->
894
    RoomQueueEmpty = p1_queue:is_empty(StateData#state.room_queue),
×
895
    RoomQueue = p1_queue:in({presence, From}, StateData#state.room_queue),
×
896
    StateData1 = StateData#state{room_queue = RoomQueue},
×
897
    if RoomQueueEmpty ->
×
898
           StateData2 = prepare_room_queue(StateData1),
×
899
           {next_state, normal_state, StateData2};
×
900
       true -> {next_state, normal_state, StateData1}
×
901
    end;
902
handle_info({process_user_message, From},
903
            normal_state = _StateName, StateData) ->
904
    RoomQueueEmpty =
×
905
        p1_queue:is_empty(StateData#state.room_queue),
906
    RoomQueue = p1_queue:in({message, From},
×
907
                            StateData#state.room_queue),
908
    StateData1 = StateData#state{room_queue = RoomQueue},
×
909
    if RoomQueueEmpty ->
×
910
           StateData2 = prepare_room_queue(StateData1),
×
911
           {next_state, normal_state, StateData2};
×
912
       true -> {next_state, normal_state, StateData1}
×
913
    end;
914
handle_info(process_room_queue,
915
            normal_state = StateName, StateData) ->
916
    case p1_queue:out(StateData#state.room_queue) of
×
917
      {{value, {message, From}}, RoomQueue} ->
918
          Activity = get_user_activity(From, StateData),
×
919
          Packet = Activity#activity.message,
×
920
          NewActivity = Activity#activity{message = undefined},
×
921
          StateData1 = store_user_activity(From, NewActivity,
×
922
                                           StateData),
923
          StateData2 = StateData1#state{room_queue = RoomQueue},
×
924
          StateData3 = prepare_room_queue(StateData2),
×
925
          process_groupchat_message(Packet, StateData3);
×
926
      {{value, {presence, From}}, RoomQueue} ->
927
          Activity = get_user_activity(From, StateData),
×
928
          {Nick, Packet} = Activity#activity.presence,
×
929
          NewActivity = Activity#activity{presence = undefined},
×
930
          StateData1 = store_user_activity(From, NewActivity,
×
931
                                           StateData),
932
          StateData2 = StateData1#state{room_queue = RoomQueue},
×
933
          StateData3 = prepare_room_queue(StateData2),
×
934
          process_presence(Nick, Packet, StateData3);
×
935
      {empty, _} -> {next_state, StateName, StateData}
×
936
    end;
937
handle_info({captcha_succeed, From}, normal_state,
938
            StateData) ->
939
    NewState = case maps:get(From, StateData#state.robots, passed) of
×
940
                   {Nick, Packet} ->
941
                       Robots = maps:put(From, passed, StateData#state.robots),
×
942
                       add_new_user(From, Nick, Packet,
×
943
                                    StateData#state{robots = Robots});
944
                   passed ->
945
                       StateData
×
946
               end,
947
    {next_state, normal_state, NewState};
×
948
handle_info({captcha_failed, From}, normal_state,
949
            StateData) ->
950
    NewState = case maps:get(From, StateData#state.robots, passed) of
×
951
                   {_Nick, Packet} ->
952
                       Robots = maps:remove(From, StateData#state.robots),
×
953
                       Txt = ?T("The CAPTCHA verification has failed"),
×
954
                       Lang = xmpp:get_lang(Packet),
×
955
                       Err = xmpp:err_not_authorized(Txt, Lang),
×
956
                       ejabberd_router:route_error(Packet, Err),
×
957
                       StateData#state{robots = Robots};
×
958
                   passed ->
959
                       StateData
×
960
               end,
961
    {next_state, normal_state, NewState};
×
962
handle_info(close_room_if_temporary_and_empty, _StateName, StateData) ->
963
    close_room_if_temporary_and_empty(StateData);
×
964
handle_info(shutdown, _StateName, StateData) ->
965
    {stop, shutdown, StateData};
×
966
handle_info({iq_reply, #iq{type = Type, sub_els = Els},
967
             #iq{from = From, to = To} = IQ}, StateName, StateData) ->
968
    ejabberd_router:route(
9✔
969
      xmpp:set_from_to(
970
        IQ#iq{type = Type, sub_els = Els},
971
        To, From)),
972
    {next_state, StateName, StateData};
9✔
973
handle_info({iq_reply, timeout, IQ}, StateName, StateData) ->
974
    Txt = ?T("Request has timed out"),
×
975
    Err = xmpp:err_recipient_unavailable(Txt, IQ#iq.lang),
×
976
    ejabberd_router:route_error(IQ, Err),
×
977
    {next_state, StateName, StateData};
×
978
handle_info(config_reloaded, StateName, StateData) ->
979
    Max = mod_muc_opt:history_size(StateData#state.server_host),
×
980
    History1 = StateData#state.history,
×
981
    Q1 = History1#lqueue.queue,
×
982
    Q2 = case p1_queue:len(Q1) of
×
983
             Len when Len > Max ->
984
                 lqueue_cut(Q1, Len-Max);
×
985
             _ ->
986
                 Q1
×
987
         end,
988
    History2 = History1#lqueue{queue = Q2, max = Max},
×
989
    {next_state, StateName, StateData#state{history = History2}};
×
990
handle_info(_Info, StateName, StateData) ->
991
    {next_state, StateName, StateData}.
×
992

993
terminate(Reason, _StateName,
994
          #state{server_host = LServer, host = Host, room = Room} = StateData) ->
995
    try
418✔
996
        ?INFO_MSG("Stopping MUC room ~ts@~ts", [Room, Host]),
418✔
997
        ReasonT = case Reason of
418✔
998
                      shutdown ->
999
                          ?T("You are being removed from the room "
×
1000
                             "because of a system shutdown");
1001
                      _ -> ?T("Room terminates")
418✔
1002
                  end,
1003
        Packet = #presence{
418✔
1004
                    type = unavailable,
1005
                    sub_els = [#muc_user{items = [#muc_item{affiliation = none,
1006
                                                            reason = ReasonT,
1007
                                                            role = none}],
1008
                                         status_codes = [332,110]}]},
1009
        maps:fold(
418✔
1010
          fun(_, #user{nick = Nick, jid = JID}, _) ->
1011
                  case Reason of
18✔
1012
                      shutdown ->
1013
                          send_wrapped(jid:replace_resource(StateData#state.jid, Nick),
×
1014
                                       JID, Packet,
1015
                                       ?NS_MUCSUB_NODES_PARTICIPANTS,
1016
                                       StateData);
1017
                      _ -> ok
18✔
1018
                  end,
1019
                  tab_remove_online_user(JID, StateData)
18✔
1020
          end, [], get_users_and_subscribers_with_node(
1021
                     ?NS_MUCSUB_NODES_PARTICIPANTS, StateData)),
1022

1023
        disable_hibernate_timer(StateData),
418✔
1024
        case StateData#state.hibernate_timer of
418✔
1025
            hibernating ->
1026
                ok;
×
1027
            _ ->
1028
                add_to_log(room_existence, stopped, StateData),
418✔
1029
                case (StateData#state.config)#config.persistent of
418✔
1030
                    false ->
1031
                        ejabberd_hooks:run(room_destroyed, LServer, [LServer, Room, Host, false]);
409✔
1032
                    {destroying, Persistent} ->
1033
                        ejabberd_hooks:run(room_destroyed, LServer, [LServer, Room, Host, Persistent]);
9✔
1034
                    _ ->
1035
                        ok
×
1036
                end
1037
        end
1038
    catch
1039
        E:R:StackTrace ->
1040
            ?ERROR_MSG("Got exception on room termination:~n** ~ts",
×
1041
                       [misc:format_exception(2, E, R, StackTrace)])
×
1042
    end.
1043

1044
%%%----------------------------------------------------------------------
1045
%%% Internal functions
1046
%%%----------------------------------------------------------------------
1047
-spec route(pid(), stanza()) -> ok.
1048
route(Pid, Packet) ->
1049
    ?DEBUG("Routing to MUC room ~p:~n~ts", [Pid, xmpp:pp(Packet)]),
4,154✔
1050
    #jid{lresource = Nick} = xmpp:get_to(Packet),
4,154✔
1051
    p1_fsm:send_event(Pid, {route, Nick, Packet}).
4,154✔
1052

1053
-spec process_groupchat_message(message(), state()) -> fsm_next().
1054
process_groupchat_message(#message{from = From, lang = Lang} = Packet, StateData) ->
1055
    IsSubscriber = is_subscriber(From, StateData),
649✔
1056
    case is_user_online(From, StateData) orelse IsSubscriber orelse
649✔
1057
           is_user_allowed_message_nonparticipant(From, StateData)
×
1058
        of
1059
      true ->
1060
          {FromNick, Role} = get_participant_data(From, StateData),
649✔
1061
          #config{moderated = Moderated} = StateData#state.config,
649✔
1062
          AllowedByModerationRules =
649✔
1063
          case {Role == moderator orelse Role == participant orelse
649✔
1064
                not Moderated, IsSubscriber} of
18✔
1065
              {true, _} -> true;
640✔
1066
              {_, true} ->
1067
                  % We assume all subscribers are at least members
1068
                  true;
×
1069
              _ ->
1070
                  false
9✔
1071
          end,
1072
          if AllowedByModerationRules ->
649✔
1073
              Subject = check_subject(Packet),
640✔
1074
              {NewStateData1, IsAllowed} =
640✔
1075
              case Subject of
1076
                  [] ->
1077
                      {StateData, true};
604✔
1078
                  _ ->
1079
                      case
36✔
1080
                          can_change_subject(Role,
1081
                                             IsSubscriber,
1082
                                             StateData)
1083
                      of
1084
                          true ->
1085
                              NSD =
27✔
1086
                              StateData#state{subject = Subject,
1087
                                              subject_author = {FromNick, From}},
1088
                              store_room(NSD),
27✔
1089
                              {NSD, true};
27✔
1090
                          _ -> {StateData, false}
9✔
1091
                      end
1092
              end,
1093
              case IsAllowed of
640✔
1094
                   true ->
1095
                       case
631✔
1096
                         ejabberd_hooks:run_fold(muc_filter_message,
1097
                                                 StateData#state.server_host,
1098
                                                 Packet,
1099
                                                 [StateData, FromNick])
1100
                           of
1101
                         drop ->
1102
                             {next_state, normal_state, StateData};
×
1103
                         NewPacket1 ->
1104
                             NewPacket = xmpp:put_meta(xmpp:remove_subtag(
631✔
1105
                                 add_stanza_id(NewPacket1, StateData), #nick{}),
1106
                                 muc_sender_real_jid, From),
1107
                             Node = if Subject == [] -> ?NS_MUCSUB_NODES_MESSAGES;
631✔
1108
                                       true -> ?NS_MUCSUB_NODES_SUBJECT
27✔
1109
                                    end,
1110
                             NewStateData2 = check_message_for_retractions(NewPacket1, NewStateData1),
631✔
1111
                             send_wrapped_multiple(
631✔
1112
                               jid:replace_resource(StateData#state.jid, FromNick),
1113
                               get_users_and_subscribers_with_node(Node, StateData),
1114
                               NewPacket, Node, NewStateData2),
1115
                             NewStateData3 = case has_body_or_subject(NewPacket) of
631✔
1116
                                               true ->
1117
                                                   add_message_to_history(FromNick, From,
622✔
1118
                                                                          NewPacket,
1119
                                                                          NewStateData2);
1120
                                               false ->
1121
                                                   NewStateData2
9✔
1122
                                             end,
1123
                             {next_state, normal_state, NewStateData3}
631✔
1124
                       end;
1125
                   _ ->
1126
                       Err = case (StateData#state.config)#config.allow_change_subj of
9✔
1127
                               true ->
1128
                                   xmpp:err_forbidden(
×
1129
                                     ?T("Only moderators and participants are "
1130
                                        "allowed to change the subject in this "
1131
                                        "room"), Lang);
1132
                               _ ->
1133
                                   xmpp:err_forbidden(
9✔
1134
                                     ?T("Only moderators are allowed to change "
1135
                                        "the subject in this room"), Lang)
1136
                             end,
1137
                       ejabberd_router:route_error(Packet, Err),
9✔
1138
                       {next_state, normal_state, StateData}
9✔
1139
                 end;
1140
             true ->
1141
                 ErrText = ?T("Visitors are not allowed to send messages "
9✔
1142
                              "to all occupants"),
1143
                 Err = xmpp:err_forbidden(ErrText, Lang),
9✔
1144
                 ejabberd_router:route_error(Packet, Err),
9✔
1145
                 {next_state, normal_state, StateData}
9✔
1146
          end;
1147
      false ->
1148
          ErrText = ?T("Only occupants are allowed to send messages "
×
1149
                       "to the conference"),
1150
          Err = xmpp:err_not_acceptable(ErrText, Lang),
×
1151
          ejabberd_router:route_error(Packet, Err),
×
1152
          {next_state, normal_state, StateData}
×
1153
    end.
1154

1155
-spec check_message_for_retractions(Packet :: message(), State :: state()) -> state().
1156
check_message_for_retractions(Packet,
1157
                          #state{config = Config, room = Room, host = Host,
1158
                                 server_host = Server} = State) ->
1159
    case xmpp:get_subtag(Packet, #fasten_apply_to{}) of
631✔
1160
        #fasten_apply_to{id = ID} = F ->
1161
            case xmpp:get_subtag(F, #message_retract{}) of
×
1162
                #message_retract{} ->
1163
                    #jid{luser = U, lserver = S} = xmpp:get_from(Packet),
×
1164
                    case remove_from_history({U, S}, ID, State) of
×
1165
                        {NewState, StanzaId} when is_integer(StanzaId) ->
1166
                            case Config#config.mam of
×
1167
                                true ->
1168
                                    mod_mam:remove_message_from_archive({Room, Host}, Server, StanzaId),
×
1169
                                    NewState;
×
1170
                                _ ->
1171
                                    NewState
×
1172
                            end;
1173
                        {NewState, _} ->
1174
                            NewState
×
1175
                    end;
1176
                _ ->
1177
                    State
×
1178
            end;
1179
        _ ->
1180
            State
631✔
1181
    end.
1182

1183
-spec add_stanza_id(Packet :: message(), State :: state()) -> message().
1184
add_stanza_id(Packet, #state{jid = JID}) ->
1185
    {AddId, NewPacket} =
631✔
1186
    case xmpp:get_meta(Packet, stanza_id, false) of
1187
        false ->
1188
            GenID = erlang:system_time(microsecond),
×
1189
            {true, xmpp:put_meta(Packet, stanza_id, GenID)};
×
1190
        _ ->
1191
            StanzaIds = xmpp:get_subtags(Packet, #stanza_id{by = #jid{}}),
631✔
1192
            HasOurStanzaId = lists:any(
631✔
1193
                fun(#stanza_id{by = JID2}) when JID == JID2 -> true;
176✔
1194
                   (_) -> false
×
1195
                end, StanzaIds),
1196
            {not HasOurStanzaId, Packet}
631✔
1197
    end,
1198
    if
631✔
1199
        AddId ->
1200
            ID = xmpp:get_meta(NewPacket, stanza_id),
455✔
1201
            IDs = integer_to_binary(ID),
455✔
1202
            xmpp:append_subtags(NewPacket, [#stanza_id{by = JID, id = IDs}]);
455✔
1203
        true ->
1204
            Packet
176✔
1205
    end.
1206

1207
-spec process_normal_message(jid(), message(), state()) -> state().
1208
process_normal_message(From, #message{lang = Lang} = Pkt, StateData) ->
1209
    Action = lists:foldl(
117✔
1210
               fun(_, {error, _} = Err) ->
1211
                       Err;
×
1212
                  (_, {ok, _} = Result) ->
1213
                       Result;
×
1214
                  (#muc_user{invites = [_|_] = Invites}, _) ->
1215
                       case check_invitation(From, Invites, Lang, StateData) of
54✔
1216
                           ok ->
1217
                               {ok, Invites};
45✔
1218
                           {error, _} = Err ->
1219
                               Err
9✔
1220
                       end;
1221
                  (#xdata{type = submit, fields = Fs}, _) ->
1222
                       try {ok, muc_request:decode(Fs)}
63✔
1223
                       catch _:{muc_request, Why} ->
1224
                               Txt = muc_request:format_error(Why),
×
1225
                               {error, xmpp:err_bad_request(Txt, Lang)}
×
1226
                       end;
1227
                  (_, Acc) ->
1228
                       Acc
×
1229
               end, ok, xmpp:get_els(Pkt)),
1230
    case Action of
117✔
1231
        {ok, [#muc_invite{}|_] = Invitations} ->
1232
            lists:foldl(
45✔
1233
              fun(Invitation, AccState) ->
1234
                      process_invitation(From, Pkt, Invitation, Lang, AccState)
45✔
1235
              end, StateData, Invitations);
1236
        {ok, [{role, participant}]} ->
1237
            process_voice_request(From, Pkt, StateData);
45✔
1238
        {ok, VoiceApproval} ->
1239
            process_voice_approval(From, Pkt, VoiceApproval, StateData);
18✔
1240
        {error, Err} ->
1241
            ejabberd_router:route_error(Pkt, Err),
9✔
1242
            StateData;
9✔
1243
        ok ->
1244
            StateData
×
1245
    end.
1246

1247
-spec process_invitation(jid(), message(), muc_invite(), binary(), state()) -> state().
1248
process_invitation(From, Pkt, Invitation, Lang, StateData) ->
1249
    IJID = route_invitation(From, Pkt, Invitation, Lang, StateData),
45✔
1250
    Config = StateData#state.config,
45✔
1251
    case Config#config.members_only of
45✔
1252
        true ->
1253
            case get_affiliation(IJID, StateData) of
9✔
1254
                none ->
1255
                    NSD = set_affiliation(IJID, member, StateData),
9✔
1256
                    send_affiliation(IJID, member, StateData),
9✔
1257
                    store_room(NSD),
9✔
1258
                    NSD;
9✔
1259
                _ ->
1260
                    StateData
×
1261
            end;
1262
        false ->
1263
            StateData
36✔
1264
    end.
1265

1266
-spec process_voice_request(jid(), message(), state()) -> state().
1267
process_voice_request(From, Pkt, StateData) ->
1268
    Lang = xmpp:get_lang(Pkt),
45✔
1269
    case (StateData#state.config)#config.allow_voice_requests of
45✔
1270
        true ->
1271
            MinInterval = (StateData#state.config)#config.voice_request_min_interval,
36✔
1272
            BareFrom = jid:remove_resource(jid:tolower(From)),
36✔
1273
            NowPriority = -erlang:system_time(microsecond),
36✔
1274
            CleanPriority = NowPriority + MinInterval * 1000000,
36✔
1275
            Times = clean_treap(StateData#state.last_voice_request_time,
36✔
1276
                                CleanPriority),
1277
            case treap:lookup(BareFrom, Times) of
36✔
1278
                error ->
1279
                    Times1 = treap:insert(BareFrom,
27✔
1280
                                          NowPriority,
1281
                                          true, Times),
1282
                    NSD = StateData#state{last_voice_request_time = Times1},
27✔
1283
                    send_voice_request(From, Lang, NSD),
27✔
1284
                    NSD;
27✔
1285
                {ok, _, _} ->
1286
                    ErrText = ?T("Please, wait for a while before sending "
9✔
1287
                                 "new voice request"),
1288
                    Err = xmpp:err_resource_constraint(ErrText, Lang),
9✔
1289
                    ejabberd_router:route_error(Pkt, Err),
9✔
1290
                    StateData#state{last_voice_request_time = Times}
9✔
1291
            end;
1292
        false ->
1293
            ErrText = ?T("Voice requests are disabled in this conference"),
9✔
1294
            Err = xmpp:err_forbidden(ErrText, Lang),
9✔
1295
            ejabberd_router:route_error(Pkt, Err),
9✔
1296
            StateData
9✔
1297
    end.
1298

1299
-spec process_voice_approval(jid(), message(), [muc_request:property()], state()) -> state().
1300
process_voice_approval(From, Pkt, VoiceApproval, StateData) ->
1301
    Lang = xmpp:get_lang(Pkt),
18✔
1302
    case is_moderator(From, StateData) of
18✔
1303
        true ->
1304
            case lists:keyfind(jid, 1, VoiceApproval) of
18✔
1305
                {_, TargetJid} ->
1306
                    Allow = proplists:get_bool(request_allow, VoiceApproval),
18✔
1307
                    case is_visitor(TargetJid, StateData) of
18✔
1308
                        true when Allow ->
1309
                            Reason = <<>>,
9✔
1310
                            NSD = set_role(TargetJid, participant, StateData),
9✔
1311
                            catch send_new_presence(
9✔
1312
                                    TargetJid, Reason, NSD, StateData),
1313
                            NSD;
9✔
1314
                        _ ->
1315
                            StateData
9✔
1316
                    end;
1317
                false ->
1318
                    ErrText = ?T("Failed to extract JID from your voice "
×
1319
                                 "request approval"),
1320
                    Err = xmpp:err_bad_request(ErrText, Lang),
×
1321
                    ejabberd_router:route_error(Pkt, Err),
×
1322
                    StateData
×
1323
            end;
1324
        false ->
1325
            ErrText = ?T("Only moderators can approve voice requests"),
×
1326
            Err = xmpp:err_not_allowed(ErrText, Lang),
×
1327
            ejabberd_router:route_error(Pkt, Err),
×
1328
            StateData
×
1329
    end.
1330

1331
-spec direct_iq_type(iq()) -> vcard | ping | request | response | pubsub | stanza_error().
1332
direct_iq_type(#iq{type = T, sub_els = SubEls, lang = Lang}) when T == get; T == set ->
1333
    case SubEls of
36✔
1334
        [El] ->
1335
            case xmpp:get_ns(El) of
36✔
1336
                ?NS_VCARD when T == get -> vcard;
×
1337
                ?NS_PUBSUB when T == get -> pubsub;
×
1338
                ?NS_PING when T == get -> ping;
36✔
1339
                _ -> request
×
1340
            end;
1341
        [] ->
1342
            xmpp:err_bad_request(?T("No child elements found"), Lang);
×
1343
        [_|_] ->
1344
            xmpp:err_bad_request(?T("Too many child elements"), Lang)
×
1345
    end;
1346
direct_iq_type(#iq{}) ->
1347
    response.
×
1348

1349
%% @doc Check if this non participant can send message to room.
1350
%%
1351
%% XEP-0045 v1.23:
1352
%% 7.9 Sending a Message to All Occupants
1353
%% an implementation MAY allow users with certain privileges
1354
%% (e.g., a room owner, room admin, or service-level admin)
1355
%% to send messages to the room even if those users are not occupants.
1356
-spec is_user_allowed_message_nonparticipant(jid(), state()) -> boolean().
1357
is_user_allowed_message_nonparticipant(JID,
1358
                                       StateData) ->
1359
    case get_service_affiliation(JID, StateData) of
9✔
1360
      owner -> true;
×
1361
      _ -> false
9✔
1362
    end.
1363

1364
-spec is_user_allowed_private_message(jid(), state()) -> boolean().
1365
is_user_allowed_private_message(JID, StateData) ->
1366
    case {(StateData#state.config)#config.allowpm,
108✔
1367
            get_role(JID, StateData)} of
1368
        {anyone, _} ->
1369
            true;
90✔
1370
        {participants, moderator} ->
1371
            true;
×
1372
        {participants, participant} ->
1373
            true;
×
1374
        {moderators, moderator} ->
1375
            true;
×
1376
        {none, _} ->
1377
            false;
18✔
1378
        {_, _} ->
1379
            false
×
1380
    end.
1381

1382
%% @doc Get information of this participant, or default values.
1383
%% If the JID is not a participant, return values for a service message.
1384
-spec get_participant_data(jid(), state()) -> {binary(), role()}.
1385
get_participant_data(From, StateData) ->
1386
    try maps:get(jid:tolower(From), StateData#state.users) of
721✔
1387
        #user{nick = FromNick, role = Role} ->
1388
            {FromNick, Role}
721✔
1389
    catch _:{badkey, _} ->
1390
            try muc_subscribers_get(jid:tolower(jid:remove_resource(From)),
×
1391
                                    StateData#state.muc_subscribers) of
1392
                #subscriber{nick = FromNick} ->
1393
                    {FromNick, none}
×
1394
            catch _:{badkey, _} ->
1395
                    {From#jid.luser, moderator}
×
1396
            end
1397
    end.
1398

1399
-spec process_presence(binary(), presence(), state()) -> fsm_transition().
1400
process_presence(Nick, #presence{from = From, type = Type0} = Packet0, StateData) ->
1401
    IsOnline = is_user_online(From, StateData),
1,443✔
1402
    if Type0 == available;
1,443✔
1403
       IsOnline and ((Type0 == unavailable) or (Type0 == error)) ->
1404
           case ejabberd_hooks:run_fold(muc_filter_presence,
1,394✔
1405
                                        StateData#state.server_host,
1406
                                        Packet0,
1407
                                        [StateData, Nick]) of
1408
             drop ->
1409
                 {next_state, normal_state, StateData};
×
1410
             #presence{} = Packet ->
1411
                 close_room_if_temporary_and_empty(
1,394✔
1412
                   do_process_presence(Nick, Packet, StateData))
1413
           end;
1414
       true ->
1415
            {next_state, normal_state, StateData}
49✔
1416
    end.
1417

1418
-spec do_process_presence(binary(), presence(), state()) -> state().
1419
do_process_presence(Nick, #presence{from = From, type = available, lang = Lang} = Packet,
1420
                    StateData) ->
1421
    case is_user_online(From, StateData) of
769✔
1422
        false ->
1423
            add_new_user(From, Nick, Packet, StateData);
715✔
1424
        true ->
1425
            case is_nick_change(From, Nick, StateData) of
54✔
1426
                true ->
1427
                    case {nick_collision(From, Nick, StateData),
18✔
1428
                          mod_muc:can_use_nick(StateData#state.server_host,
1429
                                               jid:encode(StateData#state.jid),
1430
                                               From, Nick),
1431
                          {(StateData#state.config)#config.allow_visitor_nickchange,
1432
                           is_visitor(From, StateData)}} of
1433
                        {_, _, {false, true}} ->
1434
                            Packet1 = Packet#presence{sub_els = [#muc{}]},
9✔
1435
                            ErrText = ?T("Visitors are not allowed to change their "
9✔
1436
                                         "nicknames in this room"),
1437
                            Err = xmpp:err_not_allowed(ErrText, Lang),
9✔
1438
                            ejabberd_router:route_error(Packet1, Err),
9✔
1439
                            StateData;
9✔
1440
                        {true, _, _} ->
1441
                            Packet1 = Packet#presence{sub_els = [#muc{}]},
×
1442
                            ErrText = ?T("That nickname is already in use by another "
×
1443
                                         "occupant"),
1444
                            Err = xmpp:err_conflict(ErrText, Lang),
×
1445
                            ejabberd_router:route_error(Packet1, Err),
×
1446
                            StateData;
×
1447
                        {_, false, _} ->
1448
                            Packet1 = Packet#presence{sub_els = [#muc{}]},
×
1449
                            Err = case Nick of
×
1450
                                      <<>> ->
1451
                                          xmpp:err_jid_malformed(?T("Nickname can't be empty"),
×
1452
                                                                 Lang);
1453
                                      _ ->
1454
                                          xmpp:err_conflict(?T("That nickname is registered"
×
1455
                                                               " by another person"), Lang)
1456
                                  end,
1457
                            ejabberd_router:route_error(Packet1, Err),
×
1458
                            StateData;
×
1459
                        _ ->
1460
                            change_nick(From, Nick, StateData)
9✔
1461
                    end;
1462
                false ->
1463
                    Stanza = maybe_strip_status_from_presence(
36✔
1464
                               From, Packet, StateData),
1465
                    NewState = add_user_presence(From, Stanza,
36✔
1466
                                                 StateData),
1467
                    case xmpp:has_subtag(Packet, #muc{}) of
36✔
1468
                        true ->
1469
                            send_initial_presences_and_messages(
×
1470
                              From, Nick, Packet, NewState, StateData);
1471
                        false ->
1472
                            send_new_presence(From, NewState, StateData)
36✔
1473
                    end,
1474
                    NewState
36✔
1475
            end
1476
    end;
1477
do_process_presence(Nick, #presence{from = From, type = unavailable} = Packet,
1478
                    StateData) ->
1479
    NewPacket = case {(StateData#state.config)#config.allow_visitor_status,
625✔
1480
                      is_visitor(From, StateData)} of
1481
                    {false, true} ->
1482
                        strip_status(Packet);
9✔
1483
                    _ -> Packet
616✔
1484
                end,
1485
    NewState = add_user_presence_un(From, NewPacket, StateData),
625✔
1486
    case maps:get(Nick, StateData#state.nicks, []) of
625✔
1487
        [_, _ | _] ->
1488
            Aff = get_affiliation(From, StateData),
×
1489
            Item = #muc_item{affiliation = Aff, role = none, jid = From},
×
1490
            Pres = xmpp:set_subtag(
×
1491
                     Packet, #muc_user{items = [Item],
1492
                                       status_codes = [110]}),
1493
            send_wrapped(jid:replace_resource(StateData#state.jid, Nick),
×
1494
                         From, Pres, ?NS_MUCSUB_NODES_PRESENCE, StateData);
1495
        _ ->
1496
            send_new_presence(From, NewState, StateData)
625✔
1497
    end,
1498
    Reason = xmpp:get_text(NewPacket#presence.status),
625✔
1499
    remove_online_user(From, NewState, Reason);
625✔
1500
do_process_presence(_Nick, #presence{from = From, type = error, lang = Lang} = Packet,
1501
                    StateData) ->
1502
    ErrorText = ?T("It is not allowed to send error messages to the"
×
1503
                   " room. The participant (~s) has sent an error "
1504
                   "message (~s) and got kicked from the room"),
1505
    expulse_participant(Packet, From, StateData,
×
1506
                        translate:translate(Lang, ErrorText)).
1507

1508
-spec maybe_strip_status_from_presence(jid(), presence(),
1509
                                       state()) -> presence().
1510
maybe_strip_status_from_presence(From, Packet, StateData) ->
1511
    case {(StateData#state.config)#config.allow_visitor_status,
36✔
1512
          is_visitor(From, StateData)} of
1513
        {false, true} ->
1514
            strip_status(Packet);
9✔
1515
        _Allowed -> Packet
27✔
1516
    end.
1517

1518
-spec close_room_if_temporary_and_empty(state()) -> fsm_transition().
1519
close_room_if_temporary_and_empty(StateData1) ->
1520
    case not (StateData1#state.config)#config.persistent
1,489✔
1521
        andalso maps:size(StateData1#state.users) == 0
889✔
1522
        andalso muc_subscribers_size(StateData1#state.muc_subscribers) == 0 of
409✔
1523
      true ->
1524
          ?INFO_MSG("Destroyed MUC room ~ts because it's temporary "
409✔
1525
                    "and empty",
1526
                    [jid:encode(StateData1#state.jid)]),
409✔
1527
          add_to_log(room_existence, destroyed, StateData1),
409✔
1528
          forget_room(StateData1),
409✔
1529
          {stop, normal, StateData1};
409✔
1530
      _ -> {next_state, normal_state, StateData1}
1,080✔
1531
    end.
1532

1533
-spec get_users_and_subscribers(state()) -> users().
1534
get_users_and_subscribers(StateData) ->
1535
    get_users_and_subscribers_aux(
90✔
1536
      StateData#state.muc_subscribers#muc_subscribers.subscribers,
1537
      StateData).
1538

1539
-spec get_users_and_subscribers_with_node(binary(), state()) -> users().
1540
get_users_and_subscribers_with_node(Node, StateData) ->
1541
    get_users_and_subscribers_aux(
5,059✔
1542
      muc_subscribers_get_by_node(Node, StateData#state.muc_subscribers),
1543
      StateData).
1544

1545
get_users_and_subscribers_aux(Subscribers, StateData) ->
1546
    OnlineSubscribers = maps:fold(
5,149✔
1547
                           fun(LJID, _, Acc) ->
1548
                                   LBareJID = jid:remove_resource(LJID),
6,441✔
1549
                                   case is_subscriber(LBareJID, StateData) of
6,441✔
1550
                                       true ->
1551
                                           ?SETS:add_element(LBareJID, Acc);
×
1552
                                       false ->
1553
                                           Acc
6,441✔
1554
                                   end
1555
                           end, ?SETS:new(), StateData#state.users),
1556
    maps:fold(
5,149✔
1557
       fun(LBareJID, #subscriber{nick = Nick}, Acc) ->
1558
               case ?SETS:is_element(LBareJID, OnlineSubscribers) of
144✔
1559
                   false ->
1560
                       maps:put(LBareJID,
144✔
1561
                                #user{jid = jid:make(LBareJID),
1562
                                      nick = Nick,
1563
                                      role = none,
1564
                                      last_presence = undefined},
1565
                                Acc);
1566
                   true ->
1567
                       Acc
×
1568
               end
1569
       end, StateData#state.users, Subscribers).
1570

1571
-spec is_user_online(jid(), state()) -> boolean().
1572
is_user_online(JID, StateData) ->
1573
    LJID = jid:tolower(JID),
3,760✔
1574
    maps:is_key(LJID, StateData#state.users).
3,760✔
1575

1576
-spec is_subscriber(jid(), state()) -> boolean().
1577
is_subscriber(JID, StateData) ->
1578
    LJID = jid:tolower(jid:remove_resource(JID)),
9,455✔
1579
    muc_subscribers_is_key(LJID, StateData#state.muc_subscribers).
9,455✔
1580

1581
%% Check if the user is occupant of the room, or at least is an admin or owner.
1582
-spec is_occupant_or_admin(jid(), state()) -> boolean().
1583
is_occupant_or_admin(JID, StateData) ->
1584
    FAffiliation = get_affiliation(JID, StateData),
72✔
1585
    FRole = get_role(JID, StateData),
72✔
1586
    case FRole /= none orelse
72✔
1587
           FAffiliation == member orelse
27✔
1588
           FAffiliation == admin orelse FAffiliation == owner
27✔
1589
        of
1590
      true -> true;
45✔
1591
      _ -> false
27✔
1592
    end.
1593

1594
%% Check if the user is an admin or owner.
1595
-spec is_admin(jid(), state()) -> boolean().
1596
is_admin(JID, StateData) ->
1597
    FAffiliation = get_affiliation(JID, StateData),
×
1598
    FAffiliation == admin orelse FAffiliation == owner.
×
1599

1600
%% Decide the fate of the message and its sender
1601
%% Returns: continue_delivery | forget_message | {expulse_sender, Reason}
1602
-spec decide_fate_message(message(), jid(), state()) ->
1603
                                 continue_delivery | forget_message |
1604
                                 {expulse_sender, binary()}.
1605
decide_fate_message(#message{type = error} = Msg,
1606
                    From, StateData) ->
1607
    Err = xmpp:get_error(Msg),
×
1608
    PD = case check_error_kick(Err) of
×
1609
           %% If this is an error stanza and its condition matches a criteria
1610
           true ->
1611
               Reason = str:format("This participant is considered a ghost "
×
1612
                                   "and is expulsed: ~s",
1613
                                   [jid:encode(From)]),
1614
               {expulse_sender, Reason};
×
1615
           false -> continue_delivery
×
1616
         end,
1617
    case PD of
×
1618
      {expulse_sender, R} ->
1619
          case is_user_online(From, StateData) of
×
1620
            true -> {expulse_sender, R};
×
1621
            false -> forget_message
×
1622
          end;
1623
      Other -> Other
×
1624
    end;
1625
decide_fate_message(_, _, _) -> continue_delivery.
108✔
1626

1627
%% Check if the elements of this error stanza indicate
1628
%% that the sender is a dead participant.
1629
%% If so, return true to kick the participant.
1630
-spec check_error_kick(stanza_error()) -> boolean().
1631
check_error_kick(#stanza_error{reason = Reason}) ->
1632
    case Reason of
×
1633
        #gone{} -> true;
×
1634
        'internal-server-error' -> true;
×
1635
        'item-not-found' -> true;
×
1636
        'jid-malformed' -> true;
×
1637
        'recipient-unavailable' -> true;
×
1638
        #redirect{} -> true;
×
1639
        'remote-server-not-found' -> true;
×
1640
        'remote-server-timeout' -> true;
×
1641
        'service-unavailable' -> true;
×
1642
        _ -> false
×
1643
    end;
1644
check_error_kick(undefined) ->
1645
    false.
×
1646

1647
-spec get_error_condition(stanza_error()) -> string().
1648
get_error_condition(#stanza_error{reason = Reason}) ->
1649
    case Reason of
×
1650
        #gone{} -> "gone";
×
1651
        #redirect{} -> "redirect";
×
1652
        Atom -> atom_to_list(Atom)
×
1653
    end;
1654
get_error_condition(undefined) ->
1655
    "undefined".
×
1656

1657
-spec get_error_text(stanza_error()) -> binary().
1658
get_error_text(#stanza_error{text = Txt}) ->
1659
    xmpp:get_text(Txt).
×
1660

1661
-spec make_reason(stanza(), jid(), state(), binary()) -> binary().
1662
make_reason(Packet, From, StateData, Reason1) ->
1663
    #user{nick = FromNick} = maps:get(jid:tolower(From), StateData#state.users),
×
1664
    Condition = get_error_condition(xmpp:get_error(Packet)),
×
1665
    Reason2 = unicode:characters_to_list(Reason1),
×
1666
    str:format(Reason2, [FromNick, Condition]).
×
1667

1668
-spec expulse_participant(stanza(), jid(), state(), binary()) ->
1669
                                 state().
1670
expulse_participant(Packet, From, StateData, Reason1) ->
1671
    Reason2 = make_reason(Packet, From, StateData, Reason1),
×
1672
    NewState = add_user_presence_un(From,
×
1673
                                    #presence{type = unavailable,
1674
                                              status = xmpp:mk_text(Reason2)},
1675
                                    StateData),
1676
    LJID = jid:tolower(From),
×
1677
    #user{nick = Nick} = maps:get(LJID, StateData#state.users),
×
1678
    case maps:get(Nick, StateData#state.nicks, []) of
×
1679
        [_, _ | _] ->
1680
            Aff = get_affiliation(From, StateData),
×
1681
            Item = #muc_item{affiliation = Aff, role = none, jid = From},
×
1682
            Pres = xmpp:set_subtag(
×
1683
                     Packet, #muc_user{items = [Item],
1684
                                       status_codes = [110]}),
1685
            send_wrapped(jid:replace_resource(StateData#state.jid, Nick),
×
1686
                         From, Pres, ?NS_MUCSUB_NODES_PRESENCE, StateData);
1687
        _ ->
1688
            send_new_presence(From, NewState, StateData)
×
1689
    end,
1690
    remove_online_user(From, NewState).
×
1691

1692
-spec set_affiliation(jid(), affiliation(), state()) -> state().
1693
set_affiliation(JID, Affiliation, StateData) ->
1694
    set_affiliation(JID, Affiliation, StateData, <<"">>).
445✔
1695

1696
-spec set_affiliation(jid(), affiliation(), state(), binary()) -> state().
1697
set_affiliation(JID, Affiliation,
1698
                #state{config = #config{persistent = false}} = StateData,
1699
                Reason) ->
1700
    set_affiliation_fallback(JID, Affiliation, StateData, Reason);
427✔
1701
set_affiliation(JID, Affiliation, StateData, Reason) ->
1702
    ServerHost = StateData#state.server_host,
72✔
1703
    Room = StateData#state.room,
72✔
1704
    Host = StateData#state.host,
72✔
1705
    Mod = gen_mod:db_mod(ServerHost, mod_muc),
72✔
1706
    case Mod:set_affiliation(ServerHost, Room, Host, JID, Affiliation, Reason) of
72✔
1707
        ok ->
1708
            StateData;
×
1709
        {error, _} ->
1710
            set_affiliation_fallback(JID, Affiliation, StateData, Reason)
72✔
1711
    end.
1712

1713
-spec set_affiliation_fallback(jid(), affiliation(), state(), binary()) -> state().
1714
set_affiliation_fallback(JID, Affiliation, StateData, Reason) ->
1715
    LJID = jid:remove_resource(jid:tolower(JID)),
499✔
1716
    Affiliations = case Affiliation of
499✔
1717
                       none ->
1718
                           maps:remove(LJID, StateData#state.affiliations);
18✔
1719
                       _ ->
1720
                           maps:put(LJID, {Affiliation, Reason},
481✔
1721
                                    StateData#state.affiliations)
1722
                   end,
1723
    StateData#state{affiliations = Affiliations}.
499✔
1724

1725
-spec set_affiliations(affiliations(), state()) -> state().
1726
set_affiliations(Affiliations,
1727
                 #state{config = #config{persistent = false}} = StateData) ->
1728
    set_affiliations_fallback(Affiliations, StateData);
×
1729
set_affiliations(Affiliations, StateData) ->
1730
    Room = StateData#state.room,
352✔
1731
    Host = StateData#state.host,
352✔
1732
    ServerHost = StateData#state.server_host,
352✔
1733
    Mod = gen_mod:db_mod(ServerHost, mod_muc),
352✔
1734
    case Mod:set_affiliations(ServerHost, Room, Host, Affiliations) of
352✔
1735
        ok ->
1736
            StateData;
×
1737
        {error, _} ->
1738
            set_affiliations_fallback(Affiliations, StateData)
352✔
1739
    end.
1740

1741
-spec set_affiliations_fallback(affiliations(), state()) -> state().
1742
set_affiliations_fallback(Affiliations, StateData) ->
1743
    StateData#state{affiliations = Affiliations}.
352✔
1744

1745
-spec get_affiliation(ljid() | jid(), state()) -> affiliation().
1746
get_affiliation(#jid{} = JID, StateData) ->
1747
    case get_service_affiliation(JID, StateData) of
6,039✔
1748
        owner ->
1749
            owner;
×
1750
        none ->
1751
            Aff = case do_get_affiliation(JID, StateData) of
6,039✔
1752
                      {Affiliation, _Reason} -> Affiliation;
4,090✔
1753
                      Affiliation -> Affiliation
1,949✔
1754
                  end,
1755
            case {Aff, (StateData#state.config)#config.members_only} of
6,039✔
1756
                % Subscribers should be have members affiliation in this case
1757
                {none, true} ->
1758
                    case is_subscriber(JID, StateData) of
63✔
1759
                        true -> member;
×
1760
                        _ -> none
63✔
1761
                    end;
1762
                _ ->
1763
                    Aff
5,976✔
1764
            end
1765
    end;
1766
get_affiliation(LJID, StateData) ->
1767
    get_affiliation(jid:make(LJID), StateData).
72✔
1768

1769
-spec do_get_affiliation(jid(), state()) -> affiliation() | {affiliation(), binary()}.
1770
do_get_affiliation(JID, #state{config = #config{persistent = false}} = StateData) ->
1771
    do_get_affiliation_fallback(JID, StateData);
2,735✔
1772
do_get_affiliation(JID, StateData) ->
1773
    Room = StateData#state.room,
3,304✔
1774
    Host = StateData#state.host,
3,304✔
1775
    LServer = JID#jid.lserver,
3,304✔
1776
    LUser = JID#jid.luser,
3,304✔
1777
    ServerHost = StateData#state.server_host,
3,304✔
1778
    Mod = gen_mod:db_mod(ServerHost, mod_muc),
3,304✔
1779
    case Mod:get_affiliation(ServerHost, Room, Host, LUser, LServer) of
3,304✔
1780
        {error, _} ->
1781
            do_get_affiliation_fallback(JID, StateData);
3,304✔
1782
        {ok, Affiliation} ->
1783
            Affiliation
×
1784
    end.
1785

1786
-spec do_get_affiliation_fallback(jid(), state()) -> affiliation() | {affiliation(),  binary()}.
1787
do_get_affiliation_fallback(JID, StateData) ->
1788
    LJID = jid:tolower(JID),
6,039✔
1789
    try maps:get(LJID, StateData#state.affiliations)
6,039✔
1790
    catch _:{badkey, _} ->
1791
            BareLJID = jid:remove_resource(LJID),
5,931✔
1792
            try maps:get(BareLJID, StateData#state.affiliations)
5,931✔
1793
            catch _:{badkey, _} ->
1794
                    DomainLJID = setelement(1, LJID, <<"">>),
1,949✔
1795
                    try maps:get(DomainLJID, StateData#state.affiliations)
1,949✔
1796
                    catch _:{badkey, _} ->
1797
                            DomainBareLJID = jid:remove_resource(DomainLJID),
1,949✔
1798
                            try maps:get(DomainBareLJID, StateData#state.affiliations)
1,949✔
1799
                            catch _:{badkey, _} -> none
1,949✔
1800
                            end
1801
                    end
1802
            end
1803
    end.
1804

1805
-spec get_affiliations(state()) -> affiliations().
1806
get_affiliations(#state{config = #config{persistent = false}} = StateData) ->
1807
    get_affiliations_fallback(StateData);
×
1808
get_affiliations(StateData) ->
1809
    Room = StateData#state.room,
344✔
1810
    Host = StateData#state.host,
344✔
1811
    ServerHost = StateData#state.server_host,
344✔
1812
    Mod = gen_mod:db_mod(ServerHost, mod_muc),
344✔
1813
    case Mod:get_affiliations(ServerHost, Room, Host) of
344✔
1814
        {error, _} ->
1815
            get_affiliations_fallback(StateData);
344✔
1816
        {ok, Affiliations} ->
1817
            Affiliations
×
1818
    end.
1819

1820
-spec get_affiliations_fallback(state()) -> affiliations().
1821
get_affiliations_fallback(StateData) ->
1822
    StateData#state.affiliations.
344✔
1823

1824
-spec get_service_affiliation(jid(), state()) -> owner | none.
1825
get_service_affiliation(JID, StateData) ->
1826
    {_AccessRoute, _AccessCreate, AccessAdmin,
6,966✔
1827
     _AccessPersistent, _AccessMam} =
1828
        StateData#state.access,
1829
    case acl:match_rule(StateData#state.server_host,
6,966✔
1830
                        AccessAdmin, JID)
1831
        of
1832
      allow -> owner;
×
1833
      _ -> none
6,966✔
1834
    end.
1835

1836
-spec set_role(jid(), role(), state()) -> state().
1837
set_role(JID, Role, StateData) ->
1838
    LJID = jid:tolower(JID),
153✔
1839
    LJIDs = case LJID of
153✔
1840
              {U, S, <<"">>} ->
1841
                  maps:fold(fun (J, _, Js) ->
72✔
1842
                                    case J of
126✔
1843
                                        {U, S, _} -> [J | Js];
54✔
1844
                                        _ -> Js
72✔
1845
                                    end
1846
                            end, [], StateData#state.users);
1847
              _ ->
1848
                  case maps:is_key(LJID, StateData#state.users) of
81✔
1849
                    true -> [LJID];
81✔
1850
                    _ -> []
×
1851
                  end
1852
            end,
1853
    {Users, Nicks} =
153✔
1854
        case Role of
1855
            none ->
1856
                lists:foldl(
36✔
1857
                  fun (J, {Us, Ns}) ->
1858
                          NewNs = try maps:get(J, Us) of
36✔
1859
                                      #user{nick = Nick} ->
1860
                                          maps:remove(Nick, Ns)
36✔
1861
                                  catch _:{badkey, _} ->
1862
                                          Ns
×
1863
                                  end,
1864
                          {maps:remove(J, Us), NewNs}
36✔
1865
                  end,
1866
                  {StateData#state.users, StateData#state.nicks}, LJIDs);
1867
            _ ->
1868
                {lists:foldl(
117✔
1869
                   fun (J, Us) ->
1870
                           User = maps:get(J, Us),
99✔
1871
                           if User#user.last_presence == undefined ->
99✔
1872
                                   Us;
×
1873
                              true ->
1874
                                   maps:put(J, User#user{role = Role}, Us)
99✔
1875
                           end
1876
                   end, StateData#state.users, LJIDs),
1877
                 StateData#state.nicks}
1878
        end,
1879
    Affiliation = get_affiliation(JID, StateData),
153✔
1880
    Roles = case Role of
153✔
1881
                %% Don't persist 'none' role: if someone is kicked, they will
1882
                %% maintain the same role they had *before* they were kicked,
1883
                %% unless they were banned
1884
                none when Affiliation /= outcast ->
1885
                    maps:remove(jid:remove_resource(LJID), StateData#state.roles);
27✔
1886
                NewRole ->
1887
                    maps:put(jid:remove_resource(LJID),
126✔
1888
                             NewRole,
1889
                             StateData#state.roles)
1890
    end,
1891
    StateData#state{users = Users, nicks = Nicks, roles = Roles}.
153✔
1892

1893
-spec get_role(jid(), state()) -> role().
1894
get_role(JID, StateData) ->
1895
    LJID = jid:tolower(JID),
7,200✔
1896
    try maps:get(LJID, StateData#state.users) of
7,200✔
1897
        #user{role = Role} -> Role
7,110✔
1898
    catch _:{badkey, _} -> none
90✔
1899
    end.
1900

1901
-spec get_default_role(affiliation(), state()) -> role().
1902
get_default_role(Affiliation, StateData) ->
1903
    case Affiliation of
792✔
1904
      owner -> moderator;
445✔
1905
      admin -> moderator;
×
1906
      member -> participant;
9✔
1907
      outcast -> none;
×
1908
      none ->
1909
          case (StateData#state.config)#config.members_only of
338✔
1910
            true -> none;
9✔
1911
            _ ->
1912
                case (StateData#state.config)#config.members_by_default
329✔
1913
                    of
1914
                  true -> participant;
284✔
1915
                  _ -> visitor
45✔
1916
                end
1917
          end
1918
    end.
1919

1920
-spec is_visitor(jid(), state()) -> boolean().
1921
is_visitor(Jid, StateData) ->
1922
    get_role(Jid, StateData) =:= visitor.
778✔
1923

1924
-spec is_moderator(jid(), state()) -> boolean().
1925
is_moderator(Jid, StateData) ->
1926
    get_role(Jid, StateData) =:= moderator.
99✔
1927

1928
-spec get_max_users(state()) -> non_neg_integer().
1929
get_max_users(StateData) ->
1930
    MaxUsers = (StateData#state.config)#config.max_users,
850✔
1931
    ServiceMaxUsers = get_service_max_users(StateData),
850✔
1932
    if MaxUsers =< ServiceMaxUsers -> MaxUsers;
850✔
1933
       true -> ServiceMaxUsers
×
1934
    end.
1935

1936
-spec get_service_max_users(state()) -> pos_integer().
1937
get_service_max_users(StateData) ->
1938
    mod_muc_opt:max_users(StateData#state.server_host).
1,384✔
1939

1940
-spec get_max_users_admin_threshold(state()) -> pos_integer().
1941
get_max_users_admin_threshold(StateData) ->
1942
    mod_muc_opt:max_users_admin_threshold(StateData#state.server_host).
792✔
1943

1944
-spec room_queue_new(binary(), ejabberd_shaper:shaper(), _) -> p1_queue:queue({message | presence, jid()}) | undefined.
1945
room_queue_new(ServerHost, Shaper, QueueType) ->
1946
    HaveRoomShaper = Shaper /= none,
418✔
1947
    HaveMessageShaper = mod_muc_opt:user_message_shaper(ServerHost) /= none,
418✔
1948
    HavePresenceShaper = mod_muc_opt:user_presence_shaper(ServerHost) /= none,
418✔
1949
    HaveMinMessageInterval = mod_muc_opt:min_message_interval(ServerHost) /= 0,
418✔
1950
    HaveMinPresenceInterval = mod_muc_opt:min_presence_interval(ServerHost) /= 0,
418✔
1951
    if HaveRoomShaper or HaveMessageShaper or HavePresenceShaper
418✔
1952
       or HaveMinMessageInterval or HaveMinPresenceInterval ->
1953
            p1_queue:new(QueueType);
×
1954
       true ->
1955
            undefined
418✔
1956
    end.
1957

1958
-spec get_user_activity(jid(), state()) -> #activity{}.
1959
get_user_activity(JID, StateData) ->
1960
    case treap:lookup(jid:tolower(JID),
2,092✔
1961
                      StateData#state.activity)
1962
        of
1963
      {ok, _P, A} -> A;
×
1964
      error ->
1965
          MessageShaper =
2,092✔
1966
              ejabberd_shaper:new(mod_muc_opt:user_message_shaper(StateData#state.server_host)),
1967
          PresenceShaper =
2,092✔
1968
              ejabberd_shaper:new(mod_muc_opt:user_presence_shaper(StateData#state.server_host)),
1969
          #activity{message_shaper = MessageShaper,
2,092✔
1970
                    presence_shaper = PresenceShaper}
1971
    end.
1972

1973
-spec store_user_activity(jid(), #activity{}, state()) -> state().
1974
store_user_activity(JID, UserActivity, StateData) ->
1975
    MinMessageInterval =
2,092✔
1976
        trunc(mod_muc_opt:min_message_interval(StateData#state.server_host) * 1000),
1977
    MinPresenceInterval =
2,092✔
1978
        trunc(mod_muc_opt:min_presence_interval(StateData#state.server_host) * 1000),
1979
    Key = jid:tolower(JID),
2,092✔
1980
    Now = erlang:system_time(microsecond),
2,092✔
1981
    Activity1 = clean_treap(StateData#state.activity,
2,092✔
1982
                            {1, -Now}),
1983
    Activity = case treap:lookup(Key, Activity1) of
2,092✔
1984
                 {ok, _P, _A} -> treap:delete(Key, Activity1);
×
1985
                 error -> Activity1
2,092✔
1986
               end,
1987
    StateData1 = case MinMessageInterval == 0 andalso
2,092✔
1988
                        MinPresenceInterval == 0 andalso
2,092✔
1989
                          UserActivity#activity.message_shaper == none andalso
2,092✔
1990
                            UserActivity#activity.presence_shaper == none
2,092✔
1991
                              andalso
1992
                              UserActivity#activity.message == undefined andalso
2,092✔
1993
                                UserActivity#activity.presence == undefined
2,092✔
1994
                     of
1995
                   true -> StateData#state{activity = Activity};
2,092✔
1996
                   false ->
1997
                       case UserActivity#activity.message == undefined andalso
×
1998
                              UserActivity#activity.presence == undefined
×
1999
                           of
2000
                         true ->
2001
                             {_, MessageShaperInterval} =
×
2002
                                 ejabberd_shaper:update(UserActivity#activity.message_shaper,
2003
                                               100000),
2004
                             {_, PresenceShaperInterval} =
×
2005
                                 ejabberd_shaper:update(UserActivity#activity.presence_shaper,
2006
                                               100000),
2007
                             Delay = lists:max([MessageShaperInterval,
×
2008
                                                PresenceShaperInterval,
2009
                                                MinMessageInterval,
2010
                                                MinPresenceInterval])
2011
                                       * 1000,
2012
                             Priority = {1, -(Now + Delay)},
×
2013
                             StateData#state{activity =
×
2014
                                                 treap:insert(Key, Priority,
2015
                                                              UserActivity,
2016
                                                              Activity)};
2017
                         false ->
2018
                             Priority = {0, 0},
×
2019
                             StateData#state{activity =
×
2020
                                                 treap:insert(Key, Priority,
2021
                                                              UserActivity,
2022
                                                              Activity)}
2023
                       end
2024
                 end,
2025
    reset_hibernate_timer(StateData1).
2,092✔
2026

2027
-spec clean_treap(treap:treap(), integer() | {1, integer()}) -> treap:treap().
2028
clean_treap(Treap, CleanPriority) ->
2029
    case treap:is_empty(Treap) of
2,137✔
2030
      true -> Treap;
2,119✔
2031
      false ->
2032
          {_Key, Priority, _Value} = treap:get_root(Treap),
18✔
2033
          if Priority > CleanPriority ->
18✔
2034
                 clean_treap(treap:delete_root(Treap), CleanPriority);
9✔
2035
             true -> Treap
9✔
2036
          end
2037
    end.
2038

2039
-spec prepare_room_queue(state()) -> state().
2040
prepare_room_queue(StateData) ->
2041
    case p1_queue:out(StateData#state.room_queue) of
×
2042
      {{value, {message, From}}, _RoomQueue} ->
2043
          Activity = get_user_activity(From, StateData),
×
2044
          Packet = Activity#activity.message,
×
2045
          Size = element_size(Packet),
×
2046
          {RoomShaper, RoomShaperInterval} =
×
2047
              ejabberd_shaper:update(StateData#state.room_shaper, Size),
2048
          erlang:send_after(RoomShaperInterval, self(),
×
2049
                            process_room_queue),
2050
          StateData#state{room_shaper = RoomShaper};
×
2051
      {{value, {presence, From}}, _RoomQueue} ->
2052
          Activity = get_user_activity(From, StateData),
×
2053
          {_Nick, Packet} = Activity#activity.presence,
×
2054
          Size = element_size(Packet),
×
2055
          {RoomShaper, RoomShaperInterval} =
×
2056
              ejabberd_shaper:update(StateData#state.room_shaper, Size),
2057
          erlang:send_after(RoomShaperInterval, self(),
×
2058
                            process_room_queue),
2059
          StateData#state{room_shaper = RoomShaper};
×
2060
      {empty, _} -> StateData
×
2061
    end.
2062

2063
-spec update_online_user(jid(), #user{}, state()) -> state().
2064
update_online_user(JID, #user{nick = Nick} = User, StateData) ->
2065
    LJID = jid:tolower(JID),
679✔
2066
    add_to_log(join, Nick, StateData),
679✔
2067
    Nicks1 = try maps:get(LJID, StateData#state.users) of
679✔
2068
                 #user{nick = OldNick} ->
2069
                     case lists:delete(
×
2070
                            LJID, maps:get(OldNick, StateData#state.nicks)) of
2071
                         [] ->
2072
                             maps:remove(OldNick, StateData#state.nicks);
×
2073
                         LJIDs ->
2074
                             maps:put(OldNick, LJIDs, StateData#state.nicks)
×
2075
                     end
2076
             catch _:{badkey, _} ->
2077
                     StateData#state.nicks
679✔
2078
             end,
2079
    Nicks = maps:update_with(Nick,
679✔
2080
                             fun (LJIDs) -> [LJID|LJIDs -- [LJID]] end,
×
2081
                             [LJID], Nicks1),
2082
    Users = maps:update_with(LJID,
679✔
2083
                             fun(U) ->
2084
                                     U#user{nick = Nick}
×
2085
                             end, User, StateData#state.users),
2086
    NewStateData = StateData#state{users = Users, nicks = Nicks},
679✔
2087
    case {maps:get(LJID, StateData#state.users, error),
679✔
2088
          maps:get(LJID, NewStateData#state.users, error)} of
2089
        {#user{nick = Old}, #user{nick = New}} when Old /= New ->
2090
            send_nick_changing(JID, Old, NewStateData, true, true);
×
2091
        _ ->
2092
            ok
679✔
2093
    end,
2094
    NewStateData.
679✔
2095

2096
-spec set_subscriber(jid(), binary(), [binary()], state()) -> state().
2097
set_subscriber(JID, Nick, Nodes,
2098
               #state{room = Room, host = Host, server_host = ServerHost} = StateData) ->
2099
    BareJID = jid:remove_resource(JID),
77✔
2100
    LBareJID = jid:tolower(BareJID),
77✔
2101
    MUCSubscribers =
77✔
2102
        muc_subscribers_put(
2103
          #subscriber{jid = BareJID,
2104
                      nick = Nick,
2105
                      nodes = Nodes},
2106
          StateData#state.muc_subscribers),
2107
    NewStateData = StateData#state{muc_subscribers = MUCSubscribers},
77✔
2108
    store_room(NewStateData, [{add_subscription, BareJID, Nick, Nodes}]),
77✔
2109
    case not muc_subscribers_is_key(LBareJID, StateData#state.muc_subscribers) of
77✔
2110
        true ->
2111
            Packet1a = #message{
77✔
2112
                sub_els = [#ps_event{
2113
                    items = #ps_items{
2114
                        node = ?NS_MUCSUB_NODES_SUBSCRIBERS,
2115
                        items = [#ps_item{
2116
                            id = p1_rand:get_string(),
2117
                            sub_els = [#muc_subscribe{jid = BareJID, nick = Nick}]}]}}]},
2118
            Packet1b = #message{
77✔
2119
                sub_els = [#ps_event{
2120
                    items = #ps_items{
2121
                        node = ?NS_MUCSUB_NODES_SUBSCRIBERS,
2122
                        items = [#ps_item{
2123
                            id = p1_rand:get_string(),
2124
                            sub_els = [#muc_subscribe{nick = Nick}]}]}}]},
2125
            {Packet2a, Packet2b} = ejabberd_hooks:run_fold(muc_subscribed, ServerHost, {Packet1a, Packet1b},
77✔
2126
                                                           [ServerHost, Room, Host, BareJID, StateData]),
2127
            send_subscriptions_change_notifications(Packet2a, Packet2b, NewStateData);
77✔
2128
        _ ->
2129
            ok
×
2130
    end,
2131
    NewStateData.
77✔
2132

2133
-spec add_online_user(jid(), binary(), role(), state()) -> state().
2134
add_online_user(JID, Nick, Role, StateData) ->
2135
    tab_add_online_user(JID, StateData),
679✔
2136
    User = #user{jid = JID, nick = Nick, role = Role},
679✔
2137
    reset_hibernate_timer(update_online_user(JID, User, StateData)).
679✔
2138

2139
-spec remove_online_user(jid(), state()) -> state().
2140
remove_online_user(JID, StateData) ->
2141
    remove_online_user(JID, StateData, <<"">>).
×
2142

2143
-spec remove_online_user(jid(), state(), binary()) -> state().
2144
remove_online_user(JID, StateData, Reason) ->
2145
    LJID = jid:tolower(JID),
625✔
2146
    #user{nick = Nick} = maps:get(LJID, StateData#state.users),
625✔
2147
    add_to_log(leave, {Nick, Reason}, StateData),
625✔
2148
    tab_remove_online_user(JID, StateData),
625✔
2149
    Users = maps:remove(LJID, StateData#state.users),
625✔
2150
    Nicks = try maps:get(Nick, StateData#state.nicks) of
625✔
2151
                [LJID] ->
2152
                    maps:remove(Nick, StateData#state.nicks);
625✔
2153
                U ->
2154
                    maps:put(Nick, U -- [LJID], StateData#state.nicks)
×
2155
            catch _:{badkey, _} ->
2156
                    StateData#state.nicks
×
2157
            end,
2158
    reset_hibernate_timer(StateData#state{users = Users, nicks = Nicks}).
625✔
2159

2160
-spec filter_presence(presence()) -> presence().
2161
filter_presence(Presence) ->
2162
    Els = lists:filter(
1,340✔
2163
            fun(El) ->
2164
                    XMLNS = xmpp:get_ns(El),
2,659✔
2165
                    case catch binary:part(XMLNS, 0, size(?NS_MUC)) of
2,659✔
2166
                        ?NS_MUC -> false;
679✔
2167
                        _ -> XMLNS /= ?NS_HATS
1,980✔
2168
                    end
2169
            end, xmpp:get_els(Presence)),
2170
    xmpp:set_els(Presence, Els).
1,340✔
2171

2172
-spec strip_status(presence()) -> presence().
2173
strip_status(Presence) ->
2174
    Presence#presence{status = []}.
18✔
2175

2176
-spec add_user_presence(jid(), presence(), state()) -> state().
2177
add_user_presence(JID, Presence, StateData) ->
2178
    LJID = jid:tolower(JID),
715✔
2179
    FPresence = filter_presence(Presence),
715✔
2180
    Users = maps:update_with(LJID,
715✔
2181
                             fun (#user{} = User) ->
2182
                                     User#user{last_presence = FPresence}
715✔
2183
                             end, StateData#state.users),
2184
    StateData#state{users = Users}.
715✔
2185

2186
-spec add_user_presence_un(jid(), presence(), state()) -> state().
2187
add_user_presence_un(JID, Presence, StateData) ->
2188
    LJID = jid:tolower(JID),
625✔
2189
    FPresence = filter_presence(Presence),
625✔
2190
    Users = maps:update_with(LJID,
625✔
2191
                             fun (#user{} = User) ->
2192
                                     User#user{last_presence = FPresence,
625✔
2193
                                               role = none}
2194
                             end, StateData#state.users),
2195
    StateData#state{users = Users}.
625✔
2196

2197
%% Find and return a list of the full JIDs of the users of Nick.
2198
%% Return jid record.
2199
-spec find_jids_by_nick(binary(), state()) -> [jid()].
2200
find_jids_by_nick(Nick, StateData) ->
2201
    Users = case maps:get(Nick, StateData#state.nicks, []) of
153✔
2202
                [] -> muc_subscribers_get_by_nick(
9✔
2203
                        Nick, StateData#state.muc_subscribers);
2204
                Us -> Us
144✔
2205
            end,
2206
    [jid:make(LJID) || LJID <- Users].
153✔
2207

2208
%% Find and return the full JID of the user of Nick with
2209
%% highest-priority presence.  Return jid record.
2210
-spec find_jid_by_nick(binary(), state()) -> jid() | false.
2211
find_jid_by_nick(Nick, StateData) ->
2212
    try maps:get(Nick, StateData#state.nicks) of
3,216✔
2213
        [User] -> jid:make(User);
2,460✔
2214
        [FirstUser | Users] ->
2215
            #user{last_presence = FirstPresence} =
×
2216
                maps:get(FirstUser, StateData#state.users),
2217
            {LJID, _} = lists:foldl(
×
2218
                          fun(Compare, {HighestUser, HighestPresence}) ->
2219
                                  #user{last_presence = P1} =
×
2220
                                      maps:get(Compare, StateData#state.users),
2221
                                  case higher_presence(P1, HighestPresence) of
×
2222
                                      true -> {Compare, P1};
×
2223
                                      false -> {HighestUser, HighestPresence}
×
2224
                                  end
2225
                          end, {FirstUser, FirstPresence}, Users),
2226
            jid:make(LJID)
×
2227
    catch _:{badkey, _} ->
2228
            false
756✔
2229
    end.
2230

2231
-spec higher_presence(undefined | presence(),
2232
                      undefined | presence()) -> boolean().
2233
higher_presence(Pres1, Pres2) when Pres1 /= undefined, Pres2 /= undefined ->
2234
    Pri1 = get_priority_from_presence(Pres1),
×
2235
    Pri2 = get_priority_from_presence(Pres2),
×
2236
    Pri1 > Pri2;
×
2237
higher_presence(Pres1, Pres2) ->
2238
    Pres1 > Pres2.
×
2239

2240
-spec get_priority_from_presence(presence()) -> integer().
2241
get_priority_from_presence(#presence{priority = Prio}) ->
2242
    case Prio of
×
2243
        undefined -> 0;
×
2244
        _ -> Prio
×
2245
    end.
2246

2247
-spec find_nick_by_jid(jid() | undefined, state()) -> binary().
2248
find_nick_by_jid(undefined, _StateData) ->
2249
    <<>>;
9✔
2250
find_nick_by_jid(JID, StateData) ->
2251
    LJID = jid:tolower(JID),
54✔
2252
    case maps:find(LJID, StateData#state.users) of
54✔
2253
        {ok, #user{nick = Nick}} ->
2254
            Nick;
54✔
2255
        _ ->
2256
            case maps:find(LJID, (StateData#state.muc_subscribers)#muc_subscribers.subscribers) of
×
2257
                {ok, #subscriber{nick = Nick}} ->
2258
                    Nick;
×
2259
                _ ->
2260
                    <<>>
×
2261
            end
2262
    end.
2263

2264
-spec is_nick_change(jid(), binary(), state()) -> boolean().
2265
is_nick_change(JID, Nick, StateData) ->
2266
    LJID = jid:tolower(JID),
54✔
2267
    case Nick of
54✔
2268
      <<"">> -> false;
36✔
2269
      _ ->
2270
          #user{nick = OldNick} = maps:get(LJID, StateData#state.users),
18✔
2271
          Nick /= OldNick
18✔
2272
    end.
2273

2274
-spec nick_collision(jid(), binary(), state()) -> boolean().
2275
nick_collision(User, Nick, StateData) ->
2276
    UserOfNick = case find_jid_by_nick(Nick, StateData) of
810✔
2277
                     false ->
2278
                         case muc_subscribers_get_by_nick(Nick, StateData#state.muc_subscribers) of
756✔
2279
                             [J] -> J;
×
2280
                             [] -> false
756✔
2281
                         end;
2282
                     J -> J
54✔
2283
                 end,
2284
    (UserOfNick /= false andalso
810✔
2285
      jid:remove_resource(jid:tolower(UserOfNick))
2286
        /= jid:remove_resource(jid:tolower(User))).
54✔
2287

2288
-spec add_new_user(jid(), binary(), presence(), state()) -> state();
2289
                  (jid(), binary(), iq(), state()) -> {error, stanza_error()} |
2290
                                                      {ignore, state()} |
2291
                                                      {result, muc_subscribe(), state()}.
2292
add_new_user(From, Nick, Packet, StateData) ->
2293
    Lang = xmpp:get_lang(Packet),
792✔
2294
    MaxUsers = get_max_users(StateData),
792✔
2295
    MaxAdminUsers = MaxUsers +
792✔
2296
                      get_max_users_admin_threshold(StateData),
2297
    NUsers = maps:size(StateData#state.users),
792✔
2298
    Affiliation = get_affiliation(From, StateData),
792✔
2299
    ServiceAffiliation = get_service_affiliation(From,
792✔
2300
                                                 StateData),
2301
    NConferences = tab_count_user(From, StateData),
792✔
2302
    MaxConferences =
792✔
2303
        mod_muc_opt:max_user_conferences(StateData#state.server_host),
2304
    Collision = nick_collision(From, Nick, StateData),
792✔
2305
    IsSubscribeRequest = not is_record(Packet, presence),
792✔
2306
    case {ServiceAffiliation == owner orelse
792✔
2307
             ((((Affiliation == admin orelse Affiliation == owner)
792✔
2308
               andalso NUsers < MaxAdminUsers)
445✔
2309
               orelse NUsers < MaxUsers)
347✔
2310
            andalso NConferences < MaxConferences),
792✔
2311
          Collision,
2312
          mod_muc:can_use_nick(StateData#state.server_host,
2313
                               jid:encode(StateData#state.jid), From, Nick),
2314
          get_occupant_initial_role(From, Affiliation, StateData)}
2315
        of
2316
      {false, _, _, _} when NUsers >= MaxUsers orelse NUsers >= MaxAdminUsers ->
2317
          Txt = ?T("Too many users in this conference"),
×
2318
          Err = xmpp:err_resource_constraint(Txt, Lang),
×
2319
          if not IsSubscribeRequest ->
×
2320
                  ejabberd_router:route_error(Packet, Err),
×
2321
                  StateData;
×
2322
             true ->
2323
                  {error, Err}
×
2324
          end;
2325
      {false, _, _, _} when NConferences >= MaxConferences ->
2326
          Txt = ?T("You have joined too many conferences"),
×
2327
          Err = xmpp:err_resource_constraint(Txt, Lang),
×
2328
          if not IsSubscribeRequest ->
×
2329
                  ejabberd_router:route_error(Packet, Err),
×
2330
                  StateData;
×
2331
             true ->
2332
                  {error, Err}
×
2333
          end;
2334
      {false, _, _, _} ->
2335
          Err = xmpp:err_service_unavailable(),
×
2336
          if not IsSubscribeRequest ->
×
2337
                  ejabberd_router:route_error(Packet, Err),
×
2338
                  StateData;
×
2339
             true ->
2340
                  {error, Err}
×
2341
          end;
2342
      {_, _, _, none} ->
2343
          Err = case Affiliation of
9✔
2344
                    outcast ->
2345
                        ErrText = ?T("You have been banned from this room"),
×
2346
                        xmpp:err_forbidden(ErrText, Lang);
×
2347
                    _ ->
2348
                        ErrText = ?T("Membership is required to enter this room"),
9✔
2349
                        xmpp:err_registration_required(ErrText, Lang)
9✔
2350
                end,
2351
          if not IsSubscribeRequest ->
9✔
2352
                  ejabberd_router:route_error(Packet, Err),
9✔
2353
                  StateData;
9✔
2354
             true ->
2355
                  {error, Err}
×
2356
          end;
2357
      {_, true, _, _} ->
2358
          ErrText = ?T("That nickname is already in use by another occupant"),
9✔
2359
          Err = xmpp:err_conflict(ErrText, Lang),
9✔
2360
          if not IsSubscribeRequest ->
9✔
2361
                  ejabberd_router:route_error(Packet, Err),
9✔
2362
                  StateData;
9✔
2363
             true ->
2364
                  {error, Err}
×
2365
          end;
2366
      {_, _, false, _} ->
2367
          Err = case Nick of
×
2368
                        <<>> ->
2369
                            xmpp:err_jid_malformed(?T("Nickname can't be empty"),
×
2370
                                                   Lang);
2371
                        _ ->
2372
                            xmpp:err_conflict(?T("That nickname is registered"
×
2373
                                                 " by another person"), Lang)
2374
                    end,
2375
          if not IsSubscribeRequest ->
×
2376
                  ejabberd_router:route_error(Packet, Err),
×
2377
                  StateData;
×
2378
             true ->
2379
                  {error, Err}
×
2380
          end;
2381
      {_, _, _, Role} ->
2382
          case check_password(ServiceAffiliation, Affiliation,
774✔
2383
                              Packet, From, StateData)
2384
              of
2385
            true ->
2386
                Nodes = get_subscription_nodes(Packet),
756✔
2387
                NewStateData =
756✔
2388
                      if not IsSubscribeRequest ->
2389
                              NewState = add_user_presence(
679✔
2390
                                           From, Packet,
2391
                                           add_online_user(From, Nick, Role,
2392
                                                           StateData)),
2393
                              send_initial_presences_and_messages(
679✔
2394
                                From, Nick, Packet, NewState, StateData),
2395
                              NewState;
679✔
2396
                         true ->
2397
                              set_subscriber(From, Nick, Nodes, StateData)
77✔
2398
                      end,
2399
                  ResultState =
756✔
2400
                      case NewStateData#state.just_created of
2401
                          true ->
2402
                              NewStateData#state{just_created = erlang:system_time(microsecond)};
400✔
2403
                          _ ->
2404
                              Robots = maps:remove(From, StateData#state.robots),
356✔
2405
                              NewStateData#state{robots = Robots}
356✔
2406
                      end,
2407
                  if not IsSubscribeRequest -> ResultState;
756✔
2408
                     true -> {result, subscribe_result(Packet), ResultState}
77✔
2409
                  end;
2410
            need_password ->
2411
                ErrText = ?T("A password is required to enter this room"),
9✔
2412
                Err = xmpp:err_not_authorized(ErrText, Lang),
9✔
2413
                if not IsSubscribeRequest ->
9✔
2414
                        ejabberd_router:route_error(Packet, Err),
9✔
2415
                        StateData;
9✔
2416
                   true ->
2417
                        {error, Err}
×
2418
                end;
2419
            captcha_required ->
2420
                SID = xmpp:get_id(Packet),
×
2421
                RoomJID = StateData#state.jid,
×
2422
                To = jid:replace_resource(RoomJID, Nick),
×
2423
                Limiter = {From#jid.luser, From#jid.lserver},
×
2424
                case ejabberd_captcha:create_captcha(SID, RoomJID, To,
×
2425
                                                     Lang, Limiter, From)
2426
                   of
2427
                  {ok, ID, Body, CaptchaEls} ->
2428
                      MsgPkt = #message{from = RoomJID,
×
2429
                                        to = From,
2430
                                        id = ID, body = Body,
2431
                                        sub_els = CaptchaEls},
2432
                      Robots = maps:put(From, {Nick, Packet},
×
2433
                                        StateData#state.robots),
2434
                      ejabberd_router:route(MsgPkt),
×
2435
                      NewState = StateData#state{robots = Robots},
×
2436
                      if not IsSubscribeRequest ->
×
2437
                              NewState;
×
2438
                         true ->
2439
                              {ignore, NewState}
×
2440
                      end;
2441
                  {error, limit} ->
2442
                      ErrText = ?T("Too many CAPTCHA requests"),
×
2443
                      Err = xmpp:err_resource_constraint(ErrText, Lang),
×
2444
                      if not IsSubscribeRequest ->
×
2445
                              ejabberd_router:route_error(Packet, Err),
×
2446
                              StateData;
×
2447
                         true ->
2448
                              {error, Err}
×
2449
                      end;
2450
                  _ ->
2451
                      ErrText = ?T("Unable to generate a CAPTCHA"),
×
2452
                      Err = xmpp:err_internal_server_error(ErrText, Lang),
×
2453
                      if not IsSubscribeRequest ->
×
2454
                              ejabberd_router:route_error(Packet, Err),
×
2455
                              StateData;
×
2456
                         true ->
2457
                              {error, Err}
×
2458
                      end
2459
                end;
2460
            _ ->
2461
                ErrText = ?T("Incorrect password"),
9✔
2462
                Err = xmpp:err_not_authorized(ErrText, Lang),
9✔
2463
                if not IsSubscribeRequest ->
9✔
2464
                        ejabberd_router:route_error(Packet, Err),
9✔
2465
                        StateData;
9✔
2466
                   true ->
2467
                        {error, Err}
×
2468
                end
2469
          end
2470
    end.
2471

2472
-spec check_password(affiliation(), affiliation(),
2473
                     presence() | iq(), jid(), state()) ->
2474
      boolean() | need_password | captcha_required.
2475
check_password(owner, _Affiliation, _Packet, _From,
2476
               _StateData) ->
2477
    %% Don't check pass if user is owner in MUC service (access_admin option)
2478
    true;
×
2479
check_password(_ServiceAffiliation, owner, _Packet, _From,
2480
               _StateData) ->
2481
    %% Don't check pass if user is owner in this room
2482
    true;
445✔
2483
check_password(_ServiceAffiliation, Affiliation, Packet,
2484
               From, StateData) ->
2485
    case (StateData#state.config)#config.password_protected
329✔
2486
        of
2487
      false -> check_captcha(Affiliation, From, StateData);
302✔
2488
      true ->
2489
          Pass = extract_password(Packet),
27✔
2490
          case Pass of
27✔
2491
            false -> need_password;
9✔
2492
            _ ->
2493
                case (StateData#state.config)#config.password of
18✔
2494
                  Pass -> true;
9✔
2495
                  _ -> false
9✔
2496
                end
2497
          end
2498
    end.
2499

2500
-spec check_captcha(affiliation(), jid(), state()) -> true | captcha_required.
2501
check_captcha(Affiliation, From, StateData) ->
2502
    case (StateData#state.config)#config.captcha_protected
302✔
2503
           andalso ejabberd_captcha:is_feature_available()
×
2504
        of
2505
      true when Affiliation == none ->
2506
          case maps:get(From, StateData#state.robots, error) of
×
2507
              passed -> true;
×
2508
              _ ->
2509
                WList =
×
2510
                    (StateData#state.config)#config.captcha_whitelist,
2511
                #jid{luser = U, lserver = S, lresource = R} = From,
×
2512
                case (?SETS):is_element({U, S, R}, WList) of
×
2513
                  true -> true;
×
2514
                  false ->
2515
                      case (?SETS):is_element({U, S, <<"">>}, WList) of
×
2516
                        true -> true;
×
2517
                        false ->
2518
                            case (?SETS):is_element({<<"">>, S, <<"">>}, WList)
×
2519
                                of
2520
                              true -> true;
×
2521
                              false -> captcha_required
×
2522
                            end
2523
                      end
2524
                end
2525
          end;
2526
      _ -> true
302✔
2527
    end.
2528

2529
-spec extract_password(presence() | iq()) -> binary() | false.
2530
extract_password(#presence{} = Pres) ->
2531
    case xmpp:get_subtag(Pres, #muc{}) of
27✔
2532
        #muc{password = Password} when is_binary(Password) ->
2533
            Password;
18✔
2534
        _ ->
2535
            false
9✔
2536
    end;
2537
extract_password(#iq{} = IQ) ->
2538
    case xmpp:get_subtag(IQ, #muc_subscribe{}) of
×
2539
        #muc_subscribe{password = Password} when Password /= <<"">> ->
2540
            Password;
×
2541
        _ ->
2542
            false
×
2543
    end.
2544

2545
-spec get_history(binary(), stanza(), state()) -> [lqueue_elem()].
2546
get_history(Nick, Packet, #state{history = History}) ->
2547
    case xmpp:get_subtag(Packet, #muc{}) of
679✔
2548
        #muc{history = #muc_history{} = MUCHistory} ->
2549
            Now = erlang:timestamp(),
27✔
2550
            Q = History#lqueue.queue,
27✔
2551
            filter_history(Q, Now, Nick, MUCHistory);
27✔
2552
        _ ->
2553
            p1_queue:to_list(History#lqueue.queue)
652✔
2554
    end.
2555

2556
-spec filter_history(p1_queue:queue(lqueue_elem()), erlang:timestamp(),
2557
                     binary(), muc_history()) -> [lqueue_elem()].
2558
filter_history(Queue, Now, Nick,
2559
               #muc_history{since = Since,
2560
                            seconds = Seconds,
2561
                            maxstanzas = MaxStanzas,
2562
                            maxchars = MaxChars}) ->
2563
    {History, _, _} =
27✔
2564
        lists:foldr(
2565
          fun({_, _, _, TimeStamp, Size} = Elem,
2566
              {Elems, NumStanzas, NumChars} = Acc) ->
2567
                  NowDiff = timer:now_diff(Now, TimeStamp) div 1000000,
540✔
2568
                  Chars = Size + byte_size(Nick) + 1,
540✔
2569
                  if (NumStanzas < MaxStanzas) andalso
540✔
2570
                     (TimeStamp > Since) andalso
2571
                     (NowDiff =< Seconds) andalso
2572
                     (NumChars + Chars =< MaxChars) ->
2573
                          {[Elem|Elems], NumStanzas + 1, NumChars + Chars};
261✔
2574
                     true ->
2575
                          Acc
279✔
2576
                  end
2577
          end, {[], 0, 0}, p1_queue:to_list(Queue)),
2578
    History.
27✔
2579

2580
-spec is_room_overcrowded(state()) -> boolean().
2581
is_room_overcrowded(StateData) ->
2582
    MaxUsersPresence = mod_muc_opt:max_users_presence(StateData#state.server_host),
2,172✔
2583
    maps:size(StateData#state.users) > MaxUsersPresence.
2,172✔
2584

2585
-spec presence_broadcast_allowed(jid(), state()) -> boolean().
2586
presence_broadcast_allowed(JID, StateData) ->
2587
    Role = get_role(JID, StateData),
4,137✔
2588
    lists:member(Role, (StateData#state.config)#config.presence_broadcast).
4,137✔
2589

2590
-spec send_initial_presences_and_messages(
2591
        jid(), binary(), presence(), state(), state()) -> ok.
2592
send_initial_presences_and_messages(From, Nick, Presence, NewState, OldState) ->
2593
    advertise_entity_capabilities(From, NewState),
679✔
2594
    send_existing_presences(From, NewState),
679✔
2595
    send_self_presence(From, NewState, OldState),
679✔
2596
    History = get_history(Nick, Presence, NewState),
679✔
2597
    send_history(From, History, NewState),
679✔
2598
    send_subject(From, OldState).
679✔
2599

2600
-spec advertise_entity_capabilities(jid(), state()) -> ok.
2601
advertise_entity_capabilities(JID, State) ->
2602
    AvatarHash = (State#state.config)#config.vcard_xupdate,
1,856✔
2603
    DiscoInfo = make_disco_info(JID, State),
1,856✔
2604
    Extras = iq_disco_info_extras(<<"en">>, State, true),
1,856✔
2605
    DiscoInfo1 = DiscoInfo#disco_info{xdata = [Extras]},
1,856✔
2606
    DiscoHash = mod_caps:compute_disco_hash(DiscoInfo1, sha),
1,856✔
2607
    Els1 = [#caps{hash = <<"sha-1">>,
1,856✔
2608
                  node = ejabberd_config:get_uri(),
2609
                  version = DiscoHash}],
2610
    Els2 = if is_binary(AvatarHash) ->
1,856✔
2611
                   [#vcard_xupdate{hash = AvatarHash}|Els1];
26✔
2612
              true ->
2613
                   Els1
1,830✔
2614
           end,
2615
    ejabberd_router:route(#presence{from = State#state.jid, to = JID,
1,856✔
2616
                                    id = p1_rand:get_string(),
2617
                                    sub_els = Els2}).
2618

2619
-spec send_self_presence(jid(), state(), state()) -> ok.
2620
send_self_presence(NJID, StateData, OldStateData) ->
2621
    send_new_presence(NJID, <<"">>, true, StateData, OldStateData).
679✔
2622

2623
-spec send_update_presence(jid(), state(), state()) -> ok.
2624
send_update_presence(JID, StateData, OldStateData) ->
2625
    send_update_presence(JID, <<"">>, StateData, OldStateData).
×
2626

2627
-spec send_update_presence(jid(), binary(), state(), state()) -> ok.
2628
send_update_presence(JID, Reason, StateData, OldStateData) ->
2629
    case is_room_overcrowded(StateData) of
54✔
2630
        true -> ok;
×
2631
        false -> send_update_presence1(JID, Reason, StateData, OldStateData)
54✔
2632
    end.
2633

2634
-spec send_update_presence1(jid(), binary(), state(), state()) -> ok.
2635
send_update_presence1(JID, Reason, StateData, OldStateData) ->
2636
    LJID = jid:tolower(JID),
54✔
2637
    LJIDs = case LJID of
54✔
2638
              {U, S, <<"">>} ->
2639
                    maps:fold(fun (J, _, Js) ->
54✔
2640
                                      case J of
90✔
2641
                                          {U, S, _} -> [J | Js];
36✔
2642
                                          _ -> Js
54✔
2643
                                      end
2644
                              end, [], StateData#state.users);
2645
              _ ->
2646
                  case maps:is_key(LJID, StateData#state.users) of
×
2647
                    true -> [LJID];
×
2648
                    _ -> []
×
2649
                  end
2650
            end,
2651
    lists:foreach(fun (J) ->
54✔
2652
                          send_new_presence(J, Reason, false, StateData,
36✔
2653
                                            OldStateData)
2654
                  end,
2655
                  LJIDs).
2656

2657
-spec send_new_presence(jid(), state(), state()) -> ok.
2658
send_new_presence(NJID, StateData, OldStateData) ->
2659
    send_new_presence(NJID, <<"">>, false, StateData, OldStateData).
661✔
2660

2661
-spec send_new_presence(jid(), binary(), state(), state()) -> ok.
2662
send_new_presence(NJID, Reason, StateData, OldStateData) ->
2663
    send_new_presence(NJID, Reason, false, StateData, OldStateData).
63✔
2664

2665
-spec is_ra_changed(jid(), boolean(), state(), state()) -> boolean().
2666
is_ra_changed(_, _IsInitialPresence = true, _, _) ->
2667
    false;
679✔
2668
is_ra_changed(JID, _IsInitialPresence = false, NewStateData, OldStateData) ->
2669
    NewRole = get_role(JID, NewStateData),
760✔
2670
    NewAff = get_affiliation(JID, NewStateData),
760✔
2671
    OldRole = get_role(JID, OldStateData),
760✔
2672
    OldAff = get_affiliation(JID, OldStateData),
760✔
2673
    if (NewRole == none) and (NewAff == OldAff) ->
760✔
2674
            %% A user is leaving the room;
2675
            false;
625✔
2676
       true ->
2677
            (NewRole /= OldRole) or (NewAff /= OldAff)
135✔
2678
    end.
2679

2680
-spec send_new_presence(jid(), binary(), boolean(), state(), state()) -> ok.
2681
send_new_presence(NJID, Reason, IsInitialPresence, StateData, OldStateData) ->
2682
    LNJID = jid:tolower(NJID),
1,439✔
2683
    #user{nick = Nick} = maps:get(LNJID, StateData#state.users),
1,439✔
2684
    LJID = find_jid_by_nick(Nick, StateData),
1,439✔
2685
    #user{jid = RealJID, role = Role0,
1,439✔
2686
          last_presence = Presence0} = UserInfo =
2687
        maps:get(jid:tolower(LJID), StateData#state.users),
2688
    {Role1, Presence1} =
1,439✔
2689
        case (presence_broadcast_allowed(NJID, StateData) orelse
814✔
2690
         presence_broadcast_allowed(NJID, OldStateData)) of
625✔
2691
            true -> {Role0, Presence0};
1,439✔
2692
            false -> {none, #presence{type = unavailable}}
×
2693
        end,
2694
    Affiliation = get_affiliation(LJID, StateData),
1,439✔
2695
    Node1 = case is_ra_changed(NJID, IsInitialPresence, StateData, OldStateData) of
1,439✔
2696
                true -> ?NS_MUCSUB_NODES_AFFILIATIONS;
99✔
2697
                false -> ?NS_MUCSUB_NODES_PRESENCE
1,340✔
2698
            end,
2699
    Node2 = ?NS_MUCSUB_NODES_PARTICIPANTS,
1,439✔
2700
    UserMap =
1,439✔
2701
        case is_room_overcrowded(StateData) orelse
×
2702
             (not (presence_broadcast_allowed(NJID, StateData) orelse
1,439✔
2703
                   presence_broadcast_allowed(NJID, OldStateData))) of
625✔
2704
            true ->
2705
                #{LNJID => UserInfo};
×
2706
            false ->
2707
                %% TODO: optimize further
2708
                UM1 = get_users_and_subscribers_with_node(Node1, StateData),
1,439✔
2709
                UM2 = get_users_and_subscribers_with_node(Node2, StateData),
1,439✔
2710
                maps:merge(UM1, UM2)
1,439✔
2711
        end,
2712
    maps:fold(
1,439✔
2713
      fun(LUJID, Info, _) ->
2714
              IsSelfPresence = LNJID == LUJID,
2,087✔
2715
              {Role, Presence} = if IsSelfPresence -> {Role0, Presence0};
2,087✔
2716
                                    true -> {Role1, Presence1}
648✔
2717
                                 end,
2718
              Item0 = #muc_item{affiliation = Affiliation,
2,087✔
2719
                                role = Role},
2720
              Item1 = case Info#user.role == moderator orelse
2,087✔
2721
                          (StateData#state.config)#config.anonymous
2722
                          == false orelse IsSelfPresence of
1,012✔
2723
                          true -> Item0#muc_item{jid = RealJID};
2,087✔
2724
                          false -> Item0
×
2725
                      end,
2726
              Item = Item1#muc_item{reason = Reason},
2,087✔
2727
              StatusCodes = status_codes(IsInitialPresence, IsSelfPresence,
2,087✔
2728
                                         StateData),
2729
              Pres = if Presence == undefined -> #presence{};
2,087✔
2730
                        true -> Presence
2,087✔
2731
                     end,
2732
              Packet = xmpp:set_subtag(
2,087✔
2733
                         add_presence_hats(NJID, Pres, StateData),
2734
                         #muc_user{items = [Item],
2735
                                   status_codes = StatusCodes}),
2736
              send_wrapped(jid:replace_resource(StateData#state.jid, Nick),
2,087✔
2737
                           Info#user.jid, Packet, Node1, StateData),
2738
              Type = xmpp:get_type(Packet),
2,087✔
2739
              IsSubscriber = is_subscriber(Info#user.jid, StateData),
2,087✔
2740
              IsOccupant = Info#user.last_presence /= undefined,
2,087✔
2741
              if (IsSubscriber and not IsOccupant) and
2,087✔
2742
                 (IsInitialPresence or (Type == unavailable)) ->
2743
                      send_wrapped(jid:replace_resource(StateData#state.jid, Nick),
×
2744
                                   Info#user.jid, Packet, Node2, StateData);
2745
                 true ->
2746
                      ok
2,087✔
2747
              end
2748
      end, ok, UserMap).
2749

2750
-spec send_existing_presences(jid(), state()) -> ok.
2751
send_existing_presences(ToJID, StateData) ->
2752
    case is_room_overcrowded(StateData) of
679✔
2753
        true -> ok;
×
2754
        false -> send_existing_presences1(ToJID, StateData)
679✔
2755
    end.
2756

2757
-spec send_existing_presences1(jid(), state()) -> ok.
2758
send_existing_presences1(ToJID, StateData) ->
2759
    LToJID = jid:tolower(ToJID),
679✔
2760
    #user{jid = RealToJID, role = Role} = maps:get(LToJID, StateData#state.users),
679✔
2761
    maps:fold(
679✔
2762
      fun(FromNick, _Users, _) ->
2763
              LJID = find_jid_by_nick(FromNick, StateData),
958✔
2764
              #user{jid = FromJID, role = FromRole,
958✔
2765
                    last_presence = Presence} =
2766
                  maps:get(jid:tolower(LJID), StateData#state.users),
2767
              PresenceBroadcast =
958✔
2768
                  lists:member(
2769
                    FromRole, (StateData#state.config)#config.presence_broadcast),
2770
              case {RealToJID, PresenceBroadcast} of
958✔
2771
                  {FromJID, _} -> ok;
679✔
2772
                  {_, false} -> ok;
×
2773
                  _ ->
2774
                      FromAffiliation = get_affiliation(LJID, StateData),
279✔
2775
                      Item0 = #muc_item{affiliation = FromAffiliation,
279✔
2776
                                        role = FromRole},
2777
                      Item = case Role == moderator orelse
279✔
2778
                                 (StateData#state.config)#config.anonymous
2779
                                 == false of
279✔
2780
                                 true -> Item0#muc_item{jid = FromJID};
9✔
2781
                                 false -> Item0
270✔
2782
                             end,
2783
                      Packet = xmpp:set_subtag(
279✔
2784
                                 add_presence_hats(
2785
                                   FromJID, Presence, StateData),
2786
                                 #muc_user{items = [Item]}),
2787
                      send_wrapped(jid:replace_resource(StateData#state.jid, FromNick),
279✔
2788
                                   RealToJID, Packet, ?NS_MUCSUB_NODES_PRESENCE, StateData)
2789
              end
2790
      end, ok, StateData#state.nicks).
2791

2792
-spec set_nick(jid(), binary(), state()) -> state().
2793
set_nick(JID, Nick, State) ->
2794
    LJID = jid:tolower(JID),
9✔
2795
    #user{nick = OldNick} = maps:get(LJID, State#state.users),
9✔
2796
    Users = maps:update_with(LJID,
9✔
2797
                             fun (#user{} = User) -> User#user{nick = Nick} end,
9✔
2798
                             State#state.users),
2799
    OldNickUsers = maps:get(OldNick, State#state.nicks),
9✔
2800
    NewNickUsers = maps:get(Nick, State#state.nicks, []),
9✔
2801
    Nicks = case OldNickUsers of
9✔
2802
                [LJID] ->
2803
                    maps:put(Nick, [LJID | NewNickUsers -- [LJID]],
9✔
2804
                             maps:remove(OldNick, State#state.nicks));
2805
                [_ | _] ->
2806
                    maps:put(Nick, [LJID | NewNickUsers -- [LJID]],
×
2807
                             maps:put(OldNick, OldNickUsers -- [LJID],
2808
                                      State#state.nicks))
2809
            end,
2810
    State#state{users = Users, nicks = Nicks}.
9✔
2811

2812
-spec change_nick(jid(), binary(), state()) -> state().
2813
change_nick(JID, Nick, StateData) ->
2814
    LJID = jid:tolower(JID),
9✔
2815
    #user{nick = OldNick} = maps:get(LJID, StateData#state.users),
9✔
2816
    OldNickUsers = maps:get(OldNick, StateData#state.nicks),
9✔
2817
    NewNickUsers = maps:get(Nick, StateData#state.nicks, []),
9✔
2818
    SendOldUnavailable = length(OldNickUsers) == 1,
9✔
2819
    SendNewAvailable = SendOldUnavailable orelse NewNickUsers == [],
9✔
2820
    NewStateData = set_nick(JID, Nick, StateData),
9✔
2821
    case presence_broadcast_allowed(JID, NewStateData) of
9✔
2822
        true ->
2823
            send_nick_changing(JID, OldNick, NewStateData,
9✔
2824
                               SendOldUnavailable, SendNewAvailable);
2825
        false -> ok
×
2826
    end,
2827
    add_to_log(nickchange, {OldNick, Nick}, StateData),
9✔
2828
    NewStateData.
9✔
2829

2830
-spec send_nick_changing(jid(), binary(), state(), boolean(), boolean()) -> ok.
2831
send_nick_changing(JID, OldNick, StateData,
2832
                   SendOldUnavailable, SendNewAvailable) ->
2833
    #user{jid = RealJID, nick = Nick, role = Role,
9✔
2834
          last_presence = Presence} =
2835
        maps:get(jid:tolower(JID), StateData#state.users),
2836
    Affiliation = get_affiliation(JID, StateData),
9✔
2837
    maps:fold(
9✔
2838
      fun(LJID, Info, _) when Presence /= undefined ->
2839
              IsSelfPresence = LJID == jid:tolower(JID),
18✔
2840
              Item0 = #muc_item{affiliation = Affiliation, role = Role},
18✔
2841
              Item = case Info#user.role == moderator orelse
18✔
2842
                         (StateData#state.config)#config.anonymous
2843
                         == false orelse IsSelfPresence of
9✔
2844
                         true -> Item0#muc_item{jid = RealJID};
18✔
2845
                         false -> Item0
×
2846
                     end,
2847
              Status110 = case IsSelfPresence of
18✔
2848
                              true -> [110];
9✔
2849
                              false -> []
9✔
2850
                          end,
2851
              Packet1 = #presence{
18✔
2852
                           type = unavailable,
2853
                           sub_els = [#muc_user{
2854
                                         items = [Item#muc_item{nick = Nick}],
2855
                                         status_codes = [303|Status110]}]},
2856
              Packet2 = xmpp:set_subtag(Presence,
18✔
2857
                                        #muc_user{items = [Item],
2858
                                                  status_codes = Status110}),
2859
              if SendOldUnavailable ->
18✔
2860
                      send_wrapped(
18✔
2861
                        jid:replace_resource(StateData#state.jid, OldNick),
2862
                        Info#user.jid, Packet1, ?NS_MUCSUB_NODES_PRESENCE,
2863
                        StateData);
2864
                 true -> ok
×
2865
              end,
2866
              if SendNewAvailable ->
18✔
2867
                      send_wrapped(
18✔
2868
                        jid:replace_resource(StateData#state.jid, Nick),
2869
                        Info#user.jid, Packet2, ?NS_MUCSUB_NODES_PRESENCE,
2870
                        StateData);
2871
                 true -> ok
×
2872
              end;
2873
         (_, _, _) ->
2874
              ok
×
2875
      end, ok, get_users_and_subscribers_with_node(
2876
                 ?NS_MUCSUB_NODES_PRESENCE, StateData)).
2877

2878
-spec maybe_send_affiliation(jid(), affiliation(), state()) -> ok.
2879
maybe_send_affiliation(JID, Affiliation, StateData) ->
2880
    LJID = jid:tolower(JID),
72✔
2881
    %% TODO: there should be a better way to check IsOccupant
2882
    Users = get_users_and_subscribers(StateData),
72✔
2883
    IsOccupant = case LJID of
72✔
2884
                     {LUser, LServer, <<"">>} ->
2885
                         #{} /= maps:filter(
72✔
2886
                                  fun({U, S, _}, _) ->
2887
                                          U == LUser andalso
126✔
2888
                                              S == LServer
54✔
2889
                                  end, Users);
2890
                     {_LUser, _LServer, _LResource} ->
2891
                         maps:is_key(LJID, Users)
×
2892
                 end,
2893
    case IsOccupant of
72✔
2894
      true ->
2895
          ok; % The new affiliation is published via presence.
54✔
2896
      false ->
2897
          send_affiliation(JID, Affiliation, StateData)
18✔
2898
    end.
2899

2900
-spec send_affiliation(jid(), affiliation(), state()) -> ok.
2901
send_affiliation(JID, Affiliation, StateData) ->
2902
    Item = #muc_item{jid = JID,
27✔
2903
                     affiliation = Affiliation,
2904
                     role = none},
2905
    Message = #message{id = p1_rand:get_string(),
27✔
2906
                       sub_els = [#muc_user{items = [Item]}]},
2907
    Users = get_users_and_subscribers_with_node(
27✔
2908
              ?NS_MUCSUB_NODES_AFFILIATIONS, StateData),
2909
    Recipients = case (StateData#state.config)#config.anonymous of
27✔
2910
                   true ->
2911
                       maps:filter(fun(_, #user{role = moderator}) ->
27✔
2912
                                           true;
27✔
2913
                                      (_, _) ->
2914
                                           false
×
2915
                                   end, Users);
2916
                   false ->
2917
                       Users
×
2918
                 end,
2919
    send_wrapped_multiple(StateData#state.jid, Recipients, Message,
27✔
2920
                          ?NS_MUCSUB_NODES_AFFILIATIONS, StateData).
2921

2922
-spec status_codes(boolean(), boolean(), state()) -> [pos_integer()].
2923
status_codes(IsInitialPresence, _IsSelfPresence = true, StateData) ->
2924
    S0 = [110],
1,439✔
2925
    case IsInitialPresence of
1,439✔
2926
        true ->
2927
            S1 = case StateData#state.just_created of
679✔
2928
                     true -> [201|S0];
400✔
2929
                     _ -> S0
279✔
2930
                 end,
2931
            S2 = case (StateData#state.config)#config.anonymous of
679✔
2932
                     true -> S1;
670✔
2933
                     false -> [100|S1]
9✔
2934
                 end,
2935
            S3 = case (StateData#state.config)#config.logging of
679✔
2936
                     true -> [170|S2];
×
2937
                     false -> S2
679✔
2938
                 end,
2939
            S3;
679✔
2940
        false -> S0
760✔
2941
    end;
2942
status_codes(_IsInitialPresence, _IsSelfPresence = false, _StateData) -> [].
648✔
2943

2944
-spec lqueue_new(non_neg_integer(), ram | file) -> lqueue().
2945
lqueue_new(Max, Type) ->
2946
    #lqueue{queue = p1_queue:new(Type), max = Max}.
418✔
2947

2948
-spec lqueue_in(lqueue_elem(), lqueue()) -> lqueue().
2949
%% If the message queue limit is set to 0, do not store messages.
2950
lqueue_in(_Item, LQ = #lqueue{max = 0}) -> LQ;
×
2951
%% Otherwise, rotate messages in the queue store.
2952
lqueue_in(Item, #lqueue{queue = Q1, max = Max}) ->
2953
    Len = p1_queue:len(Q1),
595✔
2954
    Q2 = p1_queue:in(Item, Q1),
595✔
2955
    if Len >= Max ->
595✔
2956
           Q3 = lqueue_cut(Q2, Len - Max + 1),
57✔
2957
           #lqueue{queue = Q3, max = Max};
57✔
2958
       true -> #lqueue{queue = Q2, max = Max}
538✔
2959
    end.
2960

2961
-spec lqueue_cut(p1_queue:queue(lqueue_elem()), non_neg_integer()) -> p1_queue:queue(lqueue_elem()).
2962
lqueue_cut(Q, 0) -> Q;
57✔
2963
lqueue_cut(Q, N) ->
2964
    {_, Q1} = p1_queue:out(Q),
57✔
2965
    lqueue_cut(Q1, N - 1).
57✔
2966

2967
-spec add_message_to_history(binary(), jid(), message(), state()) -> state().
2968
add_message_to_history(FromNick, FromJID, Packet, StateData) ->
2969
    add_to_log(text, {FromNick, Packet}, StateData),
622✔
2970
    case check_subject(Packet) of
622✔
2971
        [] ->
2972
            TimeStamp = erlang:timestamp(),
595✔
2973
            AddrPacket = case (StateData#state.config)#config.anonymous of
595✔
2974
                             true -> Packet;
595✔
2975
                             false ->
2976
                                 Addresses = #addresses{
×
2977
                                                list = [#address{type = ofrom,
2978
                                                                 jid = FromJID}]},
2979
                                 xmpp:set_subtag(Packet, Addresses)
×
2980
                         end,
2981
            TSPacket = misc:add_delay_info(
595✔
2982
                         AddrPacket, StateData#state.jid, TimeStamp),
2983
            SPacket = xmpp:set_from_to(
595✔
2984
                        TSPacket,
2985
                        jid:replace_resource(StateData#state.jid, FromNick),
2986
                        StateData#state.jid),
2987
            Size = element_size(SPacket),
595✔
2988
            Q1 = lqueue_in({FromNick, TSPacket, false,
595✔
2989
                            TimeStamp, Size},
2990
                           StateData#state.history),
2991
            StateData#state{history = Q1, just_created = erlang:system_time(microsecond)};
595✔
2992
        _ ->
2993
            StateData#state{just_created = erlang:system_time(microsecond)}
27✔
2994
    end.
2995

2996
remove_from_history(StanzaId, #state{history = #lqueue{queue = Queue} = LQueue} = StateData) ->
2997
    NewQ = p1_queue:foldl(
×
2998
        fun({_, Pkt, _, _, _} = Entry, Acc) ->
2999
            case xmpp:get_meta(Pkt, stanza_id, missing) of
×
3000
                V when V == StanzaId ->
3001
                    Acc;
×
3002
                _ ->
3003
                    p1_queue:in(Entry, Acc)
×
3004
            end
3005
        end, p1_queue:new(), Queue),
3006
    StateData#state{history = LQueue#lqueue{queue = NewQ}}.
×
3007

3008
remove_from_history({U1, S1}, OriginId, #state{history = #lqueue{queue = Queue} = LQueue} = StateData) ->
3009
    {NewQ, StanzaId} = p1_queue:foldl(
×
3010
        fun({_, Pkt, _, _, _} = Entry, {Q, none}) ->
3011
            case jid:tolower(xmpp:get_from(Pkt)) of
×
3012
                {U2, S2, _} when U1 == U2, S1 == S2 ->
3013
                    case xmpp:get_subtag(Pkt, #origin_id{}) of
×
3014
                        #origin_id{id = V} when V == OriginId ->
3015
                            {Q, xmpp:get_meta(Pkt, stanza_id, missing)};
×
3016
                        _ ->
3017
                            {p1_queue:in(Entry, Q), none}
×
3018
                    end;
3019
                _ ->
3020
                    {p1_queue:in(Entry, Q), none}
×
3021
            end;
3022
           (Entry, {Q, S}) ->
3023
               {p1_queue:in(Entry, Q), S}
×
3024
        end, {p1_queue:new(), none}, Queue),
3025
    {StateData#state{history = LQueue#lqueue{queue = NewQ}}, StanzaId}.
×
3026

3027
-spec send_history(jid(), [lqueue_elem()], state()) -> ok.
3028
send_history(JID, History, StateData) ->
3029
    lists:foreach(
679✔
3030
      fun({Nick, Packet, _HaveSubject, _TimeStamp, _Size}) ->
3031
              ejabberd_router:route(
441✔
3032
                xmpp:set_from_to(
3033
                  Packet,
3034
                  jid:replace_resource(StateData#state.jid, Nick),
3035
                  JID))
3036
      end, History).
3037

3038
-spec send_subject(jid(), state()) -> ok.
3039
send_subject(JID, #state{subject_author = {Nick, AuthorJID}} = StateData) ->
3040
    Subject = case StateData#state.subject of
679✔
3041
                  [] -> [#text{}];
670✔
3042
                  [_|_] = S -> S
9✔
3043
              end,
3044
    Packet = #message{from = AuthorJID,
679✔
3045
                      to = JID, type = groupchat, subject = Subject},
3046
    case ejabberd_hooks:run_fold(muc_filter_message,
679✔
3047
                                 StateData#state.server_host,
3048
                                 xmpp:put_meta(Packet, mam_ignore, true),
3049
                                 [StateData, Nick]) of
3050
        drop ->
3051
            ok;
×
3052
        NewPacket1 ->
3053
            FromRoomNick = jid:replace_resource(StateData#state.jid, Nick),
679✔
3054
            NewPacket2 = xmpp:set_from(NewPacket1, FromRoomNick),
679✔
3055
            ejabberd_router:route(NewPacket2)
679✔
3056
    end.
3057

3058
-spec check_subject(message()) -> [text()].
3059
check_subject(#message{subject = [_|_] = Subj, body = [],
3060
                       thread = undefined}) ->
3061
    Subj;
63✔
3062
check_subject(_) ->
3063
    [].
1,199✔
3064

3065
-spec can_change_subject(role(), boolean(), state()) -> boolean().
3066
can_change_subject(Role, IsSubscriber, StateData) ->
3067
    case (StateData#state.config)#config.allow_change_subj
36✔
3068
        of
3069
      true -> Role == moderator orelse Role == participant orelse IsSubscriber == true;
27✔
3070
      _ -> Role == moderator
9✔
3071
    end.
3072

3073
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3074
% Admin stuff
3075

3076
-spec process_iq_admin(jid(), iq(), #state{}) -> {error, stanza_error()} |
3077
                                                 {result, undefined, #state{}} |
3078
                                                 {result, muc_admin()}.
3079
process_iq_admin(_From, #iq{lang = Lang, sub_els = [#muc_admin{items = []}]},
3080
                 _StateData) ->
3081
    Txt = ?T("No 'item' element found"),
×
3082
    {error, xmpp:err_bad_request(Txt, Lang)};
×
3083
process_iq_admin(_From, #iq{type = get, lang = Lang,
3084
                            sub_els = [#muc_admin{items = [_, _|_]}]},
3085
                 _StateData) ->
3086
    ErrText = ?T("Too many <item/> elements"),
×
3087
    {error, xmpp:err_bad_request(ErrText, Lang)};
×
3088
process_iq_admin(From, #iq{type = set, lang = Lang,
3089
                           sub_els = [#muc_admin{items = Items}]},
3090
                 StateData) ->
3091
    process_admin_items_set(From, Items, Lang, StateData);
126✔
3092
process_iq_admin(From, #iq{type = get, lang = Lang,
3093
                           sub_els = [#muc_admin{items = [Item]}]},
3094
                 StateData) ->
3095
    FAffiliation = get_affiliation(From, StateData),
162✔
3096
    FRole = get_role(From, StateData),
162✔
3097
    case Item of
162✔
3098
        #muc_item{role = undefined, affiliation = undefined} ->
3099
            Txt = ?T("Neither 'role' nor 'affiliation' attribute found"),
×
3100
            {error, xmpp:err_bad_request(Txt, Lang)};
×
3101
        #muc_item{role = undefined, affiliation = Affiliation} ->
3102
            if (FAffiliation == owner) or
90✔
3103
               (FAffiliation == admin) or
3104
               ((FAffiliation == member) and
3105
                not (StateData#state.config)#config.anonymous) ->
3106
                    Items = items_with_affiliation(Affiliation, StateData),
90✔
3107
                    {result, #muc_admin{items = Items}};
90✔
3108
               true ->
3109
                    ErrText = ?T("Administrator privileges required"),
×
3110
                    {error, xmpp:err_forbidden(ErrText, Lang)}
×
3111
            end;
3112
        #muc_item{role = Role} ->
3113
            if FRole == moderator ->
72✔
3114
                    Items = items_with_role(Role, StateData),
72✔
3115
                    {result, #muc_admin{items = Items}};
72✔
3116
               true ->
3117
                    ErrText = ?T("Moderator privileges required"),
×
3118
                    {error, xmpp:err_forbidden(ErrText, Lang)}
×
3119
            end
3120
    end.
3121

3122
-spec items_with_role(role(), state()) -> [muc_item()].
3123
items_with_role(SRole, StateData) ->
3124
    lists:map(fun ({_, U}) -> user_to_item(U, StateData)
72✔
3125
              end,
3126
              search_role(SRole, StateData)).
3127

3128
-spec items_with_affiliation(affiliation(), state()) -> [muc_item()].
3129
items_with_affiliation(SAffiliation, StateData) ->
3130
    lists:map(
90✔
3131
      fun({JID, {Affiliation, Reason}}) ->
3132
              #muc_item{affiliation = Affiliation, jid = jid:make(JID),
54✔
3133
                        reason = Reason};
3134
         ({JID, Affiliation}) ->
3135
              #muc_item{affiliation = Affiliation, jid = jid:make(JID)}
×
3136
      end,
3137
      search_affiliation(SAffiliation, StateData)).
3138

3139
-spec user_to_item(#user{}, state()) -> muc_item().
3140
user_to_item(#user{role = Role, nick = Nick, jid = JID},
3141
             StateData) ->
3142
    Affiliation = get_affiliation(JID, StateData),
54✔
3143
    #muc_item{role = Role,
54✔
3144
              affiliation = Affiliation,
3145
              nick = Nick,
3146
              jid = JID}.
3147

3148
-spec search_role(role(), state()) -> [{ljid(), #user{}}].
3149
search_role(Role, StateData) ->
3150
    lists:filter(fun ({_, #user{role = R}}) -> Role == R
99✔
3151
                 end,
3152
                 maps:to_list(StateData#state.users)).
3153

3154
-spec search_affiliation(affiliation(), state()) ->
3155
                         [{ljid(),
3156
                           affiliation() | {affiliation(), binary()}}].
3157
search_affiliation(Affiliation,
3158
                   #state{config = #config{persistent = false}} = StateData) ->
3159
    search_affiliation_fallback(Affiliation, StateData);
11✔
3160
search_affiliation(Affiliation, StateData) ->
3161
    Room = StateData#state.room,
88✔
3162
    Host = StateData#state.host,
88✔
3163
    ServerHost = StateData#state.server_host,
88✔
3164
    Mod = gen_mod:db_mod(ServerHost, mod_muc),
88✔
3165
    case Mod:search_affiliation(ServerHost, Room, Host, Affiliation) of
88✔
3166
        {ok, AffiliationList} ->
3167
            AffiliationList;
×
3168
        {error, _} ->
3169
            search_affiliation_fallback(Affiliation, StateData)
88✔
3170
    end.
3171

3172
-spec search_affiliation_fallback(affiliation(), state()) ->
3173
                                  [{ljid(),
3174
                                    affiliation() | {affiliation(), binary()}}].
3175
search_affiliation_fallback(Affiliation, StateData) ->
3176
    lists:filter(
99✔
3177
      fun({_, A}) ->
3178
              case A of
171✔
3179
                  {A1, _Reason} -> Affiliation == A1;
171✔
3180
                  _ -> Affiliation == A
×
3181
              end
3182
      end, maps:to_list(StateData#state.affiliations)).
3183

3184
-spec process_admin_items_set(jid(), [muc_item()], binary(),
3185
                              #state{}) -> {result, undefined, #state{}} |
3186
                                           {error, stanza_error()}.
3187
process_admin_items_set(UJID, Items, Lang, StateData) ->
3188
    UAffiliation = get_affiliation(UJID, StateData),
126✔
3189
    URole = get_role(UJID, StateData),
126✔
3190
    case catch find_changed_items(UJID, UAffiliation, URole,
126✔
3191
                                  Items, Lang, StateData, [])
3192
        of
3193
      {result, Res} ->
3194
          ?INFO_MSG("Processing MUC admin query from ~ts in "
126✔
3195
                    "room ~ts:~n ~p",
3196
                    [jid:encode(UJID),
3197
                     jid:encode(StateData#state.jid), Res]),
126✔
3198
          case lists:foldl(process_item_change(UJID),
126✔
3199
                           StateData, lists:flatten(Res)) of
3200
              {error, _} = Err ->
3201
                  Err;
×
3202
              NSD ->
3203
                  store_room(NSD),
126✔
3204
                  {result, undefined, NSD}
126✔
3205
          end;
3206
        {error, Err} -> {error, Err}
×
3207
    end.
3208

3209
-spec process_item_change(jid()) -> fun((admin_action(), state() | {error, stanza_error()}) ->
3210
                                               state() | {error, stanza_error()}).
3211
process_item_change(UJID) ->
3212
    fun(_, {error, _} = Err) ->
126✔
3213
            Err;
×
3214
       (Item, SD) ->
3215
            process_item_change(Item, SD, UJID)
126✔
3216
    end.
3217

3218
-spec process_item_change(admin_action(), state(), undefined | jid()) -> state() | {error, stanza_error()}.
3219
process_item_change(Item, SD, UJID) ->
3220
    try case Item of
135✔
3221
            {JID, affiliation, owner, _} when JID#jid.luser == <<"">> ->
3222
                %% If the provided JID does not have username,
3223
                %% forget the affiliation completely
3224
                SD;
×
3225
            {JID, role, none, Reason} ->
3226
                send_kickban_presence(UJID, JID, Reason, 307, SD),
9✔
3227
                set_role(JID, none, SD);
9✔
3228
            {JID, affiliation, none, Reason} ->
3229
                case get_affiliation(JID, SD) of
18✔
3230
                    none -> SD;
×
3231
                    _ ->
3232
                        case (SD#state.config)#config.members_only of
18✔
3233
                            true ->
3234
                                send_kickban_presence(UJID, JID, Reason, 321, none, SD),
9✔
3235
                                maybe_send_affiliation(JID, none, SD),
9✔
3236
                                unsubscribe_from_room(JID, SD),
9✔
3237
                                SD1 = set_affiliation(JID, none, SD),
9✔
3238
                                set_role(JID, none, SD1);
9✔
3239
                            _ ->
3240
                                SD1 = set_affiliation(JID, none, SD),
9✔
3241
                                SD2 = case (SD1#state.config)#config.moderated of
9✔
3242
                                          true -> set_role(JID, visitor, SD1);
9✔
3243
                                          false -> set_role(JID, participant, SD1)
×
3244
                                      end,
3245
                                send_update_presence(JID, Reason, SD2, SD),
9✔
3246
                                maybe_send_affiliation(JID, none, SD2),
9✔
3247
                                SD2
9✔
3248
                        end
3249
                end;
3250
            {JID, affiliation, outcast, Reason} ->
3251
                send_kickban_presence(UJID, JID, Reason, 301, outcast, SD),
9✔
3252
                maybe_send_affiliation(JID, outcast, SD),
9✔
3253
                unsubscribe_from_room(JID, SD),
9✔
3254
                {result, undefined, SD2} =
9✔
3255
                    process_iq_mucsub(JID,
3256
                                      #iq{type = set,
3257
                                          sub_els = [#muc_unsubscribe{}]}, SD),
3258
                set_role(JID, none, set_affiliation(JID, outcast, SD2, Reason));
9✔
3259
            {JID, affiliation, A, Reason} when (A == admin) or (A == owner) ->
3260
                SD1 = set_affiliation(JID, A, SD, Reason),
18✔
3261
                SD2 = set_role(JID, moderator, SD1),
18✔
3262
                send_update_presence(JID, Reason, SD2, SD),
18✔
3263
                maybe_send_affiliation(JID, A, SD2),
18✔
3264
                SD2;
18✔
3265
            {JID, affiliation, member, Reason} ->
3266
                SD1 = set_affiliation(JID, member, SD, Reason),
27✔
3267
                SD2 = set_role(JID, participant, SD1),
27✔
3268
                send_update_presence(JID, Reason, SD2, SD),
27✔
3269
                maybe_send_affiliation(JID, member, SD2),
27✔
3270
                SD2;
27✔
3271
            {JID, role, Role, Reason} ->
3272
                SD1 = set_role(JID, Role, SD),
54✔
3273
                send_new_presence(JID, Reason, SD1, SD),
54✔
3274
                SD1;
54✔
3275
            {JID, affiliation, A, _Reason} ->
3276
                SD1 = set_affiliation(JID, A, SD),
×
3277
                send_update_presence(JID, SD1, SD),
×
3278
                maybe_send_affiliation(JID, A, SD1),
×
3279
                SD1
×
3280
        end
3281
    catch
3282
        E:R:StackTrace ->
3283
            FromSuffix = case UJID of
×
3284
                             #jid{} ->
3285
                                 JidString = jid:encode(UJID),
×
3286
                                 <<" from ", JidString/binary>>;
×
3287
                             undefined ->
3288
                                 <<"">>
×
3289
                         end,
3290
            ?ERROR_MSG("Failed to set item ~p~ts:~n** ~ts",
×
3291
                       [Item,
3292
                        FromSuffix,
3293
                        misc:format_exception(2, E, R, StackTrace)]),
×
3294
            {error, xmpp:err_internal_server_error()}
×
3295
    end.
3296

3297
-spec unsubscribe_from_room(jid(), state()) -> ok | error.
3298
unsubscribe_from_room(JID, SD) ->
3299
    case SD#state.config#config.members_only of
18✔
3300
        false ->
3301
            ok;
9✔
3302
        true ->
3303
            case mod_muc:unhibernate_room(SD#state.server_host, SD#state.host, SD#state.room) of
9✔
3304
                {error, _Reason0} ->
3305
                    error;
×
3306
                {ok, Pid} ->
3307
                    _UnsubPid =
9✔
3308
                        spawn(fun() ->
3309
                                 case unsubscribe(Pid, JID) of
9✔
3310
                                     ok ->
3311
                                         ok;
9✔
3312
                                     {error, Reason} ->
3313
                                         ?WARNING_MSG("Failed to automatically unsubscribe expelled member from room: ~ts",
×
3314
                                                      [Reason]),
×
3315
                                         error
×
3316
                                 end
3317
                              end)
3318
            end
3319
    end.
3320

3321
-spec find_changed_items(jid(), affiliation(), role(),
3322
                         [muc_item()], binary(), state(), [admin_action()]) ->
3323
                                {result, [admin_action()]}.
3324
find_changed_items(_UJID, _UAffiliation, _URole, [],
3325
                   _Lang, _StateData, Res) ->
3326
    {result, Res};
126✔
3327
find_changed_items(_UJID, _UAffiliation, _URole,
3328
                   [#muc_item{jid = undefined, nick = <<"">>}|_],
3329
                   Lang, _StateData, _Res) ->
3330
    Txt = ?T("Neither 'jid' nor 'nick' attribute found"),
×
3331
    throw({error, xmpp:err_bad_request(Txt, Lang)});
×
3332
find_changed_items(_UJID, _UAffiliation, _URole,
3333
                   [#muc_item{role = undefined, affiliation = undefined}|_],
3334
                   Lang, _StateData, _Res) ->
3335
    Txt = ?T("Neither 'role' nor 'affiliation' attribute found"),
×
3336
    throw({error, xmpp:err_bad_request(Txt, Lang)});
×
3337
find_changed_items(UJID, UAffiliation, URole,
3338
                   [#muc_item{jid = J, nick = Nick, reason = Reason,
3339
                              role = Role, affiliation = Affiliation}|Items],
3340
                   Lang, StateData, Res) ->
3341
    [JID | _] = JIDs =
126✔
3342
        if J /= undefined ->
3343
                [J];
63✔
3344
           Nick /= <<"">> ->
3345
                case find_jids_by_nick(Nick, StateData) of
63✔
3346
                    [] ->
3347
                        ErrText = {?T("Nickname ~s does not exist in the room"),
×
3348
                                   [Nick]},
3349
                        throw({error, xmpp:err_not_acceptable(ErrText, Lang)});
×
3350
                    JIDList ->
3351
                        JIDList
63✔
3352
                end
3353
        end,
3354
    {RoleOrAff, RoleOrAffValue} = if Role == undefined ->
126✔
3355
                                          {affiliation, Affiliation};
63✔
3356
                                     true ->
3357
                                          {role, Role}
63✔
3358
                                  end,
3359
    TAffiliation = get_affiliation(JID, StateData),
126✔
3360
    TRole = get_role(JID, StateData),
126✔
3361
    ServiceAf = get_service_affiliation(JID, StateData),
126✔
3362
    UIsSubscriber = is_subscriber(UJID, StateData),
126✔
3363
    URole1 = case {URole, UIsSubscriber} of
126✔
3364
        {none, true} -> subscriber;
×
3365
        {UR, _} -> UR
126✔
3366
    end,
3367
    CanChangeRA = case can_change_ra(UAffiliation,
126✔
3368
                                     URole1,
3369
                                     TAffiliation,
3370
                                     TRole, RoleOrAff, RoleOrAffValue,
3371
                                     ServiceAf) of
3372
                      nothing -> nothing;
×
3373
                      true -> true;
117✔
3374
                      check_owner ->
3375
                          case search_affiliation(owner, StateData) of
9✔
3376
                              [{OJID, _}] ->
3377
                                  jid:remove_resource(OJID)
3378
                                      /=
×
3379
                                      jid:tolower(jid:remove_resource(UJID));
3380
                              _ -> true
9✔
3381
                          end;
3382
                      _ -> false
×
3383
                  end,
3384
    case CanChangeRA of
126✔
3385
        nothing ->
3386
            find_changed_items(UJID, UAffiliation, URole,
×
3387
                               Items, Lang, StateData,
3388
                               Res);
3389
        true ->
3390
            MoreRes = case RoleOrAff of
126✔
3391
                          affiliation ->
3392
                              [{jid:remove_resource(Jidx),
63✔
3393
                                RoleOrAff, RoleOrAffValue, Reason}
3394
                               || Jidx <- JIDs];
63✔
3395
                          role ->
3396
                              [{Jidx, RoleOrAff, RoleOrAffValue, Reason}
63✔
3397
                               || Jidx <- JIDs]
63✔
3398
                      end,
3399
            find_changed_items(UJID, UAffiliation, URole,
126✔
3400
                               Items, Lang, StateData,
3401
                               MoreRes ++ Res);
3402
        false ->
3403
            Txt = ?T("Changing role/affiliation is not allowed"),
×
3404
            throw({error, xmpp:err_not_allowed(Txt, Lang)})
×
3405
    end.
3406

3407
-spec can_change_ra(affiliation(), role(), affiliation(), role(),
3408
                    affiliation, affiliation(), affiliation()) -> boolean() | nothing | check_owner;
3409
                   (affiliation(), role(), affiliation(), role(),
3410
                    role, role(), affiliation()) -> boolean() | nothing | check_owner.
3411
can_change_ra(_FAffiliation, _FRole, owner, _TRole,
3412
              affiliation, owner, owner) ->
3413
    %% A room owner tries to add as persistent owner a
3414
    %% participant that is already owner because he is MUC admin
3415
    true;
×
3416
can_change_ra(_FAffiliation, _FRole, _TAffiliation,
3417
              _TRole, _RoleorAffiliation, _Value, owner) ->
3418
    %% Nobody can decrease MUC admin's role/affiliation
3419
    false;
×
3420
can_change_ra(_FAffiliation, _FRole, TAffiliation,
3421
              _TRole, affiliation, Value, _ServiceAf)
3422
    when TAffiliation == Value ->
3423
    nothing;
×
3424
can_change_ra(_FAffiliation, _FRole, _TAffiliation,
3425
              TRole, role, Value, _ServiceAf)
3426
    when TRole == Value ->
3427
    nothing;
×
3428
can_change_ra(FAffiliation, _FRole, outcast, _TRole,
3429
              affiliation, none, _ServiceAf)
3430
    when (FAffiliation == owner) or
3431
           (FAffiliation == admin) ->
3432
    true;
×
3433
can_change_ra(FAffiliation, _FRole, outcast, _TRole,
3434
              affiliation, member, _ServiceAf)
3435
    when (FAffiliation == owner) or
3436
           (FAffiliation == admin) ->
3437
    true;
×
3438
can_change_ra(owner, _FRole, outcast, _TRole,
3439
              affiliation, admin, _ServiceAf) ->
3440
    true;
×
3441
can_change_ra(owner, _FRole, outcast, _TRole,
3442
              affiliation, owner, _ServiceAf) ->
3443
    true;
×
3444
can_change_ra(FAffiliation, _FRole, none, _TRole,
3445
              affiliation, outcast, _ServiceAf)
3446
    when (FAffiliation == owner) or
3447
           (FAffiliation == admin) ->
3448
    true;
×
3449
can_change_ra(FAffiliation, _FRole, none, _TRole,
3450
              affiliation, member, _ServiceAf)
3451
    when (FAffiliation == owner) or
3452
           (FAffiliation == admin) ->
3453
    true;
18✔
3454
can_change_ra(owner, _FRole, none, _TRole, affiliation,
3455
              admin, _ServiceAf) ->
3456
    true;
9✔
3457
can_change_ra(owner, _FRole, none, _TRole, affiliation,
3458
              owner, _ServiceAf) ->
3459
    true;
×
3460
can_change_ra(FAffiliation, _FRole, member, _TRole,
3461
              affiliation, outcast, _ServiceAf)
3462
    when (FAffiliation == owner) or
3463
           (FAffiliation == admin) ->
3464
    true;
×
3465
can_change_ra(FAffiliation, _FRole, member, _TRole,
3466
              affiliation, none, _ServiceAf)
3467
    when (FAffiliation == owner) or
3468
           (FAffiliation == admin) ->
3469
    true;
18✔
3470
can_change_ra(owner, _FRole, member, _TRole,
3471
              affiliation, admin, _ServiceAf) ->
3472
    true;
×
3473
can_change_ra(owner, _FRole, member, _TRole,
3474
              affiliation, owner, _ServiceAf) ->
3475
    true;
×
3476
can_change_ra(owner, _FRole, admin, _TRole, affiliation,
3477
              _Affiliation, _ServiceAf) ->
3478
    true;
9✔
3479
can_change_ra(owner, _FRole, owner, _TRole, affiliation,
3480
              _Affiliation, _ServiceAf) ->
3481
    check_owner;
9✔
3482
can_change_ra(_FAffiliation, _FRole, _TAffiliation,
3483
              _TRole, affiliation, _Value, _ServiceAf) ->
3484
    false;
×
3485
can_change_ra(_FAffiliation, moderator, _TAffiliation,
3486
              visitor, role, none, _ServiceAf) ->
3487
    true;
×
3488
can_change_ra(FAffiliation, subscriber, _TAffiliation,
3489
              visitor, role, none, _ServiceAf)
3490
    when (FAffiliation == owner) or
3491
           (FAffiliation == admin) ->
3492
    true;
×
3493
can_change_ra(_FAffiliation, moderator, _TAffiliation,
3494
              visitor, role, participant, _ServiceAf) ->
3495
    true;
18✔
3496
can_change_ra(FAffiliation, subscriber, _TAffiliation,
3497
              visitor, role, participant, _ServiceAf)
3498
    when (FAffiliation == owner) or
3499
           (FAffiliation == admin) ->
3500
    true;
×
3501
can_change_ra(FAffiliation, _FRole, _TAffiliation,
3502
              visitor, role, moderator, _ServiceAf)
3503
    when (FAffiliation == owner) or
3504
           (FAffiliation == admin) ->
3505
    true;
×
3506
can_change_ra(_FAffiliation, moderator, _TAffiliation,
3507
              participant, role, none, _ServiceAf) ->
3508
    true;
9✔
3509
can_change_ra(FAffiliation, subscriber, _TAffiliation,
3510
              participant, role, none, _ServiceAf)
3511
    when (FAffiliation == owner) or
3512
           (FAffiliation == admin) ->
3513
    true;
×
3514
can_change_ra(_FAffiliation, moderator, _TAffiliation,
3515
              participant, role, visitor, _ServiceAf) ->
3516
    true;
27✔
3517
can_change_ra(FAffiliation, subscriber, _TAffiliation,
3518
              participant, role, visitor, _ServiceAf)
3519
    when (FAffiliation == owner) or
3520
           (FAffiliation == admin) ->
3521
    true;
×
3522
can_change_ra(FAffiliation, _FRole, _TAffiliation,
3523
              participant, role, moderator, _ServiceAf)
3524
    when (FAffiliation == owner) or
3525
           (FAffiliation == admin) ->
3526
    true;
9✔
3527
can_change_ra(_FAffiliation, _FRole, owner, moderator,
3528
              role, visitor, _ServiceAf) ->
3529
    false;
×
3530
can_change_ra(owner, _FRole, _TAffiliation, moderator,
3531
              role, visitor, _ServiceAf) ->
3532
    true;
×
3533
can_change_ra(_FAffiliation, _FRole, admin, moderator,
3534
              role, visitor, _ServiceAf) ->
3535
    false;
×
3536
can_change_ra(admin, _FRole, _TAffiliation, moderator,
3537
              role, visitor, _ServiceAf) ->
3538
    true;
×
3539
can_change_ra(_FAffiliation, _FRole, owner, moderator,
3540
              role, participant, _ServiceAf) ->
3541
    false;
×
3542
can_change_ra(owner, _FRole, _TAffiliation, moderator,
3543
              role, participant, _ServiceAf) ->
3544
    true;
×
3545
can_change_ra(_FAffiliation, _FRole, admin, moderator,
3546
              role, participant, _ServiceAf) ->
3547
    false;
×
3548
can_change_ra(admin, _FRole, _TAffiliation, moderator,
3549
              role, participant, _ServiceAf) ->
3550
    true;
×
3551
can_change_ra(owner, moderator, TAffiliation,
3552
              moderator, role, none, _ServiceAf)
3553
    when TAffiliation /= owner ->
3554
    true;
×
3555
can_change_ra(owner, subscriber, TAffiliation,
3556
              moderator, role, none, _ServiceAf)
3557
    when TAffiliation /= owner ->
3558
    true;
×
3559
can_change_ra(admin, moderator, TAffiliation,
3560
              moderator, role, none, _ServiceAf)
3561
    when (TAffiliation /= owner) and
3562
         (TAffiliation /= admin) ->
3563
    true;
×
3564
can_change_ra(admin, subscriber, TAffiliation,
3565
              moderator, role, none, _ServiceAf)
3566
    when (TAffiliation /= owner) and
3567
         (TAffiliation /= admin) ->
3568
    true;
×
3569
can_change_ra(_FAffiliation, _FRole, _TAffiliation,
3570
              _TRole, role, _Value, _ServiceAf) ->
3571
    false.
×
3572

3573
-spec send_kickban_presence(undefined | jid(), jid(), binary(),
3574
                            pos_integer(), state()) -> ok.
3575
send_kickban_presence(UJID, JID, Reason, Code, StateData) ->
3576
    NewAffiliation = get_affiliation(JID, StateData),
18✔
3577
    send_kickban_presence(UJID, JID, Reason, Code, NewAffiliation,
18✔
3578
                          StateData).
3579

3580
-spec send_kickban_presence(undefined | jid(), jid(), binary(), pos_integer(),
3581
                            affiliation(), state()) -> ok.
3582
send_kickban_presence(UJID, JID, Reason, Code, NewAffiliation,
3583
                      StateData) ->
3584
    LJID = jid:tolower(JID),
36✔
3585
    LJIDs = case LJID of
36✔
3586
                {U, S, <<"">>} ->
3587
                    maps:fold(fun (J, _, Js) ->
18✔
3588
                                      case J of
36✔
3589
                                          {U, S, _} -> [J | Js];
18✔
3590
                                          _ -> Js
18✔
3591
                                      end
3592
                              end, [], StateData#state.users);
3593
                _ ->
3594
                    case maps:is_key(LJID, StateData#state.users) of
18✔
3595
                        true -> [LJID];
18✔
3596
                        _ -> []
×
3597
                    end
3598
            end,
3599
    lists:foreach(fun (LJ) ->
36✔
3600
                          #user{nick = Nick, jid = J} = maps:get(LJ, StateData#state.users),
36✔
3601
                          add_to_log(kickban, {Nick, Reason, Code}, StateData),
36✔
3602
                          tab_remove_online_user(J, StateData),
36✔
3603
                          send_kickban_presence1(UJID, J, Reason, Code,
36✔
3604
                                                 NewAffiliation, StateData)
3605
                  end,
3606
                  LJIDs).
3607

3608
-spec send_kickban_presence1(undefined | jid(), jid(), binary(), pos_integer(),
3609
                             affiliation(), state()) -> ok.
3610
send_kickban_presence1(MJID, UJID, Reason, Code, Affiliation,
3611
                       StateData) ->
3612
    #user{jid = RealJID, nick = Nick} = maps:get(jid:tolower(UJID), StateData#state.users),
36✔
3613
    ActorNick = find_nick_by_jid(MJID, StateData),
36✔
3614
    %% TODO: optimize further
3615
    UserMap =
36✔
3616
        maps:merge(
3617
          get_users_and_subscribers_with_node(
3618
            ?NS_MUCSUB_NODES_AFFILIATIONS, StateData),
3619
          get_users_and_subscribers_with_node(
3620
            ?NS_MUCSUB_NODES_PARTICIPANTS, StateData)),
3621
    maps:fold(
36✔
3622
      fun(LJID, Info, _) ->
3623
              IsSelfPresence = jid:tolower(UJID) == LJID,
72✔
3624
              Item0 = #muc_item{affiliation = Affiliation,
72✔
3625
                                role = none},
3626
              Item1 = case Info#user.role == moderator orelse
72✔
3627
                          (StateData#state.config)#config.anonymous
3628
                          == false orelse IsSelfPresence of
27✔
3629
                          true -> Item0#muc_item{jid = RealJID};
72✔
3630
                          false -> Item0
×
3631
                      end,
3632
              Item2 = Item1#muc_item{reason = Reason},
72✔
3633
              Item = case ActorNick of
72✔
3634
                         <<"">> -> Item2;
18✔
3635
                         _ -> Item2#muc_item{actor = #muc_actor{nick = ActorNick}}
54✔
3636
                     end,
3637
              Codes = if IsSelfPresence -> [110, Code];
72✔
3638
                         true -> [Code]
36✔
3639
                      end,
3640
              Packet = #presence{type = unavailable,
72✔
3641
                                 sub_els = [#muc_user{items = [Item],
3642
                                                      status_codes = Codes}]},
3643
              RoomJIDNick = jid:replace_resource(StateData#state.jid, Nick),
72✔
3644
              send_wrapped(RoomJIDNick, Info#user.jid, Packet,
72✔
3645
                           ?NS_MUCSUB_NODES_AFFILIATIONS, StateData),
3646
                          IsSubscriber = is_subscriber(Info#user.jid, StateData),
72✔
3647
              IsOccupant = Info#user.last_presence /= undefined,
72✔
3648
              if (IsSubscriber and not IsOccupant) ->
72✔
3649
                      send_wrapped(RoomJIDNick, Info#user.jid, Packet,
×
3650
                                   ?NS_MUCSUB_NODES_PARTICIPANTS, StateData);
3651
                 true ->
3652
                      ok
72✔
3653
              end
3654
      end, ok, UserMap).
3655

3656
-spec convert_legacy_fields([xdata_field()]) -> [xdata_field()].
3657
convert_legacy_fields(Fs) ->
3658
    lists:map(
1,006✔
3659
      fun(#xdata_field{var = Var} = F) ->
3660
              NewVar = case Var of
2,089✔
3661
                           <<"muc#roomconfig_allowvisitorstatus">> ->
3662
                               <<"allow_visitor_status">>;
×
3663
                           <<"muc#roomconfig_allowvisitornickchange">> ->
3664
                               <<"allow_visitor_nickchange">>;
×
3665
                           <<"muc#roomconfig_allowvoicerequests">> ->
3666
                               <<"allow_voice_requests">>;
×
3667
                           <<"muc#roomconfig_allow_subscription">> ->
3668
                               <<"allow_subscription">>;
×
3669
                           <<"muc#roomconfig_voicerequestmininterval">> ->
3670
                               <<"voice_request_min_interval">>;
×
3671
                           <<"muc#roomconfig_captcha_whitelist">> ->
3672
                               <<"captcha_whitelist">>;
×
3673
                           <<"muc#roomconfig_mam">> ->
3674
                               <<"mam">>;
×
3675
                           _ ->
3676
                               Var
2,089✔
3677
                       end,
3678
              F#xdata_field{var = NewVar}
2,089✔
3679
      end, Fs).
3680

3681
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3682
% Owner stuff
3683
-spec process_iq_owner(jid(), iq(), state()) ->
3684
                              {result, undefined | muc_owner()} |
3685
                              {result, undefined | muc_owner(), state() | stop} |
3686
                              {error, stanza_error()}.
3687
process_iq_owner(From, #iq{type = set, lang = Lang,
3688
                           sub_els = [#muc_owner{destroy = Destroy,
3689
                                                 config = Config,
3690
                                                 items = Items}]},
3691
                 StateData) ->
3692
    FAffiliation = get_affiliation(From, StateData),
1,033✔
3693
    if FAffiliation /= owner ->
1,033✔
3694
            ErrText = ?T("Owner privileges required"),
9✔
3695
            {error, xmpp:err_forbidden(ErrText, Lang)};
9✔
3696
       Destroy /= undefined, Config == undefined, Items == [] ->
3697
            ?INFO_MSG("Destroyed MUC room ~ts by the owner ~ts",
9✔
3698
                      [jid:encode(StateData#state.jid), jid:encode(From)]),
9✔
3699
            add_to_log(room_existence, destroyed, StateData),
9✔
3700
            destroy_room(Destroy, StateData);
9✔
3701
       Config /= undefined, Destroy == undefined, Items == [] ->
3702
            case Config of
1,015✔
3703
                #xdata{type = cancel} ->
3704
                    {result, undefined};
9✔
3705
                #xdata{type = submit, fields = Fs} ->
3706
                    Fs1 = convert_legacy_fields(Fs),
1,006✔
3707
                    try muc_roomconfig:decode(Fs1) of
1,006✔
3708
                        Options ->
3709
                            case is_allowed_log_change(Options, StateData, From) andalso
1,006✔
3710
                                is_allowed_persistent_change(Options, StateData, From) andalso
1,006✔
3711
                                is_allowed_mam_change(Options, StateData, From) andalso
1,006✔
3712
                                is_allowed_string_limits(Options, StateData) andalso
1,006✔
3713
                                is_password_settings_correct(Options, StateData) of
1,006✔
3714
                                true ->
3715
                                    set_config(Options, StateData, Lang);
1,006✔
3716
                                false ->
3717
                                    {error, xmpp:err_not_acceptable()}
×
3718
                            end
3719
                    catch _:{muc_roomconfig, Why} ->
3720
                            Txt = muc_roomconfig:format_error(Why),
×
3721
                            {error, xmpp:err_bad_request(Txt, Lang)}
×
3722
                    end;
3723
                _ ->
3724
                    Txt = ?T("Incorrect data form"),
×
3725
                    {error, xmpp:err_bad_request(Txt, Lang)}
×
3726
            end;
3727
       Items /= [], Config == undefined, Destroy == undefined ->
3728
            process_admin_items_set(From, Items, Lang, StateData);
×
3729
       true ->
3730
            {error, xmpp:err_bad_request()}
×
3731
    end;
3732
process_iq_owner(From, #iq{type = get, lang = Lang,
3733
                           sub_els = [#muc_owner{destroy = Destroy,
3734
                                                 config = Config,
3735
                                                 items = Items}]},
3736
                 StateData) ->
3737
    FAffiliation = get_affiliation(From, StateData),
58✔
3738
    if FAffiliation /= owner ->
58✔
3739
            ErrText = ?T("Owner privileges required"),
×
3740
            {error, xmpp:err_forbidden(ErrText, Lang)};
×
3741
       Destroy == undefined, Config == undefined ->
3742
            case Items of
58✔
3743
                [] ->
3744
                    {result,
58✔
3745
                     #muc_owner{config = get_config(Lang, StateData, From)}};
3746
                [#muc_item{affiliation = undefined}] ->
3747
                    Txt = ?T("No 'affiliation' attribute found"),
×
3748
                    {error, xmpp:err_bad_request(Txt, Lang)};
×
3749
                [#muc_item{affiliation = Affiliation}] ->
3750
                    Items = items_with_affiliation(Affiliation, StateData),
×
3751
                    {result, #muc_owner{items = Items}};
×
3752
                [_|_] ->
3753
                    Txt = ?T("Too many <item/> elements"),
×
3754
                    {error, xmpp:err_bad_request(Txt, Lang)}
×
3755
            end;
3756
       true ->
3757
            {error, xmpp:err_bad_request()}
×
3758
    end.
3759

3760
-spec is_allowed_log_change(muc_roomconfig:result(), state(), jid()) -> boolean().
3761
is_allowed_log_change(Options, StateData, From) ->
3762
    case proplists:is_defined(enablelogging, Options) of
1,006✔
3763
        false -> true;
1,006✔
3764
        true ->
3765
            allow ==
×
3766
                ejabberd_hooks:run_fold(muc_log_check_access_log,
3767
                                        StateData#state.server_host,
3768
                                        deny,
3769
                                        [StateData#state.server_host, From])
3770
    end.
3771

3772
-spec is_allowed_persistent_change(muc_roomconfig:result(), state(), jid()) -> boolean().
3773
is_allowed_persistent_change(Options, StateData, From) ->
3774
    case proplists:is_defined(persistentroom, Options) of
1,006✔
3775
      false -> true;
310✔
3776
      true ->
3777
          {_AccessRoute, _AccessCreate, _AccessAdmin,
696✔
3778
           AccessPersistent, _AccessMam} =
3779
              StateData#state.access,
3780
          allow ==
696✔
3781
            acl:match_rule(StateData#state.server_host,
3782
                           AccessPersistent, From)
3783
    end.
3784

3785
-spec is_allowed_mam_change(muc_roomconfig:result(), state(), jid()) -> boolean().
3786
is_allowed_mam_change(Options, StateData, From) ->
3787
    case proplists:is_defined(mam, Options) of
1,006✔
3788
      false -> true;
966✔
3789
      true ->
3790
          {_AccessRoute, _AccessCreate, _AccessAdmin,
40✔
3791
           _AccessPersistent, AccessMam} =
3792
              StateData#state.access,
3793
          allow ==
40✔
3794
            acl:match_rule(StateData#state.server_host,
3795
                           AccessMam, From)
3796
    end.
3797

3798
%% Check if the string fields defined in the Data Form
3799
%% are conformant to the configured limits
3800
-spec is_allowed_string_limits(muc_roomconfig:result(), state()) -> boolean().
3801
is_allowed_string_limits(Options, StateData) ->
3802
    RoomName = proplists:get_value(roomname, Options, <<"">>),
1,006✔
3803
    RoomDesc = proplists:get_value(roomdesc, Options, <<"">>),
1,006✔
3804
    Password = proplists:get_value(roomsecret, Options, <<"">>),
1,006✔
3805
    CaptchaWhitelist = proplists:get_value(captcha_whitelist, Options, []),
1,006✔
3806
    CaptchaWhitelistSize = lists:foldl(
1,006✔
3807
      fun(Jid, Sum) -> byte_size(jid:encode(Jid)) + Sum end,
×
3808
      0, CaptchaWhitelist),
3809
    MaxRoomName = mod_muc_opt:max_room_name(StateData#state.server_host),
1,006✔
3810
    MaxRoomDesc = mod_muc_opt:max_room_desc(StateData#state.server_host),
1,006✔
3811
    MaxPassword = mod_muc_opt:max_password(StateData#state.server_host),
1,006✔
3812
    MaxCaptchaWhitelist = mod_muc_opt:max_captcha_whitelist(StateData#state.server_host),
1,006✔
3813
    (byte_size(RoomName) =< MaxRoomName)
1,006✔
3814
    andalso (byte_size(RoomDesc) =< MaxRoomDesc)
1,006✔
3815
    andalso (byte_size(Password) =< MaxPassword)
1,006✔
3816
    andalso (CaptchaWhitelistSize =< MaxCaptchaWhitelist).
1,006✔
3817

3818
%% Return false if:
3819
%% "the password for a password-protected room is blank"
3820
-spec is_password_settings_correct(muc_roomconfig:result(), state()) -> boolean().
3821
is_password_settings_correct(Options, StateData) ->
3822
    Config = StateData#state.config,
1,006✔
3823
    OldProtected = Config#config.password_protected,
1,006✔
3824
    OldPassword = Config#config.password,
1,006✔
3825
    NewProtected = proplists:get_value(passwordprotectedroom, Options),
1,006✔
3826
    NewPassword = proplists:get_value(roomsecret, Options),
1,006✔
3827
    case {OldProtected, NewProtected, OldPassword, NewPassword} of
1,006✔
3828
        {true, undefined, <<"">>, undefined} -> false;
×
3829
        {true, undefined, _, <<"">>} -> false;
×
3830
        {_, true, <<"">>, undefined} -> false;
×
3831
        {_, true, _, <<"">>} -> false;
×
3832
        _ -> true
1,006✔
3833
    end.
3834

3835
-spec get_default_room_maxusers(state()) -> non_neg_integer().
3836
get_default_room_maxusers(RoomState) ->
3837
    DefRoomOpts =
58✔
3838
        mod_muc_opt:default_room_options(RoomState#state.server_host),
3839
    RoomState2 = set_opts(DefRoomOpts, RoomState),
58✔
3840
    (RoomState2#state.config)#config.max_users.
58✔
3841

3842
-spec get_config(binary(), state(), jid()) -> xdata().
3843
get_config(Lang, StateData, From) ->
3844
    {_AccessRoute, _AccessCreate, _AccessAdmin, AccessPersistent, _AccessMam} =
58✔
3845
        StateData#state.access,
3846
    ServiceMaxUsers = get_service_max_users(StateData),
58✔
3847
    DefaultRoomMaxUsers = get_default_room_maxusers(StateData),
58✔
3848
    Config = StateData#state.config,
58✔
3849
    MaxUsersRoom = get_max_users(StateData),
58✔
3850
    Title = str:translate_and_format(
58✔
3851
              Lang, ?T("Configuration of room ~s"),
3852
              [jid:encode(StateData#state.jid)]),
3853
    Fs = [{roomname, Config#config.title},
58✔
3854
          {roomdesc, Config#config.description},
3855
          {lang, Config#config.lang}] ++
3856
        case acl:match_rule(StateData#state.server_host, AccessPersistent, From) of
3857
            allow -> [{persistentroom, Config#config.persistent}];
58✔
3858
            deny -> []
×
3859
        end ++
3860
        [{publicroom, Config#config.public},
3861
         {public_list, Config#config.public_list},
3862
         {passwordprotectedroom, Config#config.password_protected},
3863
         {roomsecret, case Config#config.password_protected of
3864
                          true -> Config#config.password;
×
3865
                          false -> <<"">>
58✔
3866
                      end},
3867
         {maxusers, MaxUsersRoom,
3868
          [if is_integer(ServiceMaxUsers) -> [];
58✔
3869
              true -> [{?T("No limit"), <<"none">>}]
×
3870
           end] ++ [{integer_to_binary(N), N}
406✔
3871
                    || N <- lists:usort([ServiceMaxUsers,
58✔
3872
                                         DefaultRoomMaxUsers,
3873
                                         MaxUsersRoom
3874
                                         | ?MAX_USERS_DEFAULT_LIST]),
3875
                       N =< ServiceMaxUsers]},
638✔
3876
         {whois, if Config#config.anonymous -> moderators;
58✔
3877
                    true -> anyone
×
3878
                 end},
3879
         {presencebroadcast, Config#config.presence_broadcast},
3880
         {membersonly, Config#config.members_only},
3881
         {moderatedroom, Config#config.moderated},
3882
         {members_by_default, Config#config.members_by_default},
3883
         {changesubject, Config#config.allow_change_subj},
3884
         {allowpm, Config#config.allowpm},
3885
         {allow_private_messages_from_visitors,
3886
          Config#config.allow_private_messages_from_visitors},
3887
         {allow_query_users, Config#config.allow_query_users},
3888
         {allowinvites, Config#config.allow_user_invites},
3889
         {allow_visitor_status, Config#config.allow_visitor_status},
3890
         {allow_visitor_nickchange, Config#config.allow_visitor_nickchange},
3891
         {allow_voice_requests, Config#config.allow_voice_requests},
3892
         {allow_subscription, Config#config.allow_subscription},
3893
         {voice_request_min_interval, Config#config.voice_request_min_interval},
3894
         {pubsub, Config#config.pubsub},
3895
         {enable_hats, Config#config.enable_hats}]
3896
        ++
3897
        case ejabberd_captcha:is_feature_available() of
3898
            true ->
3899
                [{captcha_protected, Config#config.captcha_protected},
×
3900
                 {captcha_whitelist,
3901
                  lists:map(
3902
                    fun jid:make/1,
3903
                    ?SETS:to_list(Config#config.captcha_whitelist))}];
3904
            false ->
3905
                []
58✔
3906
        end
3907
        ++
3908
        case ejabberd_hooks:run_fold(muc_log_check_access_log,
3909
                                     StateData#state.server_host,
3910
                                     deny,
3911
                                     [StateData#state.server_host, From]) of
3912
            allow -> [{enablelogging, Config#config.logging}];
×
3913
            deny -> []
58✔
3914
        end,
3915
    Fields = ejabberd_hooks:run_fold(get_room_config,
58✔
3916
                                     StateData#state.server_host,
3917
                                     Fs,
3918
                                     [StateData, From, Lang]),
3919
    #xdata{type = form, title = Title,
58✔
3920
           fields = muc_roomconfig:encode(Fields, Lang)}.
3921

3922
-spec set_config(muc_roomconfig:result(), state(), binary()) ->
3923
                        {error, stanza_error()} | {result, undefined, state()}.
3924
set_config(Options, StateData, Lang) ->
3925
    try
1,006✔
3926
        #config{} = Config = set_config(Options, StateData#state.config,
1,006✔
3927
                                        StateData#state.server_host, Lang),
3928
        {result, _, NSD} = Res = change_config(Config, StateData),
1,006✔
3929
        Type = case {(StateData#state.config)#config.logging,
1,006✔
3930
                     Config#config.logging}
3931
               of
3932
                   {true, false} -> roomconfig_change_disabledlogging;
×
3933
                   {false, true} -> roomconfig_change_enabledlogging;
×
3934
                   {_, _} -> roomconfig_change
1,006✔
3935
               end,
3936
        Users = [{U#user.jid, U#user.nick, U#user.role}
1,006✔
3937
                 || U <- maps:values(StateData#state.users)],
1,006✔
3938
        add_to_log(Type, Users, NSD),
1,006✔
3939
        Res
1,006✔
3940
    catch  _:{badmatch, {error, #stanza_error{}} = Err} ->
3941
            Err
×
3942
    end.
3943

3944
-spec get_config_opt_name(pos_integer()) -> atom().
3945
get_config_opt_name(Pos) ->
3946
    Fs = [config|record_info(fields, config)],
25,296✔
3947
    lists:nth(Pos, Fs).
25,296✔
3948

3949
-spec set_config([muc_roomconfig:property()], #config{},
3950
                  binary(), binary()) -> #config{} | {error, stanza_error()}.
3951
set_config(Opts, Config, ServerHost, Lang) ->
3952
    lists:foldl(
1,006✔
3953
      fun(_, {error, _} = Err) -> Err;
×
3954
         ({roomname, Title}, C) -> C#config{title = Title};
9✔
3955
         ({roomdesc, Desc}, C) -> C#config{description = Desc};
9✔
3956
         ({changesubject, V}, C) -> C#config{allow_change_subj = V};
9✔
3957
         ({allow_query_users, V}, C) -> C#config{allow_query_users = V};
9✔
3958
         ({allowpm, V}, C) ->
3959
              C#config{allowpm = V};
9✔
3960
         ({allow_private_messages_from_visitors, V}, C) ->
3961
              C#config{allow_private_messages_from_visitors = V};
27✔
3962
         ({allow_visitor_status, V}, C) -> C#config{allow_visitor_status = V};
9✔
3963
         ({allow_visitor_nickchange, V}, C) ->
3964
              C#config{allow_visitor_nickchange = V};
9✔
3965
         ({publicroom, V}, C) -> C#config{public = V};
9✔
3966
         ({public_list, V}, C) -> C#config{public_list = V};
9✔
3967
         ({persistentroom, V}, C) -> C#config{persistent = V};
696✔
3968
         ({moderatedroom, V}, C) -> C#config{moderated = V};
9✔
3969
         ({members_by_default, V}, C) -> C#config{members_by_default = V};
45✔
3970
         ({membersonly, V}, C) -> C#config{members_only = V};
18✔
3971
         ({captcha_protected, V}, C) -> C#config{captcha_protected = V};
×
3972
         ({allowinvites, V}, C) -> C#config{allow_user_invites = V};
18✔
3973
         ({allow_subscription, V}, C) -> C#config{allow_subscription = V};
77✔
3974
         ({passwordprotectedroom, V}, C) -> C#config{password_protected = V};
18✔
3975
         ({roomsecret, V}, C) -> C#config{password = V};
18✔
3976
         ({anonymous, V}, C) -> C#config{anonymous = V};
×
3977
         ({presencebroadcast, V}, C) -> C#config{presence_broadcast = V};
×
3978
         ({allow_voice_requests, V}, C) -> C#config{allow_voice_requests = V};
9✔
3979
         ({voice_request_min_interval, V}, C) ->
3980
              C#config{voice_request_min_interval = V};
9✔
3981
         ({whois, moderators}, C) -> C#config{anonymous = true};
9✔
3982
         ({whois, anyone}, C) -> C#config{anonymous = false};
9✔
3983
         ({maxusers, V}, C) -> C#config{max_users = V};
×
3984
         ({enablelogging, V}, C) -> C#config{logging = V};
×
3985
         ({pubsub, V}, C) -> C#config{pubsub = V};
×
3986
         ({enable_hats, V}, C) -> C#config{enable_hats = V};
×
3987
         ({lang, L}, C) -> C#config{lang = L};
×
3988
         ({captcha_whitelist, Js}, C) ->
3989
              LJIDs = [jid:tolower(J) || J <- Js],
×
3990
              C#config{captcha_whitelist = ?SETS:from_list(LJIDs)};
×
3991
         ({O, V} = Opt, C) ->
3992
              case ejabberd_hooks:run_fold(set_room_option,
40✔
3993
                                           ServerHost,
3994
                                           {0, undefined},
3995
                                           [Opt, Lang]) of
3996
                  {0, undefined} ->
3997
                      ?ERROR_MSG("set_room_option hook failed for "
×
3998
                                 "option '~ts' with value ~p", [O, V]),
×
3999
                      Txt = {?T("Failed to process option '~s'"), [O]},
×
4000
                      {error, xmpp:err_internal_server_error(Txt, Lang)};
×
4001
                  {Pos, Val} ->
4002
                      setelement(Pos, C, Val)
40✔
4003
              end
4004
      end, Config, Opts).
4005

4006
-spec change_config(#config{}, state()) -> {result, undefined, state()}.
4007
change_config(Config, StateData) ->
4008
    send_config_change_info(Config, StateData),
1,015✔
4009
    StateData0 = StateData#state{config = Config},
1,015✔
4010
    StateData1 = remove_subscriptions(StateData0),
1,015✔
4011
    StateData2 =
1,015✔
4012
        case {(StateData#state.config)#config.persistent,
4013
              Config#config.persistent} of
4014
            {WasPersistent, true} ->
4015
                if not WasPersistent ->
632✔
4016
                        set_affiliations(StateData1#state.affiliations,
352✔
4017
                                         StateData1);
4018
                   true ->
4019
                        ok
280✔
4020
                end,
4021
                store_room(StateData1),
632✔
4022
                StateData1;
632✔
4023
            {true, false} ->
4024
                Affiliations = get_affiliations(StateData),
344✔
4025
                maybe_forget_room(StateData),
344✔
4026
                StateData1#state{affiliations = Affiliations};
344✔
4027
            _ ->
4028
                StateData1
39✔
4029
        end,
4030
    case {(StateData#state.config)#config.members_only,
1,015✔
4031
          Config#config.members_only} of
4032
        {false, true} ->
4033
            StateData3 = remove_nonmembers(StateData2),
18✔
4034
            {result, undefined, StateData3};
18✔
4035
        _ ->
4036
            {result, undefined, StateData2}
997✔
4037
    end.
4038

4039
-spec send_config_change_info(#config{}, state()) -> ok.
4040
send_config_change_info(Config, #state{config = Config}) -> ok;
×
4041
send_config_change_info(New, #state{config = Old} = StateData) ->
4042
    Codes = case {Old#config.logging, New#config.logging} of
1,015✔
4043
              {false, true} -> [170];
×
4044
              {true, false} -> [171];
×
4045
              _ -> []
1,015✔
4046
            end
4047
              ++
4048
              case {Old#config.anonymous, New#config.anonymous} of
4049
                {true, false} -> [172];
9✔
4050
                {false, true} -> [173];
9✔
4051
                _ -> []
997✔
4052
              end
4053
                ++
4054
                case Old#config{anonymous = New#config.anonymous,
4055
                                logging = New#config.logging} of
4056
                  New -> [];
18✔
4057
                  _ -> [104]
997✔
4058
                end,
4059
    if Codes /= [] ->
1,015✔
4060
            maps:fold(
1,015✔
4061
              fun(_LJID, #user{jid = JID}, _) ->
4062
                      advertise_entity_capabilities(JID, StateData#state{config = New})
1,177✔
4063
              end, ok, StateData#state.users),
4064
            Message = #message{type = groupchat,
1,015✔
4065
                               id = p1_rand:get_string(),
4066
                               sub_els = [#muc_user{status_codes = Codes}]},
4067
            send_wrapped_multiple(StateData#state.jid,
1,015✔
4068
                                  get_users_and_subscribers_with_node(
4069
                                    ?NS_MUCSUB_NODES_CONFIG, StateData),
4070
                                  Message,
4071
                                  ?NS_MUCSUB_NODES_CONFIG,
4072
                                  StateData);
4073
       true ->
4074
            ok
×
4075
    end.
4076

4077
-spec remove_nonmembers(state()) -> state().
4078
remove_nonmembers(StateData) ->
4079
    maps:fold(
18✔
4080
      fun(_LJID, #user{jid = JID}, SD) ->
4081
              Affiliation = get_affiliation(JID, SD),
27✔
4082
              case Affiliation of
27✔
4083
                  none ->
4084
                      catch send_kickban_presence(undefined, JID, <<"">>, 322, SD),
9✔
4085
                      set_role(JID, none, SD);
9✔
4086
                  _ -> SD
18✔
4087
              end
4088
      end, StateData, get_users_and_subscribers(StateData)).
4089

4090
-spec set_opts([{atom(), any()}], state()) -> state().
4091
set_opts(Opts, StateData) ->
4092
    case lists:keytake(persistent, 1, Opts) of
476✔
4093
        false ->
4094
            set_opts2(Opts, StateData);
×
4095
        {value, Tuple, Rest} ->
4096
            set_opts2([Tuple | Rest], StateData)
476✔
4097
    end.
4098

4099
-spec set_opts2([{atom(), any()}], state()) -> state().
4100
set_opts2([], StateData) ->
4101
    set_vcard_xupdate(StateData);
476✔
4102
set_opts2([{vcard, Val} | Opts], StateData)
4103
  when is_record(Val, vcard_temp) ->
4104
    %% default_room_options is setting a default room vcard
4105
    ValRaw = fxml:element_to_binary(xmpp:encode(Val)),
×
4106
    set_opts2([{vcard, ValRaw} | Opts], StateData);
×
4107
set_opts2([{Opt, Val} | Opts], StateData) ->
4108
    NSD = case Opt of
11,424✔
4109
            title ->
4110
                StateData#state{config =
476✔
4111
                                    (StateData#state.config)#config{title =
4112
                                                                        Val}};
4113
            description ->
4114
                StateData#state{config =
×
4115
                                    (StateData#state.config)#config{description
4116
                                                                        = Val}};
4117
            allow_change_subj ->
4118
                StateData#state{config =
476✔
4119
                                    (StateData#state.config)#config{allow_change_subj
4120
                                                                        = Val}};
4121
            allow_query_users ->
4122
                StateData#state{config =
476✔
4123
                                    (StateData#state.config)#config{allow_query_users
4124
                                                                        = Val}};
4125
            allowpm ->
4126
                StateData#state{config =
476✔
4127
                                    (StateData#state.config)#config{allowpm
4128
                                                                        = Val}};
4129
            allow_private_messages_from_visitors ->
4130
                StateData#state{config =
476✔
4131
                                    (StateData#state.config)#config{allow_private_messages_from_visitors
4132
                                                                        = Val}};
4133
            allow_visitor_nickchange ->
4134
                StateData#state{config =
476✔
4135
                                    (StateData#state.config)#config{allow_visitor_nickchange
4136
                                                                        = Val}};
4137
            allow_visitor_status ->
4138
                StateData#state{config =
476✔
4139
                                    (StateData#state.config)#config{allow_visitor_status
4140
                                                                        = Val}};
4141
            public ->
4142
                StateData#state{config =
476✔
4143
                                    (StateData#state.config)#config{public =
4144
                                                                        Val}};
4145
            public_list ->
4146
                StateData#state{config =
476✔
4147
                                    (StateData#state.config)#config{public_list
4148
                                                                        = Val}};
4149
            persistent ->
4150
                StateData#state{config =
476✔
4151
                                    (StateData#state.config)#config{persistent =
4152
                                                                        Val}};
4153
            moderated ->
4154
                StateData#state{config =
476✔
4155
                                    (StateData#state.config)#config{moderated =
4156
                                                                        Val}};
4157
            members_by_default ->
4158
                StateData#state{config =
476✔
4159
                                    (StateData#state.config)#config{members_by_default
4160
                                                                        = Val}};
4161
            members_only ->
4162
                StateData#state{config =
476✔
4163
                                    (StateData#state.config)#config{members_only
4164
                                                                        = Val}};
4165
            allow_user_invites ->
4166
                StateData#state{config =
476✔
4167
                                    (StateData#state.config)#config{allow_user_invites
4168
                                                                        = Val}};
4169
            password_protected ->
4170
                StateData#state{config =
476✔
4171
                                    (StateData#state.config)#config{password_protected
4172
                                                                        = Val}};
4173
            captcha_protected ->
4174
                StateData#state{config =
476✔
4175
                                    (StateData#state.config)#config{captcha_protected
4176
                                                                        = Val}};
4177
            password ->
4178
                StateData#state{config =
476✔
4179
                                    (StateData#state.config)#config{password =
4180
                                                                        Val}};
4181
            anonymous ->
4182
                StateData#state{config =
476✔
4183
                                    (StateData#state.config)#config{anonymous =
4184
                                                                        Val}};
4185
            presence_broadcast ->
4186
                StateData#state{config =
476✔
4187
                                    (StateData#state.config)#config{presence_broadcast =
4188
                                                                        Val}};
4189
            logging ->
4190
                StateData#state{config =
476✔
4191
                                    (StateData#state.config)#config{logging =
4192
                                                                        Val}};
4193
            mam ->
4194
                StateData#state{config =
476✔
4195
                                    (StateData#state.config)#config{mam = Val}};
4196
            captcha_whitelist ->
4197
                StateData#state{config =
×
4198
                                    (StateData#state.config)#config{captcha_whitelist
4199
                                                                        =
4200
                                                                        (?SETS):from_list(Val)}};
4201
            allow_voice_requests ->
4202
                StateData#state{config =
×
4203
                                    (StateData#state.config)#config{allow_voice_requests
4204
                                                                        = Val}};
4205
            voice_request_min_interval ->
4206
                StateData#state{config =
×
4207
                                    (StateData#state.config)#config{voice_request_min_interval
4208
                                                                        = Val}};
4209
            max_users ->
4210
                ServiceMaxUsers = get_service_max_users(StateData),
476✔
4211
                MaxUsers = if Val =< ServiceMaxUsers -> Val;
476✔
4212
                              true -> ServiceMaxUsers
×
4213
                           end,
4214
                StateData#state{config =
476✔
4215
                                    (StateData#state.config)#config{max_users =
4216
                                                                        MaxUsers}};
4217
            vcard ->
4218
                StateData#state{config =
×
4219
                                    (StateData#state.config)#config{vcard =
4220
                                                                        Val}};
4221
            vcard_xupdate ->
4222
                StateData#state{config =
×
4223
                                    (StateData#state.config)#config{vcard_xupdate =
4224
                                                                        Val}};
4225
            pubsub ->
4226
                StateData#state{config =
×
4227
                                    (StateData#state.config)#config{pubsub = Val}};
4228
            allow_subscription ->
4229
                StateData#state{config =
476✔
4230
                                    (StateData#state.config)#config{allow_subscription = Val}};
4231
            enable_hats ->
4232
                StateData#state{config =
×
4233
                                    (StateData#state.config)#config{enable_hats = Val}};
4234
            lang ->
4235
                StateData#state{config =
476✔
4236
                                    (StateData#state.config)#config{lang = Val}};
4237
            subscribers ->
4238
                  MUCSubscribers =
×
4239
                      lists:foldl(
4240
                        fun({JID, Nick, Nodes}, MUCSubs) ->
4241
                                BareJID =
×
4242
                                    case JID of
4243
                                        #jid{} -> jid:remove_resource(JID);
×
4244
                                        _ ->
4245
                                            ?ERROR_MSG("Invalid subscriber JID in set_opts ~p", [JID]),
×
4246
                                            jid:remove_resource(jid:make(JID))
×
4247
                                    end,
4248
                                muc_subscribers_put(
×
4249
                                  #subscriber{jid = BareJID,
4250
                                              nick = Nick,
4251
                                              nodes = Nodes},
4252
                                  MUCSubs)
4253
                        end, muc_subscribers_new(), Val),
4254
                  StateData#state{muc_subscribers = MUCSubscribers};
×
4255
            affiliations ->
4256
                set_affiliations(maps:from_list(Val), StateData);
×
4257
            roles ->
4258
                StateData#state{roles = maps:from_list(Val)};
×
4259
            subject ->
4260
                  Subj = if Val == <<"">> -> [];
×
4261
                            is_binary(Val) -> [#text{data = Val}];
×
4262
                            is_list(Val) -> Val
×
4263
                         end,
4264
                  StateData#state{subject = Subj};
×
4265
            subject_author when is_tuple(Val) ->
4266
                  StateData#state{subject_author = Val};
×
4267
            subject_author when is_binary(Val) -> % ejabberd 23.04 or older
4268
                  StateData#state{subject_author = {Val, #jid{}}};
×
4269
            hats_defs ->
4270
                  StateData#state{hats_defs = maps:from_list(Val)};
×
4271
            hats_users ->
4272
                  StateData#state{hats_users = maps:from_list(Val)};
×
4273
            hibernation_time -> StateData;
×
4274
            Other ->
4275
                  ?INFO_MSG("Unknown MUC room option, will be discarded: ~p", [Other]),
×
4276
                  StateData
×
4277
          end,
4278
    set_opts2(Opts, NSD).
11,424✔
4279

4280
-spec set_vcard_xupdate(state()) -> state().
4281
set_vcard_xupdate(#state{config =
4282
                             #config{vcard = VCardRaw,
4283
                                     vcard_xupdate = undefined} = Config} = State)
4284
  when VCardRaw /= <<"">> ->
4285
    case fxml_stream:parse_element(VCardRaw) of
×
4286
        {error, _} ->
4287
            State;
×
4288
        El ->
4289
            Hash = mod_vcard_xupdate:compute_hash(El),
×
4290
            State#state{config = Config#config{vcard_xupdate = Hash}}
×
4291
    end;
4292
set_vcard_xupdate(State) ->
4293
    State.
476✔
4294

4295
get_occupant_initial_role(Jid, Affiliation, #state{roles = Roles} = StateData) ->
4296
    DefaultRole = get_default_role(Affiliation, StateData),
792✔
4297
    case (StateData#state.config)#config.moderated of
792✔
4298
        true ->
4299
            get_occupant_stored_role(Jid, Roles, DefaultRole);
792✔
4300
        false ->
4301
            DefaultRole
×
4302
    end.
4303

4304
get_occupant_stored_role(Jid, Roles, DefaultRole) ->
4305
    maps:get(jid:split(jid:remove_resource(Jid)), Roles, DefaultRole).
792✔
4306

4307
-define(MAKE_CONFIG_OPT(Opt),
4308
        {get_config_opt_name(Opt), element(Opt, Config)}).
4309

4310
-spec make_opts(state(), boolean()) -> [{atom(), any()}].
4311
make_opts(StateData, Hibernation) ->
4312
    Config = StateData#state.config,
816✔
4313
    Subscribers = muc_subscribers_fold(
816✔
4314
                    fun(_LJID, Sub, Acc) ->
4315
                            [{Sub#subscriber.jid,
16✔
4316
                              Sub#subscriber.nick,
4317
                              Sub#subscriber.nodes}|Acc]
4318
                    end, [], StateData#state.muc_subscribers),
4319
    [?MAKE_CONFIG_OPT(#config.title), ?MAKE_CONFIG_OPT(#config.description),
816✔
4320
     ?MAKE_CONFIG_OPT(#config.allow_change_subj),
4321
     ?MAKE_CONFIG_OPT(#config.allow_query_users),
4322
     ?MAKE_CONFIG_OPT(#config.allowpm),
4323
     ?MAKE_CONFIG_OPT(#config.allow_private_messages_from_visitors),
4324
     ?MAKE_CONFIG_OPT(#config.allow_visitor_status),
4325
     ?MAKE_CONFIG_OPT(#config.allow_visitor_nickchange),
4326
     ?MAKE_CONFIG_OPT(#config.public), ?MAKE_CONFIG_OPT(#config.public_list),
4327
     ?MAKE_CONFIG_OPT(#config.persistent),
4328
     ?MAKE_CONFIG_OPT(#config.moderated),
4329
     ?MAKE_CONFIG_OPT(#config.members_by_default),
4330
     ?MAKE_CONFIG_OPT(#config.members_only),
4331
     ?MAKE_CONFIG_OPT(#config.allow_user_invites),
4332
     ?MAKE_CONFIG_OPT(#config.password_protected),
4333
     ?MAKE_CONFIG_OPT(#config.captcha_protected),
4334
     ?MAKE_CONFIG_OPT(#config.password), ?MAKE_CONFIG_OPT(#config.anonymous),
4335
     ?MAKE_CONFIG_OPT(#config.logging), ?MAKE_CONFIG_OPT(#config.max_users),
4336
     ?MAKE_CONFIG_OPT(#config.allow_voice_requests),
4337
     ?MAKE_CONFIG_OPT(#config.allow_subscription),
4338
     ?MAKE_CONFIG_OPT(#config.mam),
4339
     ?MAKE_CONFIG_OPT(#config.presence_broadcast),
4340
     ?MAKE_CONFIG_OPT(#config.voice_request_min_interval),
4341
     ?MAKE_CONFIG_OPT(#config.vcard),
4342
     ?MAKE_CONFIG_OPT(#config.vcard_xupdate),
4343
     ?MAKE_CONFIG_OPT(#config.pubsub),
4344
     ?MAKE_CONFIG_OPT(#config.enable_hats),
4345
     ?MAKE_CONFIG_OPT(#config.lang),
4346
     {captcha_whitelist,
4347
      (?SETS):to_list((StateData#state.config)#config.captcha_whitelist)},
4348
     {affiliations,
4349
      maps:to_list(StateData#state.affiliations)},
4350
     {roles, maps:to_list(StateData#state.roles)},
4351
     {subject, StateData#state.subject},
4352
     {subject_author, StateData#state.subject_author},
4353
     {hats_defs, maps:to_list(StateData#state.hats_defs)},
4354
     {hats_users, maps:to_list(StateData#state.hats_users)},
4355
     {hibernation_time, if Hibernation -> erlang:system_time(microsecond); true -> undefined end},
816✔
4356
     {subscribers, Subscribers}].
4357

4358
expand_opts(CompactOpts) ->
4359
    DefConfig = #config{},
×
4360
    Fields = record_info(fields, config),
×
4361
    {_, Opts1} =
×
4362
        lists:foldl(
4363
          fun(Field, {Pos, Opts}) ->
4364
                  case lists:keyfind(Field, 1, CompactOpts) of
×
4365
                      false ->
4366
                          DefV = element(Pos, DefConfig),
×
4367
                          DefVal = case (?SETS):is_set(DefV) of
×
4368
                                       true -> (?SETS):to_list(DefV);
×
4369
                                       false -> DefV
×
4370
                                   end,
4371
                          {Pos+1, [{Field, DefVal}|Opts]};
×
4372
                      {_, Val} ->
4373
                          {Pos+1, [{Field, Val}|Opts]}
×
4374
                  end
4375
          end, {2, []}, Fields),
4376
    SubjectAuthor = proplists:get_value(subject_author, CompactOpts, {<<"">>, #jid{}}),
×
4377
    Subject = proplists:get_value(subject, CompactOpts, <<"">>),
×
4378
    Subscribers = proplists:get_value(subscribers, CompactOpts, []),
×
4379
    HibernationTime = proplists:get_value(hibernation_time, CompactOpts, 0),
×
4380
    [{subject, Subject},
×
4381
     {subject_author, SubjectAuthor},
4382
     {subscribers, Subscribers},
4383
     {hibernation_time, HibernationTime}
4384
     | lists:reverse(Opts1)].
4385

4386
config_fields() ->
4387
    [subject, subject_author, subscribers, hibernate_time | record_info(fields, config)].
×
4388

4389
-spec destroy_room(muc_destroy(), state()) -> {result, undefined, stop}.
4390
destroy_room(DEl, StateData) ->
4391
    Destroy = DEl#muc_destroy{xmlns = ?NS_MUC_USER},
9✔
4392
    maps:fold(
9✔
4393
      fun(_LJID, Info, _) ->
4394
              Nick = Info#user.nick,
18✔
4395
              Item = #muc_item{affiliation = none,
18✔
4396
                               role = none},
4397
              Packet = #presence{
18✔
4398
                          type = unavailable,
4399
                          sub_els = [#muc_user{items = [Item],
4400
                                               destroy = Destroy}]},
4401
              send_wrapped(jid:replace_resource(StateData#state.jid, Nick),
18✔
4402
                           Info#user.jid, Packet,
4403
                           ?NS_MUCSUB_NODES_CONFIG, StateData)
4404
      end, ok, get_users_and_subscribers_with_node(
4405
                 ?NS_MUCSUB_NODES_CONFIG, StateData)),
4406
    forget_room(StateData),
9✔
4407
    {result, undefined, stop}.
9✔
4408

4409
-spec forget_room(state()) -> state().
4410
forget_room(StateData) ->
4411
    mod_muc:forget_room(StateData#state.server_host,
762✔
4412
                        StateData#state.host,
4413
                        StateData#state.room),
4414
    StateData.
762✔
4415

4416
-spec maybe_forget_room(state()) -> state().
4417
maybe_forget_room(StateData) ->
4418
    Forget = case (StateData#state.config)#config.persistent of
344✔
4419
                 true ->
4420
                     true;
344✔
4421
                 _ ->
4422
                     Mod = gen_mod:db_mod(StateData#state.server_host, mod_muc),
×
4423
                     erlang:function_exported(Mod, get_subscribed_rooms, 3)
×
4424
             end,
4425
    case Forget of
344✔
4426
        true ->
4427
            forget_room(StateData);
344✔
4428
        _ ->
4429
            StateData
×
4430
    end.
4431

4432
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4433
% Disco
4434

4435
-define(CONFIG_OPT_TO_FEATURE(Opt, Fiftrue, Fiffalse),
4436
        case Opt of
4437
          true -> Fiftrue;
4438
          false -> Fiffalse
4439
        end).
4440

4441
-spec make_disco_info(jid(), state()) -> disco_info().
4442
make_disco_info(From, StateData) ->
4443
    Config = StateData#state.config,
1,978✔
4444
    ServerHost = StateData#state.server_host,
1,978✔
4445
    AccessRegister = mod_muc_opt:access_register(ServerHost),
1,978✔
4446
    Feats = [?NS_VCARD, ?NS_MUC, ?NS_MUC_STABLE_ID,
1,978✔
4447
             ?NS_DISCO_INFO, ?NS_DISCO_ITEMS,
4448
             ?NS_COMMANDS,
4449
             ?NS_MESSAGE_MODERATE_0, ?NS_MESSAGE_MODERATE_1,
4450
             ?NS_MESSAGE_RETRACT,
4451
             ?CONFIG_OPT_TO_FEATURE((Config#config.public),
4452
                                    <<"muc_public">>, <<"muc_hidden">>),
1,978✔
4453
             ?CONFIG_OPT_TO_FEATURE((Config#config.persistent),
4454
                                    <<"muc_persistent">>, <<"muc_temporary">>),
1,978✔
4455
             ?CONFIG_OPT_TO_FEATURE((Config#config.members_only),
4456
                                    <<"muc_membersonly">>, <<"muc_open">>),
1,978✔
4457
             ?CONFIG_OPT_TO_FEATURE((Config#config.anonymous),
4458
                                    <<"muc_semianonymous">>, <<"muc_nonanonymous">>),
1,978✔
4459
             ?CONFIG_OPT_TO_FEATURE((Config#config.moderated),
4460
                                    <<"muc_moderated">>, <<"muc_unmoderated">>),
1,978✔
4461
             ?CONFIG_OPT_TO_FEATURE((Config#config.password_protected),
4462
                                    <<"muc_passwordprotected">>, <<"muc_unsecured">>)]
1,978✔
4463
        ++ case acl:match_rule(ServerHost, AccessRegister, From) of
4464
               allow -> [?NS_REGISTER];
1,978✔
4465
               deny -> []
×
4466
           end
4467
        ++ case Config#config.allow_subscription of
4468
               true -> [?NS_MUCSUB];
141✔
4469
               false -> []
1,837✔
4470
           end
4471
        ++ case Config#config.enable_hats of
4472
               true -> [?NS_HATS];
1,978✔
4473
               false -> []
×
4474
           end
4475
        ++ case gen_mod:is_loaded(StateData#state.server_host, mod_muc_occupantid) of
4476
               true ->
4477
                   [?NS_OCCUPANT_ID];
1,978✔
4478
               _ ->
4479
                   []
×
4480
           end
4481
        ++ case {gen_mod:is_loaded(StateData#state.server_host, mod_mam),
4482
                 Config#config.mam} of
4483
               {true, true} ->
4484
                   [?NS_MAM_TMP, ?NS_MAM_0, ?NS_MAM_1, ?NS_MAM_2, ?NS_SID_0];
88✔
4485
               _ ->
4486
                   []
1,890✔
4487
           end,
4488
    #disco_info{identities = [#identity{category = <<"conference">>,
1,978✔
4489
                                        type = <<"text">>,
4490
                                        name = (StateData#state.config)#config.title}],
4491
                features = Feats}.
4492

4493
-spec process_iq_disco_info(jid(), iq(), state()) ->
4494
                                   {result, disco_info()} | {error, stanza_error()}.
4495
process_iq_disco_info(_From, #iq{type = set, lang = Lang}, _StateData) ->
4496
    Txt = ?T("Value 'set' of 'type' attribute is not allowed"),
×
4497
    {error, xmpp:err_not_allowed(Txt, Lang)};
×
4498
process_iq_disco_info(From, #iq{type = get, lang = Lang,
4499
                                sub_els = [#disco_info{node = <<>>}]},
4500
                      StateData) ->
4501
    DiscoInfo = make_disco_info(From, StateData),
122✔
4502
    Extras = iq_disco_info_extras(Lang, StateData, false),
122✔
4503
    {result, DiscoInfo#disco_info{xdata = [Extras]}};
122✔
4504

4505
process_iq_disco_info(From, #iq{type = get, lang = Lang,
4506
                                sub_els = [#disco_info{node = ?NS_COMMANDS}]},
4507
                      StateData) ->
4508
    case (StateData#state.config)#config.enable_hats andalso
×
4509
        is_admin(From, StateData)
×
4510
    of
4511
        true ->
4512
            {result,
×
4513
             #disco_info{
4514
                identities = [#identity{category = <<"automation">>,
4515
                                        type = <<"command-list">>,
4516
                                        name = translate:translate(
4517
                                                 Lang, ?T("Commands"))}]}};
4518
        false ->
4519
            Txt = ?T("Node not found"),
×
4520
            {error, xmpp:err_item_not_found(Txt, Lang)}
×
4521
    end;
4522

4523
process_iq_disco_info(From, #iq{type = get, lang = Lang,
4524
                                sub_els = [#disco_info{node = Node}]},
4525
                      StateData)
4526
  when Node == ?MUC_HAT_CREATE_CMD;
4527
       Node == ?MUC_HAT_DESTROY_CMD;
4528
       Node == ?MUC_HAT_LISTHATS_CMD;
4529
       Node == ?MUC_HAT_ASSIGN_CMD;
4530
       Node == ?MUC_HAT_UNASSIGN_CMD;
4531
       Node == ?MUC_HAT_LISTUSERS_CMD ->
4532
    NodeName = case Node of
×
4533
               ?MUC_HAT_CREATE_CMD -> ?T("Create a Hat");
×
4534
               ?MUC_HAT_DESTROY_CMD -> ?T("Destroy a Hat");
×
4535
               ?MUC_HAT_LISTHATS_CMD -> ?T("List of Hats");
×
4536
               ?MUC_HAT_ASSIGN_CMD -> ?T("Assign a hat to a user");
×
4537
               ?MUC_HAT_UNASSIGN_CMD -> ?T("Remove a hat from a user");
×
4538
               ?MUC_HAT_LISTUSERS_CMD -> ?T("List users with hats")
×
4539
               end,
4540

4541
    case (StateData#state.config)#config.enable_hats andalso
×
4542
        is_admin(From, StateData)
×
4543
    of
4544
        true ->
4545
            {result,
×
4546
             #disco_info{
4547
                identities = [#identity{category = <<"automation">>,
4548
                                        type = <<"command-node">>,
4549
                                        name = translate:translate(
4550
                                              Lang, NodeName)}],
4551
                features = [?NS_COMMANDS]}};
4552
        false ->
4553
            Txt = ?T("Node not found"),
×
4554
            {error, xmpp:err_item_not_found(Txt, Lang)}
×
4555
    end;
4556

4557
process_iq_disco_info(From, #iq{type = get, lang = Lang,
4558
                                sub_els = [#disco_info{node = Node}]},
4559
                      StateData) ->
4560
    try
×
4561
        true = mod_caps:is_valid_node(Node),
×
4562
        DiscoInfo = make_disco_info(From, StateData),
×
4563
        Extras = iq_disco_info_extras(Lang, StateData, true),
×
4564
        DiscoInfo1 = DiscoInfo#disco_info{xdata = [Extras]},
×
4565
        Hash = mod_caps:compute_disco_hash(DiscoInfo1, sha),
×
4566
        Node = <<(ejabberd_config:get_uri())/binary, $#, Hash/binary>>,
×
4567
        {result, DiscoInfo1#disco_info{node = Node}}
×
4568
    catch _:{badmatch, _} ->
4569
            Txt = ?T("Invalid node name"),
×
4570
            {error, xmpp:err_item_not_found(Txt, Lang)}
×
4571
    end.
4572

4573
-spec iq_disco_info_extras(binary(), state(), boolean()) -> xdata().
4574
iq_disco_info_extras(Lang, StateData, Static) ->
4575
    Config = StateData#state.config,
1,978✔
4576
    Fs1 = [{roomname, Config#config.title},
1,978✔
4577
           {description, Config#config.description},
4578
           {changesubject, Config#config.allow_change_subj},
4579
           {allowinvites, Config#config.allow_user_invites},
4580
           {allow_query_users, Config#config.allow_query_users},
4581
           {allowpm, Config#config.allowpm},
4582
           {lang, Config#config.lang}],
4583
    Fs2 = case Config#config.pubsub of
1,978✔
4584
              Node when is_binary(Node), Node /= <<"">> ->
4585
                  [{pubsub, Node}|Fs1];
×
4586
              _ ->
4587
                  Fs1
1,978✔
4588
          end,
4589
    Fs3 = case Static of
1,978✔
4590
              false ->
4591
                  [{occupants, maps:size(StateData#state.nicks)}|Fs2];
122✔
4592
              true ->
4593
                  Fs2
1,856✔
4594
          end,
4595
    Fs4 = case Config#config.logging of
1,978✔
4596
              true ->
4597
                  case ejabberd_hooks:run_fold(muc_log_get_url,
×
4598
                                               StateData#state.server_host,
4599
                                               error,
4600
                                               [StateData]) of
4601
                      {ok, URL} ->
4602
                          [{logs, URL}|Fs3];
×
4603
                      error ->
4604
                          Fs3
×
4605
                  end;
4606
              false ->
4607
                  Fs3
1,978✔
4608
          end,
4609
    Fs5 = case (StateData#state.config)#config.vcard_xupdate of
1,978✔
4610
              Hash when is_binary(Hash) ->
4611
                  [{avatarhash, [Hash]} | Fs4];
26✔
4612
              _ ->
4613
                  Fs4
1,952✔
4614
          end,
4615
    Fs6 = ejabberd_hooks:run_fold(muc_disco_info_extras,
1,978✔
4616
                                   StateData#state.server_host,
4617
                                   Fs5,
4618
                                   [StateData]),
4619
    Fs7 = case (StateData#state.config)#config.enable_hats of
1,978✔
4620
              true ->
4621
                  HatsHash = get_hats_hash(StateData),
1,978✔
4622
                  [{'hats#hash', [HatsHash]} | Fs6];
1,978✔
4623
              false ->
4624
                  Fs6
×
4625
          end,
4626
    #xdata{type = result,
1,978✔
4627
           fields = muc_roominfo:encode(Fs7, Lang)}.
4628

4629
-spec process_iq_disco_items(jid(), iq(), state()) ->
4630
                                    {error, stanza_error()} | {result, disco_items()}.
4631
process_iq_disco_items(_From, #iq{type = set, lang = Lang}, _StateData) ->
4632
    Txt = ?T("Value 'set' of 'type' attribute is not allowed"),
×
4633
    {error, xmpp:err_not_allowed(Txt, Lang)};
×
4634
process_iq_disco_items(From, #iq{type = get, sub_els = [#disco_items{node = <<>>}]},
4635
                       StateData) ->
4636
    case (StateData#state.config)#config.public_list of
18✔
4637
      true ->
4638
          {result, get_mucroom_disco_items(StateData)};
9✔
4639
      _ ->
4640
          case is_occupant_or_admin(From, StateData) of
9✔
4641
            true ->
4642
                {result, get_mucroom_disco_items(StateData)};
×
4643
            _ ->
4644
                %% If the list of occupants is private,
4645
                %% the room MUST return an empty <query/> element
4646
                %% (http://xmpp.org/extensions/xep-0045.html#disco-roomitems)
4647
                {result, #disco_items{}}
9✔
4648
          end
4649
    end;
4650
process_iq_disco_items(From, #iq{type = get, lang = Lang,
4651
                                 sub_els = [#disco_items{node = ?NS_COMMANDS}]},
4652
                       StateData) ->
4653
    case (StateData#state.config)#config.enable_hats andalso
×
4654
        is_admin(From, StateData)
×
4655
    of
4656
        true ->
4657
            {result,
×
4658
             #disco_items{
4659
                items = [#disco_item{jid = StateData#state.jid,
4660
                                     node = ?MUC_HAT_CREATE_CMD,
4661
                                     name = translate:translate(
4662
                                              Lang, ?T("Create a Hat"))},
4663
                         #disco_item{jid = StateData#state.jid,
4664
                                     node = ?MUC_HAT_DESTROY_CMD,
4665
                                     name = translate:translate(
4666
                                              Lang, ?T("Destroy a Hat"))},
4667
                         #disco_item{jid = StateData#state.jid,
4668
                                     node = ?MUC_HAT_LISTHATS_CMD,
4669
                                     name = translate:translate(
4670
                                              Lang, ?T("List of Hats"))},
4671
                         #disco_item{jid = StateData#state.jid,
4672
                                     node = ?MUC_HAT_ASSIGN_CMD,
4673
                                     name = translate:translate(
4674
                                              Lang, ?T("Assign a hat to a user"))},
4675
                         #disco_item{jid = StateData#state.jid,
4676
                                     node = ?MUC_HAT_UNASSIGN_CMD,
4677
                                     name = translate:translate(
4678
                                              Lang, ?T("Remove a hat from a user"))},
4679
                         #disco_item{jid = StateData#state.jid,
4680
                                     node = ?MUC_HAT_LISTUSERS_CMD,
4681
                                     name = translate:translate(
4682
                                              Lang, ?T("List users with hats"))}]}};
4683
        false ->
4684
            Txt = ?T("Node not found"),
×
4685
            {error, xmpp:err_item_not_found(Txt, Lang)}
×
4686
    end;
4687
process_iq_disco_items(From, #iq{type = get, lang = Lang,
4688
                                 sub_els = [#disco_items{node = Node}]},
4689
                       StateData)
4690
  when Node == ?MUC_HAT_CREATE_CMD;
4691
       Node == ?MUC_HAT_DESTROY_CMD;
4692
       Node == ?MUC_HAT_LISTHATS_CMD;
4693
       Node == ?MUC_HAT_ASSIGN_CMD;
4694
       Node == ?MUC_HAT_UNASSIGN_CMD;
4695
       Node == ?MUC_HAT_LISTUSERS_CMD ->
4696
    case (StateData#state.config)#config.enable_hats andalso
×
4697
        is_admin(From, StateData)
×
4698
    of
4699
        true ->
4700
            {result, #disco_items{}};
×
4701
        false ->
4702
            Txt = ?T("Node not found"),
×
4703
            {error, xmpp:err_item_not_found(Txt, Lang)}
×
4704
    end;
4705
process_iq_disco_items(_From, #iq{lang = Lang}, _StateData) ->
4706
    Txt = ?T("Node not found"),
×
4707
    {error, xmpp:err_item_not_found(Txt, Lang)}.
×
4708

4709
-spec process_iq_captcha(jid(), iq(), state()) -> {error, stanza_error()} |
4710
                                                  {result, undefined}.
4711
process_iq_captcha(_From, #iq{type = get, lang = Lang}, _StateData) ->
4712
    Txt = ?T("Value 'get' of 'type' attribute is not allowed"),
×
4713
    {error, xmpp:err_not_allowed(Txt, Lang)};
×
4714
process_iq_captcha(_From, #iq{type = set, lang = Lang, sub_els = [SubEl]},
4715
                   _StateData) ->
4716
    case ejabberd_captcha:process_reply(SubEl) of
×
4717
      ok -> {result, undefined};
×
4718
      {error, malformed} ->
4719
            Txt = ?T("Incorrect CAPTCHA submit"),
×
4720
            {error, xmpp:err_bad_request(Txt, Lang)};
×
4721
      _ ->
4722
            Txt = ?T("The CAPTCHA verification has failed"),
×
4723
            {error, xmpp:err_not_allowed(Txt, Lang)}
×
4724
    end.
4725

4726
-spec process_iq_vcard(jid(), iq(), state()) ->
4727
                              {result, vcard_temp() | xmlel()} |
4728
                              {result, undefined, state()} |
4729
                              {error, stanza_error()}.
4730
process_iq_vcard(_From, #iq{type = get}, StateData) ->
4731
    #state{config = #config{vcard = VCardRaw}} = StateData,
36✔
4732
    case fxml_stream:parse_element(VCardRaw) of
36✔
4733
        #xmlel{} = VCard ->
4734
            {result, VCard};
27✔
4735
        {error, _} ->
4736
            {error, xmpp:err_item_not_found()}
9✔
4737
    end;
4738
process_iq_vcard(From, #iq{type = set, lang = Lang, sub_els = [Pkt]},
4739
                 StateData) ->
4740
    case get_affiliation(From, StateData) of
18✔
4741
        owner ->
4742
            SubEl = xmpp:encode(Pkt),
9✔
4743
            VCardRaw = fxml:element_to_binary(SubEl),
9✔
4744
            Hash = mod_vcard_xupdate:compute_hash(SubEl),
9✔
4745
            Config = StateData#state.config,
9✔
4746
            NewConfig = Config#config{vcard = VCardRaw, vcard_xupdate = Hash},
9✔
4747
            change_config(NewConfig, StateData);
9✔
4748
        _ ->
4749
            ErrText = ?T("Owner privileges required"),
9✔
4750
            {error, xmpp:err_forbidden(ErrText, Lang)}
9✔
4751
    end.
4752

4753
-spec process_iq_mucsub(jid(), iq(), state()) ->
4754
      {error, stanza_error()} |
4755
      {result, undefined | muc_subscribe() | muc_subscriptions(), stop | state()} |
4756
      {ignore, state()}.
4757
process_iq_mucsub(_From, #iq{type = set, lang = Lang,
4758
                             sub_els = [#muc_subscribe{}]},
4759
                  #state{just_created = Just, config = #config{allow_subscription = false}}) when Just /= true ->
4760
    {error, xmpp:err_not_allowed(?T("Subscriptions are not allowed"), Lang)};
×
4761
process_iq_mucsub(From,
4762
                  #iq{type = set, lang = Lang,
4763
                      sub_els = [#muc_subscribe{jid = #jid{} = SubJid} = Mucsub]},
4764
                  StateData) ->
4765
    FAffiliation = get_affiliation(From, StateData),
32✔
4766
    FRole = get_role(From, StateData),
32✔
4767
    if FRole == moderator; FAffiliation == owner; FAffiliation == admin ->
32✔
4768
            process_iq_mucsub(SubJid,
32✔
4769
                              #iq{type = set, lang = Lang,
4770
                                  sub_els = [Mucsub#muc_subscribe{jid = undefined}]},
4771
                              StateData);
4772
       true ->
4773
            Txt = ?T("Moderator privileges required"),
×
4774
            {error, xmpp:err_forbidden(Txt, Lang)}
×
4775
    end;
4776
process_iq_mucsub(From,
4777
                  #iq{type = set, lang = Lang,
4778
                      sub_els = [#muc_subscribe{nick = Nick}]} = Packet,
4779
                  StateData) ->
4780
    LBareJID = jid:tolower(jid:remove_resource(From)),
77✔
4781
    try muc_subscribers_get(LBareJID, StateData#state.muc_subscribers) of
77✔
4782
        #subscriber{nick = Nick1} when Nick1 /= Nick ->
4783
            Nodes = get_subscription_nodes(Packet),
×
4784
            case nick_collision(From, Nick, StateData) of
×
4785
                true ->
4786
                    ErrText = ?T("That nickname is already in use by another occupant"),
×
4787
                    {error, xmpp:err_conflict(ErrText, Lang)};
×
4788
                false ->
4789
                    case mod_muc:can_use_nick(StateData#state.server_host,
×
4790
                                              jid:encode(StateData#state.jid),
4791
                                              From, Nick) of
4792
                        false ->
4793
                            Err = case Nick of
×
4794
                                      <<>> ->
4795
                                          xmpp:err_jid_malformed(
×
4796
                                            ?T("Nickname can't be empty"),
4797
                                            Lang);
4798
                                      _ ->
4799
                                          xmpp:err_conflict(
×
4800
                                            ?T("That nickname is registered"
4801
                                               " by another person"), Lang)
4802
                                  end,
4803
                            {error, Err};
×
4804
                        true ->
4805
                            NewStateData =
×
4806
                                set_subscriber(From, Nick, Nodes, StateData),
4807
                            {result, subscribe_result(Packet), NewStateData}
×
4808
                    end
4809
            end;
4810
        #subscriber{} ->
4811
            Nodes = get_subscription_nodes(Packet),
×
4812
            NewStateData = set_subscriber(From, Nick, Nodes, StateData),
×
4813
            {result, subscribe_result(Packet), NewStateData}
×
4814
    catch _:{badkey, _} ->
4815
            SD2 = StateData#state{config = (StateData#state.config)#config{allow_subscription = true}},
77✔
4816
            add_new_user(From, Nick, Packet, SD2)
77✔
4817
    end;
4818
process_iq_mucsub(From, #iq{type = set, lang = Lang,
4819
                            sub_els = [#muc_unsubscribe{jid = #jid{} = UnsubJid}]},
4820
                  StateData) ->
4821
    FAffiliation = get_affiliation(From, StateData),
×
4822
    FRole = get_role(From, StateData),
×
4823
    if FRole == moderator; FAffiliation == owner; FAffiliation == admin ->
×
4824
            process_iq_mucsub(UnsubJid,
×
4825
                              #iq{type = set, lang = Lang,
4826
                                  sub_els = [#muc_unsubscribe{jid = undefined}]},
4827
                              StateData);
4828
       true ->
4829
            Txt = ?T("Moderator privileges required"),
×
4830
            {error, xmpp:err_forbidden(Txt, Lang)}
×
4831
    end;
4832
process_iq_mucsub(From, #iq{type = set, sub_els = [#muc_unsubscribe{}]},
4833
                  #state{room = Room, host = Host, server_host = ServerHost} = StateData) ->
4834
    BareJID = jid:remove_resource(From),
95✔
4835
    LBareJID = jid:tolower(BareJID),
95✔
4836
    try muc_subscribers_remove_exn(LBareJID, StateData#state.muc_subscribers) of
95✔
4837
        {MUCSubscribers, #subscriber{nick = Nick}} ->
4838
            NewStateData = StateData#state{muc_subscribers = MUCSubscribers},
77✔
4839
            store_room(NewStateData, [{del_subscription, LBareJID}]),
77✔
4840
            Packet1a = #message{
77✔
4841
                sub_els = [#ps_event{
4842
                    items = #ps_items{
4843
                        node = ?NS_MUCSUB_NODES_SUBSCRIBERS,
4844
                        items = [#ps_item{
4845
                            id = p1_rand:get_string(),
4846
                            sub_els = [#muc_unsubscribe{jid = BareJID, nick = Nick}]}]}}]},
4847
            Packet1b = #message{
77✔
4848
                sub_els = [#ps_event{
4849
                    items = #ps_items{
4850
                        node = ?NS_MUCSUB_NODES_SUBSCRIBERS,
4851
                        items = [#ps_item{
4852
                            id = p1_rand:get_string(),
4853
                            sub_els = [#muc_unsubscribe{nick = Nick}]}]}}]},
4854
            {Packet2a, Packet2b} = ejabberd_hooks:run_fold(muc_unsubscribed, ServerHost, {Packet1a, Packet1b},
77✔
4855
                                                           [ServerHost, Room, Host, BareJID, StateData]),
4856
            send_subscriptions_change_notifications(Packet2a, Packet2b, StateData),
77✔
4857
            NewStateData2 = case close_room_if_temporary_and_empty(NewStateData) of
77✔
4858
                {stop, normal, _} -> stop;
×
4859
                {next_state, normal_state, SD} -> SD
77✔
4860
            end,
4861
            {result, undefined, NewStateData2}
77✔
4862
        catch _:{badkey, _} ->
4863
            {result, undefined, StateData}
18✔
4864
    end;
4865
process_iq_mucsub(From, #iq{type = get, lang = Lang,
4866
                            sub_els = [#muc_subscriptions{}]},
4867
                  StateData) ->
4868
    FAffiliation = get_affiliation(From, StateData),
×
4869
    FRole = get_role(From, StateData),
×
4870
    IsModerator = FRole == moderator orelse FAffiliation == owner orelse
×
4871
                  FAffiliation == admin,
×
4872
    case IsModerator orelse is_subscriber(From, StateData) of
×
4873
        true ->
4874
            ShowJid = IsModerator orelse
×
4875
                      (StateData#state.config)#config.anonymous == false,
×
4876
            Subs = muc_subscribers_fold(
×
4877
                     fun(_, #subscriber{jid = J, nick = N, nodes = Nodes}, Acc) ->
4878
                         case ShowJid of
×
4879
                             true ->
4880
                                 [#muc_subscription{jid = J, nick = N, events = Nodes}|Acc];
×
4881
                             _ ->
4882
                                 [#muc_subscription{nick = N, events = Nodes}|Acc]
×
4883
                         end
4884
                     end, [], StateData#state.muc_subscribers),
4885
            {result, #muc_subscriptions{list = Subs}, StateData};
×
4886
        _ ->
4887
            Txt = ?T("Moderator privileges required"),
×
4888
            {error, xmpp:err_forbidden(Txt, Lang)}
×
4889
    end;
4890
process_iq_mucsub(_From, #iq{type = get, lang = Lang}, _StateData) ->
4891
    Txt = ?T("Value 'get' of 'type' attribute is not allowed"),
×
4892
    {error, xmpp:err_bad_request(Txt, Lang)}.
×
4893

4894
-spec remove_subscriptions(state()) -> state().
4895
remove_subscriptions(StateData) ->
4896
    if not (StateData#state.config)#config.allow_subscription ->
1,015✔
4897
            StateData#state{muc_subscribers = muc_subscribers_new()};
874✔
4898
       true ->
4899
            StateData
141✔
4900
    end.
4901

4902
-spec get_subscription_nodes(stanza()) -> [binary()].
4903
get_subscription_nodes(#iq{sub_els = [#muc_subscribe{events = Nodes}]}) ->
4904
    lists:filter(
154✔
4905
      fun(Node) ->
4906
              lists:member(Node, [?NS_MUCSUB_NODES_PRESENCE,
64✔
4907
                                  ?NS_MUCSUB_NODES_MESSAGES,
4908
                                  ?NS_MUCSUB_NODES_AFFILIATIONS,
4909
                                  ?NS_MUCSUB_NODES_SUBJECT,
4910
                                  ?NS_MUCSUB_NODES_CONFIG,
4911
                                  ?NS_MUCSUB_NODES_PARTICIPANTS,
4912
                                  ?NS_MUCSUB_NODES_SUBSCRIBERS])
4913
      end, Nodes);
4914
get_subscription_nodes(_) ->
4915
    [].
679✔
4916

4917
-spec subscribe_result(iq()) -> muc_subscribe().
4918
subscribe_result(#iq{sub_els = [#muc_subscribe{nick = Nick}]} = Packet) ->
4919
    #muc_subscribe{nick = Nick, events = get_subscription_nodes(Packet)}.
77✔
4920

4921
-spec get_title(state()) -> binary().
4922
get_title(StateData) ->
4923
    case (StateData#state.config)#config.title of
54✔
4924
      <<"">> -> StateData#state.room;
54✔
4925
      Name -> Name
×
4926
    end.
4927

4928
-spec get_roomdesc_reply(jid(), state(), binary()) -> {item, binary()} | false.
4929
get_roomdesc_reply(JID, StateData, Tail) ->
4930
    IsOccupantOrAdmin = is_occupant_or_admin(JID,
63✔
4931
                                             StateData),
4932
    if (StateData#state.config)#config.public or
63✔
4933
         IsOccupantOrAdmin ->
4934
           if (StateData#state.config)#config.public_list or
54✔
4935
                IsOccupantOrAdmin ->
4936
                  {item, <<(get_title(StateData))/binary,Tail/binary>>};
54✔
4937
              true -> {item, get_title(StateData)}
×
4938
           end;
4939
       true -> false
9✔
4940
    end.
4941

4942
-spec get_roomdesc_tail(state(), binary()) -> binary().
4943
get_roomdesc_tail(StateData, Lang) ->
4944
    Desc = case (StateData#state.config)#config.public of
63✔
4945
             true -> <<"">>;
54✔
4946
             _ -> translate:translate(Lang, ?T("private, "))
9✔
4947
           end,
4948
    Len = maps:size(StateData#state.nicks),
63✔
4949
    <<" (", Desc/binary, (integer_to_binary(Len))/binary, ")">>.
63✔
4950

4951
-spec get_mucroom_disco_items(state()) -> disco_items().
4952
get_mucroom_disco_items(StateData) ->
4953
    Items = maps:fold(
9✔
4954
               fun(Nick, _, Acc) ->
4955
                       [#disco_item{jid = jid:make(StateData#state.room,
9✔
4956
                                                   StateData#state.host,
4957
                                                   Nick),
4958
                                    name = Nick}|Acc]
4959
               end, [], StateData#state.nicks),
4960
    #disco_items{items = Items}.
9✔
4961

4962
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4963
% Hats
4964

4965
%% @format-begin
4966

4967
-spec process_iq_adhoc(jid(), iq(), state()) ->
4968
                          {result, adhoc_command()} |
4969
                          {result, adhoc_command(), state()} |
4970
                          {error, stanza_error()}.
4971
process_iq_adhoc(_From, #iq{type = get}, _StateData) ->
4972
    {error, xmpp:err_bad_request()};
×
4973
process_iq_adhoc(From,
4974
                 #iq{type = set,
4975
                     lang = Lang1,
4976
                     sub_els = [#adhoc_command{} = Request]},
4977
                 StateData) ->
4978
    % Ad-Hoc Commands are used only for Hats here
4979
    case StateData#state.config#config.enable_hats andalso is_admin(From, StateData) of
×
4980
        true ->
4981
            #adhoc_command{lang = Lang2,
×
4982
                           node = Node,
4983
                           action = Action,
4984
                           xdata = XData} =
4985
                Request,
4986
            Lang =
×
4987
                case Lang2 of
4988
                    <<"">> ->
4989
                        Lang1;
×
4990
                    _ ->
4991
                        Lang2
×
4992
                end,
4993
            case {Node, Action} of
×
4994
                {_, cancel} ->
4995
                    {result,
×
4996
                     xmpp_util:make_adhoc_response(Request,
4997
                                                   #adhoc_command{status = canceled,
4998
                                                                  lang = Lang,
4999
                                                                  node = Node})};
5000
                {Node, execute}
5001
                    when Node == ?MUC_HAT_CREATE_CMD;
5002
                         Node == ?MUC_HAT_DESTROY_CMD;
5003
                         Node == ?MUC_HAT_LISTHATS_CMD;
5004
                         Node == ?MUC_HAT_ASSIGN_CMD;
5005
                         Node == ?MUC_HAT_UNASSIGN_CMD;
5006
                         Node == ?MUC_HAT_LISTUSERS_CMD ->
5007
                    {Status, Form} = process_iq_adhoc_hats(Node, StateData, Lang),
×
5008
                    {result,
×
5009
                     xmpp_util:make_adhoc_response(Request,
5010
                                                   #adhoc_command{status = Status, xdata = Form})};
5011
                {Node, complete}
5012
                    when XData /= undefined andalso Node == ?MUC_HAT_CREATE_CMD;
5013
                         Node == ?MUC_HAT_DESTROY_CMD;
5014
                         Node == ?MUC_HAT_ASSIGN_CMD;
5015
                         Node == ?MUC_HAT_UNASSIGN_CMD ->
5016
                    case process_iq_adhoc_hats_complete(Node, XData, StateData, Lang) of
×
5017
                        {ok, NewStateData} ->
5018
                            {result,
×
5019
                             xmpp_util:make_adhoc_response(Request,
5020
                                                           #adhoc_command{status = completed}),
5021
                             NewStateData};
5022
                        {error, XmlElement} ->
5023
                            {error, XmlElement};
×
5024
                        error ->
5025
                            {error, xmpp:err_bad_request()}
×
5026
                    end;
5027
                {Node, complete}
5028
                    when Node == ?MUC_HAT_CREATE_CMD;
5029
                         Node == ?MUC_HAT_DESTROY_CMD;
5030
                         Node == ?MUC_HAT_ASSIGN_CMD;
5031
                         Node == ?MUC_HAT_UNASSIGN_CMD ->
5032
                    {error, xmpp:err_bad_request()};
×
5033
                {Node, _}
5034
                    when Node == ?MUC_HAT_CREATE_CMD;
5035
                         Node == ?MUC_HAT_DESTROY_CMD;
5036
                         Node == ?MUC_HAT_LISTHATS_CMD;
5037
                         Node == ?MUC_HAT_ASSIGN_CMD;
5038
                         Node == ?MUC_HAT_UNASSIGN_CMD;
5039
                         Node == ?MUC_HAT_LISTUSERS_CMD ->
5040
                    Txt = ?T("Incorrect value of 'action' attribute"),
×
5041
                    {error, xmpp:err_bad_request(Txt, Lang)};
×
5042
                _ ->
5043
                    {error, xmpp:err_item_not_found()}
×
5044
            end;
5045
        _ ->
5046
            {error, xmpp:err_forbidden()}
×
5047
    end.
5048

5049
process_iq_adhoc_hats(?MUC_HAT_LISTHATS_CMD, StateData, Lang) ->
5050
    Hats = get_defined_hats(StateData),
×
5051
    Items =
×
5052
        lists:map(fun({URI, Title, Hue}) ->
5053
                     [#xdata_field{var = <<"hats#uri">>, values = [URI]},
×
5054
                      #xdata_field{var = <<"hats#title">>, values = [Title]},
5055
                      #xdata_field{var = <<"hats#hue">>, values = [Hue]}]
5056
                  end,
5057
                  Hats),
5058
    Form =
×
5059
        #xdata{title = translate:translate(Lang, ?T("List of Hats")),
5060
               type = result,
5061
               reported =
5062
                   [#xdata_field{label = translate:translate(Lang, ?T("Hat URI")),
5063
                                 var = <<"hats#uri">>},
5064
                    #xdata_field{label = translate:translate(Lang, ?T("Hat title")),
5065
                                 var = <<"hats#title">>},
5066
                    #xdata_field{label = translate:translate(Lang, ?T("Hat hue")),
5067
                                 var = <<"hats#hue">>}],
5068
               items = Items},
5069
    {completed, Form};
×
5070
process_iq_adhoc_hats(?MUC_HAT_CREATE_CMD, _StateData, Lang) ->
5071
    Form =
×
5072
        #xdata{title = translate:translate(Lang, ?T("Create a Hat")),
5073
               type = form,
5074
               fields =
5075
                   [#xdata_field{type = 'text-single',
5076
                                 label = translate:translate(Lang, ?T("Hat URI")),
5077
                                 required = true,
5078
                                 var = <<"hats#uri">>},
5079
                    #xdata_field{type = 'text-single',
5080
                                 label = translate:translate(Lang, ?T("Hat title")),
5081
                                 required = true,
5082
                                 var = <<"hats#title">>},
5083
                    #xdata_field{type = 'text-single',
5084
                                 label = translate:translate(Lang, ?T("Hat hue")),
5085
                                 var = <<"hats#hue">>}]},
5086
    {executing, Form};
×
5087
process_iq_adhoc_hats(?MUC_HAT_DESTROY_CMD, _StateData, Lang) ->
5088
    Form =
×
5089
        #xdata{title = translate:translate(Lang, ?T("Destroy a Hat")),
5090
               type = form,
5091
               fields =
5092
                   [#xdata_field{type = 'text-single',
5093
                                 label = translate:translate(Lang, ?T("Hat URI")),
5094
                                 required = true,
5095
                                 var = <<"hat">>}]},
5096
    {executing, Form};
×
5097
process_iq_adhoc_hats(?MUC_HAT_ASSIGN_CMD, StateData, Lang) ->
5098
    Hats = get_defined_hats(StateData),
×
5099
    Options =
×
5100
        [#xdata_option{label = Title, value = Uri}
×
5101
         || {Uri, Title, _Hue} <- lists:keysort(2, Hats)],
×
5102
    Form =
×
5103
        #xdata{title = translate:translate(Lang, ?T("Assign a hat to a user")),
5104
               type = form,
5105
               fields =
5106
                   [#xdata_field{type = 'jid-single',
5107
                                 label = translate:translate(Lang, ?T("Jabber ID")),
5108
                                 required = true,
5109
                                 var = <<"hats#jid">>},
5110
                    #xdata_field{type = 'list-single',
5111
                                 label = translate:translate(Lang, ?T("The role")),
5112
                                 var = <<"hat">>,
5113
                                 options = Options}]},
5114
    {executing, Form};
×
5115
process_iq_adhoc_hats(?MUC_HAT_UNASSIGN_CMD, StateData, Lang) ->
5116
    Hats = get_defined_hats(StateData),
×
5117
    Options =
×
5118
        [#xdata_option{label = Title, value = Uri}
×
5119
         || {Uri, Title, _Hue} <- lists:keysort(2, Hats)],
×
5120
    Form =
×
5121
        #xdata{title = translate:translate(Lang, ?T("Remove a hat from a user")),
5122
               type = form,
5123
               fields =
5124
                   [#xdata_field{type = 'jid-single',
5125
                                 label = translate:translate(Lang, ?T("Jabber ID")),
5126
                                 required = true,
5127
                                 var = <<"hats#jid">>},
5128
                    #xdata_field{type = 'list-single',
5129
                                 label = translate:translate(Lang, ?T("The role")),
5130
                                 var = <<"hat">>,
5131
                                 options = Options}]},
5132
    {executing, Form};
×
5133
process_iq_adhoc_hats(?MUC_HAT_LISTUSERS_CMD, StateData, Lang) ->
5134
    Hats = get_assigned_hats(StateData),
×
5135
    Items =
×
5136
        lists:filtermap(fun({JID, URI}) ->
5137
                           case get_hat_details(URI, StateData) of
×
5138
                               false ->
5139
                                   false;
×
5140
                               {URI, Title, Hue} ->
5141
                                   Fields =
×
5142
                                       [#xdata_field{var = <<"hats#jid">>,
5143
                                                     values = [jid:encode(JID)]},
5144
                                        #xdata_field{var = <<"hats#uri">>, values = [URI]},
5145
                                        #xdata_field{var = <<"hats#title">>, values = [Title]},
5146
                                        #xdata_field{var = <<"hats#hue">>, values = [Hue]}],
5147
                                   {true, Fields}
×
5148
                           end
5149
                        end,
5150
                        Hats),
5151
    Form =
×
5152
        #xdata{title = translate:translate(Lang, ?T("List users with hats")),
5153
               type = result,
5154
               reported =
5155
                   [#xdata_field{label = translate:translate(Lang, ?T("Jabber ID")),
5156
                                 var = <<"hats#jid">>},
5157
                    #xdata_field{label = translate:translate(Lang, ?T("Hat URI")),
5158
                                 var = <<"hats#uri">>},
5159
                    #xdata_field{label = translate:translate(Lang, ?T("Hat title")),
5160
                                 var = <<"hats#title">>},
5161
                    #xdata_field{label = translate:translate(Lang, ?T("Hat hue")),
5162
                                 var = <<"hats#hue">>}],
5163
               items = Items},
5164
    {completed, Form};
×
5165
process_iq_adhoc_hats(_, _, _) ->
5166
    {executing, aaa}.
×
5167

5168
get_xdata_nonempty(Var, XData) ->
5169
    maybe
×
5170
        [Value] ?= xmpp_util:get_xdata_values(Var, XData),
×
5171
        true ?= Value /= <<>>,
×
5172
        Value
×
5173
    else
5174
        _ ->
5175
            []
×
5176
    end.
5177

5178
process_iq_adhoc_hats_complete(?MUC_HAT_CREATE_CMD, XData, StateData, _Lang) ->
5179
    URI = get_xdata_nonempty(<<"hats#uri">>, XData),
×
5180
    Title = get_xdata_nonempty(<<"hats#title">>, XData),
×
5181
    Hue = get_xdata_nonempty(<<"hats#hue">>, XData),
×
5182
    if is_binary(Title) and is_binary(URI) ->
×
5183
           {ok, AffectedJids, NewStateData} = create_hat(URI, Title, Hue, StateData),
×
5184
           store_room(NewStateData),
×
5185
           broadcast_hats_change(NewStateData),
×
5186
           [send_update_presence(AJid, NewStateData, StateData) || AJid <- AffectedJids],
×
5187
           {ok, NewStateData};
×
5188
       true ->
5189
           error
×
5190
    end;
5191
process_iq_adhoc_hats_complete(?MUC_HAT_DESTROY_CMD, XData, StateData, _Lang) ->
5192
    URI = get_xdata_nonempty(<<"hat">>, XData),
×
5193
    if is_binary(URI) ->
×
5194
           {ok, AffectedJids, NewStateData} = destroy_hat(URI, StateData),
×
5195
           store_room(NewStateData),
×
5196
           broadcast_hats_change(NewStateData),
×
5197
           [send_update_presence(AJid, NewStateData, StateData) || AJid <- AffectedJids],
×
5198
           {ok, NewStateData};
×
5199
       true ->
5200
           error
×
5201
    end;
5202
process_iq_adhoc_hats_complete(?MUC_HAT_ASSIGN_CMD, XData, StateData, Lang) ->
5203
    JID = try
×
5204
              jid:decode(get_xdata_nonempty(<<"hats#jid">>, XData))
×
5205
          catch
5206
              _:_ ->
5207
                  error
×
5208
          end,
5209
    URI = get_xdata_nonempty(<<"hat">>, XData),
×
5210
    if (JID /= error) and is_binary(URI) ->
×
5211
           case assign_hat(JID, URI, StateData) of
×
5212
               {ok, NewStateData} ->
5213
                   store_room(NewStateData),
×
5214
                   send_update_presence(JID, NewStateData, StateData),
×
5215
                   {ok, NewStateData};
×
5216
               {error, size_limit} ->
5217
                   Txt = ?T("Hats limit exceeded"),
×
5218
                   {error, xmpp:err_not_allowed(Txt, Lang)}
×
5219
           end;
5220
       true ->
5221
           error
×
5222
    end;
5223
process_iq_adhoc_hats_complete(?MUC_HAT_UNASSIGN_CMD, XData, StateData, _Lang) ->
5224
    JID = try
×
5225
              jid:decode(get_xdata_nonempty(<<"hats#jid">>, XData))
×
5226
          catch
5227
              _:_ ->
5228
                  error
×
5229
          end,
5230
    URI = get_xdata_nonempty(<<"hat">>, XData),
×
5231
    if (JID /= error) and is_binary(URI) ->
×
5232
           {ok, NewStateData} = unassign_hat(JID, URI, StateData),
×
5233
           store_room(NewStateData),
×
5234
           send_update_presence(JID, NewStateData, StateData),
×
5235
           {ok, NewStateData};
×
5236
       true ->
5237
           error
×
5238
    end.
5239

5240
create_hat(URI, Title, Hue, #state{hats_defs = Hats, hats_users = Users} = StateData) ->
5241
    Hats2 = maps:put(URI, {Title, Hue}, Hats),
×
5242

5243
    IsUpdate =
×
5244
        case maps:find(URI, Hats) of
5245
            {ok, {OldTitle, OldHue}} ->
5246
                (OldTitle /= Title) or (OldHue /= Hue);
×
5247
            error ->
5248
                false
×
5249
        end,
5250

5251
    AffectedJids =
×
5252
        case IsUpdate of
5253
            true ->
5254
                maps:fold(fun(Jid, AssignedHatsUris, ChangedAcc) ->
×
5255
                             case lists:member(URI, AssignedHatsUris) of
×
5256
                                 false ->
5257
                                     ChangedAcc;
×
5258
                                 true ->
5259
                                     [Jid | ChangedAcc]
×
5260
                             end
5261
                          end,
5262
                          [],
5263
                          Users);
5264
            false ->
5265
                []
×
5266
        end,
5267
    {ok, AffectedJids, StateData#state{hats_defs = Hats2}}.
×
5268

5269
destroy_hat(URI, #state{hats_defs = Hats, hats_users = Users} = StateData) ->
5270
    Hats2 = maps:remove(URI, Hats),
×
5271
    {AffectedJids, Users2} =
×
5272
        maps:fold(fun(Jid, AssignedHatsUris, {ChangedAcc, UsersAcc}) ->
5273
                     case AssignedHatsUris -- [URI] of
×
5274
                         [] ->
5275
                             {ChangedAcc, UsersAcc};
×
5276
                         AssignedHatsUris2 ->
5277
                             {[Jid | ChangedAcc], maps:put(Jid, AssignedHatsUris2, UsersAcc)}
×
5278
                     end
5279
                  end,
5280
                  {[], maps:new()},
5281
                  Users),
5282
    {ok, AffectedJids, StateData#state{hats_defs = Hats2, hats_users = Users2}}.
×
5283

5284
broadcast_hats_change(StateData) ->
5285
    Codes = [104],
×
5286
    Message =
×
5287
        #message{type = groupchat,
5288
                 id = p1_rand:get_string(),
5289
                 sub_els = [#muc_user{status_codes = Codes}]},
5290
    send_wrapped_multiple(StateData#state.jid,
×
5291
                          get_users_and_subscribers_with_node(?NS_MUCSUB_NODES_CONFIG, StateData),
5292
                          Message,
5293
                          ?NS_MUCSUB_NODES_CONFIG,
5294
                          StateData).
5295

5296
-spec assign_hat(jid(), binary(), state()) -> {ok, state()} | {error, size_limit}.
5297
assign_hat(JID, URI, StateData) ->
5298
    Hats = StateData#state.hats_users,
×
5299
    LJID =
×
5300
        jid:remove_resource(
5301
            jid:tolower(JID)),
5302
    UserHats = maps:get(LJID, Hats, []),
×
5303
    UserHats2 = lists:umerge([URI], UserHats),
×
5304
    USize = length(UserHats2),
×
5305
    if USize =< ?MAX_HATS_PER_USER ->
×
5306
           Hats2 = maps:put(LJID, UserHats2, Hats),
×
5307
           Size = maps:size(Hats2),
×
5308
           if Size =< ?MAX_HATS_USERS ->
×
5309
                  {ok, StateData#state{hats_users = Hats2}};
×
5310
              true ->
5311
                  {error, size_limit}
×
5312
           end;
5313
       true ->
5314
           {error, size_limit}
×
5315
    end.
5316

5317
-spec unassign_hat(jid(), binary(), state()) -> {ok, state()} | {error, size_limit}.
5318
unassign_hat(JID, URI, StateData) ->
5319
    Hats = StateData#state.hats_users,
×
5320
    LJID =
×
5321
        jid:remove_resource(
5322
            jid:tolower(JID)),
5323
    UserHats = maps:get(LJID, Hats, []),
×
5324
    Hats2 =
×
5325
        case lists:delete(URI, UserHats) of
5326
            [] ->
5327
                maps:remove(LJID, Hats);
×
5328
            UserHats2 ->
5329
                maps:put(LJID, UserHats2, Hats)
×
5330
        end,
5331
    {ok, StateData#state{hats_users = Hats2}}.
×
5332

5333
-spec get_defined_hats(state()) -> [{binary(), binary(), binary()}].
5334
get_defined_hats(StateData) ->
5335
    lists:map(fun({Uri, {Title, Hue}}) -> {Uri, Title, Hue} end,
×
5336
              maps:to_list(StateData#state.hats_defs)).
5337

5338
-spec get_assigned_hats(state()) -> [{jid(), binary()}].
5339
get_assigned_hats(StateData) ->
5340
    lists:flatmap(fun({LJID, H}) ->
1,978✔
5341
                     JID = jid:make(LJID),
×
5342
                     lists:map(fun(URI) -> {JID, URI} end, H)
×
5343
                  end,
5344
                  maps:to_list(StateData#state.hats_users)).
5345

5346
get_hats_hash(StateData) ->
5347
    str:sha(
1,978✔
5348
        misc:term_to_base64(get_assigned_hats(StateData))).
5349

5350
-spec get_hat_details(binary(), state()) -> {binary(), binary(), binary()} | false.
5351
get_hat_details(Uri, StateData) ->
5352
    lists:keyfind(Uri, 1, get_defined_hats(StateData)).
×
5353

5354
-spec add_presence_hats(jid(), #presence{}, state()) -> #presence{}.
5355
add_presence_hats(JID, Pres, StateData) ->
5356
    case StateData#state.config#config.enable_hats of
2,366✔
5357
        true ->
5358
            Hats = StateData#state.hats_users,
2,366✔
5359
            LJID =
2,366✔
5360
                jid:remove_resource(
5361
                    jid:tolower(JID)),
5362
            UserHats = maps:get(LJID, Hats, []),
2,366✔
5363
            case length(UserHats) of
2,366✔
5364
                0 ->
5365
                    Pres;
2,366✔
5366
                _ ->
5367
                    Items =
×
5368
                        lists:filtermap(fun(URI) ->
5369
                                           case get_hat_details(URI, StateData) of
×
5370
                                               false ->
5371
                                                   false;
×
5372
                                               {URI, Title, Hue} ->
5373
                                                   #muc_hat{uri = URI,
×
5374
                                                            title = Title,
5375
                                                            hue = Hue}
5376
                                           end
5377
                                        end,
5378
                                        UserHats),
5379
                    xmpp:set_subtag(Pres, #muc_hats{hats = Items})
×
5380
            end;
5381
        false ->
5382
            Pres
×
5383
    end.
5384
%% @format-end
5385

5386
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5387

5388
-spec process_iq_moderate(jid(), iq(), binary(), binary() | undefined, state()) ->
5389
    {result, undefined, state()} |
5390
    {error, stanza_error()}.
5391
process_iq_moderate(_From, #iq{type = get}, _Id, _Reason, _StateData) ->
5392
    {error, xmpp:err_bad_request()};
×
5393
process_iq_moderate(From, #iq{type = set, lang = Lang}, Id, Reason,
5394
                    #state{config = Config, room = Room, host = Host,
5395
                           jid = JID, server_host = Server} = StateData) ->
5396
    FAffiliation = get_affiliation(From, StateData),
×
5397
    FRole = get_role(From, StateData),
×
5398
    IsModerator = FRole == moderator orelse FAffiliation == owner orelse
×
5399
                  FAffiliation == admin,
×
5400
    case IsModerator of
×
5401
        false ->
5402
            {error, xmpp:err_forbidden(
×
5403
                ?T("Only moderators are allowed to retract messages"), Lang)};
5404
        _ ->
5405
            try binary_to_integer(Id) of
×
5406
                StanzaId ->
5407
                    case Config#config.mam of
×
5408
                        true ->
5409
                            mod_mam:remove_message_from_archive({Room, Host}, Server, StanzaId);
×
5410
                        _ ->
5411
                            ok
×
5412
                    end,
5413
                    By = jid:replace_resource(JID, find_nick_by_jid(From, StateData)),
×
5414
                    Mod21 = #message_moderated_21{by = By,
×
5415
                                                  reason = Reason,
5416
                                                  sub_els = [#message_retract_30{}]},
5417
                    SubEl = [#fasten_apply_to{id = Id,
×
5418
                                              sub_els = [Mod21]},
5419
                             #message_retract{id = Id,
5420
                                              reason = Reason,
5421
                                              moderated = #message_moderated{by = By}}],
5422
                    Packet0 = #message{type = groupchat,
×
5423
                                       from = From,
5424
                                       sub_els = SubEl},
5425
                    {FromNick, _Role} = get_participant_data(From, StateData),
×
5426
                    Packet = ejabberd_hooks:run_fold(muc_filter_message,
×
5427
                                                     StateData#state.server_host,
5428
                                                     xmpp:put_meta(Packet0, mam_ignore, true),
5429
                                                     [StateData, FromNick]),
5430
                    send_wrapped_multiple(JID,
×
5431
                                          get_users_and_subscribers_with_node(?NS_MUCSUB_NODES_MESSAGES, StateData),
5432
                                          Packet, ?NS_MUCSUB_NODES_MESSAGES, StateData),
5433
                    NSD = add_message_to_history(<<"">>,
×
5434
                                                 StateData#state.jid, Packet, StateData),
5435
                    {result, undefined, remove_from_history(StanzaId, NSD)}
×
5436
            catch _:_ ->
5437
                {error, xmpp:err_bad_request(
×
5438
                    ?T("Stanza id is not valid"), Lang)}
5439
            end
5440
    end.
5441

5442
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5443
% Voice request support
5444

5445
-spec prepare_request_form(jid(), binary(), binary()) -> message().
5446
prepare_request_form(Requester, Nick, Lang) ->
5447
    Title = translate:translate(Lang, ?T("Voice request")),
27✔
5448
    Instruction = translate:translate(
27✔
5449
                    Lang, ?T("Either approve or decline the voice request.")),
5450
    Fs = muc_request:encode([{role, participant},
27✔
5451
                             {jid, Requester},
5452
                             {roomnick, Nick},
5453
                             {request_allow, false}],
5454
                            Lang),
5455
    #message{type = normal,
27✔
5456
             sub_els = [#xdata{type = form,
5457
                               title = Title,
5458
                               instructions = [Instruction],
5459
                               fields = Fs}]}.
5460

5461
-spec send_voice_request(jid(), binary(), state()) -> ok.
5462
send_voice_request(From, Lang, StateData) ->
5463
    Moderators = search_role(moderator, StateData),
27✔
5464
    FromNick = find_nick_by_jid(From, StateData),
27✔
5465
    lists:foreach(
27✔
5466
      fun({_, User}) ->
5467
              ejabberd_router:route(
27✔
5468
                xmpp:set_from_to(
5469
                  prepare_request_form(From, FromNick, Lang),
5470
                  StateData#state.jid, User#user.jid))
5471
      end, Moderators).
5472

5473
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5474
% Invitation support
5475
-spec check_invitation(jid(), [muc_invite()], binary(), state()) ->
5476
                              ok | {error, stanza_error()}.
5477
check_invitation(From, Invitations, Lang, StateData) ->
5478
    FAffiliation = get_affiliation(From, StateData),
54✔
5479
    CanInvite = (StateData#state.config)#config.allow_user_invites orelse
54✔
5480
                FAffiliation == admin orelse FAffiliation == owner,
45✔
5481
    case CanInvite of
54✔
5482
        true ->
5483
            case lists:all(
45✔
5484
                   fun(#muc_invite{to = #jid{}}) -> true;
45✔
5485
                      (_) -> false
×
5486
                   end, Invitations) of
5487
                true ->
5488
                    ok;
45✔
5489
                false ->
5490
                    Txt = ?T("No 'to' attribute found in the invitation"),
×
5491
                    {error, xmpp:err_bad_request(Txt, Lang)}
×
5492
            end;
5493
        false ->
5494
            Txt = ?T("Invitations are not allowed in this conference"),
9✔
5495
            {error, xmpp:err_not_allowed(Txt, Lang)}
9✔
5496
    end.
5497

5498
-spec route_invitation(jid(), message(), muc_invite(), binary(), state()) -> jid().
5499
route_invitation(From, Pkt, Invitation, Lang, StateData) ->
5500
    #muc_invite{to = JID, reason = Reason} = Invitation,
45✔
5501
    Invite = Invitation#muc_invite{to = undefined, from = From},
45✔
5502
    Password = case (StateData#state.config)#config.password_protected of
45✔
5503
                   true ->
5504
                       (StateData#state.config)#config.password;
9✔
5505
                   false ->
5506
                       undefined
36✔
5507
               end,
5508
    XUser = #muc_user{password = Password, invites = [Invite]},
45✔
5509
    XConference = #x_conference{jid = jid:make(StateData#state.room,
45✔
5510
                                               StateData#state.host),
5511
                                reason = Reason},
5512
    Body = iolist_to_binary(
45✔
5513
             [io_lib:format(
5514
                translate:translate(
5515
                  Lang,
5516
                  ?T("~s invites you to the room ~s")),
5517
                [jid:encode(From),
5518
                 jid:encode({StateData#state.room, StateData#state.host, <<"">>})]),
5519
              case (StateData#state.config)#config.password_protected of
5520
                  true ->
5521
                      <<", ",
9✔
5522
                        (translate:translate(
5523
                           Lang, ?T("the password is")))/binary,
5524
                        " '",
5525
                        ((StateData#state.config)#config.password)/binary,
5526
                        "'">>;
5527
                  _ -> <<"">>
36✔
5528
              end,
5529
              case Reason of
5530
                  <<"">> -> <<"">>;
45✔
5531
                  _ -> <<" (", Reason/binary, ") ">>
×
5532
              end]),
5533
    Msg = #message{from = StateData#state.jid,
45✔
5534
                   to = JID,
5535
                   type = normal,
5536
                   body = xmpp:mk_text(Body),
5537
                   sub_els = [XUser, XConference]},
5538
    Msg2 = ejabberd_hooks:run_fold(muc_invite,
45✔
5539
                                   StateData#state.server_host,
5540
                                   Msg,
5541
                                   [StateData#state.jid, StateData#state.config,
5542
                                    From, JID, Reason, Pkt]),
5543
    ejabberd_router:route(Msg2),
45✔
5544
    JID.
45✔
5545

5546
%% Handle a message sent to the room by a non-participant.
5547
%% If it is a decline, send to the inviter.
5548
%% Otherwise, an error message is sent to the sender.
5549
-spec handle_roommessage_from_nonparticipant(message(), state(), jid()) -> ok.
5550
handle_roommessage_from_nonparticipant(Packet, StateData, From) ->
5551
    try xmpp:try_subtag(Packet, #muc_user{}) of
9✔
5552
        #muc_user{decline = #muc_decline{to = #jid{} = To} = Decline} = XUser ->
5553
            NewDecline = Decline#muc_decline{to = undefined, from = From},
9✔
5554
            NewXUser = XUser#muc_user{decline = NewDecline},
9✔
5555
            NewPacket = xmpp:set_subtag(Packet, NewXUser),
9✔
5556
            ejabberd_router:route(
9✔
5557
              xmpp:set_from_to(NewPacket, StateData#state.jid, To));
5558
        _ ->
5559
            ErrText = ?T("Only occupants are allowed to send messages "
×
5560
                         "to the conference"),
5561
            Err = xmpp:err_not_acceptable(ErrText, xmpp:get_lang(Packet)),
×
5562
            ejabberd_router:route_error(Packet, Err)
×
5563
    catch _:{xmpp_codec, Why} ->
5564
            Txt = xmpp:io_format_error(Why),
×
5565
            Err = xmpp:err_bad_request(Txt, xmpp:get_lang(Packet)),
×
5566
            ejabberd_router:route_error(Packet, Err)
×
5567
    end.
5568

5569
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5570
% Logging
5571

5572
add_to_log(Type, Data, StateData)
5573
    when Type == roomconfig_change_disabledlogging ->
5574
    ejabberd_hooks:run(muc_log_add,
×
5575
                       StateData#state.server_host,
5576
                       [StateData#state.server_host,
5577
                        roomconfig_change,
5578
                        Data,
5579
                        StateData#state.jid,
5580
                        make_opts(StateData, false)]);
5581
add_to_log(Type, Data, StateData) ->
5582
    case (StateData#state.config)#config.logging of
4,649✔
5583
      true ->
5584
        ejabberd_hooks:run(muc_log_add,
×
5585
                           StateData#state.server_host,
5586
                           [StateData#state.server_host,
5587
                            Type,
5588
                            Data,
5589
                            StateData#state.jid,
5590
                            make_opts(StateData, false)]);
5591
      false -> ok
4,649✔
5592
    end.
5593

5594
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5595
%% Users number checking
5596

5597
-spec tab_add_online_user(jid(), state()) -> any().
5598
tab_add_online_user(JID, StateData) ->
5599
    Room = StateData#state.room,
679✔
5600
    Host = StateData#state.host,
679✔
5601
    ServerHost = StateData#state.server_host,
679✔
5602
    ejabberd_hooks:run(join_room, ServerHost, [ServerHost, Room, Host, JID]),
679✔
5603
    mod_muc:register_online_user(ServerHost, jid:tolower(JID), Room, Host).
679✔
5604

5605
-spec tab_remove_online_user(jid(), state()) -> any().
5606
tab_remove_online_user(JID, StateData) ->
5607
    Room = StateData#state.room,
679✔
5608
    Host = StateData#state.host,
679✔
5609
    ServerHost = StateData#state.server_host,
679✔
5610
    ejabberd_hooks:run(leave_room, ServerHost, [ServerHost, Room, Host, JID]),
679✔
5611
    mod_muc:unregister_online_user(ServerHost, jid:tolower(JID), Room, Host).
679✔
5612

5613
-spec tab_count_user(jid(), state()) -> non_neg_integer().
5614
tab_count_user(JID, StateData) ->
5615
    ServerHost = StateData#state.server_host,
792✔
5616
    {LUser, LServer, _} = jid:tolower(JID),
792✔
5617
    mod_muc:count_online_rooms_by_user(ServerHost, LUser, LServer).
792✔
5618

5619
-spec element_size(stanza()) -> non_neg_integer().
5620
element_size(El) ->
5621
    byte_size(fxml:element_to_binary(xmpp:encode(El, ?NS_CLIENT))).
1,244✔
5622

5623
-spec store_room(state()) -> ok.
5624
store_room(StateData) ->
5625
    store_room(StateData, []).
1,221✔
5626
store_room(StateData, ChangesHints) ->
5627
    % Let store persistent rooms or on those backends that have get_subscribed_rooms
5628
    Mod = gen_mod:db_mod(StateData#state.server_host, mod_muc),
1,375✔
5629
    HasGSR = erlang:function_exported(Mod, get_subscribed_rooms, 3),
1,375✔
5630
    case HasGSR of
1,375✔
5631
        true ->
5632
            ok;
978✔
5633
        _ ->
5634
            erlang:put(muc_subscribers, StateData#state.muc_subscribers#muc_subscribers.subscribers)
397✔
5635
    end,
5636
    ShouldStore = case (StateData#state.config)#config.persistent of
1,375✔
5637
                      true ->
5638
                          true;
912✔
5639
                      _ ->
5640
                          case ChangesHints of
463✔
5641
                              [] ->
5642
                                  false;
437✔
5643
                              _ ->
5644
                                  HasGSR
26✔
5645
                          end
5646
                  end,
5647
    if ShouldStore ->
1,375✔
5648
            case erlang:function_exported(Mod, store_changes, 4) of
924✔
5649
                true when ChangesHints /= [] ->
5650
                    mod_muc:store_changes(
108✔
5651
                      StateData#state.server_host,
5652
                      StateData#state.host, StateData#state.room,
5653
                      ChangesHints);
5654
                _ ->
5655
                    store_room_no_checks(StateData, ChangesHints, false),
816✔
5656
                    ok
816✔
5657
            end;
5658
       true ->
5659
            ok
451✔
5660
    end.
5661

5662
-spec store_room_no_checks(state(), list(), boolean()) -> {atomic, any()}.
5663
store_room_no_checks(StateData, ChangesHints, Hibernation) ->
5664
    mod_muc:store_room(StateData#state.server_host,
816✔
5665
                       StateData#state.host, StateData#state.room,
5666
                       make_opts(StateData, Hibernation),
5667
                       ChangesHints).
5668

5669
-spec send_subscriptions_change_notifications(stanza(), stanza(), state()) -> ok.
5670
send_subscriptions_change_notifications(Packet, PacketWithoutJid, State) ->
5671
    {WJ, WN} =
154✔
5672
        maps:fold(
5673
          fun(_, #subscriber{jid = JID}, {WithJid, WithNick}) ->
5674
                  case (State#state.config)#config.anonymous == false orelse
×
5675
                      get_role(JID, State) == moderator orelse
×
5676
                      get_default_role(get_affiliation(JID, State), State) == moderator of
×
5677
                      true ->
5678
                          {[JID | WithJid], WithNick};
×
5679
                      _ ->
5680
                          {WithJid, [JID | WithNick]}
×
5681
                  end
5682
          end, {[], []},
5683
          muc_subscribers_get_by_node(?NS_MUCSUB_NODES_SUBSCRIBERS,
5684
                                      State#state.muc_subscribers)),
5685
    if WJ /= [] ->
154✔
5686
        ejabberd_router_multicast:route_multicast(State#state.jid, State#state.server_host,
×
5687
                                                  WJ, Packet, false);
5688
        true -> ok
154✔
5689
    end,
5690
    if WN /= [] ->
154✔
5691
        ejabberd_router_multicast:route_multicast(State#state.jid, State#state.server_host,
×
5692
                                                  WN, PacketWithoutJid, false);
5693
        true -> ok
154✔
5694
    end.
5695

5696
-spec send_wrapped(jid(), jid(), stanza(), binary(), state()) -> ok.
5697
send_wrapped(From, To, Packet, Node, State) ->
5698
    LTo = jid:tolower(To),
2,492✔
5699
    LBareTo = jid:tolower(jid:remove_resource(To)),
2,492✔
5700
    IsOffline = case maps:get(LTo, State#state.users, error) of
2,492✔
5701
                    #user{last_presence = undefined} -> true;
×
5702
                    error -> true;
×
5703
                    _ -> false
2,492✔
5704
                end,
5705
    if IsOffline ->
2,492✔
5706
            try muc_subscribers_get(LBareTo, State#state.muc_subscribers) of
×
5707
                #subscriber{nodes = Nodes, jid = JID} ->
5708
                    case lists:member(Node, Nodes) of
×
5709
                        true ->
5710
                            MamEnabled = (State#state.config)#config.mam,
×
5711
                            Id = case xmpp:get_subtag(Packet, #stanza_id{by = #jid{}}) of
×
5712
                                     #stanza_id{id = Id2} ->
5713
                                         Id2;
×
5714
                                     _ ->
5715
                                         p1_rand:get_string()
×
5716
                                 end,
5717
                            NewPacket = wrap(From, JID, Packet, Node, Id),
×
5718
                            NewPacket2 = xmpp:put_meta(NewPacket, in_muc_mam, MamEnabled),
×
5719
                            ejabberd_router:route(
×
5720
                              xmpp:set_from_to(NewPacket2, State#state.jid, JID));
5721
                        false ->
5722
                            ok
×
5723
                    end
5724
            catch _:{badkey, _} ->
5725
                    ok
×
5726
            end;
5727
       true ->
5728
            case Packet of
2,492✔
5729
                #presence{type = unavailable} ->
5730
                    case xmpp:get_subtag(Packet, #muc_user{}) of
967✔
5731
                        #muc_user{destroy = Destroy,
5732
                                  status_codes = Codes} ->
5733
                            case Destroy /= undefined orelse
967✔
5734
                                 (lists:member(110,Codes) andalso
949✔
5735
                                  not lists:member(303, Codes)) of
670✔
5736
                                true ->
5737
                                    ejabberd_router:route(
679✔
5738
                                      #presence{from = State#state.jid, to = To,
5739
                                                id = p1_rand:get_string(),
5740
                                                type = unavailable});
5741
                                false ->
5742
                                    ok
288✔
5743
                            end;
5744
                        _ ->
5745
                            false
×
5746
                    end;
5747
                _ ->
5748
                    ok
1,525✔
5749
            end,
5750
            ejabberd_router:route(xmpp:set_from_to(Packet, From, To))
2,492✔
5751
    end.
5752

5753
-spec wrap(jid(), undefined | jid(), stanza(), binary(), binary()) -> message().
5754
wrap(From, To, Packet, Node, Id) ->
5755
    El = xmpp:set_from_to(Packet, From, To),
144✔
5756
    #message{
144✔
5757
        id = Id,
5758
        sub_els = [#ps_event{
5759
            items = #ps_items{
5760
                node = Node,
5761
                items = [#ps_item{
5762
                    id = Id,
5763
                    sub_els = [El]}]}}]}.
5764

5765
-spec send_wrapped_multiple(jid(), users(), stanza(), binary(), state()) -> ok.
5766
send_wrapped_multiple(From, Users, Packet, Node, State) ->
5767
    {Dir, DirSub, Wra} =
1,673✔
5768
        maps:fold(
5769
          fun(_, #user{jid = To, last_presence = LP}, {Direct, DirectSub, Wrapped} = Res) ->
5770
                  IsOffline = LP == undefined,
2,060✔
5771
                  LBareTo = jid:tolower(jid:remove_resource(To)),
2,060✔
5772
                  IsSub = case muc_subscribers_find(LBareTo, State#state.muc_subscribers) of
2,060✔
5773
                              {ok, #subscriber{nodes = Nodes}} ->
5774
                                  lists:member(Node, Nodes);
144✔
5775
                              _ -> false
1,916✔
5776
                          end,
5777
                  if
2,060✔
5778
                      IsOffline ->
5779
                          if
144✔
5780
                              IsSub ->
5781
                                  {Direct, DirectSub, [To | Wrapped]};
144✔
5782
                              true ->
5783
                                  Res
×
5784
                          end;
5785
                      IsSub ->
5786
                          {Direct, [To | DirectSub], Wrapped};
×
5787
                      true ->
5788
                          {[To | Direct], DirectSub, Wrapped}
1,916✔
5789
                  end
5790
          end,
5791
          {[], [], []},
5792
          Users),
5793
    DirAll = Dir ++ DirSub,
1,673✔
5794
    case DirAll of
1,673✔
5795
        [] -> ok;
×
5796
        _ ->
5797
            case Packet of
1,673✔
5798
                #presence{type = unavailable} ->
5799
                    case xmpp:get_subtag(Packet, #muc_user{}) of
×
5800
                        #muc_user{destroy = Destroy,
5801
                                  status_codes = Codes} ->
5802
                            case Destroy /= undefined orelse
×
5803
                                 (lists:member(110,Codes) andalso
×
5804
                                  not lists:member(303, Codes)) of
×
5805
                                true ->
5806
                                    ejabberd_router_multicast:route_multicast(
×
5807
                                      From,
5808
                                      State#state.server_host,
5809
                                      DirAll,
5810
                                        #presence{id = p1_rand:get_string(),
5811
                                                  type = unavailable}, false);
5812
                                false ->
5813
                                    ok
×
5814
                            end;
5815
                        _ ->
5816
                            false
×
5817
                    end;
5818
                _ ->
5819
                    ok
1,673✔
5820
            end,
5821
            if
1,673✔
5822
                Dir /= [] ->
5823
                    ejabberd_router_multicast:route_multicast(From,
1,673✔
5824
                                                              State#state.server_host,
5825
                                                              Dir,
5826
                                                              Packet,
5827
                                                              false);
5828
                true ->
5829
                    ok
×
5830
            end,
5831
            if
1,673✔
5832
                DirSub /= [] ->
5833
                    PacketSub = xmpp:put_meta(Packet, is_muc_subscriber, true),
×
5834
                    ejabberd_router_multicast:route_multicast(From,
×
5835
                                                              State#state.server_host,
5836
                                                              DirSub,
5837
                                                              PacketSub,
5838
                                                              false);
5839
                true ->
5840
                    ok
1,673✔
5841
            end
5842
    end,
5843
    case Wra of
1,673✔
5844
        [] -> ok;
1,529✔
5845
        _ ->
5846
            MamEnabled = (State#state.config)#config.mam,
144✔
5847
            Id = case xmpp:get_subtag(Packet, #stanza_id{by = #jid{}}) of
144✔
5848
                     #stanza_id{id = Id2} ->
5849
                         Id2;
144✔
5850
                     _ ->
5851
                         p1_rand:get_string()
×
5852
                 end,
5853
            NewPacket = wrap(From, undefined, Packet, Node, Id),
144✔
5854
            NewPacket2 = xmpp:put_meta(NewPacket, in_muc_mam, MamEnabled),
144✔
5855
            ejabberd_router_multicast:route_multicast(State#state.jid, State#state.server_host,
144✔
5856
                                                      Wra, NewPacket2, true)
5857
    end.
5858

5859
%%%----------------------------------------------------------------------
5860
%%% #muc_subscribers API
5861
%%%----------------------------------------------------------------------
5862

5863
-spec muc_subscribers_new() -> #muc_subscribers{}.
5864
muc_subscribers_new() ->
5865
    #muc_subscribers{}.
874✔
5866

5867
-spec muc_subscribers_get(ljid(), #muc_subscribers{}) -> #subscriber{}.
5868
muc_subscribers_get({_, _, _} = LJID, MUCSubscribers) ->
5869
    maps:get(LJID, MUCSubscribers#muc_subscribers.subscribers).
98✔
5870

5871
-spec muc_subscribers_find(ljid(), #muc_subscribers{}) ->
5872
                                  {ok, #subscriber{}} | error.
5873
muc_subscribers_find({_, _, _} = LJID, MUCSubscribers) ->
5874
    maps:find(LJID, MUCSubscribers#muc_subscribers.subscribers).
2,060✔
5875

5876
-spec muc_subscribers_is_key(ljid(), #muc_subscribers{}) -> boolean().
5877
muc_subscribers_is_key({_, _, _} = LJID, MUCSubscribers) ->
5878
    maps:is_key(LJID, MUCSubscribers#muc_subscribers.subscribers).
9,532✔
5879

5880
-spec muc_subscribers_size(#muc_subscribers{}) -> integer().
5881
muc_subscribers_size(MUCSubscribers) ->
5882
    maps:size(MUCSubscribers#muc_subscribers.subscribers).
409✔
5883

5884
-spec muc_subscribers_fold(Fun, Acc, #muc_subscribers{}) -> Acc when
5885
    Fun :: fun((ljid(), #subscriber{}, Acc) -> Acc).
5886
muc_subscribers_fold(Fun, Init, MUCSubscribers) ->
5887
    maps:fold(Fun, Init, MUCSubscribers#muc_subscribers.subscribers).
816✔
5888

5889
-spec muc_subscribers_get_by_nick(binary(), #muc_subscribers{}) -> [#subscriber{}].
5890
muc_subscribers_get_by_nick(Nick, MUCSubscribers) ->
5891
    maps:get(Nick, MUCSubscribers#muc_subscribers.subscriber_nicks, []).
765✔
5892

5893
-spec muc_subscribers_get_by_node(binary(), #muc_subscribers{}) -> subscribers().
5894
muc_subscribers_get_by_node(Node, MUCSubscribers) ->
5895
    maps:get(Node, MUCSubscribers#muc_subscribers.subscriber_nodes, #{}).
5,213✔
5896

5897
-spec muc_subscribers_remove_exn(ljid(), #muc_subscribers{}) ->
5898
                                        {#muc_subscribers{}, #subscriber{}}.
5899
muc_subscribers_remove_exn({_, _, _} = LJID, MUCSubscribers) ->
5900
    #muc_subscribers{subscribers = Subs,
95✔
5901
                     subscriber_nicks = SubNicks,
5902
                     subscriber_nodes = SubNodes} = MUCSubscribers,
5903
    Subscriber = maps:get(LJID, Subs),
95✔
5904
    #subscriber{nick = Nick, nodes = Nodes} = Subscriber,
77✔
5905
    NewSubNicks = maps:remove(Nick, SubNicks),
77✔
5906
    NewSubs = maps:remove(LJID, Subs),
77✔
5907
    NewSubNodes =
77✔
5908
        lists:foldl(
5909
          fun(Node, Acc) ->
5910
                  NodeSubs = maps:get(Node, Acc, #{}),
32✔
5911
                  NodeSubs2 = maps:remove(LJID, NodeSubs),
32✔
5912
                  maps:put(Node, NodeSubs2, Acc)
32✔
5913
          end, SubNodes, Nodes),
5914
    {#muc_subscribers{subscribers = NewSubs,
77✔
5915
                      subscriber_nicks = NewSubNicks,
5916
                      subscriber_nodes = NewSubNodes}, Subscriber}.
5917

5918
-spec muc_subscribers_put(#subscriber{}, #muc_subscribers{}) ->
5919
                                 #muc_subscribers{}.
5920
muc_subscribers_put(Subscriber, MUCSubscribers) ->
5921
    #subscriber{jid = JID,
77✔
5922
                nick = Nick,
5923
                nodes = Nodes} = Subscriber,
5924
    #muc_subscribers{subscribers = Subs,
77✔
5925
                     subscriber_nicks = SubNicks,
5926
                     subscriber_nodes = SubNodes} = MUCSubscribers,
5927
    LJID = jid:tolower(JID),
77✔
5928
    NewSubs = maps:put(LJID, Subscriber, Subs),
77✔
5929
    NewSubNicks = maps:put(Nick, [LJID], SubNicks),
77✔
5930
    NewSubNodes =
77✔
5931
        lists:foldl(
5932
          fun(Node, Acc) ->
5933
                  NodeSubs = maps:get(Node, Acc, #{}),
32✔
5934
                  NodeSubs2 = maps:put(LJID, Subscriber, NodeSubs),
32✔
5935
                  maps:put(Node, NodeSubs2, Acc)
32✔
5936
          end, SubNodes, Nodes),
5937
    #muc_subscribers{subscribers = NewSubs,
77✔
5938
                     subscriber_nicks = NewSubNicks,
5939
                     subscriber_nodes = NewSubNodes}.
5940

5941

5942
cleanup_affiliations(State) ->
5943
    case mod_muc_opt:cleanup_affiliations_on_start(State#state.server_host) of
×
5944
        true ->
5945
            Affiliations =
×
5946
                maps:filter(
5947
                  fun({LUser, LServer, _}, _) ->
5948
                          case ejabberd_router:is_my_host(LServer) of
×
5949
                              true ->
5950
                                  ejabberd_auth:user_exists(LUser, LServer);
×
5951
                              false ->
5952
                                  true
×
5953
                          end
5954
                  end, State#state.affiliations),
5955
            State#state{affiliations = Affiliations};
×
5956
        false ->
5957
            State
×
5958
    end.
5959

5960
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5961
%% Detect messange stanzas that don't have meaningful content
5962
-spec has_body_or_subject(message()) -> boolean().
5963
has_body_or_subject(#message{body = Body, subject = Subj}) ->
5964
    Body /= [] orelse Subj /= [].
631✔
5965

5966
-spec reset_hibernate_timer(state()) -> state().
5967
reset_hibernate_timer(State) ->
5968
    case State#state.hibernate_timer of
3,814✔
5969
        hibernating ->
5970
            ok;
×
5971
        _ ->
5972
            disable_hibernate_timer(State),
3,814✔
5973
            NewTimer = case {mod_muc_opt:hibernation_timeout(State#state.server_host),
3,814✔
5974
                             maps:size(State#state.users)} of
5975
                           {infinity, _} ->
5976
                               none;
3,814✔
5977
                           {Timeout, 0} ->
5978
                               p1_fsm:send_event_after(Timeout, hibernate);
×
5979
                           _ ->
5980
                               none
×
5981
                       end,
5982
            State#state{hibernate_timer = NewTimer}
3,814✔
5983
    end.
5984

5985

5986
-spec disable_hibernate_timer(state()) -> ok.
5987
disable_hibernate_timer(State) ->
5988
    case State#state.hibernate_timer of
4,232✔
5989
        Ref when is_reference(Ref) ->
5990
            p1_fsm:cancel_timer(Ref),
×
5991
            ok;
×
5992
        _ ->
5993
            ok
4,232✔
5994
    end.
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc