./ct_report/coverage/ejabberd_sm.COVER.html

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 5483 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 46071 try
170 46071 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 6954 {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 6277 set_session(SID, JID, Priority, Info),
192 6277 ReplacedPIDs = check_for_sessions_to_replace(HostType, JID),
193 6277 mongoose_hooks:sm_register_connection(HostType, SID, JID, Info),
194 6277 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 5928 #jid{luser = LUser, lserver = LServer, lresource = LResource} = JID,
205 5928 ejabberd_sm_backend:delete_session(SID, LUser, LServer, LResource),
206 5928 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 5255 {_, Pid} = SID,
211 5255 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 5149 store_info(JID, SID, Key, undefined).
216
217 store_info_async(C2sData, SID, JID, Key, Value) ->
218 112 Info = mongoose_c2s:get_info(C2sData),
219 112 Info2 = update_info(Key, Value, Info),
220 112 Priority = mod_presence:get_old_priority(mod_presence:maybe_get_handler(C2sData)),
221 112 set_session(SID, JID, Priority, Info2),
222 112 mongoose_c2s:set_info(C2sData, Info2).
223
224 update_info(Key, undefined, Info) ->
225 6 maps:remove(Key, Info);
226 update_info(Key, Value, Info) ->
227 106 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 1616 Ss = ejabberd_sm_backend:get_sessions(LUser, LServer),
244 1616 [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 23853 #jid{luser = LUser, lserver = LServer, lresource = LResource} = JID,
263 23853 case ejabberd_sm_backend:get_sessions(LUser, LServer, LResource) of
264 [] ->
265 6071 offline;
266 Ss ->
267 17782 lists:max(Ss)
268 end.
269
270 -spec get_raw_sessions(jid:jid()) -> [session()].
271 get_raw_sessions(#jid{luser = LUser, lserver = LServer}) ->
272 5730 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 5216 set_session(SID, JID, Priority, Info),
285 5216 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 121 set_session(SID, JID, undefined, Info),
297 121 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 23842 case get_session(JID) of
304 6071 offline -> none;
305 17771 #session{sid = {_, Pid}} -> Pid
306 end.
307
308 -spec get_unique_sessions_number() -> integer().
309 get_unique_sessions_number() ->
310 131 try
311 131 C = ejabberd_sm_backend:unique_count(),
312 131 mongoose_metrics:update(global, ?UNIQUE_COUNT_CACHE, C),
313 131 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 138 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 134 Children = supervisor:which_children(mongoose_listener_sup),
338 134 Listeners = [Ref || {Ref, _, _, [mongoose_c2s_listener]} <- Children],
339 134 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 67 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 32 Acc1 = mongoose_hooks:xmpp_bounce_message(Acc),
442 32 E = mongoose_xmpp_errors:service_unavailable(<<"en">>, <<"Bounce offline message">>),
443 32 {Acc2, Err} = jlib:make_error_reply(Acc1, Packet, E),
444 32 Acc3 = ejabberd_router:route(To, From, Acc2, Err),
445 32 {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 5447 lists:map(fun(#session{sid = {_, Pid}}) -> terminate_session(Pid, <<"User removed">>) end,
453 ejabberd_sm_backend:get_sessions(LUser, LServer)),
454 5447 {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 11726 #jid{luser = LUser, lserver = LServer, lresource = LResource} = JID,
586 11726 US = {LUser, LServer},
587 11726 USR = {LUser, LServer, LResource},
588 11726 Session = #session{sid = SID,
589 usr = USR,
590 us = US,
591 priority = Priority,
592 info = Info},
593 11726 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 46071 ?LOG_DEBUG(#{what => sm_route, acc => Acc}),
607 46071 #jid{lresource = LResource} = To,
608 46071 #xmlel{name = Name} = El,
609 46071 case LResource of
610 <<>> ->
611 22322 do_route_no_resource(Name, From, To, Acc, El);
612 _ ->
613 23749 case get_session_pid(To) of
614 none ->
615 6067 do_route_offline(Name, mongoose_acc:stanza_type(Acc),
616 From, To, Acc, El);
617 Pid when is_pid(Pid) ->
618 17682 ?LOG_DEBUG(#{what => sm_route_to_pid, session_pid => Pid, acc => Acc}),
619 17682 mongoose_c2s:route(Pid, Acc),
620 17682 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 15992 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 16578 Type = mongoose_acc:stanza_type(Acc),
667 16578 case do_route_no_resource_presence(Type, From, To, Acc, El) of
668 true ->
669 16493 ResourcesPids = get_user_present_resources_and_pids(To),
670 16493 lists:foldl(fun({Resource, Pid}, Acc1) ->
671 16766 NewTo = jid:replace_resource(To, Resource),
672 16766 NewAccParams = #{element => El, from_jid => From, to_jid => NewTo},
673 16766 Acc2 = mongoose_acc:update_stanza(NewAccParams, Acc1),
674 16766 mongoose_c2s:route(Pid, Acc2),
675 16766 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 3398 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 225 HostType = mongoose_acc:host_type(Acc),
696 225 Drop = mongoose_hooks:sm_filter_offline_message(HostType, From, To, Packet),
697 225 case Drop of
698 false ->
699 225 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 5831 ?LOG_DEBUG(#{what => sm_packet_dropped, acc => Acc}),
714 5831 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 820 HostType = mongoose_acc:host_type(Acc),
728 820 PrivacyList = mongoose_hooks:privacy_get_user_list(HostType, To),
729 820 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 820 {Res, _} = mongoose_privacy:privacy_check_packet(Acc, To, PrivacyList, To, in),
741 820 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 2571 LUser = To#jid.luser,
751 2571 LServer = To#jid.lserver,
752 2571 PrioPid = get_user_present_pids(LUser, LServer),
753 2571 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 2058 mongoose_c2s:route(Pid, Acc);
761 %% Ignore other priority:
762 ({_Prio, _Pid}) ->
763 7 ok
764 end,
765 PrioPid),
766 2047 Acc;
767 _ ->
768 524 MessageType = mongoose_acc:stanza_type(Acc),
769 524 route_message_by_type(MessageType, From, To, Acc, Packet)
770 end.
771
772 route_message_by_type(<<"error">>, _From, _To, Acc, _Packet) ->
773 45 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 312 HostType = mongoose_acc:host_type(Acc),
781 312 case ejabberd_auth:does_user_exist(HostType, To, stored) of
782 true ->
783 234 case is_privacy_allow(From, To, Acc, Packet) of
784 true ->
785 222 mongoose_hooks:offline_message(Acc, From, To, Packet);
786 false ->
787 12 mongoose_hooks:failed_to_store_message(Acc)
788 end;
789 _ ->
790 78 E = mongoose_xmpp_errors:service_unavailable(<<"en">>, <<"User not found">>),
791 78 {Acc1, Err} = jlib:make_error_reply(Acc, Packet, E),
792 78 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 27410 clean_session_list(lists:keysort(#session.usr, Ss), []).
800
801
802 -spec clean_session_list([session()], [session()]) -> [session()].
803 clean_session_list([], Res) ->
804 1362 Res;
805 clean_session_list([S], Res) ->
806 26048 [S | Res];
807 clean_session_list([S1, S2 | Rest], Res) ->
808 1123 case S1#session.usr == S2#session.usr of
809 true ->
810 9 case S1#session.sid > S2#session.sid of
811
:-(
true -> clean_session_list([S1 | Rest], Res);
812 9 false -> clean_session_list([S2 | Rest], Res)
813 end;
814 false ->
815 1114 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 3566 Ss = ejabberd_sm_backend:get_sessions(LUser, LServer),
826 3566 [{S#session.priority, element(2, S#session.sid)} ||
827 3566 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 16493 Ss = ejabberd_sm_backend:get_sessions(LUser, LServer),
832 16493 [{Resource, Pid} ||
833 #session{usr = {_, _, Resource}, sid = {_, Pid}, priority = Prio}
834 16493 <- 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 6277 #jid{luser = LUser, lserver = LServer, lresource = LResource} = JID,
860 6277 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 6277 ReplacedRedundantSessions = check_existing_resources(LResource, Sessions),
864 6277 AllReplacedSessionPids = check_max_sessions(HostType, LUser, LServer, ReplacedRedundantSessions, Sessions),
865 6277 [mongoose_c2s:exit(Pid, <<"Replaced by new connection">>) || Pid <- AllReplacedSessionPids],
866 6277 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 6277 case [S#session.sid || S = #session{usr = {_, _, R}} <- Sessions, R =:= LResource] of
876
:-(
[] -> [];
877 6267 [_] -> [];
878 SIDs ->
879 10 MaxSID = lists:max(SIDs),
880 10 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 6277 SIDs = lists:filtermap(
893 fun(Session) ->
894 7352 {_, Pid} = SID = Session#session.sid,
895 7352 case ordsets:is_element(Pid, ReplacedPIDs) of
896 10 true -> false;
897 7342 false -> {true, SID}
898 end
899 end,
900 Sessions),
901 6277 MaxSessions = get_max_user_sessions(HostType, LUser, LServer),
902 6277 case length(SIDs) =< MaxSessions of
903 6277 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 6277 JID = jid:make_noprep(LUser, LServer, <<>>),
920 6277 case acl:match_rule(HostType, LServer, max_user_sessions, JID) of
921 6277 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 3398 {IQ, Acc} = mongoose_iq:info(Acc0),
935 3398 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 3164 Host = To#jid.lserver,
942 3164 case ets:lookup(sm_iqtable, {XMLNS, Host}) of
943 [{_, IQHandler}] ->
944 3159 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).
Line Hits Source