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