./ct_report/coverage/mod_muc_room.COVER.html

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