2 -- Copyright (C) 2008-2010 Matthew Wild
3 -- Copyright (C) 2008-2010 Waqas Hussain
5 -- This project is MIT/X11 licensed. Please see the
6 -- COPYING file in the source package for more information.
9 local hosts
= _G
.hosts
;
11 local log = module
._log
;
13 local st
= require
"util.stanza";
14 local sha256_hash
= require
"util.hashes".sha256
;
15 local sha256_hmac
= require
"util.hashes".hmac_sha256
;
16 local nameprep
= require
"util.encodings".stringprep
.nameprep
;
17 local uuid_gen
= require
"util.uuid".generate
;
19 local xmlns_stream
= "http://etherx.jabber.org/streams";
21 local dialback_requests
= setmetatable({}, { __mode
= 'v' });
23 local dialback_secret
= sha256_hash(module
:get_option_string("dialback_secret", uuid_gen()), true);
24 local dwd
= module
:get_option_boolean("dialback_without_dialback", false);
26 --- Helper to check that a session peer's certificate is valid
27 function check_cert_status(session
)
28 local host
= session
.direction
== "outgoing" and session
.to_host
or session
.from_host
29 local conn
= session
.conn
:socket()
31 if conn
.getpeercertificate
then
32 cert
= conn
:getpeercertificate()
35 return module
:fire_event("s2s-check-certificate", { host
= host
, session
= session
, cert
= cert
});
39 function module
.save()
40 return { dialback_secret
= dialback_secret
};
43 function module
.restore(state
)
44 dialback_secret
= state
.dialback_secret
;
47 function generate_dialback(id
, to
, from
)
48 return sha256_hmac(dialback_secret
, to
.. ' ' .. from
.. ' ' .. id
, true);
51 function initiate_dialback(session
)
52 -- generate dialback key
53 session
.dialback_key
= generate_dialback(session
.streamid
, session
.to_host
, session
.from_host
);
54 session
.sends2s(st
.stanza("db:result", { from
= session
.from_host
, to
= session
.to_host
}):text(session
.dialback_key
));
55 session
.log("debug", "sent dialback key on outgoing s2s stream");
58 function verify_dialback(id
, to
, from
, key
)
59 return key
== generate_dialback(id
, to
, from
);
62 module
:hook("stanza/jabber:server:dialback:verify", function(event
)
63 local origin
, stanza
= event
.origin
, event
.stanza
;
65 if origin
.type == "s2sin_unauthed" or origin
.type == "s2sin" then
66 -- We are being asked to verify the key, to ensure it was generated by us
67 origin
.log("debug", "verifying that dialback key is ours...");
68 local attr
= stanza
.attr
;
70 module
:log("warn", "Ignoring incoming session from %s claiming a dialback key for %s is %s",
71 origin
.from_host
or "(unknown)", attr
.from
or "(unknown)", attr
.type);
74 -- COMPAT: Grr, ejabberd breaks this one too?? it is black and white in XEP-220 example 34
75 --if attr.from ~= origin.to_host then error("invalid-from"); end
77 if verify_dialback(attr
.id
, attr
.from
, attr
.to
, stanza
[1]) then
81 origin
.log("warn", "Asked to verify a dialback key that was incorrect. An imposter is claiming to be %s?", attr
.to
);
83 origin
.log("debug", "verified dialback key... it is %s", type);
84 origin
.sends2s(st
.stanza("db:verify", { from
= attr
.to
, to
= attr
.from
, id
= attr
.id
, type = type }):text(stanza
[1]));
89 module
:hook("stanza/jabber:server:dialback:result", function(event
)
90 local origin
, stanza
= event
.origin
, event
.stanza
;
92 if origin
.type == "s2sin_unauthed" or origin
.type == "s2sin" then
93 -- he wants to be identified through dialback
94 -- We need to check the key with the Authoritative server
95 local attr
= stanza
.attr
;
96 local to
, from
= nameprep(attr
.to
), nameprep(attr
.from
);
99 -- Not a host that we serve
100 origin
.log("warn", "%s tried to connect to %s, which we don't serve", from
, to
);
101 origin
:close("host-unknown");
104 origin
:close("improper-addressing");
107 if dwd
and origin
.secure
then
108 if check_cert_status(origin
, from
) == false then
110 elseif origin
.cert_chain_status
== "valid" and origin
.cert_identity_status
== "valid" then
111 origin
.sends2s(st
.stanza("db:result", { to
= from
, from
= to
, id
= attr
.id
, type = "valid" }));
112 module
:fire_event("s2s-authenticated", { session
= origin
, host
= from
});
117 origin
.hosts
[from
] = { dialback_key
= stanza
[1] };
119 dialback_requests
[from
.."/"..origin
.streamid
] = origin
;
121 -- COMPAT: ejabberd, gmail and perhaps others do not always set 'to' and 'from'
122 -- on streams. We fill in the session's to/from here instead.
123 if not origin
.from_host
then
124 origin
.from_host
= from
;
126 if not origin
.to_host
then
130 origin
.log("debug", "asking %s if key %s belongs to them", from
, stanza
[1]);
131 module
:fire_event("route/remote", {
132 from_host
= to
, to_host
= from
;
133 stanza
= st
.stanza("db:verify", { from
= to
, to
= from
, id
= origin
.streamid
}):text(stanza
[1]);
139 module
:hook("stanza/jabber:server:dialback:verify", function(event
)
140 local origin
, stanza
= event
.origin
, event
.stanza
;
142 if origin
.type == "s2sout_unauthed" or origin
.type == "s2sout" then
143 local attr
= stanza
.attr
;
144 local dialback_verifying
= dialback_requests
[attr
.from
.."/"..(attr
.id
or "")];
145 if dialback_verifying
and attr
.from
== origin
.to_host
then
147 if attr
.type == "valid" then
148 module
:fire_event("s2s-authenticated", { session
= dialback_verifying
, host
= attr
.from
});
151 -- Warn the original connection that is was not verified successfully
152 log("warn", "authoritative server for %s denied the key", attr
.from
or "(unknown)");
155 if dialback_verifying
.destroyed
then
156 log("warn", "Incoming s2s session %s was closed in the meantime, so we can't notify it of the dialback result",
157 tostring(dialback_verifying
):match("%w+$"));
159 dialback_verifying
.sends2s(
160 st
.stanza("db:result", { from
= attr
.to
, to
= attr
.from
, id
= attr
.id
, type = valid
})
161 :text(dialback_verifying
.hosts
[attr
.from
].dialback_key
));
163 dialback_requests
[attr
.from
.."/"..(attr
.id
or "")] = nil;
169 module
:hook("stanza/jabber:server:dialback:result", function(event
)
170 local origin
, stanza
= event
.origin
, event
.stanza
;
172 if origin
.type == "s2sout_unauthed" or origin
.type == "s2sout" then
173 -- Remote server is telling us whether we passed dialback
175 local attr
= stanza
.attr
;
176 if not hosts
[attr
.to
] then
177 origin
:close("host-unknown");
179 elseif hosts
[attr
.to
].s2sout
[attr
.from
] ~= origin
then
181 origin
:close("invalid-id");
184 if stanza
.attr
.type == "valid" then
185 module
:fire_event("s2s-authenticated", { session
= origin
, host
= attr
.from
});
187 origin
:close("not-authorized", "dialback authentication failed");
193 module
:hook_tag("urn:ietf:params:xml:ns:xmpp-sasl", "failure", function (origin
, stanza
) -- luacheck: ignore 212/stanza
194 if origin
.external_auth
== "failed" then
195 module
:log("debug", "SASL EXTERNAL failed, falling back to dialback");
196 initiate_dialback(origin
);
201 module
:hook_tag(xmlns_stream
, "features", function (origin
, stanza
) -- luacheck: ignore 212/stanza
202 if not origin
.external_auth
or origin
.external_auth
== "failed" then
203 module
:log("debug", "Initiating dialback...");
204 initiate_dialback(origin
);
209 module
:hook("s2sout-authenticate-legacy", function (event
)
210 module
:log("debug", "Initiating dialback...");
211 initiate_dialback(event
.origin
);
215 -- Offer dialback to incoming hosts
216 module
:hook("s2s-stream-features", function (data
)
217 data
.features
:tag("dialback", { xmlns
='urn:xmpp:features:dialback' }):up();