./ct_report/coverage/mongoose_c2s.COVER.html

1 -module(mongoose_c2s).
2 -xep([{xep, 170}, {version, "1.0"}]).
3
4 -behaviour(gen_statem).
5 -include("mongoose_logger.hrl").
6 -include("mongoose.hrl").
7 -include("jlib.hrl").
8 -include_lib("exml/include/exml_stream.hrl").
9 -define(AUTH_RETRIES, 3).
10 -define(BIND_RETRIES, 5).
11
12 %% gen_statem callbacks
13 -export([callback_mode/0, init/1, handle_event/4, terminate/3]).
14
15 %% utils
16 -export([start_link/2, start/2, stop/2, exit/2, async/3, async_with_state/3, call/3, cast/3]).
17 -export([create_data/1, get_host_type/1, get_lserver/1, get_sid/1, get_jid/1,
18 get_info/1, set_info/2,
19 get_mod_state/2, get_listener_opts/1, merge_mod_state/2, remove_mod_state/2,
20 get_ip/1, get_socket/1, get_lang/1, get_stream_id/1, hook_arg/5]).
21 -export([get_auth_mechs/1, c2s_stream_error/2, maybe_retry_state/1, merge_states/2]).
22 -export([route/2, reroute_buffer/2, reroute_buffer_to_pid/3, open_session/1]).
23 -export([set_jid/2, set_auth_module/2, state_timeout/1, handle_state_after_packet/3]).
24 -export([replace_resource/2, generate_random_resource/0]).
25 -export([verify_user/4, maybe_open_session/3]).
26
27 -ignore_xref([get_ip/1, get_socket/1]).
28
29 -record(c2s_data, {
30 host_type :: undefined | mongooseim:host_type(),
31 lserver = ?MYNAME :: jid:lserver(),
32 lang = ?MYLANG :: ejabberd:lang(),
33 sid = ejabberd_sm:make_new_sid() :: ejabberd_sm:sid(),
34 streamid = new_stream_id() :: binary(),
35 jid :: undefined | jid:jid(),
36 socket :: undefined | mongoose_c2s_socket:socket(),
37 parser :: undefined | exml_stream:parser(),
38 shaper :: undefined | mongoose_shaper:shaper(),
39 listener_opts :: undefined | listener_opts(),
40 state_mod = #{} :: #{module() => term()},
41 info = #{} :: info()
42 }).
43 -type data() :: #c2s_data{}.
44 -type maybe_ok() :: ok | {error, atom()}.
45 -type fsm_res(State) :: gen_statem:event_handler_result(state(State), data()).
46 -type fsm_res() :: fsm_res(term()).
47 -type packet() :: {jid:jid(), jid:jid(), exml:element()}.
48 -type info() :: #{atom() => term()}.
49
50 -type retries() :: 0..8.
51 -type stream_state() :: stream_start | authenticated.
52 -type state(State) :: connect
53 | {wait_for_stream, stream_state()}
54 | {wait_for_feature_before_auth, mongoose_acc:t(), retries()}
55 | {wait_for_feature_after_auth, retries()}
56 | {wait_for_sasl_response, mongoose_acc:t(), retries()}
57 | wait_for_session_establishment
58 | session_established
59 | ?EXT_C2S_STATE(State).
60 -type state() :: state(term()).
61
62 -type listener_opts() :: #{shaper := atom(),
63 max_stanza_size := non_neg_integer(),
64 backwards_compatible_session := boolean(),
65 c2s_state_timeout := non_neg_integer(),
66 port := inet:port_number(),
67 ip_tuple := inet:ip_address(),
68 proto := tcp,
69 term() => term()}.
70
71 -export_type([packet/0, data/0, state/0, state/1, fsm_res/0, fsm_res/1, retries/0, listener_opts/0]).
72
73 %%%----------------------------------------------------------------------
74 %%% gen_statem
75 %%%----------------------------------------------------------------------
76
77 -spec callback_mode() -> gen_statem:callback_mode_result().
78 callback_mode() ->
79 7791 handle_event_function.
80
81 -spec init({module(), term(), listener_opts()}) ->
82 gen_statem:init_result(state(), data()).
83 init({SocketModule, SocketOpts, LOpts}) ->
84 7771 StateData = #c2s_data{listener_opts = LOpts},
85 7771 ConnectEvent = {next_event, internal, {connect, {SocketModule, SocketOpts}}},
86 7771 {ok, connect, StateData, ConnectEvent}.
87
88 -spec handle_event(gen_statem:event_type(), term(), state(), data()) -> fsm_res().
89 handle_event(internal, {connect, {SocketModule, SocketOpts}}, connect,
90 StateData = #c2s_data{listener_opts = #{shaper := ShaperName,
91 max_stanza_size := MaxStanzaSize} = LOpts}) ->
92 7771 {ok, Parser} = exml_stream:new_parser([{max_element_size, MaxStanzaSize}]),
93 7771 Shaper = mongoose_shaper:new(ShaperName),
94 7771 C2SSocket = mongoose_c2s_socket:new(SocketModule, SocketOpts, LOpts),
95 7486 StateData1 = StateData#c2s_data{socket = C2SSocket, parser = Parser, shaper = Shaper},
96 7486 {next_state, {wait_for_stream, stream_start}, StateData1, state_timeout(LOpts)};
97
98 handle_event(internal, #xmlstreamstart{name = Name, attrs = Attrs}, {wait_for_stream, StreamState}, StateData) ->
99 13666 StreamStart = #xmlel{name = Name, attrs = Attrs},
100 13666 handle_stream_start(StateData, StreamStart, StreamState);
101 handle_event(internal, _Unexpected, {wait_for_stream, _}, StateData) ->
102 7 case mongoose_config:get_opt(hide_service_name, false) of
103 true ->
104 1 {stop, {shutdown, stream_error}};
105 false ->
106 6 send_header(StateData),
107 6 c2s_stream_error(StateData, mongoose_xmpp_errors:xml_not_well_formed())
108 end;
109 handle_event(internal, #xmlstreamstart{}, _, StateData) ->
110 5 c2s_stream_error(StateData, mongoose_xmpp_errors:policy_violation());
111
112 handle_event(internal, #xmlstreamend{}, _, StateData) ->
113 6587 send_trailer(StateData),
114 6587 {stop, {shutdown, stream_end}};
115 handle_event(internal, #xmlstreamerror{name = <<"element too big">> = Err}, _, StateData) ->
116 3 c2s_stream_error(StateData, mongoose_xmpp_errors:policy_violation(StateData#c2s_data.lang, Err));
117 handle_event(internal, #xmlstreamerror{name = Err}, _, StateData) ->
118
:-(
c2s_stream_error(StateData, mongoose_xmpp_errors:xml_not_well_formed(StateData#c2s_data.lang, Err));
119 handle_event(internal, #xmlel{name = <<"starttls">>} = El, {wait_for_feature_before_auth, SaslAcc, Retries}, StateData) ->
120 100 case exml_query:attr(El, <<"xmlns">>) of
121 ?NS_TLS ->
122 100 handle_starttls(StateData, El, SaslAcc, Retries);
123 _ ->
124
:-(
c2s_stream_error(StateData, mongoose_xmpp_errors:invalid_namespace())
125 end;
126 handle_event(internal, #xmlel{name = <<"auth">>} = El, {wait_for_feature_before_auth, SaslAcc, Retries}, StateData) ->
127 6665 case exml_query:attr(El, <<"xmlns">>) of
128 ?NS_SASL ->
129 6665 handle_auth_start(StateData, El, SaslAcc, Retries);
130 _ ->
131
:-(
c2s_stream_error(StateData, mongoose_xmpp_errors:invalid_namespace())
132 end;
133 handle_event(internal, #xmlel{name = <<"response">>} = El, {wait_for_sasl_response, SaslAcc, Retries}, StateData) ->
134 47 case exml_query:attr(El, <<"xmlns">>) of
135 ?NS_SASL ->
136 47 handle_auth_continue(StateData, El, SaslAcc, Retries);
137 _ ->
138
:-(
c2s_stream_error(StateData, mongoose_xmpp_errors:invalid_namespace())
139 end;
140 handle_event(internal, #xmlel{name = <<"abort">>} = El, {wait_for_sasl_response, SaslAcc, Retries}, StateData) ->
141
:-(
case exml_query:attr(El, <<"xmlns">>) of
142 ?NS_SASL ->
143
:-(
handle_sasl_abort(StateData, SaslAcc, Retries);
144 _ ->
145
:-(
c2s_stream_error(StateData, mongoose_xmpp_errors:invalid_namespace())
146 end;
147 handle_event(internal, #xmlel{name = <<"iq">>} = El, {wait_for_feature_after_auth, _} = C2SState, StateData) ->
148 6568 case jlib:iq_query_info(El) of
149 #iq{type = set, xmlns = ?NS_BIND} = IQ ->
150 6568 handle_bind_resource(StateData, C2SState, El, IQ);
151 _ ->
152
:-(
Err = jlib:make_error_reply(El, mongoose_xmpp_errors:bad_request()),
153
:-(
send_element_from_server_jid(StateData, Err),
154
:-(
maybe_retry_state(StateData, C2SState)
155 end;
156 handle_event(internal, #xmlel{name = <<"iq">>} = El, wait_for_session_establishment = C2SState, StateData) ->
157 6556 case jlib:iq_query_info(El) of
158 #iq{type = set, xmlns = ?NS_SESSION} = IQ ->
159 6556 handle_session_establishment(StateData, C2SState, El, IQ);
160 _ ->
161
:-(
handle_foreign_packet(StateData, C2SState, El)
162 end;
163 handle_event(internal, #xmlel{} = El, session_established = C2SState, StateData) ->
164 17016 case verify_from(El, StateData#c2s_data.jid) of
165 false ->
166 3 c2s_stream_error(StateData, mongoose_xmpp_errors:invalid_from());
167 true ->
168 17013 case verify_to(El) of
169 true ->
170 17012 handle_c2s_packet(StateData, C2SState, El);
171 false ->
172 1 Err = jlib:make_error_reply(El, mongoose_xmpp_errors:jid_malformed()),
173 1 Acc = send_element_from_server_jid(StateData, Err),
174 1 handle_state_after_packet(StateData, C2SState, Acc)
175 end
176 end;
177
178 handle_event(internal, {flush, Acc}, C2SState, StateData) ->
179 43 handle_flush(StateData, C2SState, Acc);
180
181 %% This is an xml packet in any state other than session_established,
182 %% which is not one of the default ones defined in the RFC. For example stream management.
183 handle_event(internal, #xmlel{} = El, C2SState, StateData) ->
184 442 handle_foreign_packet(StateData, C2SState, El);
185
186 handle_event(info, Info, FsmState, StateData) ->
187 99456 handle_info(StateData, FsmState, Info);
188
189 handle_event(cast, Info, FsmState, StateData) ->
190 1435 handle_cast(StateData, FsmState, Info);
191
192 handle_event({timeout, Name}, Payload, C2SState, StateData) ->
193 45 handle_timeout(StateData, C2SState, Name, Payload);
194
195 handle_event(state_timeout, state_timeout_termination, _FsmState, StateData) ->
196
:-(
StreamConflict = mongoose_xmpp_errors:connection_timeout(),
197
:-(
send_element_from_server_jid(StateData, StreamConflict),
198
:-(
send_trailer(StateData),
199
:-(
{stop, {shutdown, state_timeout}};
200
201 handle_event(EventType, EventContent, C2SState, StateData) ->
202 64 handle_foreign_event(StateData, C2SState, EventType, EventContent).
203
204 -spec terminate(term(), state(), data()) -> term().
205 terminate(Reason, C2SState, #c2s_data{host_type = HostType, lserver = LServer, sid = SID} = StateData) ->
206 7771 ?LOG_DEBUG(#{what => c2s_statem_terminate, reason => Reason, c2s_state => C2SState, c2s_data => StateData}),
207 7771 Params = hook_arg(StateData, C2SState, terminate, Reason, Reason),
208 7771 Acc0 = mongoose_acc:new(#{host_type => HostType, lserver => LServer, location => ?LOCATION}),
209 7771 Acc1 = mongoose_acc:set_permanent(c2s, [{origin_sid, SID}], Acc0),
210 7771 Acc2 = mongoose_c2s_hooks:user_terminate(HostType, Acc1, Params),
211 7771 Acc3 = do_close_session(StateData, C2SState, Acc2, Reason),
212 7771 mongoose_c2s_hooks:reroute_unacked_messages(HostType, Acc3, Params),
213 7771 bounce_messages(StateData),
214 7771 close_parser(StateData),
215 7771 close_socket(StateData),
216 7771 ok.
217
218 %%%----------------------------------------------------------------------
219 %%% socket helpers
220 %%%----------------------------------------------------------------------
221
222 -spec handle_socket_data(data(), {_, _, iodata()}) -> fsm_res().
223 handle_socket_data(StateData = #c2s_data{socket = Socket}, Payload) ->
224 58104 case mongoose_c2s_socket:handle_data(Socket, Payload) of
225 {error, _Reason} ->
226 400 {stop, {shutdown, socket_error}, StateData};
227 Data ->
228 57704 handle_socket_packet(StateData, Data)
229 end.
230
231 -spec handle_socket_packet(data(), iodata() | {raw, [exml_stream:element()]}) -> fsm_res().
232 handle_socket_packet(StateData, {raw, Elements}) ->
233 1663 ?LOG_DEBUG(#{what => received_raw_on_stream, elements => Elements, c2s_pid => self()}),
234 1663 handle_socket_elements(StateData, Elements, 0);
235 handle_socket_packet(StateData = #c2s_data{parser = Parser}, Packet) ->
236 56041 ?LOG_DEBUG(#{what => received_xml_on_stream, packet => Packet, c2s_pid => self()}),
237 56041 case exml_stream:parse(Parser, Packet) of
238 {error, Reason} ->
239 8 NextEvent = {next_event, internal, #xmlstreamerror{name = iolist_to_binary(Reason)}},
240 8 {keep_state, StateData, NextEvent};
241 {ok, NewParser, XmlElements} ->
242 56033 Size = iolist_size(Packet),
243 56033 NewStateData = StateData#c2s_data{parser = NewParser},
244 56033 handle_socket_elements(NewStateData, XmlElements, Size)
245 end.
246
247 -spec handle_socket_elements(data(), [exml:element()], non_neg_integer()) -> fsm_res().
248 handle_socket_elements(StateData = #c2s_data{shaper = Shaper}, Elements, Size) ->
249 57696 {NewShaper, Pause} = mongoose_shaper:update(Shaper, Size),
250 57696 mongoose_metrics:update(global, [data, xmpp, received, xml_stanza_size], Size),
251 57696 NewStateData = StateData#c2s_data{shaper = NewShaper},
252 57696 MaybePauseTimeout = maybe_pause(NewStateData, Pause),
253 57696 StreamEvents = [ {next_event, internal, XmlEl} || XmlEl <- Elements ],
254 57696 {keep_state, NewStateData, MaybePauseTimeout ++ StreamEvents}.
255
256 -spec maybe_pause(data(), integer()) -> any().
257 maybe_pause(_StateData, Pause) when Pause > 0 ->
258
:-(
[{{timeout, activate_socket}, Pause, activate_socket}];
259 maybe_pause(#c2s_data{socket = Socket}, _) ->
260 57696 mongoose_c2s_socket:activate(Socket),
261 57696 [].
262
263 -spec close_socket(data()) -> ok | {error, term()}.
264 close_socket(#c2s_data{socket = undefined}) ->
265 285 ok;
266 close_socket(#c2s_data{socket = Socket}) ->
267 7486 mongoose_c2s_socket:close(Socket).
268
269 -spec activate_socket(data()) -> ok | {error, term()}.
270 activate_socket(#c2s_data{socket = Socket}) ->
271 95 mongoose_c2s_socket:activate(Socket).
272
273 %%%----------------------------------------------------------------------
274 %%% error handler helpers
275 %%%----------------------------------------------------------------------
276
277 -spec handle_foreign_event(data(), state(), gen_statem:event_type(), term()) -> fsm_res().
278 handle_foreign_event(StateData = #c2s_data{host_type = HostType, lserver = LServer},
279 C2SState, EventType, EventContent) ->
280 1023 Params = hook_arg(StateData, C2SState, EventType, EventContent, undefined),
281 1023 AccParams = #{host_type => HostType, lserver => LServer, location => ?LOCATION},
282 1023 Acc0 = mongoose_acc:new(AccParams),
283 1023 case mongoose_c2s_hooks:foreign_event(HostType, Acc0, Params) of
284 {ok, _Acc1} ->
285 26 ?LOG_WARNING(#{what => unknown_statem_event, c2s_state => C2SState,
286
:-(
event_type => EventType, event_content => EventContent}),
287 26 keep_state_and_data;
288 {stop, Acc1} ->
289 997 handle_state_after_packet(StateData, C2SState, Acc1)
290 end.
291
292 -spec handle_stop_request(data(), state(), atom()) -> fsm_res().
293 handle_stop_request(StateData = #c2s_data{host_type = HostType, lserver = LServer}, C2SState, Reason) ->
294 20 Params = hook_arg(StateData, C2SState, cast, {stop, Reason}, Reason),
295 20 AccParams = #{host_type => HostType, lserver => LServer, location => ?LOCATION},
296 20 Acc0 = mongoose_acc:new(AccParams),
297 20 Res = mongoose_c2s_hooks:user_stop_request(HostType, Acc0, Params),
298 20 stop_if_unhandled(StateData, C2SState, Res, Reason).
299
300 -spec handle_socket_closed(data(), state(), term()) -> fsm_res().
301 handle_socket_closed(StateData = #c2s_data{host_type = HostType, lserver = LServer}, C2SState, SocketClosed) ->
302 293 Params = hook_arg(StateData, C2SState, info, SocketClosed, SocketClosed),
303 293 AccParams = #{host_type => HostType, lserver => LServer, location => ?LOCATION},
304 293 Acc0 = mongoose_acc:new(AccParams),
305 293 Res = mongoose_c2s_hooks:user_socket_closed(HostType, Acc0, Params),
306 293 stop_if_unhandled(StateData, C2SState, Res, socket_closed).
307
308 -spec handle_socket_error(data(), state(), term()) -> fsm_res().
309 handle_socket_error(StateData = #c2s_data{host_type = HostType, lserver = LServer}, C2SState, SocketError) ->
310
:-(
Params = hook_arg(StateData, C2SState, info, SocketError, SocketError),
311
:-(
AccParams = #{host_type => HostType, lserver => LServer, location => ?LOCATION},
312
:-(
Acc0 = mongoose_acc:new(AccParams),
313
:-(
Res = mongoose_c2s_hooks:user_socket_error(HostType, Acc0, Params),
314
:-(
stop_if_unhandled(StateData, C2SState, Res, socket_error).
315
316 %% These conditions required that one and only one handler declared full control over it,
317 %% by making the hook stop at that point. If so, the process remains alive,
318 %% in control of the handler, otherwise, the condition is treated as terminal.
319 -spec stop_if_unhandled(data(), state(), gen_hook:hook_fn_ret(mongoose_acc:t()), atom()) -> fsm_res().
320 stop_if_unhandled(StateData, C2SState, {stop, Acc}, _) ->
321 60 handle_state_after_packet(StateData, C2SState, Acc);
322 stop_if_unhandled(_, _, {ok, _Acc}, Reason) ->
323 253 {stop, {shutdown, Reason}}.
324
325 %%%----------------------------------------------------------------------
326 %%% helpers
327 %%%----------------------------------------------------------------------
328
329 -spec handle_stream_start(data(), exml:element(), stream_state()) -> fsm_res().
330 handle_stream_start(S0, StreamStart, StreamState) ->
331 13666 Lang = get_xml_lang(StreamStart),
332 13666 From = maybe_capture_from_jid_in_stream_start(StreamStart),
333 13666 LServer = jid:nameprep(exml_query:attr(StreamStart, <<"to">>, <<>>)),
334 13666 case {StreamState,
335 exml_query:attr(StreamStart, <<"xmlns:stream">>, <<>>),
336 exml_query:attr(StreamStart, <<"version">>, <<>>),
337 mongoose_domain_api:get_domain_host_type(LServer)} of
338 {stream_start, ?NS_STREAM, ?XMPP_VERSION, {ok, HostType}} ->
339 7070 S = S0#c2s_data{host_type = HostType, lserver = LServer, jid = From, lang = Lang},
340 7070 stream_start_features_before_auth(S);
341 {authenticated, ?NS_STREAM, ?XMPP_VERSION, {ok, HostType}} ->
342 6590 S = S0#c2s_data{host_type = HostType, lserver = LServer, lang = Lang},
343 6590 stream_start_features_after_auth(S);
344 {_, ?NS_STREAM, _Pre1_0, {ok, HostType}} ->
345 %% (http://xmpp.org/rfcs/rfc6120.html#streams-negotiation-features)
346 2 S = S0#c2s_data{host_type = HostType, lserver = LServer, jid = From, lang = Lang},
347 2 stream_start_error(S, mongoose_xmpp_errors:unsupported_version());
348 {_, ?NS_STREAM, _, {error, not_found}} ->
349 2 stream_start_error(S0, mongoose_xmpp_errors:host_unknown());
350 {_, _, _, _} ->
351 2 stream_start_error(S0, mongoose_xmpp_errors:invalid_namespace())
352 end.
353
354 %% We conditionally set the jid field before authentication if it was provided by the client in the
355 %% stream-start stanza, as extensions might use this (carefully, as it hasn't been proved this is
356 %% the real identity of the client!). See RFC6120#section-4.7.1
357 -spec maybe_capture_from_jid_in_stream_start(exml:element()) -> error | jid:jid().
358 maybe_capture_from_jid_in_stream_start(StreamStart) ->
359 13666 case jid:from_binary(exml_query:attr(StreamStart, <<"from">>)) of
360 13664 error -> undefined;
361 2 Jid -> Jid
362 end.
363
364 %% As stated in BCP47, 4.4.1:
365 %% Protocols or specifications that specify limited buffer sizes for
366 %% language tags MUST allow for language tags of at least 35 characters.
367 %% Do not store long language tag to avoid possible DoS/flood attacks
368 -spec get_xml_lang(exml:element()) -> <<_:8, _:_*8>>.
369 get_xml_lang(StreamStart) ->
370 13666 case exml_query:attr(StreamStart, <<"xml:lang">>) of
371 Lang when is_binary(Lang), 0 < byte_size(Lang), byte_size(Lang) =< 35 ->
372 13568 Lang;
373 _ ->
374 98 ?MYLANG
375 end.
376
377 -spec handle_starttls(data(), exml:element(), mongoose_acc:t(), retries()) -> fsm_res().
378 handle_starttls(StateData = #c2s_data{socket = TcpSocket,
379 parser = Parser,
380 listener_opts = LOpts = #{tls := _}}, El, SaslAcc, Retries) ->
381 98 send_xml(StateData, mongoose_c2s_stanzas:tls_proceed()), %% send last negotiation chunk via tcp
382 98 case mongoose_c2s_socket:tcp_to_tls(TcpSocket, LOpts) of
383 {ok, TlsSocket} ->
384 95 {ok, NewParser} = exml_stream:reset_parser(Parser),
385 95 NewStateData = StateData#c2s_data{socket = TlsSocket,
386 parser = NewParser,
387 streamid = new_stream_id()},
388 95 activate_socket(NewStateData),
389 95 {next_state, {wait_for_stream, stream_start}, NewStateData, state_timeout(LOpts)};
390 {error, already_tls_connection} ->
391
:-(
ErrorStanza = mongoose_xmpp_errors:bad_request(StateData#c2s_data.lang, <<"bad_config">>),
392
:-(
Err = jlib:make_error_reply(El, ErrorStanza),
393
:-(
send_element_from_server_jid(StateData, Err),
394
:-(
maybe_retry_state(StateData, {wait_for_feature_before_auth, SaslAcc, Retries});
395 {error, closed} ->
396 1 {stop, {shutdown, tls_closed}};
397 {error, timeout} ->
398
:-(
{stop, {shutdown, tls_timeout}};
399 {error, {tls_alert, TlsAlert}} ->
400 2 {stop, TlsAlert}
401 end;
402 handle_starttls(StateData, _El, _SaslAcc, _Retries) ->
403 %% As defined in https://datatracker.ietf.org/doc/html/rfc6120#section-5.4.2.2, cause 2
404 2 send_element_from_server_jid(StateData, mongoose_c2s_stanzas:tls_failure()),
405 2 {stop, {shutdown, tls_failure}}.
406
407 -spec handle_auth_start(data(), exml:element(), mongoose_acc:t(), retries()) -> fsm_res().
408 handle_auth_start(StateData, El, SaslAcc, Retries) ->
409 6665 Mech = exml_query:attr(El, <<"mechanism">>),
410 6665 ClientIn = base64:mime_decode(exml_query:cdata(El)),
411 6665 StepResult = mongoose_c2s_sasl:start(StateData, SaslAcc, Mech, ClientIn),
412 6665 handle_sasl_step(StateData, StepResult, Retries).
413
414 -spec handle_auth_continue(data(), exml:element(), mongoose_acc:t(), retries()) -> fsm_res().
415 handle_auth_continue(StateData, El, SaslAcc, Retries) ->
416 47 ClientIn = base64:mime_decode(exml_query:cdata(El)),
417 47 StepResult = mongoose_c2s_sasl:continue(StateData, SaslAcc, ClientIn),
418 47 handle_sasl_step(StateData, StepResult, Retries).
419
420 -spec handle_sasl_step(data(), mongoose_c2s_sasl:result(), retries()) -> fsm_res().
421 handle_sasl_step(StateData, {success, NewSaslAcc, Result}, _Retries) ->
422 6592 handle_sasl_success(StateData, NewSaslAcc, Result);
423 handle_sasl_step(StateData, {continue, NewSaslAcc, Result}, Retries) ->
424 47 handle_sasl_continue(StateData, NewSaslAcc, Result, Retries);
425 handle_sasl_step(StateData, {failure, NewSaslAcc, Result}, Retries) ->
426 69 handle_sasl_failure(StateData, NewSaslAcc, Result, Retries);
427 handle_sasl_step(StateData, {error, NewSaslAcc, Result}, Retries) ->
428 4 handle_sasl_error(StateData, NewSaslAcc, Result, Retries).
429
430 -spec handle_sasl_success(data(), mongoose_acc:t(), mongoose_c2s_sasl:success()) -> fsm_res().
431 handle_sasl_success(StateData = #c2s_data{jid = MaybeInitialJid, info = Info}, SaslAcc,
432 #{server_out := MaybeServerOut, jid := SaslJid, auth_module := AuthMod}) ->
433 6592 case verify_initial_jid(MaybeInitialJid, SaslJid) of
434 true ->
435 6591 StateData1 = StateData#c2s_data{streamid = new_stream_id(), jid = SaslJid,
436 info = maps:merge(Info, #{auth_module => AuthMod})},
437 6591 El = mongoose_c2s_stanzas:sasl_success_stanza(MaybeServerOut),
438 6591 send_acc_from_server_jid(StateData1, SaslAcc, El),
439 6591 ?LOG_INFO(#{what => auth_success, text => <<"Accepted SASL authentication">>, c2s_state => StateData1}),
440 6591 {next_state, {wait_for_stream, authenticated}, StateData1, state_timeout(StateData1)};
441 false ->
442 1 c2s_stream_error(StateData, mongoose_xmpp_errors:invalid_from())
443 end.
444
445 %% 6.4.6. SASL Success: https://www.rfc-editor.org/rfc/rfc6120.html#section-6.4.6
446 -spec verify_initial_jid(undefined | jid:jid(), jid:jid()) -> boolean().
447 verify_initial_jid(undefined, _Jid) ->
448 6591 true;
449 verify_initial_jid(InitialJid, SaslJid) ->
450 1 jid:are_bare_equal(InitialJid, SaslJid).
451
452 -spec handle_sasl_continue(data(), mongoose_acc:t(), mongoose_c2s_sasl:continue(), retries()) -> fsm_res().
453 handle_sasl_continue(StateData, SaslAcc, #{server_out := ServerOut}, Retries) ->
454 47 El = mongoose_c2s_stanzas:sasl_challenge_stanza(ServerOut),
455 47 NewSaslAcc = send_acc_from_server_jid(StateData, SaslAcc, El),
456 47 {next_state, {wait_for_sasl_response, NewSaslAcc, Retries}, StateData, state_timeout(StateData)}.
457
458 -spec handle_sasl_failure(data(), mongoose_acc:t(), mongoose_c2s_sasl:failure(), retries()) -> fsm_res().
459 handle_sasl_failure(#c2s_data{host_type = HostType, lserver = LServer} = StateData, SaslAcc,
460 #{server_out := ServerOut, maybe_username := Username}, Retries) ->
461 69 ?LOG_INFO(#{what => auth_failed, text => <<"Failed SASL authentication">>,
462 69 username => Username, lserver => LServer, c2s_state => StateData}),
463 69 mongoose_hooks:auth_failed(HostType, LServer, Username),
464 69 El = mongoose_c2s_stanzas:sasl_failure_stanza(ServerOut),
465 69 NewSaslAcc = send_acc_from_server_jid(StateData, SaslAcc, El),
466 69 maybe_retry_state(StateData, {wait_for_feature_before_auth, NewSaslAcc, Retries}).
467
468 -spec handle_sasl_error(data(), mongoose_acc:t(), mongoose_c2s_sasl:error(), retries()) -> fsm_res().
469 handle_sasl_error(#c2s_data{lang = Lang} = StateData, _SaslAcc,
470 #{type := Type, text := Text}, _Retries) ->
471 4 Error = mongoose_xmpp_errors:Type(Lang, Text),
472 4 c2s_stream_error(StateData, Error).
473
474 -spec handle_sasl_abort(data(), mongoose_acc:t(), retries()) -> fsm_res().
475 handle_sasl_abort(StateData, SaslAcc, Retries) ->
476
:-(
Error = #{server_out => <<"aborted">>, maybe_username => undefined},
477
:-(
handle_sasl_failure(StateData, SaslAcc, Error, Retries).
478
479 -spec stream_start_features_before_auth(data()) -> fsm_res().
480 stream_start_features_before_auth(#c2s_data{sid = SID, listener_opts = LOpts} = StateData) ->
481 7070 send_header(StateData),
482 7070 SaslAcc0 = mongoose_c2s_sasl:new(StateData),
483 7070 SaslAcc1 = mongoose_acc:set_permanent(c2s, [{origin_sid, SID}], SaslAcc0),
484 7070 StreamFeatures = mongoose_c2s_stanzas:stream_features_before_auth(StateData),
485 7070 SaslAcc2 = send_acc_from_server_jid(StateData, SaslAcc1, StreamFeatures),
486 7070 {next_state, {wait_for_feature_before_auth, SaslAcc2, ?AUTH_RETRIES}, StateData, state_timeout(LOpts)}.
487
488 -spec stream_start_features_after_auth(data()) -> fsm_res().
489 stream_start_features_after_auth(#c2s_data{listener_opts = LOpts} = StateData) ->
490 6590 send_header(StateData),
491 6590 StreamFeatures = mongoose_c2s_stanzas:stream_features_after_auth(StateData),
492 6590 send_element_from_server_jid(StateData, StreamFeatures),
493 6590 {next_state, {wait_for_feature_after_auth, ?BIND_RETRIES}, StateData, state_timeout(LOpts)}.
494
495 -spec handle_bind_resource(data(), state(), exml:element(), jlib:iq()) -> fsm_res().
496 handle_bind_resource(StateData, C2SState, El, #iq{sub_el = SubEl} = IQ) ->
497 6568 Resource = case exml_query:path(SubEl, [{element, <<"resource">>}, cdata]) of
498 36 undefined -> <<>>;
499 6532 Val -> jid:resourceprep(Val)
500 end,
501 6568 case Resource of
502 error ->
503 1 Err = jlib:make_error_reply(El, mongoose_xmpp_errors:bad_request()),
504 1 send_element_from_server_jid(StateData, Err),
505 1 maybe_retry_state(StateData, C2SState);
506 Resource ->
507 6567 NewStateData = replace_resource(StateData, Resource),
508 6567 NextState = maybe_wait_for_session(NewStateData),
509 6567 Jid = get_jid(NewStateData),
510 6567 BindResult = mongoose_c2s_stanzas:successful_resource_binding(IQ, Jid),
511 6567 MAcc = mongoose_c2s_acc:new(#{c2s_state => NextState, socket_send => [BindResult]}),
512 6567 Acc = element_to_origin_accum(NewStateData, El),
513 6567 Acc1 = mongoose_acc:set_statem_acc(MAcc, Acc),
514 6567 HookParams = hook_arg(NewStateData, C2SState, internal, El, undefined),
515 6567 HostType = NewStateData#c2s_data.host_type,
516 6567 Return = verify_user(NextState, HostType, HookParams, Acc1),
517 6567 Return1 = maybe_open_session(NextState, Return, NewStateData),
518 6567 finish_open_session(Return1, NewStateData, C2SState, NextState)
519 end.
520
521 -spec handle_session_establishment(data(), state(), exml:element(), jlib:iq()) -> fsm_res().
522 handle_session_establishment(#c2s_data{host_type = HostType, lserver = LServer} = StateData, C2SState, El, IQ) ->
523 6556 Acc0 = element_to_origin_accum(StateData, El),
524 6556 SessEstablished = mongoose_c2s_stanzas:successful_session_establishment(IQ),
525 6556 ServerJid = jid:make_noprep(<<>>, LServer, <<>>),
526 6556 ParamsAcc = #{from_jid => ServerJid, to_jid => StateData#c2s_data.jid, element => SessEstablished},
527 6556 SessEstablishedAcc = mongoose_acc:update_stanza(ParamsAcc, Acc0),
528 6556 MAcc = mongoose_c2s_acc:new(#{c2s_state => session_established, route => [SessEstablishedAcc]}),
529 6556 Acc1 = mongoose_acc:set_statem_acc(MAcc, Acc0),
530 6556 HookParams = hook_arg(StateData, C2SState, internal, El, undefined),
531 6556 {_, Acc2} = mongoose_c2s_hooks:user_send_packet(HostType, Acc1, HookParams),
532 6556 {_, Acc3} = mongoose_c2s_hooks:user_send_iq(HostType, Acc2, HookParams),
533 6556 Return = verify_user(session_established, HostType, HookParams, Acc3),
534 6556 Return1 = maybe_open_session(session_established, Return, StateData),
535 6556 finish_open_session(Return1, StateData, C2SState, session_established).
536
537 -spec verify_user(state(), mongooseim:host_type(), mongoose_c2s_hooks:params(), mongoose_acc:t()) ->
538 {ok | stop, mongoose_acc:t()}.
539 verify_user(wait_for_session_establishment, _, _, Acc) ->
540 6562 {ok, Acc};
541 verify_user(session_established, HostType, #{c2s_data := StateData} = HookParams, Acc) ->
542 6571 case mongoose_c2s_hooks:user_open_session(HostType, Acc, HookParams) of
543 {ok, Acc1} ->
544 6569 ?LOG_INFO(#{what => c2s_opened_session, c2s_state => StateData}),
545 6569 {ok, Acc1};
546 {stop, Acc1} ->
547 2 Jid = StateData#c2s_data.jid,
548 2 Acc2 = mongoose_hooks:forbidden_session_hook(HostType, Acc1, Jid),
549 2 ?LOG_INFO(#{what => forbidden_session, text => <<"User not allowed to open session">>,
550 2 acc => Acc2, c2s_state => StateData}),
551 2 {stop, Acc2}
552 end.
553
554 %% Note that RFC 3921 said:
555 %% > Upon establishing a session, a connected resource is said to be an "active resource".
556 %% But, RFC 6121 says:
557 %% > [RFC3921] specified one additional
558 %% precondition: formal establishment of an instant messaging and
559 %% presence session. Implementation and deployment experience has
560 %% shown that this additional step is unnecessary. However, for
561 %% backward compatibility an implementation MAY still offer that
562 %% feature. This enables older software to connect while letting
563 %% newer software save a round trip.
564 %% Note that RFC 3921 said:
565 %% > If no priority is provided, a server SHOULD consider the priority to be zero.
566 %% But, RFC 6121 says:
567 %% > If no priority is provided, the processing server or client MUST consider the priority to be zero.
568 -spec maybe_open_session(state(), {ok | stop, mongoose_acc:t()}, data()) ->
569 {ok, mongoose_acc:t(), data()} | {stop, mongoose_acc:t()}.
570 maybe_open_session(session_established, {ok, Acc1}, StateData = #c2s_data{host_type = HostType}) ->
571 6569 {ReplacedPids, StateData2} = open_session(StateData),
572 6569 FsmActions = case ReplacedPids of
573 6559 [] -> [];
574 _ ->
575 10 Timeout = mongoose_config:get_opt({replaced_wait_timeout, HostType}),
576 10 [{{timeout, replaced_wait_timeout}, Timeout, ReplacedPids}]
577 end,
578 6569 Acc2 = mongoose_c2s_acc:to_acc(Acc1, actions, FsmActions),
579 6569 {ok, Acc2, StateData2};
580 maybe_open_session(wait_for_session_establishment, {ok, Acc}, StateData) ->
581 6562 {ok, Acc, StateData};
582 maybe_open_session(_, {stop, Acc}, _StateData) ->
583 2 {stop, Acc}.
584
585 -spec finish_open_session(_, data(), state(), state()) -> fsm_res().
586 finish_open_session({ok, Acc, StateData}, _, _OldC2SState, NewC2SState) ->
587 13121 handle_state_after_packet(StateData, NewC2SState, Acc);
588 finish_open_session({stop, Acc}, StateData, OldC2SState, _NewC2SState) ->
589 2 El = mongoose_acc:element(Acc),
590 2 Err = jlib:make_error_reply(El, mongoose_xmpp_errors:not_allowed()),
591 2 send_element_from_server_jid(StateData, Err),
592 2 maybe_retry_state(StateData, OldC2SState).
593
594 maybe_wait_for_session(#c2s_data{listener_opts = #{backwards_compatible_session := false}}) ->
595 5 session_established;
596 maybe_wait_for_session(#c2s_data{listener_opts = #{backwards_compatible_session := true}}) ->
597 6562 wait_for_session_establishment.
598
599 -spec maybe_retry_state(data(), state()) -> fsm_res().
600 maybe_retry_state(StateData = #c2s_data{listener_opts = LOpts}, C2SState) ->
601 72 case maybe_retry_state(C2SState) of
602 {stop, Reason} ->
603 2 {stop, Reason, StateData};
604 NextFsmState ->
605 70 {next_state, NextFsmState, StateData, state_timeout(LOpts)}
606 end.
607
608 -spec handle_cast(data(), state(), term()) -> fsm_res().
609 handle_cast(StateData, _C2SState, {exit, Reason}) when is_binary(Reason) ->
610 172 StreamConflict = mongoose_xmpp_errors:stream_conflict(StateData#c2s_data.lang, Reason),
611 172 send_element_from_server_jid(StateData, StreamConflict),
612 172 send_trailer(StateData),
613 172 {stop, {shutdown, Reason}};
614 handle_cast(StateData, _C2SState, {exit, system_shutdown}) ->
615 3 Error = mongoose_xmpp_errors:system_shutdown(),
616 3 send_element_from_server_jid(StateData, Error),
617 3 send_trailer(StateData),
618 3 {stop, {shutdown, system_shutdown}};
619 handle_cast(StateData, C2SState, {stop, Reason}) ->
620 20 handle_stop_request(StateData, C2SState, Reason);
621 handle_cast(_StateData, _C2SState, {async, Fun, Args}) ->
622
:-(
apply(Fun, Args),
623
:-(
keep_state_and_data;
624
625 handle_cast(StateData, _C2SState, {async_with_state, Fun, Args}) ->
626 309 StateData2 = apply(Fun, [StateData | Args]),
627 309 {keep_state, StateData2};
628 handle_cast(StateData, C2SState, Event) ->
629 931 handle_foreign_event(StateData, C2SState, cast, Event).
630
631 -spec handle_info(data(), state(), term()) -> fsm_res().
632 handle_info(StateData, C2SState, {route, Acc}) ->
633 41034 handle_route(StateData, C2SState, Acc);
634 handle_info(StateData, C2SState, #xmlel{} = El) ->
635
:-(
handle_c2s_packet(StateData, C2SState, El);
636 handle_info(StateData, _C2SState, {TcpOrSSl, _Socket, _Packet} = SocketData)
637 when TcpOrSSl =:= tcp; TcpOrSSl =:= ssl ->
638 58104 handle_socket_data(StateData, SocketData);
639 handle_info(StateData, C2SState, {Closed, _Socket} = SocketData)
640 when Closed =:= tcp_closed; Closed =:= ssl_closed ->
641 293 handle_socket_closed(StateData, C2SState, SocketData);
642 handle_info(StateData, C2SState, {Error, _Socket} = SocketData)
643 when Error =:= tcp_error; Error =:= ssl_error ->
644
:-(
handle_socket_error(StateData, C2SState, SocketData);
645 handle_info(StateData, C2SState, Info) ->
646 25 handle_foreign_event(StateData, C2SState, info, Info).
647
648 -spec handle_timeout(data(), state(), atom(), term()) -> fsm_res().
649 handle_timeout(StateData, _C2SState, activate_socket, activate_socket) ->
650
:-(
activate_socket(StateData),
651
:-(
keep_state_and_data;
652 handle_timeout(StateData, C2SState, replaced_wait_timeout, ReplacedPids) ->
653 2 [ verify_process_alive(StateData, C2SState, Pid) || Pid <- ReplacedPids ],
654 2 keep_state_and_data;
655 handle_timeout(StateData, C2SState, Name, Handler) when is_atom(Name), is_function(Handler, 2) ->
656 40 C2sAcc = Handler(Name, StateData),
657 40 handle_state_result(StateData, C2SState, undefined, C2sAcc);
658 handle_timeout(StateData, _C2SState, state_timeout, state_timeout_termination) ->
659
:-(
StreamConflict = mongoose_xmpp_errors:connection_timeout(),
660
:-(
send_element_from_server_jid(StateData, StreamConflict),
661
:-(
send_trailer(StateData),
662
:-(
{stop, {shutdown, state_timeout}};
663 handle_timeout(StateData, C2SState, Name, Payload) ->
664 3 handle_foreign_event(StateData, C2SState, {timeout, Name}, Payload).
665
666 verify_process_alive(StateData, C2SState, Pid) ->
667 2 IsAlive = case node(Pid) =:= node() of
668 1 true -> erlang:is_process_alive(Pid);
669 1 false -> rpc:call(node(Pid), erlang, is_process_alive, [Pid])
670 end,
671 2 case IsAlive of
672
:-(
false -> ok;
673 true ->
674 2 ?LOG_WARNING(#{what => c2s_replaced_wait_timeout,
675 text => <<"Some processes are not responding when handling replace messages">>,
676
:-(
replaced_pid => Pid, state_name => C2SState, c2s_state => StateData})
677 end.
678
679 -spec maybe_retry_state(state()) -> state() | {stop, term()}.
680
:-(
maybe_retry_state(connect) -> connect;
681 2 maybe_retry_state(wait_for_session_establishment) -> {stop, {shutdown, stream_end}};
682
:-(
maybe_retry_state(session_established) -> session_established;
683 maybe_retry_state({wait_for_stream, StreamState}) ->
684
:-(
{wait_for_stream, StreamState};
685 maybe_retry_state({wait_for_feature_after_auth, 0}) ->
686
:-(
{stop, {shutdown, retries}};
687 maybe_retry_state({wait_for_feature_before_auth, _, 0}) ->
688
:-(
{stop, {shutdown, retries}};
689 maybe_retry_state({wait_for_sasl_response, _, 0}) ->
690
:-(
{stop, {shutdown, retries}};
691 maybe_retry_state({wait_for_feature_after_auth, Retries}) ->
692 6 {wait_for_feature_after_auth, Retries - 1};
693 maybe_retry_state({wait_for_feature_before_auth, SaslAcc, Retries}) ->
694 70 {wait_for_feature_before_auth, SaslAcc, Retries - 1};
695 maybe_retry_state({wait_for_sasl_response, SaslAcc, Retries}) ->
696
:-(
{wait_for_sasl_response, SaslAcc, Retries - 1};
697 maybe_retry_state(?EXT_C2S_STATE(_) = State) ->
698 3 State.
699
700 %% @doc Check 'from' attribute in stanza RFC 6120 Section 8.1.2.1
701 -spec verify_from(exml:element(), jid:jid()) -> boolean().
702 verify_from(El, StateJid) ->
703 17016 case exml_query:attr(El, <<"from">>) of
704 16187 undefined -> true;
705 GJid ->
706 829 case jid:from_binary(GJid) of
707 error ->
708
:-(
false;
709 #jid{lresource = <<>>} = GivenJid ->
710 13 jid:are_bare_equal(GivenJid, StateJid);
711 #jid{} = GivenJid ->
712 816 jid:are_equal(GivenJid, StateJid)
713 end
714 end.
715
716 verify_to(El) ->
717 17013 case exml_query:attr(El, <<"to">>) of
718 undefined ->
719 9600 true;
720 Jid ->
721 7413 case jid:from_binary(Jid) of
722 1 error -> false;
723 7412 _ -> true
724 end
725 end.
726
727 -spec handle_foreign_packet(data(), state(), exml:element()) -> fsm_res().
728 handle_foreign_packet(StateData = #c2s_data{host_type = HostType, lserver = LServer}, C2SState, El) ->
729 442 ?LOG_DEBUG(#{what => packet_before_session_established_sent, packet => El, c2s_pid => self()}),
730 442 ServerJid = jid:make_noprep(<<>>, LServer, <<>>),
731 442 AccParams = #{host_type => HostType, lserver => LServer, location => ?LOCATION,
732 element => El, from_jid => ServerJid, to_jid => ServerJid},
733 442 Acc0 = mongoose_acc:new(AccParams),
734 442 HookParams = hook_arg(StateData, C2SState, internal, El, undefined),
735 442 {_, Acc1} = mongoose_c2s_hooks:user_send_xmlel(HostType, Acc0, HookParams),
736 442 handle_state_after_packet(StateData, C2SState, Acc1).
737
738 -spec handle_c2s_packet(data(), state(), exml:element()) -> fsm_res().
739 handle_c2s_packet(StateData = #c2s_data{host_type = HostType}, C2SState, El) ->
740 17012 HookParams = hook_arg(StateData, C2SState, internal, El, undefined),
741 17012 Acc = element_to_origin_accum(StateData, El),
742 17012 case mongoose_c2s_hooks:user_send_packet(HostType, Acc, HookParams) of
743 {ok, Acc1} ->
744 17012 Acc2 = handle_stanza_from_client(StateData, HookParams, Acc1, mongoose_acc:stanza_name(Acc1)),
745 17012 handle_state_after_packet(StateData, C2SState, Acc2);
746 {stop, Acc1} ->
747
:-(
handle_state_after_packet(StateData, C2SState, Acc1)
748 end.
749
750 %% @doc Process packets sent by the user (coming from user on c2s XMPP connection)
751 -spec handle_stanza_from_client(data(), mongoose_c2s_hooks:params(), mongoose_acc:t(), binary()) ->
752 mongoose_acc:t().
753 handle_stanza_from_client(#c2s_data{host_type = HostType}, HookParams, Acc, <<"message">>) ->
754 4139 TS0 = mongoose_acc:timestamp(Acc),
755 4139 Acc1 = mongoose_c2s_hooks:user_send_message(HostType, Acc, HookParams),
756 4139 Acc2 = maybe_route(Acc1),
757 4139 TS1 = erlang:system_time(microsecond),
758 4139 mongoose_metrics:update(HostType, [data, xmpp, c2s, message, processing_time], (TS1 - TS0)),
759 4139 Acc2;
760 handle_stanza_from_client(#c2s_data{host_type = HostType}, HookParams, Acc, <<"iq">>) ->
761 5728 Acc1 = mongoose_c2s_hooks:user_send_iq(HostType, Acc, HookParams),
762 5728 maybe_route(Acc1);
763 handle_stanza_from_client(#c2s_data{host_type = HostType}, HookParams, Acc, <<"presence">>) ->
764 6985 {_, Acc1} = mongoose_c2s_hooks:user_send_presence(HostType, Acc, HookParams),
765 6985 Acc1;
766 handle_stanza_from_client(#c2s_data{host_type = HostType}, HookParams, Acc, _) ->
767 160 {_, Acc1} = mongoose_c2s_hooks:user_send_xmlel(HostType, Acc, HookParams),
768 160 Acc1.
769
770 -spec maybe_route(gen_hook:hook_fn_ret(mongoose_acc:t())) -> mongoose_acc:t().
771 maybe_route({ok, Acc}) ->
772 9548 {FromJid, ToJid, El} = mongoose_acc:packet(Acc),
773 9548 ejabberd_router:route(FromJid, ToJid, Acc, El),
774 9548 Acc;
775 maybe_route({stop, Acc}) ->
776 319 Acc.
777
778 -spec handle_route(data(), state(), mongoose_acc:t()) -> fsm_res().
779 handle_route(StateData = #c2s_data{host_type = HostType}, C2SState, Acc) ->
780 41034 {From, To, El} = mongoose_acc:packet(Acc),
781 41034 FinalEl = jlib:replace_from_to(From, To, El),
782 41034 ParamsAcc = #{from_jid => From, to_jid => To, element => FinalEl},
783 41034 Acc1 = mongoose_acc:update_stanza(ParamsAcc, Acc),
784 41034 HookParams = hook_arg(StateData, C2SState, info, El, route),
785 41034 Res = mongoose_c2s_hooks:user_receive_packet(HostType, Acc1, HookParams),
786 41034 handle_route_packet(StateData, C2SState, HookParams, Res).
787
788 -spec handle_route_packet(data(), state(), mongoose_c2s_hooks:params(), mongoose_c2s_hooks:result()) -> fsm_res().
789 handle_route_packet(StateData, C2SState, HookParams, {ok, Acc}) ->
790 41030 StanzaName = mongoose_acc:stanza_name(Acc),
791 41030 case process_stanza_to_client(StateData, HookParams, Acc, StanzaName) of
792 {ok, Acc3} ->
793 35057 handle_flush(StateData, C2SState, Acc3);
794 {stop, Acc3} ->
795 5973 handle_state_after_packet(StateData, C2SState, Acc3)
796 end;
797 handle_route_packet(StateData, C2SState, _, {stop, Acc}) ->
798 4 handle_state_after_packet(StateData, C2SState, Acc).
799
800 -spec handle_flush(data(), state(), mongoose_acc:t()) -> fsm_res().
801 handle_flush(StateData = #c2s_data{host_type = HostType}, C2SState, Acc) ->
802 35100 HookParams = hook_arg(StateData, C2SState, info, Acc, flush),
803 35100 Res = mongoose_c2s_hooks:xmpp_presend_element(HostType, Acc, HookParams),
804 35100 Acc1 = maybe_send_element(StateData, Res),
805 35100 handle_state_after_packet(StateData, C2SState, Acc1).
806
807 -spec maybe_send_element(data(), mongoose_c2s_hooks:result()) -> mongoose_acc:t().
808 maybe_send_element(StateData, {ok, Acc}) ->
809 35072 send(StateData, Acc);
810 maybe_send_element(_, {stop, Acc}) ->
811 28 Acc.
812
813 %% @doc Process packets sent to the user (coming to user on c2s XMPP connection)
814 -spec process_stanza_to_client(data(), mongoose_c2s_hooks:params(), mongoose_acc:t(), binary()) ->
815 mongoose_c2s_hooks:result().
816 process_stanza_to_client(#c2s_data{host_type = HostType}, Params, Acc, <<"message">>) ->
817 12351 mongoose_c2s_hooks:user_receive_message(HostType, Acc, Params);
818 process_stanza_to_client(#c2s_data{host_type = HostType}, Params, Acc, <<"iq">>) ->
819 13620 mongoose_c2s_hooks:user_receive_iq(HostType, Acc, Params);
820 process_stanza_to_client(#c2s_data{host_type = HostType}, Params, Acc, <<"presence">>) ->
821 15058 mongoose_c2s_hooks:user_receive_presence(HostType, Acc, Params);
822 process_stanza_to_client(#c2s_data{host_type = HostType}, Params, Acc, _) ->
823 1 mongoose_c2s_hooks:user_receive_xmlel(HostType, Acc, Params).
824
825 -spec handle_state_after_packet(data(), state(), mongoose_acc:t()) -> fsm_res().
826 handle_state_after_packet(StateData, C2SState, Acc) ->
827 72736 handle_state_result(StateData, C2SState, Acc, mongoose_c2s_acc:get_statem_result(Acc)).
828
829 -spec handle_state_result(data(),
830 state(),
831 undefined | mongoose_acc:t(),
832 mongoose_c2s_acc:t()) -> fsm_res().
833 handle_state_result(StateData0, _, _, #{c2s_data := MaybeNewFsmData, hard_stop := Reason})
834 when Reason =/= undefined ->
835 5 StateData1 = case MaybeNewFsmData of
836 4 undefined -> StateData0;
837 1 _ -> MaybeNewFsmData
838 end,
839 5 {stop, {shutdown, Reason}, StateData1};
840 handle_state_result(StateData0, C2SState, MaybeAcc,
841 #{state_mod := ModuleStates, actions := MaybeActions,
842 c2s_state := MaybeNewFsmState, c2s_data := MaybeNewFsmData,
843 socket_send := MaybeSocketSend}) ->
844 72771 NextFsmState = case MaybeNewFsmState of
845 59533 undefined -> C2SState;
846 13238 _ -> MaybeNewFsmState
847 end,
848 72771 StateData1 = case MaybeNewFsmData of
849 72761 undefined -> StateData0;
850 10 _ -> MaybeNewFsmData
851 end,
852 72771 StateData2 = case map_size(ModuleStates) of
853 56622 0 -> StateData1;
854 16149 _ -> merge_mod_state(StateData1, ModuleStates)
855 end,
856 72771 [maybe_send_xml(StateData2, MaybeAcc, Send) || Send <- MaybeSocketSend ],
857 72771 {next_state, NextFsmState, StateData2, MaybeActions}.
858
859 %% @doc This function is executed when c2s receives a stanza from the TCP connection.
860 -spec element_to_origin_accum(data(), exml:element()) -> mongoose_acc:t().
861 element_to_origin_accum(StateData = #c2s_data{sid = SID, jid = Jid}, El) ->
862 30135 BaseParams = #{host_type => StateData#c2s_data.host_type,
863 lserver => StateData#c2s_data.lserver,
864 location => ?LOCATION,
865 element => El,
866 from_jid => Jid},
867 30135 Params = case exml_query:attr(El, <<"to">>) of
868 22723 undefined -> BaseParams#{ to_jid => jid:to_bare(Jid) };
869 7412 _ToBin -> BaseParams
870 end,
871 30135 Acc = mongoose_acc:new(Params),
872 30135 mongoose_acc:set_permanent(c2s, [{module, ?MODULE}, {origin_sid, SID}, {origin_jid, Jid}], Acc).
873
874 -spec stream_start_error(data(), exml:element()) -> fsm_res().
875 stream_start_error(StateData, Error) ->
876 6 send_header(StateData),
877 6 c2s_stream_error(StateData, Error).
878
879 -spec send_header(StateData :: data()) -> any().
880 send_header(StateData) ->
881 13672 Header = mongoose_c2s_stanzas:stream_header(StateData),
882 13672 send_xml(StateData, Header).
883
884 send_trailer(StateData) ->
885 6762 send_xml(StateData, ?XML_STREAM_TRAILER).
886
887 -spec c2s_stream_error(data(), exml:element()) -> fsm_res().
888 c2s_stream_error(StateData, Error) ->
889 52 ?LOG_DEBUG(#{what => c2s_stream_error, xml_error => Error, c2s_state => StateData}),
890 52 send_element_from_server_jid(StateData, Error),
891 52 send_xml(StateData, ?XML_STREAM_TRAILER),
892 52 {stop, {shutdown, stream_error}, StateData}.
893
894 -spec bounce_messages(data()) -> ok.
895 bounce_messages(StateData) ->
896 24113 receive
897 {route, Acc} ->
898 5659 reroute_one(StateData, Acc),
899 5659 bounce_messages(StateData);
900 _ ->
901 10683 bounce_messages(StateData)
902 7771 after 0 -> ok
903 end.
904
905 -spec reroute_one(data(), mongoose_acc:t()) -> mongoose_acc:t().
906 reroute_one(#c2s_data{sid = Sid}, Acc) ->
907 5866 {From, To, _El} = mongoose_acc:packet(Acc),
908 5866 Acc2 = patch_acc_for_reroute(Acc, Sid),
909 5866 ejabberd_router:route(From, To, Acc2).
910
911 -spec reroute_buffer(data(), [mongoose_acc:t()]) -> term().
912 reroute_buffer(StateData = #c2s_data{host_type = HostType, jid = Jid}, Buffer) ->
913 94 OrderedBuffer = lists:reverse(Buffer),
914 94 FilteredBuffer = mongoose_hooks:filter_unacknowledged_messages(HostType, Jid, OrderedBuffer),
915 94 [reroute_one(StateData, BufferedAcc) || BufferedAcc <- FilteredBuffer].
916
917 -spec reroute_buffer_to_pid(data(), pid(), [mongoose_acc:t()]) -> term().
918 reroute_buffer_to_pid(StateData = #c2s_data{host_type = HostType, jid = Jid}, Pid, Buffer) ->
919 12 OrderedBuffer = lists:reverse(Buffer),
920 12 FilteredBuffer = mongoose_hooks:filter_unacknowledged_messages(HostType, Jid, OrderedBuffer),
921 12 [reroute_one_to_pid(StateData, Pid, BufferedAcc) || BufferedAcc <- FilteredBuffer].
922
923 -spec reroute_one_to_pid(data(), pid(), mongoose_acc:t()) -> {route, mongoose_acc:t()}.
924 reroute_one_to_pid(#c2s_data{sid = Sid}, Pid, Acc) ->
925
:-(
Acc2 = patch_acc_for_reroute(Acc, Sid),
926
:-(
route(Pid, Acc2).
927
928 -spec route(pid(), mongoose_acc:t()) -> {route, mongoose_acc:t()}.
929 route(Pid, Acc) ->
930 39696 Pid ! {route, Acc}.
931
932 -spec open_session(data()) -> {[pid()], data()}.
933 open_session(
934 StateData = #c2s_data{host_type = HostType, sid = Sid, jid = Jid,
935 socket = Socket, info = Info}) ->
936 6581 NewFields = #{ip => mongoose_c2s_socket:get_ip(Socket),
937 conn => mongoose_c2s_socket:get_conn_type(Socket)},
938 6581 Info2 = maps:merge(Info, NewFields),
939 6581 ReplacedPids = ejabberd_sm:open_session(HostType, Sid, Jid, 0, Info2),
940 6581 {ReplacedPids, StateData#c2s_data{info = Info2}}.
941
942 -spec close_session(data(), mongoose_acc:t(), term()) -> mongoose_acc:t().
943 close_session(#c2s_data{sid = Sid, jid = Jid, info = Info}, Acc, Reason) ->
944 6583 ejabberd_sm:close_session(Acc, Sid, Jid, sm_unset_reason(Reason), Info).
945
946 -spec patch_acc_for_reroute(mongoose_acc:t(), ejabberd_sm:sid()) -> mongoose_acc:t().
947 patch_acc_for_reroute(Acc, Sid) ->
948 5866 case mongoose_acc:stanza_name(Acc) of
949 <<"message">> ->
950 140 Acc;
951 _ -> %% IQs and presences are allowed to come to the same Sid only
952 5726 case mongoose_acc:get(c2s, receiver_sid, undefined, Acc) of
953 undefined ->
954 5726 mongoose_acc:set_permanent(c2s, receiver_sid, Sid, Acc);
955 _ ->
956
:-(
Acc
957 end
958 end.
959
960 -spec close_parser(data()) -> ok.
961 285 close_parser(#c2s_data{parser = undefined}) -> ok;
962 7486 close_parser(#c2s_data{parser = Parser}) -> exml_stream:free_parser(Parser).
963
964 -spec do_close_session(data(), state(), mongoose_acc:t(), term()) -> mongoose_acc:t().
965 do_close_session(C2SData, session_established, Acc, Reason) ->
966 6528 close_session(C2SData, Acc, Reason);
967 do_close_session(C2SData, ?EXT_C2S_STATE(_), Acc, Reason) ->
968 55 close_session(C2SData, Acc, Reason);
969 do_close_session(_, _, Acc, _) ->
970 1188 Acc.
971
972 sm_unset_reason({shutdown, Reason}) ->
973 6583 Reason;
974 sm_unset_reason(normal) ->
975
:-(
normal;
976 sm_unset_reason(_) ->
977
:-(
error.
978
979 %% @doc These are the termination points - from here stanza is sent to the user
980 -spec send(data(), mongoose_acc:t()) -> mongoose_acc:t().
981 send(StateData, Acc) ->
982 35072 El = mongoose_acc:element(Acc),
983 35072 do_send_element(StateData, Acc, El).
984
985 -spec send_element_from_server_jid(data(), exml:element()) -> any().
986 send_element_from_server_jid(StateData, El) ->
987 6823 Acc = mongoose_acc:new(
988 #{host_type => StateData#c2s_data.host_type,
989 lserver => StateData#c2s_data.lserver,
990 location => ?LOCATION,
991 from_jid => jid:make_noprep(<<>>, StateData#c2s_data.lserver, <<>>),
992 to_jid => StateData#c2s_data.jid,
993 element => El}),
994 6823 do_send_element(StateData, Acc, El).
995
996 -spec send_acc_from_server_jid(data(), mongoose_acc:t(), exml:element()) -> mongoose_acc:t().
997 send_acc_from_server_jid(StateData = #c2s_data{lserver = LServer, jid = Jid}, Acc0, El) ->
998 13777 ServerJid = jid:make_noprep(<<>>, LServer, <<>>),
999 13777 ParamsAcc = #{from_jid => ServerJid, to_jid => Jid, element => El},
1000 13777 Acc1 = mongoose_acc:update_stanza(ParamsAcc, Acc0),
1001 13777 do_send_element(StateData, Acc1, El).
1002
1003 -spec maybe_send_xml(data(), mongoose_acc:t(), exml:element()) -> mongoose_acc:t().
1004 maybe_send_xml(StateData = #c2s_data{host_type = HostType, lserver = LServer}, undefined, ToSend) ->
1005
:-(
Acc = mongoose_acc:new(#{host_type => HostType, lserver => LServer, location => ?LOCATION}),
1006
:-(
do_send_element(StateData, Acc, ToSend);
1007 maybe_send_xml(StateData, Acc, ToSend) ->
1008 6867 do_send_element(StateData, Acc, ToSend).
1009
1010 -spec do_send_element(data(), mongoose_acc:t(), exml:element()) -> mongoose_acc:t().
1011 do_send_element(StateData = #c2s_data{host_type = undefined}, Acc, El) ->
1012 10 send_xml(StateData, El),
1013 10 Acc;
1014 do_send_element(StateData = #c2s_data{host_type = HostType}, Acc, #xmlel{} = El) ->
1015 62529 Res = send_xml(StateData, El),
1016 62529 Acc1 = mongoose_acc:set(c2s, send_result, Res, Acc),
1017 62529 mongoose_hooks:xmpp_send_element(HostType, Acc1, El).
1018
1019 -spec send_xml(data(), exml_stream:element() | [exml_stream:element()]) -> maybe_ok().
1020 send_xml(Data, XmlElement) when is_tuple(XmlElement) ->
1021 83123 send_xml(Data, [XmlElement]);
1022 send_xml(#c2s_data{socket = Socket}, XmlElements) when is_list(XmlElements) ->
1023 83123 [mongoose_metrics:update(global, [data, xmpp, sent, xml_stanza_size], exml:xml_size(El))
1024 83123 || El <- XmlElements],
1025 83123 mongoose_c2s_socket:send_xml(Socket, XmlElements).
1026
1027
1028 state_timeout(#c2s_data{listener_opts = LOpts}) ->
1029 6646 state_timeout(LOpts);
1030 state_timeout(#{c2s_state_timeout := Timeout}) ->
1031 27957 {state_timeout, Timeout, state_timeout_termination}.
1032
1033 -spec replace_resource(data(), binary()) -> data().
1034 replace_resource(StateData, <<>>) ->
1035 36 replace_resource(StateData, generate_random_resource());
1036 replace_resource(#c2s_data{jid = OldJid} = StateData, NewResource) ->
1037 6577 StateData#c2s_data{jid = jid:replace_resource_noprep(OldJid, NewResource)}.
1038
1039 -spec new_stream_id() -> binary().
1040 new_stream_id() ->
1041 14517 mongoose_bin:gen_from_crypto().
1042
1043 -spec generate_random_resource() -> jid:lresource().
1044 generate_random_resource() ->
1045 46 <<(mongoose_bin:gen_from_timestamp())/binary, "-", (mongoose_bin:gen_from_crypto())/binary>>.
1046
1047 -spec hook_arg(data(), state(), terminate | gen_statem:event_type(), term(), term()) ->
1048 mongoose_c2s_hooks:params().
1049 hook_arg(StateData, C2SState, EventType, #{event_tag := EventTag,
1050 event_content := EventContent}, Reason) ->
1051 995 #{c2s_data => StateData, c2s_state => C2SState,
1052 event_type => EventType, event_tag => EventTag, event_content => EventContent,
1053 reason => Reason};
1054 hook_arg(StateData, C2SState, EventType, EventContent, Reason) ->
1055 114893 #{c2s_data => StateData, c2s_state => C2SState,
1056 event_type => EventType, event_content => EventContent,
1057 reason => Reason}.
1058
1059 %%%----------------------------------------------------------------------
1060 %%% API
1061 %%%----------------------------------------------------------------------
1062
1063 -spec start({module(), term(), listener_opts()}, [gen_statem:start_opt()]) ->
1064 supervisor:startchild_ret().
1065 start(Params, ProcOpts) ->
1066 175 supervisor:start_child(mongoose_c2s_sup, [Params, ProcOpts]).
1067
1068 -spec start_link({module(), term(), listener_opts()}, [gen_statem:start_opt()]) ->
1069 gen_statem:start_ret().
1070 start_link(Params, ProcOpts) ->
1071 7771 gen_statem:start_link(?MODULE, Params, ProcOpts).
1072
1073 -spec stop(gen_statem:server_ref(), atom()) -> ok.
1074 stop(Pid, Reason) ->
1075 133 gen_statem:cast(Pid, {stop, Reason}).
1076
1077 -spec exit(pid(), binary() | atom()) -> ok.
1078 exit(Pid, Reason) ->
1079 181 gen_statem:cast(Pid, {exit, Reason}).
1080
1081 -spec async(pid(), fun(), [term()]) -> ok.
1082 async(Pid, Fun, Args) ->
1083
:-(
gen_statem:cast(Pid, {async, Fun, Args}).
1084
1085 -spec async_with_state(pid(), fun(), [term()]) -> ok.
1086 async_with_state(Pid, Fun, Args) ->
1087 5921 gen_statem:cast(Pid, {async_with_state, Fun, Args}).
1088
1089 -spec call(pid(), atom(), term()) -> term().
1090 call(Pid, EventTag, EventContent) ->
1091 79 gen_statem:call(Pid, #{event_tag => EventTag, event_content => EventContent}, 5000).
1092
1093 -spec cast(pid(), atom(), term()) -> ok.
1094 cast(Pid, EventTag, EventContent) ->
1095 931 gen_statem:cast(Pid, #{event_tag => EventTag, event_content => EventContent}).
1096
1097 -spec create_data(#{host_type := mongooseim:host_type(), jid := jid:jid()}) -> data().
1098 create_data(#{host_type := HostType, jid := Jid}) ->
1099 60 #c2s_data{host_type = HostType, jid = Jid}.
1100
1101 -spec get_auth_mechs(data()) -> [mongoose_c2s_sasl:mechanism()].
1102 get_auth_mechs(#c2s_data{host_type = HostType} = StateData) ->
1103 13735 [M || M <- cyrsasl:listmech(HostType), filter_mechanism(StateData, M)].
1104
1105 -spec filter_mechanism(data(), binary()) -> boolean().
1106 filter_mechanism(#c2s_data{socket = Socket}, <<"SCRAM-SHA-1-PLUS">>) ->
1107 427 mongoose_c2s_socket:is_channel_binding_supported(Socket);
1108 filter_mechanism(#c2s_data{socket = Socket}, <<"SCRAM-SHA-", _N:3/binary, "-PLUS">>) ->
1109 14752 mongoose_c2s_socket:is_channel_binding_supported(Socket);
1110 filter_mechanism(#c2s_data{socket = Socket, listener_opts = LOpts}, <<"EXTERNAL">>) ->
1111 192 mongoose_c2s_socket:has_peer_cert(Socket, LOpts);
1112 filter_mechanism(_, _) ->
1113 28797 true.
1114
1115 -spec get_host_type(data()) -> mongooseim:host_type().
1116 get_host_type(#c2s_data{host_type = HostType}) ->
1117 29407 HostType.
1118
1119 -spec get_lserver(data()) -> jid:lserver().
1120 get_lserver(#c2s_data{lserver = LServer}) ->
1121 47880 LServer.
1122
1123 -spec get_sid(data()) -> ejabberd_sm:sid().
1124 get_sid(#c2s_data{sid = Sid}) ->
1125 77250 Sid.
1126
1127 -spec get_ip(data()) -> term().
1128 get_ip(#c2s_data{socket = Socket}) ->
1129 386 mongoose_c2s_socket:get_ip(Socket).
1130
1131 -spec get_socket(data()) -> mongoose_c2s_socket:socket() | undefined.
1132 get_socket(#c2s_data{socket = Socket}) ->
1133 13872 Socket.
1134
1135 -spec get_jid(data()) -> jid:jid() | undefined.
1136 get_jid(#c2s_data{jid = Jid}) ->
1137 99731 Jid.
1138
1139 -spec set_jid(data(), jid:jid()) -> data().
1140 set_jid(StateData, NewJid) ->
1141 20 StateData#c2s_data{jid = NewJid}.
1142
1143 -spec set_auth_module(data(), module()) -> data().
1144 set_auth_module(StateData = #c2s_data{info = Info}, AuthModule) ->
1145 20 StateData#c2s_data{info = maps:merge(Info, #{auth_module => AuthModule})}.
1146
1147 -spec get_info(data()) -> info().
1148 get_info(#c2s_data{info = Info}) ->
1149 6324 Info.
1150
1151 -spec set_info(data(), info()) -> data().
1152 set_info(StateData, Info) ->
1153 310 StateData#c2s_data{info = Info}.
1154
1155 -spec get_lang(data()) -> ejabberd:lang().
1156 get_lang(#c2s_data{lang = Lang}) ->
1157 13676 Lang.
1158
1159 -spec get_stream_id(data()) -> binary().
1160 get_stream_id(#c2s_data{streamid = StreamId}) ->
1161 13672 StreamId.
1162
1163 -spec get_mod_state(data(), module()) -> {ok, term()} | {error, not_found}.
1164 get_mod_state(#c2s_data{state_mod = ModStates}, ModName) ->
1165 129815 case maps:get(ModName, ModStates, undefined) of
1166 104721 undefined -> {error, not_found};
1167 25094 ModState -> {ok, ModState}
1168 end.
1169
1170 -spec get_listener_opts(data()) -> listener_opts().
1171 get_listener_opts(#c2s_data{listener_opts = ListenerOpts}) ->
1172 40482 ListenerOpts.
1173
1174 -spec merge_mod_state(data(), #{module() => term()}) -> data().
1175 merge_mod_state(StateData = #c2s_data{state_mod = ModStates}, MoreModStates) ->
1176 16173 StateData#c2s_data{state_mod = maps:merge(ModStates, MoreModStates)}.
1177
1178 -spec remove_mod_state(data(), module()) -> data().
1179 remove_mod_state(StateData = #c2s_data{state_mod = ModStates}, ModName) ->
1180
:-(
StateData#c2s_data{state_mod = maps:remove(ModName, ModStates)}.
1181
1182 -spec merge_states(data(), data()) -> data().
1183 merge_states(S0 = #c2s_data{}, S1 = #c2s_data{}) ->
1184 12 S1#c2s_data{
1185 host_type = S0#c2s_data.host_type,
1186 lserver = S0#c2s_data.lserver,
1187 jid = S0#c2s_data.jid,
1188 state_mod = S0#c2s_data.state_mod,
1189 info = S0#c2s_data.info
1190 }.
Line Hits Source