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 |
22 |
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 |
22 |
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 |
:-( |
case exml_query:attr(El, <<"xmlns">>) of |
95 |
|
?NS_SASL_2 -> |
96 |
:-( |
handle_auth_response(C2SData, C2SState, El, SaslAcc, Retries); |
97 |
|
_ -> |
98 |
:-( |
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 |
:-( |
case exml_query:attr(El, <<"xmlns">>) of |
103 |
|
?NS_SASL_2 -> |
104 |
:-( |
handle_sasl_abort(C2SData, C2SState, El, SaslAcc, Retries); |
105 |
|
_ -> |
106 |
:-( |
mongoose_c2s:c2s_stream_error(C2SData, mongoose_xmpp_errors:invalid_namespace()) |
107 |
|
end; |
108 |
|
|
109 |
|
handle_event(EventType, EventContent, C2SState, C2SData) -> |
110 |
2 |
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 |
3 |
terminate(Reason, C2SState, C2SData); |
115 |
|
terminate(Reason, C2SState, C2SData) -> |
116 |
3 |
?LOG_DEBUG(#{what => sasl2_statem_terminate, reason => Reason, |
117 |
3 |
c2s_state => C2SState, c2s_data => C2SData}), |
118 |
3 |
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 |
82 |
case is_ssl_connection(C2SData) |
125 |
43 |
andalso lists:keyfind(<<"mechanisms">>, #xmlel.name, Acc) of |
126 |
|
false -> |
127 |
57 |
{ok, Acc}; |
128 |
|
#xmlel{attrs = [?XMLNS_SASL], children = Mechanisms} -> |
129 |
25 |
Sasl2Feature = feature(C2SData, Mechanisms), |
130 |
25 |
{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 |
32 |
El = mongoose_acc:element(Acc), |
137 |
32 |
case exml_query:attr(El, <<"xmlns">>, false) of |
138 |
|
?NS_SASL_2 -> |
139 |
23 |
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 |
23 |
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 |
22 |
Actions = [{push_callback_module, ?MODULE}, {next_event, internal, El}], |
152 |
22 |
ToAcc = [{c2s_state, ?EXT_C2S_STATE(C2SState)}, {actions, Actions}], |
153 |
22 |
{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 |
23 |
case mongoose_c2s:get_mod_state(C2SData, ?MODULE) of |
164 |
1 |
{ok, #{authenticated := true}} -> false; |
165 |
22 |
_ -> true |
166 |
|
end. |
167 |
|
|
168 |
|
-spec is_ssl_connection(mongoose_c2s:data()) -> boolean(). |
169 |
|
is_ssl_connection(C2SData) -> |
170 |
104 |
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 |
19 |
case mongoose_acc:get_statem_acc(SaslAcc) of |
175 |
19 |
#{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 |
22 |
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 |
20 |
HostType = mongoose_c2s:get_host_type(C2SData), |
188 |
20 |
Mech = get_selected_mech(El), |
189 |
20 |
ClientIn = get_initial_response(El), |
190 |
20 |
OriginalStateData = #{c2s_state => C2SState, c2s_data => C2SData}, |
191 |
20 |
SaslAcc1 = mongoose_c2s_acc:to_acc(SaslAcc, state_mod, {?MODULE, ModState}), |
192 |
20 |
SaslAcc2 = mongoose_hooks:sasl2_start(HostType, SaslAcc1, El), |
193 |
20 |
SaslResult = mongoose_c2s_sasl:start(C2SData, SaslAcc2, Mech, ClientIn), |
194 |
20 |
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 |
:-( |
ClientIn = base64:mime_decode(exml_query:cdata(El)), |
202 |
:-( |
OriginalStateData = #{c2s_state => C2SState, c2s_data => C2SData}, |
203 |
:-( |
SaslResult = mongoose_c2s_sasl:continue(C2SData, SaslAcc, ClientIn), |
204 |
:-( |
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 |
:-( |
Jid = mongoose_c2s:get_jid(C2SData), |
211 |
:-( |
Error = #{server_out => <<"aborted">>, maybe_username => Jid}, |
212 |
:-( |
OriginalStateData = #{c2s_state => C2SState, c2s_data => C2SData}, |
213 |
:-( |
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 |
19 |
handle_sasl_success(NewSaslAcc, Result, OriginalStateData); |
219 |
|
handle_sasl_step({continue, NewSaslAcc, Result}, OriginalStateData, Retries) -> |
220 |
:-( |
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 |
19 |
C2SData1 = build_final_c2s_data(C2SData, Jid, AuthMod), |
233 |
19 |
OriginalStateData1 = OriginalStateData#{c2s_data := C2SData1}, |
234 |
19 |
?LOG_INFO(#{what => auth_success, text => <<"Accepted SASL authentication">>, |
235 |
19 |
user => jid:to_binary(Jid), c2s_state => C2SData1}), |
236 |
19 |
HostType = mongoose_c2s:get_host_type(C2SData1), |
237 |
19 |
SaslAcc1 = set_state_data(SaslAcc, OriginalStateData1), |
238 |
19 |
SaslAcc2 = mongoose_hooks:sasl2_success(HostType, SaslAcc1, OriginalStateData1), |
239 |
19 |
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 |
:-( |
El = challenge_stanza(ServerOut), |
249 |
:-( |
ToAcc = [{socket_send, El}, |
250 |
|
{c2s_state, ?EXT_C2S_STATE({wait_for_sasl_response, SaslAcc, Retries})}], |
251 |
:-( |
SaslAcc1 = mongoose_c2s_acc:to_acc_many(SaslAcc, ToAcc), |
252 |
:-( |
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 |
1 |
LServer = mongoose_c2s:get_lserver(C2SData), |
262 |
1 |
?LOG_INFO(#{what => auth_failed, text => <<"Failed SASL authentication">>, |
263 |
1 |
username => Username, lserver => LServer, c2s_state => C2SData}), |
264 |
1 |
El = failure_stanza(ServerOut), |
265 |
1 |
case mongoose_c2s:maybe_retry_state(C2SState) of |
266 |
|
{stop, Reason} -> |
267 |
:-( |
{stop, Reason, C2SData}; |
268 |
|
C2SState1 -> |
269 |
1 |
ToAcc = [{socket_send, El}, |
270 |
|
{c2s_state, ?EXT_C2S_STATE({wait_for_feature_before_auth, SaslAcc, Retries})}], |
271 |
1 |
SaslAcc2 = mongoose_c2s_acc:to_acc_many(SaslAcc, ToAcc), |
272 |
1 |
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 |
19 |
C2SData1 = mongoose_c2s:set_jid(C2SData, Jid), |
280 |
19 |
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 |
19 |
#{c2s_data := C2SData, c2s_state := C2SState} = get_state_data(SaslAcc), |
286 |
19 |
SuccessStanza = success_stanza(SaslAcc, C2SData, MaybeServerOut), |
287 |
19 |
ToAcc = build_to_c2s_acc(SaslAcc, C2SData, OriginalStateData, SuccessStanza), |
288 |
19 |
SaslAcc1 = mongoose_c2s_acc:to_acc_many(SaslAcc, ToAcc), |
289 |
19 |
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 |
19 |
ModState = get_mod_state(SaslAcc), |
301 |
19 |
MaybeSocketSendStreamFeatures = maybe_flush_stream_features(SaslAcc, C2SData), |
302 |
19 |
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 |
7 |
[{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 |
19 |
case mongoose_acc:get(?MODULE, stream_features, true, SaslAcc) of |
325 |
|
true -> |
326 |
17 |
StreamFeaturesStanza = mongoose_c2s_stanzas:stream_features_after_auth(C2SData), |
327 |
17 |
Jid = mongoose_c2s:get_jid(C2SData), |
328 |
17 |
LServer = mongoose_c2s:get_lserver(C2SData), |
329 |
17 |
HostType = mongoose_c2s:get_host_type(C2SData), |
330 |
17 |
AccParams = #{lserver => LServer, host_type => HostType, |
331 |
|
from_jid => jid:make_noprep(<<>>, LServer, <<>>), to_jid => Jid, |
332 |
|
element => StreamFeaturesStanza}, |
333 |
17 |
Acc = mongoose_acc:strip(AccParams, SaslAcc), |
334 |
17 |
[{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 |
19 |
#{c2s_state := NewState} = mod_sasl2:get_state_data(SaslAcc), |
342 |
19 |
OldState =/= NewState. |
343 |
|
|
344 |
|
-spec success_stanza(mongoose_acc:t(), mongoose_c2s:data(), maybe_binary()) -> exml:element(). |
345 |
|
success_stanza(SaslAcc, C2SData, MaybeCData) -> |
346 |
19 |
Jid = mongoose_c2s:get_jid(C2SData), |
347 |
19 |
Inlines = get_acc_sasl2_state(SaslAcc), |
348 |
19 |
InlineAnswers = get_inline_responses(Inlines), |
349 |
19 |
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 |
:-( |
AdditionalData = success_subelement(<<"additional-data">>, base64:encode(CData)), |
355 |
:-( |
AuthorizationId = success_subelement(<<"authorization-identifier">>, jid:to_binary(Jid)), |
356 |
:-( |
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 |
19 |
[ 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 |
:-( |
Challenge = #xmlcdata{content = base64:encode(ServerOut)}, |
369 |
:-( |
sasl2_ns_stanza(<<"challenge">>, [Challenge]). |
370 |
|
|
371 |
|
-spec failure_stanza(binary()) -> exml:element(). |
372 |
|
failure_stanza(Reason) -> |
373 |
1 |
SaslErrorCode = #xmlel{name = Reason, attrs = [?XMLNS_SASL]}, |
374 |
1 |
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 |
20 |
#xmlel{name = Name, attrs = [?XMLNS_SASL_2], children = Children}. |
379 |
|
|
380 |
|
-spec success_subelement(binary(), binary()) -> exml:element(). |
381 |
|
success_subelement(Name, AuthId) -> |
382 |
19 |
#xmlel{name = Name, children = [#xmlcdata{content = AuthId}]}. |
383 |
|
|
384 |
|
%% internal |
385 |
|
-spec get_selected_mech(exml:element()) -> binary(). |
386 |
|
get_selected_mech(El) -> |
387 |
20 |
exml_query:attr(El, <<"mechanism">>, <<>>). |
388 |
|
|
389 |
|
-spec get_initial_response(exml:element()) -> binary(). |
390 |
|
get_initial_response(El) -> |
391 |
20 |
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 |
4 |
MaybeId = exml_query:attr(El, <<"id">>, not_provided), |
398 |
4 |
case if_provided_then_is_not_invalid_uuid_v4(MaybeId) of |
399 |
|
invalid_agent -> |
400 |
2 |
invalid_agent; |
401 |
|
Value -> |
402 |
2 |
Software = exml_query:path(El, [{element, <<"software">>}, cdata], not_provided), |
403 |
2 |
Device = exml_query:path(El, [{element, <<"device">>}, cdata], not_provided), |
404 |
2 |
#{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 |
3 |
try |
413 |
3 |
Uuid = uuid:string_to_uuid(Binary), |
414 |
2 |
true = uuid:is_v4(Uuid), |
415 |
1 |
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 |
25 |
InlineFeatures = mongoose_hooks:sasl2_stream_features(C2SData, []), |
424 |
25 |
InlineElem = inlines(InlineFeatures), |
425 |
25 |
#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 |
25 |
#xmlel{name = <<"inline">>, children = InlineFeatures}. |
432 |
|
|
433 |
|
-spec feature_name() -> binary(). |
434 |
|
feature_name() -> |
435 |
50 |
<<"authentication">>. |
436 |
|
|
437 |
|
-spec get_acc_sasl2_state(mongoose_acc:t()) -> [{module(), inline_request()}]. |
438 |
|
get_acc_sasl2_state(SaslAcc) -> |
439 |
19 |
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 |
36 |
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 |
66 |
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 |
42 |
mongoose_acc:set(?MODULE, c2s_state_data, OriginalStateData, SaslAcc). |