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