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 |
4519 |
handle_event_function. |
80 |
|
|
81 |
|
-spec init({module(), term(), listener_opts()}) -> |
82 |
|
gen_statem:init_result(state(), data()). |
83 |
|
init({SocketModule, SocketOpts, LOpts}) -> |
84 |
4499 |
StateData = #c2s_data{listener_opts = LOpts}, |
85 |
4499 |
ConnectEvent = {next_event, internal, {connect, {SocketModule, SocketOpts}}}, |
86 |
4499 |
{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 |
4499 |
{ok, Parser} = exml_stream:new_parser([{max_element_size, MaxStanzaSize}]), |
93 |
4499 |
Shaper = mongoose_shaper:new(ShaperName), |
94 |
4499 |
C2SSocket = mongoose_c2s_socket:new(SocketModule, SocketOpts, LOpts), |
95 |
4214 |
StateData1 = StateData#c2s_data{socket = C2SSocket, parser = Parser, shaper = Shaper}, |
96 |
4214 |
{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 |
7132 |
StreamStart = #xmlel{name = Name, attrs = Attrs}, |
100 |
7132 |
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 |
3346 |
send_trailer(StateData), |
114 |
3346 |
{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 |
3395 |
case exml_query:attr(El, <<"xmlns">>) of |
128 |
|
?NS_SASL -> |
129 |
3395 |
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 |
3306 |
case jlib:iq_query_info(El) of |
149 |
|
#iq{type = set, xmlns = ?NS_BIND} = IQ -> |
150 |
3306 |
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 |
3295 |
case jlib:iq_query_info(El) of |
158 |
|
#iq{type = set, xmlns = ?NS_SESSION} = IQ -> |
159 |
3295 |
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 |
7702 |
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 |
7699 |
case verify_to(El) of |
169 |
|
true -> |
170 |
7698 |
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 |
44 |
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 |
47091 |
handle_info(StateData, FsmState, Info); |
188 |
|
|
189 |
|
handle_event(cast, Info, FsmState, StateData) -> |
190 |
1083 |
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 |
58 |
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 |
4497 |
?LOG_DEBUG(#{what => c2s_statem_terminate, reason => Reason, c2s_state => C2SState, c2s_data => StateData}), |
207 |
4497 |
Params = hook_arg(StateData, C2SState, terminate, Reason, Reason), |
208 |
4497 |
Acc0 = mongoose_acc:new(#{host_type => HostType, lserver => LServer, location => ?LOCATION}), |
209 |
4497 |
Acc1 = mongoose_acc:set_permanent(c2s, [{origin_sid, SID}], Acc0), |
210 |
4497 |
Acc2 = mongoose_c2s_hooks:user_terminate(HostType, Acc1, Params), |
211 |
4497 |
Acc3 = do_close_session(StateData, C2SState, Acc2, Reason), |
212 |
4497 |
mongoose_c2s_hooks:reroute_unacked_messages(HostType, Acc3, Params), |
213 |
4497 |
bounce_messages(StateData), |
214 |
4497 |
close_parser(StateData), |
215 |
4497 |
close_socket(StateData), |
216 |
4497 |
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 |
29470 |
case mongoose_c2s_socket:handle_data(Socket, Payload) of |
225 |
|
{error, _Reason} -> |
226 |
400 |
{stop, {shutdown, socket_error}, StateData}; |
227 |
|
Data -> |
228 |
29070 |
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 |
1642 |
?LOG_DEBUG(#{what => received_raw_on_stream, elements => Elements, c2s_pid => self()}), |
234 |
1642 |
handle_socket_elements(StateData, Elements, 0); |
235 |
|
handle_socket_packet(StateData = #c2s_data{parser = Parser}, Packet) -> |
236 |
27428 |
?LOG_DEBUG(#{what => received_xml_on_stream, packet => Packet, c2s_pid => self()}), |
237 |
27428 |
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 |
27420 |
Size = iolist_size(Packet), |
243 |
27420 |
NewStateData = StateData#c2s_data{parser = NewParser}, |
244 |
27420 |
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 |
29062 |
{NewShaper, Pause} = mongoose_shaper:update(Shaper, Size), |
250 |
29062 |
mongoose_metrics:update(global, [data, xmpp, received, xml_stanza_size], Size), |
251 |
29062 |
NewStateData = StateData#c2s_data{shaper = NewShaper}, |
252 |
29062 |
MaybePauseTimeout = maybe_pause(NewStateData, Pause), |
253 |
29062 |
StreamEvents = [ {next_event, internal, XmlEl} || XmlEl <- Elements ], |
254 |
29062 |
{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 |
29062 |
mongoose_c2s_socket:activate(Socket), |
261 |
29062 |
[]. |
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 |
4212 |
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 |
931 |
Params = hook_arg(StateData, C2SState, EventType, EventContent, undefined), |
281 |
931 |
AccParams = #{host_type => HostType, lserver => LServer, location => ?LOCATION}, |
282 |
931 |
Acc0 = mongoose_acc:new(AccParams), |
283 |
931 |
case mongoose_c2s_hooks:foreign_event(HostType, Acc0, Params) of |
284 |
|
{ok, _Acc1} -> |
285 |
215 |
?LOG_WARNING(#{what => unknown_statem_event, c2s_state => C2SState, |
286 |
:-( |
event_type => EventType, event_content => EventContent}), |
287 |
215 |
keep_state_and_data; |
288 |
|
{stop, Acc1} -> |
289 |
716 |
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 |
16 |
Params = hook_arg(StateData, C2SState, cast, {stop, Reason}, Reason), |
295 |
16 |
AccParams = #{host_type => HostType, lserver => LServer, location => ?LOCATION}, |
296 |
16 |
Acc0 = mongoose_acc:new(AccParams), |
297 |
16 |
Res = mongoose_c2s_hooks:user_stop_request(HostType, Acc0, Params), |
298 |
16 |
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 |
272 |
Params = hook_arg(StateData, C2SState, info, SocketClosed, SocketClosed), |
303 |
272 |
AccParams = #{host_type => HostType, lserver => LServer, location => ?LOCATION}, |
304 |
272 |
Acc0 = mongoose_acc:new(AccParams), |
305 |
272 |
Res = mongoose_c2s_hooks:user_socket_closed(HostType, Acc0, Params), |
306 |
272 |
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 |
228 |
{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 |
7132 |
Lang = get_xml_lang(StreamStart), |
332 |
7132 |
From = maybe_capture_from_jid_in_stream_start(StreamStart), |
333 |
7132 |
LServer = jid:nameprep(exml_query:attr(StreamStart, <<"to">>, <<>>)), |
334 |
7132 |
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 |
3798 |
S = S0#c2s_data{host_type = HostType, lserver = LServer, jid = From, lang = Lang}, |
340 |
3798 |
stream_start_features_before_auth(S); |
341 |
|
{authenticated, ?NS_STREAM, ?XMPP_VERSION, {ok, HostType}} -> |
342 |
3328 |
S = S0#c2s_data{host_type = HostType, lserver = LServer, lang = Lang}, |
343 |
3328 |
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 |
7132 |
case jid:from_binary(exml_query:attr(StreamStart, <<"from">>)) of |
360 |
7130 |
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 |
7132 |
case exml_query:attr(StreamStart, <<"xml:lang">>) of |
371 |
|
Lang when is_binary(Lang), 0 < byte_size(Lang), byte_size(Lang) =< 35 -> |
372 |
7034 |
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 |
3395 |
Mech = exml_query:attr(El, <<"mechanism">>), |
410 |
3395 |
ClientIn = base64:mime_decode(exml_query:cdata(El)), |
411 |
3395 |
StepResult = mongoose_c2s_sasl:start(StateData, SaslAcc, Mech, ClientIn), |
412 |
3395 |
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 |
3329 |
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 |
62 |
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 |
3329 |
case verify_initial_jid(MaybeInitialJid, SaslJid) of |
434 |
|
true -> |
435 |
3328 |
StateData1 = StateData#c2s_data{streamid = new_stream_id(), jid = SaslJid, |
436 |
|
info = maps:merge(Info, #{auth_module => AuthMod})}, |
437 |
3328 |
El = mongoose_c2s_stanzas:sasl_success_stanza(MaybeServerOut), |
438 |
3328 |
send_acc_from_server_jid(StateData1, SaslAcc, El), |
439 |
3328 |
?LOG_INFO(#{what => auth_success, text => <<"Accepted SASL authentication">>, c2s_state => StateData1}), |
440 |
3328 |
{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 |
3328 |
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 |
62 |
?LOG_INFO(#{what => auth_failed, text => <<"Failed SASL authentication">>, |
462 |
62 |
username => Username, lserver => LServer, c2s_state => StateData}), |
463 |
62 |
mongoose_hooks:auth_failed(HostType, LServer, Username), |
464 |
62 |
El = mongoose_c2s_stanzas:sasl_failure_stanza(ServerOut), |
465 |
62 |
NewSaslAcc = send_acc_from_server_jid(StateData, SaslAcc, El), |
466 |
62 |
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 |
3798 |
send_header(StateData), |
482 |
3798 |
SaslAcc0 = mongoose_c2s_sasl:new(StateData), |
483 |
3798 |
SaslAcc1 = mongoose_acc:set_permanent(c2s, [{origin_sid, SID}], SaslAcc0), |
484 |
3798 |
StreamFeatures = mongoose_c2s_stanzas:stream_features_before_auth(StateData), |
485 |
3798 |
SaslAcc2 = send_acc_from_server_jid(StateData, SaslAcc1, StreamFeatures), |
486 |
3798 |
{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 |
3328 |
send_header(StateData), |
491 |
3328 |
StreamFeatures = mongoose_c2s_stanzas:stream_features_after_auth(StateData), |
492 |
3328 |
send_element_from_server_jid(StateData, StreamFeatures), |
493 |
3328 |
{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 |
3306 |
Resource = case exml_query:path(SubEl, [{element, <<"resource">>}, cdata]) of |
498 |
36 |
undefined -> <<>>; |
499 |
3270 |
Val -> jid:resourceprep(Val) |
500 |
|
end, |
501 |
3306 |
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 |
3305 |
NewStateData = replace_resource(StateData, Resource), |
508 |
3305 |
NextState = maybe_wait_for_session(NewStateData), |
509 |
3305 |
Jid = get_jid(NewStateData), |
510 |
3305 |
BindResult = mongoose_c2s_stanzas:successful_resource_binding(IQ, Jid), |
511 |
3305 |
MAcc = mongoose_c2s_acc:new(#{c2s_state => NextState, socket_send => [BindResult]}), |
512 |
3305 |
Acc = element_to_origin_accum(NewStateData, El), |
513 |
3305 |
Acc1 = mongoose_acc:set_statem_acc(MAcc, Acc), |
514 |
3305 |
HookParams = hook_arg(NewStateData, C2SState, internal, El, undefined), |
515 |
3305 |
HostType = NewStateData#c2s_data.host_type, |
516 |
3305 |
Return = verify_user(NextState, HostType, HookParams, Acc1), |
517 |
3305 |
Return1 = maybe_open_session(NextState, Return, NewStateData), |
518 |
3305 |
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 |
3295 |
Acc0 = element_to_origin_accum(StateData, El), |
524 |
3295 |
SessEstablished = mongoose_c2s_stanzas:successful_session_establishment(IQ), |
525 |
3295 |
ServerJid = jid:make_noprep(<<>>, LServer, <<>>), |
526 |
3295 |
ParamsAcc = #{from_jid => ServerJid, to_jid => StateData#c2s_data.jid, element => SessEstablished}, |
527 |
3295 |
SessEstablishedAcc = mongoose_acc:update_stanza(ParamsAcc, Acc0), |
528 |
3295 |
MAcc = mongoose_c2s_acc:new(#{c2s_state => session_established, route => [SessEstablishedAcc]}), |
529 |
3295 |
Acc1 = mongoose_acc:set_statem_acc(MAcc, Acc0), |
530 |
3295 |
HookParams = hook_arg(StateData, C2SState, internal, El, undefined), |
531 |
3295 |
{_, Acc2} = mongoose_c2s_hooks:user_send_packet(HostType, Acc1, HookParams), |
532 |
3295 |
{_, Acc3} = mongoose_c2s_hooks:user_send_iq(HostType, Acc2, HookParams), |
533 |
3295 |
Return = verify_user(session_established, HostType, HookParams, Acc3), |
534 |
3295 |
Return1 = maybe_open_session(session_established, Return, StateData), |
535 |
3295 |
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 |
3300 |
{ok, Acc}; |
541 |
|
verify_user(session_established, HostType, #{c2s_data := StateData} = HookParams, Acc) -> |
542 |
3310 |
case mongoose_c2s_hooks:user_open_session(HostType, Acc, HookParams) of |
543 |
|
{ok, Acc1} -> |
544 |
3308 |
?LOG_INFO(#{what => c2s_opened_session, c2s_state => StateData}), |
545 |
3308 |
{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 |
3308 |
{ReplacedPids, StateData2} = open_session(StateData), |
572 |
3308 |
FsmActions = case ReplacedPids of |
573 |
3298 |
[] -> []; |
574 |
|
_ -> |
575 |
10 |
Timeout = mongoose_config:get_opt({replaced_wait_timeout, HostType}), |
576 |
10 |
[{{timeout, replaced_wait_timeout}, Timeout, ReplacedPids}] |
577 |
|
end, |
578 |
3308 |
Acc2 = mongoose_c2s_acc:to_acc(Acc1, actions, FsmActions), |
579 |
3308 |
{ok, Acc2, StateData2}; |
580 |
|
maybe_open_session(wait_for_session_establishment, {ok, Acc}, StateData) -> |
581 |
3300 |
{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 |
6598 |
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 |
3300 |
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 |
65 |
case maybe_retry_state(C2SState) of |
602 |
|
{stop, Reason} -> |
603 |
2 |
{stop, Reason, StateData}; |
604 |
|
NextFsmState -> |
605 |
63 |
{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 |
166 |
StreamConflict = mongoose_xmpp_errors:stream_conflict(StateData#c2s_data.lang, Reason), |
611 |
166 |
send_element_from_server_jid(StateData, StreamConflict), |
612 |
166 |
send_trailer(StateData), |
613 |
166 |
{stop, {shutdown, Reason}}; |
614 |
|
handle_cast(StateData, _C2SState, {exit, system_shutdown}) -> |
615 |
1 |
Error = mongoose_xmpp_errors:system_shutdown(), |
616 |
1 |
send_element_from_server_jid(StateData, Error), |
617 |
1 |
send_trailer(StateData), |
618 |
1 |
{stop, {shutdown, system_shutdown}}; |
619 |
|
handle_cast(StateData, C2SState, {stop, Reason}) -> |
620 |
16 |
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 |
244 |
StateData2 = apply(Fun, [StateData | Args]), |
627 |
244 |
{keep_state, StateData2}; |
628 |
|
handle_cast(StateData, C2SState, Event) -> |
629 |
656 |
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 |
17135 |
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 |
29470 |
handle_socket_data(StateData, SocketData); |
639 |
|
handle_info(StateData, C2SState, {Closed, _Socket} = SocketData) |
640 |
|
when Closed =:= tcp_closed; Closed =:= ssl_closed -> |
641 |
272 |
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 |
214 |
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 |
63 |
{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 |
7702 |
case exml_query:attr(El, <<"from">>) of |
704 |
6893 |
undefined -> true; |
705 |
|
GJid -> |
706 |
809 |
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 |
796 |
jid:are_equal(GivenJid, StateJid) |
713 |
|
end |
714 |
|
end. |
715 |
|
|
716 |
|
verify_to(El) -> |
717 |
7699 |
case exml_query:attr(El, <<"to">>) of |
718 |
|
undefined -> |
719 |
4099 |
true; |
720 |
|
Jid -> |
721 |
3600 |
case jid:from_binary(Jid) of |
722 |
1 |
error -> false; |
723 |
3599 |
_ -> 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 |
7698 |
HookParams = hook_arg(StateData, C2SState, internal, El, undefined), |
741 |
7698 |
Acc = element_to_origin_accum(StateData, El), |
742 |
7698 |
case mongoose_c2s_hooks:user_send_packet(HostType, Acc, HookParams) of |
743 |
|
{ok, Acc1} -> |
744 |
7698 |
Acc2 = handle_stanza_from_client(StateData, HookParams, Acc1, mongoose_acc:stanza_name(Acc1)), |
745 |
7698 |
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 |
1685 |
TS0 = mongoose_acc:timestamp(Acc), |
755 |
1685 |
Acc1 = mongoose_c2s_hooks:user_send_message(HostType, Acc, HookParams), |
756 |
1685 |
Acc2 = maybe_route(Acc1), |
757 |
1685 |
TS1 = erlang:system_time(microsecond), |
758 |
1685 |
mongoose_metrics:update(HostType, [data, xmpp, c2s, message, processing_time], (TS1 - TS0)), |
759 |
1685 |
Acc2; |
760 |
|
handle_stanza_from_client(#c2s_data{host_type = HostType}, HookParams, Acc, <<"iq">>) -> |
761 |
2098 |
Acc1 = mongoose_c2s_hooks:user_send_iq(HostType, Acc, HookParams), |
762 |
2098 |
maybe_route(Acc1); |
763 |
|
handle_stanza_from_client(#c2s_data{host_type = HostType}, HookParams, Acc, <<"presence">>) -> |
764 |
3762 |
{_, Acc1} = mongoose_c2s_hooks:user_send_presence(HostType, Acc, HookParams), |
765 |
3762 |
Acc1; |
766 |
|
handle_stanza_from_client(#c2s_data{host_type = HostType}, HookParams, Acc, _) -> |
767 |
153 |
{_, Acc1} = mongoose_c2s_hooks:user_send_xmlel(HostType, Acc, HookParams), |
768 |
153 |
Acc1. |
769 |
|
|
770 |
|
-spec maybe_route(gen_hook:hook_fn_ret(mongoose_acc:t())) -> mongoose_acc:t(). |
771 |
|
maybe_route({ok, Acc}) -> |
772 |
3488 |
{FromJid, ToJid, El} = mongoose_acc:packet(Acc), |
773 |
3488 |
ejabberd_router:route(FromJid, ToJid, Acc, El), |
774 |
3488 |
Acc; |
775 |
|
maybe_route({stop, Acc}) -> |
776 |
295 |
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 |
17135 |
{From, To, El} = mongoose_acc:packet(Acc), |
781 |
17135 |
FinalEl = jlib:replace_from_to(From, To, El), |
782 |
17135 |
ParamsAcc = #{from_jid => From, to_jid => To, element => FinalEl}, |
783 |
17135 |
Acc1 = mongoose_acc:update_stanza(ParamsAcc, Acc), |
784 |
17135 |
HookParams = hook_arg(StateData, C2SState, info, El, route), |
785 |
17135 |
Res = mongoose_c2s_hooks:user_receive_packet(HostType, Acc1, HookParams), |
786 |
17135 |
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 |
17130 |
StanzaName = mongoose_acc:stanza_name(Acc), |
791 |
17130 |
case process_stanza_to_client(StateData, HookParams, Acc, StanzaName) of |
792 |
|
{ok, Acc3} -> |
793 |
13833 |
handle_flush(StateData, C2SState, Acc3); |
794 |
|
{stop, Acc3} -> |
795 |
3297 |
handle_state_after_packet(StateData, C2SState, Acc3) |
796 |
|
end; |
797 |
|
handle_route_packet(StateData, C2SState, _, {stop, Acc}) -> |
798 |
5 |
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 |
13877 |
HookParams = hook_arg(StateData, C2SState, info, Acc, flush), |
803 |
13877 |
Res = mongoose_c2s_hooks:xmpp_presend_element(HostType, Acc, HookParams), |
804 |
13877 |
Acc1 = maybe_send_element(StateData, Res), |
805 |
13877 |
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 |
13848 |
send(StateData, Acc); |
810 |
|
maybe_send_element(_, {stop, Acc}) -> |
811 |
29 |
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 |
2505 |
mongoose_c2s_hooks:user_receive_message(HostType, Acc, Params); |
818 |
|
process_stanza_to_client(#c2s_data{host_type = HostType}, Params, Acc, <<"iq">>) -> |
819 |
6455 |
mongoose_c2s_hooks:user_receive_iq(HostType, Acc, Params); |
820 |
|
process_stanza_to_client(#c2s_data{host_type = HostType}, Params, Acc, <<"presence">>) -> |
821 |
8169 |
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 |
32720 |
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 |
32755 |
NextFsmState = case MaybeNewFsmState of |
845 |
26040 |
undefined -> C2SState; |
846 |
6715 |
_ -> MaybeNewFsmState |
847 |
|
end, |
848 |
32755 |
StateData1 = case MaybeNewFsmData of |
849 |
32745 |
undefined -> StateData0; |
850 |
10 |
_ -> MaybeNewFsmData |
851 |
|
end, |
852 |
32755 |
StateData2 = case map_size(ModuleStates) of |
853 |
23590 |
0 -> StateData1; |
854 |
9165 |
_ -> merge_mod_state(StateData1, ModuleStates) |
855 |
|
end, |
856 |
32755 |
[maybe_send_xml(StateData2, MaybeAcc, Send) || Send <- MaybeSocketSend ], |
857 |
32755 |
{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 |
14298 |
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 |
14298 |
Params = case exml_query:attr(El, <<"to">>) of |
868 |
10699 |
undefined -> BaseParams#{ to_jid => jid:to_bare(Jid) }; |
869 |
3599 |
_ToBin -> BaseParams |
870 |
|
end, |
871 |
14298 |
Acc = mongoose_acc:new(Params), |
872 |
14298 |
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 |
7138 |
Header = mongoose_c2s_stanzas:stream_header(StateData), |
882 |
7138 |
send_xml(StateData, Header). |
883 |
|
|
884 |
|
send_trailer(StateData) -> |
885 |
3513 |
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 |
12932 |
receive |
897 |
|
{route, Acc} -> |
898 |
2927 |
reroute_one(StateData, Acc), |
899 |
2927 |
bounce_messages(StateData); |
900 |
|
_ -> |
901 |
5508 |
bounce_messages(StateData) |
902 |
4497 |
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 |
3131 |
{From, To, _El} = mongoose_acc:packet(Acc), |
908 |
3131 |
Acc2 = patch_acc_for_reroute(Acc, Sid), |
909 |
3131 |
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 |
88 |
OrderedBuffer = lists:reverse(Buffer), |
914 |
88 |
FilteredBuffer = mongoose_hooks:filter_unacknowledged_messages(HostType, Jid, OrderedBuffer), |
915 |
88 |
[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 |
16326 |
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 |
3320 |
NewFields = #{ip => mongoose_c2s_socket:get_ip(Socket), |
937 |
|
conn => mongoose_c2s_socket:get_conn_type(Socket)}, |
938 |
3320 |
Info2 = maps:merge(Info, NewFields), |
939 |
3320 |
ReplacedPids = ejabberd_sm:open_session(HostType, Sid, Jid, 0, Info2), |
940 |
3320 |
{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 |
3320 |
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 |
3131 |
case mongoose_acc:stanza_name(Acc) of |
949 |
|
<<"message">> -> |
950 |
132 |
Acc; |
951 |
|
_ -> %% IQs and presences are allowed to come to the same Sid only |
952 |
2999 |
case mongoose_acc:get(c2s, receiver_sid, undefined, Acc) of |
953 |
|
undefined -> |
954 |
2999 |
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 |
4212 |
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 |
3267 |
close_session(C2SData, Acc, Reason); |
967 |
|
do_close_session(C2SData, ?EXT_C2S_STATE(_), Acc, Reason) -> |
968 |
53 |
close_session(C2SData, Acc, Reason); |
969 |
|
do_close_session(_, _, Acc, _) -> |
970 |
1177 |
Acc. |
971 |
|
|
972 |
|
sm_unset_reason({shutdown, Reason}) -> |
973 |
3320 |
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 |
13848 |
El = mongoose_acc:element(Acc), |
983 |
13848 |
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 |
3553 |
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 |
3553 |
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 |
7235 |
ServerJid = jid:make_noprep(<<>>, LServer, <<>>), |
999 |
7235 |
ParamsAcc = #{from_jid => ServerJid, to_jid => Jid, element => El}, |
1000 |
7235 |
Acc1 = mongoose_acc:update_stanza(ParamsAcc, Acc0), |
1001 |
7235 |
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 |
3604 |
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 |
28230 |
Res = send_xml(StateData, El), |
1016 |
28230 |
Acc1 = mongoose_acc:set(c2s, send_result, Res, Acc), |
1017 |
28230 |
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 |
39041 |
send_xml(Data, [XmlElement]); |
1022 |
|
send_xml(#c2s_data{socket = Socket}, XmlElements) when is_list(XmlElements) -> |
1023 |
39041 |
[mongoose_metrics:update(global, [data, xmpp, sent, xml_stanza_size], exml:xml_size(El)) |
1024 |
39041 |
|| El <- XmlElements], |
1025 |
39041 |
mongoose_c2s_socket:send_xml(Socket, XmlElements). |
1026 |
|
|
1027 |
|
|
1028 |
|
state_timeout(#c2s_data{listener_opts = LOpts}) -> |
1029 |
3383 |
state_timeout(LOpts); |
1030 |
|
state_timeout(#{c2s_state_timeout := Timeout}) -> |
1031 |
14881 |
{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 |
3315 |
StateData#c2s_data{jid = jid:replace_resource_noprep(OldJid, NewResource)}. |
1038 |
|
|
1039 |
|
-spec new_stream_id() -> binary(). |
1040 |
|
new_stream_id() -> |
1041 |
7940 |
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 |
714 |
#{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 |
50782 |
#{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 |
4499 |
gen_statem:start_link(?MODULE, Params, ProcOpts). |
1072 |
|
|
1073 |
|
-spec stop(gen_statem:server_ref(), atom()) -> ok. |
1074 |
|
stop(Pid, Reason) -> |
1075 |
129 |
gen_statem:cast(Pid, {stop, Reason}). |
1076 |
|
|
1077 |
|
-spec exit(pid(), binary() | atom()) -> ok. |
1078 |
|
exit(Pid, Reason) -> |
1079 |
171 |
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 |
3222 |
gen_statem:cast(Pid, {async_with_state, Fun, Args}). |
1088 |
|
|
1089 |
|
-spec call(pid(), atom(), term()) -> term(). |
1090 |
|
call(Pid, EventTag, EventContent) -> |
1091 |
73 |
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 |
656 |
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 |
18 |
#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 |
7193 |
[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 |
395 |
mongoose_c2s_socket:is_channel_binding_supported(Socket); |
1108 |
|
filter_mechanism(#c2s_data{socket = Socket}, <<"SCRAM-SHA-", _N:3/binary, "-PLUS">>) -> |
1109 |
8122 |
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 |
15526 |
true. |
1114 |
|
|
1115 |
|
-spec get_host_type(data()) -> mongooseim:host_type(). |
1116 |
|
get_host_type(#c2s_data{host_type = HostType}) -> |
1117 |
16932 |
HostType. |
1118 |
|
|
1119 |
|
-spec get_lserver(data()) -> jid:lserver(). |
1120 |
|
get_lserver(#c2s_data{lserver = LServer}) -> |
1121 |
25016 |
LServer. |
1122 |
|
|
1123 |
|
-spec get_sid(data()) -> ejabberd_sm:sid(). |
1124 |
|
get_sid(#c2s_data{sid = Sid}) -> |
1125 |
34291 |
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 |
7330 |
Socket. |
1134 |
|
|
1135 |
|
-spec get_jid(data()) -> jid:jid() | undefined. |
1136 |
|
get_jid(#c2s_data{jid = Jid}) -> |
1137 |
49540 |
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 |
3575 |
Info. |
1150 |
|
|
1151 |
|
-spec set_info(data(), info()) -> data(). |
1152 |
|
set_info(StateData, Info) -> |
1153 |
245 |
StateData#c2s_data{info = Info}. |
1154 |
|
|
1155 |
|
-spec get_lang(data()) -> ejabberd:lang(). |
1156 |
|
get_lang(#c2s_data{lang = Lang}) -> |
1157 |
7142 |
Lang. |
1158 |
|
|
1159 |
|
-spec get_stream_id(data()) -> binary(). |
1160 |
|
get_stream_id(#c2s_data{streamid = StreamId}) -> |
1161 |
7138 |
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 |
62716 |
case maps:get(ModName, ModStates, undefined) of |
1166 |
48011 |
undefined -> {error, not_found}; |
1167 |
14705 |
ModState -> {ok, ModState} |
1168 |
|
end. |
1169 |
|
|
1170 |
|
-spec get_listener_opts(data()) -> listener_opts(). |
1171 |
|
get_listener_opts(#c2s_data{listener_opts = ListenerOpts}) -> |
1172 |
20884 |
ListenerOpts. |
1173 |
|
|
1174 |
|
-spec merge_mod_state(data(), #{module() => term()}) -> data(). |
1175 |
|
merge_mod_state(StateData = #c2s_data{state_mod = ModStates}, MoreModStates) -> |
1176 |
9189 |
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 |
|
}. |