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