Merge 0.9->0.10
[prosody.git] / plugins / mod_c2s.lua
index fbac12cdd41471e5d82c2d0f7e3e541206de6e2c..2829d5fdb61fe221d59f22d65d618abcf8c5ee68 100644 (file)
@@ -18,8 +18,6 @@ local uuid_generate = require "util.uuid".generate;
 
 local xpcall, tostring, type = xpcall, tostring, type;
 local traceback = debug.traceback;
-local t_insert, t_remove = table.insert, table.remove;
-local co_running, co_resume = coroutine.running, coroutine.resume;
 
 local xmlns_xmpp_streams = "urn:ietf:params:xml:ns:xmpp-streams";
 
@@ -29,6 +27,8 @@ 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("c2s_tcp_keepalives", module:get_option_boolean("tcp_keepalives", true));
 
+local measure_connections = module:measure("connections", "counter");
+
 local sessions = module:shared("sessions");
 local core_process_stanza = prosody.core_process_stanza;
 local hosts = prosody.hosts;
@@ -38,7 +38,6 @@ local listener = {};
 
 --- Stream events handlers
 local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'};
-local default_stream_attr = { ["xmlns:stream"] = "http://etherx.jabber.org/streams", xmlns = stream_callbacks.default_ns, version = "1.0", id = "" };
 
 function stream_callbacks.streamopened(session, attr)
        local send = session.send;
@@ -58,9 +57,7 @@ function stream_callbacks.streamopened(session, attr)
                return;
        end
 
-       send("<?xml version='1.0'?>"..st.stanza("stream:stream", {
-               xmlns = 'jabber:client', ["xmlns:stream"] = 'http://etherx.jabber.org/streams';
-               id = session.streamid, from = session.host, version = '1.0', ["xml:lang"] = 'en' }):top_tag());
+       session:open_stream();
 
        (session.log or log)("debug", "Sent reply <stream:stream> to client");
        session.notopen = nil;
@@ -69,12 +66,12 @@ function stream_callbacks.streamopened(session, attr)
        -- since we now have a new stream header, session is secured
        if session.secure == false then
                session.secure = true;
+               session.encrypted = true;
 
                local sock = session.conn:socket();
                if sock.info then
                        local info = sock:info();
-                       (session.log or log)("info", "Stream encrypted (%s) with %s, authenticated with %s and exchanged keys with %s",
-                               info.protocol, info.encryption, info.authentication, info.key);
+                       (session.log or log)("info", "Stream encrypted (%s with %s)", info.protocol, info.cipher);
                        session.compressed = info.compression;
                else
                        (session.log or log)("info", "Stream encrypted");
@@ -84,7 +81,12 @@ function stream_callbacks.streamopened(session, attr)
 
        local features = st.stanza("stream:features");
        hosts[session.host].events.fire_event("stream-features", { origin = session, features = features });
-       send(features);
+       if features.tags[1] or session.full_jid then
+               send(features);
+       else
+               (session.log or log)("warn", "No features to offer");
+               session:close{ condition = "undefined-condition", text = "No features to proceed with" };
+       end
 end
 
 function stream_callbacks.streamclosed(session)
@@ -94,7 +96,7 @@ end
 
 function stream_callbacks.error(session, error, data)
        if error == "no-stream" then
-               session.log("debug", "Invalid opening stream header");
+               session.log("debug", "Invalid opening stream header (%s)", (data:gsub("^([^\1]+)\1", "{%1}")));
                session:close("invalid-namespace");
        elseif error == "parse-error" then
                (session.log or log)("debug", "Client XML parse error: %s", tostring(data));
@@ -122,7 +124,9 @@ end
 local function handleerr(err) log("error", "Traceback[c2s]: %s", traceback(tostring(err), 2)); end
 function stream_callbacks.handlestanza(session, stanza)
        stanza = session.filter("stanzas/in", stanza);
-       t_insert(session.pending_stanzas, stanza);
+       if stanza then
+               return xpcall(function () return core_process_stanza(session, stanza) end, handleerr);
+       end
 end
 
 --- Session methods
@@ -130,8 +134,7 @@ local function session_close(session, reason)
        local log = session.log or log;
        if session.conn then
                if session.notopen then
-                       session.send("<?xml version='1.0'?>");
-                       session.send(st.stanza("stream:stream", default_stream_attr):top_tag());
+                       session:open_stream();
                end
                if reason then -- nil == no err, initiated by us, false == initiated by client
                        local stream_error = st.stanza("stream:error");
@@ -159,7 +162,7 @@ local function session_close(session, reason)
                function session.send() return false; end
 
                local reason = (reason and (reason.name or reason.text or reason.condition)) or reason;
-               session.log("info", "c2s stream for %s closed: %s", session.full_jid or ("<"..session.ip..">"), reason or "session closed");
+               session.log("debug", "c2s stream for %s closed: %s", session.full_jid or ("<"..session.ip..">"), reason or "session closed");
 
                -- Authenticated incoming stream may still be sending us stanzas, so wait for </stream:stream> from remote
                local conn = session.conn;
@@ -176,6 +179,9 @@ local function session_close(session, reason)
                        sm_destroy_session(session, reason);
                        conn:close();
                end
+       else
+               local reason = (reason and (reason.name or reason.text or reason.condition)) or reason;
+               sm_destroy_session(session, reason);
        end
 end
 
@@ -191,6 +197,7 @@ end, 200);
 
 --- Port listener
 function listener.onconnect(conn)
+       measure_connections(1);
        local session = sm_new_session(conn);
        sessions[conn] = session;
 
@@ -199,6 +206,7 @@ function listener.onconnect(conn)
        -- Client is using legacy SSL (otherwise mod_tls sets this flag)
        if conn:ssl() then
                session.secure = true;
+               session.encrypted = true;
 
                -- Check if TLS compression is used
                local sock = conn:socket();
@@ -224,63 +232,19 @@ function listener.onconnect(conn)
                session.stream:reset();
        end
 
-       session.thread = coroutine.create(function (stanza)
-               while true do
-                       core_process_stanza(session, stanza);
-                       stanza = coroutine.yield("ready");
-               end
-       end);
-
-       session.pending_stanzas = {};
-
        local filter = session.filter;
        function session.data(data)
                -- Parse the data, which will store stanzas in session.pending_stanzas
                if data then
-                       data = filter("bytes/in", data);
-                       if data then
-                               local ok, err = stream:feed(data);
+               data = filter("bytes/in", data);
+               if data then
+                       local ok, err = stream:feed(data);
                                if not ok then
                                        log("debug", "Received invalid XML (%s) %d bytes: %s", tostring(err), #data, data:sub(1, 300):gsub("[\r\n]+", " "):gsub("[%z\1-\31]", "_"));
                                        session:close("not-well-formed");
                                end
                        end
                end
-
-               if co_running() ~= session.thread and not session.paused then
-                       if session.state == "wait" then
-                               session.state = "ready";
-                               local ok, state = co_resume(session.thread);
-                               if not ok then
-                                       log("error", "Traceback[c2s]: %s", state);
-                               elseif state == "wait" then
-                                       return;
-                               end
-                       end
-                       -- We're not currently running, so start the thread to process pending stanzas
-                       local s, thread = session.pending_stanzas, session.thread;
-                       local n = #s;
-                       while n > 0 and session.state ~= "wait" do
-                               session.log("debug", "processing %d stanzas", n);
-                               local consumed;
-                               for i = 1,n do
-                                       local stanza = s[i];
-                                       local ok, state = co_resume(thread, stanza);
-                                       if not ok then
-                                               log("error", "Traceback[c2s]: %s", state);
-                                       elseif state == "wait" then
-                                               consumed = i;
-                                               session.state = "wait";
-                                               break;
-                                       end
-                               end
-                               if not consumed then consumed = n; end
-                               for i = 1, #s do
-                                       s[i] = s[consumed+i];
-                               end
-                               n = #s;
-                       end
-               end
        end
 
        if c2s_timeout then
@@ -292,22 +256,6 @@ function listener.onconnect(conn)
        end
 
        session.dispatch_stanza = stream_callbacks.handlestanza;
-
-       function session:sleep(by)
-               session.log("debug", "Sleeping for %s", by);
-               session.paused = by or "?";
-               session.conn:pause();
-               if co_running() == session.thread then
-                       coroutine.yield("wait");
-               end
-       end
-       function session:wake(by)
-               assert(session.paused == (by or "?"));
-               session.log("debug", "Waking for %s", by);
-               session.paused = nil;
-               session.conn:resume();
-               session.data(); --FIXME: next tick?
-       end
 end
 
 function listener.onincoming(conn, data)
@@ -318,10 +266,12 @@ function listener.onincoming(conn, data)
 end
 
 function listener.ondisconnect(conn, err)
+       measure_connections(-1);
        local session = sessions[conn];
        if session then
                (session.log or log)("info", "Client disconnected: %s", err or "connection closed");
                sm_destroy_session(session, err);
+               session.conn = nil;
                sessions[conn]  = nil;
        end
 end
@@ -352,7 +302,7 @@ module:hook("server-stopping", function(event)
        for _, session in pairs(sessions) do
                session:close{ condition = "system-shutdown", text = reason };
        end
-end, 1000);
+end, -100);