1 |
|
%% Steps for S2S Dialback. |
2 |
|
%% Diagram from https://xmpp.org/extensions/xep-0220.html#intro-howitworks |
3 |
|
%% |
4 |
|
%% Initiating Receiving |
5 |
|
%% Server Server |
6 |
|
%% ----------- --------- |
7 |
|
%% | | |
8 |
|
%% | [if necessary, | |
9 |
|
%% | perform DNS lookup | |
10 |
|
%% | on Target Domain, | |
11 |
|
%% | open TCP connection, | |
12 |
|
%% | and establish stream] | |
13 |
|
%% | -----------------------> | |
14 |
|
%% | | Authoritative |
15 |
|
%% | send dialback key | Server |
16 |
|
%% | -------(STEP 1)--------> | ------------- |
17 |
|
%% | | | |
18 |
|
%% | | [if necessary, | |
19 |
|
%% | | perform DNS lookup, | |
20 |
|
%% | | on Sender Domain, | |
21 |
|
%% | | open TCP connection, | |
22 |
|
%% | | and establish stream] | |
23 |
|
%% | | -----------------------> | |
24 |
|
%% | | | |
25 |
|
%% | | send verify request | |
26 |
|
%% | | -------(STEP 2)--------> | |
27 |
|
%% | | | |
28 |
|
%% | | send verify response | |
29 |
|
%% | | <------(STEP 3)--------- | |
30 |
|
%% | | |
31 |
|
%% | report dialback result | |
32 |
|
%% | <-------(STEP 4)-------- | |
33 |
|
%% | | |
34 |
|
|
35 |
|
%% Because db:result and db:verify tags are confusing, use step numbers. |
36 |
|
%% (db:result should've been named db:key). |
37 |
|
|
38 |
|
-module(mongoose_s2s_dialback). |
39 |
|
-export([step_1/2, |
40 |
|
step_2/3, |
41 |
|
step_3/3, |
42 |
|
step_4/2]). |
43 |
|
|
44 |
|
-export([parse_key/1, |
45 |
|
parse_validity/1]). |
46 |
|
|
47 |
|
-export([make_key/3]). |
48 |
|
|
49 |
|
-xep([{xep, 185}, {version, "1.0"}]). %% Dialback Key Generation and Validation |
50 |
|
-xep([{xep, 220}, {version, "1.1.1"}]). %% Server Dialback |
51 |
|
|
52 |
|
-include("mongoose.hrl"). |
53 |
|
-include("jlib.hrl"). |
54 |
|
|
55 |
|
%% Initiating server sends dialback key |
56 |
|
%% https://xmpp.org/extensions/xep-0220.html#example-1 |
57 |
|
-spec step_1(ejabberd_s2s:fromto(), ejabberd_s2s:s2s_dialback_key()) -> exml:element(). |
58 |
|
step_1(FromTo, Key) -> |
59 |
:-( |
#xmlel{name = <<"db:result">>, |
60 |
|
attrs = fromto_to_attrs(FromTo), |
61 |
|
children = [#xmlcdata{content = Key}]}. |
62 |
|
|
63 |
|
%% Receiving server sends verification request to authoritative server (step 2) |
64 |
|
-spec step_2(ejabberd_s2s:fromto(), ejabberd_s2s:s2s_dialback_key(), ejabberd_s2s:stream_id()) -> exml:element(). |
65 |
|
step_2(FromTo, Key, StreamID) -> |
66 |
:-( |
#xmlel{name = <<"db:verify">>, |
67 |
|
attrs = [{<<"id">>, StreamID} | fromto_to_attrs(FromTo)], |
68 |
|
children = [#xmlcdata{content = Key}]}. |
69 |
|
|
70 |
|
%% Receiving server is informed by authoritative server that key is valid or invalid (step 3) |
71 |
|
-spec step_3(ejabberd_s2s:fromto(), ejabberd_s2s:stream_id(), boolean()) -> exml:element(). |
72 |
|
step_3(FromTo, StreamID, IsValid) -> |
73 |
:-( |
#xmlel{name = <<"db:verify">>, |
74 |
|
attrs = [{<<"id">>, StreamID}, |
75 |
|
{<<"type">>, is_valid_to_type(IsValid)} |
76 |
|
| fromto_to_attrs(FromTo)]}. |
77 |
|
|
78 |
|
%% Receiving server sends valid or invalid verification result to initiating server (step 4) |
79 |
|
-spec step_4(ejabberd_s2s:fromto(), boolean()) -> exml:element(). |
80 |
|
step_4(FromTo, IsValid) -> |
81 |
:-( |
#xmlel{name = <<"db:result">>, |
82 |
|
attrs = [{<<"type">>, is_valid_to_type(IsValid)} |
83 |
|
| fromto_to_attrs(FromTo)]}. |
84 |
|
|
85 |
|
-spec fromto_to_attrs(ejabberd_s2s:fromto()) -> [{binary(), binary()}]. |
86 |
|
fromto_to_attrs({LocalServer, RemoteServer}) -> |
87 |
:-( |
[{<<"from">>, LocalServer}, {<<"to">>, RemoteServer}]. |
88 |
|
|
89 |
:-( |
is_valid_to_type(true) -> <<"valid">>; |
90 |
:-( |
is_valid_to_type(false) -> <<"invalid">>. |
91 |
|
|
92 |
|
-spec parse_key(exml:element()) -> false |
93 |
|
| {Step :: step_1 | step_2, |
94 |
|
FromTo :: ejabberd_s2s:fromto(), |
95 |
|
StreamID :: ejabberd_s2s:stream_id(), |
96 |
|
Key :: ejabberd_s2s:s2s_dialback_key()}. |
97 |
|
parse_key(El = #xmlel{name = <<"db:result">>}) -> |
98 |
|
%% Initiating Server Sends Dialback Key (Step 1) |
99 |
:-( |
parse_key(step_1, El); |
100 |
|
parse_key(El = #xmlel{name = <<"db:verify">>}) -> |
101 |
|
%% Receiving Server Sends Verification Request to Authoritative Server (Step 2) |
102 |
:-( |
parse_key(step_2, El); |
103 |
|
parse_key(_) -> |
104 |
:-( |
false. |
105 |
|
|
106 |
|
parse_key(Step, El) -> |
107 |
:-( |
FromTo = parse_from_to(El), |
108 |
:-( |
StreamID = exml_query:attr(El, <<"id">>, <<>>), |
109 |
:-( |
Key = exml_query:cdata(El), |
110 |
:-( |
{Step, FromTo, StreamID, Key}. |
111 |
|
|
112 |
|
%% Parse dialback verification result. |
113 |
|
%% Verification result is stored in the `type' attribute and could be `valid' or `invalid'. |
114 |
|
-spec parse_validity(exml:element()) -> false |
115 |
|
| {Step :: step_3 | step_4, |
116 |
|
FromTo :: ejabberd_s2s:fromto(), |
117 |
|
StreamID :: ejabberd_s2s:stream_id(), |
118 |
|
IsValid :: boolean()}. |
119 |
|
parse_validity(El = #xmlel{name = <<"db:verify">>}) -> |
120 |
|
%% Receiving Server is Informed by Authoritative Server that Key is Valid or Invalid (Step 3) |
121 |
:-( |
parse_validity(step_3, El); |
122 |
|
parse_validity(El = #xmlel{name = <<"db:result">>}) -> |
123 |
|
%% Receiving Server Sends Valid or Invalid Verification Result to Initiating Server (Step 4) |
124 |
:-( |
parse_validity(step_4, El); |
125 |
|
parse_validity(_) -> |
126 |
:-( |
false. |
127 |
|
|
128 |
|
parse_validity(Step, El) -> |
129 |
:-( |
FromTo = parse_from_to(El), |
130 |
:-( |
StreamID = exml_query:attr(El, <<"id">>, <<>>), |
131 |
:-( |
IsValid = exml_query:attr(El, <<"type">>) =:= <<"valid">>, |
132 |
:-( |
{Step, FromTo, StreamID, IsValid}. |
133 |
|
|
134 |
|
-spec parse_from_to(exml:element()) -> ejabberd_s2s:fromto(). |
135 |
|
parse_from_to(El) -> |
136 |
:-( |
RemoteJid = jid:from_binary(exml_query:attr(El, <<"from">>, <<>>)), |
137 |
:-( |
LocalJid = jid:from_binary(exml_query:attr(El, <<"to">>, <<>>)), |
138 |
:-( |
#jid{luser = <<>>, lresource = <<>>, lserver = LRemoteServer} = RemoteJid, |
139 |
:-( |
#jid{luser = <<>>, lresource = <<>>, lserver = LLocalServer} = LocalJid, |
140 |
|
%% We use fromto() as seen by ejabberd_s2s_out and ejabberd_s2s |
141 |
:-( |
{LLocalServer, LRemoteServer}. |
142 |
|
|
143 |
|
-spec make_key(ejabberd_s2s:fromto(), ejabberd_s2s:stream_id(), ejabberd_s2s:base16_secret()) -> |
144 |
|
ejabberd_s2s:s2s_dialback_key(). |
145 |
|
make_key({From, To}, StreamID, Secret) -> |
146 |
:-( |
SecretHashed = base16:encode(crypto:hash(sha256, Secret)), |
147 |
:-( |
HMac = crypto:mac(hmac, sha256, SecretHashed, [From, " ", To, " ", StreamID]), |
148 |
:-( |
base16:encode(HMac). |