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