Merge 0.6->0.7
authorMatthew Wild <mwild1@gmail.com>
Thu, 2 Jun 2011 14:23:58 +0000 (15:23 +0100)
committerMatthew Wild <mwild1@gmail.com>
Thu, 2 Jun 2011 14:23:58 +0000 (15:23 +0100)
72 files changed:
.hgignore
Makefile
TODO
configure
core/certmanager.lua
core/componentmanager.lua [new file with mode: 0644]
core/configmanager.lua
core/hostmanager.lua
core/loggingmanager.lua
core/modulemanager.lua
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/dns.lua
net/http.lua
net/httpserver.lua
net/multiplex_listener.lua
net/server.lua
net/server_event.lua
net/server_select.lua
net/xmppclient_listener.lua
net/xmppcomponent_listener.lua
net/xmppserver_listener.lua
plugins/mod_announce.lua
plugins/mod_bosh.lua
plugins/mod_component.lua
plugins/mod_compression.lua
plugins/mod_console.lua [new file with mode: 0644]
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_private.lua
plugins/mod_proxy65.lua
plugins/mod_register.lua
plugins/mod_roster.lua
plugins/mod_saslauth.lua
plugins/mod_tls.lua
plugins/muc/mod_muc.lua
plugins/muc/muc.lib.lua
prosody
prosody.cfg.lua.dist
prosodyctl
tests/test.lua
tools/xep227toprosody.lua
util-src/Makefile
util-src/pposix.c
util-src/signal.c
util/datamanager.lua
util/dependencies.lua
util/events.lua
util/hmac.lua
util/pluginloader.lua
util/prosodyctl.lua
util/sasl.lua
util/sasl/anonymous.lua
util/sasl/digest-md5.lua
util/sasl/plain.lua
util/sasl/scram.lua
util/sasl_cyrus.lua
util/stanza.lua
util/timer.lua

index 7937da24a01f68ddd7440f437033b278be5a0683..3449e6efa67efe5ab904a905b6b73d08e3e63431 100644 (file)
--- a/.hgignore
+++ b/.hgignore
@@ -9,7 +9,6 @@ prosody.cfg.lua
 prosody.version
 config.unix
 *.patch
-*.diff
 *.orig
 *.rej
 *.save
@@ -22,7 +21,3 @@ config.unix
 *.log
 *.err
 *.debug
-*.dll
-*.exp
-*.lib
-*.obj
index 51e376f04fe02c10a51b9d4b2bda8b6c0b0612f7..4090d554336b46cddeffc63423452e077bac2602 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -34,8 +34,7 @@ install: prosody.install prosodyctl.install prosody.cfg.lua.install util/encodin
        install -d $(MODULES)/muc
        install -m644 plugins/muc/* $(MODULES)/muc
        install -m644 certs/* $(CONFIG)/certs
-       install -d $(MODULES)/adhoc
-       install -m644 plugins/adhoc/*.lua $(MODULES)/adhoc
+       install -m644 plugins/*.lua $(MODULES)
        install -m644 man/prosodyctl.man $(MAN)/man1/prosodyctl.1
        test -e $(CONFIG)/prosody.cfg.lua || install -m644 prosody.cfg.lua.install $(CONFIG)/prosody.cfg.lua
        test -e prosody.version && install prosody.version $(SOURCE)/prosody.version || true
diff --git a/TODO b/TODO
index a49bb52bd104f641caa0af98cde6e4a7042c683f..c0d2b959cffd937db3954ef4c42d5b476822713c 100644 (file)
--- a/TODO
+++ b/TODO
@@ -1,9 +1,16 @@
+== 0.8 ==
+- Ad-hoc commands:
+       http://code.google.com/p/prosody-modules/wiki/mod_adhoc
+       http://code.google.com/p/prosody-modules/wiki/mod_adhoc_cmd_admin
+       http://code.google.com/p/prosody-modules/wiki/mod_adhoc_cmd_ping
+       http://code.google.com/p/prosody-modules/wiki/mod_adhoc_cmd_uptime
+       
+- Pubsub
+- Data storage backend abstraction
+
 == 0.9 ==
-- IPv6
-- SASL EXTERNAL
-- Roster providers
-- Web interface
+- Clustering
 
 == 1.0 ==
-- Clustering
+- Web interface?
 - World domination
index af046223933d8f18b477bc9b5b5d3b8b8befedc2..f2d8fc098624b48a289510e73285cdf9a0ecfa12 100755 (executable)
--- a/configure
+++ b/configure
@@ -11,16 +11,13 @@ LUA_BINDIR="/usr/bin"
 LUA_INCDIR="/usr/include"
 LUA_LIBDIR="/usr/lib"
 IDN_LIB=idn
-ICU_FLAGS="-licui18n -licudata -licuuc"
 OPENSSL_LIB=crypto
 CC=gcc
-CXX=g++
 LD=gcc
 
 CFLAGS="-fPIC -Wall"
 LDFLAGS="-shared"
 
-IDN_LIBRARY=idn
 # Help
 
 show_help() {
@@ -29,7 +26,7 @@ Configure Prosody prior to building.
 
 --help                      This help.
 --ostype=OS                 Use one of the OS presets.
-                            May be one of: debian, macosx, linux, freebsd
+                            May be one of: debian, macosx, linux
 --prefix=DIR                Prefix where Prosody should be installed.
                             Default is $PREFIX
 --sysconfdir=DIR            Location where the config file should be installed.
@@ -46,9 +43,6 @@ Configure Prosody prior to building.
                             Default is \$LUA_DIR/lib
 --with-idn=LIB              The name of the IDN library to link with.
                             Default is $IDN_LIB
---idn-library=(idn|icu)                 Select library to use for IDNA functionality.
-                                                                        idn: use GNU libidn (default)
-                                                                        icu: use ICU from IBM
 --with-ssl=LIB              The name of the SSL to link with.
                             Default is $OPENSSL_LIB
 --cflags=FLAGS              Flags to pass to the compiler
@@ -91,37 +85,6 @@ do
    --ostype=*)
       OSTYPE="$value"
       OSTYPE_SET=yes
-      if [ "$OSTYPE" = "debian" ]
-      then LUA_SUFFIX="5.1";
-       LUA_SUFFIX_SET=yes
-       LUA_INCDIR=/usr/include/lua5.1;
-       LUA_INCDIR_SET=yes
-       fi
-       if [ "$OSTYPE" = "macosx" ]
-       then LUA_INCDIR=/usr/local/include;
-       LUA_INCDIR_SET=yes
-       LUA_LIBDIR=/usr/local/lib
-       LUA_LIBDIR_SET=yes
-       LDFLAGS="-bundle -undefined dynamic_lookup"
-       fi
-        if [ "$OSTYPE" = "linux" ]
-        then LUA_INCDIR=/usr/local/include;
-        LUA_INCDIR_SET=yes
-        LUA_LIBDIR=/usr/local/lib
-        LUA_LIBDIR_SET=yes
-        CFLAGS="-Wall -fPIC"
-        LDFLAGS="-shared"
-        fi
-        if [ "$OSTYPE" = "freebsd" ]
-        then LUA_INCDIR="/usr/local/include/lua51"
-        LUA_INCDIR_SET=yes
-        CFLAGS="-Wall -fPIC -I/usr/local/include"
-        LDFLAGS="-I/usr/local/include -L/usr/local/lib -shared"
-        LUA_SUFFIX="-5.1"
-        LUA_SUFFIX_SET=yes
-        LUA_DIR=/usr/local
-        LUA_DIR_SET=yes
-        fi
       ;;
    --datadir=*)
        DATADIR="$value"
@@ -148,9 +111,6 @@ do
    --with-idn=*)
       IDN_LIB="$value"
       ;;
-       --idn-library=*)
-               IDN_LIBRARY="$value"
-               ;;
    --with-ssl=*)
       OPENSSL_LIB="$value"
       ;;
@@ -174,6 +134,32 @@ do
    shift
 done
 
+if [ "$OSTYPE_SET" = "yes" ]
+then
+       if [ "$OSTYPE" = "debian" ]
+       then LUA_SUFFIX="5.1";
+       LUA_SUFFIX_SET=yes
+       LUA_INCDIR=/usr/include/lua5.1;
+       LUA_INCDIR_SET=yes
+       fi
+       if [ "$OSTYPE" = "macosx" ]
+       then LUA_INCDIR=/usr/local/include;
+       LUA_INCDIR_SET=yes
+       LUA_LIBDIR=/usr/local/lib
+       LUA_LIBDIR_SET=yes
+       CFLAGS="-Wall"
+       LDFLAGS="-bundle -undefined dynamic_lookup"
+       fi
+        if [ "$OSTYPE" = "linux" ]
+        then LUA_INCDIR=/usr/local/include;
+        LUA_INCDIR_SET=yes
+        LUA_LIBDIR=/usr/local/lib
+        LUA_LIBDIR_SET=yes
+        CFLAGS="-Wall -fPIC"
+        LDFLAGS="-shared"
+        fi
+fi
+
 if [ "$PREFIX_SET" = "yes" -a ! "$SYSCONFDIR_SET" = "yes" ]
 then
    if [ "$PREFIX" = "/usr" ]
@@ -269,16 +255,6 @@ then
    LUA_BINDIR="$LUA_DIR/bin"
 fi
 
-if [ "$IDN_LIBRARY" = "icu" ]
-then
-       IDNA_LIBS="$ICU_FLAGS"
-       CFLAGS="$CFLAGS -DUSE_STRINGPREP_ICU"
-fi
-if [ "$IDN_LIBRARY" = "idn" ] 
-then
-       IDNA_LIBS="-l$IDN_LIB"
-fi
-
 echo -n "Checking Lua includes... "
 lua_h="$LUA_INCDIR/lua.h"
 if [ -e "$lua_h" ]
@@ -329,12 +305,10 @@ LUA_LIBDIR=$LUA_LIBDIR
 LUA_BINDIR=$LUA_BINDIR
 REQUIRE_CONFIG=$REQUIRE_CONFIG
 IDN_LIB=$IDN_LIB
-IDNA_LIBS=$IDNA_LIBS
 OPENSSL_LIB=$OPENSSL_LIB
 CFLAGS=$CFLAGS
 LDFLAGS=$LDFLAGS
 CC=$CC
-CXX=$CXX
 LD=$LD
 
 EOF
index 0dc0bfd4bec49004bf6bd3dd84409572f5feb04b..3dd06585fa667e3cd2d96f8dc17eb43938861d30 100644 (file)
@@ -1,11 +1,3 @@
--- 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 configmanager = require "core.configmanager";
 local log = require "util.logger".init("certmanager");
 local ssl = ssl;
@@ -14,65 +6,54 @@ local ssl_newcontext = ssl and ssl.newcontext;
 local setmetatable, tostring = setmetatable, tostring;
 
 local prosody = prosody;
-local resolve_path = configmanager.resolve_relative_path;
-local config_path = prosody.paths.config;
 
 module "certmanager"
 
--- Global SSL options if not overridden per-host
-local default_ssl_config = configmanager.get("*", "core", "ssl");
-local default_capath = "/etc/ssl/certs";
-local default_verify = (ssl and ssl.x509 and { "peer", "client_once", "continue", "ignore_purpose" }) or "none";
-local default_options = { "no_sslv2" };
+-- 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"; };
 
-function create_context(host, mode, user_ssl_config)
-       user_ssl_config = user_ssl_config or default_ssl_config;
+local default_ssl_ctx_mt = { __index = default_ssl_ctx };
+local default_ssl_ctx_in_mt = { __index = default_ssl_ctx_in };
 
-       if not ssl then return nil, "LuaSec (required for encryption) was not found"; end
-       if not user_ssl_config then return nil, "No SSL/TLS configuration present for "..host; end
-       
-       local ssl_config = {
-               mode = mode;
-               protocol = user_ssl_config.protocol or "sslv23";
-               key = resolve_path(config_path, user_ssl_config.key);
-               password = user_ssl_config.password;
-               certificate = resolve_path(config_path, user_ssl_config.certificate);
-               capath = resolve_path(config_path, user_ssl_config.capath or default_capath);
-               cafile = resolve_path(config_path, user_ssl_config.cafile);
-               verify = user_ssl_config.verify or default_verify;
-               options = user_ssl_config.options or default_options;
-               ciphers = user_ssl_config.ciphers;
-               depth = user_ssl_config.depth;
-       };
+-- Global SSL options if not overridden per-host
+local default_ssl_config = configmanager.get("*", "core", "ssl");
 
-       local ctx, err = ssl_newcontext(ssl_config);
-       if not ctx then
-               err = err or "invalid ssl config"
-               local file = err:match("^error loading (.-) %(");
-               if file then
-                       if file == "private key" then
-                               file = ssl_config.key or "your private key";
-                       elseif file == "certificate" then
-                               file = ssl_config.certificate or "your certificate file";
-                       end
-                       local reason = err:match("%((.+)%)$") or "some reason";
-                       if reason == "Permission denied" then
-                               reason = "Check that the permissions allow Prosody to read this file.";
-                       elseif reason == "No such file or directory" then
-                               reason = "Check that the path is correct, and the file exists.";
-                       elseif reason == "system lib" then
-                               reason = "Previous error (see logs), or other system error.";
-                       elseif reason == "(null)" or not reason then
-                               reason = "Check that the file exists and the permissions are correct";
+function create_context(host, mode, config)
+       local ssl_config = config and config.core.ssl or default_ssl_config;
+       if ssl and ssl_config then
+               local ctx, err = ssl_newcontext(setmetatable(ssl_config, mode == "client" and default_ssl_ctx_mt or default_ssl_ctx_in_mt));
+               if not ctx then
+                       err = err or "invalid ssl config"
+                       local file = err:match("^error loading (.-) %(");
+                       if file then
+                               if file == "private key" then
+                                       file = ssl_config.key or "your private key";
+                               elseif file == "certificate" then
+                                       file = ssl_config.certificate or "your certificate file";
+                               end
+                               local reason = err:match("%((.+)%)$") or "some reason";
+                               if reason == "Permission denied" then
+                                       reason = "Check that the permissions allow Prosody to read this file.";
+                               elseif reason == "No such file or directory" then
+                                       reason = "Check that the path is correct, and the file exists.";
+                               elseif reason == "system lib" then
+                                       reason = "Previous error (see logs), or other system error.";
+                               elseif reason == "(null)" or not reason then
+                                       reason = "Check that the file exists and the permissions are correct";
+                               else
+                                       reason = "Reason: "..tostring(reason):lower();
+                               end
+                               log("error", "SSL/TLS: Failed to load %s: %s", file, reason);
                        else
-                               reason = "Reason: "..tostring(reason):lower();
+                               log("error", "SSL/TLS: Error initialising for host %s: %s", host, err );
                        end
-                       log("error", "SSL/TLS: Failed to load %s: %s", file, reason);
-               else
-                       log("error", "SSL/TLS: Error initialising for host %s: %s", host, err );
-               end
+               end
+               return ctx, err;
+       elseif not ssl then
+               return nil, "LuaSec (required for encryption) was not found";
        end
-       return ctx, err;
+       return nil, "No SSL/TLS configuration present for "..host;
 end
 
 function reload_ssl_config()
diff --git a/core/componentmanager.lua b/core/componentmanager.lua
new file mode 100644 (file)
index 0000000..48e2798
--- /dev/null
@@ -0,0 +1,162 @@
+-- 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 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;
+local fire_event = require "core.eventmanager".fire_event;
+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;
+
+local components = {};
+
+local disco_items = require "util.multitable".new();
+local NULL = {};
+
+module "componentmanager"
+
+local function default_component_handler(origin, stanza)
+       log("warn", "Stanza being handled by default component; bouncing error for: %s", stanza:top_tag());
+       if stanza.attr.type ~= "error" and stanza.attr.type ~= "result" then
+               origin.send(st.error_reply(stanza, "wait", "service-unavailable", "Component unavailable"));
+       end
+end
+
+function load_enabled_components(config)
+       local defined_hosts = config or configmanager.getconfig();
+               
+       for host, host_config in pairs(defined_hosts) do
+               if host ~= "*" and ((host_config.core.enabled == nil or host_config.core.enabled) and type(host_config.core.component_module) == "string") then
+                       hosts[host] = create_component(host);
+                       hosts[host].connected = false;
+                       components[host] = default_component_handler;
+                       local ok, err = modulemanager.load(host, host_config.core.component_module);
+                       if not ok then
+                               log("error", "Error loading %s component %s: %s", tostring(host_config.core.component_module), tostring(host), tostring(err));
+                       else
+                               fire_event("component-activated", host, host_config);
+                               log("debug", "Activated %s component: %s", host_config.core.component_module, host);
+                       end
+               end
+       end
+end
+
+if prosody and prosody.events then
+       prosody.events.add_handler("server-starting", load_enabled_components);
+end
+
+function handle_stanza(origin, stanza)
+       local node, host = jid_split(stanza.attr.to);
+       local component = nil;
+       if host then
+               if node then component = components[node.."@"..host]; end -- hack to allow hooking node@server
+               if not component then component = components[host]; end
+       end
+       if component then
+               log("debug", "%s stanza being handled by component: %s", stanza.name, host);
+               component(origin, stanza, hosts[host]);
+       else
+               log("error", "Component manager recieved a stanza for a non-existing component: "..tostring(stanza));
+               default_component_handler(origin, stanza);
+       end
+end
+
+function create_component(host, component, events)
+       -- TODO check for host well-formedness
+       local ssl_ctx, ssl_ctx_in;
+       if host and ssl then
+               -- We need to find SSL context to use...
+               -- Discussion in prosody@ concluded that
+               -- 1 level back is usually enough by default
+               local base_host = host:gsub("^[^%.]+%.", "");
+               if hosts[base_host] then
+                       ssl_ctx = hosts[base_host].ssl_ctx;
+                       ssl_ctx_in = hosts[base_host].ssl_ctx_in;
+               else
+                       -- We have no cert, and no parent host to borrow a cert from
+                       -- Use global/default cert if there is one
+                       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(),
+                       dialback_secret = configmanager.get(host, "core", "dialback_secret") or uuid_gen() };
+end
+
+function register_component(host, component, session)
+       if not hosts[host] or (hosts[host].type == 'component' and not hosts[host].connected) then
+               local old_events = hosts[host] and hosts[host].events;
+
+               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);
+               end
+               modulemanager.load(host, "dialback");
+               modulemanager.load(host, "tls");
+               log("debug", "component added: "..host);
+               return session or hosts[host];
+       else
+               log("error", "Attempt to set component for existing host: "..host);
+       end
+end
+
+function deregister_component(host)
+       if components[host] then
+               modulemanager.unload(host, "tls");
+               modulemanager.unload(host, "dialback");
+               hosts[host].connected = nil;
+               local host_config = configmanager.getconfig()[host];
+               if host_config and ((host_config.core.enabled == nil or host_config.core.enabled) and type(host_config.core.component_module) == "string") then
+                       -- Set default handler
+                       components[host] = default_component_handler;
+               else
+                       -- Component not in config, or disabled, remove
+                       hosts[host] = nil; -- FIXME do proper unload of all modules and other cleanup before removing
+                       components[host] = nil;
+               end
+               -- remove from disco_items
+               if not(host:find("@", 1, true) or host:find("/", 1, true)) and host:find(".", 1, true) then
+                       disco_items:remove(host:sub(host:find(".", 1, true)+1), host);
+               end
+               log("debug", "component removed: "..host);
+               return true;
+       else
+               log("error", "Attempt to remove component for non-existing host: "..host);
+       end
+end
+
+function set_component_handler(host, handler)
+       components[host] = handler;
+end
+
+function get_children(host)
+       return disco_items:get(host) or NULL;
+end
+
+return _M;
index 4cc3ef467f963b360d6dbb573a3641ded702b744..54fb0a9ae82fdb61d22f4d1600f27855f84a3d46 100644 (file)
@@ -6,34 +6,34 @@
 -- COPYING file in the source package for more information.
 --
 
+
+
 local _G = _G;
-local setmetatable, loadfile, pcall, rawget, rawset, io, error, dofile, type, pairs, table =
-      setmetatable, loadfile, pcall, rawget, rawset, io, error, dofile, type, pairs, table;
-local format, math_max = string.format, math.max;
+local  setmetatable, loadfile, pcall, rawget, rawset, io, error, dofile, type, pairs, table, format =
+               setmetatable, loadfile, pcall, rawget, rawset, io, error, dofile, type, pairs, table, string.format;
 
-local fire_event = prosody and prosody.events.fire_event or function () end;
 
-local lfs = require "lfs";
-local path_sep = package.config:sub(1,1);
+local eventmanager = require "core.eventmanager";
 
 module "configmanager"
 
 local parsers = {};
 
-local config_mt = { __index = function (t, k) return rawget(t, "*"); end};
-local config = setmetatable({ ["*"] = { core = {} } }, config_mt);
+local config = { ["*"] = { core = {} } };
+
+local global_config = config["*"];
 
 -- When host not found, use global
-local host_mt = { };
+setmetatable(config, { __index = function () return global_config; end});
+local host_mt = { __index = global_config };
 
 -- When key not found in section, check key in global's section
 function section_mt(section_name)
        return { __index =      function (t, k)
-                                       local section = rawget(config["*"], section_name);
-                                       if not section then return nil; end
-                                       return section[k];
-                               end
-       };
+                                                                       local section = rawget(global_config, section_name);
+                                                                       if not section then return nil; end
+                                                                       return section[k];
+                                                       end };
 end
 
 function getconfig()
@@ -47,17 +47,8 @@ function get(host, section, key)
        end
        return nil;
 end
-function _M.rawget(host, section, key)
-       local hostconfig = rawget(config, host);
-       if hostconfig then
-               local sectionconfig = rawget(hostconfig, section);
-               if sectionconfig then
-                       return rawget(sectionconfig, key);
-               end
-       end
-end
 
-local function set(config, host, section, key, value)
+function set(host, section, key, value)
        if host and section and key then
                local hostconfig = rawget(config, host);
                if not hostconfig then
@@ -72,62 +63,16 @@ local function set(config, host, section, key, value)
        return false;
 end
 
-function _M.set(host, section, key, value)
-       return set(config, host, section, key, value);
-end
-
--- Helper function to resolve relative paths (needed by config)
-do
-       local rel_path_start = ".."..path_sep;
-       function resolve_relative_path(parent_path, path)
-               if path then
-                       -- Some normalization
-                       parent_path = parent_path:gsub("%"..path_sep.."+$", "");
-                       path = path:gsub("^%.%"..path_sep.."+", "");
-                       
-                       local is_relative;
-                       if path_sep == "/" and path:sub(1,1) ~= "/" then
-                               is_relative = true;
-                       elseif path_sep == "\\" and (path:sub(1,1) ~= "/" and path:sub(2,3) ~= ":\\") then
-                               is_relative = true;
-                       end
-                       if is_relative then
-                               return parent_path..path_sep..path;
-                       end
-               end
-               return path;
-       end     
-end
-
--- Helper function to convert a glob to a Lua pattern
-local function glob_to_pattern(glob)
-       return "^"..glob:gsub("[%p*?]", function (c)
-               if c == "*" then
-                       return ".*";
-               elseif c == "?" then
-                       return ".";
-               else
-                       return "%"..c;
-               end
-       end).."$";
-end
-
 function load(filename, format)
        format = format or filename:match("%w+$");
 
        if parsers[format] and parsers[format].load then
                local f, err = io.open(filename);
                if f then
-                       local new_config = setmetatable({ ["*"] = { core = {} } }, config_mt);
-                       local ok, err = parsers[format].load(f:read("*a"), filename, new_config);
+                       local ok, err = parsers[format].load(f:read("*a"), filename);
                        f:close();
                        if ok then
-                               config = new_config;
-                               fire_event("config-reloaded", {
-                                       filename = filename,
-                                       format = format,
-                                       config = config
-                               });
+                               eventmanager.fire_event("config-reloaded", { filename = filename, format = format });
                        end
                        return ok, "parser", err;
                end
@@ -164,23 +109,19 @@ do
        local loadstring, pcall, setmetatable = _G.loadstring, _G.pcall, _G.setmetatable;
        local setfenv, rawget, tostring = _G.setfenv, _G.rawget, _G.tostring;
        parsers.lua = {};
-       function parsers.lua.load(data, config_file, config)
+       function parsers.lua.load(data, filename)
                local env;
                -- The ' = true' are needed so as not to set off __newindex when we assign the functions below
-               env = setmetatable({
-                       Host = true, host = true, VirtualHost = true,
-                       Component = true, component = true,
-                       Include = true, include = true, RunScript = true }, {
-                               __index = function (t, k)
-                                       return rawget(_G, k) or
-                                               function (settings_table)
-                                                       config[__currenthost or "*"][k] = settings_table;
-                                               end;
-                               end,
-                               __newindex = function (t, k, v)
-                                       set(config, env.__currenthost or "*", "core", k, v);
-                               end
-               });
+               env = setmetatable({ Host = true, host = true, VirtualHost = true, Component = true, component = true,
+                                                       Include = true, include = true, RunScript = dofile }, { __index = function (t, k)
+                                                                                               return rawget(_G, k) or
+                                                                                                               function (settings_table)
+                                                                                                                       config[__currenthost or "*"][k] = settings_table;
+                                                                                                               end;
+                                                                               end,
+                                                               __newindex = function (t, k, v)
+                                                                                       set(env.__currenthost or "*", "core", k, v);
+                                                                               end});
                
                rawset(env, "__currenthost", "*") -- Default is global
                function env.VirtualHost(name)
@@ -190,13 +131,7 @@ do
                        end
                        rawset(env, "__currenthost", name);
                        -- Needs at least one setting to logically exist :)
-                       set(config, name or "*", "core", "defined", true);
-                       return function (config_options)
-                               rawset(env, "__currenthost", "*"); -- Return to global scope
-                               for option_name, option_value in pairs(config_options) do
-                                       set(config, name or "*", "core", option_name, option_value);
-                               end
-                       end;
+                       set(name or "*", "core", "defined", true);
                end
                env.Host, env.host = env.VirtualHost, env.VirtualHost;
                
@@ -205,62 +140,32 @@ do
                                error(format("Component %q clashes with previously defined Host %q, for services use a sub-domain like conference.%s",
                                        name, name, name), 0);
                        end
-                       set(config, name, "core", "component_module", "component");
+                       set(name, "core", "component_module", "component");
                        -- Don't load the global modules by default
-                       set(config, name, "core", "load_global_modules", false);
+                       set(name, "core", "load_global_modules", false);
                        rawset(env, "__currenthost", name);
-                       local function handle_config_options(config_options)
-                               rawset(env, "__currenthost", "*"); -- Return to global scope
-                               for option_name, option_value in pairs(config_options) do
-                                       set(config, name or "*", "core", option_name, option_value);
-                               end
-                       end
        
                        return function (module)
                                        if type(module) == "string" then
-                                               set(config, name, "core", "component_module", module);
-                                               return handle_config_options;
+                                               set(name, "core", "component_module", module);
                                        end
-                                       return handle_config_options(module);
                                end
                end
                env.component = env.Component;
                
-               function env.Include(file, wildcard)
-                       if file:match("[*?]") then
-                               local path_pos, glob = file:match("()([^"..path_sep.."]+)$");
-                               local path = file:sub(1, math_max(path_pos-2,0));
-                               local config_path = config_file:gsub("[^"..path_sep.."]+$", "");
-                               if #path > 0 then
-                                       path = resolve_relative_path(config_path, path);
-                               else
-                                       path = config_path;
-                               end
-                               local patt = glob_to_pattern(glob);
-                               for f in lfs.dir(path) do
-                                       if f:sub(1,1) ~= "." and f:match(patt) then
-                                               env.Include(path..path_sep..f);
-                                       end
-                               end
-                       else
-                               local f, err = io.open(file);
-                               if f then
-                                       local data = f:read("*a");
-                                       local file = resolve_relative_path(config_file:gsub("[^"..path_sep.."]+$", ""), file);
-                                       local ret, err = parsers.lua.load(data, file, config);
-                                       if not ret then error(err:gsub("%[string.-%]", file), 0); end
-                               end
-                               if not f then error("Error loading included "..file..": "..err, 0); end
-                               return f, err;
+               function env.Include(file)
+                       local f, err = io.open(file);
+                       if f then
+                               local data = f:read("*a");
+                               local ok, err = parsers.lua.load(data, file);
+                               if not ok then error(err:gsub("%[string.-%]", file), 0); end
                        end
+                       if not f then error("Error loading included "..file..": "..err, 0); end
+                       return f, err;
                end
                env.include = env.Include;
                
-               function env.RunScript(file)
-                       return dofile(resolve_relative_path(config_file:gsub("[^"..path_sep.."]+$", ""), file));
-               end
-               
-               local chunk, err = loadstring(data, "@"..config_file);
+               local chunk, err = loadstring(data, "@"..filename);
                
                if not chunk then
                        return nil, err;
index 9e74cd6bb5cdf0f5090381f3ce30bda3321b4df2..c8928b27e3aaa6dfcb1575f84f65fe432e2e0f73 100644 (file)
@@ -6,25 +6,25 @@
 -- COPYING file in the source package for more information.
 --
 
+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 disco_items = require "util.multitable".new();
-local NULL = {};
 
 local uuid_gen = require "util.uuid".generate;
 
-local log = require "util.logger".init("hostmanager");
-
-local hosts = hosts;
-local prosody_events = prosody.events;
 if not _G.prosody.incoming_s2s then
        require "core.s2smanager";
 end
 local incoming_s2s = _G.prosody.incoming_s2s;
 
+local log = require "util.logger".init("hostmanager");
+
 local pairs, setmetatable = pairs, setmetatable;
-local tostring, type = tostring, type;
 
 module "hostmanager"
 
@@ -35,10 +35,8 @@ 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 ~= false then
-                       if not host_config.core.component_module then
-                               activated_any_host = true;
-                       end
+               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
        end
@@ -47,53 +45,39 @@ local function load_enabled_hosts(config)
                log("error", "No active VirtualHost entries in the config file. This may cause unexpected behaviour as no modules will be loaded.");
        end
        
-       prosody_events.fire_event("hosts-activated", defined_hosts);
+       eventmanager.fire_event("hosts-activated", defined_hosts);
        hosts_loaded_once = true;
 end
 
-prosody_events.add_handler("server-starting", load_enabled_hosts);
+eventmanager.add_event_hook("server-starting", load_enabled_hosts);
 
 function activate(host, host_config)
-       if hosts[host] then return nil, "The host "..host.." is already activated"; end
-       host_config = host_config or configmanager.getconfig()[host];
-       if not host_config then return nil, "Couldn't find the host "..tostring(host).." defined in the current config"; end
-       local host_session = {
-               host = host;
-               s2sout = {};
-               events = events_new();
-               dialback_secret = configmanager.get(host, "core", "dialback_secret") or uuid_gen();
-               disallow_s2s = configmanager.get(host, "core", "disallow_s2s");
-       };
-       if not host_config.core.component_module then -- host
-               host_session.type = "local";
-               host_session.sessions = {};
-       else -- component
-               host_session.type = "component";
-       end
-       hosts[host] = host_session;
-       if not host:match("[@/]") then
-               disco_items:set(host:match("%.(.*)") or "*", host, true);
-       end
+       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$") or option_name:match("_interface$") then
                        log("warn", "%s: Option '%s' has no effect for virtual hosts - put it in the server-wide section instead", host, option_name);
                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);
-       prosody_events.fire_event("host-activated", host, host_config);
-       return true;
+       eventmanager.fire_event("host-activated", host, host_config);
 end
 
 function deactivate(host, reason)
        local host_session = hosts[host];
-       if not host_session then return nil, "The host "..tostring(host).." is not activated"; end
        log("info", "Deactivating host: %s", host);
-       prosody_events.fire_event("host-deactivating", host, host_session);
+       eventmanager.fire_event("host-deactivating", host, host_session);
        
-       if type(reason) ~= "table" then
-               reason = { condition = "host-gone", text = tostring(reason or "This server has stopped serving "..host) };
-       end
+       reason = reason or { condition = "host-gone", text = "This server has stopped serving "..host };
        
        -- Disconnect local users, s2s connections
        if host_session.sessions then
@@ -127,16 +111,11 @@ function deactivate(host, reason)
        end
 
        hosts[host] = nil;
-       if not host:match("[@/]") then
-               disco_items:remove(host:match("%.(.*)") or "*", host);
-       end
-       prosody_events.fire_event("host-deactivated", host);
+       eventmanager.fire_event("host-deactivated", host);
        log("info", "Deactivated host: %s", host);
-       return true;
 end
 
-function get_children(host)
-       return disco_items:get(host) or NULL;
+function getconfig(name)
 end
 
 return _M;
index 88f2bbbf506453d349427e2cb10de667ab2f200f..3ec696d50049d1c67f17bbff30f8ecab3afda2dc 100644 (file)
 local format, rep = string.format, string.rep;
 local pcall = pcall;
 local debug = debug;
-local tostring, setmetatable, rawset, pairs, ipairs, type =
+local tostring, setmetatable, rawset, pairs, ipairs, type = 
        tostring, setmetatable, rawset, pairs, ipairs, type;
 local io_open, io_write = io.open, io.write;
 local math_max, rep = math.max, string.rep;
 local os_date, os_getenv = os.date, os.getenv;
-local getstyle, setstyle = require "util.termcolours".getstyle, require "util.termcolours".setstyle;
+local getstyle, getstring = require "util.termcolours".getstyle, require "util.termcolours".getstring;
 
 if os.getenv("__FLUSH_LOG") then
        local io_flush = io.flush;
@@ -24,19 +24,20 @@ if os.getenv("__FLUSH_LOG") then
 end
 
 local config = require "core.configmanager";
+local eventmanager = require "core.eventmanager";
 local logger = require "util.logger";
-local prosody = prosody;
+local debug_mode = config.get("*", "core", "debug");
 
 _G.log = logger.init("general");
 
 module "loggingmanager"
 
--- The log config used if none specified in the config file (see reload_logging for initialization)
-local default_logging;
-local default_file_logging;
+-- The log config used if none specified in the config file
+local default_logging = { { to = "console" , levels = { min = (debug_mode and "debug") or "info" } } };
+local default_file_logging = { { to = "file", levels = { min = (debug_mode and "debug") or "info" }, timestamps = true } };
 local default_timestamp = "%b %d %H:%M:%S";
 -- The actual config loggingmanager is using
-local logging_config;
+local logging_config = config.get("*", "core", "log") or default_logging;
 
 local apply_sink_rules;
 local log_sink_types = setmetatable({}, { __newindex = function (t, k, v) rawset(t, k, v); apply_sink_rules(k); end; });
@@ -87,31 +88,9 @@ end
 -- the log_sink_types table.
 function apply_sink_rules(sink_type)
        if type(logging_config) == "table" then
-               
-               for _, level in ipairs(logging_levels) do
-                       if type(logging_config[level]) == "string" then
-                               local value = logging_config[level];
-                               if sink_type == "file" then
-                                       add_rule({
-                                               to = sink_type;
-                                               filename = value;
-                                               timestamps = true;
-                                               levels = { min = level };
-                                       });
-                               elseif value == "*"..sink_type then
-                                       add_rule({
-                                               to = sink_type;
-                                               levels = { min = level };
-                                       });
-                               end
-                       end
-               end
-               
-               for _, sink_config in ipairs(logging_config) do
-                       if (type(sink_config) == "table" and sink_config.to == sink_type) then
+               for _, sink_config in pairs(logging_config) do
+                       if sink_config.to == sink_type then
                                add_rule(sink_config);
-                       elseif (type(sink_config) == "string" and sink_config:match("^%*(.+)") == sink_type) then
-                               add_rule({ levels = { min = "debug" }, to = sink_type });
                        end
                end
        elseif type(logging_config) == "string" and (not logging_config:match("^%*")) and sink_type == "file" then
@@ -159,38 +138,6 @@ function get_levels(criteria, set)
        return set;
 end
 
--- Initialize config, etc. --
-function reload_logging()
-       local old_sink_types = {};
-       
-       for name, sink_maker in pairs(log_sink_types) do
-               old_sink_types[name] = sink_maker;
-               log_sink_types[name] = nil;
-       end
-       
-       logger.reset();
-
-       local debug_mode = config.get("*", "core", "debug");
-
-       default_logging = { { to = "console" , levels = { min = (debug_mode and "debug") or "info" } } };
-       default_file_logging = {
-               { to = "file", levels = { min = (debug_mode and "debug") or "info" }, timestamps = true }
-       };
-       default_timestamp = "%b %d %H:%M:%S";
-
-       logging_config = config.get("*", "core", "log") or default_logging;
-       
-       
-       for name, sink_maker in pairs(old_sink_types) do
-               log_sink_types[name] = sink_maker;
-       end
-       
-       prosody.events.fire_event("logging-reloaded");
-end
-
-reload_logging();
-prosody.events.add_handler("config-reloaded", reload_logging);
-
 --- Definition of built-in logging sinks ---
 
 -- Null sink, must enter log_sink_types *first*
@@ -201,7 +148,7 @@ end
 -- Column width for "source" (used by stdout and console)
 local sourcewidth = 20;
 
-function log_sink_types.stdout(config)
+function log_sink_types.stdout()
        local timestamps = config.timestamps;
        
        if timestamps == true then
@@ -223,7 +170,7 @@ function log_sink_types.stdout(config)
 end
 
 do
-       local do_pretty_printing = true;
+       local do_pretty_printing = not os_getenv("WINDIR");
        
        local logstyles = {};
        if do_pretty_printing then
@@ -250,14 +197,10 @@ do
                        if timestamps then
                                io_write(os_date(timestamps), " ");
                        end
-                       io_write(name, rep(" ", sourcewidth-namelen));
-                       setstyle(logstyles[level]);
-                       io_write(level);
-                       setstyle();
                        if ... then
-                               io_write("\t", format(message, ...), "\n");
+                               io_write(name, rep(" ", sourcewidth-namelen), getstring(logstyles[level], level), "\t", format(message, ...), "\n");
                        else
-                               io_write("\t", message, "\n");
+                               io_write(name, rep(" ", sourcewidth-namelen), getstring(logstyles[level], level), "\t", message, "\n");
                        end
                end
        end
@@ -272,6 +215,18 @@ function log_sink_types.file(config)
        end
        local write, flush = logfile.write, logfile.flush;
 
+       eventmanager.add_event_hook("reopen-log-files", function ()
+                       if logfile then
+                               logfile:close();
+                       end
+                       logfile = io_open(log, "a+");
+                       if not logfile then
+                               write, flush = empty_function, empty_function;
+                       else
+                               write, flush = logfile.write, logfile.flush;
+                       end
+               end);
+
        local timestamps = config.timestamps;
 
        if timestamps == nil or timestamps == true then
index 07a2b1c92011f71414cdccbd9963b7b5d3c497dd..8e62aecb8231ee8e6e50b8f2a071b4197b44b33d 100644 (file)
@@ -6,8 +6,11 @@
 -- COPYING file in the source package for more information.
 --
 
+local plugin_dir = CFG_PLUGINDIR or "./plugins/";
+
 local logger = require "util.logger";
 local log = logger.init("modulemanager");
+local eventmanager = require "core.eventmanager";
 local config = require "core.configmanager";
 local multitable_new = require "util.multitable".new;
 local st = require "util.stanza";
@@ -15,7 +18,6 @@ local pluginloader = require "util.pluginloader";
 
 local hosts = hosts;
 local prosody = prosody;
-local prosody_events = prosody.events;
 
 local loadfile, pcall, xpcall = loadfile, pcall, xpcall;
 local setmetatable, setfenv, getfenv = setmetatable, setfenv, getfenv;
@@ -32,13 +34,12 @@ local unpack, select = unpack, select;
 pcall = function(f, ...)
        local n = select("#", ...);
        local params = {...};
-       return xpcall(function() return f(unpack(params, 1, n)) end, function(e) return tostring(e).."\n"..debug_traceback(); end);
+       return xpcall(function() f(unpack(params, 1, n)) end, function(e) return tostring(e).."\n"..debug_traceback(); end);
 end
 
 local array, set = require "util.array", require "util.set";
 
-local autoload_modules = {"presence", "message", "iq", "offline"};
-local component_inheritable_modules = {"tls", "dialback", "iq"};
+local autoload_modules = {"presence", "message", "iq"};
 
 -- We need this to let modules access the real global namespace
 local _G = _G;
@@ -50,52 +51,66 @@ local api = api; -- Module API container
 
 local modulemap = { ["*"] = {} };
 
+local stanza_handlers = multitable_new();
+local handler_info = {};
+
 local modulehelpers = setmetatable({}, { __index = _G });
 
+local handler_table = multitable_new();
+local hooked = multitable_new();
 local hooks = multitable_new();
+local event_hooks = multitable_new();
 
 local NULL = {};
 
 -- Load modules when a host is activated
 function load_modules_for_host(host)
-       local component = config.get(host, "core", "component_module");
-       
-       local global_modules_enabled = config.get("*", "core", "modules_enabled");
-       local global_modules_disabled = config.get("*", "core", "modules_disabled");
-       local host_modules_enabled = config.get(host, "core", "modules_enabled");
-       local host_modules_disabled = config.get(host, "core", "modules_disabled");
-       
-       if host_modules_enabled == global_modules_enabled then host_modules_enabled = nil; end
-       if host_modules_disabled == global_modules_disabled then host_modules_disabled = nil; end
-       
-       local global_modules = set.new(autoload_modules) + set.new(global_modules_enabled) - set.new(global_modules_disabled);
-       if component then
-               global_modules = set.intersection(set.new(component_inheritable_modules), global_modules);
+       local disabled_set = {};
+       local modules_disabled = config.get(host, "core", "modules_disabled");
+       if modules_disabled then
+               for _, module in ipairs(modules_disabled) do
+                       disabled_set[module] = true;
+               end
        end
-       local modules = (global_modules + set.new(host_modules_enabled)) - set.new(host_modules_disabled);
-       
-       -- COMPAT w/ pre 0.8
-       if modules:contains("console") then
-               log("error", "The mod_console plugin has been renamed to mod_admin_telnet. Please update your config.");
-               modules:remove("console");
-               modules:add("admin_telnet");
+
+       -- Load auto-loaded modules for this host
+       if hosts[host].type == "local" then
+               for _, module in ipairs(autoload_modules) do
+                       if not disabled_set[module] then
+                               load(host, module);
+                       end
+               end
        end
-       
-       if component then
-               load(host, component);
+
+       -- Load modules from global section
+       if config.get(host, "core", "load_global_modules") ~= false then
+               local modules_enabled = config.get("*", "core", "modules_enabled");
+               if modules_enabled then
+                       for _, module in ipairs(modules_enabled) do
+                               if not disabled_set[module] and not is_loaded(host, module) then
+                                       load(host, module);
+                               end
+                       end
+               end
        end
-       for module in modules do
-               load(host, module);
+       
+       -- Load modules from just this host
+       local modules_enabled = config.get(host, "core", "modules_enabled");
+       if modules_enabled and modules_enabled ~= config.get("*", "core", "modules_enabled") then
+               for _, module in pairs(modules_enabled) do
+                       if not is_loaded(host, module) then
+                               load(host, module);
+                       end
+               end
        end
 end
-prosody_events.add_handler("host-activated", load_modules_for_host);
+eventmanager.add_event_hook("host-activated", load_modules_for_host);
+eventmanager.add_event_hook("component-activated", load_modules_for_host);
 --
 
 function load(host, module_name, config)
        if not (host and module_name) then
                return nil, "insufficient-parameters";
-       elseif not hosts[host] then
-               return nil, "unknown-host";
        end
        
        if not modulemap[host] then
@@ -117,12 +132,18 @@ function load(host, module_name, config)
        end
 
        local _log = logger.init(host..":"..module_name);
-       local api_instance = setmetatable({ name = module_name, host = host, path = err, config = config,  _log = _log, log = function (self, ...) return _log(...); end }, { __index = api });
+       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
+               local create_component = _G.require "core.componentmanager".create_component;
+               hosts[host] = create_component(host);
+               hosts[host].connected = false;
+               log("debug", "Created new component: %s", host);
+       end
        hosts[host].modules = modulemap[host];
        modulemap[host][module_name] = pluginenv;
        
@@ -171,6 +192,15 @@ function unload(host, name, ...)
                        log("warn", "Non-fatal error unloading module '%s' on '%s': %s", name, host, err);
                end
        end
+       local params = handler_table:get(host, name); -- , {module.host, origin_type, tag, xmlns}
+       for _, param in pairs(params or NULL) do
+               local handlers = stanza_handlers:get(param[1], param[2], param[3], param[4]);
+               if handlers then
+                       handler_info[handlers[1]] = nil;
+                       stanza_handlers:remove(param[1], param[2], param[3], param[4]);
+               end
+       end
+       event_hooks:remove(host, name);
        -- unhook event handlers hooked by module:hook
        for event, handlers in pairs(hooks:get(host, name) or NULL) do
                for handler in pairs(handlers or NULL) do
@@ -234,6 +264,36 @@ function reload(host, name, ...)
        return ok, err;
 end
 
+function handle_stanza(host, origin, stanza)
+       local name, xmlns, origin_type = stanza.name, stanza.attr.xmlns or "jabber:client", origin.type;
+       if name == "iq" and xmlns == "jabber:client" then
+               if stanza.attr.type == "get" or stanza.attr.type == "set" then
+                       xmlns = stanza.tags[1].attr.xmlns or "jabber:client";
+                       log("debug", "Stanza of type %s from %s has xmlns: %s", name, origin_type, xmlns);
+               else
+                       log("debug", "Discarding %s from %s of type: %s", name, origin_type, stanza.attr.type);
+                       return true;
+               end
+       end
+       local handlers = stanza_handlers:get(host, origin_type, name, xmlns);
+       if not handlers then handlers = stanza_handlers:get("*", origin_type, name, xmlns); end
+       if handlers then
+               log("debug", "Passing stanza to mod_%s", handler_info[handlers[1]].name);
+               (handlers[1])(origin, stanza);
+               return true;
+       else
+               if stanza.attr.xmlns == nil then
+                       log("debug", "Unhandled %s stanza: %s; xmlns=%s", origin.type, stanza.name, xmlns); -- we didn't handle it
+                       if stanza.attr.type ~= "error" and stanza.attr.type ~= "result" then
+                               origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
+                       end
+               elseif not((name == "features" or name == "error") and xmlns == "http://etherx.jabber.org/streams") then -- FIXME remove check once we handle S2S features
+                       log("warn", "Unhandled %s stream element: %s; xmlns=%s: %s", origin.type, stanza.name, xmlns, tostring(stanza)); -- we didn't handle it
+                       origin:close("unsupported-stanza-type");
+               end
+       end
+end
+
 function module_has_method(module, method)
        return type(module.module[method]) == "function";
 end
@@ -272,6 +332,33 @@ function api:set_global()
        self._log = _log;
 end
 
+local function _add_handler(module, origin_type, tag, xmlns, handler)
+       local handlers = stanza_handlers:get(module.host, origin_type, tag, xmlns);
+       local msg = (tag == "iq") and "namespace" or "payload namespace";
+       if not handlers then
+               stanza_handlers:add(module.host, origin_type, tag, xmlns, handler);
+               handler_info[handler] = module;
+               handler_table:add(module.host, module.name, {module.host, origin_type, tag, xmlns});
+               --module:log("debug", "I now handle tag '%s' [%s] with %s '%s'", tag, origin_type, msg, xmlns);
+       else
+               module:log("warn", "I wanted to handle tag '%s' [%s] with %s '%s' but mod_%s already handles that", tag, origin_type, msg, xmlns, handler_info[handlers[1]].module.name);
+       end
+end
+
+function api:add_handler(origin_type, tag, xmlns, handler)
+       if not (origin_type and tag and xmlns and handler) then return false; end
+       if type(origin_type) == "table" then
+               for _, origin_type in ipairs(origin_type) do
+                       _add_handler(self, origin_type, tag, xmlns, handler);
+               end
+       else
+               _add_handler(self, origin_type, tag, xmlns, handler);
+       end
+end
+function api:add_iq_handler(origin_type, xmlns, handler)
+       self:add_handler(origin_type, "iq", xmlns, handler);
+end
+
 function api:add_feature(xmlns)
        self:add_item("feature", xmlns);
 end
@@ -279,6 +366,20 @@ function api:add_identity(category, type, name)
        self:add_item("identity", {category = category, type = type, name = name});
 end
 
+local event_hook = function(host, mod_name, event_name, ...)
+       if type((...)) == "table" and (...).host and (...).host ~= host then return; end
+       for handler in pairs(event_hooks:get(host, mod_name, event_name) or NULL) do
+               handler(...);
+       end
+end;
+function api:add_event_hook(name, handler)
+       if not hooked:get(self.host, self.name, name) then
+               eventmanager.add_event_hook(name, function(...) event_hook(self.host, self.name, name, ...); end);
+               hooked:set(self.host, self.name, name, true);
+       end
+       event_hooks:set(self.host, self.name, name, handler, true);
+end
+
 function api:fire_event(...)
        return (hosts[self.host] or prosody).events.fire_event(...);
 end
index 59ba6579a24af27fe78470de5473bb4ff1347698..506cf20593aa7da447d09ca5836517bbda1bab3b 100644 (file)
@@ -190,19 +190,7 @@ function process_inbound_unsubscribe(username, host, jid)
        end
 end
 
-local function _get_online_roster_subscription(jidA, jidB)
-       local user = bare_sessions[jidA];
-       local item = user and (user.roster[jidB] or { subscription = "none" });
-       return item and item.subscription;
-end
 function is_contact_subscribed(username, host, jid)
-       do
-               local selfjid = username.."@"..host;
-               local subscription = _get_online_roster_subscription(selfjid, jid);
-               if subscription then return (subscription == "both" or subscription == "from"); end
-               local subscription = _get_online_roster_subscription(jid, selfjid);
-               if subscription then return (subscription == "both" or subscription == "to"); end
-       end
        local roster, err = load_roster(username, host);
        local item = roster[jid];
        return item and (item.subscription == "from" or item.subscription == "both"), err;
index f49fae9dc5ed54afb9f0cee325f0cd4ecaff5a21..0c29da143bdde35a5e92ac9b1f9962d3953d8a15 100644 (file)
@@ -16,20 +16,20 @@ 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, setmetatable
-    = tostring, pairs, ipairs, getmetatable, newproxy, error, tonumber, setmetatable;
+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;
-local initialize_filters = require "util.filters".initialize;
 local wrapclient = require "net.server".wrapclient;
 local modulemanager = require "core.modulemanager";
 local st = require "stanza";
 local stanza = st.stanza;
 local nameprep = require "util.encodings".stringprep.nameprep;
-local cert_verify_identity = require "util.x509".verify_identity;
 
-local fire_event = prosody.events.fire_event;
+local fire_event = require "core.eventmanager".fire_event;
 local uuid_gen = require "util.uuid".generate;
 
 local logger_init = require "util.logger".init;
@@ -41,14 +41,11 @@ local sha256_hash = require "util.hashes".sha256;
 local adns, dns = require "net.adns", require "net.dns";
 local config = require "core.configmanager";
 local connect_timeout = config.get("*", "core", "s2s_timeout") or 60;
-local dns_timeout = config.get("*", "core", "dns_timeout") or 15;
+local dns_timeout = config.get("*", "core", "dns_timeout") or 60;
 local max_dns_depth = config.get("*", "core", "dns_max_depth") or 3;
 
-dns.settimeout(dns_timeout);
-
-local prosody = _G.prosody;
 incoming_s2s = {};
-prosody.incoming_s2s = incoming_s2s;
+_G.prosody.incoming_s2s = incoming_s2s;
 local incoming_s2s = incoming_s2s;
 
 module "s2smanager"
@@ -57,7 +54,6 @@ function compare_srv_priorities(a,b)
        return a.priority < b.priority or (a.priority == b.priority and a.weight > b.weight);
 end
 
-local bouncy_stanzas = { message = true, presence = true, iq = true };
 local function bounce_sendq(session, reason)
        local sendq = session.sendq;
        if sendq then
@@ -71,13 +67,13 @@ local function bounce_sendq(session, reason)
                };
                for i, data in ipairs(sendq) do
                        local reply = data[2];
-                       if reply and not(reply.attr.xmlns) and bouncy_stanzas[reply.name] then
+                       local xmlns = reply.attr.xmlns;
+                       if not xmlns then
                                reply.attr.type = "error";
                                reply:tag("error", {type = "cancel"})
                                        :tag("remote-server-not-found", {xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas"}):up();
                                if reason then
-                                       reply:tag("text", {xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas"})
-                                               :text("Server-to-server connection failed: "..reason):up();
+                                       reply:tag("text", {xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas"}):text("Connection failed: "..reason):up();
                                end
                                core_process_stanza(dummy, reply);
                        end
@@ -99,14 +95,13 @@ function send_to_host(from_host, to_host, data)
                        (host.log or log)("debug", "trying to send over unauthed s2sout to "..to_host);
                        
                        -- Queue stanza until we are able to send it
-                       if host.sendq then t_insert(host.sendq, {tostring(data), data.attr.type ~= "error" and data.attr.type ~= "result" and st.reply(data)});
-                       else host.sendq = { {tostring(data), data.attr.type ~= "error" and data.attr.type ~= "result" and st.reply(data)} }; end
+                       if host.sendq then t_insert(host.sendq, {tostring(data), st.reply(data)});
+                       else host.sendq = { {tostring(data), st.reply(data)} }; end
                        host.log("debug", "stanza [%s] queued ", data.name);
                elseif host.type == "local" or host.type == "component" then
                        log("error", "Trying to send a stanza to ourselves??")
                        log("error", "Traceback: %s", get_traceback());
                        log("error", "Stanza: %s", tostring(data));
-                       return false;
                else
                        (host.log or log)("debug", "going to send stanza to "..to_host.." from "..from_host);
                        -- FIXME
@@ -122,18 +117,13 @@ function send_to_host(from_host, to_host, data)
                local host_session = new_outgoing(from_host, to_host);
 
                -- Store in buffer
-               host_session.sendq = { {tostring(data), data.attr.type ~= "error" and data.attr.type ~= "result" and st.reply(data)} };
+               host_session.sendq = { {tostring(data), st.reply(data)} };
                log("debug", "stanza [%s] queued until connection complete", tostring(data.name));
                if (not host_session.connecting) and (not host_session.conn) then
                        log("warn", "Connection to %s failed already, destroying session...", to_host);
-                       if not destroy_session(host_session, "Connection failed") then
-                               -- Already destroyed, we need to bounce our stanza
-                               bounce_sendq(host_session, host_session.destruction_reason);
-                       end
-                       return false;
+                       destroy_session(host_session);
                end
        end
-       return true;
 end
 
 local open_sessions = 0;
@@ -147,19 +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;
-       local filter = initialize_filters(session);
-       session.sends2s = function (t)
-               log("debug", "sending: %s", t.top_tag and t:top_tag() or t:match("^([^>]*>?)"));
-               if t.name then
-                       t = filter("stanzas/out", t);
-               end
-               if t then
-                       t = filter("bytes/out", tostring(t));
-                       if t then
-                               return w(conn, t);
-                       end
-               end
-       end
+       session.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
@@ -167,7 +145,7 @@ 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)("debug", "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);
@@ -181,8 +159,6 @@ function new_outgoing(from_host, to_host, connect)
                
                hosts[from_host].s2sout[to_host] = host_session;
                
-               host_session.close = destroy_session; -- This gets replaced by xmppserver_listener later
-               
                local log;
                do
                        local conn_name = "s2sout"..tostring(host_session):match("[a-f0-9]*$");
@@ -190,15 +166,9 @@ function new_outgoing(from_host, to_host, connect)
                        host_session.log = log;
                end
                
-               initialize_filters(host_session);
-               
                if connect ~= false then
                        -- Kick the connection attempting machine into life
-                       if not attempt_connection(host_session) then
-                               -- Intentionally not returning here, the
-                               -- session is needed, connected or not
-                               destroy_session(host_session);
-                       end
+                       attempt_connection(host_session);
                end
                
                if not host_session.sends2s then
@@ -264,6 +234,13 @@ function attempt_connection(host_session, err)
                        end
                end, "_xmpp-server._tcp."..connect_host..".", "SRV");
                
+               -- Set handler for DNS timeout
+               add_task(dns_timeout, function ()
+                       if handle then
+                               adns.cancel(handle, true);
+                       end
+               end);
+               
                return true; -- Attempt in progress
        elseif host_session.srv_hosts and #host_session.srv_hosts > host_session.srv_choice then -- Not our first attempt, and we also have SRV
                host_session.srv_choice = host_session.srv_choice + 1;
@@ -288,7 +265,7 @@ end
 function try_connect(host_session, connect_host, connect_port)
        host_session.connecting = true;
        local handle;
-       handle = adns.lookup(function (reply, err)
+       handle = adns.lookup(function (reply)
                handle = nil;
                host_session.connecting = nil;
                
@@ -306,23 +283,23 @@ function try_connect(host_session, connect_host, connect_port)
                
                if reply and reply[#reply] and reply[#reply].a then
                        log("debug", "DNS reply for %s gives us %s", connect_host, reply[#reply].a);
-                       local ok, err = make_connect(host_session, reply[#reply].a, connect_port);
-                       if not ok then
-                               if not attempt_connection(host_session, err or "closed") then
-                                       err = err and (": "..err) or "";
-                                       destroy_session(host_session, "Connection failed"..err);
-                               end
-                       end
+                       return make_connect(host_session, reply[#reply].a, connect_port);
                else
                        log("debug", "DNS lookup failed to get a response for %s", connect_host);
                        if not attempt_connection(host_session, "name resolution failed") then -- Retry if we can
                                log("debug", "No other records to try for %s - destroying", host_session.to_host);
-                               err = err and (": "..err) or "";
-                               destroy_session(host_session, "DNS resolution failed"..err); -- End of the line, we can't
+                               destroy_session(host_session, "DNS resolution failed"); -- End of the line, we can't
                        end
                end
        end, connect_host, "A", "IN");
 
+       -- Set handler for DNS timeout
+       add_task(dns_timeout, function ()
+               if handle then
+                       adns.cancel(handle, true);
+               end
+       end);
+       
        return true;
 end
 
@@ -332,7 +309,7 @@ function make_connect(host_session, connect_host, connect_port)
        
        local from_host, to_host = host_session.from_host, host_session.to_host;
        
-       local conn, handler = socket.tcp();
+       local conn, handler = socket.tcp()
        
        if not conn then
                log("warn", "Failed to create outgoing connection, system error: %s", handler);
@@ -350,25 +327,13 @@ function make_connect(host_session, connect_host, connect_port)
        conn = wrapclient(conn, connect_host, connect_port, cl, cl.default_mode or 1 );
        host_session.conn = conn;
        
-       local filter = initialize_filters(host_session);
-       local w, log = conn.write, host_session.log;
-       host_session.sends2s = function (t)
-               log("debug", "sending: %s", (t.top_tag and t:top_tag()) or t:match("^[^>]*>?"));
-               if t.name then
-                       t = filter("stanzas/out", t);
-               end
-               if t then
-                       t = filter("bytes/out", tostring(t));
-                       if t then
-                               return w(conn, tostring(t));
-                       end
-               end
-       end
-       
        -- Register this outgoing connection so that xmppserver_listener knows about it
        -- otherwise it will assume it is a new incoming connection
        cl.register_outgoing(conn, host_session);
        
+       local w, log = conn.write, host_session.log;
+       host_session.sends2s = function (t) log("debug", "sending: %s", (t.top_tag and t:top_tag()) or t:match("^[^>]*>?")); w(conn, tostring(t)); end
+       
        host_session:open_stream(from_host, to_host);
        
        log("debug", "Connection attempt in progress...");
@@ -393,47 +358,16 @@ function session_open_stream(session, from, to)
                from=from, to=to, version='1.0', ["xml:lang"]='en'}):top_tag());
 end
 
-local function check_cert_status(session)
-       local conn = session.conn:socket()
-       local cert
-       if conn.getpeercertificate then
-               cert = conn:getpeercertificate()
-       end
-
-       if cert then
-               local chain_valid, err = conn:getpeerchainvalid()
-               if not chain_valid then
-                       session.cert_chain_status = "invalid";
-                       (session.log or log)("debug", "certificate chain validation result: %s", err);
-               else
-                       session.cert_chain_status = "valid";
-
-                       local host = session.direction == "incoming" and session.from_host or session.to_host
-
-                       -- We'll go ahead and verify the asserted identity if the
-                       -- connecting server specified one.
-                       if host then
-                               if cert_verify_identity(host, "xmpp-server", cert) then
-                                       session.cert_identity_status = "valid"
-                               else
-                                       session.cert_identity_status = "invalid"
-                               end
-                       end
-               end
-       end
-end
-
 function streamopened(session, attr)
        local send = session.sends2s;
        
        -- TODO: #29: SASL/TLS on s2s streams
        session.version = tonumber(attr.version) or 0;
        
-       -- TODO: Rename session.secure to session.encrypted
        if session.secure == false then
                session.secure = true;
        end
-
+       
        if session.direction == "incoming" then
                -- Send a reply stream header
                session.to_host = attr.to and nameprep(attr.to);
@@ -441,26 +375,11 @@ function streamopened(session, attr)
        
                session.streamid = uuid_gen();
                (session.log or log)("debug", "incoming s2s received <stream:stream>");
-               if session.to_host then
-                       if not hosts[session.to_host] then
-                               -- Attempting to connect to a host we don't serve
-                               session:close({
-                                       condition = "host-unknown";
-                                       text = "This host does not serve "..session.to_host
-                               });
-                               return;
-                       elseif hosts[session.to_host].disallow_s2s then
-                               -- Attempting to connect to a host that disallows s2s
-                               session:close({
-                                       condition = "policy-violation";
-                                       text = "Server-to-server communication is not allowed to this host";
-                               });
-                               return;
-                       end
+               if session.to_host and not hosts[session.to_host] then
+                       -- Attempting to connect to a host we don't serve
+                       session:close({ condition = "host-unknown"; text = "This host does not serve "..session.to_host });
+                       return;
                end
-
-               if session.secure and not session.cert_chain_status then check_cert_status(session); end
-
                send("<?xml version='1.0'?>");
                send(stanza("stream:stream", { xmlns='jabber:server', ["xmlns:db"]='jabber:server:dialback',
                                ["xmlns:stream"]='http://etherx.jabber.org/streams', id=session.streamid, from=session.to_host, to=session.from_host, version=(session.version > 0 and "1.0" or nil) }):top_tag());
@@ -480,9 +399,7 @@ function streamopened(session, attr)
                -- If we are just using the connection for verifying dialback keys, we won't try and auth it
                if not attr.id then error("stream response did not give us a streamid!!!"); end
                session.streamid = attr.id;
-
-               if session.secure and not session.cert_chain_status then check_cert_status(session); end
-
+       
                -- Send unauthed buffer
                -- (stanzas which are fine to send before dialback)
                -- Note that this is *not* the stanza queue (which
@@ -546,11 +463,9 @@ function make_authenticated(session, host)
        elseif session.type == "s2sin_unauthed" then
                session.type = "s2sin";
                if host then
-                       if not session.hosts[host] then session.hosts[host] = {}; end
                        session.hosts[host].authed = true;
                end
        elseif session.type == "s2sin" and host then
-               if not session.hosts[host] then session.hosts[host] = {}; end
                session.hosts[host].authed = true;
        else
                return false;
@@ -571,16 +486,8 @@ function mark_connected(session)
        session.log("info", session.direction.." s2s connection "..from.."->"..to.." complete");
        
        local send_to_host = send_to_host;
-       function session.send(data) return send_to_host(to, from, data); end
+       function session.send(data) send_to_host(to, from, data); end
        
-       local event_data = { session = session };
-       if session.type == "s2sout" then
-               prosody.events.fire_event("s2sout-established", event_data);
-               hosts[session.from_host].events.fire_event("s2sout-established", event_data);
-       else
-               prosody.events.fire_event("s2sin-established", event_data);
-               hosts[session.to_host].events.fire_event("s2sin-established", event_data);
-       end
        
        if session.direction == "outgoing" then
                if sendq then
@@ -605,10 +512,9 @@ local resting_session = { -- Resting, not dead
                close = function (session)
                        session.log("debug", "Attempt to close already-closed session");
                end;
-               filter = function (type, data) return data; end;
        }; resting_session.__index = resting_session;
 
-function retire_session(session, reason)
+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
@@ -616,8 +522,6 @@ function retire_session(session, reason)
                end
        end
 
-       session.destruction_reason = reason;
-
        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);
@@ -625,7 +529,7 @@ end
 
 function destroy_session(session, reason)
        if session.destroyed then return; end
-       (session.log or log)("debug", "Destroying "..tostring(session.direction).." session "..tostring(session.from_host).."->"..tostring(session.to_host)..(reason and (": "..reason) or ""));
+       (session.log or log)("info", "Destroying "..tostring(session.direction).." session "..tostring(session.from_host).."->"..tostring(session.to_host));
        
        if session.direction == "outgoing" then
                hosts[session.from_host].s2sout[session.to_host] = nil;
@@ -634,21 +538,7 @@ function destroy_session(session, reason)
                incoming_s2s[session] = nil;
        end
        
-       local event_data = { session = session, reason = reason };
-       if session.type == "s2sout" then
-               prosody.events.fire_event("s2sout-destroyed", event_data);
-               if hosts[session.from_host] then
-                       hosts[session.from_host].events.fire_event("s2sout-destroyed", event_data);
-               end
-       elseif session.type == "s2sin" then
-               prosody.events.fire_event("s2sin-destroyed", event_data);
-               if hosts[session.to_host] then
-                       hosts[session.to_host].events.fire_event("s2sin-destroyed", event_data);
-               end
-       end
-       
-       retire_session(session, reason); -- Clean session until it is GC'd
-       return true;
+       retire_session(session); -- Clean session until it is GC'd
 end
 
 return _M;
index e9f81d8298e4abf6ca42207792c78639e0f5f91c..e1f1a80214e9540abcea1a17c023a8d49e0889f1 100644 (file)
@@ -27,8 +27,7 @@ local nameprep = require "util.encodings".stringprep.nameprep;
 local resourceprep = require "util.encodings".stringprep.resourceprep;
 local nodeprep = require "util.encodings".stringprep.nodeprep;
 
-local initialize_filters = require "util.filters".initialize;
-local fire_event = prosody.events.fire_event;
+local fire_event = require "core.eventmanager".fire_event;
 local add_task = require "util.timer".add_task;
 local gettime = require "socket".gettime;
 
@@ -51,20 +50,8 @@ function new_session(conn)
        end
        open_sessions = open_sessions + 1;
        log("debug", "open sessions now: ".. open_sessions);
-       
-       local filter = initialize_filters(session);
        local w = conn.write;
-       session.send = function (t)
-               if t.name then
-                       t = filter("stanzas/out", t);
-               end
-               if t then
-                       t = filter("bytes/out", tostring(t));
-                       if t then
-                               return w(conn, t);
-                       end
-               end
-       end
+       session.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);
@@ -86,7 +73,6 @@ local resting_session = { -- Resting, not dead
                close = function (session)
                        session.log("debug", "Attempt to close already-closed session");
                end;
-               filter = function (type, data) return data; end;
        }; resting_session.__index = resting_session;
 
 function retire_session(session)
@@ -103,28 +89,21 @@ function retire_session(session)
 end
 
 function destroy_session(session, err)
-       (session.log or log)("info", "Destroying session for %s (%s@%s)%s", session.full_jid or "(unknown)", session.username or "(unknown)", session.host or "(unknown)", err and (": "..err) or "");
+       (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
-               local host_session = hosts[session.host];
-               
-               -- Allow plugins to prevent session destruction
-               if host_session.events.fire_event("pre-resource-unbind", {session=session, error=err}) then
-                       return;
-               end
-               
-               host_session.sessions[session.username].sessions[session.resource] = nil;
+               hosts[session.host].sessions[session.username].sessions[session.resource] = nil;
                full_sessions[session.full_jid] = nil;
                
-               if not next(host_session.sessions[session.username].sessions) then
+               if not next(hosts[session.host].sessions[session.username].sessions) then
                        log("debug", "All resources of %s are now offline", session.username);
-                       host_session.sessions[session.username] = nil;
+                       hosts[session.host].sessions[session.username] = nil;
                        bare_sessions[session.username..'@'..session.host] = nil;
                end
 
-               host_session.events.fire_event("resource-unbind", {session=session, error=err});
+               hosts[session.host].events.fire_event("resource-unbind", {session=session, error=err});
        end
        
        retire_session(session);
index 406ad2f04d1c61fcb340a0e7dacff77ee6ae4968..d6dd53062d655ae59768b097a0c9b71d7c6d1a5e 100644 (file)
@@ -12,35 +12,14 @@ local hosts = _G.prosody.hosts;
 local tostring = tostring;
 local st = require "util.stanza";
 local send_s2s = require "core.s2smanager".send_to_host;
+local modules_handle_stanza = require "core.modulemanager".handle_stanza;
+local component_handle_stanza = require "core.componentmanager".handle_stanza;
 local jid_split = require "util.jid".split;
 local jid_prepped_split = require "util.jid".prepped_split;
 
 local full_sessions = _G.prosody.full_sessions;
 local bare_sessions = _G.prosody.bare_sessions;
 
-local function handle_unhandled_stanza(host, origin, stanza)
-       local name, xmlns, origin_type = stanza.name, stanza.attr.xmlns or "jabber:client", origin.type;
-       if name == "iq" and xmlns == "jabber:client" then
-               if stanza.attr.type == "get" or stanza.attr.type == "set" then
-                       xmlns = stanza.tags[1].attr.xmlns or "jabber:client";
-                       log("debug", "Stanza of type %s from %s has xmlns: %s", name, origin_type, xmlns);
-               else
-                       log("debug", "Discarding %s from %s of type: %s", name, origin_type, stanza.attr.type);
-                       return true;
-               end
-       end
-       if stanza.attr.xmlns == nil then
-               log("debug", "Unhandled %s stanza: %s; xmlns=%s", origin.type, stanza.name, xmlns); -- we didn't handle it
-               if stanza.attr.type ~= "error" and stanza.attr.type ~= "result" then
-                       origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
-               end
-       elseif not((name == "features" or name == "error") and xmlns == "http://etherx.jabber.org/streams") then -- FIXME remove check once we handle S2S features
-               log("warn", "Unhandled %s stream element: %s; xmlns=%s: %s", origin.type, stanza.name, xmlns, tostring(stanza)); -- we didn't handle it
-               origin:close("unsupported-stanza-type");
-       end
-end
-
-local iq_types = { set=true, get=true, result=true, error=true };
 function core_process_stanza(origin, stanza)
        (origin.log or log)("debug", "Received[%s]: %s", origin.type, stanza:top_tag())
 
@@ -48,8 +27,8 @@ function core_process_stanza(origin, stanza)
        if stanza.attr.type == "error" and #stanza.tags == 0 then return; end -- TODO invalid stanza, log
        if stanza.name == "iq" then
                if not stanza.attr.id then stanza.attr.id = ""; end -- COMPAT Jabiru doesn't send the id attribute on roster requests
-               if not iq_types[stanza.attr.type] or ((stanza.attr.type == "set" or stanza.attr.type == "get") and (#stanza.tags ~= 1)) then
-                       origin.send(st.error_reply(stanza, "modify", "bad-request", "Invalid IQ type or incorrect number of children"));
+               if (stanza.attr.type == "set" or stanza.attr.type == "get") and (#stanza.tags ~= 1) then
+                       origin.send(st.error_reply(stanza, "modify", "bad-request"));
                        return;
                end
        end
@@ -135,7 +114,7 @@ function core_process_stanza(origin, stanza)
                        if h.events.fire_event(event, {origin = origin, stanza = stanza}) then return; end
                end
                if host and not hosts[host] then host = nil; end -- COMPAT: workaround for a Pidgin bug which sets 'to' to the SRV result
-               handle_unhandled_stanza(host or origin.host or origin.to_host, origin, stanza);
+               modules_handle_stanza(host or origin.host or origin.to_host, origin, stanza);
        end
 end
 
@@ -172,7 +151,12 @@ function core_post_stanza(origin, stanza, preevents)
        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
-               handle_unhandled_stanza(h.host, origin, stanza);
+
+               if h.type == "component" then
+                       component_handle_stanza(origin, stanza);
+                       return;
+               end
+               modules_handle_stanza(h.host, origin, stanza);
        else
                core_route_stanza(origin, stanza);
        end
index 0152afd76cd1fba5077d197ee02f552b655ab25a..698d2f10413676bc28f3779e94b0f5242fe73e78 100644 (file)
 -- COPYING file in the source package for more information.
 --
 
-local modulemanager = require "core.modulemanager";
+local datamanager = require "util.datamanager";
 local log = require "util.logger".init("usermanager");
 local type = type;
+local error = error;
 local ipairs = ipairs;
+local hashes = require "util.hashes";
 local jid_bare = require "util.jid".bare;
 local config = require "core.configmanager";
 local hosts = hosts;
-local sasl_new = require "util.sasl".new;
 
-local prosody = _G.prosody;
-
-local setmetatable = setmetatable;
-
-local default_provider = "internal_plain";
+local require_provisioning = config.get("*", "core", "cyrus_require_provisioning") or false;
 
 module "usermanager"
 
-function new_null_provider()
-       local function dummy() return nil, "method not implemented"; end;
-       local function dummy_get_sasl_handler() return sasl_new(nil, {}); end
-       return setmetatable({name = "null", get_sasl_handler = dummy_get_sasl_handler}, {
-               __index = function(self, method) return dummy; end
-       });
-end
+local function is_cyrus(host) return config.get(host, "core", "sasl_backend") == "cyrus"; end
 
-local provider_mt = { __index = new_null_provider() };
+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 {};
 
-function initialize_host(host)
-       local host_session = hosts[host];
-       if host_session.type ~= "local" then return; end
-       
-       host_session.events.add_handler("item-added/auth-provider", function (event)
-               local provider = event.item;
-               local auth_provider = config.get(host, "core", "authentication") or default_provider;
-               if config.get(host, "core", "anonymous_login") then auth_provider = "anonymous"; end -- COMPAT 0.7
-               if provider.name == auth_provider then
-                       host_session.users = setmetatable(provider, provider_mt);
-               end
-               if host_session.users ~= nil and host_session.users.name ~= nil then
-                       log("debug", "host '%s' now set to use user provider '%s'", host, host_session.users.name);
-               end
-       end);
-       host_session.events.add_handler("item-removed/auth-provider", function (event)
-               local provider = event.item;
-               if host_session.users == provider then
-                       host_session.users = new_null_provider();
+       if method == nil then method = "PLAIN"; end
+       if method == "PLAIN" and credentials.password then -- PLAIN, do directly
+               if password == credentials.password then
+                       return true;
+               else
+                       return nil, "Auth failed. Invalid username or password.";
                end
-       end);
-       host_session.users = new_null_provider(); -- Start with the default usermanager provider
-       local auth_provider = config.get(host, "core", "authentication") or default_provider;
-       if config.get(host, "core", "anonymous_login") then auth_provider = "anonymous"; end -- COMPAT 0.7
-       if auth_provider ~= "null" then
-               modulemanager.load(host, "auth_"..auth_provider);
+  end
+       -- must do md5
+       -- make credentials md5
+       local pwd = credentials.password;
+       if not pwd then pwd = credentials.md5; else pwd = hashes.md5(pwd, true); end
+       -- make password md5
+       if method == "PLAIN" then
+               password = hashes.md5(password or "", true);
+       elseif method ~= "DIGEST-MD5" then
+               return nil, "Unsupported auth method";
+       end
+       -- compare
+       if password == pwd then
+               return true;
+       else
+               return nil, "Auth failed. Invalid username or password.";
        end
-end;
-prosody.events.add_handler("host-activated", initialize_host, 100);
-
-function test_password(username, host, password)
-       return hosts[host].users.test_password(username, password);
 end
 
 function get_password(username, host)
-       return hosts[host].users.get_password(username);
+       if is_cyrus(host) then return nil, "Passwords unavailable for Cyrus SASL."; end
+       return (datamanager.load(username, host, "accounts") or {}).password
 end
-
-function set_password(username, password, host)
-       return hosts[host].users.set_password(username, password);
+function set_password(username, host, password)
+       if is_cyrus(host) then return nil, "Passwords unavailable for Cyrus SASL."; end
+       local account = datamanager.load(username, host, "accounts");
+       if account then
+               account.password = password;
+               return datamanager.store(username, host, "accounts", account);
+       end
+       return nil, "Account not available.";
 end
 
 function user_exists(username, host)
-       return hosts[host].users.user_exists(username);
+       if not(require_provisioning) and is_cyrus(host) then return true; end
+       local account, err = datamanager.load(username, host, "accounts");
+       return (account or err) ~= nil; -- FIXME also check for empty credentials
 end
 
 function create_user(username, password, host)
-       return hosts[host].users.create_user(username, password);
+       if not(require_provisioning) and is_cyrus(host) then return nil, "Account creation/modification not available with Cyrus SASL."; end
+       return datamanager.store(username, host, "accounts", {password = password});
 end
 
-function delete_user(username, host)
-       return hosts[host].users.delete_user(username);
-end
-
-function get_sasl_handler(host)
-       return hosts[host].users.get_sasl_handler();
-end
-
-function get_provider(host)
-       return hosts[host].users;
+function get_supported_methods(host)
+       return {["PLAIN"] = true, ["DIGEST-MD5"] = true}; -- TODO this should be taken from the config
 end
 
 function is_admin(jid, host)
-       if host and not hosts[host] then return false; end
-
-       local is_admin;
-       jid = jid_bare(jid);
        host = host or "*";
-       
-       local host_admins = config.get(host, "core", "admins");
-       local global_admins = config.get("*", "core", "admins");
-       
-       if host_admins and host_admins ~= global_admins then
-               if type(host_admins) == "table" then
-                       for _,admin in ipairs(host_admins) do
-                               if admin == jid then
-                                       is_admin = true;
-                                       break;
-                               end
-                       end
-               elseif host_admins then
-                       log("error", "Option 'admins' for host '%s' is not a list", host);
-               end
+       local admins = config.get(host, "core", "admins");
+       if host ~= "*" and admins == config.get("*", "core", "admins") then
+               return nil;
        end
-       
-       if not is_admin and global_admins then
-               if type(global_admins) == "table" then
-                       for _,admin in ipairs(global_admins) do
-                               if admin == jid then
-                                       is_admin = true;
-                                       break;
-                               end
-                       end
-               elseif global_admins then
-                       log("error", "Global option 'admins' is not a list");
+       if type(admins) == "table" then
+               jid = jid_bare(jid);
+               for _,admin in ipairs(admins) do
+                       if admin == jid then return true; end
                end
-       end
-       
-       -- Still not an admin, check with auth provider
-       if not is_admin and host ~= "*" and hosts[host].users and hosts[host].users.is_admin then
-               is_admin = hosts[host].users.is_admin(jid);
-       end
-       return is_admin or false;
+       elseif admins then log("warn", "Option 'admins' for host '%s' is not a table", host); end
+       return nil;
 end
 
 return _M;
index eb7e3ba1796648249442ae7af04e2c6695447a0f..d86ffe7de60947700c473a0cd80da37d1d1d240c 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;
 
@@ -34,103 +32,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));
@@ -138,26 +125,44 @@ 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
-               end
-
-               local function restricted_handler(parser)
-                       cb_error(session, "parse-error", "restricted-xml", "Restricted XML, see RFC 6120 section 11.1.");
-                       if not parser:stop() then
-                               error("Failed to abort parsing");
+               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
-               
-               if lxp_supports_doctype then
-                       xml_handlers.StartDoctypeDecl = restricted_handler;
+       end
+
+       local function restricted_handler(parser)
+               cb_error(session, "parse-error", "restricted-xml", "Restricted XML, see RFC 6120 section 11.1.");
+               if not parser:stop() then
+                       error("Failed to abort parsing");
                end
-               xml_handlers.Comment = restricted_handler;
-               xml_handlers.ProcessingInstruction = restricted_handler;
-       
+       end
+
+       if lxp_supports_doctype then
+               xml_handlers.StartDoctypeDecl = restricted_handler;
+       end
+       xml_handlers.Comment = restricted_handler;
+       xml_handlers.ProcessingInstruction = restricted_handler;
+
        return xml_handlers;
 end
 
index cd69a627e044f69182ee59d5f0586ba1d6e6a17a..88d4b4b39c4c1e2b71e0b9c36424f2b998ad1700 100644 (file)
@@ -26,26 +26,22 @@ function lookup(handler, qname, qtype, qclass)
                                        return;
                                end
                                log("debug", "Records for %s not in cache, sending query (%s)...", qname, tostring(coroutine.running()));
-                               local ok, err = dns.query(qname, qtype, qclass);
-                               if ok then
-                                       coroutine.yield({ qclass or "IN", qtype or "A", qname, coroutine.running()}); -- Wait for reply
-                                       log("debug", "Reply for %s (%s)", qname, tostring(coroutine.running()));
-                               end
-                               if ok then
-                                       ok, err = pcall(handler, dns.peek(qname, qtype, qclass));
-                               else
-                                       log("error", "Error sending DNS query: %s", err);
-                                       ok, err = pcall(handler, nil, err);
-                               end
+                               dns.query(qname, qtype, qclass);
+                               coroutine.yield({ qclass or "IN", qtype or "A", qname, coroutine.running()}); -- Wait for reply
+                               log("debug", "Reply for %s (%s)", qname, tostring(coroutine.running()));
+                               local ok, err = pcall(handler, dns.peek(qname, qtype, qclass));
                                if not ok then
                                        log("error", "Error in DNS response handler: %s", tostring(err));
                                end
                        end)(dns.peek(qname, qtype, qclass));
 end
 
-function cancel(handle, call_handler, reason)
+function cancel(handle, call_handler)
        log("warn", "Cancelling DNS lookup for %s", tostring(handle[3]));
-       dns.cancel(handle[1], handle[2], handle[3], handle[4], call_handler);
+       dns.cancel(handle);
+       if call_handler then
+               coroutine.resume(handle[4]);
+       end
 end
 
 function new_async_socket(sock, resolver)
@@ -78,11 +74,7 @@ function new_async_socket(sock, resolver)
        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)
-               local getpeername = sock.getpeername;
-               log("debug", "Sending DNS query to %s", (getpeername and getpeername(sock)) or "<unconnected>");
-               return sock:send(data);
-       end
+       handler.send = function (_, data) return sock:send(data); end
        return handler;
 end
 
index 7da25c62df3e4d52c7824d71432c516ff16650e1..93dce8b3aecb5c179c2bb09e7ee6a41c325af6ef 100644 (file)
@@ -13,10 +13,8 @@ local server = require "net.server";
 local log = require "util.logger".init("connlisteners");
 local tostring = tostring;
 
-local dofile, xpcall, error =
-      dofile, xpcall, error
-
-local debug_traceback = debug.traceback;
+local dofile, pcall, error = 
+       dofile, pcall, error
 
 module "connlisteners"
 
@@ -39,7 +37,7 @@ end
 function get(name)
        local h = listeners[name];
        if not h then
-               local ok, ret = xpcall(function() dofile(listeners_dir..name:gsub("[^%w%-]", "_").."_listener.lua") end, debug_traceback);
+               local ok, ret = pcall(dofile, listeners_dir..name:gsub("[^%w%-]", "_").."_listener.lua");
                if not ok then
                        log("error", "Error while loading listener '%s': %s", tostring(name), tostring(ret));
                        return nil, ret;
index 3f1cb4f687ffc917b53e2ca5dfadc7aba65082ca..c0de97fd426db6f3573332cda332072028bedc7c 100644 (file)
@@ -2,6 +2,8 @@
 -- This file is included with Prosody IM. It has modifications,
 -- which are hereby placed in the public domain.
 
+-- public domain 20080404 lua@ztact.com
+
 
 -- todo: quick (default) header generation
 -- todo: nxdomain, error handling
 
 
 local socket = require "socket";
-local timer = require "util.timer";
-
+local ztact = require "util.ztact";
 local _, windows = pcall(require, "util.windows");
 local is_windows = (_ and windows) or os.getenv("WINDIR");
 
 local coroutine, io, math, string, table =
       coroutine, io, math, string, table;
 
-local ipairs, next, pairs, print, setmetatable, tostring, assert, error, unpack, select, type=
-      ipairs, next, pairs, print, setmetatable, tostring, assert, error, unpack, select, type;
+local ipairs, next, pairs, print, setmetatable, tostring, assert, error, unpack =
+      ipairs, next, pairs, print, setmetatable, tostring, assert, error, unpack;
 
-local ztact = { -- public domain 20080404 lua@ztact.com
-       get = function(parent, ...)
-               local len = select('#', ...);
-               for i=1,len do
-                       parent = parent[select(i, ...)];
-                       if parent == nil then break; end
-               end
-               return parent;
-       end;
-       set = function(parent, ...)
-               local len = select('#', ...);
-               local key, value = select(len-1, ...);
-               local cutpoint, cutkey;
-
-               for i=1,len-2 do
-                       local key = select (i, ...)
-                       local child = parent[key]
-
-                       if value == nil then
-                               if child == nil then
-                                       return;
-                               elseif next(child, next(child)) then
-                                       cutpoint = nil; cutkey = nil;
-                               elseif cutpoint == nil then
-                                       cutpoint = parent; cutkey = key;
-                               end
-                       elseif child == nil then
-                               child = {};
-                               parent[key] = child;
-                       end
-                       parent = child
-               end
-
-               if value == nil and cutpoint then
-                       cutpoint[cutkey] = nil;
-               else
-                       parent[key] = value;
-                       return value;
-               end
-       end;
-};
 local get, set = ztact.get, ztact.set;
 
-local default_timeout = 15;
 
 -------------------------------------------------- module dns
 module('dns')
@@ -156,31 +115,32 @@ end
 local resolver = {};
 resolver.__index = resolver;
 
-resolver.timeout = default_timeout;
 
-local function default_rr_tostring(rr)
-       local rr_val = rr.type and rr[rr.type:lower()];
-       if type(rr_val) ~= "string" then
-               return "<UNKNOWN RDATA TYPE>";
-       end
-       return rr_val;
-end
-
-local special_tostrings = {
-       LOC = resolver.LOC_tostring;
-       MX  = function (rr)
-               return string.format('%2i %s', rr.pref, rr.mx);
-       end;
-       SRV = function (rr)
-               local s = rr.srv;
-               return string.format('%5d %5d %5d %s', s.priority, s.weight, s.port, s.target);
-       end;
-};
+local SRV_tostring;
+
 
 local rr_metatable = {};   -- - - - - - - - - - - - - - - - - - -  rr_metatable
 function rr_metatable.__tostring(rr)
-       local rr_string = (special_tostrings[rr.type] or default_rr_tostring)(rr);
-       return string.format('%2s %-5s %6i %-28s %s', rr.class, rr.type, rr.ttl, rr.name, rr_string);
+       local s0 = string.format('%2s %-5s %6i %-28s', rr.class, rr.type, rr.ttl, rr.name);
+       local s1 = '';
+       if rr.type == 'A' then
+               s1 = ' '..rr.a;
+       elseif rr.type == 'MX' then
+               s1 = string.format(' %2i %s', rr.pref, rr.mx);
+       elseif rr.type == 'CNAME' then
+               s1 = ' '..rr.cname;
+       elseif rr.type == 'LOC' then
+               s1 = ' '..resolver.LOC_tostring(rr);
+       elseif rr.type == 'NS' then
+               s1 = ' '..rr.ns;
+       elseif rr.type == 'SRV' then
+               s1 = ' '..SRV_tostring(rr);
+       elseif rr.type == 'TXT' then
+               s1 = ' '..rr.txt;
+       else
+               s1 = ' <UNKNOWN RDATA TYPE>';
+       end
+       return s0..s1;
 end
 
 
@@ -389,14 +349,6 @@ function resolver:A(rr)    -- - - - - - - - - - - - - - - - - - - - - - - -  A
        rr.a = string.format('%i.%i.%i.%i', b1, b2, b3, b4);
 end
 
-function resolver:AAAA(rr)
-       local addr = {};
-       for i = 1, rr.rdlength, 2 do
-               local b1, b2 = self:byte(2);
-               table.insert(addr, ("%02x%02x"):format(b1, b2));
-       end
-       rr.aaaa = table.concat(addr, ":");
-end
 
 function resolver:CNAME(rr)    -- - - - - - - - - - - - - - - - - - - -  CNAME
        rr.cname = self:name();
@@ -482,12 +434,15 @@ function resolver:SRV(rr)    -- - - - - - - - - - - - - - - - - - - - - -  SRV
          rr.srv.target   = self:name();
 end
 
-function resolver:PTR(rr)
-       rr.ptr = self:name();
+
+function SRV_tostring(rr)    -- - - - - - - - - - - - - - - - - - SRV_tostring
+       local s = rr.srv;
+       return string.format( '%5d %5d %5d %s', s.priority, s.weight, s.port, s.target );
 end
 
+
 function resolver:TXT(rr)    -- - - - - - - - - - - - - - - - - - - - - -  TXT
-       rr.txt = self:sub (self:byte());
+       rr.txt = self:sub (rr.rdlength);
 end
 
 
@@ -569,7 +524,7 @@ end
 
 function resolver:adddefaultnameservers()    -- - - - -  adddefaultnameservers
        if is_windows then
-               if windows and windows.get_nameservers then
+               if windows then
                        for _, server in ipairs(windows.get_nameservers()) do
                                self:addnameserver(server);
                        end
@@ -607,11 +562,7 @@ function resolver:getsocket(servernum)    -- - - - - - - - - - - - - getsocket
        local sock = self.socket[servernum];
        if sock then return sock; end
 
-       local err;
-       sock, err = socket.udp();
-       if not sock then
-               return nil, err;
-       end
+       sock = socket.udp();
        if self.socket_wrapper then sock = self.socket_wrapper(sock, self); end
        sock:settimeout(0);
        -- todo: attempt to use a random port, fallback to 0
@@ -716,44 +667,18 @@ function resolver:query(qname, qtype, qclass)    -- - - - - - - - - - -- query
                retry  = socket.gettime() + self.delays[1]
        };
 
-       -- remember the query
+  -- remember the query
        self.active[id] = self.active[id] or {};
        self.active[id][question] = o;
 
-       -- remember which coroutine wants the answer
+  -- remember which coroutine wants the answer
        local co = coroutine.running();
        if co then
                set(self.wanted, qclass, qtype, qname, co, true);
                --set(self.yielded, co, qclass, qtype, qname, true);
        end
 
-       local conn, err = self:getsocket(o.server)
-       if not conn then
-               return nil, err;
-       end
-       conn:send (o.packet)
-       
-       if timer and self.timeout then
-               local num_servers = #self.server;
-               local i = 1;
-               timer.add_task(self.timeout, function ()
-                       if get(self.wanted, qclass, qtype, qname, co) then
-                               if i < num_servers then
-                                       i = i + 1;
-                                       self:servfail(conn);
-                                       o.server = self.best_server;
-                                       conn, err = self:getsocket(o.server);
-                                       if conn then
-                                               conn:send(o.packet);
-                                               return self.timeout;
-                                       end
-                               end
-                               -- Tried everything, failed
-                               self:cancel(qclass, qtype, qname, co, true);
-                       end
-               end)
-       end
-       return true;
+       self:getsocket (o.server):send (o.packet)
 end
 
 function resolver:servfail(sock)
@@ -785,7 +710,7 @@ function resolver:servfail(sock)
                        end
                end
        end
-
+   
        if num == self.best_server then
                self.best_server = self.best_server + 1;
                if self.best_server > #self.server then
@@ -795,10 +720,6 @@ function resolver:servfail(sock)
        end
 end
 
-function resolver:settimeout(seconds)
-       self.timeout = seconds;
-end
-
 function resolver:receive(rset)    -- - - - - - - - - - - - - - - - -  receive
        --print('receive');  print(self.socket);
        self.time = socket.gettime();
@@ -848,11 +769,11 @@ function resolver:receive(rset)    -- - - - - - - - - - - - - - - - -  receive
 end
 
 
-function resolver:feed(sock, packet, force)
+function resolver:feed(sock, packet)
        --print('receive'); print(self.socket);
        self.time = socket.gettime();
 
-       local response = self:decode(packet, force);
+       local response = self:decode(packet);
        if response and self.active[response.header.id]
                and self.active[response.header.id][response.question.raw] then
                --print('received response');
@@ -885,13 +806,10 @@ function resolver:feed(sock, packet, force)
        return response;
 end
 
-function resolver:cancel(qclass, qtype, qname, co, call_handler)
-       local cos = get(self.wanted, qclass, qtype, qname);
+function resolver:cancel(data)
+       local cos = get(self.wanted, unpack(data, 1, 3));
        if cos then
-               if call_handler then
-                       coroutine.resume(co);
-               end
-               cos[co] = nil;
+               cos[data[4]] = nil;
        end
 end
 
@@ -934,12 +852,12 @@ end
 function resolver:lookup(qname, qtype, qclass)    -- - - - - - - - - -  lookup
        self:query (qname, qtype, qclass)
        while self:pulse() do
-               local recvt = {}
-               for i, s in ipairs(self.socket) do
-                       recvt[i] = s
-               end
-               socket.select(recvt, nil, 4)
-       end
+           local recvt = {}
+           for i, s in ipairs(self.socket) do
+              recvt[i] = s
+           end
+           socket.select(recvt, nil, 4)
+        end
        --print(self.cache);
        return self:peek(qname, qtype, qclass);
 end
@@ -948,9 +866,6 @@ function resolver:lookupex(handler, qname, qtype, qclass)    -- - - - - - - - -
        return self:peek(qname, qtype, qclass) or self:query(qname, qtype, qclass);
 end
 
-function resolver:tohostname(ip)
-       return dns.lookup(ip:gsub("(%d+)%.(%d+)%.(%d+)%.(%d+)", "%4.%3.%2.%1.in-addr.arpa."), "PTR");
-end
 
 --print ---------------------------------------------------------------- print
 
@@ -1026,10 +941,6 @@ function dns.lookup(...)    -- - - - - - - - - - - - - - - - - - - - -  lookup
        return _resolver:lookup(...);
 end
 
-function dns.tohostname(...)
-       return _resolver:tohostname(...);
-end
-
 function dns.purge(...)    -- - - - - - - - - - - - - - - - - - - - - -  purge
        return _resolver:purge(...);
 end
@@ -1050,10 +961,6 @@ function dns.cancel(...)  -- - - - - - - - - - - - - - - - - - - - - -  cancel
        return _resolver:cancel(...);
 end
 
-function dns.settimeout(...)
-       return _resolver:settimeout(...);
-end
-
 function dns.socket_wrapper_set(...)    -- - - - - - - - -  socket_wrapper_set
        return _resolver:socket_wrapper_set(...);
 end
index 6c8e0a687b8beacb28331c9e659066a48d600cd7..0634d77391b72015f90e40571a60844c0776d776 100644 (file)
@@ -10,7 +10,6 @@
 local socket = require "socket"
 local mime = require "mime"
 local url = require "socket.url"
-local httpstream_new = require "util.httpstream".new;
 
 local server = require "net.server"
 
@@ -18,9 +17,8 @@ local connlisteners_get = require "net.connlisteners".get;
 local listener = connlisteners_get("httpclient") or error("No httpclient listener!");
 
 local t_insert, t_concat = table.insert, table.concat;
-local pairs, ipairs = pairs, ipairs;
-local tonumber, tostring, xpcall, select, debug_traceback, char, format =
-      tonumber, tostring, xpcall, select, debug.traceback, string.char, string.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");
 
@@ -29,46 +27,107 @@ module "http"
 function urlencode(s) return s and (s:gsub("%W", function (c) return format("%%%02x", c:byte()); end)); end
 function urldecode(s) return s and (s:gsub("%%(%x%x)", function (c) return char(tonumber(c,16)); end)); end
 
-local function _formencodepart(s)
-       return s and (s:gsub("%W", function (c)
-               if c ~= " " then
-                       return format("%%%02x", c:byte());
-               else
-                       return "+";
-               end
-       end));
-end
-function formencode(form)
-       local result = {};
-       for _, field in ipairs(form) do
-               t_insert(result, _formencodepart(field.name).."=".._formencodepart(field.value));
-       end
-       return t_concat(result, "&");
+local function expectbody(reqt, code)
+    if reqt.method == "HEAD" then return nil end
+    if code == 204 or code == 304 or code == 301 then return nil end
+    if code >= 100 and code < 200 then return nil end
+    return 1
 end
 
 local function request_reader(request, data, startpos)
-       if not request.parser then
-               local function success_cb(r)
-                       if request.callback then
-                               for k,v in pairs(r) do request[k] = v; end
-                               request.callback(r.body, r.code, request);
-                               request.callback = nil;
+       if not data then
+               if request.body then
+                       log("debug", "Connection closed, but we have data, calling callback...");
+                       request.callback(t_concat(request.body), request.code, request);
+               elseif request.state ~= "completed" then
+                       -- Error.. connection was closed prematurely
+                       request.callback("connection-closed", 0, request);
+                       return;
+               end
+               destroy_request(request);
+               request.body = nil;
+               request.state = "completed";
+               return;
+       end
+       if request.state == "body" and request.state ~= "completed" then
+               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)
+               end
+               t_insert(request.body, data);
+               if request.bodylength then
+                       request.havebodylength = request.havebodylength + #data;
+                       if request.havebodylength >= request.bodylength then
+                               -- We have the body
+                               log("debug", "Have full body, calling callback");
+                               if request.callback then
+                                       request.callback(t_concat(request.body), request.code, request);
+                               end
+                               request.body = nil;
+                               request.state = "completed";
+                       else
+                               log("debug", "Have "..request.havebodylength.." bytes out of "..request.bodylength);
                        end
+               end
+       elseif request.state == "headers" then
+               log("debug", "Reading headers...")
+               local pos = startpos;
+               local headers, headers_complete = request.responseheaders;
+               if not headers then
+                       headers = {};
+                       request.responseheaders = headers;
+               end
+               for line in data:sub(startpos, -1):gmatch("(.-)\r\n") do
+                       startpos = startpos + #line + 2;
+                       local k, v = line:match("(%S+): (.+)");
+                       if k and v then
+                               headers[k:lower()] = v;
+                               --log("debug", "Header: "..k:lower().." = "..v);
+                       elseif #line == 0 then
+                               headers_complete = true;
+                               break;
+                       else
+                               log("warn", "Unhandled header line: "..line);
+                       end
+               end
+               if not headers_complete then return; end
+               -- Reached the end of the headers
+               if not expectbody(request, request.code) then
+                       request.callback(nil, request.code, request);
+                       return;
+               end
+                       request.state = "body";
+               if #data > startpos then
+                       return request_reader(request, data, startpos);
+               end
+       elseif request.state == "status" then
+               log("debug", "Reading status...")
+               local http, code, text, linelen = data:match("^HTTP/(%S+) (%d+) (.-)\r\n()", startpos);
+               code = tonumber(code);
+               if not code then
+                       log("warn", "Invalid HTTP status line, telling callback then closing");
+                       local ret = request.callback("invalid-status-line", 0, request);
                        destroy_request(request);
+                       return ret;
                end
-               local function error_cb(r)
+               
+               request.code, request.responseversion = code, http;
+               
+               if request.onlystatus then
                        if request.callback then
-                               request.callback(r or "connection-closed", 0, request);
-                               request.callback = nil;
+                               request.callback(nil, code, request);
                        end
                        destroy_request(request);
+                       return;
                end
-               local function options_cb()
-                       return request;
+               
+               request.state = "headers";
+               
+               if #data > linelen then
+                       return request_reader(request, data, linelen);
                end
-               request.parser = httpstream_new(success_cb, error_cb, "client", options_cb);
        end
-       request.parser:feed(data);
 end
 
 local function handleerr(err) log("error", "Traceback[http]: %s: %s", tostring(err), debug_traceback()); end
index 74f61c56a46182b27d654b6b17cfab77a8d6df73..59ddbb12494d915ec1d39e52c9ed89799028cb1d 100644 (file)
@@ -7,20 +7,19 @@
 --
 
 
+local socket = require "socket"
 local server = require "net.server"
 local url_parse = require "socket.url".parse;
-local httpstream_new = require "util.httpstream".new;
 
 local connlisteners_start = require "net.connlisteners".start;
 local connlisteners_get = require "net.connlisteners".get;
 local listener;
 
 local t_insert, t_concat = table.insert, table.concat;
+local s_match, s_gmatch = string.match, string.gmatch;
 local tonumber, tostring, pairs, ipairs, type = tonumber, tostring, pairs, ipairs, type;
-local xpcall = xpcall;
-local debug_traceback = debug.traceback;
 
-local urlencode = function (s) return s and (s:gsub("%W", function (c) return ("%%%02x"):format(c:byte()); end)); end
+local urlencode = function (s) return s and (s:gsub("%W", function (c) return string.format("%%%02x", c:byte()); end)); end
 
 local log = require "util.logger".init("httpserver");
 
@@ -30,6 +29,10 @@ module "httpserver"
 
 local default_handler;
 
+local function expectbody(reqt)
+    return reqt.method == "POST";
+end
+
 local function send_response(request, response)
        -- Write status line
        local resp;
@@ -84,22 +87,6 @@ local function call_callback(request, err)
                callback = (request.server and request.server.handlers[base]) or default_handler;
        end
        if callback then
-               local _callback = callback;
-               function callback(method, body, request)
-                       local ok, result = xpcall(function() return _callback(method, body, request) end, debug_traceback);
-                       if ok then return result; end
-                       log("error", "Error in HTTP server handler: %s", result);
-                       -- TODO: When we support pipelining, request.destroyed
-                       -- won't be the right flag - we just want to see if there
-                       -- has been a response to this request yet.
-                       if not request.destroyed then
-                               return {
-                                       status = "500 Internal Server Error";
-                                       headers = { ["Content-Type"] = "text/plain" };
-                                       body = "There was an error processing your request. See the error log for more details.";
-                               };
-                       end
-               end
                if err then
                        log("debug", "Request error: "..err);
                        if not callback(nil, err, request) then
@@ -127,21 +114,94 @@ local function call_callback(request, err)
 end
 
 local function request_reader(request, data, startpos)
-       if not request.parser then
-               local function success_cb(r)
-                       for k,v in pairs(r) do request[k] = v; end
-                       request.url = url_parse(request.path);
-                       request.url.path = request.url.path and request.url.path:gsub("%%(%x%x)", function(x) return x.char(tonumber(x, 16)) end);
-                       request.body = { request.body };
+       if not data then
+               if request.body then
                        call_callback(request);
+               else
+                       -- Error.. connection was closed prematurely
+                       call_callback(request, "connection-closed");
                end
-               local function error_cb(r)
-                       call_callback(request, r or "connection-closed");
-                       destroy_request(request);
+               -- Here we force a destroy... the connection is gone, so we can't reply later
+               destroy_request(request);
+               return;
+       end
+       if request.state == "body" then
+               log("debug", "Reading body...")
+               if not request.body then request.body = {}; request.havebodylength, request.bodylength = 0, tonumber(request.headers["content-length"]); end
+               if startpos then
+                       data = data:sub(startpos, -1)
+               end
+               t_insert(request.body, data);
+               if request.bodylength then
+                       request.havebodylength = request.havebodylength + #data;
+                       if request.havebodylength >= request.bodylength then
+                               -- We have the body
+                               call_callback(request);
+                       end
+               end
+       elseif request.state == "headers" then
+               log("debug", "Reading headers...")
+               local pos = startpos;
+               local headers, headers_complete = request.headers;
+               if not headers then
+                       headers = {};
+                       request.headers = headers;
+               end
+               
+               for line in data:gmatch("(.-)\r\n") do
+                       startpos = (startpos or 1) + #line + 2;
+                       local k, v = line:match("(%S+): (.+)");
+                       if k and v then
+                               headers[k:lower()] = v;
+                               --log("debug", "Header: '"..k:lower().."' = '"..v.."'");
+                       elseif #line == 0 then
+                               headers_complete = true;
+                               break;
+                       else
+                               log("debug", "Unhandled header line: "..line);
+                       end
+               end
+               
+               if not headers_complete then return; end
+               
+               if not expectbody(request) then
+                       call_callback(request);
+                       return;
+               end
+               
+               -- Reached the end of the headers
+               request.state = "body";
+               if #data > startpos then
+                       return request_reader(request, data:sub(startpos, -1));
+               end
+       elseif request.state == "request" then
+               log("debug", "Reading request line...")
+               local method, path, http, linelen = data:match("^(%S+) (%S+) HTTP/(%S+)\r\n()", startpos);
+               if not method then
+                       log("warn", "Invalid HTTP status line, telling callback then closing");
+                       local ret = call_callback(request, "invalid-status-line");
+                       request:destroy();
+                       return ret;
+               end
+               
+               request.method, request.path, request.httpversion = method, path, http;
+               
+               request.url = url_parse(request.path);
+               
+               log("debug", method.." request for "..tostring(request.path) .. " on port "..request.handler:serverport());
+               
+               if request.onlystatus then
+                       if not call_callback(request) then
+                               return;
+                       end
+               end
+               
+               request.state = "headers";
+               
+               if #data > linelen then
+                       return request_reader(request, data:sub(linelen, -1));
                end
-               request.parser = httpstream_new(success_cb, error_cb);
        end
-       request.parser:feed(data);
 end
 
 -- The default handler for requests
@@ -203,7 +263,6 @@ function new_from_config(ports, handle_request, default_options)
                log("warn", "Old syntax of httpserver.new_from_config being used to register %s", handle_request);
                handle_request, default_options = default_options, { base = handle_request };
        end
-       ports = ports or {5280};
        for _, options in ipairs(ports) do
                local port = default_options.port or 5280;
                local base = default_options.base;
@@ -226,8 +285,8 @@ function new_from_config(ports, handle_request, default_options)
                        ssl.options = "no_sslv2";
                end
                
-               new{ port = port, interface = interface,
-                       base = base, handler = handle_request,
+               new{ port = port, interface = interface, 
+                       base = base, handler = handle_request, 
                        ssl = ssl, type = (ssl and "ssl") or "tcp" };
        end
 end
index b515ccce52c1ce5ca724df020df94bafd7538486..bf193ad89cde14a55c57660f7d51468feaa0fb24 100644 (file)
@@ -19,8 +19,6 @@ function server.onincoming(conn, data)
        if buf:match("^[a-zA-Z]") then
                local listener = httpserver_listener;
                conn:setlistener(listener);
-               local onconnect = listener.onconnect;
-               if onconnect then onconnect(conn) end
                listener.onincoming(conn, buf);
        elseif buf:match(">") then
                local listener;
@@ -33,8 +31,6 @@ function server.onincoming(conn, data)
                        listener = xmppclient_listener;
                end
                conn:setlistener(listener);
-               local onconnect = listener.onconnect;
-               if onconnect then onconnect(conn) end
                listener.onincoming(conn, buf);
        elseif #buf > 1024 then
                conn:close();
index 1c1a63a4743651ea6120a582dd070a584cdb8233..e0d4b85a971e6131c74d7fd7d872d95c917eb180 100644 (file)
@@ -6,7 +6,7 @@
 -- COPYING file in the source package for more information.
 --
 
-local use_luaevent = prosody and require "core.configmanager".get("*", "core", "use_libevent");
+local use_luaevent = require "core.configmanager".get("*", "core", "use_libevent");
 
 if use_luaevent then
        use_luaevent = pcall(require, "luaevent.core");
index 528305d327c06e918a523cad8420133445d8aa91..0331e793a7fa447ef339e073ffc2e9645cf79d94 100644 (file)
@@ -143,9 +143,9 @@ do
                                        debug( "new connection failed. id:", self.id, "error:", self.fatalerror )
                                else
                                        if plainssl and ssl then  -- start ssl session
-                                               self:starttls(nil, true)
+                                               self:starttls()
                                        else  -- normal connection
-                                               self:_start_session(true)
+                                               self:_start_session( self.listener.onconnect )
                                        end
                                        debug( "new connection established. id:", self.id )
                                end
@@ -155,15 +155,13 @@ do
                        self.eventconnect = addevent( base, self.conn, EV_WRITE, callback, cfg.CONNECT_TIMEOUT )
                        return true
        end
-       function interface_mt:_start_session(call_onconnect) -- new session, for example after startssl
+       function interface_mt:_start_session(onconnect) -- new session, for example after startssl
                if self.type == "client" then
                        local callback = function( )
                                self:_lock( false,  false, false )
                                --vdebug( "start listening on client socket with id:", self.id )
                                self.eventread = addevent( base, self.conn, EV_READ, self.readcallback, cfg.READ_TIMEOUT );  -- register callback
-                               if call_onconnect then
-                                       self:onconnect()
-                               end
+                               self:onconnect()
                                self.eventsession = nil
                                return -1
                        end
@@ -175,7 +173,7 @@ do
                end
                return true
        end
-       function interface_mt:_start_ssl(call_onconnect) -- old socket will be destroyed, therefore we have to close read/write events first
+       function interface_mt:_start_ssl(arg) -- old socket will be destroyed, therefore we have to close read/write events first
                        --vdebug( "starting ssl session with client id:", self.id )
                        local _
                        _ = self.eventread and self.eventread:close( )  -- close events; this must be called outside of the event callbacks!
@@ -186,7 +184,7 @@ do
                        if err then
                                self.fatalerror = err
                                self.conn = nil  -- cannot be used anymore
-                               if call_onconnect then
+                               if "onconnect" == arg then
                                        self.ondisconnect = nil  -- dont call this when client isnt really connected
                                end
                                self:_close()
@@ -213,25 +211,28 @@ do
                                                                self.send = self.conn.send  -- caching table lookups with new client object
                                                                self.receive = self.conn.receive
                                                                local onsomething
-                                                               if not call_onconnect then  -- trigger listener
-                                                                       self:onstatus("ssl-handshake-complete");
+                                                               if "onconnect" == arg then  -- trigger listener
+                                                                       onsomething = self.onconnect
+                                                               else
+                                                                       onsomething = self.onsslconnection
                                                                end
-                                                               self:_start_session( call_onconnect )
+                                                               self:_start_session( onsomething )
                                                                debug( "ssl handshake done" )
+                                                               self:onstatus("ssl-handshake-complete");
                                                                self.eventhandshake = nil
                                                                return -1
                                                        end
+                                                       debug( "error during ssl handshake:", err )
                                                        if err == "wantwrite" then
                                                                event = EV_WRITE
                                                        elseif err == "wantread" then
                                                                event = EV_READ
                                                        else
-                                                               debug( "ssl handshake error:", err )
                                                                self.fatalerror = err
                                                        end
                                                end
                                                if self.fatalerror then
-                                                       if call_onconnect then
+                                                       if "onconnect" == arg then
                                                                self.ondisconnect = nil  -- dont call this when client isnt really connected
                                                        end
                                                        self:_close()
@@ -361,10 +362,6 @@ do
                end
        end
        
-       function interface_mt:socket()
-               return self.conn
-       end
-       
        function interface_mt:server()
                return self._server or self;
        end
@@ -417,7 +414,7 @@ do
                -- No-op, we always use the underlying connection's send
        end
        
-       function interface_mt:starttls(sslctx, call_onconnect)
+       function interface_mt:starttls(sslctx)
                debug( "try to start ssl at client id:", self.id )
                local err
                self._sslctx = sslctx;
@@ -431,7 +428,7 @@ do
                self._usingssl = true
                self.startsslcallback = function( )  -- we have to start the handshake outside of a read/write event
                        self.startsslcallback = nil
-                       self:_start_ssl(call_onconnect);
+                       self:_start_ssl();
                        self.eventstarthandshake = nil
                        return -1
                end
@@ -471,6 +468,7 @@ do
        function interface_mt:ondrain()
        end
        function interface_mt:onstatus()
+               debug("server.lua: Dummy onstatus()")
        end
 end
 
@@ -702,9 +700,9 @@ do
                                local clientinterface = handleclient( client, client_ip, client_port, interface, pattern, listener, nil, sslctx )
                                --vdebug( "client id:", clientinterface, "startssl:", startssl )
                                if ssl and sslctx then
-                                       clientinterface:starttls(sslctx, true)
+                                       clientinterface:starttls(sslctx)
                                else
-                                       clientinterface:_start_session( true )
+                                       clientinterface:_start_session( clientinterface.onconnect )
                                end
                                debug( "accepted incoming client connection from:", client_ip or "<unknown IP>", client_port or "<unknown port>", "to", port or "<unknown port>");
                                
@@ -726,7 +724,7 @@ local addserver = ( function( )
                --vdebug( "creating new tcp server with following parameters:", addr or "nil", port or "nil", sslcfg or "nil", startssl or "nil")
                local server, err = socket.bind( addr, port, cfg.ACCEPT_QUEUE )  -- create server socket
                if not server then
-                       debug( "creating server socket on "..addr.." port "..port.." failed:", err )
+                       debug( "creating server socket failed because:", err )
                        return nil, err
                end
                local sslctx
@@ -848,6 +846,7 @@ function hook_signal(signal_num, handler)
 end
 
 local function link(sender, receiver, buffersize)
+       sender:set_mode(buffersize);
        local sender_locked;
        
        function receiver:ondrain()
index 13a910f8b4ce52a963b4d54f3067d621415c3c56..298e560aa00cc6a5ab2eebde12b7b53634641a08 100644 (file)
@@ -32,7 +32,6 @@ local STAT_UNIT = 1 -- byte
 local type = use "type"
 local pairs = use "pairs"
 local ipairs = use "ipairs"
-local tonumber = use "tonumber"
 local tostring = use "tostring"
 local collectgarbage = use "collectgarbage"
 
@@ -45,9 +44,8 @@ local coroutine = use "coroutine"
 
 --// lua lib methods //--
 
+local os_time = os.time
 local os_difftime = os.difftime
-local math_min = math.min
-local math_huge = math.huge
 local table_concat = table.concat
 local table_remove = table.remove
 local string_len = string.len
@@ -59,7 +57,6 @@ local coroutine_yield = coroutine.yield
 
 local luasec = use "ssl"
 local luasocket = use "socket" or require "socket"
-local luasocket_gettime = luasocket.gettime
 
 --// extern lib methods //--
 
@@ -77,7 +74,6 @@ local stats
 local idfalse
 local addtimer
 local closeall
-local addsocket
 local addserver
 local getserver
 local wrapserver
@@ -129,8 +125,6 @@ local _timer
 
 local _maxclientsperserver
 
-local _maxsslhandshake
-
 ----------------------------------// DEFINITION //--
 
 _server = { } -- key = port, value = table; list of listening servers
@@ -173,7 +167,7 @@ wrapserver = function( listeners, socket, ip, serverport, pattern, sslctx, maxco
 
        local connections = 0
 
-       local dispatch, disconnect = listeners.onconnect or listeners.onincoming, listeners.ondisconnect
+       local dispatch, disconnect = listeners.onincoming, listeners.ondisconnect
 
        local accept = socket.accept
 
@@ -489,7 +483,7 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport
                        if drain then
                                drain(handler)
                        end
-                       _ = needtls and handler:starttls(nil)
+                       _ = needtls and handler:starttls(nil, true)
                        _ = toclose and handler:close( )
                        return true
                elseif byte and ( err == "timeout" or err == "wantwrite" ) then -- want write
@@ -530,6 +524,7 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport
                                                _readlistlen = addsocket(_readlist, client, _readlistlen)
                                                return true
                                        else
+                                               out_put( "server.lua: error during ssl handshake: ", tostring(err) )
                                                if err == "wantwrite" and not wrote then
                                                        _sendlistlen = addsocket(_sendlist, client, _sendlistlen)
                                                        wrote = true
@@ -537,7 +532,6 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport
                                                        _readlistlen = addsocket(_readlist, client, _readlistlen)
                                                        read = true
                                                else
-                                                       out_put( "server.lua: ssl handshake error: ", tostring(err) )
                                                        break;
                                                end
                                                --coroutine_yield( handler, nil, err )   -- handshake not finished
@@ -570,13 +564,13 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport
                        end
                else
                        local sslctx;
-                       handler.starttls = function( self, _sslctx)
+                       handler.starttls = function( self, _sslctx, now )
                                if _sslctx then
                                        sslctx = _sslctx;
                                        handler:set_sslctx(sslctx);
                                end
-                               if bufferqueuelen > 0 then
-                                       out_put "server.lua: we need to do tls, but delaying until send buffer empty"
+                               if not now then
+                                       out_put "server.lua: we need to do tls, but delaying until later"
                                        needtls = true
                                        return
                                end
@@ -629,6 +623,16 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport
 
        _socketlist[ socket ] = handler
        _readlistlen = addsocket(_readlist, socket, _readlistlen)
+       if listeners.onconnect then
+               _sendlistlen = addsocket(_sendlist, socket, _sendlistlen)
+               handler.sendbuffer = function ()
+                       listeners.onconnect(handler);
+                       handler.sendbuffer = _sendbuffer;
+                       if bufferqueuelen > 0 then
+                               return _sendbuffer();
+                       end
+               end
+       end
        return handler, socket
 end
 
@@ -672,6 +676,7 @@ closesocket = function( socket )
 end
 
 local function link(sender, receiver, buffersize)
+       sender:set_mode(buffersize);
        local sender_locked;
        local _sendbuffer = receiver.sendbuffer;
        function receiver.sendbuffer()
@@ -793,18 +798,16 @@ stats = function( )
        return _readtraffic, _sendtraffic, _readlistlen, _sendlistlen, _timerlistlen
 end
 
-local quitting;
+local dontstop = true; -- thinking about tomorrow, ...
 
 setquitting = function (quit)
-       quitting = not not quit;
+       dontstop = not quit;
+       return;
 end
 
-loop = function(once) -- this is the main loop of the program
-       if quitting then return "quitting"; end
-       if once then quitting = "once"; end
-       local next_timer_time = math_huge;
-       repeat
-               local read, write, err = socket_select( _readlist, _sendlist, math_min(_selecttimeout, next_timer_time) )
+loop = function( ) -- this is the main loop of the program
+       while dontstop do
+               local read, write, err = socket_select( _readlist, _sendlist, _selecttimeout )
                for i, socket in ipairs( write ) do -- send data waiting in writequeues
                        local handler = _socketlist[ socket ]
                        if handler then
@@ -828,28 +831,19 @@ loop = function(once) -- this is the main loop of the program
                        handler:close( true )    -- forced disconnect
                end
                clean( _closelist )
-               _currenttime = luasocket_gettime( )
-               if _currenttime - _timer >= math_min(next_timer_time, 1) then
-                       next_timer_time = math_huge;
+               _currenttime = os_time( )
+               if os_difftime( _currenttime - _timer ) >= 1 then
                        for i = 1, _timerlistlen do
-                               local t = _timerlist[ i ]( _currenttime ) -- fire timers
-                               if t then next_timer_time = math_min(next_timer_time, t); end
+                               _timerlist[ i ]( _currenttime ) -- fire timers
                        end
                        _timer = _currenttime
-               else
-                       next_timer_time = next_timer_time - (_currenttime - _timer);
                end
                socket_sleep( _sleeptime ) -- wait some time
                --collectgarbage( )
-       until quitting;
-       if once and quitting == "once" then quitting = nil; return; end
+       end
        return "quitting"
 end
 
-step = function ()
-       return loop(true);
-end
-
 local function get_backend()
        return "select";
 end
@@ -860,18 +854,6 @@ local wrapclient = function( socket, ip, serverport, listeners, pattern, sslctx
        local handler = wrapconnection( nil, listeners, socket, ip, serverport, "clientport", pattern, sslctx )
        _socketlist[ socket ] = handler
        _sendlistlen = addsocket(_sendlist, socket, _sendlistlen)
-       if listeners.onconnect then
-               -- When socket is writeable, call onconnect
-               local _sendbuffer = handler.sendbuffer;
-               handler.sendbuffer = function ()
-                       handler.sendbuffer = _sendbuffer;
-                       listeners.onconnect(handler);
-                       -- If there was data with the incoming packet, handle it now.
-                       if #handler:bufferqueue() > 0 then
-                               return _sendbuffer();
-                       end
-               end
-       end
        return handler, socket
 end
 
@@ -897,8 +879,8 @@ use "setmetatable" ( _socketlist, { __mode = "k" } )
 use "setmetatable" ( _readtimes, { __mode = "k" } )
 use "setmetatable" ( _writetimes, { __mode = "k" } )
 
-_timer = luasocket_gettime( )
-_starttime = luasocket_gettime( )
+_timer = os_time( )
+_starttime = os_time( )
 
 addtimer( function( )
                local difftime = os_difftime( _currenttime - _starttime )
@@ -939,7 +921,6 @@ return {
        
        loop = loop,
        link = link,
-       step = step,
        stats = stats,
        closeall = closeall,
        addtimer = addtimer,
index 4cc90cbf5a8a767963012c7957f236ddc8daaf83..94daa2b2f7c95f8f846cf451da31ebe85620288a 100644 (file)
 
 local logger = require "logger";
 local log = logger.init("xmppclient_listener");
-local new_xmpp_stream = require "util.xmppstream".new;
+local lxp = require "lxp"
+local init_xmlhandlers = require "core.xmlhandlers"
+local sm_new_session = require "core.sessionmanager".new_session;
 
 local connlisteners_register = require "net.connlisteners".register;
 
+local t_insert = table.insert;
+local t_concat = table.concat;
+local t_concatall = function (t, sep) local tt = {}; for _, s in ipairs(t) do t_insert(tt, tostring(s)); end return t_concat(tt, sep); end
+local m_random = math.random;
+local format = string.format;
 local sessionmanager = require "core.sessionmanager";
 local sm_new_session, sm_destroy_session = sessionmanager.new_session, sessionmanager.destroy_session;
 local sm_streamopened = sessionmanager.streamopened;
 local sm_streamclosed = sessionmanager.streamclosed;
 local st = require "util.stanza";
-local xpcall = xpcall;
-local tostring = tostring;
-local type = type;
-local traceback = debug.traceback;
 
 local config = require "core.configmanager";
 local opt_keepalives = config.get("*", "core", "tcp_keepalives");
@@ -38,7 +41,7 @@ function stream_callbacks.error(session, error, data)
                session:close("invalid-namespace");
        elseif error == "parse-error" then
                (session.log or log)("debug", "Client XML parse error: %s", tostring(data));
-               session:close("not-well-formed");
+               session:close("xml-not-well-formed");
        elseif error == "stream-error" then
                local condition, text = "undefined-condition";
                for child in data:children() do
@@ -59,12 +62,9 @@ function stream_callbacks.error(session, error, data)
        end
 end
 
-local function handleerr(err) log("error", "Traceback[c2s]: %s: %s", tostring(err), traceback()); end
-function stream_callbacks.handlestanza(session, stanza)
-       stanza = session.filter("stanzas/in", stanza);
-       if stanza then
-               return xpcall(function () return core_process_stanza(session, stanza) end, handleerr);
-       end
+local function handleerr(err) log("error", "Traceback[c2s]: %s: %s", tostring(err), debug.traceback()); end
+function stream_callbacks.handlestanza(a, b)
+       xpcall(function () core_process_stanza(a, b) end, handleerr);
 end
 
 local sessions = {};
@@ -72,6 +72,23 @@ local xmppclient = { default_port = 5222, default_mode = "*a" };
 
 -- These are session methods --
 
+local function session_reset_stream(session)
+       -- Reset stream
+               local parser = lxp.new(init_xmlhandlers(session, stream_callbacks), "\1");
+               session.parser = parser;
+               
+               session.notopen = true;
+               
+               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
+               
+               return true;
+end
+
 local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'};
 local default_stream_attr = { ["xmlns:stream"] = "http://etherx.jabber.org/streams", xmlns = stream_callbacks.default_ns, version = "1.0", id = "" };
 local function session_close(session, reason)
@@ -111,54 +128,32 @@ end
 
 -- End of session methods --
 
-function xmppclient.onconnect(conn)
-       local session = sm_new_session(conn);
-       sessions[conn] = session;
-       
-       session.log("info", "Client connected");
-       
-       -- Client is using legacy SSL (otherwise mod_tls sets this flag)
-       if conn:ssl() then
-               session.secure = true;
-       end
-       
-       if opt_keepalives ~= nil then
-               conn:setoption("keepalive", opt_keepalives);
-       end
-       
-       session.close = session_close;
-       
-       local stream = new_xmpp_stream(session, stream_callbacks);
-       session.stream = stream;
-       
-       session.notopen = true;
-       
-       function session.reset_stream()
-               session.notopen = true;
-               session.stream:reset();
-       end
-       
-       local filter = session.filter;
-       function session.data(data)
-               data = filter("bytes/in", data);
-               if data then
-                       local ok, err = stream:feed(data);
-                       if ok then return; end
-                       log("debug", "Received invalid XML (%s) %d bytes: %s", tostring(err), #data, data:sub(1, 300):gsub("[\r\n]+", " "):gsub("[%z\1-\31]", "_"));
-                       session:close("not-well-formed");
-               end
-       end
-       
-       local handlestanza = stream_callbacks.handlestanza;
-       function session.dispatch_stanza(session, stanza)
-               return handlestanza(session, stanza);
-       end
-end
-
 function xmppclient.onincoming(conn, data)
        local session = sessions[conn];
-       if session then
-               session.data(data);
+       if not session then
+               session = sm_new_session(conn);
+               sessions[conn] = session;
+
+               session.log("info", "Client connected");
+               
+               -- Client is using legacy SSL (otherwise mod_tls sets this flag)
+               if conn:ssl() then
+                       session.secure = true;
+               end
+               
+               if opt_keepalives ~= nil then
+                       conn:setoption("keepalive", opt_keepalives);
+               end
+               
+               session.reset_stream = session_reset_stream;
+               session.close = session_close;
+               
+               session_reset_stream(session); -- Initialise, ready for use
+               
+               session.dispatch_stanza = stream_callbacks.handlestanza;
+       end
+       if data then
+               session.data(conn, data);
        end
 end
        
@@ -172,8 +167,4 @@ function xmppclient.ondisconnect(conn, err)
        end
 end
 
-function xmppclient.associate_session(conn, session)
-       sessions[conn] = session;
-end
-
 connlisteners_register("xmppclient", xmppclient);
index 90293559a30e0c1df8c71034c1b6ceda3ed51507..b87f7c96cf500e285bfbdda8552b1df35c916ad9 100644 (file)
 local hosts = _G.hosts;
 
 local t_concat = table.concat;
-local tostring = tostring;
-local type = type;
-local pairs = pairs;
 
 local lxp = require "lxp";
 local logger = require "util.logger";
 local config = require "core.configmanager";
 local connlisteners = require "net.connlisteners";
+local 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 jid_split = require "util.jid".split;
 local sha1 = require "util.hashes".sha1;
 local st = require "util.stanza";
-local new_xmpp_stream = require "util.xmppstream".new;
+local init_xmlhandlers = require "core.xmlhandlers";
 
 local sessions = {};
 
@@ -32,7 +30,7 @@ local component_listener = { default_port = 5347; default_mode = "*a"; default_i
 
 local xmlns_component = 'jabber:component:accept';
 
---- Callbacks/data for xmppstream to handle streams for us ---
+--- Callbacks/data for xmlhandlers to handle streams for us ---
 
 local stream_callbacks = { default_ns = xmlns_component };
 
@@ -45,7 +43,7 @@ function stream_callbacks.error(session, error, data, data2)
                session:close("invalid-namespace");
        elseif error == "parse-error" then
                session.log("warn", "External component %s XML parse error: %s", tostring(session.host), tostring(data));
-               session:close("not-well-formed");
+               session:close("xml-not-well-formed");
        elseif error == "stream-error" then
                local condition, text = "undefined-condition";
                for child in data:children() do
@@ -68,16 +66,19 @@ end
 
 function stream_callbacks.streamopened(session, attr)
        if config.get(attr.to, "core", "component_module") ~= "component" then
-               -- Trying to act as a component domain which
+               -- Trying to act as a component domain which 
                -- hasn't been configured
                session:close{ condition = "host-unknown", text = tostring(attr.to).." does not match any configured external components" };
                return;
        end
        
-       -- Note that we don't create the internal component
+       -- Store the original host (this is used for config, etc.)
+       session.user = attr.to;
+       -- Set the host for future reference
+       session.host = config.get(attr.to, "core", "component_address") or attr.to;
+       -- Note that we don't create the internal component 
        -- until after the external component auths successfully
 
-       session.host = attr.to;
        session.streamid = uuid_gen();
        session.notopen = nil;
        
@@ -87,7 +88,7 @@ function stream_callbacks.streamopened(session, attr)
 end
 
 function stream_callbacks.streamclosed(session)
-       session.log("debug", "Received </stream:stream>");
+       session.log("Received </stream:stream>");
        session:close();
 end
 
@@ -98,31 +99,6 @@ function stream_callbacks.handlestanza(session, stanza)
        if not stanza.attr.xmlns and stanza.name == "handshake" then
                stanza.attr.xmlns = xmlns_component;
        end
-       if not stanza.attr.xmlns or stanza.attr.xmlns == "jabber:client" then
-               local from = stanza.attr.from;
-               if from then
-                       if session.component_validate_from then
-                               local _, domain = jid_split(stanza.attr.from);
-                               if domain ~= session.host then
-                                       -- Return error
-                                       session.log("warn", "Component sent stanza with missing or invalid 'from' address");
-                                       session:close{
-                                               condition = "invalid-from";
-                                               text = "Component tried to send from address <"..tostring(from)
-                                                          .."> which is not in domain <"..tostring(session.host)..">";
-                                       };
-                                       return;
-                               end
-                       end
-               else
-                       stanza.attr.from = session.host;
-               end
-               if not stanza.attr.to then
-                       session.log("warn", "Rejecting stanza with no 'to' address");
-                       session.send(st.error_reply(stanza, "modify", "bad-request", "Components MUST specify a 'to' address on stanzas"));
-                       return;
-               end
-       end
        return core_process_stanza(session, stanza);
 end
 
@@ -165,48 +141,51 @@ local function session_close(session, reason)
 end
 
 --- Component connlistener
-function component_listener.onconnect(conn)
-       local _send = conn.write;
-       local session = { type = "component", conn = conn, send = function (data) return _send(conn, tostring(data)); end };
-
-       -- Logging functions --
-       local conn_name = "jcp"..tostring(conn):match("[a-f0-9]+$");
-       session.log = logger.init(conn_name);
-       session.close = session_close;
-       
-       session.log("info", "Incoming Jabber component connection");
-       
-       local stream = new_xmpp_stream(session, stream_callbacks);
-       session.stream = stream;
-       
-       session.notopen = true;
-       
-       function session.reset_stream()
+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(conn, tostring(data)); end };
+               sessions[conn] = session;
+
+               -- Logging functions --
+               
+               local conn_name = "jcp"..tostring(conn):match("[a-f0-9]+$");
+               session.log = logger.init(conn_name);
+               session.close = session_close;
+               
+               session.log("info", "Incoming Jabber component connection");
+               
+               local parser = lxp.new(init_xmlhandlers(session, stream_callbacks), "\1");
+               session.parser = parser;
+               
                session.notopen = true;
-               session.stream:reset();
+               
+               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
+               
+               session.dispatch_stanza = stream_callbacks.handlestanza;
+               
        end
-
-       function session.data(conn, data)
-               local ok, err = stream:feed(data);
-               if ok then return; end
-               log("debug", "Received invalid XML (%s) %d bytes: %s", tostring(err), #data, data:sub(1, 300):gsub("[\r\n]+", " "):gsub("[%z\1-\31]", "_"));
-               session:close("not-well-formed");
+       if data then
+               session.data(conn, data);
        end
-       
-       session.dispatch_stanza = stream_callbacks.handlestanza;
-
-       sessions[conn] = session;
-end
-function component_listener.onincoming(conn, data)
-       local session = sessions[conn];
-       session.data(conn, data);
 end
+       
 function component_listener.ondisconnect(conn, err)
        local session = sessions[conn];
        if session then
                (session.log or log)("info", "component disconnected: %s (%s)", tostring(session.host), tostring(err));
-               if session.on_destroy then session:on_destroy(err); end
-               sessions[conn] = nil;
+               if session.host then
+                       log("debug", "Deregistering component");
+                       cm_deregister_component(session.host);
+                       hosts[session.host].connected = nil;
+               end
+               sessions[conn]  = nil;
                for k in pairs(session) do
                        if k ~= "log" and k ~= "close" then
                                session[k] = nil;
index 3af0b9626fc7dac2902a1df1af8952afed02dcad..d1272edb117917270c8c02ef550d2a5d61da8f6f 100644 (file)
@@ -7,17 +7,11 @@
 --
 
 
-local tostring = tostring;
-local type = type;
-local xpcall = xpcall;
-local s_format = string.format;
-local traceback = debug.traceback;
 
 local logger = require "logger";
 local log = logger.init("xmppserver_listener");
-local st = require "util.stanza";
-local connlisteners_register = require "net.connlisteners".register;
-local new_xmpp_stream = require "util.xmppstream".new;
+local lxp = require "lxp"
+local init_xmlhandlers = require "core.xmlhandlers"
 local s2s_new_incoming = require "core.s2smanager".new_incoming;
 local s2s_streamopened = require "core.s2smanager".streamopened;
 local s2s_streamclosed = require "core.s2smanager".streamclosed;
@@ -33,7 +27,7 @@ function stream_callbacks.error(session, error, data)
                session:close("invalid-namespace");
        elseif error == "parse-error" then
                session.log("debug", "Server-to-server XML parse error: %s", tostring(error));
-               session:close("not-well-formed");
+               session:close("xml-not-well-formed");
        elseif error == "stream-error" then
                local condition, text = "undefined-condition";
                for child in data:children() do
@@ -54,22 +48,48 @@ function stream_callbacks.error(session, error, data)
        end
 end
 
-local function handleerr(err) log("error", "Traceback[s2s]: %s: %s", tostring(err), traceback()); end
-function stream_callbacks.handlestanza(session, stanza)
-       if stanza.attr.xmlns == "jabber:client" then --COMPAT: Prosody pre-0.6.2 may send jabber:client
-               stanza.attr.xmlns = nil;
-       end
-       stanza = session.filter("stanzas/in", stanza);
-       if stanza then
-               return xpcall(function () return core_process_stanza(session, stanza) end, handleerr);
+local function handleerr(err) log("error", "Traceback[s2s]: %s: %s", tostring(err), debug.traceback()); end
+function stream_callbacks.handlestanza(a, b)
+       if b.attr.xmlns == "jabber:client" then --COMPAT: Prosody pre-0.6.2 may send jabber:client
+               b.attr.xmlns = nil;
        end
+       xpcall(function () core_process_stanza(a, b) end, handleerr);
 end
 
+local connlisteners_register = require "net.connlisteners".register;
+
+local t_insert = table.insert;
+local t_concat = table.concat;
+local t_concatall = function (t, sep) local tt = {}; for _, s in ipairs(t) do t_insert(tt, tostring(s)); end return t_concat(tt, sep); end
+local m_random = math.random;
+local format = string.format;
+local sessionmanager = require "core.sessionmanager";
+local sm_new_session, sm_destroy_session = sessionmanager.new_session, sessionmanager.destroy_session;
+local st = require "util.stanza";
+
 local sessions = {};
 local xmppserver = { default_port = 5269, default_mode = "*a" };
 
 -- These are session methods --
 
+local function session_reset_stream(session)
+       -- Reset stream
+               local parser = lxp.new(init_xmlhandlers(session, stream_callbacks), "\1");
+               session.parser = parser;
+               
+               session.notopen = true;
+               
+               function session.data(conn, data)
+                       local ok, err = parser:parse(data);
+                       if ok then return; end
+                       (session.log or log)("warn", "Received invalid XML: %s", data);
+                       (session.log or log)("warn", "Problem was: %s", err);
+                       session:close("xml-not-well-formed");
+               end
+               
+               return true;
+end
+
 local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'};
 local default_stream_attr = { ["xmlns:stream"] = "http://etherx.jabber.org/streams", xmlns = stream_callbacks.default_ns, version = "1.0", id = "" };
 local function session_close(session, reason, remote_reason)
@@ -112,55 +132,29 @@ end
 
 -- End of session methods --
 
-local function initialize_session(session)
-       local stream = new_xmpp_stream(session, stream_callbacks);
-       session.stream = stream;
-       
-       session.notopen = true;
-               
-       function session.reset_stream()
-               session.notopen = true;
-               session.stream:reset();
-       end
-       
-       local filter = session.filter;
-       function session.data(data)
-               data = filter("bytes/in", data);
-               if data then
-                       local ok, err = stream:feed(data);
-                       if ok then return; end
-                       (session.log or log)("warn", "Received invalid XML: %s", data);
-                       (session.log or log)("warn", "Problem was: %s", err);
-                       session:close("not-well-formed");
-               end
-       end
-
-       session.close = session_close;
-       local handlestanza = stream_callbacks.handlestanza;
-       function session.dispatch_stanza(session, stanza)
-               return handlestanza(session, stanza);
-       end
-end
-
-function xmppserver.onconnect(conn)
-       if not sessions[conn] then -- May be an existing outgoing session
-               local session = s2s_new_incoming(conn);
+function xmppserver.onincoming(conn, data)
+       local session = sessions[conn];
+       if not session then
+               session = s2s_new_incoming(conn);
                sessions[conn] = session;
-       
+
                -- Logging functions --
+
+               
                local conn_name = "s2sin"..tostring(conn):match("[a-f0-9]+$");
                session.log = logger.init(conn_name);
                
                session.log("info", "Incoming s2s connection");
                
-               initialize_session(session);
+               session.reset_stream = session_reset_stream;
+               session.close = session_close;
+               
+               session_reset_stream(session); -- Initialise, ready for use
+               
+               session.dispatch_stanza = stream_callbacks.handlestanza;
        end
-end
-
-function xmppserver.onincoming(conn, data)
-       local session = sessions[conn];
-       if session then
-               session.data(data);
+       if data then
+               session.data(conn, data);
        end
 end
        
@@ -168,9 +162,9 @@ function xmppserver.onstatus(conn, status)
        if status == "ssl-handshake-complete" then
                local session = sessions[conn];
                if session and session.direction == "outgoing" then
-                       local to_host, from_host = session.to_host, session.from_host;
+                       local format, to_host, from_host = string.format, session.to_host, session.from_host;
                        session.log("debug", "Sending stream header...");
-                       session.sends2s(s_format([[<stream:stream xmlns='jabber:server' xmlns:db='jabber:server:dialback' xmlns:stream='http://etherx.jabber.org/streams' from='%s' to='%s' version='1.0'>]], from_host, to_host));
+                       session.sends2s(format([[<stream:stream xmlns='jabber:server' xmlns:db='jabber:server:dialback' xmlns:stream='http://etherx.jabber.org/streams' from='%s' to='%s' version='1.0'>]], from_host, to_host));
                end
        end
 end
@@ -196,7 +190,12 @@ function xmppserver.register_outgoing(conn, session)
        session.direction = "outgoing";
        sessions[conn] = session;
        
-       initialize_session(session);
+       session.reset_stream = session_reset_stream;
+       session.close = session_close;
+       session_reset_stream(session); -- Initialise, ready for use
+       
+       --local function handleerr(err) print("Traceback:", err, debug.traceback()); end
+       --session.stanza_dispatch = function (stanza) return select(2, xpcall(function () return core_process_stanza(session, stanza); end, handleerr));  end
 end
 
 connlisteners_register("xmppserver", xmppserver);
index 77555bec73b6aec191bc8dd1a8d594b66948aa06..d3017f6c994f0cc33ac5f495f5b8b333cf2d9d40 100644 (file)
@@ -6,38 +6,14 @@
 -- COPYING file in the source package for more information.
 --
 
-local st, jid = require "util.stanza", require "util.jid";
+local st, jid, set = require "util.stanza", require "util.jid", require "util.set";
 
 local is_admin = require "core.usermanager".is_admin;
+local admins = set.new(config.get(module:get_host(), "core", "admins"));
 
-function send_to_online(message, host)
-       local sessions;
-       if host then
-               sessions = { [host] = hosts[host] };
-       else
-               sessions = hosts;
-       end
-
-       local c = 0;
-       for hostname, host_session in pairs(sessions) do
-               if host_session.sessions then
-                       message.attr.from = hostname;
-                       for username in pairs(host_session.sessions) do
-                               c = c + 1;
-                               message.attr.to = username.."@"..hostname;
-                               core_post_stanza(host_session, message);
-                       end
-               end
-       end
-
-       return c;
-end
-
-
--- Old <message>-based jabberd-style announcement sending
-function handle_announcement(event)
-       local origin, stanza = event.origin, event.stanza;
-       local node, host, resource = jid.split(stanza.attr.to);
+function handle_announcement(data)
+       local origin, stanza = data.origin, data.stanza;
+       local host, resource = select(2, jid.split(stanza.attr.to));
        
        if resource ~= "announce/online" then
                return; -- Not an announcement
@@ -45,56 +21,25 @@ function handle_announcement(event)
        
        if not is_admin(stanza.attr.from) then
                -- Not an admin? Not allowed!
-               module:log("warn", "Non-admin '%s' tried to send server announcement", stanza.attr.from);
+               module:log("warn", "Non-admin %s tried to send server announcement", tostring(jid.bare(stanza.attr.from)));
                return;
        end
        
        module:log("info", "Sending server announcement to all online users");
+       local host_session = hosts[host];
        local message = st.clone(stanza);
        message.attr.type = "headline";
        message.attr.from = host;
        
-       local c = send_to_online(message, host);
-       module:log("info", "Announcement sent to %d online users", c);
-       return true;
-end
-module:hook("message/host", handle_announcement);
-
--- Ad-hoc command (XEP-0133)
-local dataforms_new = require "util.dataforms".new;
-local announce_layout = dataforms_new{
-       title = "Making an Announcement";
-       instructions = "Fill out this form to make an announcement to all\nactive users of this service.";
-
-       { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
-       { name = "subject", type = "text-single", label = "Subject" };
-       { name = "announcement", type = "text-multi", required = true, label = "Announcement" };
-};
-
-function announce_handler(self, data, state)
-       if state then
-               if data.action == "cancel" then
-                       return { status = "canceled" };
-               end
-
-               local fields = announce_layout:data(data.form);
-
-               module:log("info", "Sending server announcement to all online users");
-               local message = st.message({type = "headline"}, fields.announcement):up()
-                       :tag("subject"):text(fields.subject or "Announcement");
-               
-               local count = send_to_online(message, data.to);
-               
-               module:log("info", "Announcement sent to %d online users", count);
-               return { status = "completed", info = ("Announcement sent to %d online users"):format(count) };
-       else
-               return { status = "executing", form = announce_layout }, "executing";
+       local c = 0;
+       for user in pairs(host_session.sessions) do
+               c = c + 1;
+               message.attr.to = user.."@"..host;
+               core_post_stanza(host_session, message);
        end
-
+       
+       module:log("info", "Announcement sent to %d online users", c);
        return true;
 end
 
-local adhoc_new = module:require "adhoc".new;
-local announce_desc = adhoc_new("Send Announcement to Online Users", "http://jabber.org/protocol/admin#announce", announce_handler, "admin");
-module:add_item("adhoc", announce_desc);
-
+module:hook("message/host", handle_announcement);
index c2c7eae98d1b7174f749d2291b421d3a1b1f2a5b..66a79785dea936ba77779028c768039ccdb8fcb0 100644 (file)
@@ -10,33 +10,31 @@ module.host = "*" -- Global module
 
 local hosts = _G.hosts;
 local lxp = require "lxp";
-local new_xmpp_stream = require "util.xmppstream".new;
+local init_xmlhandlers = require "core.xmlhandlers"
+local server = require "net.server";
 local httpserver = require "net.httpserver";
 local sm = require "core.sessionmanager";
 local sm_destroy_session = sm.destroy_session;
 local new_uuid = require "util.uuid".generate;
-local fire_event = prosody.events.fire_event;
+local fire_event = require "core.eventmanager".fire_event;
 local core_process_stanza = core_process_stanza;
 local st = require "util.stanza";
 local logger = require "util.logger";
 local log = logger.init("mod_bosh");
-local timer = require "util.timer";
 
-local xmlns_streams = "http://etherx.jabber.org/streams";
-local xmlns_xmpp_streams = "urn:ietf:params:xml:ns:xmpp-streams";
 local xmlns_bosh = "http://jabber.org/protocol/httpbind"; -- (hard-coded into a literal in session.send)
-
-local stream_callbacks = {
-       stream_ns = xmlns_bosh, stream_tag = "body", default_ns = "jabber:client" };
+local stream_callbacks = { stream_ns = "http://jabber.org/protocol/httpbind", stream_tag = "body", default_ns = "jabber:client" };
 
 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;
 local BOSH_DEFAULT_POLLING = tonumber(module:get_option("bosh_max_polling")) or 5;
 local BOSH_DEFAULT_REQUESTS = tonumber(module:get_option("bosh_max_requests")) or 2;
+local BOSH_DEFAULT_MAXPAUSE = tonumber(module:get_option("bosh_max_pause")) or 300;
 
 local consider_bosh_secure = module:get_option_boolean("consider_bosh_secure");
 
 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
@@ -54,22 +52,6 @@ if cross_domain then
        end
 end
 
-local trusted_proxies = module:get_option_set("trusted_proxies", {"127.0.0.1"})._items;
-
-local function get_ip_from_request(request)
-       local ip = request.handler:ip();
-       local forwarded_for = request.headers["x-forwarded-for"];
-       if forwarded_for then
-               forwarded_for = forwarded_for..", "..ip;
-               for forwarded_ip in forwarded_for:gmatch("[^%s,]+") do
-                       if not trusted_proxies[forwarded_ip] then
-                               ip = forwarded_ip;
-                       end
-               end
-       end
-       return ip;
-end
-
 local t_insert, t_remove, t_concat = table.insert, table.remove, table.concat;
 local os_time = os.time;
 
@@ -101,10 +83,7 @@ end
 function handle_request(method, body, request)
        if (not body) or request.method ~= "POST" then
                if request.method == "OPTIONS" then
-                       local headers = {};
-                       for k,v in pairs(default_headers) do headers[k] = v; end
-                       headers["Content-Type"] = nil;
-                       return { headers = headers, body = "" };
+                       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
@@ -118,19 +97,12 @@ function handle_request(method, body, request)
        request.log = log;
        request.on_destroy = on_destroy_request;
        
-       local stream = new_xmpp_stream(request, stream_callbacks);
-       -- stream:feed() calls the stream_callbacks, so all stanzas in
-       -- the body are processed in this next line before it returns.
-       stream:feed(body);
+       local parser = lxp.new(init_xmlhandlers(request, stream_callbacks), "\1");
+       
+       parser:parse(body);
        
        local session = sessions[request.sid];
        if session then
-               -- Session was marked as inactive, since we have
-               -- a request open now, unmark it
-               if inactive_sessions[session] then
-                       inactive_sessions[session] = nil;
-               end
-
                local r = session.requests;
                log("debug", "Session %s has %d out of %d requests open", request.sid, #r, session.bosh_hold);
                log("debug", "and there are %d things in the send_buffer", #session.send_buffer);
@@ -160,56 +132,25 @@ function handle_request(method, body, request)
                                request.reply_before = os_time() + session.bosh_wait;
                                waiting_requests[request] = true;
                        end
+                       if inactive_sessions[session] then
+                               -- Session was marked as inactive, since we have
+                               -- a request open now, unmark it
+                               inactive_sessions[session] = nil;
+                       end
                end
                
-               if session.bosh_terminate then
-                       session.log("debug", "Closing session with %d requests open", #session.requests);
-                       session:close();
-                       return nil;
-               else
-                       return true; -- Inform httpserver we shall reply later
-               end
+               return true; -- Inform httpserver we shall reply later
        end
 end
 
 
 local function bosh_reset_stream(session) session.notopen = true; end
 
-local stream_xmlns_attr = { xmlns = "urn:ietf:params:xml:ns:xmpp-streams" };
-
 local function bosh_close_stream(session, reason)
        (session.log or log)("info", "BOSH client disconnected");
-       
-       local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate",
-               ["xmlns:streams"] = xmlns_streams });
-       
-
-       if reason then
-               close_reply.attr.condition = "remote-stream-error";
-               if type(reason) == "string" then -- assume stream error
-                       close_reply:tag("stream:error")
-                               :tag(reason, {xmlns = xmlns_xmpp_streams});
-               elseif type(reason) == "table" then
-                       if reason.condition then
-                               close_reply:tag("stream:error")
-                                       :tag(reason.condition, stream_xmlns_attr):up();
-                               if reason.text then
-                                       close_reply:tag("text", stream_xmlns_attr):text(reason.text):up();
-                               end
-                               if reason.extra then
-                                       close_reply:add_child(reason.extra);
-                               end
-                       elseif reason.name then -- a stanza
-                               close_reply = reason;
-                       end
-               end
-               log("info", "Disconnecting client, <stream:error> is: %s", tostring(close_reply));
-       end
-
-       local session_close_response = { headers = default_headers, body = tostring(close_reply) };
-
+       session_close_reply.attr.condition = reason;
        for _, held_request in ipairs(session.requests) do
-               held_request:send(session_close_response);
+               held_request:send(session_close_reply);
                held_request:destroy();
        end
        sessions[session.sid]  = nil;
@@ -227,46 +168,33 @@ function stream_callbacks.streamopened(request, attr)
                if not hosts[attr.to] then
                        -- Unknown host
                        log("debug", "BOSH client tried to connect to unknown host: %s", tostring(attr.to));
-                       local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate",
-                               ["xmlns:streams"] = xmlns_streams, condition = "host-unknown" });
-                       request:send(tostring(close_reply));
+                       session_close_reply.body.attr.condition = "host-unknown";
+                       request:send(session_close_reply);
+                       request.notopen = nil
                        return;
                end
                
                -- New session
                sid = new_uuid();
                local session = {
-                       type = "c2s_unauthed", conn = {}, sid = sid, rid = tonumber(attr.rid), host = attr.to,
+                       type = "c2s_unauthed", conn = {}, sid = sid, rid = tonumber(attr.rid)-1, host = attr.to,
                        bosh_version = attr.ver, bosh_wait = attr.wait, streamid = sid,
                        bosh_hold = BOSH_DEFAULT_HOLD, bosh_max_inactive = BOSH_DEFAULT_INACTIVITY,
                        requests = { }, send_buffer = {}, reset_stream = bosh_reset_stream,
                        close = bosh_close_stream, dispatch_stanza = core_process_stanza,
-                       log = logger.init("bosh"..sid), secure = consider_bosh_secure or request.secure,
-                       ip = get_ip_from_request(request);
+                       log = logger.init("bosh"..sid), secure = consider_bosh_secure or request.secure
                };
                sessions[sid] = session;
                
-               session.log("debug", "BOSH session created for request from %s", session.ip);
                log("info", "New BOSH session, assigned it sid '%s'", sid);
                local r, send_buffer = session.requests, session.send_buffer;
                local response = { headers = default_headers }
                function session.send(s)
-                       -- We need to ensure that outgoing stanzas have the jabber:client xmlns
-                       if s.attr and not s.attr.xmlns then
-                               s = st.clone(s);
-                               s.attr.xmlns = "jabber:client";
-                       end
                        --log("debug", "Sending BOSH data: %s", tostring(s));
                        local oldest_request = r[1];
                        if oldest_request then
                                log("debug", "We have an open request, so sending on that");
-                               response.body = t_concat({
-                                       "<body xmlns='http://jabber.org/protocol/httpbind' ",
-                                       session.bosh_terminate and "type='terminate' " or "",
-                                       "sid='", sid, "' xmlns:stream = 'http://etherx.jabber.org/streams'>",
-                                       tostring(s),
-                                       "</body>"
-                               });
+                               response.body = t_concat{"<body xmlns='http://jabber.org/protocol/httpbind' sid='", sid, "' xmlns:stream = 'http://etherx.jabber.org/streams'>", tostring(s), "</body>" };
                                oldest_request:send(response);
                                --log("debug", "Sent");
                                if oldest_request.stayopen then
@@ -285,7 +213,6 @@ function stream_callbacks.streamopened(request, attr)
                                t_insert(session.send_buffer, tostring(s));
                                log("debug", "There are now %d things in the send_buffer", #session.send_buffer);
                        end
-                       return true;
                end
                
                -- Send creation response
@@ -295,17 +222,9 @@ function stream_callbacks.streamopened(request, attr)
                fire_event("stream-features", session, features);
                --xmpp:version='1.0' xmlns:xmpp='urn:xmpp:xbosh'
                local response = st.stanza("body", { xmlns = xmlns_bosh,
-                       wait = attr.wait,
-                       inactivity = tostring(BOSH_DEFAULT_INACTIVITY),
-                       polling = tostring(BOSH_DEFAULT_POLLING),
-                       requests = tostring(BOSH_DEFAULT_REQUESTS),
-                       hold = tostring(session.bosh_hold),
-                       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);
+                                                                       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;
@@ -330,7 +249,6 @@ function stream_callbacks.streamopened(request, attr)
                        -- Repeated, ignore
                        session.log("debug", "rid repeated (on request %s), ignoring: %s (diff %d)", request.id, session.rid, diff);
                        request.notopen = nil;
-                       request.ignore = true;
                        request.sid = sid;
                        t_insert(session.requests, request);
                        return;
@@ -338,6 +256,13 @@ function stream_callbacks.streamopened(request, attr)
                session.rid = rid;
        end
        
+       if attr.type == "terminate" then
+               -- Client wants to end this session
+               session:close();
+               request.notopen = nil;
+               return;
+       end
+       
        if session.notopen then
                local features = st.stanza("stream:features");
                hosts[session.host].events.fire_event("stream-features", { origin = session, features = features });
@@ -346,44 +271,23 @@ function stream_callbacks.streamopened(request, attr)
                session.notopen = nil;
        end
        
-       if attr.type == "terminate" then
-               -- Client wants to end this session, which we'll do
-               -- after processing any stanzas in this request
-               session.bosh_terminate = true;
-       end
-
        request.notopen = nil; -- Signals that we accept this opening tag
        t_insert(session.requests, request);
        request.sid = sid;
 end
 
 function stream_callbacks.handlestanza(request, stanza)
-       if request.ignore then return; end
        log("debug", "BOSH stanza received: %s\n", stanza:top_tag());
        local session = sessions[request.sid];
        if session then
                if stanza.attr.xmlns == xmlns_bosh then
                        stanza.attr.xmlns = nil;
                end
+               session.ip = request.handler:ip();
                core_process_stanza(session, stanza);
        end
 end
 
-function stream_callbacks.error(request, error)
-       log("debug", "Error parsing BOSH request payload; %s", error);
-       if not request.sid then
-               request:send({ headers = default_headers, status = "400 Bad Request" });
-               return;
-       end
-       
-       local session = sessions[request.sid];
-       if error == "stream-error" then -- Remote stream error, we close normally
-               session:close();
-       else
-               session:close({ condition = "bad-format", text = "Error processing stream" });
-       end
-end
-
 local dead_sessions = {};
 function on_timer()
        -- log("debug", "Checking for requests soon to timeout...");
@@ -421,14 +325,13 @@ function on_timer()
                dead_sessions[i] = nil;
                sm_destroy_session(session, "BOSH client silent for over "..session.bosh_max_inactive.." seconds");
        end
-       return 1;
 end
 
 
 local function setup()
        local ports = module:get_option("bosh_ports") or { 5280 };
        httpserver.new_from_config(ports, handle_request, { base = "http-bind" });
-       timer.add_task(1, on_timer);
+       server.addtimer(on_timer);
 end
 if prosody.start_time then -- already started
        setup();
index fda271ddab671ba44e5db4e715e8793ed8a6322c..7efb4f9c3e94c2c4076854445103ed2267f7b065 100644 (file)
@@ -14,87 +14,59 @@ local hosts = _G.hosts;
 
 local t_concat = table.concat;
 
+local config = require "core.configmanager";
+local cm_register_component = require "core.componentmanager".register_component;
+local cm_deregister_component = require "core.componentmanager".deregister_component;
 local sha1 = require "util.hashes".sha1;
 local st = require "util.stanza";
 
 local log = module._log;
 
-local main_session, send;
-
-local function on_destroy(session, err)
-       if main_session == session then
-               main_session = nil;
-               send = nil;
-               session.on_destroy = nil;
-       end
-end
-
-local function handle_stanza(event)
-       local stanza = event.stanza;
-       if send then
-               stanza.attr.xmlns = nil;
-               send(stanza);
-       else
-               log("warn", "Stanza being handled by default component; bouncing error for: %s", stanza:top_tag());
-               if stanza.attr.type ~= "error" and stanza.attr.type ~= "result" then
-                       event.origin.send(st.error_reply(stanza, "wait", "service-unavailable", "Component unavailable"));
-               end
-       end
-       return true;
-end
-
-module:hook("iq/bare", handle_stanza, -1);
-module:hook("message/bare", handle_stanza, -1);
-module:hook("presence/bare", handle_stanza, -1);
-module:hook("iq/full", handle_stanza, -1);
-module:hook("message/full", handle_stanza, -1);
-module:hook("presence/full", handle_stanza, -1);
-module:hook("iq/host", handle_stanza, -1);
-module:hook("message/host", handle_stanza, -1);
-module:hook("presence/host", handle_stanza, -1);
-
 --- Handle authentication attempts by components
-function handle_component_auth(event)
-       local session, stanza = event.origin, event.stanza;
-       
-       if session.type ~= "component" then return; end
-       if main_session == session then return; end
-
+function handle_component_auth(session, stanza)
+       log("info", "Handling component auth");
        if (not session.host) or #stanza.tags > 0 then
-               (session.log or log)("warn", "Invalid component handshake for host: %s", session.host);
+               (session.log or log)("warn", "Component handshake invalid");
                session:close("not-authorized");
-               return true;
+               return;
        end
        
-       local secret = module:get_option("component_secret");
+       local secret = config.get(session.user, "core", "component_secret");
        if not secret then
-               (session.log or log)("warn", "Component attempted to identify as %s, but component_secret is not set", session.host);
+               (session.log or log)("warn", "Component attempted to identify as %s, but component_secret is not set", session.user);
                session:close("not-authorized");
-               return true;
+               return;
        end
        
        local supplied_token = t_concat(stanza);
        local calculated_token = sha1(session.streamid..secret, true);
        if supplied_token:lower() ~= calculated_token:lower() then
-               log("info", "Component authentication failed for %s", session.host);
+               log("info", "Component for %s authentication failed", session.host);
                session:close{ condition = "not-authorized", text = "Given token does not match calculated token" };
-               return true;
+               return;
        end
        
+       
+       -- Authenticated now
+       log("info", "Component authenticated: %s", session.host);
+       
        -- If component not already created for this host, create one now
-       if not main_session then
-               send = session.send;
-               main_session = session;
-               session.on_destroy = on_destroy;
-               session.component_validate_from = module:get_option_boolean("validate_from_addresses") ~= false;
-               log("info", "Component successfully authenticated: %s", session.host);
-               session.send(st.stanza("handshake"));
-       else -- TODO: Implement stanza distribution
-               log("error", "Multiple components bound to the same address, first one wins: %s", session.host);
-               session:close{ condition = "conflict", text = "Component already connected" };
+       if not hosts[session.host].connected then
+               local send = session.send;
+               session.component_session = cm_register_component(session.host, function (_, data) 
+                               if data.attr and data.attr.xmlns == "jabber:client" then
+                                       data.attr.xmlns = nil;
+                               end
+                               return send(data);
+                       end);
+               hosts[session.host].connected = true;
+               log("info", "Component successfully registered");
+       else
+               log("error", "Multiple components bound to the same address, first one wins (TODO: Implement stanza distribution)");
        end
        
-       return true;
+       -- Signal successful authentication
+       session.send(st.stanza("handshake"));
 end
 
-module:hook("stanza/jabber:component:accept:handshake", handle_component_auth);
+module:add_handler("component", "handshake", "jabber:component:accept", handle_component_auth);
index 82403016d9e936c47503a18e21ca863f5dede10d..533414924af23973fa8b1f4051d024369d217e30 100644 (file)
@@ -14,7 +14,6 @@ local xmlns_compression_feature = "http://jabber.org/features/compress"
 local xmlns_compression_protocol = "http://jabber.org/protocol/compress"
 local xmlns_stream = "http://etherx.jabber.org/streams";
 local compression_stream_feature = st.stanza("compression", {xmlns=xmlns_compression_feature}):tag("method"):text("zlib"):up();
-local add_filter = require "util.filters".add_filter;
 
 local compression_level = module:get_option("compression_level");
 -- if not defined assume admin wants best compression
@@ -39,7 +38,7 @@ end);
 module:hook("s2s-stream-features", function(event)
        local origin, features = event.origin, event.features;
        -- FIXME only advertise compression support when TLS layer has no compression enabled
-       if not origin.compressed then
+       if not origin.compressed then 
                features:add_child(compression_stream_feature);
        end
 end);
@@ -95,108 +94,121 @@ end
 
 -- setup compression for a stream
 local function setup_compression(session, deflate_stream)
-       add_filter(session, "bytes/out", function(t)
-               local status, compressed, eof = pcall(deflate_stream, tostring(t), 'sync');
-               if status == false then
-                       module:log("warn", "%s", tostring(compressed));
-                       session:close({
-                               condition = "undefined-condition";
-                               text = compressed;
-                               extra = st.stanza("failure", {xmlns="http://jabber.org/protocol/compress"}):tag("processing-failed");
-                       });
-                       return;
-               end
-               return compressed;
-       end);   
+       local old_send = (session.sends2s or session.send);
+       
+       local new_send = function(t)
+                       --TODO: Better code injection in the sending process
+                       local status, compressed, eof = pcall(deflate_stream, tostring(t), 'sync');
+                       if status == false then
+                               session:close({
+                                       condition = "undefined-condition";
+                                       text = compressed;
+                                       extra = st.stanza("failure", {xmlns="http://jabber.org/protocol/compress"}):tag("processing-failed");
+                               });
+                               module:log("warn", "%s", tostring(compressed));
+                               return;
+                       end
+                       session.conn:write(compressed);
+               end;
+       
+       if session.sends2s then session.sends2s = new_send
+       elseif session.send then session.send = new_send end
 end
 
 -- setup decompression for a stream
 local function setup_decompression(session, inflate_stream)
-       add_filter(session, "bytes/in", function(data)
-               local status, decompressed, eof = pcall(inflate_stream, data);
-               if status == false then
-                       module:log("warn", "%s", tostring(decompressed));
-                       session:close({
-                               condition = "undefined-condition";
-                               text = decompressed;
-                               extra = st.stanza("failure", {xmlns="http://jabber.org/protocol/compress"}):tag("processing-failed");
-                       });
-                       return;
-               end
-               return decompressed;
-       end);
+       local old_data = session.data
+       session.data = function(conn, data)
+                       local status, decompressed, eof = pcall(inflate_stream, data);
+                       if status == false then
+                               session:close({
+                                       condition = "undefined-condition";
+                                       text = decompressed;
+                                       extra = st.stanza("failure", {xmlns="http://jabber.org/protocol/compress"}):tag("processing-failed");
+                               });
+                               module:log("warn", "%s", tostring(decompressed));
+                               return;
+                       end
+                       old_data(conn, decompressed);
+               end;
 end
 
-module:hook("stanza/http://jabber.org/protocol/compress:compressed", function(event)
-       local session = event.origin;
-       
-       if session.type == "s2sout_unauthed" or session.type == "s2sout" then
-               session.log("debug", "Activating compression...")
-               -- create deflate and inflate streams
-               local deflate_stream = get_deflate_stream(session);
-               if not deflate_stream then return true; end
-               
-               local inflate_stream = get_inflate_stream(session);
-               if not inflate_stream then return true; end
-               
-               -- setup compression for session.w
-               setup_compression(session, deflate_stream);
-                       
-               -- setup decompression for session.data
-               setup_decompression(session, inflate_stream);
-               session:reset_stream();
-               local default_stream_attr = {xmlns = "jabber:server", ["xmlns:stream"] = "http://etherx.jabber.org/streams",
-                                                                       ["xmlns:db"] = 'jabber:server:dialback', version = "1.0", to = session.to_host, from = session.from_host};
-               session.sends2s("<?xml version='1.0'?>");
-               session.sends2s(st.stanza("stream:stream", default_stream_attr):top_tag());
-               session.compressed = true;
-               return true;
-       end
-end);
-
-module:hook("stanza/http://jabber.org/protocol/compress:compress", function(event)
-       local session, stanza = event.origin, event.stanza;
-
-       if session.type == "c2s" or session.type == "s2sin" or session.type == "c2s_unauthed" or session.type == "s2sin_unauthed" then
-               -- fail if we are already compressed
-               if session.compressed then
-                       local error_st = st.stanza("failure", {xmlns=xmlns_compression_protocol}):tag("setup-failed");
-                       (session.sends2s or session.send)(error_st);
-                       session.log("debug", "Client tried to establish another compression layer.");
-                       return true;
-               end
-               
-               -- checking if the compression method is supported
-               local method = stanza:child_with_name("method");
-               method = method and (method[1] or "");
-               if method == "zlib" then
-                       session.log("debug", "zlib compression enabled.");
-                       
+module:add_handler({"s2sout_unauthed", "s2sout"}, "compressed", xmlns_compression_protocol, 
+               function(session ,stanza)
+                       session.log("debug", "Activating compression...")
                        -- create deflate and inflate streams
                        local deflate_stream = get_deflate_stream(session);
-                       if not deflate_stream then return true; end
+                       if not deflate_stream then return end
                        
                        local inflate_stream = get_inflate_stream(session);
-                       if not inflate_stream then return true; end
-                       
-                       (session.sends2s or session.send)(st.stanza("compressed", {xmlns=xmlns_compression_protocol}));
-                       session:reset_stream();
+                       if not inflate_stream then return end
                        
                        -- setup compression for session.w
                        setup_compression(session, deflate_stream);
                                
                        -- setup decompression for session.data
                        setup_decompression(session, inflate_stream);
-                       
+                       local session_reset_stream = session.reset_stream;
+                       session.reset_stream = function(session)
+                                       session_reset_stream(session);
+                                       setup_decompression(session, inflate_stream);
+                                       return true;
+                               end;
+                       session:reset_stream();
+                       local default_stream_attr = {xmlns = "jabber:server", ["xmlns:stream"] = "http://etherx.jabber.org/streams",
+                                                                               ["xmlns:db"] = 'jabber:server:dialback', version = "1.0", to = session.to_host, from = session.from_host};
+                       session.sends2s("<?xml version='1.0'?>");
+                       session.sends2s(st.stanza("stream:stream", default_stream_attr):top_tag());
                        session.compressed = true;
-               elseif method then
-                       session.log("debug", "%s compression selected, but we don't support it.", tostring(method));
-                       local error_st = st.stanza("failure", {xmlns=xmlns_compression_protocol}):tag("unsupported-method");
-                       (session.sends2s or session.send)(error_st);
-               else
-                       (session.sends2s or session.send)(st.stanza("failure", {xmlns=xmlns_compression_protocol}):tag("setup-failed"));
                end
-               return true;
-       end
-end);
+);
+
+module:add_handler({"c2s_unauthed", "c2s", "s2sin_unauthed", "s2sin"}, "compress", xmlns_compression_protocol,
+               function(session, stanza)
+                       -- fail if we are already compressed
+                       if session.compressed then
+                               local error_st = st.stanza("failure", {xmlns=xmlns_compression_protocol}):tag("setup-failed");
+                               (session.sends2s or session.send)(error_st);
+                               session.log("debug", "Client tried to establish another compression layer.");
+                               return;
+                       end
+                       
+                       -- checking if the compression method is supported
+                       local method = stanza:child_with_name("method");
+                       method = method and (method[1] or "");
+                       if method == "zlib" then
+                               session.log("debug", "zlib compression enabled.");
+                               
+                               -- create deflate and inflate streams
+                               local deflate_stream = get_deflate_stream(session);
+                               if not deflate_stream then return end
+                               
+                               local inflate_stream = get_inflate_stream(session);
+                               if not inflate_stream then return end
+                               
+                               (session.sends2s or session.send)(st.stanza("compressed", {xmlns=xmlns_compression_protocol}));
+                               session:reset_stream();
+                               
+                               -- setup compression for session.w
+                               setup_compression(session, deflate_stream);
+                                       
+                               -- setup decompression for session.data
+                               setup_decompression(session, inflate_stream);
+                               
+                               local session_reset_stream = session.reset_stream;
+                               session.reset_stream = function(session)
+                                               session_reset_stream(session);
+                                               setup_decompression(session, inflate_stream);
+                                               return true;
+                                       end;
+                               session.compressed = true;
+                       elseif method then
+                               session.log("debug", "%s compression selected, but we don't support it.", tostring(method));
+                               local error_st = st.stanza("failure", {xmlns=xmlns_compression_protocol}):tag("unsupported-method");
+                               (session.sends2s or session.send)(error_st);
+                       else
+                               (session.sends2s or session.send)(st.stanza("failure", {xmlns=xmlns_compression_protocol}):tag("setup-failed"));
+                       end
+               end
+);
 
diff --git a/plugins/mod_console.lua b/plugins/mod_console.lua
new file mode 100644 (file)
index 0000000..e87ef53
--- /dev/null
@@ -0,0 +1,656 @@
+-- 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 = "*";
+
+local _G = _G;
+
+local prosody = _G.prosody;
+local hosts = prosody.hosts;
+local connlisteners_register = require "net.connlisteners".register;
+
+local console_listener = { default_port = 5582; default_mode = "*l"; default_interface = "127.0.0.1" };
+
+require "util.iterators";
+local jid_bare = require "util.jid".bare;
+local set, array = require "util.set", require "util.array";
+
+local commands = {};
+local def_env = {};
+local default_env_mt = { __index = def_env };
+
+prosody.console = { commands = commands, env = def_env };
+
+local function redirect_output(_G, session)
+       return setmetatable({ print = session.print }, { __index = function (t, k) return rawget(_G, k); end, __newindex = function (t, k, v) rawset(_G, k, v); end });
+end
+
+console = {};
+
+function console:new_session(conn)
+       local w = function(s) conn:write(s:gsub("\n", "\r\n")); end;
+       local session = { conn = conn;
+                       send = function (t) w(tostring(t)); end;
+                       print = function (t) w("| "..tostring(t).."\n"); end;
+                       disconnect = function () conn:close(); end;
+                       };
+       session.env = setmetatable({}, default_env_mt);
+       
+       -- Load up environment with helper objects
+       for name, t in pairs(def_env) do
+               if type(t) == "table" then
+                       session.env[name] = setmetatable({ session = session }, { __index = t });
+               end
+       end
+       
+       return session;
+end
+
+local sessions = {};
+
+function console_listener.onconnect(conn)
+       -- Handle new connection
+       local session = console:new_session(conn);
+       sessions[conn] = session;
+       printbanner(session);
+       session.send(string.char(0));
+end
+
+function console_listener.onincoming(conn, data)
+       local session = sessions[conn];
+
+       -- Handle data
+       (function(session, data)
+               local useglobalenv;
+               
+               if data:match("^>") then
+                       data = data:gsub("^>", "");
+                       useglobalenv = true;
+               elseif data == "\004" then
+                       commands["bye"](session, data);
+                       return;
+               else
+                       local command = data:lower();
+                       command = data:match("^%w+") or data:match("%p");
+                       if commands[command] then
+                               commands[command](session, data);
+                               return;
+                       end
+               end
+
+               session.env._ = data;
+               
+               local chunkname = "=console";
+               local chunk, err = loadstring("return "..data, chunkname);
+               if not chunk then
+                       chunk, err = loadstring(data, chunkname);
+                       if not chunk then
+                               err = err:gsub("^%[string .-%]:%d+: ", "");
+                               err = err:gsub("^:%d+: ", "");
+                               err = err:gsub("'<eof>'", "the end of the line");
+                               session.print("Sorry, I couldn't understand that... "..err);
+                               return;
+                       end
+               end
+               
+               setfenv(chunk, (useglobalenv and redirect_output(_G, session)) or session.env or nil);
+               
+               local ranok, taskok, message = pcall(chunk);
+               
+               if not (ranok or message or useglobalenv) and commands[data:lower()] then
+                       commands[data:lower()](session, data);
+                       return;
+               end
+               
+               if not ranok then
+                       session.print("Fatal error while running command, it did not complete");
+                       session.print("Error: "..taskok);
+                       return;
+               end
+               
+               if not message then
+                       session.print("Result: "..tostring(taskok));
+                       return;
+               elseif (not taskok) and message then
+                       session.print("Command completed with a problem");
+                       session.print("Message: "..tostring(message));
+                       return;
+               end
+               
+               session.print("OK: "..tostring(message));
+       end)(session, data);
+       
+       session.send(string.char(0));
+end
+
+function console_listener.ondisconnect(conn, err)
+       local session = sessions[conn];
+       if session then
+               session.disconnect();
+               sessions[conn] = nil;
+       end
+end
+
+connlisteners_register('console', console_listener);
+
+-- Console commands --
+-- These are simple commands, not valid standalone in Lua
+
+function commands.bye(session)
+       session.print("See you! :)");
+       session.disconnect();
+end
+commands.quit, commands.exit = commands.bye, commands.bye;
+
+commands["!"] = function (session, data)
+       if data:match("^!!") then
+               session.print("!> "..session.env._);
+               return console_listener.onincoming(session.conn, session.env._);
+       end
+       local old, new = data:match("^!(.-[^\\])!(.-)!$");
+       if old and new then
+               local ok, res = pcall(string.gsub, session.env._, old, new);
+               if not ok then
+                       session.print(res)
+                       return;
+               end
+               session.print("!> "..res);
+               return console_listener.onincoming(session.conn, res);
+       end
+       session.print("Sorry, not sure what you want");
+end
+
+function commands.help(session, data)
+       local print = session.print;
+       local section = data:match("^help (%w+)");
+       if not section then
+               print [[Commands are divided into multiple sections. For help on a particular section, ]]
+               print [[type: help SECTION (for example, 'help c2s'). Sections are: ]]
+               print [[]]
+               print [[c2s - Commands to manage local client-to-server sessions]]
+               print [[s2s - Commands to manage sessions between this server and others]]
+               print [[module - Commands to load/reload/unload modules/plugins]]
+               print [[server - Uptime, version, shutting down, etc.]]
+               print [[config - Reloading the configuration, etc.]]
+               print [[console - Help regarding the console itself]]
+       elseif section == "c2s" then
+               print [[c2s:show(jid) - Show all client sessions with the specified JID (or all if no JID given)]]
+               print [[c2s:show_insecure() - Show all unencrypted client connections]]
+               print [[c2s:show_secure() - Show all encrypted client connections]]
+               print [[c2s:close(jid) - Close all sessions for the specified JID]]
+       elseif section == "s2s" then
+               print [[s2s:show(domain) - Show all s2s connections for the given domain (or all if no domain given)]]
+               print [[s2s:close(from, to) - Close a connection from one domain to another]]
+       elseif section == "module" then
+               print [[module:load(module, host) - Load the specified module on the specified host (or all hosts if none given)]]
+               print [[module:reload(module, host) - The same, but unloads and loads the module (saving state if the module supports it)]]
+               print [[module:unload(module, host) - The same, but just unloads the module from memory]]
+               print [[module:list(host) - List the modules loaded on the specified host]]
+       elseif section == "server" then
+               print [[server:version() - Show the server's version number]]
+               print [[server:uptime() - Show how long the server has been running]]
+               print [[server:shutdown(reason) - Shut down the server, with an optional reason to be broadcast to all connections]]
+       elseif section == "config" then
+               print [[config:reload() - Reload the server configuration. Modules may need to be reloaded for changes to take effect.]]
+       elseif section == "console" then
+               print [[Hey! Welcome to Prosody's admin console.]]
+               print [[First thing, if you're ever wondering how to get out, simply type 'quit'.]]
+               print [[Secondly, note that we don't support the full telnet protocol yet (it's coming)]]
+               print [[so you may have trouble using the arrow keys, etc. depending on your system.]]
+               print [[]]
+               print [[For now we offer a couple of handy shortcuts:]]
+               print [[!! - Repeat the last command]]
+               print [[!old!new! - repeat the last command, but with 'old' replaced by 'new']]
+               print [[]]
+               print [[For those well-versed in Prosody's internals, or taking instruction from those who are,]]
+               print [[you can prefix a command with > to escape the console sandbox, and access everything in]]
+               print [[the running server. Great fun, but be careful not to break anything :)]]
+       end
+       print [[]]
+end
+
+-- Session environment --
+-- Anything in def_env will be accessible within the session as a global variable
+
+def_env.server = {};
+
+function def_env.server:insane_reload()
+       prosody.unlock_globals();
+       dofile "prosody"
+       prosody = _G.prosody;
+       return true, "Server reloaded";
+end
+
+function def_env.server:version()
+       return true, tostring(prosody.version or "unknown");
+end
+
+function def_env.server:uptime()
+       local t = os.time()-prosody.start_time;
+       local seconds = t%60;
+       t = (t - seconds)/60;
+       local minutes = t%60;
+       t = (t - minutes)/60;
+       local hours = t%24;
+       t = (t - hours)/24;
+       local days = t;
+       return true, string.format("This server has been running for %d day%s, %d hour%s and %d minute%s (since %s)", 
+               days, (days ~= 1 and "s") or "", hours, (hours ~= 1 and "s") or "", 
+               minutes, (minutes ~= 1 and "s") or "", os.date("%c", prosody.start_time));
+end
+
+function def_env.server:shutdown(reason)
+       prosody.shutdown(reason);
+       return true, "Shutdown initiated";
+end
+
+def_env.module = {};
+
+local function get_hosts_set(hosts, module)
+       if type(hosts) == "table" then
+               if hosts[1] then
+                       return set.new(hosts);
+               elseif hosts._items then
+                       return hosts;
+               end
+       elseif type(hosts) == "string" then
+               return set.new { hosts };
+       elseif hosts == nil then
+               local mm = require "modulemanager";
+               return set.new(array.collect(keys(prosody.hosts)))
+                       / function (host) return prosody.hosts[host].type == "local" or module and mm.is_loaded(host, module); end;
+       end
+end
+
+function def_env.module:load(name, hosts, config)
+       local mm = require "modulemanager";
+       
+       hosts = get_hosts_set(hosts);
+       
+       -- Load the module for each host
+       local ok, err, count = true, nil, 0;
+       for host in hosts do
+               if (not mm.is_loaded(host, name)) then
+                       ok, err = mm.load(host, name, config);
+                       if not ok then
+                               ok = false;
+                               self.session.print(err or "Unknown error loading module");
+                       else
+                               count = count + 1;
+                               self.session.print("Loaded for "..host);
+                       end
+               end
+       end
+       
+       return ok, (ok and "Module loaded onto "..count.." host"..(count ~= 1 and "s" or "")) or ("Last error: "..tostring(err));       
+end
+
+function def_env.module:unload(name, hosts)
+       local mm = require "modulemanager";
+
+       hosts = get_hosts_set(hosts, name);
+       
+       -- Unload the module for each host
+       local ok, err, count = true, nil, 0;
+       for host in hosts do
+               if mm.is_loaded(host, name) then
+                       ok, err = mm.unload(host, name);
+                       if not ok then
+                               ok = false;
+                               self.session.print(err or "Unknown error unloading module");
+                       else
+                               count = count + 1;
+                               self.session.print("Unloaded from "..host);
+                       end
+               end
+       end
+       return ok, (ok and "Module unloaded from "..count.." host"..(count ~= 1 and "s" or "")) or ("Last error: "..tostring(err));
+end
+
+function def_env.module:reload(name, hosts)
+       local mm = require "modulemanager";
+
+       hosts = get_hosts_set(hosts, name);
+       
+       -- Reload the module for each host
+       local ok, err, count = true, nil, 0;
+       for host in hosts do
+               if mm.is_loaded(host, name) then
+                       ok, err = mm.reload(host, name);
+                       if not ok then
+                               ok = false;
+                               self.session.print(err or "Unknown error reloading module");
+                       else
+                               count = count + 1;
+                               if ok == nil then
+                                       ok = true;
+                               end
+                               self.session.print("Reloaded on "..host);
+                       end
+               end
+       end
+       return ok, (ok and "Module reloaded on "..count.." host"..(count ~= 1 and "s" or "")) or ("Last error: "..tostring(err));
+end
+
+function def_env.module:list(hosts)
+       if hosts == nil then
+               hosts = array.collect(keys(prosody.hosts));
+       end
+       if type(hosts) == "string" then
+               hosts = { hosts };
+       end
+       if type(hosts) ~= "table" then
+               return false, "Please supply a host or a list of hosts you would like to see";
+       end
+       
+       local print = self.session.print;
+       for _, host in ipairs(hosts) do
+               print(host..":");
+               local modules = array.collect(keys(prosody.hosts[host] and prosody.hosts[host].modules or {})):sort();
+               if #modules == 0 then
+                       if prosody.hosts[host] then
+                               print("    No modules loaded");
+                       else
+                               print("    Host not found");
+                       end
+               else
+                       for _, name in ipairs(modules) do
+                               print("    "..name);
+                       end
+               end
+       end
+end
+
+def_env.config = {};
+function def_env.config:load(filename, format)
+       local config_load = require "core.configmanager".load;
+       local ok, err = config_load(filename, format);
+       if not ok then
+               return false, err or "Unknown error loading config";
+       end
+       return true, "Config loaded";
+end
+
+function def_env.config:get(host, section, key)
+       local config_get = require "core.configmanager".get
+       return true, tostring(config_get(host, section, key));
+end
+
+function def_env.config:reload()
+       local ok, err = prosody.reload_config();
+       return ok, (ok and "Config reloaded (you may need to reload modules to take effect)") or tostring(err);
+end
+
+def_env.hosts = {};
+function def_env.hosts:list()
+       for host, host_session in pairs(hosts) do
+               self.session.print(host);
+       end
+       return true, "Done";
+end
+
+function def_env.hosts:add(name)
+end
+
+def_env.c2s = {};
+
+local function show_c2s(callback)
+       for hostname, host in pairs(hosts) do
+               for username, user in pairs(host.sessions or {}) do
+                       for resource, session in pairs(user.sessions or {}) do
+                               local jid = username.."@"..hostname.."/"..resource;
+                               callback(jid, session);
+                       end
+               end
+       end
+end
+
+function def_env.c2s:show(match_jid)
+       local print, count = self.session.print, 0;
+       local curr_host;
+       show_c2s(function (jid, session)
+               if curr_host ~= session.host then
+                       curr_host = session.host;
+                       print(curr_host);
+               end
+               if (not match_jid) or jid:match(match_jid) then
+                       count = count + 1;
+                       local status, priority = "unavailable", tostring(session.priority or "-");
+                       if session.presence then
+                               status = session.presence:child_with_name("show");
+                               if status then
+                                       status = status:get_text() or "[invalid!]";
+                               else
+                                       status = "available";
+                               end
+                       end
+                       print("   "..jid.." - "..status.."("..priority..")");
+               end             
+       end);
+       return true, "Total: "..count.." clients";
+end
+
+function def_env.c2s:show_insecure(match_jid)
+       local print, count = self.session.print, 0;
+       show_c2s(function (jid, session)
+               if ((not match_jid) or jid:match(match_jid)) and not session.secure then
+                       count = count + 1;
+                       print(jid);
+               end             
+       end);
+       return true, "Total: "..count.." insecure client connections";
+end
+
+function def_env.c2s:show_secure(match_jid)
+       local print, count = self.session.print, 0;
+       show_c2s(function (jid, session)
+               if ((not match_jid) or jid:match(match_jid)) and session.secure then
+                       count = count + 1;
+                       print(jid);
+               end             
+       end);
+       return true, "Total: "..count.." secure client connections";
+end
+
+function def_env.c2s:close(match_jid)
+       local print, count = self.session.print, 0;
+       show_c2s(function (jid, session)
+               if jid == match_jid or jid_bare(jid) == match_jid then
+                       count = count + 1;
+                       session:close();
+               end
+       end);
+       return true, "Total: "..count.." sessions closed";
+end
+
+def_env.s2s = {};
+function def_env.s2s:show(match_jid)
+       local _print = self.session.print;
+       local print = self.session.print;
+       
+       local count_in, count_out = 0,0;
+       
+       for host, host_session in pairs(hosts) do
+               print = function (...) _print(host); _print(...); print = _print; end
+               for remotehost, session in pairs(host_session.s2sout) do
+                       if (not match_jid) or remotehost:match(match_jid) or host:match(match_jid) then
+                               count_out = count_out + 1;
+                               print("    "..host.." -> "..remotehost..(session.secure and " (encrypted)" or "")..(session.compressed and " (compressed)" or ""));
+                               if session.sendq then
+                                       print("        There are "..#session.sendq.." queued outgoing stanzas for this connection");
+                               end
+                               if session.type == "s2sout_unauthed" then
+                                       if session.connecting then
+                                               print("        Connection not yet established");
+                                               if not session.srv_hosts then
+                                                       if not session.conn then
+                                                               print("        We do not yet have a DNS answer for this host's SRV records");
+                                                       else
+                                                               print("        This host has no SRV records, using A record instead");
+                                                       end
+                                               elseif session.srv_choice then
+                                                       print("        We are on SRV record "..session.srv_choice.." of "..#session.srv_hosts);
+                                                       local srv_choice = session.srv_hosts[session.srv_choice];
+                                                       print("        Using "..(srv_choice.target or ".")..":"..(srv_choice.port or 5269));
+                                               end
+                                       elseif session.notopen then
+                                               print("        The <stream> has not yet been opened");
+                                       elseif not session.dialback_key then
+                                               print("        Dialback has not been initiated yet");
+                                       elseif session.dialback_key then
+                                               print("        Dialback has been requested, but no result received");
+                                       end
+                               end
+                       end
+               end     
+               local subhost_filter = function (h) 
+                               return (match_jid and h:match(match_jid));
+                       end
+               for session in pairs(incoming_s2s) do
+                       if session.to_host == host and ((not match_jid) or host:match(match_jid) 
+                               or (session.from_host and session.from_host:match(match_jid))
+                               -- Pft! is what I say to list comprehensions
+                               or (session.hosts and #array.collect(keys(session.hosts)):filter(subhost_filter)>0)) then
+                               count_in = count_in + 1;
+                               print("    "..host.." <- "..(session.from_host or "(unknown)")..(session.secure and " (encrypted)" or "")..(session.compressed and " (compressed)" or ""));
+                               if session.type == "s2sin_unauthed" then
+                                               print("        Connection not yet authenticated");
+                               end
+                               for name in pairs(session.hosts) do
+                                       if name ~= session.from_host then
+                                               print("        also hosts "..tostring(name));
+                                       end
+                               end
+                       end
+               end
+               
+               print = _print;
+       end
+       
+       for session in pairs(incoming_s2s) do
+               if not session.to_host and ((not match_jid) or session.from_host and session.from_host:match(match_jid)) then
+                       count_in = count_in + 1;
+                       print("Other incoming s2s connections");
+                       print("    (unknown) <- "..(session.from_host or "(unknown)"));                 
+               end
+       end
+       
+       return true, "Total: "..count_out.." outgoing, "..count_in.." incoming connections";
+end
+
+function def_env.s2s:close(from, to)
+       local print, count = self.session.print, 0;
+       
+       if not (from and to) then
+               return false, "Syntax: s2s:close('from', 'to') - Closes all s2s sessions from 'from' to 'to'";
+       elseif from == to then
+               return false, "Both from and to are the same... you can't do that :)";
+       end
+       
+       if hosts[from] and not hosts[to] then
+               -- Is an outgoing connection
+               local session = hosts[from].s2sout[to];
+               if not session then 
+                       print("No outgoing connection from "..from.." to "..to)
+               else
+                       (session.close or s2smanager.destroy_session)(session);
+                       count = count + 1;
+                       print("Closed outgoing session from "..from.." to "..to);
+               end
+       elseif hosts[to] and not hosts[from] then
+               -- Is an incoming connection
+               for session in pairs(incoming_s2s) do
+                       if session.to_host == to and session.from_host == from then
+                               (session.close or s2smanager.destroy_session)(session);
+                               count = count + 1;
+                       end
+               end
+               
+               if count == 0 then
+                       print("No incoming connections from "..from.." to "..to);
+               else
+                       print("Closed "..count.." incoming session"..((count == 1 and "") or "s").." from "..from.." to "..to);
+               end
+       elseif hosts[to] and hosts[from] then
+               return false, "Both of the hostnames you specified are local, there are no s2s sessions to close";
+       else
+               return false, "Neither of the hostnames you specified are being used on this server";
+       end
+       
+       return true, "Closed "..count.." s2s session"..((count == 1 and "") or "s");
+end
+
+def_env.host = {}; def_env.hosts = def_env.host;
+function def_env.host:activate(hostname, config)
+       local hostmanager_activate = require "core.hostmanager".activate;
+       if hosts[hostname] then
+               return false, "The host "..tostring(hostname).." is already activated";
+       end
+       
+       local defined_hosts = config or configmanager.getconfig();
+       if not config and not defined_hosts[hostname] then
+               return false, "Couldn't find "..tostring(hostname).." defined in the config, perhaps you need to config:reload()?";
+       end
+       hostmanager_activate(hostname, config or defined_hosts[hostname]);
+       return true, "Host "..tostring(hostname).." activated";
+end
+
+function def_env.host:deactivate(hostname, reason)
+       local hostmanager_deactivate = require "core.hostmanager".deactivate;
+       local host = hosts[hostname];
+       if not host then
+               return false, "The host "..tostring(hostname).." is not activated";
+       end
+       if reason then
+               reason = { condition = "host-gone", text = reason };
+       end
+       hostmanager_deactivate(hostname, reason);
+       return true, "Host "..tostring(hostname).." deactivated";
+end
+
+function def_env.host:list()
+       local print = self.session.print;
+       local i = 0;
+       for host in values(array.collect(keys(prosody.hosts)):sort()) do
+               i = i + 1;
+               print(host);
+       end
+       return true, i.." hosts";
+end
+
+-------------
+
+function printbanner(session)
+       local option = config.get("*", "core", "console_banner");
+if option == nil or option == "full" or option == "graphic" then
+session.print [[
+                   ____                \   /     _       
+                    |  _ \ _ __ ___  ___  _-_   __| |_   _ 
+                    | |_) | '__/ _ \/ __|/ _ \ / _` | | | |
+                    |  __/| | | (_) \__ \ |_| | (_| | |_| |
+                    |_|   |_|  \___/|___/\___/ \__,_|\__, |
+                    A study in simplicity            |___/ 
+
+]]
+end
+if option == nil or option == "short" or option == "full" then
+session.print("Welcome to the Prosody administration console. For a list of commands, type: help");
+session.print("You may find more help on using this console in our online documentation at ");
+session.print("http://prosody.im/doc/console\n");
+end
+if option and option ~= "short" and option ~= "full" and option ~= "graphic" then
+       if type(option) == "string" then
+               session.print(option)
+       elseif type(option) == "function" then
+               setfenv(option, redirect_output(_G, session));
+               pcall(option, session);
+       end
+end
+end
+
+prosody.net_activate_ports("console", "console", {5582}, "tcp");
index 907ca7531c6561af5e4128035a77a9f42edfd041..ee0043f1d223455e9637c8aac00b0792e73dfe83 100644 (file)
@@ -6,12 +6,11 @@
 -- COPYING file in the source package for more information.
 --
 
-local get_children = require "core.hostmanager".get_children;
+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 calculate_hash = require "util.caps".calculate_hash;
 
 local disco_items = module:get_option("disco_items") or {};
 do -- validate disco_items
@@ -36,63 +35,27 @@ module:add_identity("server", "im", "Prosody"); -- FIXME should be in the non-ex
 module:add_feature("http://jabber.org/protocol/disco#info");
 module:add_feature("http://jabber.org/protocol/disco#items");
 
--- Generate and cache disco result and caps hash
-local _cached_server_disco_info, _cached_server_caps_feature, _cached_server_caps_hash;
-local function build_server_disco_info()
-       local query = st.stanza("query", { xmlns = "http://jabber.org/protocol/disco#info" });
+module:hook("iq/host/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 reply = st.reply(stanza):query("http://jabber.org/protocol/disco#info");
        local done = {};
        for _,identity in ipairs(module:get_host_items("identity")) do
                local identity_s = identity.category.."\0"..identity.type;
                if not done[identity_s] then
-                       query:tag("identity", identity):up();
+                       reply:tag("identity", identity):up();
                        done[identity_s] = true;
                end
        end
        for _,feature in ipairs(module:get_host_items("feature")) do
                if not done[feature] then
-                       query:tag("feature", {var=feature}):up();
+                       reply:tag("feature", {var=feature}):up();
                        done[feature] = true;
                end
        end
-       _cached_server_disco_info = query;
-       _cached_server_caps_hash = calculate_hash(query);
-       _cached_server_caps_feature = st.stanza("c", {
-               xmlns = "http://jabber.org/protocol/caps";
-               hash = "sha-1";
-               node = "http://prosody.im";
-               ver = _cached_server_caps_hash;
-       });
-end
-local function clear_disco_cache()
-       _cached_server_disco_info, _cached_server_caps_feature, _cached_server_caps_hash = nil, nil, nil;
-end
-local function get_server_disco_info()
-       if not _cached_server_disco_info then build_server_disco_info(); end
-       return _cached_server_disco_info;
-end
-local function get_server_caps_feature()
-       if not _cached_server_caps_feature then build_server_disco_info(); end
-       return _cached_server_caps_feature;
-end
-local function get_server_caps_hash()
-       if not _cached_server_caps_hash then build_server_disco_info(); end
-       return _cached_server_caps_hash;
-end
-
-module:hook("item-added/identity", clear_disco_cache);
-module:hook("item-added/feature", clear_disco_cache);
-module:hook("item-removed/identity", clear_disco_cache);
-module:hook("item-removed/feature", clear_disco_cache);
-
--- Handle disco requests to the server
-module:hook("iq/host/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 ~= "" and node ~= "http://prosody.im#"..get_server_caps_hash() then return; end -- TODO fire event?
-       local reply_query = get_server_disco_info();
-       reply_query.node = node;
-       local reply = st.reply(stanza):add_child(reply_query);
        origin.send(reply);
        return true;
 end);
@@ -103,7 +66,7 @@ module:hook("iq/host/http://jabber.org/protocol/disco#items:query", function(eve
        if node and node ~= "" then return; end -- TODO fire event?
 
        local reply = st.reply(stanza):query("http://jabber.org/protocol/disco#items");
-       for jid in pairs(get_children(module.host)) do
+       for jid in pairs(componentmanager_get_children(module.host)) do
                reply:tag("item", {jid = jid}):up();
        end
        for _, item in ipairs(disco_items) do
@@ -112,15 +75,6 @@ module:hook("iq/host/http://jabber.org/protocol/disco#items:query", function(eve
        origin.send(reply);
        return true;
 end);
-
--- Handle caps stream feature
-module:hook("stream-features", function (event)
-       if event.origin.type == "c2s" then
-               event.features:add_child(get_server_caps_feature());
-       end
-end);
-
--- Handle disco requests to user accounts
 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
@@ -130,7 +84,7 @@ module:hook("iq/bare/http://jabber.org/protocol/disco#info:query", function(even
        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", { origin = origin, stanza = reply });
+               module:fire_event("account-disco-info", { session = origin, stanza = reply });
                origin.send(reply);
                return true;
        end
@@ -144,7 +98,7 @@ module:hook("iq/bare/http://jabber.org/protocol/disco#items:query", function(eve
        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", { origin = origin, stanza = reply });
+               module:fire_event("account-disco-items", { session = origin, stanza = reply });
                origin.send(reply);
                return true;
        end
index 7a876f1d6845a67f4e69f57d4564cdd73e086176..5f821cbc28e768f65c3794c66e3d61c51d42f476 100644 (file)
@@ -29,9 +29,6 @@ function inject_roster_contacts(username, host, roster)
                        if jid ~= bare_jid then
                                if not roster[jid] then roster[jid] = {}; end
                                roster[jid].subscription = "both";
-                               if groups[group_name][jid] then
-                                       roster[jid].name = groups[group_name][jid];
-                               end
                                if not roster[jid].groups then
                                        roster[jid].groups = { [group_name] = true };
                                end
@@ -103,13 +100,10 @@ function module.load()
                        groups[curr_group] = groups[curr_group] or {};
                else
                        -- Add JID
-                       local entryjid, name = line:match("([^=]*)=?(.*)");
-                       module:log("debug", "entryjid = '%s', name = '%s'", entryjid, name);
-                       local jid;
-                       jid = jid_prep(entryjid:match("%S+"));
+                       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] = name or false;
+                               groups[curr_group][jid] = true;
                                members[jid] = members[jid] or {};
                                members[jid][#members[jid]+1] = curr_group;
                        end
index 654aff067ce5d894078f37ac381888312e83e3f7..c55bd20f9b5b56c5c6e1c7ced8f32bc585fd3228 100644 (file)
@@ -8,11 +8,9 @@
 
 
 local httpserver = require "net.httpserver";
-local lfs = require "lfs";
 
 local open = io.open;
 local t_concat = table.concat;
-local stat = lfs.attributes;
 
 local http_base = config.get("*", "core", "http_path") or "www_files";
 
@@ -50,14 +48,7 @@ local function preprocess_path(path)
 end
 
 function serve_file(path)
-       local full_path = http_base..path;
-       if stat(full_path, "mode") == "directory" then
-               if stat(full_path.."/index.html", "mode") == "file" then
-                       return serve_file(path.."/index.html");
-               end
-               return response_403;
-       end
-       local f, err = open(full_path, "rb");
+       local f, err = open(http_base..path, "rb");
        if not f then return response_404; end
        local data = f:read("*a");
        f:close();
index 484a1f8fe24b490f8e2b6f612d93a64b7b40e2f8..b3001fe5139b633c0f8edec60fa3fc0bb1429b80 100644 (file)
@@ -9,70 +9,70 @@
 
 local st = require "util.stanza";
 local jid_split = require "util.jid".split;
+local user_exists = require "core.usermanager".user_exists;
 
 local full_sessions = full_sessions;
 local bare_sessions = bare_sessions;
 
-if module:get_host_type() == "local" then
-       module:hook("iq/full", function(data)
-               -- IQ to full JID recieved
-               local origin, stanza = data.origin, data.stanza;
+module:hook("iq/full", function(data)
+       -- IQ to full JID recieved
+       local origin, stanza = data.origin, data.stanza;
 
-               local session = full_sessions[stanza.attr.to];
-               if session then
-                       -- TODO fire post processing event
-                       session.send(stanza);
-               else -- resource not online
-                       if stanza.attr.type == "get" or stanza.attr.type == "set" then
-                               origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
-                       end
+       local session = full_sessions[stanza.attr.to];
+       if session then
+               -- TODO fire post processing event
+               session.send(stanza);
+       else -- resource not online
+               if stanza.attr.type == "get" or stanza.attr.type == "set" then
+                       origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
                end
-               return true;
-       end);
-end
+       end
+       return true;
+end);
 
 module:hook("iq/bare", function(data)
        -- IQ to bare JID recieved
        local origin, stanza = data.origin, data.stanza;
-       local type = stanza.attr.type;
 
+       local to = stanza.attr.to;
+       if to and not bare_sessions[to] then -- quick check for account existance
+               local node, host = jid_split(to);
+               if not user_exists(node, host) then -- full check for account existance
+                       if stanza.attr.type == "get" or stanza.attr.type == "set" then
+                               origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
+                       end
+                       return true;
+               end
+       end
        -- TODO fire post processing events
-       if type == "get" or type == "set" then
-               local child = stanza.tags[1];
-               local ret = module:fire_event("iq/bare/"..child.attr.xmlns..":"..child.name, data);
-               if ret ~= nil then return ret; end
-               return module:fire_event("iq-"..type.."/bare/"..child.attr.xmlns..":"..child.name, data);
+       if stanza.attr.type == "get" or stanza.attr.type == "set" then
+               return module:fire_event("iq/bare/"..stanza.tags[1].attr.xmlns..":"..stanza.tags[1].name, data);
        else
-               return module:fire_event("iq-"..type.."/bare/"..stanza.attr.id, data);
+               module:fire_event("iq/bare/"..stanza.attr.id, data);
+               return true;
        end
 end);
 
 module:hook("iq/self", function(data)
-       -- IQ to self JID recieved
+       -- IQ to bare JID recieved
        local origin, stanza = data.origin, data.stanza;
-       local type = stanza.attr.type;
 
-       if type == "get" or type == "set" then
-               local child = stanza.tags[1];
-               local ret = module:fire_event("iq/self/"..child.attr.xmlns..":"..child.name, data);
-               if ret ~= nil then return ret; end
-               return module:fire_event("iq-"..type.."/self/"..child.attr.xmlns..":"..child.name, data);
+       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
-               return module:fire_event("iq-"..type.."/self/"..stanza.attr.id, data);
+               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;
-       local type = stanza.attr.type;
 
-       if type == "get" or type == "set" then
-               local child = stanza.tags[1];
-               local ret = module:fire_event("iq/host/"..child.attr.xmlns..":"..child.name, data);
-               if ret ~= nil then return ret; end
-               return module:fire_event("iq-"..type.."/host/"..child.attr.xmlns..":"..child.name, data);
+       if stanza.attr.type == "get" or stanza.attr.type == "set" then
+               return module:fire_event("iq/host/"..stanza.tags[1].attr.xmlns..":"..stanza.tags[1].name, data);
        else
-               return module:fire_event("iq-"..type.."/host/"..stanza.attr.id, data);
+               module:fire_event("iq/host/"..stanza.attr.id, data);
+               return true;
        end
 end);
index a47f0223d546f4b2a533dc56a4feb94343e27184..0134d736d831440479573d42e5a5b91d046af44a 100644 (file)
@@ -11,9 +11,7 @@
 local st = require "util.stanza";
 local t_concat = table.concat;
 
-local secure_auth_only = module:get_option("c2s_require_encryption")
-       or module:get_option("require_encryption")
-       or not(module:get_option("allow_unencrypted_plain_auth"));
+local secure_auth_only = module:get_option("c2s_require_encryption") or module:get_option("require_encryption");
 
 local sessionmanager = require "core.sessionmanager";
 local usermanager = require "core.usermanager";
@@ -31,53 +29,47 @@ module:hook("stream-features", function(event)
        end
 end);
 
-module:hook("stanza/iq/jabber:iq:auth:query", function(event)
-       local session, stanza = event.origin, event.stanza;
-
-       if session.type ~= "c2s_unauthed" then
-               session.send(st.error_reply(stanza, "cancel", "service-unavailable", "Legacy authentication is only allowed for unauthenticated client connections."));
-               return true;
-       end
-
-       if secure_auth_only and not session.secure then
-               session.send(st.error_reply(stanza, "modify", "not-acceptable", "Encryption (SSL or TLS) is required to connect to this server"));
-               return true;
-       end
-       
-       local username = stanza.tags[1]:child_with_name("username");
-       local password = stanza.tags[1]:child_with_name("password");
-       local resource = stanza.tags[1]:child_with_name("resource");
-       if not (username and password and resource) then
-               local reply = st.reply(stanza);
-               session.send(reply:query("jabber:iq:auth")
-                       :tag("username"):up()
-                       :tag("password"):up()
-                       :tag("resource"):up());
-       else
-               username, password, resource = t_concat(username), t_concat(password), t_concat(resource);
-               username = nodeprep(username);
-               resource = resourceprep(resource)
-               local reply = st.reply(stanza);
-               if usermanager.test_password(username, session.host, password) then
-                       -- Authentication successful!
-                       local success, err = sessionmanager.make_authenticated(session, username);
-                       if success then
-                               local err_type, err_msg;
-                               success, err_type, err, err_msg = sessionmanager.bind_resource(session, resource);
-                               if not success then
-                                       session.send(st.error_reply(stanza, err_type, err, err_msg));
-                                       session.username, session.type = nil, "c2s_unauthed"; -- FIXME should this be placed in sessionmanager?
-                                       return true;
-                               elseif resource ~= session.resource then -- server changed resource, not supported by legacy auth
-                                       session.send(st.error_reply(stanza, "cancel", "conflict", "The requested resource could not be assigned to this session."));
-                                       session:close(); -- FIXME undo resource bind and auth instead of closing the session?
-                                       return true;
+module:add_iq_handler("c2s_unauthed", "jabber:iq:auth", 
+               function (session, stanza)
+                       if secure_auth_only and not session.secure then
+                               session.send(st.error_reply(stanza, "modify", "not-acceptable", "Encryption (SSL or TLS) is required to connect to this server"));
+                               return true;
+                       end
+                       
+                       local username = stanza.tags[1]:child_with_name("username");
+                       local password = stanza.tags[1]:child_with_name("password");
+                       local resource = stanza.tags[1]:child_with_name("resource");
+                       if not (username and password and resource) then
+                               local reply = st.reply(stanza);
+                               session.send(reply:query("jabber:iq:auth")
+                                       :tag("username"):up()
+                                       :tag("password"):up()
+                                       :tag("resource"):up());
+                       else
+                               username, password, resource = t_concat(username), t_concat(password), t_concat(resource);
+                               username = nodeprep(username);
+                               resource = resourceprep(resource)
+                               local reply = st.reply(stanza);
+                               if usermanager.validate_credentials(session.host, username, password) then
+                                       -- Authentication successful!
+                                       local success, err = sessionmanager.make_authenticated(session, username);
+                                       if success then
+                                               local err_type, err_msg;
+                                               success, err_type, err, err_msg = sessionmanager.bind_resource(session, resource);
+                                               if not success then
+                                                       session.send(st.error_reply(stanza, err_type, err, err_msg));
+                                                       session.username, session.type = nil, "c2s_unauthed"; -- FIXME should this be placed in sessionmanager?
+                                                       return true;
+                                               elseif resource ~= session.resource then -- server changed resource, not supported by legacy auth
+                                                       session.send(st.error_reply(stanza, "cancel", "conflict", "The requested resource could not be assigned to this session."));
+                                                       session:close(); -- FIXME undo resource bind and auth instead of closing the session?
+                                                       return true;
+                                               end
+                                       end
+                                       session.send(st.reply(stanza));
+                               else
+                                       session.send(st.error_reply(stanza, "auth", "not-authorized"));
                                end
                        end
-                       session.send(st.reply(stanza));
-               else
-                       session.send(st.error_reply(stanza, "auth", "not-authorized"));
-               end
-       end
-       return true;
-end);
+                       return true;
+               end);
index bd6f4b29b8a89e6139d826149ddda18eee4795d3..aa46d2d31807bb526a595b4e6d4da1fd756141c6 100644 (file)
@@ -16,7 +16,9 @@ local is_contact_subscribed = require "core.rostermanager".is_contact_subscribed
 local pairs, ipairs = pairs, ipairs;
 local next = next;
 local type = type;
-local calculate_hash = require "util.caps".calculate_hash;
+local load_roster = require "core.rostermanager".load_roster;
+local sha1 = require "util.hashes".sha1;
+local base64 = require "util.encodings".base64.encode;
 
 local NULL = {};
 local data = {};
@@ -38,8 +40,8 @@ module:add_feature("http://jabber.org/protocol/pubsub#publish");
 local function subscription_presence(user_bare, recipient)
        local recipient_bare = jid_bare(recipient);
        if (recipient_bare == user_bare) then return true end
-       local username, host = jid_split(user_bare);
-       return is_contact_subscribed(username, host, recipient_bare);
+       local item = load_roster(jid_split(user_bare))[recipient_bare];
+       return item and (item.subscription == 'from' or item.subscription == 'both');
 end
 
 local function publish(session, node, id, item)
@@ -116,44 +118,25 @@ module:hook("presence/bare", function(event)
        -- inbound presence to bare JID recieved
        local origin, stanza = event.origin, event.stanza;
        local user = stanza.attr.to or (origin.username..'@'..origin.host);
-       local t = stanza.attr.type;
-       local self = not stanza.attr.to;
 
-       if not t then -- available presence
-               if self or subscription_presence(user, stanza.attr.from) then
-                       local recipient = stanza.attr.from;
-                       local current = recipients[user] and recipients[user][recipient];
-                       local hash = get_caps_hash_from_presence(stanza, current);
-                       if current == hash or (current and current == hash_map[hash]) then return; end
-                       if not hash then
-                               if recipients[user] then recipients[user][recipient] = nil; end
+       if not stanza.attr.to or subscription_presence(user, stanza.attr.from) then
+               local recipient = stanza.attr.from;
+               local current = recipients[user] and recipients[user][recipient];
+               local hash = get_caps_hash_from_presence(stanza, current);
+               if current == hash then return; end
+               if not hash then
+                       if recipients[user] then recipients[user][recipient] = nil; end
+               else
+                       recipients[user] = recipients[user] or {};
+                       if hash_map[hash] then
+                               recipients[user][recipient] = hash_map[hash];
+                               publish_all(user, recipient, origin);
                        else
-                               recipients[user] = recipients[user] or {};
-                               if hash_map[hash] then
-                                       recipients[user][recipient] = hash_map[hash];
-                                       publish_all(user, recipient, origin);
-                               else
-                                       recipients[user][recipient] = hash;
-                                       local from_bare = origin.type == "c2s" and origin.username.."@"..origin.host;
-                                       if self or origin.type ~= "c2s" or (recipients[from_bare] and recipients[from_bare][origin.full_jid]) ~= hash then
-                                               origin.send(
-                                                       st.stanza("iq", {from=stanza.attr.to, to=stanza.attr.from, id="disco", type="get"})
-                                                               :query("http://jabber.org/protocol/disco#info")
-                                               );
-                                       end
-                               end
-                       end
-               end
-       elseif t == "unavailable" then
-               if recipients[user] then recipients[user][stanza.attr.from] = nil; end
-       elseif not self and t == "unsubscribe" then
-               local from = jid_bare(stanza.attr.from);
-               local subscriptions = recipients[user];
-               if subscriptions then
-                       for subscriber in pairs(subscriptions) do
-                               if jid_bare(subscriber) == from then
-                                       recipients[user][subscriber] = nil;
-                               end
+                               recipients[user][recipient] = hash;
+                               origin.send(
+                                       st.stanza("iq", {from=stanza.attr.to, to=stanza.attr.from, id="disco", type="get"})
+                                               :query("http://jabber.org/protocol/disco#info")
+                               );
                        end
                end
        end
@@ -222,13 +205,56 @@ module:hook("iq/bare/http://jabber.org/protocol/pubsub:pubsub", function(event)
        end
 end);
 
-module:hook("iq-result/bare/disco", function(event)
+local function calculate_hash(disco_info)
+       local identities, features, extensions = {}, {}, {};
+       for _, tag in pairs(disco_info) do
+               if tag.name == "identity" then
+                       table.insert(identities, (tag.attr.category or "").."\0"..(tag.attr.type or "").."\0"..(tag.attr["xml:lang"] or "").."\0"..(tag.attr.name or ""));
+               elseif tag.name == "feature" then
+                       table.insert(features, tag.attr.var or "");
+               elseif tag.name == "x" and tag.attr.xmlns == "jabber:x:data" then
+                       local form = {};
+                       local FORM_TYPE;
+                       for _, field in pairs(tag.tags) do
+                               if field.name == "field" and field.attr.var then
+                                       local values = {};
+                                       for _, val in pairs(field.tags) do
+                                               val = #val.tags == 0 and table.concat(val); -- FIXME use get_text?
+                                               if val then table.insert(values, val); end
+                                       end
+                                       table.sort(values);
+                                       if field.attr.var == "FORM_TYPE" then
+                                               FORM_TYPE = values[1];
+                                       elseif #values > 0 then
+                                               table.insert(form, field.attr.var.."\0"..table.concat(values, "<"));
+                                       else
+                                               table.insert(form, field.attr.var);
+                                       end
+                               end
+                       end
+                       table.sort(form);
+                       form = table.concat(form, "<");
+                       if FORM_TYPE then form = FORM_TYPE.."\0"..form; end
+                       table.insert(extensions, form);
+               end
+       end
+       table.sort(identities);
+       table.sort(features);
+       table.sort(extensions);
+       if #identities > 0 then identities = table.concat(identities, "<"):gsub("%z", "/").."<"; else identities = ""; end
+       if #features > 0 then features = table.concat(features, "<").."<"; else features = ""; end
+       if #extensions > 0 then extensions = table.concat(extensions, "<"):gsub("%z", "<").."<"; else extensions = ""; end
+       local S = identities..features..extensions;
+       local ver = base64(sha1(S));
+       return ver, S;
+end
+
+module:hook("iq/bare/disco", function(event)
        local session, stanza = event.origin, event.stanza;
        if stanza.attr.type == "result" then
                local disco = stanza.tags[1];
                if disco and disco.name == "query" and disco.attr.xmlns == "http://jabber.org/protocol/disco#info" then
                        -- Process disco response
-                       local self = not stanza.attr.to;
                        local user = stanza.attr.to or (session.username..'@'..session.host);
                        local contact = stanza.attr.from;
                        local current = recipients[user] and recipients[user][contact];
@@ -245,15 +271,6 @@ module:hook("iq-result/bare/disco", function(event)
                                end
                        end
                        hash_map[ver] = notify; -- update hash map
-                       if self then
-                               for jid, item in pairs(session.roster) do -- for all interested contacts
-                                       if item.subscription == "both" or item.subscription == "from" then
-                                               if not recipients[jid] then recipients[jid] = {}; end
-                                               recipients[jid][contact] = notify;
-                                               publish_all(jid, contact, session);
-                                       end
-                               end
-                       end
                        recipients[user][contact] = notify; -- set recipient's data to calculated data
                        -- send messages to recipient
                        publish_all(user, contact, session);
@@ -268,8 +285,8 @@ module:hook("account-disco-info", function(event)
 end);
 
 module:hook("account-disco-items", function(event)
-       local stanza = event.stanza;
-       local bare = stanza.attr.to;
+       local session, stanza = event.session, event.stanza;
+       local bare = session.username..'@'..session.host;
        local user_data = data[bare];
 
        if user_data then
index d229c1b8e35fd74f202b6e91e68c3c9318524ce1..c38f7eba75399bb9d9e421595f278454c77ec573 100644 (file)
@@ -7,7 +7,7 @@
 --
 
 
-local want_pposix_version = "0.3.5";
+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
@@ -17,6 +17,8 @@ if type(signal) == "string" then
        module:log("warn", "Couldn't load signal library, won't respond to SIGTERM");
 end
 
+local logger_set = require "util.logger".setwriter;
+
 local lfs = require "lfs";
 local stat = lfs.attributes;
 
@@ -28,7 +30,7 @@ local umask = module:get_option("umask") or "027";
 pposix.umask(umask);
 
 -- Allow switching away from root, some people like strange ports.
-module:hook("server-started", function ()
+module:add_event_hook("server-started", function ()
                local uid = module:get_option("setuid");
                local gid = module:get_option("setgid");
                if gid then
@@ -52,16 +54,16 @@ module:hook("server-started", function ()
        end);
 
 -- Don't even think about it!
-if not prosody.start_time then -- server-starting
-       local suid = module:get_option("setuid");
-       if not suid or suid == 0 or suid == "root" then
-               if pposix.getuid() == 0 and not module:get_option("run_as_root") then
-                       module:log("error", "Danger, Will Robinson! Prosody doesn't need to be run as root, so don't do it!");
-                       module:log("error", "For more information on running Prosody as root, see http://prosody.im/doc/root");
-                       prosody.shutdown("Refusing to run as root");
+module:add_event_hook("server-starting", function ()
+               local suid = module:get_option("setuid");
+               if not suid or suid == 0 or suid == "root" then
+                       if pposix.getuid() == 0 and not module:get_option("run_as_root") then
+                               module:log("error", "Danger, Will Robinson! Prosody doesn't need to be run as root, so don't do it!");
+                               module:log("error", "For more information on running Prosody as root, see http://prosody.im/doc/root");
+                               prosody.shutdown("Refusing to run as root");
+                       end
                end
-       end
-end
+       end);
 
 local pidfile;
 local pidfile_handle;
@@ -93,23 +95,14 @@ local function write_pidfile()
                                pidfile_handle = nil;
                                prosody.shutdown("Prosody already running");
                        else
-                               pidfile_handle:close();
-                               pidfile_handle, err = io.open(pidfile, "w+");
-                               if not pidfile_handle then
-                                       module:log("error", "Couldn't write pidfile at %s; %s", pidfile, err);
-                                       prosody.shutdown("Couldn't write pidfile");
-                               else
-                                       if lfs.lock(pidfile_handle, "w") then
-                                               pidfile_handle:write(tostring(pposix.getpid()));
-                                               pidfile_handle:flush();
-                                       end
-                               end
+                               pidfile_handle:write(tostring(pposix.getpid()));
+                               pidfile_handle:flush();
                        end
                end
        end
 end
 
-local syslog_opened;
+local syslog_opened 
 function syslog_sink_maker(config)
        if not syslog_opened then
                pposix.syslog_open("prosody");
@@ -117,12 +110,12 @@ function syslog_sink_maker(config)
        end
        local syslog, format = pposix.syslog_log, string.format;
        return function (name, level, message, ...)
-               if ... then
-                       syslog(level, format(message, ...));
-               else
-                       syslog(level, message);
-               end
-       end;
+                       if ... then
+                               syslog(level, format(message, ...));
+                       else
+                               syslog(level, message);
+                       end
+               end;
 end
 require "core.loggingmanager".register_sink_type("syslog", syslog_sink_maker);
 
@@ -148,15 +141,13 @@ if daemonize then
                        write_pidfile();
                end
        end
-       if not prosody.start_time then -- server-starting
-               daemonize_server();
-       end
+       module:add_event_hook("server-starting", daemonize_server);
 else
        -- Not going to daemonize, so write the pid of this process
        write_pidfile();
 end
 
-module:hook("server-stopped", remove_pidfile);
+module:add_event_hook("server-stopped", remove_pidfile);
 
 -- Set signal handlers
 if signal.signal then
index 6d039d83f9862bb1e9a7dc8861c9e778d64a1d5c..4fb8c3e4ec8323cebab4ac0a5e1e2cb893a88bc3 100644 (file)
@@ -22,6 +22,21 @@ local NULL = {};
 
 local rostermanager = require "core.rostermanager";
 local sessionmanager = require "core.sessionmanager";
+local offlinemanager = require "core.offlinemanager";
+
+local _core_route_stanza = core_route_stanza;
+local core_route_stanza;
+function core_route_stanza(origin, stanza)
+       if stanza.attr.type ~= nil and stanza.attr.type ~= "unavailable" and stanza.attr.type ~= "error" then
+               local node, host = jid_split(stanza.attr.to);
+               host = hosts[host];
+               if node and host and host.type == "local" then
+                       handle_inbound_presence_subscriptions_and_probes(origin, stanza, jid_bare(stanza.attr.from), jid_bare(stanza.attr.to), core_route_stanza);
+                       return;
+               end
+       end
+       _core_route_stanza(origin, stanza);
+end
 
 local function select_top_resources(user)
        local priority = 0;
@@ -49,7 +64,7 @@ end
 
 local ignore_presence_priority = module:get_option("ignore_presence_priority");
 
-function handle_normal_presence(origin, stanza)
+function handle_normal_presence(origin, stanza, core_route_stanza)
        if ignore_presence_priority then
                local priority = stanza:child_with_name("priority");
                if priority and priority[1] ~= "0" then
@@ -58,15 +73,6 @@ function handle_normal_presence(origin, stanza)
                        priority[1] = "0";
                end
        end
-       local priority = stanza:child_with_name("priority");
-       if priority and #priority > 0 then
-               priority = t_concat(priority);
-               if s_find(priority, "^[+-]?[0-9]+$") then
-                       priority = tonumber(priority);
-                       if priority < -128 then priority = -128 end
-                       if priority > 127 then priority = 127 end
-               else priority = 0; end
-       else priority = 0; end
        if full_sessions[origin.full_jid] then -- if user is still connected
                origin.send(stanza); -- reflect their presence back to them
        end
@@ -76,13 +82,13 @@ function handle_normal_presence(origin, stanza)
        for _, res in pairs(user and user.sessions or NULL) do -- broadcast to all resources
                if res ~= origin and res.presence then -- to resource
                        stanza.attr.to = res.full_jid;
-                       core_post_stanza(origin, stanza, true);
+                       core_route_stanza(origin, stanza);
                end
        end
        for jid, item in pairs(roster) do -- broadcast to all interested contacts
                if item.subscription == "both" or item.subscription == "from" then
                        stanza.attr.to = jid;
-                       core_post_stanza(origin, stanza, true);
+                       core_route_stanza(origin, stanza);
                end
        end
        if stanza.attr.type == nil and not origin.presence then -- initial presence
@@ -91,13 +97,13 @@ function handle_normal_presence(origin, stanza)
                for jid, item in pairs(roster) do -- probe all contacts we are subscribed to
                        if item.subscription == "both" or item.subscription == "to" then
                                probe.attr.to = jid;
-                               core_post_stanza(origin, probe, true);
+                               core_route_stanza(origin, probe);
                        end
                end
                for _, res in pairs(user and user.sessions or NULL) do -- broadcast from all available resources
                        if res ~= origin and res.presence then
                                res.presence.attr.to = origin.full_jid;
-                               core_post_stanza(res, res.presence, true);
+                               core_route_stanza(res, res.presence);
                                res.presence.attr.to = nil;
                        end
                end
@@ -110,13 +116,15 @@ function handle_normal_presence(origin, stanza)
                for jid, item in pairs(roster) do -- resend outgoing subscription requests
                        if item.ask then
                                request.attr.to = jid;
-                               core_post_stanza(origin, request, true);
+                               core_route_stanza(origin, request);
                        end
                end
-
-               if priority >= 0 then
-                        local event = { origin = origin }
-                        module:fire_event('message/offline/broadcast', event);
+               local offline = offlinemanager.load(node, host);
+               if offline then
+                       for _, msg in ipairs(offline) do
+                               origin.send(msg); -- FIXME do we need to modify to/from in any way?
+                       end
+                       offlinemanager.deleteAll(node, host);
                end
        end
        if stanza.attr.type == "unavailable" then
@@ -128,12 +136,21 @@ function handle_normal_presence(origin, stanza)
                if origin.directed then
                        for jid in pairs(origin.directed) do
                                stanza.attr.to = jid;
-                               core_post_stanza(origin, stanza, true);
+                               core_route_stanza(origin, stanza);
                        end
                        origin.directed = nil;
                end
        else
                origin.presence = stanza;
+               local priority = stanza:child_with_name("priority");
+               if priority and #priority > 0 then
+                       priority = t_concat(priority);
+                       if s_find(priority, "^[+-]?[0-9]+$") then
+                               priority = tonumber(priority);
+                               if priority < -128 then priority = -128 end
+                               if priority > 127 then priority = 127 end
+                       else priority = 0; end
+               else priority = 0; end
                if origin.priority ~= priority then
                        origin.priority = priority;
                        recalc_resource_map(user);
@@ -142,7 +159,7 @@ function handle_normal_presence(origin, stanza)
        stanza.attr.to = nil; -- reset it
 end
 
-function send_presence_of_available_resources(user, host, jid, recipient_session, stanza)
+function send_presence_of_available_resources(user, host, jid, recipient_session, core_route_stanza, stanza)
        local h = hosts[host];
        local count = 0;
        if h and h.type == "local" then
@@ -153,7 +170,7 @@ function send_presence_of_available_resources(user, host, jid, recipient_session
                                if pres then
                                        if stanza then pres = stanza; pres.attr.from = session.full_jid; end
                                        pres.attr.to = jid;
-                                       core_post_stanza(session, pres, true);
+                                       core_route_stanza(session, pres);
                                        pres.attr.to = nil;
                                        count = count + 1;
                                end
@@ -164,29 +181,26 @@ function send_presence_of_available_resources(user, host, jid, recipient_session
        return count;
 end
 
-function handle_outbound_presence_subscriptions_and_probes(origin, stanza, from_bare, to_bare)
+function handle_outbound_presence_subscriptions_and_probes(origin, stanza, from_bare, to_bare, core_route_stanza)
        local node, host = jid_split(from_bare);
-       if to_bare == from_bare then return; end -- No self contacts
+       if to_bare == origin.username.."@"..origin.host then return; end -- No self contacts
        local st_from, st_to = stanza.attr.from, stanza.attr.to;
        stanza.attr.from, stanza.attr.to = from_bare, to_bare;
        log("debug", "outbound presence "..stanza.attr.type.." from "..from_bare.." for "..to_bare);
-       if stanza.attr.type == "probe" then
-               stanza.attr.from, stanza.attr.to = st_from, st_to;
-               return;
-       elseif stanza.attr.type == "subscribe" then
+       if stanza.attr.type == "subscribe" then
                -- 1. route stanza
                -- 2. roster push (subscription = none, ask = subscribe)
                if rostermanager.set_contact_pending_out(node, host, to_bare) then
                        rostermanager.roster_push(node, host, to_bare);
                end -- else file error
-               core_post_stanza(origin, stanza);
+               core_route_stanza(origin, stanza);
        elseif stanza.attr.type == "unsubscribe" then
                -- 1. route stanza
                -- 2. roster push (subscription = none or from)
                if rostermanager.unsubscribe(node, host, to_bare) then
                        rostermanager.roster_push(node, host, to_bare); -- FIXME do roster push when roster has in fact not changed?
                end -- else file error
-               core_post_stanza(origin, stanza);
+               core_route_stanza(origin, stanza);
        elseif stanza.attr.type == "subscribed" then
                -- 1. route stanza
                -- 2. roster_push ()
@@ -194,23 +208,20 @@ function handle_outbound_presence_subscriptions_and_probes(origin, stanza, from_
                if rostermanager.subscribed(node, host, to_bare) then
                        rostermanager.roster_push(node, host, to_bare);
                end
-               core_post_stanza(origin, stanza);
-               send_presence_of_available_resources(node, host, to_bare, origin);
+               core_route_stanza(origin, stanza);
+               send_presence_of_available_resources(node, host, to_bare, origin, core_route_stanza);
        elseif stanza.attr.type == "unsubscribed" then
                -- 1. route stanza
                -- 2. roster push (subscription = none or to)
                if rostermanager.unsubscribed(node, host, to_bare) then
                        rostermanager.roster_push(node, host, to_bare);
                end
-               core_post_stanza(origin, stanza);
-       else
-               origin.send(st.error_reply(stanza, "modify", "bad-request", "Invalid presence type"));
+               core_route_stanza(origin, stanza);
        end
        stanza.attr.from, stanza.attr.to = st_from, st_to;
-       return true;
 end
 
-function handle_inbound_presence_subscriptions_and_probes(origin, stanza, from_bare, to_bare)
+function handle_inbound_presence_subscriptions_and_probes(origin, stanza, from_bare, to_bare, core_route_stanza)
        local node, host = jid_split(to_bare);
        local st_from, st_to = stanza.attr.from, stanza.attr.to;
        stanza.attr.from, stanza.attr.to = from_bare, to_bare;
@@ -219,21 +230,21 @@ function handle_inbound_presence_subscriptions_and_probes(origin, stanza, from_b
        if stanza.attr.type == "probe" then
                local result, err = rostermanager.is_contact_subscribed(node, host, from_bare);
                if result then
-                       if 0 == send_presence_of_available_resources(node, host, st_from, origin) then
-                               core_post_stanza(hosts[host], st.presence({from=to_bare, to=st_from, type="unavailable"}), true); -- TODO send last activity
+                       if 0 == send_presence_of_available_resources(node, host, st_from, origin, core_route_stanza) then
+                               core_route_stanza(hosts[host], st.presence({from=to_bare, to=st_from, type="unavailable"})); -- TODO send last activity
                        end
                elseif not err then
-                       core_post_stanza(hosts[host], st.presence({from=to_bare, to=from_bare, type="unsubscribed"}), true);
+                       core_route_stanza(hosts[host], st.presence({from=to_bare, to=from_bare, type="unsubscribed"}));
                end
        elseif stanza.attr.type == "subscribe" then
                if rostermanager.is_contact_subscribed(node, host, from_bare) then
-                       core_post_stanza(hosts[host], st.presence({from=to_bare, to=from_bare, type="subscribed"}), true); -- already subscribed
+                       core_route_stanza(hosts[host], st.presence({from=to_bare, to=from_bare, type="subscribed"})); -- already subscribed
                        -- Sending presence is not clearly stated in the RFC, but it seems appropriate
-                       if 0 == send_presence_of_available_resources(node, host, from_bare, origin) then
-                               core_post_stanza(hosts[host], st.presence({from=to_bare, to=from_bare, type="unavailable"}), true); -- TODO send last activity
+                       if 0 == send_presence_of_available_resources(node, host, from_bare, origin, core_route_stanza) then
+                               core_route_stanza(hosts[host], st.presence({from=to_bare, to=from_bare, type="unavailable"})); -- TODO send last activity
                        end
                else
-                       core_post_stanza(hosts[host], st.presence({from=to_bare, to=from_bare, type="unavailable"}), true); -- acknowledging receipt
+                       core_route_stanza(hosts[host], st.presence({from=to_bare, to=from_bare, type="unavailable"})); -- acknowledging receipt
                        if not rostermanager.is_contact_pending_in(node, host, from_bare) then
                                if rostermanager.set_contact_pending_in(node, host, from_bare) then
                                        sessionmanager.send_to_available_resources(node, host, stanza);
@@ -255,11 +266,8 @@ function handle_inbound_presence_subscriptions_and_probes(origin, stanza, from_b
                        sessionmanager.send_to_interested_resources(node, host, stanza);
                        rostermanager.roster_push(node, host, from_bare);
                end
-       else
-               origin.send(st.error_reply(stanza, "modify", "bad-request", "Invalid presence type"));
-       end
+       end -- discard any other type
        stanza.attr.from, stanza.attr.to = st_from, st_to;
-       return true;
 end
 
 local outbound_presence_handler = function(data)
@@ -270,12 +278,12 @@ local outbound_presence_handler = function(data)
        if to then
                local t = stanza.attr.type;
                if t ~= nil and t ~= "unavailable" and t ~= "error" then -- check for subscriptions and probes
-                       return handle_outbound_presence_subscriptions_and_probes(origin, stanza, jid_bare(stanza.attr.from), jid_bare(stanza.attr.to));
+                       handle_outbound_presence_subscriptions_and_probes(origin, stanza, jid_bare(stanza.attr.from), jid_bare(stanza.attr.to), core_route_stanza);
+                       return true;
                end
 
                local to_bare = jid_bare(to);
-               local roster = origin.roster;
-               if roster and not(roster[to_bare] and (roster[to_bare].subscription == "both" or roster[to_bare].subscription == "from")) then -- directed presence
+               if not(origin.roster[to_bare] and (origin.roster[to_bare].subscription == "both" or origin.roster[to_bare].subscription == "from")) then -- directed presence
                        origin.directed = origin.directed or {};
                        if t then -- removing from directed presence list on sending an error or unavailable
                                origin.directed[to] = nil; -- FIXME does it make more sense to add to_bare rather than to?
@@ -298,7 +306,8 @@ module:hook("presence/bare", function(data)
        local t = stanza.attr.type;
        if to then
                if t ~= nil and t ~= "unavailable" and t ~= "error" then -- check for subscriptions and probes sent to bare JID
-                       return handle_inbound_presence_subscriptions_and_probes(origin, stanza, jid_bare(stanza.attr.from), jid_bare(stanza.attr.to));
+                       handle_inbound_presence_subscriptions_and_probes(origin, stanza, jid_bare(stanza.attr.from), jid_bare(stanza.attr.to), core_route_stanza);
+                       return true;
                end
        
                local user = bare_sessions[to];
@@ -310,9 +319,7 @@ module:hook("presence/bare", function(data)
                        end
                end -- no resources not online, discard
        elseif not t or t == "unavailable" then
-               handle_normal_presence(origin, stanza);
-       else
-               origin.send(st.error_reply(stanza, "modify", "bad-request", "Invalid presence type"));
+               handle_normal_presence(origin, stanza, core_route_stanza);
        end
        return true;
 end);
@@ -322,7 +329,8 @@ module:hook("presence/full", function(data)
 
        local t = stanza.attr.type;
        if t ~= nil and t ~= "unavailable" and t ~= "error" then -- check for subscriptions and probes sent to full JID
-               return handle_inbound_presence_subscriptions_and_probes(origin, stanza, jid_bare(stanza.attr.from), jid_bare(stanza.attr.to));
+               handle_inbound_presence_subscriptions_and_probes(origin, stanza, jid_bare(stanza.attr.from), jid_bare(stanza.attr.to), core_route_stanza);
+               return true;
        end
 
        local session = full_sessions[stanza.attr.to];
@@ -339,10 +347,10 @@ module:hook("presence/host", function(data)
        local from_bare = jid_bare(stanza.attr.from);
        local t = stanza.attr.type;
        if t == "probe" then
-               core_post_stanza(hosts[module.host], st.presence({ from = module.host, to = from_bare, id = stanza.attr.id }));
+               core_route_stanza(hosts[module.host], st.presence({ from = module.host, to = from_bare, id = stanza.attr.id }));
        elseif t == "subscribe" then
-               core_post_stanza(hosts[module.host], st.presence({ from = module.host, to = from_bare, id = stanza.attr.id, type = "subscribed" }));
-               core_post_stanza(hosts[module.host], st.presence({ from = module.host, to = from_bare, id = stanza.attr.id }));
+               core_route_stanza(hosts[module.host], st.presence({ from = module.host, to = from_bare, id = stanza.attr.id, type = "subscribed" }));
+               core_route_stanza(hosts[module.host], st.presence({ from = module.host, to = from_bare, id = stanza.attr.id }));
        end
        return true;
 end);
@@ -361,7 +369,7 @@ module:hook("resource-unbind", function(event)
                pres:tag("status"):text("Disconnected: "..err):up();
                for jid in pairs(session.directed) do
                        pres.attr.to = jid;
-                       core_post_stanza(session, pres, true);
+                       core_route_stanza(session, pres);
                end
                session.directed = nil;
        end
index 2d696154cce03701c584f4692909fe61a2faf51a..aa953310777fefb63252e636f3d69bbce152a72c 100644 (file)
@@ -45,6 +45,28 @@ function isAnotherSessionUsingDefaultList(origin)
        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
@@ -101,7 +123,7 @@ function deleteList(privacy_lists, origin, stanza, name)
        return {"modify", "bad-request", "Not existing list specifed to be deleted."};
 end
 
-function createOrReplaceList (privacy_lists, origin, stanza, name, entries)
+function createOrReplaceList (privacy_lists, origin, stanza, name, entries, roster)
        local bare_jid = origin.username.."@"..origin.host;
        
        if privacy_lists.lists == nil then
@@ -181,7 +203,7 @@ function getList(privacy_lists, origin, stanza, name)
 
        if name == nil then
                if privacy_lists.lists then
-                       if origin.activePrivacyList then
+                       if origin.ActivePrivacyList then
                                reply:tag("active", {name=origin.activePrivacyList}):up();
                        end
                        if privacy_lists.default then
@@ -301,6 +323,7 @@ function checkIfNeedToBeBlocked(e, session)
                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
@@ -391,6 +414,7 @@ function preCheckIncoming(e)
                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
@@ -418,9 +442,7 @@ function preCheckOutgoing(e)
                        e.stanza.attr.from = e.stanza.attr.from .. "/" .. session.resource;
                end
        end
-       if session.username then -- FIXME do properly
-               return checkIfNeedToBeBlocked(e, session);
-       end
+       return checkIfNeedToBeBlocked(e, session);
 end
 
 module:hook("pre-message/full", preCheckOutgoing, 500);
index f1ebe786c111f1f39fe44d2d97e32c8e6c8aad31..abf1ec039a5d076c361d39784a2ce16dc453f757 100644 (file)
@@ -7,6 +7,7 @@
 --
 
 
+
 local st = require "util.stanza"
 
 local jid_split = require "util.jid".split;
@@ -14,40 +15,47 @@ local datamanager = require "util.datamanager"
 
 module:add_feature("jabber:iq:private");
 
-module:hook("iq/self/jabber:iq:private:query", function(event)
-       local origin, stanza = event.origin, event.stanza;
-       local type = stanza.attr.type;
-       local query = stanza.tags[1];
-       if #query.tags == 1 then
-               local tag = query.tags[1];
-               local key = tag.name..":"..tag.attr.xmlns;
-               local data, err = datamanager.load(origin.username, origin.host, "private");
-               if err then
-                       origin.send(st.error_reply(stanza, "wait", "internal-server-error"));
-                       return true;
-               end
-               if stanza.attr.type == "get" then
-                       if data and data[key] then
-                               origin.send(st.reply(stanza):tag("query", {xmlns = "jabber:iq:private"}):add_child(st.deserialize(data[key])));
-                       else
-                               origin.send(st.reply(stanza):add_child(stanza.tags[1]));
-                       end
-               else -- set
-                       if not data then data = {}; end;
-                       if #tag == 0 then
-                               data[key] = nil;
-                       else
-                               data[key] = st.preserialize(tag);
-                       end
-                       -- TODO delete datastore if empty
-                       if datamanager.store(origin.username, origin.host, "private", data) then
-                               origin.send(st.reply(stanza));
+module:add_iq_handler("c2s", "jabber:iq:private",
+       function (session, stanza)
+               local type = stanza.attr.type;
+               local query = stanza.tags[1];
+               if (type == "get" or type == "set") and query.name == "query" then
+                       local node, host = jid_split(stanza.attr.to);
+                       if not(node or host) or (node == session.username and host == session.host) then
+                               node, host = session.username, session.host;
+                               if #query.tags == 1 then
+                                       local tag = query.tags[1];
+                                       local key = tag.name..":"..tag.attr.xmlns;
+                                       local data, err = datamanager.load(node, host, "private");
+                                       if err then
+                                               session.send(st.error_reply(stanza, "wait", "internal-server-error"));
+                                               return true;
+                                       end
+                                       if stanza.attr.type == "get" then
+                                               if data and data[key] then
+                                                       session.send(st.reply(stanza):tag("query", {xmlns = "jabber:iq:private"}):add_child(st.deserialize(data[key])));
+                                               else
+                                                       session.send(st.reply(stanza):add_child(stanza.tags[1]));
+                                               end
+                                       else -- set
+                                               if not data then data = {}; end;
+                                               if #tag == 0 then
+                                                       data[key] = nil;
+                                               else
+                                                       data[key] = st.preserialize(tag);
+                                               end
+                                               -- TODO delete datastore if empty
+                                               if datamanager.store(node, host, "private", data) then
+                                                       session.send(st.reply(stanza));
+                                               else
+                                                       session.send(st.error_reply(stanza, "wait", "internal-server-error"));
+                                               end
+                                       end
+                               else
+                                       session.send(st.error_reply(stanza, "modify", "bad-format"));
+                               end
                        else
-                               origin.send(st.error_reply(stanza, "wait", "internal-server-error"));
+                               session.send(st.error_reply(stanza, "cancel", "forbidden"));
                        end
                end
-       else
-               origin.send(st.error_reply(stanza, "modify", "bad-format"));
-       end
-       return true;
-end);
+       end);
index 5b49073071736281f75752a4e54588a293229e08..190d30bef5a9d9fae1b41be3bf1c6c937b3e5776 100644 (file)
@@ -10,22 +10,25 @@ module:unload("proxy65");
 module:load("proxy65", <proxy65_jid>);
 ]]--
 
+if module:get_host_type() ~= "component" then
+       error("proxy65 should be loaded as a component, please see http://prosody.im/doc/components", 0);
+end
 
-local module = module;
-local tostring = tostring;
-local jid_split, jid_join, jid_compare = require "util.jid".split, require "util.jid".join, require "util.jid".compare;
+local jid_split, jid_join = require "util.jid".split, require "util.jid".join;
 local st = require "util.stanza";
+local componentmanager = require "core.componentmanager";
+local config_get = require "core.configmanager".get;
 local connlisteners = require "net.connlisteners";
 local sha1 = require "util.hashes".sha1;
 local server = require "net.server";
 
 local host, name = module:get_host(), "SOCKS5 Bytestreams Service";
-local sessions, transfers, replies_cache = {}, {}, {};
+local sessions, transfers, component, replies_cache = {}, {}, nil, {};
 
-local proxy_port = module:get_option("proxy65_port") or 5000;
-local proxy_interface = module:get_option("proxy65_interface") or "*";
-local proxy_address = module:get_option("proxy65_address") or (proxy_interface ~= "*" and proxy_interface) or host;
-local proxy_acl = module:get_option("proxy65_acl");
+local proxy_port = config_get(host, "core", "proxy65_port") or 5000;
+local proxy_interface = config_get(host, "core", "proxy65_interface") or "*";
+local proxy_address = config_get(host, "core", "proxy65_address") or (proxy_interface ~= "*" and proxy_interface) or host;
+local proxy_acl = config_get(host, "core", "proxy65_acl");
 local max_buffer_size = 4096;
 
 local connlistener = { default_port = proxy_port, default_interface = proxy_interface, default_mode = "*a" };
@@ -33,12 +36,12 @@ local connlistener = { default_port = proxy_port, default_interface = proxy_inte
 function connlistener.onincoming(conn, data)
        local session = sessions[conn] or {};
        
-       if session.setup == nil and data ~= nil and data:byte(1) == 0x05 and #data > 2 then
-               local nmethods = data:byte(2);
+       if session.setup == nil and data ~= nil and data:sub(1):byte() == 0x05 and data:len() > 2 then
+               local nmethods = data:sub(2):byte();
                local methods = data:sub(3);
                local supported = false;
                for i=1, nmethods, 1 do
-                       if(methods:byte(i) == 0x00) then -- 0x00 == method: NO AUTH
+                       if(methods:sub(i):byte() == 0x00) then -- 0x00 == method: NO AUTH
                                supported = true;
                                break;
                        end
@@ -63,14 +66,14 @@ function connlistener.onincoming(conn, data)
                                return;
                        end
                end
-               if data ~= nil and #data == 0x2F and  -- 40 == length of SHA1 HASH, and 7 other bytes => 47 => 0x2F
-                       data:byte(1) == 0x05 and -- SOCKS5 has 5 in first byte
-                       data:byte(2) == 0x01 and -- CMD must be 1
-                       data:byte(3) == 0x00 and -- RSV must be 0
-                       data:byte(4) == 0x03 and -- ATYP must be 3
-                       data:byte(5) == 40 and -- SHA1 HASH length must be 40 (0x28)
-                       data:byte(-2) == 0x00 and -- PORT must be 0, size 2 byte
-                       data:byte(-1) == 0x00
+               if data ~= nil and data:len() == 0x2F and  -- 40 == length of SHA1 HASH, and 7 other bytes => 47 => 0x2F
+                       data:sub(1):byte() == 0x05 and -- SOCKS5 has 5 in first byte
+                       data:sub(2):byte() == 0x01 and -- CMD must be 1
+                       data:sub(3):byte() == 0x00 and -- RSV must be 0
+                       data:sub(4):byte() == 0x03 and -- ATYP must be 3
+                       data:sub(5):byte() == 40 and -- SHA1 HASH length must be 40 (0x28)
+                       data:sub(-2):byte() == 0x00 and -- PORT must be 0, size 2 byte
+                       data:sub(-1):byte() == 0x00
                then
                        local sha = data:sub(6, 45); -- second param is not count! it's the ending index (included!)
                        if transfers[sha] == nil then
@@ -86,7 +89,7 @@ function connlistener.onincoming(conn, data)
                                server.link(conn, transfers[sha].target, max_buffer_size);
                                server.link(transfers[sha].target, conn, max_buffer_size);
                        end
-                       conn:write(string.char(5, 0, 0, 3, #sha) .. sha .. string.char(0, 0)); -- VER, REP, RSV, ATYP, BND.ADDR (sha), BND.PORT (2 Byte)
+                       conn:write(string.char(5, 0, 0, 3, sha:len()) .. sha .. string.char(0, 0)); -- VER, REP, RSV, ATYP, BND.ADDR (sha), BND.PORT (2 Byte)
                        conn:lock_read(true)
                else
                        module:log("warn", "Neither data transfer nor initial connect of a participator of a transfer.")
@@ -117,11 +120,7 @@ function connlistener.ondisconnect(conn, err)
        end
 end
 
-module:add_identity("proxy", "bytestreams", name);
-module:add_feature("http://jabber.org/protocol/bytestreams");
-
-module:hook("iq-get/host/http://jabber.org/protocol/disco#info:query", function(event)
-       local origin, stanza = event.origin, event.stanza;
+local function get_disco_info(stanza)
        local reply = replies_cache.disco_info;
        if reply == nil then
                reply = st.iq({type='result', from=host}):query("http://jabber.org/protocol/disco#info")
@@ -132,12 +131,10 @@ module:hook("iq-get/host/http://jabber.org/protocol/disco#info:query", function(
 
        reply.attr.id = stanza.attr.id;
        reply.attr.to = stanza.attr.from;
-       origin.send(reply);
-       return true;
-end, -1);
+       return reply;
+end
 
-module:hook("iq-get/host/http://jabber.org/protocol/disco#items:query", function(event)
-       local origin, stanza = event.origin, event.stanza;
+local function get_disco_items(stanza)
        local reply = replies_cache.disco_items;
        if reply == nil then
                reply = st.iq({type='result', from=host}):query("http://jabber.org/protocol/disco#items");
@@ -146,21 +143,32 @@ module:hook("iq-get/host/http://jabber.org/protocol/disco#items:query", function
        
        reply.attr.id = stanza.attr.id;
        reply.attr.to = stanza.attr.from;
-       origin.send(reply);
-       return true;
-end, -1);
+       return reply;
+end
 
-module:hook("iq-get/host/http://jabber.org/protocol/bytestreams:query", function(event)
-       local origin, stanza = event.origin, event.stanza;
+local function get_stream_host(origin, stanza)
        local reply = replies_cache.stream_host;
        local err_reply = replies_cache.stream_host_err;
        local sid = stanza.tags[1].attr.sid;
        local allow = false;
-       local jid = stanza.attr.from;
+       local jid_node, jid_host, jid_resource = jid_split(stanza.attr.from);
+       
+       if stanza.attr.from == nil then
+               jid_node = origin.username;
+               jid_host = origin.host;
+               jid_resource = origin.resource;
+       end
        
        if proxy_acl and #proxy_acl > 0 then
-               for _, acl in ipairs(proxy_acl) do
-                       if jid_compare(jid, acl) then allow = true; end
+               if host ~= nil then -- at least a domain is needed.
+                       for _, acl in ipairs(proxy_acl) do
+                               local acl_node, acl_host, acl_resource = jid_split(acl);
+                               if ((acl_node ~= nil and acl_node == jid_node) or acl_node == nil) and
+                                  ((acl_host ~= nil and acl_host == jid_host) or acl_host == nil) and
+                                  ((acl_resource ~= nil and acl_resource == jid_resource) or acl_resource == nil) then
+                                       allow = true;
+                               end
+                       end
                end
        else
                allow = true;
@@ -173,7 +181,7 @@ module:hook("iq-get/host/http://jabber.org/protocol/bytestreams:query", function
                        replies_cache.stream_host = reply;
                end
        else
-               module:log("warn", "Denying use of proxy for %s", tostring(jid));
+               module:log("warn", "Denying use of proxy for %s", tostring(jid_join(jid_node, jid_host, jid_resource)));
                if err_reply == nil then
                        err_reply = st.iq({type="error", from=host})
                                :query("http://jabber.org/protocol/bytestreams")
@@ -186,21 +194,24 @@ module:hook("iq-get/host/http://jabber.org/protocol/bytestreams:query", function
        reply.attr.id = stanza.attr.id;
        reply.attr.to = stanza.attr.from;
        reply.tags[1].attr.sid = sid;
-       origin.send(reply);
-       return true;
-end);
+       return reply;
+end
 
 module.unload = function()
+       componentmanager.deregister_component(host);
        connlisteners.deregister(module.host .. ':proxy65');
 end
 
 local function set_activation(stanza)
-       local to, reply;
-       local from = stanza.attr.from;
-       local query = stanza.tags[1];
-       local sid = query.attr.sid;
-       if query.tags[1] and query.tags[1].name == "activate" then
-               to = query.tags[1][1];
+       local from, to, sid, reply = nil;
+       from = stanza.attr.from;
+       if stanza.tags[1] ~= nil and tostring(stanza.tags[1].name) == "query" then
+               if stanza.tags[1].attr ~= nil then
+                       sid = stanza.tags[1].attr.sid;
+               end
+               if stanza.tags[1].tags[1] ~= nil and tostring(stanza.tags[1].tags[1].name) == "activate" then
+                       to = stanza.tags[1].tags[1][1];
+               end
        end
        if from ~= nil and to ~= nil and sid ~= nil then
                reply = st.iq({type="result", from=host, to=from});
@@ -209,35 +220,55 @@ local function set_activation(stanza)
        return reply, from, to, sid;
 end
 
-module:hook("iq-set/host/http://jabber.org/protocol/bytestreams:query", function(event)
-       local origin, stanza = event.origin, event.stanza;
-
-       module:log("debug", "Received activation request from %s", stanza.attr.from);
-       local reply, from, to, sid = set_activation(stanza);
-       if reply ~= nil and from ~= nil and to ~= nil and sid ~= nil then
-               local sha = sha1(sid .. from .. to, true);
-               if transfers[sha] == nil then
-                       module:log("error", "transfers[sha]: nil");
-               elseif(transfers[sha] ~= nil and transfers[sha].initiator ~= nil and transfers[sha].target ~= nil) then
-                       origin.send(reply);
-                       transfers[sha].activated = true;
-                       transfers[sha].target:lock_read(false);
-                       transfers[sha].initiator:lock_read(false);
-               else
-                       module:log("debug", "Both parties were not yet connected");
-                       local message = "Neither party is connected to the proxy";
-                       if transfers[sha].initiator then
-                               message = "The recipient is not connected to the proxy";
-                       elseif transfers[sha].target then
-                               message = "The sender (you) is not connected to the proxy";
+function handle_to_domain(origin, stanza)
+       local to_node, to_host, to_resource = jid_split(stanza.attr.to);
+       if to_node == nil then
+               local type = stanza.attr.type;
+               if type == "error" or type == "result" then return; end
+               if stanza.name == "iq" and type == "get" then
+                       local xmlns = stanza.tags[1].attr.xmlns
+                       if xmlns == "http://jabber.org/protocol/disco#info" then
+                               origin.send(get_disco_info(stanza));
+                               return true;
+                       elseif xmlns == "http://jabber.org/protocol/disco#items" then
+                               origin.send(get_disco_items(stanza));
+                               return true;
+                       elseif xmlns == "http://jabber.org/protocol/bytestreams" then
+                               origin.send(get_stream_host(origin, stanza));
+                               return true;
+                       else
+                               origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
+                               return true;
+                       end
+               elseif stanza.name == "iq" and type == "set" then
+                       module:log("debug", "Received activation request from %s", stanza.attr.from);
+                       local reply, from, to, sid = set_activation(stanza);
+                       if reply ~= nil and from ~= nil and to ~= nil and sid ~= nil then
+                               local sha = sha1(sid .. from .. to, true);
+                               if transfers[sha] == nil then
+                                       module:log("error", "transfers[sha]: nil");
+                               elseif(transfers[sha] ~= nil and transfers[sha].initiator ~= nil and transfers[sha].target ~= nil) then
+                                       origin.send(reply);
+                                       transfers[sha].activated = true;
+                                       transfers[sha].target:lock_read(false);
+                                       transfers[sha].initiator:lock_read(false);
+                               else
+                                       module:log("debug", "Both parties were not yet connected");
+                                       local message = "Neither party is connected to the proxy";
+                                       if transfers[sha].initiator then
+                                               message = "The recipient is not connected to the proxy";
+                                       elseif transfers[sha].target then
+                                               message = "The sender (you) is not connected to the proxy";
+                                       end
+                                       origin.send(st.error_reply(stanza, "cancel", "not-allowed", message));
+                               end
+                       else
+                               module:log("error", "activation failed: sid: %s, initiator: %s, target: %s", tostring(sid), tostring(from), tostring(to));
                        end
-                       origin.send(st.error_reply(stanza, "cancel", "not-allowed", message));
                end
-               return true;
-       else
-               module:log("error", "activation failed: sid: %s, initiator: %s, target: %s", tostring(sid), tostring(from), tostring(to));
        end
-end);
+       return;
+end
 
 if not connlisteners.register(module.host .. ':proxy65', connlistener) then
        module:log("error", "mod_proxy65: Could not establish a connection listener. Check your configuration please.");
@@ -245,3 +276,4 @@ if not connlisteners.register(module.host .. ':proxy65', connlistener) then
 end
 
 connlisteners.start(module.host .. ':proxy65');
+component = componentmanager.register_component(host, handle_to_domain);
index 8a818d027ca0f71c7a2f95770a22af5f8f530d2d..2818e3368522068d4f50391f6e4ed4e21e72ef31 100644 (file)
@@ -13,107 +13,82 @@ local datamanager = require "util.datamanager";
 local usermanager_user_exists = require "core.usermanager".user_exists;
 local usermanager_create_user = require "core.usermanager".create_user;
 local usermanager_set_password = require "core.usermanager".set_password;
-local usermanager_delete_user = require "core.usermanager".delete_user;
+local datamanager_store = require "util.datamanager".store;
 local os_time = os.time;
 local nodeprep = require "util.encodings".stringprep.nodeprep;
-local jid_bare = require "util.jid".bare;
-
-local compat = module:get_option_boolean("registration_compat", true);
-local allow_registration = module:get_option_boolean("allow_registration", false);
 
 module:add_feature("jabber:iq:register");
 
-local register_stream_feature = st.stanza("register", {xmlns="http://jabber.org/features/iq-register"}):up();
-module:hook("stream-features", function(event)
-        local session, features = event.origin, event.features;
-
-       -- Advertise registration to unauthorized clients only.
-       if not(allow_registration) or session.type ~= "c2s_unauthed" then
-               return
-       end
-
-       features:add_child(register_stream_feature);
-end);
-
-local function handle_registration_stanza(event)
-       local session, stanza = event.origin, event.stanza;
-
-       local query = stanza.tags[1];
-       if stanza.attr.type == "get" then
-               local reply = st.reply(stanza);
-               reply:tag("query", {xmlns = "jabber:iq:register"})
-                       :tag("registered"):up()
-                       :tag("username"):text(session.username):up()
-                       :tag("password"):up();
-               session.send(reply);
-       else -- stanza.attr.type == "set"
-               if query.tags[1] and query.tags[1].name == "remove" then
-                       -- TODO delete user auth data, send iq response, kick all user resources with a <not-authorized/>, delete all user data
-                       local username, host = session.username, session.host;
-                       
-                       local ok, err = usermanager_delete_user(username, host);
-                       
-                       if not ok then
-                               module:log("debug", "Removing user account %s@%s failed: %s", username, host, err);
-                               session.send(st.error_reply(stanza, "cancel", "service-unavailable", err));
-                               return true;
-                       end
-                       
-                       session.send(st.reply(stanza));
-                       local roster = session.roster;
-                       for _, session in pairs(hosts[host].sessions[username].sessions) do -- disconnect all resources
-                               session:close({condition = "not-authorized", text = "Account deleted"});
-                       end
-                       -- TODO datamanager should be able to delete all user data itself
-                       datamanager.store(username, host, "vcard", nil);
-                       datamanager.store(username, host, "private", nil);
-                       datamanager.list_store(username, host, "offline", nil);
-                       local bare = username.."@"..host;
-                       for jid, item in pairs(roster) do
-                               if jid and jid ~= "pending" then
-                                       if item.subscription == "both" or item.subscription == "from" or (roster.pending and roster.pending[jid]) then
-                                               core_post_stanza(hosts[host], st.presence({type="unsubscribed", from=bare, to=jid}));
-                                       end
-                                       if item.subscription == "both" or item.subscription == "to" or item.ask then
-                                               core_post_stanza(hosts[host], st.presence({type="unsubscribe", from=bare, to=jid}));
+module:add_iq_handler("c2s", "jabber:iq:register", function (session, stanza)
+       if stanza.tags[1].name == "query" then
+               local query = stanza.tags[1];
+               if stanza.attr.type == "get" then
+                       local reply = st.reply(stanza);
+                       reply:tag("query", {xmlns = "jabber:iq:register"})
+                               :tag("registered"):up()
+                               :tag("username"):text(session.username):up()
+                               :tag("password"):up();
+                       session.send(reply);
+               elseif stanza.attr.type == "set" then
+                       if query.tags[1] and query.tags[1].name == "remove" then
+                               -- TODO delete user auth data, send iq response, kick all user resources with a <not-authorized/>, delete all user data
+                               local username, host = session.username, session.host;
+                               --session.send(st.error_reply(stanza, "cancel", "not-allowed"));
+                               --return;
+                               --usermanager_set_password(username, host, nil); -- Disable account
+                               -- FIXME the disabling currently allows a different user to recreate the account
+                               -- we should add an in-memory account block mode when we have threading
+                               session.send(st.reply(stanza));
+                               local roster = session.roster;
+                               for _, session in pairs(hosts[host].sessions[username].sessions) do -- disconnect all resources
+                                       session:close({condition = "not-authorized", text = "Account deleted"});
+                               end
+                               -- TODO datamanager should be able to delete all user data itself
+                               datamanager.store(username, host, "vcard", nil);
+                               datamanager.store(username, host, "private", nil);
+                               datamanager.list_store(username, host, "offline", nil);
+                               local bare = username.."@"..host;
+                               for jid, item in pairs(roster) do
+                                       if jid and jid ~= "pending" then
+                                               if item.subscription == "both" or item.subscription == "from" or (roster.pending and roster.pending[jid]) then
+                                                       core_post_stanza(hosts[host], st.presence({type="unsubscribed", from=bare, to=jid}));
+                                               end
+                                               if item.subscription == "both" or item.subscription == "to" or item.ask then
+                                                       core_post_stanza(hosts[host], st.presence({type="unsubscribe", from=bare, to=jid}));
+                                               end
                                        end
                                end
-                       end
-                       datamanager.store(username, host, "roster", nil);
-                       datamanager.store(username, host, "privacy", nil);
-                       module:log("info", "User removed their account: %s@%s", username, host);
-                       module:fire_event("user-deregistered", { username = username, host = host, source = "mod_register", session = session });
-               else
-                       local username = nodeprep(query:get_child("username"):get_text());
-                       local password = query:get_child("password"):get_text();
-                       if username and password then
-                               if username == session.username then
-                                       if usermanager_set_password(username, password, session.host) then
-                                               session.send(st.reply(stanza));
+                               datamanager.store(username, host, "roster", nil);
+                               datamanager.store(username, host, "privacy", nil);
+                               datamanager.store(username, host, "accounts", nil); -- delete accounts datastore at the end
+                               module:log("info", "User removed their account: %s@%s", username, host);
+                               module:fire_event("user-deregistered", { username = username, host = host, source = "mod_register", session = session });
+                       else
+                               local username = query:child_with_name("username");
+                               local password = query:child_with_name("password");
+                               if username and password then
+                                       -- FIXME shouldn't use table.concat
+                                       username = nodeprep(table.concat(username));
+                                       password = table.concat(password);
+                                       if username == session.username then
+                                               if usermanager_set_password(username, session.host, password) then
+                                                       session.send(st.reply(stanza));
+                                               else
+                                                       -- TODO unable to write file, file may be locked, etc, what's the correct error?
+                                                       session.send(st.error_reply(stanza, "wait", "internal-server-error"));
+                                               end
                                        else
-                                               -- TODO unable to write file, file may be locked, etc, what's the correct error?
-                                               session.send(st.error_reply(stanza, "wait", "internal-server-error"));
+                                               session.send(st.error_reply(stanza, "modify", "bad-request"));
                                        end
                                else
                                        session.send(st.error_reply(stanza, "modify", "bad-request"));
                                end
-                       else
-                               session.send(st.error_reply(stanza, "modify", "bad-request"));
                        end
                end
-       end
-       return true;
-end
-
-module:hook("iq/self/jabber:iq:register:query", handle_registration_stanza);
-if compat then
-       module:hook("iq/host/jabber:iq:register:query", function (event)
-               local session, stanza = event.origin, event.stanza;
-               if session.type == "c2s" and jid_bare(stanza.attr.to) == session.host then
-                       return handle_registration_stanza(event);
-               end
-       end);
-end
+       else
+               session.send(st.error_reply(stanza, "cancel", "service-unavailable"));
+       end;
+end);
 
 local recent_ips = {};
 local min_seconds_between_registrations = module:get_option("min_seconds_between_registrations");
@@ -124,12 +99,10 @@ local blacklisted_ips = module:get_option("registration_blacklist") or {};
 for _, ip in ipairs(whitelisted_ips) do whitelisted_ips[ip] = true; end
 for _, ip in ipairs(blacklisted_ips) do blacklisted_ips[ip] = true; end
 
-module:hook("stanza/iq/jabber:iq:register:query", function(event)
-       local session, stanza = event.origin, event.stanza;
-
-       if not(allow_registration) or session.type ~= "c2s_unauthed" then
+module:add_iq_handler("c2s_unauthed", "jabber:iq:register", function (session, stanza)
+       if module:get_option("allow_registration") == false then
                session.send(st.error_reply(stanza, "cancel", "service-unavailable"));
-       else
+       elseif stanza.tags[1].name == "query" then
                local query = stanza.tags[1];
                if stanza.attr.type == "get" then
                        local reply = st.reply(stanza);
@@ -150,7 +123,7 @@ module:hook("stanza/iq/jabber:iq:register:query", function(event)
                                                module:log("debug", "User's IP not known; can't apply blacklist/whitelist");
                                        elseif blacklisted_ips[session.ip] or (whitelist_only and not whitelisted_ips[session.ip]) then
                                                session.send(st.error_reply(stanza, "cancel", "not-acceptable", "You are not allowed to register an account."));
-                                               return true;
+                                               return;
                                        elseif min_seconds_between_registrations and not whitelisted_ips[session.ip] then
                                                if not recent_ips[session.ip] then
                                                        recent_ips[session.ip] = { time = os_time(), count = 1 };
@@ -161,7 +134,7 @@ module:hook("stanza/iq/jabber:iq:register:query", function(event)
                                                        if os_time() - ip.time < min_seconds_between_registrations then
                                                                ip.time = os_time();
                                                                session.send(st.error_reply(stanza, "wait", "not-acceptable"));
-                                                               return true;
+                                                               return;
                                                        end
                                                        ip.time = os_time();
                                                end
@@ -178,7 +151,7 @@ module:hook("stanza/iq/jabber:iq:register:query", function(event)
                                                if usermanager_create_user(username, password, host) then
                                                        session.send(st.reply(stanza)); -- user created!
                                                        module:log("info", "User account created: %s@%s", username, host);
-                                                       module:fire_event("user-registered", {
+                                                       module:fire_event("user-registered", { 
                                                                username = username, host = host, source = "mod_register",
                                                                session = session });
                                                else
@@ -191,6 +164,8 @@ module:hook("stanza/iq/jabber:iq:register:query", function(event)
                                end
                        end
                end
-       end
-       return true;
+       else
+               session.send(st.error_reply(stanza, "cancel", "service-unavailable"));
+       end;
 end);
+
index 96cc15f26431417d47ebe7447cf0f45c451047d2..ddf02f2f808f4715694517e13f52cd2fe89b77dc 100644 (file)
@@ -7,13 +7,13 @@
 --
 
 
+
 local st = require "util.stanza"
 
 local jid_split = require "util.jid".split;
 local jid_prep = require "util.jid".prep;
 local t_concat = table.concat;
-local tonumber = tonumber;
-local pairs, ipairs = pairs, ipairs;
+local tostring = tostring;
 
 local rm_remove_from_roster = require "core.rostermanager".remove_from_roster;
 local rm_add_to_roster = require "core.rostermanager".add_to_roster;
@@ -22,7 +22,7 @@ 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"});
+local rosterver_stream_feature = st.stanza("ver", {xmlns="urn:xmpp:features:rosterver"}):tag("optional"):up();
 module:hook("stream-features", function(event)
        local origin, features = event.origin, event.features;
        if origin.username then
@@ -30,110 +30,112 @@ module:hook("stream-features", function(event)
        end
 end);
 
-module:hook("iq/self/jabber:iq:roster:query", function(event)
-       local session, stanza = event.origin, event.stanza;
-
-       if stanza.attr.type == "get" then
-               local roster = st.reply(stanza);
-               
-               local client_ver = tonumber(stanza.tags[1].attr.ver);
-               local server_ver = tonumber(session.roster[false].version or 1);
-               
-               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, item in pairs(session.roster) do
-                               if jid ~= "pending" and jid then
-                                       roster:tag("item", {
-                                               jid = jid,
-                                               subscription = item.subscription,
-                                               ask = item.ask,
-                                               name = item.name,
-                                       });
-                                       for group in pairs(item.groups) do
-                                               roster:tag("group"):text(group):up();
-                                       end
-                                       roster:up(); -- move out from item
-                               end
-                       end
-                       roster.tags[1].attr.ver = server_ver;
-               end
-               session.send(roster);
-               session.interested = true; -- resource is interested in roster updates
-       else -- stanza.attr.type == "set"
-               local query = stanza.tags[1];
-               if #query.tags == 1 and query.tags[1].name == "item"
-                               and query.tags[1].attr.xmlns == "jabber:iq:roster" and query.tags[1].attr.jid
-                               -- Protection against overwriting roster.pending, until we move it
-                               and query.tags[1].attr.jid ~= "pending" then
-                       local item = query.tags[1];
-                       local from_node, from_host = jid_split(stanza.attr.from);
-                       local from_bare = from_node and (from_node.."@"..from_host) or from_host; -- bare JID
-                       local jid = jid_prep(item.attr.jid);
-                       local node, host, resource = jid_split(jid);
-                       if not resource and host then
-                               if jid ~= from_node.."@"..from_host then
-                                       if item.attr.subscription == "remove" then
-                                               local roster = session.roster;
-                                               local r_item = roster[jid];
-                                               if r_item then
-                                                       local to_bare = node and (node.."@"..host) or host; -- bare JID
-                                                       if r_item.subscription == "both" or r_item.subscription == "from" or (roster.pending and roster.pending[jid]) then
-                                                               core_post_stanza(session, st.presence({type="unsubscribed", from=session.full_jid, to=to_bare}));
-                                                       end
-                                                       if r_item.subscription == "both" or r_item.subscription == "to" or r_item.ask then
-                                                               core_post_stanza(session, st.presence({type="unsubscribe", from=session.full_jid, to=to_bare}));
-                                                       end
-                                                       local success, err_type, err_cond, err_msg = rm_remove_from_roster(session, jid);
-                                                       if success then
-                                                               session.send(st.reply(stanza));
-                                                               rm_roster_push(from_node, from_host, jid);
-                                                       else
-                                                               session.send(st.error_reply(stanza, err_type, err_cond, err_msg));
+module:add_iq_handler("c2s", "jabber:iq:roster", 
+               function (session, stanza)
+                       if stanza.tags[1].name == "query" then
+                               if stanza.attr.type == "get" then
+                                       local roster = st.reply(stanza);
+                                       
+                                       local client_ver = tonumber(stanza.tags[1].attr.ver);
+                                       local server_ver = tonumber(session.roster[false].version or 1);
+                                       
+                                       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
+                                                       if jid ~= "pending" and jid then
+                                                               roster:tag("item", {
+                                                                       jid = jid,
+                                                                       subscription = session.roster[jid].subscription,
+                                                                       ask = session.roster[jid].ask,
+                                                                       name = session.roster[jid].name,
+                                                               });
+                                                               for group in pairs(session.roster[jid].groups) do
+                                                                       roster:tag("group"):text(group):up();
+                                                               end
+                                                               roster:up(); -- move out from item
                                                        end
-                                               else
-                                                       session.send(st.error_reply(stanza, "modify", "item-not-found"));
                                                end
-                                       else
-                                               local r_item = {name = item.attr.name, groups = {}};
-                                               if r_item.name == "" then r_item.name = nil; end
-                                               if session.roster[jid] then
-                                                       r_item.subscription = session.roster[jid].subscription;
-                                                       r_item.ask = session.roster[jid].ask;
-                                               else
-                                                       r_item.subscription = "none";
-                                               end
-                                               for _, child in ipairs(item) do
-                                                       if child.name == "group" then
-                                                               local text = t_concat(child);
-                                                               if text and text ~= "" then
-                                                                       r_item.groups[text] = true;
+                                               roster.tags[1].attr.ver = server_ver;
+                                       end
+                                       session.send(roster);
+                                       session.interested = true; -- resource is interested in roster updates
+                                       return true;
+                               elseif stanza.attr.type == "set" then
+                                       local query = stanza.tags[1];
+                                       if #query.tags == 1 and query.tags[1].name == "item"
+                                                       and query.tags[1].attr.xmlns == "jabber:iq:roster" and query.tags[1].attr.jid 
+                                                       -- Protection against overwriting roster.pending, until we move it
+                                                       and query.tags[1].attr.jid ~= "pending" then
+                                               local item = query.tags[1];
+                                               local from_node, from_host = jid_split(stanza.attr.from);
+                                               local from_bare = from_node and (from_node.."@"..from_host) or from_host; -- bare JID
+                                               local jid = jid_prep(item.attr.jid);
+                                               local node, host, resource = jid_split(jid);
+                                               if not resource and host then
+                                                       if jid ~= from_node.."@"..from_host then
+                                                               if item.attr.subscription == "remove" then
+                                                                       local roster = session.roster;
+                                                                       local r_item = roster[jid];
+                                                                       if r_item then
+                                                                               local to_bare = node and (node.."@"..host) or host; -- bare JID
+                                                                               if r_item.subscription == "both" or r_item.subscription == "from" or (roster.pending and roster.pending[jid]) then
+                                                                                       core_post_stanza(session, st.presence({type="unsubscribed", from=session.full_jid, to=to_bare}));
+                                                                               end
+                                                                               if r_item.subscription == "both" or r_item.subscription == "to" or r_item.ask then
+                                                                                       core_post_stanza(session, st.presence({type="unsubscribe", from=session.full_jid, to=to_bare}));
+                                                                               end
+                                                                               local success, err_type, err_cond, err_msg = rm_remove_from_roster(session, jid);
+                                                                               if success then
+                                                                                       session.send(st.reply(stanza));
+                                                                                       rm_roster_push(from_node, from_host, jid);
+                                                                               else
+                                                                                       session.send(st.error_reply(stanza, err_type, err_cond, err_msg));
+                                                                               end
+                                                                       else
+                                                                               session.send(st.error_reply(stanza, "modify", "item-not-found"));
+                                                                       end
+                                                               else
+                                                                       local r_item = {name = item.attr.name, groups = {}};
+                                                                       if r_item.name == "" then r_item.name = nil; end
+                                                                       if session.roster[jid] then
+                                                                               r_item.subscription = session.roster[jid].subscription;
+                                                                               r_item.ask = session.roster[jid].ask;
+                                                                       else
+                                                                               r_item.subscription = "none";
+                                                                       end
+                                                                       for _, child in ipairs(item) do 
+                                                                               if child.name == "group" then
+                                                                                       local text = t_concat(child);
+                                                                                       if text and text ~= "" then
+                                                                                               r_item.groups[text] = true;
+                                                                                       end
+                                                                               end
+                                                                       end
+                                                                       local success, err_type, err_cond, err_msg = rm_add_to_roster(session, jid, r_item);
+                                                                       if success then
+                                                                               -- Ok, send success
+                                                                               session.send(st.reply(stanza));
+                                                                               -- and push change to all resources
+                                                                               rm_roster_push(from_node, from_host, jid);
+                                                                       else
+                                                                               -- Adding to roster failed
+                                                                               session.send(st.error_reply(stanza, err_type, err_cond, err_msg));
+                                                                       end
                                                                end
+                                                       else
+                                                               -- Trying to add self to roster
+                                                               session.send(st.error_reply(stanza, "cancel", "not-allowed"));
                                                        end
-                                               end
-                                               local success, err_type, err_cond, err_msg = rm_add_to_roster(session, jid, r_item);
-                                               if success then
-                                                       -- Ok, send success
-                                                       session.send(st.reply(stanza));
-                                                       -- and push change to all resources
-                                                       rm_roster_push(from_node, from_host, jid);
                                                else
-                                                       -- Adding to roster failed
-                                                       session.send(st.error_reply(stanza, err_type, err_cond, err_msg));
+                                                       -- Invalid JID added to roster
+                                                       session.send(st.error_reply(stanza, "modify", "bad-request")); -- FIXME what's the correct error?
                                                end
+                                       else
+                                               -- Roster set didn't include a single item, or its name wasn't  'item'
+                                               session.send(st.error_reply(stanza, "modify", "bad-request"));
                                        end
-                               else
-                                       -- Trying to add self to roster
-                                       session.send(st.error_reply(stanza, "cancel", "not-allowed"));
+                                       return true;
                                end
-                       else
-                               -- Invalid JID added to roster
-                               session.send(st.error_reply(stanza, "modify", "bad-request")); -- FIXME what's the correct error?
                        end
-               else
-                       -- Roster set didn't include a single item, or its name wasn't  'item'
-                       session.send(st.error_reply(stanza, "modify", "bad-request"));
-               end
-       end
-       return true;
-end);
+               end);
index 1c0d0673fc1ebeafb92f81ea24fd2d52a616581d..d407e5da8588ff5bc8787e05fb5232c34326b03e 100644 (file)
 local st = require "util.stanza";
 local sm_bind_resource = require "core.sessionmanager".bind_resource;
 local sm_make_authenticated = require "core.sessionmanager".make_authenticated;
-local s2s_make_authenticated = require "core.s2smanager".make_authenticated;
 local base64 = require "util.encodings".base64;
 
-local cert_verify_identity = require "util.x509".verify_identity;
-
 local nodeprep = require "util.encodings".stringprep.nodeprep;
-local usermanager_get_sasl_handler = require "core.usermanager".get_sasl_handler;
+local datamanager_load = require "util.datamanager".load;
+local usermanager_validate_credentials = require "core.usermanager".validate_credentials;
+local usermanager_get_supported_methods = require "core.usermanager".get_supported_methods;
+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 md5 = require "util.hashes".md5;
+local config = require "core.configmanager";
 
 local secure_auth_only = module:get_option("c2s_require_encryption") or module:get_option("require_encryption");
-local allow_unencrypted_plain_auth = module:get_option("allow_unencrypted_plain_auth")
+local sasl_backend = module:get_option("sasl_backend") or "builtin";
+
+-- Cyrus config options
+local require_provisioning = module:get_option("cyrus_require_provisioning") or false;
+local cyrus_service_realm = module:get_option("cyrus_service_realm");
+local cyrus_service_name = module:get_option("cyrus_service_name");
+local cyrus_application_name = module:get_option("cyrus_application_name");
 
 local log = module._log;
 
@@ -29,6 +40,53 @@ 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;
+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(
+                               cyrus_service_realm or realm,
+                               cyrus_service_name or "xmpp",
+                               cyrus_application_name or "prosody"
+                       );
+               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});
        if status == "challenge" then
@@ -52,20 +110,45 @@ local function handle_status(session, status, ret, err_msg)
        elseif status == "success" then
                local username = nodeprep(session.sasl_handler.username);
 
-               local ok, err = sm_make_authenticated(session, session.sasl_handler.username);
-               if ok then
-                       session.sasl_handler = nil;
-                       session:reset_stream();
+               if not(require_provisioning) or usermanager_user_exists(username, session.host) then
+                       local aret, err = sm_make_authenticated(session, session.sasl_handler.username);
+                       if aret then
+                               session.sasl_handler = nil;
+                               session:reset_stream();
+                       else
+                               module:log("warn", "SASL succeeded but username was invalid");
+                               session.sasl_handler = session.sasl_handler:clean_clone();
+                               return "failure", "not-authorized", "User authenticated successfully, but username was invalid";
+                       end
                else
-                       module:log("warn", "SASL succeeded but username was invalid");
+                       module:log("warn", "SASL succeeded but we don't have an account provisioned for %s", username);
                        session.sasl_handler = session.sasl_handler:clean_clone();
-                       return "failure", "not-authorized", "User authenticated successfully, but username was invalid";
+                       return "failure", "not-authorized", "User authenticated successfully, but not provisioned for XMPP";
                end
        end
        return status, ret, err_msg;
 end
 
-local function sasl_process_cdata(session, stanza)
+local function sasl_handler(session, stanza)
+       if stanza.name == "auth" then
+               -- FIXME ignoring duplicates because ejabberd does
+               if config.get(session.host or "*", "core", "anonymous_login") then
+                       if stanza.attr.mechanism ~= "ANONYMOUS" then
+                               return session.send(build_reply("failure", "invalid-mechanism"));
+                       end
+               elseif stanza.attr.mechanism == "ANONYMOUS" then
+                       return session.send(build_reply("failure", "mechanism-too-weak"));
+               end
+               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
+                       return session.send(build_reply("failure", "encryption-required"));
+               end
+       elseif not session.sasl_handler then
+               return; -- FIXME ignoring out of order stanzas because ejabberd does
+       end
        local text = stanza[1];
        if text then
                text = base64.decode(text);
@@ -73,7 +156,7 @@ local function sasl_process_cdata(session, stanza)
                if not text then
                        session.sasl_handler = nil;
                        session.send(build_reply("failure", "incorrect-encoding"));
-                       return true;
+                       return;
                end
        end
        local status, ret, err_msg = session.sasl_handler:process(text);
@@ -81,160 +164,11 @@ local function sasl_process_cdata(session, stanza)
        local s = build_reply(status, ret, err_msg);
        log("debug", "sasl reply: %s", tostring(s));
        session.send(s);
-       return true;
 end
 
-module:hook_stanza(xmlns_sasl, "success", function (session, stanza)
-       if session.type ~= "s2sout_unauthed" or session.external_auth ~= "attempting" then return; end
-       module:log("debug", "SASL EXTERNAL with %s succeeded", session.to_host);
-       session.external_auth = "succeeded"
-       session:reset_stream();
-
-       local default_stream_attr = {xmlns = "jabber:server", ["xmlns:stream"] = "http://etherx.jabber.org/streams",
-                                   ["xmlns:db"] = 'jabber:server:dialback', version = "1.0", to = session.to_host, from = session.from_host};
-       session.sends2s("<?xml version='1.0'?>");
-       session.sends2s(st.stanza("stream:stream", default_stream_attr):top_tag());
-
-       s2s_make_authenticated(session, session.to_host);
-       return true;
-end)
-
-module:hook_stanza(xmlns_sasl, "failure", function (session, stanza)
-       if session.type ~= "s2sout_unauthed" or session.external_auth ~= "attempting" then return; end
-
-       module:log("info", "SASL EXTERNAL with %s failed", session.to_host)
-       -- TODO: Log the failure reason
-       session.external_auth = "failed"
-end, 500)
-
-module:hook_stanza(xmlns_sasl, "failure", function (session, stanza)
-       -- TODO: Dialback wasn't loaded.  Do something useful.
-end, 90)
-
-module:hook_stanza("http://etherx.jabber.org/streams", "features", function (session, stanza)
-       if session.type ~= "s2sout_unauthed" or not session.secure then return; end
-
-       local mechanisms = stanza:get_child("mechanisms", xmlns_sasl)
-       if mechanisms then
-               for mech in mechanisms:childtags() do
-                       if mech[1] == "EXTERNAL" then
-                               module:log("debug", "Initiating SASL EXTERNAL with %s", session.to_host);
-                               local reply = st.stanza("auth", {xmlns = xmlns_sasl, mechanism = "EXTERNAL"});
-                               reply:text(base64.encode(session.from_host))
-                               session.sends2s(reply)
-                               session.external_auth = "attempting"
-                               return true
-                       end
-               end
-       end
-end, 150);
-
-local function s2s_external_auth(session, stanza)
-       local mechanism = stanza.attr.mechanism;
-
-       if not session.secure then
-               if mechanism == "EXTERNAL" then
-                       session.sends2s(build_reply("failure", "encryption-required"))
-               else
-                       session.sends2s(build_reply("failure", "invalid-mechanism"))
-               end
-               return true;
-       end
-
-       if mechanism ~= "EXTERNAL" or session.cert_chain_status ~= "valid" then
-               session.sends2s(build_reply("failure", "invalid-mechanism"))
-               return true;
-       end
-
-       local text = stanza[1]
-       if not text then
-               session.sends2s(build_reply("failure", "malformed-request"))
-               return true
-       end
-
-       -- Either the value is "=" and we've already verified the external
-       -- cert identity, or the value is a string and either matches the
-       -- from_host (
-
-       text = base64.decode(text)
-       if not text then
-               session.sends2s(build_reply("failure", "incorrect-encoding"))
-               return true;
-       end
-
-       if session.cert_identity_status == "valid" then
-               if text ~= "" and text ~= session.from_host then
-                       session.sends2s(build_reply("failure", "invalid-authzid"))
-                       return true
-               end
-       else
-               if text == "" then
-                       session.sends2s(build_reply("failure", "invalid-authzid"))
-                       return true
-               end
-
-               local cert = session.conn:socket():getpeercertificate()
-               if (cert_verify_identity(text, "xmpp-server", cert)) then
-                       session.cert_identity_status = "valid"
-               else
-                       session.cert_identity_status = "invalid"
-                       session.sends2s(build_reply("failure", "invalid-authzid"))
-                       return true
-               end
-       end
-
-       session.external_auth = "succeeded"
-
-       if not session.from_host then
-               session.from_host = text;
-       end
-       session.sends2s(build_reply("success"))
-       module:log("info", "Accepting SASL EXTERNAL identity from %s", text or session.from_host);
-       s2s_make_authenticated(session, text or session.from_host)
-       session:reset_stream();
-       return true
-end
-
-module:hook("stanza/urn:ietf:params:xml:ns:xmpp-sasl:auth", function(event)
-       local session, stanza = event.origin, event.stanza;
-       if session.type == "s2sin_unauthed" then
-               return s2s_external_auth(session, stanza)
-       end
-
-       if session.type ~= "c2s_unauthed" then return; end
-
-       if session.sasl_handler and session.sasl_handler.selected then
-               session.sasl_handler = nil; -- allow starting a new SASL negotiation before completing an old one
-       end
-       if not session.sasl_handler then
-               session.sasl_handler = usermanager_get_sasl_handler(module.host);
-       end
-       local mechanism = stanza.attr.mechanism;
-       if not session.secure and (secure_auth_only or (mechanism == "PLAIN" and not allow_unencrypted_plain_auth)) then
-               session.send(build_reply("failure", "encryption-required"));
-               return true;
-       end
-       local valid_mechanism = session.sasl_handler:select(mechanism);
-       if not valid_mechanism then
-               session.send(build_reply("failure", "invalid-mechanism"));
-               return true;
-       end
-       return sasl_process_cdata(session, stanza);
-end);
-module:hook("stanza/urn:ietf:params:xml:ns:xmpp-sasl:response", function(event)
-       local session = event.origin;
-       if not(session.sasl_handler and session.sasl_handler.selected) then
-               session.send(build_reply("failure", "not-authorized", "Out of order SASL element"));
-               return true;
-       end
-       return sasl_process_cdata(session, event.stanza);
-end);
-module:hook("stanza/urn:ietf:params:xml:ns:xmpp-sasl:abort", function(event)
-       local session = event.origin;
-       session.sasl_handler = nil;
-       session.send(build_reply("failure", "aborted"));
-       return true;
-end);
+module:add_handler("c2s_unauthed", "auth", xmlns_sasl, sasl_handler);
+module:add_handler("c2s_unauthed", "abort", xmlns_sasl, sasl_handler);
+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' };
@@ -245,13 +179,19 @@ module:hook("stream-features", function(event)
                if secure_auth_only and not origin.secure then
                        return;
                end
-               origin.sasl_handler = usermanager_get_sasl_handler(module.host);
-               features:tag("mechanisms", mechanisms_attr);
-               for mechanism in pairs(origin.sasl_handler:mechanisms()) do
-                       if mechanism ~= "PLAIN" or origin.secure or allow_unencrypted_plain_auth then
-                               features:tag("mechanism"):text(mechanism):up();
+               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
                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();
@@ -259,45 +199,29 @@ module:hook("stream-features", function(event)
        end
 end);
 
-module:hook("s2s-stream-features", function(event)
-       local origin, features = event.origin, event.features;
-       if origin.secure and origin.type == "s2sin_unauthed" then
-               -- Offer EXTERNAL if chain is valid and either we didn't validate
-               -- the identity or it passed.
-               if origin.cert_chain_status == "valid" and origin.cert_identity_status ~= "invalid" then --TODO: Configurable
-                       module:log("debug", "Offering SASL EXTERNAL")
-                       features:tag("mechanisms", { xmlns = xmlns_sasl })
-                               :tag("mechanism"):text("EXTERNAL")
-                       :up():up();
-               end
-       end
-end);
-
-module:hook("iq/self/urn:ietf:params:xml:ns:xmpp-bind:bind", function(event)
-       local origin, stanza = event.origin, event.stanza;
+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];
-               resource = bind:child_with_name("resource");
-               resource = resource and #resource.tags == 0 and resource[1] or nil;
+               if bind and bind.attr.xmlns == xmlns_bind then
+                       resource = bind:child_with_name("resource");
+                       if resource then
+                               resource = resource[1];
+                       end
+               end
        end
-       local success, err_type, err, err_msg = sm_bind_resource(origin, resource);
-       if success then
-               origin.send(st.reply(stanza)
-                       :tag("bind", { xmlns = xmlns_bind })
-                       :tag("jid"):text(origin.full_jid));
-               origin.log("debug", "Resource bound: %s", origin.full_jid);
+       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
-               origin.send(st.error_reply(stanza, err_type, err, err_msg));
-               origin.log("debug", "Resource bind failed: %s", err_msg or err);
+               session.send(st.reply(stanza)
+                       :tag("bind", { xmlns = xmlns_bind})
+                       :tag("jid"):text(session.full_jid));
        end
-       return true;
 end);
 
-local function handle_legacy_session(event)
-       event.origin.send(st.reply(event.stanza));
-       return true;
-end
-
-module:hook("iq/self/urn:ietf:params:xml:ns:xmpp-session:session", handle_legacy_session);
-module:hook("iq/host/urn:ietf:params:xml:ns:xmpp-session:session", handle_legacy_session);
+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 cace2d693efb530b26eadb633342aa101d55533c..8b96aa157922c7bf1f63613a0e000e3baf72851f 100644 (file)
@@ -6,8 +6,6 @@
 -- COPYING file in the source package for more information.
 --
 
-local config = require "core.configmanager";
-local create_context = require "core.certmanager".create_context;
 local st = require "util.stanza";
 
 local secure_auth_only = module:get_option("c2s_require_encryption") or module:get_option("require_encryption");
@@ -47,7 +45,7 @@ module:hook("stanza/urn:ietf:params:xml:ns:xmpp-tls:starttls", function(event)
                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("debug", "TLS negotiation started for %s...", origin.type);
+               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);
@@ -85,22 +83,7 @@ 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);
+       session.conn:starttls(ssl_ctx, true);
        session.secure = false;
        return true;
 end);
-
-function module.load()
-       local ssl_config = config.rawget(module.host, "core", "ssl");
-       if not ssl_config then
-               local base_host = module.host:match("%.(.*)");
-               ssl_config = config.get(base_host, "core", "ssl");
-       end
-       host.ssl_ctx = create_context(host.host, "client", ssl_config); -- for outgoing connections
-       host.ssl_ctx_in = create_context(host.host, "server", ssl_config); -- for incoming connections
-end
-
-function module.unload()
-       host.ssl_ctx = nil;
-       host.ssl_ctx_in = nil;
-end
index ca2e6e20a522a9d59f546b013ec3777544fadf7c..de23aebba736752959bc92a93e4e77a19b3fc911 100644 (file)
@@ -15,14 +15,11 @@ local muc_host = module:get_host();
 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 then
-       if restrict_room_creation == true then 
-               restrict_room_creation = "admin";
-       elseif restrict_room_creation ~= "admin" and restrict_room_creation ~= "local" then
-               restrict_room_creation = nil;
-       end
-end
+if restrict_room_creation and restrict_room_creation ~= true then restrict_room_creation = nil; end
+
 local muc_new_room = module:require "muc".new_room;
+local register_component = require "core.componentmanager".register_component;
+local deregister_component = require "core.componentmanager".deregister_component;
 local jid_split = require "util.jid".split;
 local jid_bare = require "util.jid".bare;
 local st = require "util.stanza";
@@ -30,16 +27,12 @@ local uuid_gen = require "util.uuid".generate;
 local datamanager = require "util.datamanager";
 local um_is_admin = require "core.usermanager".is_admin;
 
-rooms = {};
-local rooms = rooms;
+local rooms = {};
 local persistent_rooms = datamanager.load(nil, muc_host, "persistent") or {};
-local component = hosts[module.host];
-
--- Configurable options
-local max_history_messages = module:get_option_number("max_history_messages");
+local component;
 
 local function is_admin(jid)
-       return um_is_admin(jid, module.host);
+       return um_is_admin(jid) or um_is_admin(jid, module.host);
 end
 
 local function room_route_stanza(room, stanza) core_post_stanza(component, stanza); end
@@ -58,9 +51,6 @@ local function room_save(room, forced)
                room._data.history = history;
        elseif forced then
                datamanager.store(node, muc_host, "config", nil);
-               if not next(room._occupants) then -- Room empty
-                       rooms[room.jid] = nil;
-               end
        end
        if forced then datamanager.store(nil, muc_host, "persistent", persistent_rooms); end
 end
@@ -68,20 +58,15 @@ end
 for jid in pairs(persistent_rooms) do
        local node = jid_split(jid);
        local data = datamanager.load(node, muc_host, "config") or {};
-       local room = muc_new_room(jid, {
-               history_length = max_history_messages;
-       });
+       local room = muc_new_room(jid);
        room._data = data._data;
-       room._data.history_length = max_history_messages; --TODO: Need to allow per-room with a global limit
        room._affiliations = data._affiliations;
        room.route_stanza = room_route_stanza;
        room.save = room_save;
        rooms[jid] = room;
 end
 
-local host_room = muc_new_room(muc_host, {
-       history_length = max_history_messages;
-});
+local host_room = muc_new_room(muc_host);
 host_room.route_stanza = room_route_stanza;
 host_room.save = room_save;
 
@@ -93,8 +78,8 @@ end
 local function get_disco_items(stanza)
        local reply = st.iq({type='result', id=stanza.attr.id, from=muc_host, to=stanza.attr.from}):query("http://jabber.org/protocol/disco#items");
        for jid, room in pairs(rooms) do
-               if not room:is_hidden() then
-                       reply:tag("item", {jid=jid, name=room:get_name()}):up();
+               if not room._data.hidden then
+                       reply:tag("item", {jid=jid, name=jid}):up();
                end
        end
        return reply; -- TODO cache disco reply
@@ -120,20 +105,15 @@ local function handle_to_domain(origin, stanza)
        end
 end
 
-function stanza_handler(event)
-       local origin, stanza = event.origin, event.stanza;
+component = register_component(muc_host, function(origin, stanza)
        local to_node, to_host, to_resource = jid_split(stanza.attr.to);
        if to_node then
                local bare = to_node.."@"..to_host;
                if to_host == muc_host or bare == muc_host then
                        local room = rooms[bare];
                        if not room then
-                               if not(restrict_room_creation) or
-                                 (restrict_room_creation == "admin" and is_admin(stanza.attr.from)) or
-                                 (restrict_room_creation == "local" and select(2, jid_split(stanza.attr.from)) == module.host:gsub("^[^%.]+%.", "")) then
-                                       room = muc_new_room(bare, {
-                                               history_length = max_history_messages;
-                                       });
+                               if not(restrict_room_creation) or is_admin(stanza.attr.from) then
+                                       room = muc_new_room(bare);
                                        room.route_stanza = room_route_stanza;
                                        room.save = room_save;
                                        rooms[bare] = room;
@@ -148,23 +128,12 @@ function stanza_handler(event)
                                origin.send(st.error_reply(stanza, "cancel", "not-allowed"));
                        end
                else --[[not for us?]] end
-               return true;
+               return;
        end
        -- to the main muc domain
        handle_to_domain(origin, stanza);
-       return true;
-end
-module:hook("iq/bare", stanza_handler, -1);
-module:hook("message/bare", stanza_handler, -1);
-module:hook("presence/bare", stanza_handler, -1);
-module:hook("iq/full", stanza_handler, -1);
-module:hook("message/full", stanza_handler, -1);
-module:hook("presence/full", stanza_handler, -1);
-module:hook("iq/host", stanza_handler, -1);
-module:hook("message/host", stanza_handler, -1);
-module:hook("presence/host", stanza_handler, -1);
-
-hosts[module.host].send = function(stanza) -- FIXME do a generic fix
+end);
+function component.send(stanza) -- FIXME do a generic fix
        if stanza.attr.type == "result" or stanza.attr.type == "error" then
                core_post_stanza(component, stanza);
        else error("component.send only supports result and error stanzas at the moment"); end
@@ -172,10 +141,14 @@ end
 
 prosody.hosts[module:get_host()].muc = { rooms = rooms };
 
+module.unload = function()
+       deregister_component(muc_host);
+end
 module.save = function()
        return {rooms = rooms};
 end
 module.restore = function(data)
+       rooms = {};
        for jid, oldroom in pairs(data.rooms or {}) do
                local room = muc_new_room(jid);
                room._jid_nick = oldroom._jid_nick;
index ec85d185d301645cc8b27daac668ef5380d6d60b..18c80325bbd1bce5db492b36643407d4d1b4bf65 100644 (file)
@@ -6,14 +6,9 @@
 -- COPYING file in the source package for more information.
 --
 
-local select = select;
-local pairs, ipairs = pairs, ipairs;
-
 local datamanager = require "util.datamanager";
 local datetime = require "util.datetime";
 
-local dataform = require "util.dataforms";
-
 local jid_split = require "util.jid".split;
 local jid_bare = require "util.jid".bare;
 local jid_prep = require "util.jid".prep;
@@ -26,7 +21,7 @@ local base64 = require "util.encodings".base64;
 local md5 = require "util.hashes".md5;
 
 local muc_domain = nil; --module:get_host();
-local default_history_length = 20;
+local history_length = 20;
 
 ------------
 local function filter_xmlns_from_array(array, filters)
@@ -93,12 +88,8 @@ room_mt.__index = room_mt;
 function room_mt:get_default_role(affiliation)
        if affiliation == "owner" or affiliation == "admin" then
                return "moderator";
-       elseif affiliation == "member" then
+       elseif affiliation == "member" or not affiliation then
                return "participant";
-       elseif not affiliation then
-               if not self:is_members_only() then
-                       return self:is_moderated() and "visitor" or "participant";
-               end
        end
 end
 
@@ -113,7 +104,7 @@ function room_mt:broadcast_presence(stanza, sid, code, nick)
        self:broadcast_except_nick(stanza, stanza.attr.from);
        local me = self._occupants[stanza.attr.from];
        if me then
-               stanza:tag("status", {code='110'}):up();
+               stanza:tag("status", {code='110'});
                stanza.attr.to = sid;
                self:_route_stanza(stanza);
        end
@@ -131,14 +122,10 @@ function room_mt:broadcast_message(stanza, historic)
                local history = self._data['history'];
                if not history then history = {}; self._data['history'] = history; end
                stanza = st.clone(stanza);
-               stanza.attr.to = "";
-               local stamp = datetime.datetime();
-               local chars = #tostring(stanza);
-               stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = muc_domain, stamp = stamp}):up(); -- XEP-0203
+               stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = muc_domain, stamp = datetime.datetime()}):up(); -- XEP-0203
                stanza:tag("x", {xmlns = "jabber:x:delay", from = muc_domain, stamp = datetime.legacy()}):up(); -- XEP-0091 (deprecated)
-               local entry = { stanza = stanza, stamp = stamp };
-               t_insert(history, entry);
-               while #history > (self._data.history_length or default_history_length) do t_remove(history, 1) end
+               t_insert(history, st.preserialize(stanza));
+               while #history > history_length do t_remove(history, 1) end
        end
 end
 function room_mt:broadcast_except_nick(stanza, nick)
@@ -164,71 +151,24 @@ function room_mt:send_occupant_list(to)
                end
        end
 end
-function room_mt:send_history(to, stanza)
+function room_mt:send_history(to)
        local history = self._data['history']; -- send discussion history
        if history then
-               local x_tag = stanza and stanza:get_child("x", "http://jabber.org/protocol/muc");
-               local history_tag = x_tag and x_tag:get_child("history", "http://jabber.org/protocol/muc");
-               
-               local maxchars = history_tag and tonumber(history_tag.attr.maxchars);
-               if maxchars then maxchars = math.floor(maxchars); end
-               
-               local maxstanzas = math.floor(history_tag and tonumber(history_tag.attr.maxstanzas) or #history);
-               if not history_tag then maxstanzas = 20; end
-
-               local seconds = history_tag and tonumber(history_tag.attr.seconds);
-               if seconds then seconds = datetime.datetime(os.time() - math.floor(seconds)); end
-
-               local since = history_tag and history_tag.attr.since;
-               if since then since = datetime.parse(since); since = since and datetime.datetime(since); end
-               if seconds and (not since or since < seconds) then since = seconds; end
-
-               local n = 0;
-               local charcount = 0;
-               local stanzacount = 0;
-               
-               for i=#history,1,-1 do
-                       local entry = history[i];
-                       if maxchars then
-                               if not entry.chars then
-                                       entry.stanza.attr.to = "";
-                                       entry.chars = #tostring(entry.stanza);
-                               end
-                               charcount = charcount + entry.chars + #to;
-                               if charcount > maxchars then break; end
-                       end
-                       if since and since > entry.stamp then break; end
-                       if n + 1 > maxstanzas then break; end
-                       n = n + 1;
-               end
-               for i=#history-n+1,#history do
-                       local msg = history[i].stanza;
-                       msg.attr.to = to;
+               for _, msg in ipairs(history) do
+                       msg = st.deserialize(msg);
+                       msg.attr.to=to;
                        self:_route_stanza(msg);
                end
        end
        if self._data['subject'] then
-               self:_route_stanza(st.message({type='groupchat', from=self._data['subject_from'] or self.jid, to=to}):tag("subject"):text(self._data['subject']));
+               self:_route_stanza(st.message({type='groupchat', from=self.jid, to=to}):tag("subject"):text(self._data['subject']));
        end
 end
 
 function room_mt:get_disco_info(stanza)
-       local count = 0; for _ in pairs(self._occupants) do count = count + 1; end
        return st.reply(stanza):query("http://jabber.org/protocol/disco#info")
-               :tag("identity", {category="conference", type="text", name=self:get_name()}):up()
-               :tag("feature", {var="http://jabber.org/protocol/muc"}):up()
-               :tag("feature", {var=self:get_password() and "muc_passwordprotected" or "muc_unsecured"}):up()
-               :tag("feature", {var=self:is_moderated() and "muc_moderated" or "muc_unmoderated"}):up()
-               :tag("feature", {var=self:is_members_only() and "muc_membersonly" or "muc_open"}):up()
-               :tag("feature", {var=self:is_persistent() and "muc_persistent" or "muc_temporary"}):up()
-               :tag("feature", {var=self:is_hidden() and "muc_hidden" or "muc_public"}):up()
-               :tag("feature", {var=self._data.whois ~= "anyone" and "muc_semianonymous" or "muc_nonanonymous"}):up()
-               :add_child(dataform.new({
-                       { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/muc#roominfo" },
-                       { name = "muc#roominfo_description", label = "Description"},
-                       { name = "muc#roominfo_occupants", label = "Number of occupants", value = tostring(count) }
-               }):form({["muc#roominfo_description"] = self:get_description()}, 'result'))
-       ;
+               :tag("identity", {category="conference", type="text"}):up()
+               :tag("feature", {var="http://jabber.org/protocol/muc"});
 end
 function room_mt:get_disco_items(stanza)
        local reply = st.reply(stanza):query("http://jabber.org/protocol/disco#items");
@@ -241,7 +181,6 @@ function room_mt:set_subject(current_nick, subject)
        -- TODO check nick's authority
        if subject == "" then subject = nil; end
        self._data['subject'] = subject;
-       self._data['subject_from'] = current_nick;
        if self.save then self:save(); end
        local msg = st.message({type='groupchat', from=current_nick})
                :tag('subject'):text(subject):up();
@@ -251,7 +190,7 @@ end
 
 local function build_unavailable_presence_from_error(stanza)
        local type, condition, text = stanza:get_error();
-       local error_message = "Kicked: "..(condition and condition:gsub("%-", " ") or "presence error");
+       local error_message = "Kicked: "..condition:gsub("%-", " ");
        if text then
                error_message = error_message..": "..text;
        end
@@ -259,87 +198,6 @@ local function build_unavailable_presence_from_error(stanza)
                :tag('status'):text(error_message);
 end
 
-function room_mt:set_name(name)
-       if name == "" or type(name) ~= "string" or name == (jid_split(self.jid)) then name = nil; end
-       if self._data.name ~= name then
-               self._data.name = name;
-               if self.save then self:save(true); end
-       end
-end
-function room_mt:get_name()
-       return self._data.name or jid_split(self.jid);
-end
-function room_mt:set_description(description)
-       if description == "" or type(description) ~= "string" then description = nil; end
-       if self._data.description ~= description then
-               self._data.description = description;
-               if self.save then self:save(true); end
-       end
-end
-function room_mt:get_description()
-       return self._data.description;
-end
-function room_mt:set_password(password)
-       if password == "" or type(password) ~= "string" then password = nil; end
-       if self._data.password ~= password then
-               self._data.password = password;
-               if self.save then self:save(true); end
-       end
-end
-function room_mt:get_password()
-       return self._data.password;
-end
-function room_mt:set_moderated(moderated)
-       moderated = moderated and true or nil;
-       if self._data.moderated ~= moderated then
-               self._data.moderated = moderated;
-               if self.save then self:save(true); end
-       end
-end
-function room_mt:is_moderated()
-       return self._data.moderated;
-end
-function room_mt:set_members_only(members_only)
-       members_only = members_only and true or nil;
-       if self._data.members_only ~= members_only then
-               self._data.members_only = members_only;
-               if self.save then self:save(true); end
-       end
-end
-function room_mt:is_members_only()
-       return self._data.members_only;
-end
-function room_mt:set_persistent(persistent)
-       persistent = persistent and true or nil;
-       if self._data.persistent ~= persistent then
-               self._data.persistent = persistent;
-               if self.save then self:save(true); end
-       end
-end
-function room_mt:is_persistent()
-       return self._data.persistent;
-end
-function room_mt:set_hidden(hidden)
-       hidden = hidden and true or nil;
-       if self._data.hidden ~= hidden then
-               self._data.hidden = hidden;
-               if self.save then self:save(true); end
-       end
-end
-function room_mt:is_hidden()
-       return self._data.hidden;
-end
-function room_mt:set_changesubject(changesubject)
-       changesubject = changesubject and true or nil;
-       if self._data.changesubject ~= changesubject then
-               self._data.changesubject = changesubject;
-               if self.save then self:save(true); end
-       end
-end
-function room_mt:get_changesubject()
-       return self._data.changesubject;
-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);
@@ -368,7 +226,7 @@ function room_mt:handle_to_occupant(origin, stanza) -- PM, vCards, etc
                                        pr.attr.to = from;
                                        pr:tag("x", {xmlns='http://jabber.org/protocol/muc#user'})
                                                :tag("item", {affiliation=occupant.affiliation or "none", role='none'}):up()
-                                               :tag("status", {code='110'}):up();
+                                               :tag("status", {code='110'});
                                        self:_route_stanza(pr);
                                        if jid ~= new_jid then
                                                pr = st.clone(occupant.sessions[new_jid])
@@ -432,15 +290,7 @@ function room_mt:handle_to_occupant(origin, stanza) -- PM, vCards, etc
                                        end
                                        is_merge = true;
                                end
-                               local password = stanza:get_child("x", "http://jabber.org/protocol/muc");
-                               password = password and password:get_child("password", "http://jabber.org/protocol/muc");
-                               password = password and password[1] ~= "" and password[1];
-                               if self:get_password() and self:get_password() ~= password then
-                                       log("debug", "%s couldn't join due to invalid password: %s", from, to);
-                                       local reply = st.error_reply(stanza, "auth", "not-authorized"):up();
-                                       reply.tags[1].attr.code = "401";
-                                       origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"}));
-                               elseif not new_nick then
+                               if not new_nick then
                                        log("debug", "%s couldn't join due to nick conflict: %s", from, to);
                                        local reply = st.error_reply(stanza, "cancel", "conflict"):up();
                                        reply.tags[1].attr.code = "409";
@@ -461,22 +311,20 @@ function room_mt:handle_to_occupant(origin, stanza) -- PM, vCards, etc
                                                self._jid_nick[from] = to;
                                                self:send_occupant_list(from);
                                                pr.attr.from = to;
-                                               pr:tag("x", {xmlns='http://jabber.org/protocol/muc#user'})
-                                                       :tag("item", {affiliation=affiliation or "none", role=role or "none"}):up();
                                                if not is_merge then
-                                                       self:broadcast_except_nick(pr, to);
+                                                       self:broadcast_presence(pr, from);
+                                               else
+                                                       pr.attr.to = from;
+                                                       self:_route_stanza(pr:tag("x", {xmlns='http://jabber.org/protocol/muc#user'})
+                                                               :tag("item", {affiliation=affiliation or "none", role=role or "none"}):up()
+                                                               :tag("status", {code='110'}));
                                                end
-                                               pr:tag("status", {code='110'}):up();
-                                               if self._data.whois == 'anyone' then
-                                                       pr:tag("status", {code='100'}):up();
+                                               if self._data.whois == 'anyone' then -- non-anonymous?
+                                                       self:_route_stanza(st.stanza("message", {from=to, to=from, type='groupchat'})
+                                                               :tag("x", {xmlns='http://jabber.org/protocol/muc#user'})
+                                                               :tag("status", {code='100'}));
                                                end
-                                               pr.attr.to = from;
-                                               self:_route_stanza(pr);
-                                               self:send_history(from, stanza);
-                                       elseif not affiliation then -- registration required for entering members-only room
-                                               local reply = st.error_reply(stanza, "auth", "registration-required"):up();
-                                               reply.tags[1].attr.code = "407";
-                                               origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"}));
+                                               self:send_history(from);
                                        else -- banned
                                                local reply = st.error_reply(stanza, "auth", "forbidden"):up();
                                                reply.tags[1].attr.code = "403";
@@ -537,84 +385,33 @@ function room_mt:handle_to_occupant(origin, stanza) -- PM, vCards, etc
 end
 
 function room_mt:send_form(origin, stanza)
+       local title = "Configuration for "..self.jid;
        origin.send(st.reply(stanza):query("http://jabber.org/protocol/muc#owner")
-               :add_child(self:get_form_layout():form())
+               :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("option", {label = 'Anyone'})
+                               :tag("value"):text('anyone'):up()
+                               :up()
+                       :up()
        );
 end
 
-function room_mt:get_form_layout()
-       local title = "Configuration for "..self.jid;
-       return dataform.new({
-               title = title,
-               instructions = title,
-               {
-                       name = 'FORM_TYPE',
-                       type = 'hidden',
-                       value = 'http://jabber.org/protocol/muc#roomconfig'
-               },
-               {
-                       name = 'muc#roomconfig_roomname',
-                       type = 'text-single',
-                       label = 'Name',
-                       value = self:get_name() or "",
-               },
-               {
-                       name = 'muc#roomconfig_roomdesc',
-                       type = 'text-single',
-                       label = 'Description',
-                       value = self:get_description() or "",
-               },
-               {
-                       name = 'muc#roomconfig_persistentroom',
-                       type = 'boolean',
-                       label = 'Make Room Persistent?',
-                       value = self:is_persistent()
-               },
-               {
-                       name = 'muc#roomconfig_publicroom',
-                       type = 'boolean',
-                       label = 'Make Room Publicly Searchable?',
-                       value = not self:is_hidden()
-               },
-               {
-                       name = 'muc#roomconfig_changesubject',
-                       type = 'boolean',
-                       label = 'Allow Occupants to Change Subject?',
-                       value = self:get_changesubject()
-               },
-               {
-                       name = 'muc#roomconfig_whois',
-                       type = 'list-single',
-                       label = 'Who May Discover Real JIDs?',
-                       value = {
-                               { value = 'moderators', label = 'Moderators Only', default = self._data.whois == 'moderators' },
-                               { value = 'anyone',     label = 'Anyone',          default = self._data.whois == 'anyone' }
-                       }
-               },
-               {
-                       name = 'muc#roomconfig_roomsecret',
-                       type = 'text-private',
-                       label = 'Password',
-                       value = self:get_password() or "",
-               },
-               {
-                       name = 'muc#roomconfig_moderatedroom',
-                       type = 'boolean',
-                       label = 'Make Room Moderated?',
-                       value = self:is_moderated()
-               },
-               {
-                       name = 'muc#roomconfig_membersonly',
-                       type = 'boolean',
-                       label = 'Make Room Members-Only?',
-                       value = self:is_members_only()
-               }
-       });
-end
-
 local valid_whois = {
-       moderators = true,
-       anyone = true,
+    moderators = true,
+    anyone = true,
 }
 
 function room_mt:process_form(origin, stanza)
@@ -623,77 +420,55 @@ function room_mt:process_form(origin, stanza)
        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", "Not a submitted form")); return; end
-
-       local fields = self:get_form_layout():data(form);
-       if fields.FORM_TYPE ~= "http://jabber.org/protocol/muc#roomconfig" then origin.send(st.error_reply(stanza, "cancel", "bad-request", "Form is not of type room configuration")); return; end
-
-       local dirty = false
-
-       local name = fields['muc#roomconfig_roomname'];
-       if name ~= self:get_name() then
-               self:set_name(name);
+       if form.attr.type ~= "submit" then origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end
+       local fields = {};
+       for _, field in pairs(form.tags) do
+               if field.name == "field" and field.attr.var and field.tags[1].name == "value" and #field.tags[1].tags == 0 then
+                       fields[field.attr.var] = field.tags[1][1] or "";
+               end
        end
+       if fields.FORM_TYPE ~= "http://jabber.org/protocol/muc#roomconfig" then origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end
 
-       local description = fields['muc#roomconfig_roomdesc'];
-       if description ~= self:get_description() then
-               self:set_description(description);
-       end
+       local dirty = false
 
        local persistent = fields['muc#roomconfig_persistentroom'];
-       dirty = dirty or (self:is_persistent() ~= persistent)
+       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 moderated = fields['muc#roomconfig_moderatedroom'];
-       dirty = dirty or (self:is_moderated() ~= moderated)
-       module:log("debug", "moderated=%s", tostring(moderated));
-
-       local membersonly = fields['muc#roomconfig_membersonly'];
-       dirty = dirty or (self:is_members_only() ~= membersonly)
-       module:log("debug", "membersonly=%s", tostring(membersonly));
-
        local public = fields['muc#roomconfig_publicroom'];
-       dirty = dirty or (self:is_hidden() ~= (not public and true or nil))
-
-       local changesubject = fields['muc#roomconfig_changesubject'];
-       dirty = dirty or (self:get_changesubject() ~= (not changesubject and true or nil))
-       module:log('debug', 'changesubject=%s', changesubject and "true" or "false")
+       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 whois = fields['muc#roomconfig_whois'];
        if not valid_whois[whois] then
-           origin.send(st.error_reply(stanza, 'cancel', 'bad-request', "Invalid value for 'whois'"));
+           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', whois)
-
-       local password = fields['muc#roomconfig_roomsecret'];
-       if self:get_password() ~= password then
-               self:set_password(password);
-       end
-       self:set_moderated(moderated);
-       self:set_members_only(membersonly);
-       self:set_persistent(persistent);
-       self:set_hidden(not public);
-       self:set_changesubject(changesubject);
+       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()
+           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'}):up();
-               end
-               if whois_changed then
-                       local code = (whois == 'moderators') and "173" or "172";
-                       msg.tags[1]:tag('status', {code = code}):up();
-               end
+           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)
+           self:broadcast_message(msg, false)
        end
 end
 
@@ -713,7 +488,8 @@ function room_mt:destroy(newjid, reason, password)
                end
                self._occupants[nick] = nil;
        end
-       self:set_persistent(false);
+       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
@@ -800,7 +576,7 @@ function room_mt:handle_to_room(origin, stanza) -- presence changes and groupcha
                        end
                elseif xmlns == "http://jabber.org/protocol/muc#owner" and (type == "get" or type == "set") and stanza.tags[1].name == "query" then
                        if self:get_affiliation(stanza.attr.from) ~= "owner" then
-                               origin.send(st.error_reply(stanza, "auth", "forbidden", "Only owners can configure rooms"));
+                               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
@@ -840,8 +616,7 @@ function room_mt:handle_to_room(origin, stanza) -- presence changes and groupcha
                        stanza.attr.from = current_nick;
                        local subject = getText(stanza, {"subject"});
                        if subject then
-                               if occupant.role == "moderator" or
-                                       ( self._data.changesubject and occupant.role == "participant" ) then -- and participant
+                               if occupant.role == "moderator" then
                                        self:set_subject(current_nick, subject); -- TODO use broadcast_message_stanza
                                else
                                        stanza.attr.from = from;
@@ -879,21 +654,14 @@ function room_mt:handle_to_room(origin, stanza) -- presence changes and groupcha
                                        :tag('x', {xmlns='http://jabber.org/protocol/muc#user'})
                                                :tag('invite', {from=_from})
                                                        :tag('reason'):text(_reason or ""):up()
-                                               :up();
-                                               if self:get_password() then
-                                                       invite:tag("password"):text(self:get_password()):up();
-                                               end
-                                       invite:up()
+                                               :up()
+                                       :up()
                                        :tag('x', {xmlns="jabber:x:conference", jid=_to}) -- COMPAT: Some older clients expect this
                                                :text(_reason or "")
                                        :up()
                                        :tag('body') -- Add a plain message for clients which don't support invites
                                                :text(_from..' invited you to the room '.._to..(_reason and (' ('.._reason..')') or ""))
                                        :up();
-                               if self:is_members_only() and not self:get_affiliation(_invitee) then
-                                       log("debug", "%s invited %s into members only room %s, granting membership", _from, _invitee, _to);
-                                       self:set_affiliation(_from, _invitee, "member", nil, "Invited by " .. self._jid_nick[_from])
-                               end
                                self:_route_stanza(invite);
                        else
                                origin.send(st.error_reply(stanza, "cancel", "jid-malformed"));
@@ -931,32 +699,19 @@ function room_mt:set_affiliation(actor, jid, affiliation, callback, reason)
        if affiliation and affiliation ~= "outcast" and affiliation ~= "owner" and affiliation ~= "admin" and affiliation ~= "member" then
                return nil, "modify", "not-acceptable";
        end
-       local actor_affiliation = self:get_affiliation(actor);
-       local target_affiliation = self:get_affiliation(jid);
-       if target_affiliation == affiliation then -- no change, shortcut
-               if callback then callback(); end
-               return true;
-       end
-       if actor_affiliation ~= "owner" then
-               if actor_affiliation ~= "admin" or target_affiliation == "owner" or target_affiliation == "admin" then
-                       return nil, "cancel", "not-allowed";
-               end
-       elseif target_affiliation == "owner" and jid_bare(actor) == jid then -- self change
-               local is_last = true;
-               for j, aff in pairs(self._affiliations) do if j ~= jid and aff == "owner" then is_last = false; break; end end
-               if is_last then
-                       return nil, "cancel", "conflict";
-               end
-       end
+       if self:get_affiliation(actor) ~= "owner" then return nil, "cancel", "not-allowed"; end
+       if jid_bare(actor) == jid then return nil, "cancel", "not-allowed"; end
        self._affiliations[jid] = affiliation;
        local role = self:get_default_role(affiliation);
-       local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"})
+       local p = st.presence()
+               :tag("x", {xmlns = "http://jabber.org/protocol/muc#user"})
                        :tag("item", {affiliation=affiliation or "none", role=role or "none"})
                                :tag("reason"):text(reason or ""):up()
                        :up();
-       local presence_type = nil;
+       local x = p.tags[1];
+       local item = x.tags[1];
        if not role then -- getting kicked
-               presence_type = "unavailable";
+               p.attr.type = "unavailable";
                if affiliation == "outcast" then
                        x:tag("status", {code="301"}):up(); -- banned
                else
@@ -969,25 +724,20 @@ function room_mt:set_affiliation(actor, jid, affiliation, callback, reason)
                        if not role then -- getting kicked
                                self._occupants[nick] = nil;
                        else
+                               t_insert(modified_nicks, nick);
                                occupant.affiliation, occupant.role = affiliation, role;
                        end
-                       for jid,pres in pairs(occupant.sessions) do -- remove for all sessions of the nick
+                       p.attr.from = nick;
+                       for jid in pairs(occupant.sessions) do -- remove for all sessions of the nick
                                if not role then self._jid_nick[jid] = nil; end
-                               local p = st.clone(pres);
-                               p.attr.from = nick;
-                               p.attr.type = presence_type;
                                p.attr.to = jid;
-                               p:add_child(x);
                                self:_route_stanza(p);
-                               if occupant.jid == jid then
-                                       modified_nicks[nick] = p;
-                               end
                        end
                end
        end
        if self.save then self:save(); end
        if callback then callback(); end
-       for nick,p in pairs(modified_nicks) do
+       for _, nick in ipairs(modified_nicks) do
                p.attr.from = nick;
                self:broadcast_except_nick(p, nick);
        end
@@ -998,60 +748,34 @@ function room_mt:get_role(nick)
        local session = self._occupants[nick];
        return session and session.role or nil;
 end
-function room_mt:can_set_role(actor_jid, occupant_jid, role)
-       local actor = self._occupants[self._jid_nick[actor_jid]];
-       local occupant = self._occupants[occupant_jid];
-       
-       if not occupant or not actor then return nil, "modify", "not-acceptable"; end
-
-       if actor.role == "moderator" then
-               if occupant.affiliation ~= "owner" and occupant.affiliation ~= "admin" then
-                       if actor.affiliation == "owner" or actor.affiliation == "admin" then
-                               return true;
-                       elseif occupant.role ~= "moderator" and role ~= "moderator" then
-                               return true;
-                       end
-               end
-       end
-       return nil, "cancel", "not-allowed";
-end
 function room_mt:set_role(actor, occupant_jid, role, callback, reason)
        if role == "none" then role = nil; end
        if role and role ~= "moderator" and role ~= "participant" and role ~= "visitor" then return nil, "modify", "not-acceptable"; end
-       local allowed, err_type, err_condition = self:can_set_role(actor, occupant_jid, role);
-       if not allowed then return allowed, err_type, err_condition; end
+       if self:get_role(self._jid_nick[actor]) ~= "moderator" then return nil, "cancel", "not-allowed"; end
        local occupant = self._occupants[occupant_jid];
-       local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"})
+       if not occupant then return nil, "modify", "not-acceptable"; end
+       if occupant.affiliation == "owner" or occupant.affiliation == "admin" then return nil, "cancel", "not-allowed"; end
+       local p = st.presence({from = occupant_jid})
+               :tag("x", {xmlns = "http://jabber.org/protocol/muc#user"})
                        :tag("item", {affiliation=occupant.affiliation or "none", nick=select(3, jid_split(occupant_jid)), role=role or "none"})
                                :tag("reason"):text(reason or ""):up()
                        :up();
-       local presence_type = nil;
        if not role then -- kick
-               presence_type = "unavailable";
+               p.attr.type = "unavailable";
                self._occupants[occupant_jid] = nil;
                for jid in pairs(occupant.sessions) do -- remove for all sessions of the nick
                        self._jid_nick[jid] = nil;
                end
-               x:tag("status", {code = "307"}):up();
+               p:tag("status", {code = "307"}):up();
        else
                occupant.role = role;
        end
-       local bp;
-       for jid,pres in pairs(occupant.sessions) do -- send to all sessions of the nick
-               local p = st.clone(pres);
-               p.attr.from = occupant_jid;
-               p.attr.type = presence_type;
+       for jid in pairs(occupant.sessions) do -- send to all sessions of the nick
                p.attr.to = jid;
-               p:add_child(x);
                self:_route_stanza(p);
-               if occupant.jid == jid then
-                       bp = p;
-               end
        end
        if callback then callback(); end
-       if bp then
-               self:broadcast_except_nick(bp, occupant_jid);
-       end
+       self:broadcast_except_nick(p, occupant_jid);
        return true;
 end
 
@@ -1093,14 +817,13 @@ end
 
 local _M = {}; -- module "muc"
 
-function _M.new_room(jid, config)
+function _M.new_room(jid)
        return setmetatable({
                jid = jid;
                _jid_nick = {};
                _occupants = {};
                _data = {
-                   whois = 'moderators';
-                   history_length = (config and config.history_length);
+                   whois = 'moderators',
                };
                _affiliations = {};
        }, room_mt);
diff --git a/prosody b/prosody
index 1a58fd33fc0228a4f661888966b2ed57ceedc7c7..0bcfce4b3f5ca696eb2b54f7fc2574c43a9bc36c 100755 (executable)
--- a/prosody
+++ b/prosody
@@ -7,8 +7,6 @@
 -- COPYING file in the source package for more information.
 --
 
--- prosody - main executable for Prosody XMPP server
-
 -- Will be modified by configure script if run --
 
 CFG_SOURCEDIR=os.getenv("PROSODY_SRCDIR");
@@ -18,24 +16,15 @@ CFG_DATADIR=os.getenv("PROSODY_DATADIR");
 
 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
 
-local function is_relative(path)
-       local path_sep = package.config:sub(1,1);
-        return ((path_sep == "/" and path:sub(1,1) ~= "/")
-       or (path_sep == "\\" and (path:sub(1,1) ~= "/" and path:sub(2,3) ~= ":\\")))
-end
-
 -- Tell Lua where to find our libraries
 if CFG_SOURCEDIR then
-       local function filter_relative_paths(path)
-               if is_relative(path) then return ""; end
-       end
-       local function sanitise_paths(paths)
-               return (paths:gsub("[^;]+;?", filter_relative_paths):gsub(";;+", ";"));
-       end
-       package.path = sanitise_paths(CFG_SOURCEDIR.."/?.lua;"..package.path);
-       package.cpath = sanitise_paths(CFG_SOURCEDIR.."/?.so;"..package.cpath);
+       package.path = CFG_SOURCEDIR.."/?.lua;"..package.path;
+       package.cpath = CFG_SOURCEDIR.."/?.so;"..package.cpath;
 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
@@ -43,16 +32,6 @@ if CFG_DATADIR then
        end
 end
 
--- Global 'prosody' object
-local prosody = { events = require "util.events".new(); };
-_G.prosody = prosody;
-
--- Check dependencies
-local dependencies = require "util.dependencies";
-if not dependencies.check_dependencies() then
-       os.exit(1);
-end
-
 -- Load the config-parsing module
 config = require "core.configmanager"
 
@@ -89,15 +68,9 @@ function read_config()
                print("\n");
                print("**************************");
                if level == "parser" then
-                       print("A problem occured while reading the config file "..(CFG_CONFIGDIR or ".").."/prosody.cfg.lua"..":");
-                       print("");
+                       print("A problem occured while reading the config file "..(CFG_CONFIGDIR or ".").."/prosody.cfg.lua");
                        local err_line, err_message = tostring(err):match("%[string .-%]:(%d*): (.*)");
-                       if err:match("chunk has too many syntax levels$") then
-                               print("An Include statement in a config file is including an already-included");
-                               print("file and causing an infinite loop. An Include statement in a config file is...");
-                       else
-                               print("Error"..(err_line and (" on line "..err_line) or "")..": "..(err_message or tostring(err)));
-                       end
+                       print("Error"..(err_line and (" on line "..err_line) or "")..": "..(err_message or tostring(err)));
                        print("");
                elseif level == "file" then
                        print("Prosody was unable to find the configuration file.");
@@ -123,21 +96,11 @@ function init_logging()
        require "core.loggingmanager"
 end
 
-function log_dependency_warnings()
-       dependencies.log_warnings();
-end
-
-function sanity_check()
-       for host, host_config in pairs(configmanager.getconfig()) do
-               if host ~= "*"
-               and host_config.core.enabled ~= false
-               and not host_config.core.component_module then
-                       return;
-               end
+function check_dependencies()
+       -- Check runtime dependencies
+       if not require "util.dependencies".check_dependencies() then
+               os.exit(1);
        end
-       log("error", "No enabled VirtualHost entries found in the config file.");
-       log("error", "At least one active host is required for Prosody to function. Exiting...");
-       os.exit(1);
 end
 
 function sandbox_require()
@@ -192,22 +155,21 @@ function init_global_state()
        full_sessions = {};
        hosts = {};
 
+       -- Global 'prosody' object
+       prosody = {};
+       local prosody = prosody;
+       
        prosody.bare_sessions = bare_sessions;
        prosody.full_sessions = full_sessions;
        prosody.hosts = hosts;
        
-       local data_path = config.get("*", "core", "data_path") or CFG_DATADIR or "data";
-       local custom_plugin_paths = config.get("*", "core", "plugin_paths");
-       if custom_plugin_paths then
-               local path_sep = package.config:sub(3,3);
-               -- path1;path2;path3;defaultpath...
-               CFG_PLUGINDIR = table.concat(custom_plugin_paths, path_sep)..path_sep..(CFG_PLUGINDIR or "plugins");
-       end
        prosody.paths = { source = CFG_SOURCEDIR, config = CFG_CONFIGDIR, 
-                         plugins = CFG_PLUGINDIR or "plugins", data = data_path };
-
+                         plugins = CFG_PLUGINDIR, data = CFG_DATADIR };
+       
        prosody.arg = _G.arg;
 
+       prosody.events = require "util.events".new();
+       
        prosody.platform = "unknown";
        if os.getenv("WINDIR") then
                prosody.platform = "windows";
@@ -238,6 +200,7 @@ function init_global_state()
        -- Function to reopen logfiles
        function prosody.reopen_logfiles()
                log("info", "Re-opening log files");
+               eventmanager.fire_event("reopen-log-files"); -- Handled by appropriate log sinks
                prosody.events.fire_event("reopen-log-files");
        end
 
@@ -328,17 +291,14 @@ end
 function load_secondary_libraries()
        --- Load and initialise core modules
        require "util.import"
-       require "util.xmppstream"
+       require "core.xmlhandlers"
        require "core.rostermanager"
+       require "core.eventmanager"
        require "core.hostmanager"
        require "core.modulemanager"
        require "core.usermanager"
        require "core.sessionmanager"
        require "core.stanza_router"
-       package.loaded['core.componentmanager'] = setmetatable({},{__index=function()
-               log("warn", "componentmanager is deprecated: %s", debug.traceback():match("\n[^\n]*\n[\s\t]*([^\n]*)"));
-               return function() end
-       end});
 
        require "net.http"
        
@@ -358,19 +318,26 @@ function load_secondary_libraries()
        ]]
 
        require "net.connlisteners";
-       require "net.httpserver";
        
        require "util.stanza"
        require "util.jid"
 end
 
 function init_data_store()
-       require "core.storagemanager";
+       local data_path = config.get("*", "core", "data_path") or CFG_DATADIR or "data";
+       require "util.datamanager".set_data_path(data_path);
+       require "util.datamanager".add_callback(function(username, host, datastore, data)
+               if config.get(host, "core", "anonymous_login") then
+                       return false;
+               end
+               return username, host, datastore, data;
+       end);
 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
@@ -476,25 +443,26 @@ end
 -- previous steps to have already been performed
 read_config();
 init_logging();
-sanity_check();
+check_dependencies();
 sandbox_require();
 set_function_metatable();
 load_libraries();
 init_global_state();
 read_version();
 log("info", "Hello and welcome to Prosody version %s", prosody.version);
-log_dependency_warnings();
 load_secondary_libraries();
 init_data_store();
 init_global_protection();
 prepare_to_start();
 
+eventmanager.fire_event("server-started");
 prosody.events.fire_event("server-started");
 
 loop();
 
 log("info", "Shutting down...");
 cleanup();
+eventmanager.fire_event("server-stopped");
 prosody.events.fire_event("server-stopped");
 log("info", "Shutdown complete");
 
index e513b11622ae901cbcc6d52e2e055d7441fe5d53..a17eb8773aaaababdb3cffe49ecb5419f61860f7 100644 (file)
@@ -1,8 +1,8 @@
 -- Prosody Example Configuration File
---
+-- 
 -- Information on configuring Prosody can be found on our
 -- website at http://prosody.im/doc/configure
---
+-- 
 -- Tip: You can check that the syntax of this file is correct
 -- when you have finished by running: luac -p prosody.cfg.lua
 -- If there are any errors, it will let you know what and where
@@ -52,37 +52,31 @@ modules_enabled = {
                "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
-               "adhoc"; -- Support for "ad-hoc commands" that can be executed with an XMPP client
-
-       -- Admin interfaces
-               "admin_adhoc"; -- Allows administration via an XMPP client that supports ad-hoc commands
-               --"admin_telnet"; -- Opens telnet console interface on localhost port 5582
 
        -- Other specific functionality
                --"posix"; -- POSIX functionality, sends server to background, enables syslog, etc.
+               --"console"; -- Opens admin telnet interface on localhost port 5582
                --"bosh"; -- Enable BOSH clients, aka "Jabber over HTTP"
                --"httpserver"; -- Serve static files from a directory over HTTP
                --"groups"; -- Shared roster support
                --"announce"; -- Send announcement to all online users
                --"welcome"; -- Welcome users who register accounts
                --"watchregistrations"; -- Alert admins of registrations
-               --"motd"; -- Send a message to users when they log in
 };
 
 -- These modules are auto-loaded, should you
--- (for some mad reason) want to disable
+-- for (for some mad reason) want to disable
 -- them then uncomment them below
 modules_disabled = {
-       -- "presence"; -- Route user/contact status information
-       -- "message"; -- Route messages
-       -- "iq"; -- Route info queries
-       -- "offline"; -- Store offline messages
+       -- "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. If you don't want
 -- to use SSL/TLS, you may comment or remove this
 ssl = {
@@ -90,44 +84,14 @@ ssl = {
        certificate = "certs/localhost.cert";
 }
 
--- Only allow encrypted streams? Encryption is already used when
--- available. These options will cause Prosody to deny connections that
--- are not encrypted. Note that some servers do not support s2s
--- encryption or have it disabled, including gmail.com and Google Apps
--- domains.
-
+-- Require encryption on client/server connections?
 --c2s_require_encryption = false
 --s2s_require_encryption = false
 
--- Select the authentication backend to use. The 'internal' providers
--- use Prosody's configured data storage to store the authentication data.
--- To allow Prosody to offer secure authentication mechanisms to clients, the
--- default provider stores passwords in plaintext. If you do not trust your
--- server please see http://prosody.im/doc/modules/mod_auth_internal_hashed
--- for information about using the hashed backend.
-
-authentication = "internal_plain"
-
--- Select the storage backend to use. By default Prosody uses flat files
--- in its configured data directory, but it also supports more backends
--- through modules. An "sql" backend is included by default, but requires
--- additional dependencies. See http://prosody.im/doc/storage for more info.
-
---storage = "sql" -- Default is "internal"
-
--- For the "sql" backend, you can uncomment *one* of the below to configure:
---sql = { driver = "SQLite3", database = "prosody.sqlite" } -- Default. 'database' is the filename.
---sql = { driver = "MySQL", database = "prosody", username = "prosody", password = "secret", host = "localhost" }
---sql = { driver = "PostgreSQL", database = "prosody", username = "prosody", password = "secret", host = "localhost" }
-
 -- Logging configuration
 -- For advanced logging see http://prosody.im/doc/logging
-log = {
-       info = "prosody.log"; -- Change 'info' to 'debug' for verbose logging
-       error = "prosody.err";
-       -- "*syslog"; -- Uncomment this for logging to syslog
-       -- "*console"; -- Log to the console, useful for debugging with daemonize=false
-}
+log = "prosody.log";
+debug = false; -- Log debug messages?
 
 ----------- Virtual hosts -----------
 -- You need to add a VirtualHost entry for each domain you wish Prosody to serve.
@@ -142,7 +106,7 @@ VirtualHost "example.com"
        -- 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 = {
+       ssl = { 
                key = "certs/example.com.key";
                certificate = "certs/example.com.crt";
        }
@@ -159,10 +123,5 @@ VirtualHost "example.com"
 --Component "proxy.example.com" "proxy65"
 
 ---Set up an external component (default component port is 5347)
---
--- External components allow adding various services, such as gateways/
--- transports to other networks like ICQ, MSN and Yahoo. For more info
--- see: http://prosody.im/doc/components#adding_an_external_component
---
 --Component "gateway.example.com"
 --     component_secret = "password"
index 8fdf3488cea6bb0bc5cdbf048f35469a502d968e..26183b21204af1db3b7ea35f26ea8256e2157645 100755 (executable)
@@ -1,7 +1,7 @@
 #!/usr/bin/env lua
 -- Prosody IM
--- Copyright (C) 2008-2010 Matthew Wild
--- Copyright (C) 2008-2010 Waqas Hussain
+-- Copyright (C) 2008-2009 Matthew Wild
+-- Copyright (C) 2008-2009 Waqas Hussain
 -- 
 -- This project is MIT/X11 licensed. Please see the
 -- COPYING file in the source package for more information.
 
 -- Will be modified by configure script if run --
 
-CFG_SOURCEDIR=os.getenv("PROSODY_SRCDIR");
+CFG_SOURCEDIR=nil;
 CFG_CONFIGDIR=os.getenv("PROSODY_CFGDIR");
-CFG_PLUGINDIR=os.getenv("PROSODY_PLUGINDIR");
+CFG_PLUGINDIR=nil;
 CFG_DATADIR=os.getenv("PROSODY_DATADIR");
 
--- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- -- -- -- -- -- -- ---- -- -- -- -- -- -- -- --
 
-local function is_relative(path)
-       local path_sep = package.config:sub(1,1);
-        return ((path_sep == "/" and path:sub(1,1) ~= "/")
-       or (path_sep == "\\" and (path:sub(1,1) ~= "/" and path:sub(2,3) ~= ":\\")))
-end
-
--- Tell Lua where to find our libraries
 if CFG_SOURCEDIR then
-       local function filter_relative_paths(path)
-               if is_relative(path) then return ""; end
-       end
-       local function sanitise_paths(paths)
-               return (paths:gsub("[^;]+;?", filter_relative_paths):gsub(";;+", ";"));
-       end
-       package.path = sanitise_paths(CFG_SOURCEDIR.."/?.lua;"..package.path);
-       package.cpath = sanitise_paths(CFG_SOURCEDIR.."/?.so;"..package.cpath);
+       package.path = CFG_SOURCEDIR.."/?.lua;"..package.path
+       package.cpath = CFG_SOURCEDIR.."/?.so;"..package.cpath
 end
 
--- 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
 
--- Global 'prosody' object
-local prosody = {
-       hosts = {};
-       events = require "util.events".new();
-       platform = "posix";
-       lock_globals = function () end;
-       unlock_globals = function () end;
-};
-_G.prosody = prosody;
-
-local dependencies = require "util.dependencies";
-if not dependencies.check_dependencies() then
-       os.exit(1);
-end
-
 config = require "core.configmanager"
 
 do
-       local filenames = {};
-       
-       local filename;
-       if arg[1] == "--config" and arg[2] then
-               table.insert(filenames, arg[2]);
-               table.remove(arg, 1); table.remove(arg, 1);
-               if CFG_CONFIGDIR then
-                       table.insert(filenames, CFG_CONFIGDIR.."/"..arg[2]);
-               end
-       else
-               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;
-               local file = io.open(filename);
-               if file then
-                       file:close();
-                       CFG_CONFIGDIR = filename:match("^(.*)[\\/][^\\/]*$");
-                       break;
-               end
-       end
-       local ok, level, err = config.load(filename);
+       -- TODO: Check for other formats when we add support for them
+       -- Use lfs? Make a new conf/ dir?
+       local ok, level, err = config.load((CFG_CONFIGDIR or ".").."/prosody.cfg.lua");
        if not ok then
                print("\n");
                print("**************************");
@@ -106,27 +56,22 @@ do
                os.exit(1);
        end
 end
-local original_logging_config = config.get("*", "core", "log");
-config.set("*", "core", "log", { { levels = { min="info" }, to = "console" } });
 
-local data_path = config.get("*", "core", "data_path") or CFG_DATADIR or "data";
-local custom_plugin_paths = config.get("*", "core", "plugin_paths");
-if custom_plugin_paths then
-       local path_sep = package.config:sub(3,3);
-       -- path1;path2;path3;defaultpath...
-       CFG_PLUGINDIR = table.concat(custom_plugin_paths, path_sep)..path_sep..(CFG_PLUGINDIR or "plugins");
+require "core.loggingmanager"
+
+if not require "util.dependencies".check_dependencies() then
+       os.exit(1);
 end
-prosody.paths = { source = CFG_SOURCEDIR, config = CFG_CONFIGDIR, 
-                 plugins = CFG_PLUGINDIR or "plugins", data = data_path };
 
-require "core.loggingmanager"
+prosody = { hosts = {}, events = events, platform = "posix" };
 
-dependencies.log_warnings();
+local data_path = config.get("*", "core", "data_path") or CFG_DATADIR or "data";
+require "util.datamanager".set_data_path(data_path);
 
 -- Switch away from root and into the prosody user --
 local switched_user, current_uid;
 
-local want_pposix_version = "0.3.5";
+local want_pposix_version = "0.3.3";
 local ok, pposix = pcall(require, "util.pposix");
 
 if ok and pposix then
@@ -137,9 +82,6 @@ if ok and pposix then
                local desired_user = config.get("*", "core", "prosody_user") or "prosody";
                local desired_group = config.get("*", "core", "prosody_group") or desired_user;
                local ok, err = pposix.setgid(desired_group);
-               if ok then
-                       ok, err = pposix.initgroups(desired_user);
-               end
                if ok then
                        ok, err = pposix.setuid(desired_user);
                        if ok then
@@ -161,45 +103,6 @@ else
        print(tostring(pposix))
 end
 
-local function test_writeable(filename)
-       local f, err = io.open(filename, "a");
-       if not f then
-               return false, err;
-       end
-       f:close();
-       return true;
-end
-
-local unwriteable_files = {};
-if type(original_logging_config) == "string" and original_logging_config:sub(1,1) ~= "*" then
-       local ok, err = test_writeable(original_logging_config);
-       if not ok then
-               table.insert(unwriteable_files, err);
-       end
-elseif type(original_logging_config) == "table" then
-       for _, rule in ipairs(original_logging_config) do
-               if rule.filename then
-                       local ok, err = test_writeable(rule.filename);
-                       if not ok then
-                               table.insert(unwriteable_files, err);
-                       end
-               end
-       end
-end
-
-if #unwriteable_files > 0 then
-       print("One of more of the Prosody log files are not");
-       print("writeable, please correct the errors and try");
-       print("starting prosodyctl again.");
-       print("");
-       for _, err in ipairs(unwriteable_files) do
-               print(err);
-       end
-       print("");
-       os.exit(1);
-end
-
-
 local error_messages = setmetatable({ 
                ["invalid-username"] = "The given username is invalid in a Jabber ID";
                ["invalid-hostname"] = "The given hostname is invalid";
@@ -207,23 +110,16 @@ local error_messages = setmetatable({
                ["no-such-user"] = "The given user does not exist on the server";
                ["unable-to-save-data"] = "Unable to store, perhaps you don't have permission?";
                ["no-pidfile"] = "There is no 'pidfile' option in the configuration file, see http://prosody.im/doc/prosodyctl#pidfile for help";
-               ["no-posix"] = "The mod_posix module is not enabled in the Prosody config file, see http://prosody.im/doc/prosodyctl for more info";
                ["no-such-method"] = "This module has no commands";
                ["not-running"] = "Prosody is not running";
                }, { __index = function (t,k) return "Error: "..(tostring(k):gsub("%-", " "):gsub("^.", string.upper)); end });
 
-hosts = prosody.hosts;
+local events = require "util.events".new();
 
-local function make_host(hostname)
-       return {
-               type = "local",
-               events = prosody.events,
-               users = require "core.usermanager".new_null_provider(hostname)
-       };
-end
+hosts = prosody.hosts;
 
 for hostname, config in pairs(config.getconfig()) do
-       hosts[hostname] = make_host(hostname);
+       hosts[hostname] = { events = events };
 end
        
 require "core.modulemanager"
@@ -232,11 +128,86 @@ require "util.prosodyctl"
 require "socket"
 -----------------------
 
-local show_message, show_warning = prosodyctl.show_message, prosodyctl.show_warning;
-local show_usage = prosodyctl.show_usage;
-local getchar, getpass = prosodyctl.getchar, prosodyctl.getpass;
-local show_yesno = prosodyctl.show_yesno;
-local read_password = prosodyctl.read_password;
+function show_message(msg, ...)
+       print(msg:format(...));
+end
+
+function show_warning(msg, ...)
+       print(msg:format(...));
+end
+
+function show_usage(usage, desc)
+       print("Usage: "..arg[0].." "..usage);
+       if desc then
+               print(" "..desc);
+       end
+end
+
+local function getchar(n)
+       local stty_ret = os.execute("stty raw -echo 2>/dev/null");
+       local ok, char;
+       if stty_ret == 0 then
+               ok, char = pcall(io.read, n or 1);
+               os.execute("stty sane");
+       else
+               ok, char = pcall(io.read, "*l");
+               if ok then
+                       char = char:sub(1, n or 1);
+               end
+       end
+       if ok then
+               return char;
+       end
+end
+       
+local function getpass()
+       local stty_ret = os.execute("stty -echo 2>/dev/null");
+       if stty_ret ~= 0 then
+               io.write("\027[08m"); -- ANSI 'hidden' text attribute
+       end
+       local ok, pass = pcall(io.read, "*l");
+       if stty_ret == 0 then
+               os.execute("stty sane");
+       else
+               io.write("\027[00m");
+       end
+       io.write("\n");
+       if ok then
+               return pass;
+       end
+end
+
+function show_yesno(prompt)
+       io.write(prompt, " ");
+       local choice = getchar():lower();
+       io.write("\n");
+       if not choice:match("%a") then
+               choice = prompt:match("%[.-(%U).-%]$");
+               if not choice then return nil; end
+       end
+       return (choice == "y");
+end
+
+local function read_password()
+       local password;
+       while true do
+               io.write("Enter new password: ");
+               password = getpass();
+               if not password then
+                       show_message("No password - cancelled");
+                       return;
+               end
+               io.write("Retype new password: ");
+               if getpass() ~= password then
+                       if not show_yesno [=[Passwords did not match, try again? [Y/n]]=] then
+                               return;
+                       end
+               else
+                       break;
+               end
+       end
+       return password;
+end
 
 local prosodyctl_timeout = (config.get("*", "core", "prosodyctl_timeout") or 5) * 2;
 -----------------------
@@ -260,17 +231,16 @@ function commands.adduser(arg)
                return 1;
        end
        
-       if not hosts[host] then
-               show_warning("The host '%s' is not listed in the configuration file (or is not enabled).", host)
-               show_warning("The user will not be able to log in until this is changed.");
-               hosts[host] = make_host(host);
-       end
-       
        if prosodyctl.user_exists{ user = user, host = host } then
                show_message [[That user already exists]];
                return 1;
        end
        
+       if not hosts[host] then
+               show_warning("The host '%s' is not listed in the configuration file (or is not enabled).", host)
+               show_warning("The user will not be able to log in until this is changed.");
+       end
+       
        local password = read_password();
        if not password then return 1; end
        
@@ -278,7 +248,7 @@ function commands.adduser(arg)
        
        if ok then return 0; end
        
-       show_message(msg)
+       show_message(error_messages[msg])
        return 1;
 end
 
@@ -299,12 +269,6 @@ function commands.passwd(arg)
                return 1;
        end
        
-       if not hosts[host] then
-               show_warning("The host '%s' is not listed in the configuration file (or is not enabled).", host)
-               show_warning("The user will not be able to log in until this is changed.");
-               hosts[host] = make_host(host);
-       end
-       
        if not prosodyctl.user_exists { user = user, host = host } then
                show_message [[That user does not exist, use prosodyctl adduser to create a new user]]
                return 1;
@@ -338,12 +302,6 @@ function commands.deluser(arg)
                return 1;
        end
        
-       if not hosts[host] then
-               show_warning("The host '%s' is not listed in the configuration file (or is not enabled).", host)
-               show_warning("The user will not be able to log in until this is changed.");
-               hosts[host] = make_host(host);
-       end
-
        if not prosodyctl.user_exists { user = user, host = host } then
                show_message [[That user does not exist on this server]]
                return 1;
@@ -481,8 +439,11 @@ function commands.restart(arg)
                return 1;
        end
        
-       commands.stop(arg);
-       return commands.start(arg);
+       local ret = commands.stop(arg);
+       if ret == 0 then
+               ret = commands.start(arg);
+       end
+       return ret;
 end
 
 -- ejabberdctl compatibility
index ae5b24f03cb6b17e8d761ef16313b3dd5ac83dda..38ef6191ad4191a2ae49a88fd24a6513e9478f19 100644 (file)
@@ -16,7 +16,6 @@ function run_all_tests()
        dotest "core.s2smanager"
        dotest "core.configmanager"
        dotest "util.stanza"
-       dotest "util.sasl.scram"
        
        dosingletest("test_sasl.lua", "latin1toutf8");
 end
@@ -217,7 +216,7 @@ function new_line_coverage_monitor(file)
                        for line, active in pairs(lines_hit) do
                                if active ~= nil then total_active_lines = total_active_lines + 1; end
                                if coverage_file then
-                                       if active == false then coverage_file:write(fn, "|", line, "|", name or "", "|miss\n");
+                                       if active == false then coverage_file:write(fn, "|", line, "|", name or "", "|miss\n"); 
                                        else coverage_file:write(fn, "|", line, "|", name or "", "|", tostring(success), "\n"); end
                                end
                        end
index 23e5948b7317339730dece8cc07a46bfad5c28d1..313b2194f19731105cf75cce1fe5f4b63556500e 100755 (executable)
@@ -36,15 +36,13 @@ end
 
 local lxp = require "lxp";
 local st = require "util.stanza";
-local xmppstream = require "util.xmppstream";
-local new_xmpp_handlers = xmppstream.new_sax_handlers;
+local init_xmlhandlers = require "core.xmlhandlers";
 local dm = require "util.datamanager"
 dm.set_data_path("data");
 
-local ns_separator = xmppstream.ns_separator;
-local ns_pattern = xmppstream.ns_pattern;
-
-local xmlns_xep227 = "http://www.xmpp.org/extensions/xep-0227.html#ns";
+local ns_separator = "\1";
+local ns_pattern = "^([^"..ns_separator.."]*)"..ns_separator.."?(.*)$";
+local ns_xep227 = "http://www.xmpp.org/extensions/xep-0227.html#ns";
 
 -----------------------------------------------------------------------
 
@@ -116,7 +114,7 @@ function store_offline_messages(username, host, offline_messages)
                --print("message :"..ch:pretty_print());
                local ret, err = dm.list_append(username, host, "offline", st.preserialize(ch));
                print("["..(err or "success").."] stored offline message: " ..username.."@"..host.." - "..ch.attr.from);
-       end
+       end
 end
 
 
@@ -148,7 +146,7 @@ local user_name = "";
 
 local cb = {
        stream_tag = "user",
-       stream_ns = xmlns_xep227,
+       stream_ns = ns_xep227,
 };
 function cb.streamopened(session, attr)
        session.notopen = false;
@@ -178,7 +176,7 @@ function cb.handlestanza(session, stanza)
        end
 end
 
-local user_handlers = new_xmpp_handlers({ notopen = true }, cb);
+local user_handlers = init_xmlhandlers({ notopen = true, }, cb);
 
 -----------------------------------------------------------------------
 
@@ -197,10 +195,10 @@ function lxp_handlers.StartElement(parser, elementname, attributes)
        if curr_host ~= "" then
                -- forward to xmlhandlers
                user_handlers:StartElement(elementname, attributes);
-       elseif (curr_ns == xmlns_xep227) and (name == "host") then
+       elseif (curr_ns == ns_xep227) and (name == "host") then
                curr_host = attributes["jid"]; -- start of host element
                print("Begin parsing host "..curr_host);
-       elseif (curr_ns ~= xmlns_xep227) or (name ~= "server-data") then
+       elseif (curr_ns ~= ns_xep227) or (name ~= "server-data") then
                io.stderr:write("Unhandled XML element: ", name, "\n");
                os.exit(1);
        end
@@ -215,14 +213,14 @@ function lxp_handlers.EndElement(parser, elementname)
        --count = count - 1;
        --io.write("- ", string.rep(" ", count), name, "  (", curr_ns, ")", "\n")
        if curr_host ~= "" then
-               if (curr_ns == xmlns_xep227) and (name == "host") then
+               if (curr_ns == ns_xep227) and (name == "host") then
                        print("End parsing host "..curr_host);
                        curr_host = "" -- end of host element
                else
                        -- forward to xmlhandlers
                        user_handlers:EndElement(elementname);
                end
-       elseif (curr_ns ~= xmlns_xep227) or (name ~= "server-data") then
+       elseif (curr_ns ~= ns_xep227) or (name ~= "server-data") then
                io.stderr:write("Unhandled XML element: ", name, "\n");
                os.exit(1);
        end
index 1ca934ad6d3baa6e7ee78359a939fc302342d6a0..4b2606dc77907ee88b404665dd43cedc4db47766 100644 (file)
@@ -7,25 +7,16 @@ LUA_LIB?=lua$(LUA_SUFFIX)
 IDN_LIB?=idn
 OPENSSL_LIB?=crypto
 CC?=gcc
-CXX?=g++
 LD?=gcc
 
 .SUFFIXES: .c .o .so
 
-encodings.so: encodings.o
-       MACOSX_DEPLOYMENT_TARGET="10.3"; export MACOSX_DEPLOYMENT_TARGET;
-       $(CC) -o $@ $< $(LDFLAGS) $(IDNA_LIBS)
-
-hashes.so: hashes.o
-       MACOSX_DEPLOYMENT_TARGET="10.3"; export MACOSX_DEPLOYMENT_TARGET;
-       $(CC) -o $@ $< $(LDFLAGS) -l$(OPENSSL_LIB)
-
 .c.o:
        $(CC) $(CFLAGS) -I$(LUA_INCDIR) -c -o $@ $<
 
 .o.so:
        MACOSX_DEPLOYMENT_TARGET="10.3"; export MACOSX_DEPLOYMENT_TARGET;
-       $(LD) -o $@ $< $(LDFLAGS)
+       $(LD) $(LDFLAGS) -o $@ $< -L$(LUA_LIBDIR) -llua$(LUA_SUFFIX) -lidn -lcrypto
 
 all: encodings.so hashes.so pposix.so signal.so
 
index ffd2128835ddb9124cc8fb858a23be23e8e2734a..9f16f178b6cffd34e529babf1ad8674a60988a7e 100644 (file)
@@ -13,7 +13,7 @@
 * POSIX support functions for Lua
 */
 
-#define MODULE_VERSION "0.3.5"
+#define MODULE_VERSION "0.3.3"
 
 #include <stdlib.h>
 #include <math.h>
@@ -22,7 +22,6 @@
 #include <sys/resource.h>
 #include <sys/types.h>
 #include <sys/stat.h>
-#include <sys/utsname.h>
 #include <fcntl.h>
 
 #include <syslog.h>
@@ -360,62 +359,6 @@ int lc_setgid(lua_State* L)
        return 2;
 }
 
-int lc_initgroups(lua_State* L)
-{
-       int ret;
-       gid_t gid;
-       struct passwd *p;
-
-       if(!lua_isstring(L, 1))
-       {
-               lua_pushnil(L);
-               lua_pushstring(L, "invalid-username");
-               return 2;
-       }
-       p = getpwnam(lua_tostring(L, 1));
-       if(!p)
-       {
-               lua_pushnil(L);
-               lua_pushstring(L, "no-such-user");
-               return 2;
-       }
-       if(lua_gettop(L) < 2)
-               lua_pushnil(L);
-       switch(lua_type(L, 2))
-       {
-       case LUA_TNIL:
-               gid = p->pw_gid;
-               break;
-       case LUA_TNUMBER:
-               gid = lua_tointeger(L, 2);
-               break;
-       default:
-               lua_pushnil(L);
-               lua_pushstring(L, "invalid-gid");
-               return 2;
-       }
-       ret = initgroups(lua_tostring(L, 1), gid);
-       switch(errno)
-       {
-       case 0:
-               lua_pushboolean(L, 1);
-               lua_pushnil(L);
-               break;
-       case ENOMEM:
-               lua_pushnil(L);
-               lua_pushstring(L, "no-memory");
-               break;
-       case EPERM:
-               lua_pushnil(L);
-               lua_pushstring(L, "permission-denied");
-               break;
-       default:
-               lua_pushnil(L);
-               lua_pushstring(L, "unknown-error");
-       }
-       return 2;
-}
-
 int lc_umask(lua_State* L)
 {
        char old_mode_string[7];
@@ -554,29 +497,6 @@ int lc_abort(lua_State* L)
        return 0;
 }
 
-int lc_uname(lua_State* L)
-{
-       struct utsname uname_info;
-       if(uname(&uname_info) != 0)
-       {
-               lua_pushnil(L);
-               lua_pushstring(L, strerror(errno));
-               return 2;
-       }
-       lua_newtable(L);
-       lua_pushstring(L, uname_info.sysname);
-       lua_setfield(L, -2, "sysname");
-       lua_pushstring(L, uname_info.nodename);
-       lua_setfield(L, -2, "nodename");
-       lua_pushstring(L, uname_info.release);
-       lua_setfield(L, -2, "release");
-       lua_pushstring(L, uname_info.version);
-       lua_setfield(L, -2, "version");
-       lua_pushstring(L, uname_info.machine);
-       lua_setfield(L, -2, "machine");
-       return 1;
-}
-
 /* Register functions */
 
 int luaopen_util_pposix(lua_State *L)
@@ -597,7 +517,6 @@ int luaopen_util_pposix(lua_State *L)
 
                { "setuid", lc_setuid },
                { "setgid", lc_setgid },
-               { "initgroups", lc_initgroups },
 
                { "umask", lc_umask },
 
@@ -606,8 +525,6 @@ int luaopen_util_pposix(lua_State *L)
                { "setrlimit", lc_setrlimit },
                { "getrlimit", lc_getrlimit },
 
-               { "uname", lc_uname },
-
                { NULL, NULL }
        };
 
@@ -620,4 +537,4 @@ int luaopen_util_pposix(lua_State *L)
        lua_setfield(L, -2, "_VERSION");
 
        return 1;
-}
+};
index 961d2d3e06acda484613a0ebdfc54a8f25625586..2d13383fbd2f8115b6454dc9f42db465a787b49d 100644 (file)
@@ -165,13 +165,13 @@ static struct signal_event *last_event = NULL;
 
 static void sighook(lua_State *L, lua_Debug *ar)
 {
-  struct signal_event *event;
   /* restore the old hook */
   lua_sethook(L, Hsig, Hmask, Hcount);
 
   lua_pushstring(L, LUA_SIGNAL);
   lua_gettable(L, LUA_REGISTRYINDEX);
 
+  struct signal_event *event;
   while((event = signal_queue))
   {
     lua_pushnumber(L, event->Nsig);
@@ -326,7 +326,7 @@ static int l_raise(lua_State *L)
   return 1;
 }
 
-#if defined(__unix__) || defined(__APPLE__)
+#if defined _POSIX_SOURCE || (defined(sun) || defined(__sun))
 
 /* define some posix only functions */
 
@@ -373,7 +373,7 @@ static int l_kill(lua_State *L)
 static const struct luaL_Reg lsignal_lib[] = {
   {"signal", l_signal},
   {"raise", l_raise},
-#if defined(__unix__) || defined(__APPLE__)
+#if defined _POSIX_SOURCE || (defined(sun) || defined(__sun))
   {"kill", l_kill},
 #endif
   {NULL, NULL}
index d5e9c88ca7ad0130b879060950d579420edb65d1..57cd2594e954d81f9c9cc56310eb6434183a8caa 100644 (file)
@@ -22,7 +22,6 @@ local t_insert = table.insert;
 local append = require "util.serialization".append;
 local path_separator = "/"; if os.getenv("WINDIR") then path_separator = "\\" end
 local lfs = require "lfs";
-local prosody = prosody;
 local raw_mkdir;
 
 if prosody.platform == "posix" then
@@ -57,7 +56,7 @@ local function mkdir(path)
        return path;
 end
 
-local data_path = (prosody and prosody.paths and prosody.paths.data) or ".";
+local data_path = "data";
 local callbacks = {};
 
 ------- API -------------
@@ -115,7 +114,7 @@ function load(username, host, datastore)
        if not data then
                local mode = lfs.attributes(getpath(username, host, datastore), "mode");
                if not mode then
-                       log("debug", "Assuming empty "..datastore.." storage ('"..ret.."') for user: "..(username or "nil").."@"..(host or "nil"));
+                       log("debug", "Failed to load "..datastore.." storage ('"..ret.."') for user: "..(username or "nil").."@"..(host or "nil"));
                        return nil;
                else -- file exists, but can't be read
                        -- TODO more detailed error checking and logging?
@@ -205,22 +204,15 @@ end
 function list_load(username, host, datastore)
        local data, ret = loadfile(getpath(username, host, datastore, "list"));
        if not data then
-               local mode = lfs.attributes(getpath(username, host, datastore, "list"), "mode");
-               if not mode then
-                       log("debug", "Assuming empty "..datastore.." storage ('"..ret.."') for user: "..(username or "nil").."@"..(host or "nil"));
-                       return nil;
-               else -- file exists, but can't be read
-                       -- TODO more detailed error checking and logging?
-                       log("error", "Failed to load "..datastore.." storage ('"..ret.."') for user: "..(username or "nil").."@"..(host or "nil"));
-                       return nil, "Error reading storage";
-               end
+               log("debug", "Failed to load "..datastore.." storage ('"..ret.."') for user: "..(username or "nil").."@"..(host or "nil"));
+               return nil;
        end
        local items = {};
        setfenv(data, {item = function(i) t_insert(items, i); end});
        local success, ret = pcall(data);
        if not success then
                log("error", "Unable to load "..datastore.." storage ('"..ret.."') for user: "..(username or "nil").."@"..(host or "nil"));
-               return nil, "Error reading storage";
+               return nil;
        end
        return items;
 end
index 5baea942af4b52781332af029b0c30e47841219d..6024dd63e6b5154fe7ac7f4cd7914be48da00631 100644 (file)
@@ -35,19 +35,6 @@ function missingdep(name, sources, msg)
        print("");
 end
 
--- COMPAT w/pre-0.8 Debian: The Debian config file used to use 
--- util.ztact, which has been removed from Prosody in 0.8. This
--- is to log an error for people who still use it, so they can
--- update their configs.
-package.preload["util.ztact"] = function ()
-       if not package.loaded["core.loggingmanager"] then
-               error("util.ztact has been removed from Prosody and you need to fix your config "
-                   .."file. More information can be found at http://prosody.im/doc/packagers#ztact", 0);
-       else
-               error("module 'util.ztact' has been deprecated in Prosody 0.8.");
-       end
-end;
-
 function check_dependencies()
        local fatal;
        
@@ -91,6 +78,11 @@ function check_dependencies()
                                ["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
        
        local encodings, err = softreq "util.encodings"
@@ -129,13 +121,5 @@ function check_dependencies()
        return not fatal;
 end
 
-function log_warnings()
-       if ssl then
-               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
-end
 
 return _M;
index 412acccd762cb755a9c0627f2689ea82b5989465..363d2ac6b0907977e862e281fb84756a6f03de91 100644 (file)
@@ -7,29 +7,29 @@
 --
 
 
+local ipairs = ipairs;
 local pairs = pairs;
 local t_insert = table.insert;
 local t_sort = table.sort;
-local setmetatable = setmetatable;
-local next = next;
+local select = select;
 
 module "events"
 
 function new()
+       local dispatchers = {};
        local handlers = {};
        local event_map = {};
-       local function _rebuild_index(handlers, event)
+       local function _rebuild_index(event) -- TODO optimize index rebuilding
                local _handlers = event_map[event];
-               if not _handlers or next(_handlers) == nil then return; end
-               local index = {};
+               local index = handlers[event];
+               if index then
+                       for i=#index,1,-1 do index[i] = nil; end
+               else index = {}; handlers[event] = index; end
                for handler in pairs(_handlers) do
                        t_insert(index, handler);
                end
                t_sort(index, function(a, b) return _handlers[a] > _handlers[b]; end);
-               handlers[event] = index;
-               return index;
        end;
-       setmetatable(handlers, { __index = _rebuild_index });
        local function add_handler(event, handler, priority)
                local map = event_map[event];
                if map then
@@ -38,16 +38,13 @@ function new()
                        map = {[handler] = priority or 0};
                        event_map[event] = map;
                end
-               handlers[event] = nil;
+               _rebuild_index(event);
        end;
        local function remove_handler(event, handler)
                local map = event_map[event];
                if map then
                        map[handler] = nil;
-                       handlers[event] = nil;
-                       if next(map) == nil then
-                               event_map[event] = nil;
-                       end
+                       _rebuild_index(event);
                end
        end;
        local function add_handlers(handlers)
@@ -60,7 +57,22 @@ function new()
                        remove_handler(event, handler);
                end
        end;
-       local function fire_event(event, ...)
+       local function _create_dispatcher(event) -- FIXME duplicate code in fire_event
+               local h = handlers[event];
+               if not h then h = {}; handlers[event] = h; end
+               local dispatcher = function(...)
+                       for i=1,#h do
+                               local ret = h[i](...);
+                               if ret ~= nil then return ret; end
+                       end
+               end;
+               dispatchers[event] = dispatcher;
+               return dispatcher;
+       end;
+       local function get_dispatcher(event)
+               return dispatchers[event] or _create_dispatcher(event);
+       end;
+       local function fire_event(event, ...) -- FIXME duplicates dispatcher code
                local h = handlers[event];
                if h then
                        for i=1,#h do
@@ -69,12 +81,24 @@ function new()
                        end
                end
        end;
+       local function get_named_arg_dispatcher(event, ...)
+               local dispatcher = get_dispatcher(event);
+               local keys = {...};
+               local data = {};
+               return function(...)
+                       for i, key in ipairs(keys) do data[key] = select(i, ...); end
+                       dispatcher(data);
+               end;
+       end;
        return {
                add_handler = add_handler;
                remove_handler = remove_handler;
-               add_handlers = add_handlers;
-               remove_handlers = remove_handlers;
+               add_plugin = add_plugin;
+               remove_plugin = remove_plugin;
+               get_dispatcher = get_dispatcher;
                fire_event = fire_event;
+               get_named_arg_dispatcher = get_named_arg_dispatcher;
+               _dispatchers = dispatchers;
                _handlers = handlers;
                _event_map = event_map;
        };
index 6df6986e2f25f2b86d11e84a32d8902a0070ed22..66dd41d81d8978796d371bc64ed080f8a35da4c4 100644 (file)
@@ -40,7 +40,7 @@ hash
 blocksize
        the blocksize for the hash function in bytes
 hex
-       return raw hash or hexadecimal string
+  return raw hash or hexadecimal string
 --]]
 function hmac(key, message, hash, blocksize, hex)
        if #key > blocksize then
index 555e41bf82b176ab001a4b5d07d6a3c60e3b1457..956b92bd640f2d5b437f43ea8b41bcfea2286568 100644 (file)
@@ -6,55 +6,41 @@
 -- COPYING file in the source package for more information.
 --
 
-local dir_sep, path_sep = package.config:match("^(%S+)%s(%S+)");
-local plugin_dir = {};
-for path in (CFG_PLUGINDIR or "./plugins/"):gsub("[/\\]", dir_sep):gmatch("[^"..path_sep.."]+") do
-       path = path..dir_sep; -- add path separator to path end
-       path = path:gsub(dir_sep..dir_sep.."+", dir_sep); -- coalesce multiple separaters
-       plugin_dir[#plugin_dir + 1] = path;
-end
+
+local plugin_dir = CFG_PLUGINDIR or "./plugins/";
 
 local io_open, os_time = io.open, os.time;
 local loadstring, pairs = loadstring, pairs;
 
+local datamanager = require "util.datamanager";
+
 module "pluginloader"
 
-local function load_file(names)
-       local file, err, path;
-       for i=1,#plugin_dir do
-               for j=1,#names do
-                       path = plugin_dir[i]..names[j];
-                       file, err = io_open(path);
-                       if file then
-                               local content = file:read("*a");
-                               file:close();
-                               return content, path;
-                       end
-               end
-       end
-       return file, err;
+local function load_file(name)
+       local file, err = io_open(plugin_dir..name);
+       if not file then return file, err; end
+       local content = file:read("*a");
+       file:close();
+       return content, name;
 end
 
-function load_resource(plugin, resource)
-       resource = resource or "mod_"..plugin..".lua";
-
-       local names = {
-               "mod_"..plugin.."/"..plugin.."/"..resource; -- mod_hello/hello/mod_hello.lua
-               "mod_"..plugin.."/"..resource;              -- mod_hello/mod_hello.lua
-               plugin.."/"..resource;                      -- hello/mod_hello.lua
-               resource;                                   -- mod_hello.lua
-       };
+function load_resource(plugin, resource, loader)
+       if not resource then
+               resource = "mod_"..plugin..".lua";
+       end
+       loader = loader or load_file;
 
-       return load_file(names);
+       local content, err = loader(plugin.."/"..resource);
+       if not content then content, err = loader(resource); end
+       -- TODO add support for packed plugins
+       
+       return content, err;
 end
 
 function load_code(plugin, resource)
        local content, err = load_resource(plugin, resource);
        if not content then return content, err; end
-       local path = err;
-       local f, err = loadstring(content, "@"..path);
-       if not f then return f, err; end
-       return f, path;
+       return loadstring(content, "@"..err);
 end
 
 return _M;
index aa1850b259e14e07d0abb277a44956e4c62f6092..04d58d1d3af33d5775aafc241e37afcfcb206a72 100644 (file)
 local config = require "core.configmanager";
 local encodings = require "util.encodings";
 local stringprep = encodings.stringprep;
-local storagemanager = require "core.storagemanager";
 local usermanager = require "core.usermanager";
 local signal = require "util.signal";
-local set = require "util.set";
 local lfs = require "lfs";
-local pcall = pcall;
 
 local nodeprep, nameprep = stringprep.nodeprep, stringprep.nameprep;
 
 local io, os = io, os;
-local print = print;
 local tostring, tonumber = tostring, tonumber;
 
 local CFG_SOURCEDIR = _G.CFG_SOURCEDIR;
 
-local _G = _G;
-local prosody = prosody;
-
 module "prosodyctl"
 
--- UI helpers
-function show_message(msg, ...)
-       print(msg:format(...));
-end
-
-function show_warning(msg, ...)
-       print(msg:format(...));
-end
-
-function show_usage(usage, desc)
-       print("Usage: ".._G.arg[0].." "..usage);
-       if desc then
-               print(" "..desc);
-       end
-end
-
-function getchar(n)
-       local stty_ret = os.execute("stty raw -echo 2>/dev/null");
-       local ok, char;
-       if stty_ret == 0 then
-               ok, char = pcall(io.read, n or 1);
-               os.execute("stty sane");
-       else
-               ok, char = pcall(io.read, "*l");
-               if ok then
-                       char = char:sub(1, n or 1);
-               end
-       end
-       if ok then
-               return char;
-       end
-end
-
-function getpass()
-       local stty_ret = os.execute("stty -echo 2>/dev/null");
-       if stty_ret ~= 0 then
-               io.write("\027[08m"); -- ANSI 'hidden' text attribute
-       end
-       local ok, pass = pcall(io.read, "*l");
-       if stty_ret == 0 then
-               os.execute("stty sane");
-       else
-               io.write("\027[00m");
-       end
-       io.write("\n");
-       if ok then
-               return pass;
-       end
-end
-
-function show_yesno(prompt)
-       io.write(prompt, " ");
-       local choice = getchar():lower();
-       io.write("\n");
-       if not choice:match("%a") then
-               choice = prompt:match("%[.-(%U).-%]$");
-               if not choice then return nil; end
-       end
-       return (choice == "y");
-end
-
-function read_password()
-       local password;
-       while true do
-               io.write("Enter new password: ");
-               password = getpass();
-               if not password then
-                       show_message("No password - cancelled");
-                       return;
-               end
-               io.write("Retype new password: ");
-               if getpass() ~= password then
-                       if not show_yesno [=[Passwords did not match, try again? [Y/n]]=] then
-                               return;
-                       end
-               else
-                       break;
-               end
-       end
-       return password;
-end
-
--- Server control
 function adduser(params)
        local user, host, password = nodeprep(params.user), nameprep(params.host), params.password;
        if not user then
@@ -120,29 +30,16 @@ function adduser(params)
        elseif not host then
                return false, "invalid-hostname";
        end
-
-       local provider = prosody.hosts[host].users;
-       if not(provider) or provider.name == "null" then
-               usermanager.initialize_host(host);
-       end
-       storagemanager.initialize_host(host);
        
-       local ok, errmsg = usermanager.create_user(user, password, host);
+       local ok = usermanager.create_user(user, password, host);
        if not ok then
-               return false, errmsg;
+               return false, "unable-to-save-data";
        end
        return true;
 end
 
 function user_exists(params)
-       local user, host, password = nodeprep(params.user), nameprep(params.host), params.password;
-       local provider = prosody.hosts[host].users;
-       if not(provider) or provider.name == "null" then
-               usermanager.initialize_host(host);
-       end
-       storagemanager.initialize_host(host);
-       
-       return usermanager.user_exists(user, host);
+       return usermanager.user_exists(params.user, params.host);
 end
 
 function passwd(params)
@@ -168,11 +65,6 @@ function getpid()
                return false, "no-pidfile";
        end
        
-       local modules_enabled = set.new(config.get("*", "core", "modules_enabled"));
-       if not modules_enabled:contains("posix") then
-               return false, "no-posix";
-       end
-       
        local file, err = io.open(pidfile, "r+");
        if not file then
                return false, "pidfile-read-failed", err;
index 17d10b804b7b9188a434d2dfe669db078912b905..306acc0c590998be8eb151e3ddc7cdf13ad12058 100644 (file)
 --    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
+local md5 = require "util.hashes".md5;
+local log = require "util.logger".init("sasl");
+local st = require "util.stanza";
+local set = require "util.set";
+local array = require "util.array";
+local to_unicode = require "util.encodings".idna.to_unicode;
+
+local tostring = tostring;
 local pairs, ipairs = pairs, ipairs;
-local t_insert = table.insert;
+local t_insert, t_concat = table.insert, table.concat;
+local s_match = string.match;
 local type = type
+local error = error
 local setmetatable = setmetatable;
 local assert = assert;
 local require = require;
 
+require "util.iterators"
+local keys = keys
+
+local array = require "util.array"
 module "sasl"
 
 --[[
@@ -47,50 +61,72 @@ local function registerMechanism(name, backends, f)
 end
 
 -- create a new SASL object which can be used to authenticate clients
-function new(realm, profile)
-       local mechanisms = profile.mechanisms;
-       if not mechanisms then
-               mechanisms = {};
-               for backend, f in pairs(profile) do
-                       if backend_mechanism[backend] then
-                               for _, mechanism in ipairs(backend_mechanism[backend]) do
-                                       mechanisms[mechanism] = true;
-                               end
-                       end
-               end
-               profile.mechanisms = mechanisms;
-       end
-       return setmetatable({ profile = profile, realm = realm, mechs = mechanisms }, method);
+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
 
--- get a fresh clone with the same realm and profile
+-- get a fresh clone with the same realm, profiles and forbidden mechanisms
 function method:clean_clone()
-       return new(self.realm, self.profile)
+       return new(self.realm, self.profile, self:forbidden())
+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
 
 -- get a list of possible SASL mechanims to use
 function method:mechanisms()
-       return self.mechs;
+       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
+                       end
+               end
+       end
+       self["possible_mechanisms"] = mechanisms;
+       return array.collect(keys(mechanisms));
 end
 
 -- select a mechanism to use
 function method:select(mechanism)
-       if not self.selected and self.mechs[mechanism] then
-               self.selected = mechanism;
-               return true;
+       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 mechanisms[self.selected](self, message);
+       return self.mech_i(self, message);
 end
 
 -- load the mechanisms
-require "util.sasl.plain"     .init(registerMechanism);
-require "util.sasl.digest-md5".init(registerMechanism);
-require "util.sasl.anonymous" .init(registerMechanism);
-require "util.sasl.scram"     .init(registerMechanism);
+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 ca5fe4040d90f4350711c9e293c2c5ac8b7687ea..7b5a5081f8d743b2891afa1b613cbbea4bd47c73 100644 (file)
@@ -16,26 +16,16 @@ local s_match = string.match;
 local log = require "util.logger".init("sasl");
 local generate_uuid = require "util.uuid".generate;
 
-module "sasl.anonymous"
+module "anonymous"
 
 --=========================
 --SASL ANONYMOUS according to RFC 4505
-
---[[
-Supported Authentication Backends
-
-anonymous:
-       function(username, realm)
-               return true; --for normal usage just return true; if you don't like the supplied username you can return false.
-       end
-]]
-
 local function anonymous(self, message)
        local username;
        repeat
                username = generate_uuid();
-       until self.profile.anonymous(self, username, self.realm);
-       self.username = username;
+       until self.profile.anonymous(username, self.realm);
+       self["username"] = username;
        return "success"
 end
 
@@ -43,4 +33,4 @@ function init(registerMechanism)
        registerMechanism("ANONYMOUS", {"anonymous"}, anonymous);
 end
 
-return _M;
+return _M;
\ No newline at end of file
index de2538fc40cc13e090462ed7054494a831fb598f..2837148ec4723270b92d11b30ac551299e230184 100644 (file)
@@ -24,7 +24,7 @@ local md5 = require "util.hashes".md5;
 local log = require "util.logger".init("sasl");
 local generate_uuid = require "util.uuid".generate;
 
-module "sasl.digest-md5"
+module "digest-md5"
 
 --=========================
 --SASL DIGEST-MD5 according to RFC 2831
@@ -181,12 +181,12 @@ local function digest(self, message)
                self.username = response["username"];
                local Y, state;
                if self.profile.plain then
-                       local password, state = self.profile.plain(self, response["username"], self.realm)
+                       local password, state = self.profile.plain(response["username"], self.realm)
                        if state == nil then return "failure", "not-authorized"
                        elseif state == false then return "failure", "account-disabled" end
                        Y = md5(response["username"]..":"..response["realm"]..":"..password);
                elseif self.profile["digest-md5"] then
-                       Y, state = self.profile["digest-md5"](self, response["username"], self.realm, response["realm"], response["charset"])
+                       Y, state = self.profile["digest-md5"](response["username"], self.realm, response["realm"], response["charset"])
                        if state == nil then return "failure", "not-authorized"
                        elseif state == false then return "failure", "account-disabled" end
                elseif self.profile["digest-md5-test"] then
@@ -240,4 +240,4 @@ function init(registerMechanism)
        registerMechanism("DIGEST-MD5", {"plain"}, digest);
 end
 
-return _M;
+return _M;
\ No newline at end of file
index fb20cf97d45e0424afd7c7044c5062dc37d68b73..3982118299ac562ab2ccb5b02fe306aee4dd6992 100644 (file)
@@ -15,7 +15,7 @@ local s_match = string.match;
 local saslprep = require "util.encodings".stringprep.saslprep;
 local log = require "util.logger".init("sasl");
 
-module "sasl.plain"
+module "plain"
 
 -- ================================
 -- SASL PLAIN according to RFC 4616
@@ -29,7 +29,7 @@ plain:
        end
 
 plain_test:
-       function(username, password, realm)
+       function(username, realm, password)
                return true or false, state;
        end
 ]]
@@ -57,10 +57,10 @@ local function plain(self, message)
        local correct, state = false, false;
        if self.profile.plain then
                local correct_password;
-               correct_password, state = self.profile.plain(self, authentication, self.realm);
-               correct = (correct_password == password);
+               correct_password, state = self.profile.plain(authentication, self.realm);
+               if correct_password == password then correct = true; else correct = false; end
        elseif self.profile.plain_test then
-               correct, state = self.profile.plain_test(self, authentication, password, self.realm);
+               correct, state = self.profile.plain_test(authentication, self.realm, password);
        end
 
        self.username = authentication
index aad33ebcaafd45791ecdde7466f2fb3714564543..1340423cd932e3d3ae1117dd57bdb2ba80bd2e26 100644 (file)
@@ -24,10 +24,10 @@ local t_concat = table.concat;
 local char = string.char;
 local byte = string.byte;
 
-module "sasl.scram"
+module "scram"
 
 --=========================
---SASL SCRAM-SHA-1 according to RFC 5802
+--SASL SCRAM-SHA-1 according to draft-ietf-sasl-scram-10
 
 --[[
 Supported Authentication Backends
@@ -35,7 +35,7 @@ Supported Authentication Backends
 scram_{MECH}:
        -- MECH being a standard hash name (like those at IANA's hash registry) with '-' replaced with '_'
        function(username, realm)
-               return stored_key, server_key, iteration_count, salt, state;
+               return salted_password, iteration_count, salt, state;
        end
 ]]
 
@@ -65,9 +65,9 @@ local function binaryXOR( a, b )
 end
 
 -- hash algorithm independent Hi(PBKDF2) implementation
-function Hi(hmac, str, salt, i)
+local function Hi(hmac, str, salt, i)
        local Ust = hmac(str, salt.."\0\0\0\1");
-       local res = Ust;
+       local res = Ust;        
        for n=1,i-1 do
                local Und = hmac(str, Ust)
                res = binaryXOR(res, Und)
@@ -79,13 +79,13 @@ end
 local function validate_username(username)
        -- check for forbidden char sequences
        for eq in username:gmatch("=(.?.?)") do
-               if eq ~= "2C" and eq ~= "3D" then
-                       return false
-               end
+               if eq ~= "2D" and eq ~= "3D" then
+                       return false 
+               end 
        end
        
-       -- replace =2C with , and =3D with =
-       username = username:gsub("=2C", ",");
+       -- replace =2D with , and =3D with =
+       username = username:gsub("=2D", ",");
        username = username:gsub("=3D", "=");
        
        -- apply SASLprep
@@ -93,21 +93,22 @@ local function validate_username(username)
        return username;
 end
 
-local function hashprep(hashname)
-       return hashname:lower():gsub("-", "_");
+local function hashprep( hashname ) 
+       local hash = hashname:lower()
+       hash = hash:gsub("-", "_")
+       return hash
 end
 
-function getAuthenticationDatabaseSHA1(password, salt, iteration_count)
+function saltedPasswordSHA1(password, salt, iteration_count)
+       local salted_password
        if type(password) ~= "string" or type(salt) ~= "string" or type(iteration_count) ~= "number" then
                return false, "inappropriate argument types"
        end
        if iteration_count < 4096 then
                log("warn", "Iteration count < 4096 which is the suggested minimum according to RFC 5802.")
        end
-       local salted_password = Hi(hmac_sha1, password, salt, iteration_count);
-       local stored_key = sha1(hmac_sha1(salted_password, "Client Key"))
-       local server_key = hmac_sha1(salted_password, "Server Key");
-       return true, stored_key, server_key
+
+       return true, Hi(hmac_sha1, password, salt, iteration_count);
 end
 
 local function scram_gen(hash_name, H_f, HMAC_f)
@@ -143,7 +144,7 @@ local function scram_gen(hash_name, H_f, HMAC_f)
                        
                        -- retreive credentials
                        if self.profile.plain then
-                               local password, state = self.profile.plain(self, self.state.name, self.realm)
+                               local password, state = self.profile.plain(self.state.name, self.realm)
                                if state == nil then return "failure", "not-authorized"
                                elseif state == false then return "failure", "account-disabled" end
                                
@@ -157,18 +158,17 @@ local function scram_gen(hash_name, H_f, HMAC_f)
                                self.state.iteration_count = default_i;
 
                                local succ = false;
-                               succ, self.state.stored_key, self.state.server_key = getAuthenticationDatabaseSHA1(password, self.state.salt, default_i, self.state.iteration_count);
+                               succ, self.state.salted_password = saltedPasswordSHA1(password, self.state.salt, default_i, self.state.iteration_count);
                                if not succ then
-                                       log("error", "Generating authentication database failed. Reason: %s", self.state.stored_key);
+                                       log("error", "Generating salted password failed. Reason: %s", self.state.salted_password);
                                        return "failure", "temporary-auth-failure";
                                end
                        elseif self.profile["scram_"..hashprep(hash_name)] then
-                               local stored_key, server_key, iteration_count, salt, state = self.profile["scram_"..hashprep(hash_name)](self, self.state.name, self.realm);
+                               local salted_password, iteration_count, salt, state = self.profile["scram_"..hashprep(hash_name)](self.state.name, self.realm);
                                if state == nil then return "failure", "not-authorized"
                                elseif state == false then return "failure", "account-disabled" end
                                
-                               self.state.stored_key = stored_key;
-                               self.state.server_key = server_key;
+                               self.state.salted_password = salted_password;
                                self.state.iteration_count = iteration_count;
                                self.state.salt = salt
                        end
@@ -190,15 +190,16 @@ local function scram_gen(hash_name, H_f, HMAC_f)
                                return "failure", "malformed-request", "Wrong nonce in client-final-message.";
                        end
                        
-                       local ServerKey = self.state.server_key;
-                       local StoredKey = self.state.stored_key;
-                       
+                       local SaltedPassword = self.state.salted_password;
+                       local ClientKey = HMAC_f(SaltedPassword, "Client Key")
+                       local ServerKey = HMAC_f(SaltedPassword, "Server Key")
+                       local StoredKey = H_f(ClientKey)
                        local AuthMessage = "n=" .. s_match(self.state.client_first_message,"n=(.+)") .. "," .. self.state.server_first_message .. "," .. s_match(client_final_message, "(.+),p=.+")
                        local ClientSignature = HMAC_f(StoredKey, AuthMessage)
-                       local ClientKey = binaryXOR(ClientSignature, base64.decode(self.state.proof))
+                       local ClientProof     = binaryXOR(ClientKey, ClientSignature)
                        local ServerSignature = HMAC_f(ServerKey, AuthMessage)
 
-                       if StoredKey == H_f(ClientKey) then
+                       if base64.encode(ClientProof) == self.state.proof then
                                local server_final_message = "v="..base64.encode(ServerSignature);
                                self["username"] = self.state.name;
                                return "success", server_final_message;
index 002118fdc07c86f14b8f0c0686e8c70537e73db0..7d35b5e4866ababb2bbd88fba6c1cbc64959bbdb 100644 (file)
 
 local cyrussasl = require "cyrussasl";
 local log = require "util.logger".init("sasl_cyrus");
+local array = require "util.array";
 
+local tostring = tostring;
+local pairs, ipairs = pairs, ipairs;
+local t_insert, t_concat = table.insert, table.concat;
+local s_match = string.match;
 local setmetatable = setmetatable
 
+local keys = keys;
+
+local print = print
 local pcall = pcall
 local s_match, s_gmatch = string.match, string.gmatch
 
@@ -79,17 +87,21 @@ end
 
 -- create a new SASL object which can be used to authenticate clients
 function new(realm, service_name, app_name)
+       local sasl_i = {};
 
        init(app_name or service_name);
 
+       sasl_i.realm = realm;
+       sasl_i.service_name = service_name;
+
        local st, ret = pcall(cyrussasl.server_new, service_name, nil, realm, nil, nil)
-       if not st then
+       if st then
+               sasl_i.cyrus = ret;
+       else
                log("error", "Creating SASL server connection failed: %s", ret);
                return nil;
        end
 
-       local sasl_i = { realm = realm, service_name = service_name, cyrus = ret };
-
        if cyrussasl.set_canon_cb then
                local c14n_cb = function (user)
                        local node = s_match(user, "^([^@]+)");
@@ -100,31 +112,37 @@ function new(realm, service_name, app_name)
        end
 
        cyrussasl.setssf(sasl_i.cyrus, 0, 0xffffffff)
-       local mechanisms = {};
-       local cyrus_mechs = cyrussasl.listmech(sasl_i.cyrus, nil, "", " ", "");
-       for w in s_gmatch(cyrus_mechs, "[^ ]+") do
-               mechanisms[w] = true;
-       end
-       sasl_i.mechs = mechanisms;
-       return setmetatable(sasl_i, method);
+       local s = setmetatable(sasl_i, method);
+       return s;
 end
 
--- get a fresh clone with the same realm and service name
+-- get a fresh clone with the same realm, profiles and forbidden mechanisms
 function method:clean_clone()
        return new(self.realm, self.service_name)
 end
 
+-- set the forbidden mechanisms
+function method:forbidden( restrict )
+       log("warn", "Called method:forbidden. NOT IMPLEMENTED.")
+       return {}
+end
+
 -- get a list of possible SASL mechanims to use
 function method:mechanisms()
-       return self.mechs;
+       local mechanisms = {}
+       local cyrus_mechs = cyrussasl.listmech(self.cyrus, nil, "", " ", "")
+       for w in s_gmatch(cyrus_mechs, "[^ ]+") do
+               mechanisms[w] = true;
+       end
+       self.mechs = mechanisms
+       return array.collect(keys(mechanisms));
 end
 
 -- select a mechanism to use
 function method:select(mechanism)
-       if not self.selected and self.mechs[mechanism] then
-               self.selected = mechanism;
-               return true;
-       end
+       self.mechanism = mechanism;
+       if not self.mechs then self:mechanisms(); end
+       return self.mechs[mechanism];
 end
 
 -- feed new messages to process into the library
@@ -132,9 +150,8 @@ function method:process(message)
        local err;
        local data;
 
-       if not self.first_step_done then
-               err, data = cyrussasl.server_start(self.cyrus, self.selected, message or "")
-               self.first_step_done = true;
+       if self.mechanism then
+               err, data = cyrussasl.server_start(self.cyrus, self.mechanism, message or "")
        else
                err, data = cyrussasl.server_step(self.cyrus, message or "")
        end
@@ -142,20 +159,17 @@ function method:process(message)
        self.username = cyrussasl.get_username(self.cyrus)
 
        if (err == 0) then -- SASL_OK
-               if self.require_provisioning and not self.require_provisioning(self.username) then
-                       return "failure", "not-authorized", "User authenticated successfully, but not provisioned for XMPP";
-               end
-               return "success", data
+          return "success", data
        elseif (err == 1) then -- SASL_CONTINUE
-               return "challenge", data
+          return "challenge", data
        elseif (err == -4) then -- SASL_NOMECH
-               log("debug", "SASL mechanism not available from remote end")
-               return "failure", "invalid-mechanism", "SASL mechanism not available"
+          log("debug", "SASL mechanism not available from remote end")
+          return "failure", "invalid-mechanism", "SASL mechanism not available"
        elseif (err == -13) then -- SASL_BADAUTH
-               return "failure", "not-authorized", sasl_errstring[err];
+          return "failure", "not-authorized", sasl_errstring[err];
        else
-               log("debug", "Got SASL error condition %d: %s", err, sasl_errstring[err]);
-               return "failure", "undefined-condition", sasl_errstring[err];
+          log("debug", "Got SASL error condition %d: %s", err, sasl_errstring[err]);
+          return "failure", "undefined-condition", sasl_errstring[err];
        end
 end
 
index de83977f38fb41d717975b18fbcb3ee29caf01ac..08ef2c9aa1649deac35eca8fd38488e7982a5372 100644 (file)
@@ -44,13 +44,11 @@ module "stanza"
 
 stanza_mt = { __type = "stanza" };
 stanza_mt.__index = stanza_mt;
-local stanza_mt = stanza_mt;
 
 function stanza(name, attr)
-       local stanza = { name = name, attr = attr or {}, tags = {} };
+       local stanza = { name = name, attr = attr or {}, tags = {}, last_add = {}};
        return setmetatable(stanza, stanza_mt);
 end
-local stanza = stanza;
 
 function stanza_mt:query(xmlns)
        return self:tag("query", { xmlns = xmlns });
@@ -62,27 +60,26 @@ end
 
 function stanza_mt:tag(name, attrs)
        local s = stanza(name, attrs);
-       local last_add = self.last_add;
-       if not last_add then last_add = {}; self.last_add = last_add; end
-       (last_add[#last_add] or self):add_direct_child(s);
-       t_insert(last_add, s);
+       (self.last_add[#self.last_add] or self):add_direct_child(s);
+       t_insert(self.last_add, s);
        return self;
 end
 
 function stanza_mt:text(text)
-       local last_add = self.last_add;
-       (last_add and last_add[#last_add] or self):add_direct_child(text);
+       (self.last_add[#self.last_add] or self):add_direct_child(text);
        return self;
 end
 
 function stanza_mt:up()
-       local last_add = self.last_add;
-       if last_add then t_remove(last_add); end
+       t_remove(self.last_add);
        return self;
 end
 
 function stanza_mt:reset()
-       self.last_add = nil;
+       local last_add = self.last_add;
+       for i = 1,#last_add do
+               last_add[i] = nil;
+       end
        return self;
 end
 
@@ -94,8 +91,7 @@ function stanza_mt:add_direct_child(child)
 end
 
 function stanza_mt:add_child(child)
-       local last_add = self.last_add;
-       (last_add and last_add[#last_add] or self):add_direct_child(child);
+       (self.last_add[#self.last_add] or self):add_direct_child(child);
        return self;
 end
 
@@ -110,14 +106,6 @@ function stanza_mt:get_child(name, xmlns)
        end
 end
 
-function stanza_mt:get_child_text(name, xmlns)
-       local tag = self:get_child(name, xmlns);
-       if tag then
-               return tag:get_text();
-       end
-       return nil;
-end
-
 function stanza_mt:child_with_name(name)
        for _, child in ipairs(self.tags) do
                if child.name == name then return child; end
@@ -134,48 +122,17 @@ function stanza_mt:children()
        local i = 0;
        return function (a)
                        i = i + 1
-                       return a[i];
+                       local v = a[i]
+                       if v then return v; end
                end, self, i;
 end
-
-function stanza_mt:childtags(name, xmlns)
-       xmlns = xmlns or self.attr.xmlns;
-       local tags = self.tags;
-       local start_i, max_i = 1, #tags;
-       return function ()
-               for i = start_i, max_i do
-                       local v = tags[i];
-                       if (not name or v.name == name)
-                       and (not xmlns or xmlns == v.attr.xmlns) then
-                               start_i = i+1;
-                               return v;
-                       end
-               end
-       end;
-end
-
-function stanza_mt:maptags(callback)
-       local tags, curr_tag = self.tags, 1;
-       local n_children, n_tags = #self, #tags;
-       
-       local i = 1;
-       while curr_tag <= n_tags do
-               if self[i] == tags[curr_tag] then
-                       local ret = callback(self[i]);
-                       if ret == nil then
-                               t_remove(self, i);
-                               t_remove(tags, curr_tag);
-                               n_children = n_children - 1;
-                               n_tags = n_tags - 1;
-                       else
-                               self[i] = ret;
-                               tags[i] = ret;
-                       end
-                       i = i + 1;
-                       curr_tag = curr_tag + 1;
-               end
-       end
-       return self;
+function stanza_mt:childtags()
+       local i = 0;
+       return function (a)
+                       i = i + 1
+                       local v = self.tags[i]
+                       if v then return v; end
+               end, self.tags[1], i;
 end
 
 local xml_escape
@@ -243,7 +200,7 @@ function stanza_mt.get_error(stanza)
        end
        type = error_tag.attr.type;
        
-       for child in error_tag:childtags() do
+       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();
@@ -255,7 +212,7 @@ function stanza_mt.get_error(stanza)
                        end
                end
        end
-       return type, condition or "undefined-condition", text;
+       return type, condition or "undefined-condition", text or "";
 end
 
 function stanza_mt.__add(s1, s2)
@@ -314,33 +271,39 @@ function deserialize(stanza)
                                end
                        end
                        stanza.tags = tags;
+                       if not stanza.last_add then
+                               stanza.last_add = {};
+                       end
                end
        end
        
        return stanza;
 end
 
-local function _clone(stanza)
-       local attr, tags = {}, {};
-       for k,v in pairs(stanza.attr) do attr[k] = v; end
-       local new = { name = stanza.name, attr = attr, tags = tags };
-       for i=1,#stanza do
-               local child = stanza[i];
-               if child.name then
-                       child = _clone(child);
-                       t_insert(tags, child);
+function clone(stanza)
+       local lookup_table = {};
+       local function _copy(object)
+               if type(object) ~= "table" then
+                       return object;
+               elseif lookup_table[object] then
+                       return lookup_table[object];
+               end
+               local new_table = {};
+               lookup_table[object] = new_table;
+               for index, value in pairs(object) do
+                       new_table[_copy(index)] = _copy(value);
                end
-               t_insert(new, child);
+               return setmetatable(new_table, getmetatable(object));
        end
-       return setmetatable(new, stanza_mt);
+       
+       return _copy(stanza)
 end
-clone = _clone;
 
 function message(attr, body)
        if not body then
                return stanza("message", attr);
        else
-               return stanza("message", attr):tag("body"):text(body):up();
+               return stanza("message", attr):tag("body"):text(body);
        end
 end
 function iq(attr)
index 3061da725384ce6d7a0a3d3e7462441e71ef1692..fa1dd7c54cadde12e4bac4aa8a23304ba167bc96 100644 (file)
@@ -11,9 +11,7 @@ local ns_addtimer = require "net.server".addtimer;
 local event = require "net.server".event;
 local event_base = require "net.server".event_base;
 
-local math_min = math.min
-local math_huge = math.huge
-local get_time = require "socket".gettime;
+local get_time = os.time;
 local t_insert = table.insert;
 local t_remove = table.remove;
 local ipairs, pairs = ipairs, pairs;
@@ -45,21 +43,14 @@ if not event then
                        new_data = {};
                end
                
-               local next_time = math_huge;
                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);
-                                       next_time = math_min(next_time, r);
-                               end
-                       else
-                               next_time = math_min(next_time, t - current_time);
+                               if type(r) == "number" then _add_task(r, func); end
                        end
                end
-               return next_time;
        end);
 else
        local EVENT_LEAVE = (event.core and event.core.LEAVE) or -1;