./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 33 Spec = {?MODULE, {?MODULE, start_link, []}, permanent, brutal_kill, worker, [?MODULE]},
137 33 {ok, _} = ejabberd_sup:start_child(Spec).
138
139 -spec start_link() -> 'ignore' | {'error', _} | {'ok', pid()}.
140 start_link() ->
141 33 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 3487 Acc = new_acc(From, To, Packet),
154 3487 route(From, To, Acc);
155 route(From, To, Acc) ->
156 4474 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 3487 {ok, HostType} = mongoose_domain_api:get_domain_host_type(To#jid.lserver),
161 3487 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 48492 try
170 48492 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 7593 {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 6525 set_session(SID, JID, Priority, Info),
192 6525 ReplacedPIDs = check_for_sessions_to_replace(HostType, JID),
193 6525 mongoose_hooks:sm_register_connection(HostType, SID, JID, Info),
194 6525 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 6178 #jid{luser = LUser, lserver = LServer, lresource = LResource} = JID,
205 6178 ejabberd_sm_backend:delete_session(SID, LUser, LServer, LResource),
206 6178 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 5678 {_, Pid} = SID,
211 5678 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 5492 store_info(JID, SID, Key, undefined).
216
217 store_info_async(C2sData, SID, JID, Key, Value) ->
218 301 Info = mongoose_c2s:get_info(C2sData),
219 301 Info2 = update_info(Key, Value, Info),
220 301 Priority = mod_presence:get_old_priority(mod_presence:maybe_get_handler(C2sData)),
221 301 set_session(SID, JID, Priority, Info2),
222 301 mongoose_c2s:set_info(C2sData, Info2).
223
224 update_info(Key, undefined, Info) ->
225 115 maps:remove(Key, Info);
226 update_info(Key, Value, Info) ->
227 186 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 10 case get_session(JID) of
233
:-(
offline -> {error, offline};
234 Session ->
235 10 case mongoose_session:get_info(Session, Key, {error, not_set}) of
236 10 {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 2066 Ss = ejabberd_sm_backend:get_sessions(LUser, LServer),
244 2066 [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 25285 #jid{luser = LUser, lserver = LServer, lresource = LResource} = JID,
263 25285 case ejabberd_sm_backend:get_sessions(LUser, LServer, LResource) of
264 [] ->
265 6353 offline;
266 Ss ->
267 18932 lists:max(Ss)
268 end.
269
270 -spec get_raw_sessions(jid:jid()) -> [session()].
271 get_raw_sessions(#jid{luser = LUser, lserver = LServer}) ->
272 5957 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 5557 set_session(SID, JID, Priority, Info),
285 5557 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 222 set_session(SID, JID, undefined, Info),
297 222 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 25172 case get_session(JID) of
304 6353 offline -> none;
305 18819 #session{sid = {_, Pid}} -> Pid
306 end.
307
308 -spec get_unique_sessions_number() -> integer().
309 get_unique_sessions_number() ->
310 120 try
311 120 C = ejabberd_sm_backend:unique_count(),
312 120 mongoose_metrics:update(global, ?UNIQUE_COUNT_CACHE, C),
313 120 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 127 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 47 ejabberd_sm_backend:get_sessions(Server).
333
334
335 -spec get_node_sessions_number() -> non_neg_integer().
336 get_node_sessions_number() ->
337 123 Children = supervisor:which_children(mongoose_listener_sup),
338 123 Listeners = [Ref || {Ref, _, _, [mongoose_c2s_listener]} <- Children],
339 123 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 120 ejabberd_sm_backend:get_sessions().
344
345
346 register_iq_handler(Host, XMLNS, IQHandler) ->
347 637 ejabberd_sm ! {register_iq_handler, Host, XMLNS, IQHandler},
348 637 ok.
349
350 -spec sync() -> ok.
351 sync() ->
352 605 gen_server:call(ejabberd_sm, sync).
353
354 unregister_iq_handler(Host, XMLNS) ->
355 573 ejabberd_sm ! {unregister_iq_handler, Host, XMLNS},
356 573 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 1 SerSess = [{Server, Session} || Session = #session{usr = {_, Server, _}} <- Sessions],
371 1 Servers = lists:usort([Server || {Server, _Session} <- SerSess]),
372 1 Map = maps:from_list([{Server, server_to_host_type(Server)} || Server <- Servers]),
373 1 HTSession = [{maps:get(Server, Map), Session} || {Server, Session} <- SerSess],
374 1 HT2Session = group_sessions(lists:sort(HTSession)),
375 1 [mongoose_hooks:sessions_cleanup(HostType, HTSessions)
376 1 || {HostType, HTSessions} <- HT2Session, HostType =/= undefined],
377 1 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 1 [].
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 169 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 1 Timeout = timer:minutes(1),
421 1 Res = gen_server:call(?MODULE, {node_cleanup, Node}, Timeout),
422 1 {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 605 case ejabberd_auth:does_user_exist(ToJID) of
430 true ->
431 531 {ok, Acc};
432 false ->
433 74 {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 46 Acc1 = mongoose_hooks:xmpp_bounce_message(Acc),
442 46 E = mongoose_xmpp_errors:service_unavailable(<<"en">>, <<"Bounce offline message">>),
443 46 {Acc2, Err} = jlib:make_error_reply(Acc1, Packet, E),
444 46 Acc3 = ejabberd_router:route(To, From, Acc2, Err),
445 46 {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 5713 lists:map(fun(#session{sid = {_, Pid}}) -> terminate_session(Pid, <<"User removed">>) end,
453 ejabberd_sm_backend:get_sessions(LUser, LServer)),
454 5713 {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 33 Backend = mongoose_config:get_opt(sm_backend),
470 33 ejabberd_sm_backend:init(#{backend => Backend}),
471
472 33 ets:new(sm_iqtable, [named_table, protected, {read_concurrency, true}]),
473 33 gen_hook:add_handler(node_cleanup, global, fun ?MODULE:node_cleanup/3, #{}, 50),
474 33 lists:foreach(fun(HostType) -> gen_hook:add_handlers(hooks(HostType)) end,
475 ?ALL_HOST_TYPES),
476 33 mongoose_instrument:set_up(instrumentation()),
477 %% Create metrics after backend has started, otherwise probe could have null value
478 33 create_metrics(),
479 33 {ok, #state{}}.
480
481 create_metrics() ->
482 33 mongoose_metrics:ensure_metric(global, ?UNIQUE_COUNT_CACHE, gauge),
483 33 mongoose_metrics:create_probe_metric(global, totalSessionCount, mongoose_metrics_probe_total_sessions),
484 33 mongoose_metrics:create_probe_metric(global, uniqueSessionCount, mongoose_metrics_probe_unique_sessions),
485 33 mongoose_metrics:create_probe_metric(global, nodeSessionCount, mongoose_metrics_probe_node_sessions).
486
487 -spec hooks(binary()) -> [gen_hook:hook_tuple()].
488 hooks(HostType) ->
489 184 [
490 {roster_in_subscription, HostType, fun ?MODULE:check_in_subscription/3, #{}, 20},
491 {offline_message, HostType, fun ?MODULE:bounce_offline_message/3, #{}, 100},
492 {offline_groupchat_message, HostType, fun ?MODULE:bounce_offline_message/3, #{}, 100},
493 {remove_user, HostType, fun ?MODULE:disconnect_removed_user/3, #{}, 100}
494 ].
495
496 %%--------------------------------------------------------------------
497 %% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
498 %% {reply, Reply, State, Timeout} |
499 %% {noreply, State} |
500 %% {noreply, State, Timeout} |
501 %% {stop, Reason, Reply, State} |
502 %% {stop, Reason, State}
503 %% Description: Handling call messages
504 %%--------------------------------------------------------------------
505 handle_call({node_cleanup, Node}, _From, State) ->
506 1 {TimeDiff, _R} = timer:tc(fun ejabberd_sm_backend:cleanup/1, [Node]),
507 1 ?LOG_INFO(#{what => sm_node_cleanup,
508 text => <<"Cleaning after a node that went down">>,
509 cleanup_node => Node,
510 1 duration => erlang:round(TimeDiff / 1000)}),
511 1 {reply, ok, State};
512 handle_call(sync, _From, State) ->
513 605 {reply, ok, State};
514 handle_call(_Request, _From, State) ->
515
:-(
Reply = ok,
516
:-(
{reply, Reply, State}.
517
518 %%--------------------------------------------------------------------
519 %% Function: handle_cast(Msg, State) -> {noreply, State} |
520 %% {noreply, State, Timeout} |
521 %% {stop, Reason, State}
522 %% Description: Handling cast messages
523 %%--------------------------------------------------------------------
524 handle_cast(_Msg, State) ->
525
:-(
{noreply, State}.
526
527 %%--------------------------------------------------------------------
528 %% Function: handle_info(Info, State) -> {noreply, State} |
529 %% {noreply, State, Timeout} |
530 %% {stop, Reason, State}
531 %% Description: Handling all non call/cast messages
532 %%--------------------------------------------------------------------
533 -spec handle_info(_, _) -> {'noreply', _}.
534 handle_info({route, From, To, Packet}, State) ->
535
:-(
route(From, To, Packet),
536
:-(
{noreply, State};
537 handle_info({register_iq_handler, Host, XMLNS, IQHandler}, State) ->
538 637 case ets:insert_new(sm_iqtable, {{XMLNS, Host}, IQHandler}) of
539 637 true -> ok;
540 false ->
541
:-(
?LOG_WARNING(#{what => register_iq_handler_duplicate,
542
:-(
xmlns => XMLNS, host => Host})
543 end,
544 637 {noreply, State};
545 handle_info({unregister_iq_handler, Host, XMLNS}, State) ->
546 573 case ets:lookup(sm_iqtable, {XMLNS, Host}) of
547 [{_, IQHandler}] ->
548 573 gen_iq_component:stop_iq_handler(IQHandler),
549 573 ets:delete(sm_iqtable, {XMLNS, Host});
550 _ ->
551
:-(
?LOG_WARNING(#{what => unregister_iq_handler_missing,
552
:-(
xmlns => XMLNS, host => Host})
553 end,
554 573 {noreply, State};
555 handle_info(_Info, State) ->
556
:-(
{noreply, State}.
557
558 %%--------------------------------------------------------------------
559 %% Function: terminate(Reason, State) -> void()
560 %% Description: This function is called by a gen_server when it is about to
561 %% terminate. It should be the opposite of Module:init/1 and do any necessary
562 %% cleaning up. When it returns, the gen_server terminates with Reason.
563 %% The return value is ignored.
564 %%--------------------------------------------------------------------
565 -spec terminate(_, state()) -> 'ok'.
566 terminate(_Reason, _State) ->
567
:-(
mongoose_instrument:tear_down(instrumentation()).
568
569 %%--------------------------------------------------------------------
570 %% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
571 %% Description: Convert process state when code is changed
572 %%--------------------------------------------------------------------
573 code_change(_OldVsn, State, _Extra) ->
574
:-(
{ok, State}.
575
576 %%--------------------------------------------------------------------
577 %%% Internal functions
578 %%--------------------------------------------------------------------
579
580 -spec set_session(SID, JID, Prio, Info) -> ok | {error, any()} when
581 SID :: sid() | 'undefined',
582 JID :: jid:jid(),
583 Prio :: priority(),
584 Info :: info().
585 set_session(SID, JID, Priority, Info) ->
586 12605 #jid{luser = LUser, lserver = LServer, lresource = LResource} = JID,
587 12605 US = {LUser, LServer},
588 12605 USR = {LUser, LServer, LResource},
589 12605 Session = #session{sid = SID,
590 usr = USR,
591 us = US,
592 priority = Priority,
593 info = Info},
594 12605 ejabberd_sm_backend:set_session(LUser, LServer, LResource, Session).
595
596 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
597
598 do_filter(From, To, Packet) ->
599
:-(
{From, To, Packet}.
600
601 -spec do_route(Acc, From, To, Payload) -> Acc when
602 Acc :: mongoose_acc:t(),
603 From :: jid:jid(),
604 To :: jid:jid(),
605 Payload :: exml:element().
606 do_route(Acc, From, To, El) ->
607 48492 ?LOG_DEBUG(#{what => sm_route, acc => Acc}),
608 48492 #jid{lresource = LResource} = To,
609 48492 #xmlel{name = Name} = El,
610 48492 case LResource of
611 <<>> ->
612 23453 do_route_no_resource(Name, From, To, Acc, El);
613 _ ->
614 25039 case get_session_pid(To) of
615 none ->
616 6349 do_route_offline(Name, mongoose_acc:stanza_type(Acc),
617 From, To, Acc, El);
618 Pid when is_pid(Pid) ->
619 18690 ?LOG_DEBUG(#{what => sm_route_to_pid, session_pid => Pid, acc => Acc}),
620 18690 mongoose_c2s:route(Pid, Acc),
621 18690 Acc
622 end
623 end.
624
625 -spec do_route_no_resource_presence_prv(From, To, Acc, Packet, Type, Reason) -> boolean() when
626 From :: jid:jid(),
627 To :: jid:jid(),
628 Acc :: mongoose_acc:t(),
629 Packet :: exml:element(),
630 Type :: 'subscribe' | 'subscribed' | 'unsubscribe' | 'unsubscribed',
631 Reason :: any().
632 do_route_no_resource_presence_prv(From, To, Acc, Packet, Type, Reason) ->
633 605 case is_privacy_allow(From, To, Acc, Packet) of
634 true ->
635 605 execute_subscription_instrumentation(From, To, Type),
636 605 Res = mongoose_hooks:roster_in_subscription(Acc, To, From, Type, Reason),
637 605 mongoose_acc:get(hook, result, false, Res);
638 false ->
639
:-(
false
640 end.
641
642 execute_subscription_instrumentation(From, To, subscribed) ->
643 169 mongoose_instrument:execute(sm_presence_subscription, #{},
644 #{from_jid => From, to_jid => To, subscription_count => 1});
645 execute_subscription_instrumentation(From, To, unsubscribed) ->
646 173 mongoose_instrument:execute(sm_presence_subscription, #{},
647 #{from_jid => From, to_jid => To, unsubscription_count => 1});
648 execute_subscription_instrumentation(_From, _To, _Type) ->
649 263 ok.
650
651 -spec do_route_no_resource_presence(Type, From, To, Acc, Packet) -> boolean() when
652 Type :: binary(),
653 From :: jid:jid(),
654 To :: jid:jid(),
655 Acc :: mongoose_acc:t(),
656 Packet :: exml:element().
657 do_route_no_resource_presence(<<"subscribe">>, From, To, Acc, Packet) ->
658 177 Reason = xml:get_path_s(Packet, [{elem, <<"status">>}, cdata]),
659 177 do_route_no_resource_presence_prv(From, To, Acc, Packet, subscribe, Reason);
660 do_route_no_resource_presence(<<"subscribed">>, From, To, Acc, Packet) ->
661 169 do_route_no_resource_presence_prv(From, To, Acc, Packet, subscribed, <<>>);
662 do_route_no_resource_presence(<<"unsubscribe">>, From, To, Acc, Packet) ->
663 86 do_route_no_resource_presence_prv(From, To, Acc, Packet, unsubscribe, <<>>);
664 do_route_no_resource_presence(<<"unsubscribed">>, From, To, Acc, Packet) ->
665 173 do_route_no_resource_presence_prv(From, To, Acc, Packet, unsubscribed, <<>>);
666 do_route_no_resource_presence(_, _, _, _, _) ->
667 17014 true.
668
669
670 -spec do_route_no_resource(Name, From, To, Acc, El) -> Acc when
671 Name :: undefined | binary(),
672 From :: jid:jid(),
673 To :: jid:jid(),
674 Acc :: mongoose_acc:t(),
675 El :: exml:element().
676 do_route_no_resource(<<"presence">>, From, To, Acc, El) ->
677 17619 Type = mongoose_acc:stanza_type(Acc),
678 17619 case do_route_no_resource_presence(Type, From, To, Acc, El) of
679 true ->
680 17533 ResourcesPids = get_user_present_resources_and_pids(To),
681 17533 lists:foldl(fun({Resource, Pid}, Acc1) ->
682 17569 NewTo = jid:replace_resource(To, Resource),
683 17569 NewAccParams = #{element => El, from_jid => From, to_jid => NewTo},
684 17569 Acc2 = mongoose_acc:update_stanza(NewAccParams, Acc1),
685 17569 mongoose_c2s:route(Pid, Acc2),
686 17569 Acc2
687 end, Acc, ResourcesPids);
688 false ->
689 86 Acc
690 end;
691 do_route_no_resource(<<"message">>, From, To, Acc, El) ->
692 2563 route_message(From, To, Acc, El);
693 do_route_no_resource(<<"iq">>, From, To, Acc, El) ->
694 3271 process_iq(From, To, Acc, El);
695 do_route_no_resource(_, _, _, Acc, _) ->
696
:-(
Acc.
697
698 -spec do_route_offline(Name, Type, From, To, Acc, Packet) -> mongoose_acc:t() when
699 Name :: 'undefined' | binary(),
700 Type :: binary(),
701 From :: jid:jid(),
702 To :: jid:jid(),
703 Acc :: mongoose_acc:t(),
704 Packet :: exml:element().
705 do_route_offline(<<"message">>, _, From, To, Acc, Packet) ->
706 229 HostType = mongoose_acc:host_type(Acc),
707 229 Drop = mongoose_hooks:sm_filter_offline_message(HostType, From, To, Packet),
708 229 case Drop of
709 false ->
710 229 route_message(From, To, Acc, Packet);
711 true ->
712
:-(
?LOG_DEBUG(#{what => sm_offline_dropped, acc => Acc}),
713
:-(
Acc
714 end;
715 do_route_offline(<<"iq">>, <<"error">>, _From, _To, Acc, _Packet) ->
716
:-(
Acc;
717 do_route_offline(<<"iq">>, <<"result">>, _From, _To, Acc, _Packet) ->
718 22 Acc;
719 do_route_offline(<<"iq">>, _, From, To, Acc, Packet) ->
720 7 E = mongoose_xmpp_errors:service_unavailable(<<"en">>, <<"Route offline">>),
721 7 {Acc1, Err} = jlib:make_error_reply(Acc, Packet, E),
722 7 ejabberd_router:route(To, From, Acc1, Err);
723 do_route_offline(_, _, _, _, Acc, _) ->
724 6091 ?LOG_DEBUG(#{what => sm_packet_dropped, acc => Acc}),
725 6091 Acc.
726
727
728 %% @doc The default list applies to the user as a whole,
729 %% and is processed if there is no active list set
730 %% for the target session/resource to which a stanza is addressed,
731 %% or if there are no current sessions for the user.
732 -spec is_privacy_allow(From, To, Acc, Packet) -> boolean() when
733 From :: jid:jid(),
734 To :: jid:jid(),
735 Acc :: mongoose_acc:t(),
736 Packet :: exml:element() | mongoose_acc:t().
737 is_privacy_allow(From, To, Acc, Packet) ->
738 890 HostType = mongoose_acc:host_type(Acc),
739 890 PrivacyList = mongoose_hooks:privacy_get_user_list(HostType, To),
740 890 is_privacy_allow(From, To, Acc, Packet, PrivacyList).
741
742
743 %% @doc Check if privacy rules allow this delivery
744 -spec is_privacy_allow(From, To, Acc, Packet, PrivacyList) -> boolean() when
745 From :: jid:jid(),
746 To :: jid:jid(),
747 Acc :: mongoose_acc:t(),
748 Packet :: exml:element(),
749 PrivacyList :: mongoose_privacy:userlist().
750 is_privacy_allow(_From, To, Acc, _Packet, PrivacyList) ->
751 890 {Res, _} = mongoose_privacy:privacy_check_packet(Acc, To, PrivacyList, To, in),
752 890 allow == Res.
753
754
755 -spec route_message(From, To, Acc, Packet) -> Acc when
756 From :: jid:jid(),
757 To :: jid:jid(),
758 Acc :: mongoose_acc:t(),
759 Packet :: exml:element().
760 route_message(From, To, Acc, Packet) ->
761 2792 LUser = To#jid.luser,
762 2792 LServer = To#jid.lserver,
763 2792 PrioPid = get_user_present_pids(LUser, LServer),
764 2792 case catch lists:max(PrioPid) of
765 {Priority, _} when is_integer(Priority), Priority >= 0 ->
766 2146 lists:foreach(
767 %% Route messages to all priority that equals the max, if
768 %% positive
769 fun({Prio, Pid}) when Prio == Priority ->
770 %% we will lose message if PID is not alive
771 2156 mongoose_c2s:route(Pid, Acc);
772 %% Ignore other priority:
773 ({_Prio, _Pid}) ->
774 7 ok
775 end,
776 PrioPid),
777 2146 Acc;
778 _ ->
779 646 MessageType = mongoose_acc:stanza_type(Acc),
780 646 route_message_by_type(MessageType, From, To, Acc, Packet)
781 end.
782
783 route_message_by_type(<<"error">>, _From, _To, Acc, _Packet) ->
784 49 Acc;
785 route_message_by_type(<<"groupchat">>, From, To, Acc, Packet) ->
786 228 mongoose_hooks:offline_groupchat_message(Acc, From, To, Packet);
787 route_message_by_type(<<"headline">>, From, To, Acc, Packet) ->
788 4 {stop, Acc1} = bounce_offline_message(Acc, #{from => From, to => To, packet => Packet}, #{}),
789 4 Acc1;
790 route_message_by_type(_, From, To, Acc, Packet) ->
791 365 HostType = mongoose_acc:host_type(Acc),
792 365 case ejabberd_auth:does_user_exist(HostType, To, stored) of
793 true ->
794 285 case is_privacy_allow(From, To, Acc, Packet) of
795 true ->
796 273 mongoose_hooks:offline_message(Acc, From, To, Packet);
797 false ->
798 12 mongoose_hooks:failed_to_store_message(Acc)
799 end;
800 _ ->
801 80 E = mongoose_xmpp_errors:service_unavailable(<<"en">>, <<"User not found">>),
802 80 {Acc1, Err} = jlib:make_error_reply(Acc, Packet, E),
803 80 ejabberd_router:route(To, From, Acc1, Err)
804 end.
805
806 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
807
808 -spec clean_session_list([session()]) -> [session()].
809 clean_session_list(Ss) ->
810 29890 clean_session_list(lists:keysort(#session.usr, Ss), []).
811
812
813 -spec clean_session_list([session()], [session()]) -> [session()].
814 clean_session_list([], Res) ->
815 1813 Res;
816 clean_session_list([S], Res) ->
817 28077 [S | Res];
818 clean_session_list([S1, S2 | Rest], Res) ->
819 960 case S1#session.usr == S2#session.usr of
820 true ->
821 9 case S1#session.sid > S2#session.sid of
822
:-(
true -> clean_session_list([S1 | Rest], Res);
823 9 false -> clean_session_list([S2 | Rest], Res)
824 end;
825 false ->
826 951 clean_session_list([S2 | Rest], [S1 | Res])
827 end.
828
829
830 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
831
832 -spec get_user_present_pids(LUser, LServer) -> [{priority(), pid()}] when
833 LUser :: jid:luser(),
834 LServer :: jid:lserver().
835 get_user_present_pids(LUser, LServer) ->
836 4070 Ss = ejabberd_sm_backend:get_sessions(LUser, LServer),
837 4070 [{S#session.priority, element(2, S#session.sid)} ||
838 4070 S <- clean_session_list(Ss), is_integer(S#session.priority)].
839
840 -spec get_user_present_resources_and_pids(jid:jid()) -> [{Resource :: binary(), pid()}].
841 get_user_present_resources_and_pids(#jid{luser = LUser, lserver = LServer}) ->
842 17533 Ss = ejabberd_sm_backend:get_sessions(LUser, LServer),
843 17533 [{Resource, Pid} ||
844 #session{usr = {_, _, Resource}, sid = {_, Pid}, priority = Prio}
845 17533 <- clean_session_list(Ss), is_integer(Prio)].
846
847 -spec get_user_present_resources(jid:jid()) -> [{priority(), binary()}].
848 get_user_present_resources(#jid{luser = LUser, lserver = LServer}) ->
849 264 Ss = ejabberd_sm_backend:get_sessions(LUser, LServer),
850 264 [{S#session.priority, element(3, S#session.usr)} ||
851 264 S <- clean_session_list(Ss), is_integer(S#session.priority)].
852
853 -spec is_offline(jid:jid()) -> boolean().
854 is_offline(#jid{luser = LUser, lserver = LServer}) ->
855 256 case catch lists:max(get_user_present_pids(LUser, LServer)) of
856 {Priority, _} when is_integer(Priority), Priority >= 0 ->
857 109 false;
858 _ ->
859 147 true
860 end.
861
862 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
863
864 %% @doc On new session, check if some existing connections need to be replace
865 -spec check_for_sessions_to_replace(HostType, JID) -> ReplacedPids when
866 HostType :: mongooseim:host_type(),
867 JID :: jid:jid(),
868 ReplacedPids :: [pid()].
869 check_for_sessions_to_replace(HostType, JID) ->
870 6525 #jid{luser = LUser, lserver = LServer, lresource = LResource} = JID,
871 6525 Sessions = ejabberd_sm_backend:get_sessions(LUser, LServer),
872 %% TODO: Depending on how this is executed, there could be an unneeded
873 %% replacement for max_sessions. We need to check this at some point.
874 6525 ReplacedRedundantSessions = check_existing_resources(LResource, Sessions),
875 6525 AllReplacedSessionPids = check_max_sessions(HostType, LUser, LServer, ReplacedRedundantSessions, Sessions),
876 6525 [mongoose_c2s:exit(Pid, <<"Replaced by new connection">>) || Pid <- AllReplacedSessionPids],
877 6525 AllReplacedSessionPids.
878
879 -spec check_existing_resources(LResource, Sessions) ->
880 ReplacedSessionsPIDs when
881 LResource :: jid:lresource(),
882 Sessions :: [session()],
883 ReplacedSessionsPIDs :: ordsets:ordset(pid()).
884 check_existing_resources(LResource, Sessions) ->
885 %% A connection exist with the same resource. We replace it:
886 6525 case [S#session.sid || S = #session{usr = {_, _, R}} <- Sessions, R =:= LResource] of
887
:-(
[] -> [];
888 6515 [_] -> [];
889 SIDs ->
890 10 MaxSID = lists:max(SIDs),
891 10 ordsets:from_list([Pid || {_, Pid} = S <- SIDs, S /= MaxSID])
892 end.
893
894 -spec check_max_sessions(HostType :: mongooseim:host_type(),
895 LUser :: jid:luser(),
896 LServer :: jid:lserver(),
897 ReplacedPIDs :: [pid()],
898 Sessions :: [session()]) ->
899 AllReplacedPIDs :: ordsets:ordset(pid()).
900 check_max_sessions(HostType, LUser, LServer, ReplacedPIDs, Sessions) ->
901 %% If the max number of sessions for a given is reached, we replace the
902 %% first one
903 6525 SIDs = lists:filtermap(
904 fun(Session) ->
905 7926 {_, Pid} = SID = Session#session.sid,
906 7926 case ordsets:is_element(Pid, ReplacedPIDs) of
907 10 true -> false;
908 7916 false -> {true, SID}
909 end
910 end,
911 Sessions),
912 6525 MaxSessions = get_max_user_sessions(HostType, LUser, LServer),
913 6525 case length(SIDs) =< MaxSessions of
914 6525 true -> ordsets:to_list(ReplacedPIDs);
915 false ->
916
:-(
{_, Pid} = lists:min(SIDs),
917
:-(
[Pid | ordsets:to_list(ReplacedPIDs)]
918 end.
919
920
921 %% @doc Get the user_max_session setting
922 %% This option defines the max number of time a given users are allowed to
923 %% log in. Defaults to infinity
924 -spec get_max_user_sessions(HostType, LUser, LServer) -> Result when
925 HostType :: mongooseim:host_type(),
926 LUser :: jid:luser(),
927 LServer :: jid:lserver(),
928 Result :: infinity | pos_integer().
929 get_max_user_sessions(HostType, LUser, LServer) ->
930 6525 JID = jid:make_noprep(LUser, LServer, <<>>),
931 6525 case acl:match_rule(HostType, LServer, max_user_sessions, JID) of
932 6525 Max when is_integer(Max) -> Max;
933
:-(
infinity -> infinity;
934
:-(
_ -> ?MAX_USER_SESSIONS
935 end.
936
937 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
938
939 -spec process_iq(From, To, Acc, Packet) -> Acc when
940 From :: jid:jid(),
941 To :: jid:jid(),
942 Acc :: mongoose_acc:t(),
943 Packet :: exml:element().
944 process_iq(From, To, Acc0, Packet) ->
945 3271 {IQ, Acc} = mongoose_iq:info(Acc0),
946 3271 process_iq(IQ, From, To, Acc, Packet).
947
948 process_iq(#iq{type = Type}, _From, _To, Acc, _Packet) when Type == result; Type == error ->
949 % results and errors are always sent to full jids, so we ignore them here
950 238 Acc;
951 process_iq(#iq{xmlns = XMLNS} = IQ, From, To, Acc, Packet) ->
952 3027 Host = To#jid.lserver,
953 3027 case ets:lookup(sm_iqtable, {XMLNS, Host}) of
954 [{_, IQHandler}] ->
955 3022 gen_iq_component:handle(IQHandler, Acc, From, To, IQ);
956 [] ->
957 5 E = mongoose_xmpp_errors:service_unavailable(<<"en">>, <<"Unknown xmlns=", XMLNS/binary, " for host=", Host/binary>>),
958 5 {Acc1, Err} = jlib:make_error_reply(Acc, Packet, E),
959 5 ejabberd_router:route(To, From, Acc1, Err)
960 end;
961 process_iq(_, From, To, Acc, Packet) ->
962 6 {Acc1, Err} = jlib:make_error_reply(Acc, Packet, mongoose_xmpp_errors:bad_request()),
963 6 ejabberd_router:route(To, From, Acc1, Err).
964
965 -spec user_resources(UserStr :: string(), ServerStr :: string()) -> [binary()].
966 user_resources(UserStr, ServerStr) ->
967
:-(
JID = jid:make_bare(list_to_binary(UserStr), list_to_binary(ServerStr)),
968
:-(
Resources = get_user_resources(JID),
969
:-(
lists:sort(Resources).
970
971 -spec get_cached_unique_count() -> non_neg_integer().
972 get_cached_unique_count() ->
973
:-(
case mongoose_metrics:get_metric_value(global, ?UNIQUE_COUNT_CACHE) of
974 {ok, DataPoints} ->
975
:-(
proplists:get_value(value, DataPoints);
976 _ ->
977
:-(
0
978 end.
979
980 %% It is used from big tests
981 -spec sm_backend() -> backend().
982 sm_backend() ->
983 7 mongoose_backend:get_backend_module(global, ?MODULE).
984
985 -spec instrumentation() -> [mongoose_instrument:spec()].
986 instrumentation() ->
987 33 [{sm_presence_subscription, #{},
988 #{metrics => #{subscription_count => spiral, unsubscription_count => spiral}}}].
Line Hits Source