./ct_report/coverage/mod_muc.COVER.html

1 %%%----------------------------------------------------------------------
2 %%% File : mod_muc.erl
3 %%% Author : Alexey Shchepin <alexey@process-one.net>
4 %%% Purpose : MUC support (XEP-0045)
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).
27 -author('alexey@process-one.net').
28 -xep([{xep, 45}, {version, "1.34.5"}]).
29 -xep([{xep, 249}, {version, "1.2"}]).
30 -behaviour(gen_server).
31 -behaviour(gen_mod).
32 -behaviour(mongoose_packet_handler).
33 -behaviour(mongoose_module_metrics).
34 -behaviour(mongoose_instrument_probe).
35
36 %% API
37 -export([start_link/2,
38 start/2,
39 stop/1,
40 supported_features/0,
41 config_spec/0,
42 instrumentation/1,
43 process_room_affiliation/1,
44 room_destroyed/4,
45 store_room/4,
46 restore_room/3,
47 forget_room/3,
48 create_instant_room/6,
49 broadcast_service_message/2,
50 can_use_nick/4,
51 room_jid_to_pid/1,
52 get_vh_rooms/2,
53 default_host/0]).
54 -export([server_host_to_muc_host/2]).
55
56 %% For testing purposes only
57 -export([register_room/4]).
58
59 %% gen_server callbacks
60 -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
61 terminate/2, code_change/3]).
62
63 %% packet handler callback
64 -export([process_packet/5]).
65
66 %% mongoose_instrument_probe callback
67 -export([probe/3]).
68
69 %% Hooks handlers
70 -export([is_muc_room_owner/3,
71 can_access_room/3,
72 remove_domain/3,
73 acc_room_affiliations/3,
74 can_access_identity/3,
75 disco_local_items/3,
76 node_cleanup_for_host_type/3]).
77
78 %% Stats
79 -export([online_rooms_number/0]).
80 -export([hibernated_rooms_number/0]).
81
82 -export([config_metrics/1]).
83
84 -ignore_xref([
85 broadcast_service_message/2, create_instant_room/6, hibernated_rooms_number/0,
86 online_rooms_number/0, register_room/4, restore_room/3, start_link/2
87 ]).
88
89 -include("mongoose.hrl").
90 -include("jlib.hrl").
91 -include("mongoose_rsm.hrl").
92 -include("mongoose_config_spec.hrl").
93 -include("mod_muc_room.hrl").
94
95 -export_type([access/0,
96 room/0,
97 nick/0,
98 packet/0,
99 role/0,
100 affiliation/0
101 ]).
102
103 -type role() :: moderator | participant | visitor | none.
104 -type affiliation() :: admin | owner | member | outcast | none.
105 -type room() :: binary().
106 -type nick() :: binary().
107 -type room_host() :: jid:simple_bare_jid().
108 -type packet() :: exml:element().
109 -type from_to_packet() ::
110 {From :: jid:jid(), To :: jid:jid(), Acc :: mongoose_acc:t(),
111 Packet :: packet()}.
112 -type access() :: {_AccessRoute, _AccessCreate, _AccessAdmin, _AccessPersistent}.
113 -type host_type() :: mongooseim:host_type().
114 -type muc_host() :: jid:lserver().
115
116 -include("mod_muc.hrl").
117
118 -type muc_room() :: #muc_room{
119 name_host :: room_host(),
120 opts :: list()
121 }.
122
123 -type muc_online_room() :: #muc_online_room{
124 name_host :: room_host(),
125 host_type :: host_type(),
126 pid :: pid()
127 }.
128 -export_type([muc_online_room/0]).
129
130 -type room_event_data() :: #{
131 from_nick := nick(),
132 from_jid := jid:jid(),
133 room_jid := jid:jid(),
134 affiliation := affiliation(),
135 role := role(),
136 timestamp := integer()
137 }.
138 -export_type([room_event_data/0]).
139
140 -record(muc_state, {host_type :: host_type(),
141 subdomain_pattern :: mongoose_subdomain_utils:subdomain_pattern(),
142 access,
143 history_size :: integer(),
144 default_room_opts :: list(),
145 room_shaper :: mongoose_shaper:shaper(),
146 http_auth_pool :: mongoose_http_client:pool(),
147 hibernated_room_check_interval :: timeout(),
148 hibernated_room_timeout :: timeout() }).
149
150 -type state() :: #muc_state{}.
151
152 -export_type([muc_room/0]).
153
154 -define(PROCNAME, ejabberd_mod_muc).
155
156
157 %%====================================================================
158 %% API
159 %%====================================================================
160 %%--------------------------------------------------------------------
161 %% Function: start_link() -> {ok, Pid} | ignore | {error, Error}
162 %% Description: Starts the server
163 %%--------------------------------------------------------------------
164 -spec start_link(host_type(), map())
165 -> ignore | {error, _} | {ok, pid()}.
166 start_link(HostType, Opts) ->
167 38 Proc = gen_mod:get_module_proc(HostType, ?PROCNAME),
168 38 gen_server:start_link({local, Proc}, ?MODULE, {HostType, Opts}, []).
169
170 -spec start(host_type(), _) -> ok.
171 start(HostType, Opts) when is_map(Opts) ->
172 38 mod_muc_online_backend:start(HostType, Opts),
173 38 start_supervisor(HostType),
174 38 start_server(HostType, Opts),
175 38 assert_server_running(HostType),
176 38 ok.
177
178 -spec stop(host_type()) -> ok.
179 stop(HostType) ->
180 38 stop_supervisor(HostType),
181 38 stop_gen_server(HostType),
182 38 mod_muc_online_backend:stop(HostType),
183 38 ok.
184
185 -spec supported_features() -> [atom()].
186 supported_features() ->
187
:-(
[dynamic_domains].
188
189 start_server(HostType, Opts) ->
190 38 Proc = gen_mod:get_module_proc(HostType, ?PROCNAME),
191 38 ChildSpec =
192 {Proc,
193 {?MODULE, start_link, [HostType, Opts]},
194 temporary,
195 1000,
196 worker,
197 [?MODULE]},
198 38 {ok, _} = ejabberd_sup:start_child(ChildSpec).
199
200 assert_server_running(HostType) ->
201 38 true = is_pid(whereis(gen_mod:get_module_proc(HostType, ?PROCNAME))).
202
203 -spec config_spec() -> mongoose_config_spec:config_section().
204 config_spec() ->
205 66 #section{
206 items = #{<<"backend">> => #option{type = atom,
207 validate = {module, mod_muc}},
208 <<"online_backend">> => #option{type = atom,
209 validate = {module, mod_muc_online}},
210 <<"host">> => #option{type = string,
211 validate = subdomain_template,
212 process = fun mongoose_subdomain_utils:make_subdomain_pattern/1},
213 <<"access">> => #option{type = atom,
214 validate = access_rule},
215 <<"access_create">> => #option{type = atom,
216 validate = access_rule},
217 <<"access_admin">> => #option{type = atom,
218 validate = access_rule},
219 <<"access_persistent">> => #option{type = atom,
220 validate = access_rule},
221 <<"history_size">> => #option{type = integer,
222 validate = non_negative},
223 <<"room_shaper">> => #option{type = atom,
224 validate = shaper},
225 <<"max_room_id">> => #option{type = int_or_infinity,
226 validate = non_negative},
227 <<"max_room_name">> => #option{type = int_or_infinity,
228 validate = non_negative},
229 <<"max_room_desc">> => #option{type = int_or_infinity,
230 validate = non_negative},
231 <<"min_message_interval">> => #option{type = integer,
232 validate = non_negative},
233 <<"min_presence_interval">> => #option{type = integer,
234 validate = non_negative},
235 <<"max_users">> => #option{type = integer,
236 validate = positive},
237 <<"max_users_admin_threshold">> => #option{type = integer,
238 validate = positive},
239 <<"user_message_shaper">> => #option{type = atom,
240 validate = shaper},
241 <<"user_presence_shaper">> => #option{type = atom,
242 validate = shaper},
243 <<"max_user_conferences">> => #option{type = integer,
244 validate = non_negative},
245 <<"http_auth_pool">> => #option{type = atom,
246 validate = pool_name},
247 <<"load_permanent_rooms_at_startup">> => #option{type = boolean},
248 <<"hibernate_timeout">> => #option{type = int_or_infinity,
249 validate = non_negative},
250 <<"hibernated_room_check_interval">> => #option{type = int_or_infinity,
251 validate = non_negative},
252 <<"hibernated_room_timeout">> => #option{type = int_or_infinity,
253 validate = non_negative},
254 <<"default_room">> => default_room_config_spec()
255 },
256 defaults = defaults()
257 }.
258
259 defaults() ->
260 66 #{<<"backend">> => mnesia,
261 <<"online_backend">> => mnesia,
262 <<"host">> => default_host(),
263 <<"access">> => all,
264 <<"access_create">> => all,
265 <<"access_admin">> => none,
266 <<"access_persistent">> => all,
267 <<"history_size">> => 20,
268 <<"room_shaper">> => none,
269 <<"max_room_id">> => infinity,
270 <<"max_room_name">> => infinity,
271 <<"max_room_desc">> => infinity,
272 <<"min_message_interval">> => 0,
273 <<"min_presence_interval">> => 0,
274 <<"max_users">> => ?MAX_USERS_DEFAULT,
275 <<"max_users_admin_threshold">> => 5,
276 <<"user_message_shaper">> => none,
277 <<"user_presence_shaper">> => none,
278 <<"max_user_conferences">> => 10,
279 <<"http_auth_pool">> => none,
280 <<"load_permanent_rooms_at_startup">> => false,
281 <<"hibernate_timeout">> => timer:seconds(90),
282 <<"hibernated_room_check_interval">> => infinity,
283 <<"hibernated_room_timeout">> => infinity,
284 <<"default_room">> => keys_as_atoms(default_room_opts())}.
285
286 keys_as_atoms(Map) ->
287 66 maps:from_list([{binary_to_atom(K), V} || {K, V} <- maps:to_list(Map)]).
288
289 default_room_config_spec() ->
290 66 #section{
291 items = #{<<"title">> => #option{type = binary},
292 <<"description">> => #option{type = binary},
293 <<"allow_change_subj">> => #option{type = boolean},
294 <<"allow_query_users">> => #option{type = boolean},
295 <<"allow_private_messages">> => #option{type = boolean},
296 <<"allow_visitor_status">> => #option{type = boolean},
297 <<"allow_visitor_nickchange">> => #option{type = boolean},
298 <<"public">> => #option{type = boolean},
299 <<"public_list">> => #option{type = boolean},
300 <<"persistent">> => #option{type = boolean},
301 <<"moderated">> => #option{type = boolean},
302 <<"members_by_default">> => #option{type = boolean},
303 <<"members_only">> => #option{type = boolean},
304 <<"allow_user_invites">> => #option{type = boolean},
305 <<"allow_multiple_sessions">> => #option{type = boolean},
306 <<"password_protected">> => #option{type = boolean},
307 <<"password">> => #option{type = binary},
308 <<"anonymous">> => #option{type = boolean},
309 <<"max_users">> => #option{type = integer,
310 validate = positive},
311 <<"logging">> => #option{type = boolean},
312 <<"maygetmemberlist">> => #list{items = #option{type = atom,
313 validate = non_empty}},
314 <<"affiliations">> => #list{items = default_room_affiliations_spec()},
315 <<"subject">> => #option{type = binary},
316 <<"subject_author">> => #option{type = binary}
317 },
318 defaults = default_room_opts()
319 }.
320
321 default_room_opts() ->
322 132 X = #config{},
323 132 #{<<"title">> => X#config.title,
324 <<"description">> => X#config.description,
325 <<"allow_change_subj">> => X#config.allow_change_subj,
326 <<"allow_query_users">> => X#config.allow_query_users,
327 <<"allow_private_messages">> => X#config.allow_private_messages,
328 <<"allow_visitor_status">> => X#config.allow_visitor_status,
329 <<"allow_visitor_nickchange">> => X#config.allow_visitor_nickchange,
330 <<"public">> => X#config.public,
331 <<"public_list">> => X#config.public_list,
332 <<"persistent">> => X#config.persistent,
333 <<"moderated">> => X#config.moderated,
334 <<"members_by_default">> => X#config.members_by_default,
335 <<"members_only">> => X#config.members_only,
336 <<"allow_user_invites">> => X#config.allow_user_invites,
337 <<"allow_multiple_sessions">> => X#config.allow_multiple_sessions,
338 <<"password_protected">> => X#config.password_protected,
339 <<"password">> => X#config.password,
340 <<"anonymous">> => X#config.anonymous,
341 <<"max_users">> => X#config.max_users,
342 <<"logging">> => X#config.logging,
343 <<"maygetmemberlist">> => X#config.maygetmemberlist,
344 <<"affiliations">> => [],
345 <<"subject">> => <<>>,
346 <<"subject_author">> => <<>>}.
347
348 default_room_affiliations_spec() ->
349 66 #section{
350 items = #{<<"user">> => #option{type = binary,
351 validate = non_empty},
352 <<"server">> => #option{type = binary,
353 validate = domain},
354 <<"resource">> => #option{type = binary},
355 <<"affiliation">> => #option{type = atom,
356 validate = non_empty}},
357 required = all,
358 process = fun ?MODULE:process_room_affiliation/1
359 }.
360
361 process_room_affiliation(#{user := User, server := Server, resource := Res, affiliation := Aff}) ->
362
:-(
{{User, Server, Res}, Aff}.
363
364 stop_gen_server(HostType) ->
365 38 Proc = gen_mod:get_module_proc(HostType, ?PROCNAME),
366 38 gen_server:call(Proc, stop),
367 %% Proc can still be alive because of a race condition
368 38 ejabberd_sup:stop_child(Proc).
369
370 %% @doc This function is called by a room in three situations:
371 %% A) The owner of the room destroyed it
372 %% B) The only participant of a temporary room leaves it
373 %% C) mod_muc:stop was called, and each room is being terminated
374 %% In this case, the mod_muc process died before the room processes
375 %% So the message sending must be catched
376 -spec room_destroyed(host_type(), jid:server(), room(), pid()) -> 'ok'.
377 room_destroyed(HostType, MucHost, Room, Pid) ->
378 529 mod_muc_online_backend:room_destroyed(HostType, MucHost, Room, Pid).
379
380 %% @doc Create a room.
381 %% If Opts = default, the default room options are used.
382 %% Else use the passed options as defined in mod_muc_room.
383 %% XXX Only used from tests.
384 -spec create_instant_room(jid:lserver(), MucHost :: jid:lserver(), Name :: room(),
385 From :: jid:jid(), Nick :: nick(), Opts :: list()) -> any().
386 create_instant_room(ServerHost, MucHost, Name, From, Nick, Opts) ->
387 486 {ok, HostType} = mongoose_domain_api:get_domain_host_type(ServerHost),
388 486 Proc = gen_mod:get_module_proc(HostType, ?PROCNAME),
389 486 gen_server:call(Proc, {create_instant, ServerHost, MucHost, Name, From, Nick, Opts}).
390
391 -spec store_room(host_type(), jid:server(), room(), list()) ->
392 {error, _} | ok.
393 store_room(HostType, MucHost, Name, Opts) ->
394 342 mod_muc_backend:store_room(HostType, MucHost, Name, Opts).
395
396 -spec restore_room(host_type(), muc_host(), room()) ->
397 {error, _} | {ok, _}.
398 restore_room(HostType, MucHost, Name) ->
399 5 mod_muc_backend:restore_room(HostType, MucHost, Name).
400
401 -spec forget_room(host_type(), jid:server(), room()) -> ok | {error, term()}.
402 forget_room(HostType, MucHost, Name) ->
403 %% Removes room from DB, even if it's already removed.
404 245 Result = mod_muc_backend:forget_room(HostType, MucHost, Name),
405 245 case Result of
406 ok ->
407 %% TODO This hook should be renamed to forget_room_hook.
408 %% We also need to think how to remove stopped rooms
409 %% (i.e. in case we want to expose room removal over REST or SQS).
410 %%
411 %% In some _rare_ cases this hook can be called more than once for the same room.
412 245 mongoose_hooks:forget_room(HostType, MucHost, Name);
413 _ ->
414 %% Room is not removed or we don't know.
415 %% XXX Handle this case better.
416
:-(
ok
417 end,
418 245 Result.
419
420 %% For rooms
421 -spec process_iq_disco_items(MucHost :: jid:server(), From :: jid:jid(),
422 To :: jid:jid(), jlib:iq()) -> mongoose_acc:t().
423 process_iq_disco_items(MucHost, From, To, #iq{lang = Lang} = IQ) ->
424 20 Rsm = jlib:rsm_decode(IQ),
425 20 Res = IQ#iq{type = result,
426 sub_el = [#xmlel{name = <<"query">>,
427 attrs = [{<<"xmlns">>, ?NS_DISCO_ITEMS}],
428 children = iq_disco_items(MucHost, From, Lang, Rsm)}]},
429 20 ejabberd_router:route(To, From, jlib:iq_to_xml(Res)).
430
431 -spec can_use_nick(host_type(), jid:server(), jid:jid(), nick()) -> boolean().
432 can_use_nick(_HostType, _Host, _JID, <<>>) ->
433
:-(
false;
434 can_use_nick(HostType, MucHost, JID, Nick) ->
435 676 mod_muc_backend:can_use_nick(HostType, MucHost, JID, Nick).
436
437 set_nick(_HostType, _MucHost, _From, <<>>) ->
438
:-(
{error, should_not_be_empty};
439 set_nick(HostType, MucHost, From, Nick) ->
440 15 mod_muc_backend:set_nick(HostType, MucHost, From, Nick).
441
442 unset_nick(HostType, MucHost, From) ->
443 6 mod_muc_backend:unset_nick(HostType, MucHost, From).
444
445 get_nick(HostType, MucHost, From) ->
446 22 mod_muc_backend:get_nick(HostType, MucHost, From).
447
448 %%====================================================================
449 %% gen_server callbacks
450 %%====================================================================
451
452 -spec init({host_type(), map()}) -> {ok, state()}.
453 init({HostType, Opts}) ->
454 38 mod_muc_backend:init(HostType, Opts),
455 38 catch ets:new(muc_online_users, [bag, named_table, public, {keypos, 2}]),
456 38 #{access := Access,
457 access_create := AccessCreate,
458 access_admin := AccessAdmin,
459 access_persistent := AccessPersistent,
460 http_auth_pool := HttpAuthPool,
461 history_size := HistorySize,
462 default_room := DefRoomOpts,
463 room_shaper := RoomShaper,
464 hibernated_room_check_interval := CheckInterval,
465 hibernated_room_timeout := HibernatedTimeout,
466 host := SubdomainPattern,
467 load_permanent_rooms_at_startup := LoadPermRoomsAtStartup} = Opts,
468 38 State = #muc_state{host_type = HostType,
469 subdomain_pattern = SubdomainPattern,
470 access = {Access, AccessCreate, AccessAdmin, AccessPersistent},
471 default_room_opts = maps:to_list(DefRoomOpts),
472 history_size = HistorySize,
473 room_shaper = RoomShaper,
474 http_auth_pool = HttpAuthPool,
475 hibernated_room_check_interval = CheckInterval,
476 hibernated_room_timeout = HibernatedTimeout},
477 %% Hooks
478 38 gen_hook:add_handlers(hooks(HostType)),
479 %% Handler
480 38 PacketHandler = mongoose_packet_handler:new(?MODULE, #{state => State}),
481 38 case SubdomainPattern of
482 38 {prefix, _} -> ok;
483
:-(
_ -> ?LOG_WARNING(#{what => muc_host_pattern_missing,
484 host_type => HostType,
485 subdomain_pattern => SubdomainPattern,
486
:-(
text => <<"Only one MUC domain would work with this host type">>})
487 end,
488 38 mongoose_domain_api:register_subdomain(HostType, SubdomainPattern, PacketHandler),
489 %% Loading
490 38 case LoadPermRoomsAtStartup of
491 false ->
492 38 ?LOG_INFO(#{what => load_permanent_rooms_at_startup, skip => true,
493 text => <<"Skip loading permanent rooms at startup. "
494 38 "Each room is loaded when someone access the room">>});
495 true ->
496
:-(
?LOG_WARNING(#{what => load_permanent_rooms_at_startup_is_deprecated, skip => false,
497 text => <<"Loading permanent rooms at startup is deprecated. "
498
:-(
"The option is ignored.">>})
499 end,
500 38 set_persistent_rooms_timer(State),
501 38 {ok, State}.
502
503 set_persistent_rooms_timer(#muc_state{hibernated_room_check_interval = infinity}) ->
504 6 ok;
505 set_persistent_rooms_timer(#muc_state{hibernated_room_check_interval = Timeout}) ->
506 216 timer:send_after(Timeout, stop_hibernated_persistent_rooms).
507
508 handle_call(stop, _From, State) ->
509 38 gen_hook:delete_handlers(hooks(State#muc_state.host_type)),
510 38 {stop, normal, ok, State};
511 handle_call({create_instant, ServerHost, MucHost, Room, From, Nick, Opts},
512 _From,
513 #muc_state{host_type = HostType,
514 access = Access,
515 default_room_opts = DefOpts,
516 history_size = HistorySize,
517 room_shaper = RoomShaper,
518 http_auth_pool = HttpAuthPool} = State) ->
519 486 ?LOG_DEBUG(#{what => muc_create_instant, room => Room, sub_host => MucHost}),
520 486 NewOpts = case Opts of
521 18 default -> DefOpts;
522 468 _ -> Opts
523 end,
524 486 try
525 486 {ok, Pid} = mod_muc_room:start_new(HostType,
526 MucHost, ServerHost, Access,
527 Room, HistorySize,
528 RoomShaper, HttpAuthPool, From,
529 Nick, [{instant, true}|NewOpts]),
530 486 register_room_or_stop_if_duplicate(HostType, MucHost, Room, Pid),
531 486 {reply, ok, State}
532 catch Class:Reason:Stacktrace ->
533
:-(
Err = #{what => muc_create_instant_failed,
534 server => ServerHost, host_type => HostType,
535 room => Room, from_jid => From,
536 class => Class, reason => Reason,
537 stacktrace => Stacktrace},
538
:-(
?LOG_ERROR(Err),
539
:-(
{reply, {error, Err}, State}
540 end.
541
542 handle_cast(_Msg, State) ->
543
:-(
{noreply, State}.
544
545 handle_info(stop_hibernated_persistent_rooms,
546 #muc_state{host_type = HostType,
547 hibernated_room_timeout = Timeout} = State)
548 when is_integer(Timeout) ->
549 184 handle_stop_hibernated_persistent_rooms(HostType, Timeout),
550 184 set_persistent_rooms_timer(State),
551 184 {noreply, State};
552 handle_info(_Info, State) ->
553
:-(
{noreply, State}.
554
555 handle_stop_hibernated_persistent_rooms(HostType, Timeout) ->
556 184 ?LOG_INFO(#{what => muc_stop_hibernated_persistent_rooms, host_type => HostType,
557 184 text => <<"Closing hibernated persistent rooms">>}),
558 184 try
559 184 Supervisor = gen_mod:get_module_proc(HostType, ejabberd_mod_muc_sup),
560 184 Now = os:timestamp(),
561 184 [stop_if_hibernated(Pid, Now, Timeout * 1000) ||
562 184 {undefined, Pid, worker, _} <- supervisor:which_children(Supervisor)]
563 catch Error:Reason:Stacktrace ->
564
:-(
?LOG_ERROR(#{what => stop_hibernated_persistent_rooms_failed,
565 error => Error, reason => Reason,
566 stacktrace => Stacktrace,
567
:-(
host_type => HostType})
568 end.
569
570 stop_if_hibernated(Pid, Now, Timeout) ->
571 610 stop_if_hibernated(Pid, Now, Timeout, erlang:process_info(Pid, current_function)).
572
573 stop_if_hibernated(Pid, Now, Timeout, {current_function, {erlang, hibernate, 3}}) ->
574 271 {dictionary, Dictionary} = erlang:process_info(Pid, dictionary),
575 271 LastHibernated = lists:keyfind(hibernated, 1, Dictionary),
576 271 stop_if_hibernated_for_specified_time(Pid, Now, Timeout, LastHibernated),
577 271 ok;
578 stop_if_hibernated(_, _, _, _) ->
579 339 ok.
580
581 stop_if_hibernated_for_specified_time(_Pid, _, _, false) ->
582
:-(
ok;
583 stop_if_hibernated_for_specified_time(Pid, Now, Timeout, {hibernated, LastHibernated}) ->
584 271 TimeDiff = timer:now_diff(Now, LastHibernated),
585 271 case TimeDiff >= Timeout of
586 true ->
587 87 Pid ! stop_persistent_room_process;
588 _ ->
589 184 ok
590 end.
591
592 terminate(_Reason, #muc_state{host_type = HostType,
593 subdomain_pattern = SubdomainPattern}) ->
594 38 mongoose_domain_api:unregister_subdomain(HostType, SubdomainPattern).
595
596 code_change(_OldVsn, State, _Extra) ->
597
:-(
{ok, State}.
598
599 %%--------------------------------------------------------------------
600 %%% Internal functions
601 %%--------------------------------------------------------------------
602 -spec start_supervisor(host_type()) -> {error, _}
603 | {ok, undefined | pid()}
604 | {ok, undefined | pid(), _}.
605 start_supervisor(HostType) ->
606 38 ChildSpec = sup_spec(HostType),
607 38 ejabberd_sup:start_child(ChildSpec).
608
609 sup_spec(HostType) ->
610 38 Proc = gen_mod:get_module_proc(HostType, ejabberd_mod_muc_sup),
611 38 {Proc,
612 {ejabberd_tmp_sup, start_link, [Proc, mod_muc_room]},
613 permanent,
614 infinity,
615 supervisor,
616 [ejabberd_tmp_sup]}.
617
618 -spec stop_supervisor(jid:server()) -> ok | {error, Reason}
619 when Reason :: not_found | restarting | running | simple_one_for_one.
620 stop_supervisor(HostType) ->
621 38 Proc = gen_mod:get_module_proc(HostType, ejabberd_mod_muc_sup),
622 38 ejabberd_sup:stop_child(Proc).
623
624 -spec process_packet(Acc :: mongoose_acc:t(),
625 From :: jid:jid(),
626 To :: jid:simple_jid() | jid:jid(),
627 El :: exml:element(),
628 #{state := state()}) -> mongoose_acc:t().
629 process_packet(Acc, From, To, El, #{state := State}) ->
630 2213 {AccessRoute, _, _, _} = State#muc_state.access,
631 2213 ServerHost = make_server_host(To, State),
632 2213 HostType = State#muc_state.host_type,
633 2213 case acl:match_rule(HostType, ServerHost, AccessRoute, From) of
634 allow ->
635 2213 {Room, MucHost, _} = jid:to_lower(To),
636 2213 route_to_room(MucHost, Room, {From, To, Acc, El}, State),
637 2210 Acc;
638 _ ->
639
:-(
#xmlel{attrs = Attrs} = El,
640
:-(
Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
641
:-(
ErrText = <<"Access denied by service policy">>,
642
:-(
ejabberd_router:route_error_reply(To, From, Acc,
643 mongoose_xmpp_errors:forbidden(Lang, ErrText))
644 end.
645
646
647 -spec route_to_room(jid:lserver(), room(), from_to_packet(), state()) -> ok | pid().
648 route_to_room(_MucHost, <<>>, {_, To, _Acc, _} = Routed, State) ->
649 85 {_, _, Nick} = jid:to_lower(To),
650 85 route_by_nick(Nick, Routed, State);
651 route_to_room(MucHost, Room, Routed, #muc_state{} = State) ->
652 2128 HostType = State#muc_state.host_type,
653 2128 case find_room_pid(HostType, MucHost, Room) of
654 {error, not_found} ->
655 55 case get_registered_room_or_route_error(MucHost, Room, Routed, State) of
656 {ok, Pid} ->
657 53 route_to_online_room(Pid, Routed);
658 {route_error, _ErrText} ->
659 2 ok
660 end;
661 {ok, Pid} ->
662 2073 route_to_online_room(Pid, Routed)
663 end.
664
665 route_to_online_room(Pid, {From, To, Acc, Packet}) ->
666 2126 ?LOG_DEBUG(#{what => muc_route_to_online_room, room_pid => Pid, acc => Acc}),
667 2126 {_, _, Nick} = jid:to_lower(To),
668 2126 ok = mod_muc_room:route(Pid, From, Nick, Acc, Packet).
669
670 -spec get_registered_room_or_route_error(muc_host(), room(), from_to_packet(), state()) -> {ok, pid()} | {route_error, binary()}.
671 get_registered_room_or_route_error(MucHost, Room, {From, To, Acc, Packet}, State) ->
672 55 #xmlel{name = Name, attrs = Attrs} = Packet,
673 55 Type = xml:get_attr_s(<<"type">>, Attrs),
674 55 case {Name, Type} of
675 {<<"presence">>, <<>>} ->
676 51 get_registered_room_or_route_error_from_presence(MucHost, Room, From, To, Acc, Packet, State);
677 _ ->
678 4 get_registered_room_or_route_error_from_packet(MucHost, Room, From, To, Acc, Packet, State)
679 end.
680
681 get_registered_room_or_route_error_from_presence(MucHost, Room, From, To, Acc,
682 Packet, #muc_state{host_type = HostType, access = Access} = State) ->
683 51 {_, AccessCreate, _, _} = Access,
684 51 ServerHost = make_server_host(To, State),
685 51 case check_user_can_create_room(HostType, ServerHost, AccessCreate, From, Room) of
686 ok ->
687 50 #muc_state{history_size = HistorySize,
688 room_shaper = RoomShaper,
689 http_auth_pool = HttpAuthPool,
690 default_room_opts = DefRoomOpts} = State,
691 50 {_, _, Nick} = jid:to_lower(To),
692 50 ServerHost = make_server_host(To, State),
693 50 Result = start_room(HostType, ServerHost, MucHost, Access, Room,
694 HistorySize, RoomShaper, HttpAuthPool,
695 From, Nick, DefRoomOpts, Acc),
696 50 case Result of
697 {ok, Pid} ->
698 50 register_room_or_stop_if_duplicate(HostType, MucHost, Room, Pid);
699 {error, {failed_to_restore, Reason}} ->
700 %% Notify user about our backend module error
701
:-(
?LOG_WARNING(#{what => muc_send_service_unavailable,
702 text => <<"Failed to restore room">>,
703 host_type => HostType,
704 room => Room, sub_host => MucHost,
705
:-(
reason => Reason, acc => Acc}),
706
:-(
Lang = exml_query:attr(Packet, <<"xml:lang">>, <<>>),
707
:-(
ErrText = <<"Service is temporary unavailable">>,
708
:-(
{Acc1, Err} = jlib:make_error_reply(
709 Acc, Packet, mongoose_xmpp_errors:service_unavailable(Lang, ErrText)),
710
:-(
ejabberd_router:route(To, From, Acc1, Err),
711
:-(
{route_error, ErrText}
712 end;
713 {error, Reason} ->
714 1 Lang = exml_query:attr(Packet, <<"xml:lang">>, <<>>),
715 1 Policy = iolist_to_binary(io_lib:format("~p", [Reason])),
716 1 ErrText = <<"Room creation is denied by service policy: ", Policy/binary>>,
717 1 {Acc1, Err} = jlib:make_error_reply(
718 Acc, Packet, mongoose_xmpp_errors:not_allowed(Lang, ErrText)),
719 1 ejabberd_router:route(To, From, Acc1, Err),
720 1 {route_error, ErrText}
721 end.
722
723 get_registered_room_or_route_error_from_packet(MucHost, Room, From, To, Acc, Packet,
724 #muc_state{host_type = HostType,
725 access = Access} = State) ->
726 4 ServerHost = make_server_host(To, State),
727 4 case restore_room(HostType, MucHost, Room) of
728 {error, room_not_found} ->
729 1 Lang = exml_query:attr(Packet, <<"xml:lang">>, <<>>),
730 1 ErrText = <<"Conference room does not exist">>,
731 1 {Acc1, Err} = jlib:make_error_reply(
732 Acc, Packet, mongoose_xmpp_errors:item_not_found(Lang, ErrText)),
733 1 ejabberd_router:route(To, From, Acc1, Err),
734 1 {route_error, ErrText};
735 {error, Reason} ->
736
:-(
?LOG_WARNING(#{what => muc_send_service_unavailable,
737 room => Room, host_type => HostType, sub_host => MucHost,
738
:-(
reason => Reason, acc => Acc}),
739
:-(
Lang = exml_query:attr(Packet, <<"xml:lang">>, <<>>),
740
:-(
ErrText = <<"Service is temporary unavailable">>,
741
:-(
{Acc1, Err} = jlib:make_error_reply(
742 Acc, Packet, mongoose_xmpp_errors:service_unavailable(Lang, ErrText)),
743
:-(
ejabberd_router:route(To, From, Acc1, Err),
744
:-(
{route_error, ErrText};
745 {ok, Opts} ->
746 3 ?LOG_DEBUG(#{what => muc_restore_room, room => Room, room_opts => Opts}),
747 3 #muc_state{history_size = HistorySize,
748 room_shaper = RoomShaper,
749 http_auth_pool = HttpAuthPool} = State,
750 3 {ok, Pid} = mod_muc_room:start_restored(HostType,
751 MucHost, ServerHost, Access,
752 Room, HistorySize,
753 RoomShaper, HttpAuthPool, Opts),
754 3 register_room_or_stop_if_duplicate(HostType, MucHost, Room, Pid)
755 end.
756
757 -spec route_by_nick(room(), from_to_packet(), state()) -> 'ok' | pid().
758 route_by_nick(<<>>, {_, _, _, Packet} = Routed, State) ->
759 85 #xmlel{name = Name} = Packet,
760 85 route_by_type(Name, Routed, State);
761 route_by_nick(_Nick, {From, To, Acc, Packet}, _State) ->
762
:-(
#xmlel{attrs = Attrs} = Packet,
763
:-(
case xml:get_attr_s(<<"type">>, Attrs) of
764 <<"error">> ->
765
:-(
Acc;
766 <<"result">> ->
767
:-(
Acc;
768 _ ->
769
:-(
{Acc1, Err} = jlib:make_error_reply(Acc, Packet, mongoose_xmpp_errors:item_not_found()),
770
:-(
ejabberd_router:route(To, From, Acc1, Err)
771 end.
772
773 -spec route_by_type(binary(), from_to_packet(), state()) -> ok | pid().
774 route_by_type(<<"iq">>, {From, To, Acc, Packet}, #muc_state{} = State) ->
775 85 HostType = State#muc_state.host_type,
776 85 MucHost = To#jid.lserver,
777 85 case jlib:iq_query_info(Packet) of
778 #iq{type = get, xmlns = ?NS_DISCO_INFO = XMLNS, lang = Lang} = IQ ->
779 12 IdentityXML = mongoose_disco:identities_to_xml([identity(Lang)]),
780 12 FeatureXML = mongoose_disco:get_muc_features(HostType, From, To, <<>>, Lang,
781 features()),
782 12 InfoXML = mongoose_disco:get_info(HostType, ?MODULE, <<>>, Lang),
783 12 Res = IQ#iq{type = result,
784 sub_el = [#xmlel{name = <<"query">>,
785 attrs = [{<<"xmlns">>, XMLNS}],
786 children = IdentityXML ++ FeatureXML ++ InfoXML}]},
787 12 ejabberd_router:route(To, From, jlib:iq_to_xml(Res));
788 #iq{type = get, xmlns = ?NS_DISCO_ITEMS} = IQ ->
789 20 proc_lib:spawn(fun() -> process_iq_disco_items(MucHost, From, To, IQ) end);
790 #iq{type = get, xmlns = ?NS_REGISTER = XMLNS, lang = Lang} = IQ ->
791 22 Result = iq_get_register_info(HostType, MucHost, From, Lang),
792 22 Res = IQ#iq{type = result,
793 sub_el = [#xmlel{name = <<"query">>,
794 attrs = [{<<"xmlns">>, XMLNS}],
795 children = Result}]},
796 22 ejabberd_router:route(To, From, jlib:iq_to_xml(Res));
797 #iq{type = set,
798 xmlns = ?NS_REGISTER = XMLNS,
799 lang = Lang,
800 sub_el = SubEl} = IQ ->
801 31 case process_iq_register_set(HostType, MucHost, From, SubEl, Lang) of
802 {result, IQRes} ->
803 23 Res = IQ#iq{type = result,
804 sub_el = [#xmlel{name = <<"query">>,
805 attrs = [{<<"xmlns">>, XMLNS}],
806 children = IQRes}]},
807 23 ejabberd_router:route(To, From, jlib:iq_to_xml(Res));
808 {error, Error} ->
809 8 {Acc1, Err} = jlib:make_error_reply(Acc, Packet, Error),
810 8 ejabberd_router:route(To, From, Acc1, Err)
811 end;
812 #iq{type = get, xmlns = ?NS_VCARD = XMLNS, lang = Lang} = IQ ->
813
:-(
Res = IQ#iq{type = result,
814 sub_el = [#xmlel{name = <<"vCard">>,
815 attrs = [{<<"xmlns">>, XMLNS}],
816 children = iq_get_vcard(Lang)}]},
817
:-(
ejabberd_router:route(To, From, jlib:iq_to_xml(Res));
818 #iq{type = get, xmlns = ?NS_MUC_UNIQUE} = IQ ->
819
:-(
Res = IQ#iq{type = result,
820 sub_el = [#xmlel{name = <<"unique">>,
821 attrs = [{<<"xmlns">>, ?NS_MUC_UNIQUE}],
822 children = [iq_get_unique(From)]}]},
823
:-(
ejabberd_router:route(To, From, jlib:iq_to_xml(Res));
824 #iq{} ->
825
:-(
?LOG_INFO(#{what => muc_ignore_unknown_iq, acc => Acc}),
826
:-(
{Acc1, Err} = jlib:make_error_reply(Acc, Packet,
827 mongoose_xmpp_errors:feature_not_implemented(<<"en">>, <<"From mod_muc">>)),
828
:-(
ejabberd_router:route(To, From, Acc1, Err);
829 Other ->
830
:-(
?LOG_INFO(#{what => muc_failed_to_parse_iq, acc => Acc, reason => Other}),
831
:-(
ok
832 end;
833 route_by_type(<<"message">>, {From, To, Acc, Packet},
834 #muc_state{host_type = HostType,
835 access = {_, _, AccessAdmin, _}} = State) ->
836
:-(
MucHost = To#jid.lserver,
837
:-(
ServerHost = make_server_host(To, State),
838
:-(
#xmlel{attrs = Attrs} = Packet,
839
:-(
case xml:get_attr_s(<<"type">>, Attrs) of
840 <<"error">> ->
841
:-(
ok;
842 _ ->
843
:-(
case acl:match_rule(HostType, ServerHost, AccessAdmin, From) of
844 allow ->
845
:-(
Msg = xml:get_path_s(Packet, [{elem, <<"body">>}, cdata]),
846
:-(
broadcast_service_message(MucHost, Msg);
847 _ ->
848
:-(
Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
849
:-(
ErrTxt = <<"Only service administrators are allowed to send service messages">>,
850
:-(
Err = mongoose_xmpp_errors:forbidden(Lang, ErrTxt),
851
:-(
{Acc1, ErrorReply} = jlib:make_error_reply(Acc, Packet, Err),
852
:-(
ejabberd_router:route(To, From, Acc1, ErrorReply)
853 end
854 end;
855 route_by_type(<<"presence">>, _Routed, _State) ->
856
:-(
ok.
857
858 -spec check_user_can_create_room(host_type(), jid:lserver(),
859 allow | atom(), jid:jid(), room()) -> ok | {error, term()}.
860 check_user_can_create_room(HostType, ServerHost, AccessCreate, From, RoomID) ->
861 51 case acl:match_rule(HostType, ServerHost, AccessCreate, From) of
862 allow ->
863 50 MaxLen = gen_mod:get_module_opt(HostType, mod_muc, max_room_id),
864 50 case (size(RoomID) =< MaxLen) of
865 50 true -> ok;
866
:-(
false -> {error, room_id_too_long}
867 end;
868 _ ->
869 1 ?LOG_WARNING(#{what => check_user_can_create_room_failed,
870 host_type => HostType,
871 server => ServerHost,
872 access_create => AccessCreate,
873 from_jid => From,
874
:-(
room_id => RoomID}),
875 1 {error, no_matching_acl_rule}
876 end.
877
878 -spec start_room(HostType :: host_type(), ServerHost :: jid:lserver(),
879 MucHost :: muc_host(), Access :: access(), room(),
880 HistorySize :: undefined | integer(), RoomShaper :: mongoose_shaper:shaper(),
881 HttpAuthPool :: none | mongoose_http_client:pool(), From :: jid:jid(), nick(),
882 DefRoomOpts :: undefined | [any()], Acc :: mongoose_acc:t())
883 -> {error, {failed_to_restore, Reason :: term()}} | {ok, pid()}.
884 start_room(HostType, ServerHost, MucHost, Access, Room,
885 HistorySize, RoomShaper, HttpAuthPool, From,
886 Nick, DefRoomOpts, Acc) ->
887 50 case mod_muc_backend:restore_room(HostType, MucHost, Room) of
888 {error, room_not_found} ->
889 48 ?LOG_DEBUG(#{what => muc_start_new_room, acc => Acc,
890 48 room => Room, host_type => HostType, sub_host => MucHost}),
891 48 mod_muc_room:start_new(HostType,
892 MucHost, ServerHost, Access,
893 Room, HistorySize,
894 RoomShaper, HttpAuthPool, From,
895 Nick, DefRoomOpts);
896 {error, Reason} ->
897
:-(
{error, {failed_to_restore, Reason}};
898 {ok, Opts} ->
899 2 ?LOG_DEBUG(#{what => muc_restore_room, acc => Acc, room => Room,
900 2 host_type => HostType, sub_host => MucHost, room_opts => Opts}),
901 2 mod_muc_room:start_restored(HostType,
902 MucHost, ServerHost, Access,
903 Room, HistorySize,
904 RoomShaper, HttpAuthPool, Opts)
905 end.
906
907 register_room_or_stop_if_duplicate(HostType, MucHost, Room, Pid) ->
908 539 case register_room(HostType, MucHost, Room, Pid) of
909 ok ->
910 496 {ok, Pid};
911 {exists, OldPid} ->
912 43 mod_muc_room:stop(Pid),
913 43 {ok, OldPid};
914 {error, Reason} ->
915
:-(
error({failed_to_register, MucHost, Room, Pid, Reason})
916 end.
917
918 -spec register_room(HostType :: host_type(), jid:server(), room(),
919 pid()) -> ok | {exists, pid()} | {error, term()}.
920 register_room(HostType, MucHost, Room, Pid) ->
921 542 mod_muc_online_backend:register_room(HostType, MucHost, Room, Pid).
922
923 -spec room_jid_to_pid(RoomJID :: jid:jid()) -> {ok, pid()} | {error, not_found}.
924 room_jid_to_pid(#jid{luser = Room, lserver = MucHost}) ->
925 984 case mongoose_domain_api:get_subdomain_host_type(MucHost) of
926 {ok, HostType} ->
927 954 find_room_pid(HostType, MucHost, Room);
928 _ ->
929 30 {error, not_found}
930 end.
931
932 find_room_pid(HostType, MucHost, Room) ->
933 3082 mod_muc_online_backend:find_room_pid(HostType, MucHost, Room).
934
935 -spec default_host() -> mongoose_subdomain_utils:subdomain_pattern().
936 default_host() ->
937 132 mongoose_subdomain_utils:make_subdomain_pattern(<<"conference.@HOST@">>).
938
939 identity(Lang) ->
940 12 #{category => <<"conference">>,
941 type => <<"text">>,
942 name => translate:translate(Lang, <<"Chatrooms">>)}.
943
944 features() ->
945 12 [?NS_DISCO_INFO, ?NS_DISCO_ITEMS, ?NS_MUC, ?NS_MUC_UNIQUE, ?NS_REGISTER, ?NS_RSM, ?NS_VCARD, ?NS_CONFERENCE].
946
947 %% Disco for rooms
948 -spec iq_disco_items(muc_host(), jid:jid(), ejabberd:lang(),
949 Rsm :: none | jlib:rsm_in()) -> any().
950 iq_disco_items(MucHost, From, Lang, none) ->
951 6 AllRooms = get_vh_rooms(MucHost) ++ get_persistent_vh_rooms(MucHost),
952 6 Rooms = lists:ukeysort(1, lists:map(fun record_to_simple/1, AllRooms)),
953 6 BareRooms = lists:filtermap(fun(Room) -> room_to_item(Room, MucHost, From, Lang) end, Rooms),
954 6 lists:ukeysort(3, BareRooms);
955 iq_disco_items(MucHost, From, Lang, Rsm) ->
956 14 {Rooms, RsmO} = get_vh_rooms(MucHost, Rsm),
957 14 RsmOut = jlib:rsm_encode(RsmO),
958 14 lists:filtermap(fun(Room) -> room_to_item(Room, MucHost, From, Lang) end, Rooms) ++ RsmOut.
959
960 room_to_item({{Name, _}, Pid}, MucHost, From, Lang) when is_pid(Pid) ->
961 119 case catch gen_fsm_compat:sync_send_all_state_event(
962 Pid, {get_disco_item, From, Lang}, 100) of
963 {item, Desc} ->
964 117 {true,
965 #xmlel{name = <<"item">>,
966 attrs = [{<<"jid">>, jid:to_binary({Name, MucHost, <<>>})},
967 {<<"name">>, Desc}]}};
968 _ ->
969 2 false
970 end;
971 room_to_item({{Name, _}, _}, MucHost, _, _) ->
972 58 {true,
973 #xmlel{name = <<"item">>,
974 attrs = [{<<"jid">>, jid:to_binary({Name, MucHost, <<>>})},
975 {<<"name">>, Name}]}
976 }.
977 record_to_simple(#muc_online_room{name_host = Room, pid = Pid}) ->
978 335 {Room, Pid};
979 record_to_simple(#muc_room{name_host = Room, opts = Opts}) ->
980 97 {Room, Opts}.
981
982 -spec get_vh_rooms(muc_host(), jlib:rsm_in()) -> {list(), jlib:rsm_out()}.
983 get_vh_rooms(MucHost, #rsm_in{max=Max, direction=Direction, id=I, index=Index}) ->
984 38 NonUndefMax = case Max of
985 25 undefined -> 134217728;
986 13 _ -> Max
987 end,
988 38 Rooms = get_vh_rooms(MucHost) ++ get_persistent_vh_rooms(MucHost),
989 38 BareSortedRooms = lists:ukeysort(1, lists:map(fun record_to_simple/1, Rooms)),
990 38 Count = erlang:length(BareSortedRooms),
991 38 L2 = case {Index, Direction} of
992 {undefined, undefined} ->
993 23 lists:sublist(BareSortedRooms, 1, NonUndefMax);
994 {undefined, aft} ->
995 2 lists:sublist(
996 lists:dropwhile(
997 23 fun({{Id, _}, _}) -> Id =< I end,
998 BareSortedRooms),
999 1,
1000 NonUndefMax);
1001 {undefined,before} when I == <<>> ->
1002 1 lists:reverse(
1003 lists:sublist(
1004 lists:reverse(BareSortedRooms), 1, NonUndefMax));
1005 {undefined, before} ->
1006 2 L = lists:takewhile(
1007 21 fun({{Id, _}, _}) -> Id < I end,
1008 BareSortedRooms),
1009 2 lists:reverse(
1010 lists:sublist(
1011 lists:reverse(L), 1, NonUndefMax));
1012 2 {Index, _} when Index < 0 orelse Index > Count -> [];
1013 {Index, _} ->
1014 8 lists:sublist(BareSortedRooms, Index + 1, NonUndefMax);
1015 Input ->
1016
:-(
?LOG_ERROR(#{what => muc_get_rooms_with_pagination_failed,
1017 text => <<"Unexpected result in get_rooms_with_pagination">>,
1018
:-(
reason => Input}),
1019
:-(
[]
1020 end,
1021 38 case L2 of
1022 [] ->
1023 7 {L2, #rsm_out{count=Count}};
1024 _ ->
1025 31 H = hd(L2),
1026 31 NewIndex = get_room_pos(H, BareSortedRooms),
1027
1028 31 {{F, _},_} = H,
1029 31 {{Last, _}, _} = lists:last(L2),
1030 31 {L2, #rsm_out{first=F, last=Last, count=Count, index=NewIndex}}
1031 end.
1032
1033 %% @doc Return the position of desired room in the list of rooms.
1034 %% The room must exist in the list. The count starts in 0.
1035 -spec get_room_pos({{binary(), any()}, any()}, [{{binary(), any()}, any}]) -> non_neg_integer().
1036 get_room_pos(Desired, Rooms) ->
1037 31 get_room_pos(Desired, Rooms, 0).
1038 get_room_pos({{NameHost, _}, _}, [{{NameHost, _}, _} | _], HeadPosition) ->
1039 31 HeadPosition;
1040 get_room_pos(Desired, [_ | Rooms], HeadPosition) ->
1041 74 get_room_pos(Desired, Rooms, HeadPosition + 1).
1042
1043 %% @doc Get a pseudo unique Room Name. The Room Name is generated as a hash of
1044 %% the requester JID, the local time and a random salt.
1045 %%
1046 %% `<<"pseudo">>' because we don't verify that there is not a room
1047 %% with the returned Name already created, nor mark the generated Name
1048 %% as `<<"already used">>'. But in practice, it is unique enough. See
1049 %% http://xmpp.org/extensions/xep-0045.html#createroom-unique
1050 -spec iq_get_unique(jid:jid()) -> jlib:xmlcdata().
1051 iq_get_unique(From) ->
1052
:-(
Raw = [From, erlang:unique_integer(), mongoose_bin:gen_from_crypto()],
1053
:-(
#xmlcdata{content = mongoose_bin:encode_crypto(term_to_binary(Raw))}.
1054
1055 -spec iq_get_register_info(host_type(), jid:server(),
1056 jid:simple_jid() | jid:jid(), ejabberd:lang())
1057 -> [exml:element(), ...].
1058 iq_get_register_info(HostType, MucHost, From, Lang) ->
1059 22 {Nick, Registered} =
1060 case catch get_nick(HostType, MucHost, From) of
1061 {'EXIT', _Reason} ->
1062
:-(
{<<>>, []};
1063 {error, _} ->
1064 8 {<<>>, []};
1065 {ok, N} ->
1066 14 {N, [#xmlel{name = <<"registered">>}]}
1067 end,
1068 22 ClientReqText = translate:translate(
1069 Lang, <<"You need a client that supports x:data to register the nickname">>),
1070 22 ClientReqEl = #xmlel{name = <<"instructions">>,
1071 children = [#xmlcdata{content = ClientReqText}]},
1072 22 EnterNicknameText = translate:translate(Lang, <<"Enter nickname you want to register">>),
1073 22 TitleText = <<(translate:translate(Lang, <<"Nickname Registration at ">>))/binary,
1074 MucHost/binary>>,
1075 22 NickField = #{type => <<"text-single">>,
1076 label => translate:translate(Lang, <<"Nickname">>),
1077 var => <<"nick">>,
1078 values => [Nick]},
1079 22 Registered ++ [ClientReqEl, mongoose_data_forms:form(#{title => TitleText,
1080 instructions => EnterNicknameText,
1081 fields => [NickField]})].
1082
1083 -spec iq_set_register_info(host_type(), jid:server(),
1084 jid:simple_jid() | jid:jid(), nick(), ejabberd:lang())
1085 -> {'error', exml:element()} | {'result', []}.
1086 iq_set_register_info(HostType, MucHost, From, Nick, Lang) ->
1087 15 case set_nick(HostType, MucHost, From, Nick) of
1088 ok ->
1089 15 {result, []};
1090 {error, conflict} ->
1091
:-(
ErrText = <<"That nickname is registered by another person">>,
1092
:-(
{error, mongoose_xmpp_errors:conflict(Lang, ErrText)};
1093 {error, should_not_be_empty} ->
1094
:-(
ErrText = <<"You must fill in field \"Nickname\" in the form">>,
1095
:-(
{error, mongoose_xmpp_errors:not_acceptable(Lang, ErrText)};
1096 {error, ErrorReason} ->
1097
:-(
?LOG_ERROR(#{what => muc_iq_set_register_info_failed,
1098 host_type => HostType, sub_host => MucHost,
1099 from_jid => jid:to_binary(From), nick => Nick,
1100
:-(
reason => ErrorReason}),
1101
:-(
{error, mongoose_xmpp_errors:internal_server_error()}
1102 end.
1103
1104 -spec iq_set_unregister_info(host_type(), jid:server(),
1105 jid:simple_jid() | jid:jid(), ejabberd:lang())
1106 -> {'error', exml:element()} | {'result', []}.
1107 iq_set_unregister_info(HostType, MucHost, From, _Lang) ->
1108 6 case unset_nick(HostType, MucHost, From) of
1109 ok ->
1110 6 {result, []};
1111 {error, ErrorReason} ->
1112
:-(
?LOG_ERROR(#{what => muc_iq_set_unregister_info_failed,
1113 host_type => HostType, sub_host => MucHost,
1114
:-(
from_jid => jid:to_binary(From), reason => ErrorReason}),
1115
:-(
{error, mongoose_xmpp_errors:internal_server_error()}
1116 end.
1117
1118 -spec process_iq_register_set(host_type(), jid:server(),
1119 jid:jid(), exml:element(), ejabberd:lang())
1120 -> {'error', exml:element()} | {'result', []}.
1121 process_iq_register_set(HostType, MucHost, From, SubEl, Lang) ->
1122 31 case xml:get_subtag(SubEl, <<"remove">>) of
1123 false ->
1124 25 case mongoose_data_forms:find_and_parse_form(SubEl) of
1125 #{type := <<"cancel">>} ->
1126 2 {result, []};
1127 #{type := <<"submit">>, kvs := KVs} ->
1128 17 process_register(HostType, MucHost, From, Lang, KVs);
1129 {error, Msg} ->
1130 4 {error, mongoose_xmpp_errors:bad_request(Lang, Msg)};
1131 _ ->
1132 2 {error, mongoose_xmpp_errors:bad_request(Lang, <<"Invalid form type">>)}
1133 end;
1134 _ ->
1135 6 iq_set_unregister_info(HostType, MucHost, From, Lang)
1136 end.
1137
1138 -spec process_register(HostType :: host_type(), MucHost :: jid:server(),
1139 From :: jid:jid(), Lang :: ejabberd:lang(),
1140 KVs :: mongoose_data_forms:kv_map()) ->
1141 {error, exml:element()} | {result, []}.
1142 process_register(HostType, MucHost, From, Lang, #{<<"nick">> := [Nick]}) ->
1143 15 iq_set_register_info(HostType, MucHost, From, Nick, Lang);
1144 process_register(_HostType, _MucHost, _From, Lang, #{}) ->
1145 2 ErrText = <<"You must fill in field \"Nickname\" in the form">>,
1146 2 {error, mongoose_xmpp_errors:not_acceptable(Lang, ErrText)}.
1147
1148 -spec iq_get_vcard(ejabberd:lang()) -> [exml:element(), ...].
1149 iq_get_vcard(Lang) ->
1150
:-(
[#xmlel{name = <<"FN">>,
1151 children = [#xmlcdata{content = <<"ejabberd/mod_muc">>}]},
1152 #xmlel{name = <<"URL">>, children = [#xmlcdata{content = ?MONGOOSE_URI}]},
1153 #xmlel{name = <<"DESC">>,
1154 children = [#xmlcdata{content =
1155 <<(translate:translate(Lang, <<"ejabberd MUC module">>))/binary,
1156 "\nCopyright (c) 2003-2011 ProcessOne">>}]}].
1157
1158 -spec broadcast_service_message(muc_host(), binary() | string()) -> ok.
1159 broadcast_service_message(MucHost, Msg) ->
1160
:-(
lists:foreach(
1161 fun(#muc_online_room{pid = Pid}) ->
1162
:-(
gen_fsm_compat:send_all_state_event(
1163 Pid, {service_message, Msg})
1164 end, get_vh_rooms(MucHost)).
1165
1166 -spec get_vh_rooms(muc_host()) -> [muc_online_room()].
1167 get_vh_rooms(MucHost) ->
1168 44 {ok, HostType} = mongoose_domain_api:get_subdomain_host_type(MucHost),
1169 44 mod_muc_online_backend:get_online_rooms(HostType, MucHost).
1170
1171 -spec get_persistent_vh_rooms(muc_host()) -> [muc_room()].
1172 get_persistent_vh_rooms(MucHost) ->
1173 44 {ok, HostType} = mongoose_domain_api:get_subdomain_host_type(MucHost),
1174 44 case mod_muc_backend:get_rooms(HostType, MucHost) of
1175 {ok, List} ->
1176 44 List;
1177 {error, _} ->
1178
:-(
[]
1179 end.
1180
1181 -spec node_cleanup(host_type(), node()) -> ok.
1182 node_cleanup(HostType, Node) ->
1183
:-(
mod_muc_online_backend:node_cleanup(HostType, Node).
1184
1185 %%====================================================================
1186 %% Hooks handlers
1187 %%====================================================================
1188
1189 -spec is_muc_room_owner(Acc, Params, Extra) -> {ok, Acc} when
1190 Acc :: boolean(),
1191 Params :: #{room := jid:jid(), user := jid:jid()},
1192 Extra :: gen_hook:extra().
1193 is_muc_room_owner(true, _, _) ->
1194
:-(
{ok, true};
1195 is_muc_room_owner(_, #{room := Room, user := User}, _) ->
1196
:-(
Result = mod_muc_room:is_room_owner(Room, User) =:= {ok, true},
1197
:-(
{ok, Result}.
1198
1199 -spec can_access_room(Acc, Params, Extra) -> {ok, Acc} when
1200 Acc :: boolean(),
1201 Params :: #{room := jid:jid(), user := jid:jid()},
1202 Extra :: gen_hook:extra().
1203 can_access_room(true, _, _) ->
1204
:-(
{ok, true};
1205 can_access_room(_, #{room := Room, user := User}, _) ->
1206 337 Result = case mod_muc_room:can_access_room(Room, User) of
1207 25 {error, _} -> false;
1208 312 {ok, CanAccess} -> CanAccess
1209 end,
1210 337 {ok, Result}.
1211
1212 -spec remove_domain(Acc, Params, Extra) -> {ok, Acc} when
1213 Acc :: mongoose_hooks:simple_acc(),
1214 Params :: #{domain := jid:lserver()},
1215 Extra :: gen_hook:extra().
1216 remove_domain(Acc, #{domain := Domain}, #{host_type := HostType}) ->
1217 1 MUCHost = server_host_to_muc_host(HostType, Domain),
1218 1 mod_muc_backend:remove_domain(HostType, MUCHost, Domain),
1219 1 {ok, Acc}.
1220
1221 -spec acc_room_affiliations(Acc, Params, Extra) -> {ok, Acc} when
1222 Acc :: mongoose_acc:t(),
1223 Params :: #{room := jid:jid()},
1224 Extra :: gen_hook:extra().
1225 acc_room_affiliations(Acc, #{room := Room}, _) ->
1226 110 NewAcc = case mongoose_acc:get(?MODULE, {affiliations, Room}, {error, not_found}, Acc) of
1227 {error, _} ->
1228 110 case mod_muc_room:get_room_users(Room) of
1229 {error, not_found} ->
1230 110 Acc;
1231 {ok, _Affs} = Res ->
1232
:-(
mongoose_acc:set(?MODULE, {affiliations, Room}, Res, Acc)
1233 end;
1234 _Affs ->
1235
:-(
Acc
1236 end,
1237 110 {ok, NewAcc}.
1238
1239 -spec can_access_identity(Acc, Params, Extra) -> {ok, Acc} when
1240 Acc :: boolean(),
1241 Params :: #{room := jid:jid(), user := jid:jid()},
1242 Extra :: gen_hook:extra().
1243 can_access_identity(true, _, _) ->
1244
:-(
{ok, true};
1245 can_access_identity(_, #{room := Room, user := User}, _) ->
1246 227 Result = case mod_muc_room:can_access_identity(Room, User) of
1247 25 {error, _} -> false;
1248 202 {ok, CanAccess} -> CanAccess
1249 end,
1250 227 {ok, Result}.
1251
1252 -spec disco_local_items(Acc, Params, Extra) -> {ok, Acc} when
1253 Acc :: mongoose_disco:item_acc(),
1254 Params :: map(),
1255 Extra :: gen_hook:extra().
1256 disco_local_items(Acc = #{host_type := HostType,
1257 to_jid := #jid{lserver = ServerHost},
1258 node := <<>>}, _, _) ->
1259 12 MUCHost = server_host_to_muc_host(HostType, ServerHost),
1260 12 Items = [#{jid => MUCHost, node => ?NS_MUC}],
1261 12 {ok, mongoose_disco:add_items(Items, Acc)};
1262 disco_local_items(Acc, _, _) ->
1263
:-(
{ok, Acc}.
1264
1265 -spec node_cleanup_for_host_type(Acc, Params, Extra) -> {ok, Acc} when
1266 Acc :: mongoose_disco:item_acc(),
1267 Params :: map(),
1268 Extra :: gen_hook:extra().
1269 node_cleanup_for_host_type(Acc, #{node := Node}, #{host_type := HostType}) ->
1270
:-(
node_cleanup(HostType, Node),
1271
:-(
{ok, Acc}.
1272
1273 online_rooms_number() ->
1274 1 lists:sum([online_rooms_number(HostType)
1275 1 || HostType <- gen_mod:hosts_with_module(?MODULE)]).
1276
1277 online_rooms_number(HostType) ->
1278 8 try
1279 8 Supervisor = gen_mod:get_module_proc(HostType, ejabberd_mod_muc_sup),
1280 8 Stats = supervisor:count_children(Supervisor),
1281 8 proplists:get_value(active, Stats)
1282 catch _:_ ->
1283
:-(
0
1284 end.
1285
1286 hibernated_rooms_number() ->
1287
:-(
lists:sum([hibernated_rooms_number(HostType)
1288
:-(
|| HostType <- gen_mod:hosts_with_module(?MODULE)]).
1289
1290 hibernated_rooms_number(HostType) ->
1291 7 try
1292 7 count_hibernated_rooms(HostType)
1293 catch _:_ ->
1294
:-(
0
1295 end.
1296
1297 count_hibernated_rooms(HostType) ->
1298 7 AllRooms = all_room_pids(HostType),
1299 7 lists:foldl(fun count_hibernated_rooms/2, 0, AllRooms).
1300
1301 all_room_pids(HostType) ->
1302 7 Supervisor = gen_mod:get_module_proc(HostType, ejabberd_mod_muc_sup),
1303 7 [Pid || {undefined, Pid, worker, _} <- supervisor:which_children(Supervisor)].
1304
1305
1306 count_hibernated_rooms(Pid, Count) ->
1307 42 case erlang:process_info(Pid, current_function) of
1308 {current_function, {erlang, hibernate, _}} ->
1309 6 Count + 1;
1310 _ ->
1311 36 Count
1312 end.
1313
1314 -spec config_metrics(mongooseim:host_type()) -> [{gen_mod:opt_key(), gen_mod:opt_value()}].
1315 config_metrics(HostType) ->
1316 24 mongoose_module_metrics:opts_for_module(HostType, ?MODULE, [backend, online_backend]).
1317
1318 -spec hooks(mongooseim:host_type()) -> gen_hook:hook_list().
1319 hooks(HostType) ->
1320 76 [{is_muc_room_owner, HostType, fun ?MODULE:is_muc_room_owner/3, #{}, 50},
1321 {can_access_room, HostType, fun ?MODULE:can_access_room/3, #{}, 50},
1322 {remove_domain, HostType, fun ?MODULE:remove_domain/3, #{}, 50},
1323 {acc_room_affiliations, HostType, fun ?MODULE:acc_room_affiliations/3, #{}, 50},
1324 {can_access_identity, HostType, fun ?MODULE:can_access_identity/3, #{}, 50},
1325 {disco_local_items, HostType, fun ?MODULE:disco_local_items/3, #{}, 250},
1326 {node_cleanup_for_host_type, HostType, fun ?MODULE:node_cleanup_for_host_type/3, #{}, 50}].
1327
1328 -spec instrumentation(host_type()) -> [mongoose_instrument:spec()].
1329 instrumentation(HostType) ->
1330 76 [{mod_muc_deep_hibernations, #{host_type => HostType},
1331 #{metrics => #{count => spiral}}},
1332 {mod_muc_process_recreations, #{host_type => HostType},
1333 #{metrics => #{count => spiral}}},
1334 {mod_muc_hibernations, #{host_type => HostType},
1335 #{metrics => #{count => spiral}}},
1336 {mod_muc_hibernated_rooms, #{host_type => HostType},
1337 #{probe => ?MODULE, metrics => #{count => gauge}}},
1338 {mod_muc_online_rooms, #{host_type => HostType},
1339 #{probe => ?MODULE, metrics => #{count => gauge}}}].
1340
1341 -spec probe(mongoose_instrument:event_type(), mongoose_instrument:labels(),
1342 mongoose_instrument:config()) -> mongoose_instrument:measurements().
1343 probe(mod_muc_hibernated_rooms, #{host_type := HostType}, _Config) ->
1344 7 #{count => hibernated_rooms_number(HostType)};
1345 probe(mod_muc_online_rooms, #{host_type := HostType}, _Config) ->
1346 7 #{count => online_rooms_number(HostType)}.
1347
1348 subdomain_pattern(HostType) ->
1349 93 gen_mod:get_module_opt(HostType, ?MODULE, host).
1350
1351 server_host_to_muc_host(HostType, ServerHost) ->
1352 93 mongoose_subdomain_utils:get_fqdn(subdomain_pattern(HostType), ServerHost).
1353
1354 make_server_host(To, #muc_state{host_type = HostType,
1355 subdomain_pattern = SubdomainPattern}) ->
1356 2318 case SubdomainPattern of
1357 {prefix, _} ->
1358 2318 mod_muc_light_utils:room_jid_to_server_host(To);
1359 {fqdn, _} ->
1360
:-(
HostType
1361 end.
Line Hits Source