103
132
read_caps([], Result) ->
106
%% get_caps reads user caps from database
107
%% here we handle a simple retry loop, to avoid race condition
108
%% when asking caps while we still did not called note_caps
109
%% timeout is set to 10s
110
%% this is to be improved, but without altering performances.
111
%% if we did not get user presence 10s after getting presence_probe
112
%% we assume it has no caps
117
get_caps(LJID, Retry) ->
118
case catch mnesia:dirty_read({user_caps, jid_to_binary(LJID)}) of
119
[#user_caps{caps=waiting}] ->
121
get_caps(LJID, Retry-1);
122
[#user_caps{caps=Caps}] ->
128
%% clear_caps removes user caps from database
130
{U, S, R} = jlib:jid_tolower(JID),
131
BJID = jid_to_binary(JID),
132
BUID = jid_to_binary({U, S, []}),
133
catch mnesia:dirty_delete({user_caps, BJID}),
134
catch mnesia:dirty_delete_object(#user_caps_resources{uid = BUID, resource = list_to_binary(R)}),
137
%% give default user resource
138
get_user_resources(LUser, LServer) ->
139
case catch mnesia:dirty_read({user_caps_resources, jid_to_binary({LUser, LServer, []})}) of
143
lists:map(fun(#user_caps_resources{resource=R}) -> binary_to_list(R) end, Resources)
146
%% note_caps should be called to make the module request disco
147
%% information. Host is the host that asks, From is the full JID that
148
%% sent the caps packet, and Caps is what read_caps returned.
149
note_caps(Host, From, Caps) ->
150
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
151
gen_server:cast(Proc, {note_caps, From, Caps}).
153
%% wait_caps should be called just before note_caps
154
%% it allows to lock get_caps usage for code using presence_probe
155
%% that may run before we get any chance to note_caps.
156
wait_caps(Host, From) ->
157
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
158
gen_server:cast(Proc, {wait_caps, From}).
160
%% get_features returns a list of features implied by the given caps
161
%% record (as extracted by read_caps). It may block, and may signal a
163
get_features(Host, Caps) ->
168
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
169
gen_server:call(Proc, {get_features, Caps})
172
start_link(Host, Opts) ->
173
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
174
gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
177
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
180
{?MODULE, start_link, [Host, Opts]},
185
supervisor:start_child(ejabberd_sup, ChildSpec).
188
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
189
gen_server:call(Proc, stop).
191
receive_packet(From, To, {xmlelement, "presence", Attrs, Els}) ->
192
case xml:get_attr_s("type", Attrs) of
208
{_, S1, _} = jlib:jid_tolower(From),
209
case jlib:jid_tolower(To) of
211
_ -> clear_caps(From)
213
%% TODO: probe client, and clean only if no answers
214
%% as far as protocol does not allow inter-server communication to
215
%% let remote server handle it's own caps to decide which user is to be
216
%% notified, we must keep a cache of online status of external contacts
217
%% this is absolutely not scallable, but we have no choice for now
218
%% we can only use unavailable presence, but if remote user just remove a local user
219
%% from its roster, then it's considered as offline, so he does not receive local PEP
220
%% anymore until he login again.
221
%% This is tracked in EJAB-943
223
note_caps(To#jid.lserver, From, read_caps(Els))
135
%%====================================================================
137
%%====================================================================
138
user_send_packet(#jid{luser = User, lserver = Server} = From,
139
#jid{luser = User, lserver = Server, lresource = ""},
140
{xmlelement, "presence", Attrs, Els}) ->
141
Type = xml:get_attr_s("type", Attrs),
142
if Type == ""; Type == "available" ->
143
case read_caps(Els) of
146
#caps{version = Version, exts = Exts} = Caps ->
147
feature_request(Server, From, Caps, [Version | Exts])
225
receive_packet(_, _, _) ->
152
user_send_packet(_From, _To, _Packet) ->
228
receive_packet(_JID, From, To, Packet) ->
229
receive_packet(From, To, Packet).
231
presence_probe(From, To, _) ->
232
wait_caps(To#jid.lserver, From).
234
remove_connection(_SID, JID, _Info) ->
237
jid_to_binary(JID) ->
238
{U, S, R} = jlib:jid_tolower(JID),
239
list_to_binary(jlib:jid_to_string({U, S, R})).
241
caps_to_binary(#caps{node = Node, version = Version, exts = Exts}) ->
242
BExts = [list_to_binary(Ext) || Ext <- Exts],
243
#caps{node = list_to_binary(Node), version = list_to_binary(Version), exts = BExts}.
245
node_to_binary(Node, SubNode) ->
246
{list_to_binary(Node), list_to_binary(SubNode)}.
248
features_to_binary(L) -> [list_to_binary(I) || I <- L].
249
binary_to_features(L) -> [binary_to_list(I) || I <- L].
251
155
%%====================================================================
252
156
%% gen_server callbacks
253
157
%%====================================================================
255
158
init([Host, _Opts]) ->
256
159
mnesia:create_table(caps_features,
257
160
[{disc_copies, [node()]},
161
{local_content, true},
258
162
{attributes, record_info(fields, caps_features)}]),
259
mnesia:create_table(user_caps,
260
[{ram_copies, [node()]},
261
{attributes, record_info(fields, user_caps)}]),
262
mnesia:create_table(user_caps_resources,
263
[{ram_copies, [node()]},
265
{attributes, record_info(fields, user_caps_resources)}]),
266
mnesia:delete_table(user_caps_default),
267
mnesia:clear_table(user_caps), % clean in case of explicitely set to disc_copies
268
mnesia:clear_table(user_caps_resources), % clean in case of explicitely set to disc_copies
269
ejabberd_hooks:add(user_receive_packet, Host, ?MODULE, receive_packet, 30),
270
ejabberd_hooks:add(s2s_receive_packet, Host, ?MODULE, receive_packet, 30),
271
ejabberd_hooks:add(presence_probe_hook, Host, ?MODULE, presence_probe, 20),
272
ejabberd_hooks:add(sm_remove_connection_hook, Host, ?MODULE, remove_connection, 20),
163
mnesia:add_table_copy(caps_features, node(), disc_copies),
164
ejabberd_hooks:add(user_send_packet, Host,
165
?MODULE, user_send_packet, 75),
273
166
{ok, #state{host = Host}}.
275
maybe_get_features(#caps{node = Node, version = Version, exts = Exts}) ->
276
SubNodes = [Version | Exts],
277
%% Make sure that we have all nodes we need to know.
278
%% If a single one is missing, we wait for more disco
280
case lists:foldl(fun(SubNode, Acc) ->
284
case mnesia:dirty_read({caps_features, {Node, SubNode}}) of
286
[#caps_features{features = Features}] -> Features ++ Acc %% TODO binary
289
end, [], SubNodes) of
291
Features -> {ok, Features}
295
{MegaSecs, Secs, _MicroSecs} = now(),
296
MegaSecs * 1000000 + Secs.
298
handle_call({get_features, Caps}, From, State) ->
299
case maybe_get_features(Caps) of
301
{reply, binary_to_features(Features), State};
303
gen_server:cast(self(), visit_feature_queries),
304
Timeout = timestamp() + 10,
305
FeatureQueries = State#state.feature_queries,
306
NewFeatureQueries = [{From, Caps, Timeout} | FeatureQueries],
307
NewState = State#state{feature_queries = NewFeatureQueries},
311
168
handle_call(stop, _From, State) ->
312
{stop, normal, ok, State}.
314
handle_cast({note_caps, From, nothing}, State) ->
315
BJID = jid_to_binary(From),
316
catch mnesia:dirty_delete({user_caps, BJID}),
318
handle_cast({note_caps, From,
319
#caps{node = Node, version = Version, exts = Exts} = Caps},
320
#state{host = Host, disco_requests = Requests} = State) ->
321
%% XXX: this leads to race conditions where ejabberd will send
322
%% lots of caps disco requests.
323
{U, S, R} = jlib:jid_tolower(From),
324
BJID = jid_to_binary(From),
325
mnesia:transaction(fun() ->
326
mnesia:write(#user_caps{jid = BJID, caps = caps_to_binary(Caps)}),
327
case ejabberd_sm:get_user_resources(U, S) of
329
% only store resources of caps aware external contacts
330
BUID = jid_to_binary({U, S, []}),
331
mnesia:write(#user_caps_resources{uid = BUID, resource = list_to_binary(R)});
336
%% Now, find which of these are not already in the database.
337
SubNodes = [Version | Exts],
338
case lists:foldl(fun(SubNode, Acc) ->
339
case mnesia:dirty_read({caps_features, node_to_binary(Node, SubNode)}) of
345
end, [], SubNodes) of
349
%% For each unknown caps "subnode", we send a disco request.
350
NewRequests = lists:foldl(
351
fun(SubNode, Dict) ->
352
ID = randoms:get_string(),
357
[{xmlelement, "query",
358
[{"xmlns", ?NS_DISCO_INFO},
359
{"node", lists:concat([Node, "#", SubNode])}],
361
ejabberd_local:register_iq_response_handler
362
(Host, ID, ?MODULE, handle_disco_response),
363
ejabberd_router:route(jlib:make_jid("", Host, ""), From, Stanza),
364
timer:send_after(?CAPS_QUERY_TIMEOUT, self(), {disco_timeout, ID, BJID}),
365
?DICT:store(ID, node_to_binary(Node, SubNode), Dict)
366
end, Requests, Missing),
367
{noreply, State#state{disco_requests = NewRequests}}
369
handle_cast({wait_caps, From}, State) ->
370
BJID = jid_to_binary(From),
371
mnesia:dirty_write(#user_caps{jid = BJID, caps = waiting}),
373
handle_cast({disco_response, From, _To,
374
#iq{type = Type, id = ID,
376
#state{disco_requests = Requests} = State) ->
377
case {Type, SubEls} of
378
{result, [{xmlelement, "query", _Attrs, Els}]} ->
379
case ?DICT:find(ID, Requests) of
382
lists:flatmap(fun({xmlelement, "feature", FAttrs, _}) ->
383
[xml:get_attr_s("var", FAttrs)];
387
mnesia:dirty_write(#caps_features{node_pair = BinaryNode, features = features_to_binary(Features)}),
388
gen_server:cast(self(), visit_feature_queries);
390
?DEBUG("ID '~s' matches no query", [ID])
393
%% XXX: if we get error, we cache empty feature not to probe the client continuously
394
case ?DICT:find(ID, Requests) of
396
mnesia:dirty_write(#caps_features{node_pair = BinaryNode}),
397
gen_server:cast(self(), visit_feature_queries);
399
?DEBUG("ID '~s' matches no query", [ID])
401
%gen_server:cast(self(), visit_feature_queries),
402
%?DEBUG("Error IQ reponse from ~s:~n~p", [jlib:jid_to_string(From), SubEls]);
404
?DEBUG("Invalid IQ contents from ~s:~n~p", [jlib:jid_to_string(From), SubEls]);
406
%% Can't do anything about errors
409
NewRequests = ?DICT:erase(ID, Requests),
410
{noreply, State#state{disco_requests = NewRequests}};
411
handle_cast({disco_timeout, ID, BJID}, #state{host = Host, disco_requests = Requests} = State) ->
412
%% do not wait a response anymore for this IQ, client certainly will never answer
413
NewRequests = case ?DICT:is_key(ID, Requests) of
415
catch mnesia:dirty_delete({user_caps, BJID}),
416
ejabberd_local:unregister_iq_response_handler(Host, ID),
417
?DICT:erase(ID, Requests);
421
{noreply, State#state{disco_requests = NewRequests}};
422
handle_cast(visit_feature_queries, #state{feature_queries = FeatureQueries} = State) ->
423
Timestamp = timestamp(),
425
lists:foldl(fun({From, Caps, Timeout}, Acc) ->
426
case maybe_get_features(Caps) of
427
wait when Timeout > Timestamp -> [{From, Caps, Timeout} | Acc];
430
gen_server:reply(From, Features),
433
end, [], FeatureQueries),
434
{noreply, State#state{feature_queries = NewFeatureQueries}}.
436
handle_disco_response(From, To, IQ) ->
437
#jid{lserver = Host} = To,
438
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
439
gen_server:cast(Proc, {disco_response, From, To, IQ}).
169
{stop, normal, ok, State};
170
handle_call(_Req, _From, State) ->
171
{reply, {error, badarg}, State}.
173
handle_cast(_Msg, State) ->
441
176
handle_info(_Info, State) ->
442
177
{noreply, State}.
444
179
terminate(_Reason, State) ->
445
180
Host = State#state.host,
446
ejabberd_hooks:delete(user_receive_packet, Host, ?MODULE, receive_packet, 30),
447
ejabberd_hooks:delete(s2s_receive_packet, Host, ?MODULE, receive_packet, 30),
448
ejabberd_hooks:delete(presence_probe_hook, Host, ?MODULE, presence_probe, 20),
449
ejabberd_hooks:delete(sm_remove_connection_hook, Host, ?MODULE, remove_connection, 20),
181
ejabberd_hooks:delete(user_send_packet, Host,
182
?MODULE, user_send_packet, 75),
452
185
code_change(_OldVsn, State, _Extra) ->
188
%%====================================================================
190
%%====================================================================
191
feature_request(Host, From, Caps, [SubNode | Tail] = SubNodes) ->
192
Node = Caps#caps.node,
193
BinaryNode = node_to_binary(Node, SubNode),
194
case mnesia:dirty_read({caps_features, BinaryNode}) of
197
xmlns = ?NS_DISCO_INFO,
198
sub_el = [{xmlelement, "query",
199
[{"xmlns", ?NS_DISCO_INFO},
200
{"node", Node ++ "#" ++ SubNode}],
204
IQReply, Host, From, Caps, SubNodes)
206
ejabberd_local:route_iq(
207
jlib:make_jid("", Host, ""), From, IQ, F);
209
feature_request(Host, From, Caps, Tail)
211
feature_request(_Host, _From, _Caps, []) ->
214
feature_response(#iq{type = result,
215
sub_el = [{xmlelement, _, _, Els}]},
216
Host, From, Caps, [SubNode | SubNodes]) ->
217
Features = lists:flatmap(
218
fun({xmlelement, "feature", FAttrs, _}) ->
219
[xml:get_attr_s("var", FAttrs)];
223
BinaryNode = node_to_binary(Caps#caps.node, SubNode),
225
#caps_features{node_pair = BinaryNode,
226
features = features_to_binary(Features)}),
227
feature_request(Host, From, Caps, SubNodes);
228
feature_response(timeout, _Host, _From, _Caps, _SubNodes) ->
230
feature_response(_IQResult, Host, From, Caps, [SubNode | SubNodes]) ->
231
%% We got type=error or invalid type=result stanza, so
232
%% we cache empty feature not to probe the client permanently
233
BinaryNode = node_to_binary(Caps#caps.node, SubNode),
234
mnesia:dirty_write(#caps_features{node_pair = BinaryNode}),
235
feature_request(Host, From, Caps, SubNodes).
237
node_to_binary(Node, SubNode) ->
238
{list_to_binary(Node), list_to_binary(SubNode)}.
240
features_to_binary(L) -> [list_to_binary(I) || I <- L].
241
binary_to_features(L) -> [binary_to_list(I) || I <- L].