./ct_report/coverage/mod_sasl2.COVER.html

1 %% Design Notes
2 %%
3 %% This module has three entry points for the statem: `authenticate', `response', and `abort'.
4 %% All three of them will generate one `OriginalStateData' that will remain unchanged for the whole
5 %% statem event, and a copy of this value will be stored into the `SaslAcc::mongoose_acc:t()', which
6 %% hook handlers can modify, and at the end of the processing, they can be compared for changes.
7 %%
8 %% This module triggers two hooks for before and after the full sasl2 mechanism is executed.
9 %% Handlers to these hooks can read the original `OriginalStateData', as well as the values
10 %% accumulated on the SaslAcc. If a value wants to be updated, it should be careful to fetch the
11 %% most recent from the accumulator, modify that one, and reinsert, otherwise it can override
12 %% updates made by previous handlers.
13 -module(mod_sasl2).
14 -xep([{xep, 388}, {version, "0.4.0"}, {status, partial}]).
15
16 -include("jlib.hrl").
17 -include("mongoose_logger.hrl").
18
19 -define(BIND_RETRIES, 3).
20 -define(XMLNS_SASL, {<<"xmlns">>, ?NS_SASL}).
21 -define(XMLNS_SASL_2, {<<"xmlns">>, ?NS_SASL_2}).
22
23 -behaviour(gen_mod).
24 -behaviour(gen_statem).
25
26 %% gen_mod callbacks
27 -export([start/2, stop/1, hooks/1, supported_features/0]).
28
29 %% gen_statem callbacks
30 -export([callback_mode/0, init/1, handle_event/4, terminate/3]).
31
32 %% hooks handlers
33 -export([c2s_stream_features/3, user_send_xmlel/3]).
34
35 %% helpers
36 -export([get_inline_request/2, get_inline_request/3, put_inline_request/3,
37 append_inline_response/3, update_inline_request/4,
38 get_state_data/1, set_state_data/2,
39 request_block_future_stream_features/1]).
40
41 -type maybe_binary() :: undefined | binary().
42 -type status() :: pending | success | failure.
43 -type inline_request() :: #{request := exml:element(),
44 response := undefined | exml:element(),
45 status := status()}.
46 -type mod_state() :: #{authenticated := boolean(),
47 id := not_provided | uuid:uuid(),
48 software := not_provided | binary(),
49 device := not_provided | binary()}.
50 -type c2s_state_data() :: #{c2s_state := mongoose_c2s:state(),
51 c2s_data := mongoose_c2s:data(),
52 _ := _}.
53
54 -export_type([c2s_state_data/0, inline_request/0]).
55
56 %% gen_mod
57 -spec start(mongooseim:host_type(), gen_mod:module_opts()) -> ok.
58 start(_HostType, _Opts) ->
59 2 ok.
60
61 -spec stop(mongooseim:host_type()) -> ok.
62 stop(_HostType) ->
63 2 ok.
64
65 -spec hooks(mongooseim:host_type()) -> gen_hook:hook_list().
66 hooks(HostType) ->
67 4 [{c2s_stream_features, HostType, fun ?MODULE:c2s_stream_features/3, #{}, 50}
68 | c2s_hooks(HostType)].
69
70 -spec c2s_hooks(mongooseim:host_type()) -> gen_hook:hook_list(mongoose_c2s_hooks:fn()).
71 c2s_hooks(HostType) ->
72 4 [{user_send_xmlel, HostType, fun ?MODULE:user_send_xmlel/3, #{}, 50}].
73
74 -spec supported_features() -> [atom()].
75 supported_features() ->
76
:-(
[dynamic_domains].
77
78 %% gen_statem
79 callback_mode() ->
80 26 handle_event_function.
81
82 -spec init(term()) -> gen_statem:init_result(mongoose_c2s:state(), mongoose_c2s:data()).
83 init(_) ->
84
:-(
{stop, this_should_have_never_been_called}.
85
86 -spec handle_event(gen_statem:event_type(), term(), mongoose_c2s:state(), mongoose_c2s:data()) ->
87 mongoose_c2s:fsm_res().
88 handle_event(internal, #xmlel{name = <<"authenticate">>} = El,
89 ?EXT_C2S_STATE({wait_for_feature_before_auth, SaslAcc, Retries}) = C2SState, C2SData) ->
90 %% We don't verify the namespace here because to here we just jumped from user_send_xmlel
91 26 handle_auth_start(C2SData, C2SState, El, SaslAcc, Retries);
92 handle_event(internal, #xmlel{name = <<"response">>} = El,
93 ?EXT_C2S_STATE({wait_for_sasl_response, SaslAcc, Retries}) = C2SState, C2SData) ->
94 2 case exml_query:attr(El, <<"xmlns">>) of
95 ?NS_SASL_2 ->
96 1 handle_auth_response(C2SData, C2SState, El, SaslAcc, Retries);
97 _ ->
98 1 mongoose_c2s:c2s_stream_error(C2SData, mongoose_xmpp_errors:invalid_namespace())
99 end;
100 handle_event(internal, #xmlel{name = <<"abort">>} = El,
101 ?EXT_C2S_STATE({_, SaslAcc, Retries}) = C2SState, C2SData) ->
102 2 case exml_query:attr(El, <<"xmlns">>) of
103 ?NS_SASL_2 ->
104 1 handle_sasl_abort(C2SData, C2SState, El, SaslAcc, Retries);
105 _ ->
106 1 mongoose_c2s:c2s_stream_error(C2SData, mongoose_xmpp_errors:invalid_namespace())
107 end;
108
109 handle_event(EventType, EventContent, C2SState, C2SData) ->
110 8 mongoose_c2s:handle_event(EventType, EventContent, C2SState, C2SData).
111
112 -spec terminate(term(), mongoose_c2s:state(), mongoose_c2s:data()) -> term().
113 terminate(Reason, ?EXT_C2S_STATE(C2SState), C2SData) ->
114 6 terminate(Reason, C2SState, C2SData);
115 terminate(Reason, C2SState, C2SData) ->
116 6 ?LOG_DEBUG(#{what => sasl2_statem_terminate, reason => Reason,
117 6 c2s_state => C2SState, c2s_data => C2SData}),
118 6 mongoose_c2s:terminate({shutdown, ?MODULE}, C2SState, C2SData).
119
120 %% Hook handlers
121 -spec c2s_stream_features(Acc, map(), gen_hook:extra()) -> {ok, Acc} when
122 Acc :: [exml:element()].
123 c2s_stream_features(Acc, #{c2s_data := C2SData}, _) ->
124 87 case is_ssl_connection(C2SData)
125 48 andalso lists:keyfind(<<"mechanisms">>, #xmlel.name, Acc) of
126 false ->
127 58 {ok, Acc};
128 #xmlel{attrs = [?XMLNS_SASL], children = Mechanisms} ->
129 29 Sasl2Feature = feature(C2SData, Mechanisms),
130 29 {ok, lists:keystore(feature_name(), #xmlel.name, Acc, Sasl2Feature)}
131 end.
132
133 -spec user_send_xmlel(mongoose_acc:t(), mongoose_c2s_hooks:params(), gen_hook:extra()) ->
134 mongoose_c2s_hooks:result().
135 user_send_xmlel(Acc, Params, _Extra) ->
136 36 El = mongoose_acc:element(Acc),
137 36 case exml_query:attr(El, <<"xmlns">>, false) of
138 ?NS_SASL_2 ->
139 27 user_send_sasl2_element(Acc, Params, El);
140 _ ->
141 9 {ok, Acc}
142 end.
143
144 -spec user_send_sasl2_element(mongoose_acc:t(), mongoose_c2s_hooks:params(), exml:element()) ->
145 mongoose_c2s_hooks:result().
146 user_send_sasl2_element(Acc, #{c2s_data := C2SData, c2s_state := C2SState}, El) ->
147 27 case is_not_sasl2_authenticated_already(C2SData) andalso is_ssl_connection(C2SData) of
148 true ->
149 %% We need to take control of the state machine to ensure no stanza
150 %% out of the established protocol is processed
151 26 Actions = [{push_callback_module, ?MODULE}, {next_event, internal, El}],
152 26 ToAcc = [{c2s_state, ?EXT_C2S_STATE(C2SState)}, {actions, Actions}],
153 26 {stop, mongoose_c2s_acc:to_acc_many(Acc, ToAcc)};
154 false ->
155 1 Lang = mongoose_c2s:get_lang(C2SData),
156 1 Stanza = mongoose_xmpp_errors:policy_violation(Lang, <<"SALS2 violation">>),
157 1 mongoose_c2s:c2s_stream_error(C2SData, Stanza),
158 1 {stop, mongoose_c2s_acc:to_acc(Acc, hard_stop, sasl2_violation)}
159 end.
160
161 -spec is_not_sasl2_authenticated_already(mongoose_c2s:data()) -> boolean().
162 is_not_sasl2_authenticated_already(C2SData) ->
163 27 case mongoose_c2s:get_mod_state(C2SData, ?MODULE) of
164 1 {ok, #{authenticated := true}} -> false;
165 26 _ -> true
166 end.
167
168 -spec is_ssl_connection(mongoose_c2s:data()) -> boolean().
169 is_ssl_connection(C2SData) ->
170 113 mongoose_c2s_socket:is_ssl(mongoose_c2s:get_socket(C2SData)).
171
172 -spec get_mod_state(mongoose_acc:t()) -> {error, not_found} | mod_state().
173 get_mod_state(SaslAcc) ->
174 20 case mongoose_acc:get_statem_acc(SaslAcc) of
175 20 #{state_mod := #{?MODULE := ModState}} -> ModState;
176
:-(
_ -> {error, not_found}
177 end.
178
179 -spec handle_auth_start(
180 mongoose_c2s:data(), mongoose_c2s:state(), exml:element(), mongoose_acc:t(), mongoose_c2s:retries()) ->
181 mongoose_c2s:fsm_res().
182 handle_auth_start(C2SData, C2SState, El, SaslAcc, Retries) ->
183 26 case init_mod_state(exml_query:subelement(El, <<"user-agent">>, not_provided)) of
184 invalid_agent ->
185 2 mongoose_c2s:c2s_stream_error(C2SData, mongoose_xmpp_errors:policy_violation());
186 ModState ->
187 24 HostType = mongoose_c2s:get_host_type(C2SData),
188 24 Mech = get_selected_mech(El),
189 24 ClientIn = get_initial_response(El),
190 24 OriginalStateData = #{c2s_state => C2SState, c2s_data => C2SData},
191 24 SaslAcc1 = mongoose_c2s_acc:to_acc(SaslAcc, state_mod, {?MODULE, ModState}),
192 24 SaslAcc2 = mongoose_hooks:sasl2_start(HostType, SaslAcc1, El),
193 24 SaslResult = mongoose_c2s_sasl:start(C2SData, SaslAcc2, Mech, ClientIn),
194 24 handle_sasl_step(SaslResult, OriginalStateData, Retries)
195 end.
196
197 -spec handle_auth_response(
198 mongoose_c2s:data(), mongoose_c2s:state(), exml:element(), mongoose_acc:t(), mongoose_c2s:retries()) ->
199 mongoose_c2s:fsm_res().
200 handle_auth_response(C2SData, C2SState, El, SaslAcc, Retries) ->
201 1 ClientIn = base64:mime_decode(exml_query:cdata(El)),
202 1 OriginalStateData = #{c2s_state => C2SState, c2s_data => C2SData},
203 1 SaslResult = mongoose_c2s_sasl:continue(C2SData, SaslAcc, ClientIn),
204 1 handle_sasl_step(SaslResult, OriginalStateData, Retries).
205
206 -spec handle_sasl_abort(
207 mongoose_c2s:data(), mongoose_c2s:state(), exml:element(), mongoose_acc:t(), mongoose_c2s:retries()) ->
208 mongoose_c2s:fsm_res().
209 handle_sasl_abort(C2SData, C2SState, _El, SaslAcc, Retries) ->
210 1 Jid = mongoose_c2s:get_jid(C2SData),
211 1 Error = #{server_out => <<"aborted">>, maybe_username => Jid},
212 1 OriginalStateData = #{c2s_state => C2SState, c2s_data => C2SData},
213 1 handle_sasl_failure(SaslAcc, Error, OriginalStateData, Retries).
214
215 -spec handle_sasl_step(mongoose_c2s_sasl:result(), c2s_state_data(), mongoose_c2s:retries()) ->
216 mongoose_c2s:fsm_res().
217 handle_sasl_step({success, NewSaslAcc, Result}, OriginalStateData, _Retries) ->
218 20 handle_sasl_success(NewSaslAcc, Result, OriginalStateData);
219 handle_sasl_step({continue, NewSaslAcc, Result}, OriginalStateData, Retries) ->
220 4 handle_sasl_continue(NewSaslAcc, Result, OriginalStateData, Retries);
221 handle_sasl_step({failure, NewSaslAcc, Result}, OriginalStateData, Retries) ->
222 1 handle_sasl_failure(NewSaslAcc, Result, OriginalStateData, Retries);
223 handle_sasl_step({error, NewSaslAcc, #{type := Type}}, OriginalStateData, Retries) ->
224
:-(
handle_sasl_failure(NewSaslAcc, #{server_out => atom_to_binary(Type),
225 maybe_username => undefined}, OriginalStateData, Retries).
226
227 -spec handle_sasl_success(mongoose_acc:t(), mongoose_c2s_sasl:success(), c2s_state_data()) ->
228 mongoose_c2s:fsm_res().
229 handle_sasl_success(SaslAcc,
230 #{server_out := MaybeServerOut, jid := Jid, auth_module := AuthMod},
231 #{c2s_data := C2SData} = OriginalStateData) ->
232 20 C2SData1 = build_final_c2s_data(C2SData, Jid, AuthMod),
233 20 OriginalStateData1 = OriginalStateData#{c2s_data := C2SData1},
234 20 ?LOG_INFO(#{what => auth_success, text => <<"Accepted SASL authentication">>,
235 20 user => jid:to_binary(Jid), c2s_state => C2SData1}),
236 20 HostType = mongoose_c2s:get_host_type(C2SData1),
237 20 SaslAcc1 = set_state_data(SaslAcc, OriginalStateData1),
238 20 SaslAcc2 = mongoose_hooks:sasl2_success(HostType, SaslAcc1, OriginalStateData1),
239 20 process_sasl2_success(SaslAcc2, OriginalStateData1, MaybeServerOut).
240
241 -spec handle_sasl_continue(
242 mongoose_acc:t(), mongoose_c2s_sasl:continue(), c2s_state_data(), mongoose_c2s:retries()) ->
243 mongoose_c2s:fsm_res().
244 handle_sasl_continue(SaslAcc,
245 #{server_out := ServerOut},
246 #{c2s_data := C2SData, c2s_state := C2SState},
247 Retries) ->
248 4 El = challenge_stanza(ServerOut),
249 4 ToAcc = [{socket_send, El},
250 {c2s_state, ?EXT_C2S_STATE({wait_for_sasl_response, SaslAcc, Retries})}],
251 4 SaslAcc1 = mongoose_c2s_acc:to_acc_many(SaslAcc, ToAcc),
252 4 mongoose_c2s:handle_state_after_packet(C2SData, C2SState, SaslAcc1).
253
254 -spec handle_sasl_failure(
255 mongoose_acc:t(), mongoose_c2s_sasl:failure(), c2s_state_data(), mongoose_c2s:retries()) ->
256 mongoose_c2s:fsm_res().
257 handle_sasl_failure(SaslAcc,
258 #{server_out := ServerOut, maybe_username := Username},
259 #{c2s_data := C2SData, c2s_state := C2SState},
260 Retries) ->
261 2 LServer = mongoose_c2s:get_lserver(C2SData),
262 2 ?LOG_INFO(#{what => auth_failed, text => <<"Failed SASL authentication">>,
263 2 username => Username, lserver => LServer, c2s_state => C2SData}),
264 2 El = failure_stanza(ServerOut),
265 2 case mongoose_c2s:maybe_retry_state(C2SState) of
266 {stop, Reason} ->
267
:-(
{stop, Reason, C2SData};
268 C2SState1 ->
269 2 ToAcc = [{socket_send, El},
270 {c2s_state, ?EXT_C2S_STATE({wait_for_feature_before_auth, SaslAcc, Retries})}],
271 2 SaslAcc2 = mongoose_c2s_acc:to_acc_many(SaslAcc, ToAcc),
272 2 mongoose_c2s:handle_state_after_packet(C2SData, C2SState1, SaslAcc2)
273 end.
274
275 %% Append to the c2s_data both the new jid and the auth module.
276 %% Note that further inline requests can later on append a new jid if a resource is negotiated.
277 -spec build_final_c2s_data(mongoose_c2s:data(), jid:jid(), module()) -> mongoose_c2s:data().
278 build_final_c2s_data(C2SData, Jid, AuthMod) ->
279 20 C2SData1 = mongoose_c2s:set_jid(C2SData, Jid),
280 20 mongoose_c2s:set_auth_module(C2SData1, AuthMod).
281
282 -spec process_sasl2_success(mongoose_acc:t(), c2s_state_data(), maybe_binary()) ->
283 mongoose_c2s:fsm_res().
284 process_sasl2_success(SaslAcc, OriginalStateData, MaybeServerOut) ->
285 20 #{c2s_data := C2SData, c2s_state := C2SState} = get_state_data(SaslAcc),
286 20 SuccessStanza = success_stanza(SaslAcc, C2SData, MaybeServerOut),
287 20 ToAcc = build_to_c2s_acc(SaslAcc, C2SData, OriginalStateData, SuccessStanza),
288 20 SaslAcc1 = mongoose_c2s_acc:to_acc_many(SaslAcc, ToAcc),
289 20 mongoose_c2s:handle_state_after_packet(C2SData, C2SState, SaslAcc1).
290
291 %% After auth and inline requests we:
292 %% - return control to mongoose_c2s (pop_callback_module),
293 %% - ensure the answer to the sasl2 request is sent in the socket first,
294 %% - then decide depending on whether an inline request has taken control of the c2s_state if
295 %% - do nothing if control was taken
296 %% - put the statem in wait_for_feature_after_auth
297 -spec build_to_c2s_acc(mongoose_acc:t(), mongoose_c2s:data(), c2s_state_data(), exml:element()) ->
298 mongoose_c2s_acc:pairs().
299 build_to_c2s_acc(SaslAcc, C2SData, OriginalStateData, SuccessStanza) ->
300 20 ModState = get_mod_state(SaslAcc),
301 20 MaybeSocketSendStreamFeatures = maybe_flush_stream_features(SaslAcc, C2SData),
302 20 case is_new_c2s_state_requested(SaslAcc, OriginalStateData) of
303 false ->
304 %% Unless specified by an inline feature, sasl2 would normally put the statem just before bind
305 8 [{socket_send_first, SuccessStanza},
306 {c2s_state, {wait_for_feature_after_auth, ?BIND_RETRIES}},
307 {actions, [pop_callback_module, mongoose_c2s:state_timeout(C2SData)]},
308 {state_mod, {?MODULE, ModState#{authenticated := true}}}
309 | MaybeSocketSendStreamFeatures];
310 true ->
311 12 [{socket_send_first, SuccessStanza},
312 {actions, [pop_callback_module]},
313 {state_mod, {?MODULE, ModState#{authenticated := true}}}
314 | MaybeSocketSendStreamFeatures]
315 end.
316
317 -spec request_block_future_stream_features(mongoose_acc:t()) -> mongoose_acc:t().
318 request_block_future_stream_features(SaslAcc) ->
319 2 mongoose_acc:set(?MODULE, stream_features, false, SaslAcc).
320
321 -spec maybe_flush_stream_features(mongoose_acc:t(), mongoose_c2s:data()) ->
322 [{flush, mongoose_acc:t()}].
323 maybe_flush_stream_features(SaslAcc, C2SData) ->
324 20 case mongoose_acc:get(?MODULE, stream_features, true, SaslAcc) of
325 true ->
326 18 StreamFeaturesStanza = mongoose_c2s_stanzas:stream_features_after_auth(C2SData),
327 18 Jid = mongoose_c2s:get_jid(C2SData),
328 18 LServer = mongoose_c2s:get_lserver(C2SData),
329 18 HostType = mongoose_c2s:get_host_type(C2SData),
330 18 AccParams = #{lserver => LServer, host_type => HostType,
331 from_jid => jid:make_noprep(<<>>, LServer, <<>>), to_jid => Jid,
332 element => StreamFeaturesStanza},
333 18 Acc = mongoose_acc:strip(AccParams, SaslAcc),
334 18 [{flush, Acc}];
335 false ->
336 2 []
337 end.
338
339 -spec is_new_c2s_state_requested(mongoose_acc:t(), c2s_state_data()) -> boolean().
340 is_new_c2s_state_requested(SaslAcc, #{c2s_state := OldState}) ->
341 20 #{c2s_state := NewState} = mod_sasl2:get_state_data(SaslAcc),
342 20 OldState =/= NewState.
343
344 -spec success_stanza(mongoose_acc:t(), mongoose_c2s:data(), maybe_binary()) -> exml:element().
345 success_stanza(SaslAcc, C2SData, MaybeCData) ->
346 20 Jid = mongoose_c2s:get_jid(C2SData),
347 20 Inlines = get_acc_sasl2_state(SaslAcc),
348 20 InlineAnswers = get_inline_responses(Inlines),
349 20 case MaybeCData of
350 undefined ->
351 19 AuthorizationId = success_subelement(<<"authorization-identifier">>, jid:to_binary(Jid)),
352 19 sasl2_ns_stanza(<<"success">>, [AuthorizationId | InlineAnswers]);
353 CData ->
354 1 AdditionalData = success_subelement(<<"additional-data">>, base64:encode(CData)),
355 1 AuthorizationId = success_subelement(<<"authorization-identifier">>, jid:to_binary(Jid)),
356 1 sasl2_ns_stanza(<<"success">>, [AdditionalData, AuthorizationId | InlineAnswers])
357 end.
358
359 -spec get_inline_responses([inline_request()]) -> [exml:element()].
360 get_inline_responses(Inlines) ->
361 20 [ Response || {Module, #{status := Status, response := Response}} <- Inlines,
362 18 ?MODULE =/= Module,
363 18 pending =/= Status,
364 17 undefined =/= Response ].
365
366 -spec challenge_stanza(binary()) -> exml:element().
367 challenge_stanza(ServerOut) ->
368 4 Challenge = #xmlcdata{content = base64:encode(ServerOut)},
369 4 sasl2_ns_stanza(<<"challenge">>, [Challenge]).
370
371 -spec failure_stanza(binary()) -> exml:element().
372 failure_stanza(Reason) ->
373 2 SaslErrorCode = #xmlel{name = Reason, attrs = [?XMLNS_SASL]},
374 2 sasl2_ns_stanza(<<"failure">>, [SaslErrorCode]).
375
376 -spec sasl2_ns_stanza(binary(), [exml:element() | exml:cdata()]) -> exml:element().
377 sasl2_ns_stanza(Name, Children) ->
378 26 #xmlel{name = Name, attrs = [?XMLNS_SASL_2], children = Children}.
379
380 -spec success_subelement(binary(), binary()) -> exml:element().
381 success_subelement(Name, AuthId) ->
382 21 #xmlel{name = Name, children = [#xmlcdata{content = AuthId}]}.
383
384 %% internal
385 -spec get_selected_mech(exml:element()) -> binary().
386 get_selected_mech(El) ->
387 24 exml_query:attr(El, <<"mechanism">>, <<>>).
388
389 -spec get_initial_response(exml:element()) -> binary().
390 get_initial_response(El) ->
391 24 base64:decode(exml_query:path(El, [{element, <<"initial-response">>}, cdata], <<>>)).
392
393 -spec init_mod_state(not_provided | exml:element()) -> invalid_agent | mod_state().
394 init_mod_state(not_provided) ->
395 18 #{authenticated => false, id => not_provided, software => not_provided, device => not_provided};
396 init_mod_state(El) ->
397 8 MaybeId = exml_query:attr(El, <<"id">>, not_provided),
398 8 case if_provided_then_is_not_invalid_uuid_v4(MaybeId) of
399 invalid_agent ->
400 2 invalid_agent;
401 Value ->
402 6 Software = exml_query:path(El, [{element, <<"software">>}, cdata], not_provided),
403 6 Device = exml_query:path(El, [{element, <<"device">>}, cdata], not_provided),
404 6 #{authenticated => false, id => Value, software => Software, device => Device}
405 end.
406
407 -spec if_provided_then_is_not_invalid_uuid_v4(not_provided | binary()) ->
408 not_provided | invalid_agent | uuid:uuid().
409 if_provided_then_is_not_invalid_uuid_v4(not_provided) ->
410 1 not_provided;
411 if_provided_then_is_not_invalid_uuid_v4(Binary) ->
412 7 try
413 7 Uuid = uuid:string_to_uuid(Binary),
414 6 true = uuid:is_v4(Uuid),
415 5 Uuid
416 catch
417 1 exit:badarg:_ -> invalid_agent;
418 1 error:{badmatch, false}:_ -> invalid_agent
419 end.
420
421 -spec feature(mongoose_c2s:data(), [exml:element()]) -> exml:element().
422 feature(C2SData, Mechanisms) ->
423 29 InlineFeatures = mongoose_hooks:sasl2_stream_features(C2SData, []),
424 29 InlineElem = inlines(InlineFeatures),
425 29 #xmlel{name = feature_name(),
426 attrs = [?XMLNS_SASL_2],
427 children = [InlineElem | Mechanisms]}.
428
429 -spec inlines([exml:element()]) -> exml:element().
430 inlines(InlineFeatures) ->
431 29 #xmlel{name = <<"inline">>, children = InlineFeatures}.
432
433 -spec feature_name() -> binary().
434 feature_name() ->
435 58 <<"authentication">>.
436
437 -spec get_acc_sasl2_state(mongoose_acc:t()) -> [{module(), inline_request()}].
438 get_acc_sasl2_state(SaslAcc) ->
439 20 mongoose_acc:get(?MODULE, SaslAcc).
440
441 -spec get_inline_request(mongoose_acc:t(), module()) -> inline_request().
442 get_inline_request(SaslAcc, ModuleRequest) ->
443 30 mongoose_acc:get(?MODULE, ModuleRequest, SaslAcc).
444
445 -spec get_inline_request(mongoose_acc:t(), module(), Default) -> Default | inline_request().
446 get_inline_request(SaslAcc, ModuleRequest, Default) ->
447 38 mongoose_acc:get(?MODULE, ModuleRequest, Default, SaslAcc).
448
449 -spec put_inline_request(mongoose_acc:t(), module(), exml:element()) -> mongoose_acc:t().
450 put_inline_request(SaslAcc, ModuleRequest, XmlRequest) ->
451 18 Request = #{request => XmlRequest, response => undefined, status => pending},
452 18 mongoose_acc:set(?MODULE, ModuleRequest, Request, SaslAcc).
453
454 -spec append_inline_response(mongoose_acc:t(), module(), exml:element()) -> mongoose_acc:t().
455 append_inline_response(SaslAcc, ModuleRequest, XmlResponse) ->
456 2 case mongoose_acc:get(?MODULE, ModuleRequest, undefined, SaslAcc) of
457 undefined ->
458
:-(
SaslAcc;
459 Request ->
460 2 Request1 = Request#{response := XmlResponse},
461 2 mongoose_acc:set(?MODULE, ModuleRequest, Request1, SaslAcc)
462 end.
463
464 -spec update_inline_request(mongoose_acc:t(), module(), exml:element(), status()) -> mongoose_acc:t().
465 update_inline_request(SaslAcc, ModuleRequest, XmlResponse, Status) ->
466 17 case mongoose_acc:get(?MODULE, ModuleRequest, undefined, SaslAcc) of
467 undefined ->
468
:-(
SaslAcc;
469 Request ->
470 17 Request1 = Request#{response := XmlResponse, status := Status},
471 17 mongoose_acc:set(?MODULE, ModuleRequest, Request1, SaslAcc)
472 end.
473
474 %% Here we extract these values after all modifications by the inline requests
475 -spec get_state_data(mongoose_acc:t()) -> c2s_state_data().
476 get_state_data(SaslAcc) ->
477 68 mongoose_acc:get(?MODULE, c2s_state_data, SaslAcc).
478
479 -spec set_state_data(mongoose_acc:t(), c2s_state_data()) -> mongoose_acc:t().
480 set_state_data(SaslAcc, OriginalStateData) ->
481 43 mongoose_acc:set(?MODULE, c2s_state_data, OriginalStateData, SaslAcc).
Line Hits Source