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