X-Git-Url: https://git.enpas.org/?a=blobdiff_plain;f=core%2Fs2smanager.lua;h=fb532a731f70382ecfd28f773cad9efba36440a5;hb=4e9ad86f42f78e32d5a983b62277a0552c853a9c;hp=75bb5d3646296e3fe50f8e5a7545056a0550a967;hpb=d1f8a1a9eb85c87794145d4f269373569e4eaa1e;p=prosody.git diff --git a/core/s2smanager.lua b/core/s2smanager.lua index 75bb5d36..fb532a73 100644 --- a/core/s2smanager.lua +++ b/core/s2smanager.lua @@ -1,4 +1,4 @@ --- Prosody IM v0.4 +-- Prosody IM -- Copyright (C) 2008-2009 Matthew Wild -- Copyright (C) 2008-2009 Waqas Hussain -- @@ -27,6 +27,7 @@ local st = require "stanza"; local stanza = st.stanza; local nameprep = require "util.encodings".stringprep.nameprep; +local fire_event = require "core.eventmanager".fire_event; local uuid_gen = require "util.uuid".generate; local logger_init = require "util.logger".init; @@ -37,11 +38,14 @@ local sha256_hash = require "util.hashes".sha256; local dialback_secret = uuid_gen(); -local adns = require "net.adns"; - +local adns, dns = require "net.adns", require "net.dns"; +local config = require "core.configmanager"; +local connect_timeout = config.get("*", "core", "s2s_timeout") or 60; local dns_timeout = config.get("*", "core", "dns_timeout") or 60; +local max_dns_depth = config.get("*", "core", "dns_max_depth") or 3; incoming_s2s = {}; +_G.prosody.incoming_s2s = incoming_s2s; local incoming_s2s = incoming_s2s; module "s2smanager" @@ -78,12 +82,8 @@ function send_to_host(from_host, to_host, data) local host = hosts[from_host].s2sout[to_host]; if host then -- We have a connection to this host already - if host.type == "s2sout_unauthed" and data.name ~= "db:verify" and ((not data.xmlns) or data.xmlns == "jabber:client" or data.xmlns == "jabber:server") then + if host.type == "s2sout_unauthed" and (data.name ~= "db:verify" or not host.dialback_key) and ((not data.xmlns) or data.xmlns == "jabber:client" or data.xmlns == "jabber:server") then (host.log or log)("debug", "trying to send over unauthed s2sout to "..to_host); - if not host.notopen and not host.dialback_key and host.sends2s then - host.log("debug", "dialback had not been initiated"); - initiate_dialback(host); - end -- Queue stanza until we are able to send it if host.sendq then t_insert(host.sendq, {tostring(data), st.reply(data)}); @@ -106,6 +106,7 @@ function send_to_host(from_host, to_host, data) else log("debug", "opening a new outgoing connection for this stanza"); local host_session = new_outgoing(from_host, to_host); + -- Store in buffer host_session.sendq = { {tostring(data), st.reply(data)} }; log("debug", "stanza [%s] queued until connection complete", tostring(data.name)); @@ -126,13 +127,26 @@ function new_incoming(conn) end open_sessions = open_sessions + 1; local w, log = conn.write, logger_init("s2sin"..tostring(conn):match("[a-f0-9]+$")); - session.sends2s = function (t) log("debug", "sending: %s", tostring(t)); w(tostring(t)); end + session.log = log; + session.sends2s = function (t) log("debug", "sending: %s", (t.top_tag and t:top_tag()) or t:match("^([^>]*>?)"); w(conn, tostring(t)); end incoming_s2s[session] = true; + add_task(connect_timeout, function () + if session.conn ~= conn or + session.type == "s2sin" then + return; -- Ok, we're connect[ed|ing] + end + -- Not connected, need to close session and clean up + (session.log or log)("warn", "Destroying incomplete session %s->%s due to inactivity", + session.from_host or "(unknown)", session.to_host or "(unknown)"); + session:close("connection-timeout"); + end); return session; end function new_outgoing(from_host, to_host) - local host_session = { to_host = to_host, from_host = from_host, notopen = true, type = "s2sout_unauthed", direction = "outgoing" }; + local host_session = { to_host = to_host, from_host = from_host, host = from_host, + notopen = true, type = "s2sout_unauthed", direction = "outgoing" }; + hosts[from_host].s2sout[to_host] = host_session; local log; @@ -142,6 +156,7 @@ function new_outgoing(from_host, to_host) host_session.log = log; end + -- Kick the connection attempting machine attempt_connection(host_session); if not host_session.sends2s then @@ -169,10 +184,14 @@ function attempt_connection(host_session, err) local from_host, to_host = host_session.from_host, host_session.to_host; local connect_host, connect_port = idna_to_ascii(to_host), 5269; + if not connect_host then + return false; + end + if not err then -- This is our first attempt log("debug", "First attempt to connect to %s, starting with SRV lookup...", to_host); host_session.connecting = true; - local answer, handle; + local handle; handle = adns.lookup(function (answer) handle = nil; host_session.connecting = nil; @@ -195,7 +214,13 @@ function attempt_connection(host_session, err) log("debug", to_host.." has no SRV records, falling back to A"); end -- Try with SRV, or just the plain hostname if no SRV - return try_connect(host_session, connect_host, connect_port); + local ok, err = try_connect(host_session, connect_host, connect_port); + if not ok then + if not attempt_connection(host_session, err) then + -- No more attempts will be made + destroy_session(host_session); + end + end end, "_xmpp-server._tcp."..connect_host..".", "SRV"); -- Set handler for DNS timeout @@ -228,6 +253,47 @@ function attempt_connection(host_session, err) end function try_connect(host_session, connect_host, connect_port) + host_session.connecting = true; + local handle; + handle = adns.lookup(function (reply) + handle = nil; + host_session.connecting = nil; + + -- COMPAT: This is a compromise for all you CNAME-(ab)users :) + if not (reply and reply[#reply] and reply[#reply].a) then + local count = max_dns_depth; + reply = dns.peek(connect_host, "CNAME", "IN"); + while count > 0 and reply and reply[#reply] and not reply[#reply].a and reply[#reply].cname do + log("debug", "Looking up %s (DNS depth is %d)", tostring(reply[#reply].cname), count); + reply = dns.peek(reply[#reply].cname, "A", "IN") or dns.peek(reply[#reply].cname, "CNAME", "IN"); + count = count - 1; + end + end + -- end of CNAME resolving + + if reply and reply[#reply] and reply[#reply].a then + log("debug", "DNS reply for %s gives us %s", connect_host, reply[#reply].a); + return make_connect(host_session, reply[#reply].a, connect_port); + else + log("debug", "DNS lookup failed to get a response for %s", connect_host); + if not attempt_connection(host_session, "name resolution failed") then -- Retry if we can + log("debug", "No other records to try for %s - destroying", host_session.to_host); + destroy_session(host_session); -- End of the line, we can't + end + end + end, connect_host, "A", "IN"); + + -- Set handler for DNS timeout + add_task(dns_timeout, function () + if handle then + adns.cancel(handle, true); + end + end); + + return true; +end + +function make_connect(host_session, connect_host, connect_port) host_session.log("info", "Beginning new connection attempt to %s (%s:%d)", host_session.to_host, connect_host, connect_port); -- Ok, we're going to try to connect @@ -239,7 +305,7 @@ function try_connect(host_session, connect_host, connect_port) local success, err = conn:connect(connect_host, connect_port); if not success and err ~= "timeout" then log("warn", "s2s connect() to %s (%s:%d) failed: %s", host_session.to_host, connect_host, connect_port, err); - return false; + return false, err; end local cl = connlisteners_get("xmppserver"); @@ -250,11 +316,22 @@ function try_connect(host_session, connect_host, connect_port) -- otherwise it will assume it is a new incoming connection cl.register_outgoing(conn, host_session); - local w = conn.write; - host_session.sends2s = function (t) log("debug", "sending: %s", tostring(t)); w(tostring(t)); end + local w, log = conn.write, host_session.log; + host_session.sends2s = function (t) log("debug", "sending: %s", (t.top_tag and t:top_tag()) or t:match("^[^>]*>?")); w(conn, tostring(t)); end - conn.write(format([[]], from_host, to_host)); + conn:write(format([[]], from_host, to_host)); log("debug", "Connection attempt in progress..."); + add_task(connect_timeout, function () + if host_session.conn ~= conn or + host_session.type == "s2sout" or + host_session.connecting then + return; -- Ok, we're connect[ed|ing] + end + -- Not connected, need to close session and clean up + (host_session.log or log)("warn", "Destroying incomplete session %s->%s due to inactivity", + host_session.from_host or "(unknown)", host_session.to_host or "(unknown)"); + host_session:close("connection-timeout"); + end); return true; end @@ -262,10 +339,16 @@ function streamopened(session, attr) local send = session.sends2s; -- TODO: #29: SASL/TLS on s2s streams - session.version = 0; --tonumber(attr.version) or 0; + session.version = tonumber(attr.version) or 0; + + if session.secure == false then + session.secure = true; + end if session.version >= 1.0 and not (attr.to and attr.from) then - log("warn", (session.to_host or "(unknown)").." failed to specify 'to' or 'from' hostname as per RFC"); + + (session.log or log)("warn", "Remote of stream "..(session.from_host or "(unknown)").."->"..(session.to_host or "(unknown)") + .." failed to specify to (%s) and/or from (%s) hostname as per RFC", tostring(attr.to), tostring(attr.from)); end if session.direction == "incoming" then @@ -277,15 +360,23 @@ function streamopened(session, attr) (session.log or log)("debug", "incoming s2s received "); send(""); send(stanza("stream:stream", { xmlns='jabber:server', ["xmlns:db"]='jabber:server:dialback', - ["xmlns:stream"]='http://etherx.jabber.org/streams', id=session.streamid, from=session.to_host }):top_tag()); + ["xmlns:stream"]='http://etherx.jabber.org/streams', id=session.streamid, from=session.to_host, version=(session.version > 0 and "1.0" or nil) }):top_tag()); if session.to_host and not hosts[session.to_host] then -- Attempting to connect to a host we don't serve session:close({ condition = "host-unknown"; text = "This host does not serve "..session.to_host }); return; end if session.version >= 1.0 then - send(st.stanza("stream:features") - :tag("dialback", { xmlns='urn:xmpp:features:dialback' }):tag("optional"):up():up()); + local features = st.stanza("stream:features"); + + if session.to_host then + hosts[session.to_host].events.fire_event("s2s-stream-features", { session = session, features = features }); + else + (session.log or log)("warn", "No 'to' on stream header from %s means we can't offer any features", session.from_host or "unknown host"); + end + + log("debug", "Sending stream features: %s", tostring(features)); + send(features); end elseif session.direction == "outgoing" then -- If we are just using the connection for verifying dialback keys, we won't try and auth it @@ -306,10 +397,14 @@ function streamopened(session, attr) end session.send_buffer = nil; - if not session.dialback_verifying then - initiate_dialback(session); - else - mark_connected(session); + -- If server is pre-1.0, don't wait for features, just do dialback + if session.version < 1.0 then + if not session.dialback_verifying then + log("debug", "Initiating dialback..."); + initiate_dialback(session); + else + mark_connected(session); + end end end @@ -359,6 +454,7 @@ function make_authenticated(session, host) return true; end +-- Stream is authorised, and ready for normal stanzas function mark_connected(session) local sendq, send = session.sendq, session.sends2s;