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