Merge 0.6->0.7
authorMatthew Wild <mwild1@gmail.com>
Wed, 24 Mar 2010 22:34:59 +0000 (22:34 +0000)
committerMatthew Wild <mwild1@gmail.com>
Wed, 24 Mar 2010 22:34:59 +0000 (22:34 +0000)
57 files changed:
core/actions.lua [deleted file]
core/componentmanager.lua
core/configmanager.lua
core/hostmanager.lua
core/loggingmanager.lua
core/modulemanager.lua
core/objectmanager.lua [deleted file]
core/rostermanager.lua
core/s2smanager.lua
core/sessionmanager.lua
core/stanza_router.lua
core/usermanager.lua
core/xmlhandlers.lua
net/adns.lua
net/connlisteners.lua
net/http.lua
net/httpclient_listener.lua
net/httpserver.lua
net/httpserver_listener.lua
net/server.lua
net/server_select.lua
net/xmppclient_listener.lua
net/xmppcomponent_listener.lua
net/xmppserver_listener.lua
plugins/mod_actions_http.lua [deleted file]
plugins/mod_bosh.lua
plugins/mod_component.lua
plugins/mod_console.lua
plugins/mod_disco.lua
plugins/mod_groups.lua
plugins/mod_httpserver.lua
plugins/mod_iq.lua
plugins/mod_legacyauth.lua
plugins/mod_pep.lua
plugins/mod_posix.lua
plugins/mod_presence.lua
plugins/mod_privacy.lua
plugins/mod_roster.lua
plugins/mod_saslauth.lua
plugins/mod_tls.lua
plugins/mod_xmlrpc.lua [deleted file]
plugins/muc/mod_muc.lua
plugins/muc/muc.lib.lua
prosody
prosody.cfg.lua.dist
tests/test.lua
util-src/pposix.c
util/datamanager.lua
util/dependencies.lua
util/events.lua
util/hmac.lua
util/pluginloader.lua
util/prosodyctl.lua
util/sasl.lua
util/stanza.lua
util/timer.lua
util/xmppstream.lua

diff --git a/core/actions.lua b/core/actions.lua
deleted file mode 100644 (file)
index ee5abc2..0000000
+++ /dev/null
@@ -1,27 +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.
---
-
-\r
-local actions = {};\r
-\r
-function register(path, t)\r
-       local curr = actions;\r
-       for comp in path:gmatch("([^/]+)/") do\r
-               if curr[comp] == nil then\r
-                       curr[comp] = {};\r
-               end\r
-               curr = curr[comp];\r
-               if type(curr) ~= "table" then\r
-                       return nil, "path-taken";\r
-               end\r
-       end\r
-       curr[path:match("/([^/]+)$")] = t;\r
-       return true;\r
-end\r
-\r
-return { actions = actions, register= register };
\ No newline at end of file
index 660d26feef6a07a3516b371c9abef7ed69ee320d..48e279846ce2d800a6726337bcb84f3336cf7ccc 100644 (file)
@@ -8,6 +8,7 @@
 
 local prosody = _G.prosody;
 local log = require "util.logger".init("componentmanager");
+local certmanager = require "core.certmanager";
 local configmanager = require "core.configmanager";
 local modulemanager = require "core.modulemanager";
 local jid_split = require "util.jid".split;
@@ -16,6 +17,7 @@ local events_new = require "util.events".new;
 local st = require "util.stanza";
 local prosody, hosts = prosody, prosody.hosts;
 local ssl = ssl;
+local uuid_gen = require "util.uuid".generate;
 
 local pairs, setmetatable, type, tostring = pairs, setmetatable, type, tostring;
 
@@ -83,15 +85,16 @@ function create_component(host, component, events)
                if hosts[base_host] then
                        ssl_ctx = hosts[base_host].ssl_ctx;
                        ssl_ctx_in = hosts[base_host].ssl_ctx_in;
-               elseif prosody.global_ssl_ctx then
+               else
                        -- We have no cert, and no parent host to borrow a cert from
                        -- Use global/default cert if there is one
-                       ssl_ctx = ssl.newcontext(prosody.global_ssl_ctx);
-                       ssl_ctx_in = ssl.newcontext(setmetatable({ mode = "server" }, { __index = prosody.global_ssl_ctx }));
+                       ssl_ctx = certmanager.create_context(host, "client");
+                       ssl_ctx_in = certmanager.create_context(host, "server");
                end
        end
        return { type = "component", host = host, connected = true, s2sout = {}, 
-                       ssl_ctx = ssl_ctx, ssl_ctx_in = ssl_ctx_in, events = events or events_new() };
+                       ssl_ctx = ssl_ctx, ssl_ctx_in = ssl_ctx_in, events = events or events_new(),
+                       dialback_secret = configmanager.get(host, "core", "dialback_secret") or uuid_gen() };
 end
 
 function register_component(host, component, session)
@@ -100,12 +103,16 @@ function register_component(host, component, session)
 
                components[host] = component;
                hosts[host] = session or create_component(host, component, old_events);
-               
+
                -- Add events object if not already one
                if not hosts[host].events then
                        hosts[host].events = old_events or events_new();
                end
-               
+
+               if not hosts[host].dialback_secret then
+                       hosts[host].dialback_secret = configmanager.get(host, "core", "dialback_secret") or uuid_gen();
+               end
+
                -- add to disco_items
                if not(host:find("@", 1, true) or host:find("/", 1, true)) and host:find(".", 1, true) then
                        disco_items:set(host:sub(host:find(".", 1, true)+1), host, true);
index b974d2f245406c46253e385a1c202ecd184bc112..e7677df67e63ff2a4a48b2362cfecf3b14fc02c6 100644 (file)
@@ -68,7 +68,7 @@ function load(filename, format)
 
        if parsers[format] and parsers[format].load then
                local f, err = io.open(filename);
-               if f then 
+               if f then
                        local ok, err = parsers[format].load(f:read("*a"), filename);
                        f:close();
                        if ok then
@@ -95,6 +95,15 @@ function addparser(format, parser)
        end
 end
 
+-- _M needed to avoid name clash with local 'parsers'
+function _M.parsers()
+       local p = {};
+       for format in pairs(parsers) do
+               table.insert(p, format);
+       end
+       return p;
+end
+
 -- Built-in Lua parser
 do
        local loadstring, pcall, setmetatable = _G.loadstring, _G.pcall, _G.setmetatable;
index 445291d8ffc7b5fa0e8880cade372d5ebc789227..eb88044995d63b5cf036e9d1d3d258096ff93e3c 100644 (file)
@@ -9,20 +9,19 @@
 local ssl = ssl
 
 local hosts = hosts;
+local certmanager = require "core.certmanager";
 local configmanager = require "core.configmanager";
 local eventmanager = require "core.eventmanager";
 local modulemanager = require "core.modulemanager";
 local events_new = require "util.events".new;
 
+local uuid_gen = require "util.uuid".generate;
+
 if not _G.prosody.incoming_s2s then
        require "core.s2smanager";
 end
 local incoming_s2s = _G.prosody.incoming_s2s;
 
--- These are the defaults if not overridden in the config
-local default_ssl_ctx = { mode = "client", protocol = "sslv23", capath = "/etc/ssl/certs", verify = "none", options = "no_sslv2"; };
-local default_ssl_ctx_in = { mode = "server", protocol = "sslv23", capath = "/etc/ssl/certs", verify = "none", options = "no_sslv2"; };
-
 local log = require "util.logger".init("hostmanager");
 
 local pairs, setmetatable = pairs, setmetatable;
@@ -36,7 +35,7 @@ local function load_enabled_hosts(config)
        local activated_any_host;
        
        for host, host_config in pairs(defined_hosts) do
-               if host ~= "*" and (host_config.core.enabled == nil or host_config.core.enabled) and not host_config.core.component_module then
+               if host ~= "*" and host_config.core.enabled ~= false and not host_config.core.component_module then
                        activated_any_host = true;
                        activate(host, host_config);
                end
@@ -53,11 +52,12 @@ end
 eventmanager.add_event_hook("server-starting", load_enabled_hosts);
 
 function activate(host, host_config)
-       hosts[host] = {type = "local", connected = true, sessions = {}, 
-                      host = host, s2sout = {}, events = events_new(), 
-                      disallow_s2s = configmanager.get(host, "core", "disallow_s2s") 
-                        or (configmanager.get(host, "core", "anonymous_login") 
-                            and (configmanager.get(host, "core", "disallow_s2s") ~= false))
+       hosts[host] = {type = "local", connected = true, sessions = {},
+                       host = host, s2sout = {}, events = events_new(),
+                       disallow_s2s = configmanager.get(host, "core", "disallow_s2s")
+                               or (configmanager.get(host, "core", "anonymous_login")
+                               and (configmanager.get(host, "core", "disallow_s2s") ~= false));
+                       dialback_secret = configmanager.get(host, "core", "dialback_secret") or uuid_gen();
                      };
        for option_name in pairs(host_config.core) do
                if option_name:match("_ports$") then
@@ -65,14 +65,9 @@ function activate(host, host_config)
                end
        end
        
-       if ssl then
-               local ssl_config = host_config.core.ssl or configmanager.get("*", "core", "ssl");
-               if ssl_config then
-                       hosts[host].ssl_ctx = ssl.newcontext(setmetatable(ssl_config, { __index = default_ssl_ctx }));
-                       hosts[host].ssl_ctx_in = ssl.newcontext(setmetatable(ssl_config, { __index = default_ssl_ctx_in }));
-               end
-        end
-
+       hosts[host].ssl_ctx = certmanager.create_context(host, "client", host_config); -- for outgoing connections
+       hosts[host].ssl_ctx_in = certmanager.create_context(host, "server", host_config); -- for incoming connections
+       
        log((hosts_loaded_once and "info") or "debug", "Activated host: %s", host);
        eventmanager.fire_event("host-activated", host, host_config);
 end
index 9e6d38cbc5958f18836700013dc964e4f3614cf5..b329bf11c8e55a577424289570e397bd19885a50 100644 (file)
@@ -94,7 +94,7 @@ function apply_sink_rules(sink_type)
                        end
                end
        elseif type(logging_config) == "string" and (not logging_config:match("^%*")) and sink_type == "file" then
-               -- User specified simply a filename, and the "file" sink type 
+               -- User specified simply a filename, and the "file" sink type
                -- was just added
                for _, sink_config in pairs(default_file_logging) do
                        sink_config.filename = logging_config;
@@ -128,7 +128,7 @@ function get_levels(criteria, set)
                                return set;
                        elseif in_range then
                                set[level] = true;
-                       end     
+                       end
                end
        end
        
@@ -161,12 +161,12 @@ function log_sink_types.stdout()
                if timestamps then
                        io_write(os_date(timestamps), " ");
                end
-               if ... then 
+               if ... then
                        io_write(name, rep(" ", sourcewidth-namelen), level, "\t", format(message, ...), "\n");
                else
                        io_write(name, rep(" ", sourcewidth-namelen), level, "\t", message, "\n");
                end
-       end     
+       end
 end
 
 do
@@ -197,7 +197,7 @@ do
                        if timestamps then
                                io_write(os_date(timestamps), " ");
                        end
-                       if ... then 
+                       if ... then
                                io_write(name, rep(" ", sourcewidth-namelen), getstring(logstyles[level], level), "\t", format(message, ...), "\n");
                        else
                                io_write(name, rep(" ", sourcewidth-namelen), getstring(logstyles[level], level), "\t", message, "\n");
@@ -237,7 +237,7 @@ function log_sink_types.file(config)
                if timestamps then
                        write(logfile, os_date(timestamps), " ");
                end
-               if ... then 
+               if ... then
                        write(logfile, name, "\t", level, "\t", format(message, ...), "\n");
                else
                        write(logfile, name, "\t" , level, "\t", message, "\n");
index 4d4468a73b3b061b26cf7b1e2e17ba256e762ab8..2f89b4430afe25198453ca81c8a7ccdf7c789d76 100644 (file)
@@ -13,7 +13,6 @@ local log = logger.init("modulemanager");
 local eventmanager = require "core.eventmanager";
 local config = require "core.configmanager";
 local multitable_new = require "util.multitable".new;
-local register_actions = require "core.actions".register;
 local st = require "util.stanza";
 local pluginloader = require "util.pluginloader";
 
@@ -28,7 +27,9 @@ local type = type;
 local next = next;
 local rawget = rawget;
 local error = error;
-local tostring = tostring;
+local tostring, tonumber = tostring, tonumber;
+
+local array, set = require "util.array", require "util.set";
 
 local autoload_modules = {"presence", "message", "iq"};
 
@@ -126,6 +127,7 @@ function load(host, module_name, config)
        local api_instance = setmetatable({ name = module_name, host = host, config = config,  _log = _log, log = function (self, ...) return _log(...); end }, { __index = api });
 
        local pluginenv = setmetatable({ module = api_instance }, { __index = _G });
+       api_instance.environment = pluginenv;
        
        setfenv(mod, pluginenv);
        if not hosts[host] then
@@ -156,6 +158,7 @@ function load(host, module_name, config)
                log("error", "Error initializing module '%s' on '%s': %s", module_name, host, err or "nil");
        end
        if success then
+               (hosts[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);
@@ -172,7 +175,7 @@ function is_loaded(host, name)
 end
 
 function unload(host, name, ...)
-       local mod = get_module(host, name); 
+       local mod = get_module(host, name);
        if not mod then return nil, "module-not-loaded"; end
        
        if module_has_method(mod, "unload") then
@@ -207,6 +210,7 @@ function unload(host, name, ...)
                end
        end
        modulemap[host][name] = nil;
+       (hosts[host] or prosody).events.fire_event("module-unloaded", { module = name, host = host });
        return true;
 end
 
@@ -287,7 +291,7 @@ function module_has_method(module, method)
 end
 
 function call_module_method(module, method, ...)
-       if module_has_method(module, method) then       
+       if module_has_method(module, method) then
                local f = module.module[method];
                return pcall(f, ...);
        else
@@ -296,7 +300,7 @@ function call_module_method(module, method, ...)
 end
 
 ----- API functions exposed to modules -----------
--- Must all be in api.* 
+-- Must all be in api.*
 
 -- Returns the name of the current module
 function api:get_name()
@@ -394,7 +398,7 @@ function api:require(lib)
                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, setmetatable({ module = self }, { __index = _G }));
+       setfenv(f, self.environment);
        return f();
 end
 
@@ -409,6 +413,85 @@ function api:get_option(name, default_value)
        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 t_remove = _G.table.remove;
 local module_items = multitable_new();
 function api:add_item(key, value)
@@ -449,19 +532,4 @@ function api:get_host_items(key)
        return result;
 end
 
---------------------------------------------------------------------
-
-local actions = {};
-
-function actions.load(params)
-       --return true, "Module loaded ("..params.module.." on "..params.host..")";
-       return load(params.host, params.module);
-end
-
-function actions.unload(params)
-       return unload(params.host, params.module);
-end
-
-register_actions("/modules", actions);
-
 return _M;
diff --git a/core/objectmanager.lua b/core/objectmanager.lua
deleted file mode 100644 (file)
index 684ed80..0000000
+++ /dev/null
@@ -1,68 +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.
---
-
-\r
-local new_multitable = require "util.multitable".new;\r
-local t_insert = table.insert;\r
-local t_concat = table.concat;\r
-local tostring = tostring;\r
-local unpack = unpack;\r
-local pairs = pairs;\r
-local error = error;\r
-local type = type;\r
-local _G = _G;\r
-\r
-local data = new_multitable();\r
-\r
-module "objectmanager"\r
-\r
-function set(...)\r
-       return data:set(...);\r
-end\r
-function remove(...)\r
-       return data:remove(...);\r
-end\r
-function get(...)\r
-       return data:get(...);\r
-end\r
-\r
-local function get_path(path)\r
-       if type(path) == "table" then return path; end\r
-       local s = {};\r
-       for part in tostring(path):gmatch("[%w_]+") do\r
-               t_insert(s, part);\r
-       end\r
-       return s;\r
-end\r
-\r
-function get_object(path)\r
-       path = get_path(path)\r
-       return data:get(unpack(path)), path;\r
-end\r
-function set_object(path, object)\r
-       path = get_path(path);\r
-       data:set(unpack(path), object);\r
-end\r
-\r
-data:set("ls", function(_dir)\r
-       local obj, dir = get_object(_dir);\r
-       if not obj then error("object not found: " .. t_concat(dir, '/')); end\r
-       local r = {};\r
-       if type(obj) == "table" then\r
-               for key, val in pairs(obj) do\r
-                       r[key] = type(val);\r
-               end\r
-       end\r
-       return r;\r
-end);\r
-data:set("get", get_object);\r
-data:set("set", set_object);\r
-data:set("echo", function(...) return {...}; end);\r
-data:set("_G", _G);\r
-\r
-return _M;\r
index 08ab35655b07dc04f306149baa1eb9bf33900bb9..e2a92696391063c72c55026e47db68d1871602e5 100644 (file)
@@ -114,8 +114,14 @@ function save_roster(username, host, roster)
                --end
        end
        if roster then
-               if not roster[false] then roster[false] = {}; end
-               roster[false].version = (roster[false].version or 0) + 1;
+               local metadata = roster[false];
+               if not metadata then
+                       metadata = {};
+                       roster[false] = metadata;
+               end
+               if metadata.version ~= true then
+                       metadata.version = (metadata.version or 0) + 1;
+               end
                return datamanager.store(username, host, "roster", roster);
        end
        log("warn", "save_roster: user had no roster to save");
index 3627661a83f601174f280c5c182c0702899adef4..053dbbe773d290ff227587bbaba758de777715a9 100644 (file)
@@ -16,8 +16,10 @@ 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, error, tonumber
-    = tostring, pairs, ipairs, getmetatable, newproxy, error, tonumber;
+local tostring, pairs, ipairs, getmetatable, newproxy, error, tonumber,
+      setmetatable
+    = tostring, pairs, ipairs, getmetatable, newproxy, error, tonumber,
+      setmetatable;
 
 local idna_to_ascii = require "util.encodings".idna.to_ascii;
 local connlisteners_get = require "net.connlisteners".get;
@@ -36,8 +38,6 @@ local log = logger_init("s2smanager");
 
 local sha256_hash = require "util.hashes".sha256;
 
-local dialback_secret = uuid_gen();
-
 local adns, dns = require "net.adns", require "net.dns";
 local config = require "core.configmanager";
 local connect_timeout = config.get("*", "core", "s2s_timeout") or 60;
@@ -137,7 +137,7 @@ function new_incoming(conn)
        open_sessions = open_sessions + 1;
        local w, log = conn.write, logger_init("s2sin"..tostring(conn):match("[a-f0-9]+$"));
        session.log = log;
-       session.sends2s = function (t) log("debug", "sending: %s", tostring(t)); w(tostring(t)); end
+       session.sends2s = function (t) log("debug", "sending: %s", t.top_tag and t:top_tag() or t:match("^([^>]*>?)")); w(conn, tostring(t)); end
        incoming_s2s[session] = true;
        add_task(connect_timeout, function ()
                if session.conn ~= conn or
@@ -145,16 +145,17 @@ function new_incoming(conn)
                        return; -- Ok, we're connect[ed|ing]
                end
                -- Not connected, need to close session and clean up
-               (session.log or log)("warn", "Destroying incomplete session %s->%s due to inactivity", 
+               (session.log or log)("warn", "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)
-               local host_session = { to_host = to_host, from_host = from_host, host = from_host, 
-                                      notopen = true, type = "s2sout_unauthed", direction = "outgoing" };
+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;
                
@@ -165,10 +166,12 @@ function new_outgoing(from_host, to_host)
                        host_session.log = log;
                end
                
-               -- Kick the connection attempting machine
-               attempt_connection(host_session);
+               if connect ~= false then
+                       -- Kick the connection attempting machine into life
+                       attempt_connection(host_session);
+               end
                
-               if not host_session.sends2s then                
+               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
@@ -182,7 +185,6 @@ function new_outgoing(from_host, to_host)
                                buffer[#buffer+1] = data;
                                log("debug", "Buffered item %d: %s", #buffer, tostring(data));
                        end
-                       
                end
 
                return host_session;
@@ -298,7 +300,7 @@ function try_connect(host_session, connect_host, connect_port)
                        adns.cancel(handle, true);
                end
        end);
-               
+       
        return true;
 end
 
@@ -323,7 +325,7 @@ function make_connect(host_session, connect_host, connect_port)
        end
        
        local cl = connlisteners_get("xmppserver");
-       conn = wrapclient(conn, connect_host, connect_port, cl, cl.default_mode or 1, hosts[from_host].ssl_ctx, false );
+       conn = wrapclient(conn, connect_host, connect_port, cl, cl.default_mode or 1 );
        host_session.conn = conn;
        
        -- Register this outgoing connection so that xmppserver_listener knows about it
@@ -331,9 +333,10 @@ function make_connect(host_session, connect_host, connect_port)
        cl.register_outgoing(conn, host_session);
        
        local w, log = conn.write, host_session.log;
-       host_session.sends2s = function (t) log("debug", "sending: %s", tostring(t)); w(tostring(t)); end
+       host_session.sends2s = function (t) log("debug", "sending: %s", (t.top_tag and t:top_tag()) or t:match("^[^>]*>?")); w(conn, tostring(t)); end
+       
+       host_session:open_stream(from_host, to_host);
        
-       conn.write(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' xml:lang='en'>]], from_host, to_host));
        log("debug", "Connection attempt in progress...");
        add_task(connect_timeout, function ()
                if host_session.conn ~= conn or
@@ -342,13 +345,20 @@ function make_connect(host_session, connect_host, connect_port)
                        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.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
+
 function streamopened(session, attr)
        local send = session.sends2s;
        
@@ -372,13 +382,13 @@ function streamopened(session, attr)
                        return;
                end
                send("<?xml version='1.0'?>");
-               send(stanza("stream:stream", { xmlns='jabber:server', ["xmlns:db"]='jabber:server:dialback', 
+               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", { session = session, features = features });
+                               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
@@ -393,13 +403,13 @@ function streamopened(session, attr)
        
                -- Send unauthed buffer
                -- (stanzas which are fine to send before dialback)
-               -- Note that this is *not* the stanza queue (which 
+               -- 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(data);
+                               session.sends2s(tostring(data));
                                send_buffer[i] = nil;
                        end
                end
@@ -415,16 +425,12 @@ function streamopened(session, attr)
                        end
                end
        end
-
        session.notopen = nil;
 end
 
 function streamclosed(session)
-       (session.log or log)("debug", "</stream:stream>");
-       if session.sends2s then
-               session.sends2s("</stream:stream>");
-       end
-       session.notopen = true;
+       (session.log or log)("debug", "Received </stream:stream>");
+       session:close();
 end
 
 function initiate_dialback(session)
@@ -435,7 +441,7 @@ function initiate_dialback(session)
 end
 
 function generate_dialback(id, to, from)
-       return sha256_hash(id..to..from..dialback_secret, true);
+       return sha256_hash(id..to..from..hosts[from].dialback_secret, true);
 end
 
 function verify_dialback(id, to, from, key)
@@ -498,9 +504,32 @@ function mark_connected(session)
        end
 end
 
-local function null_data_handler(conn, data) log("debug", "Discarding data from destroyed s2s session: %s", data); end
+local resting_session = { -- Resting, not dead
+               destroyed = true;
+               type = "s2s_destroyed";
+               open_stream = function (session)
+                       session.log("debug", "Attempt to open stream on resting session");
+               end;
+               close = function (session)
+                       session.log("debug", "Attempt to close already-closed session");
+               end;
+       }; resting_session.__index = resting_session;
+
+function retire_session(session)
+       local log = session.log or log;
+       for k in pairs(session) do
+               if k ~= "trace" and k ~= "log" and k ~= "id" then
+                       session[k] = nil;
+               end
+       end
+
+       function session.send(data) log("debug", "Discarding data sent to resting session: %s", tostring(data)); end
+       function session.data(data) log("debug", "Discarding data received from resting session: %s", tostring(data)); end
+       return setmetatable(session, resting_session);
+end
 
 function destroy_session(session, reason)
+       if session.destroyed then return; end
        (session.log or log)("info", "Destroying "..tostring(session.direction).." session "..tostring(session.from_host).."->"..tostring(session.to_host));
        
        if session.direction == "outgoing" then
@@ -510,12 +539,7 @@ function destroy_session(session, reason)
                incoming_s2s[session] = nil;
        end
        
-       for k in pairs(session) do
-               if k ~= "trace" then
-                       session[k] = nil;
-               end
-       end
-       session.data = null_data_handler;
+       retire_session(session); -- Clean session until it is GC'd
 end
 
 return _M;
index ad4ad0c97c34a039cfe2b4d9e0787f46cf46c3c8..6e771a8444819c261e114f7dbd0d8461b1efbf24 100644 (file)
@@ -8,7 +8,7 @@
 
 
 
-local tonumber, tostring = tonumber, tostring;
+local tonumber, tostring, setmetatable = tonumber, tostring, setmetatable;
 local ipairs, pairs, print, next= ipairs, pairs, print, next;
 local format = import("string", "format");
 
@@ -50,8 +50,8 @@ function new_session(conn)
        open_sessions = open_sessions + 1;
        log("debug", "open sessions now: ".. open_sessions);
        local w = conn.write;
-       session.send = function (t) w(tostring(t)); end
-       session.ip = conn.ip();
+       session.send = function (t) w(conn, tostring(t)); end
+       session.ip = conn:ip();
        local conn_name = "c2s"..tostring(conn):match("[a-f0-9]+$");
        session.log = logger.init(conn_name);
        
@@ -66,31 +66,46 @@ function new_session(conn)
        return session;
 end
 
-local function null_data_handler(conn, data) log("debug", "Discarding data from destroyed c2s session: %s", data); end
+local resting_session = { -- Resting, not dead
+               destroyed = true;
+               type = "c2s_destroyed";
+               close = function (session)
+                       session.log("debug", "Attempt to close already-closed session");
+               end;
+       }; resting_session.__index = resting_session;
+
+function retire_session(session)
+       local log = session.log or log;
+       for k in pairs(session) do
+               if k ~= "trace" and k ~= "log" and k ~= "id" then
+                       session[k] = nil;
+               end
+       end
+
+       function session.send(data) log("debug", "Discarding data sent to resting session: %s", tostring(data)); end
+       function session.data(data) log("debug", "Discarding data received from resting session: %s", tostring(data)); end
+       return setmetatable(session, resting_session);
+end
 
 function destroy_session(session, err)
        (session.log or log)("info", "Destroying session for %s (%s@%s)", session.full_jid or "(unknown)", session.username or "(unknown)", session.host or "(unknown)");
+       if session.destroyed then return; end
        
        -- Remove session/resource from user's session list
        if session.full_jid then
-               hosts[session.host].events.fire_event("resource-unbind", {session=session, error=err});
-
                hosts[session.host].sessions[session.username].sessions[session.resource] = nil;
                full_sessions[session.full_jid] = nil;
-                       
+               
                if not next(hosts[session.host].sessions[session.username].sessions) then
                        log("debug", "All resources of %s are now offline", session.username);
                        hosts[session.host].sessions[session.username] = nil;
                        bare_sessions[session.username..'@'..session.host] = nil;
                end
+
+               hosts[session.host].events.fire_event("resource-unbind", {session=session, error=err});
        end
        
-       for k in pairs(session) do
-               if k ~= "trace" then
-                       session[k] = nil;
-               end
-       end
-       session.data = null_data_handler;
+       retire_session(session);
 end
 
 function make_authenticated(session, username)
@@ -168,7 +183,12 @@ end
 
 function streamopened(session, attr)
        local send = session.send;
-       session.host = attr.to or error("Client failed to specify destination hostname");
+       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();
@@ -193,6 +213,7 @@ function streamopened(session, attr)
        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);
@@ -200,8 +221,8 @@ function streamopened(session, attr)
 end
 
 function streamclosed(session)
-       session.send("</stream:stream>");
-       session.notopen = true;
+       session.log("debug", "Received </stream:stream>");
+       session:close();
 end
 
 function send_to_available_resources(user, host, stanza)
index f7e881e302c54f380bd249ec9f3bd38400ec72ef..0e04a4e56457f3a082ca1b80c074cf2fc220632f 100644 (file)
@@ -126,7 +126,7 @@ function core_post_stanza(origin, stanza, preevents)
        local node, host, resource = jid_split(to);
        local to_bare = node and (node.."@"..host) or host; -- bare JID
 
-       local to_type;
+       local to_type, to_self;
        if node then
                if resource then
                        to_type = '/full';
@@ -134,6 +134,7 @@ function core_post_stanza(origin, stanza, preevents)
                        to_type = '/bare';
                        if node == origin.username and host == origin.host then
                                stanza.attr.to = nil;
+                               to_self = true;
                        end
                end
        else
@@ -141,6 +142,7 @@ function core_post_stanza(origin, stanza, preevents)
                        to_type = '/host';
                else
                        to_type = '/bare';
+                       to_self = true;
                end
        end
 
@@ -151,6 +153,7 @@ function core_post_stanza(origin, stanza, preevents)
        local h = hosts[to_bare] or hosts[host or origin.host];
        if h then
                if h.events.fire_event(stanza.name..to_type, event_data) then return; end -- do processing
+               if to_self and h.events.fire_event(stanza.name..'/self', event_data) then return; end -- do processing
 
                if h.type == "component" then
                        component_handle_stanza(origin, stanza);
@@ -182,7 +185,7 @@ function core_route_stanza(origin, stanza)
                        local xmlns = stanza.attr.xmlns;
                        --stanza.attr.xmlns = "jabber:server";
                        stanza.attr.xmlns = nil;
-                       log("debug", "sending s2s stanza: %s", tostring(stanza));
+                       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
                        stanza.attr.xmlns = xmlns; -- reset
                else
index 6b19b65111b84d6ecc54c351b8b56bca0c89e3fd..efb2e750529c0db4d7bfe414293d9361dc211511 100644 (file)
@@ -14,11 +14,15 @@ local ipairs = ipairs;
 local hashes = require "util.hashes";
 local jid_bare = require "util.jid".bare;
 local config = require "core.configmanager";
+local hosts = hosts;
 
 module "usermanager"
 
+local function is_cyrus(host) return config.get(host, "core", "sasl_backend") == "cyrus"; end
+
 function validate_credentials(host, username, password, method)
        log("debug", "User '%s' is being validated", username);
+       if is_cyrus(host) then return nil, "Legacy auth not supported with Cyrus SASL."; end
        local credentials = datamanager.load(username, host, "accounts") or {};
 
        if method == nil then method = "PLAIN"; end
@@ -48,14 +52,17 @@ function validate_credentials(host, username, password, method)
 end
 
 function get_password(username, host)
-  return (datamanager.load(username, host, "accounts") or {}).password
+       if is_cyrus(host) then return nil, "Passwords unavailable for Cyrus SASL."; end
+       return (datamanager.load(username, host, "accounts") or {}).password
 end
 
 function user_exists(username, host)
+       if is_cyrus(host) then return true; end
        return datamanager.load(username, host, "accounts") ~= nil; -- FIXME also check for empty credentials
 end
 
 function create_user(username, password, host)
+       if is_cyrus(host) then return nil, "Account creation/modification not available with Cyrus SASL."; end
        return datamanager.store(username, host, "accounts", {password = password});
 end
 
index c66843058a1f42f66ea7f8d91f12d92e63a2e391..b7992f77a817cdf8e7bc279ec4f92a3a3c2768cb 100644 (file)
@@ -12,8 +12,6 @@ require "util.stanza"
 
 local st = stanza;
 local tostring = tostring;
-local pairs = pairs;
-local ipairs = ipairs;
 local t_insert = table.insert;
 local t_concat = table.concat;
 
@@ -24,103 +22,92 @@ local error = error;
 module "xmlhandlers"
 
 local ns_prefixes = {
-                                               ["http://www.w3.org/XML/1998/namespace"] = "xml";
-                               }
+       ["http://www.w3.org/XML/1998/namespace"] = "xml";
+};
+
+local xmlns_streams = "http://etherx.jabber.org/streams";
+
+local ns_separator = "\1";
+local ns_pattern = "^([^"..ns_separator.."]*)"..ns_separator.."?(.*)$";
 
 function init_xmlhandlers(session, stream_callbacks)
-               local ns_stack = { "" };
-               local curr_tag;
-               local chardata = {};
-               local xml_handlers = {};
-               local log = session.log or default_log;
-               
-               local cb_streamopened = stream_callbacks.streamopened;
-               local cb_streamclosed = stream_callbacks.streamclosed;
-               local cb_error = stream_callbacks.error or function (session, e) error("XML stream error: "..tostring(e)); end;
-               local cb_handlestanza = stream_callbacks.handlestanza;
-               
-               local stream_tag = stream_callbacks.stream_tag;
-               local stream_default_ns = stream_callbacks.default_ns;
-               
-               local stanza
-               function xml_handlers:StartElement(tagname, attr)
-                       if stanza and #chardata > 0 then
-                               -- We have some character data in the buffer
-                               stanza:text(t_concat(chardata));
-                               chardata = {};
-                       end
-                       local curr_ns,name = tagname:match("^([^\1]*)\1?(.*)$");
-                       if name == "" then
-                               curr_ns, name = "", curr_ns;
-                       end
+       local chardata = {};
+       local xml_handlers = {};
+       local log = session.log or default_log;
+       
+       local cb_streamopened = stream_callbacks.streamopened;
+       local cb_streamclosed = stream_callbacks.streamclosed;
+       local cb_error = stream_callbacks.error or function(session, e) error("XML stream error: "..tostring(e)); end;
+       local cb_handlestanza = stream_callbacks.handlestanza;
+       
+       local stream_ns = stream_callbacks.stream_ns or xmlns_streams;
+       local stream_tag = stream_ns..ns_separator..(stream_callbacks.stream_tag or "stream");
+       local stream_error_tag = stream_ns..ns_separator..(stream_callbacks.error_tag or "error");
+       
+       local stream_default_ns = stream_callbacks.default_ns;
+       
+       local stanza;
+       function xml_handlers:StartElement(tagname, attr)
+               if stanza and #chardata > 0 then
+                       -- We have some character data in the buffer
+                       stanza:text(t_concat(chardata));
+                       chardata = {};
+               end
+               local curr_ns,name = tagname:match(ns_pattern);
+               if name == "" then
+                       curr_ns, name = "", curr_ns;
+               end
 
-                       if curr_ns ~= stream_default_ns then
-                               attr.xmlns = curr_ns;
-                       end
-                       
-                       -- FIXME !!!!!
-                       for i=1,#attr do
-                               local k = attr[i];
-                               attr[i] = nil;
-                               local ns, nm = k:match("^([^\1]*)\1?(.*)$");
-                               if nm ~= "" then
-                                       ns = ns_prefixes[ns]; 
-                                       if ns then 
-                                               attr[ns..":"..nm] = attr[k];
-                                               attr[k] = nil;
-                                       end
-                               end
-                       end
-                       
-                       if not stanza then --if we are not currently inside a stanza
-                               if session.notopen then
-                                       if tagname == stream_tag then
-                                               if cb_streamopened then
-                                                       cb_streamopened(session, attr);
-                                               end
-                                       else
-                                               -- Garbage before stream?
-                                               cb_error(session, "no-stream");
-                                       end
-                                       return;
-                               end
-                               if curr_ns == "jabber:client" and name ~= "iq" and name ~= "presence" and name ~= "message" then
-                                       cb_error(session, "invalid-top-level-element");
-                               end
-                               
-                               stanza = st.stanza(name, attr);
-                               curr_tag = stanza;
-                       else -- we are inside a stanza, so add a tag
-                               attr.xmlns = nil;
-                               if curr_ns ~= stream_default_ns then
-                                       attr.xmlns = curr_ns;
-                               end
-                               stanza:tag(name, attr);
-                       end
+               if curr_ns ~= stream_default_ns then
+                       attr.xmlns = curr_ns;
                end
-               function xml_handlers:CharacterData(data)
-                       if stanza then
-                               t_insert(chardata, data);
+               
+               -- FIXME !!!!!
+               for i=1,#attr do
+                       local k = attr[i];
+                       attr[i] = nil;
+                       local ns, nm = k:match(ns_pattern);
+                       if nm ~= "" then
+                               ns = ns_prefixes[ns]; 
+                               if ns then 
+                                       attr[ns..":"..nm] = attr[k];
+                                       attr[k] = nil;
+                               end
                        end
                end
-               function xml_handlers:EndElement(tagname)
-                       local curr_ns,name = tagname:match("^([^\1]*)\1?(.*)$");
-                       if name == "" then
-                               curr_ns, name = "", curr_ns;
-                       end
-                       if (not stanza) or (#stanza.last_add > 0 and name ~= stanza.last_add[#stanza.last_add].name) then 
+               
+               if not stanza then --if we are not currently inside a stanza
+                       if session.notopen then
                                if tagname == stream_tag then
-                                       if cb_streamclosed then
-                                               cb_streamclosed(session);
+                                       if cb_streamopened then
+                                               cb_streamopened(session, attr);
                                        end
-                               elseif name == "error" then
-                                       cb_error(session, "stream-error", stanza);
                                else
-                                       cb_error(session, "parse-error", "unexpected-element-close", name);
+                                       -- Garbage before stream?
+                                       cb_error(session, "no-stream");
                                end
-                               stanza, chardata = nil, {};
                                return;
                        end
+                       if curr_ns == "jabber:client" and name ~= "iq" and name ~= "presence" and name ~= "message" then
+                               cb_error(session, "invalid-top-level-element");
+                       end
+                       
+                       stanza = st.stanza(name, attr);
+               else -- we are inside a stanza, so add a tag
+                       attr.xmlns = nil;
+                       if curr_ns ~= stream_default_ns then
+                               attr.xmlns = curr_ns;
+                       end
+                       stanza:tag(name, attr);
+               end
+       end
+       function xml_handlers:CharacterData(data)
+               if stanza then
+                       t_insert(chardata, data);
+               end
+       end
+       function xml_handlers:EndElement(tagname)
+               if stanza then
                        if #chardata > 0 then
                                -- We have some character data in the buffer
                                stanza:text(t_concat(chardata));
@@ -128,12 +115,30 @@ function init_xmlhandlers(session, stream_callbacks)
                        end
                        -- Complete stanza
                        if #stanza.last_add == 0 then
-                               cb_handlestanza(session, stanza);
+                               if tagname ~= stream_error_tag then
+                                       cb_handlestanza(session, stanza);
+                               else
+                                       cb_error(session, "stream-error", stanza);
+                               end
                                stanza = nil;
                        else
                                stanza:up();
                        end
+               else
+                       if tagname == stream_tag then
+                               if cb_streamclosed then
+                                       cb_streamclosed(session);
+                               end
+                       else
+                               local curr_ns,name = tagname:match(ns_pattern);
+                               if name == "" then
+                                       curr_ns, name = "", curr_ns;
+                               end
+                               cb_error(session, "parse-error", "unexpected-element-close", name);
+                       end
+                       stanza, chardata = nil, {};
                end
+       end
        return xml_handlers;
 end
 
index 5d5a2256414bf2ee9f90d9a9f12e2b1a40f144c2..88d4b4b39c4c1e2b71e0b9c36424f2b998ad1700 100644 (file)
@@ -14,6 +14,8 @@ local log = require "util.logger".init("adns");
 local t_insert, t_remove = table.insert, table.remove;
 local coroutine, tostring, pcall = coroutine, tostring, pcall;
 
+local function dummy_send(sock, data, i, j) return (j-i)+1; end
+
 module "adns"
 
 function lookup(handler, qname, qtype, qclass)
@@ -43,35 +45,39 @@ function cancel(handle, call_handler)
 end
 
 function new_async_socket(sock, resolver)
-       local newconn, peername = {}, "<unknown>";
+       local peername = "<unknown>";
        local listener = {};
-       function listener.incoming(conn, data)
-               dns.feed(sock, data);
-       end
-       function listener.disconnect(conn, err)
-               log("warn", "DNS socket for %s disconnected: %s", peername, err);
-               local servers = resolver.server;
-               if resolver.socketset[newconn.handler] == resolver.best_server and resolver.best_server == #servers then
-                       log("error", "Exhausted all %d configured DNS servers, next lookup will try %s again", #servers, servers[1]);
+       local handler = {};
+       function listener.onincoming(conn, data)
+               if data then
+                       dns.feed(handler, data);
                end
+       end
+       function listener.ondisconnect(conn, err)
+               if err then
+                       log("warn", "DNS socket for %s disconnected: %s", peername, err);
+                       local servers = resolver.server;
+                       if resolver.socketset[conn] == resolver.best_server and resolver.best_server == #servers then
+                               log("error", "Exhausted all %d configured DNS servers, next lookup will try %s again", #servers, servers[1]);
+                       end
                
-               resolver:servfail(conn); -- Let the magic commence
+                       resolver:servfail(conn); -- Let the magic commence
+               end
        end
-       newconn.handler, newconn._socket = server.wrapclient(sock, "dns", 53, listener);
-       if not newconn.handler then
+       handler = server.wrapclient(sock, "dns", 53, listener);
+       if not handler then
                log("warn", "handler is nil");
        end
-       if not newconn._socket then
-               log("warn", "socket is nil");
-       end
-       newconn.handler.settimeout = function () end
-       newconn.handler.setsockname = function (_, ...) return sock:setsockname(...); end
-       newconn.handler.setpeername = function (_, ...) peername = (...); local ret = sock:setpeername(...); _.setsend(sock.send); return ret; end
-       newconn.handler.connect = function (_, ...) return sock:connect(...) end
-       newconn.handler.send = function (_, data) _.write(data); return _.sendbuffer(); end
-       return newconn.handler;
+       
+       handler.settimeout = function () end
+       handler.setsockname = function (_, ...) return sock:setsockname(...); end
+       handler.setpeername = function (_, ...) peername = (...); local ret = sock:setpeername(...); _:set_send(dummy_send); return ret; end
+       handler.connect = function (_, ...) return sock:connect(...) end
+       --handler.send = function (_, data) _:write(data);  return _.sendbuffer and _.sendbuffer(); end
+       handler.send = function (_, data) return sock:send(data); end
+       return handler;
 end
 
-dns:socket_wrapper_set(new_async_socket);
+dns.socket_wrapper_set(new_async_socket);
 
 return _M;
index 2bc591ac840618d1c629a5dcbe773c5287a7524b..93dce8b3aecb5c179c2bb09e7ee6a41c325af6ef 100644 (file)
@@ -53,17 +53,17 @@ function start(name, udata)
                error("No such connection module: "..name.. (err and (" ("..err..")") or ""), 0);
        end
        
-       if udata then
-               if (udata.type == "ssl" or udata.type == "tls") and not udata.ssl then
-                       error("No SSL context supplied for a "..tostring(udata.type):upper().." connection!", 0);
-               elseif udata.ssl and udata.type == "tcp" then
-                       error("SSL context supplied for a TCP connection!", 0);
-               end
+       local interface = (udata and udata.interface) or h.default_interface or "*";
+       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
        
-       return server.addserver(h, 
-                       (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), 
-                               (udata and udata.interface) or h.default_interface or "*", (udata and udata.mode) or h.default_mode or 1, (udata and udata.ssl) or nil, 99999999, udata and udata.type == "ssl");
+       return server.addserver(interface, port, h, mode, autossl and ssl or nil);
 end
 
 return _M;
index 097771654760d11a88d4fbad0731984dd63fadda..0634d77391b72015f90e40571a60844c0776d776 100644 (file)
@@ -17,11 +17,10 @@ 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 tonumber, tostring, pairs, xpcall, select, debug_traceback, char, format = 
+local tonumber, tostring, pairs, xpcall, select, debug_traceback, char, format =
         tonumber, tostring, pairs, xpcall, select, debug.traceback, string.char, string.format;
 
 local log = require "util.logger".init("http");
-local print = function () end
 
 module "http"
 
@@ -51,7 +50,7 @@ local function request_reader(request, data, startpos)
                return;
        end
        if request.state == "body" and request.state ~= "completed" then
-               print("Reading body...")
+               log("debug", "Reading body...")
                if not request.body then request.body = {}; request.havebodylength, request.bodylength = 0, tonumber(request.responseheaders["content-length"]); end
                if startpos then
                        data = data:sub(startpos, -1)
@@ -68,11 +67,11 @@ local function request_reader(request, data, startpos)
                                request.body = nil;
                                request.state = "completed";
                        else
-                               print("", "Have "..request.havebodylength.." bytes out of "..request.bodylength);
+                               log("debug", "Have "..request.havebodylength.." bytes out of "..request.bodylength);
                        end
                end
        elseif request.state == "headers" then
-               print("Reading headers...")
+               log("debug", "Reading headers...")
                local pos = startpos;
                local headers, headers_complete = request.responseheaders;
                if not headers then
@@ -84,12 +83,12 @@ local function request_reader(request, data, startpos)
                        local k, v = line:match("(%S+): (.+)");
                        if k and v then
                                headers[k:lower()] = v;
-                               --print("Header: "..k:lower().." = "..v);
+                               --log("debug", "Header: "..k:lower().." = "..v);
                        elseif #line == 0 then
                                headers_complete = true;
                                break;
                        else
-                               print("Unhandled header line: "..line);
+                               log("warn", "Unhandled header line: "..line);
                        end
                end
                if not headers_complete then return; end
@@ -103,7 +102,7 @@ local function request_reader(request, data, startpos)
                        return request_reader(request, data, startpos);
                end
        elseif request.state == "status" then
-               print("Reading status...")
+               log("debug", "Reading status...")
                local http, code, text, linelen = data:match("^HTTP/(%S+) (%d+) (.-)\r\n()", startpos);
                code = tonumber(code);
                if not code then
@@ -165,7 +164,7 @@ function request(u, ex, callback)
        end
        
        req.handler, req.conn = server.wrapclient(socket.tcp(), req.host, req.port or 80, listener, "*a");
-       req.write = req.handler.write;
+       req.write = function (...) return req.handler:write(...); end
        req.conn:settimeout(0);
        local ok, err = req.conn:connect(req.host, req.port or 80);
        if not ok and err ~= "timeout" then
@@ -212,8 +211,9 @@ end
 
 function destroy_request(request)
        if request.conn then
-               request.handler.close()
-               listener.disconnect(request.handler, "closed");
+               request.conn = nil;
+               request.handler:close()
+               listener.ondisconnect(request.handler, "closed");
        end
 end
 
index 0fbcd9285740989bce5fe1e3c04ee8676ffef65b..dfa250623af523d18c4950608c42cfe043afd78f 100644 (file)
@@ -15,7 +15,7 @@ local buffers = {}; -- Buffers of partial lines
 
 local httpclient = { default_port = 80, default_mode = "*a" };
 
-function httpclient.listener(conn, data)
+function httpclient.onincoming(conn, data)
        local request = requests[conn];
 
        if not request then
@@ -28,7 +28,7 @@ function httpclient.listener(conn, data)
        end
 end
 
-function httpclient.disconnect(conn, err)
+function httpclient.ondisconnect(conn, err)
        local request = requests[conn];
        if request and err ~= "closed" then
                request:reader(nil);
index d3f6a276dbfd13737b55ebad154b0c787783de42..59ddbb12494d915ec1d39e52c9ed89799028cb1d 100644 (file)
@@ -39,40 +39,35 @@ local function send_response(request, response)
        if response.body or response.headers then
                local body = response.body and tostring(response.body);
                log("debug", "Sending response to %s", request.id);
-               resp = { "HTTP/1.0 ", response.status or "200 OK", "\r\n"};
+               resp = { "HTTP/1.0 "..(response.status or "200 OK").."\r\n" };
                local h = response.headers;
                if h then
                        for k, v in pairs(h) do
-                               t_insert(resp, k);
-                               t_insert(resp, ": ");
-                               t_insert(resp, v);
-                               t_insert(resp, "\r\n");
+                               t_insert(resp, k..": "..v.."\r\n");
                        end
                end
                if body and not (h and h["Content-Length"]) then
-                       t_insert(resp, "Content-Length: ");
-                       t_insert(resp, #body);
-                       t_insert(resp, "\r\n");
+                       t_insert(resp, "Content-Length: "..#body.."\r\n");
                end
                t_insert(resp, "\r\n");
                
                if body and request.method ~= "HEAD" then
                        t_insert(resp, body);
                end
+               request.write(t_concat(resp));
        else
                -- Response we have is just a string (the body)
                log("debug", "Sending 200 response to %s", request.id or "<none>");
                
-               resp = { "HTTP/1.0 200 OK\r\n" };
-               t_insert(resp, "Connection: close\r\n");
-               t_insert(resp, "Content-Type: text/html\r\n");
-               t_insert(resp, "Content-Length: ");
-               t_insert(resp, #response);
-               t_insert(resp, "\r\n\r\n");
+               local resp = "HTTP/1.0 200 OK\r\n"
+                       .. "Connection: close\r\n"
+                       .. "Content-Type: text/html\r\n"
+                       .. "Content-Length: "..#response.."\r\n"
+                       .. "\r\n"
+                       .. response;
                
-               t_insert(resp, response);
+               request.write(resp);
        end
-       request.write(t_concat(resp));
        if not request.stayopen then
                request:destroy();
        end
@@ -193,7 +188,7 @@ local function request_reader(request, data, startpos)
                
                request.url = url_parse(request.path);
                
-               log("debug", method.." request for "..tostring(request.path) .. " on port "..request.handler.serverport());
+               log("debug", method.." request for "..tostring(request.path) .. " on port "..request.handler:serverport());
                
                if request.onlystatus then
                        if not call_callback(request) then
@@ -211,17 +206,17 @@ end
 
 -- The default handler for requests
 default_handler = function (method, body, request)
-       log("debug", method.." request for "..tostring(request.path) .. " on port "..request.handler.serverport());
-       return { status = "404 Not Found", 
+       log("debug", method.." request for "..tostring(request.path) .. " on port "..request.handler:serverport());
+       return { status = "404 Not Found",
                        headers = { ["Content-Type"] = "text/html" },
                        body = "<html><head><title>Page Not Found</title></head><body>Not here :(</body></html>" };
 end
 
 
 function new_request(handler)
-       return { handler = handler, conn = handler.socket, 
-                       write = handler.write, state = "request", 
-                       server = http_servers[handler.serverport()],
+       return { handler = handler, conn = handler,
+                       write = function (...) return handler:write(...); end, state = "request",
+                       server = http_servers[handler:serverport()],
                        send = send_response,
                        destroy = destroy_request,
                        id = tostring{}:match("%x+$")
@@ -239,9 +234,9 @@ function destroy_request(request)
                else
                        log("debug", "Request has no destroy callback");
                end
-               request.handler.close()
+               request.handler:close()
                if request.conn then
-                       listener.disconnect(request.handler, "closed");
+                       listener.ondisconnect(request.conn, "closed");
                end
        end
 end
index f673de77904d6148f02333e72619a42e9b5a8805..84016033ba119f42b14b47f9d2b1977c6383d0dd 100644 (file)
@@ -16,7 +16,7 @@ local requests = {}; -- Open requests
 
 local httpserver = { default_port = 80, default_mode = "*a" };
 
-function httpserver.listener(conn, data)
+function httpserver.onincoming(conn, data)
        local request = requests[conn];
 
        if not request then
@@ -24,7 +24,7 @@ function httpserver.listener(conn, data)
                requests[conn] = request;
                
                -- If using HTTPS, request is secure
-               if conn.ssl() then
+               if conn:ssl() then
                        request.secure = true;
                end
        end
@@ -34,7 +34,7 @@ function httpserver.listener(conn, data)
        end
 end
 
-function httpserver.disconnect(conn, err)
+function httpserver.ondisconnect(conn, err)
        local request = requests[conn];
        if request and not request.destroyed then
                request.conn = nil;
index 43a917580c419a67d131c003c8c30562dc292792..e0d4b85a971e6131c74d7fd7d872d95c917eb180 100644 (file)
--- \r
--- server.lua by blastbeat of the luadch project\r
--- Re-used here under the MIT/X Consortium License\r
--- \r
--- Modifications (C) 2008-2010 Matthew Wild, Waqas Hussain\r
---\r
-\r
--- // wrapping luadch stuff // --\r
-\r
-local use = function( what )\r
-    return _G[ what ]\r
-end\r
-local clean = function( tbl )\r
-    for i, k in pairs( tbl ) do\r
-        tbl[ i ] = nil\r
-    end\r
-end\r
-\r
-local log, table_concat = require ("util.logger").init("socket"), table.concat;\r
-local out_put = function (...) return log("debug", table_concat{...}); end\r
-local out_error = function (...) return log("warn", table_concat{...}); end\r
-local mem_free = collectgarbage\r
-\r
-----------------------------------// DECLARATION //--\r
-\r
---// constants //--\r
-\r
-local STAT_UNIT = 1    -- byte\r
-\r
---// lua functions //--\r
-\r
-local type = use "type"\r
-local pairs = use "pairs"\r
-local ipairs = use "ipairs"\r
-local tostring = use "tostring"\r
-local collectgarbage = use "collectgarbage"\r
-\r
---// lua libs //--\r
-\r
-local os = use "os"\r
-local table = use "table"\r
-local string = use "string"\r
-local coroutine = use "coroutine"\r
-\r
---// lua lib methods //--\r
-\r
-local os_time = os.time\r
-local os_difftime = os.difftime\r
-local table_concat = table.concat\r
-local table_remove = table.remove\r
-local string_len = string.len\r
-local string_sub = string.sub\r
-local coroutine_wrap = coroutine.wrap\r
-local coroutine_yield = coroutine.yield\r
-\r
---// extern libs //--\r
-\r
-local luasec = select( 2, pcall( require, "ssl" ) )\r
-local luasocket = require "socket"\r
-\r
---// extern lib methods //--\r
-\r
-local ssl_wrap = ( luasec and luasec.wrap )\r
-local socket_bind = luasocket.bind\r
-local socket_sleep = luasocket.sleep\r
-local socket_select = luasocket.select\r
-local ssl_newcontext = ( luasec and luasec.newcontext )\r
-\r
---// functions //--\r
-\r
-local id\r
-local loop\r
-local stats\r
-local idfalse\r
-local addtimer\r
-local closeall\r
-local addserver\r
-local getserver\r
-local wrapserver\r
-local getsettings\r
-local closesocket\r
-local removesocket\r
-local removeserver\r
-local changetimeout\r
-local wrapconnection\r
-local changesettings\r
-\r
---// tables //--\r
-\r
-local _server\r
-local _readlist\r
-local _timerlist\r
-local _sendlist\r
-local _socketlist\r
-local _closelist\r
-local _readtimes\r
-local _writetimes\r
-\r
---// simple data types //--\r
-\r
-local _\r
-local _readlistlen\r
-local _sendlistlen\r
-local _timerlistlen\r
-\r
-local _sendtraffic\r
-local _readtraffic\r
-\r
-local _selecttimeout\r
-local _sleeptime\r
-\r
-local _starttime\r
-local _currenttime\r
-\r
-local _maxsendlen\r
-local _maxreadlen\r
-\r
-local _checkinterval\r
-local _sendtimeout\r
-local _readtimeout\r
-\r
-local _cleanqueue\r
-\r
-local _timer\r
-\r
-local _maxclientsperserver\r
-\r
-----------------------------------// DEFINITION //--\r
-\r
-_server = { }    -- key = port, value = table; list of listening servers\r
-_readlist = { }    -- array with sockets to read from\r
-_sendlist = { }    -- arrary with sockets to write to\r
-_timerlist = { }    -- array of timer functions\r
-_socketlist = { }    -- key = socket, value = wrapped socket (handlers)\r
-_readtimes = { }   -- key = handler, value = timestamp of last data reading\r
-_writetimes = { }   -- key = handler, value = timestamp of last data writing/sending\r
-_closelist = { }    -- handlers to close\r
-\r
-_readlistlen = 0    -- length of readlist\r
-_sendlistlen = 0    -- length of sendlist\r
-_timerlistlen = 0    -- lenght of timerlist\r
-\r
-_sendtraffic = 0    -- some stats\r
-_readtraffic = 0\r
-\r
-_selecttimeout = 1    -- timeout of socket.select\r
-_sleeptime = 0    -- time to wait at the end of every loop\r
-\r
-_maxsendlen = 51000 * 1024    -- max len of send buffer\r
-_maxreadlen = 25000 * 1024    -- max len of read buffer\r
-\r
-_checkinterval = 1200000    -- interval in secs to check idle clients\r
-_sendtimeout = 60000    -- allowed send idle time in secs\r
-_readtimeout = 6 * 60 * 60    -- allowed read idle time in secs\r
-\r
-_cleanqueue = false    -- clean bufferqueue after using\r
-\r
-_maxclientsperserver = 1000\r
-\r
-_maxsslhandshake = 30 -- max handshake round-trips\r
-----------------------------------// PRIVATE //--\r
-\r
-wrapserver = function( listeners, socket, ip, serverport, pattern, sslctx, maxconnections, startssl )    -- this function wraps a server\r
-\r
-    maxconnections = maxconnections or _maxclientsperserver\r
-\r
-    local connections = 0\r
-\r
-    local dispatch, disconnect = listeners.incoming or listeners.listener, listeners.disconnect\r
-\r
-    local err\r
-\r
-    local ssl = false\r
-\r
-    if sslctx then\r
-        ssl = true\r
-        if not ssl_newcontext then\r
-            out_error "luasec not found"\r
-            ssl = false\r
-        end\r
-        if type( sslctx ) ~= "table" then\r
-            out_error "server.lua: wrong server sslctx"\r
-            ssl = false\r
-        end\r
-        local ctx;\r
-        ctx, err = ssl_newcontext( sslctx )\r
-        if not ctx then\r
-            err = err or "wrong sslctx parameters"\r
-            local file;\r
-            file = err:match("^error loading (.-) %(");\r
-            if file then\r
-               if file == "private key" then\r
-                       file = sslctx.key or "your private key";\r
-               elseif file == "certificate" then\r
-                       file = sslctx.certificate or "your certificate file";\r
-               end\r
-               local reason = err:match("%((.+)%)$") or "some reason";\r
-               if reason == "Permission denied" then\r
-                       reason = "Check that the permissions allow Prosody to read this file.";\r
-               elseif reason == "No such file or directory" then\r
-                       reason = "Check that the path is correct, and the file exists.";\r
-               elseif reason == "system lib" then\r
-                       reason = "Previous error (see logs), or other system error.";\r
-               else\r
-                       reason = "Reason: "..tostring(reason or "unknown"):lower();\r
-               end\r
-               log("error", "SSL/TLS: Failed to load %s: %s", file, reason);\r
-           else\r
-                log("error", "SSL/TLS: Error initialising for port %d: %s", serverport, err );\r
-            end\r
-            ssl = false\r
-        end\r
-        sslctx = ctx;\r
-    end\r
-    if not ssl then\r
-      sslctx = false;\r
-      if startssl then\r
-         log("error", "Failed to listen on port %d due to SSL/TLS to SSL/TLS initialisation errors (see logs)", serverport )\r
-         return nil, "Cannot start ssl,  see log for details"\r
-       end\r
-    end\r
-\r
-    local accept = socket.accept\r
-\r
-    --// public methods of the object //--\r
-\r
-    local handler = { }\r
-\r
-    handler.shutdown = function( ) end\r
-\r
-    handler.ssl = function( )\r
-        return ssl\r
-    end\r
-    handler.sslctx = function( )\r
-        return sslctx\r
-    end\r
-    handler.remove = function( )\r
-        connections = connections - 1\r
-    end\r
-    handler.close = function( )\r
-        for _, handler in pairs( _socketlist ) do\r
-            if handler.serverport == serverport then\r
-                handler.disconnect( handler, "server closed" )\r
-                handler.close( true )\r
-            end\r
-        end\r
-        socket:close( )\r
-        _sendlistlen = removesocket( _sendlist, socket, _sendlistlen )\r
-        _readlistlen = removesocket( _readlist, socket, _readlistlen )\r
-        _socketlist[ socket ] = nil\r
-        handler = nil\r
-        socket = nil\r
-        --mem_free( )\r
-        out_put "server.lua: closed server handler and removed sockets from list"\r
-    end\r
-    handler.ip = function( )\r
-        return ip\r
-    end\r
-    handler.serverport = function( )\r
-        return serverport\r
-    end\r
-    handler.socket = function( )\r
-        return socket\r
-    end\r
-    handler.readbuffer = function( )\r
-        if connections > maxconnections then\r
-            out_put( "server.lua: refused new client connection: server full" )\r
-            return false\r
-        end\r
-        local client, err = accept( socket )    -- try to accept\r
-        if client then\r
-            local ip, clientport = client:getpeername( )\r
-            client:settimeout( 0 )\r
-            local handler, client, err = wrapconnection( handler, listeners, client, ip, serverport, clientport, pattern, sslctx, startssl )    -- wrap new client socket\r
-            if err then    -- error while wrapping ssl socket\r
-                return false\r
-            end\r
-            connections = connections + 1\r
-            out_put( "server.lua: accepted new client connection from ", tostring(ip), ":", tostring(clientport), " to ", tostring(serverport))\r
-            return dispatch( handler )\r
-        elseif err then    -- maybe timeout or something else\r
-            out_put( "server.lua: error with new client connection: ", tostring(err) )\r
-            return false\r
-        end\r
-    end\r
-    return handler\r
-end\r
-\r
-wrapconnection = function( server, listeners, socket, ip, serverport, clientport, pattern, sslctx, startssl )    -- this function wraps a client to a handler object\r
-\r
-    socket:settimeout( 0 )\r
-\r
-    --// local import of socket methods //--\r
-\r
-    local send\r
-    local receive\r
-    local shutdown\r
-\r
-    --// private closures of the object //--\r
-\r
-    local ssl\r
-\r
-    local dispatch = listeners.incoming or listeners.listener\r
-    local status = listeners.status\r
-    local disconnect = listeners.disconnect\r
-\r
-    local bufferqueue = { }    -- buffer array\r
-    local bufferqueuelen = 0    -- end of buffer array\r
-\r
-    local toclose\r
-    local fatalerror\r
-    local needtls\r
-\r
-    local bufferlen = 0\r
-\r
-    local noread = false\r
-    local nosend = false\r
-\r
-    local sendtraffic, readtraffic = 0, 0\r
-\r
-    local maxsendlen = _maxsendlen\r
-    local maxreadlen = _maxreadlen\r
-\r
-    --// public methods of the object //--\r
-\r
-    local handler = bufferqueue    -- saves a table ^_^\r
-\r
-    handler.dispatch = function( )\r
-        return dispatch\r
-    end\r
-    handler.disconnect = function( )\r
-        return disconnect\r
-    end\r
-    handler.setlistener = function( listeners )\r
-        dispatch = listeners.incoming\r
-        disconnect = listeners.disconnect\r
-    end\r
-    handler.getstats = function( )\r
-        return readtraffic, sendtraffic\r
-    end\r
-    handler.ssl = function( )\r
-        return ssl\r
-    end\r
-    handler.sslctx = function ( )\r
-        return sslctx\r
-    end\r
-    handler.send = function( _, data, i, j )\r
-        return send( socket, data, i, j )\r
-    end\r
-    handler.receive = function( pattern, prefix )\r
-        return receive( socket, pattern, prefix )\r
-    end\r
-    handler.shutdown = function( pattern )\r
-        return shutdown( socket, pattern )\r
-    end\r
-    handler.close = function( forced )\r
-        if not handler then return true; end\r
-        _readlistlen = removesocket( _readlist, socket, _readlistlen )\r
-        _readtimes[ handler ] = nil\r
-        if bufferqueuelen ~= 0 then\r
-            if not ( forced or fatalerror ) then\r
-                handler.sendbuffer( )\r
-                if bufferqueuelen ~= 0 then   -- try again...\r
-                    if handler then\r
-                        handler.write = nil    -- ... but no further writing allowed\r
-                    end\r
-                    toclose = true\r
-                    return false\r
-                end\r
-            else\r
-                send( socket, table_concat( bufferqueue, "", 1, bufferqueuelen ), 1, bufferlen )    -- forced send\r
-            end\r
-        end\r
-        if socket then\r
-          _ = shutdown and shutdown( socket )\r
-          socket:close( )\r
-          _sendlistlen = removesocket( _sendlist, socket, _sendlistlen )\r
-          _socketlist[ socket ] = nil\r
-          socket = nil\r
-        else\r
-          out_put "server.lua: socket already closed"\r
-        end\r
-        if handler then\r
-            _writetimes[ handler ] = nil\r
-            _closelist[ handler ] = nil\r
-            handler = nil\r
-        end\r
-       if server then\r
-               server.remove( )\r
-       end\r
-        out_put "server.lua: closed client handler and removed socket from list"\r
-        return true\r
-    end\r
-    handler.ip = function( )\r
-        return ip\r
-    end\r
-    handler.serverport = function( )\r
-        return serverport\r
-    end\r
-    handler.clientport = function( )\r
-        return clientport\r
-    end\r
-    local write = function( data )\r
-        bufferlen = bufferlen + string_len( data )\r
-        if bufferlen > maxsendlen then\r
-            _closelist[ handler ] = "send buffer exceeded"   -- cannot close the client at the moment, have to wait to the end of the cycle\r
-            handler.write = idfalse    -- dont write anymore\r
-            return false\r
-        elseif socket and not _sendlist[ socket ] then\r
-            _sendlistlen = addsocket(_sendlist, socket, _sendlistlen)\r
-        end\r
-        bufferqueuelen = bufferqueuelen + 1\r
-        bufferqueue[ bufferqueuelen ] = data\r
-        if handler then\r
-               _writetimes[ handler ] = _writetimes[ handler ] or _currenttime\r
-        end\r
-        return true\r
-    end\r
-    handler.write = write\r
-    handler.bufferqueue = function( )\r
-        return bufferqueue\r
-    end\r
-    handler.socket = function( )\r
-        return socket\r
-    end\r
-    handler.pattern = function( new )\r
-        pattern = new or pattern\r
-        return pattern\r
-    end\r
-    handler.setsend = function ( newsend )\r
-        send = newsend or send\r
-        return send\r
-    end\r
-    handler.bufferlen = function( readlen, sendlen )\r
-        maxsendlen = sendlen or maxsendlen\r
-        maxreadlen = readlen or maxreadlen\r
-        return bufferlen, maxreadlen, maxsendlen\r
-    end\r
-    handler.lock = function( switch )\r
-        if switch == true then\r
-            handler.write = idfalse\r
-            local tmp = _sendlistlen\r
-            _sendlistlen = removesocket( _sendlist, socket, _sendlistlen )\r
-            _writetimes[ handler ] = nil\r
-            if _sendlistlen ~= tmp then\r
-                nosend = true\r
-            end\r
-            tmp = _readlistlen\r
-            _readlistlen = removesocket( _readlist, socket, _readlistlen )\r
-            _readtimes[ handler ] = nil\r
-            if _readlistlen ~= tmp then\r
-                noread = true\r
-            end\r
-        elseif switch == false then\r
-            handler.write = write\r
-            if noread then\r
-                noread = false\r
-                _readlistlen = addsocket(_readlist, socket, _readlistlen)\r
-                _readtimes[ handler ] = _currenttime\r
-            end\r
-            if nosend then\r
-                nosend = false\r
-                write( "" )\r
-            end\r
-        end\r
-        return noread, nosend\r
-    end\r
-    local _readbuffer = function( )    -- this function reads data\r
-        local buffer, err, part = receive( socket, pattern )    -- receive buffer with "pattern"\r
-        if not err or (err == "wantread" or err == "timeout") or string_len(part) > 0 then    -- received something\r
-            local buffer = buffer or part or ""\r
-            local len = string_len( buffer )\r
-            if len > maxreadlen then\r
-                disconnect( handler, "receive buffer exceeded" )\r
-                handler.close( true )\r
-                return false\r
-            end\r
-            local count = len * STAT_UNIT\r
-            readtraffic = readtraffic + count\r
-            _readtraffic = _readtraffic + count\r
-            _readtimes[ handler ] = _currenttime\r
-            --out_put( "server.lua: read data '", buffer:gsub("[^%w%p ]", "."), "', error: ", err )\r
-            return dispatch( handler, buffer, err )\r
-        else    -- connections was closed or fatal error\r
-            out_put( "server.lua: client ", tostring(ip), ":", tostring(clientport), " read error: ", tostring(err) )\r
-            fatalerror = true\r
-            disconnect( handler, err )\r
-           _ = handler and handler.close( )\r
-            return false\r
-        end\r
-    end\r
-    local _sendbuffer = function( )    -- this function sends data\r
-       local succ, err, byte, buffer, count;\r
-       local count;\r
-       if socket then\r
-            buffer = table_concat( bufferqueue, "", 1, bufferqueuelen )\r
-            succ, err, byte = send( socket, buffer, 1, bufferlen )\r
-            count = ( succ or byte or 0 ) * STAT_UNIT\r
-            sendtraffic = sendtraffic + count\r
-            _sendtraffic = _sendtraffic + count\r
-            _ = _cleanqueue and clean( bufferqueue )\r
-            --out_put( "server.lua: sended '", buffer, "', bytes: ", tostring(succ), ", error: ", tostring(err), ", part: ", tostring(byte), ", to: ", tostring(ip), ":", tostring(clientport) )\r
-        else\r
-            succ, err, count = false, "closed", 0;\r
-        end\r
-        if succ then    -- sending succesful\r
-            bufferqueuelen = 0\r
-            bufferlen = 0\r
-            _sendlistlen = removesocket( _sendlist, socket, _sendlistlen )    -- delete socket from writelist\r
-            _ = needtls and handler.starttls(true)\r
-            _writetimes[ handler ] = nil\r
-           _ = toclose and handler.close( )\r
-            return true\r
-        elseif byte and ( err == "timeout" or err == "wantwrite" ) then    -- want write\r
-            buffer = string_sub( buffer, byte + 1, bufferlen )    -- new buffer\r
-            bufferqueue[ 1 ] = buffer    -- insert new buffer in queue\r
-            bufferqueuelen = 1\r
-            bufferlen = bufferlen - byte\r
-            _writetimes[ handler ] = _currenttime\r
-            return true\r
-        else    -- connection was closed during sending or fatal error\r
-            out_put( "server.lua: client ", tostring(ip), ":", tostring(clientport), " write error: ", tostring(err) )\r
-            fatalerror = true\r
-            disconnect( handler, err )\r
-            _ = handler and handler.close( )\r
-            return false\r
-        end\r
-    end\r
-\r
-    -- Set the sslctx\r
-    local handshake;\r
-    function handler.set_sslctx(new_sslctx)\r
-        ssl = true\r
-        sslctx = new_sslctx;\r
-        local wrote\r
-        local read\r
-        handshake = coroutine_wrap( function( client )    -- create handshake coroutine\r
-                local err\r
-                for i = 1, _maxsslhandshake do\r
-                    _sendlistlen = ( wrote and removesocket( _sendlist, client, _sendlistlen ) ) or _sendlistlen\r
-                    _readlistlen = ( read and removesocket( _readlist, client, _readlistlen ) ) or _readlistlen\r
-                    read, wrote = nil, nil\r
-                    _, err = client:dohandshake( )\r
-                    if not err then\r
-                        out_put( "server.lua: ssl handshake done" )\r
-                        handler.readbuffer = _readbuffer    -- when handshake is done, replace the handshake function with regular functions\r
-                        handler.sendbuffer = _sendbuffer\r
-                        _ = status and status( handler, "ssl-handshake-complete" )\r
-                        _readlistlen = addsocket(_readlist, client, _readlistlen)\r
-                        return true\r
-                    else\r
-                       out_put( "server.lua: error during ssl handshake: ", tostring(err) )\r
-                       if err == "wantwrite" and not wrote then\r
-                           _sendlistlen = addsocket(_sendlist, client, _sendlistlen)\r
-                           wrote = true\r
-                       elseif err == "wantread" and not read then\r
-                           _readlistlen = addsocket(_readlist, client, _readlistlen)\r
-                           read = true\r
-                       else\r
-                           break;\r
-                       end\r
-                       --coroutine_yield( handler, nil, err )    -- handshake not finished\r
-                       coroutine_yield( )\r
-                    end\r
-                end\r
-                disconnect( handler, "ssl handshake failed" )\r
-                _ = handler and handler.close( true )    -- forced disconnect\r
-                return false    -- handshake failed\r
-            end\r
-        )\r
-    end\r
-    if sslctx then    -- ssl?\r
-       handler.set_sslctx(sslctx);\r
-        if startssl then    -- ssl now?\r
-            --out_put("server.lua: ", "starting ssl handshake")\r
-           local err\r
-            socket, err = ssl_wrap( socket, sslctx )    -- wrap socket\r
-            if err then\r
-                out_put( "server.lua: ssl error: ", tostring(err) )\r
-                --mem_free( )\r
-                return nil, nil, err    -- fatal error\r
-            end\r
-            socket:settimeout( 0 )\r
-            handler.readbuffer = handshake\r
-            handler.sendbuffer = handshake\r
-            handshake( socket ) -- do handshake\r
-            if not socket then\r
-                return nil, nil, "ssl handshake failed";\r
-            end\r
-        else\r
-            -- We're not automatically doing SSL, so we're not secure (yet)\r
-            ssl = false\r
-            handler.starttls = function( now )\r
-                if not now then\r
-                    --out_put "server.lua: we need to do tls, but delaying until later"\r
-                    needtls = true\r
-                    return\r
-                end\r
-                --out_put( "server.lua: attempting to start tls on " .. tostring( socket ) )\r
-                local oldsocket, err = socket\r
-                socket, err = ssl_wrap( socket, sslctx )    -- wrap socket\r
-                --out_put( "server.lua: sslwrapped socket is " .. tostring( socket ) )\r
-                if err then\r
-                    out_put( "server.lua: error while starting tls on client: ", tostring(err) )\r
-                    return nil, err    -- fatal error\r
-                end\r
-\r
-                socket:settimeout( 0 )\r
-\r
-                -- add the new socket to our system\r
-\r
-                send = socket.send\r
-                receive = socket.receive\r
-                shutdown = id\r
-\r
-                _socketlist[ socket ] = handler\r
-                _readlistlen = addsocket(_readlist, socket, _readlistlen)\r
-\r
-                -- remove traces of the old socket\r
-\r
-                _readlistlen = removesocket( _readlist, oldsocket, _readlistlen )\r
-                _sendlistlen = removesocket( _sendlist, oldsocket, _sendlistlen )\r
-                _socketlist[ oldsocket ] = nil\r
-\r
-                handler.starttls = nil\r
-                needtls = nil\r
-                \r
-                -- Secure now\r
-                ssl = true\r
-\r
-                handler.readbuffer = handshake\r
-                handler.sendbuffer = handshake\r
-                handshake( socket )    -- do handshake\r
-            end\r
-            handler.readbuffer = _readbuffer\r
-            handler.sendbuffer = _sendbuffer\r
-        end\r
-    else    -- normal connection\r
-        ssl = false\r
-        handler.readbuffer = _readbuffer\r
-        handler.sendbuffer = _sendbuffer\r
-    end\r
-\r
-    send = socket.send\r
-    receive = socket.receive\r
-    shutdown = ( ssl and id ) or socket.shutdown\r
-\r
-    _socketlist[ socket ] = handler\r
-    _readlistlen = addsocket(_readlist, socket, _readlistlen)\r
-\r
-    return handler, socket\r
-end\r
-\r
-id = function( )\r
-end\r
-\r
-idfalse = function( )\r
-    return false\r
-end\r
-\r
-addsocket = function( list, socket, len )\r
-    if not list[ socket ] then\r
-      len = len + 1\r
-      list[ len ] = socket\r
-      list[ socket ] = len\r
-    end\r
-    return len;\r
-end\r
-\r
-removesocket = function( list, socket, len )    -- this function removes sockets from a list ( copied from copas )\r
-    local pos = list[ socket ]\r
-    if pos then\r
-        list[ socket ] = nil\r
-        local last = list[ len ]\r
-        list[ len ] = nil\r
-        if last ~= socket then\r
-            list[ last ] = pos\r
-            list[ pos ] = last\r
-        end\r
-        return len - 1\r
-    end\r
-    return len\r
-end\r
-\r
-closesocket = function( socket )\r
-    _sendlistlen = removesocket( _sendlist, socket, _sendlistlen )\r
-    _readlistlen = removesocket( _readlist, socket, _readlistlen )\r
-    _socketlist[ socket ] = nil\r
-    socket:close( )\r
-    --mem_free( )\r
-end\r
-\r
-----------------------------------// PUBLIC //--\r
-\r
-addserver = function( listeners, port, addr, pattern, sslctx, maxconnections, startssl )    -- this function provides a way for other scripts to reg a server\r
-    local err\r
-    --out_put("server.lua: autossl on ", port, " is ", startssl)\r
-    if type( listeners ) ~= "table" then\r
-        err = "invalid listener table"\r
-    end\r
-    if not type( port ) == "number" or not ( port >= 0 and port <= 65535 ) then\r
-        err = "invalid port"\r
-    elseif _server[ port ] then\r
-        err =  "listeners on port '" .. port .. "' already exist"\r
-    elseif sslctx and not luasec then\r
-        err = "luasec not found"\r
-    end\r
-    if err then\r
-        out_error( "server.lua, port ", port, ": ", err )\r
-        return nil, err\r
-    end\r
-    addr = addr or "*"\r
-    local server, err = socket_bind( addr, port )\r
-    if err then\r
-        out_error( "server.lua, port ", port, ": ", err )\r
-        return nil, err\r
-    end\r
-    local handler, err = wrapserver( listeners, server, addr, port, pattern, sslctx, maxconnections, startssl )    -- wrap new server socket\r
-    if not handler then\r
-        server:close( )\r
-        return nil, err\r
-    end\r
-    server:settimeout( 0 )\r
-    _readlistlen = addsocket(_readlist, server, _readlistlen)\r
-    _server[ port ] = handler\r
-    _socketlist[ server ] = handler\r
-    out_put( "server.lua: new server listener on '", addr, ":", port, "'" )\r
-    return handler\r
-end\r
-\r
-getserver = function ( port )\r
-       return _server[ port ];\r
-end\r
-\r
-removeserver = function( port )\r
-    local handler = _server[ port ]\r
-    if not handler then\r
-        return nil, "no server found on port '" .. tostring( port ) .. "'"\r
-    end\r
-    handler.close( )\r
-    _server[ port ] = nil\r
-    return true\r
-end\r
-\r
-closeall = function( )\r
-    for _, handler in pairs( _socketlist ) do\r
-        handler.close( )\r
-        _socketlist[ _ ] = nil\r
-    end\r
-    _readlistlen = 0\r
-    _sendlistlen = 0\r
-    _timerlistlen = 0\r
-    _server = { }\r
-    _readlist = { }\r
-    _sendlist = { }\r
-    _timerlist = { }\r
-    _socketlist = { }\r
-    --mem_free( )\r
-end\r
-\r
-getsettings = function( )\r
-    return  _selecttimeout, _sleeptime, _maxsendlen, _maxreadlen, _checkinterval, _sendtimeout, _readtimeout, _cleanqueue, _maxclientsperserver, _maxsslhandshake\r
-end\r
-\r
-changesettings = function( new )\r
-    if type( new ) ~= "table" then\r
-        return nil, "invalid settings table"\r
-    end\r
-    _selecttimeout = tonumber( new.timeout ) or _selecttimeout\r
-    _sleeptime = tonumber( new.sleeptime ) or _sleeptime\r
-    _maxsendlen = tonumber( new.maxsendlen ) or _maxsendlen\r
-    _maxreadlen = tonumber( new.maxreadlen ) or _maxreadlen\r
-    _checkinterval = tonumber( new.checkinterval ) or _checkinterval\r
-    _sendtimeout = tonumber( new.sendtimeout ) or _sendtimeout\r
-    _readtimeout = tonumber( new.readtimeout ) or _readtimeout\r
-    _cleanqueue = new.cleanqueue\r
-    _maxclientsperserver = new._maxclientsperserver or _maxclientsperserver\r
-    _maxsslhandshake = new._maxsslhandshake or _maxsslhandshake\r
-    return true\r
-end\r
-\r
-addtimer = function( listener )\r
-    if type( listener ) ~= "function" then\r
-        return nil, "invalid listener function"\r
-    end\r
-    _timerlistlen = _timerlistlen + 1\r
-    _timerlist[ _timerlistlen ] = listener\r
-    return true\r
-end\r
-\r
-stats = function( )\r
-    return _readtraffic, _sendtraffic, _readlistlen, _sendlistlen, _timerlistlen\r
-end\r
-\r
-local dontstop = true; -- thinking about tomorrow, ...\r
-\r
-setquitting = function (quit)\r
-       dontstop = not quit;\r
-       return;\r
-end\r
-\r
-loop = function( )    -- this is the main loop of the program\r
-    while dontstop do\r
-        local read, write, err = socket_select( _readlist, _sendlist, _selecttimeout )\r
-        for i, socket in ipairs( write ) do    -- send data waiting in writequeues\r
-            local handler = _socketlist[ socket ]\r
-            if handler then\r
-                handler.sendbuffer( )\r
-            else\r
-                closesocket( socket )\r
-                out_put "server.lua: found no handler and closed socket (writelist)"    -- this should not happen\r
-            end\r
-        end\r
-        for i, socket in ipairs( read ) do    -- receive data\r
-            local handler = _socketlist[ socket ]\r
-            if handler then\r
-                handler.readbuffer( )\r
-            else\r
-                closesocket( socket )\r
-                out_put "server.lua: found no handler and closed socket (readlist)"    -- this can happen\r
-            end\r
-        end\r
-        for handler, err in pairs( _closelist ) do\r
-            handler.disconnect( )( handler, err )\r
-            handler.close( true )    -- forced disconnect\r
-        end\r
-        clean( _closelist )\r
-        _currenttime = os_time( )\r
-        if os_difftime( _currenttime - _timer ) >= 1 then\r
-            for i = 1, _timerlistlen do\r
-                _timerlist[ i ]( _currenttime )    -- fire timers\r
-            end\r
-            _timer = _currenttime\r
-        end\r
-        socket_sleep( _sleeptime )    -- wait some time\r
-        --collectgarbage( )\r
-    end\r
-    return "quitting"\r
-end\r
-\r
---// EXPERIMENTAL //--\r
-\r
-local wrapclient = function( socket, ip, serverport, listeners, pattern, sslctx, startssl )\r
-    local handler = wrapconnection( nil, listeners, socket, ip, serverport, "clientport", pattern, sslctx, startssl )\r
-    _socketlist[ socket ] = handler\r
-    _sendlistlen = addsocket(_sendlist, socket, _sendlistlen)\r
-    return handler, socket\r
-end\r
-\r
-local addclient = function( address, port, listeners, pattern, sslctx, startssl )\r
-    local client, err = luasocket.tcp( )\r
-    if err then\r
-        return nil, err\r
-    end\r
-    client:settimeout( 0 )\r
-    _, err = client:connect( address, port )\r
-    if err then    -- try again\r
-        local handler = wrapclient( client, address, port, listeners )\r
-    else\r
-        wrapconnection( nil, listeners, client, address, port, "clientport", pattern, sslctx, startssl )\r
-    end\r
-end\r
-\r
---// EXPERIMENTAL //--\r
-\r
-----------------------------------// BEGIN //--\r
-\r
-use "setmetatable" ( _socketlist, { __mode = "k" } )\r
-use "setmetatable" ( _readtimes, { __mode = "k" } )\r
-use "setmetatable" ( _writetimes, { __mode = "k" } )\r
-\r
-_timer = os_time( )\r
-_starttime = os_time( )\r
-\r
-addtimer( function( )\r
-        local difftime = os_difftime( _currenttime - _starttime )\r
-        if difftime > _checkinterval then\r
-            _starttime = _currenttime\r
-            for handler, timestamp in pairs( _writetimes ) do\r
-                if os_difftime( _currenttime - timestamp ) > _sendtimeout then\r
-                    --_writetimes[ handler ] = nil\r
-                    handler.disconnect( )( handler, "send timeout" )\r
-                    handler.close( true )    -- forced disconnect\r
-                end\r
-            end\r
-            for handler, timestamp in pairs( _readtimes ) do\r
-                if os_difftime( _currenttime - timestamp ) > _readtimeout then\r
-                    --_readtimes[ handler ] = nil\r
-                    handler.disconnect( )( handler, "read timeout" )\r
-                    handler.close( )    -- forced disconnect?\r
-                end\r
-            end\r
-        end\r
-    end\r
-)\r
-\r
-----------------------------------// PUBLIC INTERFACE //--\r
-\r
-return {\r
-\r
-    addclient = addclient,\r
-    wrapclient = wrapclient,\r
-    \r
-    loop = loop,\r
-    stats = stats,\r
-    closeall = closeall,\r
-    addtimer = addtimer,\r
-    addserver = addserver,\r
-    getserver = getserver,\r
-    getsettings = getsettings,\r
-    setquitting = setquitting,\r
-    removeserver = removeserver,\r
-    changesettings = changesettings,\r
-}\r
+-- 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 use_luaevent = require "core.configmanager".get("*", "core", "use_libevent");
+
+if use_luaevent then
+       use_luaevent = pcall(require, "luaevent.core");
+       if not use_luaevent then
+               log("error", "libevent not found, falling back to select()");
+       end
+end
+
+local server;
+
+if use_luaevent then
+       server = require "net.server_event";
+       -- util.timer requires "net.server", so instead of having
+       -- Lua look for, and load us again (causing a loop) - set this here
+       -- (usually it isn't set until we return, look down there...)
+       package.loaded["net.server"] = server;
+       
+       -- Backwards compatibility for timers, addtimer
+       -- called a function roughly every second
+       local add_task = require "util.timer".add_task;
+       function server.addtimer(f)
+               return add_task(1, function (...) f(...); return 1; end);
+       end
+       
+       -- Overwrite signal.signal() because we need to ask libevent to
+       -- handle them instead
+       local ok, signal = pcall(require, "util.signal");
+       if ok and signal then
+               local _signal_signal = signal.signal;
+               function signal.signal(signal_id, handler)
+                       if type(signal_id) == "string" then
+                               signal_id = signal[signal_id:upper()];
+                       end
+                       if type(signal_id) ~= "number" then
+                               return false, "invalid-signal";
+                       end
+                       return server.hook_signal(signal_id, handler);
+               end
+       end
+else
+       server = require "net.server_select";
+       package.loaded["net.server"] = server;
+end
+
+-- require "net.server" shall now forever return this,
+-- ie. server_select or server_event as chosen above.
+return server;
index 49cbe25ddc12a86b519f7ddb4bb41ba5fd7b8e18..0aad924d312cb0613197e9514bfa5535b0629ce1 100644 (file)
@@ -2,7 +2,7 @@
 -- server.lua by blastbeat of the luadch project
 -- Re-used here under the MIT/X Consortium License
 -- 
--- Modifications (C) 2008-2009 Matthew Wild, Waqas Hussain
+-- Modifications (C) 2008-2010 Matthew Wild, Waqas Hussain
 --
 
 -- // wrapping luadch stuff // --
index f0e756c4331d9261f2585aa0b9ad8c4585cb632a..94daa2b2f7c95f8f846cf451da31ebe85620288a 100644 (file)
@@ -27,17 +27,38 @@ local sm_streamopened = sessionmanager.streamopened;
 local sm_streamclosed = sessionmanager.streamclosed;
 local st = require "util.stanza";
 
-local stream_callbacks = { stream_tag = "http://etherx.jabber.org/streams\1stream", 
-               default_ns = "jabber:client",
+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 session.close then
-               (session.log or log)("debug", "Client XML parse error: %s", tostring(error));
+       elseif error == "parse-error" then
+               (session.log or log)("debug", "Client XML parse error: %s", tostring(data));
                session:close("xml-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
 
@@ -68,9 +89,8 @@ local function session_reset_stream(session)
                return true;
 end
 
-
 local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'};
-local default_stream_attr = { ["xmlns:stream"] = stream_callbacks.stream_tag:match("[^\1]*"), xmlns = stream_callbacks.default_ns, version = "1.0", id = "" };
+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
@@ -100,15 +120,15 @@ local function session_close(session, reason)
                        end
                end
                session.send("</stream:stream>");
-               session.conn.close();
-               xmppclient.disconnect(session.conn, (reason and (reason.text or reason.condition)) or reason or "session closed");
+               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.listener(conn, data)
+function xmppclient.onincoming(conn, data)
        local session = sessions[conn];
        if not session then
                session = sm_new_session(conn);
@@ -117,10 +137,14 @@ function xmppclient.listener(conn, data)
                session.log("info", "Client connected");
                
                -- Client is using legacy SSL (otherwise mod_tls sets this flag)
-               if conn.ssl() then
+               if conn:ssl() then
                        session.secure = true;
                end
                
+               if opt_keepalives ~= nil then
+                       conn:setoption("keepalive", opt_keepalives);
+               end
+               
                session.reset_stream = session_reset_stream;
                session.close = session_close;
                
@@ -133,7 +157,7 @@ function xmppclient.listener(conn, data)
        end
 end
        
-function xmppclient.disconnect(conn, err)
+function xmppclient.ondisconnect(conn, err)
        local session = sessions[conn];
        if session then
                (session.log or log)("info", "Client disconnected: %s", err);
index 1759adf9c8e585428d836b6aa72340888c7985b8..b87f7c96cf500e285bfbdda8552b1df35c916ad9 100644 (file)
@@ -32,18 +32,35 @@ local xmlns_component = 'jabber:component:accept';
 
 --- Callbacks/data for xmlhandlers to handle streams for us ---
 
-local stream_callbacks = { stream_tag = "http://etherx.jabber.org/streams\1stream", default_ns = xmlns_component };
+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 == "xml-parse-error" and data == "unexpected-element-close" then
-               session.log("warn", "Unexpected close of '%s' tag", data2);
-               session:close("xml-not-well-formed");
-       else
-               session.log("warn", "External component %s XML parse error: %s", tostring(session.host), tostring(error));
+       elseif error == "parse-error" then
+               session.log("warn", "External component %s XML parse error: %s", tostring(session.host), tostring(data));
                session:close("xml-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
 
@@ -71,8 +88,8 @@ function stream_callbacks.streamopened(session, attr)
 end
 
 function stream_callbacks.streamclosed(session)
-       session.send("</stream:stream>");
-       session.notopen = true;
+       session.log("Received </stream:stream>");
+       session:close();
 end
 
 local core_process_stanza = core_process_stanza;
@@ -87,8 +104,9 @@ end
 
 --- Closing a component connection
 local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'};
-local default_stream_attr = { ["xmlns:stream"] = stream_callbacks.stream_tag:match("[^\1]*"), xmlns = stream_callbacks.default_ns, version = "1.0", id = "" };
+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
@@ -117,17 +135,17 @@ local function session_close(session, reason)
                        end
                end
                session.send("</stream:stream>");
-               session.conn.close();
-               component_listener.disconnect(session.conn, "stream error");
+               session.conn:close();
+               component_listener.ondisconnect(session.conn, "stream error");
        end
 end
 
 --- Component connlistener
-function component_listener.listener(conn, data)
+function component_listener.onincoming(conn, data)
        local session = sessions[conn];
        if not session then
                local _send = conn.write;
-               session = { type = "component", conn = conn, send = function (data) return _send(tostring(data)); end };
+               session = { type = "component", conn = conn, send = function (data) return _send(conn, tostring(data)); end };
                sessions[conn] = session;
 
                -- Logging functions --
@@ -146,6 +164,7 @@ function component_listener.listener(conn, data)
                function session.data(conn, data)
                        local ok, err = parser:parse(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("xml-not-well-formed");
                end
                
@@ -157,7 +176,7 @@ function component_listener.listener(conn, data)
        end
 end
        
-function component_listener.disconnect(conn, err)
+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));
@@ -167,7 +186,12 @@ function component_listener.disconnect(conn, err)
                        hosts[session.host].connected = nil;
                end
                sessions[conn]  = nil;
-               for k in pairs(session) do session[k] = nil; end
+               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
index 46b886f7de011151ec60ad7044adcb9d6046c1df..d63f5c235d2977768f9020d74cf41b41987105c9 100644 (file)
@@ -17,16 +17,34 @@ 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 = { stream_tag = "http://etherx.jabber.org/streams\1stream", 
-               default_ns = "jabber:server",
+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");
-       else
+       elseif error == "parse-error" then
                session.log("debug", "Server-to-server XML parse error: %s", tostring(error));
                session:close("xml-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
 
@@ -70,8 +88,8 @@ local function session_reset_stream(session)
 end
 
 local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'};
-local default_stream_attr = { ["xmlns:stream"] = stream_callbacks.stream_tag:match("[^\1]*"), xmlns = stream_callbacks.default_ns, version = "1.0", id = "" };
-local function session_close(session, reason)
+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
@@ -100,18 +118,18 @@ local function session_close(session, reason)
                        end
                end
                session.sends2s("</stream:stream>");
-               if session.notopen or not session.conn.close() then
-                       session.conn.close(true); -- Force FIXME: timer?
+               if session.notopen or not session.conn:close() then
+                       session.conn:close(true); -- Force FIXME: timer?
                end
-               session.conn.close();
-               xmppserver.disconnect(session.conn, "stream error");
+               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 --
 
-function xmppserver.listener(conn, data)
+function xmppserver.onincoming(conn, data)
        local session = sessions[conn];
        if not session then
                session = s2s_new_incoming(conn);
@@ -137,7 +155,7 @@ function xmppserver.listener(conn, data)
        end
 end
        
-function xmppserver.status(conn, status)
+function xmppserver.onstatus(conn, status)
        if status == "ssl-handshake-complete" then
                local session = sessions[conn];
                if session and session.direction == "outgoing" then
@@ -148,7 +166,7 @@ function xmppserver.status(conn, status)
        end
 end
 
-function xmppserver.disconnect(conn, err)
+function xmppserver.ondisconnect(conn, err)
        local session = sessions[conn];
        if session then
                if err and err ~= "closed" and session.srv_hosts then
@@ -158,7 +176,7 @@ function xmppserver.disconnect(conn, err)
                                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));
+               (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;
diff --git a/plugins/mod_actions_http.lua b/plugins/mod_actions_http.lua
deleted file mode 100644 (file)
index e125f16..0000000
+++ /dev/null
@@ -1,86 +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 t_concat, t_insert = table.concat, table.insert;
-
-local log = log;
-
-local response_404 = { status = "404 Not Found", body = "<h1>No such action</h1>Sorry, I don't have the action you requested" };
-
-local control = require "core.actions".actions;
-
-
-local urlcodes = setmetatable({}, { __index = function (t, k) t[k] = string.char(tonumber("0x"..k)); return t[k]; end });
-
-local function urldecode(s)
-                return s and (s:gsub("+", " "):gsub("%%([a-fA-F0-9][a-fA-F0-9])", urlcodes));
-end
-
-local function query_to_table(query)
-        if type(query) == "string" and #query > 0 then
-                if query:match("=") then
-                        local params = {};
-                        for k, v in query:gmatch("&?([^=%?]+)=([^&%?]+)&?") do
-                                if k and v then
-                                        params[urldecode(k)] = urldecode(v);
-                                end
-                        end
-                        return params;
-                else
-                        return urldecode(query);
-                end
-        end
-end
-
-
-
-local http_path = { http_base };
-local function handle_request(method, body, request)
-       local path = request.url.path:gsub("^/[^/]+/", "");
-       
-       local curr = control;
-       
-       for comp in path:gmatch("([^/]+)") do
-               curr = curr[comp];
-               if not curr then
-                       return response_404;
-               end
-       end
-       
-       if type(curr) == "table" then
-               local s = {};
-               for k,v in pairs(curr) do
-                       t_insert(s, tostring(k));
-                       t_insert(s, " = ");
-                       if type(v) == "function" then
-                               t_insert(s, "action")
-                       elseif type(v) == "table" then
-                               t_insert(s, "list");
-                       else
-                               t_insert(s, tostring(v));
-                       end
-                       t_insert(s, "\n");
-               end
-               return t_concat(s);
-       elseif type(curr) == "function" then
-               local params = query_to_table(request.url.query);
-               params.host = request.headers.host:gsub(":%d+", "");
-               local ok, ret1, ret2 = pcall(curr, params);
-               if not ok then
-                       return "EPIC FAIL: "..tostring(ret1);
-               elseif not ret1 then
-                       return "FAIL: "..tostring(ret2);
-               else
-                       return "OK: "..tostring(ret2);
-               end
-       end
-end
-
-httpserver.new{ port = 5280, base = "control", handler = handle_request, ssl = false }
\ No newline at end of file
index a55b1a3ef67aa4e00ab95f80669a570586c60b7c..14cb1b9104052a8fab7e5ecfd10d169f7f98b70b 100644 (file)
@@ -23,7 +23,7 @@ local logger = require "util.logger";
 local log = logger.init("mod_bosh");
 
 local xmlns_bosh = "http://jabber.org/protocol/httpbind"; -- (hard-coded into a literal in session.send)
-local stream_callbacks = { stream_tag = "http://jabber.org/protocol/httpbind\1body", default_ns = xmlns_bosh };
+local stream_callbacks = { stream_ns = "http://jabber.org/protocol/httpbind", stream_tag = "body", default_ns = xmlns_bosh };
 
 local BOSH_DEFAULT_HOLD = tonumber(module:get_option("bosh_default_hold")) or 1;
 local BOSH_DEFAULT_INACTIVITY = tonumber(module:get_option("bosh_max_inactivity")) or 60;
@@ -34,6 +34,22 @@ local BOSH_DEFAULT_MAXPAUSE = tonumber(module:get_option("bosh_max_pause")) or 3
 local default_headers = { ["Content-Type"] = "text/xml; charset=utf-8" };
 local session_close_reply = { headers = default_headers, body = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate" }), attr = {} };
 
+local cross_domain = module:get_option("cross_domain_bosh");
+if cross_domain then
+       default_headers["Access-Control-Allow-Methods"] = "GET, POST, OPTIONS";
+       default_headers["Access-Control-Allow-Headers"] = "Content-Type";
+       default_headers["Access-Control-Max-Age"] = "7200";
+
+       if cross_domain == true then
+               default_headers["Access-Control-Allow-Origin"] = "*";
+       elseif type(cross_domain) == "table" then
+               cross_domain = table.concat(cross_domain, ", ");
+       end
+       if type(cross_domain) == "string" then
+               default_headers["Access-Control-Allow-Origin"] = cross_domain;
+       end
+end
+
 local t_insert, t_remove, t_concat = table.insert, table.remove, table.concat;
 local os_time = os.time;
 
@@ -61,9 +77,13 @@ end
 
 function handle_request(method, body, request)
        if (not body) or request.method ~= "POST" then
-               return "<html><body>You really don't look like a BOSH client to me... what do you want?</body></html>";
+               if request.method == "OPTIONS" then
+                       return { headers = default_headers, body = "" };
+               else
+                       return "<html><body>You really don't look like a BOSH client to me... what do you want?</body></html>";
+               end
        end
-       if not method then 
+       if not method then
                log("debug", "Request %s suffered error %s", tostring(request.id), body);
                return;
        end
@@ -186,14 +206,15 @@ function stream_callbacks.streamopened(request, attr)
                -- Send creation response
                
                local features = st.stanza("stream:features");
+               hosts[session.host].events.fire_event("stream-features", { origin = session, features = features });
                fire_event("stream-features", session, features);
                --xmpp:version='1.0' xmlns:xmpp='urn:xmpp:xbosh'
-               local response = st.stanza("body", { xmlns = xmlns_bosh, 
-                                                                       inactivity = tostring(BOSH_DEFAULT_INACTIVITY), polling = tostring(BOSH_DEFAULT_POLLING), requests = tostring(BOSH_DEFAULT_REQUESTS), hold = tostring(session.bosh_hold), maxpause = "120", 
-                                                                       sid = sid, authid = sid, ver  = '1.6', from = session.host, secure = 'true', ["xmpp:version"] = "1.0", 
+               local response = st.stanza("body", { xmlns = xmlns_bosh,
+                                                                       inactivity = tostring(BOSH_DEFAULT_INACTIVITY), polling = tostring(BOSH_DEFAULT_POLLING), requests = tostring(BOSH_DEFAULT_REQUESTS), hold = tostring(session.bosh_hold), maxpause = "120",
+                                                                       sid = sid, authid = sid, ver  = '1.6', from = session.host, secure = 'true', ["xmpp:version"] = "1.0",
                                                                        ["xmlns:xmpp"] = "urn:xmpp:xbosh", ["xmlns:stream"] = "http://etherx.jabber.org/streams" }):add_child(features);
                request:send{ headers = default_headers, body = tostring(response) };
-                               
+               
                request.sid = sid;
                return;
        end
@@ -237,6 +258,7 @@ function stream_callbacks.streamopened(request, attr)
        
        if session.notopen then
                local features = st.stanza("stream:features");
+               hosts[session.host].events.fire_event("stream-features", { origin = session, features = features });
                fire_event("stream-features", session, features);
                session.send(features);
                session.notopen = nil;
@@ -254,7 +276,7 @@ function stream_callbacks.handlestanza(request, stanza)
                if stanza.attr.xmlns == xmlns_bosh then
                        stanza.attr.xmlns = "jabber:client";
                end
-               session.ip = request.handler.ip();
+               session.ip = request.handler:ip();
                core_process_stanza(session, stanza);
        end
 end
@@ -298,7 +320,14 @@ function on_timer()
        end
 end
 
-local ports = module:get_option("bosh_ports") or { 5280 };
-httpserver.new_from_config(ports, handle_request, { base = "http-bind" });
 
-server.addtimer(on_timer);
+local function setup()
+       local ports = module:get_option("bosh_ports") or { 5280 };
+       httpserver.new_from_config(ports, handle_request, { base = "http-bind" });
+       server.addtimer(on_timer);
+end
+if prosody.start_time then -- already started
+       setup();
+else
+       prosody.events.add_handler("server-started", setup);
+end
index 8a7454cd3a10153791878eba66bc3706296fb09a..7efb4f9c3e94c2c4076854445103ed2267f7b065 100644 (file)
@@ -14,25 +14,14 @@ local hosts = _G.hosts;
 
 local t_concat = table.concat;
 
-local lxp = require "lxp";
-local logger = require "util.logger";
 local config = require "core.configmanager";
-local connlisteners = require "net.connlisteners";
 local cm_register_component = require "core.componentmanager".register_component;
 local cm_deregister_component = require "core.componentmanager".deregister_component;
-local uuid_gen = require "util.uuid".generate;
 local sha1 = require "util.hashes".sha1;
 local st = require "util.stanza";
-local init_xmlhandlers = require "core.xmlhandlers";
-
-local sessions = {};
 
 local log = module._log;
 
-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';
-
 --- Handle authentication attempts by components
 function handle_component_auth(session, stanza)
        log("info", "Handling component auth");
@@ -80,4 +69,4 @@ function handle_component_auth(session, stanza)
        session.send(st.stanza("handshake"));
 end
 
-module:add_handler("component", "handshake", xmlns_component, handle_component_auth);
+module:add_handler("component", "handshake", "jabber:component:accept", handle_component_auth);
index ab1d3453382e818e154a3bc9baee0cd136294eeb..8f574704c2fdd82dd88d815dc2b6acb3d0e07056 100644 (file)
@@ -33,11 +33,11 @@ end
 console = {};
 
 function console:new_session(conn)
-       local w = function(s) conn.write(s:gsub("\n", "\r\n")); end;
+       local w = function(s) conn:write(s:gsub("\n", "\r\n")); end;
        local session = { conn = conn;
                        send = function (t) w(tostring(t)); end;
                        print = function (t) w("| "..tostring(t).."\n"); end;
-                       disconnect = function () conn.close(); end;
+                       disconnect = function () conn:close(); end;
                        };
        session.env = setmetatable({}, default_env_mt);
        
@@ -53,7 +53,7 @@ end
 
 local sessions = {};
 
-function console_listener.listener(conn, data)
+function console_listener.onincoming(conn, data)
        local session = sessions[conn];
        
        if not session then
@@ -126,7 +126,7 @@ function console_listener.listener(conn, data)
        session.send(string.char(0));
 end
 
-function console_listener.disconnect(conn, err)
+function console_listener.ondisconnect(conn, err)
        local session = sessions[conn];
        if session then
                session.disconnect();
@@ -148,7 +148,7 @@ commands.quit, commands.exit = commands.bye, commands.bye;
 commands["!"] = function (session, data)
        if data:match("^!!") then
                session.print("!> "..session.env._);
-               return console_listener.listener(session.conn, session.env._);
+               return console_listener.onincoming(session.conn, session.env._);
        end
        local old, new = data:match("^!(.-[^\\])!(.-)!$");
        if old and new then
@@ -158,7 +158,7 @@ commands["!"] = function (session, data)
                        return;
                end
                session.print("!> "..res);
-               return console_listener.listener(session.conn, res);
+               return console_listener.onincoming(session.conn, res);
        end
        session.print("Sorry, not sure what you want");
 end
@@ -478,7 +478,7 @@ function def_env.s2s:show(match_jid)
                for remotehost, session in pairs(host_session.s2sout) do
                        if (not match_jid) or remotehost:match(match_jid) or host:match(match_jid) then
                                count_out = count_out + 1;
-                               print("    "..host.." -> "..remotehost..(session.secure and " (encrypted)" or ""));
+                               print("    "..host.." -> "..remotehost..(session.secure and " (encrypted)" or "")..(session.compressed and " (compressed)" or ""));
                                if session.sendq then
                                        print("        There are "..#session.sendq.." queued outgoing stanzas for this connection");
                                end
@@ -515,7 +515,7 @@ function def_env.s2s:show(match_jid)
                                -- Pft! is what I say to list comprehensions
                                or (session.hosts and #array.collect(keys(session.hosts)):filter(subhost_filter)>0)) then
                                count_in = count_in + 1;
-                               print("    "..host.." <- "..(session.from_host or "(unknown)")..(session.secure and " (encrypted)" or ""));
+                               print("    "..host.." <- "..(session.from_host or "(unknown)")..(session.secure and " (encrypted)" or "")..(session.compressed and " (compressed)" or ""));
                                if session.type == "s2sin_unauthed" then
                                                print("        Connection not yet authenticated");
                                end
index 8181c85b724d055bb12b32fc7a9e273bddb8972c..ee0043f1d223455e9637c8aac00b0792e73dfe83 100644 (file)
@@ -7,8 +7,30 @@
 --
 
 local componentmanager_get_children = require "core.componentmanager".get_children;
+local is_contact_subscribed = require "core.rostermanager".is_contact_subscribed;
+local jid_split = require "util.jid".split;
+local jid_bare = require "util.jid".bare;
 local st = require "util.stanza"
 
+local disco_items = module:get_option("disco_items") or {};
+do -- validate disco_items
+       for _, item in ipairs(disco_items) do
+               local err;
+               if type(item) ~= "table" then
+                       err = "item is not a table";
+               elseif type(item[1]) ~= "string" then
+                       err = "item jid is not a string";
+               elseif item[2] and type(item[2]) ~= "string" then
+                       err = "item name is not a string";
+               end
+               if err then
+                       module:log("error", "option disco_items is malformed: %s", err);
+                       disco_items = {}; -- TODO clean up data instead of removing it?
+                       break;
+               end
+       end
+end
+
 module:add_identity("server", "im", "Prosody"); -- FIXME should be in the non-existing mod_router
 module:add_feature("http://jabber.org/protocol/disco#info");
 module:add_feature("http://jabber.org/protocol/disco#items");
@@ -47,6 +69,37 @@ module:hook("iq/host/http://jabber.org/protocol/disco#items:query", function(eve
        for jid in pairs(componentmanager_get_children(module.host)) do
                reply:tag("item", {jid = jid}):up();
        end
+       for _, item in ipairs(disco_items) do
+               reply:tag("item", {jid=item[1], name=item[2]}):up();
+       end
        origin.send(reply);
        return true;
 end);
+module:hook("iq/bare/http://jabber.org/protocol/disco#info:query", function(event)
+       local origin, stanza = event.origin, event.stanza;
+       if stanza.attr.type ~= "get" then return; end
+       local node = stanza.tags[1].attr.node;
+       if node and node ~= "" then return; end -- TODO fire event?
+       local username = jid_split(stanza.attr.to) or origin.username;
+       if not stanza.attr.to or is_contact_subscribed(username, module.host, jid_bare(stanza.attr.from)) then
+               local reply = st.reply(stanza):tag('query', {xmlns='http://jabber.org/protocol/disco#info'});
+               if not reply.attr.from then reply.attr.from = origin.username.."@"..origin.host; end -- COMPAT To satisfy Psi when querying own account
+               module:fire_event("account-disco-info", { session = origin, stanza = reply });
+               origin.send(reply);
+               return true;
+       end
+end);
+module:hook("iq/bare/http://jabber.org/protocol/disco#items:query", function(event)
+       local origin, stanza = event.origin, event.stanza;
+       if stanza.attr.type ~= "get" then return; end
+       local node = stanza.tags[1].attr.node;
+       if node and node ~= "" then return; end -- TODO fire event?
+       local username = jid_split(stanza.attr.to) or origin.username;
+       if not stanza.attr.to or is_contact_subscribed(username, module.host, jid_bare(stanza.attr.from)) then
+               local reply = st.reply(stanza):tag('query', {xmlns='http://jabber.org/protocol/disco#items'});
+               if not reply.attr.from then reply.attr.from = origin.username.."@"..origin.host; end -- COMPAT To satisfy Psi when querying own account
+               module:fire_event("account-disco-items", { session = origin, stanza = reply });
+               origin.send(reply);
+               return true;
+       end
+end);
index 9534be806e2afc66aa537557286928ea24ecca96..d4604b1ec03d0b4f6e6a0b5ba2563de556f48528 100644 (file)
@@ -7,8 +7,8 @@
 --
 
 
-local groups = { default = {} };
-local members = { [false] = {} };
+local groups;
+local members;
 
 local groups_file;
 
@@ -20,7 +20,7 @@ local module_host = module:get_host();
 function inject_roster_contacts(username, host, roster)
        module:log("warn", "Injecting group members to roster");
        local bare_jid = username.."@"..host;
-       if not members[bare_jid] then return; end -- Not a member of any groups
+       if not members[bare_jid] and not members[false] then return; end -- Not a member of any groups
        
        local function import_jids_to_roster(group_name)
                for jid in pairs(groups[group_name]) do
@@ -39,13 +39,23 @@ function inject_roster_contacts(username, host, roster)
        end
 
        -- Find groups this JID is a member of
-       for _, group_name in ipairs(members[bare_jid]) do
-               import_jids_to_roster(group_name);
+       if members[bare_jid] then
+               for _, group_name in ipairs(members[bare_jid]) do
+                       module:log("debug", "Importing group %s", group_name);
+                       import_jids_to_roster(group_name);
+               end
        end
        
        -- Import public groups
-       for _, group_name in ipairs(members[false]) do
-               import_jids_to_roster(group_name);
+       if members[false] then
+               for _, group_name in ipairs(members[false]) do
+                       module:log("debug", "Importing group %s", group_name);
+                       import_jids_to_roster(group_name);
+               end
+       end
+       
+       if roster[false] then
+               roster[false].version = true;
        end
 end
 
@@ -57,6 +67,7 @@ function remove_virtual_contacts(username, host, datastore, data)
                                new_roster[jid] = contact;
                        end
                end
+               new_roster[false].version = nil; -- Version is void
                return username, host, datastore, new_roster;
        end
 
@@ -71,20 +82,23 @@ function module.load()
        datamanager.add_callback(remove_virtual_contacts);
        
        groups = { default = {} };
-       members = { [false] = {} };
+       members = { };
        local curr_group = "default";
        for line in io.lines(groups_file) do
                if line:match("^%s*%[.-%]%s*$") then
                        curr_group = line:match("^%s*%[(.-)%]%s*$");
                        if curr_group:match("^%+") then
                                curr_group = curr_group:gsub("^%+", "");
+                               if not members[false] then
+                                       members[false] = {};
+                               end
                                members[false][#members[false]+1] = curr_group; -- Is a public group
                        end
                        module:log("debug", "New group: %s", tostring(curr_group));
                        groups[curr_group] = groups[curr_group] or {};
                else
                        -- Add JID
-                       local jid = jid_prep(line);
+                       local jid = jid_prep(line:match("%S+"));
                        if jid then
                                module:log("debug", "New member of %s: %s", tostring(curr_group), tostring(jid));
                                groups[curr_group][jid] = true;
index a554be08ede8307bfeee4d733c7a6e004b391199..c55bd20f9b5b56c5c6e1c7ced8f32bc585fd3228 100644 (file)
@@ -76,6 +76,13 @@ local function handle_default_request(method, body, request)
        return serve_file(path);
 end
 
-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" });
+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 f13e9768390f0a395966e34d3edf9d11579c7397..b3001fe5139b633c0f8edec60fa3fc0bb1429b80 100644 (file)
@@ -53,6 +53,18 @@ module:hook("iq/bare", function(data)
        end
 end);
 
+module:hook("iq/self", function(data)
+       -- IQ to bare JID recieved
+       local origin, stanza = data.origin, data.stanza;
+
+       if stanza.attr.type == "get" or stanza.attr.type == "set" then
+               return module:fire_event("iq/self/"..stanza.tags[1].attr.xmlns..":"..stanza.tags[1].name, data);
+       else
+               module:fire_event("iq/self/"..stanza.attr.id, data);
+               return true;
+       end
+end);
+
 module:hook("iq/host", function(data)
        -- IQ to a local host recieved
        local origin, stanza = data.origin, data.stanza;
index 3593ad38a7d9395667920baed678e0cb5b850216..0134d736d831440479573d42e5a5b91d046af44a 100644 (file)
@@ -19,11 +19,12 @@ local nodeprep = require "util.encodings".stringprep.nodeprep;
 local resourceprep = require "util.encodings".stringprep.resourceprep;
 
 module:add_feature("jabber:iq:auth");
-module:add_event_hook("stream-features", function (session, features)
-       if secure_auth_only and not session.secure then
+module:hook("stream-features", function(event)
+       local origin, features = event.origin, event.features;
+       if secure_auth_only and not origin.secure then
                -- Sorry, not offering to insecure streams!
                return;
-       elseif not session.username then
+       elseif not origin.username then
                features:tag("auth", {xmlns='http://jabber.org/features/iq-auth'}):up();
        end
 end);
index faa7251b967b3a0d4d5bac2d39074696e2961c10..aa46d2d31807bb526a595b4e6d4da1fd756141c6 100644 (file)
@@ -277,3 +277,21 @@ module:hook("iq/bare/disco", function(event)
                end
        end
 end);
+
+module:hook("account-disco-info", function(event)
+       local stanza = event.stanza;
+       stanza:tag('identity', {category='pubsub', type='pep'}):up();
+       stanza:tag('feature', {var='http://jabber.org/protocol/pubsub#publish'}):up();
+end);
+
+module:hook("account-disco-items", function(event)
+       local session, stanza = event.session, event.stanza;
+       local bare = session.username..'@'..session.host;
+       local user_data = data[bare];
+
+       if user_data then
+               for node, _ in pairs(user_data) do
+                       stanza:tag('item', {jid=bare, node=node}):up(); -- TODO we need to handle queries to these nodes
+               end
+       end
+end);
index a5fd51ef80faf8acb0677904cdb5775c3365d504..5888ae10d6424e16e3bca793ed107a397222001d 100644 (file)
@@ -7,7 +7,7 @@
 --
 
 
-local want_pposix_version = "0.3.1";
+local want_pposix_version = "0.3.3";
 
 local pposix = assert(require "util.pposix");
 if pposix._VERSION ~= want_pposix_version then module:log("warn", "Unknown version (%s) of binary pposix module, expected %s", tostring(pposix._VERSION), want_pposix_version); end
@@ -26,6 +26,9 @@ local prosody = _G.prosody;
 
 module.host = "*"; -- we're a global module
 
+local umask = module:get_option("umask") or "027";
+pposix.umask(umask);
+
 -- Allow switching away from root, some people like strange ports.
 module:add_event_hook("server-started", function ()
                local uid = module:get_option("setuid");
@@ -159,4 +162,11 @@ if signal.signal then
                prosody.reload_config();
                prosody.reopen_logfiles();
        end);
+       
+       signal.signal("SIGINT", function ()
+               module:log("info", "Received SIGINT");
+               prosody.unlock_globals();
+               prosody.shutdown("Received SIGINT");
+               prosody.lock_globals();
+       end);
 end
index 108ab0d309cc355a034c7e14ebe1516a308d7722..5ad3bfdf28d07c6f108ef7b9939c320e15ca5590 100644 (file)
@@ -38,23 +38,42 @@ function core_route_stanza(origin, stanza)
        _core_route_stanza(origin, stanza);
 end
 
-local function select_top_resources(user)
-       local priority = 0;
-       local recipients = {};
-       for _, session in pairs(user.sessions) do -- find resource with greatest priority
-               if session.presence then
-                       -- TODO check active privacy list for session
+local select_top_resources;
+local bare_message_delivery_policy = module:get_option("bare_message_delivery_policy") or "priority";
+if bare_message_delivery_policy == "broadcast" then
+       function select_top_resources(user)
+               local recipients = {};
+               for _, session in pairs(user.sessions) do -- find resources with non-negative priority
                        local p = session.priority;
-                       if p > priority then
-                               priority = p;
-                               recipients = {session};
-                       elseif p == priority then
+                       if p and p >= 0 then
                                t_insert(recipients, session);
                        end
                end
+               return recipients;
+       end
+else
+       if bare_message_delivery_policy ~= "priority" then
+               module:log("warn", "Invalid value for config option bare_message_delivery_policy");
+       end
+       function select_top_resources(user)
+               local priority = 0;
+               local recipients = {};
+               for _, session in pairs(user.sessions) do -- find resource with greatest priority
+                       if session.presence then
+                               -- TODO check active privacy list for session
+                               local p = session.priority;
+                               if p > priority then
+                                       priority = p;
+                                       recipients = {session};
+                               elseif p == priority then
+                                       t_insert(recipients, session);
+                               end
+                       end
+               end
+               return recipients;
        end
-       return recipients;
 end
+
 local function recalc_resource_map(user)
        if user then
                user.top_resources = select_top_resources(user);
index a1b7acffd6f2c6ff880afd330475d6a9d04c3c5b..77b4dd129a21fa894e91b0ba18732abfd68771e7 100644 (file)
 -- Prosody IM
--- Copyright (C) 2008-2010 Matthew Wild
--- Copyright (C) 2008-2010 Waqas Hussain
+-- Copyright (C) 2009-2010 Matthew Wild
+-- Copyright (C) 2009-2010 Waqas Hussain
+-- Copyright (C) 2009 Thilo Cestonaro
 -- 
 -- This project is MIT/X11 licensed. Please see the
 -- COPYING file in the source package for more information.
 --
 
-
+local prosody = prosody;
 local st = require "util.stanza";
 local datamanager = require "util.datamanager";
+local bare_sessions, full_sessions = bare_sessions, full_sessions;
+local util_Jid = require "util.jid";
+local jid_bare = util_Jid.bare;
+local jid_split = util_Jid.split;
+local load_roster = require "core.rostermanager".load_roster;
+local to_number = tonumber;
+
+function isListUsed(origin, name, privacy_lists)
+       local user = bare_sessions[origin.username.."@"..origin.host];
+       if user then
+               for resource, session in pairs(user.sessions) do
+                       if resource ~= origin.resource then
+                               if session.activePrivacyList == name then
+                                       return true;
+                               elseif session.activePrivacyList == nil and privacy_lists.default == name then
+                                       return true;
+                               end
+                       end
+               end
+       end
+end
+
+function isAnotherSessionUsingDefaultList(origin)
+       local user = bare_sessions[origin.username.."@"..origin.host];
+       if user then
+               for resource, session in pairs(user.sessions) do
+                       if resource ~= origin.resource and session.activePrivacyList == nil then
+                               return true;
+                       end
+               end
+       end
+end
+
+function sendUnavailable(origin, to, from)
+--[[ example unavailable presence stanza
+<presence from="node@host/resource" type="unavailable" to="node@host" >
+       <status>Logged out</status>
+</presence>
+]]--
+       local presence = st.presence({from=from, type="unavailable"});
+       presence:tag("status"):text("Logged out");
+
+       local node, host = jid_bare(to);
+       local bare = node .. "@" .. host;
+       
+       local user = bare_sessions[bare];
+       if user then
+               for resource, session in pairs(user.sessions) do
+                       presence.attr.to = session.full_jid;
+                       module:log("debug", "send unavailable to: %s; from: %s", tostring(presence.attr.to), tostring(presence.attr.from));
+                       origin.send(presence);
+               end
+       end
+end
+
+function declineList(privacy_lists, origin, stanza, which)
+       if which == "default" then
+               if isAnotherSessionUsingDefaultList(origin) then
+                       return { "cancel", "conflict", "Another session is online and using the default list."};
+               end
+               privacy_lists.default = nil;
+               origin.send(st.reply(stanza));
+       elseif which == "active" then
+               origin.activePrivacyList = nil;
+               origin.send(st.reply(stanza));
+       else
+               return {"modify", "bad-request", "Neither default nor active list specifed to decline."};
+       end
+       return true;
+end
+
+function activateList(privacy_lists, origin, stanza, which, name)
+       local list = privacy_lists.lists[name];
+
+       if which == "default" and list then
+               if isAnotherSessionUsingDefaultList(origin) then
+                       return {"cancel", "conflict", "Another session is online and using the default list."};
+               end
+               privacy_lists.default = name;
+               origin.send(st.reply(stanza));
+       elseif which == "active" and list then
+               origin.activePrivacyList = name;
+               origin.send(st.reply(stanza));
+       else
+               return {"modify", "bad-request", "Either not active or default given or unknown list name specified."};
+       end
+       return true;
+end
+
+function deleteList(privacy_lists, origin, stanza, name)
+       local list = privacy_lists.lists[name];
+
+       if list then
+               if isListUsed(origin, name, privacy_lists) then
+                       return {"cancel", "conflict", "Another session is online and using the list which should be deleted."};
+               end
+               if privacy_lists.default == name then
+                       privacy_lists.default = nil;
+               end
+               if origin.activePrivacyList == name then
+                       origin.activePrivacyList = nil;
+               end
+               privacy_lists.lists[name] = nil;
+               origin.send(st.reply(stanza));
+               return true;
+       end
+       return {"modify", "bad-request", "Not existing list specifed to be deleted."};
+end
+
+function createOrReplaceList (privacy_lists, origin, stanza, name, entries, roster)
+       local bare_jid = origin.username.."@"..origin.host;
+       
+       if privacy_lists.lists == nil then
+               privacy_lists.lists = {};
+       end
+
+       local list = {};
+       privacy_lists.lists[name] = list;
+
+       local orderCheck = {};
+       list.name = name;
+       list.items = {};
+
+       for _,item in ipairs(entries) do
+               if to_number(item.attr.order) == nil or to_number(item.attr.order) < 0 or orderCheck[item.attr.order] ~= nil then
+                       return {"modify", "bad-request", "Order attribute not valid."};
+               end
+               
+               if item.attr.type ~= nil and item.attr.type ~= "jid" and item.attr.type ~= "subscription" and item.attr.type ~= "group" then
+                       return {"modify", "bad-request", "Type attribute not valid."};
+               end
+               
+               local tmp = {};
+               orderCheck[item.attr.order] = true;
+               
+               tmp["type"] = item.attr.type;
+               tmp["value"] = item.attr.value;
+               tmp["action"] = item.attr.action;
+               tmp["order"] = to_number(item.attr.order);
+               tmp["presence-in"] = false;
+               tmp["presence-out"] = false;
+               tmp["message"] = false;
+               tmp["iq"] = false;
+               
+               if #item.tags > 0 then
+                       for _,tag in ipairs(item.tags) do
+                               tmp[tag.name] = true;
+                       end
+               end
+               
+               if tmp.type == "group" then
+                       local found = false;
+                       local roster = load_roster(origin.username, origin.host);
+                       for jid,item in pairs(roster) do
+                               if item.groups ~= nil then
+                                       for group in pairs(item.groups) do
+                                               if group == tmp.value then
+                                                       found = true;
+                                                       break;
+                                               end
+                                       end
+                                       if found == true then
+                                               break;
+                                       end
+                               end
+                       end
+                       if found == false then
+                               return {"cancel", "item-not-found", "Specifed roster group not existing."};
+                       end
+               elseif tmp.type == "subscription" then
+                       if      tmp.value ~= "both" and
+                               tmp.value ~= "to" and
+                               tmp.value ~= "from" and
+                               tmp.value ~= "none" then
+                               return {"cancel", "bad-request", "Subscription value must be both, to, from or none."};
+                       end
+               end
+               
+               if tmp.action ~= "deny" and tmp.action ~= "allow" then
+                       return {"cancel", "bad-request", "Action must be either deny or allow."};
+               end
+               list.items[#list.items + 1] = tmp;
+       end
+       
+       table.sort(list, function(a, b) return a.order < b.order; end);
+
+       origin.send(st.reply(stanza));
+       if bare_sessions[bare_jid] ~= nil then
+               local iq = st.iq ( { type = "set", id="push1" } );
+               iq:tag ("query", { xmlns = "jabber:iq:privacy" } );
+               iq:tag ("list", { name = list.name } ):up();
+               iq:up();
+               for resource, session in pairs(bare_sessions[bare_jid].sessions) do
+                       iq.attr.to = bare_jid.."/"..resource
+                       session.send(iq);
+               end
+       else
+               return {"cancel", "bad-request", "internal error."};
+       end
+       return true;
+end
+
+function getList(privacy_lists, origin, stanza, name)
+       local reply = st.reply(stanza);
+       reply:tag("query", {xmlns="jabber:iq:privacy"});
+
+       if name == nil then
+               if privacy_lists.lists then
+                       if origin.ActivePrivacyList then
+                               reply:tag("active", {name=origin.activePrivacyList}):up();
+                       end
+                       if privacy_lists.default then
+                               reply:tag("default", {name=privacy_lists.default}):up();
+                       end
+                       for name,list in pairs(privacy_lists.lists) do
+                               reply:tag("list", {name=name}):up();
+                       end
+               end
+       else
+               local list = privacy_lists.lists[name];
+               if list then
+                       reply = reply:tag("list", {name=list.name});
+                       for _,item in ipairs(list.items) do
+                               reply:tag("item", {type=item.type, value=item.value, action=item.action, order=item.order});
+                               if item["message"] then reply:tag("message"):up(); end
+                               if item["iq"] then reply:tag("iq"):up(); end
+                               if item["presence-in"] then reply:tag("presence-in"):up(); end
+                               if item["presence-out"] then reply:tag("presence-out"):up(); end
+                               reply:up();
+                       end
+               else
+                       return {"cancel", "item-not-found", "Unknown list specified."};
+               end
+       end
+       
+       origin.send(reply);
+       return true;
+end
 
 module:hook("iq/bare/jabber:iq:privacy:query", function(data)
        local origin, stanza = data.origin, data.stanza;
        
-       if not stanza.attr.to then -- only service requests to own bare JID
+       if stanza.attr.to == nil then -- only service requests to own bare JID
                local query = stanza.tags[1]; -- the query element
-               local privacy_lists = datamanager.load(origin.username, origin.host, "privacy") or {};
+               local valid = false;
+               local privacy_lists = datamanager.load(origin.username, origin.host, "privacy") or { lists = {} };
+
+               if privacy_lists.lists[1] then -- Code to migrate from old privacy lists format, remove in 0.8
+                       module:log("info", "Upgrading format of stored privacy lists for %s@%s", origin.username, origin.host);
+                       local lists = privacy_lists.lists;
+                       for idx, list in ipairs(lists) do
+                               lists[list.name] = list;
+                               lists[idx] = nil;
+                       end
+               end
+
                if stanza.attr.type == "set" then
-                       -- TODO
+                       if #query.tags == 1 then --  the <query/> element MUST NOT include more than one child element
+                               for _,tag in ipairs(query.tags) do
+                                       if tag.name == "active" or tag.name == "default" then
+                                               if tag.attr.name == nil then -- Client declines the use of active / default list
+                                                       valid = declineList(privacy_lists, origin, stanza, tag.name);
+                                               else -- Client requests change of active / default list
+                                                       valid = activateList(privacy_lists, origin, stanza, tag.name, tag.attr.name);
+                                               end
+                                       elseif tag.name == "list" and tag.attr.name then -- Client adds / edits a privacy list
+                                               if #tag.tags == 0 then -- Client removes a privacy list
+                                                       valid = deleteList(privacy_lists, origin, stanza, tag.attr.name);
+                                               else -- Client edits a privacy list
+                                                       valid = createOrReplaceList(privacy_lists, origin, stanza, tag.attr.name, tag.tags);
+                                               end
+                                       end
+                               end
+                       end
                elseif stanza.attr.type == "get" then
-                       if #query.tags == 0 then -- Client requests names of privacy lists from server
-                               -- TODO
-                       elseif #query.tags == 1 and query.tags[1].name == "list" then -- Client requests a privacy list from server
-                               -- TODO
-                       else
-                               origin.send(st.error_reply(stanza, "modify", "bad-request"));
+                       local name = nil;
+                       local listsToRetrieve = 0;
+                       if #query.tags >= 1 then
+                               for _,tag in ipairs(query.tags) do
+                                       if tag.name == "list" then -- Client requests a privacy list from server
+                                               name = tag.attr.name;
+                                               listsToRetrieve = listsToRetrieve + 1;
+                                       end
+                               end
+                       end
+                       if listsToRetrieve == 0 or listsToRetrieve == 1 then
+                               valid = getList(privacy_lists, origin, stanza, name);
+                       end
+               end
+
+               if valid ~= true then
+                       valid = valid or { "cancel", "bad-request", "Couldn't understand request" };
+                       if valid[1] == nil then
+                               valid[1] = "cancel";
                        end
+                       if valid[2] == nil then
+                               valid[2] = "bad-request";
+                       end
+                       origin.send(st.error_reply(stanza, valid[1], valid[2], valid[3]));
+               else
+                       datamanager.store(origin.username, origin.host, "privacy", privacy_lists);
                end
+               return true;
        end
 end);
+
+function checkIfNeedToBeBlocked(e, session)
+       local origin, stanza = e.origin, e.stanza;
+       local privacy_lists = datamanager.load(session.username, session.host, "privacy") or {};
+       local bare_jid = session.username.."@"..session.host;
+       local to = stanza.attr.to;
+       local from = stanza.attr.from;
+       
+       local is_to_user = bare_jid == jid_bare(to);
+       local is_from_user = bare_jid == jid_bare(from);
+       
+       module:log("debug", "stanza: %s, to: %s, from: %s", tostring(stanza.name), tostring(to), tostring(from));
+       
+       if privacy_lists.lists == nil or
+               not (session.activePrivacyList or privacy_lists.default)
+       then
+               return; -- Nothing to block, default is Allow all
+       end
+       if is_from_user and is_to_user then
+               module:log("debug", "Not blocking communications between user's resources");
+               return; -- from one of a user's resource to another => HANDS OFF!
+       end
+       
+       local item;
+       local listname = session.activePrivacyList;
+       if listname == nil then
+               listname = privacy_lists.default; -- no active list selected, use default list
+       end
+       local list = privacy_lists.lists[listname];
+       if not list then
+               module:log("debug", "given privacy list not found. name: %s", listname);
+               return;
+       end
+       for _,item in ipairs(list.items) do
+               local apply = false;
+               local block = false;
+               if (
+                       (stanza.name == "message" and item.message) or
+                       (stanza.name == "iq" and item.iq) or
+                       (stanza.name == "presence" and is_to_user and item["presence-in"]) or
+                       (stanza.name == "presence" and is_from_user and item["presence-out"]) or
+                       (item.message == false and item.iq == false and item["presence-in"] == false and item["presence-out"] == false)
+               ) then
+                       apply = true;
+               end
+               if apply then
+                       local evilJid = {};
+                       apply = false;
+                       if is_to_user then
+                               module:log("debug", "evil jid is (from): %s", from);
+                               evilJid.node, evilJid.host, evilJid.resource = jid_split(from);
+                       else
+                               module:log("debug", "evil jid is (to): %s", to);
+                               evilJid.node, evilJid.host, evilJid.resource = jid_split(to);
+                       end
+                       if      item.type == "jid" and
+                               (evilJid.node and evilJid.host and evilJid.resource and item.value == evilJid.node.."@"..evilJid.host.."/"..evilJid.resource) or
+                               (evilJid.node and evilJid.host and item.value == evilJid.node.."@"..evilJid.host) or
+                               (evilJid.host and evilJid.resource and item.value == evilJid.host.."/"..evilJid.resource) or
+                               (evilJid.host and item.value == evilJid.host) then
+                               apply = true;
+                               block = (item.action == "deny");
+                       elseif item.type == "group" then
+                               local roster = load_roster(session.username, session.host);
+                               local groups = roster[evilJid.node .. "@" .. evilJid.host].groups;
+                               for group in pairs(groups) do
+                                       if group == item.value then
+                                               apply = true;
+                                               block = (item.action == "deny");
+                                               break;
+                                       end
+                               end
+                       elseif item.type == "subscription" and evilJid.node ~= nil and evilJid.host ~= nil then -- we need a valid bare evil jid
+                               local roster = load_roster(session.username, session.host);
+                               if roster[evilJid.node .. "@" .. evilJid.host].subscription == item.value then
+                                       apply = true;
+                                       block = (item.action == "deny");
+                               end
+                       elseif item.type == nil then
+                               apply = true;
+                               block = (item.action == "deny");
+                       end
+               end
+               if apply then
+                       if block then
+                               module:log("debug", "stanza blocked: %s, to: %s, from: %s", tostring(stanza.name), tostring(to), tostring(from));
+                               if stanza.name == "message" then
+                                       origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
+                               elseif stanza.name == "iq" and (stanza.attr.type == "get" or stanza.attr.type == "set") then
+                                       origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
+                               end
+                               return true; -- stanza blocked !
+                       else
+                               module:log("debug", "stanza explicitly allowed!")
+                               return;
+                       end
+               end
+       end
+end
+
+function preCheckIncoming(e)
+       local session;
+       if e.stanza.attr.to ~= nil then
+               local node, host, resource = jid_split(e.stanza.attr.to);
+               if node == nil or host == nil then
+                       return;
+               end
+               if resource == nil then
+                       local prio = 0;
+                       local session_;
+                       if bare_sessions[node.."@"..host] ~= nil then
+                               for resource, session_ in pairs(bare_sessions[node.."@"..host].sessions) do
+                                       if session_.priority ~= nil and session_.priority > prio then
+                                               session = session_;
+                                               prio = session_.priority;
+                                       end
+                               end
+                       end
+               else
+                       session = full_sessions[node.."@"..host.."/"..resource];
+               end
+               if session ~= nil then
+                       return checkIfNeedToBeBlocked(e, session);
+               else
+                       module:log("debug", "preCheckIncoming: Couldn't get session for jid: %s@%s/%s", tostring(node), tostring(host), tostring(resource));
+               end
+       end
+end
+
+function preCheckOutgoing(e)
+       local session = e.origin;
+       if e.stanza.attr.from == nil then
+               e.stanza.attr.from = session.username .. "@" .. session.host;
+               if session.resource ~= nil then
+                       e.stanza.attr.from = e.stanza.attr.from .. "/" .. session.resource;
+               end
+       end
+       return checkIfNeedToBeBlocked(e, session);
+end
+
+module:hook("pre-message/full", preCheckOutgoing, 500);
+module:hook("pre-message/bare", preCheckOutgoing, 500);
+module:hook("pre-message/host", preCheckOutgoing, 500);
+module:hook("pre-iq/full", preCheckOutgoing, 500);
+module:hook("pre-iq/bare", preCheckOutgoing, 500);
+module:hook("pre-iq/host", preCheckOutgoing, 500);
+module:hook("pre-presence/full", preCheckOutgoing, 500);
+module:hook("pre-presence/bare", preCheckOutgoing, 500);
+module:hook("pre-presence/host", preCheckOutgoing, 500);
+
+module:hook("message/full", preCheckIncoming, 500);
+module:hook("message/bare", preCheckIncoming, 500);
+module:hook("message/host", preCheckIncoming, 500);
+module:hook("iq/full", preCheckIncoming, 500);
+module:hook("iq/bare", preCheckIncoming, 500);
+module:hook("iq/host", preCheckIncoming, 500);
+module:hook("presence/full", preCheckIncoming, 500);
+module:hook("presence/bare", preCheckIncoming, 500);
+module:hook("presence/host", preCheckIncoming, 500);
index 755d263d6565d2f35474d88d7cfdbf1f32e562a4..ddf02f2f808f4715694517e13f52cd2fe89b77dc 100644 (file)
@@ -23,12 +23,12 @@ local core_post_stanza = core_post_stanza;
 module:add_feature("jabber:iq:roster");
 
 local rosterver_stream_feature = st.stanza("ver", {xmlns="urn:xmpp:features:rosterver"}):tag("optional"):up();
-module:add_event_hook("stream-features", 
-               function (session, features)
-                       if session.username then
-                               features:add_child(rosterver_stream_feature);
-                       end
-               end);
+module:hook("stream-features", function(event)
+       local origin, features = event.origin, event.features;
+       if origin.username then
+               features:add_child(rosterver_stream_feature);
+       end
+end);
 
 module:add_iq_handler("c2s", "jabber:iq:roster", 
                function (session, stanza)
@@ -36,9 +36,10 @@ module:add_iq_handler("c2s", "jabber:iq:roster",
                                if stanza.attr.type == "get" then
                                        local roster = st.reply(stanza);
                                        
-                                       local ver = stanza.tags[1].attr.ver
+                                       local client_ver = tonumber(stanza.tags[1].attr.ver);
+                                       local server_ver = tonumber(session.roster[false].version or 1);
                                        
-                                       if (not ver) or tonumber(ver) ~= (session.roster[false].version or 1) then
+                                       if not (client_ver and server_ver) or client_ver ~= server_ver then
                                                roster:query("jabber:iq:roster");
                                                -- Client does not support versioning, or has stale roster
                                                for jid in pairs(session.roster) do
@@ -55,7 +56,7 @@ module:add_iq_handler("c2s", "jabber:iq:roster",
                                                                roster:up(); -- move out from item
                                                        end
                                                end
-                                               roster.tags[1].attr.ver = tostring(session.roster[false].version or "1");
+                                               roster.tags[1].attr.ver = server_ver;
                                        end
                                        session.send(roster);
                                        session.interested = true; -- resource is interested in roster updates
index 85481ad756bfddefeddc084f5ce8f2751545a955..c03605536e59fe47dde473afdadc02a2f36c467f 100644 (file)
@@ -21,11 +21,12 @@ local usermanager_user_exists = require "core.usermanager".user_exists;
 local usermanager_get_password = require "core.usermanager".get_password;
 local t_concat, t_insert = table.concat, table.insert;
 local tostring = tostring;
-local jid_split = require "util.jid".split
+local jid_split = require "util.jid".split;
 local md5 = require "util.hashes".md5;
 local config = require "core.configmanager";
 
-local secure_auth_only = config.get(module:get_host(), "core", "c2s_require_encryption") or config.get(module:get_host(), "core", "require_encryption");
+local secure_auth_only = module:get_option("c2s_require_encryption") or module:get_option("require_encryption");
+local sasl_backend = module:get_option("sasl_backend") or "builtin";
 
 local log = module._log;
 
@@ -33,7 +34,48 @@ local xmlns_sasl ='urn:ietf:params:xml:ns:xmpp-sasl';
 local xmlns_bind ='urn:ietf:params:xml:ns:xmpp-bind';
 local xmlns_stanzas ='urn:ietf:params:xml:ns:xmpp-stanzas';
 
-local new_sasl = require "util.sasl".new;
+local new_sasl;
+if sasl_backend == "builtin" then
+       new_sasl = require "util.sasl".new;
+elseif sasl_backend == "cyrus" then
+       prosody.unlock_globals(); --FIXME: Figure out why this is needed and
+                                 -- why cyrussasl isn't caught by the sandbox
+       local ok, cyrus = pcall(require, "util.sasl_cyrus");
+       prosody.lock_globals();
+       if ok then
+               local cyrus_new = cyrus.new;
+               new_sasl = function(realm)
+                       return cyrus_new(realm, module:get_option("cyrus_service_name") or "xmpp");
+               end
+       else
+               module:log("error", "Failed to load Cyrus SASL because: %s", cyrus);
+               error("Failed to load Cyrus SASL");
+       end
+else
+       module:log("error", "Unknown SASL backend: %s", sasl_backend);
+       error("Unknown SASL backend");
+end
+
+local default_authentication_profile = {
+       plain = function(username, realm)
+               local prepped_username = nodeprep(username);
+               if not prepped_username then
+                       log("debug", "NODEprep failed on username: %s", username);
+                       return "", nil;
+               end
+               local password = usermanager_get_password(prepped_username, realm);
+               if not password then
+                       return "", nil;
+               end
+               return password, true;
+       end
+};
+
+local anonymous_authentication_profile = {
+       anonymous = function(username, realm)
+               return true; -- for normal usage you should always return true here
+       end
+};
 
 local function build_reply(status, ret, err_msg)
        local reply = st.stanza(status, {xmlns = xmlns_sasl});
@@ -54,50 +96,18 @@ end
 
 local function handle_status(session, status)
        if status == "failure" then
-               session.sasl_handler = nil;
+               session.sasl_handler = session.sasl_handler:clean_clone();
        elseif status == "success" then
                local username = nodeprep(session.sasl_handler.username);
-               session.sasl_handler = nil;
                if not username then -- TODO move this to sessionmanager
                        module:log("warn", "SASL succeeded but we didn't get a username!");
                        session.sasl_handler = nil;
                        session:reset_stream();
                        return;
-               end 
-               sm_make_authenticated(session, username);
-               session:reset_stream();
-       end
-end
-
-local function credentials_callback(mechanism, ...)
-       if mechanism == "PLAIN" then
-               local username, hostname, password = ...;
-               username = nodeprep(username);
-               if not username then
-                       return false;
-               end
-               local response = usermanager_validate_credentials(hostname, username, password, mechanism);
-               if response == nil then
-                       return false;
-               else
-                       return response;
-               end
-       elseif mechanism == "DIGEST-MD5" then
-               local function func(x) return x; end
-               local node, domain, realm, decoder = ...;
-               local prepped_node = nodeprep(node);
-               if not prepped_node then
-                       return func, nil;
-               end
-               local password = usermanager_get_password(prepped_node, domain);
-               if password then
-                       if decoder then
-                               node, realm, password = decoder(node), decoder(realm), decoder(password);
-                       end
-                       return func, md5(node..":"..realm..":"..password);
-               else
-                       return func, nil;
                end
+               sm_make_authenticated(session, session.sasl_handler.username);
+               session.sasl_handler = nil;
+               session:reset_stream();
        end
 end
 
@@ -111,8 +121,8 @@ local function sasl_handler(session, stanza)
                elseif stanza.attr.mechanism == "ANONYMOUS" then
                        return session.send(build_reply("failure", "mechanism-too-weak"));
                end
-               session.sasl_handler = new_sasl(stanza.attr.mechanism, session.host, credentials_callback);
-               if not session.sasl_handler then
+               local valid_mechanism = session.sasl_handler:select(stanza.attr.mechanism);
+               if not valid_mechanism then
                        return session.send(build_reply("failure", "invalid-mechanism"));
                end
                if secure_auth_only and not session.secure then
@@ -131,7 +141,7 @@ local function sasl_handler(session, stanza)
                        return;
                end
        end
-       local status, ret, err_msg = session.sasl_handler:feed(text);
+       local status, ret, err_msg = session.sasl_handler:process(text);
        handle_status(session, status);
        local s = build_reply(status, ret, err_msg);
        log("debug", "sasl reply: %s", tostring(s));
@@ -145,54 +155,55 @@ module:add_handler("c2s_unauthed", "response", xmlns_sasl, sasl_handler);
 local mechanisms_attr = { xmlns='urn:ietf:params:xml:ns:xmpp-sasl' };
 local bind_attr = { xmlns='urn:ietf:params:xml:ns:xmpp-bind' };
 local xmpp_session_attr = { xmlns='urn:ietf:params:xml:ns:xmpp-session' };
-module:add_event_hook("stream-features",
-               function (session, features)
-                       if not session.username then
-                               if secure_auth_only and not session.secure then
-                                       return;
-                               end
-                               features:tag("mechanisms", mechanisms_attr);
-                               -- TODO: Provide PLAIN only if TLS is active, this is a SHOULD from the introduction of RFC 4616. This behavior could be overridden via configuration but will issuing a warning or so.
-                                       if config.get(session.host or "*", "core", "anonymous_login") then
-                                               features:tag("mechanism"):text("ANONYMOUS"):up();
-                                       else
-                                               local mechanisms = usermanager_get_supported_methods(session.host or "*");
-                                               for k, v in pairs(mechanisms) do
-                                                       features:tag("mechanism"):text(k):up();
-                                               end
-                                       end
-                               features:up();
-                       else
-                               features:tag("bind", bind_attr):tag("required"):up():up();
-                               features:tag("session", xmpp_session_attr):tag("optional"):up():up();
-                       end
-               end);
-
-module:add_iq_handler("c2s", "urn:ietf:params:xml:ns:xmpp-bind",
-               function (session, stanza)
-                       log("debug", "Client requesting a resource bind");
-                       local resource;
-                       if stanza.attr.type == "set" then
-                               local bind = stanza.tags[1];
-                               if bind and bind.attr.xmlns == xmlns_bind then
-                                       resource = bind:child_with_name("resource");
-                                       if resource then
-                                               resource = resource[1];
-                                       end
-                               end
+module:hook("stream-features", function(event)
+       local origin, features = event.origin, event.features;
+       if not origin.username then
+               if secure_auth_only and not origin.secure then
+                       return;
+               end
+               local realm = module:get_option("sasl_realm") or origin.host;
+               if module:get_option("anonymous_login") then
+                       origin.sasl_handler = new_sasl(realm, anonymous_authentication_profile);
+               else
+                       origin.sasl_handler = new_sasl(realm, default_authentication_profile);
+                       if not (module:get_option("allow_unencrypted_plain_auth")) and not origin.secure then
+                               origin.sasl_handler:forbidden({"PLAIN"});
                        end
-                       local success, err_type, err, err_msg = sm_bind_resource(session, resource);
-                       if not success then
-                               session.send(st.error_reply(stanza, err_type, err, err_msg));
-                       else
-                               session.send(st.reply(stanza)
-                                       :tag("bind", { xmlns = xmlns_bind})
-                                       :tag("jid"):text(session.full_jid));
+               end
+               features:tag("mechanisms", mechanisms_attr);
+               for k, v in pairs(origin.sasl_handler:mechanisms()) do
+                       features:tag("mechanism"):text(v):up();
+               end
+               features:up();
+       else
+               features:tag("bind", bind_attr):tag("required"):up():up();
+               features:tag("session", xmpp_session_attr):tag("optional"):up():up();
+       end
+end);
+
+module:add_iq_handler("c2s", "urn:ietf:params:xml:ns:xmpp-bind", function(session, stanza)
+       log("debug", "Client requesting a resource bind");
+       local resource;
+       if stanza.attr.type == "set" then
+               local bind = stanza.tags[1];
+               if bind and bind.attr.xmlns == xmlns_bind then
+                       resource = bind:child_with_name("resource");
+                       if resource then
+                               resource = resource[1];
                        end
-               end);
+               end
+       end
+       local success, err_type, err, err_msg = sm_bind_resource(session, resource);
+       if not success then
+               session.send(st.error_reply(stanza, err_type, err, err_msg));
+       else
+               session.send(st.reply(stanza)
+                       :tag("bind", { xmlns = xmlns_bind})
+                       :tag("jid"):text(session.full_jid));
+       end
+end);
 
-module:add_iq_handler("c2s", "urn:ietf:params:xml:ns:xmpp-session",
-               function (session, stanza)
-                       log("debug", "Client requesting a session");
-                       session.send(st.reply(stanza));
-               end);
+module:add_iq_handler("c2s", "urn:ietf:params:xml:ns:xmpp-session", function(session, stanza)
+       log("debug", "Client requesting a session");
+       session.send(st.reply(stanza));
+end);
index f68552fac336178c8632d001efd26318c12cc7a0..8b96aa157922c7bf1f63613a0e000e3baf72851f 100644 (file)
 
 local st = require "util.stanza";
 
-local xmlns_stream = 'http://etherx.jabber.org/streams';
-local xmlns_starttls = 'urn:ietf:params:xml:ns:xmpp-tls';
-
 local secure_auth_only = module:get_option("c2s_require_encryption") or module:get_option("require_encryption");
 local secure_s2s_only = module:get_option("s2s_require_encryption");
+local allow_s2s_tls = module:get_option("s2s_allow_encryption") ~= false;
 
-local host = hosts[module.host];
-
+local xmlns_starttls = 'urn:ietf:params:xml:ns:xmpp-tls';
 local starttls_attr = { xmlns = xmlns_starttls };
+local starttls_proceed = st.stanza("proceed", starttls_attr);
+local starttls_failure = st.stanza("failure", starttls_attr);
+local c2s_feature = st.stanza("starttls", starttls_attr);
+local s2s_feature = st.stanza("starttls", starttls_attr);
+if secure_auth_only then c2s_feature:tag("required"):up(); end
+if secure_s2s_only then s2s_feature:tag("required"):up(); end
 
---- Client-to-server TLS handling
-module:add_handler("c2s_unauthed", "starttls", xmlns_starttls,
-               function (session, stanza)
-                       if session.conn.starttls and host.ssl_ctx_in then
-                               session.send(st.stanza("proceed", starttls_attr));
-                               session:reset_stream();
-                               if session.host and hosts[session.host].ssl_ctx_in then
-                                       session.conn.set_sslctx(hosts[session.host].ssl_ctx_in);
-                               end
-                               session.conn.starttls();
-                               session.log("info", "TLS negotiation started...");
-                               session.secure = false;
-                       else
-                               session.log("warn", "Attempt to start TLS, but TLS is not available on this connection");
-                               (session.sends2s or session.send)(st.stanza("failure", starttls_attr));
-                               session:close();
-                       end
-               end);
+local global_ssl_ctx = prosody.global_ssl_ctx;
 
-module:add_event_hook("stream-features", 
-               function (session, features)
-                       if session.conn.starttls then
-                               features:tag("starttls", starttls_attr);
-                               if secure_auth_only then
-                                       features:tag("required"):up():up();
-                               else
-                                       features:up();
-                               end
-                       end
-               end);
----
+local host = hosts[module.host];
 
--- Stop here if the user doesn't want to allow s2s encryption
-if module:get_option("s2s_allow_encryption") == false then
-       return;
+local function can_do_tls(session)
+       if session.type == "c2s_unauthed" then
+               return session.conn.starttls and host.ssl_ctx_in;
+       elseif session.type == "s2sin_unauthed" and allow_s2s_tls then
+               return session.conn.starttls and host.ssl_ctx_in;
+       elseif session.direction == "outgoing" and allow_s2s_tls then
+               return session.conn.starttls and host.ssl_ctx;
+       end
+       return false;
 end
 
---- Server-to-server TLS handling
-module:add_handler("s2sin_unauthed", "starttls", xmlns_starttls,
-               function (session, stanza)
-                       if session.conn.starttls and host.ssl_ctx_in then
-                               session.sends2s(st.stanza("proceed", starttls_attr));
-                               session:reset_stream();
-                               if session.to_host and hosts[session.to_host].ssl_ctx_in then
-                                       session.conn.set_sslctx(hosts[session.to_host].ssl_ctx_in);
-                               end
-                               session.conn.starttls();
-                               session.log("info", "TLS negotiation started for incoming s2s...");
-                               session.secure = false;
-                       else
-                               session.log("warn", "Attempt to start TLS, but TLS is not available on this s2s connection");
-                               (session.sends2s or session.send)(st.stanza("failure", starttls_attr));
-                               session:close();
-                       end
-               end);
-
+-- Hook <starttls/>
+module:hook("stanza/urn:ietf:params:xml:ns:xmpp-tls:starttls", function(event)
+       local origin = event.origin;
+       if can_do_tls(origin) then
+               (origin.sends2s or origin.send)(starttls_proceed);
+               origin:reset_stream();
+               local host = origin.to_host or origin.host;
+               local ssl_ctx = host and hosts[host].ssl_ctx_in or global_ssl_ctx;
+               origin.conn:starttls(ssl_ctx);
+               origin.log("info", "TLS negotiation started for %s...", origin.type);
+               origin.secure = false;
+       else
+               origin.log("warn", "Attempt to start TLS, but TLS is not available on this %s connection", origin.type);
+               (origin.sends2s or origin.send)(starttls_failure);
+               origin:close();
+       end
+       return true;
+end);
 
-module:hook("s2s-stream-features", 
-               function (data)
-                       local session, features = data.session, data.features;
-                       if session.to_host and session.conn.starttls then
-                               features:tag("starttls", starttls_attr);
-                               if secure_s2s_only then
-                                       features:tag("required"):up():up();
-                               else
-                                       features:up();
-                               end
-                       end
-               end);
+-- Advertize stream feature
+module:hook("stream-features", function(event)
+       local origin, features = event.origin, event.features;
+       if can_do_tls(origin) then
+               features:add_child(c2s_feature);
+       end
+end);
+module:hook("s2s-stream-features", function(event)
+       local origin, features = event.origin, event.features;
+       if can_do_tls(origin) then
+               features:add_child(s2s_feature);
+       end
+end);
 
 -- For s2sout connections, start TLS if we can
-module:hook_stanza(xmlns_stream, "features",
-               function (session, stanza)
-                       module:log("debug", "Received features element");
-                       if session.conn.starttls and stanza:child_with_ns(xmlns_starttls) then
-                               module:log("%s is offering TLS, taking up the offer...", session.to_host);
-                               session.sends2s("<starttls xmlns='"..xmlns_starttls.."'/>");
-                               return true;
-                       end
-               end, 500);
+module:hook_stanza("http://etherx.jabber.org/streams", "features", function (session, stanza)
+       module:log("debug", "Received features element");
+       if can_do_tls(session) and stanza:child_with_ns(xmlns_starttls) then
+               module:log("%s is offering TLS, taking up the offer...", session.to_host);
+               session.sends2s("<starttls xmlns='"..xmlns_starttls.."'/>");
+               return true;
+       end
+end, 500);
 
-module:hook_stanza(xmlns_starttls, "proceed",
-               function (session, stanza)
-                       module:log("debug", "Proceeding with TLS on s2sout...");
-                       local format, to_host, from_host = string.format, session.to_host, session.from_host;
-                       local ssl_ctx = session.from_host and hosts[session.from_host].ssl_ctx or global_ssl_ctx;
-                       session.conn.set_sslctx(ssl_ctx);
-                       session:reset_stream();
-                       session.conn.starttls(true);
-                       session.secure = false;
-                       return true;
-               end);
+module:hook_stanza(xmlns_starttls, "proceed", function (session, stanza)
+       module:log("debug", "Proceeding with TLS on s2sout...");
+       session:reset_stream();
+       local ssl_ctx = session.from_host and hosts[session.from_host].ssl_ctx or global_ssl_ctx;
+       session.conn:starttls(ssl_ctx, true);
+       session.secure = false;
+       return true;
+end);
diff --git a/plugins/mod_xmlrpc.lua b/plugins/mod_xmlrpc.lua
deleted file mode 100644 (file)
index 92f296c..0000000
+++ /dev/null
@@ -1,128 +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.
---
-
-
-module.host = "*" -- Global module
-
-local httpserver = require "net.httpserver";
-local st = require "util.stanza";
-local pcall = pcall;
-local unpack = unpack;
-local tostring = tostring;
-local is_admin = require "core.usermanager".is_admin;
-local jid_split = require "util.jid".split;
-local jid_bare = require "util.jid".bare;
-local b64_decode = require "util.encodings".base64.decode;
-local get_method = require "core.objectmanager".get_object;
-local validate_credentials = require "core.usermanager".validate_credentials;
-
-local translate_request = require "util.xmlrpc".translate_request;
-local create_response = require "util.xmlrpc".create_response;
-local create_error_response = require "util.xmlrpc".create_error_response;
-
-local entity_map = setmetatable({
-       ["amp"] = "&";
-       ["gt"] = ">";
-       ["lt"] = "<";
-       ["apos"] = "'";
-       ["quot"] = "\"";
-}, {__index = function(_, s)
-               if s:sub(1,1) == "#" then
-                       if s:sub(2,2) == "x" then
-                               return string.char(tonumber(s:sub(3), 16));
-                       else
-                               return string.char(tonumber(s:sub(2)));
-                       end
-               end
-       end
-});
-local function xml_unescape(str)
-       return (str:gsub("&(.-);", entity_map));
-end
-local function parse_xml(xml)
-       local stanza = st.stanza("root");
-       local regexp = "<([^>]*)>([^<]*)";
-       for elem, text in xml:gmatch(regexp) do
-               --print("[<"..elem..">|"..text.."]");
-               if elem:sub(1,1) == "!" or elem:sub(1,1) == "?" then -- neglect comments and processing-instructions
-               elseif elem:sub(1,1) == "/" then -- end tag
-                       elem = elem:sub(2);
-                       stanza:up(); -- TODO check for start-end tag name match
-               elseif elem:sub(-1,-1) == "/" then -- empty tag
-                       elem = elem:sub(1,-2);
-                       stanza:tag(elem):up();
-               else -- start tag
-                       stanza:tag(elem);
-               end
-               if #text ~= 0 then -- text
-                       stanza:text(xml_unescape(text));
-               end
-       end
-       return stanza.tags[1];
-end
-
-local function handle_xmlrpc_request(jid, method, args)
-       local is_secure_call = (method:sub(1,7) == "secure/");
-       if not is_admin(jid) and not is_secure_call then
-               return create_error_response(401, "not authorized");
-       end
-       method = get_method(method);
-       if not method then return create_error_response(404, "method not found"); end
-       args = args or {};
-       if is_secure_call then table.insert(args, 1, jid); end
-       local success, result = pcall(method, unpack(args));
-       if success then
-               success, result = pcall(create_response, result or "nil");
-               if success then
-                       return result;
-               end
-               return create_error_response(500, "Error in creating response: "..result);
-       end
-       return create_error_response(0, tostring(result):gsub("^[^:]+:%d+: ", ""));
-end
-
-local function handle_xmpp_request(origin, stanza)
-       local query = stanza.tags[1];
-       if query.name == "query" then
-               if #query.tags == 1 then
-                       local success, method, args = pcall(translate_request, query.tags[1]);
-                       if success then
-                               local result = handle_xmlrpc_request(jid_bare(stanza.attr.from), method, args);
-                               origin.send(st.reply(stanza):tag('query', {xmlns='jabber:iq:rpc'}):add_child(result));
-                       else
-                               origin.send(st.error_reply(stanza, "modify", "bad-request", method));
-                       end
-               else origin.send(st.error_reply(stanza, "modify", "bad-request", "No content in XML-RPC request")); end
-       else origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); end
-end
-module:add_iq_handler({"c2s", "s2sin"}, "jabber:iq:rpc", handle_xmpp_request);
-module:add_feature("jabber:iq:rpc");
--- TODO add <identity category='automation' type='rpc'/> to disco replies
-
-local default_headers = { ['Content-Type'] = 'text/xml' };
-local unauthorized_response = { status = '401 UNAUTHORIZED', headers = {['Content-Type']='text/html', ['WWW-Authenticate']='Basic realm="WallyWorld"'}; body = "<html><body>Authentication required</body></html>"; };
-local function handle_http_request(method, body, request)
-       -- authenticate user
-       local username, password = b64_decode(request['authorization'] or ''):gmatch('([^:]*):(.*)')(); -- TODO digest auth
-       local node, host = jid_split(username);
-       if not validate_credentials(host, node, password) then
-               return unauthorized_response;
-       end
-       -- parse request
-       local stanza = body and parse_xml(body);
-       if (not stanza) or request.method ~= "POST" then
-               return "<html><body>You really don't look like an XML-RPC client to me... what do you want?</body></html>";
-       end
-       -- execute request
-       local success, method, args = pcall(translate_request, stanza);
-       if success then
-               return { headers = default_headers; body = tostring(handle_xmlrpc_request(node.."@"..host, method, args)) };
-       end
-       return "<html><body>Error parsing XML-RPC request: "..tostring(method).."</body></html>";
-end
-httpserver.new{ port = 9000, base = "xmlrpc", handler = handle_http_request }
index 680b902aebbb84cf9c9a74dcf28c0e6e145d2188..de23aebba736752959bc92a93e4e77a19b3fc911 100644 (file)
@@ -16,7 +16,6 @@ local muc_name = module:get_option("name");
 if type(muc_name) ~= "string" then muc_name = "Prosody Chatrooms"; end
 local restrict_room_creation = module:get_option("restrict_room_creation");
 if restrict_room_creation and restrict_room_creation ~= true then restrict_room_creation = nil; end
-local history_length = 20;
 
 local muc_new_room = module:require "muc".new_room;
 local register_component = require "core.componentmanager".register_component;
index 1ec2349a23971ae3fabefd22c7c00b25be084c3e..1cc001bb0fc8ead4aca56c7e9420f0a0edadbf33 100644 (file)
@@ -59,19 +59,12 @@ local kickable_error_conditions = {
        ["service-unavailable"] = true;
        ["malformed error"] = true;
 };
+
 local function get_error_condition(stanza)
-       for _, tag in ipairs(stanza.tags) do
-               if tag.name == "error" and (not(tag.attr.xmlns) or tag.attr.xmlns == "jabber:client") then
-                       for _, cond in ipairs(tag.tags) do
-                               if cond.attr.xmlns == "urn:ietf:params:xml:ns:xmpp-stanzas" then
-                                       return cond.name;
-                               end
-                       end
-                       return "malformed error";
-               end
-       end
-       return "malformed error";
+       local _, condition = stanza:get_error();
+       return condition or "malformed error";
 end
+
 local function is_kickable_error(stanza)
        local cond = get_error_condition(stanza);
        return kickable_error_conditions[cond] and cond;
@@ -89,17 +82,6 @@ local function getTag(stanza, path) return getUsingPath(stanza, path); end
 local function getText(stanza, path) return getUsingPath(stanza, path, true); end
 -----------
 
---[[function get_room_disco_info(room, stanza)
-       return st.iq({type='result', id=stanza.attr.id, from=stanza.attr.to, to=stanza.attr.from}):query("http://jabber.org/protocol/disco#info")
-               :tag("identity", {category='conference', type='text', name=room._data["name"]):up()
-               :tag("feature", {var="http://jabber.org/protocol/muc"}); -- TODO cache disco reply
-end
-function get_room_disco_items(room, stanza)
-       return st.iq({type='result', id=stanza.attr.id, from=stanza.attr.to, to=stanza.attr.from}):query("http://jabber.org/protocol/disco#items");
-end -- TODO allow non-private rooms]]
-
---
-
 local room_mt = {};
 room_mt.__index = room_mt;
 
@@ -183,12 +165,12 @@ function room_mt:send_history(to)
        end
 end
 
-local function room_get_disco_info(self, stanza)
+function room_mt:get_disco_info(stanza)
        return st.reply(stanza):query("http://jabber.org/protocol/disco#info")
                :tag("identity", {category="conference", type="text"}):up()
                :tag("feature", {var="http://jabber.org/protocol/muc"});
 end
-local function room_get_disco_items(self, stanza)
+function room_mt:get_disco_items(stanza)
        local reply = st.reply(stanza):query("http://jabber.org/protocol/disco#items");
        for room_jid in pairs(self._occupants) do
                reply:tag("item", {jid = room_jid, name = room_jid:match("/(.*)")}):up();
@@ -206,6 +188,16 @@ function room_mt:set_subject(current_nick, subject)
        return true;
 end
 
+local function build_unavailable_presence_from_error(stanza)
+       local type, condition, text = stanza:get_error();
+       local error_message = "Kicked: "..condition:gsub("%-", " ");
+       if text then
+               error_message = error_message..": "..text;
+       end
+       return st.presence({type='unavailable', from=stanza.attr.from, to=stanza.attr.to})
+               :tag('status'):text(error_message);
+end
+
 function room_mt:handle_to_occupant(origin, stanza) -- PM, vCards, etc
        local from, to = stanza.attr.from, stanza.attr.to;
        local room = jid_bare(to);
@@ -219,8 +211,7 @@ function room_mt:handle_to_occupant(origin, stanza) -- PM, vCards, etc
                if type == "error" then -- error, kick em out!
                        if current_nick then
                                log("debug", "kicking %s from %s", current_nick, room);
-                               self:handle_to_occupant(origin, st.presence({type='unavailable', from=from, to=to})
-                                       :tag('status'):text('Kicked: '..get_error_condition(stanza))); -- send unavailable
+                               self:handle_to_occupant(origin, build_unavailable_presence_from_error(stanza));
                        end
                elseif type == "unavailable" then -- unavailable
                        if current_nick then
@@ -367,8 +358,7 @@ function room_mt:handle_to_occupant(origin, stanza) -- PM, vCards, etc
                origin.send(st.error_reply(stanza, "modify", "bad-request"));
        elseif current_nick and stanza.name == "message" and type == "error" and is_kickable_error(stanza) then
                log("debug", "%s kicked from %s for sending an error message", current_nick, self.jid);
-               self:handle_to_occupant(origin, st.presence({type='unavailable', from=stanza.attr.from, to=stanza.attr.to})
-                       :tag('status'):text('Kicked: '..get_error_condition(stanza))); -- send unavailable
+               self:handle_to_occupant(origin, build_unavailable_presence_from_error(stanza)); -- send unavailable
        else -- private stanza
                local o_data = self._occupants[to];
                if o_data then
@@ -389,51 +379,112 @@ function room_mt:handle_to_occupant(origin, stanza) -- PM, vCards, etc
        end
 end
 
-function room_mt:handle_form(origin, stanza)
-       if self:get_affiliation(stanza.attr.from) ~= "owner" then origin.send(st.error_reply(stanza, "auth", "forbidden")); return; end
-       if stanza.attr.type == "get" then
-               local title = "Configuration for "..self.jid;
-               origin.send(st.reply(stanza):query("http://jabber.org/protocol/muc#owner")
-                       :tag("x", {xmlns='jabber:x:data', type='form'})
-                               :tag("title"):text(title):up()
-                               :tag("instructions"):text(title):up()
-                               :tag("field", {type='hidden', var='FORM_TYPE'}):tag("value"):text("http://jabber.org/protocol/muc#roomconfig"):up():up()
-                               :tag("field", {type='boolean', label='Make Room Persistent?', var='muc#roomconfig_persistentroom'})
-                                       :tag("value"):text(self._data.persistent and "1" or "0"):up()
+function room_mt:send_form(origin, stanza)
+       local title = "Configuration for "..self.jid;
+       origin.send(st.reply(stanza):query("http://jabber.org/protocol/muc#owner")
+               :tag("x", {xmlns='jabber:x:data', type='form'})
+                       :tag("title"):text(title):up()
+                       :tag("instructions"):text(title):up()
+                       :tag("field", {type='hidden', var='FORM_TYPE'}):tag("value"):text("http://jabber.org/protocol/muc#roomconfig"):up():up()
+                       :tag("field", {type='boolean', label='Make Room Persistent?', var='muc#roomconfig_persistentroom'})
+                               :tag("value"):text(self._data.persistent and "1" or "0"):up()
+                       :up()
+                       :tag("field", {type='boolean', label='Make Room Publicly Searchable?', var='muc#roomconfig_publicroom'})
+                               :tag("value"):text(self._data.hidden and "0" or "1"):up()
+                       :up()
+                       :tag("field", {type='list-single', label='Who May Discover Real JIDs?', var='muc#roomconfig_whois'})
+                           :tag("value"):text(self._data.whois or 'moderators'):up()
+                           :tag("option", {label = 'Moderators Only'})
+                               :tag("value"):text('moderators'):up()
                                :up()
-                               :tag("field", {type='boolean', label='Make Room Publicly Searchable?', var='muc#roomconfig_publicroom'})
-                                       :tag("value"):text(self._data.hidden and "0" or "1"):up()
+                           :tag("option", {label = 'Anyone'})
+                               :tag("value"):text('anyone'):up()
                                :up()
-               );
-       elseif stanza.attr.type == "set" then
-               local query = stanza.tags[1];
-               local form;
-               for _, tag in ipairs(query.tags) do if tag.name == "x" and tag.attr.xmlns == "jabber:x:data" then form = tag; break; end end
-               if not form then origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); return; end
-               if form.attr.type == "cancel" then origin.send(st.reply(stanza)); return; end
-               if form.attr.type ~= "submit" then origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end
-               local fields = {};
-               for _, field in pairs(form.tags) do
-                       if field.name == "field" and field.attr.var and field.tags[1].name == "value" and #field.tags[1].tags == 0 then
-                               fields[field.attr.var] = field.tags[1][1] or "";
-                       end
+                       :up()
+       );
+end
+
+local valid_whois = {
+    moderators = true,
+    anyone = true,
+}
+
+function room_mt:process_form(origin, stanza)
+       local query = stanza.tags[1];
+       local form;
+       for _, tag in ipairs(query.tags) do if tag.name == "x" and tag.attr.xmlns == "jabber:x:data" then form = tag; break; end end
+       if not form then origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); return; end
+       if form.attr.type == "cancel" then origin.send(st.reply(stanza)); return; end
+       if form.attr.type ~= "submit" then origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end
+       local fields = {};
+       for _, field in pairs(form.tags) do
+               if field.name == "field" and field.attr.var and field.tags[1].name == "value" and #field.tags[1].tags == 0 then
+                       fields[field.attr.var] = field.tags[1][1] or "";
                end
-               if fields.FORM_TYPE ~= "http://jabber.org/protocol/muc#roomconfig" then origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end
+       end
+       if fields.FORM_TYPE ~= "http://jabber.org/protocol/muc#roomconfig" then origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end
+
+       local dirty = false
+
+       local persistent = fields['muc#roomconfig_persistentroom'];
+       if persistent == "0" or persistent == "false" then persistent = nil; elseif persistent == "1" or persistent == "true" then persistent = true;
+       else origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end
+       dirty = dirty or (self._data.persistent ~= persistent)
+       self._data.persistent = persistent;
+       module:log("debug", "persistent=%s", tostring(persistent));
 
-               local persistent = fields['muc#roomconfig_persistentroom'];
-               if persistent == "0" or persistent == "false" then persistent = nil; elseif persistent == "1" or persistent == "true" then persistent = true;
-               else origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end
-               self._data.persistent = persistent;
-               module:log("debug", "persistent=%s", tostring(persistent));
+       local public = fields['muc#roomconfig_publicroom'];
+       if public == "0" or public == "false" then public = nil; elseif public == "1" or public == "true" then public = true;
+       else origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end
+       dirty = dirty or (self._data.hidden ~= (not public and true or nil))
+       self._data.hidden = not public and true or nil;
 
-               local public = fields['muc#roomconfig_publicroom'];
-               if public == "0" or public == "false" then public = nil; elseif public == "1" or public == "true" then public = true;
-               else origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end
-               self._data.hidden = not public and true or nil;
+       local whois = fields['muc#roomconfig_whois'];
+       if not valid_whois[whois] then
+           origin.send(st.error_reply(stanza, 'cancel', 'bad-request'));
+           return;
+       end
+       local whois_changed = self._data.whois ~= whois
+       self._data.whois = whois
+       module:log('debug', 'whois=%s', tostring(whois))
+
+       if self.save then self:save(true); end
+       origin.send(st.reply(stanza));
+
+       if dirty or whois_changed then
+           local msg = st.message({type='groupchat', from=self.jid})
+                   :tag('x', {xmlns='http://jabber.org/protocol/muc#user'}):up()
+
+           if dirty then
+               msg.tags[1]:tag('status', {code = '104'})
+           end
+           if whois_changed then
+               local code = (whois == 'moderators') and 173 or 172
+               msg.tags[1]:tag('status', {code = code})
+           end
+
+           self:broadcast_message(msg, false)
+       end
+end
 
-               if self.save then self:save(true); end
-               origin.send(st.reply(stanza));
+function room_mt:destroy(newjid, reason, password)
+       local pr = st.presence({type = "unavailable"})
+               :tag("x", {xmlns = "http://jabber.org/protocol/muc#user"})
+                       :tag("item", { affiliation='none', role='none' }):up()
+                       :tag("destroy", {jid=newjid})
+       if reason then pr:tag("reason"):text(reason):up(); end
+       if password then pr:tag("password"):text(password):up(); end
+       for nick, occupant in pairs(self._occupants) do
+               pr.attr.from = nick;
+               for jid in pairs(occupant.sessions) do
+                       pr.attr.to = jid;
+                       self:_route_stanza(pr);
+                       self._jid_nick[jid] = nil;
+               end
+               self._occupants[nick] = nil;
        end
+       self._data.persistent = nil;
+       if self.save then self:save(true); end
 end
 
 function room_mt:handle_to_room(origin, stanza) -- presence changes and groupchat messages, along with disco/etc
@@ -441,9 +492,9 @@ function room_mt:handle_to_room(origin, stanza) -- presence changes and groupcha
        local xmlns = stanza.tags[1] and stanza.tags[1].attr.xmlns;
        if stanza.name == "iq" then
                if xmlns == "http://jabber.org/protocol/disco#info" and type == "get" then
-                       origin.send(room_get_disco_info(self, stanza));
+                       origin.send(self:get_disco_info(stanza));
                elseif xmlns == "http://jabber.org/protocol/disco#items" and type == "get" then
-                       origin.send(room_get_disco_items(self, stanza));
+                       origin.send(self:get_disco_items(stanza));
                elseif xmlns == "http://jabber.org/protocol/muc#admin" then
                        local actor = stanza.attr.from;
                        local affiliation = self:get_affiliation(actor);
@@ -519,7 +570,30 @@ function room_mt:handle_to_room(origin, stanza) -- presence changes and groupcha
                                origin.send(st.error_reply(stanza, "cancel", "bad-request"));
                        end
                elseif xmlns == "http://jabber.org/protocol/muc#owner" and (type == "get" or type == "set") and stanza.tags[1].name == "query" then
-                       self:handle_form(origin, stanza);
+                       if self:get_affiliation(stanza.attr.from) ~= "owner" then
+                               origin.send(st.error_reply(stanza, "auth", "forbidden"));
+                       elseif stanza.attr.type == "get" then
+                               self:send_form(origin, stanza);
+                       elseif stanza.attr.type == "set" then
+                               local child = stanza.tags[1].tags[1];
+                               if not child then
+                                       origin.send(st.error_reply(stanza, "auth", "bad-request"));
+                               elseif child.name == "destroy" then
+                                       local newjid = child.attr.jid;
+                                       local reason, password;
+                                       for _,tag in ipairs(child.tags) do
+                                               if tag.name == "reason" then
+                                                       reason = #tag.tags == 0 and tag[1];
+                                               elseif tag.name == "password" then
+                                                       password = #tag.tags == 0 and tag[1];
+                                               end
+                                       end
+                                       self:destroy(newjid, reason, password);
+                                       origin.send(st.reply(stanza));
+                               else
+                                       self:process_form(origin, stanza);
+                               end
+                       end
                elseif type == "set" or type == "get" then
                        origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
                end
@@ -551,8 +625,7 @@ function room_mt:handle_to_room(origin, stanza) -- presence changes and groupcha
        elseif stanza.name == "message" and type == "error" and is_kickable_error(stanza) then
                local current_nick = self._jid_nick[stanza.attr.from];
                log("debug", "%s kicked from %s for sending an error message", current_nick, self.jid);
-               self:handle_to_occupant(origin, st.presence({type='unavailable', from=stanza.attr.from, to=stanza.attr.to})
-                       :tag('status'):text('Kicked: '..get_error_condition(stanza))); -- send unavailable
+               self:handle_to_occupant(origin, build_unavailable_presence_from_error(stanza)); -- send unavailable
        elseif stanza.name == "presence" then -- hack - some buggy clients send presence updates to the room rather than their nick
                local to = stanza.attr.to;
                local current_nick = self._jid_nick[stanza.attr.from];
@@ -707,13 +780,11 @@ function room_mt:_route_stanza(stanza)
        local from_occupant = self._occupants[stanza.attr.from];
        if stanza.name == "presence" then
                if to_occupant and from_occupant then
-                       if to_occupant.role == "moderator" or jid_bare(to_occupant.jid) == jid_bare(from_occupant.jid) then
-                               for i=#stanza.tags,1,-1 do
-                                       local tag = stanza.tags[i];
-                                       if tag.name == "x" and tag.attr.xmlns == "http://jabber.org/protocol/muc#user" then
-                                               muc_child = tag;
-                                               break;
-                                       end
+                       if self._data.whois == 'anyone' then
+                           muc_child = stanza:get_child("x", "http://jabber.org/protocol/muc#user");
+                       else
+                               if to_occupant.role == "moderator" or jid_bare(to_occupant.jid) == jid_bare(from_occupant.jid) then
+                                       muc_child = stanza:get_child("x", "http://jabber.org/protocol/muc#user");
                                end
                        end
                end
@@ -728,6 +799,9 @@ function room_mt:_route_stanza(stanza)
                                end
                        end
                end
+               if self._data.whois == 'anyone' then
+                   muc_child:tag('status', { code = '100' });
+               end
        end
        self:route_stanza(stanza);
        if muc_child then
@@ -746,7 +820,9 @@ function _M.new_room(jid)
                jid = jid;
                _jid_nick = {};
                _occupants = {};
-               _data = {};
+               _data = {
+                   whois = 'moderators',
+               };
                _affiliations = {};
        }, room_mt);
 end
diff --git a/prosody b/prosody
index b87388dfa454d77cf214a319906b9b41a8c16797..46f3331f004bc9030f5866c5c50a6bcb3e104dc2 100755 (executable)
--- a/prosody
+++ b/prosody
@@ -16,6 +16,7 @@ CFG_DATADIR=os.getenv("PROSODY_DATADIR");
 
 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
 
+-- Tell Lua where to find our libraries
 if CFG_SOURCEDIR then
        package.path = CFG_SOURCEDIR.."/?.lua;"..package.path;
        package.cpath = CFG_SOURCEDIR.."/?.so;"..package.cpath;
@@ -24,42 +25,22 @@ end
 package.path = package.path..";"..(CFG_SOURCEDIR or ".").."/fallbacks/?.lua";
 package.cpath = package.cpath..";"..(CFG_SOURCEDIR or ".").."/fallbacks/?.so";
 
+-- Substitute ~ with path to home directory in data path
 if CFG_DATADIR then
        if os.getenv("HOME") then
                CFG_DATADIR = CFG_DATADIR:gsub("^~", os.getenv("HOME"));
        end
 end
 
--- Required to be able to find packages installed with luarocks
-if not pcall(require, "luarocks.loader") then -- Try LuaRocks 2.x
-       pcall(require, "luarocks.require") -- Try LuaRocks 1.x
-end
-
--- Replace require with one that doesn't pollute _G
-do
-       local _realG = _G;
-       local _real_require = require;
-       function require(...)
-               local curr_env = getfenv(2);
-               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
-                       old_newindex, _realG_mt.__newindex = _realG_mt.__newindex, curr_env;
-                       local ret = _real_require(...);
-                       _realG_mt.__newindex = old_newindex;
-                       return ret;
-               end
-               return _real_require(...);
-       end
-end
-
-
+-- Load the config-parsing module
 config = require "core.configmanager"
 
+-- -- -- --
+-- Define the functions we call during startup, the 
+-- actual startup happens right at the end, where these
+-- functions get called
+
 function read_config()
-       -- TODO: Check for other formats when we add support for them
-       -- Use lfs? Make a new conf/ dir?
        local filenames = {};
        
        local filename;
@@ -69,7 +50,9 @@ function read_config()
                        table.insert(filenames, CFG_CONFIGDIR.."/"..arg[2]);
                end
        else
-               table.insert(filenames, (CFG_CONFIGDIR or ".").."/prosody.cfg.lua");
+               for _, format in ipairs(config.parsers()) do
+                       table.insert(filenames, (CFG_CONFIGDIR or ".").."/prosody.cfg."..format);
+               end
        end
        for _,_filename in ipairs(filenames) do
                filename = _filename;
@@ -104,16 +87,42 @@ function read_config()
 end
 
 function load_libraries()
-       -- Initialize logging
-       require "core.loggingmanager"
-       
-       -- Check runtime dependencies
-       require "util.dependencies"
-       
        -- Load socket framework
        server = require "net.server"
 end    
 
+function init_logging()
+       -- Initialize logging
+       require "core.loggingmanager"
+end
+
+function check_dependencies()
+       -- Check runtime dependencies
+       if not require "util.dependencies".check_dependencies() then
+               os.exit(1);
+       end
+end
+
+function sandbox_require()
+       -- Replace require() with one that doesn't pollute _G, required
+       -- for neat sandboxing of modules
+       local _realG = _G;
+       local _real_require = require;
+       function require(...)
+               local curr_env = getfenv(2);
+               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
+                       old_newindex, _realG_mt.__newindex = _realG_mt.__newindex, curr_env;
+                       local ret = _real_require(...);
+                       _realG_mt.__newindex = old_newindex;
+                       return ret;
+               end
+               return _real_require(...);
+       end
+end
+
 function init_global_state()
        bare_sessions = {};
        full_sessions = {};
@@ -177,29 +186,28 @@ function init_global_state()
        end
 
        -- Load SSL settings from config, and create a ctx table
-       local global_ssl_ctx = rawget(_G, "ssl") and config.get("*", "core", "ssl");
-       if global_ssl_ctx then
-               local default_ssl_ctx = { mode = "server", protocol = "sslv23", capath = "/etc/ssl/certs", verify = "none", options = "no_sslv2"; };
-               setmetatable(global_ssl_ctx, { __index = default_ssl_ctx });
-       end
+       local certmanager = require "core.certmanager";
+       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", option.."_ports") or default;
+               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."..option.." is not a table");
+                       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 "..option.."_ports: "..tostring(port));
+                                       log("error", "Non-numeric "..ports_option..": "..tostring(port));
                                else
                                        local ok, err = cl.start(listener, {
-                                               ssl = conntype ~= "tcp" and global_ssl_ctx,
+                                               ssl = conntype == "ssl" and global_ssl_ctx,
                                                port = port,
                                                interface = (option and config.get("*", "core", option.."_interface"))
                                                        or cl.get(listener).default_interface
@@ -300,15 +308,23 @@ function init_data_store()
 end
 
 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
        eventmanager.fire_event("server-starting");
        prosody.events.fire_event("server-starting");
 
        -- start listening on sockets
-       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");
+       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    
@@ -395,7 +411,13 @@ function cleanup()
        server.setquitting(true);
 end
 
+-- Are you ready? :)
+-- These actions are in a strict order, as many depend on
+-- previous steps to have already been performed
 read_config();
+init_logging();
+check_dependencies();
+sandbox_require();
 load_libraries();
 init_global_state();
 read_version();
index 021f6f117edd11eaf2b33be2593970908471665f..c959b4fe3b24bdb74545448d01d0911874ee5e60 100644 (file)
 -- Server-wide settings go in this section
 Host "*"
 
-    -- This is a (by default, empty) list of accounts that are admins for the
-    -- server. Note that you must create the accounts separately (see
-    -- http://prosody.im/doc/creating_accounts)
-    -- Example: admins = { "user1@example.com", "user2@example.net" }
-    admins = { }
-
-    -- Enable use of libevent for better performance under high load
-    -- For more information see: http://prosody.im/doc/libevent
-    --use_libevent = true;
-    
-    -- This is the list of modules Prosody will load on startup. It looks for
-    -- mod_modulename.lua in the plugins folder, so make sure that exists too.
-    -- Documentation on modules can be found at: http://prosody.im/doc/modules
-    modules_enabled = {
-        -- Generally required
-        "roster";   -- Allow users to have a roster. Recommended ;)
-        "saslauth"; -- Authentication for clients and servers. Recommended if
-                    -- you want to log in.
-        "dialback"; -- s2s dialback support
-        "disco";    -- Service discovery
-        "posix";    -- POSIX functionality, daemonizes, enables syslog, etc.
-
-        -- Not essential, but recommended
-        "private";       -- Private XML storage (for room bookmarks, etc.)
-        "vcard";         -- Allow users to set vCards
-        "privacy";       -- Support privacy lists
-        "tls";           -- Support for secure TLS on c2s/s2s connections
-        --"compression"; -- Stream compression for client-to-server streams
-
-        -- Nice to have
-        "legacyauth"; -- Legacy authentication. Only used by some old
-                      -- clients and bots.
-        "version";    -- Replies to server version requests
-        "uptime";     -- Report how long server has been running
-        "time";       -- Let others know the time here on this server
-        "ping";       -- Replies to XMPP pings with pongs
-        "pep";        -- Enables users to publish their mood, activity, playing
-                      -- music and more
-        "register";   -- Allow users to register on this server using a client
-                      -- and change passwords
-
-        -- Other specific functionality
-        --"console";            -- telnet to port 5582
-                                -- (needs console_enabled = true)
-        --"bosh";               -- Enable BOSH clients, aka "Jabber over HTTP"
-        --"httpserver";         -- Serve static files from a directory over
-                                -- HTTP
-        --"groups";             -- Shared roster support
-        --"announce";           -- Send announcement to all online users
-        --"welcome";            -- Welcome users who register accounts
-        --"watchregistrations"; -- Alert admins of registrations
-    }
-
-    -- These modules are auto-loaded, should you for (for some mad
-    -- reason) want to disable them then uncomment them below.
-    modules_disabled = {
-        --"presence";
-        --"message";
-        --"iq";
-    }
-
-    -- Disable account creation by default, for security
-    -- For more information see http://prosody.im/doc/creating_accounts
-    allow_registration = false;
-
-    --These are the SSL/TLS-related settings.
-    --ssl = {
-    --    key = "certs/localhost.key";
-    --    certificate = "certs/localhost.cert";
-    --}
-
-    -- Require encryption on client/server connections?
-    --c2s_require_encryption = false
-    --s2s_require_encryption = false
-
-    -- Logging configuration
-    -- For advanced logging see http://prosody.im/doc/logging
-    log = "prosody.log";
-    debug = false; -- Log debug messages?
+       -- This is a (by default, empty) list of accounts that are admins for the
+       -- server. Note that you must create the accounts separately (see
+       -- http://prosody.im/doc/creating_accounts)
+       -- Example: admins = { "user1@example.com", "user2@example.net" }
+       admins = { }
+
+       -- Enable use of libevent for better performance under high load
+       -- For more information see: http://prosody.im/doc/libevent
+       --use_libevent = true;
+
+       -- This is the list of modules Prosody will load on startup. It looks for
+       -- mod_modulename.lua in the plugins folder, so make sure that exists too.
+       -- Documentation on modules can be found at: http://prosody.im/doc/modules
+       modules_enabled = {
+               -- Generally required
+               "roster";   -- Allow users to have a roster. Recommended ;)
+               "saslauth"; -- Authentication for clients and servers. Recommended if
+                           -- you want to log in.
+               "dialback"; -- s2s dialback support
+               "disco";    -- Service discovery
+               "posix";    -- POSIX functionality, daemonizes, enables syslog, etc.
+
+               -- Not essential, but recommended
+               "private";       -- Private XML storage (for room bookmarks, etc.)
+               "vcard";         -- Allow users to set vCards
+               "tls";           -- Support for secure TLS on c2s/s2s connections
+               --"privacy";     -- Support privacy lists
+               --"compression"; -- Stream compression for client-to-server streams
+
+               -- Nice to have
+               "legacyauth"; -- Legacy authentication. Only used by some old
+                             -- clients and bots.
+               "version";    -- Replies to server version requests
+               "uptime";     -- Report how long server has been running
+               "time";       -- Let others know the time here on this server
+               "ping";       -- Replies to XMPP pings with pongs
+               "pep";        -- Enables users to publish their mood, activity, playing
+                             -- music and more
+               "register";   -- Allow users to register on this server using a client
+                             -- and change passwords
+
+               -- Other specific functionality
+               --"console";            -- telnet to port 5582
+                                       -- (needs console_enabled = true)
+               --"bosh";               -- Enable BOSH clients, aka "Jabber over HTTP"
+               --"httpserver";         -- Serve static files from a directory over
+                                       -- HTTP
+               --"groups";             -- Shared roster support
+               --"announce";           -- Send announcement to all online users
+               --"welcome";            -- Welcome users who register accounts
+               --"watchregistrations"; -- Alert admins of registrations
+       }
+
+       -- These modules are auto-loaded, should you for (for some mad
+       -- reason) want to disable them then uncomment them below.
+       modules_disabled = {
+               --"presence";
+               --"message";
+               --"iq";
+       }
+
+       -- Disable account creation by default, for security
+       -- For more information see http://prosody.im/doc/creating_accounts
+       allow_registration = false;
+
+       --These are the SSL/TLS-related settings.
+       --ssl = {
+       --    key = "certs/localhost.key";
+       --    certificate = "certs/localhost.cert";
+       --}
+
+       -- Require encryption on client/server connections?
+       --c2s_require_encryption = false
+       --s2s_require_encryption = false
+
+       -- Logging configuration
+       -- For advanced logging see http://prosody.im/doc/logging
+       log = "prosody.log";
+       debug = false; -- Log debug messages?
 
 -- This allows clients to connect to localhost. No harm in it.
 Host "localhost"
@@ -120,16 +120,16 @@ Host "localhost"
 -- Section for example.com
 -- (replace example.com with your domain name)
 Host "example.com"
-    enabled = false -- This will disable the host, preserving the config, but
-                    -- denying connections (remove to enable!)
-
-    -- Assign this host a certificate for TLS, otherwise it would use the one
-    -- set in the global section (if any). Note that old-style SSL on port 5223
-    -- only supports one certificate, and will always use the global one.
-    --ssl = {
-    --    key = "certs/example.com.key";
-    --    certificate = "certs/example.com.crt";
-    --}
+       enabled = false -- This will disable the host, preserving the config, but
+                       -- denying connections (remove to enable!)
+
+       -- Assign this host a certificate for TLS, otherwise it would use the one
+       -- set in the global section (if any). Note that old-style SSL on port 5223
+       -- only supports one certificate, and will always use the global one.
+       --ssl = {
+       --    key = "certs/example.com.key";
+       --    certificate = "certs/example.com.crt";
+       --}
 
 -- Set up a MUC (multi-user chat) room server on conference.example.com:
 --Component "conference.example.com" "muc"
index 370ba09189b6d91846cc3e7673e2ad4730fcf097..38ef6191ad4191a2ae49a88fd24a6513e9478f19 100644 (file)
@@ -16,7 +16,7 @@ function run_all_tests()
        dotest "core.s2smanager"
        dotest "core.configmanager"
        dotest "util.stanza"
-               
+       
        dosingletest("test_sasl.lua", "latin1toutf8");
 end
 
@@ -106,7 +106,9 @@ function dosingletest(testname, fname)
 end
 
 function dotest(unitname)
-       local tests = setmetatable({}, { __index = _realG });
+       local _fakeG = setmetatable({}, {__index = _realG});
+       _fakeG._G = _fakeG;
+       local tests = setmetatable({}, { __index = _fakeG });
        tests.__unit = unitname;
        local chunk, err = loadfile("test_"..unitname:gsub("%.", "_")..".lua");
        if not chunk then
@@ -120,19 +122,20 @@ function dotest(unitname)
                print("WARNING: ", "Failed to initialise tests for "..unitname, err);
                return;
        end
-       
        if tests.env then setmetatable(tests.env, { __index = _realG }); end
-       local unit = setmetatable({}, { __index = setmetatable({ _G = tests.env or _G }, { __index = tests.env or _G }) });
-       unit._G = unit; _realG._G = unit;
+       local unit = setmetatable({}, { __index = setmetatable({ _G = tests.env or _fakeG }, { __index = tests.env or _fakeG }) });
        local fn = "../"..unitname:gsub("%.", "/")..".lua";
        local chunk, err = loadfile(fn);
        if not chunk then
                print("WARNING: ", "Failed to load module: "..unitname, err);
                return;
        end
-
+       
+       local oldmodule, old_M = _fakeG.module, _fakeG._M;
+       _fakeG.module = function () _M = _G end
        setfenv(chunk, unit);
        local success, err = pcall(chunk);
+       _fakeG.module, _fakeG._M = oldmodule, old_M;
        if not success then
                print("WARNING: ", "Failed to initialise module: "..unitname, err);
                return;
@@ -149,6 +152,9 @@ function dotest(unitname)
                                print("WARNING: ", unitname.."."..name.." has no test!");
                        end
                else
+                       if verbosity >= 4 then
+                               print("INFO: ", "Testing "..unitname.."."..name);
+                       end
                        local line_hook, line_info = new_line_coverage_monitor(fn);
                        debug.sethook(line_hook, "l")
                        local success, ret = pcall(test, f, unit);
index db2e85d32f8bfd7dfeb41c8db3afe87d675f7140..9f16f178b6cffd34e529babf1ad8674a60988a7e 100644 (file)
@@ -2,7 +2,7 @@
 -- Copyright (C) 2008-2010 Matthew Wild
 -- Copyright (C) 2008-2010 Waqas Hussain
 -- Copyright (C) 2009 Tobias Markmann
--- 
+--
 -- This project is MIT/X11 licensed. Please see the
 -- COPYING file in the source package for more information.
 --
 * POSIX support functions for Lua
 */
 
-#define MODULE_VERSION "0.3.1"
+#define MODULE_VERSION "0.3.3"
 
 #include <stdlib.h>
+#include <math.h>
 #include <unistd.h>
 #include <libgen.h>
 #include <sys/resource.h>
@@ -38,14 +39,14 @@ static int lc_daemonize(lua_State *L)
 {
 
        pid_t pid;
-       
+
        if ( getppid() == 1 )
        {
                lua_pushboolean(L, 0);
                lua_pushstring(L, "already-daemonized");
                return 2;
        }
-       
+
        /* Attempt initial fork */
        if((pid = fork()) < 0)
        {
@@ -61,7 +62,7 @@ static int lc_daemonize(lua_State *L)
                lua_pushnumber(L, pid);
                return 2;
        }
-       
+
        /* and we are the child process */
        if(setsid() == -1)
        {
@@ -150,7 +151,7 @@ int facility_constants[] =  {
        exist,  the  results  are  undefined.  Most portable is to use a string
        constant.
    " -- syslog manpage
-*/ 
+*/
 char* syslog_ident = NULL;
 
 int lc_syslog_open(lua_State* L)
@@ -159,12 +160,12 @@ int lc_syslog_open(lua_State* L)
        facility = facility_constants[facility];
 
        luaL_checkstring(L, 1);
-       
+
        if(syslog_ident)
                free(syslog_ident);
-       
+
        syslog_ident = strdup(lua_tostring(L, 1));
-       
+
        openlog(syslog_ident, LOG_PID, facility);
        return 0;
 }
@@ -264,7 +265,7 @@ int lc_setuid(lua_State* L)
        {
                uid = lua_tonumber(L, 1);
        }
-       
+
        if(uid>-1)
        {
                /* Ok, attempt setuid */
@@ -293,7 +294,7 @@ int lc_setuid(lua_State* L)
                        return 1;
                }
        }
-       
+
        /* Seems we couldn't find a valid UID to switch to */
        lua_pushboolean(L, 0);
        lua_pushstring(L, "invalid-uid");
@@ -322,7 +323,7 @@ int lc_setgid(lua_State* L)
        {
                gid = lua_tonumber(L, 1);
        }
-       
+
        if(gid>-1)
        {
                /* Ok, attempt setgid */
@@ -351,20 +352,47 @@ int lc_setgid(lua_State* L)
                        return 1;
                }
        }
-       
+
        /* Seems we couldn't find a valid GID to switch to */
        lua_pushboolean(L, 0);
        lua_pushstring(L, "invalid-gid");
        return 2;
 }
 
+int lc_umask(lua_State* L)
+{
+       char old_mode_string[7];
+       mode_t old_mode = umask(strtoul(luaL_checkstring(L, 1), NULL, 8));
+
+       snprintf(old_mode_string, sizeof(old_mode_string), "%03o", old_mode);
+       old_mode_string[sizeof(old_mode_string)-1] = 0;
+       lua_pushstring(L, old_mode_string);
+
+       return 1;
+}
+
+int lc_mkdir(lua_State* L)
+{
+       int ret = mkdir(luaL_checkstring(L, 1), S_IRUSR | S_IWUSR | S_IXUSR
+               | S_IRGRP | S_IWGRP | S_IXGRP
+               | S_IROTH | S_IXOTH); /* mode 775 */
+
+       lua_pushboolean(L, ret==0);
+       if(ret)
+       {
+               lua_pushstring(L, strerror(errno));
+               return 2;
+       }
+       return 1;
+}
+
 /*     Like POSIX's setrlimit()/getrlimit() API functions.
- *     
+ *
  *     Syntax:
  *     pposix.setrlimit( resource, soft limit, hard limit)
- *     
+ *
  *     Any negative limit will be replace with the current limit by an additional call of getrlimit().
- *     
+ *
  *     Example usage:
  *     pposix.setrlimit("NOFILE", 1000, 2000)
  */
@@ -393,16 +421,16 @@ int lc_setrlimit(lua_State *L) {
                lua_pushboolean(L, 0);
                lua_pushstring(L, "incorrect-arguments");
        }
-       
+
        resource = luaL_checkstring(L, 1);
        softlimit = luaL_checkinteger(L, 2);
        hardlimit = luaL_checkinteger(L, 3);
-       
+
        rid = string2resource(resource);
        if (rid != -1) {
                struct rlimit lim;
                struct rlimit lim_current;
-               
+
                if (softlimit < 0 || hardlimit < 0) {
                        if (getrlimit(rid, &lim_current)) {
                                lua_pushboolean(L, 0);
@@ -410,12 +438,12 @@ int lc_setrlimit(lua_State *L) {
                                return 2;
                        }
                }
-               
+
                if (softlimit < 0) lim.rlim_cur = lim_current.rlim_cur;
                        else lim.rlim_cur = softlimit;
                if (hardlimit < 0) lim.rlim_max = lim_current.rlim_max;
                        else lim.rlim_max = hardlimit;
-               
+
                if (setrlimit(rid, &lim)) {
                        lua_pushboolean(L, 0);
                        lua_pushstring(L, "setrlimit-failed");
@@ -436,13 +464,13 @@ int lc_getrlimit(lua_State *L) {
        const char *resource = NULL;
        int rid = -1;
        struct rlimit lim;
-       
+
        if (arguments != 1) {
                lua_pushboolean(L, 0);
                lua_pushstring(L, "invalid-arguments");
                return 2;
        }
-       
+
        resource = luaL_checkstring(L, 1);
        rid = string2resource(resource);
        if (rid != -1) {
@@ -473,50 +501,40 @@ int lc_abort(lua_State* L)
 
 int luaopen_util_pposix(lua_State *L)
 {
-       lua_newtable(L);
+       luaL_Reg exports[] = {
+               { "abort", lc_abort },
 
-       lua_pushcfunction(L, lc_abort);
-       lua_setfield(L, -2, "abort");
+               { "daemonize", lc_daemonize },
 
-       lua_pushcfunction(L, lc_daemonize);
-       lua_setfield(L, -2, "daemonize");
+               { "syslog_open", lc_syslog_open },
+               { "syslog_close", lc_syslog_close },
+               { "syslog_log", lc_syslog_log },
+               { "syslog_setminlevel", lc_syslog_setmask },
 
-       lua_pushcfunction(L, lc_syslog_open);
-       lua_setfield(L, -2, "syslog_open");
+               { "getpid", lc_getpid },
+               { "getuid", lc_getuid },
+               { "getgid", lc_getgid },
 
-       lua_pushcfunction(L, lc_syslog_close);
-       lua_setfield(L, -2, "syslog_close");
+               { "setuid", lc_setuid },
+               { "setgid", lc_setgid },
 
-       lua_pushcfunction(L, lc_syslog_log);
-       lua_setfield(L, -2, "syslog_log");
+               { "umask", lc_umask },
 
-       lua_pushcfunction(L, lc_syslog_setmask);
-       lua_setfield(L, -2, "syslog_setminlevel");
+               { "mkdir", lc_mkdir },
 
-       lua_pushcfunction(L, lc_getpid);
-       lua_setfield(L, -2, "getpid");
+               { "setrlimit", lc_setrlimit },
+               { "getrlimit", lc_getrlimit },
 
-       lua_pushcfunction(L, lc_getuid);
-       lua_setfield(L, -2, "getuid");
-       lua_pushcfunction(L, lc_getgid);
-       lua_setfield(L, -2, "getgid");
+               { NULL, NULL }
+       };
 
-       lua_pushcfunction(L, lc_setuid);
-       lua_setfield(L, -2, "setuid");
-       lua_pushcfunction(L, lc_setgid);
-       lua_setfield(L, -2, "setgid");
-       
-       lua_pushcfunction(L, lc_setrlimit);
-       lua_setfield(L, -2, "setrlimit");
-       
-       lua_pushcfunction(L, lc_getrlimit);
-       lua_setfield(L, -2, "getrlimit");
+       luaL_register(L, "pposix",  exports);
 
        lua_pushliteral(L, "pposix");
        lua_setfield(L, -2, "_NAME");
 
        lua_pushliteral(L, MODULE_VERSION);
        lua_setfield(L, -2, "_VERSION");
-       
+
        return 1;
 };
index 6058cbc6d028c0011e361771b1c33be8df800cdf..01c7aab2210f85f817f7e6c63620d5d37cab7937 100644 (file)
@@ -21,13 +21,19 @@ local next = next;
 local t_insert = table.insert;
 local append = require "util.serialization".append;
 local path_separator = "/"; if os.getenv("WINDIR") then path_separator = "\\" end
-local lfs_mkdir = require "lfs".mkdir;
+local raw_mkdir;
+
+if prosody.platform == "posix" then
+       raw_mkdir = require "util.pposix".mkdir; -- Doesn't trample on umask
+else
+       raw_mkdir = require "lfs".mkdir;
+end
 
 module "datamanager"
 
 ---- utils -----
 local encode, decode;
-do 
+do
        local urlcodes = setmetatable({}, { __index = function (t, k) t[k] = char(tonumber("0x"..k)); return t[k]; end });
 
        decode = function (s)
@@ -43,7 +49,7 @@ local _mkdir = {};
 local function mkdir(path)
        path = path:gsub("/", path_separator); -- TODO as an optimization, do this during path creation rather than here
        if not _mkdir[path] then
-               lfs_mkdir(path);
+               raw_mkdir(path);
                _mkdir[path] = true;
        end
        return path;
@@ -88,7 +94,7 @@ end
 
 function getpath(username, host, datastore, ext, create)
        ext = ext or "dat";
-       host = host and encode(host);
+       host = (host and encode(host)) or "_global";
        username = username and encode(username);
        if username then
                if create then mkdir(mkdir(mkdir(data_path).."/"..host).."/"..datastore); end
index 1c8dc375dcdac21cb62cfdfc082a8043e7829a1c..6024dd63e6b5154fe7ac7f4cd7914be48da00631 100644 (file)
@@ -6,12 +6,16 @@
 -- COPYING file in the source package for more information.
 --
 
+module("dependencies", package.seeall)
 
-local fatal;
+function softreq(...) local ok, lib =  pcall(require, ...); if ok then return lib; else return nil, lib; end end
 
-local function softreq(...) local ok, lib =  pcall(require, ...); if ok then return lib; else return nil, lib; end end
+-- Required to be able to find packages installed with luarocks
+if not softreq "luarocks.loader" then -- LuaRocks 2.x
+       softreq "luarocks.require"; -- LuaRocks <1.x
+end
 
-local function missingdep(name, sources, msg)
+function missingdep(name, sources, msg)
        print("");
        print("**************************");
        print("Prosody was unable to find "..tostring(name));
@@ -31,89 +35,91 @@ local function missingdep(name, sources, msg)
        print("");
 end
 
-local lxp = softreq "lxp"
-
-if not lxp then
-       missingdep("luaexpat", {
-                       ["Debian/Ubuntu"] = "sudo apt-get install liblua5.1-expat0";
-                       ["luarocks"] = "luarocks install luaexpat";
-                       ["Source"] = "http://www.keplerproject.org/luaexpat/";
-               });
-       fatal = true;
-end
-
-local socket = softreq "socket"
-
-if not socket then
-       missingdep("luasocket", {
-                       ["Debian/Ubuntu"] = "sudo apt-get install liblua5.1-socket2";
-                       ["luarocks"] = "luarocks install luasocket";
-                       ["Source"] = "http://www.tecgraf.puc-rio.br/~diego/professional/luasocket/";
-               });
-       fatal = true;
-end
+function check_dependencies()
+       local fatal;
        
-local lfs, err = softreq "lfs"
-if not lfs then
-       missingdep("luafilesystem", {
-                       ["luarocks"] = "luarocks install luafilesystem";
-                       ["Debian/Ubuntu"] = "sudo apt-get install liblua5.1-filesystem0";
-                       ["Source"] = "http://www.keplerproject.org/luafilesystem/";
-               });
-       fatal = true;
-end
-
-local ssl = softreq "ssl"
-
-if not ssl then
-       if config.get("*", "core", "run_without_ssl") then
-               log("warn", "Running without SSL support because run_without_ssl is defined in the config");
-       else
+       local lxp = softreq "lxp"
+       
+       if not lxp then
+               missingdep("luaexpat", {
+                               ["Debian/Ubuntu"] = "sudo apt-get install liblua5.1-expat0";
+                               ["luarocks"] = "luarocks install luaexpat";
+                               ["Source"] = "http://www.keplerproject.org/luaexpat/";
+                       });
+               fatal = true;
+       end
+       
+       local socket = softreq "socket"
+       
+       if not socket then
+               missingdep("luasocket", {
+                               ["Debian/Ubuntu"] = "sudo apt-get install liblua5.1-socket2";
+                               ["luarocks"] = "luarocks install luasocket";
+                               ["Source"] = "http://www.tecgraf.puc-rio.br/~diego/professional/luasocket/";
+                       });
+               fatal = true;
+       end
+       
+       local lfs, err = softreq "lfs"
+       if not lfs then
+               missingdep("luafilesystem", {
+                               ["luarocks"] = "luarocks install luafilesystem";
+                               ["Debian/Ubuntu"] = "sudo apt-get install liblua5.1-filesystem0";
+                               ["Source"] = "http://www.keplerproject.org/luafilesystem/";
+                       });
+               fatal = true;
+       end
+       
+       local ssl = softreq "ssl"
+       
+       if not ssl then
                missingdep("LuaSec", {
                                ["Debian/Ubuntu"] = "http://prosody.im/download/start#debian_and_ubuntu";
                                ["luarocks"] = "luarocks install luasec";
                                ["Source"] = "http://www.inf.puc-rio.br/~brunoos/luasec/";
                        }, "SSL/TLS support will not be available");
+       else
+               local major, minor, veryminor, patched = ssl._VERSION:match("(%d+)%.(%d+)%.?(%d*)(M?)");
+               if not major or ((tonumber(major) == 0 and (tonumber(minor) or 0) <= 3 and (tonumber(veryminor) or 0) <= 2) and patched ~= "M") then
+                       log("error", "This version of LuaSec contains a known bug that causes disconnects, see http://prosody.im/doc/depends");
+               end
        end
-else
-       local major, minor, veryminor, patched = ssl._VERSION:match("(%d+)%.(%d+)%.?(%d*)(M?)");
-       if not major or ((tonumber(major) == 0 and (tonumber(minor) or 0) <= 3 and (tonumber(veryminor) or 0) <= 2) and patched ~= "M") then
-               log("error", "This version of LuaSec contains a known bug that causes disconnects, see http://prosody.im/doc/depends");
+       
+       local encodings, err = softreq "util.encodings"
+       if not encodings then
+               if err:match("not found") then
+                       missingdep("util.encodings", { ["Windows"] = "Make sure you have encodings.dll from the Prosody distribution in util/";
+                                               ["GNU/Linux"] = "Run './configure' and 'make' in the Prosody source directory to build util/encodings.so";
+                                       });
+               else
+                       print "***********************************"
+                       print("util/encodings couldn't be loaded. Check that you have a recent version of libidn");
+                       print ""
+                       print("The full error was:");
+                       print(err)
+                       print "***********************************"
+               end
+               fatal = true;
        end
-end
 
-local encodings, err = softreq "util.encodings"
-if not encodings then
-       if err:match("not found") then
-               missingdep("util.encodings", { ["Windows"] = "Make sure you have encodings.dll from the Prosody distribution in util/";
-                                       ["GNU/Linux"] = "Run './configure' and 'make' in the Prosody source directory to build util/encodings.so";
-                               });
-       else
-               print "***********************************"
-               print("util/encodings couldn't be loaded. Check that you have a recent version of libidn");
-               print ""
-               print("The full error was:");
-               print(err)
-               print "***********************************"
+       local hashes, err = softreq "util.hashes"
+       if not hashes then
+               if err:match("not found") then
+                       missingdep("util.hashes", { ["Windows"] = "Make sure you have hashes.dll from the Prosody distribution in util/";
+                                               ["GNU/Linux"] = "Run './configure' and 'make' in the Prosody source directory to build util/hashes.so";
+                                       });
+               else
+                       print "***********************************"
+                       print("util/hashes couldn't be loaded. Check that you have a recent version of OpenSSL (libcrypto in particular)");
+                       print ""
+                       print("The full error was:");
+                       print(err)
+                       print "***********************************"
+               end
+               fatal = true;
        end
-       fatal = true;
+       return not fatal;
 end
 
-local hashes, err = softreq "util.hashes"
-if not hashes then
-       if err:match("not found") then
-               missingdep("util.hashes", { ["Windows"] = "Make sure you have hashes.dll from the Prosody distribution in util/";
-                                       ["GNU/Linux"] = "Run './configure' and 'make' in the Prosody source directory to build util/hashes.so";
-                               });
-       else
-               print "***********************************"
-               print("util/hashes couldn't be loaded. Check that you have a recent version of OpenSSL (libcrypto in particular)");
-               print ""
-               print("The full error was:");
-               print(err)
-               print "***********************************"
-       end
-       fatal = true;
-end
 
-if fatal then os.exit(1); end
+return _M;
index 68954a56fc047fa73259fda7a1744528e9a8ad8b..363d2ac6b0907977e862e281fb84756a6f03de91 100644 (file)
@@ -47,13 +47,13 @@ function new()
                        _rebuild_index(event);
                end
        end;
-       local function add_plugin(plugin)
-               for event, handler in pairs(plugin) do
+       local function add_handlers(handlers)
+               for event, handler in pairs(handlers) do
                        add_handler(event, handler);
                end
        end;
-       local function remove_plugin(plugin)
-               for event, handler in pairs(plugin) do
+       local function remove_handlers(handlers)
+               for event, handler in pairs(handlers) do
                        remove_handler(event, handler);
                end
        end;
index 26da1d785aa5e0e0fab8bde94c7cd8dbc9bddc61..66dd41d81d8978796d371bc64ed080f8a35da4c4 100644 (file)
@@ -7,20 +7,27 @@
 --
 
 local hashes = require "util.hashes"
-local xor = require "bit".bxor
 
-local t_insert, t_concat = table.insert, table.concat;
 local s_char = string.char;
+local s_gsub = string.gsub;
+local s_rep = string.rep;
 
 module "hmac"
 
-local function arraystr(array)
-       local t = {}
-       for i = 1,#array do
-               t_insert(t, s_char(array[i]))
-       end
-
-       return t_concat(t)
+local xor_map = {0;1;2;3;4;5;6;7;8;9;10;11;12;13;14;15;1;0;3;2;5;4;7;6;9;8;11;10;13;12;15;14;2;3;0;1;6;7;4;5;10;11;8;9;14;15;12;13;3;2;1;0;7;6;5;4;11;10;9;8;15;14;13;12;4;5;6;7;0;1;2;3;12;13;14;15;8;9;10;11;5;4;7;6;1;0;3;2;13;12;15;14;9;8;11;10;6;7;4;5;2;3;0;1;14;15;12;13;10;11;8;9;7;6;5;4;3;2;1;0;15;14;13;12;11;10;9;8;8;9;10;11;12;13;14;15;0;1;2;3;4;5;6;7;9;8;11;10;13;12;15;14;1;0;3;2;5;4;7;6;10;11;8;9;14;15;12;13;2;3;0;1;6;7;4;5;11;10;9;8;15;14;13;12;3;2;1;0;7;6;5;4;12;13;14;15;8;9;10;11;4;5;6;7;0;1;2;3;13;12;15;14;9;8;11;10;5;4;7;6;1;0;3;2;14;15;12;13;10;11;8;9;6;7;4;5;2;3;0;1;15;14;13;12;11;10;9;8;7;6;5;4;3;2;1;0;};
+local function xor(x, y)
+       local lowx, lowy = x % 16, y % 16;
+       local hix, hiy = (x - lowx) / 16, (y - lowy) / 16;
+       local lowr, hir = xor_map[lowx * 16 + lowy + 1], xor_map[hix * 16 + hiy + 1];
+       local r = hir * 16 + lowr;
+       return r;
+end
+local opadc, ipadc = s_char(0x5c), s_char(0x36);
+local ipad_map = {};
+local opad_map = {};
+for i=0,255 do
+       ipad_map[s_char(i)] = s_char(xor(0x36, i));
+       opad_map[s_char(i)] = s_char(xor(0x5c, i));
 end
 
 --[[
@@ -36,31 +43,15 @@ hex
   return raw hash or hexadecimal string
 --]]
 function hmac(key, message, hash, blocksize, hex)
-       local opad = {}
-       local ipad = {}
-       
-       for i = 1,blocksize do
-               opad[i] = 0x5c
-               ipad[i] = 0x36
-       end
-
        if #key > blocksize then
                key = hash(key)
        end
 
-       for i = 1,#key do
-               ipad[i] = xor(ipad[i],key:sub(i,i):byte())
-               opad[i] = xor(opad[i],key:sub(i,i):byte())
-       end
-
-       opad = arraystr(opad)
-       ipad = arraystr(ipad)
+       local padding = blocksize - #key;
+       local ipad = s_gsub(key, ".", ipad_map)..s_rep(ipadc, padding);
+       local opad = s_gsub(key, ".", opad_map)..s_rep(opadc, padding);
 
-       if hex then
-               return hash(opad..hash(ipad..message), true)
-       else
-               return hash(opad..hash(ipad..message))
-       end
+       return hash(opad..hash(ipad..message), hex)
 end
 
 function md5(key, message, hex)
index aedec4b4219b7af6aa8246e760c54540c30e54f0..90138a3eff86b895e6d719bb5a8cddd2a6096d7c 100644 (file)
@@ -9,11 +9,19 @@
 
 local plugin_dir = CFG_PLUGINDIR or "./plugins/";
 
-local io_open = io.open;
-local loadstring = loadstring;
+local io_open, os_time = io.open, os.time;
+local loadstring, pairs = loadstring, pairs;
+
+local datamanager = require "util.datamanager";
 
 module "pluginloader"
 
+local function load_from_datastore(name)
+       local content = datamanager.load(name, nil, "plugins");
+       if not content or not content[1] then return nil, "Resource not found"; end
+       return content[1], name;
+end
+
 local function load_file(name)
        local file, err = io_open(plugin_dir..name);
        if not file then return file, err; end
@@ -22,16 +30,36 @@ local function load_file(name)
        return content, name;
 end
 
-function load_resource(plugin, resource)
+function load_resource(plugin, resource, loader)
        if not resource then
                resource = "mod_"..plugin..".lua";
        end
-       local content, err = load_file(plugin.."/"..resource);
-       if not content then content, err = load_file(resource); end
+       loader = loader or load_file;
+
+       local content, err = loader(plugin.."/"..resource);
+       if not content then content, err = loader(resource); end
        -- TODO add support for packed plugins
+       
+       if not content and loader == load_file then
+               return load_resource(plugin, resource, load_from_datastore);
+       end
+       
        return content, err;
 end
 
+function store_resource(plugin, resource, content, metadata)
+       if not resource then
+               resource = "mod_"..plugin..".lua";
+       end
+       local store = { content };
+       if metadata then
+               for k,v in pairs(metadata) do
+                       store[k] = v;
+               end
+       end
+       datamanager.store(plugin.."/"..resource, nil, "plugins", store);
+end
+
 function load_code(plugin, resource)
        local content, err = load_resource(plugin, resource);
        if not content then return content, err; end
index 8131fed9f3edcc093fce690ed87d7c48336986eb..04d58d1d3af33d5775aafc241e37afcfcb206a72 100644 (file)
@@ -109,10 +109,8 @@ function start()
        end
        if not CFG_SOURCEDIR then
                os.execute("./prosody");
-       elseif CFG_SOURCEDIR:match("^/usr/local") then
-               os.execute("/usr/local/bin/prosody");
        else
-               os.execute("prosody");
+               os.execute(CFG_SOURCEDIR.."/../../bin/prosody");
        end
        return true;
 end
index f65e70624b9a289eb6961ce2fd83d6f0b384cecd..eb71956b692aa5942bf0690b4787865132f588ce 100644 (file)
 
 local md5 = require "util.hashes".md5;
 local log = require "util.logger".init("sasl");
-local tostring = tostring;
 local st = require "util.stanza";
-local generate_uuid = require "util.uuid".generate;
-local t_insert, t_concat = table.insert, table.concat;
-local to_byte, to_char = string.byte, string.char;
+local set = require "util.set";
+local array = require "util.array";
 local to_unicode = require "util.encodings".idna.to_unicode;
+
+local tostring = tostring;
+local pairs, ipairs = pairs, ipairs;
+local t_insert, t_concat = table.insert, table.concat;
 local s_match = string.match;
-local gmatch = string.gmatch
-local string = string
-local math = require "math"
 local type = type
 local error = error
-local print = print
-
-module "sasl"
+local setmetatable = setmetatable;
+local assert = assert;
+local require = require;
 
--- Credentials handler:
---   Arguments: ("PLAIN", user, host, password)
---   Returns: true (success) | false (fail) | nil (user unknown)
-local function new_plain(realm, credentials_handler)
-       local object = { mechanism = "PLAIN", realm = realm, credentials_handler = credentials_handler}
-       function object.feed(self, message)
-               if message == "" or message == nil then return "failure", "malformed-request" end
-               local response = message
-               local authorization = s_match(response, "([^%z]*)")
-               local authentication = s_match(response, "%z([^%z]+)%z")
-               local password = s_match(response, "%z[^%z]+%z([^%z]+)")
+require "util.iterators"
+local keys = keys
 
-    if authentication == nil or password == nil then return "failure", "malformed-request" end
-    self.username = authentication
-    local auth_success = self.credentials_handler("PLAIN", self.username, self.realm, password)
-
-    if auth_success then
-      return "success"
-    elseif auth_success == nil then
-      return "failure", "account-disabled"
-    else
-      return "failure", "not-authorized"
-    end
-  end
-  return object
-end
-
--- credentials_handler:
---   Arguments: (mechanism, node, domain, realm, decoder)
---   Returns: Password encoding, (plaintext) password
--- implementing RFC 2831
-local function new_digest_md5(realm, credentials_handler)
-       --TODO complete support for authzid
+local array = require "util.array"
+module "sasl"
 
-       local function serialize(message)
-               local data = ""
+--[[
+Authentication Backend Prototypes:
 
-               if type(message) ~= "table" then error("serialize needs an argument of type table.") end
+state = false : disabled
+state = true : enabled
+state = nil : non-existant
 
-               -- testing all possible values
-               if message["realm"] then data = data..[[realm="]]..message.realm..[[",]] end
-               if message["nonce"] then data = data..[[nonce="]]..message.nonce..[[",]] end
-               if message["qop"] then data = data..[[qop="]]..message.qop..[[",]] end
-               if message["charset"] then data = data..[[charset=]]..message.charset.."," end
-               if message["algorithm"] then data = data..[[algorithm=]]..message.algorithm.."," end
-               if message["rspauth"] then data = data..[[rspauth=]]..message.rspauth.."," end
-               data = data:gsub(",$", "")
-               return data
+plain:
+       function(username, realm)
+               return password, state;
        end
 
-       local function utf8tolatin1ifpossible(passwd)
-               local i = 1;
-               while i <= #passwd do
-                       local passwd_i = to_byte(passwd:sub(i, i));
-                       if passwd_i > 0x7F then
-                               if passwd_i < 0xC0 or passwd_i > 0xC3 then
-                                       return passwd;
-                               end
-                               i = i + 1;
-                               passwd_i = to_byte(passwd:sub(i, i));
-                               if passwd_i < 0x80 or passwd_i > 0xBF then
-                                       return passwd;
-                               end
-                       end
-                       i = i + 1;
-               end
+plain-test:
+       function(username, realm, password)
+               return true or false, state;
+       end
 
-               local p = {};
-               local j = 0;
-               i = 1;
-               while (i <= #passwd) do
-                       local passwd_i = to_byte(passwd:sub(i, i));
-                       if passwd_i > 0x7F then
-                               i = i + 1;
-                               local passwd_i_1 = to_byte(passwd:sub(i, i));
-                               t_insert(p, to_char(passwd_i%4*64 + passwd_i_1%64)); -- I'm so clever
-                       else
-                               t_insert(p, to_char(passwd_i));
-                       end
-                       i = i + 1;
-               end
-               return t_concat(p);
+digest-md5:
+       function(username, domain, realm, encoding) -- domain and realm are usually the same; for some broken
+                                                                                               -- implementations it's not
+               return digesthash, state;
        end
-       local function latin1toutf8(str)
-               local p = {};
-               for ch in gmatch(str, ".") do
-                       ch = to_byte(ch);
-                       if (ch < 0x80) then
-                               t_insert(p, to_char(ch));
-                       elseif (ch < 0xC0) then
-                               t_insert(p, to_char(0xC2, ch));
-                       else
-                               t_insert(p, to_char(0xC3, ch - 64));
-                       end
-               end
-               return t_concat(p);
+
+digest-md5-test:
+       function(username, domain, realm, encoding, digesthash)
+               return true or false, state;
        end
-       local function parse(data)
-               local message = {}
-               -- COMPAT: %z in the pattern to work around jwchat bug (sends "charset=utf-8\0")
-               for k, v in gmatch(data, [[([%w%-]+)="?([^",%z]*)"?,?]]) do -- FIXME The hacky regex makes me shudder
-                       message[k] = v;
-               end
-               return message;
+]]
+
+local method = {};
+method.__index = method;
+local mechanisms = {};
+local backend_mechanism = {};
+
+-- register a new SASL mechanims
+local function registerMechanism(name, backends, f)
+       assert(type(name) == "string", "Parameter name MUST be a string.");
+       assert(type(backends) == "string" or type(backends) == "table", "Parameter backends MUST be either a string or a table.");
+       assert(type(f) == "function", "Parameter f MUST be a function.");
+       mechanisms[name] = f
+       for _, backend_name in ipairs(backends) do
+               if backend_mechanism[backend_name] == nil then backend_mechanism[backend_name] = {}; end
+               t_insert(backend_mechanism[backend_name], name);
        end
+end
 
-       local object = { mechanism = "DIGEST-MD5", realm = realm, credentials_handler = credentials_handler};
-
-       object.nonce = generate_uuid();
-       object.step = 0;
-       object.nonce_count = {};
-
-       function object.feed(self, message)
-               self.step = self.step + 1;
-               if (self.step == 1) then
-                       local challenge = serialize({   nonce = object.nonce,
-                                                                                       qop = "auth",
-                                                                                       charset = "utf-8",
-                                                                                       algorithm = "md5-sess",
-                                                                                       realm = self.realm});
-                       return "challenge", challenge;
-               elseif (self.step == 2) then
-                       local response = parse(message);
-                       -- check for replay attack
-                       if response["nc"] then
-                               if self.nonce_count[response["nc"]] then return "failure", "not-authorized" end
-                       end
-
-                       -- check for username, it's REQUIRED by RFC 2831
-                       if not response["username"] then
-                               return "failure", "malformed-request";
-                       end
-                       self["username"] = response["username"];
-
-                       -- check for nonce, ...
-                       if not response["nonce"] then
-                               return "failure", "malformed-request";
-                       else
-                               -- check if it's the right nonce
-                               if response["nonce"] ~= tostring(self.nonce) then return "failure", "malformed-request" end
-                       end
-
-                       if not response["cnonce"] then return "failure", "malformed-request", "Missing entry for cnonce in SASL message." end
-                       if not response["qop"] then response["qop"] = "auth" end
-
-                       if response["realm"] == nil or response["realm"] == "" then
-                               response["realm"] = "";
-                       elseif response["realm"] ~= self.realm then
-                               return "failure", "not-authorized", "Incorrect realm value";
-                       end
+-- create a new SASL object which can be used to authenticate clients
+function new(realm, profile, forbidden)
+       local sasl_i = {profile = profile};
+       sasl_i.realm = realm;
+       local s = setmetatable(sasl_i, method);
+       if forbidden == nil then forbidden = {} end
+       s:forbidden(forbidden)
+       return s;
+end
 
-                       local decoder;
-                       if response["charset"] == nil then
-                               decoder = utf8tolatin1ifpossible;
-                       elseif response["charset"] ~= "utf-8" then
-                               return "failure", "incorrect-encoding", "The client's response uses "..response["charset"].." for encoding with isn't supported by sasl.lua. Supported encodings are latin or utf-8.";
-                       end
+-- get a fresh clone with the same realm, profiles and forbidden mechanisms
+function method:clean_clone()
+       return new(self.realm, self.profile, self:forbidden())
+end
 
-                       local domain = "";
-                       local protocol = "";
-                       if response["digest-uri"] then
-                               protocol, domain = response["digest-uri"]:match("(%w+)/(.*)$");
-                               if protocol == nil or domain == nil then return "failure", "malformed-request" end
-                       else
-                               return "failure", "malformed-request", "Missing entry for digest-uri in SASL message."
-                       end
+-- set the forbidden mechanisms
+function method:forbidden( restrict )
+       if restrict then
+               -- set forbidden
+               self.restrict = set.new(restrict);
+       else
+               -- get forbidden
+               return array.collect(self.restrict:items());
+       end
+end
 
-                       --TODO maybe realm support
-                       self.username = response["username"];
-                       local password_encoding, Y = self.credentials_handler("DIGEST-MD5", response["username"], self.realm, response["realm"], decoder);
-                       if Y == nil then return "failure", "not-authorized"
-                       elseif Y == false then return "failure", "account-disabled" end
-                       local A1 = "";
-                       if response.authzid then
-                               if response.authzid == self.username or response.authzid == self.username.."@"..self.realm then
-                                       -- COMPAT
-                                       log("warn", "Client is violating RFC 3920 (section 6.1, point 7).");
-                                       A1 = Y..":"..response["nonce"]..":"..response["cnonce"]..":"..response.authzid;
-                               else
-                                       return "failure", "invalid-authzid";
+-- get a list of possible SASL mechanims to use
+function method:mechanisms()
+       local mechanisms = {}
+       for backend, f in pairs(self.profile) do
+               if backend_mechanism[backend] then
+                       for _, mechanism in ipairs(backend_mechanism[backend]) do
+                               if not self.restrict:contains(mechanism) then
+                                       mechanisms[mechanism] = true;
                                end
-                       else
-                               A1 = Y..":"..response["nonce"]..":"..response["cnonce"];
-                       end
-                       local A2 = "AUTHENTICATE:"..protocol.."/"..domain;
-
-                       local HA1 = md5(A1, true);
-                       local HA2 = md5(A2, true);
-
-                       local KD = HA1..":"..response["nonce"]..":"..response["nc"]..":"..response["cnonce"]..":"..response["qop"]..":"..HA2;
-                       local response_value = md5(KD, true);
-
-                       if response_value == response["response"] then
-                               -- calculate rspauth
-                               A2 = ":"..protocol.."/"..domain;
-
-                               HA1 = md5(A1, true);
-                               HA2 = md5(A2, true);
-
-                               KD = HA1..":"..response["nonce"]..":"..response["nc"]..":"..response["cnonce"]..":"..response["qop"]..":"..HA2
-                               local rspauth = md5(KD, true);
-                               self.authenticated = true;
-                               return "challenge", serialize({rspauth = rspauth});
-                       else
-                               return "failure", "not-authorized", "The response provided by the client doesn't match the one we calculated."
                        end
-               elseif self.step == 3 then
-                       if self.authenticated ~= nil then return "success"
-                       else return "failure", "malformed-request" end
                end
        end
-       return object;
+       self["possible_mechanisms"] = mechanisms;
+       return array.collect(keys(mechanisms));
 end
 
--- Credentials handler: Can be nil. If specified, should take the mechanism as
--- the only argument, and return true for OK, or false for not-OK (TODO)
-local function new_anonymous(realm, credentials_handler)
-       local object = { mechanism = "ANONYMOUS", realm = realm, credentials_handler = credentials_handler}
-               function object.feed(self, message)
-                       return "success"
-               end
-       object["username"] = generate_uuid()
-       return object
+-- select a mechanism to use
+function method:select(mechanism)
+       if self.mech_i then
+               return false;
+       end
+       
+       self.mech_i = mechanisms[mechanism]
+       if self.mech_i == nil then 
+               return false;
+       end
+       return true;
 end
 
+-- feed new messages to process into the library
+function method:process(message)
+       --if message == "" or message == nil then return "failure", "malformed-request" end
+       return self.mech_i(self, message);
+end
 
-function new(mechanism, realm, credentials_handler)
-       local object
-       if mechanism == "PLAIN" then object = new_plain(realm, credentials_handler)
-       elseif mechanism == "DIGEST-MD5" then object = new_digest_md5(realm, credentials_handler)
-       elseif mechanism == "ANONYMOUS" then object = new_anonymous(realm, credentials_handler)
-       else
-               log("debug", "Unsupported SASL mechanism: "..tostring(mechanism));
-               return nil
-       end
-       return object
+-- load the mechanisms
+local load_mechs = {"plain", "digest-md5", "anonymous", "scram"}
+for _, mech in ipairs(load_mechs) do
+       local name = "util.sasl."..mech;
+       local m = require(name);
+       m.init(registerMechanism)
 end
 
 return _M;
index e1221caa9385ff763a504356451227713f9ea293..08ef2c9aa1649deac35eca8fd38488e7982a5372 100644 (file)
@@ -38,6 +38,8 @@ if do_pretty_printing then
        end
 end
 
+local xmlns_stanzas = "urn:ietf:params:xml:ns:xmpp-stanzas";
+
 module "stanza"
 
 stanza_mt = { __type = "stanza" };
@@ -65,7 +67,7 @@ end
 
 function stanza_mt:text(text)
        (self.last_add[#self.last_add] or self):add_direct_child(text);
-       return self; 
+       return self;
 end
 
 function stanza_mt:up()
@@ -95,7 +97,7 @@ end
 
 function stanza_mt:get_child(name, xmlns)
        for _, child in ipairs(self.tags) do
-               if (not name or child.name == name) 
+               if (not name or child.name == name)
                        and ((not xmlns and self.attr.xmlns == child.attr.xmlns)
                                or child.attr.xmlns == xmlns) then
                        
@@ -105,13 +107,13 @@ function stanza_mt:get_child(name, xmlns)
 end
 
 function stanza_mt:child_with_name(name)
-       for _, child in ipairs(self.tags) do    
+       for _, child in ipairs(self.tags) do
                if child.name == name then return child; end
        end
 end
 
 function stanza_mt:child_with_ns(ns)
-       for _, child in ipairs(self.tags) do    
+       for _, child in ipairs(self.tags) do
                if child.attr.xmlns == ns then return child; end
        end
 end
@@ -123,7 +125,6 @@ function stanza_mt:children()
                        local v = a[i]
                        if v then return v; end
                end, self, i;
-                                           
 end
 function stanza_mt:childtags()
        local i = 0;
@@ -132,7 +133,6 @@ function stanza_mt:childtags()
                        local v = self.tags[i]
                        if v then return v; end
                end, self.tags[1], i;
-                                           
 end
 
 local xml_escape
@@ -191,6 +191,30 @@ function stanza_mt.get_text(t)
        end
 end
 
+function stanza_mt.get_error(stanza)
+       local type, condition, text;
+       
+       local error_tag = stanza:get_child("error");
+       if not error_tag then
+               return nil, nil, nil;
+       end
+       type = error_tag.attr.type;
+       
+       for child in error_tag:children() do
+               if child.attr.xmlns == xmlns_stanzas then
+                       if not text and child.name == "text" then
+                               text = child:get_text();
+                       elseif not condition then
+                               condition = child.name;
+                       end
+                       if condition and text then
+                               break;
+                       end
+               end
+       end
+       return type, condition or "undefined-condition", text or "";
+end
+
 function stanza_mt.__add(s1, s2)
        return s1:add_direct_child(s2);
 end
@@ -223,14 +247,14 @@ function deserialize(stanza)
                for i=1,#attr do attr[i] = nil; end
                local attrx = {};
                for att in pairs(attr) do
-                       if s_find(att, "|", 1, true) and not s_find(k, "\1", 1, true) then
-                               local ns,na = s_match(k, "^([^|]+)|(.+)$");
+                       if s_find(att, "|", 1, true) and not s_find(att, "\1", 1, true) then
+                               local ns,na = s_match(att, "^([^|]+)|(.+)$");
                                attrx[ns.."\1"..na] = attr[att];
                                attr[att] = nil;
                        end
                end
                for a,v in pairs(attrx) do
-                       attr[x] = v;
+                       attr[a] = v;
                end
                setmetatable(stanza, stanza_mt);
                for _, child in ipairs(stanza) do
@@ -320,7 +344,7 @@ if do_pretty_printing then
        function stanza_mt.pretty_print(t)
                local children_text = "";
                for n, child in ipairs(t) do
-                       if type(child) == "string" then 
+                       if type(child) == "string" then
                                children_text = children_text .. xml_escape(child);
                        else
                                children_text = children_text .. child:pretty_print();
index 05f91be8a99c8cc23b24e50b69104a88fe0259dc..c6bd2748ec20ec0a3e40ac385c46daa8fe91662a 100644 (file)
@@ -8,6 +8,9 @@
 
 
 local ns_addtimer = require "net.server".addtimer;
+local event = require "net.server".event;
+local event_base = require "net.server".event_base;
+
 local get_time = os.time;
 local t_insert = table.insert;
 local t_remove = table.remove;
@@ -19,33 +22,51 @@ local new_data = {};
 
 module "timer"
 
-local function _add_task(delay, func)
-       local current_time = get_time();
-       delay = delay + current_time;
-       if delay >= current_time then
-               t_insert(new_data, {delay, func});
-       else func(); end
-end
-
-add_task = _add_task;
-
-ns_addtimer(function()
-       local current_time = get_time();
-       if #new_data > 0 then
-               for _, d in pairs(new_data) do
-                       t_insert(data, d);
+local _add_task;
+if not event then
+       function _add_task(delay, func)
+               local current_time = get_time();
+               delay = delay + current_time;
+               if delay >= current_time then
+                       t_insert(new_data, {delay, func});
+               else
+                       func();
                end
-               new_data = {};
        end
-       
-       for i, d in pairs(data) do
-               local t, func = d[1], d[2];
-               if t <= current_time then
-                       data[i] = nil;
-                       local r = func(current_time);
-                       if type(r) == "number" then _add_task(r, func); end
+
+       ns_addtimer(function()
+               local current_time = get_time();
+               if #new_data > 0 then
+                       for _, d in pairs(new_data) do
+                               t_insert(data, d);
+                       end
+                       new_data = {};
                end
+               
+               for i, d in pairs(data) do
+                       local t, func = d[1], d[2];
+                       if t <= current_time then
+                               data[i] = nil;
+                               local r = func(current_time);
+                               if type(r) == "number" then _add_task(r, func); end
+                       end
+               end
+       end);
+else
+       local EVENT_LEAVE = (event.core and event.core.LEAVE) or -1;
+       function _add_task(delay, func)
+               event_base:addevent(nil, 0, function ()
+                       local ret = func();
+                       if ret then
+                               return 0, ret;
+                       else
+                               return EVENT_LEAVE;
+                       end
+               end
+               , delay);
        end
-end);
+end
+
+add_task = _add_task;
 
 return _M;
index a0be4a8e957cff05ce4386407f89ccd361ed09f6..f7744feaebf23bae79c1e8f3151dfca9e164800b 100644 (file)
@@ -1,6 +1,6 @@
 -- Prosody IM
--- Copyright (C) 2008-2009 Matthew Wild
--- Copyright (C) 2008-2009 Waqas Hussain
+-- 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.