Merge with 0.5
authorWaqas Hussain <waqas20@gmail.com>
Tue, 13 Oct 2009 10:00:59 +0000 (15:00 +0500)
committerWaqas Hussain <waqas20@gmail.com>
Tue, 13 Oct 2009 10:00:59 +0000 (15:00 +0500)
core/componentmanager.lua
core/hostmanager.lua
core/modulemanager.lua
core/s2smanager.lua
core/sessionmanager.lua
plugins/mod_console.lua
plugins/mod_dialback.lua
plugins/mod_tls.lua
util/array.lua
util/dataforms.lua
util/stanza.lua

index 6984ba31aaf6a6423878a63d38715493b4a12aff..6f5e28e608abc6dc35d104f8530fb4b59c910815 100644 (file)
@@ -70,7 +70,18 @@ end
 
 function create_component(host, component, events)
        -- TODO check for host well-formedness
-       return { type = "component", host = host, connected = true, s2sout = {}, events = events or events_new() };
+       local ssl_ctx;
+       if host then
+               -- We need to find SSL context to use...
+               -- Discussion in prosody@ concluded that
+               -- 1 level back is usually enough by default
+               local base_host = host:gsub("^[^%.]+%.", "");
+               if hosts[base_host] then
+                       ssl_ctx = hosts[base_host].ssl_ctx;
+               end
+       end
+       return { type = "component", host = host, connected = true, s2sout = {}, 
+                       ssl_ctx = ssl_ctx, events = events or events_new() };
 end
 
 function register_component(host, component, session)
@@ -89,8 +100,8 @@ function register_component(host, component, session)
                if not(host:find("@", 1, true) or host:find("/", 1, true)) and host:find(".", 1, true) then
                        disco_items:set(host:sub(host:find(".", 1, true)+1), host, true);
                end
-               -- FIXME only load for a.b.c if b.c has dialback, and/or check in config
                modulemanager.load(host, "dialback");
+               modulemanager.load(host, "tls");
                log("debug", "component added: "..host);
                return session or hosts[host];
        else
index 2fcfc0f470c284c87c1f1ea725db0f38344c15af..2c8174eee054ac2d0143cda415208a850240dddd 100644 (file)
@@ -51,9 +51,11 @@ function activate(host, host_config)
                end
        end
        
-       local ssl_config = host_config.core.ssl or configmanager.get("*", "core", "ssl");
-       if ssl_config then
-               hosts[host].ssl_ctx = ssl.newcontext(setmetatable(ssl_config, { __index = default_ssl_ctx }));
+       if ssl then
+               local ssl_config = host_config.core.ssl or configmanager.get("*", "core", "ssl");
+               if ssl_config then
+                       hosts[host].ssl_ctx = ssl.newcontext(setmetatable(ssl_config, { __index = default_ssl_ctx }));
+               end
         end
 
        log((hosts_loaded_once and "info") or "debug", "Activated host: %s", host);
index 7ca12ddafc538561c0ea11b9d7cf8a959a1b5cd7..465b9dd8cd3dc2eff5067446fc90e595220bcaa6 100644 (file)
@@ -6,13 +6,10 @@
 -- COPYING file in the source package for more information.
 --
 
-
-
 local plugin_dir = CFG_PLUGINDIR or "./plugins/";
 
 local logger = require "util.logger";
 local log = logger.init("modulemanager");
-local addDiscoInfoHandler = require "core.discomanager".addDiscoInfoHandler;
 local eventmanager = require "core.eventmanager";
 local config = require "core.configmanager";
 local multitable_new = require "util.multitable".new;
@@ -50,8 +47,6 @@ local handler_info = {};
 
 local modulehelpers = setmetatable({}, { __index = _G });
 
-local features_table = multitable_new();
-local identities_table = multitable_new();
 local handler_table = multitable_new();
 local hooked = multitable_new();
 local hooks = multitable_new();
@@ -173,8 +168,6 @@ function unload(host, name, ...)
                end
        end
        modulemap[host][name] = nil;
-       features_table:remove(host, name);
-       identities_table:remove(host, name);
        local params = handler_table:get(host, name); -- , {module.host, origin_type, tag, xmlns}
        for _, param in pairs(params or NULL) do
                local handlers = stanza_handlers:get(param[1], param[2], param[3], param[4]);
@@ -254,12 +247,13 @@ function handle_stanza(host, origin, stanza)
                (handlers[1])(origin, stanza);
                return true;
        else
-               log("debug", "Unhandled %s stanza: %s; xmlns=%s", origin.type, stanza.name, xmlns); -- we didn't handle it
                if stanza.attr.xmlns == "jabber:client" then
+                       log("debug", "Unhandled %s stanza: %s; xmlns=%s", origin.type, stanza.name, xmlns); -- we didn't handle it
                        if stanza.attr.type ~= "error" and stanza.attr.type ~= "result" then
                                origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
                        end
                elseif not((name == "features" or name == "error") and xmlns == "http://etherx.jabber.org/streams") then -- FIXME remove check once we handle S2S features
+                       log("warn", "Unhandled %s stream element: %s; xmlns=%s: %s", origin.type, stanza.name, xmlns, tostring(stanza)); -- we didn't handle it
                        origin:close("unsupported-stanza-type");
                end
        end
@@ -330,50 +324,11 @@ function api:add_iq_handler(origin_type, xmlns, handler)
        self:add_handler(origin_type, "iq", xmlns, handler);
 end
 
-addDiscoInfoHandler("*host", function(reply, to, from, node)
-       if #node == 0 then
-               local done = {};
-               for module, identities in pairs(identities_table:get(to) or NULL) do -- for each module
-                       for identity, attr in pairs(identities) do
-                               if not done[identity] then
-                                       reply:tag("identity", attr):up(); -- TODO cache
-                                       done[identity] = true;
-                               end
-                       end
-               end
-               for module, identities in pairs(identities_table:get("*") or NULL) do -- for each module
-                       for identity, attr in pairs(identities) do
-                               if not done[identity] then
-                                       reply:tag("identity", attr):up(); -- TODO cache
-                                       done[identity] = true;
-                               end
-                       end
-               end
-               for module, features in pairs(features_table:get(to) or NULL) do -- for each module
-                       for feature in pairs(features) do
-                               if not done[feature] then
-                                       reply:tag("feature", {var = feature}):up(); -- TODO cache
-                                       done[feature] = true;
-                               end
-                       end
-               end
-               for module, features in pairs(features_table:get("*") or NULL) do -- for each module
-                       for feature in pairs(features) do
-                               if not done[feature] then
-                                       reply:tag("feature", {var = feature}):up(); -- TODO cache
-                                       done[feature] = true;
-                               end
-                       end
-               end
-               return next(done) ~= nil;
-       end
-end);
-
 function api:add_feature(xmlns)
-       features_table:set(self.host, self.name, xmlns, true);
+       self:add_item("feature", xmlns);
 end
-function api:add_identity(category, type)
-       identities_table:set(self.host, self.name, category.."\0"..type, {category = category, type = type});
+function api:add_identity(category, type, name)
+       self:add_item("identity", {category = category, type = type, name = name});
 end
 
 local event_hook = function(host, mod_name, event_name, ...)
@@ -420,6 +375,50 @@ function api:require(lib)
        return f();
 end
 
+function api:get_option(name, default_value)
+       return config.get(self.host, self.name, name) or config.get(self.host, "core", name) or default_value;
+end
+
+local t_remove = _G.table.remove;
+local module_items = multitable_new();
+function api:add_item(key, value)
+       self.items = self.items or {};
+       self.items[key] = self.items[key] or {};
+       t_insert(self.items[key], value);
+       self:fire_event("item-added/"..key, {source = self, item = value});
+end
+function api:remove_item(key, value)
+       local t = self.items and self.items[key] or NULL;
+       for i = #t,1,-1 do
+               if t[i] == value then
+                       t_remove(self.items[key], i);
+                       self:fire_event("item-removed/"..key, {source = self, item = value});
+                       return value;
+               end
+       end
+end
+
+function api:get_host_items(key)
+       local result = {};
+       for mod_name, module in pairs(modulemap[self.host]) do
+               module = module.module;
+               if module.items then
+                       for _, item in ipairs(module.items[key] or NULL) do
+                               t_insert(result, item);
+                       end
+               end
+       end
+       for mod_name, module in pairs(modulemap["*"]) do
+               module = module.module;
+               if module.items then
+                       for _, item in ipairs(module.items[key] or NULL) do
+                               t_insert(result, item);
+                       end
+               end
+       end
+       return result;
+end
+
 --------------------------------------------------------------------
 
 local actions = {};
index cdfadba061519c7dd0f4d405f5388f6fa086bf6b..8421afbaa01dba9ef5f9f509efc39d2203312827 100644 (file)
@@ -344,7 +344,9 @@ function streamopened(session, attr)
        end
        
        if session.version >= 1.0 and not (attr.to and attr.from) then
-               log("warn", (session.to_host or "(unknown)").." failed to specify 'to' or 'from' hostname as per RFC");
+               
+               (session.log or log)("warn", "Remote of stream "..(session.from_host or "(unknown)").."->"..(session.to_host or "(unknown)")
+                       .." failed to specify to (%s) and/or from (%s) hostname as per RFC", tostring(attr.to), tostring(attr.from));
        end
        
        if session.direction == "incoming" then
@@ -356,7 +358,7 @@ function streamopened(session, attr)
                (session.log or log)("debug", "incoming s2s received <stream:stream>");
                send("<?xml version='1.0'?>");
                send(stanza("stream:stream", { xmlns='jabber:server', ["xmlns:db"]='jabber:server:dialback', 
-                               ["xmlns:stream"]='http://etherx.jabber.org/streams', id=session.streamid, from=session.to_host, version="1.0" }):top_tag());
+                               ["xmlns:stream"]='http://etherx.jabber.org/streams', id=session.streamid, from=session.to_host, version=(session.version > 0 and "1.0" or nil) }):top_tag());
                if session.to_host and not hosts[session.to_host] then
                        -- Attempting to connect to a host we don't serve
                        session:close({ condition = "host-unknown"; text = "This host does not serve "..session.to_host });
@@ -364,7 +366,12 @@ function streamopened(session, attr)
                end
                if session.version >= 1.0 then
                        local features = st.stanza("stream:features");
-                       fire_event("s2s-stream-features", session, features);
+                                                       
+                       if session.to_host then
+                               hosts[session.to_host].events.fire_event("s2s-stream-features", { session = session, features = features });
+                       else
+                               (session.log or log)("warn", "No 'to' on stream header from %s means we can't offer any features", session.from_host or "unknown host");
+                       end
                        
                        log("debug", "Sending stream features: %s", tostring(features));
                        send(features);
index bc45d104c734ce2ad18e7491961b00905488b2fa..08e70d44dc3f53c5ea00e266a5254f4df8327fe5 100644 (file)
@@ -169,29 +169,30 @@ function streamopened(session, attr)
        session.version = tonumber(attr.version) or 0;
        session.streamid = uuid_generate();
        (session.log or session)("debug", "Client sent opening <stream:stream> to %s", session.host);
-       
-       send("<?xml version='1.0'?>");
-       send(format("<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' id='%s' from='%s' version='1.0' xml:lang='en'>", session.streamid, session.host));
 
        if not hosts[session.host] then
                -- We don't serve this host...
                session:close{ condition = "host-unknown", text = "This server does not serve "..tostring(session.host)};
                return;
        end
-       
+
+       send("<?xml version='1.0'?>");
+       send(format("<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' id='%s' from='%s' version='1.0' xml:lang='en'>", session.streamid, session.host));
+
+       (session.log or log)("debug", "Sent reply <stream:stream> to client");
+       session.notopen = nil;
+
        -- If session.secure is *false* (not nil) then it means we /were/ encrypting
        -- since we now have a new stream header, session is secured
        if session.secure == false then
                session.secure = true;
        end
-                                               
+
        local features = st.stanza("stream:features");
        fire_event("stream-features", session, features);
-       
+
        send(features);
-       
-       (session.log or log)("debug", "Sent reply <stream:stream> to client");
-       session.notopen = nil;
+
 end
 
 function streamclosed(session)
index b9d30716b929744ec56fe9a99c5e840faa0cc86a..5472eb91160a39713a469994ef4b8ba8008a5a75 100644 (file)
@@ -70,6 +70,9 @@ function console_listener.listener(conn, data)
                        if data:match("^>") then
                                data = data:gsub("^>", "");
                                useglobalenv = true;
+                       elseif data == "\004" then
+                               commands["bye"](session, data);
+                               return;
                        else
                                local command = data:lower();
                                command = data:match("^%w+") or data:match("%p");
@@ -206,7 +209,8 @@ end
 -- Anything in def_env will be accessible within the session as a global variable
 
 def_env.server = {};
-function def_env.server:reload()
+
+function def_env.server:insane_reload()
        prosody.unlock_globals();
        dofile "prosody"
        prosody = _G.prosody;
@@ -231,6 +235,11 @@ function def_env.server:uptime()
                minutes, (minutes ~= 1 and "s") or "", os.date("%c", prosody.start_time));
 end
 
+function def_env.server:shutdown(reason)
+       prosody.shutdown(reason);
+       return true, "Shutdown initiated";
+end
+
 def_env.module = {};
 
 local function get_hosts_set(hosts, module)
@@ -359,6 +368,11 @@ function def_env.config:get(host, section, key)
        return true, tostring(config_get(host, section, key));
 end
 
+function def_env.config:reload()
+       local ok, err = prosody.reload_config();
+       return ok, (ok and "Config reloaded (you may need to reload modules to take effect)") or tostring(err);
+end
+
 def_env.hosts = {};
 function def_env.hosts:list()
        for host, host_session in pairs(hosts) do
@@ -385,7 +399,12 @@ end
 
 function def_env.c2s:show(match_jid)
        local print, count = self.session.print, 0;
+       local curr_host;
        show_c2s(function (jid, session)
+               if curr_host ~= session.host then
+                       curr_host = session.host;
+                       print(curr_host);
+               end
                if (not match_jid) or jid:match(match_jid) then
                        count = count + 1;
                        local status, priority = "unavailable", tostring(session.priority or "-");
@@ -397,7 +416,7 @@ function def_env.c2s:show(match_jid)
                                        status = "available";
                                end
                        end
-                       print(jid.." - "..status.."("..priority..")");
+                       print("   "..jid.." - "..status.."("..priority..")");
                end             
        end);
        return true, "Total: "..count.." clients";
@@ -448,7 +467,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);
+                               print("    "..host.." -> "..remotehost..(session.secure and " (encrypted)" or ""));
                                if session.sendq then
                                        print("        There are "..#session.sendq.." queued outgoing stanzas for this connection");
                                end
@@ -476,12 +495,16 @@ function def_env.s2s:show(match_jid)
                                end
                        end
                end     
-               
+               local subhost_filter = function (h) 
+                               return (match_jid and h:match(match_jid));
+                       end
                for session in pairs(incoming_s2s) do
                        if session.to_host == host and ((not match_jid) or host:match(match_jid) 
-                               or (session.from_host and session.from_host:match(match_jid))) then
+                               or (session.from_host and session.from_host:match(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)"));
+                               print("    "..host.." <- "..(session.from_host or "(unknown)")..(session.secure and " (encrypted)" or ""));
                                if session.type == "s2sin_unauthed" then
                                                print("        Connection not yet authenticated");
                                end
index 30313188a82ad9d0eebfbdfe78e88e691d7e2e90..469044cd34f347cf5666c6f9156b9ba0037d9c50 100644 (file)
@@ -120,3 +120,8 @@ module:hook_stanza(xmlns_stream, "features", function (origin, stanza)
                s2s_initiate_dialback(origin);
                return true;
        end, 100);
+
+-- Offer dialback to incoming hosts
+module:hook("s2s-stream-features", function (data)
+               data.features:tag("dialback", { xmlns='urn:xmpp:features:dialback' }):tag("optional"):up():up();
+       end);
index e854d67648a41d03346a27bebf39e0c5ded841c6..79a74deb2d5e31343cfd33ca0de246dad7958fa9 100644 (file)
@@ -56,11 +56,10 @@ module:add_event_hook("stream-features",
                        end
                end);
 
-module:add_event_hook("s2s-stream-features", 
-               function (session, features)
-                       -- This hook is possibly called once per host (at least if the
-                       -- remote server does not specify a to/from.
-                       if session.to_host and session.conn.starttls and not features:child_with_ns(xmlns_starttls) then
+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();
                                if secure_s2s_only then
                                        features:tag("required"):up():up();
@@ -74,7 +73,7 @@ module:add_event_hook("s2s-stream-features",
 module:hook_stanza(xmlns_stream, "features",
                function (session, stanza)
                        module:log("debug", "Received features element");
-                       if stanza:child_with_ns(xmlns_starttls) then
+                       if session.conn.starttls and stanza:child_with_ns(xmlns_starttls) then
                                module:log("%s is offering TLS, taking up the offer...", session.to_host);
                                session.sends2s("<starttls xmlns='"..xmlns_starttls.."'/>");
                                return true;
@@ -87,5 +86,6 @@ module:hook_stanza(xmlns_starttls, "proceed",
                        local format, to_host, from_host = string.format, session.to_host, session.from_host;
                        session:reset_stream();
                        session.conn.starttls(true);
+                       session.secure = false;
                        return true;
                end);
index 316a2861b6d46f327d643201880a574fde026ca5..686f55b1ef2f0b855039dc3cf89630b4388a708e 100644 (file)
@@ -111,7 +111,6 @@ end
 
 -- Setup methods from array_base
 for method, f in pairs(array_base) do
-       local method = method; -- Yes, this is necessary :)
        local base_method = f;
        -- Setup global array method which makes new array
        array[method] = function (old_a, ...)
index ed62f9b185dfdc60a23ca287987344d9c9e14617..b9fd23f2e2a0781104b969cfeb609815764f268b 100644 (file)
@@ -37,7 +37,7 @@ function form_t.form(layout, data)
                -- Add field tag
                form:tag("field", { type = field_type, var = field.name, label = field.label });
 
-               local value = data[field.name] or field.value;
+               local value = (data and data[field.name]) or field.value;
                
                -- Add value, depending on type
                if field_type == "hidden" then
@@ -106,6 +106,9 @@ field_readers["text-single"] =
 field_readers["text-private"] = 
        field_readers["text-single"];
 
+field_readers["jid-single"] =
+       field_readers["text-single"];
+
 field_readers["text-multi"] = 
        function (field_tag)
                local result = {};
index 3bf7ea58a4bdcfa89b079da08f710f312bffe672..4e833caad35d3f3e3bb771645035752b818bf08e 100644 (file)
@@ -6,7 +6,6 @@
 -- COPYING file in the source package for more information.
 --
 
-
 local t_insert      =  table.insert;
 local t_concat      =  table.concat;
 local t_remove      =  table.remove;
@@ -28,9 +27,15 @@ local s_find        =   string.find;
 local os            =            os;
 
 local do_pretty_printing = not os.getenv("WINDIR");
-local getstyle, getstring = require "util.termcolours".getstyle, require "util.termcolours".getstring;
-
-local log = require "util.logger".init("stanza");
+local getstyle, getstring;
+if do_pretty_printing then
+       local ok, termcolours = pcall(require, "util.termcolours");
+       if ok then
+               getstyle, getstring = termcolours.getstyle, termcolours.getstring;
+       else
+               do_pretty_printing = nil;
+       end
+end
 
 module "stanza"
 
@@ -118,10 +123,13 @@ function stanza_mt:childtags()
                                            
 end
 
-local xml_escape = (function()
+local xml_escape
+do
        local escape_table = { ["'"] = "&apos;", ["\""] = "&quot;", ["<"] = "&lt;", [">"] = "&gt;", ["&"] = "&amp;" };
-       return function(str) return (s_gsub(str, "['&<>\"]", escape_table)); end
-end)();
+       function xml_escape(str) return (s_gsub(str, "['&<>\"]", escape_table)); end
+       _M.xml_escape = xml_escape;
+end
+
 local function _dostring(t, buf, self, xml_escape)
        local nsid = 0;
        local name = t.name