| 1 | -- Prosody IM |
| 2 | -- Copyright (C) 2008-2010 Matthew Wild |
| 3 | -- Copyright (C) 2008-2010 Waqas Hussain |
| 4 | -- |
| 5 | -- This project is MIT/X11 licensed. Please see the |
| 6 | -- COPYING file in the source package for more information. |
| 7 | -- |
| 8 | |
| 9 | |
| 10 | local hosts = _G.hosts; |
| 11 | local send_s2s = require "core.s2smanager".send_to_host; |
| 12 | local s2s_make_authenticated = require "core.s2smanager".make_authenticated; |
| 13 | local s2s_initiate_dialback = require "core.s2smanager".initiate_dialback; |
| 14 | local s2s_verify_dialback = require "core.s2smanager".verify_dialback; |
| 15 | local s2s_destroy_session = require "core.s2smanager".destroy_session; |
| 16 | |
| 17 | local log = module._log; |
| 18 | |
| 19 | local st = require "util.stanza"; |
| 20 | |
| 21 | local xmlns_stream = "http://etherx.jabber.org/streams"; |
| 22 | local xmlns_dialback = "jabber:server:dialback"; |
| 23 | |
| 24 | local dialback_requests = setmetatable({}, { __mode = 'v' }); |
| 25 | |
| 26 | module:hook("stanza/jabber:server:dialback:verify", function(event) |
| 27 | local origin, stanza = event.origin, event.stanza; |
| 28 | |
| 29 | if origin.type == "s2sin_unauthed" or origin.type == "s2sin" then |
| 30 | -- We are being asked to verify the key, to ensure it was generated by us |
| 31 | origin.log("debug", "verifying that dialback key is ours..."); |
| 32 | local attr = stanza.attr; |
| 33 | -- COMPAT: Grr, ejabberd breaks this one too?? it is black and white in XEP-220 example 34 |
| 34 | --if attr.from ~= origin.to_host then error("invalid-from"); end |
| 35 | local type; |
| 36 | if s2s_verify_dialback(attr.id, attr.from, attr.to, stanza:get_text()) then |
| 37 | type = "valid" |
| 38 | else |
| 39 | type = "invalid" |
| 40 | origin.log("warn", "Asked to verify a dialback key that was incorrect. An imposter is claiming to be %s?", attr.to); |
| 41 | end |
| 42 | origin.log("debug", "verified dialback key... it is %s", type); |
| 43 | origin.sends2s(st.stanza("db:verify", { from = attr.to, to = attr.from, id = attr.id, type = type }):text(stanza:get_text())); |
| 44 | return true; |
| 45 | end |
| 46 | end); |
| 47 | |
| 48 | module:hook("stanza/jabber:server:dialback:result", function(event) |
| 49 | local origin, stanza = event.origin, event.stanza; |
| 50 | |
| 51 | if origin.type == "s2sin_unauthed" or origin.type == "s2sin" then |
| 52 | -- he wants to be identified through dialback |
| 53 | -- We need to check the key with the Authoritative server |
| 54 | local attr = stanza.attr; |
| 55 | origin.hosts[attr.from] = { dialback_key = stanza:get_text() }; |
| 56 | |
| 57 | if not hosts[attr.to] then |
| 58 | -- Not a host that we serve |
| 59 | origin.log("info", "%s tried to connect to %s, which we don't serve", attr.from, attr.to); |
| 60 | origin:close("host-unknown"); |
| 61 | return true; |
| 62 | end |
| 63 | |
| 64 | dialback_requests[attr.from] = origin; |
| 65 | |
| 66 | if not origin.from_host then |
| 67 | -- Just used for friendlier logging |
| 68 | origin.from_host = attr.from; |
| 69 | end |
| 70 | if not origin.to_host then |
| 71 | -- Just used for friendlier logging |
| 72 | origin.to_host = attr.to; |
| 73 | end |
| 74 | |
| 75 | origin.log("debug", "asking %s if key %s belongs to them", attr.from, stanza:get_text()); |
| 76 | send_s2s(attr.to, attr.from, |
| 77 | st.stanza("db:verify", { from = attr.to, to = attr.from, id = origin.streamid }):text(stanza:get_text())); |
| 78 | return true; |
| 79 | end |
| 80 | end); |
| 81 | |
| 82 | module:hook("stanza/jabber:server:dialback:verify", function(event) |
| 83 | local origin, stanza = event.origin, event.stanza; |
| 84 | |
| 85 | if origin.type == "s2sout_unauthed" or origin.type == "s2sout" then |
| 86 | local attr = stanza.attr; |
| 87 | local dialback_verifying = dialback_requests[attr.from]; |
| 88 | if dialback_verifying then |
| 89 | local valid; |
| 90 | if attr.type == "valid" then |
| 91 | s2s_make_authenticated(dialback_verifying, attr.from); |
| 92 | valid = "valid"; |
| 93 | else |
| 94 | -- Warn the original connection that is was not verified successfully |
| 95 | log("warn", "authoritative server for "..(attr.from or "(unknown)").." denied the key"); |
| 96 | valid = "invalid"; |
| 97 | end |
| 98 | if not dialback_verifying.sends2s then |
| 99 | log("warn", "Incoming s2s session %s was closed in the meantime, so we can't notify it of the db result", tostring(dialback_verifying):match("%w+$")); |
| 100 | else |
| 101 | dialback_verifying.sends2s( |
| 102 | st.stanza("db:result", { from = attr.to, to = attr.from, id = attr.id, type = valid }) |
| 103 | :text(dialback_verifying.hosts[attr.from].dialback_key)); |
| 104 | end |
| 105 | dialback_requests[attr.from] = nil; |
| 106 | end |
| 107 | return true; |
| 108 | end |
| 109 | end); |
| 110 | |
| 111 | module:hook("stanza/jabber:server:dialback:result", function(event) |
| 112 | local origin, stanza = event.origin, event.stanza; |
| 113 | |
| 114 | if origin.type == "s2sout_unauthed" or origin.type == "s2sout" then |
| 115 | -- Remote server is telling us whether we passed dialback |
| 116 | |
| 117 | local attr = stanza.attr; |
| 118 | if not hosts[attr.to] then |
| 119 | origin:close("host-unknown"); |
| 120 | return true; |
| 121 | elseif hosts[attr.to].s2sout[attr.from] ~= origin then |
| 122 | -- This isn't right |
| 123 | origin:close("invalid-id"); |
| 124 | return true; |
| 125 | end |
| 126 | if stanza.attr.type == "valid" then |
| 127 | s2s_make_authenticated(origin, attr.from); |
| 128 | else |
| 129 | s2s_destroy_session(origin) |
| 130 | end |
| 131 | return true; |
| 132 | end |
| 133 | end); |
| 134 | |
| 135 | module:hook_stanza("urn:ietf:params:xml:ns:xmpp-sasl", "failure", function (origin, stanza) |
| 136 | if origin.external_auth == "failed" then |
| 137 | module:log("debug", "SASL EXTERNAL failed, falling back to dialback"); |
| 138 | s2s_initiate_dialback(origin); |
| 139 | return true; |
| 140 | end |
| 141 | end, 100); |
| 142 | |
| 143 | module:hook_stanza(xmlns_stream, "features", function (origin, stanza) |
| 144 | if not origin.external_auth or origin.external_auth == "failed" then |
| 145 | s2s_initiate_dialback(origin); |
| 146 | return true; |
| 147 | end |
| 148 | end, 100); |
| 149 | |
| 150 | -- Offer dialback to incoming hosts |
| 151 | module:hook("s2s-stream-features", function (data) |
| 152 | data.features:tag("dialback", { xmlns='urn:xmpp:features:dialback' }):tag("optional"):up():up(); |
| 153 | end); |