1 |
|
%%%---------------------------------------------------------------------- |
2 |
|
%%% File : ejabberd_sm.erl |
3 |
|
%%% Author : Alexey Shchepin <alexey@process-one.net> |
4 |
|
%%% Purpose : Session manager |
5 |
|
%%% Created : 24 Nov 2002 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 |
|
-module(ejabberd_sm). |
26 |
|
-author('alexey@process-one.net'). |
27 |
|
|
28 |
|
-behaviour(gen_server). |
29 |
|
-behaviour(gen_iq_component). |
30 |
|
|
31 |
|
|
32 |
|
%% API |
33 |
|
-export([start/0, |
34 |
|
start_link/0, |
35 |
|
route/3, |
36 |
|
route/4, |
37 |
|
make_new_sid/0, |
38 |
|
open_session/5, |
39 |
|
close_session/5, |
40 |
|
store_info/4, |
41 |
|
get_info/2, |
42 |
|
remove_info/3, |
43 |
|
get_user_resources/1, |
44 |
|
set_presence/6, |
45 |
|
unset_presence/5, |
46 |
|
get_unique_sessions_number/0, |
47 |
|
get_total_sessions_number/0, |
48 |
|
get_node_sessions_number/0, |
49 |
|
get_vh_session_number/1, |
50 |
|
get_vh_session_list/1, |
51 |
|
get_full_session_list/0, |
52 |
|
register_iq_handler/3, |
53 |
|
unregister_iq_handler/2, |
54 |
|
user_resources/2, |
55 |
|
get_session_pid/1, |
56 |
|
get_session/1, |
57 |
|
get_session_ip/1, |
58 |
|
get_user_present_resources/1, |
59 |
|
get_raw_sessions/1, |
60 |
|
is_offline/1, |
61 |
|
get_user_present_pids/2, |
62 |
|
sync/0, |
63 |
|
run_session_cleanup_hook/1, |
64 |
|
terminate_session/2, |
65 |
|
sm_backend/0 |
66 |
|
]). |
67 |
|
|
68 |
|
%% Hook handlers |
69 |
|
-export([node_cleanup/3, |
70 |
|
check_in_subscription/3, |
71 |
|
bounce_offline_message/3, |
72 |
|
disconnect_removed_user/3 |
73 |
|
]). |
74 |
|
|
75 |
|
%% c2s async callback |
76 |
|
-export([store_info_async/5]). |
77 |
|
|
78 |
|
%% gen_server callbacks |
79 |
|
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, |
80 |
|
terminate/2, code_change/3]). |
81 |
|
|
82 |
|
%% xmpp_router callback |
83 |
|
-export([do_filter/3]). |
84 |
|
-export([do_route/4]). |
85 |
|
|
86 |
|
-ignore_xref([do_filter/3, do_route/4, get_unique_sessions_number/0, |
87 |
|
get_user_present_pids/2, start_link/0, user_resources/2, sm_backend/0]). |
88 |
|
|
89 |
|
-include("mongoose.hrl"). |
90 |
|
-include("jlib.hrl"). |
91 |
|
-include("session.hrl"). |
92 |
|
|
93 |
|
-record(state, {}). |
94 |
|
-type state() :: #state{}. |
95 |
|
|
96 |
|
-type sid() :: {mongoose_lib:microseconds(), pid()}. |
97 |
|
-type priority() :: integer() | undefined. |
98 |
|
|
99 |
|
-type session() :: #session{ |
100 |
|
sid :: sid(), |
101 |
|
usr :: jid:simple_jid(), |
102 |
|
us :: jid:simple_bare_jid(), |
103 |
|
priority :: priority(), |
104 |
|
info :: info() |
105 |
|
}. |
106 |
|
-type info() :: #{info_key() => any()}. |
107 |
|
|
108 |
|
-type backend() :: ejabberd_sm_mnesia | ejabberd_sm_redis | ejabberd_sm_cets. |
109 |
|
-type close_reason() :: resumed | normal | replaced. |
110 |
|
-type info_key() :: atom(). |
111 |
|
|
112 |
|
-export_type([session/0, |
113 |
|
sid/0, |
114 |
|
priority/0, |
115 |
|
backend/0, |
116 |
|
close_reason/0, |
117 |
|
info/0, |
118 |
|
info_key/0 |
119 |
|
]). |
120 |
|
|
121 |
|
%% default value for the maximum number of user connections |
122 |
|
-define(MAX_USER_SESSIONS, 100). |
123 |
|
-define(UNIQUE_COUNT_CACHE, [cache, unique_sessions_number]). |
124 |
|
|
125 |
|
%%==================================================================== |
126 |
|
%% API |
127 |
|
%%==================================================================== |
128 |
|
%%-------------------------------------------------------------------- |
129 |
|
%% Function: start_link() -> {ok, Pid} | ignore | {error, Error} |
130 |
|
%% Description: Starts the server |
131 |
|
%%-------------------------------------------------------------------- |
132 |
|
|
133 |
|
-spec start() -> {ok, pid()}. |
134 |
|
start() -> |
135 |
92 |
Spec = {?MODULE, {?MODULE, start_link, []}, permanent, brutal_kill, worker, [?MODULE]}, |
136 |
92 |
{ok, _} = ejabberd_sup:start_child(Spec). |
137 |
|
|
138 |
|
-spec start_link() -> 'ignore' | {'error', _} | {'ok', pid()}. |
139 |
|
start_link() -> |
140 |
92 |
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). |
141 |
|
|
142 |
|
|
143 |
|
%% You MUST NOT call this function from the big tests. |
144 |
|
%% In 99% you should call ejabberd_router:route/3 instead. |
145 |
|
%% This function would fail for the first routed IQ. |
146 |
|
-spec route(From, To, Packet) -> Acc when |
147 |
|
From :: jid:jid(), |
148 |
|
To :: jid:jid(), |
149 |
|
Packet :: exml:element() | mongoose_acc:t(), |
150 |
|
Acc :: mongoose_acc:t(). |
151 |
|
route(From, To, #xmlel{} = Packet) -> |
152 |
4514 |
Acc = new_acc(From, To, Packet), |
153 |
4514 |
route(From, To, Acc); |
154 |
|
route(From, To, Acc) -> |
155 |
5462 |
route(From, To, Acc, mongoose_acc:element(Acc)). |
156 |
|
|
157 |
|
-spec new_acc(jid:jid(), jid:jid(), exml:element()) -> mongoose_acc:t(). |
158 |
|
new_acc(From, To = #jid{lserver = LServer}, Packet) -> |
159 |
4514 |
{ok, HostType} = mongoose_domain_api:get_domain_host_type(To#jid.lserver), |
160 |
4514 |
mongoose_acc:new(#{location => ?LOCATION, |
161 |
|
host_type => HostType, |
162 |
|
lserver => LServer, |
163 |
|
element => Packet, |
164 |
|
from_jid => From, |
165 |
|
to_jid => To}). |
166 |
|
|
167 |
|
route(From, To, Acc, El) -> |
168 |
52196 |
try |
169 |
52196 |
do_route(Acc, From, To, El) |
170 |
|
catch Class:Reason:Stacktrace -> |
171 |
:-( |
?LOG_ERROR(#{what => sm_route_failed, |
172 |
|
text => <<"Failed to route stanza in ejabberd_sm">>, |
173 |
|
class => Class, reason => Reason, stacktrace => Stacktrace, |
174 |
:-( |
acc => Acc}), |
175 |
:-( |
Acc |
176 |
|
end. |
177 |
|
|
178 |
|
-spec make_new_sid() -> sid(). |
179 |
|
make_new_sid() -> |
180 |
8820 |
{erlang:system_time(microsecond), self()}. |
181 |
|
|
182 |
|
-spec open_session(HostType, SID, JID, Priority, Info) -> ReplacedPids when |
183 |
|
HostType :: binary(), |
184 |
|
SID :: 'undefined' | sid(), |
185 |
|
JID :: jid:jid(), |
186 |
|
Priority :: integer() | undefined, |
187 |
|
Info :: info(), |
188 |
|
ReplacedPids :: [pid()]. |
189 |
|
open_session(HostType, SID, JID, Priority, Info) -> |
190 |
7807 |
set_session(SID, JID, Priority, Info), |
191 |
7807 |
ReplacedPIDs = check_for_sessions_to_replace(HostType, JID), |
192 |
7807 |
mongoose_hooks:sm_register_connection_hook(HostType, SID, JID, Info), |
193 |
7807 |
ReplacedPIDs. |
194 |
|
|
195 |
|
-spec close_session(Acc, SID, JID, Reason, Info) -> Acc1 when |
196 |
|
Acc :: mongoose_acc:t(), |
197 |
|
SID :: 'undefined' | sid(), |
198 |
|
JID :: jid:jid(), |
199 |
|
Reason :: close_reason(), |
200 |
|
Info :: info(), |
201 |
|
Acc1 :: mongoose_acc:t(). |
202 |
|
close_session(Acc, SID, JID, Reason, Info) -> |
203 |
7803 |
#jid{luser = LUser, lserver = LServer, lresource = LResource} = JID, |
204 |
7803 |
ejabberd_sm_backend:delete_session(SID, LUser, LServer, LResource), |
205 |
7803 |
mongoose_hooks:sm_remove_connection_hook(Acc, SID, JID, Info, Reason). |
206 |
|
|
207 |
|
-spec store_info(jid:jid(), sid(), info_key(), any()) -> ok. |
208 |
|
store_info(JID, SID, Key, Value) -> |
209 |
6156 |
{_, Pid} = SID, |
210 |
6156 |
mongoose_c2s:async_with_state(Pid, fun ejabberd_sm:store_info_async/5, [SID, JID, Key, Value]). |
211 |
|
|
212 |
|
-spec remove_info(jid:jid(), sid(), info_key()) -> ok. |
213 |
|
remove_info(JID, SID, Key) -> |
214 |
6060 |
store_info(JID, SID, Key, undefined). |
215 |
|
|
216 |
|
store_info_async(C2sData, SID, JID, Key, Value) -> |
217 |
102 |
Info = mongoose_c2s:get_info(C2sData), |
218 |
102 |
Info2 = update_info(Key, Value, Info), |
219 |
102 |
Priority = mod_presence:get_old_priority(mod_presence:maybe_get_handler(C2sData)), |
220 |
102 |
set_session(SID, JID, Priority, Info2), |
221 |
102 |
mongoose_c2s:set_info(C2sData, Info2). |
222 |
|
|
223 |
|
update_info(Key, undefined, Info) -> |
224 |
6 |
maps:remove(Key, Info); |
225 |
|
update_info(Key, Value, Info) -> |
226 |
96 |
maps:put(Key, Value, Info). |
227 |
|
|
228 |
|
-spec get_info(jid:jid(), info_key()) -> |
229 |
|
{ok, any()} | {error, offline | not_set}. |
230 |
|
get_info(JID, Key) -> |
231 |
:-( |
case get_session(JID) of |
232 |
:-( |
offline -> {error, offline}; |
233 |
|
Session -> |
234 |
:-( |
case mongoose_session:get_info(Session, Key, {error, not_set}) of |
235 |
:-( |
{Key, Value} -> {ok, Value}; |
236 |
:-( |
Other -> Other |
237 |
|
end |
238 |
|
end. |
239 |
|
|
240 |
|
-spec get_user_resources(JID :: jid:jid()) -> [binary()]. |
241 |
|
get_user_resources(#jid{luser = LUser, lserver = LServer}) -> |
242 |
1606 |
Ss = ejabberd_sm_backend:get_sessions(LUser, LServer), |
243 |
1606 |
[element(3, S#session.usr) || S <- clean_session_list(Ss)]. |
244 |
|
|
245 |
|
|
246 |
|
-spec get_session_ip(JID) -> undefined | {inet:ip_address(), integer()} when |
247 |
|
JID :: jid:jid(). |
248 |
|
get_session_ip(JID) -> |
249 |
1 |
case get_session(JID) of |
250 |
:-( |
offline -> undefined; |
251 |
|
Session -> |
252 |
1 |
case mongoose_session:get_info(Session, ip, undefined) of |
253 |
1 |
{ip, Val} -> Val; |
254 |
:-( |
Other -> Other |
255 |
|
end |
256 |
|
end. |
257 |
|
|
258 |
|
-spec get_session(JID) -> offline | session() when |
259 |
|
JID :: jid:jid(). |
260 |
|
get_session(JID) -> |
261 |
28387 |
#jid{luser = LUser, lserver = LServer, lresource = LResource} = JID, |
262 |
28387 |
case ejabberd_sm_backend:get_sessions(LUser, LServer, LResource) of |
263 |
|
[] -> |
264 |
9872 |
offline; |
265 |
|
Ss -> |
266 |
18515 |
lists:max(Ss) |
267 |
|
end. |
268 |
|
|
269 |
|
-spec get_raw_sessions(jid:jid()) -> [session()]. |
270 |
|
get_raw_sessions(#jid{luser = LUser, lserver = LServer}) -> |
271 |
5533 |
clean_session_list( |
272 |
|
ejabberd_sm_backend:get_sessions(LUser, LServer)). |
273 |
|
|
274 |
|
-spec set_presence(Acc, SID, JID, Prio, Presence, Info) -> Acc1 when |
275 |
|
Acc :: mongoose_acc:t(), |
276 |
|
Acc1 :: mongoose_acc:t(), |
277 |
|
SID :: 'undefined' | sid(), |
278 |
|
JID :: jid:jid(), |
279 |
|
Prio :: 'undefined' | integer(), |
280 |
|
Presence :: any(), |
281 |
|
Info :: info(). |
282 |
|
set_presence(Acc, SID, JID, Priority, Presence, Info) -> |
283 |
6105 |
set_session(SID, JID, Priority, Info), |
284 |
6105 |
mongoose_hooks:set_presence_hook(Acc, JID, Presence). |
285 |
|
|
286 |
|
|
287 |
|
-spec unset_presence(Acc, SID, JID, Status, Info) -> Acc1 when |
288 |
|
Acc :: mongoose_acc:t(), |
289 |
|
Acc1 :: mongoose_acc:t(), |
290 |
|
SID :: 'undefined' | sid(), |
291 |
|
JID :: jid:jid(), |
292 |
|
Status :: binary(), |
293 |
|
Info :: info(). |
294 |
|
unset_presence(Acc, SID, JID, Status, Info) -> |
295 |
125 |
set_session(SID, JID, undefined, Info), |
296 |
125 |
mongoose_hooks:unset_presence_hook(Acc, JID, Status). |
297 |
|
|
298 |
|
|
299 |
|
-spec get_session_pid(JID) -> none | pid() when |
300 |
|
JID :: jid:jid(). |
301 |
|
get_session_pid(JID) -> |
302 |
28377 |
case get_session(JID) of |
303 |
9872 |
offline -> none; |
304 |
18505 |
#session{sid = {_, Pid}} -> Pid |
305 |
|
end. |
306 |
|
|
307 |
|
-spec get_unique_sessions_number() -> integer(). |
308 |
|
get_unique_sessions_number() -> |
309 |
241 |
try |
310 |
241 |
C = ejabberd_sm_backend:unique_count(), |
311 |
241 |
mongoose_metrics:update(global, ?UNIQUE_COUNT_CACHE, C), |
312 |
241 |
C |
313 |
|
catch |
314 |
|
_:_ -> |
315 |
:-( |
get_cached_unique_count() |
316 |
|
end. |
317 |
|
|
318 |
|
|
319 |
|
-spec get_total_sessions_number() -> integer(). |
320 |
|
get_total_sessions_number() -> |
321 |
248 |
ejabberd_sm_backend:total_count(). |
322 |
|
|
323 |
|
|
324 |
|
-spec get_vh_session_number(jid:server()) -> non_neg_integer(). |
325 |
|
get_vh_session_number(Server) -> |
326 |
19 |
length(ejabberd_sm_backend:get_sessions(Server)). |
327 |
|
|
328 |
|
|
329 |
|
-spec get_vh_session_list(jid:server()) -> [session()]. |
330 |
|
get_vh_session_list(Server) -> |
331 |
62 |
ejabberd_sm_backend:get_sessions(Server). |
332 |
|
|
333 |
|
|
334 |
|
-spec get_node_sessions_number() -> non_neg_integer(). |
335 |
|
get_node_sessions_number() -> |
336 |
244 |
Children = supervisor:which_children(mongoose_listener_sup), |
337 |
244 |
Listeners = [Ref || {Ref, _, _, [mongoose_c2s_listener]} <- Children], |
338 |
244 |
lists:sum([maps:get(active_connections, ranch:info(Ref)) || Ref <- Listeners]). |
339 |
|
|
340 |
|
-spec get_full_session_list() -> [session()]. |
341 |
|
get_full_session_list() -> |
342 |
104 |
ejabberd_sm_backend:get_sessions(). |
343 |
|
|
344 |
|
|
345 |
|
register_iq_handler(Host, XMLNS, IQHandler) -> |
346 |
943 |
ejabberd_sm ! {register_iq_handler, Host, XMLNS, IQHandler}, |
347 |
943 |
ok. |
348 |
|
|
349 |
|
-spec sync() -> ok. |
350 |
|
sync() -> |
351 |
943 |
gen_server:call(ejabberd_sm, sync). |
352 |
|
|
353 |
|
unregister_iq_handler(Host, XMLNS) -> |
354 |
932 |
ejabberd_sm ! {unregister_iq_handler, Host, XMLNS}, |
355 |
932 |
ok. |
356 |
|
|
357 |
|
-spec run_session_cleanup_hook(#session{}) -> mongoose_acc:t(). |
358 |
|
run_session_cleanup_hook(#session{usr = {U, S, R}, sid = SID}) -> |
359 |
:-( |
{ok, HostType} = mongoose_domain_api:get_domain_host_type(S), |
360 |
:-( |
Acc = mongoose_acc:new( |
361 |
|
#{location => ?LOCATION, |
362 |
|
host_type => HostType, |
363 |
|
lserver => S, |
364 |
|
element => undefined}), |
365 |
:-( |
mongoose_hooks:session_cleanup(S, Acc, U, R, SID). |
366 |
|
|
367 |
|
-spec terminate_session(jid:jid() | pid(), binary()) -> ok | no_session. |
368 |
|
terminate_session(#jid{} = Jid, Reason) -> |
369 |
19 |
case get_session_pid(Jid) of |
370 |
|
none -> |
371 |
4 |
no_session; |
372 |
|
Pid -> |
373 |
15 |
terminate_session(Pid, Reason) |
374 |
|
end; |
375 |
|
terminate_session(Pid, Reason) -> |
376 |
52 |
mongoose_c2s:exit(Pid, Reason). |
377 |
|
|
378 |
|
%%==================================================================== |
379 |
|
%% Hook handlers |
380 |
|
%%==================================================================== |
381 |
|
|
382 |
|
-spec node_cleanup(Acc, Args, Extra) -> {ok, Acc} when |
383 |
|
Acc :: any(), |
384 |
|
Args :: #{node := node()}, |
385 |
|
Extra :: map(). |
386 |
|
node_cleanup(Acc, #{node := Node}, _) -> |
387 |
8 |
Timeout = timer:minutes(1), |
388 |
8 |
Res = gen_server:call(?MODULE, {node_cleanup, Node}, Timeout), |
389 |
8 |
{ok, maps:put(?MODULE, Res, Acc)}. |
390 |
|
|
391 |
|
-spec check_in_subscription(Acc, Args, Extra)-> {ok, Acc} | {stop, false} when |
392 |
|
Acc :: any(), |
393 |
|
Args :: #{to := jid:jid()}, |
394 |
|
Extra :: map(). |
395 |
|
check_in_subscription(Acc, #{to := ToJID}, _) -> |
396 |
585 |
case ejabberd_auth:does_user_exist(ToJID) of |
397 |
|
true -> |
398 |
512 |
{ok, Acc}; |
399 |
|
false -> |
400 |
73 |
{stop, mongoose_acc:set(hook, result, false, Acc)} |
401 |
|
end. |
402 |
|
|
403 |
|
-spec bounce_offline_message(Acc, Args, Extra) -> {stop, Acc} when |
404 |
|
Acc :: map(), |
405 |
|
Args :: #{from := jid:jid(), to := jid:jid(), packet := exml:element()}, |
406 |
|
Extra :: map(). |
407 |
|
bounce_offline_message(Acc, #{from := From, to := To, packet := Packet}, _) -> |
408 |
32 |
Acc1 = mongoose_hooks:xmpp_bounce_message(Acc), |
409 |
32 |
E = mongoose_xmpp_errors:service_unavailable(<<"en">>, <<"Bounce offline message">>), |
410 |
32 |
{Acc2, Err} = jlib:make_error_reply(Acc1, Packet, E), |
411 |
32 |
Acc3 = ejabberd_router:route(To, From, Acc2, Err), |
412 |
32 |
{stop, Acc3}. |
413 |
|
|
414 |
|
-spec disconnect_removed_user(Acc, Args, Extra) -> {ok, Acc} when |
415 |
|
Acc :: mongoose_acc:t(), |
416 |
|
Args :: #{jid := jid:jid()}, |
417 |
|
Extra :: map(). |
418 |
|
disconnect_removed_user(Acc, #{jid := #jid{luser = LUser, lserver = LServer}}, _) -> |
419 |
6349 |
lists:map(fun(#session{sid = {_, Pid}}) -> terminate_session(Pid, <<"User removed">>) end, |
420 |
|
ejabberd_sm_backend:get_sessions(LUser, LServer)), |
421 |
6349 |
{ok, Acc}. |
422 |
|
|
423 |
|
%%==================================================================== |
424 |
|
%% gen_server callbacks |
425 |
|
%%==================================================================== |
426 |
|
|
427 |
|
%%-------------------------------------------------------------------- |
428 |
|
%% Function: init(Args) -> {ok, State} | |
429 |
|
%% {ok, State, Timeout} | |
430 |
|
%% ignore | |
431 |
|
%% {stop, Reason} |
432 |
|
%% Description: Initiates the server |
433 |
|
%%-------------------------------------------------------------------- |
434 |
|
-spec init(_) -> {ok, state()}. |
435 |
|
init([]) -> |
436 |
92 |
Backend = mongoose_config:get_opt(sm_backend), |
437 |
92 |
ejabberd_sm_backend:init(#{backend => Backend}), |
438 |
|
|
439 |
92 |
ets:new(sm_iqtable, [named_table, protected, {read_concurrency, true}]), |
440 |
92 |
gen_hook:add_handler(node_cleanup, global, fun ?MODULE:node_cleanup/3, #{}, 50), |
441 |
92 |
lists:foreach(fun(HostType) -> gen_hook:add_handlers(hooks(HostType)) end, |
442 |
|
?ALL_HOST_TYPES), |
443 |
|
%% Create metrics after backend has started, otherwise probe could have null value |
444 |
92 |
create_metrics(), |
445 |
92 |
{ok, #state{}}. |
446 |
|
|
447 |
|
create_metrics() -> |
448 |
92 |
mongoose_metrics:ensure_metric(global, ?UNIQUE_COUNT_CACHE, gauge), |
449 |
92 |
mongoose_metrics:create_probe_metric(global, totalSessionCount, mongoose_metrics_probe_total_sessions), |
450 |
92 |
mongoose_metrics:create_probe_metric(global, uniqueSessionCount, mongoose_metrics_probe_unique_sessions), |
451 |
92 |
mongoose_metrics:create_probe_metric(global, nodeSessionCount, mongoose_metrics_probe_node_sessions). |
452 |
|
|
453 |
|
-spec hooks(binary()) -> [gen_hook:hook_tuple()]. |
454 |
|
hooks(HostType) -> |
455 |
503 |
[ |
456 |
|
{roster_in_subscription, HostType, fun ?MODULE:check_in_subscription/3, #{}, 20}, |
457 |
|
{offline_message_hook, HostType, fun ?MODULE:bounce_offline_message/3, #{}, 100}, |
458 |
|
{offline_groupchat_message_hook, HostType, fun ?MODULE:bounce_offline_message/3, #{}, 100}, |
459 |
|
{remove_user, HostType, fun ?MODULE:disconnect_removed_user/3, #{}, 100} |
460 |
|
]. |
461 |
|
|
462 |
|
%%-------------------------------------------------------------------- |
463 |
|
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} | |
464 |
|
%% {reply, Reply, State, Timeout} | |
465 |
|
%% {noreply, State} | |
466 |
|
%% {noreply, State, Timeout} | |
467 |
|
%% {stop, Reason, Reply, State} | |
468 |
|
%% {stop, Reason, State} |
469 |
|
%% Description: Handling call messages |
470 |
|
%%-------------------------------------------------------------------- |
471 |
|
handle_call({node_cleanup, Node}, _From, State) -> |
472 |
8 |
{TimeDiff, _R} = timer:tc(fun ejabberd_sm_backend:cleanup/1, [Node]), |
473 |
8 |
?LOG_INFO(#{what => sm_node_cleanup, |
474 |
|
text => <<"Cleaning after a node that went down">>, |
475 |
|
cleanup_node => Node, |
476 |
8 |
duration => erlang:round(TimeDiff / 1000)}), |
477 |
8 |
{reply, ok, State}; |
478 |
|
handle_call(sync, _From, State) -> |
479 |
943 |
{reply, ok, State}; |
480 |
|
handle_call(_Request, _From, State) -> |
481 |
:-( |
Reply = ok, |
482 |
:-( |
{reply, Reply, State}. |
483 |
|
|
484 |
|
%%-------------------------------------------------------------------- |
485 |
|
%% Function: handle_cast(Msg, State) -> {noreply, State} | |
486 |
|
%% {noreply, State, Timeout} | |
487 |
|
%% {stop, Reason, State} |
488 |
|
%% Description: Handling cast messages |
489 |
|
%%-------------------------------------------------------------------- |
490 |
|
handle_cast(_Msg, State) -> |
491 |
:-( |
{noreply, State}. |
492 |
|
|
493 |
|
%%-------------------------------------------------------------------- |
494 |
|
%% Function: handle_info(Info, State) -> {noreply, State} | |
495 |
|
%% {noreply, State, Timeout} | |
496 |
|
%% {stop, Reason, State} |
497 |
|
%% Description: Handling all non call/cast messages |
498 |
|
%%-------------------------------------------------------------------- |
499 |
|
-spec handle_info(_, _) -> {'noreply', _}. |
500 |
|
handle_info({route, From, To, Packet}, State) -> |
501 |
:-( |
route(From, To, Packet), |
502 |
:-( |
{noreply, State}; |
503 |
|
handle_info({register_iq_handler, Host, XMLNS, IQHandler}, State) -> |
504 |
943 |
case ets:insert_new(sm_iqtable, {{XMLNS, Host}, IQHandler}) of |
505 |
943 |
true -> ok; |
506 |
|
false -> |
507 |
:-( |
?LOG_WARNING(#{what => register_iq_handler_duplicate, |
508 |
:-( |
xmlns => XMLNS, host => Host}) |
509 |
|
end, |
510 |
943 |
{noreply, State}; |
511 |
|
handle_info({unregister_iq_handler, Host, XMLNS}, State) -> |
512 |
932 |
case ets:lookup(sm_iqtable, {XMLNS, Host}) of |
513 |
|
[{_, IQHandler}] -> |
514 |
932 |
gen_iq_component:stop_iq_handler(IQHandler), |
515 |
932 |
ets:delete(sm_iqtable, {XMLNS, Host}); |
516 |
|
_ -> |
517 |
:-( |
?LOG_WARNING(#{what => unregister_iq_handler_missing, |
518 |
:-( |
xmlns => XMLNS, host => Host}) |
519 |
|
end, |
520 |
932 |
{noreply, State}; |
521 |
|
handle_info(_Info, State) -> |
522 |
:-( |
{noreply, State}. |
523 |
|
|
524 |
|
%%-------------------------------------------------------------------- |
525 |
|
%% Function: terminate(Reason, State) -> void() |
526 |
|
%% Description: This function is called by a gen_server when it is about to |
527 |
|
%% terminate. It should be the opposite of Module:init/1 and do any necessary |
528 |
|
%% cleaning up. When it returns, the gen_server terminates with Reason. |
529 |
|
%% The return value is ignored. |
530 |
|
%%-------------------------------------------------------------------- |
531 |
|
-spec terminate(_, state()) -> 'ok'. |
532 |
|
terminate(_Reason, _State) -> |
533 |
:-( |
ok. |
534 |
|
|
535 |
|
%%-------------------------------------------------------------------- |
536 |
|
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState} |
537 |
|
%% Description: Convert process state when code is changed |
538 |
|
%%-------------------------------------------------------------------- |
539 |
|
code_change(_OldVsn, State, _Extra) -> |
540 |
:-( |
{ok, State}. |
541 |
|
|
542 |
|
%%-------------------------------------------------------------------- |
543 |
|
%%% Internal functions |
544 |
|
%%-------------------------------------------------------------------- |
545 |
|
|
546 |
|
-spec set_session(SID, JID, Prio, Info) -> ok | {error, any()} when |
547 |
|
SID :: sid() | 'undefined', |
548 |
|
JID :: jid:jid(), |
549 |
|
Prio :: priority(), |
550 |
|
Info :: info(). |
551 |
|
set_session(SID, JID, Priority, Info) -> |
552 |
14139 |
#jid{luser = LUser, lserver = LServer, lresource = LResource} = JID, |
553 |
14139 |
US = {LUser, LServer}, |
554 |
14139 |
USR = {LUser, LServer, LResource}, |
555 |
14139 |
Session = #session{sid = SID, |
556 |
|
usr = USR, |
557 |
|
us = US, |
558 |
|
priority = Priority, |
559 |
|
info = Info}, |
560 |
14139 |
ejabberd_sm_backend:set_session(LUser, LServer, LResource, Session). |
561 |
|
|
562 |
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
563 |
|
|
564 |
|
do_filter(From, To, Packet) -> |
565 |
:-( |
{From, To, Packet}. |
566 |
|
|
567 |
|
-spec do_route(Acc, From, To, Payload) -> Acc when |
568 |
|
Acc :: mongoose_acc:t(), |
569 |
|
From :: jid:jid(), |
570 |
|
To :: jid:jid(), |
571 |
|
Payload :: exml:element(). |
572 |
|
do_route(Acc, From, To, El) -> |
573 |
52196 |
?LOG_DEBUG(#{what => sm_route, acc => Acc}), |
574 |
52196 |
#jid{lresource = LResource} = To, |
575 |
52196 |
#xmlel{name = Name} = El, |
576 |
52196 |
case LResource of |
577 |
|
<<>> -> |
578 |
24883 |
do_route_no_resource(Name, From, To, Acc, El); |
579 |
|
_ -> |
580 |
27313 |
case get_session_pid(To) of |
581 |
|
none -> |
582 |
9868 |
do_route_offline(Name, mongoose_acc:stanza_type(Acc), |
583 |
|
From, To, Acc, El); |
584 |
|
Pid when is_pid(Pid) -> |
585 |
17445 |
?LOG_DEBUG(#{what => sm_route_to_pid, session_pid => Pid, acc => Acc}), |
586 |
17445 |
mongoose_c2s:route(Pid, Acc), |
587 |
17445 |
Acc |
588 |
|
end |
589 |
|
end. |
590 |
|
|
591 |
|
-spec do_route_no_resource_presence_prv(From, To, Acc, Packet, Type, Reason) -> boolean() when |
592 |
|
From :: jid:jid(), |
593 |
|
To :: jid:jid(), |
594 |
|
Acc :: mongoose_acc:t(), |
595 |
|
Packet :: exml:element(), |
596 |
|
Type :: 'subscribe' | 'subscribed' | 'unsubscribe' | 'unsubscribed', |
597 |
|
Reason :: any(). |
598 |
|
do_route_no_resource_presence_prv(From, To, Acc, Packet, Type, Reason) -> |
599 |
585 |
case is_privacy_allow(From, To, Acc, Packet) of |
600 |
|
true -> |
601 |
585 |
Res = mongoose_hooks:roster_in_subscription(Acc, To, From, Type, Reason), |
602 |
585 |
mongoose_acc:get(hook, result, false, Res); |
603 |
|
false -> |
604 |
:-( |
false |
605 |
|
end. |
606 |
|
|
607 |
|
-spec do_route_no_resource_presence(Type, From, To, Acc, Packet) -> boolean() when |
608 |
|
Type :: binary(), |
609 |
|
From :: jid:jid(), |
610 |
|
To :: jid:jid(), |
611 |
|
Acc :: mongoose_acc:t(), |
612 |
|
Packet :: exml:element(). |
613 |
|
do_route_no_resource_presence(<<"subscribe">>, From, To, Acc, Packet) -> |
614 |
170 |
Reason = xml:get_path_s(Packet, [{elem, <<"status">>}, cdata]), |
615 |
170 |
do_route_no_resource_presence_prv(From, To, Acc, Packet, subscribe, Reason); |
616 |
|
do_route_no_resource_presence(<<"subscribed">>, From, To, Acc, Packet) -> |
617 |
163 |
do_route_no_resource_presence_prv(From, To, Acc, Packet, subscribed, <<>>); |
618 |
|
do_route_no_resource_presence(<<"unsubscribe">>, From, To, Acc, Packet) -> |
619 |
85 |
do_route_no_resource_presence_prv(From, To, Acc, Packet, unsubscribe, <<>>); |
620 |
|
do_route_no_resource_presence(<<"unsubscribed">>, From, To, Acc, Packet) -> |
621 |
167 |
do_route_no_resource_presence_prv(From, To, Acc, Packet, unsubscribed, <<>>); |
622 |
|
do_route_no_resource_presence(_, _, _, _, _) -> |
623 |
18685 |
true. |
624 |
|
|
625 |
|
|
626 |
|
-spec do_route_no_resource(Name, From, To, Acc, El) -> Acc when |
627 |
|
Name :: undefined | binary(), |
628 |
|
From :: jid:jid(), |
629 |
|
To :: jid:jid(), |
630 |
|
Acc :: mongoose_acc:t(), |
631 |
|
El :: exml:element(). |
632 |
|
do_route_no_resource(<<"presence">>, From, To, Acc, El) -> |
633 |
19270 |
Type = mongoose_acc:stanza_type(Acc), |
634 |
19270 |
case do_route_no_resource_presence(Type, From, To, Acc, El) of |
635 |
|
true -> |
636 |
19185 |
ResourcesPids = get_user_present_resources_and_pids(To), |
637 |
19185 |
lists:foldl(fun({Resource, Pid}, Acc1) -> |
638 |
19387 |
NewTo = jid:replace_resource(To, Resource), |
639 |
19387 |
NewAccParams = #{element => El, from_jid => From, to_jid => NewTo}, |
640 |
19387 |
Acc2 = mongoose_acc:update_stanza(NewAccParams, Acc1), |
641 |
19387 |
mongoose_c2s:route(Pid, Acc2), |
642 |
19387 |
Acc2 |
643 |
|
end, Acc, ResourcesPids); |
644 |
|
false -> |
645 |
85 |
Acc |
646 |
|
end; |
647 |
|
do_route_no_resource(<<"message">>, From, To, Acc, El) -> |
648 |
2252 |
route_message(From, To, Acc, El); |
649 |
|
do_route_no_resource(<<"iq">>, From, To, Acc, El) -> |
650 |
3361 |
process_iq(From, To, Acc, El); |
651 |
|
do_route_no_resource(_, _, _, Acc, _) -> |
652 |
:-( |
Acc. |
653 |
|
|
654 |
|
-spec do_route_offline(Name, Type, From, To, Acc, Packet) -> mongoose_acc:t() when |
655 |
|
Name :: 'undefined' | binary(), |
656 |
|
Type :: binary(), |
657 |
|
From :: jid:jid(), |
658 |
|
To :: jid:jid(), |
659 |
|
Acc :: mongoose_acc:t(), |
660 |
|
Packet :: exml:element(). |
661 |
|
do_route_offline(<<"message">>, _, From, To, Acc, Packet) -> |
662 |
174 |
HostType = mongoose_acc:host_type(Acc), |
663 |
174 |
Drop = mongoose_hooks:sm_filter_offline_message(HostType, From, To, Packet), |
664 |
174 |
case Drop of |
665 |
|
false -> |
666 |
174 |
route_message(From, To, Acc, Packet); |
667 |
|
true -> |
668 |
:-( |
?LOG_DEBUG(#{what => sm_offline_dropped, acc => Acc}), |
669 |
:-( |
Acc |
670 |
|
end; |
671 |
|
do_route_offline(<<"iq">>, <<"error">>, _From, _To, Acc, _Packet) -> |
672 |
:-( |
Acc; |
673 |
|
do_route_offline(<<"iq">>, <<"result">>, _From, _To, Acc, _Packet) -> |
674 |
1 |
Acc; |
675 |
|
do_route_offline(<<"iq">>, _, From, To, Acc, Packet) -> |
676 |
1000 |
E = mongoose_xmpp_errors:service_unavailable(<<"en">>, <<"Route offline">>), |
677 |
1000 |
{Acc1, Err} = jlib:make_error_reply(Acc, Packet, E), |
678 |
1000 |
ejabberd_router:route(To, From, Acc1, Err); |
679 |
|
do_route_offline(_, _, _, _, Acc, _) -> |
680 |
8693 |
?LOG_DEBUG(#{what => sm_packet_dropped, acc => Acc}), |
681 |
8693 |
Acc. |
682 |
|
|
683 |
|
|
684 |
|
%% @doc The default list applies to the user as a whole, |
685 |
|
%% and is processed if there is no active list set |
686 |
|
%% for the target session/resource to which a stanza is addressed, |
687 |
|
%% or if there are no current sessions for the user. |
688 |
|
-spec is_privacy_allow(From, To, Acc, Packet) -> boolean() when |
689 |
|
From :: jid:jid(), |
690 |
|
To :: jid:jid(), |
691 |
|
Acc :: mongoose_acc:t(), |
692 |
|
Packet :: exml:element() | mongoose_acc:t(). |
693 |
|
is_privacy_allow(From, To, Acc, Packet) -> |
694 |
788 |
HostType = mongoose_acc:host_type(Acc), |
695 |
788 |
PrivacyList = mongoose_hooks:privacy_get_user_list(HostType, To), |
696 |
788 |
is_privacy_allow(From, To, Acc, Packet, PrivacyList). |
697 |
|
|
698 |
|
|
699 |
|
%% @doc Check if privacy rules allow this delivery |
700 |
|
-spec is_privacy_allow(From, To, Acc, Packet, PrivacyList) -> boolean() when |
701 |
|
From :: jid:jid(), |
702 |
|
To :: jid:jid(), |
703 |
|
Acc :: mongoose_acc:t(), |
704 |
|
Packet :: exml:element(), |
705 |
|
PrivacyList :: mongoose_privacy:userlist(). |
706 |
|
is_privacy_allow(_From, To, Acc, _Packet, PrivacyList) -> |
707 |
788 |
{Res, _} = mongoose_privacy:privacy_check_packet(Acc, To, PrivacyList, To, in), |
708 |
788 |
allow == Res. |
709 |
|
|
710 |
|
|
711 |
|
-spec route_message(From, To, Acc, Packet) -> Acc when |
712 |
|
From :: jid:jid(), |
713 |
|
To :: jid:jid(), |
714 |
|
Acc :: mongoose_acc:t(), |
715 |
|
Packet :: exml:element(). |
716 |
|
route_message(From, To, Acc, Packet) -> |
717 |
2426 |
LUser = To#jid.luser, |
718 |
2426 |
LServer = To#jid.lserver, |
719 |
2426 |
PrioPid = get_user_present_pids(LUser, LServer), |
720 |
2426 |
case catch lists:max(PrioPid) of |
721 |
|
{Priority, _} when is_integer(Priority), Priority >= 0 -> |
722 |
1975 |
lists:foreach( |
723 |
|
%% Route messages to all priority that equals the max, if |
724 |
|
%% positive |
725 |
|
fun({Prio, Pid}) when Prio == Priority -> |
726 |
|
%% we will lose message if PID is not alive |
727 |
1986 |
mongoose_c2s:route(Pid, Acc); |
728 |
|
%% Ignore other priority: |
729 |
|
({_Prio, _Pid}) -> |
730 |
7 |
ok |
731 |
|
end, |
732 |
|
PrioPid), |
733 |
1975 |
Acc; |
734 |
|
_ -> |
735 |
451 |
MessageType = mongoose_acc:stanza_type(Acc), |
736 |
451 |
route_message_by_type(MessageType, From, To, Acc, Packet) |
737 |
|
end. |
738 |
|
|
739 |
|
route_message_by_type(<<"error">>, _From, _To, Acc, _Packet) -> |
740 |
25 |
Acc; |
741 |
|
route_message_by_type(<<"groupchat">>, From, To, Acc, Packet) -> |
742 |
166 |
mongoose_hooks:offline_groupchat_message_hook(Acc, From, To, Packet); |
743 |
|
route_message_by_type(<<"headline">>, From, To, Acc, Packet) -> |
744 |
1 |
{stop, Acc1} = bounce_offline_message(Acc, #{from => From, to => To, packet => Packet}, #{}), |
745 |
1 |
Acc1; |
746 |
|
route_message_by_type(_, From, To, Acc, Packet) -> |
747 |
259 |
HostType = mongoose_acc:host_type(Acc), |
748 |
259 |
case ejabberd_auth:does_user_exist(HostType, To, stored) of |
749 |
|
true -> |
750 |
203 |
case is_privacy_allow(From, To, Acc, Packet) of |
751 |
|
true -> |
752 |
191 |
mongoose_hooks:offline_message_hook(Acc, From, To, Packet); |
753 |
|
false -> |
754 |
12 |
mongoose_hooks:failed_to_store_message(Acc) |
755 |
|
end; |
756 |
|
_ -> |
757 |
56 |
E = mongoose_xmpp_errors:service_unavailable(<<"en">>, <<"User not found">>), |
758 |
56 |
{Acc1, Err} = jlib:make_error_reply(Acc, Packet, E), |
759 |
56 |
ejabberd_router:route(To, From, Acc1, Err) |
760 |
|
end. |
761 |
|
|
762 |
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
763 |
|
|
764 |
|
-spec clean_session_list([session()]) -> [session()]. |
765 |
|
clean_session_list(Ss) -> |
766 |
29745 |
clean_session_list(lists:keysort(#session.usr, Ss), []). |
767 |
|
|
768 |
|
|
769 |
|
-spec clean_session_list([session()], [session()]) -> [session()]. |
770 |
|
clean_session_list([], Res) -> |
771 |
1283 |
Res; |
772 |
|
clean_session_list([S], Res) -> |
773 |
28462 |
[S | Res]; |
774 |
|
clean_session_list([S1, S2 | Rest], Res) -> |
775 |
1032 |
case S1#session.usr == S2#session.usr of |
776 |
|
true -> |
777 |
:-( |
case S1#session.sid > S2#session.sid of |
778 |
:-( |
true -> clean_session_list([S1 | Rest], Res); |
779 |
:-( |
false -> clean_session_list([S2 | Rest], Res) |
780 |
|
end; |
781 |
|
false -> |
782 |
1032 |
clean_session_list([S2 | Rest], [S1 | Res]) |
783 |
|
end. |
784 |
|
|
785 |
|
|
786 |
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
787 |
|
|
788 |
|
-spec get_user_present_pids(LUser, LServer) -> [{priority(), pid()}] when |
789 |
|
LUser :: jid:luser(), |
790 |
|
LServer :: jid:lserver(). |
791 |
|
get_user_present_pids(LUser, LServer) -> |
792 |
3420 |
Ss = ejabberd_sm_backend:get_sessions(LUser, LServer), |
793 |
3420 |
[{S#session.priority, element(2, S#session.sid)} || |
794 |
3420 |
S <- clean_session_list(Ss), is_integer(S#session.priority)]. |
795 |
|
|
796 |
|
-spec get_user_present_resources_and_pids(jid:jid()) -> [{Resource :: binary(), pid()}]. |
797 |
|
get_user_present_resources_and_pids(#jid{luser = LUser, lserver = LServer}) -> |
798 |
19185 |
Ss = ejabberd_sm_backend:get_sessions(LUser, LServer), |
799 |
19185 |
[{Resource, Pid} || |
800 |
|
#session{usr = {_, _, Resource}, sid = {_, Pid}, priority = Prio} |
801 |
19185 |
<- clean_session_list(Ss), is_integer(Prio)]. |
802 |
|
|
803 |
|
-spec get_user_present_resources(jid:jid()) -> [{priority(), binary()}]. |
804 |
|
get_user_present_resources(#jid{luser = LUser, lserver = LServer}) -> |
805 |
1 |
Ss = ejabberd_sm_backend:get_sessions(LUser, LServer), |
806 |
1 |
[{S#session.priority, element(3, S#session.usr)} || |
807 |
1 |
S <- clean_session_list(Ss), is_integer(S#session.priority)]. |
808 |
|
|
809 |
|
-spec is_offline(jid:jid()) -> boolean(). |
810 |
|
is_offline(#jid{luser = LUser, lserver = LServer}) -> |
811 |
:-( |
case catch lists:max(get_user_present_pids(LUser, LServer)) of |
812 |
|
{Priority, _} when is_integer(Priority), Priority >= 0 -> |
813 |
:-( |
false; |
814 |
|
_ -> |
815 |
:-( |
true |
816 |
|
end. |
817 |
|
|
818 |
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
819 |
|
|
820 |
|
%% @doc On new session, check if some existing connections need to be replace |
821 |
|
-spec check_for_sessions_to_replace(HostType, JID) -> ReplacedPids when |
822 |
|
HostType :: mongooseim:host_type(), |
823 |
|
JID :: jid:jid(), |
824 |
|
ReplacedPids :: [pid()]. |
825 |
|
check_for_sessions_to_replace(HostType, JID) -> |
826 |
7807 |
#jid{luser = LUser, lserver = LServer, lresource = LResource} = JID, |
827 |
7807 |
Sessions = ejabberd_sm_backend:get_sessions(LUser, LServer), |
828 |
|
%% TODO: Depending on how this is executed, there could be an unneeded |
829 |
|
%% replacement for max_sessions. We need to check this at some point. |
830 |
7807 |
ReplacedRedundantSessions = check_existing_resources(LResource, Sessions), |
831 |
7807 |
AllReplacedSessionPids = check_max_sessions(HostType, LUser, LServer, ReplacedRedundantSessions, Sessions), |
832 |
7807 |
[mongoose_c2s:exit(Pid, <<"Replaced by new connection">>) || Pid <- AllReplacedSessionPids], |
833 |
7807 |
AllReplacedSessionPids. |
834 |
|
|
835 |
|
-spec check_existing_resources(LResource, Sessions) -> |
836 |
|
ReplacedSessionsPIDs when |
837 |
|
LResource :: jid:lresource(), |
838 |
|
Sessions :: [session()], |
839 |
|
ReplacedSessionsPIDs :: ordsets:ordset(pid()). |
840 |
|
check_existing_resources(LResource, Sessions) -> |
841 |
|
%% A connection exist with the same resource. We replace it: |
842 |
7807 |
case [S#session.sid || S = #session{usr = {_, _, R}} <- Sessions, R =:= LResource] of |
843 |
:-( |
[] -> []; |
844 |
7802 |
[_] -> []; |
845 |
|
SIDs -> |
846 |
5 |
MaxSID = lists:max(SIDs), |
847 |
5 |
ordsets:from_list([Pid || {_, Pid} = S <- SIDs, S /= MaxSID]) |
848 |
|
end. |
849 |
|
|
850 |
|
-spec check_max_sessions(HostType :: mongooseim:host_type(), |
851 |
|
LUser :: jid:luser(), |
852 |
|
LServer :: jid:lserver(), |
853 |
|
ReplacedPIDs :: [pid()], |
854 |
|
Sessions :: [session()]) -> |
855 |
|
AllReplacedPIDs :: ordsets:ordset(pid()). |
856 |
|
check_max_sessions(HostType, LUser, LServer, ReplacedPIDs, Sessions) -> |
857 |
|
%% If the max number of sessions for a given is reached, we replace the |
858 |
|
%% first one |
859 |
7807 |
SIDs = lists:filtermap( |
860 |
|
fun(Session) -> |
861 |
9014 |
{_, Pid} = SID = Session#session.sid, |
862 |
9014 |
case ordsets:is_element(Pid, ReplacedPIDs) of |
863 |
5 |
true -> false; |
864 |
9009 |
false -> {true, SID} |
865 |
|
end |
866 |
|
end, |
867 |
|
Sessions), |
868 |
7807 |
MaxSessions = get_max_user_sessions(HostType, LUser, LServer), |
869 |
7807 |
case length(SIDs) =< MaxSessions of |
870 |
7807 |
true -> ordsets:to_list(ReplacedPIDs); |
871 |
|
false -> |
872 |
:-( |
{_, Pid} = lists:min(SIDs), |
873 |
:-( |
[Pid | ordsets:to_list(ReplacedPIDs)] |
874 |
|
end. |
875 |
|
|
876 |
|
|
877 |
|
%% @doc Get the user_max_session setting |
878 |
|
%% This option defines the max number of time a given users are allowed to |
879 |
|
%% log in. Defaults to infinity |
880 |
|
-spec get_max_user_sessions(HostType, LUser, LServer) -> Result when |
881 |
|
HostType :: mongooseim:host_type(), |
882 |
|
LUser :: jid:luser(), |
883 |
|
LServer :: jid:lserver(), |
884 |
|
Result :: infinity | pos_integer(). |
885 |
|
get_max_user_sessions(HostType, LUser, LServer) -> |
886 |
7807 |
JID = jid:make_noprep(LUser, LServer, <<>>), |
887 |
7807 |
case acl:match_rule(HostType, LServer, max_user_sessions, JID) of |
888 |
7807 |
Max when is_integer(Max) -> Max; |
889 |
:-( |
infinity -> infinity; |
890 |
:-( |
_ -> ?MAX_USER_SESSIONS |
891 |
|
end. |
892 |
|
|
893 |
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
894 |
|
|
895 |
|
-spec process_iq(From, To, Acc, Packet) -> Acc when |
896 |
|
From :: jid:jid(), |
897 |
|
To :: jid:jid(), |
898 |
|
Acc :: mongoose_acc:t(), |
899 |
|
Packet :: exml:element(). |
900 |
|
process_iq(From, To, Acc0, Packet) -> |
901 |
3361 |
{IQ, Acc} = mongoose_iq:info(Acc0), |
902 |
3361 |
process_iq(IQ, From, To, Acc, Packet). |
903 |
|
|
904 |
|
process_iq(#iq{type = Type}, _From, _To, Acc, _Packet) when Type == result; Type == error -> |
905 |
|
% results and errors are always sent to full jids, so we ignore them here |
906 |
234 |
Acc; |
907 |
|
process_iq(#iq{xmlns = XMLNS} = IQ, From, To, Acc, Packet) -> |
908 |
3127 |
Host = To#jid.lserver, |
909 |
3127 |
case ets:lookup(sm_iqtable, {XMLNS, Host}) of |
910 |
|
[{_, IQHandler}] -> |
911 |
3122 |
gen_iq_component:handle(IQHandler, Acc, From, To, IQ); |
912 |
|
[] -> |
913 |
5 |
E = mongoose_xmpp_errors:service_unavailable(<<"en">>, <<"Unknown xmlns=", XMLNS/binary, " for host=", Host/binary>>), |
914 |
5 |
{Acc1, Err} = jlib:make_error_reply(Acc, Packet, E), |
915 |
5 |
ejabberd_router:route(To, From, Acc1, Err) |
916 |
|
end; |
917 |
|
process_iq(_, From, To, Acc, Packet) -> |
918 |
:-( |
{Acc1, Err} = jlib:make_error_reply(Acc, Packet, mongoose_xmpp_errors:bad_request()), |
919 |
:-( |
ejabberd_router:route(To, From, Acc1, Err). |
920 |
|
|
921 |
|
-spec user_resources(UserStr :: string(), ServerStr :: string()) -> [binary()]. |
922 |
|
user_resources(UserStr, ServerStr) -> |
923 |
:-( |
JID = jid:make_bare(list_to_binary(UserStr), list_to_binary(ServerStr)), |
924 |
:-( |
Resources = get_user_resources(JID), |
925 |
:-( |
lists:sort(Resources). |
926 |
|
|
927 |
|
-spec get_cached_unique_count() -> non_neg_integer(). |
928 |
|
get_cached_unique_count() -> |
929 |
:-( |
case mongoose_metrics:get_metric_value(global, ?UNIQUE_COUNT_CACHE) of |
930 |
|
{ok, DataPoints} -> |
931 |
:-( |
proplists:get_value(value, DataPoints); |
932 |
|
_ -> |
933 |
:-( |
0 |
934 |
|
end. |
935 |
|
|
936 |
|
%% It is used from big tests |
937 |
|
-spec sm_backend() -> backend(). |
938 |
|
sm_backend() -> |
939 |
5 |
mongoose_backend:get_backend_module(global, ?MODULE). |