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