Merge timber->trunk - thanks everyone!
authorMatthew Wild <mwild1@gmail.com>
Tue, 24 Apr 2012 20:59:20 +0000 (21:59 +0100)
committerMatthew Wild <mwild1@gmail.com>
Tue, 24 Apr 2012 20:59:20 +0000 (21:59 +0100)
47 files changed:
Makefile
core/certmanager.lua
core/configmanager.lua
core/loggingmanager.lua
core/moduleapi.lua [new file with mode: 0644]
core/modulemanager.lua
core/portmanager.lua [new file with mode: 0644]
core/s2smanager.lua
core/sessionmanager.lua
core/stanza_router.lua
net/connlisteners.lua [deleted file]
net/http.lua
net/http/codes.lua [new file with mode: 0644]
net/http/parser.lua [new file with mode: 0644]
net/http/server.lua [new file with mode: 0644]
net/httpclient_listener.lua [deleted file]
net/httpserver.lua
net/multiplex_listener.lua [deleted file]
net/server_event.lua
net/server_select.lua
net/xmppclient_listener.lua [deleted file]
net/xmppcomponent_listener.lua [deleted file]
net/xmppserver_listener.lua [deleted file]
plugins/adhoc/adhoc.lib.lua
plugins/mod_admin_adhoc.lua
plugins/mod_admin_telnet.lua
plugins/mod_auth_internal_hashed.lua
plugins/mod_auth_internal_plain.lua
plugins/mod_bosh.lua
plugins/mod_c2s.lua [new file with mode: 0644]
plugins/mod_component.lua
plugins/mod_dialback.lua
plugins/mod_http.lua [new file with mode: 0644]
plugins/mod_http_files.lua [new file with mode: 0644]
plugins/mod_httpserver.lua [deleted file]
plugins/mod_motd.lua
plugins/mod_net_multiplex.lua [new file with mode: 0644]
plugins/mod_posix.lua
plugins/mod_proxy65.lua
plugins/s2s/mod_s2s.lua [new file with mode: 0644]
plugins/s2s/s2sout.lib.lua [new file with mode: 0644]
prosody
util/debug.lua
util/helpers.lua
util/iterators.lua
util/logger.lua
util/set.lua

index 0fa8b28eeb4ead9465017b46f32327e5d9fa6919..356563f22c6c8afcd75cc7da0605ce65a6879bb8 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -32,12 +32,8 @@ install: prosody.install prosodyctl.install prosody.cfg.lua.install util/encodin
        install -m644 util/*.so $(SOURCE)/util
        install -d $(SOURCE)/util/sasl
        install -m644 util/sasl/* $(SOURCE)/util/sasl
-       install -m644 plugins/*.lua $(MODULES)
-       install -d $(MODULES)/muc
-       install -m644 plugins/muc/* $(MODULES)/muc
+       umask 0022 && cp -r plugins/* $(MODULES)
        install -m644 certs/* $(CONFIG)/certs
-       install -d $(MODULES)/adhoc
-       install -m644 plugins/adhoc/*.lua $(MODULES)/adhoc
        install -m644 man/prosodyctl.man $(MAN)/man1/prosodyctl.1
        test -e $(CONFIG)/prosody.cfg.lua || install -m644 prosody.cfg.lua.install $(CONFIG)/prosody.cfg.lua
        test -e prosody.version && install prosody.version $(SOURCE)/prosody.version || true
index 8b82ac47750cf39b1b345e11bd37ae6e9e67cd2d..cccf30988dadd055cc70305415351b6a4eb42993 100644 (file)
@@ -35,7 +35,7 @@ function create_context(host, mode, user_ssl_config)
                mode = mode;
                protocol = user_ssl_config.protocol or "sslv23";
                key = resolve_path(config_path, user_ssl_config.key);
-               password = user_ssl_config.password;
+               password = user_ssl_config.password or function() log("error", "Encrypted certificate for %s requires 'ssl' 'password' to be set in config", host); end;
                certificate = resolve_path(config_path, user_ssl_config.certificate);
                capath = resolve_path(config_path, user_ssl_config.capath or default_capath);
                cafile = resolve_path(config_path, user_ssl_config.cafile);
index 8591949272896550998328271d404b8e509bc414..e2253171a7459805956690174a6199d158ca4986 100644 (file)
@@ -41,6 +41,9 @@ function getconfig()
 end
 
 function get(host, section, key)
+       if not key then
+               section, key = "core", section;
+       end
        local sec = config[host][section];
        if sec then
                return sec[key];
index 426425c15e32fc0a61e33d43249fc8946f2a716a..56a3ee2c10640dd53eb2591ff04c6fbc0bcb18e8 100644 (file)
@@ -48,34 +48,12 @@ local logging_levels = { "debug", "info", "warn", "error" }
 local function add_rule(sink_config)
        local sink_maker = log_sink_types[sink_config.to];
        if sink_maker then
-               if sink_config.levels and not sink_config.source then
-                       -- Create sink
-                       local sink = sink_maker(sink_config);
-                       
-                       -- Set sink for all chosen levels
-                       for level in pairs(get_levels(sink_config.levels)) do
-                               logger.add_level_sink(level, sink);
-                       end
-               elseif sink_config.source and not sink_config.levels then
-                       logger.add_name_sink(sink_config.source, sink_maker(sink_config));
-               elseif sink_config.source and sink_config.levels then
-                       local levels = get_levels(sink_config.levels);
-                       local sink = sink_maker(sink_config);
-                       logger.add_name_sink(sink_config.source,
-                               function (name, level, ...)
-                                       if levels[level] then
-                                               return sink(name, level, ...);
-                                       end
-                               end);
-               else
-                       -- All sources
-                       -- Create sink
-                       local sink = sink_maker(sink_config);
-                       
-                       -- Set sink for all levels
-                       for _, level in pairs(logging_levels) do
-                               logger.add_level_sink(level, sink);
-                       end
+               -- Create sink
+               local sink = sink_maker(sink_config);
+               
+               -- Set sink for all chosen levels
+               for level in pairs(get_levels(sink_config.levels or logging_levels)) do
+                       logger.add_level_sink(level, sink);
                end
        else
                -- No such sink type
diff --git a/core/moduleapi.lua b/core/moduleapi.lua
new file mode 100644 (file)
index 0000000..d16ee41
--- /dev/null
@@ -0,0 +1,326 @@
+-- Prosody IM
+-- Copyright (C) 2008-2012 Matthew Wild
+-- Copyright (C) 2008-2012 Waqas Hussain
+-- 
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+local config = require "core.configmanager";
+local modulemanager = require "modulemanager";
+local array = require "util.array";
+local set = require "util.set";
+local logger = require "util.logger";
+local pluginloader = require "util.pluginloader";
+
+local multitable_new = require "util.multitable".new;
+
+local t_insert, t_remove, t_concat = table.insert, table.remove, table.concat;
+local error, setmetatable, setfenv, type = error, setmetatable, setfenv, type;
+local ipairs, pairs, select, unpack = ipairs, pairs, select, unpack;
+local tonumber, tostring = tonumber, tostring;
+
+local prosody = prosody;
+local hosts = prosody.hosts;
+local core_post_stanza = prosody.core_post_stanza;
+
+-- Registry of shared module data
+local shared_data = setmetatable({}, { __mode = "v" });
+
+local NULL = {};
+
+local api = {};
+
+-- Returns the name of the current module
+function api:get_name()
+       return self.name;
+end
+
+-- Returns the host that the current module is serving
+function api:get_host()
+       return self.host;
+end
+
+function api:get_host_type()
+       return hosts[self.host].type;
+end
+
+function api:set_global()
+       self.host = "*";
+       -- Update the logger
+       local _log = logger.init("mod_"..self.name);
+       self.log = function (self, ...) return _log(...); end;
+       self._log = _log;
+       self.global = true;
+end
+
+function api:add_feature(xmlns)
+       self:add_item("feature", xmlns);
+end
+function api:add_identity(category, type, name)
+       self:add_item("identity", {category = category, type = type, name = name});
+end
+function api:add_extension(data)
+       self:add_item("extension", data);
+end
+
+function api:fire_event(...)
+       return (hosts[self.host] or prosody).events.fire_event(...);
+end
+
+function api:hook_object_event(object, event, handler, priority)
+       self.event_handlers[handler] = { name = event, priority = priority, object = object };
+       return object.add_handler(event, handler, priority);
+end
+
+function api:hook(event, handler, priority)
+       return self:hook_object_event((hosts[self.host] or prosody).events, event, handler, priority);
+end
+
+function api:hook_global(event, handler, priority)
+       return self:hook_object_event(prosody.events, event, handler, priority);
+end
+
+function api:hook_stanza(xmlns, name, handler, priority)
+       if not handler and type(name) == "function" then
+               -- If only 2 options then they specified no xmlns
+               xmlns, name, handler, priority = nil, xmlns, name, handler;
+       elseif not (handler and name) then
+               self:log("warn", "Error: Insufficient parameters to module:hook_stanza()");
+               return;
+       end
+       return self:hook("stanza/"..(xmlns and (xmlns..":") or "")..name, function (data) return handler(data.origin, data.stanza, data); end, priority);
+end
+
+function api:require(lib)
+       local f, n = pluginloader.load_code(self.name, lib..".lib.lua");
+       if not f then
+               f, n = pluginloader.load_code(lib, lib..".lib.lua");
+       end
+       if not f then error("Failed to load plugin library '"..lib.."', error: "..n); end -- FIXME better error message
+       setfenv(f, self.environment);
+       return f();
+end
+
+function api:depends(name)
+       if not self.dependencies then
+               self.dependencies = {};
+               self:hook("module-reloaded", function (event)
+                       if self.dependencies[event.module] then
+                               self:log("info", "Auto-reloading due to reload of %s:%s", event.host, event.module);
+                               modulemanager.reload(self.host, self.name);
+                               return;
+                       end
+               end);
+               self:hook("module-unloaded", function (event)
+                       if self.dependencies[event.module] then
+                               self:log("info", "Auto-unloading due to unload of %s:%s", event.host, event.module);
+                               modulemanager.unload(self.host, self.name);
+                       end
+               end);
+       end
+       local mod = modulemanager.get_module(self.host, name) or modulemanager.get_module("*", name);
+       if mod and mod.module.host == "*" and modulemanager.module_has_method(mod, "add_host") then
+               mod = nil; -- This is a shared module, so we still want to load it on our host
+       end
+       if not mod then
+               local err;
+               mod, err = modulemanager.load(self.host, name);
+               if not mod then
+                       return error(("Unable to load required module, mod_%s: %s"):format(name, ((err or "unknown error"):gsub("%-", " ")) ));
+               end
+       end
+       self.dependencies[name] = true;
+       return mod;
+end
+
+-- Returns one or more shared tables at the specified virtual paths
+-- Intentionally does not allow the table at a path to be _set_, it
+-- is auto-created if it does not exist.
+function api:shared(...)
+       if not self.shared_data then self.shared_data = {}; end
+       local paths = { n = select("#", ...), ... };
+       local data_array = {};
+       local default_path_components = { self.host, self.name };
+       for i = 1, paths.n do
+               local path = paths[i];
+               if path:sub(1,1) ~= "/" then -- Prepend default components
+                       local n_components = select(2, path:gsub("/", "%1"));
+                       path = (n_components<#default_path_components and "/" or "")..t_concat(default_path_components, "/", 1, #default_path_components-n_components).."/"..path;
+               end
+               local shared = shared_data[path];
+               if not shared then
+                       shared = {};
+                       shared_data[path] = shared;
+               end
+               t_insert(data_array, shared);
+               self.shared_data[path] = shared;
+       end
+       return unpack(data_array);
+end
+
+function api:get_option(name, default_value)
+       local value = config.get(self.host, self.name, name);
+       if value == nil then
+               value = config.get(self.host, "core", name);
+               if value == nil then
+                       value = default_value;
+               end
+       end
+       return value;
+end
+
+function api:get_option_string(name, default_value)
+       local value = self:get_option(name, default_value);
+       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 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(modulemanager.get_modules(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(modulemanager.get_modules("*")) 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
+
+function api:handle_items(type, added_cb, removed_cb, existing)
+       self:hook("item-added/"..type, added_cb);
+       self:hook("item-removed/"..type, removed_cb);
+       if existing ~= false then
+               for _, item in ipairs(self:get_host_items(type)) do
+                       added_cb({ item = item });
+               end
+       end
+end
+
+function api:provides(name, item)
+       if not item then item = self.environment; end
+       if not item.name then
+               local item_name = self.name;
+               -- Strip a provider prefix to find the item name
+               -- (e.g. "auth_foo" -> "foo" for an auth provider)
+               if item_name:find(name.."_", 1, true) == 1 then
+                       item_name = item_name:sub(#name+2);
+               end
+               item.name = item_name;
+       end
+       self:add_item(name.."-provider", item);
+end
+
+function api:send(stanza)
+       return core_post_stanza(hosts[self.host], stanza);
+end
+
+function api:add_timer(delay, callback)
+       return timer.add_task(delay, function (t)
+               if self.loaded == false then return; end
+               return callback(t);
+       end);
+end
+
+return api;
index c4d956957a2b296eb396c7f45e32d2c70e92cc8f..bf1e1924acbb59b49010604b1cb4cc1991a0cb09 100644 (file)
@@ -9,13 +9,10 @@
 local logger = require "util.logger";
 local log = logger.init("modulemanager");
 local config = require "core.configmanager";
-local multitable_new = require "util.multitable".new;
-local st = require "util.stanza";
 local pluginloader = require "util.pluginloader";
 
 local hosts = hosts;
 local prosody = prosody;
-local prosody_events = prosody.events;
 
 local loadfile, pcall, xpcall = loadfile, pcall, xpcall;
 local setmetatable, setfenv, getfenv = setmetatable, setfenv, getfenv;
@@ -37,7 +34,7 @@ end
 
 local array, set = require "util.array", require "util.set";
 
-local autoload_modules = {"presence", "message", "iq", "offline"};
+local autoload_modules = {"presence", "message", "iq", "offline", "c2s", "s2s"};
 local component_inheritable_modules = {"tls", "dialback", "iq"};
 
 -- We need this to let modules access the real global namespace
@@ -45,15 +42,11 @@ local _G = _G;
 
 module "modulemanager"
 
-api = {};
-local api = api; -- Module API container
+local api = _G.require "core.moduleapi"; -- Module API container
 
+-- [host] = { [module] = module_env }
 local modulemap = { ["*"] = {} };
 
-local modulehelpers = setmetatable({}, { __index = _G });
-
-local hooks = multitable_new();
-
 local NULL = {};
 
 -- Load modules when a host is activated
@@ -88,24 +81,74 @@ function load_modules_for_host(host)
                load(host, module);
        end
 end
-prosody_events.add_handler("host-activated", load_modules_for_host);
---
+prosody.events.add_handler("host-activated", load_modules_for_host);
+
+--- Private helpers ---
+
+local function do_unload_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
+               local ok, err = call_module_method(mod, "unload");
+               if (not ok) and err then
+                       log("warn", "Non-fatal error unloading module '%s' on '%s': %s", name, host, err);
+               end
+       end
+       
+       for handler, event in pairs(mod.module.event_handlers) do
+               event.object.remove_handler(event.name, handler);
+       end
+       
+       if mod.module.items then -- remove items
+               local events = (host == "*" and prosody.events) or hosts[host].events;
+               for key,t in pairs(mod.module.items) do
+                       for i = #t,1,-1 do
+                               local value = t[i];
+                               t[i] = nil;
+                               events.fire_event("item-removed/"..key, {source = mod.module, item = value});
+                       end
+               end
+       end
+       mod.module.loaded = false;
+       modulemap[host][name] = nil;
+       return true;
+end
 
-function load(host, module_name, config)
+local function do_load_module(host, module_name)
        if not (host and module_name) then
                return nil, "insufficient-parameters";
-       elseif not hosts[host] then
+       elseif not hosts[host] and host ~= "*"then
                return nil, "unknown-host";
        end
        
        if not modulemap[host] then
                modulemap[host] = {};
+               if host ~= "*" then
+                       hosts[host].modules = modulemap[host];
+               end
        end
        
        if modulemap[host][module_name] then
                log("warn", "%s is already loaded for %s, so not loading again", module_name, host);
                return nil, "module-already-loaded";
        elseif modulemap["*"][module_name] then
+               local mod = modulemap["*"][module_name];
+               if module_has_method(mod, "add_host") then
+                       local _log = logger.init(host..":"..module_name);
+                       local host_module_api = setmetatable({
+                               host = host, event_handlers = {}, items = {};
+                               _log = _log, log = function (self, ...) return _log(...); end;
+                       },{
+                               __index = modulemap["*"][module_name].module;
+                       });
+                       local ok, result, module_err = call_module_method(mod, "add_host", host_module_api);
+                       if not ok or result == false then return nil, ok and module_err or result; end
+                       local host_module = setmetatable({ module = host_module_api }, { __index = mod });
+                       host_module.module.environment = host_module;
+                       modulemap[host][module_name] = host_module;
+                       return host_module;
+               end
                return nil, "global-module-already-loaded";
        end
        
@@ -117,88 +160,44 @@ function load(host, module_name, config)
        end
 
        local _log = logger.init(host..":"..module_name);
-       local api_instance = setmetatable({ name = module_name, host = host, path = err, _log = _log, log = function (self, ...) return _log(...); end }, { __index = api });
+       local api_instance = setmetatable({ name = module_name, host = host, path = err,
+               _log = _log, log = function (self, ...) return _log(...); end, event_handlers = {} }
+               , { __index = api });
 
        local pluginenv = setmetatable({ module = api_instance }, { __index = _G });
        api_instance.environment = pluginenv;
        
        setfenv(mod, pluginenv);
-       hosts[host].modules = modulemap[host];
-       modulemap[host][module_name] = pluginenv;
        
-       local success, err = pcall(mod);
-       if success then
+       local ok, err = pcall(mod);
+       if ok then
+               -- Call module's "load"
                if module_has_method(pluginenv, "load") then
-                       success, err = call_module_method(pluginenv, "load");
-                       if not success then
+                       ok, err = call_module_method(pluginenv, "load");
+                       if not ok then
                                log("warn", "Error loading module '%s' on '%s': %s", module_name, host, err or "nil");
                        end
                end
 
-               -- Use modified host, if the module set one
-               if api_instance.host == "*" and host ~= "*" then
-                       modulemap[host][module_name] = nil;
-                       modulemap["*"][module_name] = pluginenv;
-                       api_instance:set_global();
-               end
-       else
-               log("error", "Error initializing module '%s' on '%s': %s", module_name, host, err or "nil");
-       end
-       if success then
-               (hosts[api_instance.host] or prosody).events.fire_event("module-loaded", { module = module_name, host = host });
-               return true;
-       else -- load failed, unloading
-               unload(api_instance.host, module_name);
-               return nil, err;
-       end
-end
-
-function get_module(host, name)
-       return modulemap[host] and modulemap[host][name];
-end
-
-function is_loaded(host, name)
-       return modulemap[host] and modulemap[host][name] and true;
-end
-
-function unload(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
-               local ok, err = call_module_method(mod, "unload");
-               if (not ok) and err then
-                       log("warn", "Non-fatal error unloading module '%s' on '%s': %s", name, host, err);
-               end
-       end
-       -- unhook event handlers hooked by module:hook
-       for event, handlers in pairs(hooks:get(host, name) or NULL) do
-               for handler in pairs(handlers or NULL) do
-                       (hosts[host] or prosody).events.remove_handler(event, handler);
-               end
-       end
-       -- unhook event handlers hooked by module:hook_global
-       for event, handlers in pairs(hooks:get("*", name) or NULL) do
-               for handler in pairs(handlers or NULL) do
-                       prosody.events.remove_handler(event, handler);
-               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 = mod.module, item = value});
+               modulemap[api_instance.host][module_name] = pluginenv;
+               if api_instance.host == "*" then
+                       if not api_instance.global then -- COMPAT w/pre-0.9
+                               log("warn", "mod_%s: Setting module.host = '*' deprecated, call module:set_global() instead", module_name);
+                               api_instance:set_global();
+                       end
+                       if host ~= api_instance.host and module_has_method(pluginenv, "add_host") then
+                               -- Now load the module again onto the host it was originally being loaded on
+                               ok, err = do_load_module(host, module_name);
                        end
                end
        end
-       modulemap[host][name] = nil;
-       (hosts[host] or prosody).events.fire_event("module-unloaded", { module = name, host = host });
-       return true;
+       if not ok then
+               log("error", "Error initializing module '%s' on '%s': %s", module_name, host, err or "nil");
+       end
+       return ok and pluginenv, err;
 end
 
-function reload(host, name, ...)
+local function do_reload_module(host, name)
        local mod = get_module(host, name);
        if not mod then return nil, "module-not-loaded"; end
 
@@ -209,7 +208,6 @@ function reload(host, name, ...)
        end
 
        local saved;
-
        if module_has_method(mod, "save") then
                local ok, ret, err = call_module_method(mod, "save");
                if ok then
@@ -225,8 +223,8 @@ function reload(host, name, ...)
                end
        end
 
-       unload(host, name, ...);
-       local ok, err = load(host, name, ...);
+       do_unload_module(host, name);
+       local ok, err = do_load_module(host, name);
        if ok then
                mod = get_module(host, name);
                if module_has_method(mod, "restore") then
@@ -235,231 +233,62 @@ function reload(host, name, ...)
                                log("warn", "Error restoring module '%s' from '%s': %s", name, host, err);
                        end
                end
-               return true;
        end
-       return ok, err;
+       return ok and mod, err;
 end
 
-function module_has_method(module, method)
-       return type(module.module[method]) == "function";
-end
+--- Public API ---
 
-function call_module_method(module, method, ...)
-       if module_has_method(module, method) then
-               local f = module.module[method];
-               return pcall(f, ...);
-       else
-               return false, "no-such-method";
-       end
-end
-
------ API functions exposed to modules -----------
--- Must all be in api.*
-
--- Returns the name of the current module
-function api:get_name()
-       return self.name;
-end
-
--- Returns the host that the current module is serving
-function api:get_host()
-       return self.host;
-end
-
-function api:get_host_type()
-       return hosts[self.host].type;
-end
-
-function api:set_global()
-       self.host = "*";
-       -- Update the logger
-       local _log = logger.init("mod_"..self.name);
-       self.log = function (self, ...) return _log(...); end;
-       self._log = _log;
-end
-
-function api:add_feature(xmlns)
-       self:add_item("feature", xmlns);
-end
-function api:add_identity(category, type, name)
-       self:add_item("identity", {category = category, type = type, name = name});
-end
-function api:add_extension(data)
-       self:add_item("extension", data);
-end
-
-function api:fire_event(...)
-       return (hosts[self.host] or prosody).events.fire_event(...);
-end
-
-function api:hook(event, handler, priority)
-       hooks:set(self.host, self.name, event, handler, true);
-       (hosts[self.host] or prosody).events.add_handler(event, handler, priority);
-end
-
-function api:hook_global(event, handler, priority)
-       hooks:set("*", self.name, event, handler, true);
-       prosody.events.add_handler(event, handler, priority);
-end
-
-function api:hook_stanza(xmlns, name, handler, priority)
-       if not handler and type(name) == "function" then
-               -- If only 2 options then they specified no xmlns
-               xmlns, name, handler, priority = nil, xmlns, name, handler;
-       elseif not (handler and name) then
-               self:log("warn", "Error: Insufficient parameters to module:hook_stanza()");
-               return;
+-- Load a module and fire module-loaded event
+function load(host, name)
+       local mod, err = do_load_module(host, name);
+       if mod then
+               (hosts[mod.module.host] or prosody).events.fire_event("module-loaded", { module = name, host = host });
        end
-       return api.hook(self, "stanza/"..(xmlns and (xmlns..":") or "")..name, function (data) return handler(data.origin, data.stanza, data); end, priority);
+       return mod, err;
 end
 
-function api:require(lib)
-       local f, n = pluginloader.load_code(self.name, lib..".lib.lua");
-       if not f then
-               f, n = pluginloader.load_code(lib, lib..".lib.lua");
-       end
-       if not f then error("Failed to load plugin library '"..lib.."', error: "..n); end -- FIXME better error message
-       setfenv(f, self.environment);
-       return f();
-end
-
-function api:get_option(name, default_value)
-       local value = config.get(self.host, self.name, name);
-       if value == nil then
-               value = config.get(self.host, "core", name);
-               if value == nil then
-                       value = default_value;
-               end
-       end
-       return value;
-end
-
-function api:get_option_string(name, default_value)
-       local value = self:get_option(name, default_value);
-       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);
+-- Unload a module and fire module-unloaded
+function unload(host, name)
+       local ok, err = do_unload_module(host, name);
+       if ok then
+               (hosts[host] or prosody).events.fire_event("module-unloaded", { module = name, host = host });
        end
-       return ret;
+       return ok, err;
 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);
+function reload(host, name)
+       local ok, err = do_reload_module(host, name);
+       if ok then
+               (hosts[host] or prosody).events.fire_event("module-reloaded", { module = name, host = host });
+       elseif not is_loaded(host, name) then
+               (hosts[host] or prosody).events.fire_event("module-unloaded", { module = name, host = host });
        end
-       return ret;
+       return ok, err;
 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
+function get_module(host, name)
+       return modulemap[host] and modulemap[host][name];
 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);
+function get_modules(host)
+       return modulemap[host];
 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
+function is_loaded(host, name)
+       return modulemap[host] and modulemap[host][name] and true;
 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;
+function module_has_method(module, method)
+       return type(rawget(module.module, method)) == "function";
 end
 
-function api:handle_items(type, added_cb, removed_cb, existing)
-       self:hook("item-added/"..type, added_cb);
-       self:hook("item-removed/"..type, removed_cb);
-       if existing ~= false then
-               for _, item in ipairs(self:get_host_items(type)) do
-                       added_cb({ item = item });
-               end
+function call_module_method(module, method, ...)
+       local f = rawget(module.module, method);
+       if type(f) == "function" then
+               return pcall(f, ...);
+       else
+               return false, "no-such-method";
        end
 end
 
diff --git a/core/portmanager.lua b/core/portmanager.lua
new file mode 100644 (file)
index 0000000..da238db
--- /dev/null
@@ -0,0 +1,194 @@
+
+local multitable = require "util.multitable";
+local fire_event = prosody.events.fire_event;
+
+--- Config
+
+local default_interfaces = { "*" };
+local default_local_interfaces = { "127.0.0.1" };
+if config.get("*", "use_ipv6") then
+       table.insert(default_interfaces, "::");
+       table.insert(default_local_interfaces, "::1");
+end
+
+--- Private state
+
+-- service_name -> { service_info, ... }
+local services = setmetatable({}, { __index = function (t, k) rawset(t, k, {}); return rawget(t, k); end });
+
+-- service_name, interface (string), port (number)
+local active_services = multitable.new();
+
+--- Private helpers
+
+local function error_to_friendly_message(service_name, port, err)
+       local friendly_message = err;
+       if err:match(" in use") then
+               -- FIXME: Use service_name here
+               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
+       return friendly_message;
+end
+
+module("portmanager", package.seeall);
+
+prosody.events.add_handler("item-added/net-provider", function (event)
+       local item = event.item;
+       register_service(item.name, item);
+end);
+prosody.events.add_handler("item-removed/net-provider", function (event)
+       local item = event.item;
+       unregister_service(item.name, item);
+end);
+
+--- Public API
+
+function activate_service(service_name)
+       local service_info = services[service_name][1];
+       if not service_info then
+               return nil, "Unknown service: "..service_name;
+       end
+       
+       local listener = service_info.listener;
+
+       local config_prefix = (service_info.config_prefix or service_name).."_";
+       if config_prefix == "_" then
+               config_prefix = "";
+       end
+
+       local bind_interfaces = set.new(config.get("*", config_prefix.."interfaces")
+               or config.get("*", config_prefix.."interface") -- COMPAT w/pre-0.9
+               or (service_info.private and default_local_interfaces)
+               or config.get("*", "interfaces")
+               or config.get("*", "interface") -- COMPAT w/pre-0.9
+               or listener.default_interface -- COMPAT w/pre0.9
+               or default_interfaces);
+       
+       local bind_ports = set.new(config.get("*", config_prefix.."ports")
+               or service_info.default_ports
+               or {service_info.default_port
+                   or listener.default_port -- COMPAT w/pre-0.9
+                  });
+
+       local mode = listener.default_mode or "*a";
+       local ssl;
+       if service_info.encryption == "ssl" then
+               ssl = prosody.global_ssl_ctx;
+               if not ssl then
+                       return nil, "global-ssl-context-required";
+               end
+       end
+       
+       for interface in bind_interfaces do
+               for port in bind_ports do
+                       if #active_services:search(nil, interface, port) > 0 then
+                               log("error", "Multiple services configured to listen on the same port ([%s]:%d): %s, %s", interface, port, active_services:search(nil, interface, port)[1][1].service.name or "<unnamed>", service_name or "<unnamed>");
+                       else
+                               local handler, err = server.addserver(interface, port, listener, mode, ssl);
+                               if not handler then
+                                       log("error", "Failed to open server port %d on %s, %s", port, interface, error_to_friendly_message(service_name, port, err));
+                               else
+                                       log("debug", "Added listening service %s to [%s]:%d", service_name, interface, port);
+                                       active_services:add(service_name, interface, port, {
+                                               server = handler;
+                                               service = service_info;
+                                       });
+                               end
+                       end
+               end
+       end
+       log("info", "Activated service '%s'", service_name);
+       return true;
+end
+
+function deactivate(service_name)
+       local active = active_services:search(service_name)[1];
+       if not active then return; end
+       for interface, ports in pairs(active) do
+               for port, active_service in pairs(ports) do
+                       close(interface, port);
+               end
+       end
+       log("info", "Deactivated service '%s'", service_name);
+end
+
+function register_service(service_name, service_info)
+       table.insert(services[service_name], service_info);
+
+       if not active_services:get(service_name) then
+               log("debug", "No active service for %s, activating...", service_name);
+               local ok, err = activate_service(service_name);
+               if not ok then
+                       log("error", "Failed to activate service '%s': %s", service_name, err or "unknown error");
+               end
+       end
+       
+       fire_event("service-added", { name = service_name, service = service_info });
+       return true;
+end
+
+function unregister_service(service_name, service_info)
+       local service_info_list = services[service_name];
+       for i, service in ipairs(service_info_list) do
+               if service == service_info then
+                       table.remove(service_info_list, i);
+               end
+       end
+       if active_services[service_name] == service_info then
+               deactivate(service_name);
+               if #service_info_list > 0 then -- Other services registered with this name
+                       activate(service_name); -- Re-activate with the next available one
+               end
+       end
+       fire_event("service-removed", { name = service_name, service = service_info });
+end
+
+function close(interface, port)
+       local service, server = get_service_at(interface, port);
+       if not service then
+               return false, "port-not-open";
+       end
+       server:close();
+       active_services:remove(service.name, interface, port);
+       log("debug", "Removed listening service %s from [%s]:%d", service.name, interface, port);
+       return true;
+end
+
+function get_service_at(interface, port)
+       local data = active_services:search(nil, interface, port)[1][1];
+       return data.service, data.server;
+end
+
+function get_service(service_name)
+       return services[service_name];
+end
+
+function get_active_services(...)
+       return active_services;
+end
+
+function get_registered_services()
+       return services;
+end
+
+return _M;
index acb0baebbdf6aa066ee5292980b5583f3c78e403..5907ff2f455b6659a88a95cfc0b80e9b5926b556 100644 (file)
@@ -9,18 +9,13 @@
 
 
 local hosts = hosts;
-local sessions = sessions;
 local core_process_stanza = function(a, b) core_process_stanza(a, b); end
-local add_task = require "util.timer".add_task;
-local socket = require "socket";
 local format = string.format;
 local t_insert, t_sort = table.insert, table.sort;
 local get_traceback = debug.traceback;
 local tostring, pairs, ipairs, getmetatable, newproxy, type, error, tonumber, setmetatable
     = tostring, pairs, ipairs, getmetatable, newproxy, type, error, tonumber, setmetatable;
 
-local idna_to_ascii = require "util.encodings".idna.to_ascii;
-local connlisteners_get = require "net.connlisteners".get;
 local initialize_filters = require "util.filters".initialize;
 local wrapclient = require "net.server".wrapclient;
 local st = require "stanza";
@@ -41,11 +36,12 @@ local sha256_hash = require "util.hashes".sha256;
 
 local adns, dns = require "net.adns", require "net.dns";
 local config = require "core.configmanager";
-local connect_timeout = config.get("*", "core", "s2s_timeout") or 60;
 local dns_timeout = config.get("*", "core", "dns_timeout") or 15;
-local max_dns_depth = config.get("*", "core", "dns_max_depth") or 3;
+local cfg_sources = config.get("*", "core", "s2s_interface")
+       or config.get("*", "core", "interface");
 local sources;
 
+--FIXME: s2sout should create its own resolver w/ timeout
 dns.settimeout(dns_timeout);
 
 local prosody = _G.prosody;
@@ -55,89 +51,6 @@ local incoming_s2s = incoming_s2s;
 
 module "s2smanager"
 
-function compare_srv_priorities(a,b)
-       return a.priority < b.priority or (a.priority == b.priority and a.weight > b.weight);
-end
-
-local bouncy_stanzas = { message = true, presence = true, iq = true };
-local function bounce_sendq(session, reason)
-       local sendq = session.sendq;
-       if sendq then
-               session.log("info", "sending error replies for "..#sendq.." queued stanzas because of failed outgoing connection to "..tostring(session.to_host));
-               local dummy = {
-                       type = "s2sin";
-                       send = function(s)
-                               (session.log or log)("error", "Replying to to an s2s error reply, please report this! Traceback: %s", get_traceback());
-                       end;
-                       dummy = true;
-               };
-               for i, data in ipairs(sendq) do
-                       local reply = data[2];
-                       if reply and not(reply.attr.xmlns) and bouncy_stanzas[reply.name] then
-                               reply.attr.type = "error";
-                               reply:tag("error", {type = "cancel"})
-                                       :tag("remote-server-not-found", {xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas"}):up();
-                               if reason then
-                                       reply:tag("text", {xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas"})
-                                               :text("Server-to-server connection failed: "..reason):up();
-                               end
-                               core_process_stanza(dummy, reply);
-                       end
-                       sendq[i] = nil;
-               end
-               session.sendq = nil;
-       end
-end
-
-function send_to_host(from_host, to_host, data)
-       if not hosts[from_host] then
-               log("warn", "Attempt to send stanza from %s - a host we don't serve", from_host);
-               return false;
-       end
-       local host = hosts[from_host].s2sout[to_host];
-       if host then
-               -- We have a connection to this host already
-               if host.type == "s2sout_unauthed" and (data.name ~= "db:verify" or not host.dialback_key) then
-                       (host.log or log)("debug", "trying to send over unauthed s2sout to "..to_host);
-                       
-                       -- Queue stanza until we are able to send it
-                       if host.sendq then t_insert(host.sendq, {tostring(data), data.attr.type ~= "error" and data.attr.type ~= "result" and st.reply(data)});
-                       else host.sendq = { {tostring(data), data.attr.type ~= "error" and data.attr.type ~= "result" and st.reply(data)} }; end
-                       host.log("debug", "stanza [%s] queued ", data.name);
-               elseif host.type == "local" or host.type == "component" then
-                       log("error", "Trying to send a stanza to ourselves??")
-                       log("error", "Traceback: %s", get_traceback());
-                       log("error", "Stanza: %s", tostring(data));
-                       return false;
-               else
-                       (host.log or log)("debug", "going to send stanza to "..to_host.." from "..from_host);
-                       -- FIXME
-                       if host.from_host ~= from_host then
-                               log("error", "WARNING! This might, possibly, be a bug, but it might not...");
-                               log("error", "We are going to send from %s instead of %s", tostring(host.from_host), tostring(from_host));
-                       end
-                       host.sends2s(data);
-                       host.log("debug", "stanza sent over "..host.type);
-               end
-       else
-               log("debug", "opening a new outgoing connection for this stanza");
-               local host_session = new_outgoing(from_host, to_host);
-
-               -- Store in buffer
-               host_session.sendq = { {tostring(data), data.attr.type ~= "error" and data.attr.type ~= "result" and st.reply(data)} };
-               log("debug", "stanza [%s] queued until connection complete", tostring(data.name));
-               if (not host_session.connecting) and (not host_session.conn) then
-                       log("warn", "Connection to %s failed already, destroying session...", to_host);
-                       if not destroy_session(host_session, "Connection failed") then
-                               -- Already destroyed, we need to bounce our stanza
-                               bounce_sendq(host_session, host_session.destruction_reason);
-                       end
-                       return false;
-               end
-       end
-       return true;
-end
-
 local open_sessions = 0;
 
 function new_incoming(conn)
@@ -147,496 +60,18 @@ function new_incoming(conn)
                getmetatable(session.trace).__gc = function () open_sessions = open_sessions - 1; end;
        end
        open_sessions = open_sessions + 1;
-       local w, log = conn.write, logger_init("s2sin"..tostring(conn):match("[a-f0-9]+$"));
-       session.log = log;
-       local filter = initialize_filters(session);
-       session.sends2s = function (t)
-               log("debug", "sending: %s", t.top_tag and t:top_tag() or t:match("^([^>]*>?)"));
-               if t.name then
-                       t = filter("stanzas/out", t);
-               end
-               if t then
-                       t = filter("bytes/out", tostring(t));
-                       if t then
-                               return w(conn, t);
-                       end
-               end
-       end
+       session.log = logger_init("s2sin"..tostring(conn):match("[a-f0-9]+$"));
        incoming_s2s[session] = true;
-       add_task(connect_timeout, function ()
-               if session.conn ~= conn or
-                  session.type == "s2sin" then
-                       return; -- Ok, we're connect[ed|ing]
-               end
-               -- Not connected, need to close session and clean up
-               (session.log or log)("debug", "Destroying incomplete session %s->%s due to inactivity",
-                   session.from_host or "(unknown)", session.to_host or "(unknown)");
-               session:close("connection-timeout");
-       end);
        return session;
 end
 
 function new_outgoing(from_host, to_host, connect)
-               local host_session = { to_host = to_host, from_host = from_host, host = from_host,
-                                      notopen = true, type = "s2sout_unauthed", direction = "outgoing",
-                                      open_stream = session_open_stream };
-               
-               hosts[from_host].s2sout[to_host] = host_session;
-               
-               host_session.close = destroy_session; -- This gets replaced by xmppserver_listener later
-               
-               local log;
-               do
-                       local conn_name = "s2sout"..tostring(host_session):match("[a-f0-9]*$");
-                       log = logger_init(conn_name);
-                       host_session.log = log;
-               end
-               
-               initialize_filters(host_session);
-               
-               if connect ~= false then
-                       -- Kick the connection attempting machine into life
-                       if not attempt_connection(host_session) then
-                               -- Intentionally not returning here, the
-                               -- session is needed, connected or not
-                               destroy_session(host_session);
-                       end
-               end
-               
-               if not host_session.sends2s then
-                       -- A sends2s which buffers data (until the stream is opened)
-                       -- note that data in this buffer will be sent before the stream is authed
-                       -- and will not be ack'd in any way, successful or otherwise
-                       local buffer;
-                       function host_session.sends2s(data)
-                               if not buffer then
-                                       buffer = {};
-                                       host_session.send_buffer = buffer;
-                               end
-                               log("debug", "Buffering data on unconnected s2sout to %s", to_host);
-                               buffer[#buffer+1] = data;
-                               log("debug", "Buffered item %d: %s", #buffer, tostring(data));
-                       end
-               end
-
-               return host_session;
-end
-
-
-function attempt_connection(host_session, err)
-       local from_host, to_host = host_session.from_host, host_session.to_host;
-       local connect_host, connect_port = to_host and idna_to_ascii(to_host), 5269;
-       
-       if not connect_host then
-               return false;
-       end
-       
-       if not err then -- This is our first attempt
-               log("debug", "First attempt to connect to %s, starting with SRV lookup...", to_host);
-               host_session.connecting = true;
-               local handle;
-               handle = adns.lookup(function (answer)
-                       handle = nil;
-                       host_session.connecting = nil;
-                       if answer then
-                               log("debug", to_host.." has SRV records, handling...");
-                               local srv_hosts = {};
-                               host_session.srv_hosts = srv_hosts;
-                               for _, record in ipairs(answer) do
-                                       t_insert(srv_hosts, record.srv);
-                               end
-                               if #srv_hosts == 1 and srv_hosts[1].target == "." then
-                                       log("debug", to_host.." does not provide a XMPP service");
-                                       destroy_session(host_session, err); -- Nothing to see here
-                                       return;
-                               end
-                               t_sort(srv_hosts, compare_srv_priorities);
-                               
-                               local srv_choice = srv_hosts[1];
-                               host_session.srv_choice = 1;
-                               if srv_choice then
-                                       connect_host, connect_port = srv_choice.target or to_host, srv_choice.port or connect_port;
-                                       log("debug", "Best record found, will connect to %s:%d", connect_host, connect_port);
-                               end
-                       else
-                               log("debug", to_host.." has no SRV records, falling back to A");
-                       end
-                       -- Try with SRV, or just the plain hostname if no SRV
-                       local ok, err = try_connect(host_session, connect_host, connect_port);
-                       if not ok then
-                               if not attempt_connection(host_session, err) then
-                                       -- No more attempts will be made
-                                       destroy_session(host_session, err);
-                               end
-                       end
-               end, "_xmpp-server._tcp."..connect_host..".", "SRV");
-               
-               return true; -- Attempt in progress
-       elseif host_session.ip_hosts then
-               return try_connect(host_session, connect_host, connect_port, err);
-       elseif host_session.srv_hosts and #host_session.srv_hosts > host_session.srv_choice then -- Not our first attempt, and we also have SRV
-               host_session.srv_choice = host_session.srv_choice + 1;
-               local srv_choice = host_session.srv_hosts[host_session.srv_choice];
-               connect_host, connect_port = srv_choice.target or to_host, srv_choice.port or connect_port;
-               host_session.log("info", "Connection failed (%s). Attempt #%d: This time to %s:%d", tostring(err), host_session.srv_choice, connect_host, connect_port);
-       else
-               host_session.log("info", "Out of connection options, can't connect to %s", tostring(host_session.to_host));
-               -- We're out of options
-               return false;
-       end
-       
-       if not (connect_host and connect_port) then
-               -- Likely we couldn't resolve DNS
-               log("warn", "Hmm, we're without a host (%s) and port (%s) to connect to for %s, giving up :(", tostring(connect_host), tostring(connect_port), tostring(to_host));
-               return false;
-       end
-       
-       return try_connect(host_session, connect_host, connect_port);
-end
-
-function try_next_ip(host_session)
-       host_session.connecting = nil;
-       host_session.ip_choice = host_session.ip_choice + 1;
-       local ip = host_session.ip_hosts[host_session.ip_choice];
-       local ok, err= make_connect(host_session, ip.ip, ip.port);
-       if not ok then
-               if not attempt_connection(host_session, err or "closed") then
-                       err = err and (": "..err) or "";
-                       destroy_session(host_session, "Connection failed"..err);
-               end
-       end
-end
-
-function try_connect(host_session, connect_host, connect_port, err)
-       host_session.connecting = true;
-
-       if not err then
-               local IPs = {};
-               host_session.ip_hosts = IPs;
-               local handle4, handle6;
-               local has_other = false;
-
-               if not sources then
-                       sources =  {};
-                       local cfg_sources = config.get("*", "core", "interface") or connlisteners_get("xmppserver").default_interface;
-                       if type(cfg_sources) == "string" then
-                               cfg_sources = { cfg_sources };
-                       end
-                       for i, source in ipairs(cfg_sources) do
-                               if source == "*" then
-                                       sources[i] = new_ip("0.0.0.0", "IPv4");
-                               else
-                                       sources[i] = new_ip(source, (source:find(":") and "IPv6") or "IPv4");
-                               end
-                       end
-               end
-
-               handle4 = adns.lookup(function (reply, err)
-                       handle4 = nil;
-
-                       -- COMPAT: This is a compromise for all you CNAME-(ab)users :)
-                       if not (reply and reply[#reply] and reply[#reply].a) then
-                               local count = max_dns_depth;
-                               reply = dns.peek(connect_host, "CNAME", "IN");
-                               while count > 0 and reply and reply[#reply] and not reply[#reply].a and reply[#reply].cname do
-                                       log("debug", "Looking up %s (DNS depth is %d)", tostring(reply[#reply].cname), count);
-                                       reply = dns.peek(reply[#reply].cname, "A", "IN") or dns.peek(reply[#reply].cname, "CNAME", "IN");
-                                       count = count - 1;
-                               end
-                       end
-                       -- end of CNAME resolving
-
-                       if reply and reply[#reply] and reply[#reply].a then
-                               for _, ip in ipairs(reply) do
-                                       log("debug", "DNS reply for %s gives us %s", connect_host, ip.a);
-                                       IPs[#IPs+1] = new_ip(ip.a, "IPv4");
-                               end
-                       end
-
-                       if has_other then
-                               if #IPs > 0 then
-                                       rfc3484_dest(host_session.ip_hosts, sources);
-                                       for i = 1, #IPs do
-                                               IPs[i] = {ip = IPs[i], port = connect_port};
-                                       end
-                                       host_session.ip_choice = 0;
-                                       try_next_ip(host_session);
-                               else
-                                       log("debug", "DNS lookup failed to get a response for %s", connect_host);
-                                       host_session.ip_hosts = nil;
-                                       if not attempt_connection(host_session, "name resolution failed") then -- Retry if we can
-                                               log("debug", "No other records to try for %s - destroying", host_session.to_host);
-                                               err = err and (": "..err) or "";
-                                               destroy_session(host_session, "DNS resolution failed"..err); -- End of the line, we can't
-                                       end
-                               end
-                       else
-                               has_other = true;
-                       end
-               end, connect_host, "A", "IN");
-
-               handle6 = adns.lookup(function (reply, err)
-                       handle6 = nil;
-
-                       if reply and reply[#reply] and reply[#reply].aaaa then
-                               for _, ip in ipairs(reply) do
-                                       log("debug", "DNS reply for %s gives us %s", connect_host, ip.aaaa);
-                                       IPs[#IPs+1] = new_ip(ip.aaaa, "IPv6");
-                               end
-                       end
-
-                       if has_other then
-                               if #IPs > 0 then
-                                       rfc3484_dest(host_session.ip_hosts, sources);
-                                       for i = 1, #IPs do
-                                               IPs[i] = {ip = IPs[i], port = connect_port};
-                                       end
-                                       host_session.ip_choice = 0;
-                                       try_next_ip(host_session);
-                               else
-                                       log("debug", "DNS lookup failed to get a response for %s", connect_host);
-                                       host_session.ip_hosts = nil;
-                                       if not attempt_connection(host_session, "name resolution failed") then -- Retry if we can
-                                               log("debug", "No other records to try for %s - destroying", host_session.to_host);
-                                               err = err and (": "..err) or "";
-                                               destroy_session(host_session, "DNS resolution failed"..err); -- End of the line, we can't
-                                       end
-                               end
-                       else
-                               has_other = true;
-                       end
-               end, connect_host, "AAAA", "IN");
-
-               return true;
-       elseif host_session.ip_hosts and #host_session.ip_hosts > host_session.ip_choice then -- Not our first attempt, and we also have IPs left to try
-               try_next_ip(host_session);
-       else
-               host_session.ip_hosts = nil;
-               if not attempt_connection(host_session, "out of IP addresses") then -- Retry if we can
-                       log("debug", "No other records to try for %s - destroying", host_session.to_host);
-                       err = err and (": "..err) or "";
-                       destroy_session(host_session, "Connecting failed"..err); -- End of the line, we can't
-                       return false;
-               end
-       end
-
-       return true;
-end
-
-function make_connect(host_session, connect_host, connect_port)
-       (host_session.log or log)("info", "Beginning new connection attempt to %s ([%s]:%d)", host_session.to_host, connect_host.addr, connect_port);
-       -- Ok, we're going to try to connect
-       
-       local from_host, to_host = host_session.from_host, host_session.to_host;
-       
-       local conn, handler;
-       if connect_host.proto == "IPv4" then
-               conn, handler = socket.tcp();
-       elseif socket.tcp6 then
-               conn, handler = socket.tcp6();
-       end
-       
-       if not conn then
-               log("warn", "Failed to create outgoing connection, system error: %s", handler);
-               return false, handler;
-       end
-
-       conn:settimeout(0);
-       local success, err = conn:connect(connect_host.addr, connect_port);
-       if not success and err ~= "timeout" then
-               log("warn", "s2s connect() to %s (%s:%d) failed: %s", host_session.to_host, connect_host.addr, connect_port, err);
-               return false, err;
-       end
-       
-       local cl = connlisteners_get("xmppserver");
-       conn = wrapclient(conn, connect_host.addr, connect_port, cl, cl.default_mode or 1 );
-       host_session.conn = conn;
-       
-       local filter = initialize_filters(host_session);
-       local w, log = conn.write, host_session.log;
-       host_session.sends2s = function (t)
-               log("debug", "sending: %s", (t.top_tag and t:top_tag()) or t:match("^[^>]*>?"));
-               if t.name then
-                       t = filter("stanzas/out", t);
-               end
-               if t then
-                       t = filter("bytes/out", tostring(t));
-                       if t then
-                               return w(conn, tostring(t));
-                       end
-               end
-       end
-       
-       -- Register this outgoing connection so that xmppserver_listener knows about it
-       -- otherwise it will assume it is a new incoming connection
-       cl.register_outgoing(conn, host_session);
-       
-       host_session:open_stream(from_host, to_host);
-       
-       log("debug", "Connection attempt in progress...");
-       add_task(connect_timeout, function ()
-               if host_session.conn ~= conn or
-                  host_session.type == "s2sout" or
-                  host_session.connecting then
-                       return; -- Ok, we're connect[ed|ing]
-               end
-               -- Not connected, need to close session and clean up
-               (host_session.log or log)("warn", "Destroying incomplete session %s->%s due to inactivity",
-                   host_session.from_host or "(unknown)", host_session.to_host or "(unknown)");
-               host_session:close("connection-timeout");
-       end);
-       return true;
-end
-
-function session_open_stream(session, from, to)
-       session.sends2s(st.stanza("stream:stream", {
-               xmlns='jabber:server', ["xmlns:db"]='jabber:server:dialback',
-               ["xmlns:stream"]='http://etherx.jabber.org/streams',
-               from=from, to=to, version='1.0', ["xml:lang"]='en'}):top_tag());
-end
-
-local function check_cert_status(session)
-       local conn = session.conn:socket()
-       local cert
-       if conn.getpeercertificate then
-               cert = conn:getpeercertificate()
-       end
-
-       if cert then
-               local chain_valid, errors = conn:getpeerverification()
-               -- Is there any interest in printing out all/the number of errors here?
-               if not chain_valid then
-                       (session.log or log)("debug", "certificate chain validation result: invalid");
-                       session.cert_chain_status = "invalid";
-               else
-                       (session.log or log)("debug", "certificate chain validation result: valid");
-                       session.cert_chain_status = "valid";
-
-                       local host;
-                       if session.direction == "incoming" then
-                               host = session.from_host;
-                       else
-                               host = session.to_host;
-                       end
-
-                       -- We'll go ahead and verify the asserted identity if the
-                       -- connecting server specified one.
-                       if host then
-                               if cert_verify_identity(host, "xmpp-server", cert) then
-                                       session.cert_identity_status = "valid"
-                               else
-                                       session.cert_identity_status = "invalid"
-                               end
-                       end
-               end
-       end
-end
-
-function streamopened(session, attr)
-       local send = session.sends2s;
-       
-       -- TODO: #29: SASL/TLS on s2s streams
-       session.version = tonumber(attr.version) or 0;
-       
-       -- TODO: Rename session.secure to session.encrypted
-       if session.secure == false then
-               session.secure = true;
-       end
-
-       if session.direction == "incoming" then
-               -- Send a reply stream header
-               session.to_host = attr.to and nameprep(attr.to);
-               session.from_host = attr.from and nameprep(attr.from);
-       
-               session.streamid = uuid_gen();
-               (session.log or log)("debug", "Incoming s2s received %s", st.stanza("stream:stream", attr):top_tag());
-               if session.to_host then
-                       if 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
-                               });
-                               return;
-                       elseif hosts[session.to_host].disallow_s2s then
-                               -- Attempting to connect to a host that disallows s2s
-                               session:close({
-                                       condition = "policy-violation";
-                                       text = "Server-to-server communication is not allowed to this host";
-                               });
-                               return;
-                       end
-               end
-
-               if session.secure and not session.cert_chain_status then check_cert_status(session); end
-
-               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, to=session.from_host, version=(session.version > 0 and "1.0" or nil) }):top_tag());
-               if session.version >= 1.0 then
-                       local features = st.stanza("stream:features");
-                       
-                       if session.to_host then
-                               hosts[session.to_host].events.fire_event("s2s-stream-features", { origin = 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);
-               end
-       elseif session.direction == "outgoing" then
-               -- If we are just using the connection for verifying dialback keys, we won't try and auth it
-               if not attr.id then error("stream response did not give us a streamid!!!"); end
-               session.streamid = attr.id;
-
-               if session.secure and not session.cert_chain_status then check_cert_status(session); end
-
-               -- Send unauthed buffer
-               -- (stanzas which are fine to send before dialback)
-               -- Note that this is *not* the stanza queue (which
-               -- we can only send if auth succeeds) :)
-               local send_buffer = session.send_buffer;
-               if send_buffer and #send_buffer > 0 then
-                       log("debug", "Sending s2s send_buffer now...");
-                       for i, data in ipairs(send_buffer) do
-                               session.sends2s(tostring(data));
-                               send_buffer[i] = nil;
-                       end
-               end
-               session.send_buffer = nil;
-       
-               -- If server is pre-1.0, don't wait for features, just do dialback
-               if session.version < 1.0 then
-                       if not session.dialback_verifying then
-                               log("debug", "Initiating dialback...");
-                               initiate_dialback(session);
-                       else
-                               mark_connected(session);
-                       end
-               end
-       end
-       session.notopen = nil;
-end
-
-function streamclosed(session)
-       (session.log or log)("debug", "Received </stream:stream>");
-       session:close();
-end
-
-function initiate_dialback(session)
-       -- generate dialback key
-       session.dialback_key = generate_dialback(session.streamid, session.to_host, session.from_host);
-       session.sends2s(format("<db:result from='%s' to='%s'>%s</db:result>", session.from_host, session.to_host, session.dialback_key));
-       session.log("info", "sent dialback key on outgoing s2s stream");
-end
-
-function generate_dialback(id, to, from)
-       return sha256_hash(id..to..from..hosts[from].dialback_secret, true);
-end
-
-function verify_dialback(id, to, from, key)
-       return key == generate_dialback(id, to, from);
+       local host_session = { to_host = to_host, from_host = from_host, host = from_host,
+                              notopen = true, type = "s2sout_unauthed", direction = "outgoing" };
+       hosts[from_host].s2sout[to_host] = host_session;
+       local conn_name = "s2sout"..tostring(host_session):match("[a-f0-9]*$");
+       host_session.log = logger_init(conn_name);
+       return host_session;
 end
 
 function make_authenticated(session, host)
@@ -678,10 +113,7 @@ function mark_connected(session)
        local from, to = session.from_host, session.to_host;
        
        session.log("info", session.direction.." s2s connection "..from.."->"..to.." complete");
-       
-       local send_to_host = send_to_host;
-       function session.send(data) return send_to_host(to, from, data); end
-       
+
        local event_data = { session = session };
        if session.type == "s2sout" then
                prosody.events.fire_event("s2sout-established", event_data);
@@ -701,6 +133,7 @@ function mark_connected(session)
                        session.sendq = nil;
                end
                
+               session.ip_hosts = nil;
                session.srv_hosts = nil;
        end
 end
@@ -738,7 +171,7 @@ function destroy_session(session, reason)
        
        if session.direction == "outgoing" then
                hosts[session.from_host].s2sout[session.to_host] = nil;
-               bounce_sendq(session, reason);
+               session:bounce_sendq(reason);
        elseif session.direction == "incoming" then
                incoming_s2s[session] = nil;
        end
index b1ec819f22182947abfc3baf483679e83fdec7b9..c101bf4e5e3cc4c4442eb5c7d6780177874b394e 100644 (file)
@@ -6,11 +6,8 @@
 -- COPYING file in the source package for more information.
 --
 
-
-
 local tonumber, tostring, setmetatable = tonumber, tostring, setmetatable;
 local ipairs, pairs, print, next= ipairs, pairs, print, next;
-local format = string.format;
 
 local hosts = hosts;
 local full_sessions = full_sessions;
@@ -19,12 +16,11 @@ local bare_sessions = bare_sessions;
 local logger = require "util.logger";
 local log = logger.init("sessionmanager");
 local error = error;
-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 nodeprep = require "util.encodings".stringprep.nodeprep;
+local uuid_generate = require "util.uuid".generate;
 
 local initialize_filters = require "util.filters".initialize;
 local fire_event = prosody.events.fire_event;
@@ -33,8 +29,6 @@ local gettime = require "socket".gettime;
 
 local st = require "util.stanza";
 
-local c2s_timeout = config_get("*", "core", "c2s_timeout");
-
 local newproxy = newproxy;
 local getmetatable = getmetatable;
 
@@ -67,14 +61,6 @@ function new_session(conn)
        session.ip = conn:ip();
        local conn_name = "c2s"..tostring(conn):match("[a-f0-9]+$");
        session.log = logger.init(conn_name);
-       
-       if c2s_timeout then
-               add_task(c2s_timeout, function ()
-                       if session.type == "c2s_unauthed" then
-                               session:close("connection-timeout");
-                       end
-               end);
-       end
                
        return session;
 end
@@ -216,50 +202,6 @@ function bind_resource(session, resource)
        return true;
 end
 
-function streamopened(session, attr)
-       local send = session.send;
-       session.host = attr.to;
-       if not session.host then
-               session:close{ condition = "improper-addressing",
-                       text = "A 'to' attribute is required on stream headers" };
-               return;
-       end
-       session.host = nameprep(session.host);
-       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);
-
-       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");
-       hosts[session.host].events.fire_event("stream-features", { origin = session, features = features });
-       fire_event("stream-features", session, features);
-
-       send(features);
-
-end
-
-function streamclosed(session)
-       session.log("debug", "Received </stream:stream>");
-       session:close();
-end
-
 function send_to_available_resources(user, host, stanza)
        local jid = user.."@"..host;
        local count = 0;
index 406ad2f04d1c61fcb340a0e7dacff77ee6ae4968..54c5a1a6a8122db45cb96e6cfddbade83c980ec3 100644 (file)
@@ -11,7 +11,6 @@ local log = require "util.logger".init("stanzarouter")
 local hosts = _G.prosody.hosts;
 local tostring = tostring;
 local st = require "util.stanza";
-local send_s2s = require "core.s2smanager".send_to_host;
 local jid_split = require "util.jid".split;
 local jid_prepped_split = require "util.jid".prepped_split;
 
@@ -105,11 +104,6 @@ function core_process_stanza(origin, stanza)
                stanza.attr.from = from;
        end
 
-       --[[if to and not(hosts[to]) and not(hosts[to_bare]) and (hosts[host] and hosts[host].type ~= "local") then -- not for us?
-               log("warn", "stanza recieved for a non-local server");
-               return; -- FIXME what should we do here?
-       end]] -- FIXME
-
        if (origin.type == "s2sin" or origin.type == "c2s" or origin.type == "component") and xmlns == nil then
                if origin.type == "s2sin" and not origin.dummy then
                        local host_status = origin.hosts[from_host];
@@ -189,26 +183,18 @@ function core_route_stanza(origin, stanza)
        if hosts[host] then
                -- old stanza routing code removed
                core_post_stanza(origin, stanza);
-       elseif origin.type == "c2s" then
-               -- Remote host
+       else
+               log("debug", "Routing to remote...");
                if not hosts[from_host] then
                        log("error", "No hosts[from_host] (please report): %s", tostring(stanza));
-               end
-               if (not hosts[from_host]) or (not hosts[from_host].disallow_s2s) then
+               else
                        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);
-                       send_s2s(origin.host, host, stanza); -- TODO handle remote routing errors
+                       local routed = prosody.events.fire_event("route/remote", { origin = origin, stanza = stanza, from_host = from_host, to_host = host }); --FIXME: Should be per-host (shared modules!)
                        stanza.attr.xmlns = xmlns; -- reset
-               else
-                       core_route_stanza(hosts[from_host], st.error_reply(stanza, "cancel", "not-allowed", "Communication with remote servers is not allowed"));
+                       if routed == nil then
+                               core_route_stanza(hosts[from_host], st.error_reply(stanza, "cancel", "not-allowed", "Communication with remote domains is not enabled"));
+                       end
                end
-       elseif origin.type == "component" or origin.type == "local" then
-               -- Route via s2s for components and modules
-               log("debug", "Routing outgoing stanza for %s to %s", from_host, host);
-               send_s2s(from_host, host, stanza);
-       else
-               log("warn", "received %s stanza from unhandled connection type: %s", tostring(stanza.name), tostring(origin.type));
        end
 end
diff --git a/net/connlisteners.lua b/net/connlisteners.lua
deleted file mode 100644 (file)
index 6a227c9..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
--- Prosody IM
--- Copyright (C) 2008-2010 Matthew Wild
--- Copyright (C) 2008-2010 Waqas Hussain
--- 
--- This project is MIT/X11 licensed. Please see the
--- COPYING file in the source package for more information.
---
-
-
-
-local listeners_dir = (CFG_SOURCEDIR or ".").."/net/";
-local server = require "net.server";
-local log = require "util.logger".init("connlisteners");
-local tostring = tostring;
-local type = type
-local ipairs = ipairs
-
-local dofile, xpcall, error =
-      dofile, xpcall, error
-
-local debug_traceback = debug.traceback;
-
-module "connlisteners"
-
-local listeners = {};
-
-function register(name, listener)
-       if listeners[name] and listeners[name] ~= listener then
-               log("debug", "Listener %s is already registered, not registering any more", name);
-               return false;
-       end
-       listeners[name] = listener;
-       log("debug", "Registered connection listener %s", name);
-       return true;
-end
-
-function deregister(name)
-       listeners[name] = nil;
-end
-
-function get(name)
-       local h = listeners[name];
-       if not h then
-               local ok, ret = xpcall(function() dofile(listeners_dir..name:gsub("[^%w%-]", "_").."_listener.lua") end, debug_traceback);
-               if not ok then
-                       log("error", "Error while loading listener '%s': %s", tostring(name), tostring(ret));
-                       return nil, ret;
-               end
-               h = listeners[name];
-       end
-       return h;
-end
-
-function start(name, udata)
-       local h, err = get(name);
-       if not h then
-               error("No such connection module: "..name.. (err and (" ("..err..")") or ""), 0);
-       end
-       
-       local interfaces = (udata and udata.interface) or h.default_interface or "*";
-       if type(interfaces) == "string" then interfaces = {interfaces}; end
-       local port = (udata and udata.port) or h.default_port or error("Can't start listener "..name.." because no port was specified, and it has no default port", 0);
-       local mode = (udata and udata.mode) or h.default_mode or 1;
-       local ssl = (udata and udata.ssl) or nil;
-       local autossl = udata and udata.type == "ssl";
-       
-       if autossl and not ssl then
-               return nil, "no ssl context";
-       end
-
-       ok, err = true, {};
-       for _, interface in ipairs(interfaces) do
-               local handler
-               handler, err[interface] = server.addserver(interface, port, h, mode, autossl and ssl or nil);
-               ok = ok and handler;
-       end
-
-       return ok, err;
-end
-
-return _M;
index 6287f408fbf32eaa3875aef6c22ebc65f2e18eb2..59f2c0804924778b58d191192610f41b7198224a 100644 (file)
@@ -13,9 +13,6 @@ local httpstream_new = require "util.httpstream".new;
 
 local server = require "net.server"
 
-local connlisteners_get = require "net.connlisteners".get;
-local listener = connlisteners_get("httpclient") or error("No httpclient listener!");
-
 local t_insert, t_concat = table.insert, table.concat;
 local pairs, ipairs = pairs, ipairs;
 local tonumber, tostring, xpcall, select, debug_traceback, char, format =
@@ -25,6 +22,52 @@ local log = require "util.logger".init("http");
 
 module "http"
 
+local requests = {}; -- Open requests
+
+local listener = { default_port = 80, default_mode = "*a" };
+
+function listener.onconnect(conn)
+       local req = requests[conn];
+       -- Send the request
+       local request_line = { req.method or "GET", " ", req.path, " HTTP/1.1\r\n" };
+       if req.query then
+               t_insert(request_line, 4, "?"..req.query);
+       end
+       
+       conn:write(t_concat(request_line));
+       local t = { [2] = ": ", [4] = "\r\n" };
+       for k, v in pairs(req.headers) do
+               t[1], t[3] = k, v;
+               conn:write(t_concat(t));
+       end
+       conn:write("\r\n");
+       
+       if req.body then
+               conn:write(req.body);
+       end
+end
+
+function listener.onincoming(conn, data)
+       local request = requests[conn];
+
+       if not request then
+               log("warn", "Received response from connection %s with no request attached!", tostring(conn));
+               return;
+       end
+
+       if data and request.reader then
+               request:reader(data);
+       end
+end
+
+function listener.ondisconnect(conn, err)
+       local request = requests[conn];
+       if request and request.conn then
+               request:reader(nil);
+       end
+       requests[conn] = nil;
+end
+
 function urlencode(s) return s and (s:gsub("%W", function (c) return format("%%%02x", c:byte()); end)); end
 function urldecode(s) return s and (s:gsub("%%(%x%x)", function (c) return char(tonumber(c,16)); end)); end
 
@@ -152,8 +195,7 @@ function request(u, ex, callback)
        req.reader = request_reader;
        req.state = "status";
 
-       listener.register_request(req.handler, req);
-
+       requests[req.handler] = req;
        return req;
 end
 
diff --git a/net/http/codes.lua b/net/http/codes.lua
new file mode 100644 (file)
index 0000000..2e70102
--- /dev/null
@@ -0,0 +1,66 @@
+
+local response_codes = {
+       -- Source: http://www.iana.org/assignments/http-status-codes
+       -- s/^\(\d*\)\s*\(.*\S\)\s*\[RFC.*\]\s*$/^I["\1"] = "\2";
+       [100] = "Continue";
+       [101] = "Switching Protocols";
+       [102] = "Processing";
+
+       [200] = "OK";
+       [201] = "Created";
+       [202] = "Accepted";
+       [203] = "Non-Authoritative Information";
+       [204] = "No Content";
+       [205] = "Reset Content";
+       [206] = "Partial Content";
+       [207] = "Multi-Status";
+       [208] = "Already Reported";
+       [226] = "IM Used";
+
+       [300] = "Multiple Choices";
+       [301] = "Moved Permanently";
+       [302] = "Found";
+       [303] = "See Other";
+       [304] = "Not Modified";
+       [305] = "Use Proxy";
+       -- The 306 status code was used in a previous version of [RFC2616], is no longer used, and the code is reserved.
+       [307] = "Temporary Redirect";
+
+       [400] = "Bad Request";
+       [401] = "Unauthorized";
+       [402] = "Payment Required";
+       [403] = "Forbidden";
+       [404] = "Not Found";
+       [405] = "Method Not Allowed";
+       [406] = "Not Acceptable";
+       [407] = "Proxy Authentication Required";
+       [408] = "Request Timeout";
+       [409] = "Conflict";
+       [410] = "Gone";
+       [411] = "Length Required";
+       [412] = "Precondition Failed";
+       [413] = "Request Entity Too Large";
+       [414] = "Request-URI Too Long";
+       [415] = "Unsupported Media Type";
+       [416] = "Requested Range Not Satisfiable";
+       [417] = "Expectation Failed";
+       [422] = "Unprocessable Entity";
+       [423] = "Locked";
+       [424] = "Failed Dependency";
+       -- The 425 status code is reserved for the WebDAV advanced collections expired proposal [RFC2817]
+       [426] = "Upgrade Required";
+
+       [500] = "Internal Server Error";
+       [501] = "Not Implemented";
+       [502] = "Bad Gateway";
+       [503] = "Service Unavailable";
+       [504] = "Gateway Timeout";
+       [505] = "HTTP Version Not Supported";
+       [506] = "Variant Also Negotiates"; -- Experimental
+       [507] = "Insufficient Storage";
+       [508] = "Loop Detected";
+       [510] = "Not Extended";
+};
+
+for k,v in pairs(response_codes) do response_codes[k] = k.." "..v; end
+return setmetatable(response_codes, { __index = function(t, k) return k.." Unassigned"; end })
diff --git a/net/http/parser.lua b/net/http/parser.lua
new file mode 100644 (file)
index 0000000..c98c75a
--- /dev/null
@@ -0,0 +1,116 @@
+
+local tonumber = tonumber;
+local assert = assert;
+
+local httpstream = {};
+
+function httpstream.new(success_cb, error_cb, parser_type, options_cb)
+       local client = true;
+       if not parser_type or parser_type == "server" then client = false; else assert(parser_type == "client", "Invalid parser type"); end
+       local buf = "";
+       local chunked;
+       local state = nil;
+       local packet;
+       local len;
+       local have_body;
+       local error;
+       return {
+               feed = function(self, data)
+                       if error then return nil, "parse has failed"; end
+                       if not data then -- EOF
+                               if state and client and not len then -- reading client body until EOF
+                                       packet.body = buf;
+                                       success_cb(packet);
+                               elseif buf ~= "" then -- unexpected EOF
+                                       error = true; return error_cb();
+                               end
+                               return;
+                       end
+                       buf = buf..data;
+                       while #buf > 0 do
+                               if state == nil then -- read request
+                                       local index = buf:find("\r\n\r\n", nil, true);
+                                       if not index then return; end -- not enough data
+                                       local method, path, httpversion, status_code, reason_phrase;
+                                       local first_line;
+                                       local headers = {};
+                                       for line in buf:sub(1,index+1):gmatch("([^\r\n]+)\r\n") do -- parse request
+                                               if first_line then
+                                                       local key, val = line:match("^([^%s:]+): *(.*)$");
+                                                       if not key then error = true; return error_cb("invalid-header-line"); end -- TODO handle multi-line and invalid headers
+                                                       key = key:lower();
+                                                       headers[key] = headers[key] and headers[key]..","..val or val;
+                                               else
+                                                       first_line = line;
+                                                       if client then
+                                                               httpversion, status_code, reason_phrase = line:match("^HTTP/(1%.[01]) (%d%d%d) (.*)$");
+                                                               if not status_code then error = true; return error_cb("invalid-status-line"); end
+                                                               have_body = not
+                                                                        ( (options_cb and options_cb().method == "HEAD")
+                                                                       or (status_code == 204 or status_code == 304 or status_code == 301)
+                                                                       or (status_code >= 100 and status_code < 200) );
+                                                               chunked = have_body and headers["transfer-encoding"] == "chunked";
+                                                       else
+                                                               method, path, httpversion = line:match("^(%w+) (%S+) HTTP/(1%.[01])$");
+                                                               if not method then error = true; return error_cb("invalid-status-line"); end
+                                                               path = path:gsub("^//+", "/"); -- TODO parse url more
+                                                       end
+                                               end
+                                       end
+                                       len = tonumber(headers["content-length"]); -- TODO check for invalid len
+                                       if client then
+                                               -- FIXME handle '100 Continue' response (by skipping it)
+                                               if not have_body then len = 0; end
+                                               packet = {
+                                                       code = status_code;
+                                                       httpversion = httpversion;
+                                                       headers = headers;
+                                                       body = have_body and "" or nil;
+                                                       -- COMPAT the properties below are deprecated
+                                                       responseversion = httpversion;
+                                                       responseheaders = headers;
+                                               };
+                                       else
+                                               len = len or 0;
+                                               packet = {
+                                                       method = method;
+                                                       path = path;
+                                                       httpversion = httpversion;
+                                                       headers = headers;
+                                                       body = nil;
+                                               };
+                                       end
+                                       buf = buf:sub(index + 4);
+                                       state = true;
+                               end
+                               if state then -- read body
+                                       if client then
+                                               if chunked then
+                                                       local index = buf:find("\r\n", nil, true);
+                                                       if not index then return; end -- not enough data
+                                                       local chunk_size = buf:match("^%x+");
+                                                       if not chunk_size then error = true; return error_cb("invalid-chunk-size"); end
+                                                       chunk_size = tonumber(chunk_size, 16);
+                                                       index = index + 2;
+                                                       if chunk_size == 0 then
+                                                               state = nil; success_cb(packet);
+                                                       elseif #buf - index + 1 >= chunk_size then -- we have a chunk
+                                                               packet.body = packet.body..buf:sub(index, index + chunk_size - 1);
+                                                               buf = buf:sub(index + chunk_size);
+                                                       end
+                                                       error("trailers"); -- FIXME MUST read trailers
+                                               elseif len and #buf >= len then
+                                                       packet.body, buf = buf:sub(1, len), buf:sub(len + 1);
+                                                       state = nil; success_cb(packet);
+                                               end
+                                       elseif #buf >= len then
+                                               packet.body, buf = buf:sub(1, len), buf:sub(len + 1);
+                                               state = nil; success_cb(packet);
+                                       end
+                               end
+                       end
+               end;
+       };
+end
+
+return httpstream;
diff --git a/net/http/server.lua b/net/http/server.lua
new file mode 100644 (file)
index 0000000..185ac9a
--- /dev/null
@@ -0,0 +1,283 @@
+
+local t_insert, t_remove, t_concat = table.insert, table.remove, table.concat;
+local parser_new = require "net.http.parser".new;
+local events = require "util.events".new();
+local addserver = require "net.server".addserver;
+local log = require "util.logger".init("http.server");
+local os_date = os.date;
+local pairs = pairs;
+local s_upper = string.upper;
+local setmetatable = setmetatable;
+local xpcall = xpcall;
+local debug = debug;
+local tostring = tostring;
+local codes = require "net.http.codes";
+local _G = _G;
+local legacy_httpserver = require "net.httpserver";
+
+local _M = {};
+
+local sessions = {};
+local handlers = {};
+
+local listener = {};
+
+local function is_wildcard_event(event)
+       return event:sub(-2, -1) == "/*";
+end
+local function is_wildcard_match(wildcard_event, event)
+       return wildcard_event:sub(1, -2) == event:sub(1, #wildcard_event-1);
+end
+
+local event_map = events._event_map;
+setmetatable(events._handlers, {
+       __index = function (handlers, curr_event)
+               if is_wildcard_event(curr_event) then return; end -- Wildcard events cannot be fired
+               -- Find all handlers that could match this event, sort them
+               -- and then put the array into handlers[event]
+               local matching_handlers_set = {};
+               local handlers_array = {};
+               for event, handlers_set in pairs(event_map) do
+                       if event == curr_event or
+                       is_wildcard_event(event) and is_wildcard_match(event, curr_event) then
+                               for handler, priority in pairs(handlers_set) do
+                                       matching_handlers_set[handler] = { (select(2, event:gsub("/", "%1"))), priority };
+                                       table.insert(handlers_array, handler);
+                               end
+                       end
+               end
+               if #handlers_array == 0 then return; end
+               table.sort(handlers_array, function(b, a)
+                       local a_score, b_score = matching_handlers_set[a], matching_handlers_set[b];
+                       for i = 1, #a_score do
+                               if a ~= b then -- If equal, compare next score value
+                                       return a_score[i] < b_score[i];
+                               end
+                       end
+                       return false;
+               end);
+               handlers[curr_event] = handlers_array;
+               return handlers_array;
+       end;
+       __newindex = function (handlers, curr_event, handlers_array)
+               if handlers_array == nil
+               and is_wildcard_event(curr_event) then
+                       -- Invalidate all matching
+                       for event in pairs(handlers) do
+                               if is_wildcard_match(curr_event, event) then
+                                       handlers[event] = nil;
+                               end
+                       end
+               end
+       end;
+});
+
+local handle_request;
+local _1, _2, _3;
+local function _handle_request() return handle_request(_1, _2, _3); end
+local function _traceback_handler(err) log("error", "Traceback[http]: %s: %s", tostring(err), debug.traceback()); end
+
+function listener.onconnect(conn)
+       local secure = conn:ssl() and true or nil;
+       local pending = {};
+       local waiting = false;
+       local function process_next(last_response)
+               --if waiting then log("debug", "can't process_next, waiting"); return; end
+               if sessions[conn] and #pending > 0 then
+                       local request = t_remove(pending);
+                       --log("debug", "process_next: %s", request.path);
+                       waiting = true;
+                       --handle_request(conn, request, process_next);
+                       _1, _2, _3 = conn, request, process_next;
+                       if not xpcall(_handle_request, _traceback_handler) then
+                               conn:write("HTTP/1.0 503 Internal Server Error\r\n\r\nAn error occured during the processing of this request.");
+                               conn:close();
+                       end
+               else
+                       --log("debug", "ready for more");
+                       waiting = false;
+               end
+       end
+       local function success_cb(request)
+               --log("debug", "success_cb: %s", request.path);
+               request.secure = secure;
+               t_insert(pending, request);
+               if not waiting then
+                       process_next();
+               end
+       end
+       local function error_cb(err)
+               log("debug", "error_cb: %s", err or "<nil>");
+               -- FIXME don't close immediately, wait until we process current stuff
+               -- FIXME if err, send off a bad-request response
+               sessions[conn] = nil;
+               conn:close();
+       end
+       sessions[conn] = parser_new(success_cb, error_cb);
+end
+
+function listener.ondisconnect(conn)
+       sessions[conn] = nil;
+end
+
+function listener.onincoming(conn, data)
+       sessions[conn]:feed(data);
+end
+
+local headerfix = setmetatable({}, {
+       __index = function(t, k)
+               local v = "\r\n"..k:gsub("_", "-"):gsub("%f[%w].", s_upper)..": ";
+               t[k] = v;
+               return v;
+       end
+});
+
+function _M.hijack_response(response, listener)
+       error("TODO");
+end
+function handle_request(conn, request, finish_cb)
+       --log("debug", "handler: %s", request.path);
+       local headers = {};
+       for k,v in pairs(request.headers) do headers[k:gsub("-", "_")] = v; end
+       request.headers = headers;
+       request.conn = conn;
+
+       local date_header = os_date('!%a, %d %b %Y %H:%M:%S GMT'); -- FIXME use
+       local conn_header = request.headers.connection;
+       local keep_alive = conn_header == "Keep-Alive" or (request.httpversion == "1.1" and conn_header ~= "close");
+
+       local response = {
+               request = request;
+               status_code = 200;
+               headers = { date = date_header, connection = (keep_alive and "Keep-Alive" or "close") };
+               conn = conn;
+               send = _M.send_response;
+               finish_cb = finish_cb;
+       };
+
+       if not request.headers.host then
+               response.status_code = 400;
+               response.headers.content_type = "text/html";
+               response:send("<html><head>400 Bad Request</head><body>400 Bad Request: No Host header.</body></html>");
+       else
+               -- TODO call handler
+               --response.headers.content_type = "text/plain";
+               --response:send("host="..(request.headers.host or "").."\npath="..request.path.."\n"..(request.body or ""));
+               local host = request.headers.host;
+               if host then
+                       host = host:match("[^:]*"):lower();
+                       local event = request.method.." "..host..request.path:match("[^?]*");
+                       local payload = { request = request, response = response };
+                       --log("debug", "Firing event: %s", event);
+                       local result = events.fire_event(event, payload);
+                       if result ~= nil then
+                               if result ~= true then
+                                       local code, body = 200, "";
+                                       local result_type = type(result);
+                                       if result_type == "number" then
+                                               response.status_code = result;
+                                       elseif result_type == "string" then
+                                               body = result;
+                                       elseif result_type == "table" then
+                                               body = result.body;
+                                               result.body = nil;
+                                               for k, v in pairs(result) do
+                                                       response[k] = v;
+                                               end
+                                       end
+                                       response:send(body);
+                               end
+                               return;
+                       end
+               end
+
+               -- if handler not called, fallback to legacy httpserver handlers
+               _M.legacy_handler(request, response);
+       end
+end
+function _M.send_response(response, body)
+       local status_line = "HTTP/"..response.request.httpversion.." "..(response.status or codes[response.status_code]);
+       local headers = response.headers;
+       body = body or "";
+       headers.content_length = #body;
+
+       local output = { status_line };
+       for k,v in pairs(headers) do
+               t_insert(output, headerfix[k]..v);
+       end
+       t_insert(output, "\r\n\r\n");
+       t_insert(output, body);
+
+       response.conn:write(t_concat(output));
+       if headers.connection == "Keep-Alive" then
+               response:finish_cb();
+       else
+               response.conn:close();
+       end
+end
+function _M.legacy_handler(request, response)
+       log("debug", "Invoking legacy handler");
+       local base = request.path:match("^/([^/?]+)");
+       local legacy_server = legacy_httpserver and legacy_httpserver.new.http_servers[5280];
+       local handler = legacy_server and legacy_server.handlers[base];
+       if not handler then handler = legacy_httpserver and legacy_httpserver.set_default_handler.default_handler; end
+       if handler then
+               -- add legacy properties to request object
+               request.url = { path = request.path };
+               request.handler = response.conn;
+               request.id = tostring{}:match("%x+$");
+               local headers = {};
+               for k,v in pairs(request.headers) do
+                       headers[k:gsub("_", "-")] = v;
+               end
+               request.headers = headers;
+               function request:send(resp)
+                       if self.destroyed then return; end
+                       if resp.body or resp.headers then
+                               if resp.headers then
+                                       for k,v in pairs(resp.headers) do response.headers[k] = v; end
+                               end
+                               response:send(resp.body)
+                       else
+                               response:send(resp)
+                       end
+                       self.sent = true;
+                       self:destroy();
+               end
+               function request:destroy()
+                       if self.destroyed then return; end
+                       if not self.sent then return self:send(""); end
+                       self.destroyed = true;
+                       if self.on_destroy then
+                               log("debug", "Request has destroy callback");
+                               self:on_destroy();
+                       else
+                               log("debug", "Request has no destroy callback");
+                       end
+               end
+               local r = handler(request.method, request.body, request);
+               if r ~= true then
+                       request:send(r);
+               end
+       else
+               log("debug", "No handler found");
+               response.status_code = 404;
+               response.headers.content_type = "text/html";
+               response:send("<html><head><title>404 Not Found</title></head><body>404 Not Found: No such page.</body></html>");
+       end
+end
+
+function _M.add_handler(event, handler, priority)
+       events.add_handler(event, handler, priority);
+end
+function _M.remove_handler(event, handler)
+       events.remove_handler(event, handler);
+end
+
+function _M.listen_on(port, interface, ssl)
+       addserver(interface or "*", port, listener, "*a", ssl);
+end
+
+_M.listener = listener;
+_M.codes = codes;
+return _M;
diff --git a/net/httpclient_listener.lua b/net/httpclient_listener.lua
deleted file mode 100644 (file)
index c4e3c15..0000000
+++ /dev/null
@@ -1,66 +0,0 @@
--- Prosody IM
--- Copyright (C) 2008-2010 Matthew Wild
--- Copyright (C) 2008-2010 Waqas Hussain
--- 
--- This project is MIT/X11 licensed. Please see the
--- COPYING file in the source package for more information.
---
-
-local log = require "util.logger".init("httpclient_listener");
-local t_concat, t_insert = table.concat, table.insert;
-
-local connlisteners_register = require "net.connlisteners".register;
-
-local requests = {}; -- Open requests
-local buffers = {}; -- Buffers of partial lines
-
-local httpclient = { default_port = 80, default_mode = "*a" };
-
-function httpclient.onconnect(conn)
-       local req = requests[conn];
-       -- Send the request
-       local request_line = { req.method or "GET", " ", req.path, " HTTP/1.1\r\n" };
-       if req.query then
-               t_insert(request_line, 4, "?"..req.query);
-       end
-       
-       conn:write(t_concat(request_line));
-       local t = { [2] = ": ", [4] = "\r\n" };
-       for k, v in pairs(req.headers) do
-               t[1], t[3] = k, v;
-               conn:write(t_concat(t));
-       end
-       conn:write("\r\n");
-       
-       if req.body then
-               conn:write(req.body);
-       end
-end
-
-function httpclient.onincoming(conn, data)
-       local request = requests[conn];
-
-       if not request then
-               log("warn", "Received response from connection %s with no request attached!", tostring(conn));
-               return;
-       end
-
-       if data and request.reader then
-               request:reader(data);
-       end
-end
-
-function httpclient.ondisconnect(conn, err)
-       local request = requests[conn];
-       if request and request.conn then
-               request:reader(nil);
-       end
-       requests[conn] = nil;
-end
-
-function httpclient.register_request(conn, req)
-       log("debug", "Attaching request %s to connection %s", tostring(req.id or req), tostring(conn));
-       requests[conn] = req;
-end
-
-connlisteners_register("httpclient", httpclient);
index 44e8e24d735f16dea0735e2ec2aa3d933f2b516d..0f5a41861a97bae1dc93a4162155ee22eb4d873d 100644 (file)
@@ -10,8 +10,8 @@
 local url_parse = require "socket.url".parse;
 local httpstream_new = require "util.httpstream".new;
 
-local connlisteners_start = require "net.connlisteners".start;
-local connlisteners_get = require "net.connlisteners".get;
+--local connlisteners_start = require "net.connlisteners".start;
+--local connlisteners_get = require "net.connlisteners".get;
 local listener;
 
 local t_insert, t_concat = table.insert, table.concat;
@@ -164,7 +164,7 @@ end
 
 function destroy_request(request)
        log("debug", "Destroying request %s", request.id);
-       listener = listener or connlisteners_get("httpserver");
+       --listener = listener or connlisteners_get("httpserver");
        if not request.destroyed then
                request.destroyed = true;
                if request.on_destroy then
@@ -186,7 +186,7 @@ function new(params)
                http_server = { handlers = {} };
                http_servers[params.port] = http_server;
                -- We weren't already listening on this port, so start now
-               connlisteners_start("httpserver", params);
+               --connlisteners_start("httpserver", params);
        end
        if params.base then
                http_server.handlers[params.base] = params.handler;
diff --git a/net/multiplex_listener.lua b/net/multiplex_listener.lua
deleted file mode 100644 (file)
index b515ccc..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-
-local connlisteners_register = require "net.connlisteners".register;
-local connlisteners_get = require "net.connlisteners".get;
-
-local httpserver_listener = connlisteners_get("httpserver");
-local xmppserver_listener = connlisteners_get("xmppserver");
-local xmppclient_listener = connlisteners_get("xmppclient");
-local xmppcomponent_listener = connlisteners_get("xmppcomponent");
-
-local server = { default_mode = "*a" };
-
-local buffer = {};
-
-function server.onincoming(conn, data)
-       if not data then return; end
-       local buf = buffer[conn];
-       buffer[conn] = nil;
-       buf = buf and buf..data or data;
-       if buf:match("^[a-zA-Z]") then
-               local listener = httpserver_listener;
-               conn:setlistener(listener);
-               local onconnect = listener.onconnect;
-               if onconnect then onconnect(conn) end
-               listener.onincoming(conn, buf);
-       elseif buf:match(">") then
-               local listener;
-               local xmlns = buf:match("%sxmlns%s*=%s*['\"]([^'\"]*)");
-               if xmlns == "jabber:server" then
-                       listener = xmppserver_listener;
-               elseif xmlns == "jabber:component:accept" then
-                       listener = xmppcomponent_listener;
-               else
-                       listener = xmppclient_listener;
-               end
-               conn:setlistener(listener);
-               local onconnect = listener.onconnect;
-               if onconnect then onconnect(conn) end
-               listener.onincoming(conn, buf);
-       elseif #buf > 1024 then
-               conn:close();
-       else
-               buffer[conn] = buf;
-       end
-end
-
-function server.ondisconnect(conn, err)
-       buffer[conn] = nil; -- warn if no buffer?
-end
-
-connlisteners_register("multiplex", server);
index dbf5161f133920c41b40e1a3e0b0817d0d2f8816..8d6f5597b945d91eaed1d742afdcc58caf0528b0 100644 (file)
@@ -343,24 +343,11 @@ do
                                return nil, "writebuffer not empty, waiting"
                        end
                else
-                       debug( "try to close server with id:", self.id, "args:", now )
+                       debug( "try to close server with id:", tostring(self.id), "args:", tostring(now) )
                        self.fatalerror = "server to close"
                        self:_lock( true )
-                       local count = 0
-                       for _, item in ipairs( interfacelist( ) ) do
-                               if ( item.type ~= "server" ) and ( item._server == self ) then  -- client/server match
-                                       if item:close( now ) then  -- writebuffer was empty
-                                               count = count + 1
-                                       end
-                               end
-                       end
-                       local timeout = 0  -- dont wait for unfinished writebuffers of clients...
-                       if not now then
-                               timeout = cfg.WRITE_TIMEOUT  -- ...or wait for it
-                       end
-                       self:_close( timeout )  -- add new event to remove the server interface
-                       debug( "seconds remained until server is closed:", timeout )
-                       return count  -- returns finished clients with empty writebuffer
+                       self:_close( 0 )  -- add new event to remove the server interface
+                       return true
                end
        end
        
index 8802f620daffdd0f7077dd59fea1d7b56162d2b6..d6cfc1f81879e87eeba3873de07660f1cc860eba 100644 (file)
@@ -202,6 +202,7 @@ wrapserver = function( listeners, socket, ip, serverport, pattern, sslctx, maxco
                socket:close( )
                _sendlistlen = removesocket( _sendlist, socket, _sendlistlen )
                _readlistlen = removesocket( _readlist, socket, _readlistlen )
+               _server[ip..":"..serverport] = nil;
                _socketlist[ socket ] = nil
                handler = nil
                socket = nil
diff --git a/net/xmppclient_listener.lua b/net/xmppclient_listener.lua
deleted file mode 100644 (file)
index 4cc90cb..0000000
+++ /dev/null
@@ -1,179 +0,0 @@
--- Prosody IM
--- Copyright (C) 2008-2010 Matthew Wild
--- Copyright (C) 2008-2010 Waqas Hussain
--- 
--- This project is MIT/X11 licensed. Please see the
--- COPYING file in the source package for more information.
---
-
-
-
-local logger = require "logger";
-local log = logger.init("xmppclient_listener");
-local new_xmpp_stream = require "util.xmppstream".new;
-
-local connlisteners_register = require "net.connlisteners".register;
-
-local sessionmanager = require "core.sessionmanager";
-local sm_new_session, sm_destroy_session = sessionmanager.new_session, sessionmanager.destroy_session;
-local sm_streamopened = sessionmanager.streamopened;
-local sm_streamclosed = sessionmanager.streamclosed;
-local st = require "util.stanza";
-local xpcall = xpcall;
-local tostring = tostring;
-local type = type;
-local traceback = debug.traceback;
-
-local config = require "core.configmanager";
-local opt_keepalives = config.get("*", "core", "tcp_keepalives");
-
-local stream_callbacks = { default_ns = "jabber:client",
-               streamopened = sm_streamopened, streamclosed = sm_streamclosed, handlestanza = core_process_stanza };
-
-local xmlns_xmpp_streams = "urn:ietf:params:xml:ns:xmpp-streams";
-
-function stream_callbacks.error(session, error, data)
-       if error == "no-stream" then
-               session.log("debug", "Invalid opening stream header");
-               session:close("invalid-namespace");
-       elseif error == "parse-error" then
-               (session.log or log)("debug", "Client XML parse error: %s", tostring(data));
-               session:close("not-well-formed");
-       elseif error == "stream-error" then
-               local condition, text = "undefined-condition";
-               for child in data:children() do
-                       if child.attr.xmlns == xmlns_xmpp_streams then
-                               if child.name ~= "text" then
-                                       condition = child.name;
-                               else
-                                       text = child:get_text();
-                               end
-                               if condition ~= "undefined-condition" and text then
-                                       break;
-                               end
-                       end
-               end
-               text = condition .. (text and (" ("..text..")") or "");
-               session.log("info", "Session closed by remote with error: %s", text);
-               session:close(nil, text);
-       end
-end
-
-local function handleerr(err) log("error", "Traceback[c2s]: %s: %s", tostring(err), traceback()); end
-function stream_callbacks.handlestanza(session, stanza)
-       stanza = session.filter("stanzas/in", stanza);
-       if stanza then
-               return xpcall(function () return core_process_stanza(session, stanza) end, handleerr);
-       end
-end
-
-local sessions = {};
-local xmppclient = { default_port = 5222, default_mode = "*a" };
-
--- These are session methods --
-
-local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'};
-local default_stream_attr = { ["xmlns:stream"] = "http://etherx.jabber.org/streams", xmlns = stream_callbacks.default_ns, version = "1.0", id = "" };
-local function session_close(session, reason)
-       local log = session.log or log;
-       if session.conn then
-               if session.notopen then
-                       session.send("<?xml version='1.0'?>");
-                       session.send(st.stanza("stream:stream", default_stream_attr):top_tag());
-               end
-               if reason then
-                       if type(reason) == "string" then -- assume stream error
-                               log("info", "Disconnecting client, <stream:error> is: %s", reason);
-                               session.send(st.stanza("stream:error"):tag(reason, {xmlns = 'urn:ietf:params:xml:ns:xmpp-streams' }));
-                       elseif type(reason) == "table" then
-                               if reason.condition then
-                                       local stanza = st.stanza("stream:error"):tag(reason.condition, stream_xmlns_attr):up();
-                                       if reason.text then
-                                               stanza:tag("text", stream_xmlns_attr):text(reason.text):up();
-                                       end
-                                       if reason.extra then
-                                               stanza:add_child(reason.extra);
-                                       end
-                                       log("info", "Disconnecting client, <stream:error> is: %s", tostring(stanza));
-                                       session.send(stanza);
-                               elseif reason.name then -- a stanza
-                                       log("info", "Disconnecting client, <stream:error> is: %s", tostring(reason));
-                                       session.send(reason);
-                               end
-                       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");
-       end
-end
-
-
--- End of session methods --
-
-function xmppclient.onconnect(conn)
-       local session = sm_new_session(conn);
-       sessions[conn] = session;
-       
-       session.log("info", "Client connected");
-       
-       -- Client is using legacy SSL (otherwise mod_tls sets this flag)
-       if conn:ssl() then
-               session.secure = true;
-       end
-       
-       if opt_keepalives ~= nil then
-               conn:setoption("keepalive", opt_keepalives);
-       end
-       
-       session.close = session_close;
-       
-       local stream = new_xmpp_stream(session, stream_callbacks);
-       session.stream = stream;
-       
-       session.notopen = true;
-       
-       function session.reset_stream()
-               session.notopen = true;
-               session.stream:reset();
-       end
-       
-       local filter = session.filter;
-       function session.data(data)
-               data = filter("bytes/in", data);
-               if data then
-                       local ok, err = stream:feed(data);
-                       if ok then return; end
-                       log("debug", "Received invalid XML (%s) %d bytes: %s", tostring(err), #data, data:sub(1, 300):gsub("[\r\n]+", " "):gsub("[%z\1-\31]", "_"));
-                       session:close("not-well-formed");
-               end
-       end
-       
-       local handlestanza = stream_callbacks.handlestanza;
-       function session.dispatch_stanza(session, stanza)
-               return handlestanza(session, stanza);
-       end
-end
-
-function xmppclient.onincoming(conn, data)
-       local session = sessions[conn];
-       if session then
-               session.data(data);
-       end
-end
-       
-function xmppclient.ondisconnect(conn, err)
-       local session = sessions[conn];
-       if session then
-               (session.log or log)("info", "Client disconnected: %s", err);
-               sm_destroy_session(session, err);
-               sessions[conn]  = nil;
-               session = nil;
-       end
-end
-
-function xmppclient.associate_session(conn, session)
-       sessions[conn] = session;
-end
-
-connlisteners_register("xmppclient", xmppclient);
diff --git a/net/xmppcomponent_listener.lua b/net/xmppcomponent_listener.lua
deleted file mode 100644 (file)
index dd7b2b9..0000000
+++ /dev/null
@@ -1,218 +0,0 @@
--- Prosody IM
--- Copyright (C) 2008-2010 Matthew Wild
--- Copyright (C) 2008-2010 Waqas Hussain
--- 
--- This project is MIT/X11 licensed. Please see the
--- COPYING file in the source package for more information.
---
-
-
-local t_concat = table.concat;
-local tostring = tostring;
-local type = type;
-local pairs = pairs;
-
-local lxp = require "lxp";
-local logger = require "util.logger";
-local config = require "core.configmanager";
-local connlisteners = require "net.connlisteners";
-local uuid_gen = require "util.uuid".generate;
-local jid_split = require "util.jid".split;
-local sha1 = require "util.hashes".sha1;
-local st = require "util.stanza";
-local new_xmpp_stream = require "util.xmppstream".new;
-
-local sessions = {};
-
-local log = logger.init("componentlistener");
-
-local component_listener = { default_port = 5347; default_mode = "*a"; default_interface = config.get("*", "core", "component_interface") or "127.0.0.1" };
-
-local xmlns_component = 'jabber:component:accept';
-
---- Callbacks/data for xmppstream to handle streams for us ---
-
-local stream_callbacks = { default_ns = xmlns_component };
-
-local xmlns_xmpp_streams = "urn:ietf:params:xml:ns:xmpp-streams";
-
-function stream_callbacks.error(session, error, data, data2)
-       if session.destroyed then return; end
-       log("warn", "Error processing component stream: "..tostring(error));
-       if error == "no-stream" then
-               session:close("invalid-namespace");
-       elseif error == "parse-error" then
-               session.log("warn", "External component %s XML parse error: %s", tostring(session.host), tostring(data));
-               session:close("not-well-formed");
-       elseif error == "stream-error" then
-               local condition, text = "undefined-condition";
-               for child in data:children() do
-                       if child.attr.xmlns == xmlns_xmpp_streams then
-                               if child.name ~= "text" then
-                                       condition = child.name;
-                               else
-                                       text = child:get_text();
-                               end
-                               if condition ~= "undefined-condition" and text then
-                                       break;
-                               end
-                       end
-               end
-               text = condition .. (text and (" ("..text..")") or "");
-               session.log("info", "Session closed by remote with error: %s", text);
-               session:close(nil, text);
-       end
-end
-
-function stream_callbacks.streamopened(session, attr)
-       if config.get(attr.to, "core", "component_module") ~= "component" then
-               -- Trying to act as a component domain which
-               -- hasn't been configured
-               session:close{ condition = "host-unknown", text = tostring(attr.to).." does not match any configured external components" };
-               return;
-       end
-       
-       -- Note that we don't create the internal component
-       -- until after the external component auths successfully
-
-       session.host = attr.to;
-       session.streamid = uuid_gen();
-       session.notopen = nil;
-       
-       session.send(st.stanza("stream:stream", { xmlns=xmlns_component,
-                       ["xmlns:stream"]='http://etherx.jabber.org/streams', id=session.streamid, from=session.host }):top_tag());
-
-end
-
-function stream_callbacks.streamclosed(session)
-       session.log("debug", "Received </stream:stream>");
-       session:close();
-end
-
-local core_process_stanza = core_process_stanza;
-
-function stream_callbacks.handlestanza(session, stanza)
-       -- Namespaces are icky.
-       if not stanza.attr.xmlns and stanza.name == "handshake" then
-               stanza.attr.xmlns = xmlns_component;
-       end
-       if not stanza.attr.xmlns or stanza.attr.xmlns == "jabber:client" then
-               local from = stanza.attr.from;
-               if from then
-                       if session.component_validate_from then
-                               local _, domain = jid_split(stanza.attr.from);
-                               if domain ~= session.host then
-                                       -- Return error
-                                       session.log("warn", "Component sent stanza with missing or invalid 'from' address");
-                                       session:close{
-                                               condition = "invalid-from";
-                                               text = "Component tried to send from address <"..tostring(from)
-                                                          .."> which is not in domain <"..tostring(session.host)..">";
-                                       };
-                                       return;
-                               end
-                       end
-               else
-                       stanza.attr.from = session.host;
-               end
-               if not stanza.attr.to then
-                       session.log("warn", "Rejecting stanza with no 'to' address");
-                       session.send(st.error_reply(stanza, "modify", "bad-request", "Components MUST specify a 'to' address on stanzas"));
-                       return;
-               end
-       end
-       return core_process_stanza(session, stanza);
-end
-
---- Closing a component connection
-local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'};
-local default_stream_attr = { ["xmlns:stream"] = "http://etherx.jabber.org/streams", xmlns = stream_callbacks.default_ns, version = "1.0", id = "" };
-local function session_close(session, reason)
-       if session.destroyed then return; end
-       local log = session.log or log;
-       if session.conn then
-               if session.notopen then
-                       session.send("<?xml version='1.0'?>");
-                       session.send(st.stanza("stream:stream", default_stream_attr):top_tag());
-               end
-               if reason then
-                       if type(reason) == "string" then -- assume stream error
-                               log("info", "Disconnecting component, <stream:error> is: %s", reason);
-                               session.send(st.stanza("stream:error"):tag(reason, {xmlns = 'urn:ietf:params:xml:ns:xmpp-streams' }));
-                       elseif type(reason) == "table" then
-                               if reason.condition then
-                                       local stanza = st.stanza("stream:error"):tag(reason.condition, stream_xmlns_attr):up();
-                                       if reason.text then
-                                               stanza:tag("text", stream_xmlns_attr):text(reason.text):up();
-                                       end
-                                       if reason.extra then
-                                               stanza:add_child(reason.extra);
-                                       end
-                                       log("info", "Disconnecting component, <stream:error> is: %s", tostring(stanza));
-                                       session.send(stanza);
-                               elseif reason.name then -- a stanza
-                                       log("info", "Disconnecting component, <stream:error> is: %s", tostring(reason));
-                                       session.send(reason);
-                               end
-                       end
-               end
-               session.send("</stream:stream>");
-               session.conn:close();
-               component_listener.ondisconnect(session.conn, "stream error");
-       end
-end
-
---- Component connlistener
-function component_listener.onconnect(conn)
-       local _send = conn.write;
-       local session = { type = "component", conn = conn, send = function (data) return _send(conn, tostring(data)); end };
-
-       -- Logging functions --
-       local conn_name = "jcp"..tostring(conn):match("[a-f0-9]+$");
-       session.log = logger.init(conn_name);
-       session.close = session_close;
-       
-       session.log("info", "Incoming Jabber component connection");
-       
-       local stream = new_xmpp_stream(session, stream_callbacks);
-       session.stream = stream;
-       
-       session.notopen = true;
-       
-       function session.reset_stream()
-               session.notopen = true;
-               session.stream:reset();
-       end
-
-       function session.data(conn, data)
-               local ok, err = stream:feed(data);
-               if ok then return; end
-               log("debug", "Received invalid XML (%s) %d bytes: %s", tostring(err), #data, data:sub(1, 300):gsub("[\r\n]+", " "):gsub("[%z\1-\31]", "_"));
-               session:close("not-well-formed");
-       end
-       
-       session.dispatch_stanza = stream_callbacks.handlestanza;
-
-       sessions[conn] = session;
-end
-function component_listener.onincoming(conn, data)
-       local session = sessions[conn];
-       session.data(conn, data);
-end
-function component_listener.ondisconnect(conn, err)
-       local session = sessions[conn];
-       if session then
-               (session.log or log)("info", "component disconnected: %s (%s)", tostring(session.host), tostring(err));
-               if session.on_destroy then session:on_destroy(err); end
-               sessions[conn] = nil;
-               for k in pairs(session) do
-                       if k ~= "log" and k ~= "close" then
-                               session[k] = nil;
-                       end
-               end
-               session.destroyed = true;
-               session = nil;
-       end
-end
-
-connlisteners.register('xmppcomponent', component_listener);
diff --git a/net/xmppserver_listener.lua b/net/xmppserver_listener.lua
deleted file mode 100644 (file)
index 048c6c7..0000000
+++ /dev/null
@@ -1,209 +0,0 @@
--- Prosody IM
--- Copyright (C) 2008-2010 Matthew Wild
--- Copyright (C) 2008-2010 Waqas Hussain
--- 
--- This project is MIT/X11 licensed. Please see the
--- COPYING file in the source package for more information.
---
-
-
-local tostring = tostring;
-local type = type;
-local xpcall = xpcall;
-local s_format = string.format;
-local traceback = debug.traceback;
-
-local logger = require "logger";
-local log = logger.init("xmppserver_listener");
-local st = require "util.stanza";
-local connlisteners_register = require "net.connlisteners".register;
-local new_xmpp_stream = require "util.xmppstream".new;
-local s2s_new_incoming = require "core.s2smanager".new_incoming;
-local s2s_streamopened = require "core.s2smanager".streamopened;
-local s2s_streamclosed = require "core.s2smanager".streamclosed;
-local s2s_destroy_session = require "core.s2smanager".destroy_session;
-local s2s_attempt_connect = require "core.s2smanager".attempt_connection;
-local stream_callbacks = { default_ns = "jabber:server",
-               streamopened = s2s_streamopened, streamclosed = s2s_streamclosed, handlestanza =  core_process_stanza };
-
-local xmlns_xmpp_streams = "urn:ietf:params:xml:ns:xmpp-streams";
-
-function stream_callbacks.error(session, error, data)
-       if error == "no-stream" then
-               session:close("invalid-namespace");
-       elseif error == "parse-error" then
-               session.log("debug", "Server-to-server XML parse error: %s", tostring(error));
-               session:close("not-well-formed");
-       elseif error == "stream-error" then
-               local condition, text = "undefined-condition";
-               for child in data:children() do
-                       if child.attr.xmlns == xmlns_xmpp_streams then
-                               if child.name ~= "text" then
-                                       condition = child.name;
-                               else
-                                       text = child:get_text();
-                               end
-                               if condition ~= "undefined-condition" and text then
-                                       break;
-                               end
-                       end
-               end
-               text = condition .. (text and (" ("..text..")") or "");
-               session.log("info", "Session closed by remote with error: %s", text);
-               session:close(nil, text);
-       end
-end
-
-local function handleerr(err) log("error", "Traceback[s2s]: %s: %s", tostring(err), traceback()); end
-function stream_callbacks.handlestanza(session, stanza)
-       if stanza.attr.xmlns == "jabber:client" then --COMPAT: Prosody pre-0.6.2 may send jabber:client
-               stanza.attr.xmlns = nil;
-       end
-       stanza = session.filter("stanzas/in", stanza);
-       if stanza then
-               return xpcall(function () return core_process_stanza(session, stanza) end, handleerr);
-       end
-end
-
-local sessions = {};
-local xmppserver = { default_port = 5269, default_mode = "*a", default_interface = "*" };
-
--- These are session methods --
-
-local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'};
-local default_stream_attr = { ["xmlns:stream"] = "http://etherx.jabber.org/streams", xmlns = stream_callbacks.default_ns, version = "1.0", id = "" };
-local function session_close(session, reason, remote_reason)
-       local log = session.log or log;
-       if session.conn then
-               if session.notopen then
-                       session.sends2s("<?xml version='1.0'?>");
-                       session.sends2s(st.stanza("stream:stream", default_stream_attr):top_tag());
-               end
-               if reason then
-                       if type(reason) == "string" then -- assume stream error
-                               log("info", "Disconnecting %s[%s], <stream:error> is: %s", session.host or "(unknown host)", session.type, reason);
-                               session.sends2s(st.stanza("stream:error"):tag(reason, {xmlns = 'urn:ietf:params:xml:ns:xmpp-streams' }));
-                       elseif type(reason) == "table" then
-                               if reason.condition then
-                                       local stanza = st.stanza("stream:error"):tag(reason.condition, stream_xmlns_attr):up();
-                                       if reason.text then
-                                               stanza:tag("text", stream_xmlns_attr):text(reason.text):up();
-                                       end
-                                       if reason.extra then
-                                               stanza:add_child(reason.extra);
-                                       end
-                                       log("info", "Disconnecting %s[%s], <stream:error> is: %s", session.host or "(unknown host)", session.type, tostring(stanza));
-                                       session.sends2s(stanza);
-                               elseif reason.name then -- a stanza
-                                       log("info", "Disconnecting %s->%s[%s], <stream:error> is: %s", session.from_host or "(unknown host)", session.to_host or "(unknown host)", session.type, tostring(reason));
-                                       session.sends2s(reason);
-                               end
-                       end
-               end
-               session.sends2s("</stream:stream>");
-               if session.notopen or not session.conn:close() then
-                       session.conn:close(true); -- Force FIXME: timer?
-               end
-               session.conn:close();
-               xmppserver.ondisconnect(session.conn, remote_reason or (reason and (reason.text or reason.condition)) or reason or "stream closed");
-       end
-end
-
-
--- End of session methods --
-
-local function initialize_session(session)
-       local stream = new_xmpp_stream(session, stream_callbacks);
-       session.stream = stream;
-       
-       session.notopen = true;
-               
-       function session.reset_stream()
-               session.notopen = true;
-               session.stream:reset();
-       end
-       
-       local filter = session.filter;
-       function session.data(data)
-               data = filter("bytes/in", data);
-               if data then
-                       local ok, err = stream:feed(data);
-                       if ok then return; end
-                       (session.log or log)("warn", "Received invalid XML: %s", data);
-                       (session.log or log)("warn", "Problem was: %s", err);
-                       session:close("not-well-formed");
-               end
-       end
-
-       session.close = session_close;
-       local handlestanza = stream_callbacks.handlestanza;
-       function session.dispatch_stanza(session, stanza)
-               return handlestanza(session, stanza);
-       end
-end
-
-function xmppserver.onconnect(conn)
-       if not sessions[conn] then -- May be an existing outgoing session
-               local session = s2s_new_incoming(conn);
-               sessions[conn] = session;
-       
-               -- Logging functions --
-               local conn_name = "s2sin"..tostring(conn):match("[a-f0-9]+$");
-               session.log = logger.init(conn_name);
-               
-               session.log("info", "Incoming s2s connection");
-               
-               initialize_session(session);
-       end
-end
-
-function xmppserver.onincoming(conn, data)
-       local session = sessions[conn];
-       if session then
-               session.data(data);
-       end
-end
-       
-function xmppserver.onstatus(conn, status)
-       if status == "ssl-handshake-complete" then
-               local session = sessions[conn];
-               if session and session.direction == "outgoing" then
-                       local to_host, from_host = session.to_host, session.from_host;
-                       session.log("debug", "Sending stream header...");
-                       session.sends2s(s_format([[<stream:stream xmlns='jabber:server' xmlns:db='jabber:server:dialback' xmlns:stream='http://etherx.jabber.org/streams' from='%s' to='%s' version='1.0'>]], from_host, to_host));
-               end
-       end
-end
-
-function xmppserver.ondisconnect(conn, err)
-       local session = sessions[conn];
-       if session then
-               if err and err ~= "closed"  and session.type == "s2sout_unauthed" then
-                       (session.log or log)("debug", "s2s connection attempt failed: %s", err);
-                       if s2s_attempt_connect(session, err) then
-                               (session.log or log)("debug", "...so we're going to try another target");
-                               return; -- Session lives for now
-                       end
-               end
-               (session.log or log)("info", "s2s disconnected: %s->%s (%s)", tostring(session.from_host), tostring(session.to_host), tostring(err or "closed"));
-               s2s_destroy_session(session, err);
-               sessions[conn]  = nil;
-               session = nil;
-       end
-end
-
-function xmppserver.register_outgoing(conn, session)
-       session.direction = "outgoing";
-       sessions[conn] = session;
-       
-       initialize_session(session);
-end
-
-connlisteners_register("xmppserver", xmppserver);
-
-
--- We need to perform some initialisation when a connection is created
--- We also need to perform that same initialisation at other points (SASL, TLS, ...)
-
--- ...and we need to handle data
--- ...and record all sessions associated with connections
index 0cb4efe1097c06b4bfe6f32384bd9ed348367844..57059b3f3319bc961686feb7a65c82a2517a69f0 100644 (file)
@@ -25,6 +25,7 @@ function _M.new(name, node, handler, permission)
 end
 
 function _M.handle_cmd(command, origin, stanza)
+       local cmdtag, actions;
        local sessionid = stanza.tags[1].attr.sessionid or uuid.generate();
        local dataIn = {};
        dataIn.to = stanza.attr.to;
@@ -58,7 +59,7 @@ function _M.handle_cmd(command, origin, stanza)
                elseif name == "error" then
                        cmdtag:tag("note", {type="error"}):text(content.message):up();
                elseif name =="actions" then
-                       local actions = st.stanza("actions");
+                       actions = st.stanza("actions");
                        for _, action in ipairs(content) do
                                if (action == "prev") or (action == "next") or (action == "complete") then
                                        actions:tag(action):up();
@@ -67,7 +68,6 @@ function _M.handle_cmd(command, origin, stanza)
                                                '" at node "'..command.node..'" provided an invalid action "'..action..'"');
                                end
                        end
-                       cmdtag:add_child(actions);
                elseif name == "form" then
                        cmdtag:add_child((content.layout or content):form(content.values));
                elseif name == "result" then
@@ -76,6 +76,13 @@ function _M.handle_cmd(command, origin, stanza)
                        cmdtag:add_child(content);
                end
        end
+
+       if not actions then
+               actions = st.stanza("actions");
+               actions:tag("complete"):up();
+       end
+       cmdtag:add_child(actions);
+
        stanza:add_child(cmdtag);
        origin.send(stanza);
 
index d78c1aeec0e14bc23c41812a01c0ebd0ddf0778f..6f1357a9ba68a14f3c66327d4593125692a3750e 100644 (file)
@@ -10,7 +10,8 @@ local prosody = _G.prosody;
 local hosts = prosody.hosts;
 local t_concat = table.concat;
 
-require "util.iterators";
+local iterators = require "util.iterators";
+local keys, values = iterators.keys, iterators.values;
 local usermanager_user_exists = require "core.usermanager".user_exists;
 local usermanager_create_user = require "core.usermanager".create_user;
 local usermanager_get_password = require "core.usermanager".get_password;
@@ -23,6 +24,7 @@ local dataforms_new = require "util.dataforms".new;
 local array = require "util.array";
 local modulemanager = require "modulemanager";
 
+module:depends"adhoc";
 local adhoc_new = module:require "adhoc".new;
 
 function add_user_command_handler(self, data, state)
index 0dfdc827c3e6c4310867bb59771c94264953614d..202170bab9fea016b23f16a238d31b221cd01261 100644 (file)
@@ -6,27 +6,25 @@
 -- COPYING file in the source package for more information.
 --
 
-module.host = "*";
+module:set_global();
 
 local _G = _G;
 
 local prosody = _G.prosody;
 local hosts = prosody.hosts;
-local connlisteners_register = require "net.connlisteners".register;
 
-local console_listener = { default_port = 5582; default_mode = "*l"; default_interface = "127.0.0.1" };
+local console_listener = { default_port = 5582; default_mode = "*l"; interface = "127.0.0.1" };
 
-require "util.iterators";
+local iterators = require "util.iterators";
+local keys, values = iterators.keys, iterators.values;
 local jid_bare = require "util.jid".bare;
 local set, array = require "util.set", require "util.array";
 local cert_verify_identity = require "util.x509".verify_identity;
 
-local commands = {};
-local def_env = {};
+local commands = module:shared("commands")
+local def_env = module:shared("env");
 local default_env_mt = { __index = def_env };
 
-prosody.console = { commands = commands, env = def_env };
-
 local function redirect_output(_G, session)
        local env = setmetatable({ print = session.print }, { __index = function (t, k) return rawget(_G, k); end });
        env.dofile = function(name)
@@ -149,8 +147,6 @@ function console_listener.ondisconnect(conn, err)
        end
 end
 
-connlisteners_register('console', console_listener);
-
 -- Console commands --
 -- These are simple commands, not valid standalone in Lua
 
@@ -281,8 +277,12 @@ local function get_hosts_set(hosts, module)
                return set.new { hosts };
        elseif hosts == nil then
                local mm = require "modulemanager";
-               return set.new(array.collect(keys(prosody.hosts)))
+               local hosts_set = set.new(array.collect(keys(prosody.hosts)))
                        / function (host) return prosody.hosts[host].type == "local" or module and mm.is_loaded(host, module); end;
+               if module and mm.get_module("*", module) then
+                       hosts_set:add("*");
+               end
+               return hosts_set;
        end
 end
 
@@ -292,16 +292,22 @@ function def_env.module:load(name, hosts, config)
        hosts = get_hosts_set(hosts);
        
        -- Load the module for each host
-       local ok, err, count = true, nil, 0;
+       local ok, err, count, mod = true, nil, 0, nil;
        for host in hosts do
                if (not mm.is_loaded(host, name)) then
-                       ok, err = mm.load(host, name, config);
-                       if not ok then
+                       mod, err = mm.load(host, name, config);
+                       if not mod then
                                ok = false;
+                               if err == "global-module-already-loaded" then
+                                       if count > 0 then
+                                               ok, err, count = true, nil, 1;
+                                       end
+                                       break;
+                               end
                                self.session.print(err or "Unknown error loading module");
                        else
                                count = count + 1;
-                               self.session.print("Loaded for "..host);
+                               self.session.print("Loaded for "..mod.module.host);
                        end
                end
        end
@@ -334,11 +340,15 @@ end
 function def_env.module:reload(name, hosts)
        local mm = require "modulemanager";
 
-       hosts = get_hosts_set(hosts, name);
-       
+       hosts = array.collect(get_hosts_set(hosts, name)):sort(function (a, b)
+               if a == "*" then return true
+               elseif b == "*" then return false
+               else return a < b; end
+       end);
+
        -- Reload the module for each host
        local ok, err, count = true, nil, 0;
-       for host in hosts do
+       for _, host in ipairs(hosts) do
                if mm.is_loaded(host, name) then
                        ok, err = mm.reload(host, name);
                        if not ok then
@@ -359,6 +369,7 @@ end
 function def_env.module:list(hosts)
        if hosts == nil then
                hosts = array.collect(keys(prosody.hosts));
+               table.insert(hosts, 1, "*");
        end
        if type(hosts) == "string" then
                hosts = { hosts };
@@ -369,8 +380,8 @@ function def_env.module:list(hosts)
        
        local print = self.session.print;
        for _, host in ipairs(hosts) do
-               print(host..":");
-               local modules = array.collect(keys(prosody.hosts[host] and prosody.hosts[host].modules or {})):sort();
+               print((host == "*" and "Global" or host)..":");
+               local modules = array.collect(keys(modulemanager.get_modules(host) or {})):sort();
                if #modules == 0 then
                        if prosody.hosts[host] then
                                print("    No modules loaded");
@@ -766,6 +777,51 @@ function def_env.host:list()
        return true, i.." hosts";
 end
 
+def_env.port = {};
+
+function def_env.port:list()
+       local print = self.session.print;
+       local services = portmanager.get_active_services().data;
+       local ordered_services, n_ports = {}, 0;
+       for service, interfaces in pairs(services) do
+               table.insert(ordered_services, service);
+       end
+       table.sort(ordered_services);
+       for _, service in ipairs(ordered_services) do
+               local ports_list = {};
+               for interface, ports in pairs(services[service]) do
+                       for port in pairs(ports) do
+                               table.insert(ports_list, "["..interface.."]:"..port);
+                       end
+               end
+               n_ports = n_ports + #ports_list;
+               print(service..": "..table.concat(ports_list, ", "));
+       end
+       return true, #ordered_services.." services listening on "..n_ports.." ports";
+end
+
+function def_env.port:close(close_port, close_interface)
+       close_port = assert(tonumber(close_port), "Invalid port number");
+       local n_closed = 0;
+       local services = portmanager.get_active_services().data;
+       for service, interfaces in pairs(services) do
+               for interface, ports in pairs(interfaces) do
+                       if not close_interface or close_interface == interface then
+                               if ports[close_port] then
+                                       self.session.print("Closing ["..interface.."]:"..close_port.."...");
+                                       local ok, err = portmanager.close(interface, close_port)
+                                       if not ok then
+                                               self.session.print("Failed to close "..interface.." "..port..": "..err);
+                                       else
+                                               n_closed = n_closed + 1;
+                                       end
+                               end
+                       end
+               end
+       end
+       return true, "Closed "..n_closed.." ports";
+end
+
 -------------
 
 function printbanner(session)
@@ -796,4 +852,9 @@ if option and option ~= "short" and option ~= "full" and option ~= "graphic" the
 end
 end
 
-prosody.net_activate_ports("console", "console", {5582}, "tcp");
+module:add_item("net-provider", {
+       name = "console";
+       listener = console_listener;
+       default_port = 5582;
+       private = true;
+});
index ee810426694a8980c10b0d0d72c1e823071401ac..399044aded8bac6b067df0c65679add0ecd9d74a 100644 (file)
@@ -54,7 +54,7 @@ local iteration_count = 4096;
 
 function new_hashpass_provider(host)
        local provider = { name = "internal_hashed" };
-       log("debug", "initializing hashpass authentication provider for host '%s'", host);
+       log("debug", "initializing internal_hashed authentication provider for host '%s'", host);
 
        function provider.test_password(username, password)
                local credentials = datamanager.load(username, host, "accounts") or {};
index 784553ea6d4ca499725b1da8d4677dd039fcb9a1..93b503510e96281fd4d33e64ff4eb378da53db68 100644 (file)
@@ -23,7 +23,7 @@ local prosody = _G.prosody;
 
 function new_default_provider(host)
        local provider = { name = "internal_plain" };
-       log("debug", "initializing default authentication provider for host '%s'", host);
+       log("debug", "initializing internal_plain authentication provider for host '%s'", host);
 
        function provider.test_password(username, password)
                log("debug", "test password '%s' for user %s at host %s", password, username, module.host);
index 8e87e140042dc4dea64e5ca83386e24eba63778b..c5576004ecf497890ec9a73f6a2d9c354bbc2820 100644 (file)
@@ -6,7 +6,7 @@
 -- COPYING file in the source package for more information.
 --
 
-module.host = "*" -- Global module
+module:set_global(); -- Global module
 
 local hosts = _G.hosts;
 local new_xmpp_stream = require "util.xmppstream".new;
diff --git a/plugins/mod_c2s.lua b/plugins/mod_c2s.lua
new file mode 100644 (file)
index 0000000..743fe3d
--- /dev/null
@@ -0,0 +1,242 @@
+-- Prosody IM
+-- Copyright (C) 2008-2010 Matthew Wild
+-- Copyright (C) 2008-2010 Waqas Hussain
+-- 
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+module:set_global();
+
+local add_task = require "util.timer".add_task;
+local new_xmpp_stream = require "util.xmppstream".new;
+local nameprep = require "util.encodings".stringprep.nameprep;
+local portmanager = require "core.portmanager";
+local sessionmanager = require "core.sessionmanager";
+local st = require "util.stanza";
+local sm_new_session, sm_destroy_session = sessionmanager.new_session, sessionmanager.destroy_session;
+local uuid_generate = require "util.uuid".generate;
+
+local xpcall, tostring, type = xpcall, tostring, type;
+local format = string.format;
+local traceback = debug.traceback;
+
+local xmlns_xmpp_streams = "urn:ietf:params:xml:ns:xmpp-streams";
+
+local log = module._log;
+
+local c2s_timeout = module:get_option_number("c2s_timeout");
+local opt_keepalives = module:get_option_boolean("tcp_keepalives", false);
+
+local sessions = module:shared("sessions");
+
+local stream_callbacks = { default_ns = "jabber:client", handlestanza = core_process_stanza };
+local listener = {};
+
+--- Stream events handlers
+local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'};
+local default_stream_attr = { ["xmlns:stream"] = "http://etherx.jabber.org/streams", xmlns = stream_callbacks.default_ns, version = "1.0", id = "" };
+
+function stream_callbacks.streamopened(session, attr)
+       local send = session.send;
+       session.host = nameprep(attr.to);
+       if not session.host then
+               session:close{ condition = "improper-addressing",
+                       text = "A valid 'to' attribute is required on stream headers" };
+               return;
+       end
+       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);
+
+       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");
+       hosts[session.host].events.fire_event("stream-features", { origin = session, features = features });
+       module:fire_event("stream-features", session, features);
+
+       send(features);
+end
+
+function stream_callbacks.streamclosed(session)
+       session.log("debug", "Received </stream:stream>");
+       session:close();
+end
+
+function stream_callbacks.error(session, error, data)
+       if error == "no-stream" then
+               session.log("debug", "Invalid opening stream header");
+               session:close("invalid-namespace");
+       elseif error == "parse-error" then
+               (session.log or log)("debug", "Client XML parse error: %s", tostring(data));
+               session:close("not-well-formed");
+       elseif error == "stream-error" then
+               local condition, text = "undefined-condition";
+               for child in data:children() do
+                       if child.attr.xmlns == xmlns_xmpp_streams then
+                               if child.name ~= "text" then
+                                       condition = child.name;
+                               else
+                                       text = child:get_text();
+                               end
+                               if condition ~= "undefined-condition" and text then
+                                       break;
+                               end
+                       end
+               end
+               text = condition .. (text and (" ("..text..")") or "");
+               session.log("info", "Session closed by remote with error: %s", text);
+               session:close(nil, text);
+       end
+end
+
+local function handleerr(err) log("error", "Traceback[c2s]: %s: %s", tostring(err), traceback()); end
+function stream_callbacks.handlestanza(session, stanza)
+       stanza = session.filter("stanzas/in", stanza);
+       if stanza then
+               return xpcall(function () return core_process_stanza(session, stanza) end, handleerr);
+       end
+end
+
+--- Session methods
+local function session_close(session, reason)
+       local log = session.log or log;
+       if session.conn then
+               if session.notopen then
+                       session.send("<?xml version='1.0'?>");
+                       session.send(st.stanza("stream:stream", default_stream_attr):top_tag());
+               end
+               if reason then
+                       if type(reason) == "string" then -- assume stream error
+                               log("info", "Disconnecting client, <stream:error> is: %s", reason);
+                               session.send(st.stanza("stream:error"):tag(reason, {xmlns = 'urn:ietf:params:xml:ns:xmpp-streams' }));
+                       elseif type(reason) == "table" then
+                               if reason.condition then
+                                       local stanza = st.stanza("stream:error"):tag(reason.condition, stream_xmlns_attr):up();
+                                       if reason.text then
+                                               stanza:tag("text", stream_xmlns_attr):text(reason.text):up();
+                                       end
+                                       if reason.extra then
+                                               stanza:add_child(reason.extra);
+                                       end
+                                       log("info", "Disconnecting client, <stream:error> is: %s", tostring(stanza));
+                                       session.send(stanza);
+                               elseif reason.name then -- a stanza
+                                       log("info", "Disconnecting client, <stream:error> is: %s", tostring(reason));
+                                       session.send(reason);
+                               end
+                       end
+               end
+               session.send("</stream:stream>");
+               session.conn:close();
+               listener.ondisconnect(session.conn, (reason and (reason.text or reason.condition)) or reason or "session closed");
+       end
+end
+
+--- Port listener
+function listener.onconnect(conn)
+       local session = sm_new_session(conn);
+       sessions[conn] = session;
+       
+       session.log("info", "Client connected");
+       
+       -- Client is using legacy SSL (otherwise mod_tls sets this flag)
+       if conn:ssl() then
+               session.secure = true;
+       end
+       
+       if opt_keepalives then
+               conn:setoption("keepalive", opt_keepalives);
+       end
+       
+       session.close = session_close;
+       
+       local stream = new_xmpp_stream(session, stream_callbacks);
+       session.stream = stream;
+       session.notopen = true;
+       
+       function session.reset_stream()
+               session.notopen = true;
+               session.stream:reset();
+       end
+       
+       local filter = session.filter;
+       function session.data(data)
+               data = filter("bytes/in", data);
+               if data then
+                       local ok, err = stream:feed(data);
+                       if ok then return; end
+                       log("debug", "Received invalid XML (%s) %d bytes: %s", tostring(err), #data, data:sub(1, 300):gsub("[\r\n]+", " "):gsub("[%z\1-\31]", "_"));
+                       session:close("not-well-formed");
+               end
+       end
+
+       
+       if c2s_timeout then
+               add_task(c2s_timeout, function ()
+                       if session.type == "c2s_unauthed" then
+                               session:close("connection-timeout");
+                       end
+               end);
+       end
+
+       session.dispatch_stanza = stream_callbacks.handlestanza;
+end
+
+function listener.onincoming(conn, data)
+       local session = sessions[conn];
+       if session then
+               session.data(data);
+       end
+end
+
+function listener.ondisconnect(conn, err)
+       local session = sessions[conn];
+       if session then
+               (session.log or log)("info", "Client disconnected: %s", err);
+               sm_destroy_session(session, err);
+               sessions[conn]  = nil;
+               session = nil;
+       end
+end
+
+function listener.associate_session(conn, session)
+       sessions[conn] = session;
+end
+
+module:add_item("net-provider", {
+       name = "c2s";
+       listener = listener;
+       default_port = 5222;
+       encryption = "starttls";
+       multiplex = {
+               pattern = "^<.*:stream.*%sxmlns%s*=%s*(['\"])jabber:client%1.*>";
+       };
+});
+
+module:add_item("net-provider", {
+       name = "legacy_ssl";
+       listener = listener;
+       encryption = "ssl";
+       multiplex = {
+               pattern = "^<.*:stream.*%sxmlns%s*=%s*(['\"])jabber:client%1.*>";
+       };
+});
+
+
index f7d09930777190905a98390d3b9c188f59f83931..5b1eefc767537797769866a9e8cb6657a3745299 100644 (file)
 -- COPYING file in the source package for more information.
 --
 
-if module:get_host_type() ~= "component" then
-       error("Don't load mod_component manually, it should be for a component, please see http://prosody.im/doc/components", 0);
-end
+module:set_global();
 
 local t_concat = table.concat;
 
+local logger = require "util.logger";
 local sha1 = require "util.hashes".sha1;
 local st = require "util.stanza";
 
+local jid_split = require "util.jid".split;
+local new_xmpp_stream = require "util.xmppstream".new;
+local uuid_gen = require "util.uuid".generate;
+
+
 local log = module._log;
 
-local main_session, send;
+local sessions = module:shared("sessions");
 
-local function on_destroy(session, err)
-       if main_session == session then
-               connected = false;
-               main_session = nil;
+function module.add_host(module)
+       if module:get_host_type() ~= "component" then
+               error("Don't load mod_component manually, it should be for a component, please see http://prosody.im/doc/components", 0);
+       end
+       
+       local env = module.environment;
+       env.connected = false;
+
+       local send;
+
+       local function on_destroy(session, err)
+               env.connected = false;
                send = nil;
                session.on_destroy = nil;
        end
+       
+       -- Handle authentication attempts by component
+       local function handle_component_auth(event)
+               local session, stanza = event.origin, event.stanza;
+               
+               if session.type ~= "component" then return; end
+       
+               if (not session.host) or #stanza.tags > 0 then
+                       (session.log or log)("warn", "Invalid component handshake for host: %s", session.host);
+                       session:close("not-authorized");
+                       return true;
+               end
+               
+               local secret = module:get_option("component_secret");
+               if not secret then
+                       (session.log or log)("warn", "Component attempted to identify as %s, but component_secret is not set", session.host);
+                       session:close("not-authorized");
+                       return true;
+               end
+               
+               local supplied_token = t_concat(stanza);
+               local calculated_token = sha1(session.streamid..secret, true);
+               if supplied_token:lower() ~= calculated_token:lower() then
+                       module:log("info", "Component authentication failed for %s", session.host);
+                       session:close{ condition = "not-authorized", text = "Given token does not match calculated token" };
+                       return true;
+               end
+               
+               if env.connected then
+                       module:log("error", "Second component attempted to connect, denying connection");
+                       session:close{ condition = "conflict", text = "Component already connected" };
+               end
+               
+               env.connected = true;
+               send = session.send;
+               session.on_destroy = on_destroy;
+               session.component_validate_from = module:get_option_boolean("validate_from_addresses", true);
+               module:log("info", "External component successfully authenticated");
+               session.send(st.stanza("handshake"));
+       
+               return true;
+       end
+       module:hook("stanza/jabber:component:accept:handshake", handle_component_auth);
+
+       -- Handle stanzas addressed to this component
+       local function handle_stanza(event)
+               local stanza = event.stanza;
+               if send then
+                       stanza.attr.xmlns = nil;
+                       send(stanza);
+               else
+                       module:log("warn", "Component not connected, bouncing error for: %s", stanza:top_tag());
+                       if stanza.attr.type ~= "error" and stanza.attr.type ~= "result" then
+                               event.origin.send(st.error_reply(stanza, "wait", "service-unavailable", "Component unavailable"));
+                       end
+               end
+               return true;
+       end
+       
+       module:hook("iq/bare", handle_stanza, -1);
+       module:hook("message/bare", handle_stanza, -1);
+       module:hook("presence/bare", handle_stanza, -1);
+       module:hook("iq/full", handle_stanza, -1);
+       module:hook("message/full", handle_stanza, -1);
+       module:hook("presence/full", handle_stanza, -1);
+       module:hook("iq/host", handle_stanza, -1);
+       module:hook("message/host", handle_stanza, -1);
+       module:hook("presence/host", handle_stanza, -1);
 end
 
-local function handle_stanza(event)
-       local stanza = event.stanza;
-       if send then
-               stanza.attr.xmlns = nil;
-               send(stanza);
-       else
-               log("warn", "Component not connected, bouncing error for: %s", stanza:top_tag());
-               if stanza.attr.type ~= "error" and stanza.attr.type ~= "result" then
-                       event.origin.send(st.error_reply(stanza, "wait", "service-unavailable", "Component unavailable"));
+--- Network and stream part ---
+
+local xmlns_component = 'jabber:component:accept';
+
+local listener = {};
+
+--- Callbacks/data for xmppstream to handle streams for us ---
+
+local stream_callbacks = { default_ns = xmlns_component };
+
+local xmlns_xmpp_streams = "urn:ietf:params:xml:ns:xmpp-streams";
+
+function stream_callbacks.error(session, error, data, data2)
+       if session.destroyed then return; end
+       module:log("warn", "Error processing component stream: "..tostring(error));
+       if error == "no-stream" then
+               session:close("invalid-namespace");
+       elseif error == "parse-error" then
+               session.log("warn", "External component %s XML parse error: %s", tostring(session.host), tostring(data));
+               session:close("not-well-formed");
+       elseif error == "stream-error" then
+               local condition, text = "undefined-condition";
+               for child in data:children() do
+                       if child.attr.xmlns == xmlns_xmpp_streams then
+                               if child.name ~= "text" then
+                                       condition = child.name;
+                               else
+                                       text = child:get_text();
+                               end
+                               if condition ~= "undefined-condition" and text then
+                                       break;
+                               end
+                       end
                end
+               text = condition .. (text and (" ("..text..")") or "");
+               session.log("info", "Session closed by remote with error: %s", text);
+               session:close(nil, text);
        end
-       return true;
 end
 
-module:hook("iq/bare", handle_stanza, -1);
-module:hook("message/bare", handle_stanza, -1);
-module:hook("presence/bare", handle_stanza, -1);
-module:hook("iq/full", handle_stanza, -1);
-module:hook("message/full", handle_stanza, -1);
-module:hook("presence/full", handle_stanza, -1);
-module:hook("iq/host", handle_stanza, -1);
-module:hook("message/host", handle_stanza, -1);
-module:hook("presence/host", handle_stanza, -1);
-
---- Handle authentication attempts by components
-function handle_component_auth(event)
-       local session, stanza = event.origin, event.stanza;
-       
-       if session.type ~= "component" then return; end
-       if main_session == session then return; end
+function stream_callbacks.streamopened(session, attr)
+       if not hosts[attr.to] or not hosts[attr.to].modules.component then
+               session:close{ condition = "host-unknown", text = tostring(attr.to).." does not match any configured external components" };
+               return;
+       end
+       session.host = attr.to;
+       session.streamid = uuid_gen();
+       session.notopen = nil;
+       -- Return stream header
+       session.send(st.stanza("stream:stream", { xmlns=xmlns_component,
+                       ["xmlns:stream"]='http://etherx.jabber.org/streams', id=session.streamid, from=session.host }):top_tag());
+end
 
-       if (not session.host) or #stanza.tags > 0 then
-               (session.log or log)("warn", "Invalid component handshake for host: %s", session.host);
-               session:close("not-authorized");
-               return true;
+function stream_callbacks.streamclosed(session)
+       session.log("debug", "Received </stream:stream>");
+       session:close();
+end
+
+local core_process_stanza = core_process_stanza;
+
+function stream_callbacks.handlestanza(session, stanza)
+       -- Namespaces are icky.
+       if not stanza.attr.xmlns and stanza.name == "handshake" then
+               stanza.attr.xmlns = xmlns_component;
        end
-       
-       local secret = module:get_option("component_secret");
-       if not secret then
-               (session.log or log)("warn", "Component attempted to identify as %s, but component_secret is not set", session.host);
-               session:close("not-authorized");
-               return true;
+       if not stanza.attr.xmlns or stanza.attr.xmlns == "jabber:client" then
+               local from = stanza.attr.from;
+               if from then
+                       if session.component_validate_from then
+                               local _, domain = jid_split(stanza.attr.from);
+                               if domain ~= session.host then
+                                       -- Return error
+                                       session.log("warn", "Component sent stanza with missing or invalid 'from' address");
+                                       session:close{
+                                               condition = "invalid-from";
+                                               text = "Component tried to send from address <"..tostring(from)
+                                                          .."> which is not in domain <"..tostring(session.host)..">";
+                                       };
+                                       return;
+                               end
+                       end
+               else
+                       stanza.attr.from = session.host; -- COMPAT: Strictly we shouldn't allow this
+               end
+               if not stanza.attr.to then
+                       session.log("warn", "Rejecting stanza with no 'to' address");
+                       session.send(st.error_reply(stanza, "modify", "bad-request", "Components MUST specify a 'to' address on stanzas"));
+                       return;
+               end
        end
-       
-       local supplied_token = t_concat(stanza);
-       local calculated_token = sha1(session.streamid..secret, true);
-       if supplied_token:lower() ~= calculated_token:lower() then
-               log("info", "Component authentication failed for %s", session.host);
-               session:close{ condition = "not-authorized", text = "Given token does not match calculated token" };
-               return true;
+       return core_process_stanza(session, stanza);
+end
+
+--- Closing a component connection
+local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'};
+local default_stream_attr = { ["xmlns:stream"] = "http://etherx.jabber.org/streams", xmlns = stream_callbacks.default_ns, version = "1.0", id = "" };
+local function session_close(session, reason)
+       if session.destroyed then return; end
+       local log = session.log or log;
+       if session.conn then
+               if session.notopen then
+                       session.send("<?xml version='1.0'?>");
+                       session.send(st.stanza("stream:stream", default_stream_attr):top_tag());
+               end
+               if reason then
+                       if type(reason) == "string" then -- assume stream error
+                               module:log("info", "Disconnecting component, <stream:error> is: %s", reason);
+                               session.send(st.stanza("stream:error"):tag(reason, {xmlns = 'urn:ietf:params:xml:ns:xmpp-streams' }));
+                       elseif type(reason) == "table" then
+                               if reason.condition then
+                                       local stanza = st.stanza("stream:error"):tag(reason.condition, stream_xmlns_attr):up();
+                                       if reason.text then
+                                               stanza:tag("text", stream_xmlns_attr):text(reason.text):up();
+                                       end
+                                       if reason.extra then
+                                               stanza:add_child(reason.extra);
+                                       end
+                                       module:log("info", "Disconnecting component, <stream:error> is: %s", tostring(stanza));
+                                       session.send(stanza);
+                               elseif reason.name then -- a stanza
+                                       module:log("info", "Disconnecting component, <stream:error> is: %s", tostring(reason));
+                                       session.send(reason);
+                               end
+                       end
+               end
+               session.send("</stream:stream>");
+               session.conn:close();
+               listener.ondisconnect(session.conn, "stream error");
        end
+end
+
+--- Component connlistener
+
+function listener.onconnect(conn)
+       local _send = conn.write;
+       local session = { type = "component", conn = conn, send = function (data) return _send(conn, tostring(data)); end };
+
+       -- Logging functions --
+       local conn_name = "jcp"..tostring(conn):match("[a-f0-9]+$");
+       session.log = logger.init(conn_name);
+       session.close = session_close;
        
-       -- If component not already created for this host, create one now
-       if not main_session then
-               connected = true;
-               send = session.send;
-               main_session = session;
-               session.on_destroy = on_destroy;
-               session.component_validate_from = module:get_option_boolean("validate_from_addresses", true);
-               log("info", "Component successfully authenticated: %s", session.host);
-               session.send(st.stanza("handshake"));
-       else -- TODO: Implement stanza distribution
-               log("error", "Multiple components bound to the same address, first one wins: %s", session.host);
-               session:close{ condition = "conflict", text = "Component already connected" };
+       session.log("info", "Incoming Jabber component connection");
+       
+       local stream = new_xmpp_stream(session, stream_callbacks);
+       session.stream = stream;
+       
+       session.notopen = true;
+       
+       function session.reset_stream()
+               session.notopen = true;
+               session.stream:reset();
+       end
+
+       function session.data(conn, data)
+               local ok, err = stream:feed(data);
+               if ok then return; end
+               module:log("debug", "Received invalid XML (%s) %d bytes: %s", tostring(err), #data, data:sub(1, 300):gsub("[\r\n]+", " "):gsub("[%z\1-\31]", "_"));
+               session:close("not-well-formed");
        end
        
-       return true;
+       session.dispatch_stanza = stream_callbacks.handlestanza;
+
+       sessions[conn] = session;
+end
+function listener.onincoming(conn, data)
+       local session = sessions[conn];
+       session.data(conn, data);
+end
+function listener.ondisconnect(conn, err)
+       local session = sessions[conn];
+       if session then
+               (session.log or log)("info", "component disconnected: %s (%s)", tostring(session.host), tostring(err));
+               if session.on_destroy then session:on_destroy(err); end
+               sessions[conn] = nil;
+               for k in pairs(session) do
+                       if k ~= "log" and k ~= "close" then
+                               session[k] = nil;
+                       end
+               end
+               session.destroyed = true;
+               session = nil;
+       end
 end
 
-module:hook("stanza/jabber:component:accept:handshake", handle_component_auth);
+module:add_item("net-provider", {
+       name = "component";
+       listener = listener;
+       default_port = 5347;
+       multiplex = {
+               pattern = "^<.*:stream.*%sxmlns%s*=%s*(['\"])jabber:component%1.*>";
+       };
+});
index e27f8657b2a4b24b7fb90321cac49ae6ed5af7c3..e578c4129e55dd688f6d6851a87fc658ad15b729 100644 (file)
@@ -6,22 +6,36 @@
 -- COPYING file in the source package for more information.
 --
 
+local format = string.format;
 
 local hosts = _G.hosts;
-local send_s2s = require "core.s2smanager".send_to_host;
 local s2s_make_authenticated = require "core.s2smanager".make_authenticated;
-local s2s_initiate_dialback = require "core.s2smanager".initiate_dialback;
-local s2s_verify_dialback = require "core.s2smanager".verify_dialback;
 
 local log = module._log;
 
 local st = require "util.stanza";
+local sha256_hash = require "util.hashes".sha256;
 
 local xmlns_stream = "http://etherx.jabber.org/streams";
 local xmlns_dialback = "jabber:server:dialback";
 
 local dialback_requests = setmetatable({}, { __mode = 'v' });
 
+function generate_dialback(id, to, from)
+       return sha256_hash(id..to..from..hosts[from].dialback_secret, true);
+end
+
+function initiate_dialback(session)
+       -- generate dialback key
+       session.dialback_key = generate_dialback(session.streamid, session.to_host, session.from_host);
+       session.sends2s(format("<db:result from='%s' to='%s'>%s</db:result>", session.from_host, session.to_host, session.dialback_key));
+       session.log("info", "sent dialback key on outgoing s2s stream");
+end
+
+function verify_dialback(id, to, from, key)
+       return key == generate_dialback(id, to, from);
+end
+
 module:hook("stanza/jabber:server:dialback:verify", function(event)
        local origin, stanza = event.origin, event.stanza;
        
@@ -32,7 +46,7 @@ module:hook("stanza/jabber:server:dialback:verify", function(event)
                -- COMPAT: Grr, ejabberd breaks this one too?? it is black and white in XEP-220 example 34
                --if attr.from ~= origin.to_host then error("invalid-from"); end
                local type;
-               if s2s_verify_dialback(attr.id, attr.from, attr.to, stanza[1]) then
+               if verify_dialback(attr.id, attr.from, attr.to, stanza[1]) then
                        type = "valid"
                else
                        type = "invalid"
@@ -72,8 +86,7 @@ module:hook("stanza/jabber:server:dialback:result", function(event)
                end
                
                origin.log("debug", "asking %s if key %s belongs to them", attr.from, stanza[1]);
-               send_s2s(attr.to, attr.from,
-                       st.stanza("db:verify", { from = attr.to, to = attr.from, id = origin.streamid }):text(stanza[1]));
+               origin.send(st.stanza("db:verify", { from = attr.to, to = attr.from, id = origin.streamid }):text(stanza[1]));
                return true;
        end
 end);
@@ -84,6 +97,7 @@ module:hook("stanza/jabber:server:dialback:verify", function(event)
        if origin.type == "s2sout_unauthed" or origin.type == "s2sout" then
                local attr = stanza.attr;
                local dialback_verifying = dialback_requests[attr.from.."/"..(attr.id or "")];
+               module:log("debug", tostring(dialback_verifying).." "..attr.from.." "..origin.to_host);
                if dialback_verifying and attr.from == origin.to_host then
                        local valid;
                        if attr.type == "valid" then
@@ -134,18 +148,25 @@ end);
 module:hook_stanza("urn:ietf:params:xml:ns:xmpp-sasl", "failure", function (origin, stanza)
        if origin.external_auth == "failed" then
                module:log("debug", "SASL EXTERNAL failed, falling back to dialback");
-               s2s_initiate_dialback(origin);
+               initiate_dialback(origin);
                return true;
        end
 end, 100);
 
 module:hook_stanza(xmlns_stream, "features", function (origin, stanza)
        if not origin.external_auth or origin.external_auth == "failed" then
-               s2s_initiate_dialback(origin);
+               module:log("debug", "Initiating dialback...");
+               initiate_dialback(origin);
                return true;
        end
 end, 100);
 
+module:hook("s2s-authenticate-legacy", function (event)
+       module:log("debug", "Initiating dialback...");
+       initiate_dialback(event.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' }):up();
diff --git a/plugins/mod_http.lua b/plugins/mod_http.lua
new file mode 100644 (file)
index 0000000..6da4db2
--- /dev/null
@@ -0,0 +1,98 @@
+-- Prosody IM
+-- Copyright (C) 2008-2012 Matthew Wild
+-- Copyright (C) 2008-2012 Waqas Hussain
+-- 
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+module:set_global();
+
+local parse_url = require "socket.url".parse;
+local server = require "net.http.server";
+
+local function normalize_path(path)
+       if path:sub(1,1) ~= "/" then path = "/"..path; end
+       if path:sub(-1,-1) == "/" then path = path:sub(1, -2); end
+       return path;
+end
+
+local function get_http_event(host, app_path, key)
+       local method, path = key:match("^(%S+)%s+(.+)$");
+       if not method then
+               if key:sub(1,1) ~= "/" then
+                       return nil;
+               end
+               method, path = "GET", key;
+       end
+       path = normalize_path(path);
+       return method:upper().." "..host..app_path..path;
+end
+
+function module.add_host(module)
+       local host = module.host;
+       local apps = {};
+       module.environment.apps = apps;
+       local function http_app_added(event)
+               local app_name = event.item.name;
+               local default_app_path = event.item.default_path or "/"..app_name;
+               local app_path = normalize_path(module:get_option_string(app_name.."_http_path", default_app_path));
+               if not app_name then            
+                       -- TODO: Link to docs
+                       module:log("error", "HTTP app has no 'name', add one or use module:provides('http', app)");
+                       return;
+               end
+               apps[app_name] = apps[app_name] or {};
+               local app_handlers = apps[app_name];
+               for key, handler in pairs(event.item.route or {}) do
+                       local event_name = get_http_event(host, app_path, key);
+                       if event_name then
+                               if event_name:sub(-2, -1) == "/*" then
+                                       local base_path = event_name:match("/(.+)/*$");
+                                       local _handler = handler;
+                                       handler = function (event)
+                                               local path = event.request.path:sub(#base_path+1);
+                                               return _handler(event, path);
+                                       end;
+                               end
+                               if not app_handlers[event_name] then
+                                       app_handlers[event_name] = handler;
+                                       server.add_handler(event_name, handler);
+                               else
+                                       module:log("warn", "App %s added handler twice for '%s', ignoring", app_name, event_name);
+                               end
+                       else
+                               module:log("error", "Invalid route in %s: %q", app_name, key);
+                       end
+               end
+       end
+       
+       local function http_app_removed(event)
+               local app_handlers = apps[event.item.name];
+               apps[event.item.name] = nil;
+               for event, handler in pairs(app_handlers) do
+                       server.remove_handler(event, handler);
+               end
+       end
+       
+       module:handle_items("http-provider", http_app_added, http_app_removed);
+end
+
+module:add_item("net-provider", {
+       name = "http";
+       listener = server.listener;
+       default_port = 5280;
+       multiplex = {
+               pattern = "^[A-Z]";
+       };
+});
+
+module:add_item("net-provider", {
+       name = "https";
+       listener = server.listener;
+       default_port = 5281;
+       encryption = "ssl";
+       multiplex = {
+               pattern = "^[A-Z]";
+       };
+});
diff --git a/plugins/mod_http_files.lua b/plugins/mod_http_files.lua
new file mode 100644 (file)
index 0000000..437633e
--- /dev/null
@@ -0,0 +1,86 @@
+-- Prosody IM
+-- Copyright (C) 2008-2010 Matthew Wild
+-- Copyright (C) 2008-2010 Waqas Hussain
+-- 
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+module:depends("http");
+local lfs = require "lfs";
+
+local open = io.open;
+local stat = lfs.attributes;
+
+local http_base = module:get_option_string("http_path", "www_files");
+
+local response_400 = "<h1>Bad Request</h1>Sorry, we didn't understand your request :(";
+local response_403 = "<h1>Forbidden</h1>You don't have permission to view the contents of this directory :(";
+local response_404 = "<h1>Page Not Found</h1>Sorry, we couldn't find what you were looking for :(";
+
+-- TODO: Should we read this from /etc/mime.types if it exists? (startup time...?)
+local mime_map = {
+       html = "text/html";
+       htm = "text/html";
+       xml = "text/xml";
+       xsl = "text/xml";
+       txt = "text/plain; charset=utf-8";
+       js = "text/javascript";
+       css = "text/css";
+};
+
+local function preprocess_path(path)
+       if path:sub(1,1) ~= "/" then
+               path = "/"..path;
+       end
+       local level = 0;
+       for component in path:gmatch("([^/]+)/") do
+               if component == ".." then
+                       level = level - 1;
+               elseif component ~= "." then
+                       level = level + 1;
+               end
+               if level < 0 then
+                       return nil;
+               end
+       end
+       return path;
+end
+
+function serve_file(event, path)
+       local response = event.response;
+       path = path and preprocess_path(path);
+       if not path then
+               response.status = 400;
+               return response:send(response_400);
+       end
+       local full_path = http_base..path;
+       if stat(full_path, "mode") == "directory" then
+               if stat(full_path.."/index.html", "mode") == "file" then
+                       return serve_file(event, path.."/index.html");
+               end
+               response.status = 403;
+               return response:send(response_403);
+       end
+       local f, err = open(full_path, "rb");
+       if not f then
+               response.status = 404;
+               return response:send(response_404.."<br/>"..tostring(err));
+       end
+       local data = f:read("*a");
+       f:close();
+       if not data then
+               response.status = 403;
+               return response:send(response_403);
+       end
+       local ext = path:match("%.([^.]*)$");
+       response.headers.content_type = mime_map[ext]; -- Content-Type should be nil when not known
+       return response:send(data);
+end
+
+module:provides("http", {
+       route = {
+               ["/*"] = serve_file;
+       };
+});
+
diff --git a/plugins/mod_httpserver.lua b/plugins/mod_httpserver.lua
deleted file mode 100644 (file)
index 654aff0..0000000
+++ /dev/null
@@ -1,97 +0,0 @@
--- Prosody IM
--- Copyright (C) 2008-2010 Matthew Wild
--- Copyright (C) 2008-2010 Waqas Hussain
--- 
--- This project is MIT/X11 licensed. Please see the
--- COPYING file in the source package for more information.
---
-
-
-local httpserver = require "net.httpserver";
-local lfs = require "lfs";
-
-local open = io.open;
-local t_concat = table.concat;
-local stat = lfs.attributes;
-
-local http_base = config.get("*", "core", "http_path") or "www_files";
-
-local response_400 = { status = "400 Bad Request", body = "<h1>Bad Request</h1>Sorry, we didn't understand your request :(" };
-local response_403 = { status = "403 Forbidden", body = "<h1>Forbidden</h1>You don't have permission to view the contents of this directory :(" };
-local response_404 = { status = "404 Not Found", body = "<h1>Page Not Found</h1>Sorry, we couldn't find what you were looking for :(" };
-
--- TODO: Should we read this from /etc/mime.types if it exists? (startup time...?)
-local mime_map = {
-       html = "text/html";
-       htm = "text/html";
-       xml = "text/xml";
-       xsl = "text/xml";
-       txt = "text/plain; charset=utf-8";
-       js = "text/javascript";
-       css = "text/css";
-};
-
-local function preprocess_path(path)
-       if path:sub(1,1) ~= "/" then
-               path = "/"..path;
-       end
-       local level = 0;
-       for component in path:gmatch("([^/]+)/") do
-               if component == ".." then
-                       level = level - 1;
-               elseif component ~= "." then
-                       level = level + 1;
-               end
-               if level < 0 then
-                       return nil;
-               end
-       end
-       return path;
-end
-
-function serve_file(path)
-       local full_path = http_base..path;
-       if stat(full_path, "mode") == "directory" then
-               if stat(full_path.."/index.html", "mode") == "file" then
-                       return serve_file(path.."/index.html");
-               end
-               return response_403;
-       end
-       local f, err = open(full_path, "rb");
-       if not f then return response_404; end
-       local data = f:read("*a");
-       f:close();
-       if not data then
-               return response_403;
-       end
-       local ext = path:match("%.([^.]*)$");
-       local mime = mime_map[ext]; -- Content-Type should be nil when not known
-       return {
-               headers = { ["Content-Type"] = mime; };
-               body = data;
-       };
-end
-
-local function handle_file_request(method, body, request)
-       local path = preprocess_path(request.url.path);
-       if not path then return response_400; end
-       path = path:gsub("^/[^/]+", ""); -- Strip /files/
-       return serve_file(path);
-end
-
-local function handle_default_request(method, body, request)
-       local path = preprocess_path(request.url.path);
-       if not path then return response_400; end
-       return serve_file(path);
-end
-
-local function setup()
-       local ports = config.get(module.host, "core", "http_ports") or { 5280 };
-       httpserver.set_default_handler(handle_default_request);
-       httpserver.new_from_config(ports, handle_file_request, { base = "files" });
-end
-if prosody.start_time then -- already started
-       setup();
-else
-       prosody.events.add_handler("server-started", setup);
-end
index d567288b8b985ed0fbb371294f5b591892c08329..39b74de98ef4af57e95bb635aa0016a1fe427b68 100644 (file)
@@ -13,17 +13,18 @@ local motd_jid = module:get_option_string("motd_jid", host);
 
 if not motd_text then return; end
 
+local jid_join = require "util.jid".join;
 local st = require "util.stanza";
 
 motd_text = motd_text:gsub("^%s*(.-)%s*$", "%1"):gsub("\n%s+", "\n"); -- Strip indentation from the config
 
-module:hook("resource-bind",
-       function (event)
-               local session = event.session;
-               local motd_stanza =
-                       st.message({ to = session.username..'@'..session.host, from = motd_jid })
-                               :tag("body"):text(motd_text);
-               core_route_stanza(hosts[host], motd_stanza);
-               module:log("debug", "MOTD send to user %s@%s", session.username, session.host);
-
-end);
+module:hook("presence/bare", function (event)
+               local session, stanza = event.origin, event.stanza;
+               if not session.presence and not stanza.attr.type then
+                       local motd_stanza =
+                               st.message({ to = session.full_jid, from = motd_jid })
+                                       :tag("body"):text(motd_text);
+                       core_route_stanza(hosts[host], motd_stanza);
+                       module:log("debug", "MOTD send to user %s", session.full_jid);
+               end
+end, 1);
diff --git a/plugins/mod_net_multiplex.lua b/plugins/mod_net_multiplex.lua
new file mode 100644 (file)
index 0000000..44e1c1e
--- /dev/null
@@ -0,0 +1,70 @@
+module:set_global();
+
+local max_buffer_len = module:get_option_number("multiplex_buffer_size", 1024);
+
+local portmanager = require "core.portmanager";
+
+local available_services = {};
+
+local function add_service(service)
+       local multiplex_pattern = service.multiplex and service.multiplex.pattern;
+       if multiplex_pattern then
+               module:log("debug", "Adding multiplex service %q with pattern %q", service.name, multiplex_pattern);
+               available_services[service] = multiplex_pattern;
+       else
+               module:log("debug", "Service %q is not multiplex-capable", service.name);
+       end
+end
+module:hook("service-added", function (event) add_service(event.service); end);
+module:hook("service-removed", function (event)        available_services[event.service] = nil; end);
+
+for service_name, services in pairs(portmanager.get_registered_services()) do
+       for i, service in ipairs(services) do
+               add_service(service);
+       end
+end
+
+local buffers = {};
+
+local listener = { default_mode = "*a" };
+
+function listener.onconnect()
+end
+
+function listener.onincoming(conn, data)
+       if not data then return; end
+       local buf = buffers[conn];
+       buffers[conn] = nil;
+       buf = buf and buf..data or data;
+       for service, multiplex_pattern in pairs(available_services) do
+               if buf:match(multiplex_pattern) then
+                       module:log("debug", "Routing incoming connection to %s", service.name);
+                       local listener = service.listener;
+                       conn:setlistener(listener);
+                       local onconnect = listener.onconnect;
+                       if onconnect then onconnect(conn) end
+                       return listener.onincoming(conn, buf);
+               end
+       end
+       if #buf > max_buffer_len then -- Give up
+               conn:close();
+       else
+               buffers[conn] = buf;
+       end
+end
+
+function listener.ondisconnect(conn, err)
+       buffers[conn] = nil; -- warn if no buffer?
+end
+
+module:add_item("net-provider", {
+       name = "multiplex";
+       config_prefix = "";
+       listener = listener;
+});
+
+module:provides("net", {
+       name = "multiplex_ssl";
+       config_prefix = "ssl";
+       listener = listener;
+});
index d229c1b8e35fd74f202b6e91e68c3c9318524ce1..b388fb9d3ab1963f43fd9256e6f0a9ade8cf3eb1 100644 (file)
@@ -22,7 +22,7 @@ local stat = lfs.attributes;
 
 local prosody = _G.prosody;
 
-module.host = "*"; -- we're a global module
+module:set_global(); -- we're a global module
 
 local umask = module:get_option("umask") or "027";
 pposix.umask(umask);
index d02f3b581d94d67492d3ddf86202c4bcaaae1c73..155cb60dc44b11d344a1b861bba0b44f385a3988 100644 (file)
@@ -6,35 +6,21 @@
 -- This project is MIT/X11 licensed. Please see the
 -- COPYING file in the source package for more information.
 --
---[[
-* to restart the proxy in the console: e.g.
-module:unload("proxy65");
-> server.removeserver(<proxy65_port>);
-module:load("proxy65", <proxy65_jid>);
-]]--
 
+module:set_global();
 
-local module = module;
-local tostring = tostring;
 local jid_compare, jid_prep = require "util.jid".compare, require "util.jid".prep;
 local st = require "util.stanza";
-local connlisteners = require "net.connlisteners";
 local sha1 = require "util.hashes".sha1;
-local server = require "net.server";
 local b64 = require "util.encodings".base64.encode;
+local server = require "net.server";
 
-local host, name = module:get_host(), "SOCKS5 Bytestreams Service";
-local sessions, transfers = {}, {};
-
-local proxy_port = module:get_option("proxy65_port") or 5000;
-local proxy_interface = module:get_option("proxy65_interface") or "*";
-local proxy_address = module:get_option("proxy65_address") or (proxy_interface ~= "*" and proxy_interface) or host;
-local proxy_acl = module:get_option("proxy65_acl");
+local sessions, transfers = module:shared("sessions", "transfers");
 local max_buffer_size = 4096;
 
-local connlistener = { default_port = proxy_port, default_interface = proxy_interface, default_mode = "*a" };
+local listener = {};
 
-function connlistener.onincoming(conn, data)
+function listener.onincoming(conn, data)
        local session = sessions[conn] or {};
 
        local transfer = transfers[session.sha];
@@ -84,7 +70,7 @@ function connlistener.onincoming(conn, data)
        end
 end
 
-function connlistener.ondisconnect(conn, err)
+function listener.ondisconnect(conn, err)
        local session = sessions[conn];
        if session then
                if transfers[session.sha] then
@@ -101,88 +87,90 @@ function connlistener.ondisconnect(conn, err)
        end
 end
 
-module:add_identity("proxy", "bytestreams", name);
-module:add_feature("http://jabber.org/protocol/bytestreams");
-
-module:hook("iq-get/host/http://jabber.org/protocol/disco#info:query", function(event)
-       local origin, stanza = event.origin, event.stanza;
-       origin.send(st.reply(stanza):query("http://jabber.org/protocol/disco#info")
-               :tag("identity", {category='proxy', type='bytestreams', name=name}):up()
-               :tag("feature", {var="http://jabber.org/protocol/bytestreams"}) );
-       return true;
-end, -1);
-
-module:hook("iq-get/host/http://jabber.org/protocol/disco#items:query", function(event)
-       local origin, stanza = event.origin, event.stanza;
-       origin.send(st.reply(stanza):query("http://jabber.org/protocol/disco#items"));
-       return true;
-end, -1);
+function module.add_host(module)
+       local host, name = module:get_host(), module:get_option_string("name", "SOCKS5 Bytestreams Service");
+       
+       local proxy_address = module:get_option("proxy65_address", host);
+       local proxy_port = module:get_option_number("proxy65_port", next(portmanager.get_active_services():search("proxy65", nil)[1]));
+       local proxy_acl = module:get_option("proxy65_acl");
 
-module:hook("iq-get/host/http://jabber.org/protocol/bytestreams:query", function(event)
-       local origin, stanza = event.origin, event.stanza;
+       module:add_identity("proxy", "bytestreams", name);
+       module:add_feature("http://jabber.org/protocol/bytestreams");
+       
+       module:hook("iq-get/host/http://jabber.org/protocol/disco#info:query", function(event)
+               local origin, stanza = event.origin, event.stanza;
+               origin.send(st.reply(stanza):query("http://jabber.org/protocol/disco#info")
+                       :tag("identity", {category='proxy', type='bytestreams', name=name}):up()
+                       :tag("feature", {var="http://jabber.org/protocol/bytestreams"}) );
+               return true;
+       end, -1);
+       
+       module:hook("iq-get/host/http://jabber.org/protocol/disco#items:query", function(event)
+               local origin, stanza = event.origin, event.stanza;
+               origin.send(st.reply(stanza):query("http://jabber.org/protocol/disco#items"));
+               return true;
+       end, -1);
        
-       -- check ACL
-       while proxy_acl and #proxy_acl > 0 do -- using 'while' instead of 'if' so we can break out of it
-               local jid = stanza.attr.from;
-               for _, acl in ipairs(proxy_acl) do
-                       if jid_compare(jid, acl) then break; end
+       module:hook("iq-get/host/http://jabber.org/protocol/bytestreams:query", function(event)
+               local origin, stanza = event.origin, event.stanza;
+               
+               -- check ACL
+               while proxy_acl and #proxy_acl > 0 do -- using 'while' instead of 'if' so we can break out of it
+                       local jid = stanza.attr.from;
+                       for _, acl in ipairs(proxy_acl) do
+                               if jid_compare(jid, acl) then break; end
+                       end
+                       module:log("warn", "Denying use of proxy for %s", tostring(stanza.attr.from));
+                       origin.send(st.error_reply(stanza, "auth", "forbidden"));
+                       return true;
                end
-               module:log("warn", "Denying use of proxy for %s", tostring(stanza.attr.from));
-               origin.send(st.error_reply(stanza, "auth", "forbidden"));
+       
+               local sid = stanza.tags[1].attr.sid;
+               origin.send(st.reply(stanza):tag("query", {xmlns="http://jabber.org/protocol/bytestreams", sid=sid})
+                       :tag("streamhost", {jid=host, host=proxy_address, port=proxy_port}));
                return true;
-       end
-
-       local sid = stanza.tags[1].attr.sid;
-       origin.send(st.reply(stanza):tag("query", {xmlns="http://jabber.org/protocol/bytestreams", sid=sid})
-               :tag("streamhost", {jid=host, host=proxy_address, port=proxy_port}));
-       return true;
-end);
-
-module.unload = function()
-       connlisteners.deregister(module.host .. ':proxy65');
-end
-
-module:hook("iq-set/host/http://jabber.org/protocol/bytestreams:query", function(event)
-       local origin, stanza = event.origin, event.stanza;
-
-       local query = stanza.tags[1];
-       local sid = query.attr.sid;
-       local from = stanza.attr.from;
-       local to = query:get_child_text("activate");
-       local prepped_to = jid_prep(to);
-
-       local info = "sid: "..tostring(sid)..", initiator: "..tostring(from)..", target: "..tostring(prepped_to or to);
-       if prepped_to and sid then
-               local sha = sha1(sid .. from .. prepped_to, true);
-               if not transfers[sha] then
-                       module:log("debug", "Activation request has unknown session id; activation failed (%s)", info);
-                       origin.send(st.error_reply(stanza, "modify", "item-not-found"));
-               elseif not transfers[sha].initiator then
-                       module:log("debug", "The sender was not connected to the proxy; activation failed (%s)", info);
-                       origin.send(st.error_reply(stanza, "cancel", "not-allowed", "The sender (you) is not connected to the proxy"));
-               --elseif not transfers[sha].target then -- can't happen, as target is set when a transfer object is created
-               --      module:log("debug", "The recipient was not connected to the proxy; activation failed (%s)", info);
-               --      origin.send(st.error_reply(stanza, "cancel", "not-allowed", "The recipient is not connected to the proxy"));
-               else -- if transfers[sha].initiator ~= nil and transfers[sha].target ~= nil then
-                       module:log("debug", "Transfer activated (%s)", info);
-                       transfers[sha].activated = true;
-                       transfers[sha].target:resume();
-                       transfers[sha].initiator:resume();
-                       origin.send(st.reply(stanza));
+       end);
+       
+       module:hook("iq-set/host/http://jabber.org/protocol/bytestreams:query", function(event)
+               local origin, stanza = event.origin, event.stanza;
+       
+               local query = stanza.tags[1];
+               local sid = query.attr.sid;
+               local from = stanza.attr.from;
+               local to = query:get_child_text("activate");
+               local prepped_to = jid_prep(to);
+       
+               local info = "sid: "..tostring(sid)..", initiator: "..tostring(from)..", target: "..tostring(prepped_to or to);
+               if prepped_to and sid then
+                       local sha = sha1(sid .. from .. prepped_to, true);
+                       if not transfers[sha] then
+                               module:log("debug", "Activation request has unknown session id; activation failed (%s)", info);
+                               origin.send(st.error_reply(stanza, "modify", "item-not-found"));
+                       elseif not transfers[sha].initiator then
+                               module:log("debug", "The sender was not connected to the proxy; activation failed (%s)", info);
+                               origin.send(st.error_reply(stanza, "cancel", "not-allowed", "The sender (you) is not connected to the proxy"));
+                       --elseif not transfers[sha].target then -- can't happen, as target is set when a transfer object is created
+                       --      module:log("debug", "The recipient was not connected to the proxy; activation failed (%s)", info);
+                       --      origin.send(st.error_reply(stanza, "cancel", "not-allowed", "The recipient is not connected to the proxy"));
+                       else -- if transfers[sha].initiator ~= nil and transfers[sha].target ~= nil then
+                               module:log("debug", "Transfer activated (%s)", info);
+                               transfers[sha].activated = true;
+                               transfers[sha].target:resume();
+                               transfers[sha].initiator:resume();
+                               origin.send(st.reply(stanza));
+                       end
+               elseif to and sid then
+                       module:log("debug", "Malformed activation jid; activation failed (%s)", info);
+                       origin.send(st.error_reply(stanza, "modify", "jid-malformed"));
+               else
+                       module:log("debug", "Bad request; activation failed (%s)", info);
+                       origin.send(st.error_reply(stanza, "modify", "bad-request"));
                end
-       elseif to and sid then
-               module:log("debug", "Malformed activation jid; activation failed (%s)", info);
-               origin.send(st.error_reply(stanza, "modify", "jid-malformed"));
-       else
-               module:log("debug", "Bad request; activation failed (%s)", info);
-               origin.send(st.error_reply(stanza, "modify", "bad-request"));
-       end
-       return true;
-end);
-
-if not connlisteners.register(module.host .. ':proxy65', connlistener) then
-       module:log("error", "mod_proxy65: Could not establish a connection listener. Check your configuration please.");
-       module:log("error", "Possibly two proxy65 components are configured to share the same port.");
+               return true;
+       end);
 end
 
-connlisteners.start(module.host .. ':proxy65');
+module:provides("net", {
+       default_port = 5000;
+       listener = listener;
+});
diff --git a/plugins/s2s/mod_s2s.lua b/plugins/s2s/mod_s2s.lua
new file mode 100644 (file)
index 0000000..b0bd5b4
--- /dev/null
@@ -0,0 +1,477 @@
+-- Prosody IM
+-- Copyright (C) 2008-2010 Matthew Wild
+-- Copyright (C) 2008-2010 Waqas Hussain
+-- 
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+module:set_global();
+
+local tostring, type = tostring, type;
+local t_insert = table.insert;
+local xpcall, traceback = xpcall, debug.traceback;
+
+local add_task = require "util.timer".add_task;
+local st = require "util.stanza";
+local initialize_filters = require "util.filters".initialize;
+local nameprep = require "util.encodings".stringprep.nameprep;
+local new_xmpp_stream = require "util.xmppstream".new;
+local s2s_new_incoming = require "core.s2smanager".new_incoming;
+local s2s_new_outgoing = require "core.s2smanager".new_outgoing;
+local s2s_destroy_session = require "core.s2smanager".destroy_session;
+local uuid_gen = require "util.uuid".generate;
+local cert_verify_identity = require "util.x509".verify_identity;
+
+local s2sout = module:require("s2sout");
+
+local connect_timeout = module:get_option_number("s2s_timeout", 60);
+
+local sessions = module:shared("sessions");
+
+--- Handle stanzas to remote domains
+
+local bouncy_stanzas = { message = true, presence = true, iq = true };
+local function bounce_sendq(session, reason)
+       local sendq = session.sendq;
+       if not sendq then return; end
+       session.log("info", "sending error replies for "..#sendq.." queued stanzas because of failed outgoing connection to "..tostring(session.to_host));
+       local dummy = {
+               type = "s2sin";
+               send = function(s)
+                       (session.log or log)("error", "Replying to to an s2s error reply, please report this! Traceback: %s", get_traceback());
+               end;
+               dummy = true;
+       };
+       for i, data in ipairs(sendq) do
+               local reply = data[2];
+               if reply and not(reply.attr.xmlns) and bouncy_stanzas[reply.name] then
+                       reply.attr.type = "error";
+                       reply:tag("error", {type = "cancel"})
+                               :tag("remote-server-not-found", {xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas"}):up();
+                       if reason then
+                               reply:tag("text", {xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas"})
+                                       :text("Server-to-server connection failed: "..reason):up();
+                       end
+                       core_process_stanza(dummy, reply);
+               end
+               sendq[i] = nil;
+       end
+       session.sendq = nil;
+end
+
+module:hook("route/remote", function (event)
+       local from_host, to_host, stanza = event.from_host, event.to_host, event.stanza;
+       if not hosts[from_host] then
+               log("warn", "Attempt to send stanza from %s - a host we don't serve", from_host);
+               return false;
+       end
+       local host = hosts[from_host].s2sout[to_host];
+       if host then
+               -- We have a connection to this host already
+               if host.type == "s2sout_unauthed" and (stanza.name ~= "db:verify" or not host.dialback_key) then
+                       (host.log or log)("debug", "trying to send over unauthed s2sout to "..to_host);
+
+                       -- Queue stanza until we are able to send it
+                       if host.sendq then t_insert(host.sendq, {tostring(stanza), stanza.attr.type ~= "error" and stanza.attr.type ~= "result" and st.reply(stanza)});
+                       else host.sendq = { {tostring(stanza), stanza.attr.type ~= "error" and stanza.attr.type ~= "result" and st.reply(stanza)} }; end
+                       host.log("debug", "stanza [%s] queued ", stanza.name);
+                       return true;
+               elseif host.type == "local" or host.type == "component" then
+                       log("error", "Trying to send a stanza to ourselves??")
+                       log("error", "Traceback: %s", get_traceback());
+                       log("error", "Stanza: %s", tostring(stanza));
+                       return false;
+               else
+                       (host.log or log)("debug", "going to send stanza to "..to_host.." from "..from_host);
+                       -- FIXME
+                       if host.from_host ~= from_host then
+                               log("error", "WARNING! This might, possibly, be a bug, but it might not...");
+                               log("error", "We are going to send from %s instead of %s", tostring(host.from_host), tostring(from_host));
+                       end
+                       host.sends2s(stanza);
+                       host.log("debug", "stanza sent over "..host.type);
+                       return true;
+               end
+       end
+end, 200);
+
+module:hook("route/remote", function (event)
+       local from_host, to_host, stanza = event.from_host, event.to_host, event.stanza;
+       log("debug", "opening a new outgoing connection for this stanza");
+       local host_session = s2s_new_outgoing(from_host, to_host);
+
+       -- Store in buffer
+       host_session.bounce_sendq = bounce_sendq;
+       host_session.sendq = { {tostring(stanza), stanza.attr.type ~= "error" and stanza.attr.type ~= "result" and st.reply(stanza)} };
+       log("debug", "stanza [%s] queued until connection complete", tostring(stanza.name));
+       s2sout.initiate_connection(host_session);
+       if (not host_session.connecting) and (not host_session.conn) then
+               log("warn", "Connection to %s failed already, destroying session...", to_host);
+               s2s_destroy_session(host_session, "Connection failed");
+               return false;
+       end
+       return true;
+end, 100);
+
+--- Helper to check that a session peer's certificate is valid
+local function check_cert_status(session)
+       local conn = session.conn:socket()
+       local cert
+       if conn.getpeercertificate then
+               cert = conn:getpeercertificate()
+       end
+
+       if cert then
+               local chain_valid, errors = conn:getpeerverification()
+               -- Is there any interest in printing out all/the number of errors here?
+               if not chain_valid then
+                       (session.log or log)("debug", "certificate chain validation result: invalid");
+                       session.cert_chain_status = "invalid";
+               else
+                       (session.log or log)("debug", "certificate chain validation result: valid");
+                       session.cert_chain_status = "valid";
+
+                       local host = session.direction == "incoming" and session.from_host or session.to_host
+
+                       -- We'll go ahead and verify the asserted identity if the
+                       -- connecting server specified one.
+                       if host then
+                               if cert_verify_identity(host, "xmpp-server", cert) then
+                                       session.cert_identity_status = "valid"
+                               else
+                                       session.cert_identity_status = "invalid"
+                               end
+                       end
+               end
+       end
+end
+
+--- XMPP stream event handlers
+
+local stream_callbacks = { default_ns = "jabber:server", handlestanza =  core_process_stanza };
+
+local xmlns_xmpp_streams = "urn:ietf:params:xml:ns:xmpp-streams";
+
+function stream_callbacks.streamopened(session, attr)
+       local send = session.sends2s;
+       
+       -- TODO: #29: SASL/TLS on s2s streams
+       session.version = tonumber(attr.version) or 0;
+       
+       -- TODO: Rename session.secure to session.encrypted
+       if session.secure == false then
+               session.secure = true;
+       end
+
+       if session.direction == "incoming" then
+               -- Send a reply stream header
+               
+               -- Validate to/from
+               local to, from = nameprep(attr.to), nameprep(attr.from);
+               if not to and attr.to then -- COMPAT: Some servers do not reliably set 'to' (especially on stream restarts)
+                       session:close({ condition = "improper-addressing", text = "Invalid 'to' address" });
+                       return;
+               end
+               if not from and attr.from then -- COMPAT: Some servers do not reliably set 'from' (especially on stream restarts)
+                       session:close({ condition = "improper-addressing", text = "Invalid 'from' address" });
+                       return;
+               end
+               
+               -- Set session.[from/to]_host if they have not been set already and if
+               -- this session isn't already authenticated
+               if session.type == "s2sin_unauthed" and from and not session.from_host then
+                       session.from_host = from;
+               elseif from ~= session.from_host then
+                       session:close({ condition = "improper-addressing", text = "New stream 'from' attribute does not match original" });
+                       return;
+               end
+               if session.type == "s2sin_unauthed" and to and not session.to_host then
+                       session.to_host = to;
+               elseif to ~= session.to_host then
+                       session:close({ condition = "improper-addressing", text = "New stream 'to' attribute does not match original" });
+                       return;
+               end
+               
+               session.streamid = uuid_gen();
+               (session.log or log)("debug", "Incoming s2s received %s", st.stanza("stream:stream", attr):top_tag());
+               if session.to_host then
+                       if 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
+                               });
+                               return;
+                       elseif hosts[session.to_host].disallow_s2s then
+                               -- Attempting to connect to a host that disallows s2s
+                               session:close({
+                                       condition = "policy-violation";
+                                       text = "Server-to-server communication is not allowed to this host";
+                               });
+                               return;
+                       end
+               end
+
+               if session.secure and not session.cert_chain_status then check_cert_status(session); end
+
+               send("<?xml version='1.0'?>");
+               send(st.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, to=session.from_host, version=(session.version > 0 and "1.0" or nil) }):top_tag());
+               if session.version >= 1.0 then
+                       local features = st.stanza("stream:features");
+                       
+                       if session.to_host then
+                               hosts[session.to_host].events.fire_event("s2s-stream-features", { origin = 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);
+               end
+       elseif session.direction == "outgoing" then
+               -- If we are just using the connection for verifying dialback keys, we won't try and auth it
+               if not attr.id then error("stream response did not give us a streamid!!!"); end
+               session.streamid = attr.id;
+
+               if session.secure and not session.cert_chain_status then check_cert_status(session); end
+
+               -- Send unauthed buffer
+               -- (stanzas which are fine to send before dialback)
+               -- Note that this is *not* the stanza queue (which
+               -- we can only send if auth succeeds) :)
+               local send_buffer = session.send_buffer;
+               if send_buffer and #send_buffer > 0 then
+                       log("debug", "Sending s2s send_buffer now...");
+                       for i, data in ipairs(send_buffer) do
+                               session.sends2s(tostring(data));
+                               send_buffer[i] = nil;
+                       end
+               end
+               session.send_buffer = nil;
+       
+               -- If server is pre-1.0, don't wait for features, just do dialback
+               if session.version < 1.0 then
+                       if not session.dialback_verifying then
+                               hosts[session.from_host].events.fire_event("s2s-authenticate-legacy", { origin = session });
+                       else
+                               s2s_mark_connected(session);
+                       end
+               end
+       end
+       session.notopen = nil;
+       session.send = function(stanza) prosody.events.fire_event("route/remote", { from_host = session.to_host, to_host = session.from_host, stanza = stanza}) end;
+end
+
+function stream_callbacks.streamclosed(session)
+       (session.log or log)("debug", "Received </stream:stream>");
+       session:close();
+end
+
+function stream_callbacks.streamdisconnected(session, err)
+       if err and err ~= "closed" then
+               (session.log or log)("debug", "s2s connection attempt failed: %s", err);
+               if s2sout.attempt_connection(session, err) then
+                       (session.log or log)("debug", "...so we're going to try another target");
+                       return true; -- Session lives for now
+               end
+       end
+       (session.log or log)("info", "s2s disconnected: %s->%s (%s)", tostring(session.from_host), tostring(session.to_host), tostring(err or "closed"));
+       s2s_destroy_session(session, err);
+end
+
+function stream_callbacks.error(session, error, data)
+       if error == "no-stream" then
+               session:close("invalid-namespace");
+       elseif error == "parse-error" then
+               session.log("debug", "Server-to-server XML parse error: %s", tostring(error));
+               session:close("not-well-formed");
+       elseif error == "stream-error" then
+               local condition, text = "undefined-condition";
+               for child in data:children() do
+                       if child.attr.xmlns == xmlns_xmpp_streams then
+                               if child.name ~= "text" then
+                                       condition = child.name;
+                               else
+                                       text = child:get_text();
+                               end
+                               if condition ~= "undefined-condition" and text then
+                                       break;
+                               end
+                       end
+               end
+               text = condition .. (text and (" ("..text..")") or "");
+               session.log("info", "Session closed by remote with error: %s", text);
+               session:close(nil, text);
+       end
+end
+
+local function handleerr(err) log("error", "Traceback[s2s]: %s: %s", tostring(err), traceback()); end
+function stream_callbacks.handlestanza(session, stanza)
+       if stanza.attr.xmlns == "jabber:client" then --COMPAT: Prosody pre-0.6.2 may send jabber:client
+               stanza.attr.xmlns = nil;
+       end
+       stanza = session.filter("stanzas/in", stanza);
+       if stanza then
+               return xpcall(function () return core_process_stanza(session, stanza) end, handleerr);
+       end
+end
+
+local listener = {};
+
+--- Session methods
+local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'};
+local default_stream_attr = { ["xmlns:stream"] = "http://etherx.jabber.org/streams", xmlns = stream_callbacks.default_ns, version = "1.0", id = "" };
+local function session_close(session, reason, remote_reason)
+       local log = session.log or log;
+       if session.conn then
+               if session.notopen then
+                       session.sends2s("<?xml version='1.0'?>");
+                       session.sends2s(st.stanza("stream:stream", default_stream_attr):top_tag());
+               end
+               if reason then
+                       if type(reason) == "string" then -- assume stream error
+                               log("info", "Disconnecting %s[%s], <stream:error> is: %s", session.host or "(unknown host)", session.type, reason);
+                               session.sends2s(st.stanza("stream:error"):tag(reason, {xmlns = 'urn:ietf:params:xml:ns:xmpp-streams' }));
+                       elseif type(reason) == "table" then
+                               if reason.condition then
+                                       local stanza = st.stanza("stream:error"):tag(reason.condition, stream_xmlns_attr):up();
+                                       if reason.text then
+                                               stanza:tag("text", stream_xmlns_attr):text(reason.text):up();
+                                       end
+                                       if reason.extra then
+                                               stanza:add_child(reason.extra);
+                                       end
+                                       log("info", "Disconnecting %s[%s], <stream:error> is: %s", session.host or "(unknown host)", session.type, tostring(stanza));
+                                       session.sends2s(stanza);
+                               elseif reason.name then -- a stanza
+                                       log("info", "Disconnecting %s->%s[%s], <stream:error> is: %s", session.from_host or "(unknown host)", session.to_host or "(unknown host)", session.type, tostring(reason));
+                                       session.sends2s(reason);
+                               end
+                       end
+               end
+               session.sends2s("</stream:stream>");
+               if session.notopen or not session.conn:close() then
+                       session.conn:close(true); -- Force FIXME: timer?
+               end
+               session.conn:close();
+               listener.ondisconnect(session.conn, remote_reason or (reason and (reason.text or reason.condition)) or reason or "stream closed");
+       end
+end
+
+-- Session initialization logic shared by incoming and outgoing
+local function initialize_session(session)
+       local stream = new_xmpp_stream(session, stream_callbacks);
+       session.stream = stream;
+       
+       session.notopen = true;
+               
+       function session.reset_stream()
+               session.notopen = true;
+               session.stream:reset();
+       end
+       
+       local filter = session.filter;
+       function session.data(data)
+               data = filter("bytes/in", data);
+               if data then
+                       local ok, err = stream:feed(data);
+                       if ok then return; end
+                       (session.log or log)("warn", "Received invalid XML: %s", data);
+                       (session.log or log)("warn", "Problem was: %s", err);
+                       session:close("not-well-formed");
+               end
+       end
+
+       session.close = session_close;
+
+       local handlestanza = stream_callbacks.handlestanza;
+       function session.dispatch_stanza(session, stanza)
+               return handlestanza(session, stanza);
+       end
+
+       local conn = session.conn;
+       add_task(connect_timeout, function ()
+               if session.conn ~= conn or session.connecting
+               or session.type == "s2sin" or session.type == "s2sout" then
+                       return; -- Ok, we're connect[ed|ing]
+               end
+               -- Not connected, need to close session and clean up
+               (session.log or log)("debug", "Destroying incomplete session %s->%s due to inactivity",
+               session.from_host or "(unknown)", session.to_host or "(unknown)");
+               session:close("connection-timeout");
+       end);
+end
+
+function listener.onconnect(conn)
+       if not sessions[conn] then -- May be an existing outgoing session
+               local session = s2s_new_incoming(conn);
+               sessions[conn] = session;
+               session.log("debug", "Incoming s2s connection");
+
+               local filter = initialize_filters(session);
+               local w = conn.write;
+               session.sends2s = function (t)
+                       log("debug", "sending: %s", t.top_tag and t:top_tag() or t:match("^([^>]*>?)"));
+                       if t.name then
+                               t = filter("stanzas/out", t);
+                       end
+                       if t then
+                               t = filter("bytes/out", tostring(t));
+                               if t then
+                                       return w(conn, t);
+                               end
+                       end
+               end
+       
+               initialize_session(session);
+       end
+end
+
+function listener.onincoming(conn, data)
+       local session = sessions[conn];
+       if session then
+               session.data(data);
+       end
+end
+       
+function listener.onstatus(conn, status)
+       if status == "ssl-handshake-complete" then
+               local session = sessions[conn];
+               if session and session.direction == "outgoing" then
+                       local to_host, from_host = session.to_host, session.from_host;
+                       session.log("debug", "Sending stream header...");
+                       session:open_stream(session.from_host, session.to_host);
+               end
+       end
+end
+
+function listener.ondisconnect(conn, err)
+       local session = sessions[conn];
+       if session then
+               if stream_callbacks.streamdisconnected(session, err) then
+                       return; -- Connection lives, for now
+               end
+       end
+       sessions[conn] = nil;
+end
+
+function listener.register_outgoing(conn, session)
+       session.direction = "outgoing";
+       sessions[conn] = session;
+       initialize_session(session);
+end
+
+s2sout.set_listener(listener);
+
+module:add_item("net-provider", {
+       name = "s2s";
+       listener = listener;
+       default_port = 5269;
+       encryption = "starttls";
+       multiplex = {
+               pattern = "^<.*:stream.*%sxmlns%s*=%s*(['\"])jabber:server%1.*>";
+       };
+});
+
diff --git a/plugins/s2s/s2sout.lib.lua b/plugins/s2s/s2sout.lib.lua
new file mode 100644 (file)
index 0000000..af55b27
--- /dev/null
@@ -0,0 +1,346 @@
+-- Prosody IM
+-- Copyright (C) 2008-2010 Matthew Wild
+-- Copyright (C) 2008-2010 Waqas Hussain
+-- 
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+--- Module containing all the logic for connecting to a remote server
+
+local portmanager = require "core.portmanager";
+local wrapclient = require "net.server".wrapclient;
+local initialize_filters = require "util.filters".initialize;
+local idna_to_ascii = require "util.encodings".idna.to_ascii;
+local add_task = require "util.timer".add_task;
+local new_ip = require "util.ip".new_ip;
+local rfc3484_dest = require "util.rfc3484".destination;
+local socket = require "socket";
+local t_insert, t_sort, ipairs = table.insert, table.sort, ipairs;
+local st = require "util.stanza";
+
+local s2s_new_outgoing = require "core.s2smanager".new_outgoing;
+local s2s_destroy_session = require "core.s2smanager".destroy_session;
+
+local sources = {};
+
+local max_dns_depth = module:get_option_number("dns_max_depth", 3);
+
+local s2sout = {};
+
+local s2s_listener;
+
+
+function s2sout.set_listener(listener)
+       s2s_listener = listener;
+end
+
+local function compare_srv_priorities(a,b)
+       return a.priority < b.priority or (a.priority == b.priority and a.weight > b.weight);
+end
+
+local function session_open_stream(session, from, to)
+       session.sends2s(st.stanza("stream:stream", {
+               xmlns='jabber:server', ["xmlns:db"]='jabber:server:dialback',
+               ["xmlns:stream"]='http://etherx.jabber.org/streams',
+               from=from, to=to, version='1.0', ["xml:lang"]='en'}):top_tag());
+end
+
+function s2sout.initiate_connection(host_session)
+       initialize_filters(host_session);
+       host_session.open_stream = session_open_stream;
+       
+       -- Kick the connection attempting machine into life
+       if not s2sout.attempt_connection(host_session) then
+               -- Intentionally not returning here, the
+               -- session is needed, connected or not
+               s2s_destroy_session(host_session);
+       end
+       
+       if not host_session.sends2s then
+               -- A sends2s which buffers data (until the stream is opened)
+               -- note that data in this buffer will be sent before the stream is authed
+               -- and will not be ack'd in any way, successful or otherwise
+               local buffer;
+               function host_session.sends2s(data)
+                       if not buffer then
+                               buffer = {};
+                               host_session.send_buffer = buffer;
+                       end
+                       log("debug", "Buffering data on unconnected s2sout to %s", to_host);
+                       buffer[#buffer+1] = data;
+                       log("debug", "Buffered item %d: %s", #buffer, tostring(data));
+               end
+       end
+end
+
+function s2sout.attempt_connection(host_session, err)
+       local from_host, to_host = host_session.from_host, host_session.to_host;
+       local connect_host, connect_port = to_host and idna_to_ascii(to_host), 5269;
+       
+       if not connect_host then
+               return false;
+       end
+       
+       if not err then -- This is our first attempt
+               log("debug", "First attempt to connect to %s, starting with SRV lookup...", to_host);
+               host_session.connecting = true;
+               local handle;
+               handle = adns.lookup(function (answer)
+                       handle = nil;
+                       host_session.connecting = nil;
+                       if answer then
+                               log("debug", to_host.." has SRV records, handling...");
+                               local srv_hosts = {};
+                               host_session.srv_hosts = srv_hosts;
+                               for _, record in ipairs(answer) do
+                                       t_insert(srv_hosts, record.srv);
+                               end
+                               if #srv_hosts == 1 and srv_hosts[1].target == "." then
+                                       log("debug", to_host.." does not provide a XMPP service");
+                                       s2s_destroy_session(host_session, err); -- Nothing to see here
+                                       return;
+                               end
+                               t_sort(srv_hosts, compare_srv_priorities);
+                               
+                               local srv_choice = srv_hosts[1];
+                               host_session.srv_choice = 1;
+                               if srv_choice then
+                                       connect_host, connect_port = srv_choice.target or to_host, srv_choice.port or connect_port;
+                                       log("debug", "Best record found, will connect to %s:%d", connect_host, connect_port);
+                               end
+                       else
+                               log("debug", to_host.." has no SRV records, falling back to A");
+                       end
+                       -- Try with SRV, or just the plain hostname if no SRV
+                       local ok, err = s2sout.try_connect(host_session, connect_host, connect_port);
+                       if not ok then
+                               if not s2sout.attempt_connection(host_session, err) then
+                                       -- No more attempts will be made
+                                       s2s_destroy_session(host_session, err);
+                               end
+                       end
+               end, "_xmpp-server._tcp."..connect_host..".", "SRV");
+               
+               return true; -- Attempt in progress
+       elseif host_session.ip_hosts then
+               return s2sout.try_connect(host_session, connect_host, connect_port, err);
+       elseif host_session.srv_hosts and #host_session.srv_hosts > host_session.srv_choice then -- Not our first attempt, and we also have SRV
+               host_session.srv_choice = host_session.srv_choice + 1;
+               local srv_choice = host_session.srv_hosts[host_session.srv_choice];
+               connect_host, connect_port = srv_choice.target or to_host, srv_choice.port or connect_port;
+               host_session.log("info", "Connection failed (%s). Attempt #%d: This time to %s:%d", tostring(err), host_session.srv_choice, connect_host, connect_port);
+       else
+               host_session.log("info", "Out of connection options, can't connect to %s", tostring(host_session.to_host));
+               -- We're out of options
+               return false;
+       end
+       
+       if not (connect_host and connect_port) then
+               -- Likely we couldn't resolve DNS
+               log("warn", "Hmm, we're without a host (%s) and port (%s) to connect to for %s, giving up :(", tostring(connect_host), tostring(connect_port), tostring(to_host));
+               return false;
+       end
+
+       return s2sout.try_connect(host_session, connect_host, connect_port);
+end
+
+function s2sout.try_next_ip(host_session)
+       host_session.connecting = nil;
+       host_session.ip_choice = host_session.ip_choice + 1;
+       local ip = host_session.ip_hosts[host_session.ip_choice];
+       local ok, err= s2sout.make_connect(host_session, ip.ip, ip.port);
+       if not ok then
+               if not s2sout.attempt_connection(host_session, err or "closed") then
+                       err = err and (": "..err) or "";
+                       s2s_destroy_session(host_session, "Connection failed"..err);
+               end
+       end
+end
+
+function s2sout.try_connect(host_session, connect_host, connect_port, err)
+       host_session.connecting = true;
+
+       if not err then
+               local IPs = {};
+               host_session.ip_hosts = IPs;
+               local handle4, handle6;
+               local has_other = false;
+
+               handle4 = adns.lookup(function (reply, err)
+                       handle4 = nil;
+
+                       -- COMPAT: This is a compromise for all you CNAME-(ab)users :)
+                       if not (reply and reply[#reply] and reply[#reply].a) then
+                               local count = max_dns_depth;
+                               reply = dns.peek(connect_host, "CNAME", "IN");
+                               while count > 0 and reply and reply[#reply] and not reply[#reply].a and reply[#reply].cname do
+                                       log("debug", "Looking up %s (DNS depth is %d)", tostring(reply[#reply].cname), count);
+                                       reply = dns.peek(reply[#reply].cname, "A", "IN") or dns.peek(reply[#reply].cname, "CNAME", "IN");
+                                       count = count - 1;
+                               end
+                       end
+                       -- end of CNAME resolving
+
+                       if reply and reply[#reply] and reply[#reply].a then
+                               for _, ip in ipairs(reply) do
+                                       log("debug", "DNS reply for %s gives us %s", connect_host, ip.a);
+                                       IPs[#IPs+1] = new_ip(ip.a, "IPv4");
+                               end
+                       end
+
+                       if has_other then
+                               if #IPs > 0 then
+                                       rfc3484_dest(host_session.ip_hosts, sources);
+                                       for i = 1, #IPs do
+                                               IPs[i] = {ip = IPs[i], port = connect_port};
+                                       end
+                                       host_session.ip_choice = 0;
+                                       s2sout.try_next_ip(host_session);
+                               else
+                                       log("debug", "DNS lookup failed to get a response for %s", connect_host);
+                                       host_session.ip_hosts = nil;
+                                       if not s2sout.attempt_connection(host_session, "name resolution failed") then -- Retry if we can
+                                               log("debug", "No other records to try for %s - destroying", host_session.to_host);
+                                               err = err and (": "..err) or "";
+                                               s2s_destroy_session(host_session, "DNS resolution failed"..err); -- End of the line, we can't
+                                       end
+                               end
+                       else
+                               has_other = true;
+                       end
+               end, connect_host, "A", "IN");
+
+               handle6 = adns.lookup(function (reply, err)
+                       handle6 = nil;
+
+                       if reply and reply[#reply] and reply[#reply].aaaa then
+                               for _, ip in ipairs(reply) do
+                                       log("debug", "DNS reply for %s gives us %s", connect_host, ip.aaaa);
+                                       IPs[#IPs+1] = new_ip(ip.aaaa, "IPv6");
+                               end
+                       end
+
+                       if has_other then
+                               if #IPs > 0 then
+                                       rfc3484_dest(host_session.ip_hosts, sources);
+                                       for i = 1, #IPs do
+                                               IPs[i] = {ip = IPs[i], port = connect_port};
+                                       end
+                                       host_session.ip_choice = 0;
+                                       s2sout.try_next_ip(host_session);
+                               else
+                                       log("debug", "DNS lookup failed to get a response for %s", connect_host);
+                                       host_session.ip_hosts = nil;
+                                       if not s2sout.attempt_connection(host_session, "name resolution failed") then -- Retry if we can
+                                               log("debug", "No other records to try for %s - destroying", host_session.to_host);
+                                               err = err and (": "..err) or "";
+                                               s2s_destroy_session(host_session, "DNS resolution failed"..err); -- End of the line, we can't
+                                       end
+                               end
+                       else
+                               has_other = true;
+                       end
+               end, connect_host, "AAAA", "IN");
+
+               return true;
+       elseif host_session.ip_hosts and #host_session.ip_hosts > host_session.ip_choice then -- Not our first attempt, and we also have IPs left to try
+               s2sout.try_next_ip(host_session);
+       else
+               host_session.ip_hosts = nil;
+               if not s2sout.attempt_connection(host_session, "out of IP addresses") then -- Retry if we can
+                       log("debug", "No other records to try for %s - destroying", host_session.to_host);
+                       err = err and (": "..err) or "";
+                       s2s_destroy_session(host_session, "Connecting failed"..err); -- End of the line, we can't
+                       return false;
+               end
+       end
+
+       return true;
+end
+
+function s2sout.make_connect(host_session, connect_host, connect_port)
+       (host_session.log or log)("info", "Beginning new connection attempt to %s ([%s]:%d)", host_session.to_host, connect_host.addr, connect_port);
+       -- Ok, we're going to try to connect
+       
+       local from_host, to_host = host_session.from_host, host_session.to_host;
+       
+       local conn, handler;
+       if connect_host.proto == "IPv4" then
+               conn, handler = socket.tcp();
+       else
+               conn, handler = socket.tcp6();
+       end
+       
+       if not conn then
+               log("warn", "Failed to create outgoing connection, system error: %s", handler);
+               return false, handler;
+       end
+
+       conn:settimeout(0);
+       local success, err = conn:connect(connect_host.addr, connect_port);
+       if not success and err ~= "timeout" then
+               log("warn", "s2s connect() to %s (%s:%d) failed: %s", host_session.to_host, connect_host.addr, connect_port, err);
+               return false, err;
+       end
+       
+       conn = wrapclient(conn, connect_host.addr, connect_port, s2s_listener, "*a");
+       host_session.conn = conn;
+       
+       local filter = initialize_filters(host_session);
+       local w, log = conn.write, host_session.log;
+       host_session.sends2s = function (t)
+               log("debug", "sending: %s", (t.top_tag and t:top_tag()) or t:match("^[^>]*>?"));
+               if t.name then
+                       t = filter("stanzas/out", t);
+               end
+               if t then
+                       t = filter("bytes/out", tostring(t));
+                       if t then
+                               return w(conn, tostring(t));
+                       end
+               end
+       end
+       
+       -- Register this outgoing connection so that xmppserver_listener knows about it
+       -- otherwise it will assume it is a new incoming connection
+       s2s_listener.register_outgoing(conn, host_session);
+       
+       host_session:open_stream(from_host, to_host);
+       
+       log("debug", "Connection attempt in progress...");
+       return true;
+end
+
+module:hook_global("service-added", function (event)
+       if event.name ~= "s2s" then return end
+
+       local s2s_sources = portmanager.get_active_services():get("s2s");
+       if not s2s_sources then
+               module:log("warn", "s2s not listening on any ports, outgoing connections may fail");
+               return;
+       end
+       for source, _ in pairs(s2s_sources) do
+               if source == "*" or source == "0.0.0.0" then
+                       if not socket.local_addresses then
+                               sources[#sources + 1] = new_ip("0.0.0.0", "IPv4");
+                       else
+                               for _, addr in ipairs(socket.local_addresses("ipv4", true)) do
+                                       sources[#sources + 1] = new_ip(addr, "IPv4");
+                               end
+                       end
+               elseif source == "::" then
+                       if not socket.local_addresses then
+                               sources[#sources + 1] = new_ip("::", "IPv6");
+                       else
+                               for _, addr in ipairs(socket.local_addresses("ipv6", true)) do
+                                       sources[#sources + 1] = new_ip(addr, "IPv6");
+                               end
+                       end
+               else
+                       sources[#sources + 1] = new_ip(source, (source:find(":") and "IPv6") or "IPv4");
+               end
+       end
+end);
+
+return s2sout;
diff --git a/prosody b/prosody
index e18b4e61c94b57bdac591e6c8590109d2eac0918..64021fd32f2548ce4ac65417229e957cebcf7109 100755 (executable)
--- a/prosody
+++ b/prosody
@@ -150,10 +150,14 @@ function sandbox_require()
                local curr_env_mt = getmetatable(getfenv(2));
                local _realG_mt = getmetatable(_realG);
                if curr_env_mt and curr_env_mt.__index and not curr_env_mt.__newindex and _realG_mt then
-                       local old_newindex
+                       local old_newindex, old_index;
                        old_newindex, _realG_mt.__newindex = _realG_mt.__newindex, curr_env;
+                       old_index, _realG_mt.__index = _realG_mt.__index, function (_G, k)
+                               return rawget(curr_env, k);
+                       end;
                        local ret = _real_require(...);
                        _realG_mt.__newindex = old_newindex;
+                       _realG_mt.__index = old_index;
                        return ret;
                end
                return _real_require(...);
@@ -254,63 +258,6 @@ function init_global_state()
        local global_ssl_ctx = certmanager.create_context("*", "server");
        prosody.global_ssl_ctx = global_ssl_ctx;
 
-       local cl = require "net.connlisteners";
-       function prosody.net_activate_ports(option, listener, default, conntype)
-               conntype = conntype or (global_ssl_ctx and "tls") or "tcp";
-               local ports_option = option and option.."_ports" or "ports";
-               if not cl.get(listener) then return; end
-               local ports = config.get("*", "core", ports_option) or default;
-               if type(ports) == "number" then ports = {ports} end;
-               
-               if type(ports) ~= "table" then
-                       log("error", "core."..ports_option.." is not a table");
-               else
-                       for _, port in ipairs(ports) do
-                               port = tonumber(port);
-                               if type(port) ~= "number" then
-                                       log("error", "Non-numeric "..ports_option..": "..tostring(port));
-                               else
-                                       local ok, errors = cl.start(listener, {
-                                               ssl = conntype == "ssl" and global_ssl_ctx,
-                                               port = port,
-                                               interface = (option and config.get("*", "core", option.."_interface"))
-                                                       or cl.get(listener).default_interface
-                                                       or config.get("*", "core", "interface"),
-                                               type = conntype
-                                       });
-                                       if not ok then
-                                               for addr, err in pairs(errors) do
-                                                       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 on %s, %s", port, addr, friendly_message);
-                                               end
-                                       end
-                               end
-                       end
-               end
-       end
 end
 
 function read_version()
@@ -333,6 +280,7 @@ function load_secondary_libraries()
        require "util.xmppstream"
        require "core.rostermanager"
        require "core.hostmanager"
+       require "core.portmanager"
        require "core.modulemanager"
        require "core.usermanager"
        require "core.sessionmanager"
@@ -359,9 +307,6 @@ function load_secondary_libraries()
        if remdebug then remdebug.engine.start() end
        ]]
 
-       require "net.connlisteners";
-       require "net.httpserver";
-       
        require "util.stanza"
        require "util.jid"
 end
@@ -374,20 +319,6 @@ function prepare_to_start()
        log("info", "Prosody is using the %s backend for connection handling", server.get_backend());
        -- Signal to modules that we are ready to start
        prosody.events.fire_event("server-starting");
-
-       -- start listening on sockets
-       if config.get("*", "core", "ports") then
-               prosody.net_activate_ports(nil, "multiplex", {5222, 5269});
-               if config.get("*", "core", "ssl_ports") then
-                       prosody.net_activate_ports("ssl", "multiplex", {5223}, "ssl");
-               end
-       else
-               prosody.net_activate_ports("c2s", "xmppclient", {5222});
-               prosody.net_activate_ports("s2s", "xmppserver", {5269});
-               prosody.net_activate_ports("component", "xmppcomponent", {5347}, "tcp");
-               prosody.net_activate_ports("legacy_ssl", "xmppclient", {}, "ssl");
-       end
-
        prosody.start_time = os.time();
 end    
 
index 3736dd3498c08b88fe6cbb45569766a1e2fc9061..7caf21ce94eb1c4dde0042b8a6250ceb696fc1fc 100644 (file)
@@ -22,8 +22,9 @@ do
                location         = _("yellow");
        };
 end
+module("debugx", package.seeall);
 
-local function get_locals_table(level)
+function get_locals_table(level)
        level = level + 1; -- Skip this function itself
        local locals = {};
        for local_num = 1, math.huge do
@@ -34,7 +35,7 @@ local function get_locals_table(level)
        return locals;
 end
 
-local function get_upvalues_table(func)
+function get_upvalues_table(func)
        local upvalues = {};
        if func then
                for upvalue_num = 1, math.huge do
@@ -46,7 +47,7 @@ local function get_upvalues_table(func)
        return upvalues;
 end
 
-local function string_from_var_table(var_table, max_line_len, indent_str)
+function string_from_var_table(var_table, max_line_len, indent_str)
        local var_string = {};
        local col_pos = 0;
        max_line_len = max_line_len or math.huge;
@@ -103,8 +104,8 @@ function get_traceback_table(thread, start_level)
        return levels;
 end
 
-function debug.traceback(...)
-       local ok, ret = pcall(debug._traceback, ...);
+function traceback(...)
+       local ok, ret = pcall(_traceback, ...);
        if not ok then
                return "Error in error handling: "..ret;
        end
@@ -116,7 +117,8 @@ local function build_source_boundary_marker(last_source_desc)
        return getstring(styles.boundary_padding, "v"..padding).." "..getstring(styles.filename, last_source_desc).." "..getstring(styles.boundary_padding, padding..(#last_source_desc%2==0 and "-v" or "v "));
 end
 
-function debug._traceback(thread, message, level)
+function _traceback(thread, message, level)
+
        if type(thread) ~= "thread" then
                thread, message, level = coroutine.running(), thread, message;
        end
@@ -178,3 +180,9 @@ function debug._traceback(thread, message, level)
 
        return message.."stack traceback:\n"..table.concat(lines, "\n");
 end
+
+function use()
+       debug.traceback = traceback;
+end
+
+return _M;
index 1135617606210d4419d1567dd9e10ea65d1a640a..ad23dd79572295c5bc7c727b65f489fc09c9a278 100644 (file)
@@ -6,6 +6,8 @@
 -- COPYING file in the source package for more information.
 --
 
+local debug = require "util.debug";
+
 module("helpers", package.seeall);
 
 -- Helper functions for debugging
@@ -28,7 +30,34 @@ function log_events(events, name, logger)
 end
 
 function revert_log_events(events)
-       events.fire_event, events[events.fire_event] = events[events.fire_event], nil; -- :)
+       events.fire_event, events[events.fire_event] = events[events.fire_event], nil; -- :))
+end
+
+function show_events(events)
+       local event_handlers = events._handlers;
+       local events_array = {};
+       local event_handler_arrays = {};
+       for event in pairs(events._event_map) do
+               local handlers = event_handlers[event];
+               table.insert(events_array, event);
+               local handler_strings = {};
+               for i, handler in ipairs(handlers) do
+                       local upvals = debug.string_from_var_table(debug.get_upvalues_table(handler));
+                       handler_strings[i] = "  "..i..": "..tostring(handler)..(upvals and ("\n        "..upvals) or "");
+               end
+               event_handler_arrays[event] = handler_strings;
+       end
+       table.sort(events_array);
+       local i = 1;
+       repeat
+               local handlers = event_handler_arrays[events_array[i]];
+               for j=#handlers, 1, -1 do
+                       table.insert(events_array, i+1, handlers[j]);
+               end
+               if i > 1 then events_array[i] = "\n"..events_array[i]; end
+               i = i + #handlers + 1
+       until i == #events_array;
+       return table.concat(events_array, "\n");
 end
 
 function get_upvalue(f, get_name)
index aa0b172b764bdfcc233c8f10da64c426f006389b..fb89f4a55b796db4fd91ff7ab4c4b90cab519920 100644 (file)
@@ -8,8 +8,10 @@
 
 --[[ Iterators ]]--
 
+local it = {};
+
 -- Reverse an iterator
-function reverse(f, s, var)
+function it.reverse(f, s, var)
        local results = {};
 
        -- First call the normal iterator
@@ -34,12 +36,12 @@ end
 local function _keys_it(t, key)
        return (next(t, key));
 end
-function keys(t)
+function it.keys(t)
        return _keys_it, t;
 end
 
 -- Iterate only over values in a table
-function values(t)
+function it.values(t)
        local key, val;
        return function (t)
                key, val = next(t, key);
@@ -48,7 +50,7 @@ function values(t)
 end
 
 -- Given an iterator, iterate only over unique items
-function unique(f, s, var)
+function it.unique(f, s, var)
        local set = {};
        
        return function ()
@@ -65,7 +67,7 @@ function unique(f, s, var)
 end
 
 --[[ Return the number of items an iterator returns ]]--
-function count(f, s, var)
+function it.count(f, s, var)
        local x = 0;
        
        while true do
@@ -79,7 +81,7 @@ function count(f, s, var)
 end
 
 -- Return the first n items an iterator returns
-function head(n, f, s, var)
+function it.head(n, f, s, var)
        local c = 0;
        return function (s, var)
                if c >= n then
@@ -91,7 +93,7 @@ function head(n, f, s, var)
 end
 
 -- Skip the first n items an iterator returns
-function skip(n, f, s, var)
+function it.skip(n, f, s, var)
        for i=1,n do
                var = f(s, var);
        end
@@ -99,7 +101,7 @@ function skip(n, f, s, var)
 end
 
 -- Return the last n items an iterator returns
-function tail(n, f, s, var)
+function it.tail(n, f, s, var)
        local results, count = {}, 0;
        while true do
                local ret = { f(s, var) };
@@ -121,13 +123,13 @@ function tail(n, f, s, var)
 end
 
 local function _range_iter(max, curr) if curr < max then return curr + 1; end end
-function range(x, y)
+function it.range(x, y)
        if not y then x, y = 1, x; end -- Default to 1..x if y not given
        return _range_iter, y, x-1;
 end
 
 -- Convert the values returned by an iterator to an array
-function it2array(f, s, var)
+function it.to_array(f, s, var)
        local t, var = {};
        while true do
                var = f(s, var);
@@ -139,7 +141,7 @@ end
 
 -- Treat the return of an iterator as key,value pairs,
 -- and build a table
-function it2table(f, s, var)
+function it.to_table(f, s, var)
        local t, var2 = {};
        while true do
                var, var2 = f(s, var);
@@ -149,3 +151,4 @@ function it2table(f, s, var)
        return t;
 end
 
+return it;
index c3bf399266703a6832e30ed14c6ab42183052c4e..4fadb4b9beaec0175d3189086242a8faac74e2fd 100644 (file)
@@ -13,8 +13,7 @@ local ipairs, pairs, setmetatable = ipairs, pairs, setmetatable;
 
 module "logger"
 
-local name_sinks, level_sinks = {}, {};
-local name_patterns = {};
+local level_sinks = {};
 
 local make_logger;
 
@@ -46,17 +45,7 @@ function make_logger(source_name, level)
                level_sinks[level] = level_handlers;
        end
 
-       local source_handlers = name_sinks[source_name];
-       
        local logger = function (message, ...)
-               if source_handlers then
-                       for i = 1,#source_handlers do
-                               if source_handlers[i](source_name, level, message, ...) == false then
-                                       return;
-                               end
-                       end
-               end
-               
                for i = 1,#level_handlers do
                        level_handlers[i](source_name, level, message, ...);
                end
@@ -66,14 +55,12 @@ function make_logger(source_name, level)
 end
 
 function reset()
-       for k in pairs(name_sinks) do name_sinks[k] = nil; end
        for level, handler_list in pairs(level_sinks) do
                -- Clear all handlers for this level
                for i = 1, #handler_list do
                        handler_list[i] = nil;
                end
        end
-       for k in pairs(name_patterns) do name_patterns[k] = nil; end
 end
 
 function add_level_sink(level, sink_function)
@@ -84,22 +71,6 @@ function add_level_sink(level, sink_function)
        end
 end
 
-function add_name_sink(name, sink_function, exclusive)
-       if not name_sinks[name] then
-               name_sinks[name] = { sink_function };
-       else
-               name_sinks[name][#name_sinks[name] + 1] = sink_function;
-       end
-end
-
-function add_name_pattern_sink(name_pattern, sink_function, exclusive)
-       if not name_patterns[name_pattern] then
-               name_patterns[name_pattern] = { sink_function };
-       else
-               name_patterns[name_pattern][#name_patterns[name_pattern] + 1] = sink_function;
-       end
-end
-
 _M.new = make_logger;
 
 return _M;
index e4cc2dffbd428b6be84c2ec749ba0cd2156b67b3..050446ec3f3aca668bd70933e7d5802f6651b4cf 100644 (file)
@@ -82,8 +82,10 @@ function new(list)
        end
        
        function set:add_list(list)
-               for _, item in ipairs(list) do
-                       items[item] = true;
+               if list then
+                       for _, item in ipairs(list) do
+                               items[item] = true;
+                       end
                end
        end