Merge 0.9->0.10
[prosody.git] / plugins / mod_c2s.lua
index fbac12cdd41471e5d82c2d0f7e3e541206de6e2c..7a8af406acdd6b3fe2a5710bea28338365c08d36 100644 (file)
@@ -15,11 +15,10 @@ local sessionmanager = require "core.sessionmanager";
 local st = require "util.stanza";
 local sm_new_session, sm_destroy_session = sessionmanager.new_session, sessionmanager.destroy_session;
 local uuid_generate = require "util.uuid".generate;
+local runner = require "util.async".runner;
 
 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";
 
@@ -35,10 +34,10 @@ local hosts = prosody.hosts;
 
 local stream_callbacks = { default_ns = "jabber:client" };
 local listener = {};
+local runner_callbacks = {};
 
 --- 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");
@@ -119,10 +116,9 @@ function stream_callbacks.error(session, error, data)
        end
 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);
+       session.thread:run(stanza);
 end
 
 --- Session methods
@@ -130,8 +126,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 +154,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;
@@ -179,6 +174,19 @@ local function session_close(session, reason)
        end
 end
 
+local function session_open_stream(session)
+       local attr = {
+               ["xmlns:stream"] = 'http://etherx.jabber.org/streams',
+               xmlns = stream_callbacks.default_ns,
+               version = "1.0",
+               ["xml:lang"] = 'en',
+               id = session.streamid or "",
+               from = session.host
+       };
+       session.send("<?xml version='1.0'?>");
+       session.send(st.stanza("stream:stream", attr):top_tag());
+end
+
 module:hook_global("user-deleted", function(event)
        local username, host = event.username, event.host;
        local user = hosts[host].sessions[username];
@@ -189,6 +197,18 @@ module:hook_global("user-deleted", function(event)
        end
 end, 200);
 
+function runner_callbacks:ready()
+       self.data.conn:resume();
+end
+
+function runner_callbacks:waiting()
+       self.data.conn:pause();
+end
+
+function runner_callbacks:error(err)
+       (self.data.log or log)("error", "Traceback[c2s]: %s", err);
+end
+
 --- Port listener
 function listener.onconnect(conn)
        local session = sm_new_session(conn);
@@ -199,6 +219,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();
@@ -213,6 +234,7 @@ function listener.onconnect(conn)
                conn:setoption("keepalive", opt_keepalives);
        end
 
+       session.open_stream = session_open_stream;
        session.close = session_close;
 
        local stream = new_xmpp_stream(session, stream_callbacks);
@@ -224,14 +246,9 @@ 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 = {};
+       session.thread = runner(function (stanza)
+               core_process_stanza(session, stanza);
+       end, runner_callbacks, session);
 
        local filter = session.filter;
        function session.data(data)
@@ -246,41 +263,6 @@ function listener.onconnect(conn)
                                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 +274,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)