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