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