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