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