1 |
|
%%%---------------------------------------------------------------------- |
2 |
|
%%% File : ejabberd_c2s.erl |
3 |
|
%%% Author : Alexey Shchepin <alexey@process-one.net> |
4 |
|
%%% Purpose : Serve C2S connection |
5 |
|
%%% Created : 16 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 |
|
|
26 |
|
-module(ejabberd_c2s). |
27 |
|
-author('alexey@process-one.net'). |
28 |
|
-update_info({update, 0}). |
29 |
|
%% External exports |
30 |
|
-export([start/2, |
31 |
|
stop/1, |
32 |
|
terminate_session/2, |
33 |
|
start_link/2, |
34 |
|
send_text/2, |
35 |
|
socket_type/0, |
36 |
|
get_presence/1, |
37 |
|
get_aux_field/2, |
38 |
|
set_aux_field/3, |
39 |
|
del_aux_field/2, |
40 |
|
get_subscription/2, |
41 |
|
get_subscribed/1, |
42 |
|
send_filtered/5, |
43 |
|
store_session_info/4, |
44 |
|
remove_session_info/3, |
45 |
|
get_info/1, |
46 |
|
run_remote_hook/3, |
47 |
|
run_remote_hook_after/4]). |
48 |
|
|
49 |
|
%% gen_fsm callbacks |
50 |
|
-export([init/1, |
51 |
|
wait_for_stream/2, |
52 |
|
wait_for_feature_before_auth/2, |
53 |
|
wait_for_feature_after_auth/2, |
54 |
|
wait_for_session_or_sm/2, |
55 |
|
wait_for_sasl_response/2, |
56 |
|
session_established/2, session_established/3, |
57 |
|
resume_session/2, resume_session/3, |
58 |
|
handle_event/3, |
59 |
|
handle_sync_event/4, |
60 |
|
code_change/4, |
61 |
|
handle_info/3, |
62 |
|
terminate/4, |
63 |
|
print_state/1]). |
64 |
|
|
65 |
|
-ignore_xref([del_aux_field/2, get_info/1, print_state/1, resume_session/2, |
66 |
|
resume_session/3, send_text/2, session_established/2, session_established/3, |
67 |
|
socket_type/0, start_link/2, wait_for_feature_after_auth/2, |
68 |
|
wait_for_feature_before_auth/2, wait_for_sasl_response/2, |
69 |
|
wait_for_session_or_sm/2, wait_for_stream/2]). |
70 |
|
|
71 |
|
-include("mongoose.hrl"). |
72 |
|
-include("ejabberd_c2s.hrl"). |
73 |
|
-include("jlib.hrl"). |
74 |
|
-include_lib("exml/include/exml.hrl"). |
75 |
|
-xep([{xep, 18}, {version, "0.2"}]). |
76 |
|
-behaviour(p1_fsm_old). |
77 |
|
|
78 |
|
-export_type([broadcast/0, packet/0, state/0]). |
79 |
|
|
80 |
|
-type packet() :: {jid:jid(), jid:jid(), exml:element()}. |
81 |
|
-type sock_data() :: term(). |
82 |
|
-type start_result() :: {error, _} |
83 |
|
| {ok, undefined | pid()} |
84 |
|
| {ok, undefined | pid(), _}. |
85 |
|
|
86 |
|
%%%---------------------------------------------------------------------- |
87 |
|
%%% API |
88 |
|
%%%---------------------------------------------------------------------- |
89 |
|
|
90 |
|
-spec start(sock_data(), ejabberd_listener:opts()) -> start_result(). |
91 |
|
start(SockData, Opts) -> |
92 |
4360 |
?SUPERVISOR_START(SockData, Opts). |
93 |
|
|
94 |
|
-spec start_link(sock_data(), ejabberd_listener:opts()) -> start_result(). |
95 |
|
start_link(SockData, Opts) -> |
96 |
4360 |
p1_fsm_old:start_link(ejabberd_c2s, {SockData, Opts}, |
97 |
|
?FSMOPTS ++ fsm_limit_opts(Opts)). |
98 |
|
|
99 |
|
socket_type() -> |
100 |
4366 |
xml_stream. |
101 |
|
|
102 |
|
%% @doc Return Username, Resource and presence information |
103 |
|
get_presence(FsmRef) -> |
104 |
22 |
p1_fsm_old:sync_send_all_state_event(FsmRef, get_presence, 1000). |
105 |
|
|
106 |
|
get_info(FsmRef) -> |
107 |
11 |
p1_fsm_old:sync_send_all_state_event(FsmRef, get_info, 5000). |
108 |
|
|
109 |
|
|
110 |
|
-spec get_aux_field(Key :: aux_key(), |
111 |
|
State :: state()) -> 'error' | {'ok', aux_value()}. |
112 |
|
get_aux_field(Key, #state{aux_fields = Opts}) -> |
113 |
93 |
case lists:keyfind(Key, 1, Opts) of |
114 |
|
{_, Val} -> |
115 |
39 |
{ok, Val}; |
116 |
|
_ -> |
117 |
54 |
error |
118 |
|
end. |
119 |
|
|
120 |
|
|
121 |
|
-spec set_aux_field(Key :: aux_key(), |
122 |
|
Val :: aux_value(), |
123 |
|
State :: state()) -> state(). |
124 |
|
set_aux_field(Key, Val, #state{aux_fields = Opts} = State) -> |
125 |
79 |
Opts1 = lists:keydelete(Key, 1, Opts), |
126 |
79 |
State#state{aux_fields = [{Key, Val}|Opts1]}. |
127 |
|
|
128 |
|
|
129 |
|
-spec del_aux_field(Key :: aux_key(), State :: state()) -> aux_value(). |
130 |
|
del_aux_field(Key, #state{aux_fields = Opts} = State) -> |
131 |
:-( |
Opts1 = lists:keydelete(Key, 1, Opts), |
132 |
:-( |
State#state{aux_fields = Opts1}. |
133 |
|
|
134 |
|
|
135 |
|
-spec get_subscription(From :: jid:jid(), State :: state()) -> |
136 |
|
both | from | 'none' | 'to'. |
137 |
|
get_subscription(From = #jid{}, StateData) -> |
138 |
165 |
{LFrom, LBFrom} = lowcase_and_bare(From), |
139 |
165 |
F = is_subscribed_to_my_presence(LFrom, LBFrom, StateData), |
140 |
165 |
T = am_i_subscribed_to_presence(LFrom, LBFrom, StateData), |
141 |
165 |
case {F, T} of |
142 |
131 |
{true, true} -> both; |
143 |
2 |
{true, _} -> from; |
144 |
16 |
{_, true} -> to; |
145 |
16 |
_ -> none |
146 |
|
end. |
147 |
|
|
148 |
|
send_filtered(FsmRef, Feature, From, To, Packet) -> |
149 |
4 |
FsmRef ! {send_filtered, Feature, From, To, Packet}. |
150 |
|
|
151 |
|
%% @doc Stops the session gracefully, entering resume state if applicable |
152 |
|
stop(FsmRef) -> |
153 |
134 |
p1_fsm_old:send_event(FsmRef, closed). |
154 |
|
|
155 |
|
%% @doc terminates the session immediately and unconditionally, sending the user a stream conflict |
156 |
|
%% error specifying the reason |
157 |
|
terminate_session(none, _Reason) -> |
158 |
3 |
no_session; |
159 |
|
terminate_session(#jid{} = Jid, Reason) -> |
160 |
10 |
terminate_session(ejabberd_sm:get_session_pid(Jid), Reason); |
161 |
|
terminate_session(Pid, Reason) when is_pid(Pid) -> |
162 |
32 |
Pid ! {exit, Reason}. |
163 |
|
|
164 |
|
store_session_info(FsmRef, JID, Key, Value) -> |
165 |
86 |
FsmRef ! {store_session_info, JID, Key, Value, self()}. |
166 |
|
|
167 |
|
remove_session_info(FsmRef, JID, Key) -> |
168 |
19 |
FsmRef ! {remove_session_info, JID, Key, self()}. |
169 |
|
|
170 |
|
run_remote_hook(Pid, HandlerName, Args) -> |
171 |
98 |
Pid ! {run_remote_hook, HandlerName, Args}. |
172 |
|
|
173 |
|
run_remote_hook_after(Delay, Pid, HandlerName, Args) -> |
174 |
76 |
erlang:send_after(Delay, Pid, {run_remote_hook, HandlerName, Args}). |
175 |
|
|
176 |
|
%%%---------------------------------------------------------------------- |
177 |
|
%%% Callback functions from gen_fsm |
178 |
|
%%%---------------------------------------------------------------------- |
179 |
|
|
180 |
|
-spec init({sock_data(), ejabberd_listener:opts()}) -> |
181 |
|
{stop, normal} | {ok, wait_for_stream, state(), non_neg_integer()}. |
182 |
|
init({{SockMod, Socket}, Opts}) -> |
183 |
4360 |
Access = case lists:keyfind(access, 1, Opts) of |
184 |
4193 |
{_, A} -> A; |
185 |
167 |
_ -> all |
186 |
|
end, |
187 |
4360 |
Shaper = case lists:keyfind(shaper, 1, Opts) of |
188 |
4193 |
{_, S} -> S; |
189 |
167 |
_ -> none |
190 |
|
end, |
191 |
4360 |
XMLSocket = |
192 |
|
case lists:keyfind(xml_socket, 1, Opts) of |
193 |
167 |
{_, XS} -> XS; |
194 |
4193 |
_ -> false |
195 |
|
end, |
196 |
4360 |
Zlib = case lists:keyfind(zlib, 1, Opts) of |
197 |
4035 |
{_, ZlibLimit} -> {true, ZlibLimit}; |
198 |
325 |
_ -> {false, 0} |
199 |
|
end, |
200 |
4360 |
Verify = case lists:member(verify_peer, Opts) of |
201 |
60 |
true -> verify_peer; |
202 |
4300 |
false -> verify_none |
203 |
|
end, |
204 |
4360 |
HibernateAfter = |
205 |
|
case lists:keyfind(hibernate_after, 1, Opts) of |
206 |
:-( |
{_, HA} -> HA; |
207 |
4360 |
_ -> 0 |
208 |
|
end, |
209 |
4360 |
StartTLS = lists:member(starttls, Opts) orelse Verify =:= verify_peer, |
210 |
4360 |
StartTLSRequired = lists:member(starttls_required, Opts), |
211 |
4360 |
TLSEnabled = lists:member(tls, Opts), |
212 |
4360 |
TLS = StartTLS orelse StartTLSRequired orelse TLSEnabled, |
213 |
4360 |
TLSOpts1 = |
214 |
3993 |
lists:filter(fun({certfile, _}) -> true; |
215 |
20 |
({cafile, _}) -> true; |
216 |
317 |
({ciphers, _}) -> true; |
217 |
:-( |
({protocol_options, _}) -> true; |
218 |
3834 |
({dhfile, _}) -> true; |
219 |
902 |
({tls_module, _}) -> true; |
220 |
199 |
({ssl_options, _}) -> true; |
221 |
50516 |
(_) -> false |
222 |
|
end, Opts), |
223 |
4360 |
TLSOpts = verify_opts(Verify) ++ TLSOpts1, |
224 |
4360 |
[ssl_crl_cache:insert({file, CRL}) || CRL <- proplists:get_value(crlfiles, Opts, [])], |
225 |
4360 |
IP = peerip(SockMod, Socket), |
226 |
|
%% Check if IP is blacklisted: |
227 |
4360 |
case is_ip_blacklisted(IP) of |
228 |
|
true -> |
229 |
:-( |
?LOG_INFO(#{what => c2s_blacklisted_ip, ip => IP, |
230 |
:-( |
text => <<"Connection attempt from blacklisted IP">>}), |
231 |
:-( |
{stop, normal}; |
232 |
|
false -> |
233 |
4360 |
Socket1 = |
234 |
|
case TLSEnabled of |
235 |
964 |
true -> mongoose_transport:starttls(SockMod, Socket, TLSOpts); |
236 |
3396 |
false -> Socket |
237 |
|
end, |
238 |
3910 |
SocketMonitor = mongoose_transport:monitor(SockMod, Socket1), |
239 |
3910 |
CredOpts = mongoose_credentials:make_opts(Opts), |
240 |
3910 |
{ok, wait_for_stream, #state{server = ?MYNAME, |
241 |
|
socket = Socket1, |
242 |
|
sockmod = SockMod, |
243 |
|
socket_monitor = SocketMonitor, |
244 |
|
xml_socket = XMLSocket, |
245 |
|
zlib = Zlib, |
246 |
|
tls = TLS, |
247 |
|
tls_required = StartTLSRequired, |
248 |
|
tls_enabled = TLSEnabled, |
249 |
|
tls_options = TLSOpts, |
250 |
|
tls_verify = Verify, |
251 |
|
streamid = new_id(), |
252 |
|
access = Access, |
253 |
|
shaper = Shaper, |
254 |
|
ip = IP, |
255 |
|
lang = ?MYLANG, |
256 |
|
hibernate_after= HibernateAfter, |
257 |
|
cred_opts = CredOpts |
258 |
|
}, |
259 |
|
?C2S_OPEN_TIMEOUT} |
260 |
|
end. |
261 |
|
|
262 |
|
%% @doc Return list of all available resources of contacts, |
263 |
|
get_subscribed(FsmRef) -> |
264 |
6 |
p1_fsm_old:sync_send_all_state_event(FsmRef, get_subscribed, 1000). |
265 |
|
|
266 |
|
%%---------------------------------------------------------------------- |
267 |
|
%% Func: StateName/2 |
268 |
|
%%---------------------------------------------------------------------- |
269 |
|
|
270 |
|
-spec wait_for_stream(Item :: ejabberd:xml_stream_item(), |
271 |
|
StateData :: state()) -> fsm_return(). |
272 |
|
wait_for_stream({xmlstreamstart, _Name, _} = StreamStart, StateData) -> |
273 |
6541 |
handle_stream_start(StreamStart, StateData); |
274 |
|
wait_for_stream(timeout, StateData) -> |
275 |
:-( |
{stop, normal, StateData}; |
276 |
|
wait_for_stream(closed, StateData) -> |
277 |
516 |
{stop, normal, StateData}; |
278 |
|
wait_for_stream(_UnexpectedItem, #state{server = Server} = StateData) -> |
279 |
4 |
case mongoose_config:get_opt(hide_service_name, false) of |
280 |
|
true -> |
281 |
1 |
{stop, normal, StateData}; |
282 |
|
false -> |
283 |
3 |
send_header(StateData, Server, << "1.0">>, <<"">>), |
284 |
3 |
c2s_stream_error(mongoose_xmpp_errors:xml_not_well_formed(), StateData) |
285 |
|
end. |
286 |
|
|
287 |
|
handle_stream_start({xmlstreamstart, _Name, Attrs}, #state{} = S0) -> |
288 |
6541 |
Server = jid:nameprep(xml:get_attr_s(<<"to">>, Attrs)), |
289 |
6541 |
Lang = get_xml_lang(Attrs), |
290 |
6541 |
S1 = S0#state{server = Server, lang = Lang}, |
291 |
6541 |
case {xml:get_attr_s(<<"xmlns:stream">>, Attrs), |
292 |
|
mongoose_domain_api:get_domain_host_type(Server)} of |
293 |
|
{?NS_STREAM, {ok, HostType}} -> |
294 |
6537 |
StreamMgmtConfig = case gen_mod:is_loaded(HostType, mod_stream_management) of |
295 |
6519 |
true -> false; |
296 |
18 |
_ -> disabled |
297 |
|
end, |
298 |
6537 |
S = S1#state{host_type = HostType, stream_mgmt = StreamMgmtConfig}, |
299 |
6537 |
change_shaper(S, jid:make_noprep(<<>>, Server, <<>>)), |
300 |
6537 |
Version = xml:get_attr_s(<<"version">>, Attrs), |
301 |
6537 |
stream_start_by_protocol_version(Version, S); |
302 |
|
{?NS_STREAM, {error, not_found}} -> |
303 |
2 |
stream_start_error(mongoose_xmpp_errors:host_unknown(), S1); |
304 |
|
{_InvalidNS, _} -> |
305 |
2 |
stream_start_error(mongoose_xmpp_errors:invalid_namespace(), S1) |
306 |
|
end. |
307 |
|
|
308 |
|
stream_start_error(Error, StateData) -> |
309 |
6 |
send_header(StateData, ?MYNAME, <<"">>, ?MYLANG), |
310 |
6 |
c2s_stream_error(Error, StateData). |
311 |
|
|
312 |
|
-spec c2s_stream_error(Error, State) -> Result when |
313 |
|
Error :: exml:element(), |
314 |
|
State :: state(), |
315 |
|
Result :: {stop, normal, state()}. |
316 |
|
c2s_stream_error(Error, StateData) -> |
317 |
24 |
?LOG_DEBUG(#{what => c2s_stream_error, xml_error => Error, c2s_state => StateData}), |
318 |
24 |
send_element_from_server_jid(StateData, Error), |
319 |
23 |
send_trailer(StateData), |
320 |
23 |
{stop, normal, StateData}. |
321 |
|
|
322 |
|
%% See RFC 6120 4.3.2: |
323 |
|
%% |
324 |
|
%% If the initiating entity includes in the initial stream header |
325 |
|
%% the 'version' attribute set to a value of at least <<"1.0">> [...] |
326 |
|
%% receiving entity MUST send a <features/> child element [...] |
327 |
|
%% |
328 |
|
%% (http://xmpp.org/rfcs/rfc6120.html#streams-negotiation-features) |
329 |
|
stream_start_by_protocol_version(<<"1.0">>, #state{} = S) -> |
330 |
6535 |
stream_start_negotiate_features(S); |
331 |
|
stream_start_by_protocol_version(_Pre1_0, S) -> |
332 |
2 |
stream_start_error(mongoose_xmpp_errors:unsupported_version(), S). |
333 |
|
|
334 |
|
stream_start_negotiate_features(#state{} = S) -> |
335 |
6535 |
send_header(S, S#state.server, <<"1.0">>, ?MYLANG), |
336 |
6535 |
case {S#state.authenticated, S#state.resource} of |
337 |
|
{false, _} -> |
338 |
3473 |
stream_start_features_before_auth(S); |
339 |
|
{_, <<>>} -> |
340 |
3060 |
stream_start_features_after_auth(S); |
341 |
|
{_, _} -> |
342 |
2 |
send_element_from_server_jid(S, #xmlel{name = <<"stream:features">>}), |
343 |
2 |
fsm_next_state(wait_for_session_or_sm, S) |
344 |
|
end. |
345 |
|
|
346 |
|
stream_start_features_before_auth(#state{server = Server, |
347 |
|
host_type = HostType, |
348 |
|
cred_opts = CredOpts} = S) -> |
349 |
3473 |
Creds0 = mongoose_credentials:new(Server, HostType, CredOpts), |
350 |
3473 |
Creds = maybe_add_cert(Creds0, S), |
351 |
3473 |
SASLState = cyrsasl:server_new(<<"jabber">>, Server, HostType, <<>>, [], Creds), |
352 |
3473 |
SockMod = (S#state.sockmod):get_sockmod(S#state.socket), |
353 |
3473 |
send_element_from_server_jid(S, stream_features(determine_features(SockMod, S))), |
354 |
3473 |
fsm_next_state(wait_for_feature_before_auth, |
355 |
|
S#state{sasl_state = SASLState}). |
356 |
|
|
357 |
|
stream_start_features_after_auth(#state{} = S) -> |
358 |
3060 |
SockMod = (S#state.sockmod):get_sockmod(S#state.socket), |
359 |
3060 |
Features = (maybe_compress_feature(SockMod, S) |
360 |
|
++ [#xmlel{name = <<"bind">>, |
361 |
|
attrs = [{<<"xmlns">>, ?NS_BIND}]}, |
362 |
|
#xmlel{name = <<"session">>, |
363 |
|
attrs = [{<<"xmlns">>, ?NS_SESSION}]}] |
364 |
|
++ maybe_roster_versioning_feature(S) |
365 |
|
++ hook_enabled_features(S) ), |
366 |
3060 |
send_element_from_server_jid(S, stream_features(Features)), |
367 |
3060 |
fsm_next_state(wait_for_feature_after_auth, S). |
368 |
|
|
369 |
|
maybe_roster_versioning_feature(#state{host_type = HostType}) -> |
370 |
3060 |
mongoose_hooks:roster_get_versioning_feature(HostType). |
371 |
|
|
372 |
|
stream_features(FeatureElements) -> |
373 |
6533 |
#xmlel{name = <<"stream:features">>, |
374 |
|
children = FeatureElements}. |
375 |
|
|
376 |
|
%% From RFC 6120, section 5.3.1: |
377 |
|
%% |
378 |
|
%% If TLS is mandatory-to-negotiate, the receiving entity SHOULD NOT |
379 |
|
%% advertise support for any stream feature except STARTTLS during the |
380 |
|
%% initial stage of the stream negotiation process, because further stream |
381 |
|
%% features might depend on prior negotiation of TLS given the order of |
382 |
|
%% layers in XMPP (e.g., the particular SASL mechanisms offered by the |
383 |
|
%% receiving entity will likely depend on whether TLS has been negotiated). |
384 |
|
%% |
385 |
|
%% http://xmpp.org/rfcs/rfc6120.html#tls-rules-mtn |
386 |
|
determine_features(SockMod, #state{tls = TLS, tls_enabled = TLSEnabled, |
387 |
|
tls_required = TLSRequired} = S) -> |
388 |
3473 |
OtherFeatures = maybe_compress_feature(SockMod, S) |
389 |
|
++ maybe_sasl_mechanisms(S) |
390 |
|
++ hook_enabled_features(S), |
391 |
3473 |
case can_use_tls(SockMod, TLS, TLSEnabled) of |
392 |
|
true -> |
393 |
3216 |
case TLSRequired of |
394 |
30 |
true -> [starttls_stanza(required)]; |
395 |
3186 |
_ -> [starttls_stanza(optional)] ++ OtherFeatures |
396 |
|
end; |
397 |
|
false -> |
398 |
257 |
OtherFeatures |
399 |
|
end. |
400 |
|
|
401 |
|
maybe_compress_feature(SockMod, #state{zlib = {ZLib, _}}) -> |
402 |
6533 |
case can_use_zlib_compression(ZLib, SockMod) of |
403 |
5926 |
true -> [compression_zlib()]; |
404 |
607 |
_ -> [] |
405 |
|
end. |
406 |
|
|
407 |
|
maybe_sasl_mechanisms(#state{host_type = HostType} = S) -> |
408 |
3473 |
case cyrsasl:listmech(HostType) of |
409 |
:-( |
[] -> []; |
410 |
|
Mechanisms -> |
411 |
3473 |
[#xmlel{name = <<"mechanisms">>, |
412 |
|
attrs = [{<<"xmlns">>, ?NS_SASL}], |
413 |
7474 |
children = [ mechanism(M) || M <- Mechanisms, filter_mechanism(M, S) ]}] |
414 |
|
end. |
415 |
|
|
416 |
|
hook_enabled_features(#state{host_type = HostType, server = LServer}) -> |
417 |
6533 |
mongoose_hooks:c2s_stream_features(HostType, LServer). |
418 |
|
|
419 |
|
starttls_stanza(TLSRequired) |
420 |
|
when TLSRequired =:= required; |
421 |
|
TLSRequired =:= optional -> |
422 |
3216 |
#xmlel{name = <<"starttls">>, |
423 |
|
attrs = [{<<"xmlns">>, ?NS_TLS}], |
424 |
30 |
children = [ #xmlel{name = <<"required">>} || TLSRequired =:= required ]}. |
425 |
|
|
426 |
|
can_use_tls(SockMod, TLS, TLSEnabled) -> |
427 |
3473 |
TLS == true andalso (TLSEnabled == false) andalso SockMod == gen_tcp. |
428 |
|
|
429 |
|
can_use_zlib_compression(Zlib, SockMod) -> |
430 |
6533 |
Zlib andalso ( (SockMod == gen_tcp) orelse |
431 |
146 |
(SockMod == ejabberd_tls) ). |
432 |
|
|
433 |
|
compression_zlib() -> |
434 |
5926 |
#xmlel{name = <<"compression">>, |
435 |
|
attrs = [{<<"xmlns">>, ?NS_FEATURE_COMPRESS}], |
436 |
|
children = [#xmlel{name = <<"method">>, |
437 |
|
children = [#xmlcdata{content = <<"zlib">>}]}]}. |
438 |
|
|
439 |
|
mechanism(S) -> |
440 |
7474 |
#xmlel{name = <<"mechanism">>, |
441 |
|
children = [#xmlcdata{content = S}]}. |
442 |
|
|
443 |
|
filter_mechanism(<<"EXTERNAL">>, S) -> |
444 |
192 |
case get_peer_cert(S) of |
445 |
70 |
error -> false; |
446 |
122 |
_ -> true |
447 |
|
end; |
448 |
|
filter_mechanism(<<"SCRAM-SHA-1-PLUS">>, S) -> |
449 |
294 |
is_channel_binding_supported(S); |
450 |
|
filter_mechanism(<<"SCRAM-SHA-", _N:3/binary, "-PLUS">>, S) -> |
451 |
7242 |
is_channel_binding_supported(S); |
452 |
13924 |
filter_mechanism(_, _) -> true. |
453 |
|
|
454 |
|
is_channel_binding_supported(State) -> |
455 |
7536 |
Socket = State#state.socket, |
456 |
7536 |
SockMod = (State#state.sockmod):get_sockmod(Socket), |
457 |
7536 |
is_fast_tls_configured(SockMod, Socket). |
458 |
|
|
459 |
|
is_fast_tls_configured(ejabberd_tls, Socket) -> |
460 |
94 |
fast_tls == ejabberd_tls:get_sockmod(ejabberd_socket:get_socket(Socket)); |
461 |
|
is_fast_tls_configured(_, _) -> |
462 |
7442 |
false. |
463 |
|
|
464 |
|
get_xml_lang(Attrs) -> |
465 |
6541 |
case xml:get_attr_s(<<"xml:lang">>, Attrs) of |
466 |
|
Lang when size(Lang) =< 35 -> |
467 |
|
%% As stated in BCP47, 4.4.1: |
468 |
|
%% Protocols or specifications that |
469 |
|
%% specify limited buffer sizes for |
470 |
|
%% language tags MUST allow for |
471 |
|
%% language tags of at least 35 characters. |
472 |
6541 |
Lang; |
473 |
|
_ -> |
474 |
|
%% Do not store long language tag to |
475 |
|
%% avoid possible DoS/flood attacks |
476 |
:-( |
<<>> |
477 |
|
end. |
478 |
|
|
479 |
4300 |
verify_opts(verify_none) -> [verify_none]; |
480 |
60 |
verify_opts(verify_peer) -> []. |
481 |
|
|
482 |
|
-spec get_peer_cert(state()) -> any() | error. |
483 |
|
get_peer_cert(#state{socket = Socket, |
484 |
|
sockmod = SockMod }) -> |
485 |
3665 |
case mongoose_transport:get_peer_certificate(SockMod, Socket) of |
486 |
183 |
{ok, Cert} -> Cert; |
487 |
3482 |
_ -> error |
488 |
|
end. |
489 |
|
|
490 |
|
maybe_add_cert(Creds, S) -> |
491 |
3473 |
case get_peer_cert(S) of |
492 |
3412 |
error -> Creds; |
493 |
61 |
Cert -> mongoose_credentials:set(Creds, client_cert, Cert) |
494 |
|
end. |
495 |
|
|
496 |
|
-spec wait_for_feature_before_auth(Item :: ejabberd:xml_stream_item(), |
497 |
|
State :: state()) -> fsm_return(). |
498 |
|
wait_for_feature_before_auth({xmlstreamelement, |
499 |
|
#xmlel{name = <<"enable">>} = El}, StateData) -> |
500 |
1 |
maybe_unexpected_sm_request(wait_for_feature_before_auth, El, StateData); |
501 |
|
wait_for_feature_before_auth({xmlstreamelement, El}, StateData) -> |
502 |
3588 |
#xmlel{name = Name, attrs = Attrs, children = Els} = El, |
503 |
3588 |
{Zlib, _} = StateData#state.zlib, |
504 |
3588 |
TLS = StateData#state.tls, |
505 |
3588 |
TLSEnabled = StateData#state.tls_enabled, |
506 |
3588 |
TLSRequired = StateData#state.tls_required, |
507 |
3588 |
SockMod = (StateData#state.sockmod):get_sockmod(StateData#state.socket), |
508 |
3588 |
case {xml:get_attr_s(<<"xmlns">>, Attrs), Name} of |
509 |
|
{?NS_SASL, <<"auth">>} when TLSEnabled or not TLSRequired -> |
510 |
3103 |
Mech = xml:get_attr_s(<<"mechanism">>, Attrs), |
511 |
3103 |
ClientIn = jlib:decode_base64(xml:get_cdata(Els)), |
512 |
3103 |
SaslState = StateData#state.sasl_state, |
513 |
3103 |
HostType = StateData#state.host_type, |
514 |
3103 |
AuthMech = [M || M <- cyrsasl:listmech(HostType), filter_mechanism(M, StateData)], |
515 |
3103 |
SocketData = #{socket => StateData#state.socket, auth_mech => AuthMech}, |
516 |
3103 |
StepResult = cyrsasl:server_start(SaslState, Mech, ClientIn, SocketData), |
517 |
3103 |
{NewFSMState, NewStateData} = handle_sasl_step(StateData, StepResult), |
518 |
3101 |
fsm_next_state(NewFSMState, NewStateData); |
519 |
|
{?NS_TLS, <<"starttls">>} when TLS == true, |
520 |
|
TLSEnabled == false, |
521 |
|
SockMod == gen_tcp -> |
522 |
91 |
TLSOpts = case mongoose_config:lookup_opt([domain_certfile, StateData#state.host_type]) of |
523 |
|
{error, not_found} -> |
524 |
91 |
StateData#state.tls_options; |
525 |
|
{ok, CertFile} -> |
526 |
:-( |
lists:keystore(certfile, 1, StateData#state.tls_options, {certfile, CertFile}) |
527 |
|
end, |
528 |
91 |
TLSSocket = mongoose_transport:starttls(StateData#state.sockmod, |
529 |
|
StateData#state.socket, |
530 |
|
TLSOpts, exml:to_binary(tls_proceed())), |
531 |
89 |
fsm_next_state(wait_for_stream, |
532 |
|
StateData#state{socket = TLSSocket, |
533 |
|
streamid = new_id(), |
534 |
|
tls_enabled = true |
535 |
|
}); |
536 |
|
{?NS_COMPRESS, <<"compress">>} when Zlib == true, |
537 |
|
((SockMod == gen_tcp) or |
538 |
|
(SockMod == ejabberd_tls)) -> |
539 |
6 |
check_compression_auth(El, wait_for_feature_before_auth, StateData); |
540 |
|
_ -> |
541 |
388 |
terminate_when_tls_required_but_not_enabled(TLSRequired, TLSEnabled, |
542 |
|
StateData, El) |
543 |
|
end; |
544 |
|
wait_for_feature_before_auth(timeout, StateData) -> |
545 |
:-( |
{stop, normal, StateData}; |
546 |
|
wait_for_feature_before_auth({xmlstreamend, _Name}, StateData) -> |
547 |
264 |
send_trailer(StateData), |
548 |
264 |
{stop, normal, StateData}; |
549 |
|
wait_for_feature_before_auth({xmlstreamerror, _}, StateData) -> |
550 |
:-( |
c2s_stream_error(mongoose_xmpp_errors:xml_not_well_formed(), StateData); |
551 |
|
wait_for_feature_before_auth(closed, StateData) -> |
552 |
55 |
{stop, normal, StateData}; |
553 |
|
wait_for_feature_before_auth(_, StateData) -> |
554 |
3 |
c2s_stream_error(mongoose_xmpp_errors:policy_violation(), StateData). |
555 |
|
|
556 |
|
compressed() -> |
557 |
10 |
#xmlel{name = <<"compressed">>, |
558 |
|
attrs = [{<<"xmlns">>, ?NS_COMPRESS}]}. |
559 |
|
|
560 |
|
compress_unsupported_method() -> |
561 |
:-( |
#xmlel{name = <<"failure">>, |
562 |
|
attrs = [{<<"xmlns">>, ?NS_COMPRESS}], |
563 |
|
children = [#xmlel{name = <<"unsupported-method">>}]}. |
564 |
|
|
565 |
|
tls_proceed() -> |
566 |
91 |
#xmlel{name = <<"proceed">>, |
567 |
|
attrs = [{<<"xmlns">>, ?NS_TLS}]}. |
568 |
|
|
569 |
|
compress_setup_failed() -> |
570 |
6 |
#xmlel{name = <<"failure">>, |
571 |
|
attrs = [{<<"xmlns">>, ?NS_COMPRESS}], |
572 |
|
children = [#xmlel{name = <<"setup-failed">>}]}. |
573 |
|
|
574 |
|
-spec wait_for_sasl_response(Item :: ejabberd:xml_stream_item(), |
575 |
|
State :: state()) -> fsm_return(). |
576 |
|
wait_for_sasl_response({xmlstreamelement, |
577 |
|
#xmlel{name = <<"enable">>} = El}, StateData) -> |
578 |
:-( |
maybe_unexpected_sm_request(wait_for_sasl_response, El, StateData); |
579 |
|
wait_for_sasl_response({xmlstreamelement, El}, StateData) -> |
580 |
13 |
#xmlel{name = Name, attrs = Attrs, children = Els} = El, |
581 |
13 |
case {xml:get_attr_s(<<"xmlns">>, Attrs), Name} of |
582 |
|
{?NS_SASL, <<"response">>} -> |
583 |
13 |
ClientIn = jlib:decode_base64(xml:get_cdata(Els)), |
584 |
13 |
StepResult = cyrsasl:server_step(StateData#state.sasl_state, ClientIn), |
585 |
13 |
{NewFSMState, NewStateData} = handle_sasl_step(StateData, StepResult), |
586 |
13 |
fsm_next_state(NewFSMState, NewStateData); |
587 |
|
_ -> |
588 |
:-( |
process_unauthenticated_stanza(StateData, El), |
589 |
:-( |
fsm_next_state(wait_for_feature_before_auth, StateData) |
590 |
|
end; |
591 |
|
wait_for_sasl_response(timeout, StateData) -> |
592 |
:-( |
{stop, normal, StateData}; |
593 |
|
wait_for_sasl_response({xmlstreamend, _Name}, StateData) -> |
594 |
:-( |
send_trailer(StateData), |
595 |
:-( |
{stop, normal, StateData}; |
596 |
|
wait_for_sasl_response({xmlstreamerror, _}, StateData) -> |
597 |
:-( |
c2s_stream_error(mongoose_xmpp_errors:xml_not_well_formed(), StateData); |
598 |
|
wait_for_sasl_response(closed, StateData) -> |
599 |
:-( |
{stop, normal, StateData}. |
600 |
|
|
601 |
|
-spec wait_for_feature_after_auth(Item :: ejabberd:xml_stream_item(), |
602 |
|
State :: state()) -> fsm_return(). |
603 |
|
wait_for_feature_after_auth({xmlstreamelement, |
604 |
|
#xmlel{name = <<"enable">>} = El}, StateData) -> |
605 |
1 |
maybe_unexpected_sm_request(wait_for_feature_after_auth, El, StateData); |
606 |
|
wait_for_feature_after_auth({xmlstreamelement, |
607 |
|
#xmlel{name = <<"resume">>} = El}, StateData) -> |
608 |
15 |
maybe_resume_session(wait_for_feature_after_auth, El, StateData); |
609 |
|
wait_for_feature_after_auth({xmlstreamelement, El}, StateData) -> |
610 |
3040 |
case jlib:iq_query_info(El) of |
611 |
|
#iq{type = set, xmlns = ?NS_BIND, sub_el = SubEl} = IQ -> |
612 |
3032 |
R1 = xml:get_path_s(SubEl, [{elem, <<"resource">>}, cdata]), |
613 |
3032 |
R = case jid:resourceprep(R1) of |
614 |
:-( |
error -> error; |
615 |
36 |
<<>> -> generate_random_resource(); |
616 |
2996 |
Resource -> Resource |
617 |
|
end, |
618 |
3032 |
case R of |
619 |
|
error -> |
620 |
:-( |
Err = jlib:make_error_reply(El, mongoose_xmpp_errors:bad_request()), |
621 |
:-( |
send_element_from_server_jid(StateData, Err), |
622 |
:-( |
fsm_next_state(wait_for_feature_after_auth, StateData); |
623 |
|
_ -> |
624 |
3032 |
JID = jid:replace_resource(StateData#state.jid, R), |
625 |
3032 |
JIDEl = #xmlel{name = <<"jid">>, |
626 |
|
children = [#xmlcdata{content = jid:to_binary(JID)}]}, |
627 |
3032 |
Res = IQ#iq{type = result, |
628 |
|
sub_el = [#xmlel{name = <<"bind">>, |
629 |
|
attrs = [{<<"xmlns">>, ?NS_BIND}], |
630 |
|
children = [JIDEl]}]}, |
631 |
3032 |
XmlEl = jlib:iq_to_xml(Res), |
632 |
3032 |
send_element_from_server_jid(StateData, XmlEl), |
633 |
3032 |
fsm_next_state(wait_for_session_or_sm, |
634 |
|
StateData#state{resource = R, jid = JID}) |
635 |
|
end; |
636 |
|
_ -> |
637 |
8 |
maybe_do_compress(El, wait_for_feature_after_auth, StateData) |
638 |
|
end; |
639 |
|
|
640 |
|
wait_for_feature_after_auth(timeout, StateData) -> |
641 |
:-( |
{stop, normal, StateData}; |
642 |
|
|
643 |
|
wait_for_feature_after_auth({xmlstreamend, _Name}, StateData) -> |
644 |
3 |
send_trailer(StateData), |
645 |
3 |
{stop, normal, StateData}; |
646 |
|
|
647 |
|
wait_for_feature_after_auth({xmlstreamerror, _}, StateData) -> |
648 |
:-( |
c2s_stream_error(mongoose_xmpp_errors:xml_not_well_formed(), StateData); |
649 |
|
|
650 |
|
wait_for_feature_after_auth(closed, StateData) -> |
651 |
5 |
{stop, normal, StateData}; |
652 |
|
|
653 |
|
wait_for_feature_after_auth(_, StateData) -> |
654 |
1 |
c2s_stream_error(mongoose_xmpp_errors:policy_violation(), StateData). |
655 |
|
|
656 |
|
-spec wait_for_session_or_sm(Item :: ejabberd:xml_stream_item(), |
657 |
|
State :: state()) -> fsm_return(). |
658 |
|
wait_for_session_or_sm({xmlstreamelement, |
659 |
|
#xmlel{name = <<"enable">>} = El}, StateData) -> |
660 |
5 |
maybe_enable_stream_mgmt(wait_for_session_or_sm, El, StateData); |
661 |
|
|
662 |
|
wait_for_session_or_sm({xmlstreamelement, |
663 |
|
#xmlel{name = <<"r">>} = El}, StateData) -> |
664 |
1 |
maybe_send_sm_ack(xml:get_tag_attr_s(<<"xmlns">>, El), |
665 |
|
StateData#state.stream_mgmt, |
666 |
|
StateData#state.stream_mgmt_in, |
667 |
|
wait_for_session_or_sm, StateData); |
668 |
|
|
669 |
|
wait_for_session_or_sm({xmlstreamelement, El}, StateData0) -> |
670 |
3029 |
StateData = maybe_increment_sm_incoming(StateData0#state.stream_mgmt, |
671 |
|
StateData0), |
672 |
3029 |
case jlib:iq_query_info(El) of |
673 |
|
#iq{type = set, xmlns = ?NS_SESSION} -> |
674 |
3027 |
Acc = element_to_origin_accum(El, StateData0), |
675 |
3027 |
{Res, _Acc1, NStateData} = maybe_open_session(Acc, StateData), |
676 |
3027 |
case Res of |
677 |
:-( |
stop -> {stop, normal, NStateData}; |
678 |
1 |
wait -> fsm_next_state(wait_for_session_or_sm, NStateData); |
679 |
3026 |
established -> fsm_next_state_pack(session_established, NStateData) |
680 |
|
end; |
681 |
|
_ -> |
682 |
2 |
maybe_do_compress(El, wait_for_session_or_sm, StateData) |
683 |
|
end; |
684 |
|
|
685 |
|
wait_for_session_or_sm(timeout, StateData) -> |
686 |
:-( |
{stop, normal, StateData}; |
687 |
|
|
688 |
|
wait_for_session_or_sm({xmlstreamend, _Name}, StateData) -> |
689 |
:-( |
send_trailer(StateData), |
690 |
:-( |
{stop, normal, StateData}; |
691 |
|
|
692 |
|
wait_for_session_or_sm({xmlstreamerror, _}, StateData) -> |
693 |
:-( |
c2s_stream_error(mongoose_xmpp_errors:xml_not_well_formed(), StateData); |
694 |
|
|
695 |
|
wait_for_session_or_sm(closed, StateData) -> |
696 |
5 |
{stop, normal, StateData}; |
697 |
|
|
698 |
|
wait_for_session_or_sm(_, StateData) -> |
699 |
1 |
c2s_stream_error(mongoose_xmpp_errors:policy_violation(), StateData). |
700 |
|
|
701 |
|
maybe_do_compress(El = #xmlel{name = Name, attrs = Attrs}, NextState, StateData) -> |
702 |
10 |
SockMod = (StateData#state.sockmod):get_sockmod(StateData#state.socket), |
703 |
10 |
{Zlib, _} = StateData#state.zlib, |
704 |
10 |
case {xml:get_attr_s(<<"xmlns">>, Attrs), Name} of |
705 |
|
{?NS_COMPRESS, <<"compress">>} when Zlib == true, |
706 |
|
((SockMod == gen_tcp) or |
707 |
|
(SockMod == ejabberd_tls)) -> |
708 |
10 |
check_compression_auth(El, NextState, StateData); |
709 |
|
_ -> |
710 |
:-( |
process_unauthenticated_stanza(StateData, El), |
711 |
:-( |
fsm_next_state(NextState, StateData) |
712 |
|
|
713 |
|
end. |
714 |
|
|
715 |
|
check_compression_auth(_El, NextState, StateData = #state{authenticated = false}) -> |
716 |
6 |
send_element_from_server_jid(StateData, compress_setup_failed()), |
717 |
4 |
fsm_next_state(NextState, StateData); |
718 |
|
check_compression_auth(El, NextState, StateData) -> |
719 |
10 |
check_compression_method(El, NextState, StateData). |
720 |
|
|
721 |
|
check_compression_method(El, NextState, StateData) -> |
722 |
10 |
case exml_query:path(El, [{element, <<"method">>}, cdata]) of |
723 |
|
undefined -> |
724 |
:-( |
send_element_from_server_jid(StateData, compress_setup_failed()), |
725 |
:-( |
fsm_next_state(NextState, StateData); |
726 |
|
<<"zlib">> -> |
727 |
10 |
{_, ZlibLimit} = StateData#state.zlib, |
728 |
10 |
Socket = StateData#state.socket, |
729 |
10 |
ZlibSocket |
730 |
|
= (StateData#state.sockmod):compress(Socket, ZlibLimit, exml:to_binary(compressed())), |
731 |
10 |
fsm_next_state(wait_for_stream, |
732 |
|
StateData#state{socket = ZlibSocket, streamid = new_id()}); |
733 |
|
_ -> |
734 |
:-( |
send_element_from_server_jid(StateData, compress_unsupported_method()), |
735 |
:-( |
fsm_next_state(NextState, StateData) |
736 |
|
end. |
737 |
|
|
738 |
|
-spec maybe_open_session(mongoose_acc:t(), state()) -> |
739 |
|
{wait | stop | established, mongoose_acc:t(), state()}. |
740 |
|
maybe_open_session(Acc, #state{jid = JID} = StateData) -> |
741 |
3027 |
case user_allowed(JID, StateData) of |
742 |
|
true -> |
743 |
3026 |
do_open_session(Acc, JID, StateData); |
744 |
|
_ -> |
745 |
1 |
Acc1 = mongoose_hooks:forbidden_session_hook(StateData#state.host_type, |
746 |
|
Acc, JID), |
747 |
1 |
?LOG_INFO(#{what => forbidden_session, |
748 |
|
text => <<"User not allowed to open session">>, |
749 |
1 |
acc => Acc, c2s_state => StateData}), |
750 |
1 |
{Acc2, Err} = jlib:make_error_reply(Acc1, mongoose_xmpp_errors:not_allowed()), |
751 |
1 |
Acc3 = send_element(Acc2, Err, StateData), |
752 |
1 |
{wait, Acc3, StateData} |
753 |
|
end. |
754 |
|
|
755 |
|
-spec do_open_session(mongoose_acc:t(), jid:jid(), state()) -> |
756 |
|
{stop | established, mongoose_acc:t(), state()}. |
757 |
|
do_open_session(Acc, JID, StateData) -> |
758 |
3026 |
?LOG_INFO(#{what => c2s_opened_session, text => <<"Opened session">>, |
759 |
3026 |
acc => Acc, c2s_state => StateData}), |
760 |
3026 |
Resp = jlib:make_result_iq_reply(mongoose_acc:element(Acc)), |
761 |
3026 |
Packet = {jid:to_bare(StateData#state.jid), StateData#state.jid, Resp}, |
762 |
3026 |
case send_and_maybe_buffer_stanza(Acc, Packet, StateData) of |
763 |
|
{ok, Acc1, NStateData} -> |
764 |
3026 |
do_open_session_common(Acc1, JID, NStateData); |
765 |
|
{resume, Acc1, NStateData} -> |
766 |
:-( |
case maybe_enter_resume_session(NStateData) of |
767 |
|
{stop, normal, NextStateData} -> % error, resume not possible |
768 |
:-( |
c2s_stream_error(mongoose_xmpp_errors:stream_internal_server_error(), NextStateData), |
769 |
:-( |
{stop, Acc1, NStateData}; |
770 |
|
{_, _, NextStateData, _} -> |
771 |
:-( |
do_open_session_common(Acc1, JID, NextStateData) |
772 |
|
end |
773 |
|
end. |
774 |
|
|
775 |
|
do_open_session_common(Acc, JID, #state{host_type = HostType, |
776 |
|
jid = JID} = NewStateData0) -> |
777 |
3026 |
change_shaper(NewStateData0, JID), |
778 |
3026 |
Acc1 = mongoose_hooks:roster_get_subscription_lists(HostType, Acc, JID), |
779 |
3026 |
{Fs, Ts, Pending} = mongoose_acc:get(roster, subscription_lists, {[], [], []}, Acc1), |
780 |
3026 |
LJID = jid:to_lower(jid:to_bare(JID)), |
781 |
3026 |
Fs1 = [LJID | Fs], |
782 |
3026 |
Ts1 = [LJID | Ts], |
783 |
3026 |
PrivList = mongoose_hooks:privacy_get_user_list(HostType, JID), |
784 |
3026 |
SID = ejabberd_sm:make_new_sid(), |
785 |
3026 |
Conn = get_conn_type(NewStateData0), |
786 |
3026 |
Info = #{ip => NewStateData0#state.ip, conn => Conn, |
787 |
|
auth_module => NewStateData0#state.auth_module }, |
788 |
3026 |
ReplacedPids = ejabberd_sm:open_session(HostType, SID, JID, Info), |
789 |
|
|
790 |
3026 |
RefsAndPids = [{monitor(process, PID), PID} || PID <- ReplacedPids], |
791 |
3026 |
case RefsAndPids of |
792 |
|
[] -> |
793 |
3017 |
ok; |
794 |
|
_ -> |
795 |
9 |
Timeout = get_replaced_wait_timeout(HostType), |
796 |
9 |
erlang:send_after(Timeout, self(), replaced_wait_timeout) |
797 |
|
end, |
798 |
|
|
799 |
3026 |
NewStateData = |
800 |
|
NewStateData0#state{sid = SID, |
801 |
|
conn = Conn, |
802 |
|
replaced_pids = RefsAndPids, |
803 |
|
pres_f = gb_sets:from_list(Fs1), |
804 |
|
pres_t = gb_sets:from_list(Ts1), |
805 |
|
pending_invitations = Pending, |
806 |
|
privacy_list = PrivList}, |
807 |
3026 |
{established, Acc1, NewStateData}. |
808 |
|
|
809 |
|
get_replaced_wait_timeout(HostType) -> |
810 |
9 |
mongoose_config:get_opt({replaced_wait_timeout, HostType}). |
811 |
|
|
812 |
|
-spec session_established(Item :: ejabberd:xml_stream_item(), |
813 |
|
State :: state()) -> fsm_return(). |
814 |
|
session_established({xmlstreamelement, |
815 |
|
#xmlel{name = <<"enable">>} = El}, StateData) -> |
816 |
56 |
maybe_enable_stream_mgmt(session_established, El, StateData); |
817 |
|
|
818 |
|
session_established({xmlstreamelement, |
819 |
|
#xmlel{name = <<"a">>} = El}, StateData) -> |
820 |
38 |
stream_mgmt_handle_ack(session_established, El, StateData); |
821 |
|
|
822 |
|
session_established({xmlstreamelement, |
823 |
|
#xmlel{name = <<"r">>} = El}, StateData) -> |
824 |
6 |
maybe_send_sm_ack(xml:get_tag_attr_s(<<"xmlns">>, El), |
825 |
|
StateData#state.stream_mgmt, |
826 |
|
StateData#state.stream_mgmt_in, |
827 |
|
session_established, StateData); |
828 |
|
session_established({xmlstreamelement, |
829 |
|
#xmlel{name = <<"inactive">>} = El}, State) -> |
830 |
10 |
mongoose_metrics:update(State#state.server, modCSIInactive, 1), |
831 |
|
|
832 |
10 |
maybe_inactivate_session(xml:get_tag_attr_s(<<"xmlns">>, El), State); |
833 |
|
|
834 |
|
session_established({xmlstreamelement, |
835 |
|
#xmlel{name = <<"active">>} = El}, State) -> |
836 |
3 |
mongoose_metrics:update(State#state.server, modCSIActive, 1), |
837 |
|
|
838 |
3 |
maybe_activate_session(xml:get_tag_attr_s(<<"xmlns">>, El), State); |
839 |
|
|
840 |
|
session_established({xmlstreamelement, El}, StateData) -> |
841 |
7538 |
FromJID = StateData#state.jid, |
842 |
|
% Check 'from' attribute in stanza RFC 3920 Section 9.1.2 |
843 |
7538 |
case check_from(El, FromJID) of |
844 |
|
'invalid-from' -> |
845 |
2 |
c2s_stream_error(mongoose_xmpp_errors:invalid_from(), StateData); |
846 |
|
_NewEl -> |
847 |
7536 |
NewState = maybe_increment_sm_incoming(StateData#state.stream_mgmt, |
848 |
|
StateData), |
849 |
|
% initialise accumulator, fill with data |
850 |
7536 |
El1 = fix_message_from_user(El, StateData#state.lang), |
851 |
7536 |
Acc0 = element_to_origin_accum(El1, StateData), |
852 |
7536 |
Acc1 = mongoose_hooks:c2s_preprocessing_hook(StateData#state.host_type, |
853 |
|
Acc0, NewState), |
854 |
7536 |
case mongoose_acc:get(hook, result, undefined, Acc1) of |
855 |
78 |
drop -> fsm_next_state(session_established, NewState); |
856 |
7458 |
_ -> process_outgoing_stanza(Acc1, NewState) |
857 |
|
end |
858 |
|
end; |
859 |
|
|
860 |
|
%% We hibernate the process to reduce memory consumption after a |
861 |
|
%% configurable activity timeout |
862 |
|
session_established(timeout, StateData) -> |
863 |
:-( |
{next_state, session_established, StateData, hibernate()}; |
864 |
|
session_established({xmlstreamend, _Name}, StateData) -> |
865 |
2907 |
send_trailer(StateData), |
866 |
2907 |
{stop, normal, StateData}; |
867 |
|
|
868 |
|
session_established({xmlstreamerror, <<"child element too big">> = E}, StateData) -> |
869 |
:-( |
PolicyViolationErr = mongoose_xmpp_errors:policy_violation(StateData#state.lang, E), |
870 |
:-( |
c2s_stream_error(PolicyViolationErr, StateData); |
871 |
|
session_established({xmlstreamerror, _}, StateData) -> |
872 |
:-( |
c2s_stream_error(mongoose_xmpp_errors:xml_not_well_formed(), StateData); |
873 |
|
session_established(closed, StateData) -> |
874 |
75 |
?LOG_DEBUG(#{what => c2s_closed, c2s_state => StateData, |
875 |
75 |
text => <<"Session established closed - trying to enter resume_session">>}), |
876 |
75 |
maybe_enter_resume_session(StateData#state.stream_mgmt_id, StateData). |
877 |
|
|
878 |
|
%% @doc Process packets sent by user (coming from user on c2s XMPP |
879 |
|
%% connection) |
880 |
|
%% eventually it should return {mongoose_acc:t(), fsm_return()} so that the accumulator |
881 |
|
%% comes back whence it originated |
882 |
|
-spec process_outgoing_stanza(mongoose_acc:t(), state()) -> fsm_return(). |
883 |
|
process_outgoing_stanza(Acc, StateData) -> |
884 |
7458 |
ToJID = mongoose_acc:to_jid(Acc), |
885 |
7458 |
Element = mongoose_acc:element(Acc), |
886 |
7458 |
NState = process_outgoing_stanza(Acc, ToJID, Element#xmlel.name, StateData), |
887 |
7458 |
fsm_next_state(session_established, NState). |
888 |
|
|
889 |
|
process_outgoing_stanza(Acc, ToJID, <<"presence">>, StateData) -> |
890 |
3415 |
#jid{user = User, server = Server} = FromJID = mongoose_acc:from_jid(Acc), |
891 |
3415 |
HostType = mongoose_acc:host_type(Acc), |
892 |
3415 |
Res = mongoose_hooks:c2s_update_presence(HostType, Acc), |
893 |
3415 |
El = mongoose_acc:element(Res), |
894 |
3415 |
Res1 = mongoose_hooks:user_send_packet(Res, FromJID, ToJID, El), |
895 |
3415 |
{_Acc1, NState} = case ToJID of |
896 |
|
#jid{user = User, |
897 |
|
server = Server, |
898 |
|
resource = <<>>} -> |
899 |
3001 |
presence_update(Res1, FromJID, StateData); |
900 |
|
_ -> |
901 |
414 |
presence_track(Res1, StateData) |
902 |
|
end, |
903 |
3415 |
NState; |
904 |
|
process_outgoing_stanza(Acc0, ToJID, <<"iq">>, StateData) -> |
905 |
2184 |
{XMLNS, Acc} = mongoose_iq:xmlns(Acc0), |
906 |
2184 |
FromJID = mongoose_acc:from_jid(Acc), |
907 |
2184 |
El = mongoose_acc:element(Acc), |
908 |
2184 |
{_Acc, NState} = case XMLNS of |
909 |
|
?NS_PRIVACY -> |
910 |
176 |
process_privacy_iq(Acc, ToJID, StateData); |
911 |
|
?NS_BLOCKING -> |
912 |
38 |
process_privacy_iq(Acc, ToJID, StateData); |
913 |
|
_ -> |
914 |
1970 |
Acc2 = mongoose_hooks:user_send_packet(Acc, FromJID, |
915 |
|
ToJID, El), |
916 |
1970 |
Acc3 = check_privacy_and_route(Acc2, StateData), |
917 |
1970 |
{Acc3, StateData} |
918 |
|
end, |
919 |
2184 |
NState; |
920 |
|
process_outgoing_stanza(Acc, ToJID, <<"message">>, StateData) -> |
921 |
1859 |
FromJID = mongoose_acc:from_jid(Acc), |
922 |
1859 |
El = mongoose_acc:element(Acc), |
923 |
1859 |
Acc1 = mongoose_hooks:user_send_packet(Acc, FromJID, ToJID, El), |
924 |
1859 |
_Acc2 = check_privacy_and_route(Acc1, StateData), |
925 |
1859 |
StateData; |
926 |
|
process_outgoing_stanza(_Acc, _ToJID, _Name, StateData) -> |
927 |
:-( |
StateData. |
928 |
|
|
929 |
|
%%------------------------------------------------------------------------- |
930 |
|
%% session may be terminated for example by mod_ping there is still valid |
931 |
|
%% connection and resource want to send stanza. |
932 |
|
resume_session({xmlstreamelement, _}, StateData) -> |
933 |
:-( |
Err = mongoose_xmpp_errors:policy_violation(StateData#state.lang, |
934 |
|
<<"session in resume state cannot accept incoming stanzas">>), |
935 |
:-( |
maybe_send_element_from_server_jid_safe(StateData, Err), |
936 |
:-( |
maybe_send_trailer_safe(StateData), |
937 |
:-( |
{next_state, resume_session, StateData, hibernate()}; |
938 |
|
|
939 |
|
%%------------------------------------------------------------------------- |
940 |
|
%% ignore mod_ping closed messages because we are already in resume session |
941 |
|
%% state |
942 |
|
resume_session(closed, StateData) -> |
943 |
7 |
{next_state, resume_session, StateData, hibernate()}; |
944 |
|
resume_session(timeout, StateData) -> |
945 |
:-( |
{next_state, resume_session, StateData, hibernate()}; |
946 |
|
resume_session(Msg, StateData) -> |
947 |
:-( |
?UNEXPECTED_INFO(Msg), |
948 |
:-( |
{next_state, resume_session, StateData, hibernate()}. |
949 |
|
|
950 |
|
|
951 |
|
%%---------------------------------------------------------------------- |
952 |
|
%% Func: StateName/3 |
953 |
|
%% Returns: {next_state, NextStateName, NextStateData} | |
954 |
|
%% {next_state, NextStateName, NextStateData, Timeout} | |
955 |
|
%% {reply, Reply, NextStateName, NextStateData} | |
956 |
|
%% {reply, Reply, NextStateName, NextStateData, Timeout} | |
957 |
|
%% {stop, Reason, NewStateData} | |
958 |
|
%% {stop, Reason, Reply, NewStateData} |
959 |
|
%%---------------------------------------------------------------------- |
960 |
|
|
961 |
|
session_established(resume, From, SD) -> |
962 |
1 |
handover_session(SD, From). |
963 |
|
|
964 |
|
resume_session(resume, From, SD) -> |
965 |
10 |
handover_session(SD, From). |
966 |
|
|
967 |
|
%%---------------------------------------------------------------------- |
968 |
|
%% Func: handle_event/3 |
969 |
|
%% Returns: {next_state, NextStateName, NextStateData} | |
970 |
|
%% {next_state, NextStateName, NextStateData, Timeout} | |
971 |
|
%% {stop, Reason, NewStateData} |
972 |
|
%%---------------------------------------------------------------------- |
973 |
|
|
974 |
|
handle_event(keep_alive_packet, session_established, |
975 |
|
#state{host_type = HostType, jid = JID} = StateData) -> |
976 |
7 |
mongoose_hooks:user_sent_keep_alive(HostType, JID), |
977 |
7 |
fsm_next_state(session_established, StateData); |
978 |
|
handle_event(_Event, StateName, StateData) -> |
979 |
460 |
fsm_next_state(StateName, StateData). |
980 |
|
|
981 |
|
%%---------------------------------------------------------------------- |
982 |
|
%% Func: handle_sync_event/4 |
983 |
|
%% Returns: {next_state, NextStateName, NextStateData} | |
984 |
|
%% {next_state, NextStateName, NextStateData, Timeout} | |
985 |
|
%% {reply, Reply, NextStateName, NextStateData} | |
986 |
|
%% {reply, Reply, NextStateName, NextStateData, Timeout} | |
987 |
|
%% {stop, Reason, NewStateData} | |
988 |
|
%% {stop, Reason, Reply, NewStateData} |
989 |
|
%%---------------------------------------------------------------------- |
990 |
|
-spec handle_sync_event(Evt :: atom(), |
991 |
|
From :: any(), |
992 |
|
StateName :: statename(), |
993 |
|
State :: state()) |
994 |
|
-> {'reply', Reply :: [any()], statename(), state()} |
995 |
|
| {'reply', Reply :: 'ok' | {_, _, _, _}, statename(), state(), timeout()}. |
996 |
|
handle_sync_event(get_presence, _From, StateName, StateData) -> |
997 |
22 |
User = StateData#state.user, |
998 |
22 |
PresLast = StateData#state.pres_last, |
999 |
|
|
1000 |
22 |
Show = get_showtag(PresLast), |
1001 |
22 |
Status = get_statustag(PresLast), |
1002 |
22 |
Resource = StateData#state.resource, |
1003 |
|
|
1004 |
22 |
Reply = {User, Resource, Show, Status}, |
1005 |
22 |
fsm_reply(Reply, StateName, StateData); |
1006 |
|
handle_sync_event(get_info, _From, StateName, StateData) -> |
1007 |
11 |
{reply, make_c2s_info(StateData), StateName, StateData}; |
1008 |
|
handle_sync_event(get_subscribed, _From, StateName, StateData) -> |
1009 |
6 |
Subscribed = gb_sets:to_list(StateData#state.pres_f), |
1010 |
6 |
{reply, Subscribed, StateName, StateData}; |
1011 |
|
handle_sync_event(_Event, _From, StateName, StateData) -> |
1012 |
:-( |
Reply = ok, |
1013 |
:-( |
fsm_reply(Reply, StateName, StateData). |
1014 |
|
|
1015 |
|
|
1016 |
|
code_change(_OldVsn, StateName, StateData, _Extra) -> |
1017 |
:-( |
{ok, StateName, StateData}. |
1018 |
|
|
1019 |
|
%%---------------------------------------------------------------------- |
1020 |
|
%% Func: handle_info/3 |
1021 |
|
%% Returns: {next_state, NextStateName, NextStateData} | |
1022 |
|
%% {next_state, NextStateName, NextStateData, Timeout} | |
1023 |
|
%% {stop, Reason, NewStateData} |
1024 |
|
%%---------------------------------------------------------------------- |
1025 |
|
|
1026 |
|
|
1027 |
|
%%% system events |
1028 |
|
handle_info({exit, Reason}, _StateName, StateData = #state{lang = Lang}) -> |
1029 |
29 |
Acc = new_acc(StateData, #{location => ?LOCATION, element => undefined}), |
1030 |
29 |
send_element(Acc, mongoose_xmpp_errors:stream_conflict(Lang, Reason), StateData), |
1031 |
23 |
send_trailer(StateData), |
1032 |
23 |
{stop, normal, StateData}; |
1033 |
|
handle_info(replaced, _StateName, StateData) -> |
1034 |
9 |
Lang = StateData#state.lang, |
1035 |
9 |
StreamConflict = mongoose_xmpp_errors:stream_conflict(Lang, <<"Replaced by new connection">>), |
1036 |
9 |
maybe_send_element_from_server_jid_safe(StateData, StreamConflict), |
1037 |
9 |
maybe_send_trailer_safe(StateData), |
1038 |
9 |
{stop, normal, StateData#state{authenticated = replaced}}; |
1039 |
|
handle_info(new_offline_messages, session_established, |
1040 |
|
#state{pres_last = Presence, pres_invis = Invisible} = StateData) |
1041 |
|
when Presence =/= undefined orelse Invisible -> |
1042 |
1 |
Acc = new_acc(StateData, #{location => ?LOCATION, element => undefined}), |
1043 |
1 |
resend_offline_messages(Acc, StateData), |
1044 |
1 |
{next_state, session_established, StateData}; |
1045 |
|
handle_info({'DOWN', Monitor, _Type, _Object, _Info}, _StateName, StateData) |
1046 |
|
when Monitor == StateData#state.socket_monitor -> |
1047 |
34 |
maybe_enter_resume_session(StateData#state.stream_mgmt_id, StateData); |
1048 |
|
handle_info({'DOWN', Monitor, _Type, Object, Info}, StateName, |
1049 |
|
#state{ replaced_pids = ReplacedPids } = StateData) -> |
1050 |
210 |
case lists:keytake(Monitor, 1, ReplacedPids) of |
1051 |
|
{value, {Monitor, Object}, NReplacedPids} -> |
1052 |
8 |
NStateData = StateData#state{ replaced_pids = NReplacedPids }, |
1053 |
8 |
fsm_next_state(StateName, NStateData); |
1054 |
|
_ -> |
1055 |
202 |
?LOG_WARNING(#{what => unexpected_c2s_down_info, |
1056 |
|
text => <<"C2S process got DOWN message from unknown process">>, |
1057 |
|
monitor_ref => Monitor, monitor_pid => Object, down_info => Info, |
1058 |
:-( |
state_name => StateName, c2s_state => StateData}), |
1059 |
202 |
fsm_next_state(StateName, StateData) |
1060 |
|
end; |
1061 |
|
handle_info(replaced_wait_timeout, StateName, #state{ replaced_pids = [] } = StateData) -> |
1062 |
:-( |
fsm_next_state(StateName, StateData); |
1063 |
|
handle_info(replaced_wait_timeout, StateName, #state{ replaced_pids = ReplacedPids } = StateData) -> |
1064 |
1 |
lists:foreach( |
1065 |
|
fun({Monitor, Pid}) -> |
1066 |
1 |
?LOG_WARNING(#{what => c2s_replaced_wait_timeout, |
1067 |
|
text => <<"Some processes are not responding when handling replace messages">>, |
1068 |
|
monitor_ref => Monitor, replaced_pid => Pid, |
1069 |
:-( |
state_name => StateName, c2s_state => StateData}) |
1070 |
|
end, ReplacedPids), |
1071 |
1 |
fsm_next_state(StateName, StateData#state{ replaced_pids = [] }); |
1072 |
|
handle_info(system_shutdown, StateName, StateData) -> |
1073 |
1 |
case StateName of |
1074 |
|
wait_for_stream -> |
1075 |
:-( |
send_header(StateData, ?MYNAME, <<"1.0">>, <<"en">>), |
1076 |
:-( |
ok; |
1077 |
|
_ -> |
1078 |
1 |
ok |
1079 |
|
end, |
1080 |
1 |
c2s_stream_error(mongoose_xmpp_errors:system_shutdown(), StateData); |
1081 |
|
handle_info({force_update_presence, LUser}, StateName, |
1082 |
|
#state{user = LUser, host_type = HostType} = StateData) -> |
1083 |
:-( |
NewStateData = |
1084 |
|
case StateData#state.pres_last of |
1085 |
|
#xmlel{name = <<"presence">>} = PresEl -> |
1086 |
:-( |
Acc = element_to_origin_accum(PresEl, StateData), |
1087 |
:-( |
mongoose_hooks:c2s_update_presence(HostType, Acc), |
1088 |
:-( |
{_Acc, StateData2} = presence_update(Acc, StateData#state.jid, StateData), |
1089 |
:-( |
StateData2; |
1090 |
|
_ -> |
1091 |
:-( |
StateData |
1092 |
|
end, |
1093 |
:-( |
{next_state, StateName, NewStateData}; |
1094 |
|
handle_info(resume_timeout, resume_session, StateData) -> |
1095 |
4 |
{stop, normal, StateData}; |
1096 |
|
handle_info(check_buffer_full, StateName, StateData) -> |
1097 |
3 |
case is_buffer_full(StateData#state.stream_mgmt_buffer_size, |
1098 |
|
StateData#state.stream_mgmt_buffer_max) of |
1099 |
|
true -> |
1100 |
2 |
Err = mongoose_xmpp_errors:stream_resource_constraint((StateData#state.lang), |
1101 |
|
<<"too many unacked stanzas">>), |
1102 |
2 |
c2s_stream_error(Err, StateData); |
1103 |
|
false -> |
1104 |
1 |
fsm_next_state(StateName, |
1105 |
|
StateData#state{stream_mgmt_constraint_check_tref = undefined}) |
1106 |
|
end; |
1107 |
|
handle_info({store_session_info, JID, Key, Value, _FromPid}, StateName, StateData) -> |
1108 |
86 |
ejabberd_sm:store_info(JID, Key, Value), |
1109 |
86 |
fsm_next_state(StateName, StateData); |
1110 |
|
handle_info({remove_session_info, JID, Key, _FromPid}, StateName, StateData) -> |
1111 |
19 |
ejabberd_sm:remove_info(JID, Key), |
1112 |
19 |
fsm_next_state(StateName, StateData); |
1113 |
|
handle_info(Info, StateName, StateData) -> |
1114 |
13812 |
handle_incoming_message(Info, StateName, StateData). |
1115 |
|
|
1116 |
|
%%% incoming messages from other users or services to this device |
1117 |
|
handle_incoming_message({send_text, Text}, StateName, StateData) -> |
1118 |
|
% it seems to be sometimes, by event sent from s2s |
1119 |
:-( |
send_text(StateData, Text), |
1120 |
:-( |
fsm_next_state(StateName, StateData); |
1121 |
|
handle_incoming_message({broadcast, Broadcast}, StateName, StateData) -> |
1122 |
451 |
Acc = new_acc(StateData, #{location => ?LOCATION, element => undefined}), |
1123 |
451 |
?LOG_DEBUG(#{what => c2s_broadcast, |
1124 |
|
brodcast_data => Broadcast, |
1125 |
451 |
state_name => StateName, c2s_state => StateData}), |
1126 |
451 |
{Acc1, Res} = handle_routed_broadcast(Acc, Broadcast, StateData), |
1127 |
451 |
handle_broadcast_result(Acc1, Res, StateName, StateData); |
1128 |
|
handle_incoming_message({route, From, To, Acc}, StateName, StateData) -> |
1129 |
13269 |
process_incoming_stanza_with_conflict_check(From, To, Acc, StateName, StateData); |
1130 |
|
handle_incoming_message({send_filtered, Feature, From, To, Packet}, StateName, StateData) -> |
1131 |
|
% this is used by pubsub and should be rewritten when someone rewrites pubsub module |
1132 |
4 |
Acc = new_acc(StateData, #{location => ?LOCATION, |
1133 |
|
from_jid => From, |
1134 |
|
to_jid => To, |
1135 |
|
element => Packet}), |
1136 |
4 |
Drop = mongoose_hooks:c2s_filter_packet(StateData, Feature, To, Packet), |
1137 |
4 |
case {Drop, StateData#state.jid} of |
1138 |
|
{true, _} -> |
1139 |
1 |
?LOG_DEBUG(#{what => c2s_dropped_packet, acc => Acc, |
1140 |
1 |
text => <<"c2s_filter_packet hook dropped a packet">>}), |
1141 |
1 |
fsm_next_state(StateName, StateData); |
1142 |
|
{_, To} -> |
1143 |
:-( |
FinalPacket = jlib:replace_from_to(From, To, Packet), |
1144 |
:-( |
case privacy_check_packet(FinalPacket, From, To, in, StateData) of |
1145 |
|
allow -> |
1146 |
:-( |
{Act, _, NStateData} = send_and_maybe_buffer_stanza(Acc, |
1147 |
|
{From, To, FinalPacket}, |
1148 |
|
StateData), |
1149 |
:-( |
finish_state(Act, StateName, NStateData); |
1150 |
|
_ -> |
1151 |
:-( |
fsm_next_state(StateName, StateData) |
1152 |
|
end; |
1153 |
|
_ -> |
1154 |
3 |
FinalPacket = jlib:replace_from_to(From, To, Packet), |
1155 |
3 |
ejabberd_router:route(From, To, FinalPacket), |
1156 |
3 |
fsm_next_state(StateName, StateData) |
1157 |
|
end; |
1158 |
|
handle_incoming_message({run_remote_hook, HandlerName, Args}, StateName, StateData) -> |
1159 |
88 |
HostType = ejabberd_c2s_state:host_type(StateData), |
1160 |
88 |
HandlerState = maps:get(HandlerName, StateData#state.handlers, empty_state), |
1161 |
88 |
case mongoose_hooks:c2s_remote_hook(HostType, HandlerName, Args, |
1162 |
|
HandlerState, StateData) of |
1163 |
|
{error, E} -> |
1164 |
:-( |
?LOG_ERROR(#{what => custom_c2s_hook_handler_error, |
1165 |
|
text => <<"c2s_remote_hook failed">>, reason => E, |
1166 |
|
handler_name => HandlerName, handler_args => Args, |
1167 |
|
handler_state => HandlerState, |
1168 |
:-( |
state_name => StateName, c2s_state => StateData}), |
1169 |
:-( |
fsm_next_state(StateName, StateData); |
1170 |
|
NewHandlerState -> |
1171 |
88 |
NewStates = maps:put(HandlerName, NewHandlerState, StateData#state.handlers), |
1172 |
88 |
fsm_next_state(StateName, StateData#state{handlers = NewStates}) |
1173 |
|
end; |
1174 |
|
handle_incoming_message(Info, StateName, StateData) -> |
1175 |
:-( |
?UNEXPECTED_INFO(Info), |
1176 |
:-( |
fsm_next_state(StateName, StateData). |
1177 |
|
|
1178 |
|
process_incoming_stanza_with_conflict_check(From, To, Acc, StateName, StateData) -> |
1179 |
13269 |
Check1 = check_incoming_accum_for_conflicts(Acc, StateData), |
1180 |
13269 |
Check2 = check_receiver_sid_conflict(Acc, StateData), |
1181 |
13269 |
case {Check1, Check2} of |
1182 |
|
{conflict, _} -> %% A race condition detected |
1183 |
|
%% Same jid, but different sids |
1184 |
5 |
OriginSID = mongoose_acc:get(c2s, origin_sid, undefined, Acc), |
1185 |
5 |
?LOG_WARNING(#{what => conflict_check_failed, |
1186 |
|
text => <<"Drop Acc that is addressed to another connection " |
1187 |
|
"(origin SID check failed)">>, |
1188 |
|
c2s_sid => StateData#state.sid, origin_sid => OriginSID, |
1189 |
:-( |
acc => Acc, state_name => StateName, c2s_state => StateData}), |
1190 |
5 |
finish_state(ok, StateName, StateData); |
1191 |
|
{_, conflict} -> |
1192 |
2 |
ReceiverSID = mongoose_acc:get(c2s, receiver_sid, undefined, Acc), |
1193 |
2 |
?LOG_WARNING(#{what => conflict_check_failed, |
1194 |
|
text => <<"Drop Acc that is addressed to another connection " |
1195 |
|
"(receiver SID check failed)">>, |
1196 |
|
c2s_sid => StateData#state.sid, receiver_sid => ReceiverSID, |
1197 |
:-( |
acc => Acc, state_name => StateName, c2s_state => StateData}), |
1198 |
2 |
finish_state(ok, StateName, StateData); |
1199 |
|
_ -> %% Continue processing |
1200 |
13262 |
process_incoming_stanza(From, To, Acc, StateName, StateData) |
1201 |
|
end. |
1202 |
|
|
1203 |
|
check_receiver_sid_conflict(Acc, #state{sid = Sid}) -> |
1204 |
13269 |
case mongoose_acc:get(c2s, receiver_sid, undefined, Acc) of |
1205 |
|
undefined -> |
1206 |
13267 |
ok; |
1207 |
|
Sid -> |
1208 |
:-( |
ok; |
1209 |
|
_ -> |
1210 |
2 |
conflict |
1211 |
|
end. |
1212 |
|
|
1213 |
|
%% If jid is the same, but sid is not, then we have a conflict. |
1214 |
|
%% jid example is alice@localhost/res1. |
1215 |
|
%% sid example is `{now(), pid()}'. |
1216 |
|
%% The conflict can happen, when actions with an accumulator were initiated by |
1217 |
|
%% one process but the resulting stanzas were routed to another process with |
1218 |
|
%% the same JID but different SID. |
1219 |
|
%% The conflict usually happens when an user is reconnecting. |
1220 |
|
%% Both origin_sid and origin_jid props should be defined. |
1221 |
|
%% But we don't force developers to set both of them, so we should correctly |
1222 |
|
%% process stanzas, that have only one properly set. |
1223 |
|
%% |
1224 |
|
%% "Incoming" means that stanza is coming from ejabberd_router. |
1225 |
|
-spec check_incoming_accum_for_conflicts(mongoose_acc:t(), state()) -> |
1226 |
|
unknown_origin | different_origin | same_device | conflict. |
1227 |
|
check_incoming_accum_for_conflicts(Acc, #state{sid = SID, jid = JID, |
1228 |
|
stream_mgmt_resumed_from = OldSID}) -> |
1229 |
13269 |
OriginSID = mongoose_acc:get(c2s, origin_sid, undefined, Acc), |
1230 |
13269 |
OriginJID = mongoose_acc:get(c2s, origin_jid, undefined, Acc), |
1231 |
13269 |
AreDefined = OriginJID =/= undefined andalso OriginSID =/= undefined, |
1232 |
13269 |
case AreDefined of |
1233 |
|
false -> |
1234 |
3167 |
unknown_origin; |
1235 |
|
true -> |
1236 |
10102 |
SameJID = jid:are_equal(OriginJID, JID), |
1237 |
10102 |
SameSID = OriginSID =:= SID, |
1238 |
|
% It's possible to receive a response addressed to a process |
1239 |
|
% which we resumed from - still valid! |
1240 |
10102 |
SameOldSession = OriginSID =:= OldSID, |
1241 |
10102 |
case {SameJID, SameSID or SameOldSession} of |
1242 |
|
{false, _} -> |
1243 |
2169 |
different_origin; |
1244 |
|
{_, true} -> |
1245 |
7928 |
same_device; |
1246 |
|
_ -> |
1247 |
5 |
conflict |
1248 |
|
end |
1249 |
|
end. |
1250 |
|
|
1251 |
|
process_incoming_stanza(From, To, Acc, StateName, StateData) -> |
1252 |
13262 |
#xmlel{ name = Name } = Packet = mongoose_acc:element(Acc), |
1253 |
13262 |
{Act, _NextAcc, NextState} = case handle_routed(Name, From, To, Acc, StateData) of |
1254 |
|
{allow, NewAcc, NewPacket, NewState} -> |
1255 |
2 |
preprocess_and_ship(NewAcc, From, To, NewPacket, NewState); |
1256 |
|
{allow, NewAcc, NewState} -> |
1257 |
10304 |
preprocess_and_ship(NewAcc, From, To, Packet, NewState); |
1258 |
|
{Reason, NewAcc, NewState} -> |
1259 |
2956 |
response_negative(Name, Reason, From, To, NewAcc), |
1260 |
2956 |
{ok, NewAcc, NewState} |
1261 |
|
end, |
1262 |
13262 |
finish_state(Act, StateName, NextState). |
1263 |
|
|
1264 |
|
|
1265 |
|
-spec preprocess_and_ship(Acc :: mongoose_acc:t(), |
1266 |
|
From :: jid:jid(), |
1267 |
|
To :: jid:jid(), |
1268 |
|
El :: exml:element(), |
1269 |
|
StateData :: state()) -> {ok | resume, mongoose_acc:t(), state()}. |
1270 |
|
preprocess_and_ship(Acc, From, To, El, StateData) -> |
1271 |
10306 |
#xmlel{attrs = Attrs} = El, |
1272 |
10306 |
Attrs2 = jlib:replace_from_to_attrs(jid:to_binary(From), |
1273 |
|
jid:to_binary(To), |
1274 |
|
Attrs), |
1275 |
10306 |
FixedEl = El#xmlel{attrs = Attrs2}, |
1276 |
10306 |
Acc2 = mongoose_hooks:user_receive_packet(StateData#state.host_type, Acc, |
1277 |
|
StateData#state.jid, From, To, FixedEl), |
1278 |
10306 |
ship_to_local_user(Acc2, {From, To, FixedEl}, StateData). |
1279 |
|
|
1280 |
|
response_negative(<<"iq">>, forbidden, From, To, Acc) -> |
1281 |
1 |
send_back_error(mongoose_xmpp_errors:forbidden(), From, To, Acc); |
1282 |
|
response_negative(<<"iq">>, deny, From, To, Acc) -> |
1283 |
6 |
IqType = mongoose_acc:stanza_type(Acc), |
1284 |
6 |
response_iq_deny(IqType, From, To, Acc); |
1285 |
|
response_negative(<<"message">>, deny, From, To, Acc) -> |
1286 |
32 |
Acc1 = mod_amp:check_packet(Acc, delivery_failed), |
1287 |
32 |
send_back_error(mongoose_xmpp_errors:service_unavailable(), From, To, Acc1); |
1288 |
|
response_negative(_, _, _, _, Acc) -> |
1289 |
2917 |
Acc. |
1290 |
|
|
1291 |
|
response_iq_deny(<<"get">>, From, To, Acc) -> |
1292 |
1 |
send_back_error(mongoose_xmpp_errors:service_unavailable(), From, To, Acc); |
1293 |
|
response_iq_deny(<<"set">>, From, To, Acc) -> |
1294 |
1 |
send_back_error(mongoose_xmpp_errors:service_unavailable(), From, To, Acc); |
1295 |
|
response_iq_deny(_, _, _, Acc) -> |
1296 |
4 |
Acc. |
1297 |
|
|
1298 |
|
send_back_error(Etype, From, To, Acc) -> |
1299 |
35 |
{Acc1, Err} = jlib:make_error_reply(Acc, Etype), |
1300 |
35 |
ejabberd_router:route(To, From, Acc1, Err). |
1301 |
|
|
1302 |
|
handle_routed(<<"presence">>, From, To, Acc, StateData) -> |
1303 |
7238 |
handle_routed_presence(From, To, Acc, StateData); |
1304 |
|
handle_routed(<<"iq">>, From, To, Acc, StateData) -> |
1305 |
2634 |
handle_routed_iq(From, To, Acc, StateData); |
1306 |
|
handle_routed(<<"message">>, _From, To, Acc, StateData) -> |
1307 |
3390 |
{Acc1, Res} = privacy_check_packet(Acc, To, in, StateData), |
1308 |
3390 |
case Res of |
1309 |
|
allow -> |
1310 |
3358 |
{allow, Acc1, StateData}; |
1311 |
|
deny -> |
1312 |
22 |
{deny, Acc1, StateData}; |
1313 |
|
block -> |
1314 |
10 |
{deny, Acc1, StateData} |
1315 |
|
end; |
1316 |
|
handle_routed(_, _From, _To, Acc, StateData) -> |
1317 |
:-( |
{ignore, Acc, StateData}. |
1318 |
|
|
1319 |
|
-spec handle_routed_iq(From :: jid:jid(), |
1320 |
|
To :: jid:jid(), |
1321 |
|
Acc :: mongoose_acc:t(), |
1322 |
|
StateData :: state()) -> routing_result(). |
1323 |
|
handle_routed_iq(From, To, Acc, StateData) -> |
1324 |
2634 |
{Qi, Acc1} = mongoose_iq:info(Acc), |
1325 |
2634 |
handle_routed_iq(From, To, Acc1, Qi, StateData). |
1326 |
|
|
1327 |
|
-spec handle_routed_iq(From :: jid:jid(), |
1328 |
|
To :: jid:jid(), |
1329 |
|
Acc :: mongoose_acc:t(), |
1330 |
|
IQ :: invalid | not_iq | jlib:iq(), |
1331 |
|
StateData :: state()) -> routing_result(). |
1332 |
|
handle_routed_iq(From, To, Acc0, #iq{ xmlns = ?NS_LAST, type = Type }, StateData) |
1333 |
|
when Type /= result, Type /= error -> |
1334 |
|
% we could make iq handlers handle full jids as well, but wouldn't it be an overkill? |
1335 |
1 |
{Acc, HasFromSub} = case is_subscribed_to_my_presence(From, StateData) of |
1336 |
|
true -> |
1337 |
:-( |
{A, R} = privacy_check_packet(Acc0, To, out, StateData), |
1338 |
:-( |
{A, R == 'allow'}; |
1339 |
|
false -> |
1340 |
1 |
{Acc0, false} |
1341 |
|
end, |
1342 |
1 |
case HasFromSub of |
1343 |
|
true -> |
1344 |
:-( |
{Acc1, Res} = privacy_check_packet(Acc, To, in, StateData), |
1345 |
:-( |
case Res of |
1346 |
|
allow -> |
1347 |
:-( |
{allow, Acc1, StateData}; |
1348 |
|
_ -> |
1349 |
:-( |
{deny, Acc1, StateData} |
1350 |
|
end; |
1351 |
|
_ -> |
1352 |
1 |
{forbidden, Acc, StateData} |
1353 |
|
end; |
1354 |
|
handle_routed_iq(_From, To, Acc, #iq{}, StateData) -> |
1355 |
2633 |
{Acc1, Res} = privacy_check_packet(Acc, To, in, StateData), |
1356 |
2633 |
case Res of |
1357 |
|
allow -> |
1358 |
2627 |
{allow, Acc1, StateData}; |
1359 |
|
deny -> |
1360 |
6 |
{deny, Acc1, StateData} |
1361 |
|
end; |
1362 |
|
handle_routed_iq(_From, _To, Acc, IQ, StateData) |
1363 |
|
when (IQ == invalid) or (IQ == not_iq) -> |
1364 |
:-( |
{invalid, Acc, StateData}. |
1365 |
|
|
1366 |
|
-spec handle_routed_broadcast(Acc :: mongoose_acc:t(), |
1367 |
|
Broadcast :: broadcast_type(), |
1368 |
|
StateData :: state()) -> |
1369 |
|
{mongoose_acc:t(), broadcast_result()}. |
1370 |
|
handle_routed_broadcast(Acc, {item, IJID, ISubscription}, StateData) -> |
1371 |
369 |
{Acc2, NewState} = roster_change(Acc, IJID, ISubscription, StateData), |
1372 |
369 |
{Acc2, {new_state, NewState}}; |
1373 |
|
handle_routed_broadcast(Acc, {privacy_list, PrivList, PrivListName}, StateData) -> |
1374 |
53 |
case mongoose_hooks:privacy_updated_list(StateData#state.host_type, |
1375 |
|
StateData#state.privacy_list, PrivList) of |
1376 |
|
false -> |
1377 |
:-( |
{Acc, {new_state, StateData}}; |
1378 |
|
NewPL -> |
1379 |
53 |
PrivPushIQ = privacy_list_push_iq(PrivListName), |
1380 |
53 |
F = jid:to_bare(StateData#state.jid), |
1381 |
53 |
T = StateData#state.jid, |
1382 |
53 |
PrivPushEl = jlib:replace_from_to(F, T, jlib:iq_to_xml(PrivPushIQ)), |
1383 |
53 |
Acc1 = maybe_update_presence(Acc, StateData, NewPL), |
1384 |
53 |
Res = {send_new, F, T, PrivPushEl, StateData#state{privacy_list = NewPL}}, |
1385 |
53 |
{Acc1, Res} |
1386 |
|
end; |
1387 |
|
handle_routed_broadcast(Acc, {blocking, UserList, Action, JIDs}, StateData) -> |
1388 |
29 |
blocking_push_to_resources(Action, JIDs, StateData), |
1389 |
29 |
blocking_presence_to_contacts(Action, JIDs, StateData), |
1390 |
29 |
Res = {new_state, StateData#state{privacy_list = UserList}}, |
1391 |
29 |
{Acc, Res}; |
1392 |
|
handle_routed_broadcast(Acc, _, StateData) -> |
1393 |
:-( |
{Acc, {new_state, StateData}}. |
1394 |
|
|
1395 |
|
-spec handle_broadcast_result(mongoose_acc:t(), broadcast_result(), StateName :: atom(), |
1396 |
|
StateData :: state()) -> |
1397 |
|
any(). |
1398 |
|
handle_broadcast_result(Acc, {send_new, From, To, Stanza, NewState}, StateName, _StateData) -> |
1399 |
53 |
{Act, _, NewStateData} = ship_to_local_user(Acc, {From, To, Stanza}, NewState), |
1400 |
53 |
finish_state(Act, StateName, NewStateData); |
1401 |
|
handle_broadcast_result(_Acc, {new_state, NewState}, StateName, _StateData) -> |
1402 |
398 |
fsm_next_state(StateName, NewState). |
1403 |
|
|
1404 |
|
privacy_list_push_iq(PrivListName) -> |
1405 |
53 |
#iq{type = set, xmlns = ?NS_PRIVACY, |
1406 |
|
id = <<"push", (mongoose_bin:gen_from_crypto())/binary>>, |
1407 |
|
sub_el = [#xmlel{name = <<"query">>, |
1408 |
|
attrs = [{<<"xmlns">>, ?NS_PRIVACY}], |
1409 |
|
children = [#xmlel{name = <<"list">>, |
1410 |
|
attrs = [{<<"name">>, PrivListName}]}]}]}. |
1411 |
|
|
1412 |
|
-spec handle_routed_presence(From :: jid:jid(), To :: jid:jid(), |
1413 |
|
Acc0 :: mongoose_acc:t(), StateData :: state()) -> routing_result(). |
1414 |
|
handle_routed_presence(From, To, Acc, StateData) -> |
1415 |
7238 |
Packet = mongoose_acc:element(Acc), |
1416 |
7238 |
State = mongoose_hooks:c2s_presence_in(StateData, From, To, Packet), |
1417 |
7238 |
case mongoose_acc:stanza_type(Acc) of |
1418 |
|
<<"probe">> -> |
1419 |
2913 |
{LFrom, LBFrom} = lowcase_and_bare(From), |
1420 |
2913 |
NewState = case am_i_available_to(LFrom, LBFrom, State) of |
1421 |
2912 |
true -> State; |
1422 |
1 |
false -> make_available_to(LFrom, LBFrom, State) |
1423 |
|
end, |
1424 |
2913 |
Acc1 = process_presence_probe(From, To, Acc, NewState), |
1425 |
2913 |
{probe, Acc1, NewState}; |
1426 |
|
<<"error">> -> |
1427 |
26 |
NewA = gb_sets:del_element(jid:to_lower(From), State#state.pres_a), |
1428 |
26 |
{allow, Acc, State#state{pres_a = NewA}}; |
1429 |
|
<<"invisible">> -> |
1430 |
2 |
#xmlel{ attrs = Attrs } = Packet, |
1431 |
2 |
Attrs1 = lists:keydelete(<<"type">>, 1, Attrs), |
1432 |
2 |
Attrs2 = [{<<"type">>, <<"unavailable">>} | Attrs1], |
1433 |
2 |
NEl = Packet#xmlel{attrs = Attrs2}, |
1434 |
2 |
{allow, Acc, NEl, State}; |
1435 |
|
<<"subscribe">> -> |
1436 |
75 |
{Acc1, SRes} = privacy_check_packet(Acc, To, in, State), |
1437 |
75 |
{SRes, Acc1, State}; |
1438 |
|
<<"subscribed">> -> |
1439 |
68 |
{Acc1, SRes} = privacy_check_packet(Acc, To, in, State), |
1440 |
68 |
{SRes, Acc1, State}; |
1441 |
|
<<"unsubscribe">> -> |
1442 |
10 |
{Acc1, SRes} = privacy_check_packet(Acc, To, in, State), |
1443 |
10 |
{SRes, Acc1, State}; |
1444 |
|
<<"unsubscribed">> -> |
1445 |
10 |
{Acc1, SRes} = privacy_check_packet(Acc, To, in, State), |
1446 |
10 |
{SRes, Acc1, State}; |
1447 |
|
_ -> |
1448 |
4134 |
handle_routed_available_presence(State, From, To, Acc) |
1449 |
|
end. |
1450 |
|
|
1451 |
|
-spec handle_routed_available_presence(State :: state(), |
1452 |
|
From :: jid:jid(), |
1453 |
|
To :: jid:jid(), |
1454 |
|
Acc :: mongoose_acc:t()) -> routing_result(). |
1455 |
|
handle_routed_available_presence(State, From, To, Acc) -> |
1456 |
4134 |
{Acc1, Res} = privacy_check_packet(Acc, To, in, State), |
1457 |
4134 |
case Res of |
1458 |
|
allow -> |
1459 |
4130 |
{LFrom, LBFrom} = lowcase_and_bare(From), |
1460 |
4130 |
case am_i_available_to(LFrom, LBFrom, State) of |
1461 |
3583 |
true -> {allow, Acc1, State}; |
1462 |
547 |
false -> {allow, Acc1, make_available_to(LFrom, LBFrom, State)} |
1463 |
|
end; |
1464 |
|
_ -> |
1465 |
4 |
{deny, Acc1, State} |
1466 |
|
end. |
1467 |
|
|
1468 |
|
am_i_available_to(LFrom, LBFrom, State) -> |
1469 |
7043 |
gb_sets:is_element(LFrom, State#state.pres_a) |
1470 |
6717 |
orelse (LFrom /= LBFrom) |
1471 |
6717 |
andalso gb_sets:is_element(LBFrom, State#state.pres_a). |
1472 |
|
|
1473 |
|
make_available_to(LFrom, LBFrom, State) -> |
1474 |
548 |
case gb_sets:is_element(LFrom, State#state.pres_f) of |
1475 |
|
true -> |
1476 |
:-( |
A = gb_sets:add_element(LFrom, State#state.pres_a), |
1477 |
:-( |
State#state{pres_a = A}; |
1478 |
|
false -> |
1479 |
548 |
case gb_sets:is_element(LBFrom, State#state.pres_f) of |
1480 |
|
true -> |
1481 |
1 |
A = gb_sets:add_element(LBFrom, State#state.pres_a), |
1482 |
1 |
State#state{pres_a = A}; |
1483 |
|
false -> |
1484 |
547 |
State |
1485 |
|
end |
1486 |
|
end. |
1487 |
|
|
1488 |
|
%%---------------------------------------------------------------------- |
1489 |
|
%% Func: print_state/1 |
1490 |
|
%% Purpose: Prepare the state to be printed on error log |
1491 |
|
%% Returns: State to print |
1492 |
|
%%---------------------------------------------------------------------- |
1493 |
|
-spec print_state(state()) -> state(). |
1494 |
|
print_state(State = #state{pres_t = T, pres_f = F, pres_a = A, pres_i = I}) -> |
1495 |
:-( |
State#state{pres_t = {pres_t, gb_sets:size(T)}, |
1496 |
|
pres_f = {pres_f, gb_sets:size(F)}, |
1497 |
|
pres_a = {pres_a, gb_sets:size(A)}, |
1498 |
|
pres_i = {pres_i, gb_sets:size(I)} |
1499 |
|
}. |
1500 |
|
|
1501 |
|
%%---------------------------------------------------------------------- |
1502 |
|
%% Func: terminate/4 |
1503 |
|
%% Purpose: Shutdown the fsm |
1504 |
|
%% Returns: any |
1505 |
|
%%---------------------------------------------------------------------- |
1506 |
|
-spec terminate(Reason :: any(), statename(), state(), list()) -> ok. |
1507 |
|
terminate({handover_session, From}, StateName, StateData, UnreadMessages) -> |
1508 |
|
% do handover first |
1509 |
11 |
NewStateData = do_handover_session(StateData, UnreadMessages), |
1510 |
11 |
p1_fsm_old:reply(From, {ok, NewStateData}), |
1511 |
|
% and then run the normal termination |
1512 |
11 |
terminate(normal, StateName, NewStateData, []); |
1513 |
|
terminate(_Reason, StateName, StateData, UnreadMessages) -> |
1514 |
3910 |
InitialAcc0 = new_acc(StateData, #{location => ?LOCATION, element => undefined}), |
1515 |
3910 |
Acc = case StateData#state.stream_mgmt of |
1516 |
71 |
true -> mongoose_acc:set(stream_mgmt, h, StateData#state.stream_mgmt_in, InitialAcc0); |
1517 |
3839 |
_ -> InitialAcc0 |
1518 |
|
end, |
1519 |
3910 |
case {should_close_session(StateName), StateData#state.authenticated} of |
1520 |
|
{false, _} -> |
1521 |
874 |
ok; |
1522 |
|
%% if we are in an state which has a session established |
1523 |
|
{_, replaced} -> |
1524 |
9 |
?LOG_INFO(#{what => replaced_session, |
1525 |
|
text => <<"Replaced by new connection">>, |
1526 |
9 |
state_name => StateName, c2s_state => StateData}), |
1527 |
9 |
StatusEl = #xmlel{name = <<"status">>, |
1528 |
|
children = [#xmlcdata{content = <<"Replaced by new connection">>}]}, |
1529 |
9 |
Packet = #xmlel{name = <<"presence">>, |
1530 |
|
attrs = [{<<"type">>, <<"unavailable">>}], |
1531 |
|
children = [StatusEl]}, |
1532 |
9 |
Acc0 = element_to_origin_accum(Packet, StateData), |
1533 |
9 |
ejabberd_sm:close_session_unset_presence( |
1534 |
|
Acc, |
1535 |
|
StateData#state.sid, |
1536 |
|
StateData#state.jid, |
1537 |
|
<<"Replaced by new connection">>, |
1538 |
|
replaced), |
1539 |
9 |
Acc1 = presence_broadcast(Acc0, StateData#state.pres_a, StateData), |
1540 |
9 |
presence_broadcast(Acc1, StateData#state.pres_i, StateData), |
1541 |
9 |
reroute_unacked_messages(StateData, UnreadMessages); |
1542 |
|
{_, resumed} -> |
1543 |
11 |
StreamConflict = mongoose_xmpp_errors:stream_conflict( |
1544 |
|
StateData#state.lang, <<"Resumed by new connection">>), |
1545 |
11 |
maybe_send_element_from_server_jid_safe(StateData, StreamConflict), |
1546 |
11 |
maybe_send_trailer_safe(StateData), |
1547 |
11 |
?LOG_INFO(#{what => stream_resumed, |
1548 |
|
stream_mgmt_in => StateData#state.stream_mgmt_id, |
1549 |
11 |
state_name => StateName, c2s_state => StateData}); |
1550 |
|
_ -> |
1551 |
3016 |
?LOG_INFO(#{what => close_session, |
1552 |
3016 |
state_name => StateName, c2s_state => StateData}), |
1553 |
|
|
1554 |
3016 |
EmptySet = gb_sets:new(), |
1555 |
3016 |
case StateData of |
1556 |
|
#state{pres_last = undefined, |
1557 |
|
pres_a = EmptySet, |
1558 |
|
pres_i = EmptySet, |
1559 |
|
pres_invis = false} -> |
1560 |
433 |
ejabberd_sm:close_session(Acc, |
1561 |
|
StateData#state.sid, |
1562 |
|
StateData#state.jid, |
1563 |
|
normal); |
1564 |
|
_ -> |
1565 |
2583 |
Packet = #xmlel{name = <<"presence">>, |
1566 |
|
attrs = [{<<"type">>, <<"unavailable">>}]}, |
1567 |
2583 |
Acc0 = element_to_origin_accum(Packet, StateData), |
1568 |
2583 |
ejabberd_sm:close_session_unset_presence( |
1569 |
|
Acc, |
1570 |
|
StateData#state.sid, |
1571 |
|
StateData#state.jid, |
1572 |
|
<<"">>, |
1573 |
|
normal), |
1574 |
2583 |
Acc1 = presence_broadcast(Acc0, StateData#state.pres_a, StateData), |
1575 |
2583 |
presence_broadcast(Acc1, StateData#state.pres_i, StateData) |
1576 |
|
end, |
1577 |
3016 |
reroute_unacked_messages(StateData, UnreadMessages) |
1578 |
|
end, |
1579 |
3910 |
(StateData#state.sockmod):close(StateData#state.socket), |
1580 |
3910 |
ok. |
1581 |
|
|
1582 |
|
-spec reroute_unacked_messages(StateData :: state(), list()) -> any(). |
1583 |
|
reroute_unacked_messages(StateData, UnreadMessages) -> |
1584 |
3025 |
?LOG_DEBUG(#{what => rerouting_unacked_messages, |
1585 |
3025 |
unread_messages => UnreadMessages, c2s_state => StateData}), |
1586 |
3025 |
flush_stream_mgmt_buffer(StateData), |
1587 |
3025 |
bounce_csi_buffer(StateData), |
1588 |
3025 |
bounce_messages(UnreadMessages, StateData). |
1589 |
|
|
1590 |
|
%%%---------------------------------------------------------------------- |
1591 |
|
%%% Internal functions |
1592 |
|
%%%---------------------------------------------------------------------- |
1593 |
|
|
1594 |
|
fix_message_from_user(#xmlel{attrs = Attrs} = El0, Lang) -> |
1595 |
7536 |
NewEl1 = jlib:remove_delay_tags(El0), |
1596 |
7536 |
case xml:get_attr_s(<<"xml:lang">>, Attrs) of |
1597 |
|
<<>> -> |
1598 |
7532 |
case Lang of |
1599 |
86 |
<<>> -> NewEl1; |
1600 |
|
Lang -> |
1601 |
7446 |
xml:replace_tag_attr(<<"xml:lang">>, Lang, NewEl1) |
1602 |
|
end; |
1603 |
|
_ -> |
1604 |
4 |
NewEl1 |
1605 |
|
end. |
1606 |
|
|
1607 |
34 |
should_close_session(resume_session) -> true; |
1608 |
3002 |
should_close_session(session_established) -> true; |
1609 |
874 |
should_close_session(_) -> false. |
1610 |
|
|
1611 |
|
-spec generate_random_resource() -> jid:lresource(). |
1612 |
|
generate_random_resource() -> |
1613 |
36 |
<<(mongoose_bin:gen_from_crypto())/binary, (mongoose_bin:gen_from_timestamp())/binary>>. |
1614 |
|
|
1615 |
|
-spec change_shaper(state(), jid:jid()) -> any(). |
1616 |
|
change_shaper(#state{host_type = HostType, server = Server, shaper = ShaperRule, |
1617 |
|
socket = Socket, sockmod = SockMod}, JID) -> |
1618 |
9563 |
Shaper = acl:match_rule(HostType, Server, ShaperRule, JID), |
1619 |
9563 |
SockMod:change_shaper(Socket, Shaper). |
1620 |
|
|
1621 |
|
-spec send_text(state(), Text :: binary()) -> any(). |
1622 |
|
send_text(StateData, Text) -> |
1623 |
34627 |
?LOG_DEBUG(#{what => c2s_send_text, text => <<"Send XML to the socket">>, |
1624 |
34627 |
send_text => Text, c2s_state => StateData}), |
1625 |
34627 |
Size = size(Text), |
1626 |
34627 |
mongoose_metrics:update(global, [data, xmpp, sent, xml_stanza_size], Size), |
1627 |
34627 |
(StateData#state.sockmod):send(StateData#state.socket, Text). |
1628 |
|
|
1629 |
|
-spec maybe_send_element_from_server_jid_safe(state(), exml:element()) -> any(). |
1630 |
|
maybe_send_element_from_server_jid_safe(State, El) -> |
1631 |
25 |
maybe_send_element_from_server_jid_safe(no_acc, State, El). |
1632 |
|
|
1633 |
|
-spec maybe_send_element_from_server_jid_safe(mongoose_acc:t() | no_acc, |
1634 |
|
state(), |
1635 |
|
exml:element()) -> any(). |
1636 |
|
maybe_send_element_from_server_jid_safe(Acc, #state{stream_mgmt = StreamMgmt} = State, El) |
1637 |
|
when StreamMgmt =:= false; StreamMgmt =:= disabled -> |
1638 |
13195 |
send_element_from_server_jid(Acc, State, El); |
1639 |
|
maybe_send_element_from_server_jid_safe(Acc, State, El) -> |
1640 |
215 |
case catch send_element_from_server_jid(Acc, State, El) of |
1641 |
176 |
ok -> ok; |
1642 |
39 |
_ -> error |
1643 |
|
end. |
1644 |
|
|
1645 |
|
-spec send_element_from_server_jid(state(), exml:element()) -> any(). |
1646 |
|
send_element_from_server_jid(StateData, #xmlel{} = El) -> |
1647 |
13175 |
send_element_from_server_jid(no_acc, StateData, El). |
1648 |
|
|
1649 |
|
-spec send_element_from_server_jid(mongoose_acc:t() | no_acc, state(), exml:element()) -> any(). |
1650 |
|
send_element_from_server_jid(no_acc, #state{server = Server} = StateData, #xmlel{} = El) -> |
1651 |
13200 |
Acc = new_acc(StateData, #{location => ?LOCATION, |
1652 |
|
from_jid => jid:make_noprep(<<>>, Server, <<>>), |
1653 |
|
to_jid => StateData#state.jid, |
1654 |
|
element => El}), |
1655 |
13200 |
send_element_from_server_jid(Acc, StateData, El); |
1656 |
|
send_element_from_server_jid(Acc, StateData, #xmlel{} = El) -> |
1657 |
26585 |
Acc1 = send_element(Acc, El, StateData), |
1658 |
26541 |
mongoose_acc:get(c2s, send_result, Acc1). |
1659 |
|
|
1660 |
|
%% @doc This is the termination point - from here stanza is sent to the user |
1661 |
|
-spec send_element(mongoose_acc:t(), exml:element(), state()) -> mongoose_acc:t(). |
1662 |
|
send_element(Acc, El, #state{host_type = undefined} = StateData) -> |
1663 |
7 |
Res = do_send_element(El, StateData), |
1664 |
7 |
mongoose_acc:set(c2s, send_result, Res, Acc); |
1665 |
|
send_element(Acc, El, #state{host_type = HostType} = StateData) -> |
1666 |
26690 |
Acc1 = mongoose_hooks:xmpp_send_element(HostType, Acc, El), |
1667 |
26690 |
Res = do_send_element(El, StateData), |
1668 |
26639 |
mongoose_acc:set(c2s, send_result, Res, Acc1). |
1669 |
|
|
1670 |
|
do_send_element(El, #state{sockmod = SockMod} = StateData) |
1671 |
|
when StateData#state.xml_socket -> |
1672 |
1367 |
mongoose_transport:send_xml(SockMod, StateData#state.socket, |
1673 |
|
{xmlstreamelement, El}); |
1674 |
|
do_send_element(El, StateData) -> |
1675 |
25330 |
send_text(StateData, exml:to_binary(El)). |
1676 |
|
|
1677 |
|
-spec send_header(State :: state(), |
1678 |
|
Server :: jid:server(), |
1679 |
|
Version :: binary(), |
1680 |
|
Lang :: ejabberd:lang()) -> any(). |
1681 |
|
send_header(StateData, Server, Version, Lang) |
1682 |
|
when StateData#state.xml_socket -> |
1683 |
330 |
VersionAttr = case Version of |
1684 |
:-( |
<<>> -> []; |
1685 |
330 |
_ -> [{<<"version">>, Version}] |
1686 |
|
end, |
1687 |
330 |
LangAttr = case Lang of |
1688 |
:-( |
<<>> -> []; |
1689 |
330 |
_ -> [{<<"xml:lang">>, Lang}] |
1690 |
|
end, |
1691 |
330 |
Header = {xmlstreamstart, |
1692 |
|
<<"stream:stream">>, |
1693 |
|
VersionAttr ++ |
1694 |
|
LangAttr ++ |
1695 |
|
[{<<"xmlns">>, ?NS_CLIENT}, |
1696 |
|
{<<"xmlns:stream">>, <<"http://etherx.jabber.org/streams">>}, |
1697 |
|
{<<"id">>, StateData#state.streamid}, |
1698 |
|
{<<"from">>, Server}]}, |
1699 |
330 |
(StateData#state.sockmod):send_xml(StateData#state.socket, Header); |
1700 |
|
send_header(StateData, Server, Version, Lang) -> |
1701 |
6214 |
VersionStr = case Version of |
1702 |
6 |
<<>> -> <<>>; |
1703 |
6208 |
_ -> <<" version='", (Version)/binary, "'">> |
1704 |
|
end, |
1705 |
6214 |
LangStr = case Lang of |
1706 |
3 |
<<>> -> <<>>; |
1707 |
6211 |
_ when is_binary(Lang) -> <<" xml:lang='", (Lang)/binary, "'">> |
1708 |
|
end, |
1709 |
6214 |
Header = <<"<?xml version='1.0'?>", |
1710 |
|
"<stream:stream xmlns='jabber:client' ", |
1711 |
|
"xmlns:stream='http://etherx.jabber.org/streams' ", |
1712 |
|
"id='", (StateData#state.streamid)/binary, "' ", |
1713 |
|
"from='", (Server)/binary, "'", |
1714 |
|
(VersionStr)/binary, |
1715 |
|
(LangStr)/binary, ">">>, |
1716 |
6214 |
send_text(StateData, Header). |
1717 |
|
|
1718 |
|
-spec maybe_send_trailer_safe(State :: state()) -> any(). |
1719 |
|
maybe_send_trailer_safe(#state{stream_mgmt = StreamMgmt} = State) |
1720 |
|
when StreamMgmt =:= false; StreamMgmt =:= disabled -> |
1721 |
4 |
send_trailer(State); |
1722 |
|
maybe_send_trailer_safe(StateData) -> |
1723 |
21 |
catch send_trailer(StateData). |
1724 |
|
|
1725 |
|
send_trailer(StateData) when StateData#state.xml_socket -> |
1726 |
162 |
(StateData#state.sockmod):send_xml(StateData#state.socket, |
1727 |
|
{xmlstreamend, <<"stream:stream">>}); |
1728 |
|
send_trailer(StateData) -> |
1729 |
3083 |
send_text(StateData, ?STREAM_TRAILER). |
1730 |
|
|
1731 |
|
|
1732 |
|
-spec send_and_maybe_buffer_stanza(mongoose_acc:t(), packet(), state()) -> |
1733 |
|
{ok | resume, mongoose_acc:t(), state()}. |
1734 |
|
send_and_maybe_buffer_stanza(Acc, {J1, J2, El}, State)-> |
1735 |
13359 |
{SendResult, _, BufferedStateData} = send_and_maybe_buffer_stanza_no_ack(Acc, |
1736 |
|
{J1, J2, El}, |
1737 |
|
State), |
1738 |
13359 |
Acc1 = mod_amp:check_packet(Acc, result_to_amp_event(SendResult)), |
1739 |
13359 |
case SendResult of |
1740 |
|
ok -> |
1741 |
13324 |
try maybe_send_ack_request(Acc1, BufferedStateData) of |
1742 |
|
ResAcc -> |
1743 |
13323 |
{ok, ResAcc, BufferedStateData} |
1744 |
|
catch |
1745 |
|
_:E -> |
1746 |
1 |
?LOG_DEBUG(#{what => send_ack_request_error, |
1747 |
|
text => <<"maybe_send_ack_request crashed, entering resume session next">>, |
1748 |
1 |
reason => E, c2s_state => State}), |
1749 |
1 |
{resume, Acc1, BufferedStateData} |
1750 |
|
end; |
1751 |
|
_ -> |
1752 |
35 |
?LOG_DEBUG(#{what => send_element_error, |
1753 |
|
text => <<"Sending element failed, entering resume session next">>, |
1754 |
35 |
reason => SendResult, c2s_state => State}), |
1755 |
35 |
{resume, Acc1, BufferedStateData} |
1756 |
|
end. |
1757 |
|
|
1758 |
13324 |
result_to_amp_event(ok) -> delivered; |
1759 |
35 |
result_to_amp_event(_) -> delivery_failed. |
1760 |
|
|
1761 |
|
-spec send_and_maybe_buffer_stanza_no_ack(mongoose_acc:t(), packet(), state()) -> |
1762 |
|
{ok | any(), mongoose_acc:t(), state()}. |
1763 |
|
send_and_maybe_buffer_stanza_no_ack(Acc, {_, _, Stanza} = Packet, State) -> |
1764 |
13385 |
SendResult = maybe_send_element_from_server_jid_safe(Acc, State, Stanza), |
1765 |
13385 |
BufferedStateData = buffer_out_stanza(Acc, Packet, State), |
1766 |
13385 |
{SendResult, Acc, BufferedStateData}. |
1767 |
|
|
1768 |
|
|
1769 |
|
-spec new_id() -> binary(). |
1770 |
|
new_id() -> |
1771 |
7061 |
mongoose_bin:gen_from_crypto(). |
1772 |
|
|
1773 |
|
-spec get_conn_type(state()) -> conntype(). |
1774 |
|
get_conn_type(StateData) -> |
1775 |
3037 |
case (StateData#state.sockmod):get_sockmod(StateData#state.socket) of |
1776 |
2831 |
gen_tcp -> c2s; |
1777 |
35 |
ejabberd_tls -> c2s_tls; |
1778 |
|
ejabberd_zlib -> |
1779 |
8 |
case ejabberd_zlib:get_sockmod(ejabberd_socket:get_socket(StateData#state.socket)) of |
1780 |
:-( |
gen_tcp -> c2s_compressed; |
1781 |
8 |
ejabberd_tls -> c2s_compressed_tls |
1782 |
|
end; |
1783 |
163 |
_ -> unknown |
1784 |
|
end. |
1785 |
|
|
1786 |
|
|
1787 |
|
-spec process_presence_probe(From :: jid:simple_jid() | jid:jid(), |
1788 |
|
To :: jid:jid(), |
1789 |
|
Acc :: mongoose_acc:t(), |
1790 |
|
State :: state()) -> mongoose_acc:t(). |
1791 |
|
process_presence_probe(From, To, Acc, StateData) -> |
1792 |
2913 |
{LFrom, LBareFrom} = lowcase_and_bare(From), |
1793 |
2913 |
case StateData#state.pres_last of |
1794 |
|
undefined -> |
1795 |
1 |
Acc; |
1796 |
|
_ -> |
1797 |
2912 |
case {should_retransmit_last_presence(LFrom, LBareFrom, StateData), |
1798 |
|
specifically_visible_to(LFrom, StateData)} of |
1799 |
|
{true, _} -> |
1800 |
2912 |
Timestamp = StateData#state.pres_timestamp, |
1801 |
2912 |
TS = calendar:system_time_to_rfc3339(Timestamp, [{offset, "Z"}]), |
1802 |
2912 |
Packet = xml:append_subtags( |
1803 |
|
StateData#state.pres_last, |
1804 |
|
%% To is the one sending the presence (the target of the probe) |
1805 |
|
[jlib:timestamp_to_xml(TS, To, <<>>)]), |
1806 |
2912 |
check_privacy_and_route_probe(StateData, From, To, Acc, Packet); |
1807 |
|
{false, true} -> |
1808 |
:-( |
ejabberd_router:route(To, From, Acc, #xmlel{name = <<"presence">>}); |
1809 |
|
_ -> |
1810 |
:-( |
Acc |
1811 |
|
end |
1812 |
|
end. |
1813 |
|
|
1814 |
|
-spec check_privacy_and_route_probe(StateData :: state(), |
1815 |
|
From :: jid:jid(), |
1816 |
|
To :: jid:jid(), |
1817 |
|
Acc :: mongoose_acc:t(), |
1818 |
|
Packet :: exml:element()) -> mongoose_acc:t(). |
1819 |
|
check_privacy_and_route_probe(StateData, From, To, Acc, Packet) -> |
1820 |
2912 |
{Acc1, Res} = privacy_check_packet(Acc, Packet, To, From, out, StateData), |
1821 |
2912 |
case Res of |
1822 |
|
allow -> |
1823 |
2912 |
Pid = element(2, StateData#state.sid), |
1824 |
2912 |
Acc2 = mongoose_hooks:presence_probe_hook( |
1825 |
|
StateData#state.host_type, |
1826 |
|
Acc1, |
1827 |
|
From, To, Pid), |
1828 |
|
%% Don't route a presence probe to oneself |
1829 |
2912 |
case jid:are_equal(From, To) of |
1830 |
|
false -> |
1831 |
139 |
ejabberd_router:route(To, From, Acc2, Packet); |
1832 |
|
true -> |
1833 |
2773 |
Acc2 |
1834 |
|
end; |
1835 |
|
_ -> |
1836 |
:-( |
Acc1 |
1837 |
|
end. |
1838 |
|
|
1839 |
|
should_retransmit_last_presence(LFrom, LBareFrom, |
1840 |
|
#state{pres_invis = Invisible} = S) -> |
1841 |
2912 |
not Invisible |
1842 |
2912 |
andalso is_subscribed_to_my_presence(LFrom, LBareFrom, S) |
1843 |
2912 |
andalso not invisible_to(LFrom, LBareFrom, S). |
1844 |
|
|
1845 |
|
is_subscribed_to_my_presence(JID, S) -> |
1846 |
34 |
{Lowcase, Bare} = lowcase_and_bare(JID), |
1847 |
34 |
is_subscribed_to_my_presence(Lowcase, Bare, S). |
1848 |
|
|
1849 |
|
is_subscribed_to_my_presence(LFrom, LBareFrom, S) -> |
1850 |
3111 |
gb_sets:is_element(LFrom, S#state.pres_f) |
1851 |
3103 |
orelse (LFrom /= LBareFrom) |
1852 |
3046 |
andalso gb_sets:is_element(LBareFrom, S#state.pres_f). |
1853 |
|
|
1854 |
|
am_i_subscribed_to_presence(LJID, LBareJID, S) -> |
1855 |
165 |
gb_sets:is_element(LJID, S#state.pres_t) |
1856 |
150 |
orelse (LJID /= LBareJID) |
1857 |
133 |
andalso gb_sets:is_element(LBareJID, S#state.pres_t). |
1858 |
|
|
1859 |
|
lowcase_and_bare(JID) -> |
1860 |
10155 |
LJID = jid:to_lower(JID), |
1861 |
10155 |
{ LJID, jid:to_bare(LJID)}. |
1862 |
|
|
1863 |
|
invisible_to(LFrom, LBareFrom, S) -> |
1864 |
2912 |
gb_sets:is_element(LFrom, S#state.pres_i) |
1865 |
2912 |
orelse (LFrom /= LBareFrom) |
1866 |
2912 |
andalso gb_sets:is_element(LBareFrom, S#state.pres_i). |
1867 |
|
|
1868 |
|
%% @doc Is generally invisible, but visible to a particular resource? |
1869 |
|
specifically_visible_to(LFrom, #state{pres_invis = Invisible} = S) -> |
1870 |
2912 |
Invisible |
1871 |
:-( |
andalso gb_sets:is_element(LFrom, S#state.pres_f) |
1872 |
:-( |
andalso gb_sets:is_element(LFrom, S#state.pres_a). |
1873 |
|
|
1874 |
|
%% @doc User updates his presence (non-directed presence packet) |
1875 |
|
-spec presence_update(Acc :: mongoose_acc:t(), |
1876 |
|
From :: 'undefined' | jid:jid(), |
1877 |
|
State :: state()) -> {mongoose_acc:t(), state()}. |
1878 |
|
presence_update(Acc, From, StateData) -> |
1879 |
3001 |
Packet = mongoose_acc:element(Acc), |
1880 |
3001 |
case mongoose_acc:stanza_type(Acc) of |
1881 |
|
<<"unavailable">> -> |
1882 |
196 |
Status = exml_query:path(Packet, [{element, <<"status">>}, cdata], <<>>), |
1883 |
196 |
Info = #{ip => StateData#state.ip, conn => StateData#state.conn, |
1884 |
|
auth_module => StateData#state.auth_module }, |
1885 |
196 |
Acc1 = ejabberd_sm:unset_presence(Acc, |
1886 |
|
StateData#state.sid, |
1887 |
|
StateData#state.jid, |
1888 |
|
Status, |
1889 |
|
Info), |
1890 |
196 |
Acc2 = presence_broadcast(Acc1, StateData#state.pres_a, StateData), |
1891 |
196 |
Acc3 = presence_broadcast(Acc2, StateData#state.pres_i, StateData), |
1892 |
|
% and here we reach the end |
1893 |
196 |
{Acc3, StateData#state{pres_last = undefined, |
1894 |
|
pres_timestamp = undefined, |
1895 |
|
pres_a = gb_sets:new(), |
1896 |
|
pres_i = gb_sets:new(), |
1897 |
|
pres_invis = false}}; |
1898 |
|
<<"invisible">> -> |
1899 |
1 |
NewPriority = get_priority_from_presence(Packet), |
1900 |
1 |
Acc0 = update_priority(Acc, NewPriority, Packet, StateData), |
1901 |
1 |
case StateData#state.pres_invis of |
1902 |
|
false -> |
1903 |
1 |
Acc1 = presence_broadcast(Acc0, |
1904 |
|
StateData#state.pres_a, |
1905 |
|
StateData), |
1906 |
1 |
Acc2 = presence_broadcast(Acc1, |
1907 |
|
StateData#state.pres_i, |
1908 |
|
StateData), |
1909 |
1 |
S1 = StateData#state{pres_last = undefined, |
1910 |
|
pres_timestamp = undefined, |
1911 |
|
pres_a = gb_sets:new(), |
1912 |
|
pres_i = gb_sets:new(), |
1913 |
|
pres_invis = true}, |
1914 |
1 |
presence_broadcast_first(Acc2, From, S1, Packet); |
1915 |
|
true -> |
1916 |
:-( |
{Acc0, StateData} |
1917 |
|
end; |
1918 |
|
<<"error">> -> |
1919 |
:-( |
{Acc, StateData}; |
1920 |
|
<<"probe">> -> |
1921 |
:-( |
{Acc, StateData}; |
1922 |
|
<<"subscribe">> -> |
1923 |
:-( |
{Acc, StateData}; |
1924 |
|
<<"subscribed">> -> |
1925 |
:-( |
{Acc, StateData}; |
1926 |
|
<<"unsubscribe">> -> |
1927 |
:-( |
{Acc, StateData}; |
1928 |
|
<<"unsubscribed">> -> |
1929 |
:-( |
{Acc, StateData}; |
1930 |
|
_ -> |
1931 |
2804 |
presence_update_to_available(Acc, From, Packet, StateData) |
1932 |
|
end. |
1933 |
|
|
1934 |
|
-spec presence_update_to_available(Acc :: mongoose_acc:t(), |
1935 |
|
From :: jid:jid(), |
1936 |
|
Packet :: exml:element(), |
1937 |
|
StateData :: state()) -> {mongoose_acc:t(), state()}. |
1938 |
|
presence_update_to_available(Acc, From, Packet, StateData) -> |
1939 |
2804 |
OldPriority = case StateData#state.pres_last of |
1940 |
|
undefined -> |
1941 |
2781 |
0; |
1942 |
|
OldPresence -> |
1943 |
23 |
get_priority_from_presence(OldPresence) |
1944 |
|
end, |
1945 |
2804 |
NewPriority = get_priority_from_presence(Packet), |
1946 |
2804 |
Timestamp = erlang:system_time(second), |
1947 |
2804 |
Acc1 = update_priority(Acc, NewPriority, Packet, StateData), |
1948 |
|
|
1949 |
2804 |
NewStateData = StateData#state{pres_last = Packet, |
1950 |
|
pres_invis = false, |
1951 |
|
pres_timestamp = Timestamp}, |
1952 |
|
|
1953 |
2804 |
FromUnavail = (StateData#state.pres_last == undefined) or StateData#state.pres_invis, |
1954 |
2804 |
?LOG_DEBUG(#{what => presence_update_to_available, |
1955 |
|
text => <<"Presence changes from unavailable to available">>, |
1956 |
2804 |
from_unavail => FromUnavail, acc => Acc, c2s_state => StateData}), |
1957 |
2804 |
presence_update_to_available(FromUnavail, Acc1, OldPriority, NewPriority, From, |
1958 |
|
Packet, NewStateData). |
1959 |
|
|
1960 |
|
%% @doc the first one is run when presence changes from unavailable to anything else |
1961 |
|
-spec presence_update_to_available(FromUnavailable :: boolean(), |
1962 |
|
Acc :: mongoose_acc:t(), |
1963 |
|
OldPriority :: integer(), |
1964 |
|
NewPriority :: integer(), |
1965 |
|
From :: jid:jid(), |
1966 |
|
Packet :: exml:element(), |
1967 |
|
StateData :: state()) -> {mongoose_acc:t(), state()}. |
1968 |
|
presence_update_to_available(true, Acc, _, NewPriority, From, Packet, StateData) -> |
1969 |
2781 |
Acc2 = mongoose_hooks:user_available_hook(StateData#state.host_type, |
1970 |
|
Acc, |
1971 |
|
StateData#state.jid), |
1972 |
2781 |
Res = case NewPriority >= 0 of |
1973 |
|
true -> |
1974 |
2781 |
Acc3 = mongoose_hooks:roster_get_subscription_lists( |
1975 |
|
StateData#state.host_type, |
1976 |
|
Acc2, |
1977 |
|
StateData#state.jid), |
1978 |
2781 |
{_, _, Pending} = mongoose_acc:get(roster, subscription_lists, |
1979 |
|
{[], [], []}, Acc3), |
1980 |
2781 |
Acc4 = resend_offline_messages(Acc3, StateData), |
1981 |
2781 |
resend_subscription_requests(Acc4, |
1982 |
|
StateData#state{pending_invitations = Pending}); |
1983 |
|
false -> |
1984 |
:-( |
{Acc2, StateData} |
1985 |
|
end, |
1986 |
2781 |
{Accum, NewStateData1} = Res, |
1987 |
2781 |
presence_broadcast_first(Accum, From, NewStateData1, Packet); |
1988 |
|
presence_update_to_available(false, Acc, OldPriority, NewPriority, From, Packet, StateData) -> |
1989 |
23 |
Acc2 = presence_broadcast_to_trusted(Acc, |
1990 |
|
StateData, |
1991 |
|
From, |
1992 |
|
StateData#state.pres_f, |
1993 |
|
StateData#state.pres_a, |
1994 |
|
Packet), |
1995 |
23 |
Acc3 = case OldPriority < 0 andalso NewPriority >= 0 of |
1996 |
|
true -> |
1997 |
:-( |
resend_offline_messages(Acc2, StateData); |
1998 |
|
false -> |
1999 |
23 |
Acc2 |
2000 |
|
end, |
2001 |
23 |
{Acc3, StateData}. |
2002 |
|
|
2003 |
|
%% @doc User sends a directed presence packet |
2004 |
|
-spec presence_track(Acc :: mongoose_acc:t(), |
2005 |
|
State :: state()) -> {mongoose_acc:t(), state()}. |
2006 |
|
presence_track(Acc, StateData) -> |
2007 |
414 |
To = mongoose_acc:to_jid(Acc), |
2008 |
414 |
LTo = jid:to_lower(To), |
2009 |
414 |
case mongoose_acc:stanza_type(Acc) of |
2010 |
|
<<"unavailable">> -> |
2011 |
12 |
Acc1 = check_privacy_and_route(Acc, StateData), |
2012 |
12 |
I = gb_sets:del_element(LTo, StateData#state.pres_i), |
2013 |
12 |
A = gb_sets:del_element(LTo, StateData#state.pres_a), |
2014 |
12 |
{Acc1, StateData#state{pres_i = I, |
2015 |
|
pres_a = A}}; |
2016 |
|
<<"invisible">> -> |
2017 |
:-( |
Acc1 = check_privacy_and_route(Acc, StateData), |
2018 |
:-( |
I = gb_sets:add_element(LTo, StateData#state.pres_i), |
2019 |
:-( |
A = gb_sets:del_element(LTo, StateData#state.pres_a), |
2020 |
:-( |
{Acc1, StateData#state{pres_i = I, |
2021 |
|
pres_a = A}}; |
2022 |
|
<<"subscribe">> -> |
2023 |
51 |
Acc1 = process_presence_subscription_and_route(Acc, subscribe, StateData), |
2024 |
51 |
{Acc1, StateData}; |
2025 |
|
<<"subscribed">> -> |
2026 |
48 |
Acc1 = process_presence_subscription_and_route(Acc, subscribed, StateData), |
2027 |
48 |
{Acc1, StateData}; |
2028 |
|
<<"unsubscribe">> -> |
2029 |
3 |
Acc1 = process_presence_subscription_and_route(Acc, unsubscribe, StateData), |
2030 |
3 |
{Acc1, StateData}; |
2031 |
|
<<"unsubscribed">> -> |
2032 |
4 |
Acc1 = process_presence_subscription_and_route(Acc, unsubscribed, StateData), |
2033 |
4 |
{Acc1, StateData}; |
2034 |
|
<<"error">> -> |
2035 |
2 |
Acc1 = check_privacy_and_route(Acc, StateData), |
2036 |
2 |
{Acc1, StateData}; |
2037 |
|
<<"probe">> -> |
2038 |
:-( |
Acc1 = check_privacy_and_route(Acc, StateData), |
2039 |
:-( |
{Acc1, StateData}; |
2040 |
|
_ -> |
2041 |
294 |
Acc1 = check_privacy_and_route(Acc, StateData), |
2042 |
294 |
I = gb_sets:del_element(LTo, StateData#state.pres_i), |
2043 |
294 |
A = gb_sets:add_element(LTo, StateData#state.pres_a), |
2044 |
294 |
{Acc1, StateData#state{pres_i = I, |
2045 |
|
pres_a = A}} |
2046 |
|
end. |
2047 |
|
|
2048 |
|
-spec process_presence_subscription_and_route(Acc :: mongoose_acc:t(), |
2049 |
|
Type :: subscribe | subscribed | unsubscribe | unsubscribed, |
2050 |
|
StateData :: state()) -> mongoose_acc:t(). |
2051 |
|
process_presence_subscription_and_route(Acc, Type, StateData) -> |
2052 |
106 |
From = mongoose_acc:from_jid(Acc), |
2053 |
106 |
To = mongoose_acc:to_jid(Acc), |
2054 |
106 |
Acc1 = mongoose_hooks:roster_out_subscription(Acc, From, To, Type), |
2055 |
106 |
check_privacy_and_route(Acc1, jid:to_bare(From), StateData). |
2056 |
|
|
2057 |
|
-spec check_privacy_and_route(Acc :: mongoose_acc:t(), |
2058 |
|
StateData :: state()) -> mongoose_acc:t(). |
2059 |
|
check_privacy_and_route(Acc, StateData) -> |
2060 |
4137 |
check_privacy_and_route(Acc, mongoose_acc:from_jid(Acc), StateData). |
2061 |
|
|
2062 |
|
-spec check_privacy_and_route(Acc :: mongoose_acc:t(), |
2063 |
|
FromRoute :: jid:jid(), |
2064 |
|
StateData :: state()) -> mongoose_acc:t(). |
2065 |
|
check_privacy_and_route(Acc, FromRoute, StateData) -> |
2066 |
4243 |
From = mongoose_acc:from_jid(Acc), |
2067 |
4243 |
To = mongoose_acc:to_jid(Acc), |
2068 |
4243 |
{Acc1, Res} = privacy_check_packet(Acc, To, out, StateData), |
2069 |
4243 |
Packet = mongoose_acc:element(Acc1), |
2070 |
4243 |
case Res of |
2071 |
|
deny -> |
2072 |
2 |
{Acc2, Err} = jlib:make_error_reply(Acc1, Packet, |
2073 |
|
mongoose_xmpp_errors:not_acceptable_cancel()), |
2074 |
2 |
ejabberd_router:route(To, From, Acc2, Err); |
2075 |
|
block -> |
2076 |
4 |
{Acc2, Err} = jlib:make_error_reply(Acc1, Packet, mongoose_xmpp_errors:not_acceptable_blocked()), |
2077 |
4 |
ejabberd_router:route(To, From, Acc2, Err); |
2078 |
|
allow -> |
2079 |
4237 |
ejabberd_router:route(FromRoute, To, Acc1, Packet) |
2080 |
|
end. |
2081 |
|
|
2082 |
|
|
2083 |
|
-spec privacy_check_packet(Packet :: exml:element(), |
2084 |
|
From :: jid:jid(), |
2085 |
|
To :: jid:jid(), |
2086 |
|
Dir :: 'in' | 'out', |
2087 |
|
StateData :: state()) -> allow|deny|block. |
2088 |
|
privacy_check_packet(#xmlel{} = Packet, From, To, Dir, StateData) -> |
2089 |
|
% in some cases we need an accumulator-less privacy check |
2090 |
14 |
Acc = new_acc(StateData, #{location => ?LOCATION, |
2091 |
|
from_jid => From, |
2092 |
|
to_jid => To, |
2093 |
|
element => Packet}), |
2094 |
14 |
{_, Res} = privacy_check_packet(Acc, To, Dir, StateData), |
2095 |
14 |
Res. |
2096 |
|
|
2097 |
|
-spec privacy_check_packet(Acc :: mongoose_acc:t(), |
2098 |
|
To :: jid:jid(), |
2099 |
|
Dir :: 'in' | 'out', |
2100 |
|
StateData :: state()) -> {mongoose_acc:t(), allow|deny|block}. |
2101 |
|
privacy_check_packet(Acc, To, Dir, StateData) -> |
2102 |
17679 |
mongoose_privacy:privacy_check_packet(Acc, |
2103 |
|
StateData#state.jid, |
2104 |
|
StateData#state.privacy_list, |
2105 |
|
To, |
2106 |
|
Dir). |
2107 |
|
|
2108 |
|
-spec privacy_check_packet(Acc :: mongoose_acc:t(), |
2109 |
|
Packet :: exml:element(), |
2110 |
|
From :: jid:jid(), |
2111 |
|
To :: jid:jid(), |
2112 |
|
Dir :: 'in' | 'out', |
2113 |
|
StateData :: state()) -> {mongoose_acc:t(), allow|deny|block}. |
2114 |
|
privacy_check_packet(Acc, Packet, From, To, Dir, StateData) -> |
2115 |
5955 |
mongoose_privacy:privacy_check_packet({Acc, Packet}, |
2116 |
|
StateData#state.jid, |
2117 |
|
StateData#state.privacy_list, |
2118 |
|
From, |
2119 |
|
To, |
2120 |
|
Dir). |
2121 |
|
|
2122 |
|
-spec presence_broadcast(Acc :: mongoose_acc:t(), |
2123 |
|
JIDSet :: jid_set(), |
2124 |
|
State :: state()) -> mongoose_acc:t(). |
2125 |
|
presence_broadcast(Acc, JIDSet, StateData) -> |
2126 |
5578 |
From = mongoose_acc:from_jid(Acc), |
2127 |
5578 |
lists:foldl(fun(JID, A) -> |
2128 |
3102 |
FJID = jid:make(JID), |
2129 |
3102 |
{A1, Res} = privacy_check_packet(A, FJID, out, StateData), |
2130 |
3102 |
case Res of |
2131 |
|
allow -> |
2132 |
3097 |
ejabberd_router:route(From, FJID, A1); |
2133 |
|
_ -> |
2134 |
5 |
A1 |
2135 |
|
end |
2136 |
|
end, Acc, gb_sets:to_list(JIDSet)). |
2137 |
|
|
2138 |
|
-spec presence_broadcast_to_trusted(Acc :: mongoose_acc:t(), |
2139 |
|
State :: state(), |
2140 |
|
From :: 'undefined' | jid:jid(), |
2141 |
|
T :: jid_set(), |
2142 |
|
A :: jid_set(), |
2143 |
|
Packet :: exml:element()) -> mongoose_acc:t(). |
2144 |
|
presence_broadcast_to_trusted(Acc, StateData, From, T, A, Packet) -> |
2145 |
23 |
lists:foldl( |
2146 |
|
fun(JID, Ac) -> |
2147 |
27 |
case gb_sets:is_element(JID, T) of |
2148 |
|
true -> |
2149 |
27 |
FJID = jid:make(JID), |
2150 |
27 |
check_privacy_and_route_or_ignore(Ac, StateData, From, FJID, Packet, out); |
2151 |
|
_ -> |
2152 |
:-( |
Ac |
2153 |
|
end |
2154 |
|
end, Acc, gb_sets:to_list(A)). |
2155 |
|
|
2156 |
|
-spec presence_broadcast_first(mongoose_acc:t(), |
2157 |
|
From :: 'undefined' | jid:jid(), |
2158 |
|
State :: state(), |
2159 |
|
Packet :: exml:element()) -> {mongoose_acc:t(), state()}. |
2160 |
|
presence_broadcast_first(Acc0, From, StateData, Packet) -> |
2161 |
2782 |
Stanza = #xmlel{name = <<"presence">>, |
2162 |
|
attrs = [{<<"type">>, <<"probe">>}]}, |
2163 |
2782 |
Acc = gb_sets:fold(fun(JID, A) -> |
2164 |
2792 |
ejabberd_router:route(From, jid:make(JID), A, Stanza) |
2165 |
|
end, |
2166 |
|
Acc0, |
2167 |
|
StateData#state.pres_t), |
2168 |
2782 |
case StateData#state.pres_invis of |
2169 |
|
true -> |
2170 |
1 |
{Acc, StateData}; |
2171 |
|
false -> |
2172 |
2781 |
{As, AccFinal} = gb_sets:fold( |
2173 |
|
fun(JID, {A, Accum}) -> |
2174 |
2792 |
FJID = jid:make(JID), |
2175 |
2792 |
Accum1 = check_privacy_and_route_or_ignore(Accum, StateData, From, FJID, |
2176 |
|
Packet, out), |
2177 |
2792 |
{gb_sets:add_element(JID, A), Accum1} |
2178 |
|
end, |
2179 |
|
{StateData#state.pres_a, Acc}, |
2180 |
|
StateData#state.pres_f), |
2181 |
2781 |
{AccFinal, StateData#state{pres_a = As}} |
2182 |
|
end. |
2183 |
|
|
2184 |
|
-spec roster_change(Acc :: mongoose_acc:t(), |
2185 |
|
IJID :: jid:simple_jid() | jid:jid(), |
2186 |
|
ISubscription :: from | to | both | none, |
2187 |
|
State :: state()) -> {mongoose_acc:t(), state()}. |
2188 |
|
roster_change(Acc, IJID, ISubscription, StateData) -> |
2189 |
369 |
LIJID = jid:to_lower(IJID), |
2190 |
369 |
IsSubscribedToMe = (ISubscription == both) or (ISubscription == from), |
2191 |
369 |
AmISubscribedTo = (ISubscription == both) or (ISubscription == to), |
2192 |
369 |
WasSubscribedToMe = gb_sets:is_element(LIJID, StateData#state.pres_f), |
2193 |
369 |
FSet = case IsSubscribedToMe of |
2194 |
|
true -> |
2195 |
104 |
gb_sets:add_element(LIJID, StateData#state.pres_f); |
2196 |
|
false -> |
2197 |
265 |
gb_sets:del_element(LIJID, StateData#state.pres_f) |
2198 |
|
end, |
2199 |
369 |
TSet = case AmISubscribedTo of |
2200 |
|
true -> |
2201 |
110 |
gb_sets:add_element(LIJID, StateData#state.pres_t); |
2202 |
|
false -> |
2203 |
259 |
gb_sets:del_element(LIJID, StateData#state.pres_t) |
2204 |
|
end, |
2205 |
369 |
case StateData#state.pres_last of |
2206 |
|
undefined -> |
2207 |
:-( |
{Acc, StateData#state{pres_f = FSet, pres_t = TSet}}; |
2208 |
|
P -> |
2209 |
369 |
?LOG_DEBUG(#{what => roster_changed, roster_jid => LIJID, |
2210 |
369 |
acc => Acc, c2s_state => StateData}), |
2211 |
369 |
From = StateData#state.jid, |
2212 |
369 |
To = jid:make(IJID), |
2213 |
369 |
IsntInvisible = not StateData#state.pres_invis, |
2214 |
369 |
ImAvailableTo = gb_sets:is_element(LIJID, StateData#state.pres_a), |
2215 |
369 |
ImInvisibleTo = gb_sets:is_element(LIJID, StateData#state.pres_i), |
2216 |
369 |
BecomeAvailable = IsntInvisible and IsSubscribedToMe and not WasSubscribedToMe, |
2217 |
369 |
BecomeUnavailable = not IsSubscribedToMe and WasSubscribedToMe |
2218 |
|
and (ImAvailableTo or ImInvisibleTo), |
2219 |
369 |
case {BecomeAvailable, BecomeUnavailable} of |
2220 |
|
{true, _} -> |
2221 |
71 |
?LOG_DEBUG(#{what => become_available_to, roster_jid => LIJID, |
2222 |
71 |
acc => Acc, c2s_state => StateData}), |
2223 |
71 |
Acc1 = check_privacy_and_route_or_ignore(Acc, StateData, From, To, P, out), |
2224 |
71 |
A = gb_sets:add_element(LIJID, |
2225 |
|
StateData#state.pres_a), |
2226 |
71 |
NState = StateData#state{pres_a = A, |
2227 |
|
pres_f = FSet, |
2228 |
|
pres_t = TSet}, |
2229 |
71 |
{Acc1, NState}; |
2230 |
|
{_, true} -> |
2231 |
17 |
?LOG_DEBUG(#{what => become_unavailable_to, roster_jid => LIJID, |
2232 |
17 |
acc => Acc, c2s_state => StateData}), |
2233 |
17 |
PU = #xmlel{name = <<"presence">>, |
2234 |
|
attrs = [{<<"type">>, <<"unavailable">>}]}, |
2235 |
17 |
Acc1 = check_privacy_and_route_or_ignore(Acc, StateData, From, To, PU, out), |
2236 |
17 |
I = gb_sets:del_element(LIJID, |
2237 |
|
StateData#state.pres_i), |
2238 |
17 |
A = gb_sets:del_element(LIJID, |
2239 |
|
StateData#state.pres_a), |
2240 |
17 |
NState = StateData#state{pres_i = I, |
2241 |
|
pres_a = A, |
2242 |
|
pres_f = FSet, |
2243 |
|
pres_t = TSet}, |
2244 |
17 |
{Acc1, NState}; |
2245 |
|
_ -> |
2246 |
281 |
{Acc, StateData#state{pres_f = FSet, pres_t = TSet}} |
2247 |
|
end |
2248 |
|
end. |
2249 |
|
|
2250 |
|
-spec update_priority(Acc :: mongoose_acc:t(), |
2251 |
|
Priority :: integer(), |
2252 |
|
Packet :: exml:element(), |
2253 |
|
State :: state()) -> mongoose_acc:t(). |
2254 |
|
update_priority(Acc, Priority, Packet, StateData) -> |
2255 |
2805 |
Info = #{ip => StateData#state.ip, conn => StateData#state.conn, |
2256 |
|
auth_module => StateData#state.auth_module }, |
2257 |
2805 |
ejabberd_sm:set_presence(Acc, |
2258 |
|
StateData#state.sid, |
2259 |
|
StateData#state.jid, |
2260 |
|
Priority, |
2261 |
|
Packet, |
2262 |
|
Info). |
2263 |
|
|
2264 |
|
|
2265 |
|
-spec get_priority_from_presence(Packet :: exml:element()) -> integer(). |
2266 |
|
get_priority_from_presence(undefined) -> |
2267 |
:-( |
0; |
2268 |
|
get_priority_from_presence(PresencePacket) -> |
2269 |
2838 |
case xml:get_subtag(PresencePacket, <<"priority">>) of |
2270 |
|
false -> |
2271 |
2835 |
0; |
2272 |
|
SubEl -> |
2273 |
3 |
try binary_to_integer(xml:get_tag_cdata(SubEl)) of |
2274 |
3 |
P when is_integer(P) -> P |
2275 |
|
catch |
2276 |
:-( |
error:badarg -> 0 |
2277 |
|
end |
2278 |
|
end. |
2279 |
|
|
2280 |
|
-spec process_privacy_iq(Acc :: mongoose_acc:t(), |
2281 |
|
To :: jid:jid(), |
2282 |
|
StateData :: state()) -> {mongoose_acc:t(), state()}. |
2283 |
|
process_privacy_iq(Acc1, To, StateData) -> |
2284 |
214 |
case mongoose_iq:info(Acc1) of |
2285 |
|
{#iq{type = Type, sub_el = SubEl} = IQ, Acc2} when Type == get; Type == set -> |
2286 |
210 |
From = mongoose_acc:from_jid(Acc2), |
2287 |
210 |
{Acc3, NewStateData} = process_privacy_iq(Acc2, Type, To, StateData), |
2288 |
210 |
Res = mongoose_acc:get(hook, result, |
2289 |
|
{error, mongoose_xmpp_errors:feature_not_implemented( |
2290 |
|
<<"en">>, <<"Failed to handle the privacy IQ request in c2s">>)}, Acc3), |
2291 |
210 |
IQRes = case Res of |
2292 |
|
{result, Result} -> |
2293 |
132 |
IQ#iq{type = result, sub_el = Result}; |
2294 |
|
{result, Result, _} -> |
2295 |
70 |
IQ#iq{type = result, sub_el = Result}; |
2296 |
|
{error, Error} -> |
2297 |
8 |
IQ#iq{type = error, sub_el = [SubEl, Error]} |
2298 |
|
end, |
2299 |
210 |
Acc4 = ejabberd_router:route(To, From, Acc3, jlib:iq_to_xml(IQRes)), |
2300 |
210 |
{Acc4, NewStateData}; |
2301 |
|
_ -> |
2302 |
4 |
{Acc1, StateData} |
2303 |
|
end. |
2304 |
|
|
2305 |
|
-spec process_privacy_iq(Acc :: mongoose_acc:t(), |
2306 |
|
Type :: get | set, |
2307 |
|
To :: jid:jid(), |
2308 |
|
StateData :: state()) -> {mongoose_acc:t(), state()}. |
2309 |
|
process_privacy_iq(Acc, get, To, StateData) -> |
2310 |
66 |
From = mongoose_acc:from_jid(Acc), |
2311 |
66 |
{IQ, Acc1} = mongoose_iq:info(Acc), |
2312 |
66 |
Acc2 = mongoose_hooks:privacy_iq_get(StateData#state.host_type, Acc1, |
2313 |
|
From, To, IQ, StateData#state.privacy_list), |
2314 |
66 |
{Acc2, StateData}; |
2315 |
|
process_privacy_iq(Acc, set, To, StateData) -> |
2316 |
144 |
From = mongoose_acc:from_jid(Acc), |
2317 |
144 |
{IQ, Acc1} = mongoose_iq:info(Acc), |
2318 |
144 |
Acc2 = mongoose_hooks:privacy_iq_set(StateData#state.host_type, Acc1, |
2319 |
|
From, To, IQ), |
2320 |
144 |
case mongoose_acc:get(hook, result, undefined, Acc2) of |
2321 |
|
{result, _, NewPrivList} -> |
2322 |
70 |
maybe_update_presence(Acc2, StateData, NewPrivList), |
2323 |
70 |
NState = StateData#state{privacy_list = NewPrivList}, |
2324 |
70 |
{Acc2, NState}; |
2325 |
74 |
_ -> {Acc2, StateData} |
2326 |
|
end. |
2327 |
|
|
2328 |
|
|
2329 |
|
-spec resend_offline_messages(mongoose_acc:t(), state()) -> mongoose_acc:t(). |
2330 |
|
resend_offline_messages(Acc, StateData) -> |
2331 |
2782 |
?LOG_DEBUG(#{what => resend_offline_messages, |
2332 |
2782 |
acc => Acc, c2s_state => StateData}), |
2333 |
2782 |
Acc1 = mongoose_hooks:resend_offline_messages_hook(Acc, StateData#state.jid), |
2334 |
2782 |
Rs = mongoose_acc:get(offline, messages, [], Acc1), |
2335 |
2782 |
Acc2 = lists:foldl( |
2336 |
|
fun({route, From, To, MsgAcc}, A) -> |
2337 |
136 |
resend_offline_message(A, StateData, From, To, MsgAcc, in) |
2338 |
|
end, |
2339 |
|
Acc1, |
2340 |
|
Rs), |
2341 |
2782 |
mongoose_acc:delete(offline, messages, Acc2). % they are gone from db backend and sent |
2342 |
|
|
2343 |
|
|
2344 |
|
resend_offline_message(Acc0, StateData, From, To, Acc, in) -> |
2345 |
136 |
Packet = mongoose_acc:element(Acc), |
2346 |
136 |
NewAcc = strip_c2s_fields(Acc), |
2347 |
136 |
check_privacy_and_route_or_ignore(NewAcc, StateData, From, To, Packet, in), |
2348 |
136 |
Acc0. |
2349 |
|
|
2350 |
|
|
2351 |
|
-spec check_privacy_and_route_or_ignore(Acc :: mongoose_acc:t(), |
2352 |
|
StateData :: state(), |
2353 |
|
From :: jid:jid(), |
2354 |
|
To :: jid:jid(), |
2355 |
|
Packet :: exml:element(), |
2356 |
|
Dir :: in | out) -> any(). |
2357 |
|
check_privacy_and_route_or_ignore(Acc, StateData, From, To, Packet, Dir) -> |
2358 |
3043 |
{Acc2, Res} = privacy_check_packet(Acc, Packet, From, To, Dir, StateData), |
2359 |
3043 |
case Res of |
2360 |
3043 |
allow -> ejabberd_router:route(From, To, Acc2, Packet); |
2361 |
:-( |
_ -> Acc2 |
2362 |
|
end. |
2363 |
|
|
2364 |
|
-spec resend_subscription_requests(mongoose_acc:t(), state()) -> {mongoose_acc:t(), state()}. |
2365 |
|
resend_subscription_requests(Acc, #state{pending_invitations = Pending} = StateData) -> |
2366 |
2781 |
{NewAcc, NewState} = lists:foldl( |
2367 |
|
fun(XMLPacket, {A, #state{} = State}) -> |
2368 |
3 |
A1 = send_element(A, XMLPacket, State), |
2369 |
|
% We retrieve From i To from a stanza, because Acc has |
2370 |
|
% from_jid and to_jid that apply to 'available' stanza sent |
2371 |
|
% by the client |
2372 |
3 |
{value, From} = xml:get_tag_attr(<<"from">>, XMLPacket), |
2373 |
3 |
{value, To} = xml:get_tag_attr(<<"to">>, XMLPacket), |
2374 |
3 |
PacketTuple = {jid:from_binary(From), jid:from_binary(To), XMLPacket}, |
2375 |
3 |
BufferedStateData = buffer_out_stanza(A1, PacketTuple, State), |
2376 |
|
% this one will be next to tackle |
2377 |
3 |
A2 = maybe_send_ack_request(A1, BufferedStateData), |
2378 |
3 |
{A2, BufferedStateData} |
2379 |
|
end, {Acc, StateData}, Pending), |
2380 |
2781 |
{NewAcc, NewState#state{pending_invitations = []}}. |
2381 |
|
|
2382 |
|
|
2383 |
|
get_showtag(undefined) -> |
2384 |
:-( |
<<"unavailable">>; |
2385 |
|
get_showtag(Presence) -> |
2386 |
22 |
case xml:get_path_s(Presence, [{elem, <<"show">>}, cdata]) of |
2387 |
8 |
<<>> -> <<"available">>; |
2388 |
14 |
ShowTag -> ShowTag |
2389 |
|
end. |
2390 |
|
|
2391 |
|
|
2392 |
|
get_statustag(undefined) -> |
2393 |
:-( |
<<>>; |
2394 |
|
get_statustag(Presence) -> |
2395 |
22 |
case xml:get_path_s(Presence, [{elem, <<"status">>}, cdata]) of |
2396 |
22 |
ShowTag -> ShowTag |
2397 |
|
end. |
2398 |
|
|
2399 |
|
|
2400 |
|
-spec process_unauthenticated_stanza(State :: state(), |
2401 |
|
El :: exml:element()) -> any(). |
2402 |
|
process_unauthenticated_stanza(StateData, El) -> |
2403 |
384 |
NewEl = case xml:get_tag_attr_s(<<"xml:lang">>, El) of |
2404 |
|
<<>> -> |
2405 |
384 |
case StateData#state.lang of |
2406 |
:-( |
<<>> -> El; |
2407 |
|
L -> |
2408 |
384 |
xml:replace_tag_attr(<<"xml:lang">>, L, El) |
2409 |
|
end; |
2410 |
|
_ -> |
2411 |
:-( |
El |
2412 |
|
end, |
2413 |
384 |
Lang = xml:get_tag_attr_s(<<"xml:lang">>, NewEl), |
2414 |
384 |
case jlib:iq_query_info(NewEl) of |
2415 |
|
#iq{} = IQ -> |
2416 |
380 |
Res = mongoose_hooks:c2s_unauthenticated_iq( |
2417 |
|
StateData#state.host_type, |
2418 |
|
StateData#state.server, |
2419 |
|
IQ, StateData#state.ip), |
2420 |
380 |
case Res of |
2421 |
|
empty -> |
2422 |
|
% The only reasonable IQ's here are auth and register IQ's |
2423 |
|
% They contain secrets, so don't include subelements to response |
2424 |
:-( |
Text = <<"Forbidden unauthenticated stanza">>, |
2425 |
:-( |
ResIQ = IQ#iq{type = error, |
2426 |
|
sub_el = [mongoose_xmpp_errors:service_unavailable(Lang, Text)]}, |
2427 |
:-( |
Res1 = jlib:replace_from_to( |
2428 |
|
jid:make_noprep(<<>>, StateData#state.server, <<>>), |
2429 |
|
jid:make_noprep(<<>>, <<>>, <<>>), |
2430 |
|
jlib:iq_to_xml(ResIQ)), |
2431 |
:-( |
send_element_from_server_jid(StateData, jlib:remove_attr(<<"to">>, Res1)); |
2432 |
|
_ -> |
2433 |
380 |
send_element_from_server_jid(StateData, Res) |
2434 |
|
end; |
2435 |
|
_ -> |
2436 |
|
% Drop any stanza, which isn't IQ stanza |
2437 |
4 |
ok |
2438 |
|
end. |
2439 |
|
|
2440 |
|
|
2441 |
|
-spec peerip(SockMod :: ejabberd:sockmod(), inet:socket()) -> |
2442 |
|
undefined | {inet:ip_address(), inet:port_number()}. |
2443 |
|
peerip(SockMod, Socket) -> |
2444 |
4377 |
case mongoose_transport:peername(SockMod, Socket) of |
2445 |
4377 |
{ok, IPOK} -> IPOK; |
2446 |
:-( |
_ -> undefined |
2447 |
|
end. |
2448 |
|
|
2449 |
|
|
2450 |
|
%% @doc fsm_next_state_pack: Pack the StateData structure to improve sharing. |
2451 |
|
fsm_next_state_pack(StateName, StateData) -> |
2452 |
3026 |
fsm_next_state_gc(StateName, pack(StateData)). |
2453 |
|
|
2454 |
|
|
2455 |
|
%% @doc fsm_next_state_gc: Garbage collect the process heap to make use of |
2456 |
|
%% the newly packed StateData structure. |
2457 |
|
fsm_next_state_gc(StateName, PackedStateData) -> |
2458 |
3026 |
erlang:garbage_collect(), |
2459 |
3026 |
fsm_next_state(StateName, PackedStateData). |
2460 |
|
|
2461 |
|
|
2462 |
|
%% @doc fsm_next_state: Generate the next_state FSM tuple with different |
2463 |
|
%% timeout, depending on the future state |
2464 |
|
fsm_next_state(session_established, StateData) -> |
2465 |
24790 |
{next_state, session_established, StateData, maybe_hibernate(StateData)}; |
2466 |
|
fsm_next_state(StateName, StateData) -> |
2467 |
13641 |
{next_state, StateName, StateData, ?C2S_OPEN_TIMEOUT}. |
2468 |
|
|
2469 |
|
|
2470 |
|
%% @doc fsm_reply: Generate the reply FSM tuple with different timeout, |
2471 |
|
%% depending on the future state |
2472 |
|
fsm_reply(Reply, session_established, StateData) -> |
2473 |
22 |
{reply, Reply, session_established, StateData, maybe_hibernate(StateData)}; |
2474 |
|
fsm_reply(Reply, StateName, StateData) -> |
2475 |
:-( |
{reply, Reply, StateName, StateData, ?C2S_OPEN_TIMEOUT}. |
2476 |
|
|
2477 |
|
|
2478 |
|
%% @doc Used by c2s blacklist plugins |
2479 |
|
-spec is_ip_blacklisted('undefined' | {inet:ip_address(), inet:port_number()} |
2480 |
|
) -> boolean(). |
2481 |
|
is_ip_blacklisted(undefined) -> |
2482 |
:-( |
false; |
2483 |
|
is_ip_blacklisted({IP, _Port}) -> |
2484 |
4360 |
mongoose_hooks:check_bl_c2s(IP). |
2485 |
|
|
2486 |
|
|
2487 |
|
%% @doc Check from attributes. |
2488 |
|
-spec check_from(El, C2SJID) -> Result when |
2489 |
|
El :: exml:element(), C2SJID :: jid:jid(), |
2490 |
|
Result :: 'invalid-from' | exml:element(). |
2491 |
|
check_from(El, #jid{ luser = C2SU, lserver = C2SS, lresource = C2SR }) -> |
2492 |
7538 |
case xml:get_tag_attr(<<"from">>, El) of |
2493 |
|
false -> |
2494 |
6784 |
El; |
2495 |
|
{value, SJID} -> |
2496 |
754 |
case jid:from_binary(SJID) of |
2497 |
|
#jid{ luser = U, lserver = S, lresource = R } |
2498 |
752 |
when U == C2SU andalso S == C2SS andalso (R == C2SR orelse R == <<>>) -> El; |
2499 |
2 |
_ -> 'invalid-from' |
2500 |
|
end |
2501 |
|
end. |
2502 |
|
|
2503 |
|
|
2504 |
|
fsm_limit_opts(Opts) -> |
2505 |
4360 |
case lists:keyfind(max_fsm_queue, 1, Opts) of |
2506 |
|
{_, N} when is_integer(N) -> |
2507 |
:-( |
[{max_queue, N}]; |
2508 |
|
_ -> |
2509 |
4360 |
case mongoose_config:lookup_opt(max_fsm_queue) of |
2510 |
|
{ok, N} -> |
2511 |
4360 |
[{max_queue, N}]; |
2512 |
|
{error, not_found} -> |
2513 |
:-( |
[] |
2514 |
|
end |
2515 |
|
end. |
2516 |
|
|
2517 |
|
-spec bounce_messages(list(), #state{}) -> 'ok'. |
2518 |
|
bounce_messages(UnreadMessages, StateData) -> |
2519 |
6364 |
case get_msg(UnreadMessages) of |
2520 |
|
{ok, {route, From, To, Acc}, RemainedUnreadMessages} -> |
2521 |
70 |
reroute(From, To, Acc, StateData), |
2522 |
70 |
bounce_messages(RemainedUnreadMessages, StateData); |
2523 |
|
{ok, {store_session_info, JID, Key, Value, _FromPid}, _} -> |
2524 |
:-( |
ejabberd_sm:store_info(JID, Key, Value); |
2525 |
|
{ok, _, RemainedUnreadMessages} -> |
2526 |
|
% ignore this one, get the next message |
2527 |
3269 |
bounce_messages(RemainedUnreadMessages, StateData); |
2528 |
|
{error, no_messages} -> |
2529 |
3025 |
ok |
2530 |
|
end. |
2531 |
|
|
2532 |
|
%% Return the messages in reverse order than they were received in! |
2533 |
|
-spec flush_messages(list()) -> [mongoose_acc:t()]. |
2534 |
|
flush_messages(UnreadMessages) -> |
2535 |
11 |
flush_messages([], UnreadMessages). |
2536 |
|
|
2537 |
|
-spec flush_messages([mongoose_acc:t()], list()) -> [mongoose_acc:t()]. |
2538 |
|
flush_messages(Acc, UnreadMessages) -> |
2539 |
13 |
case get_msg(UnreadMessages) of |
2540 |
|
{ok, {route, From, To, MongooseAcc}, RemainedUnreadMessages} -> |
2541 |
2 |
El = mongoose_acc:element(MongooseAcc), |
2542 |
2 |
NewMongooseAcc = update_stanza(From, To, El, MongooseAcc), |
2543 |
2 |
NewAcc = [NewMongooseAcc | Acc], |
2544 |
2 |
flush_messages(NewAcc, RemainedUnreadMessages); |
2545 |
|
{ok, _, RemainedUnreadMessages} -> |
2546 |
|
% ignore this one, get the next message |
2547 |
:-( |
flush_messages(Acc, RemainedUnreadMessages); |
2548 |
|
{error, no_messages} -> |
2549 |
11 |
Acc |
2550 |
|
end. |
2551 |
|
|
2552 |
|
get_msg([H | T]) -> |
2553 |
33 |
{ok, H, T}; |
2554 |
|
get_msg([]) -> |
2555 |
6344 |
receive |
2556 |
3308 |
Msg -> {ok, Msg, []} |
2557 |
|
after 0 -> |
2558 |
3036 |
{error, no_messages} |
2559 |
|
end. |
2560 |
|
|
2561 |
|
%%%---------------------------------------------------------------------- |
2562 |
|
%%% XEP-0016 |
2563 |
|
%%%---------------------------------------------------------------------- |
2564 |
|
|
2565 |
|
maybe_update_presence(Acc, StateData = #state{jid = JID, pres_f = Froms}, NewList) -> |
2566 |
|
% Our own jid is added to pres_f, even though we're not a "contact", so for |
2567 |
|
% the purposes of this check we don't want it: |
2568 |
123 |
SelfJID = jid:to_lower(jid:to_bare(JID)), |
2569 |
123 |
FromsExceptSelf = gb_sets:del_element(SelfJID, Froms), |
2570 |
|
|
2571 |
123 |
gb_sets:fold( |
2572 |
|
fun(T, Ac) -> |
2573 |
7 |
send_unavail_if_newly_blocked(Ac, StateData, jid:make(T), NewList) |
2574 |
|
end, Acc, FromsExceptSelf). |
2575 |
|
|
2576 |
|
send_unavail_if_newly_blocked(Acc, StateData = #state{jid = JID}, |
2577 |
|
ContactJID, NewList) -> |
2578 |
7 |
Packet = #xmlel{name = <<"presence">>, |
2579 |
|
attrs = [{<<"type">>, <<"unavailable">>}]}, |
2580 |
|
%% WARNING: we can not use accumulator to cache privacy check result - this is |
2581 |
|
%% the only place where the list to check against changes |
2582 |
7 |
OldResult = privacy_check_packet(Packet, JID, ContactJID, out, StateData), |
2583 |
7 |
NewResult = privacy_check_packet(Packet, JID, ContactJID, out, |
2584 |
|
StateData#state{privacy_list = NewList}), |
2585 |
7 |
send_unavail_if_newly_blocked(Acc, OldResult, NewResult, JID, |
2586 |
|
ContactJID, Packet). |
2587 |
|
|
2588 |
|
send_unavail_if_newly_blocked(Acc, allow, deny, From, To, Packet) -> |
2589 |
2 |
ejabberd_router:route(From, To, Acc, Packet); |
2590 |
|
send_unavail_if_newly_blocked(Acc, _, _, _, _, _) -> |
2591 |
5 |
Acc. |
2592 |
|
|
2593 |
|
%%%---------------------------------------------------------------------- |
2594 |
|
%%% XEP-0191 |
2595 |
|
%%%---------------------------------------------------------------------- |
2596 |
|
|
2597 |
|
-spec blocking_push_to_resources(Action :: blocking_type(), |
2598 |
|
JIDS :: [binary()], |
2599 |
|
State :: state()) -> ok. |
2600 |
|
blocking_push_to_resources(Action, JIDs, StateData) -> |
2601 |
29 |
SubEl = |
2602 |
|
case Action of |
2603 |
|
block -> |
2604 |
20 |
#xmlel{name = <<"block">>, |
2605 |
|
attrs = [{<<"xmlns">>, ?NS_BLOCKING}], |
2606 |
|
children = lists:map( |
2607 |
|
fun(JID) -> |
2608 |
26 |
#xmlel{name = <<"item">>, |
2609 |
|
attrs = [{<<"jid">>, JID}]} |
2610 |
|
end, JIDs)}; |
2611 |
|
unblock -> |
2612 |
9 |
#xmlel{name = <<"unblock">>, |
2613 |
|
attrs = [{<<"xmlns">>, ?NS_BLOCKING}], |
2614 |
|
children = lists:map( |
2615 |
|
fun(JID) -> |
2616 |
7 |
#xmlel{name = <<"item">>, |
2617 |
|
attrs = [{<<"jid">>, JID}]} |
2618 |
|
end, JIDs)} |
2619 |
|
end, |
2620 |
29 |
PrivPushIQ = #iq{type = set, xmlns = ?NS_BLOCKING, |
2621 |
|
id = <<"push">>, |
2622 |
|
sub_el = [SubEl]}, |
2623 |
29 |
F = jid:to_bare(StateData#state.jid), |
2624 |
29 |
T = StateData#state.jid, |
2625 |
29 |
PrivPushEl = jlib:replace_from_to(F, T, jlib:iq_to_xml(PrivPushIQ)), |
2626 |
29 |
ejabberd_router:route(F, T, PrivPushEl), |
2627 |
29 |
ok. |
2628 |
|
|
2629 |
|
-spec blocking_presence_to_contacts(Action :: blocking_type(), |
2630 |
|
JIDs :: [binary()], |
2631 |
|
State :: state()) -> ok. |
2632 |
|
blocking_presence_to_contacts(_Action, [], _StateData) -> |
2633 |
29 |
ok; |
2634 |
|
blocking_presence_to_contacts(Action, [Jid|JIDs], StateData) -> |
2635 |
33 |
Pres = case Action of |
2636 |
|
block -> |
2637 |
26 |
#xmlel{name = <<"presence">>, |
2638 |
|
attrs = [{<<"xml:lang">>, <<"en">>}, {<<"type">>, <<"unavailable">>}] |
2639 |
|
}; |
2640 |
|
unblock -> |
2641 |
7 |
StateData#state.pres_last |
2642 |
|
end, |
2643 |
33 |
T = jid:from_binary(Jid), |
2644 |
33 |
case is_subscribed_to_my_presence(T, StateData) of |
2645 |
|
true -> |
2646 |
:-( |
F = jid:to_bare(StateData#state.jid), |
2647 |
:-( |
ejabberd_router:route(F, T, Pres); |
2648 |
|
false -> |
2649 |
33 |
ok |
2650 |
|
end, |
2651 |
33 |
blocking_presence_to_contacts(Action, JIDs, StateData). |
2652 |
|
|
2653 |
|
-type pack_tree() :: gb_trees:tree(binary() | jid:simple_jid(), |
2654 |
|
binary() | jid:simple_jid()). |
2655 |
|
|
2656 |
|
%% @doc Try to reduce the heap footprint of the four presence sets |
2657 |
|
%% by ensuring that we re-use strings and Jids wherever possible. |
2658 |
|
-spec pack(S :: state()) -> state(). |
2659 |
|
pack(S = #state{pres_a=A, |
2660 |
|
pres_i=I, |
2661 |
|
pres_f=F, |
2662 |
|
pres_t=T}) -> |
2663 |
3026 |
{NewA, Pack1} = pack_jid_set(A, gb_trees:empty()), |
2664 |
3026 |
{NewI, Pack2} = pack_jid_set(I, Pack1), |
2665 |
3026 |
{NewF, Pack3} = pack_jid_set(F, Pack2), |
2666 |
3026 |
{NewT, _Pack4} = pack_jid_set(T, Pack3), |
2667 |
|
%% Throw away Pack4 so that if we delete references to |
2668 |
|
%% Strings or Jids in any of the sets there will be |
2669 |
|
%% no live references for the GC to find. |
2670 |
3026 |
S#state{pres_a=NewA, |
2671 |
|
pres_i=NewI, |
2672 |
|
pres_f=NewF, |
2673 |
|
pres_t=NewT}. |
2674 |
|
|
2675 |
|
|
2676 |
|
-spec pack_jid_set(Set :: jid_set(), |
2677 |
|
Pack :: pack_tree()) -> {jid_set(), pack_tree()}. |
2678 |
|
pack_jid_set(Set, Pack) -> |
2679 |
12104 |
Jids = gb_sets:to_list(Set), |
2680 |
12104 |
{PackedJids, NewPack} = pack_jids(Jids, Pack, []), |
2681 |
12104 |
{gb_sets:from_list(PackedJids), NewPack}. |
2682 |
|
|
2683 |
|
|
2684 |
|
-spec pack_jids([{_, _, _}], Pack :: pack_tree(), Acc :: [jid:simple_jid()]) -> |
2685 |
|
{[jid:simple_jid()], pack_tree()}. |
2686 |
12104 |
pack_jids([], Pack, Acc) -> {Acc, Pack}; |
2687 |
|
pack_jids([{U, S, R}=Jid | Jids], Pack, Acc) -> |
2688 |
6074 |
case gb_trees:lookup(Jid, Pack) of |
2689 |
|
{value, PackedJid} -> |
2690 |
3037 |
pack_jids(Jids, Pack, [PackedJid | Acc]); |
2691 |
|
none -> |
2692 |
3037 |
{NewU, Pack1} = pack_string(U, Pack), |
2693 |
3037 |
{NewS, Pack2} = pack_string(S, Pack1), |
2694 |
3037 |
{NewR, Pack3} = pack_string(R, Pack2), |
2695 |
3037 |
NewJid = {NewU, NewS, NewR}, |
2696 |
3037 |
NewPack = gb_trees:insert(NewJid, NewJid, Pack3), |
2697 |
3037 |
pack_jids(Jids, NewPack, [NewJid | Acc]) |
2698 |
|
end. |
2699 |
|
|
2700 |
|
|
2701 |
|
-spec pack_string(String :: binary(), Pack :: pack_tree()) -> {binary(), pack_tree()}. |
2702 |
|
pack_string(String, Pack) -> |
2703 |
9111 |
case gb_trees:lookup(String, Pack) of |
2704 |
|
{value, PackedString} -> |
2705 |
22 |
{PackedString, Pack}; |
2706 |
|
none -> |
2707 |
9089 |
{String, gb_trees:insert(String, String, Pack)} |
2708 |
|
end. |
2709 |
|
|
2710 |
|
%%%---------------------------------------------------------------------- |
2711 |
|
%%% XEP-0352: Client State Indication |
2712 |
|
%%%---------------------------------------------------------------------- |
2713 |
|
maybe_inactivate_session(?NS_CSI, #state{csi_state = active} = State) -> |
2714 |
8 |
fsm_next_state(session_established, State#state{csi_state = inactive}); |
2715 |
|
maybe_inactivate_session(_, State) -> |
2716 |
2 |
fsm_next_state(session_established, State). |
2717 |
|
|
2718 |
|
maybe_activate_session(?NS_CSI, #state{csi_state = inactive} = State) -> |
2719 |
3 |
resend_csi_buffer(State); |
2720 |
|
maybe_activate_session(_, State) -> |
2721 |
:-( |
fsm_next_state(session_established, State). |
2722 |
|
|
2723 |
|
resend_csi_buffer(State) -> |
2724 |
3 |
NewState = flush_csi_buffer(State), |
2725 |
3 |
fsm_next_state(session_established, NewState#state{csi_state=active}). |
2726 |
|
|
2727 |
|
-spec ship_to_local_user(mongoose_acc:t(), packet(), state()) -> |
2728 |
|
{ok | resume, mongoose_acc:t(), state()}. |
2729 |
|
ship_to_local_user(Acc, Packet, State) -> |
2730 |
10359 |
maybe_csi_inactive_optimisation(Acc, Packet, State). |
2731 |
|
|
2732 |
|
-spec maybe_csi_inactive_optimisation(mongoose_acc:t(), packet(), state()) -> |
2733 |
|
{ok | resume, mongoose_acc:t(), state()}. |
2734 |
|
maybe_csi_inactive_optimisation(Acc, Packet, #state{csi_state = active} = State) -> |
2735 |
10333 |
send_and_maybe_buffer_stanza(Acc, Packet, State); |
2736 |
|
maybe_csi_inactive_optimisation(Acc, {From,To,El}, #state{csi_buffer = Buffer} = State) -> |
2737 |
26 |
NewAcc = update_stanza(From, To, El, Acc), |
2738 |
26 |
NewBuffer = [NewAcc | Buffer], |
2739 |
26 |
NewState = flush_or_buffer_packets(State#state{csi_buffer = NewBuffer}), |
2740 |
26 |
{ok, Acc, NewState}. |
2741 |
|
|
2742 |
|
flush_or_buffer_packets(State) -> |
2743 |
26 |
MaxBuffSize = gen_mod:get_module_opt(State#state.host_type, mod_csi, |
2744 |
|
buffer_max, 20), |
2745 |
26 |
case length(State#state.csi_buffer) > MaxBuffSize of |
2746 |
|
true -> |
2747 |
1 |
flush_csi_buffer(State); |
2748 |
|
_ -> |
2749 |
25 |
State |
2750 |
|
end. |
2751 |
|
|
2752 |
|
-spec flush_csi_buffer(state()) -> state(). |
2753 |
|
flush_csi_buffer(#state{csi_buffer = BufferOut} = State) -> |
2754 |
|
%%lists:foldr to preserve order |
2755 |
14 |
F = fun(Acc, {_, _, OldState}) -> |
2756 |
26 |
{From, To, El} = mongoose_acc:packet(Acc), |
2757 |
26 |
send_and_maybe_buffer_stanza_no_ack(Acc, {From, To, El}, OldState) |
2758 |
|
end, |
2759 |
14 |
{_, _, NewState} = lists:foldr(F, {ok, ok, State}, BufferOut), |
2760 |
14 |
NewState#state{csi_buffer = []}. |
2761 |
|
|
2762 |
|
bounce_csi_buffer(#state{csi_buffer = []}) -> |
2763 |
3023 |
ok; |
2764 |
|
bounce_csi_buffer(#state{csi_buffer = Buffer} = State) -> |
2765 |
2 |
re_route_packets(Buffer, State). |
2766 |
|
|
2767 |
|
%%%---------------------------------------------------------------------- |
2768 |
|
%%% XEP-0198: Stream Management |
2769 |
|
%%%---------------------------------------------------------------------- |
2770 |
|
maybe_enable_stream_mgmt(NextState, El, StateData = #state{host_type = HostType}) -> |
2771 |
61 |
case {xml:get_tag_attr_s(<<"xmlns">>, El), |
2772 |
|
StateData#state.stream_mgmt, |
2773 |
|
xml:get_tag_attr_s(<<"resume">>, El)} |
2774 |
|
of |
2775 |
|
{?NS_STREAM_MGNT_3, false, Resume} -> |
2776 |
|
%% turn on |
2777 |
60 |
{NewSD, EnabledEl} = case lists:member(Resume, [<<"true">>, <<"1">>]) of |
2778 |
|
false -> |
2779 |
27 |
{StateData, stream_mgmt_enabled()}; |
2780 |
|
true -> |
2781 |
33 |
enable_stream_resumption(StateData) |
2782 |
|
end, |
2783 |
60 |
send_element_from_server_jid(NewSD, EnabledEl), |
2784 |
60 |
BufferMax = mod_stream_management:get_buffer_max(HostType), |
2785 |
60 |
AckFreq = mod_stream_management:get_ack_freq(HostType), |
2786 |
60 |
ResumeTimeout = mod_stream_management:get_resume_timeout(HostType), |
2787 |
60 |
fsm_next_state(NextState, |
2788 |
|
NewSD#state{stream_mgmt = true, |
2789 |
|
stream_mgmt_buffer_max = BufferMax, |
2790 |
|
stream_mgmt_ack_freq = AckFreq, |
2791 |
|
stream_mgmt_resume_timeout = ResumeTimeout}); |
2792 |
|
{?NS_STREAM_MGNT_3, true, _} -> |
2793 |
1 |
c2s_stream_error(stream_mgmt_failed(<<"unexpected-request">>), StateData); |
2794 |
|
{?NS_STREAM_MGNT_3, disabled, _} -> |
2795 |
:-( |
c2s_stream_error(stream_mgmt_failed(<<"feature-not-implemented">>), StateData); |
2796 |
|
{_, _, _} -> |
2797 |
|
%% invalid namespace |
2798 |
:-( |
c2s_stream_error(mongoose_xmpp_errors:invalid_namespace(), StateData) |
2799 |
|
end. |
2800 |
|
|
2801 |
|
enable_stream_resumption(SD = #state{host_type = HostType}) -> |
2802 |
33 |
SMID = mod_stream_management:make_smid(), |
2803 |
33 |
SID = case SD#state.sid of |
2804 |
:-( |
undefined -> ejabberd_sm:make_new_sid(); |
2805 |
33 |
RSID -> RSID |
2806 |
|
end, |
2807 |
33 |
ok = mod_stream_management:register_smid(HostType, SMID, SID), |
2808 |
33 |
{SD#state{stream_mgmt_id = SMID, sid = SID}, |
2809 |
|
stream_mgmt_enabled([{<<"id">>, SMID}, {<<"resume">>, <<"true">>}])}. |
2810 |
|
|
2811 |
|
maybe_unexpected_sm_request(NextState, El, StateData) -> |
2812 |
2 |
case xml:get_tag_attr_s(<<"xmlns">>, El) of |
2813 |
|
?NS_STREAM_MGNT_3 -> |
2814 |
2 |
send_element_from_server_jid(StateData, stream_mgmt_failed(<<"unexpected-request">>)), |
2815 |
2 |
fsm_next_state(NextState, StateData); |
2816 |
|
_ -> |
2817 |
:-( |
c2s_stream_error(mongoose_xmpp_errors:invalid_namespace(), StateData) |
2818 |
|
end. |
2819 |
|
|
2820 |
|
stream_mgmt_handle_ack(NextState, El, #state{} = SD) -> |
2821 |
49 |
case {exml_query:attr(El, <<"xmlns">>), stream_mgmt_parse_h(El)} of |
2822 |
|
{NS, _} when NS =/= ?NS_STREAM_MGNT_3 -> |
2823 |
:-( |
maybe_send_element_from_server_jid_safe(SD, mongoose_xmpp_errors:invalid_namespace()), |
2824 |
:-( |
maybe_send_trailer_safe(SD), |
2825 |
:-( |
{stop, normal, SD}; |
2826 |
|
{_, invalid_h_attribute} -> |
2827 |
1 |
PolicyViolationErr = mongoose_xmpp_errors:policy_violation( |
2828 |
|
SD#state.lang, <<"Invalid h attribute">>), |
2829 |
1 |
maybe_send_element_from_server_jid_safe(SD, PolicyViolationErr), |
2830 |
1 |
maybe_send_trailer_safe(SD), |
2831 |
1 |
{stop, normal, SD}; |
2832 |
|
{_, Handled} -> |
2833 |
48 |
try |
2834 |
48 |
NSD = #state{} = do_handle_ack(Handled, |
2835 |
|
SD#state.stream_mgmt_out_acked, |
2836 |
|
SD#state.stream_mgmt_buffer, |
2837 |
|
SD#state.stream_mgmt_buffer_size, |
2838 |
|
SD), |
2839 |
44 |
fsm_next_state(NextState, NSD) |
2840 |
|
catch |
2841 |
|
throw:{undefined_condition, H, OldAcked} -> |
2842 |
4 |
#xmlel{children = [UndefCond, Text]} = ErrorStanza0 |
2843 |
|
= mongoose_xmpp_errors:undefined_condition( |
2844 |
|
SD#state.lang, <<"You acknowledged more stanzas that what has been sent">>), |
2845 |
4 |
HandledCountField = sm_handled_count_too_high_stanza(H, OldAcked), |
2846 |
4 |
ErrorStanza = ErrorStanza0#xmlel{children = [UndefCond, HandledCountField, Text]}, |
2847 |
4 |
maybe_send_element_from_server_jid_safe(SD, ErrorStanza), |
2848 |
4 |
maybe_send_trailer_safe(SD), |
2849 |
4 |
{stop, normal, SD} |
2850 |
|
end |
2851 |
|
end. |
2852 |
|
|
2853 |
|
stream_mgmt_parse_h(El) -> |
2854 |
49 |
case catch binary_to_integer(exml_query:attr(El, <<"h">>)) of |
2855 |
48 |
H when is_integer(H) -> H; |
2856 |
1 |
_ -> invalid_h_attribute |
2857 |
|
end. |
2858 |
|
|
2859 |
|
do_handle_ack(Handled, OldAcked, Buffer, BufferSize, SD) -> |
2860 |
48 |
ToDrop = calc_to_drop(Handled, OldAcked), |
2861 |
48 |
ToDrop > BufferSize andalso throw({undefined_condition, Handled, OldAcked}), |
2862 |
44 |
{Dropped, NewBuffer} = drop_last(ToDrop, Buffer), |
2863 |
44 |
NewSize = BufferSize - Dropped, |
2864 |
44 |
SD#state{stream_mgmt_out_acked = Handled, |
2865 |
|
stream_mgmt_buffer = NewBuffer, |
2866 |
|
stream_mgmt_buffer_size = NewSize}. |
2867 |
|
|
2868 |
|
calc_to_drop(Handled, OldAcked) when Handled >= OldAcked -> |
2869 |
48 |
Handled - OldAcked; |
2870 |
|
calc_to_drop(Handled, OldAcked) -> |
2871 |
:-( |
Handled + ?STREAM_MGMT_H_MAX - OldAcked + 1. |
2872 |
|
|
2873 |
|
maybe_send_sm_ack(?NS_STREAM_MGNT_3, StreamMgmt, _NIncoming, NextState, StateData) |
2874 |
|
when StreamMgmt =:= false; StreamMgmt =:= disabled -> |
2875 |
:-( |
?LOG_WARNING(#{what => unexpected_r, c2s_state => StateData, |
2876 |
:-( |
text => <<"received <r/> but stream management is off!">>}), |
2877 |
:-( |
fsm_next_state(NextState, StateData); |
2878 |
|
maybe_send_sm_ack(?NS_STREAM_MGNT_3, true, NIncoming, |
2879 |
|
NextState, StateData) -> |
2880 |
7 |
send_element_from_server_jid(StateData, stream_mgmt_ack(NIncoming)), |
2881 |
7 |
fsm_next_state(NextState, StateData); |
2882 |
|
maybe_send_sm_ack(_, _, _, _NextState, StateData) -> |
2883 |
:-( |
c2s_stream_error(mongoose_xmpp_errors:invalid_namespace(), StateData). |
2884 |
|
|
2885 |
|
maybe_increment_sm_incoming(StreamMgmt, StateData) |
2886 |
|
when StreamMgmt =:= false; StreamMgmt =:= disabled -> |
2887 |
10481 |
StateData; |
2888 |
|
maybe_increment_sm_incoming(true, StateData) -> |
2889 |
84 |
Incoming = StateData#state.stream_mgmt_in, |
2890 |
84 |
StateData#state{stream_mgmt_in = increment_sm_incoming(Incoming)}. |
2891 |
|
|
2892 |
|
increment_sm_incoming(Incoming) -> |
2893 |
84 |
increment_sm_counter(Incoming, 1). |
2894 |
|
|
2895 |
|
increment_sm_counter(Incoming, Increment) |
2896 |
|
when Incoming + Increment >= ?STREAM_MGMT_H_MAX -> |
2897 |
:-( |
Increment - 1; |
2898 |
|
increment_sm_counter(Incoming, Increment) -> |
2899 |
84 |
Incoming + Increment. |
2900 |
|
|
2901 |
|
stream_mgmt_enabled() -> |
2902 |
27 |
stream_mgmt_enabled([]). |
2903 |
|
|
2904 |
|
stream_mgmt_enabled(ExtraAttrs) -> |
2905 |
60 |
#xmlel{name = <<"enabled">>, |
2906 |
|
attrs = [{<<"xmlns">>, ?NS_STREAM_MGNT_3}] ++ ExtraAttrs}. |
2907 |
|
|
2908 |
|
stream_mgmt_failed(Reason) -> |
2909 |
5 |
stream_mgmt_failed(Reason, []). |
2910 |
|
|
2911 |
|
stream_mgmt_failed(Reason, Attrs) -> |
2912 |
6 |
ReasonEl = #xmlel{name = Reason, |
2913 |
|
attrs = [{<<"xmlns">>, ?NS_STANZAS}]}, |
2914 |
6 |
#xmlel{name = <<"failed">>, |
2915 |
|
attrs = [{<<"xmlns">>, ?NS_STREAM_MGNT_3} | Attrs], |
2916 |
|
children = [ReasonEl]}. |
2917 |
|
|
2918 |
|
stream_mgmt_ack(NIncoming) -> |
2919 |
7 |
#xmlel{name = <<"a">>, |
2920 |
|
attrs = [{<<"xmlns">>, ?NS_STREAM_MGNT_3}, |
2921 |
|
{<<"h">>, integer_to_binary(NIncoming)}]}. |
2922 |
|
|
2923 |
|
-spec buffer_out_stanza(mongoose_acc:t(), packet(), state()) -> state(). |
2924 |
|
buffer_out_stanza(_Acc, _Packet, #state{stream_mgmt = StreamMgmt} = S) |
2925 |
|
when StreamMgmt =:= false; StreamMgmt =:= disabled -> |
2926 |
13193 |
S; |
2927 |
|
buffer_out_stanza(_Acc, _Packet, #state{stream_mgmt_buffer_max = no_buffer} = S) -> |
2928 |
:-( |
S; |
2929 |
|
buffer_out_stanza(Acc, Packet, #state{server = Server, |
2930 |
|
stream_mgmt_buffer = Buffer, |
2931 |
|
stream_mgmt_buffer_size = BufferSize, |
2932 |
|
stream_mgmt_buffer_max = BufferMax} = S) -> |
2933 |
195 |
NewSize = BufferSize + 1, |
2934 |
195 |
Timestamp = os:system_time(microsecond), |
2935 |
195 |
{From, To, El} = maybe_add_timestamp(Packet, Timestamp, Server), |
2936 |
195 |
Acc1 = update_stanza(From, To, El, Acc), |
2937 |
195 |
NS = case is_buffer_full(NewSize, BufferMax) of |
2938 |
|
true -> |
2939 |
17 |
defer_resource_constraint_check(S); |
2940 |
|
_ -> |
2941 |
178 |
S |
2942 |
|
end, |
2943 |
195 |
Acc2 = notify_unacknowledged_msg_if_in_resume_state(Acc1, NS), |
2944 |
195 |
NS#state{stream_mgmt_buffer_size = NewSize, |
2945 |
|
stream_mgmt_buffer = [Acc2 | Buffer]}. |
2946 |
|
|
2947 |
|
is_buffer_full(_BufferSize, infinity) -> |
2948 |
:-( |
false; |
2949 |
|
is_buffer_full(BufferSize, BufferMax) when BufferSize =< BufferMax -> |
2950 |
179 |
false; |
2951 |
|
is_buffer_full(_, _) -> |
2952 |
19 |
true. |
2953 |
|
|
2954 |
|
%% @doc Drop last N elements from List. |
2955 |
|
%% It's not an error if N > length(List). |
2956 |
|
%% The actual number of dropped elements and an empty list is returned. |
2957 |
|
%% @end |
2958 |
|
-spec drop_last(N, List1) -> {Dropped, List2} when |
2959 |
|
N :: non_neg_integer(), |
2960 |
|
List1 :: list(), |
2961 |
|
Dropped :: non_neg_integer(), |
2962 |
|
List2 :: list(). |
2963 |
|
drop_last(N, List) -> |
2964 |
44 |
{ToDrop, List2} = lists:foldr(fun(E, {0, Acc}) -> |
2965 |
31 |
{0, [E | Acc]}; |
2966 |
|
(_, {ToDrop, Acc}) -> |
2967 |
61 |
{ToDrop-1, Acc} |
2968 |
|
end, {N, []}, List), |
2969 |
44 |
{N - ToDrop, List2}. |
2970 |
|
|
2971 |
|
maybe_send_ack_request(Acc, #state{stream_mgmt = StreamMgmt}) |
2972 |
|
when StreamMgmt =:= false; StreamMgmt =:= disabled -> |
2973 |
13181 |
Acc; |
2974 |
|
maybe_send_ack_request(Acc, #state{stream_mgmt_ack_freq = never}) -> |
2975 |
75 |
Acc; |
2976 |
|
maybe_send_ack_request(Acc, #state{stream_mgmt_out_acked = Out, |
2977 |
|
stream_mgmt_buffer_size = BufferSize, |
2978 |
|
stream_mgmt_ack_freq = AckFreq} = State) |
2979 |
|
when (Out + BufferSize) rem AckFreq == 0 -> |
2980 |
70 |
send_element(Acc, stream_mgmt_request(), State); |
2981 |
|
maybe_send_ack_request(Acc, _) -> |
2982 |
1 |
Acc. |
2983 |
|
|
2984 |
|
stream_mgmt_request() -> |
2985 |
70 |
#xmlel{name = <<"r">>, |
2986 |
|
attrs = [{<<"xmlns">>, ?NS_STREAM_MGNT_3}]}. |
2987 |
|
|
2988 |
|
flush_stream_mgmt_buffer(#state{stream_mgmt = StreamMgmt}) |
2989 |
|
when StreamMgmt =:= false; StreamMgmt =:= disabled -> |
2990 |
2968 |
false; |
2991 |
|
flush_stream_mgmt_buffer(#state{stream_mgmt_buffer = Buffer} = State) -> |
2992 |
57 |
re_route_packets(Buffer, State). |
2993 |
|
|
2994 |
|
re_route_packets(Buffer, StateData) -> |
2995 |
59 |
[reroute(Acc, StateData) || Acc <- lists:reverse(Buffer)], |
2996 |
59 |
ok. |
2997 |
|
|
2998 |
|
reroute(Acc, StateData) -> |
2999 |
132 |
{From, To, _El} = mongoose_acc:packet(Acc), |
3000 |
132 |
reroute(From, To, Acc, StateData). |
3001 |
|
|
3002 |
|
reroute(From, To, Acc, #state{sid = SID}) -> |
3003 |
202 |
Acc2 = patch_acc_for_reroute(Acc, SID), |
3004 |
202 |
ejabberd_router:route(From, To, Acc2). |
3005 |
|
|
3006 |
|
patch_acc_for_reroute(Acc, SID) -> |
3007 |
202 |
case mongoose_acc:stanza_name(Acc) of |
3008 |
|
<<"message">> -> |
3009 |
101 |
Acc; |
3010 |
|
_ -> %% IQs and presences are allowed to come to the same SID only |
3011 |
101 |
case mongoose_acc:get(c2s, receiver_sid, undefined, Acc) of |
3012 |
|
undefined -> |
3013 |
101 |
mongoose_acc:set_permanent(c2s, receiver_sid, SID, Acc); |
3014 |
|
_ -> |
3015 |
:-( |
Acc |
3016 |
|
end |
3017 |
|
end. |
3018 |
|
|
3019 |
|
notify_unacknowledged_messages(#state{stream_mgmt_buffer = Buffer} = State) -> |
3020 |
34 |
NewBuffer = [maybe_notify_unacknowledged_msg(Acc, State) || Acc <- lists:reverse(Buffer)], |
3021 |
34 |
State#state{stream_mgmt_buffer = lists:reverse(NewBuffer)}. |
3022 |
|
|
3023 |
|
notify_unacknowledged_msg_if_in_resume_state(Acc, |
3024 |
|
#state{stream_mgmt_resume_tref = TRef, |
3025 |
|
stream_mgmt = true} = State) when TRef =/= undefined -> |
3026 |
18 |
maybe_notify_unacknowledged_msg(Acc, State); |
3027 |
|
notify_unacknowledged_msg_if_in_resume_state(Acc, _) -> |
3028 |
177 |
Acc. |
3029 |
|
|
3030 |
|
maybe_notify_unacknowledged_msg(Acc, #state{jid = Jid, host_type = HostType}) -> |
3031 |
99 |
case mongoose_acc:stanza_name(Acc) of |
3032 |
61 |
<<"message">> -> notify_unacknowledged_msg(HostType, Acc, Jid); |
3033 |
38 |
_ -> Acc |
3034 |
|
end. |
3035 |
|
|
3036 |
|
notify_unacknowledged_msg(HostType, Acc, Jid) -> |
3037 |
61 |
NewAcc = mongoose_hooks:unacknowledged_message(HostType, Acc, Jid), |
3038 |
61 |
mongoose_acc:strip(NewAcc). |
3039 |
|
|
3040 |
|
finish_state(ok, StateName, StateData) -> |
3041 |
13286 |
fsm_next_state(StateName, StateData); |
3042 |
|
finish_state(resume, _, StateData) -> |
3043 |
36 |
maybe_enter_resume_session(StateData). |
3044 |
|
|
3045 |
|
maybe_enter_resume_session(StateData) -> |
3046 |
36 |
maybe_enter_resume_session(StateData#state.stream_mgmt_id, StateData). |
3047 |
|
|
3048 |
|
maybe_enter_resume_session(undefined, StateData) -> |
3049 |
59 |
{stop, normal, StateData}; |
3050 |
|
maybe_enter_resume_session(_SMID, #state{} = SD) -> |
3051 |
86 |
NSD = case SD#state.stream_mgmt_resume_tref of |
3052 |
|
undefined -> |
3053 |
34 |
Seconds = timer:seconds(SD#state.stream_mgmt_resume_timeout), |
3054 |
34 |
TRef = erlang:send_after(Seconds, self(), resume_timeout), |
3055 |
34 |
NewState = SD#state{stream_mgmt_resume_tref = TRef}, |
3056 |
34 |
notify_unacknowledged_messages(NewState); |
3057 |
|
_TRef -> |
3058 |
52 |
SD |
3059 |
|
end, |
3060 |
86 |
{next_state, resume_session, NSD, hibernate()}. |
3061 |
|
|
3062 |
|
maybe_resume_session(NextState, El, StateData = #state{host_type = HostType}) -> |
3063 |
15 |
case {xml:get_tag_attr_s(<<"xmlns">>, El), |
3064 |
|
xml:get_tag_attr_s(<<"previd">>, El)} of |
3065 |
|
{?NS_STREAM_MGNT_3, SMID} -> |
3066 |
14 |
FromSMID = mod_stream_management:get_session_from_smid(HostType, SMID), |
3067 |
14 |
do_resume_session(SMID, El, FromSMID, StateData); |
3068 |
|
{InvalidNS, _} -> |
3069 |
1 |
?LOG_INFO(#{what => c2s_ignores_resume, |
3070 |
|
text => <<"ignoring <resume/> element with invalid namespace">>, |
3071 |
1 |
invalid_ns => InvalidNS, c2s_state => StateData}), |
3072 |
1 |
fsm_next_state(NextState, StateData) |
3073 |
|
end. |
3074 |
|
|
3075 |
|
-spec do_resume_session(SMID, El, FromSMID, StateData) -> NewState when |
3076 |
|
SMID :: mod_stream_management:smid(), |
3077 |
|
El :: exml:element(), |
3078 |
|
FromSMID :: {sid, ejabberd_sm:sid()} | {stale_h, non_neg_integer()} | {error, smid_not_found}, |
3079 |
|
StateData :: state(), |
3080 |
|
NewState :: tuple(). |
3081 |
|
do_resume_session(SMID, El, {sid, {_, Pid}}, StateData) -> |
3082 |
12 |
try |
3083 |
12 |
{ok, OldState} = p1_fsm_old:sync_send_event(Pid, resume), |
3084 |
11 |
SID = ejabberd_sm:make_new_sid(), |
3085 |
11 |
Conn = get_conn_type(StateData), |
3086 |
11 |
MergedState = merge_state(OldState, |
3087 |
|
StateData#state{sid = SID, conn = Conn}), |
3088 |
11 |
case stream_mgmt_handle_ack(session_established, El, MergedState) of |
3089 |
|
{stop, _, _} = Stop -> |
3090 |
1 |
Stop; |
3091 |
|
{next_state, session_established, NSD, _} -> |
3092 |
10 |
Priority = get_priority_from_presence(NSD#state.pres_last), |
3093 |
10 |
Info = #{ip => NSD#state.ip, conn => NSD#state.conn, |
3094 |
|
auth_module => NSD#state.auth_module }, |
3095 |
10 |
ejabberd_sm:open_session(NSD#state.host_type, SID, NSD#state.jid, Priority, Info), |
3096 |
10 |
ok = mod_stream_management:register_smid(NSD#state.host_type, SMID, SID), |
3097 |
10 |
try |
3098 |
10 |
Resumed = stream_mgmt_resumed(NSD#state.stream_mgmt_id, |
3099 |
|
NSD#state.stream_mgmt_in), |
3100 |
10 |
send_element_from_server_jid(NSD, Resumed), |
3101 |
10 |
[begin |
3102 |
9 |
Elem = mongoose_acc:element(Acc), |
3103 |
9 |
send_element(Acc, Elem, NSD) |
3104 |
10 |
end || Acc <- lists:reverse(NSD#state.stream_mgmt_buffer)], |
3105 |
|
|
3106 |
10 |
NSD2 = flush_csi_buffer(NSD), |
3107 |
|
|
3108 |
10 |
NSD3 = NSD2#state{ stream_mgmt_resumed_from = OldState#state.sid }, |
3109 |
|
|
3110 |
10 |
fsm_next_state(session_established, NSD3) |
3111 |
|
catch |
3112 |
|
%% errors from send_element |
3113 |
|
_:_ -> |
3114 |
:-( |
?LOG_INFO(#{what => resumption_error, |
3115 |
|
text => <<"resumption error while resending old stanzas" |
3116 |
|
" entering resume state again">>, |
3117 |
:-( |
smid => SMID, c2s_state => NSD}), |
3118 |
:-( |
maybe_enter_resume_session(SMID, NSD) |
3119 |
|
end |
3120 |
|
end |
3121 |
|
catch |
3122 |
|
_Class:Reason:Stacktrace -> |
3123 |
1 |
?LOG_WARNING(#{what => resumption_error, reason => invalid_response, |
3124 |
|
text => <<"Resumption error because of invalid response">>, |
3125 |
|
error => Reason, stacktrace => Stacktrace, |
3126 |
:-( |
c2s_state => StateData}), |
3127 |
1 |
send_element_from_server_jid(StateData, stream_mgmt_failed(<<"item-not-found">>)), |
3128 |
1 |
fsm_next_state(wait_for_feature_after_auth, StateData) |
3129 |
|
end; |
3130 |
|
|
3131 |
|
do_resume_session(SMID, _El, {stale_h, H}, StateData) when is_integer(H) -> |
3132 |
1 |
?LOG_INFO(#{what => resumption_error, reason => session_resumption_timed_out, |
3133 |
1 |
smid => SMID, stale_h => H, c2s_state => StateData}), |
3134 |
1 |
send_element_from_server_jid( |
3135 |
|
StateData, stream_mgmt_failed(<<"item-not-found">>, [{<<"h">>, integer_to_binary(H)}])), |
3136 |
1 |
fsm_next_state(wait_for_feature_after_auth, StateData); |
3137 |
|
do_resume_session(SMID, _El, {error, smid_not_found}, StateData) -> |
3138 |
1 |
?LOG_INFO(#{what => resumption_error, reason => no_previous_session_for_smid, |
3139 |
1 |
smid => SMID, c2s_state => StateData}), |
3140 |
1 |
send_element_from_server_jid(StateData, stream_mgmt_failed(<<"item-not-found">>)), |
3141 |
1 |
fsm_next_state(wait_for_feature_after_auth, StateData). |
3142 |
|
|
3143 |
|
merge_state(OldSD, SD) -> |
3144 |
11 |
Preserve = [#state.jid, |
3145 |
|
#state.user, |
3146 |
|
#state.server, |
3147 |
|
#state.resource, |
3148 |
|
#state.pres_t, |
3149 |
|
#state.pres_f, |
3150 |
|
#state.pres_a, |
3151 |
|
#state.pres_i, |
3152 |
|
#state.pres_last, |
3153 |
|
#state.pres_pri, |
3154 |
|
#state.pres_timestamp, |
3155 |
|
#state.pres_invis, |
3156 |
|
#state.privacy_list, |
3157 |
|
#state.aux_fields, |
3158 |
|
#state.csi_buffer, |
3159 |
|
#state.stream_mgmt, |
3160 |
|
#state.stream_mgmt_in, |
3161 |
|
#state.stream_mgmt_id, |
3162 |
|
#state.stream_mgmt_out_acked, |
3163 |
|
#state.stream_mgmt_buffer, |
3164 |
|
#state.stream_mgmt_buffer_size, |
3165 |
|
#state.stream_mgmt_buffer_max, |
3166 |
|
#state.stream_mgmt_resume_timeout, |
3167 |
|
#state.stream_mgmt_ack_freq], |
3168 |
11 |
Copy = fun(Index, {Stale, Acc}) -> |
3169 |
264 |
{Stale, setelement(Index, Acc, element(Index, Stale))} |
3170 |
|
end, |
3171 |
11 |
element(2, lists:foldl(Copy, {OldSD, SD}, Preserve)). |
3172 |
|
|
3173 |
|
stream_mgmt_resumed(SMID, Handled) -> |
3174 |
10 |
#xmlel{name = <<"resumed">>, |
3175 |
|
attrs = [{<<"xmlns">>, ?NS_STREAM_MGNT_3}, |
3176 |
|
{<<"previd">>, SMID}, |
3177 |
|
{<<"h">>, integer_to_binary(Handled)}]}. |
3178 |
|
|
3179 |
|
handover_session(SD, From)-> |
3180 |
11 |
true = SD#state.stream_mgmt, |
3181 |
11 |
Acc = new_acc(SD, #{location => ?LOCATION, element => undefined}), |
3182 |
11 |
ejabberd_sm:close_session(Acc, |
3183 |
|
SD#state.sid, |
3184 |
|
SD#state.jid, |
3185 |
|
resumed), |
3186 |
|
%the actual handover to be done on termination |
3187 |
11 |
{stop, {handover_session, From}, SD}. |
3188 |
|
|
3189 |
|
do_handover_session(SD, UnreadMessages) -> |
3190 |
11 |
Messages = flush_messages(UnreadMessages), |
3191 |
11 |
NewCsiBuffer = Messages ++ SD#state.csi_buffer, |
3192 |
11 |
SD#state{authenticated = resumed, |
3193 |
|
csi_buffer = NewCsiBuffer}. |
3194 |
|
|
3195 |
|
maybe_add_timestamp({F, T, #xmlel{name= <<"message">>}=Packet}=PacketTuple, Timestamp, Server) -> |
3196 |
104 |
Type = xml:get_tag_attr_s(<<"type">>, Packet), |
3197 |
104 |
case Type of |
3198 |
|
<<"error">> -> |
3199 |
:-( |
PacketTuple; |
3200 |
|
<<"headline">> -> |
3201 |
2 |
PacketTuple; |
3202 |
|
_ -> |
3203 |
102 |
{F, T, add_timestamp(Timestamp, Server, Packet)} |
3204 |
|
end; |
3205 |
|
maybe_add_timestamp(Packet, _Timestamp, _Server) -> |
3206 |
91 |
Packet. |
3207 |
|
|
3208 |
|
add_timestamp(TimeStamp, Server, Packet) -> |
3209 |
102 |
case xml:get_subtag(Packet, <<"delay">>) of |
3210 |
|
false -> |
3211 |
83 |
TimeStampXML = timestamp_xml(Server, TimeStamp), |
3212 |
83 |
xml:append_subtags(Packet, [TimeStampXML]); |
3213 |
|
_ -> |
3214 |
19 |
Packet |
3215 |
|
end. |
3216 |
|
|
3217 |
|
timestamp_xml(Server, Time) -> |
3218 |
83 |
FromJID = jid:make_noprep(<<>>, Server, <<>>), |
3219 |
83 |
TS = calendar:system_time_to_rfc3339(Time, [{offset, "Z"}, {unit, microsecond}]), |
3220 |
83 |
jlib:timestamp_to_xml(TS, FromJID, <<"SM Storage">>). |
3221 |
|
|
3222 |
|
defer_resource_constraint_check(#state{stream_mgmt_constraint_check_tref = undefined} = State)-> |
3223 |
14 |
Seconds = timer:seconds(?CONSTRAINT_CHECK_TIMEOUT), |
3224 |
14 |
TRef = erlang:send_after(Seconds, self(), check_buffer_full), |
3225 |
14 |
State#state{stream_mgmt_constraint_check_tref = TRef}; |
3226 |
|
defer_resource_constraint_check(State)-> |
3227 |
3 |
State. |
3228 |
|
|
3229 |
|
-spec sm_handled_count_too_high_stanza(non_neg_integer(), non_neg_integer()) -> exml:element(). |
3230 |
|
sm_handled_count_too_high_stanza(Handled, OldAcked) -> |
3231 |
4 |
#xmlel{name = <<"handled-count-too-high">>, |
3232 |
|
attrs = [{<<"xmlns">>, ?NS_STREAM_MGNT_3}, |
3233 |
|
{<<"h">>, integer_to_binary(Handled)}, |
3234 |
|
{<<"send-count">>, integer_to_binary(OldAcked)}]}. |
3235 |
|
|
3236 |
|
-spec sasl_success_stanza(any()) -> exml:element(). |
3237 |
|
sasl_success_stanza(ServerOut) -> |
3238 |
3054 |
C = case ServerOut of |
3239 |
3044 |
undefined -> []; |
3240 |
10 |
_ -> [#xmlcdata{content = jlib:encode_base64(ServerOut)}] |
3241 |
|
end, |
3242 |
3054 |
#xmlel{name = <<"success">>, |
3243 |
|
attrs = [{<<"xmlns">>, ?NS_SASL}], |
3244 |
|
children = C}. |
3245 |
|
|
3246 |
|
-spec sasl_failure_stanza(binary() | {binary(), iodata() | undefined}) -> exml:element(). |
3247 |
|
sasl_failure_stanza(Error) when is_binary(Error) -> |
3248 |
48 |
sasl_failure_stanza({Error, undefined}); |
3249 |
|
sasl_failure_stanza({Error, Text}) -> |
3250 |
49 |
#xmlel{name = <<"failure">>, |
3251 |
|
attrs = [{<<"xmlns">>, ?NS_SASL}], |
3252 |
|
children = [#xmlel{name = Error} | maybe_text_tag(Text)]}. |
3253 |
|
|
3254 |
48 |
maybe_text_tag(undefined) -> []; |
3255 |
|
maybe_text_tag(Text) -> |
3256 |
1 |
[#xmlel{name = <<"text">>, |
3257 |
|
children = [#xmlcdata{content = Text}]}]. |
3258 |
|
|
3259 |
|
-spec sasl_challenge_stanza(any()) -> exml:element(). |
3260 |
|
sasl_challenge_stanza(Challenge) -> |
3261 |
13 |
#xmlel{name = <<"challenge">>, |
3262 |
|
attrs = [{<<"xmlns">>, ?NS_SASL}], |
3263 |
|
children = Challenge}. |
3264 |
|
|
3265 |
|
handle_sasl_success(State, Creds) -> |
3266 |
3054 |
ServerOut = mongoose_credentials:get(Creds, sasl_success_response, undefined), |
3267 |
3054 |
send_element_from_server_jid(State, sasl_success_stanza(ServerOut)), |
3268 |
3052 |
User = mongoose_credentials:get(Creds, username), |
3269 |
3052 |
AuthModule = mongoose_credentials:get(Creds, auth_module), |
3270 |
3052 |
StreamID = new_id(), |
3271 |
3052 |
Server = State#state.server, |
3272 |
3052 |
NewState = State#state{ streamid = StreamID, |
3273 |
|
authenticated = true, |
3274 |
|
auth_module = AuthModule, |
3275 |
|
user = User, |
3276 |
|
jid = jid:make(User, Server, <<>>)}, |
3277 |
3052 |
?LOG_INFO(#{what => auth_success, text => <<"Accepted SASL authentication">>, |
3278 |
|
stream_id => StreamID, auth_module => AuthModule, |
3279 |
3052 |
c2s_state => NewState}), |
3280 |
3052 |
{wait_for_stream, NewState}. |
3281 |
|
|
3282 |
|
handle_sasl_step(#state{host_type = HostType, server = Server, socket = Sock} = State, |
3283 |
|
StepRes) -> |
3284 |
3116 |
case StepRes of |
3285 |
|
{ok, Creds} -> |
3286 |
3054 |
handle_sasl_success(State, Creds); |
3287 |
|
{continue, ServerOut, NewSASLState} -> |
3288 |
13 |
Challenge = [#xmlcdata{content = jlib:encode_base64(ServerOut)}], |
3289 |
13 |
send_element_from_server_jid(State, sasl_challenge_stanza(Challenge)), |
3290 |
13 |
{wait_for_sasl_response, State#state{sasl_state = NewSASLState}}; |
3291 |
|
{error, Error, Username} -> |
3292 |
17 |
IP = peerip(State#state.sockmod, Sock), |
3293 |
17 |
?LOG_INFO(#{what => auth_failed, |
3294 |
|
text => <<"Failed SASL authentication">>, |
3295 |
|
user => Username, server => Server, |
3296 |
17 |
ip => IP, c2s_state => State}), |
3297 |
17 |
mongoose_hooks:auth_failed(HostType, Server, Username), |
3298 |
17 |
send_element_from_server_jid(State, sasl_failure_stanza(Error)), |
3299 |
17 |
{wait_for_feature_before_auth, State}; |
3300 |
|
{error, Error} -> |
3301 |
32 |
mongoose_hooks:auth_failed(HostType, Server, unknown), |
3302 |
32 |
send_element_from_server_jid(State, sasl_failure_stanza(Error)), |
3303 |
32 |
{wait_for_feature_before_auth, State} |
3304 |
|
end. |
3305 |
|
|
3306 |
|
user_allowed(JID, #state{host_type = HostType, server = Server, access = Access}) -> |
3307 |
3027 |
case acl:match_rule(HostType, Server, Access, JID) of |
3308 |
|
allow -> |
3309 |
3026 |
open_session_allowed_hook(HostType, JID); |
3310 |
|
deny -> |
3311 |
1 |
false |
3312 |
|
end. |
3313 |
|
|
3314 |
|
open_session_allowed_hook(HostType, JID) -> |
3315 |
3026 |
allow == mongoose_hooks:session_opening_allowed_for_user(HostType, JID). |
3316 |
|
|
3317 |
|
terminate_when_tls_required_but_not_enabled(true, false, StateData, _El) -> |
3318 |
4 |
Lang = StateData#state.lang, |
3319 |
4 |
c2s_stream_error(mongoose_xmpp_errors:policy_violation(Lang, <<"Use of STARTTLS required">>), |
3320 |
|
StateData); |
3321 |
|
terminate_when_tls_required_but_not_enabled(_, _, StateData, El) -> |
3322 |
384 |
process_unauthenticated_stanza(StateData, El), |
3323 |
384 |
fsm_next_state(wait_for_feature_before_auth, StateData). |
3324 |
|
|
3325 |
|
%% @doc This function is executed when c2s receives a stanza from TCP connection. |
3326 |
|
-spec element_to_origin_accum(jlib:xmlel(), StateData :: state()) -> |
3327 |
|
mongoose_acc:t(). |
3328 |
|
element_to_origin_accum(El, StateData = #state{sid = SID, jid = JID}) -> |
3329 |
13155 |
BaseParams = #{ |
3330 |
|
location => ?LOCATION, |
3331 |
|
element => El, |
3332 |
|
from_jid => JID |
3333 |
|
}, |
3334 |
13155 |
Params = |
3335 |
|
case exml_query:attr(El, <<"to">>) of |
3336 |
9380 |
undefined -> BaseParams#{ to_jid => jid:to_bare(JID) }; |
3337 |
3775 |
_ToBin -> BaseParams |
3338 |
|
end, |
3339 |
13155 |
Acc = new_acc(StateData, Params), |
3340 |
13155 |
Acc1 = mongoose_acc:set_permanent(c2s, origin_sid, SID, Acc), |
3341 |
13155 |
mongoose_acc:set_permanent(c2s, origin_jid, JID, Acc1). |
3342 |
|
|
3343 |
|
-spec hibernate() -> hibernate | infinity. |
3344 |
|
hibernate() -> |
3345 |
24905 |
{_, QueueLen} = process_info(self(), message_queue_len), |
3346 |
24905 |
InternalQueueLen = get('$internal_queue_len'), |
3347 |
24905 |
case QueueLen + InternalQueueLen of |
3348 |
11566 |
0 -> hibernate; |
3349 |
13339 |
_ -> infinity |
3350 |
|
end. |
3351 |
|
|
3352 |
|
-spec maybe_hibernate(state()) -> hibernate | infinity | pos_integer(). |
3353 |
24812 |
maybe_hibernate(#state{hibernate_after = 0}) -> hibernate(); |
3354 |
:-( |
maybe_hibernate(#state{hibernate_after = HA}) -> HA. |
3355 |
|
|
3356 |
|
make_c2s_info(_StateData = #state{stream_mgmt_buffer_size = SMBufSize}) -> |
3357 |
11 |
#{stream_mgmt_buffer_size => SMBufSize}. |
3358 |
|
|
3359 |
|
-spec update_stanza(jid:jid(), jid:jid(), exml:element(), mongoose_acc:t()) -> |
3360 |
|
mongoose_acc:t(). |
3361 |
|
update_stanza(From, To, #xmlel{} = Element, Acc) -> |
3362 |
223 |
HostType = mongoose_acc:host_type(Acc), |
3363 |
223 |
LServer = mongoose_acc:lserver(Acc), |
3364 |
223 |
Params = #{host_type => HostType, lserver => LServer, |
3365 |
|
element => Element, from_jid => From, to_jid => To}, |
3366 |
223 |
NewAcc = mongoose_acc:strip(Params, Acc), |
3367 |
223 |
strip_c2s_fields(NewAcc). |
3368 |
|
|
3369 |
|
-spec strip_c2s_fields(mongoose_acc:t()) -> mongoose_acc:t(). |
3370 |
|
strip_c2s_fields(Acc) -> |
3371 |
|
%% TODO: verify if we really need to strip down these 2 fields |
3372 |
359 |
mongoose_acc:delete_many(c2s, [origin_jid, origin_sid], Acc). |
3373 |
|
|
3374 |
|
-spec new_acc(state(), mongoose_acc:new_acc_params()) -> mongoose_acc:t(). |
3375 |
|
new_acc(#state{host_type = HostType, server = LServer}, Params) -> |
3376 |
30775 |
mongoose_acc:new(Params#{host_type => HostType, lserver => LServer}). |