./ct_report/coverage/mod_last.COVER.html

1 %%%----------------------------------------------------------------------
2 %%% File : mod_last.erl
3 %%% Author : Alexey Shchepin <alexey@process-one.net>
4 %%% Purpose : jabber:iq:last support (XEP-0012)
5 %%% Created : 24 Oct 2003 by Alexey Shchepin <alexey@process-one.net>
6 %%%
7 %%%
8 %%% ejabberd, Copyright (C) 2002-2014 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 along
21 %%% with this program; if not, write to the Free Software Foundation, Inc.,
22 %%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23 %%%
24 %%%----------------------------------------------------------------------
25
26 -module(mod_last).
27
28 -author('alexey@process-one.net').
29
30 -xep([{xep, 12}, {version, "2.0"}]).
31
32 -behaviour(gen_mod).
33 -behaviour(mongoose_module_metrics).
34
35 %% Gen_mod callbacks
36 -export([start/2,
37 stop/1,
38 hooks/1,
39 config_spec/0,
40 supported_features/0]).
41
42 %% IQ and hook handlers
43 -export([user_receive_iq/3,
44 process_local_iq/5,
45 process_sm_iq/5,
46 remove_user/3,
47 unset_presence/3,
48 session_cleanup/3,
49 sessions_cleanup/3,
50 remove_domain/3]).
51
52 %% API
53 -export([store_last_info/5,
54 get_last_info/3,
55 count_active_users/3]).
56
57 -export([config_metrics/1]).
58
59 -include("mongoose.hrl").
60 -include("mongoose_config_spec.hrl").
61
62 -include("jlib.hrl").
63
64 %% ------------------------------------------------------------------
65 %% Backend callbacks
66
67 -export_type([timestamp/0, status/0]).
68
69 -type timestamp() :: non_neg_integer().
70 -type status() :: binary().
71
72 -spec start(mongooseim:host_type(), gen_mod:module_opts()) -> ok.
73 start(HostType, #{iqdisc := IQDisc} = Opts) ->
74 10 mod_last_backend:init(HostType, Opts),
75 10 [gen_iq_handler:add_iq_handler_for_domain(HostType, ?NS_LAST, Component, Fn, #{}, IQDisc) ||
76 10 {Component, Fn} <- iq_handlers()],
77 10 ok.
78
79 -spec stop(mongooseim:host_type()) -> ok.
80 stop(HostType) ->
81 10 [gen_iq_handler:remove_iq_handler_for_domain(HostType, ?NS_LAST, Component) ||
82 10 {Component, _Fn} <- iq_handlers()],
83 10 gen_hook:delete_handlers(hooks(HostType)).
84
85 iq_handlers() ->
86 20 [{ejabberd_local, fun ?MODULE:process_local_iq/5},
87 {ejabberd_sm, fun ?MODULE:process_sm_iq/5}].
88
89 -spec hooks(mongooseim:host_type()) -> gen_hook:hook_list().
90 hooks(HostType) ->
91 30 [{remove_user, HostType, fun ?MODULE:remove_user/3, #{}, 50},
92 {anonymous_purge, HostType, fun ?MODULE:remove_user/3, #{}, 50},
93 {unset_presence, HostType, fun ?MODULE:unset_presence/3, #{}, 50},
94 {session_cleanup, HostType, fun ?MODULE:session_cleanup/3, #{}, 50},
95 {sessions_cleanup, HostType, fun ?MODULE:sessions_cleanup/3, #{}, 50},
96 {remove_domain, HostType, fun ?MODULE:remove_domain/3, #{}, 50}
97 | c2s_hooks(HostType) ].
98
99 -spec c2s_hooks(mongooseim:host_type()) -> gen_hook:hook_list(mongoose_c2s_hooks:fn()).
100 c2s_hooks(HostType) ->
101 30 [
102 {user_receive_iq, HostType, fun ?MODULE:user_receive_iq/3, #{}, 50}
103 ].
104
105 %%%
106 %%% config_spec
107 %%%
108
109 -spec config_spec() -> mongoose_config_spec:config_section().
110 config_spec() ->
111 202 #section{
112 items = #{<<"iqdisc">> => mongoose_config_spec:iqdisc(),
113 <<"backend">> => #option{type = atom,
114 validate = {module, mod_last}}
115 },
116 defaults = #{<<"iqdisc">> => one_queue,
117 <<"backend">> => mnesia
118 }
119 }.
120
121
:-(
supported_features() -> [dynamic_domains].
122
123 %%%
124 %%% Uptime of ejabberd node
125 %%%
126 -spec process_local_iq(mongoose_acc:t(), jid:jid(), jid:jid(), jlib:iq(), map())
127 -> {mongoose_acc:t(), jlib:iq()}.
128 process_local_iq(Acc, _From, _To, #iq{type = Type, sub_el = SubEl} = IQ, _Extra) ->
129 1 case Type of
130 set ->
131
:-(
{Acc, IQ#iq{type = error, sub_el = [SubEl, mongoose_xmpp_errors:not_allowed()]}};
132 get ->
133 1 Sec = get_node_uptime(),
134 1 {Acc, IQ#iq{type = result,
135 sub_el =
136 [#xmlel{name = <<"query">>,
137 attrs =
138 [{<<"xmlns">>, ?NS_LAST},
139 {<<"seconds">>,
140 integer_to_binary(Sec)}],
141 children = []}]}}
142 end.
143
144 -spec get_node_uptime() -> non_neg_integer().
145 get_node_uptime() ->
146 1 case mongoose_config:lookup_opt(node_start) of
147 {ok, {node_start, Seconds}} ->
148
:-(
erlang:system_time(second) - Seconds;
149 {error, not_found} ->
150 1 trunc(element(1, erlang:statistics(wall_clock))/1000)
151 end.
152
153 %%%
154 %%% Serve queries about user last online
155 %%%
156 -spec process_sm_iq(mongoose_acc:t(), jid:jid(), jid:jid(), jlib:iq(), map()) ->
157 {mongoose_acc:t(), jlib:iq()}.
158 process_sm_iq(Acc, _From, _To, #iq{type = set, sub_el = SubEl} = IQ, _Extra) ->
159
:-(
{Acc, IQ#iq{type = error, sub_el = [SubEl, mongoose_xmpp_errors:not_allowed()]}};
160 process_sm_iq(Acc, From, To, #iq{type = get, sub_el = SubEl} = IQ, _Extra) ->
161 3 HostType = mongoose_acc:host_type(Acc),
162 3 case can_respond(HostType, From, To) of
163 true ->
164 2 UserListRecord = mongoose_hooks:privacy_get_user_list(HostType, To),
165 2 {Res, Acc1} = mongoose_privacy:privacy_check_packet(Acc, To, UserListRecord, To, From, out),
166 2 {Acc1, make_response(HostType, IQ, SubEl, To, Res)};
167 false ->
168 1 {Acc, IQ#iq{type = error, sub_el = [SubEl, mongoose_xmpp_errors:forbidden()]}}
169 end.
170
171 can_respond(HostType, From, To) ->
172 5 {Subscription, _Groups} = mongoose_hooks:roster_get_jid_info(HostType, To, From),
173 5 MutualSubscription = Subscription =:= both,
174 5 RequesterSubscribedToTarget = Subscription =:= from,
175 5 QueryingSameUsersLast = jid:are_bare_equal(From, To),
176 5 MutualSubscription or RequesterSubscribedToTarget or QueryingSameUsersLast.
177
178 -spec make_response(mongooseim:host_type(), jlib:iq(), SubEl :: 'undefined' | [exml:element()],
179 jid:jid(), allow | deny) -> jlib:iq().
180 make_response(_HostType, IQ, SubEl, _, deny) ->
181
:-(
IQ#iq{type = error, sub_el = [SubEl, mongoose_xmpp_errors:forbidden()]};
182 make_response(HostType, IQ, SubEl, JID, allow) ->
183 2 #jid{luser = LUser, lserver = LServer} = JID,
184 2 case ejabberd_sm:get_user_resources(JID) of
185 [] ->
186 1 case get_last(HostType, LUser, LServer) of
187 {error, _Reason} ->
188
:-(
IQ#iq{type = error,
189 sub_el = [SubEl, mongoose_xmpp_errors:internal_server_error()]};
190 not_found ->
191
:-(
IQ#iq{type = error,
192 sub_el = [SubEl, mongoose_xmpp_errors:service_unavailable()]};
193 {ok, TimeStamp, Status} ->
194 1 TimeStamp2 = erlang:system_time(second),
195 1 Sec = TimeStamp2 - TimeStamp,
196 1 IQ#iq{type = result,
197 sub_el =
198 [#xmlel{name = <<"query">>,
199 attrs =
200 [{<<"xmlns">>, ?NS_LAST},
201 {<<"seconds">>,
202 integer_to_binary(Sec)}],
203 children = [{xmlcdata, Status}]}]}
204 end;
205 _ ->
206 1 IQ#iq{type = result,
207 sub_el =
208 [#xmlel{name = <<"query">>,
209 attrs =
210 [{<<"xmlns">>, ?NS_LAST},
211 {<<"seconds">>, <<"0">>}],
212 children = []}]}
213 end.
214
215 -spec get_last_info(mongooseim:host_type(), jid:luser(), jid:lserver())
216 -> 'not_found' | {'ok', timestamp(), status()}.
217 get_last_info(HostType, LUser, LServer) ->
218 34 case get_last(HostType, LUser, LServer) of
219
:-(
{error, _Reason} -> not_found;
220 34 Res -> Res
221 end.
222
223 %%%
224 %%% Hook handlers
225 %%%
226
227 -spec remove_user(Acc, Params, Extra) -> {ok, Acc} when
228 Acc :: mongoose_acc:t(),
229 Params :: #{jid := jid:jid()},
230 Extra :: gen_hook:extra().
231 remove_user(Acc, #{jid := #jid{luser = LUser, lserver = LServer}}, #{host_type := HostType}) ->
232 54 R = mod_last_backend:remove_user(HostType, LUser, LServer),
233 54 mongoose_lib:log_if_backend_error(R, ?MODULE, ?LINE, {Acc, LUser, LServer}),
234 54 {ok, Acc}.
235
236 -spec remove_domain(Acc, Params, Extra) -> {ok, Acc} when
237 Acc :: mongoose_hooks:simple_acc(),
238 Params :: #{domain := jid:lserver()},
239 Extra :: gen_hook:extra().
240 remove_domain(Acc, #{domain := Domain}, #{host_type := HostType}) ->
241
:-(
mod_last_backend:remove_domain(HostType, Domain),
242
:-(
{ok, Acc}.
243
244 -spec user_receive_iq(mongoose_acc:t(), mongoose_c2s_hooks:params(), gen_hook:extra()) ->
245 mongoose_c2s_hooks:result().
246 user_receive_iq(Acc, _Params, _Extra) ->
247 44 case mongoose_iq:info(Acc) of
248 {#iq{type = get, xmlns = ?NS_LAST}, Acc1} ->
249 2 maybe_forward_last(Acc1);
250 {_, Acc1} ->
251 42 {ok, Acc1}
252 end.
253
254 -spec maybe_forward_last(mongoose_acc:t()) -> mongoose_c2s_hooks:result().
255 maybe_forward_last(Acc) ->
256 2 HostType = mongoose_acc:host_type(Acc),
257 2 {From, To, _} = mongoose_acc:packet(Acc),
258 2 case can_respond(HostType, From, To) of
259 true ->
260 1 {ok, Acc};
261 false ->
262 1 {Acc1, Err} = jlib:make_error_reply(Acc, mongoose_xmpp_errors:forbidden()),
263 1 ejabberd_router:route(To, From, Acc1, Err),
264 1 {stop, Acc}
265 end.
266
267 -spec unset_presence(Acc, Params, Extra) -> {ok, Acc} when
268 Acc :: mongoose_acc:t(),
269 Params :: #{jid := jid:jid(), status := status()},
270 Extra :: gen_hook:extra().
271 unset_presence(Acc, #{jid := #jid{luser = LUser, lserver = LServer}, status := Status}, _) ->
272 29 {ok, store_last_info(Acc, LUser, LServer, Status)}.
273
274 -spec session_cleanup(Acc, Params, Extra) -> {ok, Acc} when
275 Acc :: mongoose_acc:t(),
276 Params :: #{jid := jid:jid()},
277 Extra :: gen_hook:extra().
278 session_cleanup(Acc, #{jid := #jid{luser = LUser, lserver = LServer}}, _) ->
279 345 {ok, session_cleanup(Acc, LUser, LServer, <<>>)}.
280
281 -spec sessions_cleanup(Acc, Params, Extra) -> {ok, Acc} when
282 Acc :: map(),
283 Params :: #{sessions := [ejabberd_sm:session()]},
284 Extra :: gen_hook:extra().
285 sessions_cleanup(Acc = #{host_type := HostType}, #{sessions := Sessions}, _) ->
286 1 mod_last_backend:sessions_cleanup(HostType, Sessions),
287 1 {ok, Acc}.
288
289 -spec store_last_info(mongoose_acc:t(), jid:luser(), jid:lserver(), status()) -> mongoose_acc:t().
290 store_last_info(Acc, LUser, LServer, Status) ->
291 29 HostType = mongoose_acc:host_type(Acc),
292 29 TimeStamp = erlang:system_time(second),
293 29 store_last_info(HostType, LUser, LServer, TimeStamp, Status),
294 29 Acc.
295
296 -spec session_cleanup(mongoose_acc:t(), jid:luser(), jid:lserver(), status()) -> mongoose_acc:t().
297 session_cleanup(Acc, LUser, LServer, Status) ->
298 345 HostType = mongoose_acc:host_type(Acc),
299 345 TimeStamp = erlang:system_time(second),
300 345 session_cleanup(HostType, LUser, LServer, TimeStamp, Status),
301 345 Acc.
302
303 -spec store_last_info(mongooseim:host_type(), jid:luser(), jid:lserver(),
304 timestamp(), status()) -> ok.
305 store_last_info(HostType, LUser, LServer, TimeStamp, Status) ->
306 66 case mod_last_backend:set_last_info(HostType, LUser, LServer, TimeStamp, Status) of
307 {error, Reason} ->
308
:-(
?LOG_ERROR(#{what => set_last_info_failed,
309 text => <<"Unexpected error while storing mod_last information">>,
310 user => LUser, server => LServer,
311 timestamp => TimeStamp, status => Status,
312
:-(
reason => Reason});
313 ok ->
314 66 ok
315 end.
316
317 -spec session_cleanup(mongooseim:host_type(), jid:luser(), jid:lserver(),
318 timestamp(), status()) -> ok.
319 session_cleanup(HostType, LUser, LServer, TimeStamp, Status) ->
320 345 case mod_last_backend:session_cleanup(HostType, LUser, LServer, TimeStamp, Status) of
321 {error, Reason} ->
322
:-(
?LOG_ERROR(#{what => session_cleanup_failed,
323 text => <<"Unexpected error while storing mod_last information">>,
324 user => LUser, server => LServer,
325 timestamp => TimeStamp, status => Status,
326
:-(
reason => Reason});
327 ok ->
328 345 ok
329 end.
330
331 -spec get_last(mongooseim:host_type(), jid:luser(), jid:lserver()) ->
332 {ok, timestamp(), status()} | {error, term()} | not_found.
333 get_last(HostType, LUser, LServer) ->
334 35 mod_last_backend:get_last(HostType, LUser, LServer).
335
336 -spec count_active_users(mongooseim:host_type(), jid:lserver(), timestamp()) -> non_neg_integer().
337 count_active_users(HostType, LServer, Timestamp) ->
338 6 mod_last_backend:count_active_users(HostType, LServer, Timestamp).
339
340 -spec config_metrics(mongooseim:host_type()) -> [{gen_mod:opt_key(), gen_mod:opt_value()}].
341 config_metrics(HostType) ->
342 12 mongoose_module_metrics:opts_for_module(HostType, ?MODULE, [backend]).
Line Hits Source