./ct_report/coverage/mod_muc_room.COVER.html

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-2011 ProcessOne
9 %%%
10 %%% This program is free software; you can redistribute it and/or
11 %%% modify it under the terms of the GNU General Public License as
12 %%% published by the Free Software Foundation; either version 2 of the
13 %%% License, or (at your option) any later version.
14 %%%
15 %%% This program is distributed in the hope that it will be useful,
16 %%% but WITHOUT ANY WARRANTY; without even the implied warranty of
17 %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 %%% General Public License for more details.
19 %%%
20 %%% You should have received a copy of the GNU General Public License
21 %%% along with this program; if not, write to the Free Software
22 %%% Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
23 %%%
24 %%%----------------------------------------------------------------------
25
26 -module(mod_muc_room).
27 -author('alexey@process-one.net').
28 -behaviour(gen_fsm_compat).
29
30 %% External exports
31 -export([start_link/1,
32 start_new/11,
33 start_restored/9,
34 route/5,
35 stop/1]).
36
37 %% API exports
38 -export([get_room_users/1,
39 get_room_affiliations/1,
40 set_admin_items/3,
41 get_room_config/1,
42 change_room_config/2,
43 delete_room/2,
44 is_room_owner/2,
45 can_access_room/2,
46 can_access_identity/2]).
47
48 %% gen_fsm callbacks
49 -export([init/1,
50 normal_state/2,
51 normal_state/3,
52 locked_state/2,
53 initial_state/2,
54 handle_event/3,
55 handle_sync_event/4,
56 handle_info/3,
57 terminate/3,
58 code_change/4]).
59
60 -ignore_xref([initial_state/2, locked_state/2, normal_state/2, normal_state/3, start_link/1]).
61
62 -import(mongoose_lib, [maps_append/3,
63 maps_foreach/2,
64 pairs_foreach/2,
65 maps_or_pairs_foreach/2]).
66
67 -include("mongoose.hrl").
68 -include("jlib.hrl").
69 -include("mod_muc_room.hrl").
70
71 -record(routed_message, {allowed,
72 type,
73 from,
74 packet,
75 lang
76 }).
77 -type routed_message() :: #routed_message{}.
78
79 -record(routed_nick_message, {allow_pm,
80 online,
81 type,
82 from,
83 nick,
84 lang,
85 packet,
86 decide,
87 jid
88 }).
89 -type routed_nick_message() :: #routed_nick_message{}.
90
91 -record(routed_iq, {iq,
92 from,
93 packet
94 }).
95 -type routed_iq() :: #routed_iq{}.
96
97 -record(routed_nick_iq, {allow_query,
98 online,
99 iq,
100 packet,
101 lang,
102 nick,
103 jid,
104 from,
105 stanza
106 }).
107 -type routed_nick_iq() :: #routed_nick_iq{}.
108
109 %%%----------------------------------------------------------------------
110 %%% Types
111 %%%----------------------------------------------------------------------
112 -export_type([config/0, user/0, activity/0]).
113
114 -type statename() :: 'locked_state' | 'normal_state'.
115 -type fsm_return() :: {'next_state', statename(), state()}
116 | {'next_state', statename(), state(), timeout() | hibernate}
117 | {'stop', any(), state()}.
118
119 -type lqueue() :: #lqueue{}.
120 -type state() :: #state{}.
121 -type config() :: #config{}.
122 -type user() :: #user{}.
123 -type activity() :: #activity{}.
124 -type stanzaid() :: {binary(), jid:resource()}.
125 -type new_user_strategy() :: 'allowed'
126 | 'conflict_registered'
127 | 'conflict_use'
128 | 'invalid_password'
129 | 'limit_reached'
130 | 'require_membership'
131 | 'require_password'
132 | 'user_banned'
133 | 'http_auth'.
134 -type users_map() :: #{jid:simple_jid() => user()}.
135 -type users_pairs() :: [{jid:simple_jid(), user()}].
136 -type sessions_map() :: #{mod_muc:nick() => jid:jid()}.
137 -type affiliations_map() :: #{jid:simple_jid() => mod_muc:affiliation()}.
138
139
140 -type update_inbox_for_muc_payload() :: #{
141 host_type := mongooseim:host_type(),
142 room_jid := jid:jid(),
143 from_jid := jid:jid(),
144 from_room_jid := jid:jid(),
145 packet := exml:element(),
146 affiliations_map := affiliations_map()
147 }.
148 -export_type([update_inbox_for_muc_payload/0]).
149
150 -define(MAX_USERS_DEFAULT_LIST,
151 [5, 10, 20, 30, 50, 100, 200, 500, 1000, 2000, 5000]).
152
153 %%%----------------------------------------------------------------------
154 %%% API
155 %%%----------------------------------------------------------------------
156
157 -spec start_new(HostType :: mongooseim:host_type(), Host :: jid:lserver(), ServerHost :: jid:lserver(),
158 Access :: _, Room :: mod_muc:room(), HistorySize :: integer(),
159 RoomShaper :: mongoose_shaper:shaper(), HttpAuthPool :: none | mongoose_http_client:pool(),
160 Creator :: jid:jid(), Nick :: mod_muc:nick(),
161 DefRoomOpts :: list()) -> {ok, pid()}.
162 start_new(HostType, Host, ServerHost, Access, Room,
163 HistorySize, RoomShaper, HttpAuthPool, Creator, Nick, DefRoomOpts) ->
164 93 Supervisor = gen_mod:get_module_proc(HostType, ejabberd_mod_muc_sup),
165 93 Args = #{init_type => start_new, host_type => HostType,
166 muc_host => Host, server_host => ServerHost, access => Access,
167 room_name => Room, history_size => HistorySize,
168 room_shaper => RoomShaper, http_auth_pool => HttpAuthPool,
169 creator => Creator, nick => Nick, def_opts => DefRoomOpts},
170 93 supervisor:start_child(Supervisor, [Args]).
171
172 -spec start_restored(HostType :: mongooseim:host_type(), Host :: jid:lserver(), ServerHost :: jid:lserver(),
173 Access :: _, Room :: mod_muc:room(), HistorySize :: integer(),
174 RoomShaper :: mongoose_shaper:shaper(), HttpAuthPool :: none | mongoose_http_client:pool(),
175 Opts :: list()) -> {ok, pid()}.
176 start_restored(HostType, Host, ServerHost, Access, Room,
177 HistorySize, RoomShaper, HttpAuthPool, Opts)
178 when is_list(Opts) ->
179 2 Supervisor = gen_mod:get_module_proc(HostType, ejabberd_mod_muc_sup),
180 2 Args = #{init_type => start_restored, host_type => HostType,
181 muc_host => Host, server_host => ServerHost,
182 access => Access, room_name => Room, history_size => HistorySize,
183 room_shaper => RoomShaper, http_auth_pool => HttpAuthPool,
184 opts => Opts},
185 2 supervisor:start_child(Supervisor, [Args]).
186
187 start_link(Args = #{}) ->
188 95 gen_fsm_compat:start_link(?MODULE, Args, []).
189
190 stop(Pid) ->
191 16 gen_fsm_compat:stop(Pid).
192
193 -spec get_room_users(RoomJID :: jid:jid()) ->
194 {ok, [user()]} | {error, not_found}.
195 get_room_users(RoomJID) ->
196 36 case mod_muc:room_jid_to_pid(RoomJID) of
197 {ok, Pid} ->
198
:-(
gen_fsm_compat:sync_send_all_state_event(Pid, get_room_users);
199 {error, Reason} ->
200 36 {error, Reason}
201 end.
202
203 -spec get_room_affiliations(RoomJID :: jid:jid()) ->
204 {ok, affiliations_map()} | {error, not_found}.
205 get_room_affiliations(RoomJID) ->
206
:-(
case mod_muc:room_jid_to_pid(RoomJID) of
207 {ok, Pid} ->
208
:-(
gen_fsm_compat:sync_send_all_state_event(Pid, get_room_affiliations);
209 {error, Reason} ->
210
:-(
{error, Reason}
211 end.
212
213 -spec is_room_owner(RoomJID :: jid:jid(), UserJID :: jid:jid()) ->
214 {ok, boolean()} | {error, not_found}.
215 is_room_owner(RoomJID, UserJID) ->
216
:-(
case mod_muc:room_jid_to_pid(RoomJID) of
217 {ok, Pid} ->
218
:-(
gen_fsm_compat:sync_send_all_state_event(Pid, {is_room_owner, UserJID});
219 {error, Reason} ->
220
:-(
{error, Reason}
221 end.
222
223 -type error_xml() :: #xmlel{}.
224 -type item_xml() :: #xmlel{}.
225
226 -spec set_admin_items(jid:jid(), jid:jid(), [item_xml()]) ->
227 ok | {error, not_found | error_xml()}.
228 set_admin_items(RoomJID, ModJID, Items) ->
229
:-(
case mod_muc:room_jid_to_pid(RoomJID) of
230 {ok, Pid} ->
231
:-(
gen_fsm_compat:sync_send_event(Pid, {set_admin_items, ModJID, Items});
232 {error, Reason} ->
233
:-(
{error, Reason}
234 end.
235
236 -spec get_room_config(jid:jid()) ->
237 {ok, config()} | {error, not_found}.
238 get_room_config(RoomJID) ->
239
:-(
case mod_muc:room_jid_to_pid(RoomJID) of
240 {ok, Pid} ->
241
:-(
gen_fsm_compat:sync_send_all_state_event(Pid, get_config);
242 {error, Reason} ->
243
:-(
{error, Reason}
244 end.
245
246 -spec change_room_config(jid:jid(), config()) ->
247 {ok, config()} | {error, not_found}.
248 change_room_config(RoomJID, NewConfig) ->
249
:-(
case mod_muc:room_jid_to_pid(RoomJID) of
250 {ok, Pid} ->
251
:-(
gen_fsm_compat:sync_send_all_state_event(Pid, {change_config, NewConfig});
252 {error, Reason} ->
253
:-(
{error, Reason}
254 end.
255
256 -spec delete_room(jid:jid(), binary()) ->
257 ok | {error, not_found}.
258 delete_room(RoomJID, ReasonIn) ->
259
:-(
case mod_muc:room_jid_to_pid(RoomJID) of
260 {ok, Pid} ->
261
:-(
gen_fsm_compat:send_all_state_event(Pid, {destroy, ReasonIn});
262 {error, Reason} ->
263
:-(
{error, Reason}
264 end.
265
266 %% @doc Return true if UserJID can read room messages
267 -spec can_access_room(RoomJID :: jid:jid(), UserJID :: jid:jid()) ->
268 {ok, boolean()} | {error, not_found}.
269 can_access_room(RoomJID, UserJID) ->
270 126 case mod_muc:room_jid_to_pid(RoomJID) of
271 {ok, Pid} ->
272 117 gen_fsm_compat:sync_send_all_state_event(Pid, {can_access_room, UserJID});
273 Error ->
274 9 Error
275 end.
276
277 %% @doc Return true if UserJID can read real user JIDs
278 -spec can_access_identity(RoomJID :: jid:jid(), UserJID :: jid:jid()) ->
279 {ok, boolean()} | {error, not_found}.
280 can_access_identity(RoomJID, UserJID) ->
281 82 case mod_muc:room_jid_to_pid(RoomJID) of
282 {ok, Pid} ->
283 73 gen_fsm_compat:sync_send_all_state_event(Pid, {can_access_identity, UserJID});
284 {error, Reason} ->
285 9 {error, Reason}
286 end.
287
288 %%%----------------------------------------------------------------------
289 %%% Callback functions from gen_fsm
290 %%%----------------------------------------------------------------------
291
292 %% @doc A room is created. Depending on request type (MUC/groupchat 1.0) the
293 %% next state is determined accordingly (a locked room for MUC or an instant
294 %% one for groupchat).
295 -spec init(map()) ->
296 {ok, statename(), state()} | {ok, statename(), state(), timeout()}.
297 init(#{init_type := start_new} = Args) ->
298 93 init_new(Args);
299 init(#{init_type := start_restored} = Args) ->
300 2 init_restored(Args).
301
302 init_new(#{init_type := start_new, host_type := HostType, muc_host := Host,
303 server_host := ServerHost, access := Access, room_name := Room,
304 history_size := HistorySize, room_shaper := RoomShaper,
305 http_auth_pool := HttpAuthPool, creator := Creator, nick := _Nick,
306 def_opts := DefRoomOpts}) when is_list(DefRoomOpts) ->
307 93 process_flag(trap_exit, true),
308 93 Shaper = mongoose_shaper:new(RoomShaper),
309 93 State = #state{host = Host, host_type = HostType, server_host = ServerHost,
310 access = Access,
311 room = Room,
312 history = lqueue_new(HistorySize),
313 jid = jid:make_bare(Room, Host),
314 just_created = true,
315 room_shaper = Shaper,
316 http_auth_pool = HttpAuthPool,
317 hibernate_timeout = read_hibernate_timeout(HostType)},
318 93 State1 = set_opts(DefRoomOpts, State),
319 93 State2 = set_affiliation(Creator, owner, State1),
320 93 ?LOG_INFO(ls(#{what => muc_room_started,
321 93 creator_jid => jid:to_binary(Creator)}, State)),
322 93 add_to_log(room_existence, created, State2),
323 93 State3 = case proplists:get_value(subject, DefRoomOpts, none) of
324 none ->
325 93 State2;
326 _ ->
327
:-(
set_opts([{subject_timestamp, get_current_timestamp()}], State2)
328 end,
329 93 case proplists:get_value(instant, DefRoomOpts, false) of
330 true ->
331 %% Instant room -- groupchat 1.0 request
332 93 add_to_log(room_existence, started, State3),
333 93 save_persistent_room_state(State3),
334 93 {ok, normal_state, State3, State3#state.hibernate_timeout};
335 false ->
336 %% Locked room waiting for configuration -- MUC request
337
:-(
{ok, initial_state, State3}
338 end.
339
340 %% @doc A room is restored
341 init_restored(#{init_type := start_restored,
342 host_type := HostType, muc_host := Host,
343 server_host := ServerHost, access := Access,
344 room_name := Room, history_size := HistorySize,
345 room_shaper := RoomShaper, http_auth_pool := HttpAuthPool,
346 opts := Opts}) ->
347 2 process_flag(trap_exit, true),
348 2 Shaper = mongoose_shaper:new(RoomShaper),
349 2 State = set_opts(Opts, #state{host = Host, host_type = HostType,
350 server_host = ServerHost,
351 access = Access,
352 room = Room,
353 history = lqueue_new(HistorySize),
354 jid = jid:make_bare(Room, Host),
355 room_shaper = Shaper,
356 http_auth_pool = HttpAuthPool,
357 hibernate_timeout = read_hibernate_timeout(HostType)
358 }),
359 2 add_to_log(room_existence, started, State),
360 2 mongoose_metrics:update(global, [mod_muc, process_recreations], 1),
361 2 {ok, normal_state, State, State#state.hibernate_timeout}.
362
363 %% @doc In the locked state StateData contains the same settings it previously
364 %% held for the normal_state. The fsm awaits either a confirmation or a
365 %% configuration form from the creator. Responds with error to any other queries.
366 -spec locked_error({'route', jid:jid(), _, mongoose_acc:t(), exml:element()},
367 statename(), state()) -> fsm_return().
368 locked_error({route, From, ToNick, Acc, #xmlel{attrs = Attrs} = Packet},
369 NextState, StateData) ->
370
:-(
?LOG_INFO(ls(#{what => muc_route_to_locked_room, acc => Acc}, StateData)),
371
:-(
ErrText = <<"This room is locked">>,
372
:-(
Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
373
:-(
{Acc1, Err} = jlib:make_error_reply(Acc, Packet, mongoose_xmpp_errors:item_not_found(Lang, ErrText)),
374
:-(
ejabberd_router:route(jid:replace_resource(StateData#state.jid,
375 ToNick),
376 From, Acc1, Err),
377
:-(
{next_state, NextState, StateData}.
378
379 %% @doc Receive the room-creating Stanza. Will crash if any other stanza is
380 %% received in this state.
381 -spec initial_state({'route', From :: jid:jid(), To :: mod_muc:nick(),
382 Acc :: mongoose_acc:t(), Presence :: exml:element()}, state()) -> fsm_return().
383 initial_state({route, From, ToNick, _Acc, % TOODOO
384 #xmlel{name = <<"presence">>} = Presence}, StateData) ->
385 %% this should never happen so crash if it does
386
:-(
<<>> = exml_query:attr(Presence, <<"type">>, <<>>),
387
:-(
owner = get_affiliation(From, StateData), %% prevent race condition (2 users create same room)
388
:-(
XNamespaces = exml_query:paths(Presence, [{element, <<"x">>}, {attr, <<"xmlns">>}]),
389
:-(
case lists:member(?NS_MUC, XNamespaces) of
390 true ->
391 %% FIXME
392
:-(
add_to_log(room_existence, started, StateData),
393
:-(
process_presence(From, ToNick, Presence, StateData, locked_state);
394 %% The fragment of normal_state with Activity that used to do this - how does that work?
395 %% Seems to work without it
396 false ->
397 %% groupchat 1.0 user, straight to normal_state
398
:-(
process_presence(From, ToNick, Presence, StateData)
399 end.
400
401 -spec is_query_allowed(exml:element()) -> boolean().
402 is_query_allowed(#xmlel{children = Els}) ->
403
:-(
case xml:remove_cdata(Els) of
404 [#xmlel{name = <<"destroy">>}] ->
405
:-(
true;
406 [El] ->
407
:-(
mongoose_data_forms:is_form(El, [<<"submit">>, <<"cancel">>]);
408 _ ->
409
:-(
false
410 end.
411
412 -spec locked_state_process_owner_iq(jid:jid(), exml:element(),
413 ejabberd:lang(), 'error' | 'get' | 'invalid' | 'result', _)
414 -> {{'error', exml:element()}, statename()}
415 | {{result, [exml:element() | jlib:xmlcdata()], state() | stop}, statename()}.
416 locked_state_process_owner_iq(From, Query, Lang, set, StateData) ->
417
:-(
Result = case is_query_allowed(Query) of
418 true ->
419
:-(
process_iq_owner(From, set, Lang, Query, StateData, locked_state);
420 false ->
421
:-(
{error, mongoose_xmpp_errors:item_not_found(Lang, <<"Query not allowed">>)}
422 end,
423
:-(
{Result, normal_state};
424 locked_state_process_owner_iq(From, Query, Lang, get, StateData) ->
425
:-(
{process_iq_owner(From, get, Lang, Query, StateData, locked_state), locked_state};
426 locked_state_process_owner_iq(_From, _Query, Lang, _Type, _StateData) ->
427
:-(
{{error, mongoose_xmpp_errors:item_not_found(Lang, <<"Wrong type">>)}, locked_state}.
428
429
430 %% @doc Destroy room / confirm instant room / configure room
431 -spec locked_state({'route', From :: jid:jid(), To :: mod_muc:nick(),
432 Acc :: mongoose_acc:t(), Packet :: exml:element()}, state()) -> fsm_return().
433 locked_state({route, From, _ToNick, Acc,
434 #xmlel{name = <<"iq">>} = Packet}, StateData) ->
435
:-(
#iq{lang = Lang, sub_el = Query, xmlns = NS} = IQ = jlib:iq_query_info(Packet),
436
:-(
{Result, NextState1} =
437 case {NS, get_affiliation(From, StateData)} of
438 {?NS_MUC_OWNER, owner} ->
439
:-(
locked_state_process_owner_iq(From, Query, Lang, IQ#iq.type, StateData);
440 {?NS_DISCO_INFO, owner} ->
441
:-(
{process_iq_disco_info(From, IQ#iq.type, Lang, StateData), locked_state};
442 _ ->
443
:-(
ErrText = <<"This room is locked">>,
444
:-(
{{error, mongoose_xmpp_errors:item_not_found(Lang, ErrText)}, locked_state}
445 end,
446
:-(
MkQueryResult = fun(Res) ->
447
:-(
IQ#iq{type = result,
448 sub_el = [#xmlel{name = <<"query">>,
449 attrs = [{<<"xmlns">>, NS}],
450 children = Res}]}
451 end,
452
:-(
{IQRes, StateData3, NextState2} =
453 case Result of
454
:-(
{result, InnerRes, stop} -> {MkQueryResult(InnerRes), StateData, stop};
455
:-(
{result, InnerRes, StateData2} -> {MkQueryResult(InnerRes), StateData2, NextState1};
456
:-(
{error, Error} -> {IQ#iq{type = error, sub_el = [Query, Error]}, StateData, NextState1}
457 end,
458
:-(
ejabberd_router:route(StateData3#state.jid, From, Acc, jlib:iq_to_xml(IQRes)),
459
:-(
case NextState2 of
460 stop ->
461
:-(
{stop, normal, StateData3};
462 locked_state ->
463
:-(
{next_state, NextState2, StateData3};
464 normal_state ->
465
:-(
next_normal_state(StateData3#state{just_created = false})
466 end;
467 %% Let owner leave. Destroy the room.
468 locked_state({route, From, ToNick, _Acc,
469 #xmlel{name = <<"presence">>, attrs = Attrs} = Presence} = Call,
470 StateData) ->
471
:-(
case xml:get_attr_s(<<"type">>, Attrs) =:= <<"unavailable">>
472
:-(
andalso get_affiliation(From, StateData) =:= owner of
473 true ->
474 %% Will let the owner leave and destroy the room if it's not persistant
475 %% The rooms are not persistent by default, but just to be safe...
476
:-(
NewConfig = (StateData#state.config)#config{persistent = false},
477
:-(
StateData1 = StateData#state{config = NewConfig},
478
:-(
process_presence(From, ToNick, Presence, StateData1, locked_state);
479 _ ->
480
:-(
locked_error(Call, locked_state, StateData)
481 end;
482 locked_state(timeout, StateData) ->
483
:-(
{next_state, locked_state, StateData};
484 locked_state(Call, StateData) ->
485
:-(
locked_error(Call, locked_state, StateData).
486
487
488 -spec normal_state({route, From :: jid:jid(), To :: mod_muc:nick(), Acc :: mongoose_acc:t(),
489 Packet :: exml:element()}, state()) -> fsm_return().
490 normal_state({route, From, <<>>, _Acc,
491 #xmlel{name = <<"message">>, attrs = Attrs} = Packet},
492 StateData) ->
493 95 Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
494 95 Type = xml:get_attr_s(<<"type">>, Attrs),
495
496 95 NewStateData = route_message(#routed_message{
497 allowed = can_send_to_conference(From, StateData),
498 type = Type,
499 from = From,
500 packet = Packet,
501 lang = Lang}, StateData),
502 95 next_normal_state(NewStateData);
503 normal_state({route, From, <<>>, Acc0,
504 #xmlel{name = <<"iq">>} = Packet},
505 StateData) ->
506 113 {IQ, Acc} = mongoose_iq:info(Acc0),
507 113 {RoutingEffect, NewStateData} = route_iq(Acc, #routed_iq{
508 iq = IQ,
509 from = From,
510 packet = Packet}, StateData),
511 113 case RoutingEffect of
512 113 ok -> next_normal_state(NewStateData);
513
:-(
stop -> {stop, normal, NewStateData}
514 end;
515 normal_state({route, From, Nick, _Acc,
516 #xmlel{name = <<"presence">>} = Packet},
517 StateData) ->
518 % FIXME sessions do we need to route presences to all sessions
519 212 Activity = get_user_activity(From, StateData),
520 212 Now = os:system_time(microsecond),
521 212 MinPresenceInterval = trunc(get_opt(StateData, min_presence_interval) * 1000000),
522 212 case (Now >= Activity#activity.presence_time + MinPresenceInterval) and
523 (Activity#activity.presence == undefined) of
524 true ->
525 212 NewActivity = Activity#activity{presence_time = Now},
526 212 StateData1 = store_user_activity(From, NewActivity, StateData),
527 212 process_presence(From, Nick, Packet, StateData1);
528 false ->
529
:-(
case Activity#activity.presence == undefined of
530 true ->
531
:-(
Interval = (Activity#activity.presence_time +
532 MinPresenceInterval - Now) div 1000,
533
:-(
erlang:send_after(Interval, self(), {process_user_presence, From});
534 false ->
535
:-(
ok
536 end,
537
:-(
NewActivity = Activity#activity{presence = {Nick, Packet}},
538
:-(
StateData1 = store_user_activity(From, NewActivity, StateData),
539
:-(
next_normal_state(StateData1)
540 end;
541 normal_state({route, From, ToNick, _Acc,
542 #xmlel{name = <<"message">>, attrs = Attrs} = Packet},
543 StateData) ->
544 8 Type = xml:get_attr_s(<<"type">>, Attrs),
545 8 FunRouteNickMessage = fun(JID, StateDataAcc) ->
546 8 route_nick_message(#routed_nick_message{
547 allow_pm = (StateDataAcc#state.config)#config.allow_private_messages,
548 online = is_user_online(From, StateDataAcc),
549 type = Type,
550 from = From,
551 nick = ToNick,
552 lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
553 decide = decide_fate_message(Type, Packet, From, StateDataAcc),
554 packet = Packet,
555 jid = JID}, StateDataAcc)
556 end,
557 8 NewStateData = case find_jids_by_nick(ToNick, StateData) of
558
:-(
[] -> FunRouteNickMessage(false, StateData);
559 8 JIDs -> lists:foldl(FunRouteNickMessage, StateData, JIDs)
560 end,
561 8 next_normal_state(NewStateData);
562 normal_state({route, From, ToNick, _Acc,
563 #xmlel{name = <<"iq">>, attrs = Attrs} = Packet},
564 StateData) ->
565
:-(
Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
566
:-(
StanzaId = xml:get_attr_s(<<"id">>, Attrs),
567
:-(
FunRouteNickIq = fun(JID) ->
568
:-(
route_nick_iq(#routed_nick_iq{
569 allow_query = (StateData#state.config)#config.allow_query_users,
570 online = is_user_online_iq(StanzaId, From, StateData),
571 jid = JID,
572 iq = jlib:iq_query_info(Packet),
573 packet = Packet,
574 lang = Lang,
575 from = From,
576 stanza = StanzaId,
577 nick = ToNick}, StateData)
578 end,
579
:-(
case find_jids_by_nick(ToNick, StateData) of
580
:-(
[] -> FunRouteNickIq(false);
581
:-(
JIDs -> lists:foreach(FunRouteNickIq, JIDs)
582 end,
583
:-(
next_normal_state(StateData);
584 normal_state({http_auth, AuthPid, Result, From, Nick, Packet, Role}, StateData) ->
585
:-(
AuthPids = StateData#state.http_auth_pids,
586
:-(
StateDataWithoutPid = StateData#state{http_auth_pids = lists:delete(AuthPid, AuthPids)},
587
:-(
NewStateData = handle_http_auth_result(Result, From, Nick, Packet, Role, StateDataWithoutPid),
588
:-(
destroy_temporary_room_if_empty(NewStateData, normal_state);
589 normal_state(timeout, StateData) ->
590 29 erlang:put(hibernated, os:timestamp()),
591 29 mongoose_metrics:update(global, [mod_muc, hibernations], 1),
592 29 {next_state, normal_state, StateData, hibernate};
593 normal_state(_Event, StateData) ->
594
:-(
next_normal_state(StateData).
595
596 normal_state({set_admin_items, UJID, Items}, _From,
597 #state{hibernate_timeout = Timeout} = StateData) ->
598
:-(
case process_admin_items_set(UJID, Items, <<"en">>, StateData) of
599 {result, [], StateData2} ->
600
:-(
{reply, ok, normal_state, StateData2, Timeout};
601 {error, Error} ->
602
:-(
{reply, {error, Error}, normal_state, StateData, Timeout}
603 end.
604
605 handle_event({service_message, Msg}, _StateName, StateData) ->
606
:-(
MessagePkt = #xmlel{name = <<"message">>,
607 attrs = [{<<"type">>, <<"groupchat">>}],
608 children = [#xmlel{name = <<"body">>,
609 children = [#xmlcdata{content = Msg}]}]},
610
:-(
send_to_all_users(MessagePkt, StateData),
611
:-(
NSD = add_message_to_history(<<>>,
612 StateData#state.jid,
613 MessagePkt,
614 StateData),
615
:-(
next_normal_state(NSD);
616
617 handle_event({destroy, Reason}, _StateName, StateData) ->
618 65 {result, [], stop} =
619 destroy_room(
620 #xmlel{name = <<"destroy">>, attrs = [{<<"xmlns">>, ?NS_MUC_OWNER}],
621 children = case Reason of
622 65 none -> [];
623 _Else ->
624
:-(
[#xmlel{name = <<"reason">>,
625 children = [#xmlcdata{content = Reason}]}]
626 end}, StateData),
627 65 ?LOG_INFO(ls(#{what => muc_room_destroyed, text => <<"Destroyed MUC room">>,
628 65 reason => Reason}, StateData)),
629 65 add_to_log(room_existence, destroyed, StateData),
630 65 {stop, shutdown, StateData};
631 handle_event(destroy, StateName, StateData) ->
632 65 handle_event({destroy, none}, StateName, StateData);
633
634 handle_event({set_affiliations, Affiliations},
635 #state{hibernate_timeout = Timeout} = StateName, StateData) ->
636
:-(
{next_state, StateName, StateData#state{affiliations = Affiliations}, Timeout};
637
638 handle_event(_Event, StateName, #state{hibernate_timeout = Timeout} = StateData) ->
639
:-(
{next_state, StateName, StateData, Timeout}.
640
641 handle_sync_event({get_disco_item, JID, Lang}, _From, StateName, StateData) ->
642
:-(
Reply = get_roomdesc_reply(JID, StateData,
643 get_roomdesc_tail(StateData, Lang)),
644
:-(
reply_with_timeout(Reply, StateName, StateData);
645 handle_sync_event(get_config, _From, StateName, StateData) ->
646
:-(
reply_with_timeout({ok, StateData#state.config}, StateName, StateData);
647 handle_sync_event(get_state, _From, StateName, StateData) ->
648
:-(
reply_with_timeout({ok, StateData}, StateName, StateData);
649 handle_sync_event(get_room_users, _From, StateName, StateData) ->
650
:-(
reply_with_timeout({ok, maps:values(StateData#state.users)}, StateName, StateData);
651 handle_sync_event(get_room_affiliations, _From, StateName, StateData) ->
652
:-(
reply_with_timeout({ok, StateData#state.affiliations}, StateName, StateData);
653 handle_sync_event({is_room_owner, UserJID}, _From, StateName, StateData) ->
654
:-(
reply_with_timeout({ok, get_affiliation(UserJID, StateData) =:= owner}, StateName, StateData);
655 handle_sync_event({can_access_room, UserJID}, _From, StateName, StateData) ->
656 117 reply_with_timeout({ok, can_read_conference(UserJID, StateData)}, StateName, StateData);
657 handle_sync_event({can_access_identity, UserJID}, _From, StateName, StateData) ->
658 73 reply_with_timeout({ok, can_user_access_identity(UserJID, StateData)}, StateName, StateData);
659 handle_sync_event({change_config, Config}, _From, StateName, StateData) ->
660
:-(
{result, [], NSD} = change_config(Config, StateData),
661
:-(
reply_with_timeout({ok, NSD#state.config}, StateName, NSD);
662 handle_sync_event({change_state, NewStateData}, _From, StateName, _StateData) ->
663
:-(
reply_with_timeout({ok, NewStateData}, StateName, NewStateData);
664 handle_sync_event(_Event, _From, StateName, StateData) ->
665
:-(
reply_with_timeout(ok, StateName, StateData).
666
667 reply_with_timeout(Reply, StateName, #state{hibernate_timeout = Timeout} = State) ->
668 190 {reply, Reply, StateName, State, Timeout}.
669
670 code_change(_OldVsn, StateName, StateData, _Extra) ->
671
:-(
{ok, StateName, StateData}.
672
673 maybe_prepare_room_queue(RoomQueue, StateData) ->
674
:-(
StateData1 = StateData#state{room_queue = RoomQueue},
675
:-(
case queue:is_empty(StateData#state.room_queue) of
676 true ->
677
:-(
StateData2 = prepare_room_queue(StateData1),
678
:-(
next_normal_state(StateData2);
679 _ ->
680
:-(
next_normal_state(StateData1)
681 end.
682
683 -type info_msg() :: {process_user_presence | process_user_message, jid:jid()}
684 | process_room_queue.
685 -spec handle_info(info_msg(), statename(), state()) -> fsm_return().
686 handle_info({process_user_presence, From}, normal_state = _StateName, StateData) ->
687
:-(
RoomQueue = queue:in({presence, From}, StateData#state.room_queue),
688
:-(
maybe_prepare_room_queue(RoomQueue, StateData);
689 handle_info({process_user_message, From}, normal_state = _StateName, StateData) ->
690
:-(
RoomQueue = queue:in({message, From}, StateData#state.room_queue),
691
:-(
maybe_prepare_room_queue(RoomQueue, StateData);
692 handle_info(process_room_queue, normal_state, StateData) ->
693
:-(
case queue:out(StateData#state.room_queue) of
694 {{value, {message, From}}, RoomQueue} ->
695
:-(
Activity = get_user_activity(From, StateData),
696
:-(
Packet = Activity#activity.message,
697
:-(
NewActivity = Activity#activity{message = undefined},
698
:-(
StateData1 =
699 store_user_activity(
700 From, NewActivity, StateData),
701
:-(
StateData2 =
702 StateData1#state{
703 room_queue = RoomQueue},
704
:-(
StateData3 = prepare_room_queue(StateData2),
705
:-(
process_groupchat_message(From, Packet, StateData3);
706 {{value, {presence, From}}, RoomQueue} ->
707
:-(
Activity = get_user_activity(From, StateData),
708
:-(
{Nick, Packet} = Activity#activity.presence,
709
:-(
NewActivity = Activity#activity{presence = undefined},
710
:-(
StateData1 =
711 store_user_activity(
712 From, NewActivity, StateData),
713
:-(
StateData2 =
714 StateData1#state{
715 room_queue = RoomQueue},
716
:-(
StateData3 = prepare_room_queue(StateData2),
717
:-(
process_presence(From, Nick, Packet, StateData3);
718 {empty, _} ->
719
:-(
next_normal_state(StateData)
720 end;
721 handle_info({'EXIT', FromPid, _Reason}, StateName, StateData) ->
722
:-(
AuthPids = StateData#state.http_auth_pids,
723
:-(
StateWithoutPid = StateData#state{http_auth_pids = lists:delete(FromPid, AuthPids)},
724
:-(
destroy_temporary_room_if_empty(StateWithoutPid, StateName);
725 handle_info(stop_persistent_room_process, normal_state,
726 #state{room = RoomName,
727 config = #config{persistent = true}} = StateData) ->
728 2 maybe_stop_persistent_room(RoomName, is_empty_room(StateData), StateData);
729 handle_info(_Info, StateName, #state{hibernate_timeout = Timeout} = StateData) ->
730
:-(
{next_state, StateName, StateData, Timeout}.
731
732 maybe_stop_persistent_room(RoomName, true, State) ->
733 2 do_stop_persistent_room(RoomName, State);
734 maybe_stop_persistent_room(RoomName, _, State) ->
735
:-(
stop_if_only_owner_is_online(RoomName, count_users(State), State).
736
737 stop_if_only_owner_is_online(RoomName, 1, #state{users = Users, jid = RoomJID} = State) ->
738
:-(
[{LJID, #user{jid = LastUser, nick = Nick}}] = maps:to_list(Users),
739
740
:-(
case get_affiliation(LastUser, State) of
741 owner ->
742
:-(
ItemAttrs = [{<<"affiliation">>, <<"owner">>}, {<<"role">>, <<"none">>}],
743
:-(
Packet = unavailable_presence(ItemAttrs, <<"Room hibernation">>),
744
:-(
FromRoom = jid:replace_resource(RoomJID, Nick),
745
:-(
ejabberd_router:route(FromRoom, LastUser, Packet),
746
:-(
tab_remove_online_user(LJID, State),
747
:-(
do_stop_persistent_room(RoomName, State);
748 _ ->
749
:-(
next_normal_state(State)
750 end;
751 stop_if_only_owner_is_online(_, _, State) ->
752
:-(
next_normal_state(State).
753
754 do_stop_persistent_room(_RoomName, State) ->
755 2 ?LOG_INFO(ls(#{what => muc_room_stopping_persistent,
756 2 text => <<"Stopping persistent room's process">>}, State)),
757 2 mongoose_metrics:update(global, [mod_muc, deep_hibernations], 1),
758 2 {stop, normal, State}.
759
760 %% @doc Purpose: Shutdown the fsm
761 -spec terminate(any(), statename(), state()) -> 'ok'.
762 terminate(Reason, _StateName, StateData) ->
763 95 ?LOG_INFO(ls(#{what => muc_room_stopping, text => <<"Stopping room's process">>,
764 95 reason => Reason}, StateData)),
765 95 ReasonT = case Reason of
766 65 shutdown -> <<"You are being removed from the room because of a system shutdown">>;
767 30 _ -> <<"Room terminates">>
768 end,
769 95 ItemAttrs = [{<<"affiliation">>, <<"none">>}, {<<"role">>, <<"none">>}],
770 95 Packet = unavailable_presence(ItemAttrs, ReasonT),
771 95 maps_foreach(
772 fun(LJID, Info) ->
773 16 Nick = Info#user.nick,
774 16 case Reason of
775 shutdown ->
776 16 ejabberd_router:route(
777 jid:replace_resource(StateData#state.jid, Nick),
778 Info#user.jid,
779 Packet);
780
:-(
_ -> ok
781 end,
782 16 tab_remove_online_user(LJID, StateData)
783 end, StateData#state.users),
784 95 add_to_log(room_existence, stopped, StateData),
785 95 mod_muc:room_destroyed(StateData#state.host_type,
786 StateData#state.host,
787 StateData#state.room, self()),
788 95 ok.
789
790 %%%----------------------------------------------------------------------
791 %%% Internal functions
792 %%%----------------------------------------------------------------------
793
794 unavailable_presence(ItemAttrs, ReasonT) ->
795 95 ReasonEl = #xmlel{name = <<"reason">>,
796 children = [#xmlcdata{content = ReasonT}]},
797 95 #xmlel{name = <<"presence">>,
798 attrs = [{<<"type">>, <<"unavailable">>}],
799 children = [#xmlel{name = <<"x">>,
800 attrs = [{<<"xmlns">>, ?NS_MUC_USER}],
801 children = [#xmlel{name = <<"item">>,
802 attrs = ItemAttrs,
803 children = [ReasonEl]},
804 #xmlel{name = <<"status">>,
805 attrs = [{<<"code">>, <<"332">>}]}
806 ]}]}.
807
808 -spec occupant_jid(user(), 'undefined' | jid:jid()) -> 'error' | jid:jid().
809 occupant_jid(#user{nick=Nick}, RoomJID) ->
810 16 jid:replace_resource(RoomJID, Nick).
811
812
813 -spec route(atom() | pid() | port() | {atom(), _} | {'via', _, _},
814 From :: jid:jid(), To :: mod_muc:nick(), Acc :: mongoose_acc:t(),
815 Pkt :: exml:element()) -> 'ok'.
816 route(Pid, From, ToNick, Acc, Packet) ->
817 428 gen_fsm_compat:send_event(Pid, {route, From, ToNick, Acc, Packet}).
818
819
820 -spec process_groupchat_message(jid:simple_jid() | jid:jid(),
821 exml:element(), state()) -> fsm_return().
822 process_groupchat_message(From, #xmlel{name = <<"message">>,
823 attrs = Attrs} = Packet,
824 StateData) ->
825 91 Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
826 91 case can_send_to_conference(From, StateData) of
827 true ->
828 91 process_message_from_allowed_user(From, Packet, StateData);
829 false ->
830
:-(
send_error_only_occupants(<<"messages">>, Packet, Lang,
831 StateData#state.jid, From),
832
:-(
next_normal_state(StateData)
833 end.
834
835 can_send_to_conference(From, StateData) ->
836 186 is_user_online(From, StateData)
837 orelse
838 4 is_allowed_nonparticipant(From, StateData).
839
840 can_read_conference(UserJID,
841 StateData=#state{config = #config{members_only = MembersOnly,
842 password_protected = Protected}}) ->
843 117 Affiliation = get_affiliation(UserJID, StateData),
844 %% In a members-only chat room, only owners, admins or members can query a room archive.
845 117 case {MembersOnly, Protected} of
846 {_, true} ->
847 %% For querying password-protected room user should be a member
848 %% or inside the room
849 12 is_user_online(UserJID, StateData)
850 orelse
851 12 lists:member(Affiliation, [owner, admin, member]);
852 {true, false} ->
853
:-(
lists:member(Affiliation, [owner, admin, member]);
854 {false, false} ->
855 %% Outcast (banned) cannot read
856 105 Affiliation =/= outcast
857 end.
858
859 can_user_access_identity(UserJID, StateData) ->
860 73 is_room_non_anonymous(StateData)
861 orelse
862 12 is_user_moderator(UserJID, StateData).
863
864 is_room_non_anonymous(StateData) ->
865 73 not is_room_anonymous(StateData).
866
867 is_room_anonymous(#state{config = #config{anonymous = IsAnon}}) ->
868 73 IsAnon.
869
870 is_user_moderator(UserJID, StateData) ->
871 12 get_role(UserJID, StateData) =:= moderator.
872
873 process_message_from_allowed_user(From, #xmlel{attrs = Attrs} = Packet,
874 StateData) ->
875 91 Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
876 91 {FromNick, Role} = get_participant_data(From, StateData),
877 91 CanSendBroadcasts = can_send_broadcasts(Role, StateData),
878 91 case CanSendBroadcasts of
879 true ->
880 91 {NewState, Changed} = change_subject_if_allowed(FromNick, Role,
881 Packet, StateData),
882 91 case Changed of
883 true ->
884 91 broadcast_room_packet(From, FromNick, Role, Packet, NewState);
885 false ->
886
:-(
change_subject_error(From, FromNick, Packet, Lang, NewState),
887
:-(
next_normal_state(NewState)
888 end;
889 false ->
890
:-(
ErrText = <<"Visitors are not allowed to send messages to all occupants">>,
891
:-(
Err = jlib:make_error_reply(Packet, mongoose_xmpp_errors:forbidden(Lang, ErrText)),
892
:-(
ejabberd_router:route(StateData#state.jid, From, Err),
893
:-(
next_normal_state(StateData)
894 end.
895
896 can_send_broadcasts(Role, StateData) ->
897 (Role == moderator)
898 or (Role == participant)
899 91 or ((StateData#state.config)#config.moderated == false).
900
901 broadcast_room_packet(From, FromNick, Role, Packet, StateData) ->
902 91 Activity = get_user_activity(From, StateData),
903 91 TS = Activity#activity.message_time,
904 91 Affiliation = get_affiliation(From, StateData),
905 91 EventData = #{from_nick => FromNick, from_jid => From,
906 room_jid => StateData#state.jid, role => Role,
907 affiliation => Affiliation, timestamp => TS},
908 91 FilteredPacket = mongoose_hooks:filter_room_packet(
909 StateData#state.host_type, Packet, EventData),
910 91 RouteFrom = jid:replace_resource(StateData#state.jid,
911 FromNick),
912 91 RoomJid = StateData#state.jid,
913 91 HookInfo = #{host_type => StateData#state.host_type,
914 room_jid => RoomJid,
915 from_jid => From,
916 from_room_jid => RouteFrom,
917 packet => FilteredPacket,
918 affiliations_map => StateData#state.affiliations},
919 91 run_update_inbox_for_muc_hook(StateData#state.host_type, HookInfo),
920 91 maps_foreach(fun(_LJID, Info) ->
921 198 ejabberd_router:route(RouteFrom,
922 Info#user.jid,
923 FilteredPacket)
924 end, StateData#state.users),
925 91 NewStateData2 = add_message_to_history(FromNick,
926 From,
927 FilteredPacket,
928 StateData),
929 91 next_normal_state(NewStateData2).
930
931 -spec run_update_inbox_for_muc_hook(mongooseim:host_type(),
932 update_inbox_for_muc_payload()) -> ok.
933 run_update_inbox_for_muc_hook(HostType, HookInfo) ->
934 91 mongoose_hooks:update_inbox_for_muc(HostType, HookInfo),
935 91 ok.
936
937 change_subject_error(From, FromNick, Packet, Lang, StateData) ->
938
:-(
Err = case (StateData#state.config)#config.allow_change_subj of
939
:-(
true -> mongoose_xmpp_errors:forbidden(Lang, <<"Only moderators and participants are allowed"
940 " to change the subject in this room">>);
941
:-(
_ -> mongoose_xmpp_errors:forbidden(Lang, <<"Only moderators are allowed"
942 " to change the subject in this room">>)
943 end,
944
:-(
ejabberd_router:route(jid:replace_resource(StateData#state.jid,
945 FromNick),
946 From,
947 jlib:make_error_reply(Packet, Err)).
948
949 change_subject_if_allowed(FromNick, Role, Packet, StateData) ->
950 91 case check_subject(Packet) of
951 false ->
952 91 {StateData, true};
953 Subject ->
954
:-(
case can_change_subject(Role, StateData) of
955 true ->
956
:-(
NSD = StateData#state{subject = Subject,
957 subject_author = FromNick,
958 subject_timestamp = get_current_timestamp()},
959
:-(
save_persistent_room_state(NSD),
960
:-(
{NSD, true};
961 _ ->
962
:-(
{StateData, false}
963 end
964 end.
965
966 save_persistent_room_state(StateData) ->
967 93 case (StateData#state.config)#config.persistent of
968 true ->
969 75 mod_muc:store_room(StateData#state.host_type,
970 StateData#state.host,
971 StateData#state.room,
972 make_opts(StateData));
973 _ ->
974 18 ok
975 end.
976
977 %% @doc Check if this non participant can send message to room.
978 %%
979 %% XEP-0045 v1.23:
980 %% 7.9 Sending a Message to All Occupants
981 %% an implementation MAY allow users with certain privileges
982 %% (e.g., a room owner, room admin, or service-level admin)
983 %% to send messages to the room even if those users are not occupants.
984 -spec is_allowed_nonparticipant(jid:jid(), state()) -> boolean().
985 is_allowed_nonparticipant(JID, StateData) ->
986 4 get_service_affiliation(JID, StateData) =:= owner.
987
988 %% @doc Get information of this participant, or default values.
989 %% If the JID is not a participant, return values for a service message.
990 -spec get_participant_data(jid:simple_jid() | jid:jid(), state()) -> {_, _}.
991 get_participant_data(From, StateData) ->
992 91 case maps:find(jid:to_lower(From), StateData#state.users) of
993 {ok, #user{nick = FromNick, role = Role}} ->
994 91 {FromNick, Role};
995 error ->
996
:-(
{<<>>, moderator}
997 end.
998
999 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1000 %% Presence processing
1001
1002 %% @doc Process presence stanza and destroy the room, if it is empty.
1003 -spec process_presence(From :: jid:jid(), Nick :: mod_muc:nick(),
1004 Packet :: exml:element(), state()) -> fsm_return().
1005 process_presence(From, ToNick, Presence, StateData) ->
1006 212 StateData1 = process_presence1(From, ToNick, Presence, StateData),
1007 212 destroy_temporary_room_if_empty(StateData1, normal_state).
1008
1009
1010 -spec process_presence(From :: jid:jid(), Nick :: mod_muc:nick(),
1011 Presence :: exml:element(), state(), statename()) -> fsm_return().
1012 process_presence(From, ToNick, Presence, StateData, NextState) ->
1013
:-(
StateData1 = process_presence(From, ToNick, Presence, StateData),
1014
:-(
rewrite_next_state(NextState, StateData1).
1015
1016
1017 -spec rewrite_next_state(statename(), fsm_return()) -> fsm_return().
1018 rewrite_next_state(NewState, {next_state, _, StateData, Timeout}) ->
1019
:-(
{next_state, NewState, StateData, Timeout};
1020 rewrite_next_state(NewState, {next_state, _, StateData}) ->
1021
:-(
{next_state, NewState, StateData};
1022 rewrite_next_state(_, {stop, normal, StateData}) ->
1023
:-(
{stop, normal, StateData}.
1024
1025
1026 -spec destroy_temporary_room_if_empty(state(), atom()) -> fsm_return().
1027 destroy_temporary_room_if_empty(StateData=#state{config=C=#config{}}, NextState) ->
1028 212 case (not C#config.persistent) andalso is_empty_room(StateData)
1029 12 andalso StateData#state.http_auth_pids =:= [] of
1030 true ->
1031 12 ?LOG_INFO(ls(#{what => muc_empty_room_destroyed,
1032 text => <<"Destroyed MUC room because it's temporary and empty">>},
1033 12 StateData)),
1034 12 add_to_log(room_existence, destroyed, StateData),
1035 12 {stop, normal, StateData};
1036 _ ->
1037 200 case NextState of
1038 normal_state ->
1039 200 next_normal_state(StateData);
1040 _ ->
1041
:-(
{next_state, NextState, StateData}
1042 end
1043 end.
1044
1045 next_normal_state(#state{hibernate_timeout = Timeout} = StateData) ->
1046 507 {next_state, normal_state, StateData, Timeout}.
1047
1048 -spec process_presence1(From, Nick, Packet, state()) -> state() when
1049 From :: jid:jid(),
1050 Nick :: mod_muc:nick(),
1051 Packet :: exml:element().
1052 process_presence1(From, Nick, #xmlel{name = <<"presence">>, attrs = Attrs} = Packet,
1053 StateData = #state{}) ->
1054 212 Type = xml:get_attr_s(<<"type">>, Attrs),
1055 212 Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
1056 212 case Type of
1057 <<"unavailable">> ->
1058 94 process_presence_unavailable(From, Packet, StateData);
1059 <<"error">> ->
1060
:-(
process_presence_error(From, Packet, Lang, StateData);
1061 <<>> ->
1062 118 case is_new_nick_of_online_user(From, Nick, StateData) of
1063 true ->
1064
:-(
process_presence_nick_change(From, Nick, Packet, Lang, StateData);
1065 false ->
1066
:-(
process_simple_presence(From, Packet, StateData);
1067 user_is_offline ->
1068 %% at this point we know that the presence has no type
1069 %% (user wants to enter the room)
1070 %% and that the user is not alredy online
1071 118 handle_new_user(From, Nick, Packet, StateData, Attrs)
1072 end;
1073 _NotOnline ->
1074
:-(
StateData
1075 end.
1076
1077
1078 -spec process_simple_presence(jid:jid(), exml:element(), state()) -> state().
1079 process_simple_presence(From, Packet, StateData) ->
1080
:-(
NewPacket = check_and_strip_visitor_status(From, Packet, StateData),
1081
:-(
NewState = add_user_presence(From, NewPacket, StateData),
1082
:-(
send_new_presence(From, NewState),
1083
:-(
NewState.
1084
1085
1086 -spec process_presence_error(jid:simple_jid() | jid:jid(),
1087 exml:element(), ejabberd:lang(), state()) -> state().
1088 process_presence_error(From, Packet, Lang, StateData) ->
1089
:-(
case is_user_online(From, StateData) of
1090 true ->
1091
:-(
ErrorText
1092 = <<"This participant is kicked from the room because he sent an error presence">>,
1093
:-(
expulse_participant(Packet, From, StateData, translate:translate(Lang, ErrorText));
1094 _ ->
1095
:-(
StateData
1096 end.
1097
1098
1099 -spec process_presence_unavailable(jid:jid(), exml:element(), state())
1100 -> state().
1101 process_presence_unavailable(From, Packet, StateData) ->
1102 94 case is_user_online(From, StateData) of
1103 true ->
1104 94 NewPacket = check_and_strip_visitor_status(From, Packet, StateData),
1105 94 NewState = add_user_presence_un(From, NewPacket, StateData),
1106 94 send_new_presence_un(From, NewState),
1107 94 Reason = case xml:get_subtag(NewPacket, <<"status">>) of
1108
:-(
false -> <<>>;
1109 94 StatusEl -> xml:get_tag_cdata(StatusEl)
1110 end,
1111 94 remove_online_user(From, NewState, Reason);
1112 _ ->
1113
:-(
StateData
1114 end.
1115
1116
1117 -spec choose_nick_change_strategy(jid:jid(), binary(), state())
1118 -> 'allowed' | 'conflict_registered' | 'conflict_use' | 'not_allowed_visitor'.
1119 choose_nick_change_strategy(From, Nick, StateData) ->
1120
:-(
case {is_nick_exists(Nick, StateData),
1121 mod_muc:can_use_nick(StateData#state.host_type, StateData#state.host, From, Nick),
1122 (StateData#state.config)#config.allow_visitor_nickchange,
1123 is_visitor(From, StateData)} of
1124 {_, _, false, true} ->
1125
:-(
not_allowed_visitor;
1126 {true, _, _, _} ->
1127
:-(
conflict_use;
1128 {_, false, _, _} ->
1129
:-(
conflict_registered;
1130 _ ->
1131
:-(
allowed
1132 end.
1133
1134
1135 -spec process_presence_nick_change(jid:jid(), mod_muc:nick(), exml:element(),
1136 ejabberd:lang(), state()) -> state().
1137 process_presence_nick_change(From, Nick, Packet, Lang, StateData) ->
1138
:-(
case choose_nick_change_strategy(From, Nick, StateData) of
1139 not_allowed_visitor ->
1140
:-(
ErrText = <<"Visitors are not allowed to change their nicknames in this room">>,
1141
:-(
Err = jlib:make_error_reply(Packet, mongoose_xmpp_errors:not_allowed(Lang, ErrText)),
1142
:-(
route_error(Nick, From, Err, StateData);
1143 conflict_use ->
1144
:-(
ErrText = <<"That nickname is already in use by another occupant">>,
1145
:-(
Err = jlib:make_error_reply(Packet, mongoose_xmpp_errors:conflict(Lang, ErrText)),
1146
:-(
route_error(Nick, From, Err, StateData);
1147 conflict_registered ->
1148
:-(
ErrText = <<"That nickname is registered by another person">>,
1149
:-(
Err = jlib:make_error_reply(Packet, mongoose_xmpp_errors:conflict(Lang, ErrText)),
1150
:-(
route_error(Nick, From, Err, StateData);
1151 allowed ->
1152
:-(
change_nick(From, Nick, StateData)
1153 end.
1154
1155
1156 -spec check_and_strip_visitor_status(jid:jid(), exml:element(), state())
1157 -> exml:element().
1158 check_and_strip_visitor_status(From, Packet, StateData) ->
1159 94 case {(StateData#state.config)#config.allow_visitor_status,
1160 is_visitor(From, StateData)} of
1161 {false, true} ->
1162
:-(
strip_status(Packet);
1163 _ ->
1164 94 Packet
1165 end.
1166
1167
1168 -spec handle_new_user(jid:jid(), mod_muc:nick(), exml:element(), state(),
1169 [{binary(), binary()}]) -> state().
1170 handle_new_user(From, Nick = <<>>, _Packet, StateData, Attrs) ->
1171
:-(
Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
1172
:-(
ErrText = <<"No nickname">>,
1173
:-(
Error =jlib:make_error_reply(
1174 #xmlel{name = <<"presence">>},
1175 mongoose_xmpp_errors:jid_malformed(Lang, ErrText)),
1176 %ejabberd_route(From, To, Packet),
1177
:-(
ejabberd_router:route(jid:replace_resource(StateData#state.jid, Nick), From, Error),
1178
:-(
StateData;
1179 handle_new_user(From, Nick, Packet, StateData, Attrs) ->
1180 118 case exml_query:path(Packet, [{element, <<"x">>}]) of
1181 undefined ->
1182
:-(
Response = kick_stanza_for_old_protocol(Attrs),
1183
:-(
ejabberd_router:route(jid:replace_resource(StateData#state.jid, Nick), From, Response);
1184 _ ->
1185 118 add_new_user(From, Nick, Packet, StateData)
1186 end.
1187
1188
1189 -spec is_user_online(jid:simple_jid() | jid:jid(), state()) -> boolean().
1190 is_user_online(JID, StateData) ->
1191 300 LJID = jid:to_lower(JID),
1192 300 maps:is_key(LJID, StateData#state.users).
1193
1194
1195 %% @doc Check if the user is occupant of the room, or at least is an admin
1196 %% or owner.
1197 -spec is_occupant_or_admin(jid:jid(), state()) -> boolean().
1198 is_occupant_or_admin(JID, StateData) ->
1199
:-(
FAffiliation = get_affiliation(JID, StateData),
1200
:-(
FRole = get_role(JID, StateData),
1201
:-(
(FRole /= none) orelse
1202
:-(
(FAffiliation == admin) orelse
1203
:-(
(FAffiliation == owner).
1204
1205 %%%
1206 %%% Handle IQ queries of vCard
1207 %%%
1208
1209 -spec is_user_online_iq(_, jid:jid(), state())
1210 -> {'false', _, jid:jid()} | {'true', _, jid:jid()}.
1211 is_user_online_iq(StanzaId, JID, StateData) when JID#jid.lresource /= <<>> ->
1212
:-(
{is_user_online(JID, StateData), StanzaId, JID};
1213 is_user_online_iq(StanzaId, JID, StateData) when JID#jid.lresource == <<>> ->
1214
:-(
try stanzaid_unpack(StanzaId) of
1215 {OriginalId, Resource} ->
1216
:-(
JIDWithResource = jid:replace_resource(JID, Resource),
1217
:-(
{is_user_online(JIDWithResource, StateData),
1218 OriginalId, JIDWithResource}
1219 catch
1220 _:_ ->
1221
:-(
{is_user_online(JID, StateData), StanzaId, JID}
1222 end.
1223
1224
1225 -spec handle_iq_vcard(jid:jid(), jid:simple_jid() | jid:jid(),
1226 binary(), any(), exml:element()) ->
1227 {jid:simple_jid() | jid:jid(), exml:element()}.
1228 handle_iq_vcard(FromFull, ToJID, StanzaId, NewId, Packet) ->
1229
:-(
ToBareJID = jid:to_bare(ToJID),
1230
:-(
IQ = jlib:iq_query_info(Packet),
1231
:-(
handle_iq_vcard2(FromFull, ToJID, ToBareJID, StanzaId, NewId, IQ, Packet).
1232
1233
1234 -spec handle_iq_vcard2(FromFull :: jid:jid(),
1235 ToJID :: jid:simple_jid() | jid:jid(),
1236 ToBareJID :: jid:simple_jid() | jid:jid(),
1237 binary(), _NewID, 'invalid' | 'not_iq' | 'reply' | jlib:iq(),
1238 exml:element()) -> {jid:simple_jid() | jid:jid(), exml:element()}.
1239 handle_iq_vcard2(_FromFull, ToJID, ToBareJID, StanzaId, _NewId,
1240 #iq{type = get, xmlns = ?NS_VCARD}, Packet)
1241 when ToBareJID /= ToJID ->
1242
:-(
{ToBareJID, change_stanzaid(StanzaId, ToJID, Packet)};
1243 handle_iq_vcard2(_FromFull, ToJID, _ToBareJID, _StanzaId, NewId, _IQ, Packet) ->
1244
:-(
{ToJID, change_stanzaid(NewId, Packet)}.
1245
1246
1247 -spec stanzaid_pack(binary(), jid:resource()) -> binary().
1248 stanzaid_pack(OriginalId, Resource) ->
1249
:-(
Data64 = base64:encode(<<"ejab\0", OriginalId/binary, 0, Resource/binary>>),
1250
:-(
<<"berd", Data64/binary>>.
1251
1252
1253 -spec stanzaid_unpack(binary()) -> stanzaid().
1254 stanzaid_unpack(<<"berd", StanzaIdBase64/binary>>) ->
1255
:-(
StanzaId = base64:decode(StanzaIdBase64),
1256
:-(
[<<"ejab">>, OriginalId, Resource] = binary:split(StanzaId, <<"\0">>),
1257
:-(
{OriginalId, Resource}.
1258
1259
1260 -spec change_stanzaid(binary(), exml:element()) -> exml:element().
1261 change_stanzaid(NewId, Packet) ->
1262
:-(
XE = #xmlel{attrs = Attrs} = jlib:remove_attr(<<"id">>, Packet),
1263
:-(
XE#xmlel{attrs = [{<<"id">>, NewId} | Attrs]}.
1264 change_stanzaid(PreviousId, ToJID, Packet) ->
1265
:-(
NewId = stanzaid_pack(PreviousId, ToJID#jid.lresource),
1266
:-(
change_stanzaid(NewId, Packet).
1267
1268 %%%
1269 %%%
1270
1271 -spec role_to_binary(mod_muc:role()) -> binary().
1272 role_to_binary(Role) ->
1273 271 case Role of
1274 86 moderator -> <<"moderator">>;
1275 91 participant -> <<"participant">>;
1276
:-(
visitor -> <<"visitor">>;
1277 94 none -> <<"none">>
1278 end.
1279
1280 -spec affiliation_to_binary(mod_muc:affiliation()) -> binary().
1281 affiliation_to_binary(Affiliation) ->
1282 271 case Affiliation of
1283 135 owner -> <<"owner">>;
1284
:-(
admin -> <<"admin">>;
1285
:-(
member -> <<"member">>;
1286
:-(
outcast -> <<"outcast">>;
1287 136 none -> <<"none">>
1288 end.
1289
1290 -spec binary_to_role(binary()) -> mod_muc:role().
1291 binary_to_role(Role) ->
1292
:-(
case Role of
1293
:-(
<<"moderator">> -> moderator;
1294
:-(
<<"participant">> -> participant;
1295
:-(
<<"visitor">> -> visitor;
1296
:-(
<<"none">> -> none
1297 end.
1298
1299 -spec binary_to_affiliation(binary()) -> mod_muc:affiliation().
1300 binary_to_affiliation(Affiliation) ->
1301
:-(
case Affiliation of
1302
:-(
<<"owner">> -> owner;
1303
:-(
<<"admin">> -> admin;
1304
:-(
<<"member">> -> member;
1305
:-(
<<"outcast">> -> outcast;
1306
:-(
<<"none">> -> none
1307 end.
1308
1309
1310 %% @doc Decide the fate of the message and its sender
1311 %% Returns: continue_delivery | forget_message | {expulse_sender, Reason}
1312 -spec decide_fate_message(binary(), exml:element(), jid:simple_jid() | jid:jid(),
1313 state()) -> 'continue_delivery'
1314 | 'forget_message'
1315 | {'expulse_sender', string()}.
1316 decide_fate_message(<<"error">>, Packet, From, StateData) ->
1317 %% Make a preliminary decision
1318
:-(
PD = case check_error_kick(Packet) of
1319 %% If this is an error stanza and its condition matches a criteria
1320 true ->
1321
:-(
Reason = "This participant is considered a ghost and is expulsed: " ++
1322 binary_to_list(jid:to_binary(From)),
1323
:-(
{expulse_sender, Reason};
1324 false ->
1325
:-(
continue_delivery
1326 end,
1327
:-(
case PD of
1328 {expulse_sender, R} ->
1329
:-(
case is_user_online(From, StateData) of
1330 true ->
1331
:-(
{expulse_sender, R};
1332 false ->
1333
:-(
forget_message
1334 end;
1335 Other ->
1336
:-(
Other
1337 end;
1338 decide_fate_message(_, _, _, _) ->
1339 8 continue_delivery.
1340
1341
1342 %% @doc Check if the elements of this error stanza indicate
1343 %% that the sender is a dead participant.
1344 %% If so, return true to kick the participant.
1345 -spec check_error_kick(exml:element()) -> boolean().
1346 check_error_kick(Packet) ->
1347
:-(
case get_error_condition(Packet) of
1348
:-(
<<"gone">> -> true;
1349
:-(
<<"internal-server-error">> -> true;
1350
:-(
<<"item-not-found">> -> true;
1351
:-(
<<"jid-malformed">> -> true;
1352
:-(
<<"recipient-unavailable">> -> true;
1353
:-(
<<"redirect">> -> true;
1354
:-(
<<"remote-server-not-found">> -> true;
1355
:-(
<<"remote-server-timeout">> -> true;
1356
:-(
<<"service-unavailable">> -> true;
1357
:-(
_ -> false
1358 end.
1359
1360
1361 -spec get_error_condition(exml:element()) -> binary().
1362 get_error_condition(Packet) ->
1363
:-(
case catch get_error_condition2(Packet) of
1364 {condition, ErrorCondition} ->
1365
:-(
ErrorCondition;
1366 {'EXIT', _} ->
1367
:-(
<<"badformed error stanza">>
1368 end.
1369
1370
1371 -spec get_error_condition2(exml:element()) -> {condition, binary()}.
1372 get_error_condition2(Packet) ->
1373
:-(
#xmlel{children = EEls} = xml:get_subtag(Packet, <<"error">>),
1374
:-(
[Condition] = [Name || #xmlel{name = Name,
1375 attrs = [{<<"xmlns">>, ?NS_STANZAS}],
1376
:-(
children = []} <- EEls],
1377
:-(
{condition, Condition}.
1378
1379
1380 -spec expulse_participant(exml:element(), jid:jid(), state(), binary()) -> state().
1381 expulse_participant(Packet, From, StateData, Reason1) ->
1382
:-(
ErrorCondition = get_error_condition(Packet),
1383
:-(
Reason2 = <<Reason1/binary, ": ", ErrorCondition/binary>>,
1384
:-(
NewState = add_user_presence_un(
1385 From,
1386 #xmlel{name = <<"presence">>, attrs = [{<<"type">>, <<"unavailable">>}],
1387 children = [#xmlel{name = <<"status">>,
1388 children = [#xmlcdata{content = Reason2}]}]},
1389 StateData),
1390
:-(
send_new_presence_un(From, NewState),
1391
:-(
remove_online_user(From, NewState).
1392
1393
1394 -spec access_admin(state()) -> any().
1395 access_admin(#state{access=Access}) ->
1396 837 {_AccessRoute, _AccessCreate, AccessAdmin, _AccessPersistent} = Access,
1397 837 AccessAdmin.
1398
1399
1400 -spec access_persistent(state()) -> any().
1401 access_persistent(#state{access=Access}) ->
1402
:-(
{_AccessRoute, _AccessCreate, _AccessAdmin, AccessPersistent} = Access,
1403
:-(
AccessPersistent.
1404
1405
1406 -spec set_affiliation(jid:jid(), mod_muc:affiliation(), state()) -> state().
1407 set_affiliation(JID, Affiliation, StateData)
1408 when is_atom(Affiliation) ->
1409 93 LJID = jid:to_bare(jid:to_lower(JID)),
1410 93 Affiliations = case Affiliation of
1411
:-(
none -> maps:remove(LJID, StateData#state.affiliations);
1412 93 _ -> maps:put(LJID, Affiliation, StateData#state.affiliations)
1413 end,
1414 93 StateData#state{affiliations = Affiliations}.
1415
1416
1417 -spec set_affiliation_and_reason(jid:jid(), mod_muc:affiliation(), term(),
1418 state()) -> state().
1419 set_affiliation_and_reason(JID, Affiliation, Reason, StateData)
1420 when is_atom(Affiliation) ->
1421
:-(
LJID = jid:to_bare(jid:to_lower(JID)),
1422
:-(
Affiliations = case Affiliation of
1423
:-(
none -> maps:remove(LJID, StateData#state.affiliations);
1424
:-(
_ -> maps:put(LJID, {Affiliation, Reason}, StateData#state.affiliations)
1425 end,
1426
:-(
StateData#state{affiliations = Affiliations}.
1427
1428
1429 -spec get_affiliation(jid:jid(), state()) -> mod_muc:affiliation().
1430 get_affiliation(JID, StateData) ->
1431 597 AccessAdmin = access_admin(StateData),
1432 597 case acl:match_rule(StateData#state.host_type, StateData#state.server_host, AccessAdmin, JID) of
1433 allow ->
1434
:-(
owner;
1435 _ ->
1436 597 LJID = jid:to_lower(JID),
1437 597 LJID1 = jid:to_bare(LJID),
1438 597 LJID2 = setelement(1, LJID, <<>>),
1439 597 LJID3 = jid:to_bare(LJID2),
1440 597 lookup_affiliation([ LJID, LJID1, LJID2, LJID3 ], StateData#state.affiliations)
1441 end.
1442
1443 -spec lookup_affiliation(JIDs :: [jid:simple_jid()],
1444 Affiliations :: affiliations_map()) ->
1445 mod_muc:affiliation().
1446 lookup_affiliation([ JID | RJIDs ], Affiliations) ->
1447 1702 case maps:find(JID, Affiliations) of
1448
:-(
{ok, {Affiliation, _Reason}} -> Affiliation;
1449 343 {ok, Affiliation} -> Affiliation;
1450 1359 _ -> lookup_affiliation(RJIDs, Affiliations)
1451 end;
1452 lookup_affiliation([], _Affiliations) ->
1453 254 none.
1454
1455 -spec get_service_affiliation(jid:jid(), state()) -> mod_muc:affiliation().
1456 get_service_affiliation(JID, StateData) ->
1457 240 AccessAdmin = access_admin(StateData),
1458 240 case acl:match_rule(StateData#state.host_type, StateData#state.server_host, AccessAdmin, JID) of
1459 allow ->
1460
:-(
owner;
1461 _ ->
1462 240 none
1463 end.
1464
1465
1466 -spec set_role(JID :: jid:jid(), Role :: mod_muc:role(), state()) -> state().
1467 set_role(JID, none, StateData) ->
1468
:-(
erase_matched_users(JID, StateData);
1469 set_role(JID, Role, StateData) ->
1470
:-(
update_matched_users(fun(User) -> User#user{role = Role} end,
1471 JID, StateData).
1472
1473
1474 -spec get_role( jid:jid(), state()) -> mod_muc:role().
1475 get_role(JID, StateData) ->
1476 106 LJID = jid:to_lower(JID),
1477 106 case maps:find(LJID, StateData#state.users) of
1478 106 {ok, #user{role = Role}} -> Role;
1479
:-(
_ -> none
1480 end.
1481
1482
1483 -spec get_default_role(mod_muc:affiliation(), state()) -> mod_muc:role().
1484 53 get_default_role(owner, _StateData) -> moderator;
1485
:-(
get_default_role(admin, _StateData) -> moderator;
1486
:-(
get_default_role(member, _StateData) -> participant;
1487
:-(
get_default_role(outcast, _StateData) -> none;
1488 get_default_role(none, StateData) ->
1489 65 case (StateData#state.config)#config.members_only of
1490
:-(
true -> none;
1491 _ ->
1492 65 case (StateData#state.config)#config.members_by_default of
1493 65 true -> participant;
1494
:-(
_ -> visitor
1495 end
1496 end.
1497
1498
1499 -spec is_visitor(jid:jid(), state()) -> boolean().
1500 is_visitor(Jid, StateData) ->
1501 94 get_role(Jid, StateData) =:= visitor.
1502
1503
1504 -spec is_empty_room(state()) -> boolean().
1505 is_empty_room(#state{users=Users}) ->
1506 66 is_empty_map(Users).
1507
1508
1509 -spec is_empty_map(map()) -> boolean().
1510 is_empty_map(Map) ->
1511 66 maps:size(Map) =:= 0.
1512
1513
1514 -spec map_foreach_value(fun((_) -> ok), users_map()) -> any().
1515 map_foreach_value(F, Map) ->
1516 65 maps:fold(fun(_Key, Value, _) -> F(Value) end, ok, Map).
1517
1518
1519 -spec count_users(state()) -> non_neg_integer().
1520 count_users(#state{users=Users}) ->
1521 118 maps:size(Users).
1522
1523
1524 -spec get_max_users(state()) -> integer() | none.
1525 get_max_users(StateData) ->
1526 118 MaxUsers = (StateData#state.config)#config.max_users,
1527 118 ServiceMaxUsers = get_service_max_users(StateData),
1528 118 case MaxUsers =< ServiceMaxUsers of
1529 118 true -> MaxUsers;
1530
:-(
false -> ServiceMaxUsers
1531 end.
1532
1533
1534 -spec get_service_max_users(state()) -> integer() | none.
1535 get_service_max_users(StateData) ->
1536 120 get_opt(StateData, max_users).
1537
1538 -spec get_max_users_admin_threshold(state()) -> integer().
1539 get_max_users_admin_threshold(StateData) ->
1540 118 get_opt(StateData, max_users_admin_threshold).
1541
1542 -spec get_user_activity(jid:simple_jid() | jid:jid(), state())
1543 -> activity().
1544 get_user_activity(JID, StateData) ->
1545 394 case treap:lookup(jid:to_lower(JID), StateData#state.activity) of
1546
:-(
{ok, _P, A} -> A;
1547 error ->
1548 394 MessageShaper = mongoose_shaper:new(get_opt(StateData, user_message_shaper)),
1549 394 PresenceShaper = mongoose_shaper:new(get_opt(StateData, user_presence_shaper)),
1550 394 #activity{message_shaper = MessageShaper,
1551 presence_shaper = PresenceShaper}
1552 end.
1553
1554
1555 -spec store_user_activity(jid:simple_jid() | jid:jid(), activity(),
1556 state()) -> state().
1557 store_user_activity(JID, UserActivity, StateData) ->
1558 303 MinMessageInterval = get_opt(StateData, min_message_interval),
1559 303 MinPresenceInterval = get_opt(StateData, min_presence_interval),
1560 303 Key = jid:to_lower(JID),
1561 303 Now = os:system_time(microsecond),
1562 303 Activity1 = clean_treap(StateData#state.activity, {1, -Now}),
1563 303 Activity =
1564 case treap:lookup(Key, Activity1) of
1565 {ok, _P, _A} ->
1566
:-(
treap:delete(Key, Activity1);
1567 error ->
1568 303 Activity1
1569 end,
1570 303 StateData1 =
1571
:-(
case (MinMessageInterval == 0) andalso
1572 303 (MinPresenceInterval == 0) andalso
1573 303 (UserActivity#activity.message_shaper == none) andalso
1574 303 (UserActivity#activity.presence_shaper == none) andalso
1575 303 (UserActivity#activity.message == undefined) andalso
1576 303 (UserActivity#activity.presence == undefined) of
1577 true ->
1578 303 StateData#state{activity = Activity};
1579 false ->
1580
:-(
case (UserActivity#activity.message == undefined) andalso
1581
:-(
(UserActivity#activity.presence == undefined) of
1582 true ->
1583
:-(
{_, MessageShaperInterval} =
1584 mongoose_shaper:update(UserActivity#activity.message_shaper,
1585 100000),
1586
:-(
{_, PresenceShaperInterval} =
1587 mongoose_shaper:update(UserActivity#activity.presence_shaper,
1588 100000),
1589
:-(
Delay = lists:max([MessageShaperInterval,
1590 PresenceShaperInterval,
1591 MinMessageInterval * 1000,
1592 MinPresenceInterval * 1000]) * 1000,
1593
:-(
Priority = {1, -(Now + Delay)},
1594
:-(
StateData#state{
1595 activity = treap:insert(
1596 Key,
1597 Priority,
1598 UserActivity,
1599 Activity)};
1600 false ->
1601
:-(
Priority = {0, 0},
1602
:-(
StateData#state{
1603 activity = treap:insert(
1604 Key,
1605 Priority,
1606 UserActivity,
1607 Activity)}
1608 end
1609 end,
1610 303 StateData1.
1611
1612
1613 -spec clean_treap(treap:treap(), {1, integer()}) -> treap:treap().
1614 clean_treap(Treap, CleanPriority) ->
1615 303 case treap:is_empty(Treap) of
1616 true ->
1617 303 Treap;
1618 false ->
1619
:-(
{_Key, Priority, _Value} = treap:get_root(Treap),
1620
:-(
case Priority > CleanPriority of
1621
:-(
true -> clean_treap(treap:delete_root(Treap), CleanPriority);
1622
:-(
false -> Treap
1623 end
1624 end.
1625
1626
1627 -spec prepare_room_queue(state()) -> state().
1628 prepare_room_queue(StateData) ->
1629
:-(
case queue:out(StateData#state.room_queue) of
1630 {{value, {message, From}}, _RoomQueue} ->
1631
:-(
Activity = get_user_activity(From, StateData),
1632
:-(
Packet = Activity#activity.message,
1633
:-(
Size = element_size(Packet),
1634
:-(
{RoomShaper, RoomShaperInterval} =
1635 mongoose_shaper:update(StateData#state.room_shaper, Size),
1636
:-(
erlang:send_after(
1637 RoomShaperInterval, self(),
1638 process_room_queue),
1639
:-(
StateData#state{
1640 room_shaper = RoomShaper};
1641 {{value, {presence, From}}, _RoomQueue} ->
1642
:-(
Activity = get_user_activity(From, StateData),
1643
:-(
{_Nick, Packet} = Activity#activity.presence,
1644
:-(
Size = element_size(Packet),
1645
:-(
{RoomShaper, RoomShaperInterval} =
1646 mongoose_shaper:update(StateData#state.room_shaper, Size),
1647
:-(
erlang:send_after(
1648 RoomShaperInterval, self(),
1649 process_room_queue),
1650
:-(
StateData#state{
1651 room_shaper = RoomShaper};
1652 {empty, _} ->
1653
:-(
StateData
1654 end.
1655
1656 -spec is_first_session(mod_muc:nick(), state()) -> boolean().
1657 is_first_session(Nick, StateData) ->
1658 110 case maps:find(Nick, StateData#state.sessions) of
1659
:-(
{ok, _Val} -> false;
1660 110 error -> true
1661 end.
1662
1663 -spec is_last_session(mod_muc:nick(), state()) -> boolean().
1664 is_last_session(Nick, StateData) ->
1665 188 case maps:find(Nick, StateData#state.sessions) of
1666 188 {ok, [_Val]} -> true;
1667
:-(
_ -> false
1668 end.
1669
1670 -spec add_online_user(jid:jid(), mod_muc:nick(), mod_muc:role(), state())
1671 -> state().
1672 add_online_user(JID, Nick, Role, StateData) ->
1673 110 LJID = jid:to_lower(JID),
1674 110 Sessions = maps_append(Nick, JID, StateData#state.sessions),
1675 110 Info = #user{jid = JID,
1676 nick = Nick,
1677 role = Role},
1678 110 Users = maps:put(LJID, Info, StateData#state.users),
1679 110 case is_first_session(Nick, StateData) of
1680 true ->
1681 110 add_to_log(join, Nick, StateData),
1682 110 tab_add_online_user(JID, StateData),
1683 110 run_join_room_hook(JID, StateData);
1684 _ ->
1685
:-(
ok
1686 end,
1687 110 notify_users_modified(StateData#state{users = Users, sessions = Sessions}).
1688
1689 -spec run_join_room_hook(jid:jid(), state()) -> ok.
1690 run_join_room_hook(JID, #state{room = Room, host = Host, jid = MucJID, server_host = ServerHost}) ->
1691 110 mongoose_hooks:join_room(ServerHost, Room, Host, JID, MucJID),
1692 110 ok.
1693
1694 -spec remove_online_user(jid:jid(), state()) -> state().
1695 remove_online_user(JID, StateData) ->
1696
:-(
remove_online_user(JID, StateData, <<>>).
1697
1698 -spec remove_online_user(jid:jid(), state(), Reason :: binary()) -> state().
1699 remove_online_user(JID, StateData, Reason) ->
1700
1701 94 LJID = jid:to_lower(JID),
1702 94 {ok, #user{nick = Nick}} =
1703 maps:find(LJID, StateData#state.users),
1704 94 Sessions = case is_last_session(Nick, StateData) of
1705 true ->
1706 94 add_to_log(leave, {Nick, Reason}, StateData),
1707 94 tab_remove_online_user(JID, StateData),
1708 94 run_leave_room_hook(JID, StateData),
1709 94 maps:remove(Nick, StateData#state.sessions);
1710 false ->
1711
:-(
IsOtherLJID = fun(J) -> jid:to_lower(J) /= LJID end,
1712
:-(
F = fun (JIDs) -> lists:filter(IsOtherLJID, JIDs) end,
1713
:-(
maps:update_with(Nick, F, StateData#state.sessions)
1714 end,
1715 94 Users = maps:remove(LJID, StateData#state.users),
1716
1717 94 notify_users_modified(StateData#state{users = Users, sessions = Sessions}).
1718
1719 -spec run_leave_room_hook(jid:jid(), state()) -> ok.
1720 run_leave_room_hook(JID, #state{room = Room, host = Host, jid = MucJID, server_host = ServerHost}) ->
1721 94 mongoose_hooks:leave_room(ServerHost, Room, Host, JID, MucJID),
1722 94 ok.
1723
1724 -spec filter_presence(exml:element()) -> exml:element().
1725 filter_presence(#xmlel{name = <<"presence">>, attrs = Attrs, children = Els}) ->
1726 204 FEls = lists:filter(
1727 fun(#xmlcdata{}) ->
1728
:-(
false;
1729 (#xmlel{attrs = Attrs1}) ->
1730 204 XMLNS = xml:get_attr_s(<<"xmlns">>, Attrs1),
1731 204 case XMLNS of
1732 110 <<?NS_MUC_S, _/binary>> -> false;
1733 94 _ -> true
1734 end
1735 end, Els),
1736 204 #xmlel{name = <<"presence">>, attrs = Attrs, children = FEls}.
1737
1738
1739 -spec strip_status(exml:element()) -> exml:element().
1740 strip_status(#xmlel{name = <<"presence">>, attrs = Attrs,
1741 children = Els}) ->
1742
:-(
FEls = lists:filter(
1743 fun(#xmlel{name = <<"status">>}) ->
1744
:-(
false;
1745
:-(
(_) -> true
1746 end, Els),
1747
:-(
#xmlel{name = <<"presence">>, attrs = Attrs, children = FEls}.
1748
1749
1750 -spec add_user_presence(jid:jid(), exml:element(), state()) -> state().
1751 add_user_presence(JID, Presence, StateData) ->
1752 110 LJID = jid:to_lower(JID),
1753 110 FPresence = filter_presence(Presence),
1754 110 Users =
1755 maps:update_with(
1756 LJID,
1757 fun(#user{} = User) ->
1758 110 User#user{last_presence = FPresence}
1759 end, StateData#state.users),
1760 110 notify_users_modified(StateData#state{users = Users}).
1761
1762
1763 -spec add_user_presence_un(jid:simple_jid() | jid:jid(), exml:element(),
1764 state()) -> state().
1765 add_user_presence_un(JID, Presence, StateData) ->
1766 94 LJID = jid:to_lower(JID),
1767 94 FPresence = filter_presence(Presence),
1768 94 Users =
1769 maps:update_with(
1770 LJID,
1771 fun(#user{} = User) ->
1772 94 User#user{last_presence = FPresence, role = none}
1773 end, StateData#state.users),
1774 94 notify_users_modified(StateData#state{users = Users}).
1775
1776
1777 -spec is_nick_exists(mod_muc:nick(), state()) -> boolean().
1778 is_nick_exists(Nick, StateData) ->
1779 118 maps:is_key(Nick, StateData#state.sessions).
1780
1781
1782 -spec find_jids_by_nick(mod_muc:nick(), state()) -> [jid:jid()].
1783 find_jids_by_nick(Nick, StateData) ->
1784 126 case maps:find(Nick, StateData#state.sessions) of
1785 118 error -> [];
1786 8 {ok, JIDs} -> JIDs
1787 end.
1788
1789 -spec is_new_nick_of_online_user(jid:simple_jid() | jid:jid(), mod_muc:nick(),
1790 state()) -> boolean() | user_is_offline.
1791 is_new_nick_of_online_user(JID, Nick, StateData) ->
1792 118 LJID = jid:to_lower(JID),
1793 118 case maps:find(LJID, StateData#state.users) of
1794
:-(
{ok, #user{nick = OldNick}} -> Nick /= <<>> andalso Nick /= OldNick;
1795 118 error -> user_is_offline
1796 end.
1797
1798 -spec is_user_limit_reached(jid:jid(), mod_muc:affiliation(), state()) -> boolean().
1799 is_user_limit_reached(From, Affiliation, StateData) ->
1800 118 MaxUsers = get_max_users(StateData),
1801 118 MaxAdminUsers = case MaxUsers of
1802
:-(
none -> none;
1803 118 _ -> MaxUsers + get_max_users_admin_threshold(StateData)
1804 end,
1805 118 NUsers = count_users(StateData),
1806 118 ServiceAffiliation = get_service_affiliation(From, StateData),
1807 118 NConferences = tab_count_user(From),
1808 118 MaxConferences = get_opt(StateData, max_user_conferences),
1809 118 (ServiceAffiliation == owner orelse
1810 118 MaxUsers == none orelse
1811 118 ((Affiliation == admin orelse Affiliation == owner) andalso
1812 53 NUsers < MaxAdminUsers) orelse
1813 65 NUsers < MaxUsers) andalso
1814 118 NConferences < MaxConferences.
1815
1816 is_next_session_of_occupant(From, Nick, StateData) ->
1817 118 IsAllowed = (StateData#state.config)#config.allow_multiple_sessions,
1818 118 case {IsAllowed, find_jids_by_nick(Nick, StateData)} of
1819 {false, _} ->
1820 118 false;
1821 {_, []} ->
1822
:-(
false;
1823 {true, Jids} ->
1824
:-(
lists:any(fun(Jid) ->
1825
:-(
From#jid.lserver == Jid#jid.lserver
1826
:-(
andalso From#jid.luser == Jid#jid.luser
1827 end, Jids)
1828 end.
1829
1830 -spec choose_new_user_strategy(jid:jid(), mod_muc:nick(),
1831 mod_muc:affiliation(), mod_muc:role(), [jlib:xmlcdata() | exml:element()],
1832 state()) -> new_user_strategy().
1833 choose_new_user_strategy(From, Nick, Affiliation, Role, Els, StateData) ->
1834 118 case {is_user_limit_reached(From, Affiliation, StateData),
1835 is_nick_exists(Nick, StateData),
1836 is_next_session_of_occupant(From, Nick, StateData),
1837 mod_muc:can_use_nick(StateData#state.host_type, StateData#state.host, From, Nick),
1838 Role,
1839 Affiliation} of
1840 {false, _, _, _, _, _} ->
1841
:-(
limit_reached;
1842 {_, _, _, _, none, outcast} ->
1843
:-(
user_banned;
1844 {_, _, _, _, none, _} ->
1845
:-(
require_membership;
1846 {_, true, false, _, _, _} ->
1847
:-(
conflict_use;
1848 {_, _, _, false, _, _} ->
1849
:-(
conflict_registered;
1850 _ ->
1851 118 choose_new_user_password_strategy(From, Els, StateData)
1852 end.
1853
1854 -spec choose_new_user_password_strategy(jid:jid(), [jlib:xmlcdata() | exml:element()],
1855 state()) -> new_user_strategy().
1856 choose_new_user_password_strategy(From, Els, StateData) ->
1857 118 ServiceAffiliation = get_service_affiliation(From, StateData),
1858 118 Config = StateData#state.config,
1859 118 case is_password_required(ServiceAffiliation, Config) of
1860 110 false -> allowed;
1861 8 true -> case extract_password(Els) of
1862 8 false -> require_password;
1863
:-(
Password -> check_password(StateData, Password)
1864 end
1865 end.
1866
1867 -spec add_new_user(jid:jid(), mod_muc:nick(), exml:element(), state()
1868 ) -> state().
1869 add_new_user(From, Nick, #xmlel{attrs = Attrs, children = Els} = Packet, StateData) ->
1870 118 Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
1871 118 Affiliation = get_affiliation(From, StateData),
1872 118 Role = get_default_role(Affiliation, StateData),
1873 118 case choose_new_user_strategy(From, Nick, Affiliation, Role, Els, StateData) of
1874 limit_reached ->
1875 % max user reached and user is not admin or owner
1876
:-(
Err = jlib:make_error_reply(Packet, mongoose_xmpp_errors:service_unavailable_wait()),
1877
:-(
route_error(Nick, From, Err, StateData);
1878 user_banned ->
1879
:-(
ErrText = <<"You have been banned from this room">>,
1880
:-(
Err = jlib:make_error_reply(Packet, mongoose_xmpp_errors:forbidden(Lang, ErrText)),
1881
:-(
route_error(Nick, From, Err, StateData);
1882 require_membership ->
1883
:-(
ErrText = <<"Membership is required to enter this room">>,
1884
:-(
Err = jlib:make_error_reply(
1885 Packet, mongoose_xmpp_errors:registration_required(Lang, ErrText)),
1886
:-(
route_error(Nick, From, Err, StateData);
1887 conflict_use ->
1888
:-(
ErrText = <<"That nickname is already in use by another occupant">>,
1889
:-(
Err = jlib:make_error_reply(Packet, mongoose_xmpp_errors:conflict(Lang, ErrText)),
1890
:-(
route_error(Nick, From, Err, StateData);
1891 conflict_registered ->
1892
:-(
ErrText = <<"That nickname is registered by another person">>,
1893
:-(
Err = jlib:make_error_reply(Packet, mongoose_xmpp_errors:conflict(Lang, ErrText)),
1894
:-(
route_error(Nick, From, Err, StateData);
1895 require_password ->
1896 8 ErrText = <<"A password is required to enter this room">>,
1897 8 Err = jlib:make_error_reply(
1898 Packet, mongoose_xmpp_errors:not_authorized(Lang, ErrText)),
1899 8 route_error(Nick, From, Err, StateData);
1900 invalid_password ->
1901
:-(
ErrText = <<"Incorrect password">>,
1902
:-(
Err = jlib:make_error_reply(
1903 Packet, mongoose_xmpp_errors:not_authorized(Lang, ErrText)),
1904
:-(
route_error(Nick, From, Err, StateData);
1905 http_auth ->
1906
:-(
Password = extract_password(Els),
1907
:-(
perform_http_auth(From, Nick, Packet, Role, Password, StateData);
1908 allowed ->
1909 110 do_add_new_user(From, Nick, Packet, Role, StateData)
1910 end.
1911
1912 perform_http_auth(From, Nick, Packet, Role, Password, StateData) ->
1913
:-(
RoomPid = self(),
1914
:-(
RoomJid = StateData#state.jid,
1915
:-(
Pool = StateData#state.http_auth_pool,
1916
:-(
case is_empty_room(StateData) of
1917 true ->
1918
:-(
Result = make_http_auth_request(From, RoomJid, Password, Pool),
1919
:-(
handle_http_auth_result(Result, From, Nick, Packet, Role, StateData);
1920 false ->
1921 %% Perform the request in a separate process to prevent room freeze
1922
:-(
Pid = proc_lib:spawn_link(
1923 fun() ->
1924
:-(
Result = make_http_auth_request(From, RoomJid, Password, Pool),
1925
:-(
gen_fsm_compat:send_event(RoomPid, {http_auth, self(), Result,
1926 From, Nick, Packet, Role})
1927 end),
1928
:-(
AuthPids = StateData#state.http_auth_pids,
1929
:-(
StateData#state{http_auth_pids = [Pid | AuthPids]}
1930 end.
1931
1932 make_http_auth_request(From, RoomJid, Password, Pool) ->
1933
:-(
Query = uri_string:compose_query(
1934 [{<<"from">>, jid:to_binary(From)},
1935 {<<"to">>, jid:to_binary(RoomJid)},
1936 {<<"pass">>, Password}
1937 ]),
1938
:-(
Path = <<"check_password", "?", Query/binary>>,
1939
:-(
case mongoose_http_client:get(global, Pool, Path, []) of
1940
:-(
{ok, {<<"200">>, Body}} -> decode_http_auth_response(Body);
1941
:-(
_ -> error
1942 end.
1943
1944 handle_http_auth_result(allowed, From, Nick, Packet, Role, StateData) ->
1945
:-(
do_add_new_user(From, Nick, Packet, Role, StateData);
1946 handle_http_auth_result({invalid_password, ErrorMsg}, From, Nick, Packet, _Role, StateData) ->
1947
:-(
reply_not_authorized(From, Nick, Packet, StateData, ErrorMsg);
1948 handle_http_auth_result(error, From, Nick, Packet, _Role, StateData) ->
1949
:-(
reply_service_unavailable(From, Nick, Packet, StateData, <<"Internal server error">>).
1950
1951 decode_http_auth_response(Body) ->
1952
:-(
try decode_json_auth_response(Body) of
1953 {0, _} ->
1954
:-(
allowed;
1955 {AuthCode, Msg} ->
1956
:-(
{invalid_password, iolist_to_binary([integer_to_list(AuthCode), $ , Msg])}
1957 catch
1958
:-(
error:_ -> error
1959 end.
1960
1961 decode_json_auth_response(Body) ->
1962
:-(
Elements = jiffy:decode(Body, [return_maps]),
1963
:-(
Code = maps:get(<<"code">>, Elements, undefined),
1964
:-(
Msg = maps:get(<<"msg">>, Elements, undefined),
1965
:-(
{Code, Msg}.
1966
1967 reply_not_authorized(From, Nick, Packet, StateData, ErrText) ->
1968
:-(
Lang = xml:get_attr_s(<<"xml:lang">>, Packet#xmlel.attrs),
1969
:-(
Err = jlib:make_error_reply(Packet, mongoose_xmpp_errors:not_authorized(Lang, ErrText)),
1970
:-(
route_error(Nick, From, Err, StateData).
1971
1972 reply_service_unavailable(From, Nick, Packet, StateData, ErrText) ->
1973
:-(
Lang = xml:get_attr_s(<<"xml:lang">>, Packet#xmlel.attrs),
1974
:-(
Err = jlib:make_error_reply(Packet, mongoose_xmpp_errors:service_unavailable(Lang, ErrText)),
1975
:-(
route_error(Nick, From, Err, StateData).
1976
1977 do_add_new_user(From, Nick, #xmlel{attrs = Attrs, children = Els} = Packet,
1978 Role, StateData) ->
1979 110 Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
1980 110 NewState =
1981 add_user_presence(
1982 From, Packet,
1983 add_online_user(From, Nick, Role, StateData)),
1984 110 send_existing_presences(From, NewState),
1985 110 send_new_presence(From, NewState),
1986 110 Shift = count_stanza_shift(Nick, Els, NewState),
1987 110 case send_history(From, Shift, NewState) of
1988 true ->
1989
:-(
ok;
1990 _ ->
1991 110 send_subject(From, Lang, StateData)
1992 end,
1993 110 case NewState#state.just_created of
1994 true ->
1995 51 NewState#state{just_created = false};
1996 false ->
1997 59 Robots = maps:remove(From, StateData#state.robots),
1998 59 NewState#state{robots = Robots}
1999 end.
2000
2001 is_password_required(owner, _Config) ->
2002 %% Don't check pass if user is owner in MUC service (access_admin option)
2003
:-(
false;
2004 is_password_required(_, Config) ->
2005 118 Config#config.password_protected.
2006
2007 check_password(#state{http_auth_pool = none,
2008 config = #config{password = Password}}, Password) ->
2009
:-(
allowed;
2010 check_password(#state{http_auth_pool = none}, _Password) ->
2011
:-(
?LOG_WARNING(#{what => muc_check_password_failed,
2012
:-(
text => <<"http_auth_pool not found">>}),
2013
:-(
invalid_password;
2014 check_password(#state{http_auth_pool = _Pool}, _Password) ->
2015
:-(
http_auth.
2016
2017 -spec extract_password([jlib:xmlcdata() | exml:element()]) -> 'false' | binary().
2018 extract_password([]) ->
2019
:-(
false;
2020 extract_password([#xmlel{attrs = Attrs} = El | Els]) ->
2021 8 case xml:get_attr_s(<<"xmlns">>, Attrs) of
2022 ?NS_MUC ->
2023 8 case xml:get_subtag(El, <<"password">>) of
2024 false ->
2025 8 false;
2026 SubEl ->
2027
:-(
xml:get_tag_cdata(SubEl)
2028 end;
2029 _ ->
2030
:-(
extract_password(Els)
2031 end;
2032 extract_password([_ | Els]) ->
2033
:-(
extract_password(Els).
2034
2035
2036 -spec count_stanza_shift(mod_muc:nick(), [jlib:xmlcdata() | exml:element()],
2037 state()) -> any().
2038 count_stanza_shift(Nick, Els, StateData) ->
2039 110 HL = lqueue_to_list(StateData#state.history),
2040 110 Since = extract_history(Els, <<"since">>),
2041 110 Shift0 = case Since of
2042 false ->
2043 110 0;
2044 _ ->
2045
:-(
count_seconds_shift(Since, HL)
2046 end,
2047 110 Seconds = extract_history(Els, <<"seconds">>),
2048 110 Shift1 = case Seconds of
2049 false ->
2050 110 0;
2051 _ ->
2052
:-(
Sec = os:system_time(seconds) - Seconds,
2053
:-(
count_seconds_shift(Sec, HL)
2054 end,
2055 110 MaxStanzas = extract_history(Els, <<"maxstanzas">>),
2056 110 Shift2 = case MaxStanzas of
2057 false ->
2058 110 0;
2059 _ ->
2060
:-(
count_maxstanzas_shift(MaxStanzas, HL)
2061 end,
2062 110 MaxChars = extract_history(Els, <<"maxchars">>),
2063 110 Shift3 = case MaxChars of
2064 false ->
2065 110 0;
2066 _ ->
2067
:-(
count_maxchars_shift(Nick, MaxChars, HL)
2068 end,
2069 110 lists:max([Shift0, Shift1, Shift2, Shift3]).
2070
2071
2072 -spec count_seconds_shift(integer(), [any()]) -> number().
2073 count_seconds_shift(Seconds, HistoryList) ->
2074
:-(
lists:sum(
2075 lists:map(
2076 fun({_Nick, _Packet, _HaveSubject, TimeStamp, _Size}) ->
2077
:-(
case TimeStamp < Seconds of
2078
:-(
true -> 1;
2079
:-(
false -> 0
2080 end
2081 end, HistoryList)).
2082
2083
2084 -spec count_maxstanzas_shift(non_neg_integer(), [any()]) -> integer().
2085 count_maxstanzas_shift(MaxStanzas, HistoryList) ->
2086
:-(
S = length(HistoryList) - MaxStanzas,
2087
:-(
max(0, S).
2088
2089
2090 -spec count_maxchars_shift(mod_muc:nick(), non_neg_integer(),
2091 [any()]) -> non_neg_integer().
2092 count_maxchars_shift(Nick, MaxSize, HistoryList) ->
2093
:-(
NLen = string:len(binary_to_list(Nick)) + 1,
2094
:-(
Sizes = lists:map(
2095 fun({_Nick, _Packet, _HaveSubject, _TimeStamp, Size}) ->
2096
:-(
Size + NLen
2097 end, HistoryList),
2098
:-(
calc_shift(MaxSize, Sizes).
2099
2100
2101 -spec calc_shift(non_neg_integer(), [number()]) -> non_neg_integer().
2102 calc_shift(MaxSize, Sizes) ->
2103
:-(
Total = lists:sum(Sizes),
2104
:-(
calc_shift(MaxSize, Total, 0, Sizes).
2105
2106
2107 -spec calc_shift(_MaxSize :: non_neg_integer(),
2108 _Size :: number(), Shift :: non_neg_integer(), TSizes :: [number()]
2109 ) -> non_neg_integer().
2110 calc_shift(_MaxSize, _Size, Shift, []) ->
2111
:-(
Shift;
2112 calc_shift(MaxSize, Size, Shift, _Sizes) when MaxSize >= Size ->
2113
:-(
Shift;
2114 calc_shift(MaxSize, Size, Shift, [S | TSizes]) ->
2115
:-(
calc_shift(MaxSize, Size - S, Shift + 1, TSizes).
2116
2117
2118 -spec extract_history([jlib:xmlcdata() | exml:element()], Type :: binary()) ->
2119 false | non_neg_integer().
2120 extract_history([], _Type) ->
2121
:-(
false;
2122 extract_history([#xmlel{attrs = Attrs} = El | Els], Type) ->
2123 440 case xml:get_attr_s(<<"xmlns">>, Attrs) of
2124 ?NS_MUC ->
2125 440 parse_history_val(xml:get_path_s(El, [{elem, <<"history">>}, {attr, Type}]), Type);
2126 _ ->
2127
:-(
extract_history(Els, Type)
2128 end;
2129 extract_history([_ | Els], Type) ->
2130
:-(
extract_history(Els, Type).
2131
2132 -spec parse_history_val(binary(), binary()) -> false | non_neg_integer().
2133 parse_history_val(AttrVal, <<"since">>) ->
2134 110 case catch calendar:rfc3339_to_system_time(binary_to_list(AttrVal)) of
2135 IntVal when is_integer(IntVal) and (IntVal >= 0) ->
2136
:-(
IntVal;
2137 _ ->
2138 110 false
2139 end;
2140 parse_history_val(AttrVal, _) ->
2141 330 case catch binary_to_integer(AttrVal) of
2142 IntVal when is_integer(IntVal) and (IntVal >= 0) ->
2143
:-(
IntVal;
2144 _ ->
2145 330 false
2146 end.
2147
2148 -spec send_update_presence(jid:jid(), Reason :: binary(), state()) -> any().
2149 send_update_presence(JID, Reason, StateData) ->
2150
:-(
foreach_matched_jid(fun(J) ->
2151
:-(
send_new_presence(J, Reason, StateData)
2152 end, JID, StateData).
2153
2154
2155 -spec foreach_matched_jid(fun((_) -> 'ok'), jid:jid(), state()) -> ok.
2156 foreach_matched_jid(F, JID, #state{users=Users}) ->
2157
:-(
LJID = jid:to_lower(JID),
2158
:-(
case LJID of
2159 %% Match by bare JID
2160 {U, S, <<>>} ->
2161
:-(
FF = fun({U0, S0, _}, #user{jid = MatchedJID})
2162 when U =:= U0, S =:= S0 ->
2163
:-(
F(MatchedJID);
2164
:-(
(_, _) -> ok
2165 end,
2166
:-(
maps_foreach(FF, Users);
2167 %% Match by full JID
2168 _ ->
2169
:-(
case maps:is_key(LJID, Users) of
2170 true ->
2171
:-(
F(JID),
2172
:-(
ok;
2173 false ->
2174
:-(
ok
2175 end
2176 end.
2177
2178
2179 -spec foreach_matched_user(fun((_) -> 'ok'), jid:simple_jid() | jid:jid(),
2180 state()) -> ok.
2181 foreach_matched_user(F, JID, #state{users=Users}) ->
2182
:-(
LJID = jid:to_lower(JID),
2183
:-(
case LJID of
2184 %% Match by bare JID
2185 {U, S, <<>>} ->
2186
:-(
FF = fun({U0, S0, _}, User) when U =:= U0, S =:= S0 ->
2187
:-(
F(User);
2188
:-(
(_, _) -> ok
2189 end,
2190
:-(
maps_foreach(FF, Users);
2191 %% Match by full JID
2192 _ ->
2193
:-(
case maps:find(LJID, Users) of
2194
:-(
{ok, User} -> F(User);
2195
:-(
error -> ok
2196 end
2197 end.
2198
2199
2200 -spec foreach_user(fun((_) -> 'ok'), state()) -> any().
2201 foreach_user(F, #state{users=Users}) ->
2202 65 map_foreach_value(F, Users).
2203
2204
2205 -spec erase_matched_users(jid:simple_jid() | jid:jid(), state()) -> state().
2206 erase_matched_users(JID, StateData=#state{users=Users, sessions=Sessions}) ->
2207
:-(
LJID = jid:to_lower(JID),
2208
:-(
{NewUsers, NewSessions} = erase_matched_users_map(LJID, Users, Sessions),
2209
:-(
notify_users_modified(StateData#state{users=NewUsers, sessions=NewSessions}).
2210
2211
2212 -spec erase_matched_users_map(error | jid:simple_jid(),
2213 users_map(), sessions_map()) -> any().
2214 erase_matched_users_map({U, S, <<>>}, Users, Sessions) ->
2215
:-(
FF = fun({U0, S0, _} = J, #user{nick=Nick}, {Us, Ss}) when U =:= U0 andalso S =:= S0->
2216
:-(
{maps:remove(J, Us), maps:remove(Nick, Ss)};
2217 (_, _, Acc) ->
2218
:-(
Acc
2219 end,
2220
:-(
maps:fold(FF, {Users, Sessions}, Users);
2221 erase_matched_users_map(LJID, Users, Sessions) ->
2222
:-(
{ok, #user{nick=Nick}} = maps:find(LJID, Users),
2223
:-(
{maps:remove(LJID, Users), maps:remove(Nick, Sessions)}.
2224
2225
2226 -spec update_matched_users(F :: fun((user()) -> user()), JID :: jid:jid(),
2227 state()) -> state().
2228 update_matched_users(F, JID, StateData=#state{users=Users}) ->
2229
:-(
LJID = jid:to_lower(JID),
2230
:-(
NewUsers = update_matched_users_map(F, LJID, Users),
2231
:-(
notify_users_modified(StateData#state{users=NewUsers}).
2232
2233
2234 -spec update_matched_users_map(fun((user()) -> user()),
2235 error | jid:simple_jid(), users_map()) -> any().
2236 update_matched_users_map(F, {U, S, <<>>}, Users) ->
2237
:-(
FF = fun({U0, S0, _} = J, User, Us) when U =:= U0 andalso S =:= S0->
2238
:-(
maps:put(J, F(User), Us);
2239 (_, _, Us) ->
2240
:-(
Us
2241 end,
2242
:-(
maps:fold(FF, Users, Users);
2243 update_matched_users_map(F, LJID, Users) ->
2244
:-(
case maps:find(LJID, Users) of
2245
:-(
{ok, User} -> maps:put(LJID, F(User), Users);
2246
:-(
error -> Users
2247 end.
2248
2249 -spec send_new_presence_un(jid:jid(), state()) -> 'ok'.
2250 send_new_presence_un(NJID, StateData) ->
2251 94 send_new_presence_un(NJID, <<>>, StateData).
2252
2253
2254 -spec send_new_presence_un(jid:jid(), binary(), state()) -> 'ok'.
2255 send_new_presence_un(NJID, Reason, StateData) ->
2256 94 {ok, #user{nick = Nick}} = maps:find(jid:to_lower(NJID), StateData#state.users),
2257 94 case is_last_session(Nick, StateData) of
2258 true ->
2259 94 send_new_presence(NJID, Reason, StateData);
2260 false ->
2261
:-(
UserJIDs = maps:get(Nick, StateData#state.sessions),
2262
:-(
GetUserTupleByJID = fun(JID) ->
2263
:-(
LJID = jid:to_lower(JID),
2264
:-(
{LJID, maps:get(LJID, StateData#state.users)}
2265 end,
2266
:-(
CurrentSessionUsers = lists:map(GetUserTupleByJID, UserJIDs),
2267
:-(
send_new_presence_to(NJID, Reason, CurrentSessionUsers, StateData)
2268 end.
2269
2270
2271 -spec send_new_presence(jid:jid(), state()) -> 'ok'.
2272 send_new_presence(NJID, StateData) ->
2273 110 send_new_presence(NJID, <<>>, StateData).
2274
2275
2276 -spec send_new_presence(jid:jid(), binary(), state()) -> 'ok'.
2277 send_new_presence(NJID, Reason, StateData) ->
2278 204 send_new_presence_to(NJID, Reason, StateData#state.users, StateData).
2279
2280
2281 %% Receivers can be a list or a map
2282 -spec send_new_presence_to(jid:jid(), binary(), users_map() | users_pairs(), state()) -> ok.
2283 send_new_presence_to(NJID, Reason, Receivers, StateData) ->
2284 204 {ok, #user{ role = Role } = User} = maps:find(jid:to_lower(NJID), StateData#state.users),
2285 204 Affiliation = get_affiliation(NJID, StateData),
2286 204 BAffiliation = affiliation_to_binary(Affiliation),
2287 204 BRole = role_to_binary(Role),
2288 204 F = fun(_LJID, Info) ->
2289 324 send_new_presence_to_single(NJID, User, BAffiliation, BRole, Reason, Info, StateData)
2290 end,
2291 204 maps_or_pairs_foreach(F, Receivers).
2292
2293 send_new_presence_to_single(NJID, #user{jid = RealJID, nick = Nick, last_presence = Presence},
2294 BAffiliation, BRole, Reason, ReceiverInfo, StateData) ->
2295 324 ItemAttrs =
2296 139 case (ReceiverInfo#user.role == moderator) orelse
2297 185 ((StateData#state.config)#config.anonymous == false) of
2298 true ->
2299 256 [{<<"jid">>, jid:to_binary(RealJID)},
2300 {<<"affiliation">>, BAffiliation},
2301 {<<"role">>, BRole}];
2302 _ ->
2303 68 [{<<"affiliation">>, BAffiliation},
2304 {<<"role">>, BRole}]
2305 end,
2306 324 ItemEls = case Reason of
2307 <<>> ->
2308 324 [];
2309 _ ->
2310
:-(
[#xmlel{name = <<"reason">>, children = [#xmlcdata{content = Reason}]}]
2311 end,
2312 324 Status = case StateData#state.just_created of
2313 true ->
2314 51 [status_code(201)];
2315 false ->
2316 273 []
2317 end,
2318 324 Status2 = case (NJID == ReceiverInfo#user.jid) of
2319 true ->
2320 204 Status0 = case (StateData#state.config)#config.logging of
2321 true ->
2322
:-(
[status_code(170) | Status];
2323 false ->
2324 204 Status
2325 end,
2326 204 Status1 = case ((StateData#state.config)#config.anonymous==false) of
2327 true ->
2328 140 [status_code(100) | Status0];
2329 false ->
2330 64 Status0
2331 end,
2332 204 case ((NJID == ReceiverInfo#user.jid)==true) of
2333 true ->
2334 204 [status_code(110) | Status1];
2335 false ->
2336
:-(
Status1
2337 end;
2338 false ->
2339 120 Status
2340 end,
2341 324 Packet = xml:append_subtags(
2342 Presence,
2343 [#xmlel{name = <<"x">>, attrs = [{<<"xmlns">>, ?NS_MUC}],
2344 children = [#xmlel{name = <<"item">>, attrs = ItemAttrs,
2345 children = ItemEls} | Status2]}]),
2346 324 ejabberd_router:route(jid:replace_resource(StateData#state.jid, Nick),
2347 ReceiverInfo#user.jid, Packet).
2348
2349 -spec send_existing_presences(jid:jid(), state()) -> 'ok'.
2350 send_existing_presences(ToJID, StateData) ->
2351 110 LToJID = jid:to_lower(ToJID),
2352 110 {ok, #user{jid = RealToJID, role = Role, nick = _Nick}} =
2353 maps:find(LToJID, StateData#state.users),
2354 % if you don't want to send presences of other sessions of occupant with ToJID
2355 % switch following lines
2356 % JIDsToSkip = [RealToJID | find_jids_by_nick(_Nick, StateData)],
2357 110 JIDsToSkip = [RealToJID],
2358 110 maps_foreach(
2359 fun({_, #user{jid = FromJID}} = User) ->
2360 177 case lists:member(FromJID, JIDsToSkip) of
2361 110 true -> ok;
2362 67 _ -> send_existing_presence(User, Role, RealToJID, StateData)
2363 end
2364 end, StateData#state.users).
2365
2366 -spec send_existing_presence({jid:simple_jid(), mod_muc_room_user()}, mod_muc:role(),
2367 jid:jid(), state()) -> mongoose_acc:t().
2368 send_existing_presence({_LJID, #user{jid = FromJID, nick = FromNick,
2369 role = FromRole, last_presence = Presence}},
2370 Role, RealToJID, StateData) ->
2371 67 FromAffiliation = get_affiliation(FromJID, StateData),
2372 67 ItemAttrs =
2373 67 case (Role == moderator) orelse ((StateData#state.config)#config.anonymous == false) of
2374 true ->
2375 43 [{<<"jid">>, jid:to_binary(FromJID)},
2376 {<<"affiliation">>,
2377 affiliation_to_binary(FromAffiliation)},
2378 {<<"role">>, role_to_binary(FromRole)}];
2379 _ ->
2380 24 [{<<"affiliation">>,
2381 affiliation_to_binary(FromAffiliation)},
2382 {<<"role">>, role_to_binary(FromRole)}]
2383 end,
2384 67 Packet = xml:append_subtags(
2385 Presence,
2386 [#xmlel{name = <<"x">>,
2387 attrs = [{<<"xmlns">>, ?NS_MUC_USER}],
2388 children = [#xmlel{name = <<"item">>,
2389 attrs = ItemAttrs}]}]),
2390 67 ejabberd_router:route(jid:replace_resource(StateData#state.jid, FromNick), RealToJID, Packet).
2391
2392 -spec send_config_update(atom(), state()) -> 'ok'.
2393 send_config_update(Type, StateData) ->
2394
:-(
Status = case Type of
2395
:-(
logging_enabled -> <<"170">>;
2396
:-(
logging_disabled -> <<"171">>;
2397
:-(
nonanonymous -> <<"172">>;
2398
:-(
semianonymous -> <<"173">>
2399 end,
2400
:-(
Message = jlib:make_config_change_message(Status),
2401
:-(
send_to_all_users(Message, StateData).
2402
2403
2404 -spec send_invitation(jid:jid(), jid:jid(), binary(), state()) -> mongoose_acc:t().
2405 send_invitation(From, To, Reason, StateData=#state{host=Host,
2406 server_host=ServerHost,
2407 jid=RoomJID}) ->
2408
:-(
mongoose_hooks:invitation_sent(Host, ServerHost, RoomJID, From, To, Reason),
2409
:-(
Config = StateData#state.config,
2410
:-(
Password = case Config#config.password_protected of
2411
:-(
false -> <<>>;
2412
:-(
true -> Config#config.password
2413 end,
2414
:-(
Packet = jlib:make_invitation(jid:to_bare(From), Password, Reason),
2415
:-(
ejabberd_router:route(RoomJID, To, Packet).
2416
2417
2418 -spec change_nick(jid:jid(), binary(), state()) -> state().
2419 change_nick(JID, Nick, StateData) ->
2420
:-(
LJID = jid:to_lower(JID),
2421
:-(
{ok, #user{nick = OldNick}} =
2422 maps:find(LJID, StateData#state.users),
2423
:-(
Users =
2424 maps:update_with(
2425 LJID,
2426 fun(#user{} = User) ->
2427
:-(
User#user{nick = Nick}
2428 end, StateData#state.users),
2429
:-(
{ok, JIDs} = maps:find(OldNick, StateData#state.sessions),
2430
:-(
Sessions = maps:remove(OldNick, maps:put(Nick, JIDs, StateData#state.sessions)),
2431
:-(
NewStateData = notify_users_modified(StateData#state{users = Users, sessions = Sessions}),
2432
:-(
send_nick_changing(JID, OldNick, NewStateData),
2433
:-(
add_to_log(nickchange, {OldNick, Nick}, StateData),
2434
:-(
NewStateData.
2435
2436
2437 -spec send_nick_changing(jid:jid(), mod_muc:nick(), state()) -> 'ok'.
2438 send_nick_changing(JID, OldNick, StateData) ->
2439
:-(
User = maps:find(jid:to_lower(JID), StateData#state.users),
2440
:-(
{ok, #user{jid = RealJID,
2441 nick = Nick,
2442 role = Role,
2443 last_presence = Presence}} = User,
2444
:-(
Affiliation = get_affiliation(JID, StateData),
2445
:-(
maps_foreach(mk_send_nick_change(Presence, OldNick, JID, RealJID,
2446 Affiliation, Role, Nick, StateData),
2447 StateData#state.users).
2448
2449 mk_send_nick_change(Presence, OldNick, JID, RealJID, Affiliation,
2450 Role, Nick, StateData) ->
2451
:-(
fun({LJID, Info}) ->
2452
:-(
send_nick_change(Presence, OldNick, JID, RealJID, Affiliation,
2453 Role, Nick, LJID, Info, StateData)
2454 end.
2455
2456 send_nick_change(Presence, OldNick, JID, RealJID, Affiliation, Role,
2457 Nick, _LJID, Info, #state{} = S) ->
2458
:-(
MaybePublicJID = case is_nick_change_public(Info, S#state.config) of
2459
:-(
true -> RealJID;
2460
:-(
false -> undefined
2461 end,
2462
:-(
MaybeSelfPresenceCode = case JID == Info#user.jid of
2463
:-(
true -> status_code(110);
2464
:-(
false -> undefined
2465 end,
2466
:-(
Unavailable = nick_unavailable_presence(MaybePublicJID, Nick, Affiliation,
2467 Role, MaybeSelfPresenceCode),
2468
:-(
ejabberd_router:route(jid:replace_resource(S#state.jid, OldNick),
2469 Info#user.jid, Unavailable),
2470
:-(
Available = nick_available_presence(Presence, MaybePublicJID, Affiliation,
2471 Role, MaybeSelfPresenceCode),
2472
:-(
ejabberd_router:route(jid:replace_resource(S#state.jid, Nick),
2473 Info#user.jid, Available).
2474
2475 -spec is_nick_change_public(user(), config()) -> boolean().
2476 is_nick_change_public(UserInfo, RoomConfig) ->
2477
:-(
UserInfo#user.role == moderator
2478 orelse
2479
:-(
RoomConfig#config.anonymous == false.
2480
2481 -spec status_code(integer()) -> exml:element().
2482 status_code(Code) ->
2483 395 #xmlel{name = <<"status">>,
2484 attrs = [{<<"code">>, integer_to_binary(Code)}]}.
2485
2486 -spec nick_unavailable_presence(MaybeJID, Nick, Affiliation, Role, MaybeCode) ->
2487 exml:element() when
2488 MaybeJID :: 'undefined' | jid:jid(),
2489 Nick :: mod_muc:nick(),
2490 Affiliation :: mod_muc:affiliation(),
2491 Role :: mod_muc:role(),
2492 MaybeCode :: 'undefined' | exml:element().
2493 nick_unavailable_presence(MaybeJID, Nick, Affiliation, Role, MaybeCode) ->
2494
:-(
presence(<<"unavailable">>,
2495 [muc_user_x([muc_user_item(MaybeJID, Nick, Affiliation, Role),
2496 status_code(303)]
2497
:-(
++ [MaybeCode || MaybeCode /= undefined])]).
2498
2499 -spec nick_available_presence(LastPresence, MaybeJID, Affiliation,
2500 Role, MaybeCode) -> exml:element() when
2501 LastPresence :: exml:element(),
2502 MaybeJID :: 'undefined' | jid:jid(),
2503 Affiliation :: mod_muc:affiliation(),
2504 Role :: mod_muc:role(),
2505 MaybeCode :: 'undefined' | exml:element().
2506 nick_available_presence(LastPresence, MaybeJID, Affiliation, Role, MaybeCode) ->
2507
:-(
Item = muc_user_item(MaybeJID, undefined, Affiliation, Role),
2508
:-(
xml:append_subtags(LastPresence,
2509
:-(
[muc_user_x([Item] ++ [MaybeCode
2510
:-(
|| MaybeCode /= undefined])]).
2511
2512 -spec muc_user_item(MaybeJID, MaybeNick, Affiliation, Role) -> exml:element() when
2513 MaybeJID :: 'undefined' | jid:jid(),
2514 MaybeNick :: 'undefined' | mod_muc:nick(),
2515 Affiliation :: mod_muc:affiliation(),
2516 Role :: mod_muc:role().
2517 muc_user_item(MaybeJID, MaybeNick, Affiliation, Role) ->
2518
:-(
#xmlel{name = <<"item">>,
2519
:-(
attrs = [{<<"jid">>, jid:to_binary(MaybeJID)}
2520
:-(
|| MaybeJID /= undefined] ++
2521
:-(
[{<<"nick">>, MaybeNick} || MaybeNick /= undefined] ++
2522 [{<<"affiliation">>, affiliation_to_binary(Affiliation)},
2523 {<<"role">>, role_to_binary(Role)}]}.
2524
2525 -spec muc_user_x([exml:element()]) -> exml:element().
2526 muc_user_x(Children) ->
2527
:-(
#xmlel{name = <<"x">>,
2528 attrs = [{<<"xmlns">>, ?NS_MUC_USER}],
2529 children = Children}.
2530
2531 -spec presence(binary(), [exml:element()]) -> exml:element().
2532 %% Add and validate other types if need be.
2533 presence(<<"unavailable">> = Type, Children) ->
2534
:-(
#xmlel{name = <<"presence">>,
2535
:-(
attrs = [{<<"type">>, Type} || Type /= <<"available">>],
2536 children = Children}.
2537
2538
2539 -spec lqueue_new(integer()) -> lqueue().
2540 lqueue_new(Max) ->
2541 95 #lqueue{queue = queue:new(),
2542 len = 0,
2543 max = Max}.
2544
2545
2546 %% @doc If the message queue limit is set to 0, do not store messages.
2547 %% Otherwise, rotate messages in the queue store.
2548 -spec lqueue_in(any(), lqueue()) -> lqueue().
2549 lqueue_in(_Item, LQ = #lqueue{max = 0}) ->
2550
:-(
LQ;
2551 lqueue_in(Item, #lqueue{queue = Q1, len = Len, max = Max}) ->
2552 91 Q2 = queue:in(Item, Q1),
2553 91 case Len >= Max of
2554 true ->
2555
:-(
Q3 = lqueue_cut(Q2, Len - Max + 1),
2556
:-(
#lqueue{queue = Q3, len = Max, max = Max};
2557 false ->
2558 91 #lqueue{queue = Q2, len = Len + 1, max = Max}
2559 end.
2560
2561
2562 -spec lqueue_cut(queue:queue(), non_neg_integer()) -> queue:queue().
2563 lqueue_cut(Q, 0) ->
2564
:-(
Q;
2565 lqueue_cut(Q, N) ->
2566
:-(
{_, Q1} = queue:out(Q),
2567
:-(
lqueue_cut(Q1, N - 1).
2568
2569
2570 -spec lqueue_to_list(lqueue()) -> [any()].
2571 lqueue_to_list(#lqueue{queue = Q1}) ->
2572 220 queue:to_list(Q1).
2573
2574
2575 -spec add_message_to_history(mod_muc:nick(), jid:jid(), exml:element(),
2576 state()) -> state().
2577 add_message_to_history(FromNick, FromJID, Packet, StateData) ->
2578 91 HaveSubject = case xml:get_subtag(Packet, <<"subject">>) of
2579 false ->
2580 91 false;
2581 _ ->
2582
:-(
true
2583 end,
2584 91 SystemTime = os:system_time(second),
2585 91 TimeStamp = calendar:system_time_to_rfc3339(SystemTime, [{offset, "Z"}]),
2586 %% Chatroom history is stored as XMPP packets, so
2587 %% the decision to include the original sender's JID or not is based on the
2588 %% chatroom configuration when the message was originally sent.
2589 %% Also, if the chatroom is anonymous, even moderators will not get the real JID
2590 91 SenderJid = case (StateData#state.config)#config.anonymous of
2591 24 true -> StateData#state.jid;
2592 67 false -> FromJID
2593 end,
2594 91 TSPacket = xml:append_subtags(Packet, [jlib:timestamp_to_xml(TimeStamp, SenderJid, <<>>)]),
2595 91 SPacket = jlib:replace_from_to(
2596 jid:replace_resource(StateData#state.jid, FromNick),
2597 StateData#state.jid,
2598 TSPacket),
2599 91 Size = element_size(SPacket),
2600 91 Q1 = lqueue_in({FromNick, TSPacket, HaveSubject, SystemTime, Size},
2601 StateData#state.history),
2602 91 add_to_log(text, {FromNick, Packet}, StateData),
2603 91 mongoose_hooks:room_packet(StateData#state.host,
2604 FromNick, FromJID, StateData#state.jid, Packet),
2605 91 StateData#state{history = Q1}.
2606
2607
2608 -spec send_history(jid:jid(), Shift :: non_neg_integer(), state()) -> boolean().
2609 send_history(JID, Shift, StateData) ->
2610 110 lists:foldl(
2611 fun({Nick, Packet, HaveSubject, _TimeStamp, _Size}, B) ->
2612
:-(
ejabberd_router:route(
2613 jid:replace_resource(StateData#state.jid, Nick),
2614 JID,
2615 Packet),
2616
:-(
B or HaveSubject
2617 end, false, lists:nthtail(Shift, lqueue_to_list(StateData#state.history))).
2618
2619
2620 -spec send_subject(jid:jid(), ejabberd:lang(), state()) -> mongoose_acc:t().
2621 send_subject(JID, _Lang, StateData = #state{subject = <<>>, subject_author = <<>>}) ->
2622 110 Packet = #xmlel{name = <<"message">>,
2623 attrs = [{<<"type">>, <<"groupchat">>}],
2624 children = [#xmlel{name = <<"subject">>},
2625 #xmlel{name = <<"body">>}]},
2626 110 ejabberd_router:route(
2627 StateData#state.jid,
2628 JID,
2629 Packet);
2630 send_subject(JID, _Lang, StateData) ->
2631
:-(
Subject = StateData#state.subject,
2632
:-(
TimeStamp = StateData#state.subject_timestamp,
2633
:-(
RoomJID = StateData#state.jid,
2634
:-(
Packet = #xmlel{name = <<"message">>,
2635 attrs = [{<<"type">>, <<"groupchat">>}],
2636 children = [#xmlel{name = <<"subject">>,
2637 children = [#xmlcdata{content = Subject}]},
2638 #xmlel{name = <<"delay">>,
2639 attrs = [{<<"xmlns">>, ?NS_DELAY},
2640 {<<"from">>, jid:to_binary(RoomJID)},
2641 {<<"stamp">>, TimeStamp}]}]},
2642
:-(
ejabberd_router:route(RoomJID, JID, Packet).
2643
2644
2645 -spec check_subject(exml:element()) -> 'false' | binary().
2646 check_subject(Packet) ->
2647 91 case xml:get_subtag(Packet, <<"subject">>) of
2648 false ->
2649 91 false;
2650 SubjEl ->
2651
:-(
xml:get_tag_cdata(SubjEl)
2652 end.
2653
2654
2655 -spec can_change_subject(mod_muc:role(), state()) -> boolean().
2656 can_change_subject(Role, StateData) ->
2657
:-(
case (StateData#state.config)#config.allow_change_subj of
2658 true ->
2659
:-(
(Role == moderator) orelse (Role == participant);
2660 _ ->
2661
:-(
Role == moderator
2662 end.
2663
2664 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2665 % Admin stuff
2666
2667 -spec process_iq_admin(jid:jid(), get | set, ejabberd:lang(), exml:element(), state()) ->
2668 state() | {error, exml:element()}.
2669 process_iq_admin(From, set, Lang, SubEl, StateData) ->
2670
:-(
#xmlel{children = Items} = SubEl,
2671
:-(
process_admin_items_set(From, Items, Lang, StateData);
2672 process_iq_admin(From, get, Lang, SubEl, StateData) ->
2673
:-(
case xml:get_subtag(SubEl, <<"item">>) of
2674 false ->
2675
:-(
{error, mongoose_xmpp_errors:bad_request()};
2676 Item ->
2677
:-(
FAffiliation = get_affiliation(From, StateData),
2678
:-(
FRole = get_role(From, StateData),
2679
:-(
{RoleOrAff, _} = ExtractResult = extract_role_or_affiliation(Item),
2680
:-(
IsAllowed = iq_admin_allowed(get, RoleOrAff, FAffiliation, FRole, StateData),
2681
:-(
case {IsAllowed, ExtractResult} of
2682 {true, {role, Role}} ->
2683
:-(
Items = items_with_role(Role, StateData),
2684
:-(
{result, Items, StateData};
2685 {true, {affiliation, Affiliation}} ->
2686
:-(
Items = items_with_affiliation(Affiliation, StateData),
2687
:-(
{result, Items, StateData};
2688 {_, {role, _}} ->
2689
:-(
ErrText = <<"Moderator privileges required">>,
2690
:-(
{error, mongoose_xmpp_errors:forbidden(Lang, ErrText)};
2691 {_, {affiliation, _}} ->
2692
:-(
ErrText = <<"Administrator privileges required">>,
2693
:-(
{error, mongoose_xmpp_errors:forbidden(Lang, ErrText)};
2694 {_, Error} ->
2695
:-(
Error
2696 end
2697 end.
2698
2699 -spec extract_role_or_affiliation(Item :: exml:element()) ->
2700 {role, mod_muc:role()} | {affiliation, mod_muc:affiliation()} | {error, exml:element()}.
2701 extract_role_or_affiliation(Item) ->
2702
:-(
case {xml:get_tag_attr(<<"role">>, Item), xml:get_tag_attr(<<"affiliation">>, Item)} of
2703 {false, false} ->
2704
:-(
{error, mongoose_xmpp_errors:bad_request()};
2705 {false, {value, BAffiliation}} ->
2706
:-(
case catch binary_to_affiliation(BAffiliation) of
2707
:-(
{'EXIT', _} -> {error, mongoose_xmpp_errors:bad_request()};
2708
:-(
Affiliation -> {affiliation, Affiliation}
2709 end;
2710 {{value, BRole}, _} ->
2711
:-(
case catch binary_to_role(BRole) of
2712
:-(
{'EXIT', _} -> {error, mongoose_xmpp_errors:bad_request()};
2713
:-(
Role -> {role, Role}
2714 end
2715 end.
2716
2717 -spec iq_admin_allowed(atom(), atom(), atom(), atom(), state()) -> boolean().
2718 iq_admin_allowed(get, What, FAff, none, State) ->
2719 %% no role is translated to 'visitor'
2720
:-(
iq_admin_allowed(get, What, FAff, visitor, State);
2721 iq_admin_allowed(get, role, _, moderator, _) ->
2722 %% moderator is allowed by definition, needs it to do his duty
2723
:-(
true;
2724 iq_admin_allowed(get, role, _, Role, State) ->
2725
:-(
Cfg = State#state.config,
2726
:-(
lists:member(Role, Cfg#config.maygetmemberlist);
2727 iq_admin_allowed(get, affiliation, owner, _, _) ->
2728
:-(
true;
2729 iq_admin_allowed(get, affiliation, admin, _, _) ->
2730
:-(
true;
2731 iq_admin_allowed(get, affiliation, _, Role, State) ->
2732
:-(
Cfg = State#state.config,
2733
:-(
lists:member(Role, Cfg#config.maygetmemberlist).
2734
2735
2736 -spec items_with_role(mod_muc:role(), state()) -> [exml:element()].
2737 items_with_role(BRole, StateData) ->
2738
:-(
lists:map(
2739 fun({_, U}) ->
2740
:-(
user_to_item(U, StateData)
2741 end, search_role(BRole, StateData)).
2742
2743
2744 -spec items_with_affiliation(mod_muc:affiliation(), state()) -> [exml:element()].
2745 items_with_affiliation(BAffiliation, StateData) ->
2746
:-(
lists:map(
2747 fun({JID, {Affiliation, Reason}}) ->
2748
:-(
#xmlel{name = <<"item">>,
2749 attrs = [{<<"affiliation">>, affiliation_to_binary(Affiliation)},
2750 {<<"jid">>, jid:to_binary(JID)}],
2751 children = [#xmlel{name = <<"reason">>,
2752 children = [#xmlcdata{content = Reason}]}]};
2753 ({JID, Affiliation}) ->
2754
:-(
#xmlel{name = <<"item">>,
2755 attrs = [{<<"affiliation">>, affiliation_to_binary(Affiliation)},
2756 {<<"jid">>, jid:to_binary(JID)}]}
2757 end, search_affiliation(BAffiliation, StateData)).
2758
2759
2760 -spec user_to_item(user(), state()) -> exml:element().
2761 user_to_item(#user{role = Role,
2762 nick = Nick,
2763 jid = JID
2764 }, StateData) ->
2765
:-(
Affiliation = get_affiliation(JID, StateData),
2766
:-(
#xmlel{name = <<"item">>,
2767 attrs = [{<<"role">>, role_to_binary(Role)},
2768 {<<"affiliation">>, affiliation_to_binary(Affiliation)},
2769 {<<"nick">>, Nick},
2770 {<<"jid">>, jid:to_binary(JID)}]}.
2771
2772
2773 -spec search_role(mod_muc:role(), state()) -> users_pairs().
2774 search_role(Role, StateData) ->
2775
:-(
F = fun(_, #user{role = R}) -> Role == R end,
2776
:-(
maps:to_list(maps:filter(F, StateData#state.users)).
2777
2778
2779 -spec search_affiliation(mod_muc:affiliation(), state()) -> [{_, _}].
2780 search_affiliation(Affiliation, StateData) when is_atom(Affiliation) ->
2781
:-(
F = fun(_, A) ->
2782
:-(
case A of
2783 {A1, _Reason} ->
2784
:-(
Affiliation == A1;
2785 _ ->
2786
:-(
Affiliation == A
2787 end
2788 end,
2789
:-(
maps:to_list(maps:filter(F, StateData#state.affiliations)).
2790
2791
2792 -spec process_admin_items_set(jid:jid(), [exml:element(), ...], ejabberd:lang(), state()) ->
2793 {'error', exml:element()} | {'result', [], state()}.
2794 process_admin_items_set(UJID, Items, Lang, StateData) ->
2795
:-(
UAffiliation = get_affiliation(UJID, StateData),
2796
:-(
URole = get_role(UJID, StateData),
2797
:-(
case find_changed_items(UJID, UAffiliation, URole, Items, Lang, StateData, []) of
2798 {result, Res} ->
2799 %% TODO Pass Acc here
2800
:-(
?LOG_INFO(ls(#{what => muc_admin_query, text => <<"Processing MUC admin query">>,
2801
:-(
from_jid => jid:to_binary(UJID), result => Res}, StateData)),
2802
:-(
NSD = lists:foldl(
2803 fun(ChangedItem, SD) ->
2804
:-(
process_admin_item_set(ChangedItem, UJID, SD)
2805 end, StateData, Res),
2806
:-(
save_persistent_room_state(NSD),
2807
:-(
{result, [], NSD};
2808 Err ->
2809
:-(
Err
2810 end.
2811
2812 process_admin_item_set(ChangedItem, UJID, SD) ->
2813
:-(
try
2814
:-(
process_admin_item_set_unsafe(ChangedItem, UJID, SD)
2815 catch
2816 Class:Reason:Stacktrace ->
2817
:-(
?LOG_ERROR(ls(#{what => muc_admin_item_set_failed,
2818 from_jid => jid:to_binary(UJID),
2819 changed_item => ChangedItem,
2820
:-(
class => Class, reason => Reason, stacktrace => Stacktrace}, SD)),
2821
:-(
SD
2822 end.
2823
2824 process_admin_item_set_unsafe({JID, affiliation, owner, _}, _UJID, SD)
2825 when (JID#jid.luser == <<>>) ->
2826 %% If the provided JID does not have username,
2827 %% ignore the affiliation completely
2828
:-(
SD;
2829 process_admin_item_set_unsafe({JID, role, none, Reason}, _UJID, SD) ->
2830
:-(
safe_send_kickban_presence(JID, Reason, <<"307">>, SD),
2831
:-(
set_role(JID, none, SD);
2832 process_admin_item_set_unsafe({JID, affiliation, none, Reason}, _UJID, SD) ->
2833
:-(
case (SD#state.config)#config.members_only of
2834 true ->
2835
:-(
safe_send_kickban_presence(JID, Reason, <<"321">>, none, SD),
2836
:-(
SD1 = set_affiliation_and_reason(JID, none, Reason, SD),
2837
:-(
set_role(JID, none, SD1);
2838 _ ->
2839
:-(
SD1 = set_affiliation_and_reason(JID, none, Reason, SD),
2840
:-(
send_update_presence(JID, Reason, SD1),
2841
:-(
SD1
2842 end;
2843 process_admin_item_set_unsafe({JID, affiliation, outcast, Reason}, _UJID, SD) ->
2844
:-(
safe_send_kickban_presence(JID, Reason, <<"301">>, outcast, SD),
2845
:-(
set_affiliation_and_reason(JID, outcast, Reason, set_role(JID, none, SD));
2846 process_admin_item_set_unsafe({JID, affiliation, A, Reason}, _UJID, SD)
2847 when (A == admin) or (A == owner) ->
2848
:-(
SD1 = set_affiliation_and_reason(JID, A, Reason, SD),
2849
:-(
SD2 = set_role(JID, moderator, SD1),
2850
:-(
send_update_presence(JID, Reason, SD2),
2851
:-(
SD2;
2852 process_admin_item_set_unsafe({JID, affiliation, member, Reason}, UJID, SD) ->
2853
:-(
case (SD#state.config)#config.members_only of
2854
:-(
true -> send_invitation(UJID, JID, Reason, SD);
2855
:-(
_ -> ok
2856 end,
2857
:-(
SD1 = set_affiliation_and_reason(JID, member, Reason, SD),
2858
:-(
SD2 = set_role(JID, participant, SD1),
2859
:-(
send_update_presence(JID, Reason, SD2),
2860
:-(
SD2;
2861 process_admin_item_set_unsafe({JID, role, Role, Reason}, _UJID, SD) ->
2862
:-(
SD1 = set_role(JID, Role, SD),
2863
:-(
catch send_new_presence(JID, Reason, SD1),
2864
:-(
SD1;
2865 process_admin_item_set_unsafe({JID, affiliation, A, Reason}, _UJID, SD) ->
2866
:-(
SD1 = set_affiliation(JID, A, SD),
2867
:-(
send_update_presence(JID, Reason, SD1),
2868
:-(
SD1.
2869
2870 -type res_row() :: {jid:simple_jid() | jid:jid(),
2871 'affiliation' | 'role', any(), any()}.
2872 -type find_changed_items_res() :: {'error', exml:element()} | {'result', [res_row()]}.
2873 -spec find_changed_items(jid:jid(), mod_muc:affiliation(), mod_muc:role(),
2874 [exml:element()], ejabberd:lang(), state(), [res_row()]) ->
2875 find_changed_items_res().
2876 find_changed_items(_UJID, _UAffiliation, _URole, [], _Lang, _StateData, Res) ->
2877
:-(
{result, Res};
2878 find_changed_items(UJID, UAffiliation, URole, [#xmlcdata{} | Items],
2879 Lang, StateData, Res) ->
2880
:-(
find_changed_items(UJID, UAffiliation, URole, Items, Lang, StateData, Res);
2881 find_changed_items(UJID, UAffiliation, URole,
2882 [#xmlel{name = <<"item">>, attrs = Attrs} = Item | Items],
2883 Lang, StateData, Res) ->
2884
:-(
case get_affected_jid(Attrs, Lang, StateData) of
2885 {value, JID} ->
2886
:-(
check_changed_item(UJID, UAffiliation, URole, JID, Item, Items, Lang, StateData, Res);
2887 Err ->
2888
:-(
Err
2889 end;
2890 find_changed_items(_UJID, _UAffiliation, _URole, _Items, _Lang, _StateData, _Res) ->
2891
:-(
{error, mongoose_xmpp_errors:bad_request()}.
2892
2893 -spec get_affected_jid(Attrs :: [{binary(), binary()}],
2894 Lang :: ejabberd:lang(),
2895 StateData :: state()) ->
2896 {value,jid:jid()} | {error, exml:element()}.
2897 get_affected_jid(Attrs, Lang, StateData) ->
2898
:-(
case {xml:get_attr(<<"jid">>, Attrs), xml:get_attr(<<"nick">>, Attrs)} of
2899 {{value, S}, _} ->
2900
:-(
case jid:from_binary(S) of
2901 error ->
2902
:-(
ErrText = <<(translate:translate(Lang, <<"Jabber ID ">>))/binary,
2903 S/binary, (translate:translate(Lang, <<" is invalid">>))/binary>>,
2904
:-(
{error, mongoose_xmpp_errors:not_acceptable(Lang, ErrText)};
2905 J ->
2906
:-(
{value, J}
2907 end;
2908 {_, {value, N}} ->
2909
:-(
case find_jids_by_nick(N, StateData) of
2910 [] ->
2911
:-(
ErrText
2912 = <<(translate:translate(Lang, <<"Nickname ">>))/binary, N/binary,
2913 (translate:translate(Lang, <<" does not exist in the room">>))/binary>>,
2914
:-(
{error, mongoose_xmpp_errors:not_acceptable(Lang, ErrText)};
2915 [FirstSessionJid | _RestOfSessions] ->
2916
:-(
{value, FirstSessionJid}
2917 end;
2918 _ ->
2919
:-(
{error, mongoose_xmpp_errors:bad_request()}
2920 end.
2921
2922 -spec check_changed_item(jid:jid(), mod_muc:affiliation(), mod_muc:role(),jid:jid(), exml:element(),
2923 [exml:element()], ejabberd:lang(), state(), [res_row()]) ->
2924 find_changed_items_res().
2925 check_changed_item(UJID, UAffiliation, URole, JID, #xmlel{ attrs = Attrs } = Item, Items,
2926 Lang, StateData, Res) ->
2927
:-(
TAffiliation = get_affiliation(JID, StateData),
2928
:-(
TRole = get_role(JID, StateData),
2929
:-(
case which_property_changed(Attrs, Lang) of
2930 {role, Role} ->
2931
:-(
ServiceAf = get_service_affiliation(JID, StateData),
2932
:-(
CanChangeRA =
2933 case can_change_ra(UAffiliation, URole, TAffiliation, TRole, role, Role, ServiceAf) of
2934
:-(
nothing -> nothing;
2935
:-(
true -> true;
2936
:-(
check_owner -> is_owner(UJID, StateData);
2937
:-(
_ -> false
2938 end,
2939
:-(
case CanChangeRA of
2940
:-(
nothing -> find_changed_items(UJID, UAffiliation, URole,
2941 Items, Lang, StateData, Res);
2942
:-(
true -> find_changed_items(UJID, UAffiliation, URole, Items, Lang, StateData,
2943 [{JID, role, Role, decode_reason(Item)} | Res]);
2944
:-(
_ -> {error, mongoose_xmpp_errors:not_allowed()}
2945 end;
2946 {affiliation, Affiliation} ->
2947
:-(
ServiceAf = get_service_affiliation(JID, StateData),
2948
:-(
CanChangeRA =
2949 case can_change_ra(UAffiliation, URole, TAffiliation, TRole,
2950 affiliation, Affiliation, ServiceAf) of
2951
:-(
nothing -> nothing;
2952
:-(
true -> true;
2953
:-(
cancel -> cancel;
2954
:-(
check_owner -> is_owner(UJID, StateData);
2955 _ ->
2956
:-(
false
2957 end,
2958
:-(
case CanChangeRA of
2959
:-(
nothing -> find_changed_items(UJID, UAffiliation, URole, Items,
2960 Lang, StateData, Res);
2961
:-(
true -> find_changed_items(UJID, UAffiliation, URole, Items, Lang, StateData,
2962 [{jid:to_bare(JID), affiliation,
2963 Affiliation, decode_reason(Item)} | Res]);
2964
:-(
cancel -> {error, mongoose_xmpp_errors:not_allowed()};
2965
:-(
false -> {error, mongoose_xmpp_errors:forbidden()}
2966 end;
2967
:-(
Err -> Err
2968 end.
2969
2970 -spec is_owner(UJID ::jid:jid(), StateData :: state()) -> boolean().
2971 is_owner(UJID, StateData) ->
2972
:-(
case search_affiliation(owner, StateData) of
2973
:-(
[{OJID, _}] -> jid:to_bare(OJID) /= jid:to_lower(jid:to_bare(UJID));
2974
:-(
_ -> true
2975 end.
2976
2977 -spec which_property_changed(Attrs :: [{binary(), binary()}], Lang :: ejabberd:lang()) ->
2978 {affiliation, mod_muc:affiliation()} | {role, mod_muc:role()} | {error, exml:element()}.
2979 which_property_changed(Attrs, Lang) ->
2980
:-(
case {xml:get_attr(<<"role">>, Attrs), xml:get_attr(<<"affiliation">>, Attrs)} of
2981 {false, false} ->
2982
:-(
{error, mongoose_xmpp_errors:bad_request()};
2983 {false, {value, BAffiliation}} ->
2984
:-(
case catch binary_to_affiliation(BAffiliation) of
2985 {'EXIT', _} ->
2986
:-(
ErrText1 = <<(translate:translate(Lang, <<"Invalid affiliation ">>))/binary,
2987 BAffiliation/binary>>,
2988
:-(
{error, mongoose_xmpp_errors:not_acceptable(Lang, ErrText1)};
2989 Affiliation ->
2990
:-(
{affiliation, Affiliation}
2991 end;
2992 {{value, BRole}, _} ->
2993
:-(
case catch binary_to_role(BRole) of
2994 {'EXIT', _} ->
2995
:-(
ErrText1 = <<(translate:translate(Lang, <<"Invalid role ">>))/binary,
2996 BRole/binary>>,
2997
:-(
{error, mongoose_xmpp_errors:bad_request(Lang, ErrText1)};
2998 Role ->
2999
:-(
{role, Role}
3000 end
3001 end.
3002
3003 -spec can_change_ra(FAff :: mod_muc:affiliation(), FRole :: mod_muc:role(),
3004 TAff :: mod_muc:affiliation(), TRole :: mod_muc:role(),
3005 RoleOrAff :: affiliation | role, Value :: any(),
3006 ServiceAff :: mod_muc:affiliation())
3007 -> cancel | check_owner | false | nothing | true.
3008 can_change_ra(FAff, _FRole, TAff, _TRole, affiliation, Value, ServiceAff) ->
3009
:-(
can_change_aff(FAff, TAff, Value, ServiceAff);
3010 can_change_ra(FAff, FRole, TAff, TRole, role, Value, ServiceAff) ->
3011
:-(
can_change_role(FAff, FRole, TAff, TRole, Value, ServiceAff).
3012
3013 %% A room owner tries to add as persistent owner a
3014 %% participant that is already owner because he is MUC admin:
3015
:-(
can_change_aff(_FAff, owner, owner, owner) -> true;
3016 %% Nobody can decrease MUC admin's role/affiliation:
3017
:-(
can_change_aff(_FAff, _TAff, _Value, owner) -> false;
3018 can_change_aff(FAff, TAff, Value, _ServiceAf) ->
3019
:-(
can_change_aff(FAff, TAff, Value).
3020
3021 %% Nobody can decrease MUC admin's role/affiliation:
3022
:-(
can_change_role(_FAff, _FRole, _TAff, _TRole, _Value, owner) -> false;
3023 can_change_role(FAff, FRole, TAff, TRole, Value, _ServiceAf) ->
3024
:-(
can_change_role(FAff, FRole, TAff, TRole, Value).
3025
3026 %% Arguments:
3027 %% - Affiliation of the user making the request
3028 %% - Old affiliation
3029 %% - New affiliation
3030
:-(
can_change_aff(_FAff, Aff, Aff) -> nothing;
3031
:-(
can_change_aff(owner, outcast, none) -> true;
3032
:-(
can_change_aff(owner, outcast, member) -> true;
3033
:-(
can_change_aff(owner, outcast, admin) -> true;
3034
:-(
can_change_aff(owner, outcast, owner) -> true;
3035
:-(
can_change_aff(owner, none, outcast) -> true;
3036
:-(
can_change_aff(owner, none, member) -> true;
3037
:-(
can_change_aff(owner, none, admin) -> true;
3038
:-(
can_change_aff(owner, none, owner) -> true;
3039
:-(
can_change_aff(owner, member, outcast) -> true;
3040
:-(
can_change_aff(owner, member, none) -> true;
3041
:-(
can_change_aff(owner, member, admin) -> true;
3042
:-(
can_change_aff(owner, member, owner) -> true;
3043
:-(
can_change_aff(owner, admin, _Aff) -> true;
3044
:-(
can_change_aff(owner, owner, _Aff) -> check_owner;
3045
:-(
can_change_aff(admin, none, member) -> true;
3046
:-(
can_change_aff(admin, none, outcast) -> true;
3047
:-(
can_change_aff(admin, outcast, none) -> true;
3048
:-(
can_change_aff(admin, outcast, member) -> true;
3049
:-(
can_change_aff(admin, member, outcast) -> true;
3050
:-(
can_change_aff(admin, member, none) -> true;
3051
:-(
can_change_aff(none, admin, _Aff) -> cancel;
3052
:-(
can_change_aff(none, owner, _Aff) -> cancel;
3053
:-(
can_change_aff(admin, owner, _Aff) -> cancel;
3054
:-(
can_change_aff(_FAff, _TAff, _Aff) -> false.
3055
3056 %% Arguments:
3057 %% - Affiliation of the user making the request
3058 %% - Role of the user making the request
3059 %% - Old affiliation
3060 %% - Old role
3061 %% - New role
3062
:-(
can_change_role(_FAff, _FRole, _TAff, Role, Role) -> nothing;
3063
:-(
can_change_role(_FAff, moderator, _TAff, visitor, none) -> true;
3064
:-(
can_change_role(_FAff, moderator, _TAff, visitor, participant) -> true;
3065
:-(
can_change_role(owner, _FRole, _TAff, visitor, moderator) -> true;
3066
:-(
can_change_role(admin, _FRole, _TAff, visitor, moderator) -> true;
3067
:-(
can_change_role(_FAff, moderator, _TAff, participant, none) -> true;
3068
:-(
can_change_role(_FAff, moderator, _TAff, participant, visitor) -> true;
3069
:-(
can_change_role(owner, _FRole, _TAff, participant, moderator) -> true;
3070
:-(
can_change_role(admin, _FRole, _TAff, participant, moderator) -> true;
3071 %% Owner/admin are always moderators:
3072
:-(
can_change_role(_FAff, _FRole, owner, moderator, visitor) -> false;
3073
:-(
can_change_role(_FAff, _FRole, admin, moderator, visitor) -> false;
3074
:-(
can_change_role(_FAff, _FRole, owner, moderator, participant) -> false;
3075
:-(
can_change_role(_FAff, _FRole, admin, moderator, participant) -> false;
3076 %% Non owner/admin could loose their moderator status:
3077
:-(
can_change_role(owner, _FRole, _TAff, moderator, visitor) -> true;
3078
:-(
can_change_role(admin, _FRole, _TAff, moderator, visitor) -> true;
3079
:-(
can_change_role(owner, _FRole, _TAff, moderator, participant) -> true;
3080
:-(
can_change_role(admin, _FRole, _TAff, moderator, participant) -> true;
3081
:-(
can_change_role(_FAff, _FRole, _TAff, _TRole, _NewRole) -> false.
3082
3083 safe_send_kickban_presence(JID, Reason, Code, StateData) ->
3084
:-(
try
3085
:-(
send_kickban_presence(JID, Reason, Code, StateData)
3086 catch
3087 Class:ErrorReason:Stacktrace ->
3088
:-(
?LOG_ERROR(ls(#{what => muc_send_kickban_presence_failed,
3089 kick_jid => jid:to_binary(JID), kick_reason => Reason,
3090
:-(
class => Class, reason => ErrorReason, stacktrace => Stacktrace}, StateData))
3091 end.
3092
3093 -spec send_kickban_presence(jid:jid(), binary(), Code :: binary(),
3094 state()) -> any().
3095 send_kickban_presence(JID, Reason, Code, StateData) ->
3096
:-(
NewAffiliation = get_affiliation(JID, StateData),
3097
:-(
send_kickban_presence(JID, Reason, Code, NewAffiliation, StateData).
3098
3099
3100 safe_send_kickban_presence(JID, Reason, Code, NewAffiliation, StateData) ->
3101
:-(
try
3102
:-(
send_kickban_presence(JID, Reason, Code, NewAffiliation, StateData)
3103 catch
3104 Class:ErrorReason:Stacktrace ->
3105
:-(
?LOG_ERROR(ls(#{what => muc_send_kickban_presence_failed,
3106 new_affiliation => NewAffiliation,
3107 kick_jid => jid:to_binary(JID), kick_reason => Reason,
3108
:-(
class => Class, reason => ErrorReason, stacktrace => Stacktrace}, StateData))
3109 end.
3110
3111 -spec send_kickban_presence(jid:simple_jid() | jid:jid(),
3112 Reason :: binary(), Code :: binary(),
3113 mod_muc:affiliation(), state()) -> any().
3114 send_kickban_presence(JID, Reason, Code, NewAffiliation, StateData) ->
3115
:-(
foreach_matched_user(fun(#user{nick = Nick, jid = J}) ->
3116
:-(
add_to_log(kickban, {Nick, Reason, Code}, StateData),
3117
:-(
tab_remove_online_user(J, StateData),
3118
:-(
send_kickban_presence1(J, Reason, Code, NewAffiliation, StateData)
3119 end, JID, StateData).
3120
3121
3122 -spec send_kickban_presence1(jid:jid(), Reason :: binary(), Code :: binary(),
3123 mod_muc:affiliation(), state()) -> 'ok'.
3124 send_kickban_presence1(UJID, Reason, Code, Affiliation, StateData) ->
3125
:-(
{ok, #user{jid = RealJID,
3126 nick = Nick}} =
3127 maps:find(jid:to_lower(UJID), StateData#state.users),
3128
:-(
BAffiliation = affiliation_to_binary(Affiliation),
3129
:-(
BannedJIDString = jid:to_binary(RealJID),
3130
:-(
F = fun(Info) ->
3131
:-(
JidAttrList = case (Info#user.role == moderator) orelse
3132 ((StateData#state.config)#config.anonymous
3133
:-(
== false) of
3134
:-(
true -> [{<<"jid">>, BannedJIDString}];
3135
:-(
false -> []
3136 end,
3137
:-(
ItemAttrs = [{<<"affiliation">>, BAffiliation},
3138 {<<"role">>, <<"none">>}] ++ JidAttrList,
3139
:-(
ItemEls = case Reason of
3140 <<>> ->
3141
:-(
[];
3142 _ ->
3143
:-(
[#xmlel{name = <<"reason">>, children = [#xmlcdata{content = Reason}]}]
3144 end,
3145
:-(
Packet = #xmlel{name = <<"presence">>,
3146 attrs = [{<<"type">>, <<"unavailable">>}],
3147 children = [#xmlel{name = <<"x">>,
3148 attrs = [{<<"xmlns">>, ?NS_MUC_USER}],
3149 children = [#xmlel{name = <<"item">>,
3150 attrs = ItemAttrs,
3151 children = ItemEls},
3152 #xmlel{name = <<"status">>,
3153 attrs = [{<<"code">>, Code}]}]}]},
3154
:-(
ejabberd_router:route(
3155 jid:replace_resource(StateData#state.jid, Nick),
3156 Info#user.jid,
3157 Packet)
3158 end,
3159
:-(
foreach_user(F, StateData).
3160
3161
3162
3163 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3164 % Owner stuff
3165
3166 -spec process_iq_owner(jid:jid(), get | set, ejabberd:lang(), exml:element(),
3167 state(), statename()) ->
3168 {error, exml:element()} | {result, [exml:element() | jlib:xmlcdata()], state() | stop}.
3169 process_iq_owner(From, Type, Lang, SubEl, StateData, StateName) ->
3170
:-(
case get_affiliation(From, StateData) of
3171 owner ->
3172
:-(
process_authorized_iq_owner(From, Type, Lang, SubEl, StateData, StateName);
3173 _ ->
3174
:-(
ErrText = <<"Owner privileges required">>,
3175
:-(
{error, mongoose_xmpp_errors:forbidden(Lang, ErrText)}
3176 end.
3177
3178 -spec process_authorized_iq_owner(jid:jid(), get | set, ejabberd:lang(), exml:element(),
3179 state(), statename()) ->
3180 {error, exml:element()} | {result, [exml:element() | jlib:xmlcdata()], state() | stop}.
3181 process_authorized_iq_owner(From, set, Lang, SubEl, StateData, StateName) ->
3182
:-(
#xmlel{children = Els} = SubEl,
3183
:-(
case xml:remove_cdata(Els) of
3184 [#xmlel{name = <<"destroy">>} = SubEl1] ->
3185
:-(
?LOG_INFO(ls(#{what => muc_room_destroy,
3186 text => <<"Destroyed MUC room by the owner">>,
3187
:-(
from_jid => jid:to_binary(From)}, StateData)),
3188
:-(
add_to_log(room_existence, destroyed, StateData),
3189
:-(
destroy_room(SubEl1, StateData);
3190 [XEl] ->
3191
:-(
case {mongoose_data_forms:parse_form(XEl), StateName} of
3192 {#{type := <<"cancel">>}, locked_state} ->
3193
:-(
?LOG_INFO(ls(#{what => muc_cancel_locked,
3194 text => <<"Received cancel before the room was configured "
3195 "- destroy room">>,
3196
:-(
from_jid => jid:to_binary(From)}, StateData)),
3197
:-(
add_to_log(room_existence, destroyed, StateData),
3198
:-(
destroy_room(XEl, StateData);
3199 {#{type := <<"cancel">>}, normal_state} ->
3200 %% received cancel when room was configured - continue without changes
3201
:-(
{result, [], StateData};
3202 {#{type := <<"submit">>, kvs := KVs}, _} ->
3203
:-(
process_authorized_submit_owner(From, maps:to_list(KVs), StateData);
3204 {{error, Msg}, _} ->
3205
:-(
{error, mongoose_xmpp_errors:bad_request(Lang, Msg)};
3206 _ ->
3207
:-(
{error, mongoose_xmpp_errors:bad_request(Lang, <<"Invalid form type">>)}
3208 end;
3209 _ ->
3210
:-(
{error, mongoose_xmpp_errors:bad_request()}
3211 end;
3212 process_authorized_iq_owner(From, get, Lang, SubEl, StateData, _StateName) ->
3213
:-(
case exml_query:path(SubEl, [{element, <<"item">>}, {attr, <<"affiliation">>}]) of
3214 undefined ->
3215
:-(
get_config(Lang, StateData, From);
3216 BAffiliation ->
3217
:-(
case catch binary_to_affiliation(BAffiliation) of
3218 {'EXIT', _} ->
3219
:-(
InvAffT = translate:translate(Lang, <<"Invalid affiliation ">>),
3220
:-(
ErrText = <<InvAffT/binary, BAffiliation/binary>>,
3221
:-(
{error, mongoose_xmpp_errors:not_acceptable(Lang, ErrText)};
3222 Affiliation ->
3223
:-(
Items = items_with_affiliation(Affiliation, StateData),
3224
:-(
{result, Items, StateData}
3225 end
3226 end.
3227
3228 -spec process_authorized_submit_owner(From ::jid:jid(), [{binary(), [binary()]}],
3229 StateData :: state()) ->
3230 {error, exml:element()} | {result, [exml:element() | jlib:xmlcdata()], state() | stop}.
3231 process_authorized_submit_owner(_From, [], StateData) ->
3232 %confirm an instant room
3233
:-(
save_persistent_room_state(StateData),
3234
:-(
{result, [], StateData};
3235 process_authorized_submit_owner(From, XData, StateData) ->
3236 %attempt to configure
3237
:-(
case is_allowed_log_change(XData, StateData, From)
3238
:-(
andalso is_allowed_persistent_change(XData, StateData, From)
3239
:-(
andalso is_allowed_room_name_desc_limits(XData, StateData)
3240
:-(
andalso is_password_settings_correct(XData, StateData) of
3241
:-(
true -> set_config(XData, StateData);
3242
:-(
false -> {error, mongoose_xmpp_errors:not_acceptable(<<"en">>, <<"not allowed to configure">>)}
3243 end.
3244
3245 -spec is_allowed_log_change([{binary(), [binary()]}], state(), jid:jid()) -> boolean().
3246 is_allowed_log_change(XData, StateData, From) ->
3247
:-(
case lists:keymember(<<"muc#roomconfig_enablelogging">>, 1, XData) of
3248 false ->
3249
:-(
true;
3250 true ->
3251
:-(
(allow == mod_muc_log:check_access_log(
3252 StateData#state.host_type,
3253 StateData#state.server_host, From))
3254 end.
3255
3256
3257 -spec is_allowed_persistent_change([{binary(), [binary()]}], state(), jid:jid()) -> boolean().
3258 is_allowed_persistent_change(XData, StateData, From) ->
3259
:-(
case lists:keymember(<<"muc#roomconfig_persistentroom">>, 1, XData) of
3260 false ->
3261
:-(
true;
3262 true ->
3263
:-(
AccessPersistent = access_persistent(StateData),
3264
:-(
(allow == acl:match_rule(StateData#state.host_type, StateData#state.server_host,
3265 AccessPersistent, From))
3266 end.
3267
3268
3269 %% @doc Check if the Room Name and Room Description defined in the Data Form
3270 %% are conformant to the configured limits
3271 -spec is_allowed_room_name_desc_limits([{binary(), [binary()]}], state()) -> boolean().
3272 is_allowed_room_name_desc_limits(XData, StateData) ->
3273
:-(
IsNameAccepted =
3274 case lists:keysearch(<<"muc#roomconfig_roomname">>, 1, XData) of
3275 {value, {_, [N]}} ->
3276
:-(
byte_size(N) =< get_opt(StateData, max_room_name);
3277 _ ->
3278
:-(
true
3279 end,
3280
:-(
IsDescAccepted =
3281 case lists:keysearch(<<"muc#roomconfig_roomdesc">>, 1, XData) of
3282 {value, {_, [D]}} ->
3283
:-(
byte_size(D) =< get_opt(StateData, max_room_desc);
3284 _ ->
3285
:-(
true
3286 end,
3287
:-(
IsNameAccepted and IsDescAccepted.
3288
3289 %% @doc Return false if:
3290 %% `<<"the password for a password-protected room is blank">>'
3291 -spec is_password_settings_correct([{binary(), [binary()]}], state()) -> boolean().
3292 is_password_settings_correct(KVs, StateData) ->
3293
:-(
Config = StateData#state.config,
3294
:-(
OldProtected = Config#config.password_protected,
3295
:-(
OldPassword = Config#config.password,
3296
:-(
NewProtected =
3297 case lists:keysearch(<<"muc#roomconfig_passwordprotectedroom">>, 1, KVs) of
3298 {value, {_, [<<"1">>]}} ->
3299
:-(
true;
3300 {value, {_, [<<"0">>]}} ->
3301
:-(
false;
3302 _ ->
3303
:-(
undefined
3304 end,
3305
:-(
NewPassword =
3306 case lists:keysearch(<<"muc#roomconfig_roomsecret">>, 1, KVs) of
3307 {value, {_, [P]}} ->
3308
:-(
P;
3309 _ ->
3310
:-(
undefined
3311 end,
3312
:-(
case {OldProtected, NewProtected, OldPassword, NewPassword} of
3313 {true, undefined, <<>>, undefined} ->
3314
:-(
false;
3315 {true, undefined, _, <<>>} ->
3316
:-(
false;
3317 {_, true, <<>>, undefined} ->
3318
:-(
false;
3319 {_, true, _, <<>>} ->
3320
:-(
false;
3321 _ ->
3322
:-(
true
3323 end.
3324
3325 -spec get_default_room_maxusers(state()) -> any().
3326 get_default_room_maxusers(RoomState) ->
3327
:-(
#{max_users := MaxUsers} = get_opt(RoomState, default_room),
3328
:-(
MaxUsers.
3329
3330 -spec get_config(ejabberd:lang(), state(), jid:jid())
3331 -> {'result', [exml:element(), ...], state()}.
3332 get_config(Lang, StateData, From) ->
3333
:-(
AccessPersistent = access_persistent(StateData),
3334
:-(
Config = StateData#state.config,
3335
:-(
TitleTxt = translate:translate(Lang, <<"Configuration of room ">>),
3336
:-(
Title = <<TitleTxt/binary, (jid:to_binary(StateData#state.jid))/binary>>,
3337
:-(
Fields =
3338 [stringxfield(<<"Room title">>,
3339 <<"muc#roomconfig_roomname">>,
3340 Config#config.title, Lang),
3341 stringxfield(<<"Room description">>,
3342 <<"muc#roomconfig_roomdesc">>,
3343 Config#config.description, Lang)
3344 ] ++
3345 case acl:match_rule(StateData#state.host_type, StateData#state.server_host,
3346 AccessPersistent, From) of
3347 allow ->
3348
:-(
[boolxfield(<<"Make room persistent">>,
3349 <<"muc#roomconfig_persistentroom">>,
3350 Config#config.persistent, Lang)];
3351
:-(
_ -> []
3352 end ++ [
3353 boolxfield(<<"Make room public searchable">>,
3354 <<"muc#roomconfig_publicroom">>,
3355 Config#config.public, Lang),
3356 boolxfield(<<"Make participants list public">>,
3357 <<"public_list">>,
3358 Config#config.public_list, Lang),
3359 boolxfield(<<"Make room password protected">>,
3360 <<"muc#roomconfig_passwordprotectedroom">>,
3361 Config#config.password_protected, Lang),
3362 privatexfield(<<"Password">>,
3363 <<"muc#roomconfig_roomsecret">>,
3364 case Config#config.password_protected of
3365
:-(
true -> Config#config.password;
3366
:-(
false -> <<>>
3367 end, Lang),
3368 getmemberlist_field(Lang),
3369 maxusers_field(Lang, StateData),
3370 whois_field(Lang, Config),
3371 boolxfield(<<"Make room members-only">>,
3372 <<"muc#roomconfig_membersonly">>,
3373 Config#config.members_only, Lang),
3374 boolxfield(<<"Make room moderated">>,
3375 <<"muc#roomconfig_moderatedroom">>,
3376 Config#config.moderated, Lang),
3377 boolxfield(<<"Default users as participants">>,
3378 <<"members_by_default">>,
3379 Config#config.members_by_default, Lang),
3380 boolxfield(<<"Allow users to change the subject">>,
3381 <<"muc#roomconfig_changesubject">>,
3382 Config#config.allow_change_subj, Lang),
3383 boolxfield(<<"Allow users to send private messages">>,
3384 <<"allow_private_messages">>,
3385 Config#config.allow_private_messages, Lang),
3386 boolxfield(<<"Allow users to query other users">>,
3387 <<"allow_query_users">>,
3388 Config#config.allow_query_users, Lang),
3389 boolxfield(<<"Allow users to send invites">>,
3390 <<"muc#roomconfig_allowinvites">>,
3391 Config#config.allow_user_invites, Lang),
3392 boolxfield(<<"Allow users to enter room with multiple sessions">>,
3393 <<"muc#roomconfig_allowmultisessions">>,
3394 Config#config.allow_multiple_sessions, Lang),
3395 boolxfield(<<"Allow visitors to send status text in presence updates">>,
3396 <<"muc#roomconfig_allowvisitorstatus">>,
3397 Config#config.allow_visitor_status, Lang),
3398 boolxfield(<<"Allow visitors to change nickname">>,
3399 <<"muc#roomconfig_allowvisitornickchange">>,
3400 Config#config.allow_visitor_nickchange, Lang)
3401 ] ++
3402 case mod_muc_log:check_access_log(StateData#state.host_type,
3403 StateData#state.server_host, From) of
3404 allow ->
3405
:-(
[boolxfield(
3406 <<"Enable logging">>,
3407 <<"muc#roomconfig_enablelogging">>,
3408 Config#config.logging, Lang)];
3409
:-(
_ -> []
3410 end,
3411
:-(
InstructionsTxt = translate:translate(
3412 Lang, <<"You need an x:data capable client to configure room">>),
3413
:-(
{result, [#xmlel{name = <<"instructions">>, children = [#xmlcdata{content = InstructionsTxt}]},
3414 mongoose_data_forms:form(#{title => Title, ns => ?NS_MUC_CONFIG, fields => Fields})],
3415 StateData}.
3416
3417 -spec getmemberlist_field(Lang :: ejabberd:lang()) -> mongoose_data_forms:field().
3418 getmemberlist_field(Lang) ->
3419
:-(
LabelTxt = translate:translate(
3420 Lang, <<"Roles and affiliations that may retrieve member list">>),
3421
:-(
Values = [<<"moderator">>, <<"participant">>, <<"visitor">>],
3422
:-(
Options = [{translate:translate(Lang, Opt), Opt} || Opt <- Values],
3423
:-(
#{type => <<"list-multi">>, label => LabelTxt,
3424 var => <<"muc#roomconfig_getmemberlist">>, values => Values, options => Options}.
3425
3426 maxusers_field(Lang, StateData) ->
3427
:-(
ServiceMaxUsers = get_service_max_users(StateData),
3428
:-(
DefaultRoomMaxUsers = get_default_room_maxusers(StateData),
3429
:-(
{MaxUsersRoomInteger, MaxUsersRoomString} =
3430 case get_max_users(StateData) of
3431 N when is_integer(N) ->
3432
:-(
{N, integer_to_binary(N)};
3433
:-(
_ -> {0, <<"none">>}
3434 end,
3435
:-(
LabelTxt = translate:translate(Lang, <<"Maximum Number of Occupants">>),
3436
:-(
Options = if
3437
:-(
is_integer(ServiceMaxUsers) -> [];
3438
:-(
true -> {translate:translate(Lang, <<"No limit">>), <<"none">>}
3439 end ++
3440
:-(
[integer_to_binary(N) ||
3441
:-(
N <- lists:usort([ServiceMaxUsers, DefaultRoomMaxUsers, MaxUsersRoomInteger |
3442
:-(
?MAX_USERS_DEFAULT_LIST]), N =< ServiceMaxUsers],
3443
:-(
#{type => <<"list-single">>, label => LabelTxt,
3444 var => <<"muc#roomconfig_maxusers">>, values => [MaxUsersRoomString], options => Options}.
3445
3446 -spec whois_field(Lang :: ejabberd:lang(), Config :: config()) -> mongoose_data_forms:field().
3447 whois_field(Lang, Config) ->
3448
:-(
Value = if Config#config.anonymous -> <<"moderators">>;
3449
:-(
true -> <<"anyone">>
3450 end,
3451
:-(
Options = [{translate:translate(Lang, <<"moderators only">>), <<"moderators">>},
3452 {translate:translate(Lang, <<"anyone">>), <<"anyone">>}],
3453
:-(
#{type => <<"list-single">>, label => translate:translate(Lang, <<"moderators only">>),
3454 var => <<"muc#roomconfig_whois">>, values => [Value], options => Options}.
3455
3456 -spec set_config([{binary(), [binary()]}], state()) -> any().
3457 set_config(XData, StateData) ->
3458
:-(
case set_xoption(XData, StateData#state.config) of
3459 #config{} = Config ->
3460
:-(
Res = change_config(Config, StateData),
3461
:-(
{result, _, NSD} = Res,
3462
:-(
PrevLogging = (StateData#state.config)#config.logging,
3463
:-(
NewLogging = Config#config.logging,
3464
:-(
PrevAnon = (StateData#state.config)#config.anonymous,
3465
:-(
NewAnon = Config#config.anonymous,
3466
:-(
Type = notify_config_change_and_get_type(PrevLogging, NewLogging,
3467 PrevAnon, NewAnon, StateData),
3468
:-(
Users = [{U#user.jid, U#user.nick, U#user.role} ||
3469
:-(
{_, U} <- maps:to_list(StateData#state.users)],
3470
:-(
add_to_log(Type, Users, NSD),
3471
:-(
Res;
3472 Err ->
3473
:-(
Err
3474 end.
3475
3476 -spec notify_config_change_and_get_type(PrevLogging :: boolean(), NewLogging :: boolean(),
3477 PrevAnon :: boolean(), NewAnon :: boolean(),
3478 StateData :: state()) ->
3479 roomconfig_change_disabledlogging | roomconfig_change_enabledlogging
3480 | roomconfig_change_nonanonymous | roomconfig_change_anonymous | roomconfig_change.
3481 notify_config_change_and_get_type(true, false, _, _, StateData) ->
3482
:-(
send_config_update(logging_disabled, StateData),
3483
:-(
roomconfig_change_disabledlogging;
3484 notify_config_change_and_get_type(false, true, _, _, StateData) ->
3485
:-(
send_config_update(logging_enabled, StateData),
3486
:-(
roomconfig_change_enabledlogging;
3487 notify_config_change_and_get_type(_, _, true, false, StateData) ->
3488
:-(
send_config_update(nonanonymous, StateData),
3489
:-(
roomconfig_change_nonanonymous;
3490 notify_config_change_and_get_type(_, _, false, true, StateData) ->
3491
:-(
send_config_update(semianonymous, StateData),
3492
:-(
roomconfig_change_anonymous;
3493 notify_config_change_and_get_type(_, _, _, _, _StateData) ->
3494
:-(
roomconfig_change.
3495
3496 -define(SET_BOOL_XOPT(Opt, Val),
3497 case Val of
3498 <<"0">> -> set_xoption(Opts, Config#config{Opt = false});
3499 <<"false">> -> set_xoption(Opts, Config#config{Opt = false});
3500 <<"1">> -> set_xoption(Opts, Config#config{Opt = true});
3501 <<"true">> -> set_xoption(Opts, Config#config{Opt = true});
3502 _ -> {error, mongoose_xmpp_errors:bad_request()}
3503 end).
3504
3505 -define(SET_NAT_XOPT(Opt, Val),
3506 case catch binary_to_integer(Val) of
3507 I when is_integer(I),
3508 I > 0 ->
3509 set_xoption(Opts, Config#config{Opt = I});
3510 _ ->
3511 {error, mongoose_xmpp_errors:bad_request()}
3512 end).
3513
3514 -define(SET_XOPT(Opt, Val),
3515 set_xoption(Opts, Config#config{Opt = Val})).
3516
3517 -spec set_xoption([{binary(), [binary()]}], config()) -> config() | {error, exml:element()}.
3518 set_xoption([], Config) ->
3519
:-(
Config;
3520 set_xoption([{<<"muc#roomconfig_roomname">>, [Val]} | Opts], Config) ->
3521
:-(
?SET_XOPT(title, Val);
3522 set_xoption([{<<"muc#roomconfig_roomdesc">>, [Val]} | Opts], Config) ->
3523
:-(
?SET_XOPT(description, Val);
3524 set_xoption([{<<"muc#roomconfig_changesubject">>, [Val]} | Opts], Config) ->
3525
:-(
?SET_BOOL_XOPT(allow_change_subj, Val);
3526 set_xoption([{<<"allow_query_users">>, [Val]} | Opts], Config) ->
3527
:-(
?SET_BOOL_XOPT(allow_query_users, Val);
3528 set_xoption([{<<"allow_private_messages">>, [Val]} | Opts], Config) ->
3529
:-(
?SET_BOOL_XOPT(allow_private_messages, Val);
3530 set_xoption([{<<"muc#roomconfig_allowvisitorstatus">>, [Val]} | Opts], Config) ->
3531
:-(
?SET_BOOL_XOPT(allow_visitor_status, Val);
3532 set_xoption([{<<"muc#roomconfig_allowvisitornickchange">>, [Val]} | Opts], Config) ->
3533
:-(
?SET_BOOL_XOPT(allow_visitor_nickchange, Val);
3534 set_xoption([{<<"muc#roomconfig_publicroom">>, [Val]} | Opts], Config) ->
3535
:-(
?SET_BOOL_XOPT(public, Val);
3536 set_xoption([{<<"public_list">>, [Val]} | Opts], Config) ->
3537
:-(
?SET_BOOL_XOPT(public_list, Val);
3538 set_xoption([{<<"muc#roomconfig_persistentroom">>, [Val]} | Opts], Config) ->
3539
:-(
?SET_BOOL_XOPT(persistent, Val);
3540 set_xoption([{<<"muc#roomconfig_moderatedroom">>, [Val]} | Opts], Config) ->
3541
:-(
?SET_BOOL_XOPT(moderated, Val);
3542 set_xoption([{<<"members_by_default">>, [Val]} | Opts], Config) ->
3543
:-(
?SET_BOOL_XOPT(members_by_default, Val);
3544 set_xoption([{<<"muc#roomconfig_membersonly">>, [Val]} | Opts], Config) ->
3545
:-(
?SET_BOOL_XOPT(members_only, Val);
3546 set_xoption([{<<"muc#roomconfig_allowinvites">>, [Val]} | Opts], Config) ->
3547
:-(
?SET_BOOL_XOPT(allow_user_invites, Val);
3548 set_xoption([{<<"muc#roomconfig_allowmultisessions">>, [Val]} | Opts], Config) ->
3549
:-(
?SET_BOOL_XOPT(allow_multiple_sessions, Val);
3550 set_xoption([{<<"muc#roomconfig_passwordprotectedroom">>, [Val]} | Opts], Config) ->
3551
:-(
?SET_BOOL_XOPT(password_protected, Val);
3552 set_xoption([{<<"muc#roomconfig_roomsecret">>, [Val]} | Opts], Config) ->
3553
:-(
?SET_XOPT(password, Val);
3554 set_xoption([{<<"anonymous">>, [Val]} | Opts], Config) ->
3555
:-(
?SET_BOOL_XOPT(anonymous, Val);
3556 set_xoption([{<<"muc#roomconfig_whois">>, [Val]} | Opts], Config) ->
3557
:-(
case Val of
3558 <<"moderators">> ->
3559
:-(
?SET_XOPT(anonymous, true);
3560 <<"anyone">> ->
3561
:-(
?SET_XOPT(anonymous, false);
3562 _ ->
3563
:-(
{error, mongoose_xmpp_errors:bad_request()}
3564 end;
3565 set_xoption([{<<"muc#roomconfig_maxusers">>, [Val]} | Opts], Config) ->
3566
:-(
case Val of
3567 <<"none">> ->
3568
:-(
?SET_XOPT(max_users, none);
3569 _ ->
3570
:-(
?SET_NAT_XOPT(max_users, Val)
3571 end;
3572 set_xoption([{<<"muc#roomconfig_getmemberlist">>, Val} | Opts], Config) ->
3573
:-(
case Val of
3574 [<<"none">>] ->
3575
:-(
?SET_XOPT(maygetmemberlist, []);
3576 _ ->
3577
:-(
?SET_XOPT(maygetmemberlist, [binary_to_role(V) || V <- Val])
3578 end;
3579 set_xoption([{<<"muc#roomconfig_enablelogging">>, [Val]} | Opts], Config) ->
3580
:-(
?SET_BOOL_XOPT(logging, Val);
3581 set_xoption([_ | _Opts], _Config) ->
3582
:-(
{error, mongoose_xmpp_errors:bad_request()}.
3583
3584
3585 -spec change_config(config(), state()) -> {'result', [], state()}.
3586 change_config(Config, StateData) ->
3587
:-(
NSD = StateData#state{config = Config},
3588
:-(
case {(StateData#state.config)#config.persistent,
3589 Config#config.persistent} of
3590 {_, true} ->
3591
:-(
mod_muc:store_room(NSD#state.host_type, NSD#state.host, NSD#state.room, make_opts(NSD));
3592 {true, false} ->
3593
:-(
mod_muc:forget_room(NSD#state.host_type, NSD#state.host, NSD#state.room);
3594 {false, false} ->
3595
:-(
ok
3596 end,
3597
:-(
case {(StateData#state.config)#config.members_only,
3598 Config#config.members_only} of
3599 {false, true} ->
3600
:-(
NSD1 = remove_nonmembers(NSD),
3601
:-(
{result, [], NSD1};
3602 _ ->
3603
:-(
{result, [], NSD}
3604 end.
3605
3606
3607 -spec remove_nonmembers(state()) -> state().
3608 remove_nonmembers(StateData) ->
3609
:-(
F = fun(_LJID, #user{jid = JID}, SD) ->
3610
:-(
Affiliation = get_affiliation(JID, SD),
3611
:-(
case Affiliation of
3612 none ->
3613
:-(
safe_send_kickban_presence(JID, <<>>, <<"322">>, SD),
3614
:-(
set_role(JID, none, SD);
3615 _ ->
3616
:-(
SD
3617 end
3618 end,
3619
:-(
maps:fold(F, StateData, StateData#state.users).
3620
3621 -spec set_opts(Opts :: [{atom(), term()}], state()) -> state().
3622 set_opts([], SD) ->
3623 95 SD;
3624 set_opts([{Opt, Val} | Opts], SD=#state{config = C = #config{}}) ->
3625 311 NSD = case Opt of
3626 title ->
3627 2 SD#state{config = C#config{title = Val}};
3628 description ->
3629 2 SD#state{config = C#config{description = Val}};
3630 allow_change_subj ->
3631 2 SD#state{config = C#config{allow_change_subj = Val}};
3632 allow_query_users ->
3633 2 SD#state{config = C#config{allow_query_users = Val}};
3634 allow_private_messages ->
3635 2 SD#state{config = C#config{allow_private_messages = Val}};
3636 allow_visitor_nickchange ->
3637 2 SD#state{config = C#config{allow_visitor_nickchange = Val}};
3638 allow_visitor_status ->
3639 2 SD#state{config = C#config{allow_visitor_status = Val}};
3640 public ->
3641 2 SD#state{config = C#config{public = Val}};
3642 public_list ->
3643 2 SD#state{config = C#config{public_list = Val}};
3644 persistent ->
3645 77 SD#state{config = C#config{persistent = Val}};
3646 moderated ->
3647 2 SD#state{config = C#config{moderated = Val}};
3648 members_by_default ->
3649 2 SD#state{config = C#config{members_by_default = Val}};
3650 members_only ->
3651 2 SD#state{config = C#config{members_only = Val}};
3652 allow_user_invites ->
3653 2 SD#state{config = C#config{allow_user_invites = Val}};
3654 allow_multiple_sessions ->
3655 2 SD#state{config = C#config{allow_multiple_sessions = Val}};
3656 password_protected ->
3657 10 SD#state{config = C#config{password_protected = Val}};
3658 password ->
3659 10 SD#state{config = C#config{password = Val}};
3660 anonymous ->
3661 81 SD#state{config = C#config{anonymous = Val}};
3662 logging ->
3663 2 SD#state{config = C#config{logging = Val}};
3664 max_users ->
3665 2 MaxUsers = min(Val, get_service_max_users(SD)),
3666 2 SD#state{config = C#config{max_users = MaxUsers}};
3667 maygetmemberlist ->
3668 2 SD#state{config = C#config{maygetmemberlist = Val}};
3669 affiliations ->
3670 2 SD#state{affiliations = maps:from_list(Val)};
3671 subject ->
3672 2 SD#state{subject = Val};
3673 subject_author ->
3674 2 SD#state{subject_author = Val};
3675 subject_timestamp ->
3676
:-(
SD#state{subject_timestamp = Val};
3677 _ ->
3678 93 SD
3679 end,
3680 311 set_opts(Opts, NSD).
3681
3682
3683 -define(MAKE_CONFIG_OPT(Opt), {Opt, Config#config.Opt}).
3684
3685 -spec make_opts(state()) -> [{atom(), _}, ...].
3686 make_opts(StateData) ->
3687 75 Config = StateData#state.config,
3688 75 [
3689 ?MAKE_CONFIG_OPT(title),
3690 ?MAKE_CONFIG_OPT(description),
3691 ?MAKE_CONFIG_OPT(allow_change_subj),
3692 ?MAKE_CONFIG_OPT(allow_query_users),
3693 ?MAKE_CONFIG_OPT(allow_private_messages),
3694 ?MAKE_CONFIG_OPT(allow_visitor_status),
3695 ?MAKE_CONFIG_OPT(allow_visitor_nickchange),
3696 ?MAKE_CONFIG_OPT(public),
3697 ?MAKE_CONFIG_OPT(public_list),
3698 ?MAKE_CONFIG_OPT(persistent),
3699 ?MAKE_CONFIG_OPT(moderated),
3700 ?MAKE_CONFIG_OPT(members_by_default),
3701 ?MAKE_CONFIG_OPT(members_only),
3702 ?MAKE_CONFIG_OPT(allow_user_invites),
3703 ?MAKE_CONFIG_OPT(allow_multiple_sessions),
3704 ?MAKE_CONFIG_OPT(password_protected),
3705 ?MAKE_CONFIG_OPT(password),
3706 ?MAKE_CONFIG_OPT(anonymous),
3707 ?MAKE_CONFIG_OPT(logging),
3708 ?MAKE_CONFIG_OPT(max_users),
3709 ?MAKE_CONFIG_OPT(maygetmemberlist),
3710 {affiliations, maps:to_list(StateData#state.affiliations)},
3711 {subject, StateData#state.subject},
3712 {subject_author, StateData#state.subject_author}
3713 ].
3714
3715 -spec destroy_room(exml:element(), state()) -> {result, [], stop}.
3716 destroy_room(DestroyEl, StateData) ->
3717 65 remove_each_occupant_from_room(DestroyEl, StateData),
3718 65 case (StateData#state.config)#config.persistent of
3719 true ->
3720 59 mod_muc:forget_room(StateData#state.host_type,
3721 StateData#state.host,
3722 StateData#state.room);
3723 false ->
3724 6 ok
3725 end,
3726 65 {result, [], stop}.
3727
3728
3729 %% @doc Service Removes Each Occupant
3730 %%
3731 %% Send only one presence stanza of type "unavailable" to each occupant
3732 %% so that the user knows he or she has been removed from the room.
3733 %%
3734 %% If extended presence information specifying the JID of an alternate
3735 %% location and the reason for the room destruction was provided by the
3736 %% room owner, the presence stanza MUST include that information.
3737 %% @end
3738 -spec remove_each_occupant_from_room(exml:element(), state()) -> any().
3739 remove_each_occupant_from_room(DestroyEl, StateData) ->
3740 65 Packet = presence_stanza_of_type_unavailable(DestroyEl),
3741 65 send_to_occupants(Packet, StateData).
3742
3743
3744 -spec send_to_occupants(exml:element(), state()) -> any().
3745 send_to_occupants(Packet, StateData=#state{jid=RoomJID}) ->
3746 65 F = fun(User=#user{jid=UserJID}) ->
3747 16 ejabberd_router:route(occupant_jid(User, RoomJID), UserJID, Packet)
3748 end,
3749 65 foreach_user(F, StateData).
3750
3751 -spec send_to_all_users(exml:element(), state()) -> any().
3752 send_to_all_users(Packet, StateData=#state{jid=RoomJID}) ->
3753
:-(
F = fun(#user{jid = UserJID}) ->
3754
:-(
ejabberd_router:route(RoomJID, UserJID, Packet)
3755 end,
3756
:-(
foreach_user(F, StateData).
3757
3758
3759 -spec presence_stanza_of_type_unavailable(exml:element()) -> exml:element().
3760 presence_stanza_of_type_unavailable(DestroyEl) ->
3761 65 ItemEl = #xmlel{
3762 name = <<"item">>,
3763 attrs = [{<<"affiliation">>, <<"none">>}, {<<"role">>, <<"none">>}]},
3764 65 XEl = #xmlel{
3765 name = <<"x">>,
3766 attrs = [{<<"xmlns">>, ?NS_MUC_USER}],
3767 children = [ItemEl, DestroyEl]},
3768 65 #xmlel{
3769 name = <<"presence">>,
3770 attrs = [{<<"type">>, <<"unavailable">>}],
3771 children = [XEl]}.
3772
3773
3774 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3775 % Disco
3776
3777 -spec config_opt_to_feature(boolean(), Fiftrue :: binary(), Fiffalse :: binary()) -> binary().
3778 config_opt_to_feature(Opt, Fiftrue, Fiffalse) ->
3779
:-(
case Opt of
3780
:-(
true -> Fiftrue;
3781
:-(
false -> Fiffalse
3782 end.
3783
3784
3785 -spec process_iq_disco_info(jid:jid(), 'get' | 'set', ejabberd:lang(),
3786 state()) -> {'error', exml:element()}
3787 | {'result', [exml:element()], state()}.
3788 process_iq_disco_info(_From, set, _Lang, _StateData) ->
3789
:-(
{error, mongoose_xmpp_errors:not_allowed()};
3790 process_iq_disco_info(From, get, Lang, StateData) ->
3791
:-(
RoomJID = StateData#state.jid,
3792
:-(
Config = StateData#state.config,
3793
:-(
HostType = StateData#state.host_type,
3794
:-(
IdentityXML = mongoose_disco:identities_to_xml([identity(get_title(StateData))]),
3795
:-(
FeatureXML = mongoose_disco:get_muc_features(HostType, From, RoomJID, <<>>, Lang,
3796 room_features(Config)),
3797
:-(
InfoXML = iq_disco_info_extras(Lang, StateData),
3798
:-(
{result, IdentityXML ++ FeatureXML ++ InfoXML, StateData}.
3799
3800 identity(Name) ->
3801
:-(
#{category => <<"conference">>,
3802 type => <<"text">>,
3803 name => Name}.
3804
3805 -spec room_features(config()) -> [mongoose_disco:feature()].
3806 room_features(Config) ->
3807
:-(
[?NS_MUC,
3808 ?NS_MUC_STABLE_ID,
3809 config_opt_to_feature((Config#config.public),
3810 <<"muc_public">>, <<"muc_hidden">>),
3811 config_opt_to_feature((Config#config.persistent),
3812 <<"muc_persistent">>, <<"muc_temporary">>),
3813 config_opt_to_feature((Config#config.members_only),
3814 <<"muc_membersonly">>, <<"muc_open">>),
3815 config_opt_to_feature((Config#config.anonymous),
3816 <<"muc_semianonymous">>, <<"muc_nonanonymous">>),
3817 config_opt_to_feature((Config#config.moderated),
3818 <<"muc_moderated">>, <<"muc_unmoderated">>),
3819 config_opt_to_feature((Config#config.password_protected),
3820 <<"muc_passwordprotected">>, <<"muc_unsecured">>)].
3821
3822 -spec iq_disco_info_extras(ejabberd:lang(), state()) -> [exml:element()].
3823 iq_disco_info_extras(Lang, StateData) ->
3824
:-(
Len = integer_to_binary(maps:size(StateData#state.users)),
3825
:-(
Description = (StateData#state.config)#config.description,
3826
:-(
Fields = [info_field(<<"Room description">>, <<"muc#roominfo_description">>, Description, Lang),
3827 info_field(<<"Number of occupants">>, <<"muc#roominfo_occupants">>, Len, Lang)],
3828
:-(
Info = #{xmlns => <<"http://jabber.org/protocol/muc#roominfo">>, fields => Fields},
3829
:-(
mongoose_disco:info_list_to_xml([Info]).
3830
3831 -spec info_field(binary(), binary(), binary(), ejabberd:lang()) -> mongoose_disco:info_field().
3832 info_field(Label, Var, Value, Lang) ->
3833
:-(
#{label => translate:translate(Lang, Label), var => Var, values => [Value]}.
3834
3835 -spec process_iq_disco_items(jid:jid(), 'get' | 'set', ejabberd:lang(),
3836 state()) -> {'error', exml:element()}
3837 | {'result', [exml:element()], state()}.
3838 process_iq_disco_items(_From, set, _Lang, _StateData) ->
3839
:-(
{error, mongoose_xmpp_errors:not_allowed()};
3840 process_iq_disco_items(From, get, _Lang, StateData) ->
3841
:-(
case (StateData#state.config)#config.public_list of
3842 true ->
3843
:-(
{result, get_mucroom_disco_items(StateData), StateData};
3844 _ ->
3845
:-(
case is_occupant_or_admin(From, StateData) of
3846 true ->
3847
:-(
{result, get_mucroom_disco_items(StateData), StateData};
3848 _ ->
3849
:-(
{error, mongoose_xmpp_errors:forbidden()}
3850 end
3851 end.
3852
3853
3854 -spec get_title(state()) -> binary() | mod_muc:room().
3855 get_title(StateData) ->
3856
:-(
case (StateData#state.config)#config.title of
3857 <<>> ->
3858
:-(
StateData#state.room;
3859 Name ->
3860
:-(
Name
3861 end.
3862
3863
3864 -spec get_roomdesc_reply(jid:jid(), state(), Tail :: binary()
3865 ) -> 'false' | {'item', _}.
3866 get_roomdesc_reply(JID, StateData, Tail) ->
3867
:-(
IsOccupantOrAdmin = is_occupant_or_admin(JID, StateData),
3868
:-(
case {(StateData#state.config)#config.public or IsOccupantOrAdmin,
3869 (StateData#state.config)#config.public_list or IsOccupantOrAdmin} of
3870 {true, true} ->
3871
:-(
Title = get_title(StateData),
3872
:-(
{item, <<Title/binary, Tail/binary>>};
3873 {true, false} ->
3874
:-(
{item, get_title(StateData)};
3875 _ ->
3876
:-(
false
3877 end.
3878
3879
3880 -spec get_roomdesc_tail(state(), ejabberd:lang()) -> binary().
3881 get_roomdesc_tail(StateData, Lang) ->
3882
:-(
Desc = case (StateData#state.config)#config.public of
3883 true ->
3884
:-(
<<>>;
3885 _ ->
3886
:-(
translate:translate(Lang, <<"private, ">>)
3887 end,
3888
:-(
Count = count_users(StateData),
3889
:-(
CountBin = list_to_binary(integer_to_list(Count)),
3890
:-(
<<" (", Desc/binary, CountBin/binary, ")">>.
3891
3892
3893 -spec get_mucroom_disco_items(state()) -> [exml:element()].
3894 get_mucroom_disco_items(StateData=#state{jid=RoomJID}) ->
3895
:-(
maps:fold(fun(_LJID, User, Acc) ->
3896
:-(
Item = disco_item(User, RoomJID),
3897
:-(
[Item|Acc]
3898 end, [], StateData#state.users).
3899
3900 -spec disco_item(user(), 'undefined' | jid:jid()) -> exml:element().
3901 disco_item(User=#user{nick=Nick}, RoomJID) ->
3902
:-(
#xmlel{
3903 name = <<"item">>,
3904 attrs = [{<<"jid">>, jid:to_binary(occupant_jid(User, RoomJID))},
3905 {<<"name">>, Nick}]}.
3906
3907 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3908 %% Handle voice request or approval (XEP-0045 7.13, 8.6)
3909 -spec check_voice_approval(From :: jid:jid(), El :: exml:element(),
3910 Lang :: ejabberd:lang(), StateData :: state()
3911 ) -> {form, BRole :: binary()}
3912 | {role, BRole :: binary(), RoomNick :: mod_muc:nick()}
3913 | {error, any()}
3914 | ok.
3915 check_voice_approval(From, XEl, Lang, StateData) ->
3916
:-(
case mongoose_data_forms:find_and_parse_form(XEl) of
3917 #{type := <<"submit">>, kvs := #{<<"muc#role">> := [BRole]} = KVs} ->
3918
:-(
case {get_role(From, StateData) =:= moderator,
3919 maps:find(<<"muc#request_allow">>, KVs),
3920 maps:find(<<"muc#roomnick">>, KVs)} of
3921 {_, error, error} ->
3922
:-(
case catch binary_to_role(BRole) of
3923
:-(
{'EXIT', _} -> {error, mongoose_xmpp_errors:bad_request()};
3924
:-(
_ -> {form, BRole}
3925 end;
3926 {false, _, _} ->
3927
:-(
{error, mongoose_xmpp_errors:not_allowed()};
3928 {true, {ok, [<<"true">>]}, error} ->
3929
:-(
{error, mongoose_xmpp_errors:bad_request()};
3930 {true, {ok, [<<"true">>]}, {ok, [RoomNick]}} ->
3931
:-(
{role, BRole, RoomNick};
3932 {true, _, _} ->
3933
:-(
ok
3934 end;
3935 {error, Msg} ->
3936
:-(
{error, mongoose_xmpp_errors:bad_request(Lang, Msg)};
3937 _ ->
3938
:-(
{error, mongoose_xmpp_errors:bad_request(Lang, <<"Invalid form">>)}
3939 end.
3940
3941 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3942 %% Invitation support
3943
3944 -spec check_invitation(jid:simple_jid() | jid:jid(),
3945 [jlib:xmlcdata() | exml:element()], ejabberd:lang(), state())
3946 -> {'error', _} | {'ok', [jid:jid()]}.
3947 check_invitation(FromJID, Els, Lang, StateData) ->
3948
:-(
try
3949
:-(
unsafe_check_invitation(FromJID, Els, Lang, StateData)
3950
:-(
catch throw:{error, Reason} -> {error, Reason}
3951 end.
3952
3953
3954 -spec unsafe_check_invitation(jid:jid(), [jlib:xmlcdata() | exml:element()],
3955 ejabberd:lang(), state()) -> {ok, [jid:jid()]}.
3956 unsafe_check_invitation(FromJID, Els, Lang,
3957 StateData=#state{host=Host, server_host=ServerHost, jid=RoomJID}) ->
3958
:-(
FAffiliation = get_affiliation(FromJID, StateData),
3959
:-(
CanInvite = (StateData#state.config)#config.allow_user_invites
3960
:-(
orelse (FAffiliation == admin)
3961
:-(
orelse (FAffiliation == owner),
3962
:-(
case CanInvite of
3963 false ->
3964
:-(
throw({error, mongoose_xmpp_errors:forbidden()});
3965 true ->
3966
:-(
InviteEls = find_invite_elems(Els),
3967 %% Decode all JIDs first, so we fail early if any JID is invalid.
3968
:-(
JIDs = lists:map(fun decode_destination_jid/1, InviteEls),
3969
:-(
lists:foreach(
3970 fun(InviteEl) ->
3971
:-(
{JID, Reason, Msg} = create_invite(FromJID, InviteEl, Lang, StateData),
3972
:-(
mongoose_hooks:invitation_sent(Host, ServerHost, RoomJID,
3973 FromJID, JID, Reason),
3974
:-(
ejabberd_router:route(StateData#state.jid, JID, Msg)
3975 end, InviteEls),
3976
:-(
{ok, JIDs}
3977 end.
3978
3979 -spec create_invite(FromJID ::jid:jid(), InviteEl :: exml:element(),
3980 Lang :: ejabberd:lang(), StateData :: state()) ->
3981 {JID ::jid:jid(), Reason :: binary(), Msg :: exml:element()}.
3982 create_invite(FromJID, InviteEl, Lang, StateData) ->
3983
:-(
JID = decode_destination_jid(InviteEl),
3984 %% Create an invitation message and send it to the user.
3985
:-(
Reason = decode_reason(InviteEl),
3986
:-(
ContinueEl =
3987 case xml:get_path_s(InviteEl, [{elem, <<"continue">>}]) of
3988
:-(
<<>> -> [];
3989
:-(
Continue1 -> [Continue1]
3990 end,
3991
:-(
ReasonEl = #xmlel{
3992 name = <<"reason">>,
3993 children = [#xmlcdata{content = Reason}]},
3994
:-(
OutInviteEl = #xmlel{
3995 name = <<"invite">>,
3996 attrs = [{<<"from">>, jid:to_binary(FromJID)}],
3997 children = [ReasonEl] ++ ContinueEl},
3998
:-(
PasswdEl = create_password_elem(StateData),
3999
:-(
BodyEl = invite_body_elem(FromJID, Reason, Lang, StateData),
4000
:-(
Msg = create_invite_message_elem(
4001 OutInviteEl, BodyEl, PasswdEl, Reason),
4002
:-(
{JID, Reason, Msg}.
4003
4004 -spec decode_destination_jid(exml:element()) -> jid:jid().
4005 decode_destination_jid(InviteEl) ->
4006
:-(
case jid:from_binary(xml:get_tag_attr_s(<<"to">>, InviteEl)) of
4007
:-(
error -> throw({error, mongoose_xmpp_errors:jid_malformed()});
4008
:-(
JID -> JID
4009 end.
4010
4011
4012 -spec find_invite_elems([jlib:xmlcdata() | exml:element()]) -> [exml:element()].
4013 find_invite_elems(Els) ->
4014
:-(
case xml:remove_cdata(Els) of
4015 [#xmlel{name = <<"x">>, children = Els1} = XEl] ->
4016
:-(
case xml:get_tag_attr_s(<<"xmlns">>, XEl) of
4017 ?NS_MUC_USER ->
4018
:-(
ok;
4019 _ ->
4020
:-(
throw({error, mongoose_xmpp_errors:bad_request()})
4021 end,
4022
4023
:-(
InviteEls =
4024
:-(
[InviteEl || #xmlel{name = <<"invite">>} = InviteEl <- Els1],
4025
:-(
case InviteEls of
4026 [_|_] ->
4027
:-(
InviteEls;
4028 _ ->
4029
:-(
throw({error, mongoose_xmpp_errors:bad_request()})
4030 end;
4031 _ ->
4032
:-(
throw({error, mongoose_xmpp_errors:bad_request()})
4033 end.
4034
4035
4036 -spec create_password_elem(state()) -> [exml:element()].
4037 create_password_elem(#state{config=#config{password_protected=IsProtected,
4038 password=Password}}) ->
4039
:-(
case IsProtected of
4040 true ->
4041
:-(
[#xmlel{
4042 name = <<"password">>,
4043 children = [#xmlcdata{content = Password}]}];
4044 _ ->
4045
:-(
[]
4046 end.
4047
4048
4049 -spec invite_body_elem(jid:jid(), binary(), ejabberd:lang(), state()
4050 ) -> exml:element().
4051 invite_body_elem(FromJID, Reason, Lang, StateData) ->
4052
:-(
Text = invite_body_text(FromJID, Reason, Lang, StateData),
4053
:-(
#xmlel{
4054 name = <<"body">>,
4055 children = [#xmlcdata{content = Text}]}.
4056
4057
4058 -spec invite_body_text(jid:jid(), binary(), ejabberd:lang(), state()) -> binary().
4059 invite_body_text(FromJID, Reason, Lang,
4060 #state{
4061 jid=RoomJID,
4062 config=#config{
4063 password_protected=IsProtected,
4064 password=Password}}) ->
4065
:-(
BFromJID = jid:to_binary(FromJID),
4066
:-(
BRoomJID = jid:to_binary(RoomJID),
4067
:-(
ITranslate = translate:translate(Lang, <<" invites you to the room ">>),
4068
:-(
IMessage = <<BFromJID/binary, ITranslate/binary, BRoomJID/binary>>,
4069
:-(
BPassword = case IsProtected of
4070 true ->
4071
:-(
PTranslate = translate:translate(Lang, <<"the password is">>),
4072
:-(
<<", ", PTranslate/binary, " '", Password/binary, "'">>;
4073 _ ->
4074
:-(
<<>>
4075 end,
4076
:-(
BReason = case Reason of
4077
:-(
<<>> -> <<>>;
4078
:-(
_ -> <<" (", Reason/binary, ") ">>
4079 end,
4080
:-(
<<IMessage/binary, BPassword/binary, BReason/binary>>.
4081
4082
4083 -spec create_invite_message_elem(Inv :: exml:element(), Body :: exml:element(),
4084 Passwd :: [exml:element()], Reason :: binary()
4085 ) -> exml:element().
4086 create_invite_message_elem(InviteEl, BodyEl, PasswdEl, Reason)
4087 when is_list(PasswdEl), is_binary(Reason) ->
4088
:-(
UserXEl = #xmlel{
4089 name = <<"x">>,
4090 attrs = [{<<"xmlns">>, ?NS_MUC_USER}],
4091 children = [InviteEl|PasswdEl]},
4092
:-(
#xmlel{
4093 name = <<"message">>,
4094 attrs = [{<<"type">>, <<"normal">>}],
4095 children = [UserXEl, BodyEl]}.
4096
4097
4098 %% @doc Handle a message sent to the room by a non-participant.
4099 %% If it is a decline, send to the inviter.
4100 %% Otherwise, an error message is sent to the sender.
4101 -spec handle_roommessage_from_nonparticipant(exml:element(), ejabberd:lang(),
4102 state(), jid:simple_jid() | jid:jid()) -> mongoose_acc:t().
4103 handle_roommessage_from_nonparticipant(Packet, Lang, StateData, From) ->
4104 4 case catch check_decline_invitation(Packet) of
4105 {true, DeclineData} ->
4106
:-(
send_decline_invitation(DeclineData, StateData#state.jid, From);
4107 _ ->
4108 4 send_error_only_occupants(<<"messages">>, Packet, Lang, StateData#state.jid, From)
4109 end.
4110
4111
4112 %% @doc Check in the packet is a decline. If so, also returns the splitted
4113 %% packet. This function must be catched, because it crashes when the packet
4114 %% is not a decline message.
4115 -spec check_decline_invitation(exml:element()) ->
4116 {'true', {exml:element(), exml:element(), exml:element(), 'error' | jid:jid()}}.
4117 check_decline_invitation(Packet) ->
4118 4 #xmlel{name = <<"message">>} = Packet,
4119 4 XEl = xml:get_subtag(Packet, <<"x">>),
4120 4 ?NS_MUC_USER = xml:get_tag_attr_s(<<"xmlns">>, XEl),
4121
:-(
DEl = xml:get_subtag(XEl, <<"decline">>),
4122
:-(
ToString = xml:get_tag_attr_s(<<"to">>, DEl),
4123
:-(
ToJID = jid:from_binary(ToString),
4124
:-(
{true, {Packet, XEl, DEl, ToJID}}.
4125
4126
4127 %% @doc Send the decline to the inviter user.
4128 %% The original stanza must be slightly modified.
4129 -spec send_decline_invitation({exml:element(), exml:element(), exml:element(), jid:jid()},
4130 jid:jid(), jid:simple_jid() | jid:jid()) -> mongoose_acc:t().
4131 send_decline_invitation({Packet, XEl, DEl, ToJID}, RoomJID, FromJID) ->
4132
:-(
FromString = jid:to_binary(FromJID),
4133
:-(
#xmlel{name = <<"decline">>, attrs = DAttrs, children = DEls} = DEl,
4134
:-(
DAttrs2 = lists:keydelete(<<"to">>, 1, DAttrs),
4135
:-(
DAttrs3 = [{<<"from">>, FromString} | DAttrs2],
4136
:-(
DEl2 = #xmlel{name = <<"decline">>, attrs = DAttrs3, children = DEls},
4137
:-(
XEl2 = xml:replace_subelement(XEl, DEl2),
4138
:-(
Packet2 = xml:replace_subelement(Packet, XEl2),
4139
:-(
ejabberd_router:route(RoomJID, ToJID, Packet2).
4140
4141 -spec send_error_only_occupants(binary(), exml:element(),
4142 binary() | nonempty_string(),
4143 jid:jid(), jid:jid()) -> mongoose_acc:t().
4144 send_error_only_occupants(What, Packet, Lang, RoomJID, From)
4145 when is_binary(What) ->
4146 4 ErrText = <<"Only occupants are allowed to send ",
4147 What/bytes, " to the conference">>,
4148 4 Err = jlib:make_error_reply(Packet, mongoose_xmpp_errors:not_acceptable(Lang, ErrText)),
4149 4 ejabberd_router:route(RoomJID, From, Err).
4150
4151
4152 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4153 % Logging
4154
4155 -spec add_to_log(atom(), any(), state()) -> 'ok'.
4156 add_to_log(Type, Data, StateData)
4157 when Type == roomconfig_change_disabledlogging ->
4158 %% When logging is disabled, the config change message must be logged:
4159
:-(
mod_muc_log:add_to_log(
4160 StateData#state.server_host, roomconfig_change, Data,
4161 jid:to_binary(StateData#state.jid), make_opts(StateData));
4162 add_to_log(Type, Data, StateData) ->
4163 655 case (StateData#state.config)#config.logging of
4164 true ->
4165
:-(
mod_muc_log:add_to_log(
4166 StateData#state.server_host, Type, Data,
4167 jid:to_binary(StateData#state.jid), make_opts(StateData));
4168 false ->
4169 655 ok
4170 end.
4171
4172 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4173 %% Users number checking
4174
4175 -spec tab_add_online_user(jid:jid(), state()) -> any().
4176 tab_add_online_user(JID, StateData) ->
4177 110 {LUser, LServer, _} = jid:to_lower(JID),
4178 110 US = {LUser, LServer},
4179 110 Room = StateData#state.room,
4180 110 Host = StateData#state.host,
4181 110 catch ets:insert(
4182 muc_online_users,
4183 #muc_online_users{us = US, room = Room, host = Host}).
4184
4185
4186 -spec tab_remove_online_user(jid:simple_jid() | jid:jid(), state()) -> any().
4187 tab_remove_online_user(JID, StateData) ->
4188 110 {LUser, LServer, _} = jid:to_lower(JID),
4189 110 US = {LUser, LServer},
4190 110 Room = StateData#state.room,
4191 110 Host = StateData#state.host,
4192 110 catch ets:delete_object(
4193 muc_online_users,
4194 #muc_online_users{us = US, room = Room, host = Host}).
4195
4196
4197 -spec tab_count_user(jid:jid()) -> non_neg_integer().
4198 tab_count_user(JID) ->
4199 118 {LUser, LServer, _} = jid:to_lower(JID),
4200 118 US = {LUser, LServer},
4201 118 case catch ets:select(
4202 muc_online_users,
4203 [{#muc_online_users{us = US, _ = '_'}, [], [[]]}]) of
4204 Res when is_list(Res) ->
4205 118 length(Res);
4206 _ ->
4207
:-(
0
4208 end.
4209
4210 element_size(El) ->
4211 182 exml:xml_size(El).
4212
4213 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4214 %% Routing functions
4215
4216 -spec route_message(routed_message(), state()) -> state().
4217 route_message(#routed_message{allowed = true, type = <<"groupchat">>,
4218 from = From, packet = Packet, lang = Lang}, StateData) ->
4219 91 Activity = get_user_activity(From, StateData),
4220 91 Now = os:system_time(microsecond),
4221 91 MinMessageInterval = trunc(get_opt(StateData, min_message_interval) * 1000000),
4222 91 Size = element_size(Packet),
4223 91 {MessageShaper, MessageShaperInterval} = mongoose_shaper:update(Activity#activity.message_shaper, Size),
4224 91 case {Activity#activity.message /= undefined,
4225 Now >= Activity#activity.message_time + MinMessageInterval,
4226 MessageShaperInterval} of
4227 {true, _, _} ->
4228
:-(
ErrText = <<"Traffic rate limit is exceeded">>,
4229
:-(
Err = jlib:make_error_reply(Packet, mongoose_xmpp_errors:resource_constraint(Lang, ErrText)),
4230
:-(
ejabberd_router:route(StateData#state.jid, From, Err),
4231
:-(
StateData;
4232 {false, true, 0} ->
4233 91 {RoomShaper, RoomShaperInterval} = mongoose_shaper:update(StateData#state.room_shaper, Size),
4234 91 RoomQueueEmpty = queue:is_empty(StateData#state.room_queue),
4235 91 case {RoomShaperInterval, RoomQueueEmpty} of
4236 {0, true} ->
4237 91 NewActivity = Activity#activity{
4238 message_time = Now,
4239 message_shaper = MessageShaper},
4240 91 StateData1 = store_user_activity(From, NewActivity, StateData),
4241 91 StateData2 = StateData1#state{room_shaper = RoomShaper},
4242 91 {next_state, normal_state, StateData3, _} =
4243 process_groupchat_message(From, Packet, StateData2),
4244 91 StateData3;
4245 _ ->
4246
:-(
StateData1 = schedule_queue_processing_when_empty(
4247 RoomQueueEmpty, RoomShaper, RoomShaperInterval, StateData),
4248
:-(
NewActivity = Activity#activity{
4249 message_time = Now,
4250 message_shaper = MessageShaper,
4251 message = Packet},
4252
:-(
RoomQueue = queue:in({message, From}, StateData#state.room_queue),
4253
:-(
StateData2 = store_user_activity(From, NewActivity, StateData1),
4254
:-(
StateData2#state{room_queue = RoomQueue}
4255 end;
4256 _ ->
4257
:-(
MessageInterval = (Activity#activity.message_time + MinMessageInterval - Now) div 1000,
4258
:-(
Interval = lists:max([MessageInterval, MessageShaperInterval]),
4259
:-(
erlang:send_after(Interval, self(), {process_user_message, From}),
4260
:-(
NewActivity = Activity#activity{
4261 message = Packet,
4262 message_shaper = MessageShaper},
4263
:-(
store_user_activity(From, NewActivity, StateData)
4264 end;
4265 route_message(#routed_message{allowed = true, type = <<"error">>, from = From,
4266 packet = Packet, lang = Lang}, StateData) ->
4267
:-(
case is_user_online(From, StateData) of
4268 true ->
4269
:-(
ErrorText
4270 = <<"This participant is kicked from the room because he sent an error message">>,
4271
:-(
expulse_participant(Packet, From, StateData, translate:translate(Lang, ErrorText));
4272 _ ->
4273
:-(
StateData
4274 end;
4275 route_message(#routed_message{allowed = true, type = <<"chat">>, from = From, packet = Packet,
4276 lang = Lang}, StateData) ->
4277
:-(
ErrText = <<"It is not allowed to send private messages to the conference">>,
4278
:-(
Err = jlib:make_error_reply(
4279 Packet, mongoose_xmpp_errors:not_acceptable(Lang, ErrText)),
4280
:-(
ejabberd_router:route(
4281 StateData#state.jid,
4282 From, Err),
4283
:-(
StateData;
4284 route_message(#routed_message{allowed = true, type = Type, from = From,
4285 packet = #xmlel{name = <<"message">>,
4286 children = Els} = Packet, lang = Lang},
4287 StateData) when (Type == <<>> orelse Type == <<"normal">>) ->
4288
4289
:-(
Invite = xml:get_path_s(Packet, [{elem, <<"x">>}, {elem, <<"invite">>}]),
4290
:-(
case Invite of
4291 <<>> ->
4292
:-(
AppType = check_voice_approval(From, Packet, Lang, StateData),
4293
:-(
route_voice_approval(AppType, From, Packet, Lang, StateData);
4294 _ ->
4295
:-(
InType = check_invitation(From, Els, Lang, StateData),
4296
:-(
route_invitation(InType, From, Packet, Lang, StateData)
4297 end;
4298 route_message(#routed_message{allowed = true, from = From, packet = Packet,
4299 lang = Lang}, StateData) ->
4300
:-(
ErrText = <<"Improper message type">>,
4301
:-(
Err = jlib:make_error_reply(Packet, mongoose_xmpp_errors:not_acceptable(Lang, ErrText)),
4302
:-(
ejabberd_router:route(StateData#state.jid,
4303 From, Err),
4304
:-(
StateData;
4305 route_message(#routed_message{type = <<"error">>}, StateData) ->
4306
:-(
StateData;
4307 route_message(#routed_message{from = From, packet = Packet, lang = Lang},
4308 StateData) ->
4309 4 handle_roommessage_from_nonparticipant(Packet, Lang, StateData, From),
4310 4 StateData.
4311
4312 -spec schedule_queue_processing_when_empty(RoomQueueEmpty :: boolean(),
4313 RoomShaper :: mongoose_shaper:shaper(),
4314 RoomShaperInterval :: non_neg_integer(),
4315 StateData :: state()) -> state().
4316 schedule_queue_processing_when_empty(true, RoomShaper, RoomShaperInterval, StateData) ->
4317
:-(
erlang:send_after(RoomShaperInterval, self(), process_room_queue),
4318
:-(
StateData#state{room_shaper = RoomShaper};
4319 schedule_queue_processing_when_empty(_RoomQueueEmpty, _RoomShaper,
4320 _RoomShaperInterval, StateData) ->
4321
:-(
StateData.
4322
4323 -spec route_error(mod_muc:nick(), jid:jid(), exml:element(), state()) -> state().
4324 route_error(Nick, From, Error, StateData) ->
4325 %% TODO: s/Nick/<<>>/
4326 8 ejabberd_router:route(jid:replace_resource(StateData#state.jid, Nick),
4327 From, Error),
4328 8 StateData.
4329
4330
4331 -spec route_voice_approval('ok' | {'error', exml:element()} | {'form', binary()}
4332 | {'role', binary(), binary()}, jid:jid(), exml:element(),
4333 ejabberd:lang(), state()) -> state().
4334 route_voice_approval({error, ErrType}, From, Packet, _Lang, StateData) ->
4335
:-(
ejabberd_router:route(StateData#state.jid, From,
4336 jlib:make_error_reply(Packet, ErrType)),
4337
:-(
StateData;
4338 route_voice_approval({form, RoleName}, From, _Packet, _Lang, StateData) ->
4339
:-(
{Nick, _} = get_participant_data(From, StateData),
4340
:-(
ApprovalForm = make_voice_approval_form(From, Nick, RoleName),
4341
:-(
F = fun({_, Info}) ->
4342
:-(
ejabberd_router:route(StateData#state.jid, Info#user.jid,
4343 ApprovalForm)
4344 end,
4345
:-(
lists:foreach(F, search_role(moderator, StateData)),
4346
:-(
StateData;
4347 route_voice_approval({role, BRole, Nick}, From, Packet, Lang, StateData) ->
4348
:-(
Items = [#xmlel{name = <<"item">>,
4349 attrs = [{<<"role">>, BRole},
4350 {<<"nick">>, Nick}]}],
4351
:-(
case process_admin_items_set(From, Items, Lang, StateData) of
4352
:-(
{result, _Res, SD1} -> SD1;
4353 {error, Error} ->
4354
:-(
ejabberd_router:route(StateData#state.jid, From,
4355 jlib:make_error_reply(Packet, Error)),
4356
:-(
StateData
4357 end;
4358 route_voice_approval(_Type, From, Packet, _Lang, StateData) ->
4359
:-(
ejabberd_router:route(StateData#state.jid, From,
4360 jlib:make_error_reply(Packet, mongoose_xmpp_errors:bad_request())),
4361
:-(
StateData.
4362
4363
4364 -spec route_invitation(InvitationsOrError,
4365 From, Packet, Lang, state()) -> state() when
4366 InvitationsOrError :: {'error', jlib:xmlcdata() | exml:element()}
4367 | {'ok', [jid:jid()]},
4368 From :: jid:simple_jid() | jid:jid(),
4369 Packet :: exml:element(),
4370 Lang :: ejabberd:lang().
4371 route_invitation({error, Error}, From, Packet, _Lang, StateData) ->
4372
:-(
Err = jlib:make_error_reply(Packet, Error),
4373
:-(
ejabberd_router:route(StateData#state.jid, From, Err),
4374
:-(
StateData;
4375 route_invitation({ok, IJIDs}, _From, _Packet, _Lang,
4376 #state{ config = #config{ members_only = true } } = StateData0) ->
4377
:-(
lists:foldl(
4378 fun(IJID, StateData) ->
4379
:-(
case get_affiliation(IJID, StateData) of
4380 none ->
4381
:-(
NSD = set_affiliation(IJID, member, StateData),
4382
:-(
store_room_if_persistent(NSD),
4383
:-(
NSD;
4384 _ ->
4385
:-(
StateData
4386 end
4387 end, StateData0, IJIDs);
4388 route_invitation({ok, _IJIDs}, _From, _Packet, _Lang, StateData0) ->
4389
:-(
StateData0.
4390
4391 -spec store_room_if_persistent(state()) -> any().
4392 store_room_if_persistent(#state{ host = Host, room = Room, host_type = HostType,
4393 config = #config{ persistent = true } } = StateData) ->
4394
:-(
mod_muc:store_room(HostType, Host, Room, make_opts(StateData));
4395 store_room_if_persistent(_SD) ->
4396
:-(
ok.
4397
4398 -spec route_iq(mongoose_acc:t(), routed_iq(), state()) -> {ok | stop, state()}.
4399 route_iq(_Acc, #routed_iq{iq = #iq{type = Type}}, StateData)
4400 when Type == error; Type == result ->
4401
:-(
{ok, StateData};
4402 route_iq(Acc, #routed_iq{iq = #iq{type = Type, xmlns = ?NS_MUC_ADMIN, lang = Lang,
4403 sub_el = SubEl}, from = From} = Routed, StateData) ->
4404
:-(
Res = process_iq_admin(From, Type, Lang, SubEl, StateData),
4405
:-(
do_route_iq(Acc, Res, Routed, StateData);
4406 route_iq(Acc, #routed_iq{iq = #iq{type = Type, xmlns = ?NS_MUC_OWNER, lang = Lang,
4407 sub_el = SubEl}, from = From} = Routed, StateData) ->
4408
:-(
Res = process_iq_owner(From, Type, Lang, SubEl, StateData, normal_state),
4409
:-(
do_route_iq(Acc, Res, Routed, StateData);
4410 route_iq(Acc, #routed_iq{iq = #iq{type = Type, xmlns = ?NS_DISCO_INFO, lang = Lang},
4411 from = From} = Routed, StateData) ->
4412
:-(
Res = process_iq_disco_info(From, Type, Lang, StateData),
4413
:-(
do_route_iq(Acc, Res, Routed, StateData);
4414 route_iq(Acc, #routed_iq{iq = #iq{type = Type, xmlns = ?NS_DISCO_ITEMS, lang = Lang},
4415 from = From} = Routed, StateData) ->
4416
:-(
Res = process_iq_disco_items(From, Type, Lang, StateData),
4417
:-(
do_route_iq(Acc, Res, Routed, StateData);
4418 route_iq(Acc, #routed_iq{iq = IQ = #iq{}, packet = Packet, from = From},
4419 #state{host = Host, host_type = HostType, jid = RoomJID} = StateData) ->
4420 %% Custom IQ, addressed to this room's JID.
4421 113 case mod_muc_iq:process_iq(Host, From, RoomJID, Acc, IQ) of
4422 {Acc1, error} ->
4423
:-(
?LOG_WARNING(#{what => muc_process_iq_failed, acc => Acc, server => Host,
4424
:-(
host_type => HostType, room_jid => RoomJID}),
4425
:-(
E = mongoose_xmpp_errors:feature_not_implemented(
4426 <<"en">>, <<"From mod_muc_room">>),
4427
:-(
{Acc2, Err} = jlib:make_error_reply(Acc1, Packet, E),
4428
:-(
ejabberd_router:route(RoomJID, From, Acc2, Err);
4429 113 _ -> ok
4430 end,
4431 113 {ok, StateData};
4432 route_iq(Acc, #routed_iq{packet = Packet, from = From}, StateData) ->
4433
:-(
{Acc1, Err} = jlib:make_error_reply(
4434 Acc, Packet, mongoose_xmpp_errors:feature_not_implemented()),
4435
:-(
ejabberd_router:route(StateData#state.jid, From, Acc1, Err),
4436
:-(
{ok, StateData}.
4437
4438
4439 -spec do_route_iq(mongoose_acc:t(), {result, [exml:element()], state()} | {error, exml:element()},
4440 routed_iq(), state()) -> {ok | stop, state()}.
4441 do_route_iq(Acc, Res1, #routed_iq{iq = #iq{xmlns = XMLNS, sub_el = SubEl} = IQ,
4442 from = From}, StateData) ->
4443
:-(
{IQRes, RoutingResult} = case Res1 of
4444 {result, Res, SD} ->
4445
:-(
{
4446 IQ#iq{type = result,
4447 sub_el = [#xmlel{name = <<"query">>,
4448 attrs = [{<<"xmlns">>, XMLNS}],
4449 children = Res}]},
4450 case SD of
4451
:-(
stop -> {stop, StateData};
4452
:-(
_ -> {ok, SD}
4453 end
4454 };
4455 {error, Error} ->
4456
:-(
{
4457 IQ#iq{type = error, sub_el = [SubEl, Error]},
4458 {ok, StateData}
4459 }
4460 end,
4461
:-(
ejabberd_router:route(StateData#state.jid, From, Acc,
4462 jlib:iq_to_xml(IQRes)),
4463
:-(
RoutingResult.
4464
4465
4466 -spec route_nick_message(routed_nick_message(), state()) -> state().
4467 route_nick_message(#routed_nick_message{decide = {expulse_sender, _Reason},
4468 packet = Packet, lang = Lang, from = From}, StateData) ->
4469
:-(
ErrorText = <<"This participant is kicked from the room because he",
4470 "sent an error message to another participant">>,
4471
:-(
?LOG_DEBUG(ls(#{what => muc_expulse_sender, text => ErrorText,
4472
:-(
user => From#jid.luser, exml_packet => Packet}, StateData)),
4473
:-(
expulse_participant(Packet, From, StateData, translate:translate(Lang, ErrorText));
4474 route_nick_message(#routed_nick_message{decide = forget_message}, StateData) ->
4475
:-(
StateData;
4476 route_nick_message(#routed_nick_message{decide = continue_delivery, allow_pm = true,
4477 online = true, packet = Packet, from = From, type = <<"groupchat">>,
4478 lang = Lang, nick = ToNick}, StateData) ->
4479
:-(
ErrText = <<"It is not allowed to send private messages of type groupchat">>,
4480
:-(
Err = jlib:make_error_reply(
4481 Packet, mongoose_xmpp_errors:bad_request(Lang, ErrText)),
4482
:-(
route_error(ToNick, From, Err, StateData),
4483
:-(
StateData;
4484 route_nick_message(#routed_nick_message{decide = continue_delivery, allow_pm = true,
4485 online = true, packet = Packet, from = From,
4486 lang = Lang, nick = ToNick, jid = false}, StateData) ->
4487
:-(
ErrText = <<"Recipient is not in the conference room">>,
4488
:-(
Err = jlib:make_error_reply(
4489 Packet, mongoose_xmpp_errors:item_not_found(Lang, ErrText)),
4490
:-(
route_error(ToNick, From, Err, StateData),
4491
:-(
StateData;
4492 route_nick_message(#routed_nick_message{decide = continue_delivery, allow_pm = true,
4493 online = true, packet = Packet, from = From, jid = ToJID}, StateData) ->
4494 8 Packet1 = maybe_add_x_element(Packet),
4495 8 {ok, #user{nick = FromNick}} = maps:find(jid:to_lower(From),
4496 StateData#state.users),
4497 8 ejabberd_router:route(
4498 jid:replace_resource(StateData#state.jid, FromNick), ToJID, Packet1),
4499 8 StateData;
4500 route_nick_message(#routed_nick_message{decide = continue_delivery,
4501 allow_pm = true,
4502 online = false} = Routed, StateData) ->
4503
:-(
#routed_nick_message{packet = Packet, from = From,
4504 lang = Lang, nick = ToNick} = Routed,
4505
:-(
RoomJID = jid:replace_resource(StateData#state.jid, ToNick),
4506
:-(
send_error_only_occupants(<<"messages">>, Packet, Lang, RoomJID, From),
4507
:-(
StateData;
4508 route_nick_message(#routed_nick_message{decide = continue_delivery, allow_pm = false,
4509 packet = Packet, from = From,
4510 lang = Lang, nick = ToNick}, StateData) ->
4511
:-(
ErrText = <<"It is not allowed to send private messages">>,
4512
:-(
Err = jlib:make_error_reply(
4513 Packet, mongoose_xmpp_errors:forbidden(Lang, ErrText)),
4514
:-(
route_error(ToNick, From, Err, StateData),
4515
:-(
StateData.
4516
4517
4518 -spec route_nick_iq(routed_nick_iq(), state()) -> 'ok'.
4519 route_nick_iq(#routed_nick_iq{allow_query = true, online = {true, _, _}, jid = false,
4520 iq = reply}, _StateData) ->
4521
:-(
ok;
4522 route_nick_iq(#routed_nick_iq{allow_query = true, online = {true, _, _}, jid = false,
4523 packet = Packet, lang = Lang, from = From, nick = ToNick}, StateData) ->
4524
:-(
ErrText = <<"Recipient is not in the conference room">>,
4525
:-(
Err = jlib:make_error_reply(
4526 Packet, mongoose_xmpp_errors:item_not_found(Lang, ErrText)),
4527
:-(
route_error(ToNick, From, Err, StateData);
4528 route_nick_iq(#routed_nick_iq{allow_query = true, online = {true, NewId, FromFull},
4529 jid = ToJID, packet = Packet, stanza = StanzaId}, StateData) ->
4530
:-(
{ok, #user{nick = FromNick}} = maps:find(jid:to_lower(FromFull),
4531 StateData#state.users),
4532
:-(
{ToJID2, Packet2} = handle_iq_vcard(FromFull, ToJID, StanzaId, NewId, Packet),
4533
:-(
ejabberd_router:route(
4534 jid:replace_resource(StateData#state.jid, FromNick),
4535 ToJID2, Packet2);
4536 route_nick_iq(#routed_nick_iq{online = {false, _, _}, iq = reply}, _StateData) ->
4537
:-(
ok;
4538 route_nick_iq(#routed_nick_iq{online = {false, _, _}, from = From, nick = ToNick,
4539 packet = Packet, lang = Lang}, StateData) ->
4540
:-(
RoomJID = jid:replace_resource(StateData#state.jid, ToNick),
4541
:-(
send_error_only_occupants(<<"queries">>, Packet, Lang, RoomJID, From);
4542 route_nick_iq(#routed_nick_iq{iq = reply}, _StateData) ->
4543
:-(
ok;
4544 route_nick_iq(#routed_nick_iq{packet = Packet, lang = Lang, nick = ToNick,
4545 from = From}, StateData) ->
4546
:-(
ErrText = <<"Queries to the conference members are "
4547 "not allowed in this room">>,
4548
:-(
Err = jlib:make_error_reply(Packet, mongoose_xmpp_errors:not_allowed(Lang, ErrText)),
4549
:-(
route_error(ToNick, From, Err, StateData).
4550
4551
4552 -spec decode_reason(exml:element()) -> binary().
4553 decode_reason(Elem) ->
4554
:-(
xml:get_path_s(Elem, [{elem, <<"reason">>}, cdata]).
4555
4556 -spec make_voice_approval_form(From :: jid:simple_jid() | jid:jid(),
4557 Nick :: binary(), Role :: binary()) -> exml:element().
4558 make_voice_approval_form(From, Nick, Role) ->
4559
:-(
Title = <<"Voice request">>,
4560
:-(
Instructions = <<"To approve this request"
4561 " for voice, select the &quot;Grant voice to this person?&quot; checkbox"
4562 " and click OK. To skip this request, click the cancel button.">>,
4563
:-(
Fields = [#{var => <<"muc#role">>, type => <<"list-single">>,
4564 label => <<"Request role">>, values => [Role]},
4565 #{var => <<"muc#jid">>, type => <<"jid-single">>,
4566 label => <<"User ID">>, values => [jid:to_binary(From)]},
4567 #{var => <<"muc#roomnick">>, type => <<"text-single">>,
4568 label => <<"Room Nickname">>, values => [Nick]},
4569 #{var => <<"muc#request_allow">>, type => <<"boolean">>,
4570 label => <<"Grant voice to this person?">>, values => [<<"false">>]}],
4571
:-(
Form = mongoose_data_forms:form(#{title => Title, instructions => Instructions,
4572 ns => ?NS_MUC_REQUEST, fields => Fields}),
4573
:-(
#xmlel{name = <<"message">>, children = [Form]}.
4574
4575 -spec xfield(binary(), any(), binary(), binary(), ejabberd:lang()) -> mongoose_data_forms:field().
4576 xfield(Type, Label, Var, Val, Lang) ->
4577
:-(
#{type => Type, label => translate:translate(Lang, Label), var => Var, values => [Val]}.
4578
4579 -spec boolxfield(any(), binary(), any(), ejabberd:lang()) -> mongoose_data_forms:field().
4580 boolxfield(Label, Var, Val, Lang) ->
4581
:-(
xfield(<<"boolean">>, Label, Var,
4582 case Val of
4583
:-(
true -> <<"1">>;
4584
:-(
_ -> <<"0">>
4585 end, Lang).
4586
4587 stringxfield(Label, Var, Val, Lang) ->
4588
:-(
xfield(<<"text-single">>, Label, Var, Val, Lang).
4589
4590 privatexfield(Label, Var, Val, Lang) ->
4591
:-(
xfield(<<"text-private">>, Label, Var, Val, Lang).
4592
4593 notify_users_modified(#state{host_type = HostType, jid = JID, users = Users} = State) ->
4594 408 mod_muc_log:set_room_occupants(HostType, self(), JID, maps:values(Users)),
4595 408 State.
4596
4597 ls(LogMap, State) ->
4598
:-(
maps:merge(LogMap, #{room => State#state.room,
4599 sub_host => State#state.host}).
4600
4601 get_opt(#state{host_type = HostType}, Opt) ->
4602 2053 gen_mod:get_module_opt(HostType, mod_muc, Opt).
4603
4604 get_current_timestamp() ->
4605
:-(
SystemTime = os:system_time(second),
4606
:-(
TimeStamp = calendar:system_time_to_rfc3339(SystemTime, [{offset, "Z"}]),
4607
:-(
list_to_binary(TimeStamp).
4608
4609 read_hibernate_timeout(HostType) ->
4610 95 gen_mod:get_module_opt(HostType, mod_muc, hibernate_timeout).
4611
4612 maybe_add_x_element(Msg) ->
4613 8 {xmlel, Type, InfoXML, Children} = Msg,
4614 8 case lists:member({xmlel, <<"x">>, [{<<"xmlns">>, ?NS_MUC_USER}], []}, Children) of
4615
:-(
true -> Msg;
4616 false ->
4617 8 NewChildren = lists:append(Children,
4618 [{xmlel, <<"x">>, [{<<"xmlns">>, ?NS_MUC_USER}], []}]),
4619 8 {xmlel, Type, InfoXML, NewChildren}
4620 end.
4621
4622 kick_stanza_for_old_protocol(Attrs) ->
4623
:-(
Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
4624
:-(
ErrText = <<"You are not in the room.">>,
4625
:-(
ErrText2 = translate:translate(Lang, ErrText),
4626
:-(
Response = #xmlel{name = <<"presence">>, attrs = [{<<"type">>, <<"unavailable">>}]},
4627
:-(
ItemAttrs = [{<<"affiliation">>, <<"none">>}, {<<"role">>, <<"none">>}],
4628
:-(
ItemEls = [#xmlel{name = <<"reason">>, children = [#xmlcdata{content = ErrText2}]}],
4629
:-(
Status = [status_code(110), status_code(307), status_code(333)],
4630
:-(
xml:append_subtags(
4631 Response,
4632 [#xmlel{name = <<"x">>, attrs = [{<<"xmlns">>, ?NS_MUC}],
4633 children = [#xmlel{name = <<"item">>, attrs = ItemAttrs,
4634 children = ItemEls} | Status]}]).
Line Hits Source