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/4, open_session/5, |
39 |
|
close_session/4, |
40 |
|
store_info/3, |
41 |
|
get_info/2, |
42 |
|
remove_info/2, |
43 |
|
check_in_subscription/5, |
44 |
|
bounce_offline_message/4, |
45 |
|
disconnect_removed_user/3, |
46 |
|
get_user_resources/1, |
47 |
|
set_presence/6, |
48 |
|
unset_presence/5, |
49 |
|
close_session_unset_presence/5, |
50 |
|
get_unique_sessions_number/0, |
51 |
|
get_total_sessions_number/0, |
52 |
|
get_node_sessions_number/0, |
53 |
|
get_vh_session_number/1, |
54 |
|
get_vh_session_list/1, |
55 |
|
get_full_session_list/0, |
56 |
|
register_iq_handler/3, |
57 |
|
unregister_iq_handler/2, |
58 |
|
force_update_presence/2, |
59 |
|
user_resources/2, |
60 |
|
get_session_pid/1, |
61 |
|
get_session/1, |
62 |
|
get_session_ip/1, |
63 |
|
get_user_present_resources/1, |
64 |
|
get_raw_sessions/1, |
65 |
|
is_offline/1, |
66 |
|
get_user_present_pids/2, |
67 |
|
sync/0, |
68 |
|
run_session_cleanup_hook/1, |
69 |
|
sm_backend/0 |
70 |
|
]). |
71 |
|
|
72 |
|
%% Hook handlers |
73 |
|
-export([node_cleanup/2]). |
74 |
|
|
75 |
|
%% gen_server callbacks |
76 |
|
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, |
77 |
|
terminate/2, code_change/3]). |
78 |
|
|
79 |
|
%% xmpp_router callback |
80 |
|
-export([do_filter/3]). |
81 |
|
-export([do_route/4]). |
82 |
|
|
83 |
|
-ignore_xref([bounce_offline_message/4, check_in_subscription/5, disconnect_removed_user/3, |
84 |
|
do_filter/3, do_route/4, force_update_presence/2, get_unique_sessions_number/0, |
85 |
|
get_user_present_pids/2, node_cleanup/2, start_link/0, user_resources/2, |
86 |
|
sm_backend/0]). |
87 |
|
|
88 |
|
-include("mongoose.hrl"). |
89 |
|
-include("jlib.hrl"). |
90 |
|
-include("ejabberd_commands.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. |
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 |
|
]). |
119 |
|
|
120 |
|
%% default value for the maximum number of user connections |
121 |
|
-define(MAX_USER_SESSIONS, 100). |
122 |
|
-define(UNIQUE_COUNT_CACHE, [cache, unique_sessions_number]). |
123 |
|
|
124 |
|
%%==================================================================== |
125 |
|
%% API |
126 |
|
%%==================================================================== |
127 |
|
%%-------------------------------------------------------------------- |
128 |
|
%% Function: start_link() -> {ok, Pid} | ignore | {error, Error} |
129 |
|
%% Description: Starts the server |
130 |
|
%%-------------------------------------------------------------------- |
131 |
|
|
132 |
|
-spec start() -> {ok, pid()}. |
133 |
|
start() -> |
134 |
80 |
Spec = {?MODULE, {?MODULE, start_link, []}, permanent, brutal_kill, worker, [?MODULE]}, |
135 |
80 |
{ok, _} = ejabberd_sup:start_child(Spec). |
136 |
|
|
137 |
|
-spec start_link() -> 'ignore' | {'error', _} | {'ok', pid()}. |
138 |
|
start_link() -> |
139 |
80 |
mongoose_metrics:ensure_metric(global, ?UNIQUE_COUNT_CACHE, gauge), |
140 |
80 |
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() | ejabberd_c2s:broadcast(), |
150 |
|
Acc :: mongoose_acc:t(). |
151 |
|
route(From, To, #xmlel{} = Packet) -> |
152 |
:-( |
Acc = new_acc(From, To, Packet), |
153 |
:-( |
route(From, To, Acc); |
154 |
|
route(From, To, {broadcast, #xmlel{} = Payload}) -> |
155 |
:-( |
Acc = new_acc(From, To, Payload), |
156 |
:-( |
route(From, To, Acc, {broadcast, Payload}); |
157 |
|
route(From, To, {broadcast, Payload}) -> |
158 |
365 |
Acc = new_acc(To), |
159 |
365 |
route(From, To, Acc, {broadcast, Payload}); |
160 |
|
route(From, To, Acc) -> |
161 |
:-( |
route(From, To, Acc, mongoose_acc:element(Acc)). |
162 |
|
|
163 |
|
-spec new_acc(jid:jid(), jid:jid(), exml:element()) -> mongoose_acc:t(). |
164 |
|
new_acc(From, To = #jid{lserver = LServer}, Packet) -> |
165 |
:-( |
{ok, HostType} = mongoose_domain_api:get_domain_host_type(To#jid.lserver), |
166 |
:-( |
mongoose_acc:new(#{location => ?LOCATION, |
167 |
|
host_type => HostType, |
168 |
|
lserver => LServer, |
169 |
|
element => Packet, |
170 |
|
from_jid => From, |
171 |
|
to_jid => To}). |
172 |
|
|
173 |
|
-spec new_acc(jid:jid()) -> mongoose_acc:t(). |
174 |
|
new_acc(To = #jid{lserver = LServer}) -> |
175 |
365 |
{ok, HostType} = mongoose_domain_api:get_domain_host_type(To#jid.lserver), |
176 |
365 |
mongoose_acc:new(#{location => ?LOCATION, |
177 |
|
host_type => HostType, |
178 |
|
lserver => LServer, |
179 |
|
element => undefined}). |
180 |
|
|
181 |
|
route(From, To, Acc, {broadcast, Payload}) -> |
182 |
392 |
try |
183 |
392 |
do_route(Acc, From, To, {broadcast, Payload}) |
184 |
|
catch Class:Reason:Stacktrace -> |
185 |
:-( |
?LOG_ERROR(#{what => sm_route_failed, |
186 |
|
text => <<"Failed to route broadcast in ejabberd_sm">>, |
187 |
|
class => Class, reason => Reason, stacktrace => Stacktrace, |
188 |
:-( |
payload => Payload, acc => Acc}), |
189 |
:-( |
Acc |
190 |
|
end; |
191 |
|
route(From, To, Acc, El) -> |
192 |
14630 |
try |
193 |
14630 |
do_route(Acc, From, To, El) |
194 |
|
catch Class:Reason:Stacktrace -> |
195 |
:-( |
?LOG_ERROR(#{what => sm_route_failed, |
196 |
|
text => <<"Failed to route stanza in ejabberd_sm">>, |
197 |
|
class => Class, reason => Reason, stacktrace => Stacktrace, |
198 |
:-( |
acc => Acc}), |
199 |
:-( |
Acc |
200 |
|
end. |
201 |
|
|
202 |
|
-spec make_new_sid() -> ejabberd_sm:sid(). |
203 |
|
make_new_sid() -> |
204 |
2659 |
{erlang:system_time(microsecond), self()}. |
205 |
|
|
206 |
|
-spec open_session(HostType, SID, JID, Info) -> ReplacedPids when |
207 |
|
HostType :: binary(), |
208 |
|
SID :: 'undefined' | sid(), |
209 |
|
JID :: jid:jid(), |
210 |
|
Info :: info(), |
211 |
|
ReplacedPids :: [pid()]. |
212 |
|
open_session(HostType, SID, JID, Info) -> |
213 |
2645 |
open_session(HostType, SID, JID, undefined, Info). |
214 |
|
|
215 |
|
-spec open_session(HostType, SID, JID, Priority, Info) -> ReplacedPids when |
216 |
|
HostType :: binary(), |
217 |
|
SID :: 'undefined' | sid(), |
218 |
|
JID :: jid:jid(), |
219 |
|
Priority :: integer() | undefined, |
220 |
|
Info :: info(), |
221 |
|
ReplacedPids :: [pid()]. |
222 |
|
open_session(HostType, SID, JID, Priority, Info) -> |
223 |
2658 |
set_session(SID, JID, Priority, Info), |
224 |
2658 |
ReplacedPIDs = check_for_sessions_to_replace(HostType, JID), |
225 |
2658 |
mongoose_hooks:sm_register_connection_hook(HostType, SID, JID, Info), |
226 |
2658 |
ReplacedPIDs. |
227 |
|
|
228 |
|
-spec close_session(Acc, SID, JID, Reason) -> Acc1 when |
229 |
|
Acc :: mongoose_acc:t(), |
230 |
|
SID :: 'undefined' | sid(), |
231 |
|
JID :: jid:jid(), |
232 |
|
Reason :: close_reason(), |
233 |
|
Acc1 :: mongoose_acc:t(). |
234 |
|
close_session(Acc, SID, JID, Reason) -> |
235 |
2655 |
#jid{luser = LUser, lserver = LServer, lresource = LResource} = JID, |
236 |
2655 |
Info = case ejabberd_sm_backend:get_sessions(LUser, LServer, LResource) of |
237 |
|
[Session] -> |
238 |
2646 |
Session#session.info; |
239 |
|
_ -> |
240 |
9 |
[] |
241 |
|
end, |
242 |
2655 |
ejabberd_sm_backend:delete_session(SID, LUser, LServer, LResource), |
243 |
2655 |
mongoose_hooks:sm_remove_connection_hook(Acc, SID, JID, Info, Reason). |
244 |
|
|
245 |
|
-spec store_info(jid:jid(), info_key(), any()) -> |
246 |
|
{ok, any()} | {error, offline}. |
247 |
|
store_info(JID, Key, Value) -> |
248 |
224 |
case get_session(JID) of |
249 |
:-( |
offline -> {error, offline}; |
250 |
|
#session{sid = SID, priority = SPriority, info = SInfo} -> |
251 |
224 |
case SID of |
252 |
|
{_, Pid} when self() =:= Pid -> |
253 |
|
%% It's safe to allow process update its own record |
254 |
138 |
update_session(SID, JID, SPriority, |
255 |
|
maps:put(Key, Value, SInfo)), |
256 |
138 |
{ok, Key}; |
257 |
|
{_, Pid} -> |
258 |
|
%% Ask the process to update its record itself |
259 |
|
%% Async operation |
260 |
86 |
ejabberd_c2s:store_session_info(Pid, JID, Key, Value), |
261 |
86 |
{ok, Key} |
262 |
|
end |
263 |
|
end. |
264 |
|
|
265 |
|
-spec get_info(jid:jid(), info_key()) -> |
266 |
|
{ok, any()} | {error, offline | not_set}. |
267 |
|
get_info(JID, Key) -> |
268 |
10 |
case get_session(JID) of |
269 |
:-( |
offline -> {error, offline}; |
270 |
|
Session -> |
271 |
10 |
case mongoose_session:get_info(Session, Key, {error, not_set}) of |
272 |
10 |
{Key, Value} -> {ok, Value}; |
273 |
:-( |
Other -> Other |
274 |
|
end |
275 |
|
end. |
276 |
|
|
277 |
|
-spec remove_info(jid:jid(), info_key()) -> |
278 |
|
ok | {error, offline}. |
279 |
|
remove_info(JID, Key) -> |
280 |
2501 |
case get_session(JID) of |
281 |
2262 |
offline -> {error, offline}; |
282 |
|
#session{sid = SID, priority = SPriority, info = SInfo} -> |
283 |
239 |
case SID of |
284 |
|
{_, Pid} when self() =:= Pid -> |
285 |
|
%% It's safe to allow process update its own record |
286 |
220 |
update_session(SID, JID, SPriority, |
287 |
|
maps:remove(Key, SInfo)), |
288 |
220 |
ok; |
289 |
|
{_, Pid} -> |
290 |
|
%% Ask the process to update its record itself |
291 |
|
%% Async operation |
292 |
19 |
ejabberd_c2s:remove_session_info(Pid, JID, Key), |
293 |
19 |
ok |
294 |
|
end |
295 |
|
end. |
296 |
|
|
297 |
|
-spec check_in_subscription(Acc, ToJID, FromJID, Type, Reason) -> any() | {stop, false} when |
298 |
|
Acc :: any(), |
299 |
|
ToJID :: jid:jid(), |
300 |
|
FromJID :: jid:jid(), |
301 |
|
Type :: any(), |
302 |
|
Reason :: any(). |
303 |
|
check_in_subscription(Acc, ToJID, _FromJID, _Type, _Reason) -> |
304 |
207 |
case ejabberd_auth:does_user_exist(ToJID) of |
305 |
|
true -> |
306 |
178 |
Acc; |
307 |
|
false -> |
308 |
29 |
{stop, mongoose_acc:set(hook, result, false, Acc)} |
309 |
|
end. |
310 |
|
|
311 |
|
-spec bounce_offline_message(Acc, From, To, Packet) -> {stop, Acc} when |
312 |
|
Acc :: map(), |
313 |
|
From :: jid:jid(), |
314 |
|
To :: jid:jid(), |
315 |
|
Packet :: exml:element(). |
316 |
|
bounce_offline_message(Acc, From, To, Packet) -> |
317 |
30 |
Acc1 = mongoose_hooks:xmpp_bounce_message(Acc), |
318 |
30 |
E = mongoose_xmpp_errors:service_unavailable(<<"en">>, <<"Bounce offline message">>), |
319 |
30 |
{Acc2, Err} = jlib:make_error_reply(Acc1, Packet, E), |
320 |
30 |
Acc3 = ejabberd_router:route(To, From, Acc2, Err), |
321 |
30 |
{stop, Acc3}. |
322 |
|
|
323 |
|
-spec disconnect_removed_user(mongoose_acc:t(), User :: jid:user(), |
324 |
|
Server :: jid:server()) -> mongoose_acc:t(). |
325 |
|
disconnect_removed_user(Acc, User, Server) -> |
326 |
2236 |
lists:map(fun({_, Pid}) -> ejabberd_c2s:terminate_session(Pid, <<"User removed">>) end, |
327 |
|
get_user_present_pids(User, Server)), |
328 |
2236 |
Acc. |
329 |
|
|
330 |
|
|
331 |
|
-spec get_user_resources(JID :: jid:jid()) -> [binary()]. |
332 |
|
get_user_resources(JID) -> |
333 |
1071 |
#jid{luser = LUser, lserver = LServer} = JID, |
334 |
1071 |
Ss = ejabberd_sm_backend:get_sessions(LUser, LServer), |
335 |
1071 |
[element(3, S#session.usr) || S <- clean_session_list(Ss)]. |
336 |
|
|
337 |
|
|
338 |
|
-spec get_session_ip(JID) -> undefined | {inet:ip_address(), integer()} when |
339 |
|
JID :: jid:jid(). |
340 |
|
get_session_ip(JID) -> |
341 |
1 |
case get_session(JID) of |
342 |
:-( |
offline -> undefined; |
343 |
|
Session -> |
344 |
1 |
case mongoose_session:get_info(Session, ip, undefined) of |
345 |
1 |
{ip, Val} -> Val; |
346 |
:-( |
Other -> Other |
347 |
|
end |
348 |
|
end. |
349 |
|
|
350 |
|
-spec get_session(JID) -> offline | session() when |
351 |
|
JID :: jid:jid(). |
352 |
|
get_session(JID) -> |
353 |
14183 |
#jid{luser = LUser, lserver = LServer, lresource = LResource} = JID, |
354 |
14183 |
case ejabberd_sm_backend:get_sessions(LUser, LServer, LResource) of |
355 |
|
[] -> |
356 |
2838 |
offline; |
357 |
|
Ss -> |
358 |
11345 |
lists:max(Ss) |
359 |
|
end. |
360 |
|
|
361 |
|
-spec get_raw_sessions(jid:jid()) -> [session()]. |
362 |
|
get_raw_sessions(#jid{luser = LUser, lserver = LServer}) -> |
363 |
2982 |
clean_session_list( |
364 |
|
ejabberd_sm_backend:get_sessions(LUser, LServer)). |
365 |
|
|
366 |
|
-spec set_presence(Acc, SID, JID, Prio, Presence, Info) -> Acc1 when |
367 |
|
Acc :: mongoose_acc:t(), |
368 |
|
Acc1 :: mongoose_acc:t(), |
369 |
|
SID :: 'undefined' | sid(), |
370 |
|
JID :: jid:jid(), |
371 |
|
Prio :: 'undefined' | integer(), |
372 |
|
Presence :: any(), |
373 |
|
Info :: info(). |
374 |
|
set_presence(Acc, SID, JID, Priority, Presence, Info) -> |
375 |
2496 |
set_session(SID, JID, Priority, Info), |
376 |
2496 |
mongoose_hooks:set_presence_hook(Acc, JID, Presence). |
377 |
|
|
378 |
|
|
379 |
|
-spec unset_presence(Acc, SID, JID, Status, Info) -> Acc1 when |
380 |
|
Acc :: mongoose_acc:t(), |
381 |
|
Acc1 :: mongoose_acc:t(), |
382 |
|
SID :: 'undefined' | sid(), |
383 |
|
JID :: jid:jid(), |
384 |
|
Status :: binary(), |
385 |
|
Info :: info(). |
386 |
|
unset_presence(Acc, SID, JID, Status, Info) -> |
387 |
200 |
set_session(SID, JID, undefined, Info), |
388 |
200 |
mongoose_hooks:unset_presence_hook(Acc, JID, Status). |
389 |
|
|
390 |
|
|
391 |
|
-spec close_session_unset_presence(Acc, SID, JID, Status, Reason) -> Acc1 when |
392 |
|
Acc :: mongoose_acc:t(), |
393 |
|
SID :: 'undefined' | sid(), |
394 |
|
JID :: jid:jid(), |
395 |
|
Status :: binary(), |
396 |
|
Reason :: close_reason(), |
397 |
|
Acc1 :: mongoose_acc:t(). |
398 |
|
close_session_unset_presence(Acc, SID, JID, Status, Reason) -> |
399 |
2279 |
Acc1 = close_session(Acc, SID, JID, Reason), |
400 |
2279 |
mongoose_hooks:unset_presence_hook(Acc1, JID, Status). |
401 |
|
|
402 |
|
|
403 |
|
-spec get_session_pid(JID) -> none | pid() when |
404 |
|
JID :: jid:jid(). |
405 |
|
get_session_pid(JID) -> |
406 |
11365 |
case get_session(JID) of |
407 |
576 |
offline -> none; |
408 |
10789 |
#session{sid = {_, Pid}} -> Pid |
409 |
|
end. |
410 |
|
|
411 |
|
-spec get_unique_sessions_number() -> integer(). |
412 |
|
get_unique_sessions_number() -> |
413 |
94 |
try |
414 |
94 |
C = ejabberd_sm_backend:unique_count(), |
415 |
94 |
mongoose_metrics:update(global, ?UNIQUE_COUNT_CACHE, C), |
416 |
94 |
C |
417 |
|
catch |
418 |
|
_:_ -> |
419 |
:-( |
get_cached_unique_count() |
420 |
|
end. |
421 |
|
|
422 |
|
|
423 |
|
-spec get_total_sessions_number() -> integer(). |
424 |
|
get_total_sessions_number() -> |
425 |
96 |
ejabberd_sm_backend:total_count(). |
426 |
|
|
427 |
|
|
428 |
|
-spec get_vh_session_number(jid:server()) -> non_neg_integer(). |
429 |
|
get_vh_session_number(Server) -> |
430 |
1 |
length(ejabberd_sm_backend:get_sessions(Server)). |
431 |
|
|
432 |
|
|
433 |
|
-spec get_vh_session_list(jid:server()) -> [session()]. |
434 |
|
get_vh_session_list(Server) -> |
435 |
19 |
ejabberd_sm_backend:get_sessions(Server). |
436 |
|
|
437 |
|
|
438 |
|
-spec get_node_sessions_number() -> non_neg_integer(). |
439 |
|
get_node_sessions_number() -> |
440 |
93 |
{value, {active, Active}} = lists:keysearch(active, 1, |
441 |
|
supervisor:count_children(ejabberd_c2s_sup)), |
442 |
93 |
Active. |
443 |
|
|
444 |
|
|
445 |
|
-spec get_full_session_list() -> [session()]. |
446 |
|
get_full_session_list() -> |
447 |
84 |
ejabberd_sm_backend:get_sessions(). |
448 |
|
|
449 |
|
|
450 |
|
register_iq_handler(Host, XMLNS, IQHandler) -> |
451 |
250 |
ejabberd_sm ! {register_iq_handler, Host, XMLNS, IQHandler}, |
452 |
250 |
ok. |
453 |
|
|
454 |
|
-spec sync() -> ok. |
455 |
|
sync() -> |
456 |
230 |
gen_server:call(ejabberd_sm, sync). |
457 |
|
|
458 |
|
unregister_iq_handler(Host, XMLNS) -> |
459 |
202 |
ejabberd_sm ! {unregister_iq_handler, Host, XMLNS}, |
460 |
202 |
ok. |
461 |
|
|
462 |
|
-spec run_session_cleanup_hook(#session{}) -> mongoose_acc:t(). |
463 |
|
run_session_cleanup_hook(#session{usr = {U, S, R}, sid = SID}) -> |
464 |
:-( |
{ok, HostType} = mongoose_domain_api:get_domain_host_type(S), |
465 |
:-( |
Acc = mongoose_acc:new( |
466 |
|
#{location => ?LOCATION, |
467 |
|
host_type => HostType, |
468 |
|
lserver => S, |
469 |
|
element => undefined}), |
470 |
:-( |
mongoose_hooks:session_cleanup(S, Acc, U, R, SID). |
471 |
|
|
472 |
|
%%==================================================================== |
473 |
|
%% Hook handlers |
474 |
|
%%==================================================================== |
475 |
|
|
476 |
|
node_cleanup(Acc, Node) -> |
477 |
:-( |
Timeout = timer:minutes(1), |
478 |
:-( |
Res = gen_server:call(?MODULE, {node_cleanup, Node}, Timeout), |
479 |
:-( |
maps:put(?MODULE, Res, Acc). |
480 |
|
|
481 |
|
%%==================================================================== |
482 |
|
%% gen_server callbacks |
483 |
|
%%==================================================================== |
484 |
|
|
485 |
|
%%-------------------------------------------------------------------- |
486 |
|
%% Function: init(Args) -> {ok, State} | |
487 |
|
%% {ok, State, Timeout} | |
488 |
|
%% ignore | |
489 |
|
%% {stop, Reason} |
490 |
|
%% Description: Initiates the server |
491 |
|
%%-------------------------------------------------------------------- |
492 |
|
-spec init(_) -> {ok, state()}. |
493 |
|
init([]) -> |
494 |
80 |
{Backend, Opts} = mongoose_config:get_opt(sm_backend), |
495 |
80 |
ejabberd_sm_backend:init([{backend, Backend}|Opts]), |
496 |
|
|
497 |
80 |
ets:new(sm_iqtable, [named_table, protected, {read_concurrency, true}]), |
498 |
80 |
ejabberd_hooks:add(node_cleanup, global, ?MODULE, node_cleanup, 50), |
499 |
80 |
lists:foreach(fun(HostType) -> ejabberd_hooks:add(hooks(HostType)) end, |
500 |
|
?ALL_HOST_TYPES), |
501 |
80 |
ejabberd_commands:register_commands(commands()), |
502 |
80 |
{ok, #state{}}. |
503 |
|
|
504 |
|
hooks(HostType) -> |
505 |
409 |
[ |
506 |
|
{roster_in_subscription, HostType, ejabberd_sm, check_in_subscription, 20}, |
507 |
|
{offline_message_hook, HostType, ejabberd_sm, bounce_offline_message, 100}, |
508 |
|
{offline_groupchat_message_hook, HostType, ejabberd_sm, bounce_offline_message, 100}, |
509 |
|
{remove_user, HostType, ejabberd_sm, disconnect_removed_user, 100} |
510 |
|
]. |
511 |
|
|
512 |
|
%%-------------------------------------------------------------------- |
513 |
|
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} | |
514 |
|
%% {reply, Reply, State, Timeout} | |
515 |
|
%% {noreply, State} | |
516 |
|
%% {noreply, State, Timeout} | |
517 |
|
%% {stop, Reason, Reply, State} | |
518 |
|
%% {stop, Reason, State} |
519 |
|
%% Description: Handling call messages |
520 |
|
%%-------------------------------------------------------------------- |
521 |
|
handle_call({node_cleanup, Node}, _From, State) -> |
522 |
:-( |
{TimeDiff, _R} = timer:tc(fun ejabberd_sm_backend:cleanup/1, [Node]), |
523 |
:-( |
?LOG_INFO(#{what => sm_node_cleanup, |
524 |
|
text => <<"Cleaning after a node that went down">>, |
525 |
|
cleanup_node => Node, |
526 |
:-( |
duration => erlang:round(TimeDiff / 1000)}), |
527 |
:-( |
{reply, ok, State}; |
528 |
|
handle_call(sync, _From, State) -> |
529 |
230 |
{reply, ok, State}; |
530 |
|
handle_call(_Request, _From, State) -> |
531 |
:-( |
Reply = ok, |
532 |
:-( |
{reply, Reply, State}. |
533 |
|
|
534 |
|
%%-------------------------------------------------------------------- |
535 |
|
%% Function: handle_cast(Msg, State) -> {noreply, State} | |
536 |
|
%% {noreply, State, Timeout} | |
537 |
|
%% {stop, Reason, State} |
538 |
|
%% Description: Handling cast messages |
539 |
|
%%-------------------------------------------------------------------- |
540 |
|
handle_cast(_Msg, State) -> |
541 |
:-( |
{noreply, State}. |
542 |
|
|
543 |
|
%%-------------------------------------------------------------------- |
544 |
|
%% Function: handle_info(Info, State) -> {noreply, State} | |
545 |
|
%% {noreply, State, Timeout} | |
546 |
|
%% {stop, Reason, State} |
547 |
|
%% Description: Handling all non call/cast messages |
548 |
|
%%-------------------------------------------------------------------- |
549 |
|
-spec handle_info(_, _) -> {'noreply', _}. |
550 |
|
handle_info({route, From, To, Packet}, State) -> |
551 |
:-( |
route(From, To, Packet), |
552 |
:-( |
{noreply, State}; |
553 |
|
handle_info({register_iq_handler, Host, XMLNS, IQHandler}, State) -> |
554 |
250 |
case ets:insert_new(sm_iqtable, {{XMLNS, Host}, IQHandler}) of |
555 |
250 |
true -> ok; |
556 |
|
false -> |
557 |
:-( |
?LOG_WARNING(#{what => register_iq_handler_duplicate, |
558 |
:-( |
xmlns => XMLNS, host => Host}) |
559 |
|
end, |
560 |
250 |
{noreply, State}; |
561 |
|
handle_info({unregister_iq_handler, Host, XMLNS}, State) -> |
562 |
202 |
case ets:lookup(sm_iqtable, {XMLNS, Host}) of |
563 |
|
[{_, IQHandler}] -> |
564 |
202 |
gen_iq_component:stop_iq_handler(IQHandler), |
565 |
202 |
ets:delete(sm_iqtable, {XMLNS, Host}); |
566 |
|
_ -> |
567 |
:-( |
?LOG_WARNING(#{what => unregister_iq_handler_missing, |
568 |
:-( |
xmlns => XMLNS, host => Host}) |
569 |
|
end, |
570 |
202 |
{noreply, State}; |
571 |
|
handle_info(_Info, State) -> |
572 |
:-( |
{noreply, State}. |
573 |
|
|
574 |
|
%%-------------------------------------------------------------------- |
575 |
|
%% Function: terminate(Reason, State) -> void() |
576 |
|
%% Description: This function is called by a gen_server when it is about to |
577 |
|
%% terminate. It should be the opposite of Module:init/1 and do any necessary |
578 |
|
%% cleaning up. When it returns, the gen_server terminates with Reason. |
579 |
|
%% The return value is ignored. |
580 |
|
%%-------------------------------------------------------------------- |
581 |
|
-spec terminate(_, state()) -> 'ok'. |
582 |
|
terminate(_Reason, _State) -> |
583 |
:-( |
try |
584 |
:-( |
ejabberd_commands:unregister_commands(commands()) |
585 |
|
catch Class:Reason:Stacktrace -> |
586 |
:-( |
?LOG_ERROR(#{what => sm_terminate_failed, |
587 |
|
text => <<"Caught error while terminating SM">>, |
588 |
:-( |
class => Class, reason => Reason, stacktrace => Stacktrace}) |
589 |
|
end, |
590 |
:-( |
ok. |
591 |
|
|
592 |
|
%%-------------------------------------------------------------------- |
593 |
|
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState} |
594 |
|
%% Description: Convert process state when code is changed |
595 |
|
%%-------------------------------------------------------------------- |
596 |
|
code_change(_OldVsn, State, _Extra) -> |
597 |
:-( |
{ok, State}. |
598 |
|
|
599 |
|
%%-------------------------------------------------------------------- |
600 |
|
%%% Internal functions |
601 |
|
%%-------------------------------------------------------------------- |
602 |
|
|
603 |
|
-spec set_session(SID, JID, Prio, Info) -> ok | {error, any()} when |
604 |
|
SID :: sid() | 'undefined', |
605 |
|
JID :: jid:jid(), |
606 |
|
Prio :: priority(), |
607 |
|
Info :: info(). |
608 |
|
set_session(SID, JID, Priority, Info) -> |
609 |
5354 |
#jid{luser = LUser, lserver = LServer, lresource = LResource} = JID, |
610 |
5354 |
US = {LUser, LServer}, |
611 |
5354 |
USR = {LUser, LServer, LResource}, |
612 |
5354 |
Session = #session{sid = SID, |
613 |
|
usr = USR, |
614 |
|
us = US, |
615 |
|
priority = Priority, |
616 |
|
info = Info}, |
617 |
5354 |
ejabberd_sm_backend:create_session(LUser, LServer, LResource, Session). |
618 |
|
|
619 |
|
-spec update_session(SID, JID, Prio, Info) -> ok | {error, any()} when |
620 |
|
SID :: sid() | 'undefined', |
621 |
|
JID :: jid:jid(), |
622 |
|
Prio :: priority(), |
623 |
|
Info :: info(). |
624 |
|
update_session(SID, JID, Priority, Info) -> |
625 |
358 |
#jid{luser = LUser, lserver = LServer, lresource = LResource} = JID, |
626 |
358 |
US = {LUser, LServer}, |
627 |
358 |
USR = {LUser, LServer, LResource}, |
628 |
358 |
Session = #session{sid = SID, |
629 |
|
usr = USR, |
630 |
|
us = US, |
631 |
|
priority = Priority, |
632 |
|
info = Info}, |
633 |
358 |
ejabberd_sm_backend:update_session(LUser, LServer, LResource, Session). |
634 |
|
|
635 |
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
636 |
|
|
637 |
|
do_filter(From, To, Packet) -> |
638 |
:-( |
{From, To, Packet}. |
639 |
|
|
640 |
|
-spec do_route(Acc, From, To, Payload) -> Acc when |
641 |
|
Acc :: mongoose_acc:t(), |
642 |
|
From :: jid:jid(), |
643 |
|
To :: jid:jid(), |
644 |
|
Payload :: exml:element() | ejabberd_c2s:broadcast(). |
645 |
|
do_route(Acc, From, To, {broadcast, Payload} = Broadcast) -> |
646 |
392 |
?LOG_DEBUG(#{what => sm_route_broadcast, acc => Acc, payload => Payload}), |
647 |
392 |
#jid{ luser = LUser, lserver = LServer, lresource = LResource} = To, |
648 |
392 |
case LResource of |
649 |
|
<<>> -> |
650 |
327 |
CurrentPids = get_user_present_pids(LUser, LServer), |
651 |
327 |
Acc1 = mongoose_hooks:sm_broadcast(Acc, From, To, Broadcast, length(CurrentPids)), |
652 |
327 |
?LOG_DEBUG(#{what => sm_broadcast, session_pids => CurrentPids}), |
653 |
327 |
BCast = {broadcast, Payload}, |
654 |
327 |
lists:foreach(fun({_, Pid}) -> Pid ! BCast end, CurrentPids), |
655 |
327 |
Acc1; |
656 |
|
_ -> |
657 |
65 |
case get_session_pid(To) of |
658 |
|
none -> |
659 |
1 |
Acc; % do nothing |
660 |
|
Pid when is_pid(Pid) -> |
661 |
64 |
?LOG_DEBUG(#{what => sm_broadcast, session_pid => Pid}), |
662 |
64 |
Pid ! Broadcast, |
663 |
64 |
Acc |
664 |
|
end |
665 |
|
end; |
666 |
|
do_route(Acc, From, To, El) -> |
667 |
20391 |
?LOG_DEBUG(#{what => sm_route, acc => Acc}), |
668 |
20391 |
#jid{lresource = LResource} = To, |
669 |
20391 |
#xmlel{name = Name} = El, |
670 |
20391 |
case LResource of |
671 |
|
<<>> -> |
672 |
9208 |
do_route_no_resource(Name, From, To, Acc, El); |
673 |
|
_ -> |
674 |
11183 |
case get_session_pid(To) of |
675 |
|
none -> |
676 |
571 |
do_route_offline(Name, mongoose_acc:stanza_type(Acc), |
677 |
|
From, To, Acc, El); |
678 |
|
Pid when is_pid(Pid) -> |
679 |
10612 |
?LOG_DEBUG(#{what => sm_route_to_pid, |
680 |
10612 |
session_pid => Pid, acc => Acc}), |
681 |
10612 |
Pid ! {route, From, To, Acc}, |
682 |
10612 |
Acc |
683 |
|
end |
684 |
|
end. |
685 |
|
|
686 |
|
-spec do_route_no_resource_presence_prv(From, To, Acc, Packet, Type, Reason) -> boolean() when |
687 |
|
From :: jid:jid(), |
688 |
|
To :: jid:jid(), |
689 |
|
Acc :: mongoose_acc:t(), |
690 |
|
Packet :: exml:element(), |
691 |
|
Type :: 'subscribe' | 'subscribed' | 'unsubscribe' | 'unsubscribed', |
692 |
|
Reason :: any(). |
693 |
|
do_route_no_resource_presence_prv(From, To, Acc, Packet, Type, Reason) -> |
694 |
207 |
case is_privacy_allow(From, To, Acc, Packet) of |
695 |
|
true -> |
696 |
207 |
Res = mongoose_hooks:roster_in_subscription(Acc, To, From, Type, Reason), |
697 |
207 |
mongoose_acc:get(hook, result, false, Res); |
698 |
|
false -> |
699 |
:-( |
false |
700 |
|
end. |
701 |
|
|
702 |
|
-spec do_route_no_resource_presence(Type, From, To, Acc, Packet) -> boolean() when |
703 |
|
Type :: binary(), |
704 |
|
From :: jid:jid(), |
705 |
|
To :: jid:jid(), |
706 |
|
Acc :: mongoose_acc:t(), |
707 |
|
Packet :: exml:element(). |
708 |
|
do_route_no_resource_presence(<<"subscribe">>, From, To, Acc, Packet) -> |
709 |
51 |
Reason = xml:get_path_s(Packet, [{elem, <<"status">>}, cdata]), |
710 |
51 |
do_route_no_resource_presence_prv(From, To, Acc, Packet, subscribe, Reason); |
711 |
|
do_route_no_resource_presence(<<"subscribed">>, From, To, Acc, Packet) -> |
712 |
45 |
do_route_no_resource_presence_prv(From, To, Acc, Packet, subscribed, <<>>); |
713 |
|
do_route_no_resource_presence(<<"unsubscribe">>, From, To, Acc, Packet) -> |
714 |
42 |
do_route_no_resource_presence_prv(From, To, Acc, Packet, unsubscribe, <<>>); |
715 |
|
do_route_no_resource_presence(<<"unsubscribed">>, From, To, Acc, Packet) -> |
716 |
69 |
do_route_no_resource_presence_prv(From, To, Acc, Packet, unsubscribed, <<>>); |
717 |
|
do_route_no_resource_presence(_, _, _, _, _) -> |
718 |
7660 |
true. |
719 |
|
|
720 |
|
|
721 |
|
-spec do_route_no_resource(Name, From, To, Acc, El) -> Acc when |
722 |
|
Name :: undefined | binary(), |
723 |
|
From :: jid:jid(), |
724 |
|
To :: jid:jid(), |
725 |
|
Acc :: mongoose_acc:t(), |
726 |
|
El :: exml:element(). |
727 |
|
do_route_no_resource(<<"presence">>, From, To, Acc, El) -> |
728 |
7867 |
Type = mongoose_acc:stanza_type(Acc), |
729 |
7867 |
case do_route_no_resource_presence(Type, From, To, Acc, El) of |
730 |
|
true -> |
731 |
7823 |
PResources = get_user_present_resources(To), |
732 |
7823 |
lists:foldl(fun({_, R}, A) -> |
733 |
5761 |
do_route(A, From, jid:replace_resource(To, R), El) |
734 |
|
end, |
735 |
|
Acc, |
736 |
|
PResources); |
737 |
|
false -> |
738 |
44 |
Acc |
739 |
|
end; |
740 |
|
do_route_no_resource(<<"message">>, From, To, Acc, El) -> |
741 |
734 |
route_message(From, To, Acc, El); |
742 |
|
do_route_no_resource(<<"iq">>, From, To, Acc, El) -> |
743 |
607 |
process_iq(From, To, Acc, El); |
744 |
|
do_route_no_resource(_, _, _, Acc, _) -> |
745 |
:-( |
Acc. |
746 |
|
|
747 |
|
-spec do_route_offline(Name, Type, From, To, Acc, Packet) -> mongoose_acc:t() when |
748 |
|
Name :: 'undefined' | binary(), |
749 |
|
Type :: binary(), |
750 |
|
From :: jid:jid(), |
751 |
|
To :: jid:jid(), |
752 |
|
Acc :: mongoose_acc:t(), |
753 |
|
Packet :: exml:element(). |
754 |
|
do_route_offline(<<"message">>, _, From, To, Acc, Packet) -> |
755 |
303 |
HostType = mongoose_acc:host_type(Acc), |
756 |
303 |
Drop = mongoose_hooks:sm_filter_offline_message(HostType, From, To, Packet), |
757 |
303 |
case Drop of |
758 |
|
false -> |
759 |
303 |
route_message(From, To, Acc, Packet); |
760 |
|
true -> |
761 |
:-( |
?LOG_DEBUG(#{what => sm_offline_dropped, acc => Acc}), |
762 |
:-( |
Acc |
763 |
|
end; |
764 |
|
do_route_offline(<<"iq">>, <<"error">>, _From, _To, Acc, _Packet) -> |
765 |
:-( |
Acc; |
766 |
|
do_route_offline(<<"iq">>, <<"result">>, _From, _To, Acc, _Packet) -> |
767 |
17 |
Acc; |
768 |
|
do_route_offline(<<"iq">>, _, From, To, Acc, Packet) -> |
769 |
3 |
E = mongoose_xmpp_errors:service_unavailable(<<"en">>, <<"Route offline">>), |
770 |
3 |
{Acc1, Err} = jlib:make_error_reply(Acc, Packet, E), |
771 |
3 |
ejabberd_router:route(To, From, Acc1, Err); |
772 |
|
do_route_offline(_, _, _, _, Acc, _) -> |
773 |
248 |
?LOG_DEBUG(#{what => sm_packet_dropped, acc => Acc}), |
774 |
248 |
Acc. |
775 |
|
|
776 |
|
|
777 |
|
%% @doc The default list applies to the user as a whole, |
778 |
|
%% and is processed if there is no active list set |
779 |
|
%% for the target session/resource to which a stanza is addressed, |
780 |
|
%% or if there are no current sessions for the user. |
781 |
|
-spec is_privacy_allow(From, To, Acc, Packet) -> boolean() when |
782 |
|
From :: jid:jid(), |
783 |
|
To :: jid:jid(), |
784 |
|
Acc :: mongoose_acc:t(), |
785 |
|
Packet :: exml:element() | mongoose_acc:t(). |
786 |
|
is_privacy_allow(From, To, Acc, Packet) -> |
787 |
439 |
HostType = mongoose_acc:host_type(Acc), |
788 |
439 |
PrivacyList = mongoose_hooks:privacy_get_user_list(HostType, To), |
789 |
439 |
is_privacy_allow(From, To, Acc, Packet, PrivacyList). |
790 |
|
|
791 |
|
|
792 |
|
%% @doc Check if privacy rules allow this delivery |
793 |
|
%% Function copied from ejabberd_c2s.erl |
794 |
|
-spec is_privacy_allow(From, To, Acc, Packet, PrivacyList) -> boolean() when |
795 |
|
From :: jid:jid(), |
796 |
|
To :: jid:jid(), |
797 |
|
Acc :: mongoose_acc:t(), |
798 |
|
Packet :: exml:element(), |
799 |
|
PrivacyList :: mongoose_privacy:userlist(). |
800 |
|
is_privacy_allow(_From, To, Acc, _Packet, PrivacyList) -> |
801 |
439 |
{_, Res} = mongoose_privacy:privacy_check_packet(Acc, To, PrivacyList, To, in), |
802 |
439 |
allow == Res. |
803 |
|
|
804 |
|
|
805 |
|
-spec route_message(From, To, Acc, Packet) -> Acc when |
806 |
|
From :: jid:jid(), |
807 |
|
To :: jid:jid(), |
808 |
|
Acc :: mongoose_acc:t(), |
809 |
|
Packet :: exml:element(). |
810 |
|
route_message(From, To, Acc, Packet) -> |
811 |
1037 |
LUser = To#jid.luser, |
812 |
1037 |
LServer = To#jid.lserver, |
813 |
1037 |
PrioPid = get_user_present_pids(LUser, LServer), |
814 |
1037 |
case catch lists:max(PrioPid) of |
815 |
|
{Priority, _} when is_integer(Priority), Priority >= 0 -> |
816 |
572 |
lists:foreach( |
817 |
|
%% Route messages to all priority that equals the max, if |
818 |
|
%% positive |
819 |
|
fun({Prio, Pid}) when Prio == Priority -> |
820 |
|
%% we will lose message if PID is not alive |
821 |
588 |
Pid ! {route, From, To, Acc}; |
822 |
|
%% Ignore other priority: |
823 |
|
({_Prio, _Pid}) -> |
824 |
1 |
ok |
825 |
|
end, |
826 |
|
PrioPid), |
827 |
572 |
Acc; |
828 |
|
_ -> |
829 |
465 |
MessageType = mongoose_acc:stanza_type(Acc), |
830 |
465 |
route_message_by_type(MessageType, From, To, Acc, Packet) |
831 |
|
end. |
832 |
|
|
833 |
|
route_message_by_type(<<"error">>, _From, _To, Acc, _Packet) -> |
834 |
32 |
Acc; |
835 |
|
route_message_by_type(<<"groupchat">>, From, To, Acc, Packet) -> |
836 |
157 |
mongoose_hooks:offline_groupchat_message_hook(Acc, From, To, Packet); |
837 |
|
route_message_by_type(<<"headline">>, From, To, Acc, Packet) -> |
838 |
2 |
{stop, Acc1} = bounce_offline_message(Acc, From, To, Packet), |
839 |
2 |
Acc1; |
840 |
|
route_message_by_type(_, From, To, Acc, Packet) -> |
841 |
274 |
HostType = mongoose_acc:host_type(Acc), |
842 |
274 |
case ejabberd_auth:does_user_exist(HostType, To, stored) of |
843 |
|
true -> |
844 |
232 |
case is_privacy_allow(From, To, Acc, Packet) of |
845 |
|
true -> |
846 |
220 |
mongoose_hooks:offline_message_hook(Acc, From, To, Packet); |
847 |
|
false -> |
848 |
12 |
mongoose_hooks:failed_to_store_message(Acc) |
849 |
|
end; |
850 |
|
_ -> |
851 |
42 |
E = mongoose_xmpp_errors:service_unavailable(<<"en">>, <<"User not found">>), |
852 |
42 |
{Acc1, Err} = jlib:make_error_reply(Acc, Packet, E), |
853 |
42 |
ejabberd_router:route(To, From, Acc1, Err) |
854 |
|
end. |
855 |
|
|
856 |
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
857 |
|
|
858 |
|
-spec clean_session_list([session()]) -> [session()]. |
859 |
|
clean_session_list(Ss) -> |
860 |
15936 |
clean_session_list(lists:keysort(#session.usr, Ss), []). |
861 |
|
|
862 |
|
|
863 |
|
-spec clean_session_list([session()], [session()]) -> [session()]. |
864 |
|
clean_session_list([], Res) -> |
865 |
5514 |
Res; |
866 |
|
clean_session_list([S], Res) -> |
867 |
10422 |
[S | Res]; |
868 |
|
clean_session_list([S1, S2 | Rest], Res) -> |
869 |
886 |
case S1#session.usr == S2#session.usr of |
870 |
|
true -> |
871 |
9 |
case S1#session.sid > S2#session.sid of |
872 |
:-( |
true -> clean_session_list([S1 | Rest], Res); |
873 |
9 |
false -> clean_session_list([S2 | Rest], Res) |
874 |
|
end; |
875 |
|
false -> |
876 |
877 |
clean_session_list([S2 | Rest], [S1 | Res]) |
877 |
|
end. |
878 |
|
|
879 |
|
|
880 |
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
881 |
|
|
882 |
|
-spec get_user_present_pids(LUser, LServer) -> [{priority(), pid()}] when |
883 |
|
LUser :: jid:luser(), |
884 |
|
LServer :: jid:lserver(). |
885 |
|
get_user_present_pids(LUser, LServer) -> |
886 |
3752 |
Ss = ejabberd_sm_backend:get_sessions(LUser, LServer), |
887 |
3752 |
[{S#session.priority, element(2, S#session.sid)} || |
888 |
3752 |
S <- clean_session_list(Ss), is_integer(S#session.priority)]. |
889 |
|
|
890 |
|
-spec get_user_present_resources(jid:jid()) -> [{priority(), binary()}]. |
891 |
|
get_user_present_resources(#jid{luser = LUser, lserver = LServer}) -> |
892 |
8131 |
Ss = ejabberd_sm_backend:get_sessions(LUser, LServer), |
893 |
8131 |
[{S#session.priority, element(3, S#session.usr)} || |
894 |
8131 |
S <- clean_session_list(Ss), is_integer(S#session.priority)]. |
895 |
|
|
896 |
|
-spec is_offline(jid:jid()) -> boolean(). |
897 |
|
is_offline(#jid{luser = LUser, lserver = LServer}) -> |
898 |
152 |
case catch lists:max(get_user_present_pids(LUser, LServer)) of |
899 |
|
{Priority, _} when is_integer(Priority), Priority >= 0 -> |
900 |
63 |
false; |
901 |
|
_ -> |
902 |
89 |
true |
903 |
|
end. |
904 |
|
|
905 |
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
906 |
|
|
907 |
|
%% @doc On new session, check if some existing connections need to be replace |
908 |
|
-spec check_for_sessions_to_replace(HostType, JID) -> ReplacedPids when |
909 |
|
HostType :: mongooseim:host_type(), |
910 |
|
JID :: jid:jid(), |
911 |
|
ReplacedPids :: [pid()]. |
912 |
|
check_for_sessions_to_replace(HostType, JID) -> |
913 |
2658 |
#jid{luser = LUser, lserver = LServer, lresource = LResource} = JID, |
914 |
|
%% TODO: Depending on how this is executed, there could be an unneeded |
915 |
|
%% replacement for max_sessions. We need to check this at some point. |
916 |
2658 |
ReplacedRedundantSessions = check_existing_resources(HostType, LUser, LServer, LResource), |
917 |
2658 |
AllReplacedSessionPids = check_max_sessions(HostType, LUser, LServer, ReplacedRedundantSessions), |
918 |
2658 |
[Pid ! replaced || Pid <- AllReplacedSessionPids], |
919 |
2658 |
AllReplacedSessionPids. |
920 |
|
|
921 |
|
-spec check_existing_resources(HostType, LUser, LServer, LResource) -> |
922 |
|
ReplacedSessionsPIDs when |
923 |
|
HostType :: mongooseim:host_type(), |
924 |
|
LUser :: jid:luser(), |
925 |
|
LServer :: jid:lserver(), |
926 |
|
LResource :: jid:lresource(), |
927 |
|
ReplacedSessionsPIDs :: ordsets:ordset(pid()). |
928 |
|
check_existing_resources(_HostType, LUser, LServer, LResource) -> |
929 |
|
%% A connection exist with the same resource. We replace it: |
930 |
2658 |
Sessions = ejabberd_sm_backend:get_sessions(LUser, LServer, LResource), |
931 |
2658 |
case [S#session.sid || S <- Sessions] of |
932 |
:-( |
[] -> []; |
933 |
|
SIDs -> |
934 |
2658 |
MaxSID = lists:max(SIDs), |
935 |
2658 |
ordsets:from_list([Pid || {_, Pid} = S <- SIDs, S /= MaxSID]) |
936 |
|
end. |
937 |
|
|
938 |
|
-spec check_max_sessions(HostType :: mongooseim:host_type(), |
939 |
|
LUser :: jid:luser(), |
940 |
|
LServer :: jid:lserver(), |
941 |
|
ReplacedPIDs :: [pid()]) -> |
942 |
|
AllReplacedPIDs :: ordsets:ordset(pid()). |
943 |
|
check_max_sessions(HostType, LUser, LServer, ReplacedPIDs) -> |
944 |
|
%% If the max number of sessions for a given is reached, we replace the |
945 |
|
%% first one |
946 |
2658 |
SIDs = lists:filtermap( |
947 |
|
fun(Session) -> |
948 |
2839 |
{_, Pid} = SID = Session#session.sid, |
949 |
2839 |
case ordsets:is_element(Pid, ReplacedPIDs) of |
950 |
9 |
true -> false; |
951 |
2830 |
false -> {true, SID} |
952 |
|
end |
953 |
|
end, |
954 |
|
ejabberd_sm_backend:get_sessions(LUser, LServer)), |
955 |
2658 |
MaxSessions = get_max_user_sessions(HostType, LUser, LServer), |
956 |
2658 |
case length(SIDs) =< MaxSessions of |
957 |
2658 |
true -> ordsets:to_list(ReplacedPIDs); |
958 |
|
false -> |
959 |
:-( |
{_, Pid} = lists:min(SIDs), |
960 |
:-( |
[Pid | ordsets:to_list(ReplacedPIDs)] |
961 |
|
end. |
962 |
|
|
963 |
|
|
964 |
|
%% @doc Get the user_max_session setting |
965 |
|
%% This option defines the max number of time a given users are allowed to |
966 |
|
%% log in. Defaults to infinity |
967 |
|
-spec get_max_user_sessions(HostType, LUser, LServer) -> Result when |
968 |
|
HostType :: mongooseim:host_type(), |
969 |
|
LUser :: jid:luser(), |
970 |
|
LServer :: jid:lserver(), |
971 |
|
Result :: infinity | pos_integer(). |
972 |
|
get_max_user_sessions(HostType, LUser, LServer) -> |
973 |
2658 |
JID = jid:make_noprep(LUser, LServer, <<>>), |
974 |
2658 |
case acl:match_rule(HostType, LServer, max_user_sessions, JID) of |
975 |
2658 |
Max when is_integer(Max) -> Max; |
976 |
:-( |
infinity -> infinity; |
977 |
:-( |
_ -> ?MAX_USER_SESSIONS |
978 |
|
end. |
979 |
|
|
980 |
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
981 |
|
|
982 |
|
-spec process_iq(From, To, Acc, Packet) -> Acc when |
983 |
|
From :: jid:jid(), |
984 |
|
To :: jid:jid(), |
985 |
|
Acc :: mongoose_acc:t(), |
986 |
|
Packet :: exml:element(). |
987 |
|
process_iq(From, To, Acc0, Packet) -> |
988 |
607 |
{IQ, Acc} = mongoose_iq:info(Acc0), |
989 |
607 |
process_iq(IQ, From, To, Acc, Packet). |
990 |
|
|
991 |
|
process_iq(#iq{type = Type}, _From, _To, Acc, _Packet) when Type == result; Type == error -> |
992 |
|
% results and errors are always sent to full jids, so we ignore them here |
993 |
171 |
Acc; |
994 |
|
process_iq(#iq{xmlns = XMLNS} = IQ, From, To, Acc, Packet) -> |
995 |
430 |
Host = To#jid.lserver, |
996 |
430 |
case ets:lookup(sm_iqtable, {XMLNS, Host}) of |
997 |
|
[{_, IQHandler}] -> |
998 |
425 |
gen_iq_component:handle(IQHandler, Acc, From, To, IQ); |
999 |
|
[] -> |
1000 |
5 |
E = mongoose_xmpp_errors:service_unavailable(<<"en">>, <<"Unknown xmlns=", XMLNS/binary, " for host=", Host/binary>>), |
1001 |
5 |
{Acc1, Err} = jlib:make_error_reply(Acc, Packet, E), |
1002 |
5 |
ejabberd_router:route(To, From, Acc1, Err) |
1003 |
|
end; |
1004 |
|
process_iq(_, From, To, Acc, Packet) -> |
1005 |
6 |
{Acc1, Err} = jlib:make_error_reply(Acc, Packet, mongoose_xmpp_errors:bad_request()), |
1006 |
6 |
ejabberd_router:route(To, From, Acc1, Err). |
1007 |
|
|
1008 |
|
|
1009 |
|
-spec force_update_presence(mongooseim:host_type(), {jid:luser(), jid:lserver()}) -> 'ok'. |
1010 |
|
force_update_presence(_HostType, {LUser, LServer}) -> |
1011 |
:-( |
Ss = ejabberd_sm_backend:get_sessions(LUser, LServer), |
1012 |
:-( |
lists:foreach(fun(#session{sid = {_, Pid}}) -> |
1013 |
:-( |
Pid ! {force_update_presence, LUser} |
1014 |
|
end, Ss). |
1015 |
|
|
1016 |
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
1017 |
|
%%% ejabberd commands |
1018 |
|
|
1019 |
|
-spec commands() -> [ejabberd_commands:cmd(), ...]. |
1020 |
|
commands() -> |
1021 |
80 |
[ |
1022 |
|
%% TODO: Implement following API functions with pluggable backends architcture |
1023 |
|
%% #ejabberd_commands{name = connected_users, |
1024 |
|
%% tags = [session], |
1025 |
|
%% desc = "List all established sessions", |
1026 |
|
%% module = ?MODULE, function = connected_users, |
1027 |
|
%% args = [], |
1028 |
|
%% result = {connected_users, {list, {sessions, string}}}}, |
1029 |
|
%% #ejabberd_commands{name = connected_users_number, |
1030 |
|
%% tags = [session, stats], |
1031 |
|
%% desc = "Get the number of established sessions", |
1032 |
|
%% module = ?MODULE, function = connected_users_number, |
1033 |
|
%% args = [], |
1034 |
|
%% result = {num_sessions, integer}}, |
1035 |
|
#ejabberd_commands{name = user_resources, |
1036 |
|
tags = [session], |
1037 |
|
desc = "List user's connected resources", |
1038 |
|
module = ?MODULE, function = user_resources, |
1039 |
|
args = [{user, string}, {host, string}], |
1040 |
|
result = {resources, {list, {resource, binary}}}} |
1041 |
|
]. |
1042 |
|
|
1043 |
|
|
1044 |
|
-spec user_resources(UserStr :: string(), ServerStr :: string()) -> [binary()]. |
1045 |
|
user_resources(UserStr, ServerStr) -> |
1046 |
:-( |
JID = jid:make(list_to_binary(UserStr), list_to_binary(ServerStr), <<"">>), |
1047 |
:-( |
Resources = get_user_resources(JID), |
1048 |
:-( |
lists:sort(Resources). |
1049 |
|
|
1050 |
|
-spec get_cached_unique_count() -> non_neg_integer(). |
1051 |
|
get_cached_unique_count() -> |
1052 |
:-( |
case mongoose_metrics:get_metric_value(global, ?UNIQUE_COUNT_CACHE) of |
1053 |
|
{ok, DataPoints} -> |
1054 |
:-( |
proplists:get_value(value, DataPoints); |
1055 |
|
_ -> |
1056 |
:-( |
0 |
1057 |
|
end. |
1058 |
|
|
1059 |
|
%% It is used from big tests |
1060 |
|
-spec sm_backend() -> backend(). |
1061 |
|
sm_backend() -> |
1062 |
4 |
mongoose_backend:get_backend_module(global, ?MODULE). |