Merge with Maranda
authorMatthew Wild <mwild1@gmail.com>
Sun, 22 Jul 2012 17:52:20 +0000 (18:52 +0100)
committerMatthew Wild <mwild1@gmail.com>
Sun, 22 Jul 2012 17:52:20 +0000 (18:52 +0100)
14 files changed:
core/moduleapi.lua
core/sessionmanager.lua
net/http.lua
net/server_event.lua
net/server_select.lua
plugins/adhoc/adhoc.lib.lua
plugins/mod_admin_adhoc.lua
plugins/mod_admin_telnet.lua
plugins/mod_c2s.lua
plugins/mod_iq.lua
plugins/mod_message.lua
plugins/mod_presence.lua
plugins/mod_s2s/mod_s2s.lua
plugins/mod_s2s/s2sout.lib.lua

index 24d29dfeae3ae30591c71698e0a57ddc546badec..572dc1797de9322c40833fe75458b1650197ea3f 100644 (file)
@@ -14,8 +14,6 @@ local logger = require "util.logger";
 local pluginloader = require "util.pluginloader";
 local timer = require "util.timer";
 
-local multitable_new = require "util.multitable".new;
-
 local t_insert, t_remove, t_concat = table.insert, table.remove, table.concat;
 local error, setmetatable, setfenv, type = error, setmetatable, setfenv, type;
 local ipairs, pairs, select, unpack = ipairs, pairs, select, unpack;
@@ -339,4 +337,4 @@ function api:load_resource(path, mode)
        return io.open(path, mode);
 end
 
-return api;
+return api;
\ No newline at end of file
index 37c1626ad3c6fdadaaf0287819877206076a8e04..131c29f7f14a38feb42f9b4002f66e4a2ac29b37 100644 (file)
@@ -82,7 +82,7 @@ function retire_session(session)
                end
        end
 
-       function session.send(data) log("debug", "Discarding data sent to resting session: %s", tostring(data)); end
+       function session.send(data) log("debug", "Discarding data sent to resting session: %s", tostring(data)); return false; end
        function session.data(data) log("debug", "Discarding data received from resting session: %s", tostring(data)); end
        return setmetatable(session, resting_session);
 end
index f2061e00ebe114db102e0cc9f5dfa0565e01b656..273eee090535196ebbf06309dbf832da6507b10b 100644 (file)
@@ -7,7 +7,7 @@
 --
 
 local socket = require "socket"
-local mime = require "mime"
+local b64 = require "util.encodings".base64.encode;
 local url = require "socket.url"
 local httpstream_new = require "util.httpstream".new;
 
@@ -154,7 +154,7 @@ function request(u, ex, callback)
        };
        
        if req.userinfo then
-               headers["Authorization"] = "Basic "..mime.b64(req.userinfo);
+               headers["Authorization"] = "Basic "..b64(req.userinfo);
        end
 
        if ex then
@@ -203,7 +203,6 @@ function destroy_request(request)
        if request.conn then
                request.conn = nil;
                request.handler:close()
-               listener.ondisconnect(request.handler, "closed");
        end
 end
 
index 03a7708c24841d576d8f75111682f2e6657d3af8..3c4185af96b07e2a7ecbd26bba16a988787ece16 100644 (file)
@@ -249,7 +249,7 @@ do
                        return true
        end
        function interface_mt:_destroy()  -- close this interface + events and call last listener
-                       debug( "closing client with id:", self.id )
+                       debug( "closing client with id:", self.id, self.fatalerror )
                        self:_lock( true, true, true )  -- first of all, lock the interface to avoid further actions
                        local _
                        _ = self.eventread and self.eventread:close( )  -- close events; this must be called outside of the event callbacks!
@@ -328,22 +328,22 @@ do
                end
                return true
        end
-       function interface_mt:close(now)
+       function interface_mt:close()
                if self.nointerface then return nil, "locked"; end
                debug( "try to close client connection with id:", self.id )
                if self.type == "client" then
                        self.fatalerror = "client to close"
-                       if ( not self.eventwrite ) or now then  -- try to close immediately
-                               self:_lock( true, true, true )
-                               self:_close()
-                               return true
-                       else  -- wait for incomplete write request
+                       if self.eventwrite then -- wait for incomplete write request
                                self:_lock( true, true, false )
                                debug "closing delayed until writebuffer is empty"
                                return nil, "writebuffer not empty, waiting"
+                       else -- close now
+                               self:_lock( true, true, true )
+                               self:_close()
+                               return true
                        end
                else
-                       debug( "try to close server with id:", tostring(self.id), "args:", tostring(now) )
+                       debug( "try to close server with id:", tostring(self.id))
                        self.fatalerror = "server to close"
                        self:_lock( true )
                        self:_close( 0 )  -- add new event to remove the server interface
index de637f705ca155315c6fa2e19791692741121ab6..c0f8742e1adf2eb425f15bd7ab4dc35107b27fcf 100644 (file)
@@ -314,22 +314,28 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport
                end
                return false, "setoption not implemented";
        end
-       handler.close = function( self, forced )
+       handler.force_close = function ( self )
+               if bufferqueuelen ~= 0 then
+                       out_put("discarding unwritten data for ", tostring(ip), ":", tostring(clientport))
+                       for i = bufferqueuelen, 1, -1 do
+                               bufferqueue[i] = nil;
+                       end
+                       bufferqueuelen = 0;
+               end
+               return self:close();
+       end
+       handler.close = function( self )
                if not handler then return true; end
                _readlistlen = removesocket( _readlist, socket, _readlistlen )
                _readtimes[ handler ] = nil
                if bufferqueuelen ~= 0 then
-                       if not ( forced or fatalerror ) then
-                               handler.sendbuffer( )
-                               if bufferqueuelen ~= 0 then -- try again...
-                                       if handler then
-                                               handler.write = nil -- ... but no further writing allowed
-                                       end
-                                       toclose = true
-                                       return false
+                       handler.sendbuffer() -- Try now to send any outstanding data
+                       if bufferqueuelen ~= 0 then -- Still not empty, so we'll try again later
+                               if handler then
+                                       handler.write = nil -- ... but no further writing allowed
                                end
-                       else
-                               send( socket, table_concat( bufferqueue, "", 1, bufferqueuelen ), 1, bufferlen )        -- forced send
+                               toclose = true
+                               return false
                        end
                end
                if socket then
@@ -347,7 +353,7 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport
                        local _handler = handler;
                        handler = nil
                        if disconnect then
-                               disconnect(_handler, "closed");
+                               disconnect(_handler, false);
                        end
                end
                if server then
@@ -480,7 +486,7 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport
                        _ = _cleanqueue and clean( bufferqueue )
                        --out_put( "server.lua: sended '", buffer, "', bytes: ", tostring(succ), ", error: ", tostring(err), ", part: ", tostring(byte), ", to: ", tostring(ip), ":", tostring(clientport) )
                else
-                       succ, err, count = false, "closed", 0;
+                       succ, err, count = false, "unexpected close", 0;
                end
                if succ then    -- sending succesful
                        bufferqueuelen = 0
@@ -491,7 +497,7 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport
                                drain(handler)
                        end
                        _ = needtls and handler:starttls(nil)
-                       _ = toclose and handler:close( )
+                       _ = toclose and handler:force_close( )
                        return true
                elseif byte and ( err == "timeout" or err == "wantwrite" ) then -- want write
                        buffer = string_sub( buffer, byte + 1, bufferlen ) -- new buffer
@@ -504,7 +510,7 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport
                        out_put( "server.lua: client ", tostring(ip), ":", tostring(clientport), " write error: ", tostring(err) )
                        fatalerror = true
                        disconnect( handler, err )
-                       _ = handler and handler:close( )
+                       _ = handler and handler:force_close( )
                        return false
                end
        end
@@ -547,7 +553,7 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport
                                end
                                out_put( "server.lua: ssl handshake error: ", tostring(err or "handshake too long") )
                                disconnect( handler, "ssl handshake failed" )
-                               _ = handler and handler:close( true )    -- forced disconnect
+                               _ = handler and handler:force_close()
                                return false, err -- handshake failed
                        end
                )
@@ -810,7 +816,7 @@ loop = function(once) -- this is the main loop of the program
                end
                for handler, err in pairs( _closelist ) do
                        handler.disconnect( )( handler, err )
-                       handler:close( true )    -- forced disconnect
+                       handler:force_close()    -- forced disconnect
                end
                clean( _closelist )
                _currenttime = luasocket_gettime( )
@@ -896,7 +902,7 @@ addtimer( function( )
                                if os_difftime( _currenttime - timestamp ) > _sendtimeout then
                                        --_writetimes[ handler ] = nil
                                        handler.disconnect( )( handler, "send timeout" )
-                                       handler:close( true )    -- forced disconnect
+                                       handler:force_close()    -- forced disconnect
                                end
                        end
                        for handler, timestamp in pairs( _readtimes ) do
index eff3caff30397b6898d9636a0be8c15c0a5ecc18..f951017233e661fb843d015fbcb8788ba87ff5e1 100644 (file)
@@ -12,7 +12,7 @@ local states = {}
 
 local _M = {};
 
-function _cmdtag(desc, status, sessionid, action)
+local function _cmdtag(desc, status, sessionid, action)
        local cmd = st.stanza("command", { xmlns = xmlns_cmd, node = desc.node, status = status });
        if sessionid then cmd.attr.sessionid = sessionid; end
        if action then cmd.attr.action = action; end
@@ -35,6 +35,7 @@ function _M.handle_cmd(command, origin, stanza)
        local data, state = command:handler(dataIn, states[sessionid]);
        states[sessionid] = state;
        local stanza = st.reply(stanza);
+       local cmdtag;
        if data.status == "completed" then
                states[sessionid] = nil;
                cmdtag = command:cmdtag("completed", sessionid);
index efb7f72bea7a2bf6493cac42194ac2bd7fb7192f..ee89d84fbb0da8be3153db98fb9ddea81ba458b4 100644 (file)
@@ -469,7 +469,6 @@ function load_module_handler(self, data, state)
                        '". Error was: "'..tostring(err or "<unspecified>")..'"' } };
                end
        else
-               local modules = array.collect(keys(hosts[data.to].modules)):sort();
                return { status = "executing", form = layout }, "executing";
        end
 end
index d378edf4ca18208b381d329db8529d751fc6fbe3..c5e423da17cdfb4ab680426cb84e4832880b8d97 100644 (file)
@@ -76,22 +76,22 @@ end
 function console_listener.onincoming(conn, data)
        local session = sessions[conn];
 
-       -- Handle data
-       (function(session, data)
+       -- Handle data (loop allows us to break to add \0 after response)
+       repeat
                local useglobalenv;
-               
+
                if data:match("^>") then
                        data = data:gsub("^>", "");
                        useglobalenv = true;
                elseif data == "\004" then
                        commands["bye"](session, data);
-                       return;
+                       break;
                else
                        local command = data:lower();
                        command = data:match("^%w+") or data:match("%p");
                        if commands[command] then
                                commands[command](session, data);
-                               return;
+                               break;
                        end
                end
 
@@ -106,7 +106,7 @@ function console_listener.onincoming(conn, data)
                                err = err:gsub("^:%d+: ", "");
                                err = err:gsub("'<eof>'", "the end of the line");
                                session.print("Sorry, I couldn't understand that... "..err);
-                               return;
+                               break;
                        end
                end
                
@@ -116,26 +116,26 @@ function console_listener.onincoming(conn, data)
                
                if not (ranok or message or useglobalenv) and commands[data:lower()] then
                        commands[data:lower()](session, data);
-                       return;
+                       break;
                end
                
                if not ranok then
                        session.print("Fatal error while running command, it did not complete");
                        session.print("Error: "..taskok);
-                       return;
+                       break;
                end
                
                if not message then
                        session.print("Result: "..tostring(taskok));
-                       return;
+                       break;
                elseif (not taskok) and message then
                        session.print("Command completed with a problem");
                        session.print("Message: "..tostring(message));
-                       return;
+                       break;
                end
                
                session.print("OK: "..tostring(message));
-       end)(session, data);
+       until true
        
        session.send(string.char(0));
 end
@@ -187,6 +187,7 @@ function commands.help(session, data)
                print [[s2s - Commands to manage sessions between this server and others]]
                print [[module - Commands to load/reload/unload modules/plugins]]
                print [[host - Commands to activate, deactivate and list virtual hosts]]
+               print [[user - Commands to create and delete users, and change their passwords]]
                print [[server - Uptime, version, shutting down, etc.]]
                print [[config - Reloading the configuration, etc.]]
                print [[console - Help regarding the console itself]]
@@ -208,6 +209,10 @@ function commands.help(session, data)
                print [[host:activate(hostname) - Activates the specified host]]
                print [[host:deactivate(hostname) - Disconnects all clients on this host and deactivates]]
                print [[host:list() - List the currently-activated hosts]]
+       elseif section == "user" then
+               print [[user:create(jid, password) - Create the specified user account]]
+               print [[user:password(jid, password) - Set the password for the specified user account]]
+               print [[user:delete(jid, password) - Permanently remove the specified user account]]
        elseif section == "server" then
                print [[server:version() - Show the server's version number]]
                print [[server:uptime() - Show how long the server has been running]]
@@ -891,6 +896,37 @@ function def_env.muc:room(room_jid)
        return setmetatable({ room = room_obj }, console_room_mt);
 end
 
+def_env.user = {};
+function def_env.user:create(jid, password)
+       local username, host = jid_split(jid);
+       local ok, err = um.create_user(username, password, host);
+       if ok then
+               return true, "User created";
+       else
+               return nil, "Could not create user: "..err;
+       end
+end
+
+function def_env.user:delete(jid)
+       local username, host = jid_split(jid);
+       local ok, err = um.delete_user(username, host);
+       if ok then
+               return true, "User deleted";
+       else
+               return nil, "Could not delete user: "..err;
+       end
+end
+
+function def_env.user:passwd(jid, password)
+       local username, host = jid_split(jid);
+       local ok, err = um.set_password(username, password, host);
+       if ok then
+               return true, "User created";
+       else
+               return nil, "Could not change password for user: "..err;
+       end
+end
+
 -------------
 
 function printbanner(session)
index 55c53e2de905a491dce7b4c80af60c155cf2c141..75a6f689782d5779b9ad436bd57490f05e0e7224 100644 (file)
@@ -24,6 +24,7 @@ local xmlns_xmpp_streams = "urn:ietf:params:xml:ns:xmpp-streams";
 local log = module._log;
 
 local c2s_timeout = module:get_option_number("c2s_timeout");
+local stream_close_timeout = module:get_option_number("c2s_close_timeout", 5);
 local opt_keepalives = module:get_option_boolean("tcp_keepalives", false);
 
 local sessions = module:shared("sessions");
@@ -143,8 +144,27 @@ local function session_close(session, reason)
                        end
                end
                session.send("</stream:stream>");
-               session.conn:close();
-               listener.ondisconnect(session.conn, (reason and (reason.text or reason.condition)) or reason or "session closed");
+               
+               function session.send() return false; end
+               
+               local reason = (reason and (reason.text or reason.condition)) or reason or "session closed";
+               session.log("info", "c2s stream for %s closed: %s", session.full_jid or ("<"..session.ip..">"), reason);
+
+               -- Authenticated incoming stream may still be sending us stanzas, so wait for </stream:stream> from remote
+               local conn = session.conn;
+               if reason == "session closed" and not session.notopen and session.type == "c2s" then
+                       -- Grace time to process data from authenticated cleanly-closed stream
+                       add_task(stream_close_timeout, function ()
+                               if not session.destroyed then
+                                       session.log("warn", "Failed to receive a stream close response, closing connection anyway...");
+                                       sm_destroy_session(session, reason);
+                                       conn:close();
+                               end
+                       end);
+               else
+                       sm_destroy_session(session, reason);
+                       conn:close();
+               end
        end
 end
 
@@ -208,10 +228,9 @@ end
 function listener.ondisconnect(conn, err)
        local session = sessions[conn];
        if session then
-               (session.log or log)("info", "Client disconnected: %s", err);
+               (session.log or log)("info", "Client disconnected: %s", err or "connection closed");
                sm_destroy_session(session, err);
                sessions[conn]  = nil;
-               session = nil;
        end
 end
 
index 6412ad114f79f4d7977aeb90a722e66b6a3dc337..8044a533aeadec26d3d23e1d3738b16f413c8dc8 100644 (file)
@@ -17,10 +17,7 @@ if module:get_host_type() == "local" then
                local origin, stanza = data.origin, data.stanza;
 
                local session = full_sessions[stanza.attr.to];
-               if session then
-                       -- TODO fire post processing event
-                       session.send(stanza);
-               else -- resource not online
+               if not (session and session.send(stanza)) then
                        if stanza.attr.type == "get" or stanza.attr.type == "set" then
                                origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
                        end
index ebff2fe775984e0d4d3dd6511b2a0aac7a6ee44e..0b0ad8e4985a2a5d4d057081a208b8bd98325b60 100644 (file)
@@ -35,10 +35,13 @@ local function process_to_bare(bare, origin, stanza)
                if user then -- some resources are connected
                        local recipients = user.top_resources;
                        if recipients then
+                               local sent;
                                for i=1,#recipients do
-                                       recipients[i].send(stanza);
+                                       sent = recipients[i].send(stanza) or sent;
+                               end
+                               if sent then
+                                       return true;
                                end
-                               return true;
                        end
                end
                -- no resources are online
@@ -65,9 +68,7 @@ module:hook("message/full", function(data)
        local origin, stanza = data.origin, data.stanza;
        
        local session = full_sessions[stanza.attr.to];
-       if session then
-               -- TODO fire post processing event
-               session.send(stanza);
+       if session and session.send(stanza) then
                return true;
        else -- resource not online
                return process_to_bare(jid_bare(stanza.attr.to), origin, stanza);
index 6d039d83f9862bb1e9a7dc8861c9e778d64a1d5c..09a6f9f264ee6efcd2720b056666654b83f7dc8d 100644 (file)
@@ -352,13 +352,15 @@ module:hook("resource-unbind", function(event)
        -- Send unavailable presence
        if session.presence then
                local pres = st.presence{ type = "unavailable" };
-               if not(err) or err == "closed" then err = "connection closed"; end
-               pres:tag("status"):text("Disconnected: "..err):up();
+               if err then
+                       pres:tag("status"):text("Disconnected: "..err):up();
+               end
                session:dispatch_stanza(pres);
        elseif session.directed then
                local pres = st.presence{ type = "unavailable", from = session.full_jid };
-               if not(err) or err == "closed" then err = "connection closed"; end
-               pres:tag("status"):text("Disconnected: "..err):up();
+               if err then
+                       pres:tag("status"):text("Disconnected: "..err):up();
+               end
                for jid in pairs(session.directed) do
                        pres.attr.to = jid;
                        core_post_stanza(session, pres, true);
index f6c206065a47e6a01cfe3fd0bf48d4de42e54212..f686fcfb6dd29605f5f68f3c5dbdf84717438530 100644 (file)
@@ -31,6 +31,7 @@ local cert_verify_identity = require "util.x509".verify_identity;
 local s2sout = module:require("s2sout");
 
 local connect_timeout = module:get_option_number("s2s_timeout", 60);
+local stream_close_timeout = module:get_option_number("s2s_close_timeout", 5);
 
 local sessions = module:shared("sessions");
 
@@ -97,9 +98,10 @@ function route_to_existing_session(event)
                                log("error", "WARNING! This might, possibly, be a bug, but it might not...");
                                log("error", "We are going to send from %s instead of %s", tostring(host.from_host), tostring(from_host));
                        end
-                       host.sends2s(stanza);
-                       host.log("debug", "stanza sent over "..host.type);
-                       return true;
+                       if host.sends2s(stanza) then
+                               host.log("debug", "stanza sent over "..host.type);
+                               return true;
+                       end
                end
        end
 end
@@ -291,18 +293,6 @@ function stream_callbacks.streamclosed(session)
        session:close();
 end
 
-function stream_callbacks.streamdisconnected(session, err)
-       if err and err ~= "closed" and session.direction == "outgoing" and session.notopen then
-               (session.log or log)("debug", "s2s connection attempt failed: %s", err);
-               if s2sout.attempt_connection(session, err) then
-                       (session.log or log)("debug", "...so we're going to try another target");
-                       return true; -- Session lives for now
-               end
-       end
-       (session.log or log)("info", "s2s disconnected: %s->%s (%s)", tostring(session.from_host), tostring(session.to_host), tostring(err or "closed"));
-       s2s_destroy_session(session, err);
-end
-
 function stream_callbacks.error(session, error, data)
        if error == "no-stream" then
                session:close("invalid-namespace");
@@ -374,11 +364,26 @@ local function session_close(session, reason, remote_reason)
                        end
                end
                session.sends2s("</stream:stream>");
-               if session.notopen or not session.conn:close() then
-                       session.conn:close(true); -- Force FIXME: timer?
+
+               function session.sends2s() return false; end
+               
+               local reason = remote_reason or (reason and (reason.text or reason.condition)) or reason or "stream closed";
+               session.log("info", "%s s2s stream %s->%s closed: %s", session.direction, session.from_host or "(unknown host)", session.to_host or "(unknown host)", reason);
+               
+               -- Authenticated incoming stream may still be sending us stanzas, so wait for </stream:stream> from remote
+               local conn = session.conn;
+               if not session.notopen and session.type == "s2sin" then
+                       add_task(stream_close_timeout, function ()
+                               if not session.destroyed then
+                                       session.log("warn", "Failed to receive a stream close response, closing connection anyway...");
+                                       s2s_destroy_session(session, reason);
+                                       conn:close();
+                               end
+                       end);
+               else
+                       s2s_destroy_session(session, reason);
+                       conn:close(); -- Close immediately, as this is an outgoing connection or is not authed
                end
-               session.conn:close();
-               listener.ondisconnect(session.conn, remote_reason or (reason and (reason.text or reason.condition)) or reason or "stream closed");
        end
 end
 
@@ -413,11 +418,9 @@ local function initialize_session(session)
                return handlestanza(session, stanza);
        end
 
-       local conn = session.conn;
        add_task(connect_timeout, function ()
-               if session.conn ~= conn or session.connecting
-               or session.type == "s2sin" or session.type == "s2sout" then
-                       return; -- Ok, we're connect[ed|ing]
+               if session.type == "s2sin" or session.type == "s2sout" then
+                       return; -- Ok, we're connected
                end
                -- Not connected, need to close session and clean up
                (session.log or log)("debug", "Destroying incomplete session %s->%s due to inactivity",
@@ -474,11 +477,17 @@ end
 function listener.ondisconnect(conn, err)
        local session = sessions[conn];
        if session then
-               if stream_callbacks.streamdisconnected(session, err) then
-                       return; -- Connection lives, for now
+               if err and session.direction == "outgoing" and session.notopen then
+                       (session.log or log)("debug", "s2s connection attempt failed: %s", err);
+                       if s2sout.attempt_connection(session, err) then
+                               (session.log or log)("debug", "...so we're going to try another target");
+                               return; -- Session lives for now
+                       end
                end
+               (session.log or log)("debug", "s2s disconnected: %s->%s (%s)", tostring(session.from_host), tostring(session.to_host), tostring(err or "connection closed"));
+               s2s_destroy_session(session, err);
+               sessions[conn] = nil;
        end
-       sessions[conn] = nil;
 end
 
 function listener.register_outgoing(conn, session)
index 48a49036b43b577652a1804352d027b87f45fac0..d2c6023e65c556418fe45d2c665f15f7f964d83e 100644 (file)
@@ -170,92 +170,91 @@ function s2sout.try_connect(host_session, connect_host, connect_port, err)
                local IPs = {};
                host_session.ip_hosts = IPs;
                local handle4, handle6;
-               local has_other = false;
+               local have_other_result = not(has_ipv4) or not(has_ipv6) or false;
 
                if has_ipv4 then
-               handle4 = adns.lookup(function (reply, err)
-                       handle4 = 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;
+                       handle4 = adns.lookup(function (reply, err)
+                               handle4 = 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
-                       -- end of CNAME resolving
+                               -- end of CNAME resolving
 
-                       if reply and reply[#reply] and reply[#reply].a then
-                               for _, ip in ipairs(reply) do
-                                       log("debug", "DNS reply for %s gives us %s", connect_host, ip.a);
-                                       IPs[#IPs+1] = new_ip(ip.a, "IPv4");
+                               if reply and reply[#reply] and reply[#reply].a then
+                                       for _, ip in ipairs(reply) do
+                                               log("debug", "DNS reply for %s gives us %s", connect_host, ip.a);
+                                               IPs[#IPs+1] = new_ip(ip.a, "IPv4");
+                                       end
                                end
-                       end
 
-                       if has_other then
-                               if #IPs > 0 then
-                                       rfc3484_dest(host_session.ip_hosts, sources);
-                                       for i = 1, #IPs do
-                                               IPs[i] = {ip = IPs[i], port = connect_port};
+                               if have_other_result then
+                                       if #IPs > 0 then
+                                               rfc3484_dest(host_session.ip_hosts, sources);
+                                               for i = 1, #IPs do
+                                                       IPs[i] = {ip = IPs[i], port = connect_port};
+                                               end
+                                               host_session.ip_choice = 0;
+                                               s2sout.try_next_ip(host_session);
+                                       else
+                                               log("debug", "DNS lookup failed to get a response for %s", connect_host);
+                                               host_session.ip_hosts = nil;
+                                               if not s2sout.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);
+                                                       err = err and (": "..err) or "";
+                                                       s2s_destroy_session(host_session, "DNS resolution failed"..err); -- End of the line, we can't
+                                               end
                                        end
-                                       host_session.ip_choice = 0;
-                                       s2sout.try_next_ip(host_session);
                                else
-                                       log("debug", "DNS lookup failed to get a response for %s", connect_host);
-                                       host_session.ip_hosts = nil;
-                                       if not s2sout.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);
-                                               err = err and (": "..err) or "";
-                                               s2s_destroy_session(host_session, "DNS resolution failed"..err); -- End of the line, we can't
-                                       end
+                                       have_other_result = true;
                                end
-                       else
-                               has_other = true;
-                       end
-               end, connect_host, "A", "IN");
+                       end, connect_host, "A", "IN");
                else
-                       has_other = true;
+                       have_other_result = true;
                end
 
                if has_ipv6 then
-               handle6 = adns.lookup(function (reply, err)
-                       handle6 = nil;
+                       handle6 = adns.lookup(function (reply, err)
+                               handle6 = nil;
 
-                       if reply and reply[#reply] and reply[#reply].aaaa then
-                               for _, ip in ipairs(reply) do
-                                       log("debug", "DNS reply for %s gives us %s", connect_host, ip.aaaa);
-                                       IPs[#IPs+1] = new_ip(ip.aaaa, "IPv6");
+                               if reply and reply[#reply] and reply[#reply].aaaa then
+                                       for _, ip in ipairs(reply) do
+                                               log("debug", "DNS reply for %s gives us %s", connect_host, ip.aaaa);
+                                               IPs[#IPs+1] = new_ip(ip.aaaa, "IPv6");
+                                       end
                                end
-                       end
 
-                       if has_other then
-                               if #IPs > 0 then
-                                       rfc3484_dest(host_session.ip_hosts, sources);
-                                       for i = 1, #IPs do
-                                               IPs[i] = {ip = IPs[i], port = connect_port};
+                               if have_other_result then
+                                       if #IPs > 0 then
+                                               rfc3484_dest(host_session.ip_hosts, sources);
+                                               for i = 1, #IPs do
+                                                       IPs[i] = {ip = IPs[i], port = connect_port};
+                                               end
+                                               host_session.ip_choice = 0;
+                                               s2sout.try_next_ip(host_session);
+                                       else
+                                               log("debug", "DNS lookup failed to get a response for %s", connect_host);
+                                               host_session.ip_hosts = nil;
+                                               if not s2sout.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);
+                                                       err = err and (": "..err) or "";
+                                                       s2s_destroy_session(host_session, "DNS resolution failed"..err); -- End of the line, we can't
+                                               end
                                        end
-                                       host_session.ip_choice = 0;
-                                       s2sout.try_next_ip(host_session);
                                else
-                                       log("debug", "DNS lookup failed to get a response for %s", connect_host);
-                                       host_session.ip_hosts = nil;
-                                       if not s2sout.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);
-                                               err = err and (": "..err) or "";
-                                               s2s_destroy_session(host_session, "DNS resolution failed"..err); -- End of the line, we can't
-                                       end
+                                       have_other_result = true;
                                end
-                       else
-                               has_other = true;
-                       end
-               end, connect_host, "AAAA", "IN");
+                       end, connect_host, "AAAA", "IN");
                else
-                       has_other = true;
+                       have_other_result = true;
                end
-
                return true;
        elseif host_session.ip_hosts and #host_session.ip_hosts > host_session.ip_choice then -- Not our first attempt, and we also have IPs left to try
                s2sout.try_next_ip(host_session);