1 |
|
-module(mongoose_c2s_stanzas). |
2 |
|
|
3 |
|
-include("jlib.hrl"). |
4 |
|
-include("mongoose.hrl"). |
5 |
|
-include_lib("exml/include/exml_stream.hrl"). |
6 |
|
|
7 |
|
-export([ |
8 |
|
stream_header/1, |
9 |
|
stream_features_before_auth/1, |
10 |
|
tls_proceed/0, |
11 |
|
tls_failure/0, |
12 |
|
stream_features_after_auth/1, |
13 |
|
sasl_success_stanza/1, |
14 |
|
sasl_failure_stanza/1, |
15 |
|
sasl_challenge_stanza/1, |
16 |
|
successful_resource_binding/2, |
17 |
|
successful_session_establishment/1 |
18 |
|
]). |
19 |
|
|
20 |
|
-spec stream_header(mongoose_c2s:data()) -> exml_stream:start(). |
21 |
|
stream_header(StateData) -> |
22 |
12029 |
Lang = mongoose_c2s:get_lang(StateData), |
23 |
12029 |
LServer = mongoose_c2s:get_lserver(StateData), |
24 |
12029 |
StreamId = mongoose_c2s:get_stream_id(StateData), |
25 |
12029 |
MaybeFrom = [ {<<"to">>, jid:to_binary(Jid)} |
26 |
12029 |
|| Jid <- [mongoose_c2s:get_jid(StateData)], Jid =/= undefined], |
27 |
12029 |
Attrs = [{<<"xmlns">>, ?NS_CLIENT}, |
28 |
|
{<<"xmlns:stream">>, <<"http://etherx.jabber.org/streams">>}, |
29 |
|
{<<"id">>, StreamId}, |
30 |
|
{<<"from">>, LServer}, |
31 |
|
{<<"version">>, ?XMPP_VERSION}, |
32 |
|
{<<"xml:lang">>, Lang} | MaybeFrom ], |
33 |
12029 |
#xmlstreamstart{name = <<"stream:stream">>, attrs = Attrs}. |
34 |
|
|
35 |
|
-spec stream_features([exml:element() | exml:cdata()]) -> exml:element(). |
36 |
|
stream_features(Features) -> |
37 |
12035 |
#xmlel{name = <<"stream:features">>, children = Features}. |
38 |
|
|
39 |
|
-spec stream_features_before_auth(mongoose_c2s:data()) -> exml:element(). |
40 |
|
stream_features_before_auth(StateData) -> |
41 |
6134 |
HostType = mongoose_c2s:get_host_type(StateData), |
42 |
6134 |
LServer = mongoose_c2s:get_lserver(StateData), |
43 |
6134 |
Socket = mongoose_c2s:get_socket(StateData), |
44 |
6134 |
LOpts = mongoose_c2s:get_listener_opts(StateData), |
45 |
6134 |
IsSSL = mongoose_c2s_socket:is_ssl(Socket), |
46 |
6134 |
Features = determine_features(StateData, HostType, LServer, LOpts, IsSSL), |
47 |
6134 |
stream_features(Features). |
48 |
|
|
49 |
|
%% From RFC 6120, section 5.3.1: |
50 |
|
%% |
51 |
|
%% If TLS is mandatory-to-negotiate, the receiving entity SHOULD NOT |
52 |
|
%% advertise support for any stream feature except STARTTLS during the |
53 |
|
%% initial stage of the stream negotiation process, because further stream |
54 |
|
%% features might depend on prior negotiation of TLS given the order of |
55 |
|
%% layers in XMPP (e.g., the particular SASL mechanisms offered by the |
56 |
|
%% receiving entity will likely depend on whether TLS has been negotiated). |
57 |
|
%% |
58 |
|
%% http://xmpp.org/rfcs/rfc6120.html#tls-rules-mtn |
59 |
|
determine_features(_, _, _, #{tls := #{mode := starttls_required}}, false) -> |
60 |
20 |
[starttls_stanza(required)]; |
61 |
|
determine_features(StateData, HostType, LServer, #{tls := #{mode := starttls}}, false) -> |
62 |
5817 |
InitialFeatures = [starttls_stanza(optional) | maybe_sasl_mechanisms(StateData)], |
63 |
5817 |
StreamFeaturesParams = #{c2s_data => StateData, lserver => LServer}, |
64 |
5817 |
mongoose_hooks:c2s_stream_features(HostType, StreamFeaturesParams, InitialFeatures); |
65 |
|
determine_features(StateData, HostType, LServer, _, _) -> |
66 |
297 |
InitialFeatures = maybe_sasl_mechanisms(StateData), |
67 |
297 |
StreamFeaturesParams = #{c2s_data => StateData, lserver => LServer}, |
68 |
297 |
mongoose_hooks:c2s_stream_features(HostType, StreamFeaturesParams, InitialFeatures). |
69 |
|
|
70 |
|
-spec maybe_sasl_mechanisms(mongoose_c2s:data()) -> [exml:element()]. |
71 |
|
maybe_sasl_mechanisms(StateData) -> |
72 |
6114 |
case mongoose_c2s:get_auth_mechs(StateData) of |
73 |
65 |
[] -> []; |
74 |
|
Mechanisms -> |
75 |
6049 |
[#xmlel{name = <<"mechanisms">>, |
76 |
|
attrs = [{<<"xmlns">>, ?NS_SASL}], |
77 |
12580 |
children = [ mechanism(M) || M <- Mechanisms ]}] |
78 |
|
end. |
79 |
|
|
80 |
|
-spec mechanism(binary()) -> exml:element(). |
81 |
|
mechanism(M) -> |
82 |
12580 |
#xmlel{name = <<"mechanism">>, children = [#xmlcdata{content = M}]}. |
83 |
|
|
84 |
|
-spec starttls_stanza(required | optional) -> exml:element(). |
85 |
|
starttls_stanza(TLSRequired) when TLSRequired =:= required; TLSRequired =:= optional -> |
86 |
5837 |
#xmlel{name = <<"starttls">>, |
87 |
|
attrs = [{<<"xmlns">>, ?NS_TLS}], |
88 |
20 |
children = [ #xmlel{name = <<"required">>} || TLSRequired =:= required ]}. |
89 |
|
|
90 |
|
-spec tls_proceed() -> exml:element(). |
91 |
|
tls_proceed() -> |
92 |
98 |
#xmlel{name = <<"proceed">>, |
93 |
|
attrs = [{<<"xmlns">>, ?NS_TLS}]}. |
94 |
|
|
95 |
|
-spec tls_failure() -> exml:element(). |
96 |
|
tls_failure() -> |
97 |
2 |
#xmlel{name = <<"failure">>, |
98 |
|
attrs = [{<<"xmlns">>, ?NS_TLS}]}. |
99 |
|
|
100 |
|
-spec stream_features_after_auth(mongoose_c2s:data()) -> exml:element(). |
101 |
|
stream_features_after_auth(StateData) -> |
102 |
5901 |
case mongoose_c2s:get_listener_opts(StateData) of |
103 |
|
#{backwards_compatible_session := false} -> |
104 |
7 |
Features = [#xmlel{name = <<"bind">>, |
105 |
|
attrs = [{<<"xmlns">>, ?NS_BIND}]} |
106 |
|
| hook_enabled_features(StateData)], |
107 |
7 |
stream_features(Features); |
108 |
|
#{backwards_compatible_session := true} -> |
109 |
5894 |
Features = [#xmlel{name = <<"session">>, |
110 |
|
attrs = [{<<"xmlns">>, ?NS_SESSION}]}, |
111 |
|
#xmlel{name = <<"bind">>, |
112 |
|
attrs = [{<<"xmlns">>, ?NS_BIND}]} |
113 |
|
| hook_enabled_features(StateData)], |
114 |
5894 |
stream_features(Features) |
115 |
|
end. |
116 |
|
|
117 |
|
hook_enabled_features(StateData) -> |
118 |
5901 |
HostType = mongoose_c2s:get_host_type(StateData), |
119 |
5901 |
LServer = mongoose_c2s:get_lserver(StateData), |
120 |
5901 |
InitialFeatures = mongoose_hooks:roster_get_versioning_feature(HostType), |
121 |
5901 |
StreamFeaturesParams = #{c2s_data => StateData, lserver => LServer}, |
122 |
5901 |
mongoose_hooks:c2s_stream_features(HostType, StreamFeaturesParams, InitialFeatures). |
123 |
|
|
124 |
|
-spec sasl_success_stanza(undefined | binary()) -> exml:element(). |
125 |
|
sasl_success_stanza(ServerOut) -> |
126 |
5884 |
C = case ServerOut of |
127 |
5841 |
undefined -> []; |
128 |
43 |
_ -> [#xmlcdata{content = jlib:encode_base64(ServerOut)}] |
129 |
|
end, |
130 |
5884 |
#xmlel{name = <<"success">>, |
131 |
|
attrs = [{<<"xmlns">>, ?NS_SASL}], |
132 |
|
children = C}. |
133 |
|
|
134 |
|
-spec sasl_failure_stanza(binary() | {binary(), iodata() | undefined}) -> exml:element(). |
135 |
|
sasl_failure_stanza(Error) when is_binary(Error) -> |
136 |
64 |
sasl_failure_stanza({Error, undefined}); |
137 |
|
sasl_failure_stanza({Error, Text}) -> |
138 |
65 |
#xmlel{name = <<"failure">>, |
139 |
|
attrs = [{<<"xmlns">>, ?NS_SASL}], |
140 |
|
children = [#xmlel{name = Error} | maybe_text_tag(Text)]}. |
141 |
|
|
142 |
64 |
maybe_text_tag(undefined) -> []; |
143 |
|
maybe_text_tag(Text) -> |
144 |
1 |
[#xmlel{name = <<"text">>, |
145 |
|
children = [#xmlcdata{content = Text}]}]. |
146 |
|
|
147 |
|
-spec sasl_challenge_stanza(binary()) -> exml:element(). |
148 |
|
sasl_challenge_stanza(ServerOut) -> |
149 |
47 |
#xmlel{name = <<"challenge">>, |
150 |
|
attrs = [{<<"xmlns">>, ?NS_SASL}], |
151 |
|
children = [#xmlcdata{content = jlib:encode_base64(ServerOut)}]}. |
152 |
|
|
153 |
|
-spec successful_resource_binding(jlib:iq(), jid:jid()) -> exml:element(). |
154 |
|
successful_resource_binding(IQ, Jid) -> |
155 |
5860 |
JIDEl = #xmlel{name = <<"jid">>, |
156 |
|
children = [#xmlcdata{content = jid:to_binary(Jid)}]}, |
157 |
5860 |
Res = IQ#iq{type = result, |
158 |
|
sub_el = [#xmlel{name = <<"bind">>, |
159 |
|
attrs = [{<<"xmlns">>, ?NS_BIND}], |
160 |
|
children = [JIDEl]}]}, |
161 |
5860 |
jlib:iq_to_xml(Res). |
162 |
|
|
163 |
|
-spec successful_session_establishment(jlib:iq()) -> exml:element(). |
164 |
|
successful_session_establishment(IQ) -> |
165 |
5849 |
Res = IQ#iq{type = result, |
166 |
|
sub_el = [#xmlel{name = <<"session">>, |
167 |
|
attrs = [{<<"xmlns">>, ?NS_SESSION}]}]}, |
168 |
5849 |
jlib:iq_to_xml(Res). |