1 |
|
-module(mod_stream_management_sasl2). |
2 |
|
|
3 |
|
-define(SM, mod_stream_management). |
4 |
|
|
5 |
|
-include("jlib.hrl"). |
6 |
|
-include("mongoose_ns.hrl"). |
7 |
|
|
8 |
|
-export([hooks/1]). |
9 |
|
|
10 |
|
-export([sasl2_stream_features/3, |
11 |
|
sasl2_start/3, |
12 |
|
sasl2_success/3, |
13 |
|
bind2_stream_features/3, |
14 |
|
bind2_enable_features/3]). |
15 |
|
|
16 |
|
%% Helpers |
17 |
|
-spec hooks(mongooseim:host_type()) -> gen_hook:hook_list(). |
18 |
|
hooks(HostType) -> |
19 |
404 |
[ |
20 |
|
{sasl2_stream_features, HostType, fun ?MODULE:sasl2_stream_features/3, #{}, 50}, |
21 |
|
{sasl2_start, HostType, fun ?MODULE:sasl2_start/3, #{}, 50}, |
22 |
|
{sasl2_success, HostType, fun ?MODULE:sasl2_success/3, #{}, 20}, |
23 |
|
{bind2_stream_features, HostType, fun ?MODULE:bind2_stream_features/3, #{}, 50}, |
24 |
|
{bind2_enable_features, HostType, fun ?MODULE:bind2_enable_features/3, #{}, 50} |
25 |
|
]. |
26 |
|
|
27 |
|
%% Hook handlers |
28 |
|
-spec sasl2_stream_features(Acc, #{c2s_data := mongoose_c2s:data()}, gen_hook:extra()) -> |
29 |
|
{ok, Acc} when Acc :: [exml:element()]. |
30 |
|
sasl2_stream_features(Acc, _, _) -> |
31 |
29 |
Resume = #xmlel{name = <<"sm">>, attrs = [{<<"xmlns">>, ?NS_STREAM_MGNT_3}]}, |
32 |
29 |
{ok, [Resume | Acc]}. |
33 |
|
|
34 |
|
-spec sasl2_start(SaslAcc, #{stanza := exml:element()}, gen_hook:extra()) -> |
35 |
|
{ok, SaslAcc} when SaslAcc :: mongoose_acc:t(). |
36 |
|
sasl2_start(SaslAcc, #{stanza := El}, _) -> |
37 |
24 |
case exml_query:path(El, [{element_with_ns, <<"resume">>, ?NS_STREAM_MGNT_3}]) of |
38 |
|
undefined -> |
39 |
17 |
{ok, SaslAcc}; |
40 |
|
SmRequest -> |
41 |
7 |
{ok, mod_sasl2:put_inline_request(SaslAcc, ?MODULE, SmRequest)} |
42 |
|
end. |
43 |
|
|
44 |
|
%% If SASL2 inline stream resumption is requested, that MUST be processed FIRST by the server. |
45 |
|
-spec sasl2_success(mongoose_acc:t(), mod_sasl2:c2s_state_data(), gen_hook:extra()) -> |
46 |
|
mongoose_c2s_hooks:result(). |
47 |
|
sasl2_success(SaslAcc, _, _) -> |
48 |
20 |
case mod_sasl2:get_inline_request(SaslAcc, ?MODULE, undefined) of |
49 |
|
undefined -> |
50 |
13 |
{ok, SaslAcc}; |
51 |
|
SmRequest -> |
52 |
7 |
handle_sasl2_resume(SaslAcc, SmRequest) |
53 |
|
end. |
54 |
|
|
55 |
|
-spec bind2_stream_features(Acc, #{c2s_data := mongoose_c2s:data()}, gen_hook:extra()) -> |
56 |
|
{ok, Acc} when Acc :: [exml:element()]. |
57 |
|
bind2_stream_features(Acc, _, _) -> |
58 |
29 |
SmFeature = #xmlel{name = <<"feature">>, attrs = [{<<"var">>, ?NS_STREAM_MGNT_3}]}, |
59 |
29 |
{ok, [SmFeature | Acc]}. |
60 |
|
|
61 |
|
-spec bind2_enable_features(SaslAcc, mod_sasl2:c2s_state_data(), gen_hook:extra()) -> |
62 |
|
{ok, SaslAcc} when SaslAcc :: mongoose_acc:t(). |
63 |
|
bind2_enable_features(SaslAcc, Params, _) -> |
64 |
10 |
Inline = #{request := BindRequest} = mod_bind2:get_bind_request(SaslAcc), |
65 |
10 |
case exml_query:subelement_with_name_and_ns(BindRequest, <<"enable">>, ?NS_STREAM_MGNT_3) of |
66 |
|
undefined -> |
67 |
8 |
{ok, SaslAcc}; |
68 |
|
El -> |
69 |
2 |
handle_bind_enable(SaslAcc, Params, Inline, El) |
70 |
|
end. |
71 |
|
|
72 |
|
%% If resumption is successful: |
73 |
|
%% - MUST skip resource binding (a resumed session already has a resource bound) |
74 |
|
%% - MUST entirely ignore the potentially inlined <bind/> request |
75 |
|
%% - MUST NOT send stream features in this case (overriding behaviour defined in SASL2) |
76 |
|
-spec handle_sasl2_resume(mongoose_acc:t(), mod_sasl2:inline_request()) -> |
77 |
|
mongoose_c2s_hooks:result(). |
78 |
|
handle_sasl2_resume(SaslAcc, #{request := El}) -> |
79 |
7 |
#{c2s_state := C2SState, c2s_data := C2SData} = mod_sasl2:get_state_data(SaslAcc), |
80 |
7 |
case mod_stream_management:handle_resume(C2SData, C2SState, El) of |
81 |
|
{stream_mgmt_error, ErrorStanza} -> |
82 |
2 |
{ok, mod_sasl2:update_inline_request(SaslAcc, ?MODULE, ErrorStanza, failure)}; |
83 |
|
{error, _ErrorStanza, _Reason} -> %% This signifies a stream-error, but we discard those here |
84 |
1 |
SimpleErrorStanza = mod_stream_management_stanzas:stream_mgmt_failed(<<"bad-request">>), |
85 |
1 |
{ok, mod_sasl2:update_inline_request(SaslAcc, ?MODULE, SimpleErrorStanza, failure)}; |
86 |
|
{error, ErrorStanza} -> |
87 |
2 |
{ok, mod_sasl2:update_inline_request(SaslAcc, ?MODULE, ErrorStanza, failure)}; |
88 |
|
{ok, #{resumed := Resumed, forward := ToForward} = Ret} -> |
89 |
2 |
SaslAcc1 = mod_sasl2:update_inline_request(SaslAcc, ?MODULE, Resumed, success), |
90 |
2 |
SaslAcc2 = mod_sasl2:set_state_data(SaslAcc1, Ret), |
91 |
2 |
SaslAcc3 = mod_sasl2:request_block_future_stream_features(SaslAcc2), |
92 |
|
%% We stop here to completely ignore subsequent inlines (for example BIND2) |
93 |
2 |
{stop, mongoose_c2s_acc:to_acc(SaslAcc3, socket_send, ToForward)} |
94 |
|
end. |
95 |
|
|
96 |
|
-spec handle_bind_enable(SaslAcc, mod_sasl2:c2s_state_data(), mod_sasl2:inline_request(), exml:element()) -> |
97 |
|
{ok, SaslAcc} when SaslAcc :: mongoose_acc:t(). |
98 |
|
handle_bind_enable(SaslAcc, #{c2s_data := C2SData}, Inline, El) -> |
99 |
2 |
case mod_stream_management:if_not_already_enabled_create_sm_state(C2SData) of |
100 |
|
error -> |
101 |
:-( |
mod_stream_management:stream_error(SaslAcc, C2SData); |
102 |
|
SmState -> |
103 |
2 |
do_handle_bind_enable(SaslAcc, C2SData, SmState, Inline, El) |
104 |
|
end. |
105 |
|
|
106 |
|
-spec do_handle_bind_enable(SaslAcc, mongoose_c2s:data(), mod_stream_management:sm_state(), mod_sasl2:inline_request(), exml:element()) -> |
107 |
|
{ok, SaslAcc} when SaslAcc :: mongoose_acc:t(). |
108 |
|
do_handle_bind_enable(SaslAcc, C2SData, SmState, Inline, El) -> |
109 |
2 |
case exml_query:attr(El, <<"resume">>, false) of |
110 |
|
false -> |
111 |
1 |
Stanza = mod_stream_management_stanzas:stream_mgmt_enabled(), |
112 |
1 |
SaslAcc1 = mongoose_c2s_acc:to_acc(SaslAcc, state_mod, {?SM, SmState}), |
113 |
1 |
SaslAcc2 = mod_bind2:append_inline_bound_answer(SaslAcc1, Inline, Stanza), |
114 |
1 |
{ok, SaslAcc2}; |
115 |
|
Attr when Attr =:= <<"true">>; Attr =:= <<"1">> -> |
116 |
1 |
Stanza = mod_stream_management:register_smid_return_enabled_stanza(C2SData), |
117 |
1 |
SaslAcc1 = mongoose_c2s_acc:to_acc(SaslAcc, state_mod, {?SM, SmState}), |
118 |
1 |
SaslAcc2 = mod_bind2:append_inline_bound_answer(SaslAcc1, Inline, Stanza), |
119 |
1 |
{ok, SaslAcc2}; |
120 |
|
_ -> |
121 |
:-( |
mod_stream_management:stream_error(SaslAcc, C2SData) |
122 |
|
end. |