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