Merge 0.6.2/waqas with 0.6.2/MattJ
authorMatthew Wild <mwild1@gmail.com>
Wed, 3 Mar 2010 22:05:05 +0000 (22:05 +0000)
committerMatthew Wild <mwild1@gmail.com>
Wed, 3 Mar 2010 22:05:05 +0000 (22:05 +0000)
34 files changed:
core/configmanager.lua
core/hostmanager.lua
core/modulemanager.lua
core/s2smanager.lua
core/sessionmanager.lua
core/stanza_router.lua
man/prosodyctl.man
net/dns.lua
net/httpserver.lua
net/xmppclient_listener.lua
net/xmppcomponent_listener.lua
net/xmppserver_listener.lua
plugins/mod_component.lua
plugins/mod_compression.lua
plugins/mod_console.lua
plugins/mod_debug.lua [deleted file]
plugins/mod_offline.lua [deleted file]
plugins/mod_pep.lua
plugins/mod_presence.lua
plugins/mod_register.lua
plugins/mod_saslauth.lua
plugins/mod_selftests.lua [deleted file]
plugins/mod_tls.lua
plugins/muc/muc.lib.lua
prosody
prosody.cfg.lua.dist
tools/ejabberd2prosody.lua
tools/ejabberdsql2prosody.lua
util-src/encodings.c
util-src/pposix.c
util/dataforms.lua
util/dependencies.lua
util/sasl.lua
util/stanza.lua

index 1fbe83b8258aa127cd3afed0f22ae5f8dea220fb..a7c7c4be59d1f0dcd0b75abce87e970619d50b26 100644 (file)
@@ -9,8 +9,9 @@
 
 
 local _G = _G;
-local  setmetatable, loadfile, pcall, rawget, rawset, io, error, dofile, type = 
-               setmetatable, loadfile, pcall, rawget, rawset, io, error, dofile, type;
+local  setmetatable, loadfile, pcall, rawget, rawset, io, error, dofile, type, pairs, table, format =
+               setmetatable, loadfile, pcall, rawget, rawset, io, error, dofile, type, pairs, table, string.format;
+
 
 local eventmanager = require "core.eventmanager";
 
@@ -115,6 +116,10 @@ do
                
                rawset(env, "__currenthost", "*") -- Default is global
                function env.Host(name)
+                       if rawget(config, name) and rawget(config[name].core, "component_module") then
+                               error(format("Host %q clashes with previously defined %s Component %q, for services use a sub-domain like conference.%s",
+                                       name, config[name].core.component_module:gsub("^%a+$", { component = "external", muc = "MUC"}), name, name), 0);
+                       end
                        rawset(env, "__currenthost", name);
                        -- Needs at least one setting to logically exist :)
                        set(name or "*", "core", "defined", true);
@@ -122,6 +127,10 @@ do
                env.host = env.Host;
                
                function env.Component(name)
+                       if rawget(config, name) and rawget(config[name].core, "defined") and not rawget(config[name].core, "component_module") then
+                               error(format("Component %q clashes with previously defined Host %q, for services use a sub-domain like conference.%s",
+                                       name, name, name), 0);
+                       end
                        set(name, "core", "component_module", "component");
                        -- Don't load the global modules by default
                        set(name, "core", "load_global_modules", false);
index 713788ddeee2e0b32e68f1e02f02722b7a39d579..125e71ba8d393285417fb7a07439c8ee843e67e3 100644 (file)
@@ -33,12 +33,19 @@ local hosts_loaded_once;
 
 local function load_enabled_hosts(config)
        local defined_hosts = config or configmanager.getconfig();
+       local activated_any_host;
        
        for host, host_config in pairs(defined_hosts) do
                if host ~= "*" and (host_config.core.enabled == nil or host_config.core.enabled) and not host_config.core.component_module then
+                       activated_any_host = true;
                        activate(host, host_config);
                end
        end
+       
+       if not activated_any_host then
+               log("error", "No hosts defined in the config file. This may cause unexpected behaviour as no modules will be loaded.");
+       end
+       
        eventmanager.fire_event("hosts-activated", defined_hosts);
        hosts_loaded_once = true;
 end
index 65a38ccb258b002876b6e7c32902c75dec22a8b8..ecb1bc6c7c020873f23181dd90e144fb54f1d62e 100644 (file)
@@ -28,9 +28,7 @@ local type = type;
 local next = next;
 local rawget = rawget;
 local error = error;
-local tostring, tonumber = tostring, tonumber;
-
-local array, set = require "util.array", require "util.set";
+local tostring = tostring;
 
 local autoload_modules = {"presence", "message", "iq"};
 
@@ -158,7 +156,6 @@ function load(host, module_name, config)
                log("error", "Error initializing module '%s' on '%s': %s", module_name, host, err or "nil");
        end
        if success then
-               hosts[host].events.fire_event("module-loaded", { module = module_name, host = host });
                return true;
        else -- load failed, unloading
                unload(api_instance.host, module_name);
@@ -175,7 +172,7 @@ function is_loaded(host, name)
 end
 
 function unload(host, name, ...)
-       local mod = get_module(host, name);
+       local mod = get_module(host, name); 
        if not mod then return nil, "module-not-loaded"; end
        
        if module_has_method(mod, "unload") then
@@ -200,8 +197,16 @@ function unload(host, name, ...)
                end
        end
        hooks:remove(host, name);
+       if mod.module.items then -- remove items
+               for key,t in pairs(mod.module.items) do
+                       for i = #t,1,-1 do
+                               local value = t[i];
+                               t[i] = nil;
+                               hosts[host].events.fire_event("item-removed/"..key, {source = self, item = value});
+                       end
+               end
+       end
        modulemap[host][name] = nil;
-       hosts[host].events.fire_event("module-unloaded", { module = name, host = host });
        return true;
 end
 
@@ -282,7 +287,7 @@ function module_has_method(module, method)
 end
 
 function call_module_method(module, method, ...)
-       if module_has_method(module, method) then
+       if module_has_method(module, method) then       
                local f = module.module[method];
                return pcall(f, ...);
        else
@@ -291,7 +296,7 @@ function call_module_method(module, method, ...)
 end
 
 ----- API functions exposed to modules -----------
--- Must all be in api.*
+-- Must all be in api.* 
 
 -- Returns the name of the current module
 function api:get_name()
@@ -404,85 +409,6 @@ function api:get_option(name, default_value)
        return value;
 end
 
-function api:get_option_string(...)
-       local value = self:get_option(...);
-       if type(value) == "table" then
-               if #value > 1 then
-                       self:log("error", "Config option '%s' does not take a list, using just the first item", name);
-               end
-               value = value[1];
-       end
-       if value == nil then
-               return nil;
-       end
-       return tostring(value);
-end
-
-function api:get_option_number(name, ...)
-       local value = self:get_option(name, ...);
-       if type(value) == "table" then
-               if #value > 1 then
-                       self:log("error", "Config option '%s' does not take a list, using just the first item", name);
-               end
-               value = value[1];
-       end
-       local ret = tonumber(value);
-       if value ~= nil and ret == nil then
-               self:log("error", "Config option '%s' not understood, expecting a number", name);
-       end
-       return ret;
-end
-
-function api:get_option_boolean(name, ...)
-       local value = self:get_option(name, ...);
-       if type(value) == "table" then
-               if #value > 1 then
-                       self:log("error", "Config option '%s' does not take a list, using just the first item", name);
-               end
-               value = value[1];
-       end
-       if value == nil then
-               return nil;
-       end
-       local ret = value == true or value == "true" or value == 1 or nil;
-       if ret == nil then
-               ret = (value == false or value == "false" or value == 0);
-               if ret then
-                       ret = false;
-               else
-                       ret = nil;
-               end
-       end
-       if ret == nil then
-               self:log("error", "Config option '%s' not understood, expecting true/false", name);
-       end
-       return ret;
-end
-
-function api:get_option_array(name, ...)
-       local value = self:get_option(name, ...);
-
-       if value == nil then
-               return nil;
-       end
-       
-       if type(value) ~= "table" then
-               return array{ value }; -- Assume any non-list is a single-item list
-       end
-       
-       return array():append(value); -- Clone
-end
-
-function api:get_option_set(name, ...)
-       local value = self:get_option_array(name, ...);
-       
-       if value == nil then
-               return nil;
-       end
-       
-       return set.new(value);
-end
-
 local t_remove = _G.table.remove;
 local module_items = multitable_new();
 function api:add_item(key, value)
index aabfddd600dfa9a904f7d84e751fd958a621c1d4..92f073549fa08503827d66d7c19767a36e6aee84 100644 (file)
@@ -504,6 +504,8 @@ function mark_connected(session)
        end
 end
 
+local function null_data_handler(conn, data) log("debug", "Discarding data from destroyed s2s session: %s", data); end
+
 function destroy_session(session, reason)
        (session.log or log)("info", "Destroying "..tostring(session.direction).." session "..tostring(session.from_host).."->"..tostring(session.to_host));
        
@@ -519,6 +521,7 @@ function destroy_session(session, reason)
                        session[k] = nil;
                end
        end
+       session.data = null_data_handler;
 end
 
 return _M;
index 69160af71289f88aa3b955603457223be09884ca..2dd0070b4469774a4fedd04410b326b38457a5e5 100644 (file)
@@ -24,6 +24,7 @@ local uuid_generate = require "util.uuid".generate;
 local rm_load_roster = require "core.rostermanager".load_roster;
 local config_get = require "core.configmanager".get;
 local nameprep = require "util.encodings".stringprep.nameprep;
+local resourceprep = require "util.encodings".stringprep.resourceprep;
 
 local fire_event = require "core.eventmanager".fire_event;
 local add_task = require "util.timer".add_task;
@@ -49,8 +50,8 @@ function new_session(conn)
        open_sessions = open_sessions + 1;
        log("debug", "open sessions now: ".. open_sessions);
        local w = conn.write;
-       session.send = function (t) w(conn, tostring(t)); end
-       session.ip = conn:ip();
+       session.send = function (t) w(tostring(t)); end
+       session.ip = conn.ip();
        local conn_name = "c2s"..tostring(conn):match("[a-f0-9]+$");
        session.log = logger.init(conn_name);
        
@@ -65,6 +66,8 @@ function new_session(conn)
        return session;
 end
 
+local function null_data_handler(conn, data) log("debug", "Discarding data from destroyed c2s session: %s", data); end
+
 function destroy_session(session, err)
        (session.log or log)("info", "Destroying session for %s (%s@%s)", session.full_jid or "(unknown)", session.username or "(unknown)", session.host or "(unknown)");
        
@@ -87,6 +90,7 @@ function destroy_session(session, err)
                        session[k] = nil;
                end
        end
+       session.data = null_data_handler;
 end
 
 function make_authenticated(session, username)
@@ -105,7 +109,8 @@ function bind_resource(session, resource)
        if session.resource then return nil, "cancel", "already-bound", "Cannot bind multiple resources on a single connection"; end
        -- We don't support binding multiple resources
 
-       resource = resource or uuid_generate();
+       resource = resourceprep(resource);
+       resource = resource ~= "" and resource or uuid_generate();
        --FIXME: Randomly-generated resources must be unique per-user, and never conflict with existing
        
        if not hosts[session.host].sessions[session.username] then
index ad312b850ef16d4dbe2ae7e70c5ba2b92fd9c6b2..b5b1b45f6f23f5836b73974e9de2fe3493a1ae56 100644 (file)
@@ -36,12 +36,14 @@ function core_process_stanza(origin, stanza)
                end
        end
 
-       if origin.type == "c2s" then
+       if origin.type == "c2s" and stanza.attr.xmlns == "jabber:client" then
                if not origin.full_jid
                        and not(stanza.name == "iq" and stanza.attr.type == "set" and stanza.tags[1] and stanza.tags[1].name == "bind"
                                        and stanza.tags[1].attr.xmlns == "urn:ietf:params:xml:ns:xmpp-bind") then
                        -- authenticated client isn't bound and current stanza is not a bind request
-                       origin.send(st.error_reply(stanza, "auth", "not-authorized")); -- FIXME maybe allow stanzas to account or server
+                       if stanza.attr.type ~= "result" and stanza.attr.type ~= "error" then
+                               origin.send(st.error_reply(stanza, "auth", "not-authorized")); -- FIXME maybe allow stanzas to account or server
+                       end
                        return;
                end
 
@@ -98,7 +100,7 @@ function core_process_stanza(origin, stanza)
                                return; -- FIXME what should we do here? does this work with subdomains?
                        end
                end
-               core_post_stanza(origin, stanza);
+               core_post_stanza(origin, stanza, origin.full_jid);
        else
                local h = hosts[stanza.attr.to or origin.host or origin.to_host];
                if h then
@@ -119,7 +121,7 @@ function core_process_stanza(origin, stanza)
        end
 end
 
-function core_post_stanza(origin, stanza)
+function core_post_stanza(origin, stanza, preevents)
        local to = stanza.attr.to;
        local node, host, resource = jid_split(to);
        local to_bare = node and (node.."@"..host) or host; -- bare JID
@@ -143,7 +145,7 @@ function core_post_stanza(origin, stanza)
        end
 
        local event_data = {origin=origin, stanza=stanza};
-       if origin.full_jid == stanza.attr.from then -- c2s connection
+       if preevents then -- c2s connection
                if hosts[origin.host].events.fire_event('pre-'..stanza.name..to_type, event_data) then return; end -- do preprocessing
        end
        local h = hosts[to_bare] or hosts[host or origin.host];
@@ -180,7 +182,7 @@ function core_route_stanza(origin, stanza)
                        local xmlns = stanza.attr.xmlns;
                        --stanza.attr.xmlns = "jabber:server";
                        stanza.attr.xmlns = nil;
-                       log("debug", "sending s2s stanza: %s", tostring(stanza.top_tag and stanza:top_tag()) or stanza);
+                       log("debug", "sending s2s stanza: %s", tostring(stanza));
                        send_s2s(origin.host, host, stanza); -- TODO handle remote routing errors
                        stanza.attr.xmlns = xmlns; -- reset
                else
@@ -191,6 +193,6 @@ function core_route_stanza(origin, stanza)
                log("debug", "Routing outgoing stanza for %s to %s", from_host, host);
                send_s2s(from_host, host, stanza);
        else
-               log("warn", "received stanza from unhandled connection type: %s", origin.type);
+               log("warn", "received %s stanza from unhandled connection type: %s", tostring(stanza.name), tostring(origin.type));
        end
 end
index e677443fa6d14e8b5f3142ee28908db583f799f9..44a84145a47ded481a1f6130b86faff962493d20 100644 (file)
@@ -76,4 +76,4 @@ determine if a host has been configured.
 More information may be found online at: \fIhttp://prosody.im/\fP
 
 .SH AUTHORS
-Dwayne Bent <dbb.0@liqd.org>
+Dwayne Bent <dbb.1@liqd.org>
index ca0ec622b35fbe518878d3594ce58e3e0cab072a..1b5321af165c89c644266cdbf43577c6d5fd07ac 100644 (file)
@@ -594,17 +594,18 @@ end
 
 function resolver:remember(rr, type)    -- - - - - - - - - - - - - -  remember
        --print ('remember', type, rr.class, rr.type, rr.name)
+       local qname, qtype, qclass = standardize(rr.name, rr.type, rr.class);
 
        if type ~= '*' then
-               type = rr.type;
-               local all = get(self.cache, rr.class, '*', rr.name);
+               type = qtype;
+               local all = get(self.cache, qclass, '*', qname);
                --print('remember all', all);
                if all then append(all, rr); end
        end
 
        self.cache = self.cache or setmetatable({}, cache_metatable);
-       local rrs = get(self.cache, rr.class, type, rr.name) or
-               set(self.cache, rr.class, type, rr.name, setmetatable({}, rrs_metatable));
+       local rrs = get(self.cache, qclass, type, qname) or
+               set(self.cache, qclass, type, qname, setmetatable({}, rrs_metatable));
        append(rrs, rr);
 
        if type == 'MX' then self.unsorted[rrs] = true; end
index dae49d2ad6a86cd113f63b77c247fd0aea91c2b9..6bd641f90895ec63d792ab2daf44eac87fc55d7a 100644 (file)
@@ -36,8 +36,8 @@ end
 local function send_response(request, response)
        -- Write status line
        local resp;
-       if response.body then
-               local body = tostring(response.body);
+       if response.body or response.headers then
+               local body = response.body and tostring(response.body);
                log("debug", "Sending response to %s", request.id);
                resp = { "HTTP/1.0 ", response.status or "200 OK", "\r\n"};
                local h = response.headers;
@@ -49,14 +49,14 @@ local function send_response(request, response)
                                t_insert(resp, "\r\n");
                        end
                end
-               if not (h and h["Content-Length"]) then
+               if body and not (h and h["Content-Length"]) then
                        t_insert(resp, "Content-Length: ");
                        t_insert(resp, #body);
                        t_insert(resp, "\r\n");
                end
                t_insert(resp, "\r\n");
                
-               if request.method ~= "HEAD" then
+               if body and request.method ~= "HEAD" then
                        t_insert(resp, body);
                end
        else
@@ -147,22 +147,29 @@ local function request_reader(request, data, startpos)
        elseif request.state == "headers" then
                log("debug", "Reading headers...")
                local pos = startpos;
-               local headers = request.headers or {};
+               local headers, headers_complete = request.headers;
+               if not headers then
+                       headers = {};
+                       request.headers = headers;
+               end
+               
                for line in data:gmatch("(.-)\r\n") do
                        startpos = (startpos or 1) + #line + 2;
                        local k, v = line:match("(%S+): (.+)");
                        if k and v then
                                headers[k:lower()] = v;
---                             log("debug", "Header: "..k:lower().." = "..v);
+                               --log("debug", "Header: '"..k:lower().."' = '"..v.."'");
                        elseif #line == 0 then
-                               request.headers = headers;
+                               headers_complete = true;
                                break;
                        else
                                log("debug", "Unhandled header line: "..line);
                        end
                end
                
-               if not expectbody(request) then 
+               if not headers_complete then return; end
+               
+               if not expectbody(request) then
                        call_callback(request);
                        return;
                end
@@ -176,7 +183,10 @@ local function request_reader(request, data, startpos)
                log("debug", "Reading request line...")
                local method, path, http, linelen = data:match("^(%S+) (%S+) HTTP/(%S+)\r\n()", startpos);
                if not method then
-                       return call_callback(request, "invalid-status-line");
+                       log("warn", "Invalid HTTP status line, telling callback then closing");
+                       local ret = call_callback(request, "invalid-status-line");
+                       request:destroy();
+                       return ret;
                end
                
                request.method, request.path, request.httpversion = method, path, http;
index 223fa89b62a3da3894fa2c066f416e4eee5f349c..f455e7be380ef58e6c74d056395760fee4b6859a 100644 (file)
@@ -100,15 +100,15 @@ local function session_close(session, reason)
                        end
                end
                session.send("</stream:stream>");
-               session.conn:close();
-               xmppclient.ondisconnect(session.conn, (reason and (reason.text or reason.condition)) or reason or "session closed");
+               session.conn.close();
+               xmppclient.disconnect(session.conn, (reason and (reason.text or reason.condition)) or reason or "session closed");
        end
 end
 
 
 -- End of session methods --
 
-function xmppclient.onincoming(conn, data)
+function xmppclient.listener(conn, data)
        local session = sessions[conn];
        if not session then
                session = sm_new_session(conn);
@@ -117,7 +117,7 @@ function xmppclient.onincoming(conn, data)
                session.log("info", "Client connected");
                
                -- Client is using legacy SSL (otherwise mod_tls sets this flag)
-               if conn:ssl() then
+               if conn.ssl() then
                        session.secure = true;
                end
                
@@ -133,7 +133,7 @@ function xmppclient.onincoming(conn, data)
        end
 end
        
-function xmppclient.ondisconnect(conn, err)
+function xmppclient.disconnect(conn, err)
        local session = sessions[conn];
        if session then
                (session.log or log)("info", "Client disconnected: %s", err);
index 4920548d925c52b46bc1bc32782a45ae833ed439..2045d28fc3816f6ea6fae6a2c98739014401700e 100644 (file)
@@ -118,16 +118,16 @@ local function session_close(session, reason)
                end
                session.send("</stream:stream>");
                session.conn.close();
-               component_listener.ondisconnect(session.conn, "stream error");
+               component_listener.disconnect(session.conn, "stream error");
        end
 end
 
 --- Component connlistener
-function component_listener.onincoming(conn, data)
+function component_listener.listener(conn, data)
        local session = sessions[conn];
        if not session then
                local _send = conn.write;
-               session = { type = "component", conn = conn, send = function (data) return _send(conn, tostring(data)); end };
+               session = { type = "component", conn = conn, send = function (data) return _send(tostring(data)); end };
                sessions[conn] = session;
 
                -- Logging functions --
@@ -157,7 +157,7 @@ function component_listener.onincoming(conn, data)
        end
 end
        
-function component_listener.ondisconnect(conn, err)
+function component_listener.disconnect(conn, err)
        local session = sessions[conn];
        if session then
                (session.log or log)("info", "component disconnected: %s (%s)", tostring(session.host), tostring(err));
index 4394eac50ededa17fb2fa3546a64b362d0136fee..86a3d7356ace928ceca3b31bef67adb2c76ece83 100644 (file)
@@ -162,7 +162,6 @@ function xmppserver.disconnect(conn, err)
                s2s_destroy_session(session, err);
                sessions[conn]  = nil;
                session = nil;
-               collectgarbage("collect");
        end
 end
 
index 69a42eaf8b80404759f8c2632acffd7e6a0d9d21..4201bb1961ff778eeeaf2440c8862ec28db17987 100644 (file)
@@ -44,7 +44,7 @@ function handle_component_auth(session, stanza)
        
        local secret = config.get(session.user, "core", "component_secret");
        if not secret then
-               (session.log or log)("warn", "Component attempted to identify as %s, but component_password is not set", session.user);
+               (session.log or log)("warn", "Component attempted to identify as %s, but component_secret is not set", session.user);
                session:close("not-authorized");
                return;
        end
index 8fdf9dcce592983219bf53fa3f7669dd0bff45ec..ec9e24ec28ddf8779e24c7756db26473bdb4a561 100644 (file)
@@ -8,9 +8,9 @@
 local st = require "util.stanza";
 local zlib = require "zlib";
 local pcall = pcall;
+
 local xmlns_compression_feature = "http://jabber.org/features/compress"
 local xmlns_compression_protocol = "http://jabber.org/protocol/compress"
-local xmlns_stream = "http://etherx.jabber.org/streams";
 local compression_stream_feature = st.stanza("compression", {xmlns=xmlns_compression_feature}):tag("method"):text("zlib"):up();
 
 local compression_level = module:get_option("compression_level");
@@ -34,143 +34,13 @@ module:add_event_hook("stream-features",
                end
 );
 
-module:hook("s2s-stream-features",
-               function (data)
-                       local session, features = data.session, data.features;
-                       -- FIXME only advertise compression support when TLS layer has no compression enabled
-                       if not session.compressed then 
-                               features:add_child(compression_stream_feature);
-                       end
-               end
-);
-
--- Hook to activate compression if remote server supports it.
-module:hook_stanza(xmlns_stream, "features",
-               function (session, stanza)
-                       if not session.compressed then
-                               -- does remote server support compression?
-                               local comp_st = stanza:child_with_name("compression");
-                               if comp_st then
-                                       -- do we support the mechanism
-                                       for a in comp_st:children() do
-                                               local algorithm = a[1]
-                                               if algorithm == "zlib" then
-                                                       session.sends2s(st.stanza("compress", {xmlns=xmlns_compression_protocol}):tag("method"):text("zlib"))
-                                                       session.log("info", "Enabled compression using zlib.")
-                                                       return true;
-                                               end
-                                       end
-                                       session.log("debug", "Remote server supports no compression algorithm we support.")
-                               end
-                       end
-               end
-, 250);
-
-
--- returns either nil or a fully functional ready to use inflate stream
-local function get_deflate_stream(session)
-       local status, deflate_stream = pcall(zlib.deflate, compression_level);
-       if status == false then
-               local error_st = st.stanza("failure", {xmlns=xmlns_compression_protocol}):tag("setup-failed");
-               (session.sends2s or session.send)(error_st);
-               session.log("error", "Failed to create zlib.deflate filter.");
-               module:log("error", deflate_stream);
-               return
-       end
-       return deflate_stream
-end
-
--- returns either nil or a fully functional ready to use inflate stream
-local function get_inflate_stream(session)
-       local status, inflate_stream = pcall(zlib.inflate);
-       if status == false then
-               local error_st = st.stanza("failure", {xmlns=xmlns_compression_protocol}):tag("setup-failed");
-               (session.sends2s or session.send)(error_st);
-               session.log("error", "Failed to create zlib.deflate filter.");
-               module:log("error", inflate_stream);
-               return
-       end
-       return inflate_stream
-end
-
--- setup compression for a stream
-local function setup_compression(session, deflate_stream)
-       local old_send = (session.sends2s or session.send);
-       
-       local new_send = function(t)
-                       --TODO: Better code injection in the sending process
-                       session.log(t)
-                       local status, compressed, eof = pcall(deflate_stream, tostring(t), 'sync');
-                       if status == false then
-                               session:close({
-                                       condition = "undefined-condition";
-                                       text = compressed;
-                                       extra = st.stanza("failure", {xmlns="http://jabber.org/protocol/compress"}):tag("processing-failed");
-                               });
-                               module:log("warn", compressed);
-                               return;
-                       end
-                       session.conn:write(compressed);
-               end;
-       
-       if session.sends2s then session.sends2s = new_send
-       elseif session.send then session.send = new_send end
-end
-
--- setup decompression for a stream
-local function setup_decompression(session, inflate_stream)
-       local old_data = session.data
-       session.data = function(conn, data)
-                       local status, decompressed, eof = pcall(inflate_stream, data);
-                       if status == false then
-                               session:close({
-                                       condition = "undefined-condition";
-                                       text = decompressed;
-                                       extra = st.stanza("failure", {xmlns="http://jabber.org/protocol/compress"}):tag("processing-failed");
-                               });
-                               module:log("warn", decompressed);
-                               return;
-                       end
-                       old_data(conn, decompressed);
-               end;
-end
-
-module:add_handler({"s2sout_unauthed", "s2sout"}, "compressed", xmlns_compression_protocol, 
-               function(session ,stanza)
-                       session.log("debug", "Activating compression...")
-                       -- create deflate and inflate streams
-                       local deflate_stream = get_deflate_stream(session);
-                       if not deflate_stream then return end
-                       
-                       local inflate_stream = get_inflate_stream(session);
-                       if not inflate_stream then return end
-                       
-                       -- setup compression for session.w
-                       setup_compression(session, deflate_stream);
-                               
-                       -- setup decompression for session.data
-                       setup_decompression(session, inflate_stream);
-                       local session_reset_stream = session.reset_stream;
-                       session.reset_stream = function(session)
-                                       session_reset_stream(session);
-                                       setup_decompression(session, inflate_stream);
-                                       return true;
-                               end;
-                       session:reset_stream();
-                       local default_stream_attr = {xmlns = "jabber:server", ["xmlns:stream"] = "http://etherx.jabber.org/streams",
-                                                                               ["xmlns:db"] = 'jabber:server:dialback', version = "1.0", to = session.to_host, from = session.from_host};
-                       session.sends2s("<?xml version='1.0'?>");
-                       session.sends2s(st.stanza("stream:stream", default_stream_attr):top_tag());
-                       session.compressed = true;
-               end
-);
-
-module:add_handler({"c2s_unauthed", "c2s", "s2sin_unauthed", "s2sin"}, "compress", xmlns_compression_protocol,
+-- TODO Support compression on S2S level too.
+module:add_handler({"c2s_unauthed", "c2s"}, "compress", xmlns_compression_protocol,
                function(session, stanza)
                        -- fail if we are already compressed
                        if session.compressed then
                                local error_st = st.stanza("failure", {xmlns=xmlns_compression_protocol}):tag("unsupported-method");
-                               (session.sends2s or session.send)(error_st);
+                               session.send(error_st);
                                session.log("warn", "Tried to establish another compression layer.");
                        end
                        
@@ -178,35 +48,75 @@ module:add_handler({"c2s_unauthed", "c2s", "s2sin_unauthed", "s2sin"}, "compress
                        local method = stanza:child_with_name("method")[1];
                        if method == "zlib" then
                                session.log("info", method.." compression selected.");
+                               session.send(st.stanza("compressed", {xmlns=xmlns_compression_protocol}));
+                               session:reset_stream();
                                
                                -- create deflate and inflate streams
-                               local deflate_stream = get_deflate_stream(session);
-                               if not deflate_stream then return end
-                               
-                               local inflate_stream = get_inflate_stream(session);
-                               if not inflate_stream then return end
+                               local status, deflate_stream = pcall(zlib.deflate, compression_level);
+                               if status == false then
+                                       local error_st = st.stanza("failure", {xmlns=xmlns_compression_protocol}):tag("setup-failed");
+                                       session.send(error_st);
+                                       session.log("error", "Failed to create zlib.deflate filter.");
+                                       module:log("error", deflate_stream);
+                                       return
+                               end
                                
-                               (session.sends2s or session.send)(st.stanza("compressed", {xmlns=xmlns_compression_protocol}));
-                               session:reset_stream();
+                               local status, inflate_stream = pcall(zlib.inflate);
+                               if status == false then
+                                       local error_st = st.stanza("failure", {xmlns=xmlns_compression_protocol}):tag("setup-failed");
+                                       session.send(error_st);
+                                       session.log("error", "Failed to create zlib.deflate filter.");
+                                       module:log("error", inflate_stream);
+                                       return
+                               end
                                
                                -- setup compression for session.w
-                               setup_compression(session, deflate_stream);
+                               local old_send = session.send;
+                               
+                               session.send = function(t)
+                                               local status, compressed, eof = pcall(deflate_stream, tostring(t), 'sync');
+                                               if status == false then
+                                                       session:close({
+                                                               condition = "undefined-condition";
+                                                               text = compressed;
+                                                               extra = st.stanza("failure", {xmlns="http://jabber.org/protocol/compress"}):tag("processing-failed");
+                                                       });
+                                                       module:log("warn", compressed);
+                                                       return;
+                                               end
+                                               old_send(compressed);
+                                       end;
                                        
                                -- setup decompression for session.data
-                               setup_decompression(session, inflate_stream);
+                               local function setup_decompression(session)
+                                       local old_data = session.data
+                                       session.data = function(conn, data)
+                                                       local status, decompressed, eof = pcall(inflate_stream, data);
+                                                       if status == false then
+                                                               session:close({
+                                                                       condition = "undefined-condition";
+                                                                       text = decompressed;
+                                                                       extra = st.stanza("failure", {xmlns="http://jabber.org/protocol/compress"}):tag("processing-failed");
+                                                               });
+                                                               module:log("warn", decompressed);
+                                                               return;
+                                                       end
+                                                       old_data(conn, decompressed);
+                                               end;
+                               end
+                               setup_decompression(session);
                                
                                local session_reset_stream = session.reset_stream;
                                session.reset_stream = function(session)
                                                session_reset_stream(session);
-                                               setup_decompression(session, inflate_stream);
+                                               setup_decompression(session);
                                                return true;
                                        end;
                                session.compressed = true;
                        else
                                session.log("info", method.." compression selected. But we don't support it.");
                                local error_st = st.stanza("failure", {xmlns=xmlns_compression_protocol}):tag("unsupported-method");
-                               (session.sends2s or session.send)(error_st);
+                               session.send(error_st);
                        end
                end
 );
-
index 5e6b684661bedc38ac1352e46af61734faf22af8..d8362a07d25f4b1194b62dee9eb18ceb400fefdd 100644 (file)
@@ -33,11 +33,11 @@ end
 console = {};
 
 function console:new_session(conn)
-       local w = function(s) conn:write(s:gsub("\n", "\r\n")); end;
+       local w = function(s) conn.write(s:gsub("\n", "\r\n")); end;
        local session = { conn = conn;
                        send = function (t) w(tostring(t)); end;
                        print = function (t) w("| "..tostring(t).."\n"); end;
-                       disconnect = function () conn:close(); end;
+                       disconnect = function () conn.close(); end;
                        };
        session.env = setmetatable({}, default_env_mt);
        
@@ -53,7 +53,7 @@ end
 
 local sessions = {};
 
-function console_listener.onincoming(conn, data)
+function console_listener.listener(conn, data)
        local session = sessions[conn];
        
        if not session then
@@ -126,7 +126,7 @@ function console_listener.onincoming(conn, data)
        session.send(string.char(0));
 end
 
-function console_listener.ondisconnect(conn, err)
+function console_listener.disconnect(conn, err)
        local session = sessions[conn];
        if session then
                session.disconnect();
@@ -192,7 +192,7 @@ function commands.help(session, data)
        elseif section == "server" then
                print [[server:version() - Show the server's version number]]
                print [[server:uptime() - Show how long the server has been running]]
-               --print [[server:shutdown(reason) - Shut down the server, with an optional reason to be broadcast to all connections]]
+               print [[server:shutdown(reason) - Shut down the server, with an optional reason to be broadcast to all connections]]
        elseif section == "config" then
                print [[config:reload() - Reload the server configuration. Modules may need to be reloaded for changes to take effect.]]
        elseif section == "console" then
@@ -478,7 +478,7 @@ function def_env.s2s:show(match_jid)
                for remotehost, session in pairs(host_session.s2sout) do
                        if (not match_jid) or remotehost:match(match_jid) or host:match(match_jid) then
                                count_out = count_out + 1;
-                               print("    "..host.." -> "..remotehost..(session.secure and " (encrypted)" or "")..(session.compressed and " (compressed)" or ""));
+                               print("    "..host.." -> "..remotehost..(session.secure and " (encrypted)" or ""));
                                if session.sendq then
                                        print("        There are "..#session.sendq.." queued outgoing stanzas for this connection");
                                end
@@ -515,7 +515,7 @@ function def_env.s2s:show(match_jid)
                                -- Pft! is what I say to list comprehensions
                                or (session.hosts and #array.collect(keys(session.hosts)):filter(subhost_filter)>0)) then
                                count_in = count_in + 1;
-                               print("    "..host.." <- "..(session.from_host or "(unknown)")..(session.secure and " (encrypted)" or "")..(session.compressed and " (compressed)" or ""));
+                               print("    "..host.." <- "..(session.from_host or "(unknown)")..(session.secure and " (encrypted)" or ""));
                                if session.type == "s2sin_unauthed" then
                                                print("        Connection not yet authenticated");
                                end
diff --git a/plugins/mod_debug.lua b/plugins/mod_debug.lua
deleted file mode 100644 (file)
index 9f80202..0000000
+++ /dev/null
@@ -1,191 +0,0 @@
--- Prosody IM
--- Copyright (C) 2008-2009 Matthew Wild
--- Copyright (C) 2008-2009 Waqas Hussain
--- 
--- This project is MIT/X11 licensed. Please see the
--- COPYING file in the source package for more information.
---
-
-module.host = "*";
-
-local connlisteners_register = require "net.connlisteners".register;
-
-local console_listener = { default_port = 5583; default_mode = "*l"; default_interface = "127.0.0.1" };
-
-local sha256, missingglobal = require "util.hashes".sha256;
-
-local commands = {};
-local debug_env = {};
-local debug_env_mt = { __index = function (t, k) return rawget(_G, k) or missingglobal(k); end, __newindex = function (t, k, v) rawset(_G, k, v); end };
-
-local t_insert, t_concat = table.insert, table.concat;
-local t_concatall = function (t, sep) local tt = {}; for k, s in pairs(t) do tt[k] = tostring(s); end return t_concat(tt, sep); end
-
-
-setmetatable(debug_env, debug_env_mt);
-
-console = {};
-
-function console:new_session(conn)
-       local w = function(s) conn.write(s:gsub("\n", "\r\n")); end;
-       local session = { conn = conn;
-                       send = function (t) w(tostring(t)); end;
-                       print = function (t) w("| "..tostring(t).."\n"); end;
-                       disconnect = function () conn.close(); end;
-                       };
-       
-       return session;
-end
-
-local sessions = {};
-
-function console_listener.listener(conn, data)
-       local session = sessions[conn];
-       
-       if not session then
-               -- Handle new connection
-               session = console:new_session(conn);
-               sessions[conn] = session;
-               printbanner(session);
-       end
-       if data then
-               -- Handle data
-               (function(session, data)
-                       if data:match("[!.]$") then
-                               local command = data:lower();
-                               command = data:match("^%w+") or data:match("%p");
-                               if commands[command] then
-                                       commands[command](session, data);
-                                       return;
-                               end
-                       end
-                       
-                       local chunk, err = loadstring("return "..data);
-                       if not chunk then
-                               chunk, err = loadstring(data);
-                               if not chunk then
-                                       err = err:gsub("^%[string .-%]:%d+: ", "");
-                                       err = err:gsub("^:%d+: ", "");
-                                       err = err:gsub("'<eof>'", "the end of the line");
-                                       session.print("Sorry, I couldn't understand that... "..err);
-                                       return;
-                               end
-                       end
-                       
-                       debug_env.print = session.print;
-                       
-                       setfenv(chunk, debug_env);
-                       
-                       local ret = { pcall(chunk) };
-                       
-                       if not ret[1] then
-                               session.print("Fatal error while running command, it did not complete");
-                               session.print("Error: "..ret[2]);
-                               return;
-                       end
-                       
-                       table.remove(ret, 1);
-                       
-                       local retstr = t_concatall(ret, ", ");
-                       if retstr ~= "" then
-                               session.print("Result: "..retstr);
-                       else
-                               session.print("No result, or nil");
-                               return;
-                       end
-               end)(session, data);
-       end
-       session.send(string.char(0));
-end
-
-function console_listener.disconnect(conn, err)
-       
-end
-
-connlisteners_register('debug', console_listener);
-require "net.connlisteners".start("debug");
-
--- Console commands --
--- These are simple commands, not valid standalone in Lua
-
-function commands.bye(session)
-       session.print("See you! :)");
-       session.disconnect();
-end
-
-commands["!"] = function (session, data)
-       if data:match("^!!") then
-               session.print("!> "..session.env._);
-               return console_listener.listener(session.conn, session.env._);
-       end
-       local old, new = data:match("^!(.-[^\\])!(.-)!$");
-       if old and new then
-               local ok, res = pcall(string.gsub, session.env._, old, new);
-               if not ok then
-                       session.print(res)
-                       return;
-               end
-               session.print("!> "..res);
-               return console_listener.listener(session.conn, res);
-       end
-       session.print("Sorry, not sure what you want");
-end
-
-function printbanner(session)
-session.print [[
-                   ____                \   /     _       
-                    |  _ \ _ __ ___  ___  _-_   __| |_   _ 
-                    | |_) | '__/ _ \/ __|/ _ \ / _` | | | |
-                    |  __/| | | (_) \__ \ |_| | (_| | |_| |
-                    |_|   |_|  \___/|___/\___/ \__,_|\__, |
-                    A study in simplicity            |___/ 
-
-]]
-session.print("Welcome to the Prosody debug console. For a list of commands, type: help");
-session.print("You may find more help on using this console in our online documentation at ");
-session.print("http://prosody.im/doc/debugconsole\n");
-end
-
-local byte, char = string.byte, string.char;
-local gmatch, gsub = string.gmatch, string.gsub;
-
-local function vdecode(text, key)
-       local keyarr = {};
-       for l in gmatch(key, ".") do t_insert(keyarr, byte(l) - 32) end
-       local pos, keylen = 0, #keyarr;
-       return (gsub(text, ".", function (letter)
-                                                       if byte(letter) < 32 then return ""; end
-                                                       pos = (pos%keylen)+1;
-                                                       return char(((byte(letter) - 32 - keyarr[pos]) % 94) + 32);
-                                               end));
-end
-
-local subst = {
-       ["f880c08056ba7dbecb1ccfe5d7728bd6dcd654e94f7a9b21788c43397bae0bc5"] =
-               [=[nRYeKR$l'5Ix%u*1Mc-K}*bwv*\ $1KLMBd$KH R38`$[6}VQ@,6Qn]=];
-       ["92f718858322157202ec740698c1390e47bc819e52b6a099c54c378a9f7529d6"] =
-               [=[V\Z5`WZ5,T$<)7LM'w3Z}M(7V'{pa) &'>0+{v)O(0M*V5K$$LL$|2wT}6
-                1as*")e!>]=];
-       ["467b65edcc7c7cd70abf2136cc56abd037216a6cd9e17291a2219645be2e2216"] =
-               [=[i#'Z,E1-"YaHW(j/0xs]I4x&%(Jx1h&18'(exNWT D3b+K{*8}w(%D {]=];
-       ["f73729d7f2fbe686243a25ac088c7e6aead3d535e081329f2817438a5c78bee5"] =
-               [=[,3+(Q{3+W\ftQ%wvv/C0z-l%f>ABc(vkp<bb8]=];
-       ["6afa189489b096742890d0c5bd17d5bb8af8ac460c7026984b64e8f14a40404e"] =
-               [=[9N{)5j34gd*}&]H&dy"I&7(",a F1v6jY+IY7&S+86)1z(Vo]=];
-       ["cc5e5293ef8a1acbd9dd2bcda092c5c77ef46d3ec5aea65024fca7ed4b3c94a9"] = 
-               [=[_]Rc}IF'Kfa&))Ry+6|x!K2|T*Vze)%4Hwz'L3uI|OwIa)|q#uq2+Qu u7
-               [V3(z(*TYY|T\1_W'2] Dwr{-{@df#W.H5^x(ydtr{c){UuV@]=];
-       ["b3df231fd7ddf73f72f39cb2510b1fe39318f4724728ed58948a180663184d3e"] =
-               [=[iH!"9NLS'%geYw3^R*fvWM1)MwxLS!d[zP(p0sQ|8tX{dWO{9w!+W)b"MU
-               W)V8&(2Wx"'dTL9*PP%1"JV(I|Jr1^f'-Hc3U\2H3Z='K#,)dPm]=];
-       }
-
-function missingglobal(name)
-       if sha256 then
-               local hash = sha256(name.."|"..name:reverse(), true);
-               
-               if subst[hash] then
-                       return vdecode(subst[hash], sha256(name:reverse(), true));
-               end
-       end
-end
diff --git a/plugins/mod_offline.lua b/plugins/mod_offline.lua
deleted file mode 100644 (file)
index c74d011..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
--- Prosody IM
--- Copyright (C) 2008-2009 Matthew Wild
--- Copyright (C) 2008-2009 Waqas Hussain
--- 
--- This project is MIT/X11 licensed. Please see the
--- COPYING file in the source package for more information.
---
-
-\r
-local datamanager = require "util.datamanager";\r
-local st = require "util.stanza";\r
-local datetime = require "util.datetime";\r
-local ipairs = ipairs;
-local jid_split = require "util.jid".split;\r
-\r
-module:add_feature("msgoffline");\r
-\r
-module:hook("message/offline/store", function(event)\r
-       local origin, stanza = event.origin, event.stanza;\r
-       local to = stanza.attr.to;\r
-       local node, host;\r
-       if to then\r
-               node, host = jid_split(to)\r
-       else\r
-               node, host = origin.username, origin.host;\r
-       end\r
-       \r
-       stanza.attr.stamp, stanza.attr.stamp_legacy = datetime.datetime(), datetime.legacy();\r
-       local result = datamanager.list_append(node, host, "offline", st.preserialize(stanza));\r
-       stanza.attr.stamp, stanza.attr.stamp_legacy = nil, nil;\r
-       \r
-       return true;\r
-end);\r
-\r
-module:hook("message/offline/broadcast", function(event)\r
-       local origin = event.origin;\r
-       local node, host = origin.username, origin.host;\r
-       \r
-       local data = datamanager.list_load(node, host, "offline");\r
-       if not data then return true; end\r
-       for _, stanza in ipairs(data) do\r
-               stanza = st.deserialize(stanza);\r
-               stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = host, stamp = stanza.attr.stamp}):up(); -- XEP-0203\r
-               stanza:tag("x", {xmlns = "jabber:x:delay", from = host, stamp = stanza.attr.stamp_legacy}):up(); -- XEP-0091 (deprecated)\r
-               stanza.attr.stamp, stanza.attr.stamp_legacy = nil, nil;\r
-               origin.send(stanza);\r
-       end\r
-       return true;\r
-end);\r
-\r
-module:hook("message/offline/delete", function(event)\r
-       local origin = event.origin;\r
-       local node, host = origin.username, origin.host;\r
-\r
-       return datamanager.list_store(node, host, "offline", nil);\r
-end);\r
index bfe2286763117a299ce2e4bb641e922a15f99828..ef03ab0f68abdf8c729eeaf1f2ec8186f68809d4 100644 (file)
@@ -37,9 +37,16 @@ end
 module:add_identity("pubsub", "pep", "Prosody");
 module:add_feature("http://jabber.org/protocol/pubsub#publish");
 
-local function publish(session, node, item)
+local function subscription_presence(user_bare, recipient)
+       local recipient_bare = jid_bare(recipient);
+       if (recipient_bare == user_bare) then return true end
+       local item = load_roster(jid_split(user_bare))[recipient_bare];
+       return item and (item.subscription == 'from' or item.subscription == 'both');
+end
+
+local function publish(session, node, id, item)
        item.attr.xmlns = nil;
-       local disable = #item.tags ~= 1 or #item.tags[1].tags == 0;
+       local disable = #item.tags ~= 1 or #item.tags[1] == 0;
        if #item.tags == 0 then item.name = "retract"; end
        local bare = session.username..'@'..session.host;
        local stanza = st.message({from=bare, type='headline'})
@@ -58,9 +65,9 @@ local function publish(session, node, item)
                end
        else
                if not user_data then user_data = {}; data[bare] = user_data; end
-               user_data[node] = stanza;
+               user_data[node] = {id or "1", item};
        end
-       
+
        -- broadcast
        for recipient, notify in pairs(recipients[bare] or NULL) do
                if notify[node] then
@@ -74,10 +81,14 @@ local function publish_all(user, recipient, session)
        local notify = recipients[user] and recipients[user][recipient];
        if d and notify then
                for node in pairs(notify) do
-                       local message = d[node];
-                       if message then
-                               message.attr.to = recipient;
-                               session.send(message);
+                       if d[node] then
+                               local id, item = unpack(d[node]);
+                               session.send(st.message({from=user, to=recipient, type='headline'})
+                                       :tag('event', {xmlns='http://jabber.org/protocol/pubsub#event'})
+                                               :tag('items', {node=node})
+                                                       :add_child(item)
+                                               :up()
+                                       :up());
                        end
                end
        end
@@ -106,11 +117,9 @@ end
 module:hook("presence/bare", function(event)
        -- inbound presence to bare JID recieved
        local origin, stanza = event.origin, event.stanza;
-       
        local user = stanza.attr.to or (origin.username..'@'..origin.host);
-       local bare = jid_bare(stanza.attr.from);
-       local item = load_roster(jid_split(user))[bare];
-       if not stanza.attr.to or (item and (item.subscription == 'from' or item.subscription == 'both')) then
+
+       if not stanza.attr.to or subscription_presence(user, stanza.attr.from) then
                local recipient = stanza.attr.from;
                local current = recipients[user] and recipients[user][recipient];
                local hash = get_caps_hash_from_presence(stanza, current);
@@ -135,19 +144,63 @@ end, 10);
 
 module:hook("iq/bare/http://jabber.org/protocol/pubsub:pubsub", function(event)
        local session, stanza = event.origin, event.stanza;
+       local payload = stanza.tags[1];
+
        if stanza.attr.type == 'set' and (not stanza.attr.to or jid_bare(stanza.attr.from) == stanza.attr.to) then
-               local payload = stanza.tags[1];
-               if payload.name == 'pubsub' then -- <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+               payload = payload.tags[1];
+               if payload and (payload.name == 'publish' or payload.name == 'retract') and payload.attr.node then -- <publish node='http://jabber.org/protocol/tune'>
+                       local node = payload.attr.node;
                        payload = payload.tags[1];
-                       if payload and (payload.name == 'publish' or payload.name == 'retract') and payload.attr.node then -- <publish node='http://jabber.org/protocol/tune'>
-                               local node = payload.attr.node;
-                               payload = payload.tags[1];
-                               if payload and payload.name == "item" then -- <item>
-                                       session.send(st.reply(stanza));
-                                       publish(session, node, st.clone(payload));
+                       if payload and payload.name == "item" then -- <item>
+                               local id = payload.attr.id;
+                               session.send(st.reply(stanza));
+                               publish(session, node, id, st.clone(payload));
+                               return true;
+                       end
+               end
+       elseif stanza.attr.type == 'get' then
+               local user = stanza.attr.to and jid_bare(stanza.attr.to) or session.username..'@'..session.host;
+               if subscription_presence(user, stanza.attr.from) then
+                       local user_data = data[user];
+                       local node, requested_id;
+                       payload = payload.tags[1];
+                       if payload and payload.name == 'items' then
+                               node = payload.attr.node;
+                               local item = payload.tags[1];
+                               if item and item.name == "item" then
+                                       requested_id = item.attr.id;
+                               end
+                       end
+                       if node and user_data and user_data[node] then -- Send the last item
+                               local id, item = unpack(user_data[node]);
+                               if not requested_id or id == requested_id then
+                                       local stanza = st.reply(stanza)
+                                               :tag('pubsub', {xmlns='http://jabber.org/protocol/pubsub'})
+                                                       :tag('items', {node=node})
+                                                               :add_child(item)
+                                                       :up()
+                                               :up();
+                                       session.send(stanza);
+                                       return true;
+                               else -- requested item doesn't exist
+                                       local stanza = st.reply(stanza)
+                                               :tag('pubsub', {xmlns='http://jabber.org/protocol/pubsub'})
+                                                       :tag('items', {node=node})
+                                               :up();
+                                       session.send(stanza);
                                        return true;
                                end
+                       elseif node then -- node doesn't exist
+                               session.send(st.error_reply(stanza, 'cancel', 'item-not-found'));
+                               return true;
+                       else --invalid request
+                               session.send(st.error_reply(stanza, 'modify', 'bad-request'));
+                               return true;
                        end
+               else --no presence subscription
+                       session.send(st.error_reply(stanza, 'auth', 'not-authorized')
+                               :tag('presence-subscription-required', {xmlns='http://jabber.org/protocol/pubsub#errors'}));
+                       return true;
                end
        end
 end);
index abbc3a3da3473638f260182398162dc047a9cd2d..a39d9c19cd87d2de3b5d0e5ccb09e0cc17d4ca5b 100644 (file)
@@ -18,6 +18,7 @@ local st = require "util.stanza";
 local jid_split = require "util.jid".split;
 local jid_bare = require "util.jid".bare;
 local hosts = hosts;
+local NULL = {};
 
 local rostermanager = require "core.rostermanager";
 local sessionmanager = require "core.sessionmanager";
@@ -54,16 +55,21 @@ local function select_top_resources(user)
        end
        return recipients;
 end
-local function recalc_resource_map(origin)
-       local user = hosts[origin.host].sessions[origin.username];
-       user.top_resources = select_top_resources(user);
-       if #user.top_resources == 0 then user.top_resources = nil; end
+local function recalc_resource_map(user)
+       if user then
+               user.top_resources = select_top_resources(user);
+               if #user.top_resources == 0 then user.top_resources = nil; end
+       end
 end
 
 function handle_normal_presence(origin, stanza, core_route_stanza)
+       if full_sessions[origin.full_jid] then -- if user is still connected
+               origin.send(stanza); -- reflect their presence back to them
+       end
        local roster = origin.roster;
        local node, host = origin.username, origin.host;
-       for _, res in pairs(hosts[host].sessions[node].sessions) do -- broadcast to all resources
+       local user = bare_sessions[node.."@"..host];
+       for _, res in pairs(user and user.sessions or NULL) do -- broadcast to all resources
                if res ~= origin and res.presence then -- to resource
                        stanza.attr.to = res.full_jid;
                        core_route_stanza(origin, stanza);
@@ -76,6 +82,7 @@ function handle_normal_presence(origin, stanza, core_route_stanza)
                end
        end
        if stanza.attr.type == nil and not origin.presence then -- initial presence
+               origin.presence = stanza; -- FIXME repeated later
                local probe = st.presence({from = origin.full_jid, type = "probe"});
                for jid, item in pairs(roster) do -- probe all contacts we are subscribed to
                        if item.subscription == "both" or item.subscription == "to" then
@@ -83,7 +90,7 @@ function handle_normal_presence(origin, stanza, core_route_stanza)
                                core_route_stanza(origin, probe);
                        end
                end
-               for _, res in pairs(hosts[host].sessions[node].sessions) do -- broadcast from all available resources
+               for _, res in pairs(user and user.sessions or NULL) do -- broadcast from all available resources
                        if res ~= origin and res.presence then
                                res.presence.attr.to = origin.full_jid;
                                core_route_stanza(res, res.presence);
@@ -114,7 +121,7 @@ function handle_normal_presence(origin, stanza, core_route_stanza)
                origin.presence = nil;
                if origin.priority then
                        origin.priority = nil;
-                       recalc_resource_map(origin);
+                       recalc_resource_map(user);
                end
                if origin.directed then
                        for jid in pairs(origin.directed) do
@@ -136,7 +143,7 @@ function handle_normal_presence(origin, stanza, core_route_stanza)
                else priority = 0; end
                if origin.priority ~= priority then
                        origin.priority = priority;
-                       recalc_resource_map(origin);
+                       recalc_resource_map(user);
                end
        end
        stanza.attr.to = nil; -- reset it
@@ -217,7 +224,7 @@ function handle_inbound_presence_subscriptions_and_probes(origin, stanza, from_b
        if stanza.attr.type == "probe" then
                if rostermanager.is_contact_subscribed(node, host, from_bare) then
                        if 0 == send_presence_of_available_resources(node, host, st_from, origin, core_route_stanza) then
-                               -- TODO send last recieved unavailable presence (or we MAY do nothing, which is fine too)
+                               core_route_stanza(hosts[host], st.presence({from=to_bare, to=from_bare, type="unavailable"})); -- TODO send last activity
                        end
                else
                        core_route_stanza(hosts[host], st.presence({from=to_bare, to=from_bare, type="unsubscribed"}));
@@ -227,7 +234,7 @@ function handle_inbound_presence_subscriptions_and_probes(origin, stanza, from_b
                        core_route_stanza(hosts[host], st.presence({from=to_bare, to=from_bare, type="subscribed"})); -- already subscribed
                        -- Sending presence is not clearly stated in the RFC, but it seems appropriate
                        if 0 == send_presence_of_available_resources(node, host, from_bare, origin, core_route_stanza) then
-                               -- TODO send last recieved unavailable presence (or we MAY do nothing, which is fine too)
+                               core_route_stanza(hosts[host], st.presence({from=to_bare, to=from_bare, type="unavailable"})); -- TODO send last activity
                        end
                else
                        core_route_stanza(hosts[host], st.presence({from=to_bare, to=from_bare, type="unavailable"})); -- acknowledging receipt
@@ -326,6 +333,20 @@ module:hook("presence/full", function(data)
        end -- resource not online, discard
        return true;
 end);
+module:hook("presence/host", function(data)
+       -- inbound presence to the host
+       local origin, stanza = data.origin, data.stanza;
+       
+       local from_bare = jid_bare(stanza.attr.from);
+       local t = stanza.attr.type;
+       if t == "probe" then
+               core_route_stanza(hosts[module.host], st.presence({ from = module.host, to = from_bare, id = stanza.attr.id }));
+       elseif t == "subscribe" then
+               core_route_stanza(hosts[module.host], st.presence({ from = module.host, to = from_bare, id = stanza.attr.id, type = "subscribed" }));
+               core_route_stanza(hosts[module.host], st.presence({ from = module.host, to = from_bare, id = stanza.attr.id }));
+       end
+       return true;
+end);
 
 module:hook("resource-unbind", function(event)
        local session, err = event.session, event.error;
index bda40124fc88de44724e22a9d8619375dec604b8..be1be0aebecb1dca7c28a74df66e74af49ff8c90 100644 (file)
@@ -141,7 +141,7 @@ module:add_iq_handler("c2s_unauthed", "jabber:iq:register", function (session, s
                                        username = nodeprep(table.concat(username));
                                        password = table.concat(password);
                                        local host = module.host;
-                                       if not username then
+                                       if not username or username == "" then
                                                session.send(st.error_reply(stanza, "modify", "not-acceptable", "The requested username is invalid."));
                                        elseif usermanager_user_exists(username, host) then
                                                session.send(st.error_reply(stanza, "cancel", "conflict", "The requested username already exists."));
index e248479bc9f471651ae786494ba01d12c3c5b8d9..2aee2be07740c67f1e692326c58825543bd63c73 100644 (file)
@@ -38,13 +38,13 @@ local new_sasl = require "util.sasl".new;
 local function build_reply(status, ret, err_msg)
        local reply = st.stanza(status, {xmlns = xmlns_sasl});
        if status == "challenge" then
-               log("debug", "%s", ret or "");
+               --log("debug", "CHALLENGE: %s", ret or "");
                reply:text(base64.encode(ret or ""));
        elseif status == "failure" then
                reply:tag(ret):up();
                if err_msg then reply:tag("text"):text(err_msg); end
        elseif status == "success" then
-               log("debug", "%s", ret or "");
+               --log("debug", "SUCCESS: %s", ret or "");
                reply:text(base64.encode(ret or ""));
        else
                module:log("error", "Unknown sasl status: %s", status);
@@ -124,7 +124,7 @@ local function sasl_handler(session, stanza)
        local text = stanza[1];
        if text then
                text = base64.decode(text);
-               log("debug", "%s", text);
+               --log("debug", "AUTH: %s", text:gsub("[%z\001-\008\011\012\014-\031]", " "));
                if not text then
                        session.sasl_handler = nil;
                        session.send(build_reply("failure", "incorrect-encoding"));
diff --git a/plugins/mod_selftests.lua b/plugins/mod_selftests.lua
deleted file mode 100644 (file)
index 1f41363..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
--- Prosody IM
--- Copyright (C) 2008-2009 Matthew Wild
--- Copyright (C) 2008-2009 Waqas Hussain
--- 
--- This project is MIT/X11 licensed. Please see the
--- COPYING file in the source package for more information.
---
-
-module.host = "*" -- Global module
-
-local st = require "util.stanza";
-local register_component = require "core.componentmanager".register_component;
-local core_route_stanza = core_route_stanza;
-local socket = require "socket";
-local ping_hosts = module:get_option("ping_hosts") or { "coversant.interop.xmpp.org", "djabberd.interop.xmpp.org", "djabberd-trunk.interop.xmpp.org", "ejabberd.interop.xmpp.org", "openfire.interop.xmpp.org" };
-
-local open_pings = {};
-
-local t_insert = table.insert;
-
-local log = require "util.logger".init("mod_selftests");
-
-local tests_jid = "self_tests@getjabber.ath.cx";
-local host = "getjabber.ath.cx";
-
-if not (tests_jid and host) then
-       for currhost in pairs(host) do
-               if currhost ~= "localhost" then
-                       tests_jid, host = "self_tests@"..currhost, currhost;
-               end
-       end
-end
-
-if tests_jid and host then
-       local bot = register_component(tests_jid,       function(origin, stanza, ourhost)
-                                                                               local time = open_pings[stanza.attr.id];
-                                                                               
-                                                                               if time then
-                                                                                       log("info", "Ping reply from %s in %fs", tostring(stanza.attr.from), socket.gettime() - time);
-                                                                               else
-                                                                                       log("info", "Unexpected reply: %s", stanza:pretty_print());
-                                                                               end
-                                                                       end);
-
-
-       local our_origin = hosts[host];
-       module:add_event_hook("server-started", 
-                                       function ()
-                                               local id = st.new_id();
-                                               local ping_attr = { xmlns = 'urn:xmpp:ping' };
-                                               local function send_ping(to)
-                                                       log("info", "Sending ping to %s", to);
-                                                       core_route_stanza(our_origin, st.iq{ to = to, from = tests_jid, id = id, type = "get" }:tag("ping", ping_attr));
-                                                       open_pings[id] = socket.gettime();
-                                               end
-                                               
-                                               for _, host in ipairs(ping_hosts) do
-                                                       send_ping(host);
-                                               end
-                                       end);
-end
index cceef308fdb036cf45e20e38f1ab73052e38a366..67555b1578e4c436d464091bfa4007349e4ecc0c 100644 (file)
@@ -14,9 +14,11 @@ local xmlns_starttls = 'urn:ietf:params:xml:ns:xmpp-tls';
 local secure_auth_only = module:get_option("c2s_require_encryption") or module:get_option("require_encryption");
 local secure_s2s_only = module:get_option("s2s_require_encryption");
 
+local host = hosts[module.host];
+
 module:add_handler("c2s_unauthed", "starttls", xmlns_starttls,
                function (session, stanza)
-                       if session.conn.starttls then
+                       if session.conn.starttls and host.ssl_ctx_in then
                                session.send(st.stanza("proceed", { xmlns = xmlns_starttls }));
                                session:reset_stream();
                                if session.host and hosts[session.host].ssl_ctx_in then
@@ -26,14 +28,15 @@ module:add_handler("c2s_unauthed", "starttls", xmlns_starttls,
                                session.log("info", "TLS negotiation started...");
                                session.secure = false;
                        else
-                               -- FIXME: What reply?
                                session.log("warn", "Attempt to start TLS, but TLS is not available on this connection");
+                               (session.sends2s or session.send)(st.stanza("failure", { xmlns = xmlns_starttls }));
+                               session:close();
                        end
                end);
                
 module:add_handler("s2sin_unauthed", "starttls", xmlns_starttls,
                function (session, stanza)
-                       if session.conn.starttls then
+                       if session.conn.starttls and host.ssl_ctx_in then
                                session.sends2s(st.stanza("proceed", { xmlns = xmlns_starttls }));
                                session:reset_stream();
                                if session.to_host and hosts[session.to_host].ssl_ctx_in then
@@ -43,8 +46,9 @@ module:add_handler("s2sin_unauthed", "starttls", xmlns_starttls,
                                session.log("info", "TLS negotiation started for incoming s2s...");
                                session.secure = false;
                        else
-                               -- FIXME: What reply?
                                session.log("warn", "Attempt to start TLS, but TLS is not available on this s2s connection");
+                               (session.sends2s or session.send)(st.stanza("failure", { xmlns = xmlns_starttls }));
+                               session:close();
                        end
                end);
 
@@ -66,7 +70,7 @@ module:hook("s2s-stream-features",
                function (data)
                        local session, features = data.session, data.features;
                        if session.to_host and session.conn.starttls then
-                               features:tag("starttls", starttls_attr):up();
+                               features:tag("starttls", starttls_attr);
                                if secure_s2s_only then
                                        features:tag("required"):up():up();
                                else
index 002498af371f69157b20471d465240884aaaf107..6068788649b6b421fd85472eee79e9cff716e370 100644 (file)
@@ -389,70 +389,51 @@ function room_mt:handle_to_occupant(origin, stanza) -- PM, vCards, etc
        end
 end
 
-function room_mt:send_form(origin, stanza)
-       local title = "Configuration for "..self.jid;
-       origin.send(st.reply(stanza):query("http://jabber.org/protocol/muc#owner")
-               :tag("x", {xmlns='jabber:x:data', type='form'})
-                       :tag("title"):text(title):up()
-                       :tag("instructions"):text(title):up()
-                       :tag("field", {type='hidden', var='FORM_TYPE'}):tag("value"):text("http://jabber.org/protocol/muc#roomconfig"):up():up()
-                       :tag("field", {type='boolean', label='Make Room Persistent?', var='muc#roomconfig_persistentroom'})
-                               :tag("value"):text(self._data.persistent and "1" or "0"):up()
-                       :up()
-                       :tag("field", {type='boolean', label='Make Room Publicly Searchable?', var='muc#roomconfig_publicroom'})
-                               :tag("value"):text(self._data.hidden and "0" or "1"):up()
-                       :up()
-       );
-end
-
-function room_mt:process_form(origin, stanza)
-       local query = stanza.tags[1];
-       local form;
-       for _, tag in ipairs(query.tags) do if tag.name == "x" and tag.attr.xmlns == "jabber:x:data" then form = tag; break; end end
-       if not form then origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); return; end
-       if form.attr.type == "cancel" then origin.send(st.reply(stanza)); return; end
-       if form.attr.type ~= "submit" then origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end
-       local fields = {};
-       for _, field in pairs(form.tags) do
-               if field.name == "field" and field.attr.var and field.tags[1].name == "value" and #field.tags[1].tags == 0 then
-                       fields[field.attr.var] = field.tags[1][1] or "";
+function room_mt:handle_form(origin, stanza)
+       if self:get_affiliation(stanza.attr.from) ~= "owner" then origin.send(st.error_reply(stanza, "auth", "forbidden")); return; end
+       if stanza.attr.type == "get" then
+               local title = "Configuration for "..self.jid;
+               origin.send(st.reply(stanza):query("http://jabber.org/protocol/muc#owner")
+                       :tag("x", {xmlns='jabber:x:data', type='form'})
+                               :tag("title"):text(title):up()
+                               :tag("instructions"):text(title):up()
+                               :tag("field", {type='hidden', var='FORM_TYPE'}):tag("value"):text("http://jabber.org/protocol/muc#roomconfig"):up():up()
+                               :tag("field", {type='boolean', label='Make Room Persistent?', var='muc#roomconfig_persistentroom'})
+                                       :tag("value"):text(self._data.persistent and "1" or "0"):up()
+                               :up()
+                               :tag("field", {type='boolean', label='Make Room Publicly Searchable?', var='muc#roomconfig_publicroom'})
+                                       :tag("value"):text(self._data.hidden and "0" or "1"):up()
+                               :up()
+               );
+       elseif stanza.attr.type == "set" then
+               local query = stanza.tags[1];
+               local form;
+               for _, tag in ipairs(query.tags) do if tag.name == "x" and tag.attr.xmlns == "jabber:x:data" then form = tag; break; end end
+               if not form then origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); return; end
+               if form.attr.type == "cancel" then origin.send(st.reply(stanza)); return; end
+               if form.attr.type ~= "submit" then origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end
+               local fields = {};
+               for _, field in pairs(form.tags) do
+                       if field.name == "field" and field.attr.var and field.tags[1].name == "value" and #field.tags[1].tags == 0 then
+                               fields[field.attr.var] = field.tags[1][1] or "";
+                       end
                end
-       end
-       if fields.FORM_TYPE ~= "http://jabber.org/protocol/muc#roomconfig" then origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end
+               if fields.FORM_TYPE ~= "http://jabber.org/protocol/muc#roomconfig" then origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end
 
-       local persistent = fields['muc#roomconfig_persistentroom'];
-       if persistent == "0" or persistent == "false" then persistent = nil; elseif persistent == "1" or persistent == "true" then persistent = true;
-       else origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end
-       self._data.persistent = persistent;
-       module:log("debug", "persistent=%s", tostring(persistent));
+               local persistent = fields['muc#roomconfig_persistentroom'];
+               if persistent == "0" or persistent == "false" then persistent = nil; elseif persistent == "1" or persistent == "true" then persistent = true;
+               else origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end
+               self._data.persistent = persistent;
+               module:log("debug", "persistent=%s", tostring(persistent));
 
-       local public = fields['muc#roomconfig_publicroom'];
-       if public == "0" or public == "false" then public = nil; elseif public == "1" or public == "true" then public = true;
-       else origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end
-       self._data.hidden = not public and true or nil;
+               local public = fields['muc#roomconfig_publicroom'];
+               if public == "0" or public == "false" then public = nil; elseif public == "1" or public == "true" then public = true;
+               else origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end
+               self._data.hidden = not public and true or nil;
 
-       if self.save then self:save(true); end
-       origin.send(st.reply(stanza));
-end
-
-function room_mt:destroy(newjid, reason, password)
-       local pr = st.presence({type = "unavailable"})
-               :tag("x", {xmlns = "http://jabber.org/protocol/muc#user"})
-                       :tag("item", { affiliation='none', role='none' }):up()
-                       :tag("destroy", {jid=newjid})
-       if reason then pr:tag("reason"):text(reason):up(); end
-       if password then pr:tag("password"):text(password):up(); end
-       for nick, occupant in pairs(self._occupants) do
-               pr.attr.from = nick;
-               for jid in pairs(occupant.sessions) do
-                       pr.attr.to = jid;
-                       self:_route_stanza(pr);
-                       self._jid_nick[jid] = nil;
-               end
-               self._occupants[nick] = nil;
+               if self.save then self:save(true); end
+               origin.send(st.reply(stanza));
        end
-       self._data.persistent = nil;
-       if self.save then self:save(true); end
 end
 
 function room_mt:handle_to_room(origin, stanza) -- presence changes and groupchat messages, along with disco/etc
@@ -482,6 +463,9 @@ function room_mt:handle_to_room(origin, stanza) -- presence changes and groupcha
                                        if not item.attr.jid and item.attr.nick then -- COMPAT Workaround for Miranda sending 'nick' instead of 'jid' when changing affiliation
                                                local occupant = self._occupants[self.jid.."/"..item.attr.nick];
                                                if occupant then item.attr.jid = occupant.jid; end
+                                       elseif not item.attr.nick and item.attr.jid then
+                                               local nick = self._jid_nick[item.attr.jid];
+                                               if nick then item.attr.nick = select(3, jid_split(nick)); end
                                        end
                                        local reason = item.tags[1] and item.tags[1].name == "reason" and #item.tags[1] == 1 and item.tags[1][1];
                                        if item.attr.affiliation and item.attr.jid and not item.attr.role then
@@ -513,9 +497,14 @@ function room_mt:handle_to_room(origin, stanza) -- presence changes and groupcha
                                                        -- TODO allow admins and owners not in room? Provide read-only access to everyone who can see the participants anyway?
                                                        if _rol == "none" then _rol = nil; end
                                                        local reply = st.reply(stanza):query("http://jabber.org/protocol/muc#admin");
-                                                       for nick, occupant in pairs(self._occupants) do
+                                                       for occupant_jid, occupant in pairs(self._occupants) do
                                                                if occupant.role == _rol then
-                                                                       reply:tag("item", {nick = nick, role = _rol or "none", affiliation = occupant.affiliation or "none", jid = occupant.jid}):up();
+                                                                       reply:tag("item", {
+                                                                               nick = select(3, jid_split(occupant_jid)),
+                                                                               role = _rol or "none",
+                                                                               affiliation = occupant.affiliation or "none",
+                                                                               jid = occupant.jid
+                                                                               }):up();
                                                                end
                                                        end
                                                        origin.send(reply);
@@ -530,30 +519,7 @@ function room_mt:handle_to_room(origin, stanza) -- presence changes and groupcha
                                origin.send(st.error_reply(stanza, "cancel", "bad-request"));
                        end
                elseif xmlns == "http://jabber.org/protocol/muc#owner" and (type == "get" or type == "set") and stanza.tags[1].name == "query" then
-                       if self:get_affiliation(stanza.attr.from) ~= "owner" then
-                               origin.send(st.error_reply(stanza, "auth", "forbidden"));
-                       elseif stanza.attr.type == "get" then
-                               self:send_form(origin, stanza);
-                       elseif stanza.attr.type == "set" then
-                               local child = stanza.tags[1].tags[1];
-                               if not child then
-                                       origin.send(st.error_reply(stanza, "auth", "bad-request"));
-                               elseif child.name == "destroy" then
-                                       local newjid = child.attr.jid;
-                                       local reason, password;
-                                       for _,tag in ipairs(child.tags) do
-                                               if tag.name == "reason" then
-                                                       reason = #tag.tags == 0 and tag[1];
-                                               elseif tag.name == "password" then
-                                                       password = #tag.tags == 0 and tag[1];
-                                               end
-                                       end
-                                       self:destroy(newjid, reason, password);
-                                       origin.send(st.reply(stanza));
-                               else
-                                       self:process_form(origin, stanza);
-                               end
-                       end
+                       self:handle_form(origin, stanza);
                elseif type == "set" or type == "get" then
                        origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
                end
@@ -704,21 +670,21 @@ function room_mt:get_role(nick)
        local session = self._occupants[nick];
        return session and session.role or nil;
 end
-function room_mt:set_role(actor, nick, role, callback, reason)
+function room_mt:set_role(actor, occupant_jid, role, callback, reason)
        if role == "none" then role = nil; end
        if role and role ~= "moderator" and role ~= "participant" and role ~= "visitor" then return nil, "modify", "not-acceptable"; end
        if self:get_affiliation(actor) ~= "owner" then return nil, "cancel", "not-allowed"; end
-       local occupant = self._occupants[nick];
+       local occupant = self._occupants[occupant_jid];
        if not occupant then return nil, "modify", "not-acceptable"; end
        if occupant.affiliation == "owner" or occupant.affiliation == "admin" then return nil, "cancel", "not-allowed"; end
-       local p = st.presence({from = nick})
+       local p = st.presence({from = occupant_jid})
                :tag("x", {xmlns = "http://jabber.org/protocol/muc#user"})
-                       :tag("item", {affiliation=occupant.affiliation or "none", nick=nick, role=role or "none"})
+                       :tag("item", {affiliation=occupant.affiliation or "none", nick=select(3, jid_split(occupant_jid)), role=role or "none"})
                                :tag("reason"):text(reason or ""):up()
                        :up();
        if not role then -- kick
                p.attr.type = "unavailable";
-               self._occupants[nick] = nil;
+               self._occupants[occupant_jid] = nil;
                for jid in pairs(occupant.sessions) do -- remove for all sessions of the nick
                        self._jid_nick[jid] = nil;
                end
@@ -731,7 +697,7 @@ function room_mt:set_role(actor, nick, role, callback, reason)
                self:_route_stanza(p);
        end
        if callback then callback(); end
-       self:broadcast_except_nick(p, nick);
+       self:broadcast_except_nick(p, occupant_jid);
        return true;
 end
 
diff --git a/prosody b/prosody
index 40aeac5ed8dce99f6313fed3323293b66188cd31..7f4a2cec58a38e70df493abdbdd44b8cdfb27df0 100755 (executable)
--- a/prosody
+++ b/prosody
@@ -198,7 +198,7 @@ function init_global_state()
                                if type(port) ~= "number" then
                                        log("error", "Non-numeric "..option.."_ports: "..tostring(port));
                                else
-                                       cl.start(listener, { 
+                                       local ok, err = cl.start(listener, {
                                                ssl = conntype ~= "tcp" and global_ssl_ctx,
                                                port = port,
                                                interface = (option and config.get("*", "core", option.."_interface"))
@@ -206,6 +206,33 @@ function init_global_state()
                                                        or config.get("*", "core", "interface"),
                                                type = conntype
                                        });
+                                       if not ok then
+                                               local friendly_message = err;
+                                               if err:match(" in use") then
+                                                       if port == 5222 or port == 5223 or port == 5269 then
+                                                               friendly_message = "check that Prosody or another XMPP server is "
+                                                                       .."not already running and using this port";
+                                                       elseif port == 80 or port == 81 then
+                                                               friendly_message = "check that a HTTP server is not already using "
+                                                                       .."this port";
+                                                       elseif port == 5280 then
+                                                               friendly_message = "check that Prosody or a BOSH connection manager "
+                                                                       .."is not already running";
+                                                       else
+                                                               friendly_message = "this port is in use by another application";
+                                                       end
+                                               elseif err:match("permission") then
+                                                       friendly_message = "Prosody does not have sufficient privileges to use this port";
+                                               elseif err == "no ssl context" then
+                                                       if not config.get("*", "core", "ssl") then
+                                                               friendly_message = "there is no 'ssl' config under Host \"*\" which is "
+                                                                       .."require for legacy SSL ports";
+                                                       else
+                                                               friendly_message = "initializing SSL support failed, see previous log entries";
+                                                       end
+                                               end
+                                               log("error", "Failed to open server port %d, %s", port, friendly_message);
+                                       end
                                end
                        end
                end
@@ -280,15 +307,18 @@ function prepare_to_start()
        -- start listening on sockets
        prosody.net_activate_ports("c2s", "xmppclient", {5222});
        prosody.net_activate_ports("s2s", "xmppserver", {5269});
-       prosody.net_activate_ports("component", "xmppcomponent", {}, "tcp");
+       prosody.net_activate_ports("component", "xmppcomponent", {5347}, "tcp");
        prosody.net_activate_ports("legacy_ssl", "xmppclient", {}, "ssl");
 
        prosody.start_time = os.time();
 end    
 
 function init_global_protection()
-       -- Catch global accesses --
-       local locked_globals_mt = { __index = function (t, k) error("Attempt to read a non-existent global '"..k.."'", 2); end, __newindex = function (t, k, v) error("Attempt to set a global: "..tostring(k).." = "..tostring(v), 2); end }
+       -- Catch global accesses
+       local locked_globals_mt = {
+               __index = function (t, k) log("warn", "%s", debug.traceback("Attempt to read a non-existent global '"..tostring(k).."'", 2)); end;
+               __newindex = function (t, k, v) error("Attempt to set a global: "..tostring(k).." = "..tostring(v), 2); end;
+       };
                
        function prosody.unlock_globals()
                setmetatable(_G, nil);
index d660a9bd21d80b0219700be4ace0af008292059e..04a1ce5ee84f442c521df8a5324cc86ffabfc5ff 100644 (file)
@@ -63,7 +63,7 @@ Host "*"
 
                        -- Other specific functionality
                                --"posix"; -- POSIX functionality, sends server to background, enables syslog, etc.
-                               --"console"; -- telnet to port 5582 (needs console_enabled = true)
+                               --"console"; -- Opens admin telnet interface on localhost port 5582
                                --"bosh"; -- Enable BOSH clients, aka "Jabber over HTTP"
                                --"httpserver"; -- Serve static files from a directory over HTTP
                          };
index 7b19260d749fc2cc74fc0e9bc8eb2bda401a68a6..d0c22df71843c51cfcdb7976c04aa39a4827c05c 100755 (executable)
@@ -49,7 +49,7 @@ function vcard(node, host, stanza)
 end
 function password(node, host, password)
        local ret, err = dm.store(node, host, "accounts", {password = password});
-       print("["..(err or "success").."] accounts: "..node.."@"..host.." = "..password);
+       print("["..(err or "success").."] accounts: "..node.."@"..host);
 end
 function roster(node, host, jid, item)
        local roster = dm.load(node, host, "roster") or {};
index f652af5ba034a4caa0c20a3a142826c8acc9d291..0e5dd0c4e9e54b6ae862008415a0240a9076d2e0 100644 (file)
@@ -254,7 +254,7 @@ end
 for i, row in ipairs(t["users"] or NULL) do
        local node, password = row.username, row.password;
        local ret, err = dm.store(node, host, "accounts", {password = password});
-       print("["..(err or "success").."] accounts: "..node.."@"..host.." = "..password);
+       print("["..(err or "success").."] accounts: "..node.."@"..host);
 end
 
 function roster(node, host, jid, item)
index 5147512f82fd210b9285c0db73259363e3be5ee8..c573a330b6709ba4a8feb4b69771944309f49ab6 100644 (file)
@@ -174,7 +174,7 @@ static int Lidna_to_ascii(lua_State *L)             /** idna.to_ascii(s) */
        size_t len;
        const char *s = luaL_checklstring(L, 1, &len);
        char* output = NULL;
-       int ret = idna_to_ascii_8z(s, &output, 0);
+       int ret = idna_to_ascii_8z(s, &output, IDNA_USE_STD3_ASCII_RULES);
        if (ret == IDNA_SUCCESS) {
                lua_pushstring(L, output);
                idn_free(output);
index 94086ed64dc3ca24e77e7e8faec8a24dcecd7d96..cef752879bbcfaf1c503fa15249a8367fe39826d 100644 (file)
@@ -463,9 +463,10 @@ int lc_getrlimit(lua_State *L) {
        return 3;
 }
 
-void lc_abort(lua_State* L)
+int lc_abort(lua_State* L)
 {
        abort();
+       return 0;
 }
 
 /* Register functions */
index a3bde8caab1c8879965efef52a9b42a715b03a2c..56671347a86eb4b6fe96cef917f66e5b6c8f90a8 100644 (file)
@@ -23,8 +23,8 @@ function new(layout)
        return setmetatable(layout, form_mt);
 end
 
-function form_t.form(layout, data)
-       local form = st.stanza("x", { xmlns = xmlns_forms, type = "form" });
+function form_t.form(layout, data, formtype)
+       local form = st.stanza("x", { xmlns = xmlns_forms, type = formtype or "form" });
        if layout.title then
                form:tag("title"):text(layout.title):up();
        end
index a0535e5c44d7c002157d279938d1e5163d6e2a28..cb0226448268c01f5f5e6ecb630ce08f27b79d20 100644 (file)
@@ -17,8 +17,12 @@ local function missingdep(name, sources, msg)
        print("Prosody was unable to find "..tostring(name));
        print("This package can be obtained in the following ways:");
        print("");
-       for k,v in pairs(sources) do
-               print("", k, v);
+       local longest_platform = 0;
+       for platform in pairs(sources) do
+               longest_platform = math.max(longest_platform, #platform);
+       end
+       for platform, source in pairs(sources) do
+               print("", platform..":"..(" "):rep(4+longest_platform-#platform)..source);
        end
        print("");
        print(msg or (name.." is required for Prosody to run, so we will now exit."));
index 9c8fff7804a28edd6b7e4e430b846d3d23093696..5bc6db75c2835c0b3887fdb32467a190d4dc18b8 100644 (file)
 
 local md5 = require "util.hashes".md5;
 local log = require "util.logger".init("sasl");
-local st = require "util.stanza";
-local set = require "util.set";
-local array = require "util.array";
-local to_unicode = require "util.encodings".idna.to_unicode;
-
 local tostring = tostring;
-local pairs, ipairs = pairs, ipairs;
+local st = require "util.stanza";
+local generate_uuid = require "util.uuid".generate;
 local t_insert, t_concat = table.insert, table.concat;
+local to_byte, to_char = string.byte, string.char;
+local to_unicode = require "util.encodings".idna.to_unicode;
 local s_match = string.match;
+local gmatch = string.gmatch
+local string = string
+local math = require "math"
 local type = type
 local error = error
-local setmetatable = setmetatable;
-local assert = assert;
-local require = require;
-
-require "util.iterators"
-local keys = keys
+local print = print
 
-local array = require "util.array"
 module "sasl"
 
---[[
-Authentication Backend Prototypes:
+-- Credentials handler:
+--   Arguments: ("PLAIN", user, host, password)
+--   Returns: true (success) | false (fail) | nil (user unknown)
+local function new_plain(realm, credentials_handler)
+       local object = { mechanism = "PLAIN", realm = realm, credentials_handler = credentials_handler}
+       function object.feed(self, message)
+               if message == "" or message == nil then return "failure", "malformed-request" end
+               local response = message
+               local authorization = s_match(response, "([^%z]*)")
+               local authentication = s_match(response, "%z([^%z]+)%z")
+               local password = s_match(response, "%z[^%z]+%z([^%z]+)")
 
-state = false : disabled
-state = true : enabled
-state = nil : non-existant
+    if authentication == nil or password == nil then return "failure", "malformed-request" end
+    self.username = authentication
+    local auth_success = self.credentials_handler("PLAIN", self.username, self.realm, password)
 
-plain:
-       function(username, realm)
-               return password, state;
-       end
+    if auth_success then
+      return "success"
+    elseif auth_success == nil then
+      return "failure", "account-disabled"
+    else
+      return "failure", "not-authorized"
+    end
+  end
+  return object
+end
 
-plain-test:
-       function(username, realm, password)
-               return true or false, state;
-       end
+-- credentials_handler:
+--   Arguments: (mechanism, node, domain, realm, decoder)
+--   Returns: Password encoding, (plaintext) password
+-- implementing RFC 2831
+local function new_digest_md5(realm, credentials_handler)
+       --TODO complete support for authzid
+
+       local function serialize(message)
+               local data = ""
 
-digest-md5:
-       function(username, domain, realm, encoding) -- domain and realm are usually the same; for some broken
-                                                                                               -- implementations it's not
-               return digesthash, state;
+               if type(message) ~= "table" then error("serialize needs an argument of type table.") end
+
+               -- testing all possible values
+               if message["realm"] then data = data..[[realm="]]..message.realm..[[",]] end
+               if message["nonce"] then data = data..[[nonce="]]..message.nonce..[[",]] end
+               if message["qop"] then data = data..[[qop="]]..message.qop..[[",]] end
+               if message["charset"] then data = data..[[charset=]]..message.charset.."," end
+               if message["algorithm"] then data = data..[[algorithm=]]..message.algorithm.."," end
+               if message["rspauth"] then data = data..[[rspauth=]]..message.rspauth.."," end
+               data = data:gsub(",$", "")
+               return data
        end
 
-digest-md5-test:
-       function(username, domain, realm, encoding, digesthash)
-               return true or false, state;
+       local function utf8tolatin1ifpossible(passwd)
+               local i = 1;
+               while i <= #passwd do
+                       local passwd_i = to_byte(passwd:sub(i, i));
+                       if passwd_i > 0x7F then
+                               if passwd_i < 0xC0 or passwd_i > 0xC3 then
+                                       return passwd;
+                               end
+                               i = i + 1;
+                               passwd_i = to_byte(passwd:sub(i, i));
+                               if passwd_i < 0x80 or passwd_i > 0xBF then
+                                       return passwd;
+                               end
+                       end
+                       i = i + 1;
+               end
+
+               local p = {};
+               local j = 0;
+               i = 1;
+               while (i <= #passwd) do
+                       local passwd_i = to_byte(passwd:sub(i, i));
+                       if passwd_i > 0x7F then
+                               i = i + 1;
+                               local passwd_i_1 = to_byte(passwd:sub(i, i));
+                               t_insert(p, to_char(passwd_i%4*64 + passwd_i_1%64)); -- I'm so clever
+                       else
+                               t_insert(p, to_char(passwd_i));
+                       end
+                       i = i + 1;
+               end
+               return t_concat(p);
        end
-]]
-
-local method = {};
-method.__index = method;
-local mechanisms = {};
-local backend_mechanism = {};
-
--- register a new SASL mechanims
-local function registerMechanism(name, backends, f)
-       assert(type(name) == "string", "Parameter name MUST be a string.");
-       assert(type(backends) == "string" or type(backends) == "table", "Parameter backends MUST be either a string or a table.");
-       assert(type(f) == "function", "Parameter f MUST be a function.");
-       mechanisms[name] = f
-       for _, backend_name in ipairs(backends) do
-               if backend_mechanism[backend_name] == nil then backend_mechanism[backend_name] = {}; end
-               t_insert(backend_mechanism[backend_name], name);
+       local function latin1toutf8(str)
+               local p = {};
+               for ch in gmatch(str, ".") do
+                       ch = to_byte(ch);
+                       if (ch < 0x80) then
+                               t_insert(p, to_char(ch));
+                       elseif (ch < 0xC0) then
+                               t_insert(p, to_char(0xC2, ch));
+                       else
+                               t_insert(p, to_char(0xC3, ch - 64));
+                       end
+               end
+               return t_concat(p);
+       end
+       local function parse(data)
+               local message = {}
+               -- COMPAT: %z in the pattern to work around jwchat bug (sends "charset=utf-8\0")
+               for k, v in gmatch(data, [[([%w%-]+)="?([^",%z]*)"?,?]]) do -- FIXME The hacky regex makes me shudder
+                       message[k] = v;
+               end
+               return message;
        end
-end
 
--- create a new SASL object which can be used to authenticate clients
-function new(realm, profile, forbidden)
-       local sasl_i = {profile = profile};
-       sasl_i.realm = realm;
-       local s = setmetatable(sasl_i, method);
-       if forbidden == nil then forbidden = {} end
-       s:forbidden(forbidden)
-       return s;
-end
+       local object = { mechanism = "DIGEST-MD5", realm = realm, credentials_handler = credentials_handler};
 
--- get a fresh clone with the same realm, profiles and forbidden mechanisms
-function method:clean_clone()
-       return new(self.realm, self.profile, self:forbidden())
-end
+       object.nonce = generate_uuid();
+       object.step = 0;
+       object.nonce_count = {};
 
--- set the forbidden mechanisms
-function method:forbidden( restrict )
-       if restrict then
-               -- set forbidden
-               self.restrict = set.new(restrict);
-       else
-               -- get forbidden
-               return array.collect(self.restrict:items());
-       end
-end
+       function object.feed(self, message)
+               self.step = self.step + 1;
+               if (self.step == 1) then
+                       local challenge = serialize({   nonce = object.nonce,
+                                                                                       qop = "auth",
+                                                                                       charset = "utf-8",
+                                                                                       algorithm = "md5-sess",
+                                                                                       realm = self.realm});
+                       return "challenge", challenge;
+               elseif (self.step == 2) then
+                       local response = parse(message);
+                       -- check for replay attack
+                       if response["nc"] then
+                               if self.nonce_count[response["nc"]] then return "failure", "not-authorized" end
+                       end
+
+                       -- check for username, it's REQUIRED by RFC 2831
+                       if not response["username"] then
+                               return "failure", "malformed-request";
+                       end
+                       self["username"] = response["username"];
+
+                       -- check for nonce, ...
+                       if not response["nonce"] then
+                               return "failure", "malformed-request";
+                       else
+                               -- check if it's the right nonce
+                               if response["nonce"] ~= tostring(self.nonce) then return "failure", "malformed-request" end
+                       end
+
+                       if not response["cnonce"] then return "failure", "malformed-request", "Missing entry for cnonce in SASL message." end
+                       if not response["qop"] then response["qop"] = "auth" end
+
+                       if response["realm"] == nil or response["realm"] == "" then
+                               response["realm"] = "";
+                       elseif response["realm"] ~= self.realm then
+                               return "failure", "not-authorized", "Incorrect realm value";
+                       end
+
+                       local decoder;
+                       if response["charset"] == nil then
+                               decoder = utf8tolatin1ifpossible;
+                       elseif response["charset"] ~= "utf-8" then
+                               return "failure", "incorrect-encoding", "The client's response uses "..response["charset"].." for encoding with isn't supported by sasl.lua. Supported encodings are latin or utf-8.";
+                       end
+
+                       local domain = "";
+                       local protocol = "";
+                       if response["digest-uri"] then
+                               protocol, domain = response["digest-uri"]:match("(%w+)/(.*)$");
+                               if protocol == nil or domain == nil then return "failure", "malformed-request" end
+                       else
+                               return "failure", "malformed-request", "Missing entry for digest-uri in SASL message."
+                       end
 
--- get a list of possible SASL mechanims to use
-function method:mechanisms()
-       local mechanisms = {}
-       for backend, f in pairs(self.profile) do
-               if backend_mechanism[backend] then
-                       for _, mechanism in ipairs(backend_mechanism[backend]) do
-                               if not self.restrict:contains(mechanism) then
-                                       mechanisms[mechanism] = true;
+                       --TODO maybe realm support
+                       self.username = response["username"];
+                       local password_encoding, Y = self.credentials_handler("DIGEST-MD5", response["username"], self.realm, response["realm"], decoder);
+                       if Y == nil then return "failure", "not-authorized"
+                       elseif Y == false then return "failure", "account-disabled" end
+                       local A1 = "";
+                       if response.authzid then
+                               if response.authzid == self.username or response.authzid == self.username.."@"..self.realm then
+                                       -- COMPAT
+                                       log("warn", "Client is violating RFC 3920 (section 6.1, point 7).");
+                                       A1 = Y..":"..response["nonce"]..":"..response["cnonce"]..":"..response.authzid;
+                               else
+                                       return "failure", "invalid-authzid";
                                end
+                       else
+                               A1 = Y..":"..response["nonce"]..":"..response["cnonce"];
+                       end
+                       local A2 = "AUTHENTICATE:"..protocol.."/"..domain;
+
+                       local HA1 = md5(A1, true);
+                       local HA2 = md5(A2, true);
+
+                       local KD = HA1..":"..response["nonce"]..":"..response["nc"]..":"..response["cnonce"]..":"..response["qop"]..":"..HA2;
+                       local response_value = md5(KD, true);
+
+                       if response_value == response["response"] then
+                               -- calculate rspauth
+                               A2 = ":"..protocol.."/"..domain;
+
+                               HA1 = md5(A1, true);
+                               HA2 = md5(A2, true);
+
+                               KD = HA1..":"..response["nonce"]..":"..response["nc"]..":"..response["cnonce"]..":"..response["qop"]..":"..HA2
+                               local rspauth = md5(KD, true);
+                               self.authenticated = true;
+                               return "challenge", serialize({rspauth = rspauth});
+                       else
+                               return "failure", "not-authorized", "The response provided by the client doesn't match the one we calculated."
                        end
+               elseif self.step == 3 then
+                       if self.authenticated ~= nil then return "success"
+                       else return "failure", "malformed-request" end
                end
        end
-       self["possible_mechanisms"] = mechanisms;
-       return array.collect(keys(mechanisms));
+       return object;
 end
 
--- select a mechanism to use
-function method:select(mechanism)
-       if self.mech_i then
-               return false;
-       end
-       
-       self.mech_i = mechanisms[mechanism]
-       if self.mech_i == nil then 
-               return false;
-       end
-       return true;
+-- Credentials handler: Can be nil. If specified, should take the mechanism as
+-- the only argument, and return true for OK, or false for not-OK (TODO)
+local function new_anonymous(realm, credentials_handler)
+       local object = { mechanism = "ANONYMOUS", realm = realm, credentials_handler = credentials_handler}
+               function object.feed(self, message)
+                       return "success"
+               end
+       object["username"] = generate_uuid()
+       return object
 end
 
--- feed new messages to process into the library
-function method:process(message)
-       --if message == "" or message == nil then return "failure", "malformed-request" end
-       return self.mech_i(self, message);
-end
 
--- load the mechanisms
-load_mechs = {"plain", "digest-md5", "anonymous", "scram"}
-for _, mech in ipairs(load_mechs) do
-       local name = "util.sasl."..mech;
-       local m = require(name);
-       m.init(registerMechanism)
+function new(mechanism, realm, credentials_handler)
+       local object
+       if mechanism == "PLAIN" then object = new_plain(realm, credentials_handler)
+       elseif mechanism == "DIGEST-MD5" then object = new_digest_md5(realm, credentials_handler)
+       elseif mechanism == "ANONYMOUS" then object = new_anonymous(realm, credentials_handler)
+       else
+               log("debug", "Unsupported SASL mechanism: "..tostring(mechanism));
+               return nil
+       end
+       return object
 end
 
 return _M;
index 8d3b7747b39dadb50ff73c98dc5bd250f34e7471..069daa53ed7e4546f7b8a2fa4e6b418c757e6de5 100644 (file)
@@ -291,13 +291,16 @@ function reply(orig)
        return stanza(orig.name, orig.attr and { to = orig.attr.from, from = orig.attr.to, id = orig.attr.id, type = ((orig.name == "iq" and "result") or orig.attr.type) });
 end
 
-function error_reply(orig, type, condition, message)
-       local t = reply(orig);
-       t.attr.type = "error";
-       t:tag("error", {type = type})
-               :tag(condition, {xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas"}):up();
-       if (message) then t:tag("text"):text(message):up(); end
-       return t; -- stanza ready for adding app-specific errors
+do
+       local xmpp_stanzas_attr = { xmlns = xmlns_stanzas };
+       function error_reply(orig, type, condition, message)
+               local t = reply(orig);
+               t.attr.type = "error";
+               t:tag("error", {type = type}) --COMPAT: Some day xmlns:stanzas goes here
+                       :tag(condition, xmpp_stanzas_attr):up();
+               if (message) then t:tag("text", xmpp_stanzas_attr):text(message):up(); end
+               return t; -- stanza ready for adding app-specific errors
+       end
 end
 
 function presence(attr)