./ct_report/coverage/mod_muc_room.COVER.html

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