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