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