Merge 0.9->trunk
authorMatthew Wild <mwild1@gmail.com>
Mon, 24 Jun 2013 17:29:04 +0000 (18:29 +0100)
committerMatthew Wild <mwild1@gmail.com>
Mon, 24 Jun 2013 17:29:04 +0000 (18:29 +0100)
40 files changed:
configure
core/certmanager.lua
net/dns.lua
net/server_event.lua
net/server_select.lua
plugins/mod_admin_telnet.lua
plugins/mod_bosh.lua
plugins/mod_c2s.lua
plugins/mod_disco.lua
plugins/mod_http_files.lua
plugins/mod_pep.lua
plugins/mod_pubsub.lua [deleted file]
plugins/mod_pubsub/mod_pubsub.lua [new file with mode: 0644]
plugins/mod_pubsub/pubsub.lib.lua [new file with mode: 0644]
plugins/mod_register.lua
plugins/mod_s2s/mod_s2s.lua
plugins/mod_tls.lua
plugins/muc/mod_muc.lua
plugins/muc/muc.lib.lua
prosody
prosody.cfg.lua.dist
prosodyctl
tests/test.lua
tests/test_core_configmanager.lua
tests/test_core_modulemanager.lua [deleted file]
tests/test_core_s2smanager.lua
tests/test_net_http.lua [deleted file]
tests/test_util_http.lua [new file with mode: 0644]
tests/test_util_ip.lua [new file with mode: 0644]
tests/test_util_rfc3484.lua [deleted file]
tests/test_util_rfc6724.lua [new file with mode: 0644]
tools/ejabberd2prosody.lua
tools/ejabberdsql2prosody.lua
tools/jabberd14sql2prosody.lua
tools/openfire2prosody.lua
tools/xep227toprosody.lua
util/ip.lua
util/iterators.lua
util/sasl.lua
util/sasl/external.lua [new file with mode: 0644]

index ecf77a866b5af14f52f6b7e93868c02a68447eb9..87fd870bf90e91fc1c3e5f0ac8e9806df56b461c 100755 (executable)
--- a/configure
+++ b/configure
@@ -94,32 +94,31 @@ do
    --ostype=*)
       OSTYPE="$value"
       OSTYPE_SET=yes
-      if [ "$OSTYPE" = "debian" ]
-      then LUA_SUFFIX="5.1";
-       LUA_SUFFIX_SET=yes
-       RUNWITH="lua5.1"
-       LUA_INCDIR=/usr/include/lua5.1;
-       LUA_INCDIR_SET=yes
-       CFLAGS="$CFLAGS -D_GNU_SOURCE"
-       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;
+      if [ "$OSTYPE" = "debian" ]; then
+        LUA_SUFFIX="5.1";
+       LUA_SUFFIX_SET=yes
+       RUNWITH="lua5.1"
+       LUA_INCDIR=/usr/include/lua5.1;
+       LUA_INCDIR_SET=yes
+       CFLAGS="$CFLAGS -D_GNU_SOURCE"
+       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"
-        CFLAGS="$CFLAGS -D_GNU_SOURCE"
+        CFLAGS="-Wall -fPIC -D_GNU_SOURCE"
         LDFLAGS="-shared"
-        fi
-        if [ "$OSTYPE" = "freebsd" -o "$OSTYPE" = "openbsd" ]
-        then LUA_INCDIR="/usr/local/include/lua51"
+      fi
+      if [ "$OSTYPE" = "freebsd" -o "$OSTYPE" = "openbsd" ]; 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"
@@ -127,10 +126,10 @@ do
         LUA_SUFFIX_SET=yes
         LUA_DIR=/usr/local
         LUA_DIR_SET=yes
-        fi
-        if [ "$OSTYPE" = "openbsd" ]
-        then LUA_INCDIR="/usr/local/include";
-        fi
+      fi
+      if [ "$OSTYPE" = "openbsd" ]; then
+        LUA_INCDIR="/usr/local/include";
+      fi
       ;;
    --datadir=*)
        DATADIR="$value"
@@ -286,7 +285,7 @@ then
        IDNA_LIBS="$ICU_FLAGS"
        CFLAGS="$CFLAGS -DUSE_STRINGPREP_ICU"
 fi
-if [ "$IDN_LIBRARY" = "idn" ] 
+if [ "$IDN_LIBRARY" = "idn" ]
 then
        IDNA_LIBS="-l$IDN_LIB"
 fi
index 4bcac40d9e440d4544f2da2bd02ee1423ff8fafa..92b63ec3596335880abca2e7561b916926d1e355 100644 (file)
@@ -12,6 +12,7 @@ local ssl = ssl;
 local ssl_newcontext = ssl and ssl.newcontext;
 
 local tostring = tostring;
+local pairs = pairs;
 
 local prosody = prosody;
 local resolve_path = configmanager.resolve_relative_path;
@@ -28,52 +29,60 @@ end
 module "certmanager"
 
 -- Global SSL options if not overridden per-host
-local default_ssl_config = configmanager.get("*", "ssl");
-local default_capath = "/etc/ssl/certs";
-local default_verify = (ssl and ssl.x509 and { "peer", "client_once", }) or "none";
-local default_options = { "no_sslv2", luasec_has_noticket and "no_ticket" or nil };
-local default_verifyext = { "lsec_continue", "lsec_ignore_purpose" };
+local global_ssl_config = configmanager.get("*", "ssl");
+
+local core_defaults = {
+       capath = "/etc/ssl/certs";
+       protocol = "sslv23";
+       verify = (ssl and ssl.x509 and { "peer", "client_once", }) or "none";
+       options = { "no_sslv2", luasec_has_noticket and "no_ticket" or nil };
+       verifyext = { "lsec_continue", "lsec_ignore_purpose" };
+       curve = "secp384r1";
+}
+local path_options = { -- These we pass through resolve_path()
+       key = true, certificate = true, cafile = true, capath = true
+}
 
 if ssl and not luasec_has_verifyext and ssl.x509 then
        -- COMPAT mw/luasec-hg
-       for i=1,#default_verifyext do -- Remove lsec_ prefix
-               default_verify[#default_verify+1] = default_verifyext[i]:sub(6);
+       for i=1,#core_defaults.verifyext do -- Remove lsec_ prefix
+               core_defaults.verify[#core_defaults.verify+1] = core_defaults.verifyext[i]:sub(6);
        end
 end
-if luasec_has_no_compression and configmanager.get("*", "ssl_compression") ~= true then
-       default_options[#default_options+1] = "no_compression";
-end
 
-if luasec_has_no_compression then -- Has no_compression? Then it has these too...
-       default_options[#default_options+1] = "single_dh_use";
-       default_options[#default_options+1] = "single_ecdh_use";
+if luasec_has_no_compression and configmanager.get("*", "ssl_compression") ~= true then
+       core_defaults.options[#core_defaults.options+1] = "no_compression";
 end
 
 function create_context(host, mode, user_ssl_config)
-       user_ssl_config = user_ssl_config or default_ssl_config;
+       user_ssl_config = user_ssl_config or {}
+       user_ssl_config.mode = mode;
 
        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 or function() log("error", "Encrypted certificate for %s requires 'ssl' 'password' to be set in config", host); end;
-               certificate = resolve_path(config_path, user_ssl_config.certificate);
-               capath = resolve_path(config_path, user_ssl_config.capath or default_capath);
-               cafile = resolve_path(config_path, user_ssl_config.cafile);
-               verify = user_ssl_config.verify or default_verify;
-               verifyext = user_ssl_config.verifyext or default_verifyext;
-               options = user_ssl_config.options or default_options;
-               depth = user_ssl_config.depth;
-               curve = user_ssl_config.curve or "secp384r1";
-               dhparam = user_ssl_config.dhparam;
-       };
-
-       local ctx, err = ssl_newcontext(ssl_config);
-
-       -- LuaSec ignores the cipher list from the config, so we have to take care
+
+       if global_ssl_config then
+               for option,default_value in pairs(global_ssl_config) do
+                       if not user_ssl_config[option] then
+                               user_ssl_config[option] = default_value;
+                       end
+               end
+       end
+       for option,default_value in pairs(core_defaults) do
+               if not user_ssl_config[option] then
+                       user_ssl_config[option] = default_value;
+               end
+       end
+       user_ssl_config.password = user_ssl_config.password or function() log("error", "Encrypted certificate for %s requires 'ssl' 'password' to be set in config", host); end;
+       for option in pairs(path_options) do
+               user_ssl_config[option] = user_ssl_config[option] and resolve_path(config_path, user_ssl_config[option]);
+       end
+
+       if not user_ssl_config.key then return nil, "No key present in SSL/TLS configuration for "..host; end
+       if not user_ssl_config.certificate then return nil, "No certificate present in SSL/TLS configuration for "..host; end
+
+       local ctx, err = ssl_newcontext(user_ssl_config);
+
+       -- COMPAT Older LuaSec ignores the cipher list from the config, so we have to take care
        -- of it ourselves (W/A for #x)
        if ctx and user_ssl_config.ciphers then
                local success;
@@ -86,9 +95,9 @@ function create_context(host, mode, user_ssl_config)
                local file = err:match("^error loading (.-) %(");
                if file then
                        if file == "private key" then
-                               file = ssl_config.key or "your private key";
+                               file = user_ssl_config.key or "your private key";
                        elseif file == "certificate" then
-                               file = ssl_config.certificate or "your certificate file";
+                               file = user_ssl_config.certificate or "your certificate file";
                        end
                        local reason = err:match("%((.+)%)$") or "some reason";
                        if reason == "Permission denied" then
@@ -111,7 +120,7 @@ function create_context(host, mode, user_ssl_config)
 end
 
 function reload_ssl_config()
-       default_ssl_config = configmanager.get("*", "ssl");
+       global_ssl_config = configmanager.get("*", "ssl");
 end
 
 prosody.events.add_handler("config-reloaded", reload_ssl_config);
index 89b50255811b606af3ff2a53e0a91312bbe5e974..bd42db73800c13f262034159b0b1657d8b77a205 100644 (file)
@@ -14,6 +14,7 @@
 
 local socket = require "socket";
 local timer = require "util.timer";
+local new_ip = require "util.ip".new_ip;
 
 local _, windows = pcall(require, "util.windows");
 local is_windows = (_ and windows) or os.getenv("WINDIR");
@@ -597,11 +598,12 @@ function resolver:adddefaultnameservers()    -- - - - -  adddefaultnameservers
                if resolv_conf then
                        for line in resolv_conf:lines() do
                                line = line:gsub("#.*$", "")
-                                       :match('^%s*nameserver%s+(.*)%s*$');
+                                       :match('^%s*nameserver%s+([%x:%.]*)%s*$');
                                if line then
-                                       line:gsub("%f[%d.](%d+%.%d+%.%d+%.%d+)%f[^%d.]", function (address)
-                                               self:addnameserver(address)
-                                       end);
+                                       local ip = new_ip(line);
+                                       if ip then
+                                               self:addnameserver(ip.addr);
+                                       end
                                end
                        end
                end
@@ -621,7 +623,12 @@ function resolver:getsocket(servernum)    -- - - - - - - - - - - - - getsocket
        if sock then return sock; end
 
        local err;
-       sock, err = socket.udp();
+       local peer = self.server[servernum];
+       if peer:find(":") then
+               sock, err = socket.udp6();
+       else
+               sock, err = socket.udp();
+       end
        if not sock then
                return nil, err;
        end
@@ -629,7 +636,7 @@ function resolver:getsocket(servernum)    -- - - - - - - - - - - - - getsocket
        sock:settimeout(0);
        -- todo: attempt to use a random port, fallback to 0
        sock:setsockname('*', 0);
-       sock:setpeername(self.server[servernum], 53);
+       sock:setpeername(peer, 53);
        self.socket[servernum] = sock;
        self.socketset[sock] = servernum;
        return sock;
index 5eae95a9a04bc8e44880b16c414736828cfc3ddb..dc48e338b21e3997098b25c17a7d3e3c3685fa4f 100644 (file)
@@ -437,10 +437,11 @@ do
        end
        
        function interface_mt:setlistener(listener)
-               self.onconnect, self.ondisconnect, self.onincoming, self.ontimeout, self.onstatus
-                       = listener.onconnect, listener.ondisconnect, listener.onincoming, listener.ontimeout, listener.onstatus;
+               self.onconnect, self.ondisconnect, self.onincoming, self.ontimeout, self.onreadtimeout, self.onstatus
+                       = listener.onconnect, listener.ondisconnect, listener.onincoming,
+                         listener.ontimeout, listener.onreadtimeout, listener.onstatus;
        end
-       
+
        -- Stub handlers
        function interface_mt:onconnect()
        end
@@ -450,6 +451,12 @@ do
        end
        function interface_mt:ontimeout()
        end
+       function interface_mt:onreadtimeout()
+               self.fatalerror = "timeout during receiving"
+               debug( "connection failed:", self.fatalerror )
+               self:_close()
+               self.eventread = nil
+       end
        function interface_mt:ondrain()
        end
        function interface_mt:onstatus()
@@ -477,6 +484,7 @@ do
                        ondisconnect = listener.ondisconnect;  -- will be called when client disconnects
                        onincoming = listener.onincoming;  -- will be called when client sends data
                        ontimeout = listener.ontimeout; -- called when fatal socket timeout occurs
+                       onreadtimeout = listener.onreadtimeout; -- called when socket inactivity timeout occurs
                        onstatus = listener.onstatus; -- called for status changes (e.g. of SSL/TLS)
                        eventread = false, eventwrite = false, eventclose = false,
                        eventhandshake = false, eventstarthandshake = false;  -- event handler
@@ -574,61 +582,56 @@ do
                                interface.eventread = nil
                                return -1
                        end
-                       if EV_TIMEOUT == event then  -- took too long to get some data from client -> disconnect
-                               interface.fatalerror = "timeout during receiving"
-                               debug( "connection failed:", interface.fatalerror )
+                       if EV_TIMEOUT == event and interface:onreadtimeout() ~= true then
+                               return -1 -- took too long to get some data from client -> disconnect
+                       end
+                       if interface._usingssl then  -- handle luasec
+                               if interface.eventwritetimeout then  -- ok, in the past writecallback was regged
+                                       local ret = interface.writecallback( )  -- call it
+                                       --vdebug( "tried to write in readcallback, result:", tostring(ret) )
+                               end
+                               if interface.eventreadtimeout then
+                                       interface.eventreadtimeout:close( )
+                                       interface.eventreadtimeout = nil
+                               end
+                       end
+                       local buffer, err, part = interface.conn:receive( interface._pattern )  -- receive buffer with "pattern"
+                       --vdebug( "read data:", tostring(buffer), "error:", tostring(err), "part:", tostring(part) )
+                       buffer = buffer or part
+                       if buffer and #buffer > cfg.MAX_READ_LENGTH then  -- check buffer length
+                               interface.fatalerror = "receive buffer exceeded"
+                               debug( "fatal error:", interface.fatalerror )
                                interface:_close()
                                interface.eventread = nil
                                return -1
-                       else -- can read
-                               if interface._usingssl then  -- handle luasec
-                                       if interface.eventwritetimeout then  -- ok, in the past writecallback was regged
-                                               local ret = interface.writecallback( )  -- call it
-                                               --vdebug( "tried to write in readcallback, result:", tostring(ret) )
-                                       end
-                                       if interface.eventreadtimeout then
-                                               interface.eventreadtimeout:close( )
-                                               interface.eventreadtimeout = nil
+                       end
+                       if err and ( err ~= "timeout" and err ~= "wantread" ) then
+                               if "wantwrite" == err then -- need to read on write event
+                                       if not interface.eventwrite then  -- register new write event if needed
+                                               interface.eventwrite = addevent( base, interface.conn, EV_WRITE, interface.writecallback, cfg.WRITE_TIMEOUT )
                                        end
-                               end
-                               local buffer, err, part = interface.conn:receive( interface._pattern )  -- receive buffer with "pattern"
-                               --vdebug( "read data:", tostring(buffer), "error:", tostring(err), "part:", tostring(part) )
-                               buffer = buffer or part
-                               if buffer and #buffer > cfg.MAX_READ_LENGTH then  -- check buffer length
-                                       interface.fatalerror = "receive buffer exceeded"
-                                       debug( "fatal error:", interface.fatalerror )
+                                       interface.eventreadtimeout = addevent( base, nil, EV_TIMEOUT,
+                                               function( )
+                                                       interface:_close()
+                                               end, cfg.READ_TIMEOUT
+                                       )
+                                       debug( "wantwrite during read attempt, reg it in writecallback but dont know what really happens next..." )
+                                       -- to be honest i dont know what happens next, if it is allowed to first read, the write etc...
+                               else  -- connection was closed or fatal error
+                                       interface.fatalerror = err
+                                       debug( "connection failed in read event:", interface.fatalerror )
                                        interface:_close()
                                        interface.eventread = nil
                                        return -1
                                end
-                               if err and ( err ~= "timeout" and err ~= "wantread" ) then
-                                       if "wantwrite" == err then -- need to read on write event
-                                               if not interface.eventwrite then  -- register new write event if needed
-                                                       interface.eventwrite = addevent( base, interface.conn, EV_WRITE, interface.writecallback, cfg.WRITE_TIMEOUT )
-                                               end
-                                               interface.eventreadtimeout = addevent( base, nil, EV_TIMEOUT,
-                                                       function( )
-                                                               interface:_close()
-                                                       end, cfg.READ_TIMEOUT
-                                               )
-                                               debug( "wantwrite during read attempt, reg it in writecallback but dont know what really happens next..." )
-                                               -- to be honest i dont know what happens next, if it is allowed to first read, the write etc...
-                                       else  -- connection was closed or fatal error
-                                               interface.fatalerror = err
-                                               debug( "connection failed in read event:", interface.fatalerror )
-                                               interface:_close()
-                                               interface.eventread = nil
-                                               return -1
-                                       end
-                               else
-                                       interface.onincoming( interface, buffer, err )  -- send new data to listener
-                               end
-                               if interface.noreading then
-                                       interface.eventread = nil;
-                                       return -1;
-                               end
-                               return EV_READ, cfg.READ_TIMEOUT
+                       else
+                               interface.onincoming( interface, buffer, err )  -- send new data to listener
+                       end
+                       if interface.noreading then
+                               interface.eventread = nil;
+                               return -1;
                        end
+                       return EV_READ, cfg.READ_TIMEOUT
                end
 
                client:settimeout( 0 )  -- set non blocking
index d08947159df2c03bbab51522f4b87a632e57e807..98e9f8475cf8c932b90742dac26b01bd6054b3c2 100644 (file)
@@ -145,7 +145,7 @@ _tcpbacklog = 128 -- some kind of hint to the OS
 _maxsendlen = 51000 * 1024 -- max len of send buffer
 _maxreadlen = 25000 * 1024 -- max len of read buffer
 
-_checkinterval = 1200000 -- interval in secs to check idle clients
+_checkinterval = 30 -- interval in secs to check idle clients
 _sendtimeout = 60000 -- allowed send idle time in secs
 _readtimeout = 6 * 60 * 60 -- allowed read idle time in secs
 
@@ -863,16 +863,16 @@ loop = function(once) -- this is the main loop of the program
                        _starttime = _currenttime
                        for handler, timestamp in pairs( _writetimes ) do
                                if os_difftime( _currenttime - timestamp ) > _sendtimeout then
-                                       --_writetimes[ handler ] = nil
                                        handler.disconnect( )( handler, "send timeout" )
                                        handler:force_close()    -- forced disconnect
                                end
                        end
                        for handler, timestamp in pairs( _readtimes ) do
                                if os_difftime( _currenttime - timestamp ) > _readtimeout then
-                                       --_readtimes[ handler ] = nil
-                                       handler.disconnect( )( handler, "read timeout" )
-                                       handler:close( )        -- forced disconnect?
+                                       if not(handler.onreadtimeout) or handler:onreadtimeout() ~= true then
+                                               handler.disconnect( )( handler, "read timeout" )
+                                               handler:close( )        -- forced disconnect?
+                                       end
                                end
                        end
                end
index b942e9bd8efce95db07c93a53f3c10ab6f002ee0..6fc378bb6253ad936187b3689fba24c67fed1899 100644 (file)
@@ -17,7 +17,6 @@ local _G = _G;
 
 local prosody = _G.prosody;
 local hosts = prosody.hosts;
-local incoming_s2s = prosody.incoming_s2s;
 
 local console_listener = { default_port = 5582; default_mode = "*a"; interface = "127.0.0.1" };
 
@@ -484,6 +483,25 @@ end
 function def_env.hosts:add(name)
 end
 
+local function session_flags(session, line)
+       line = line or {};
+       if session.cert_identity_status == "valid" then
+               line[#line+1] = "(secure)";
+       elseif session.secure then
+               line[#line+1] = "(encrypted)";
+       end
+       if session.compressed then
+               line[#line+1] = "(compressed)";
+       end
+       if session.smacks then
+               line[#line+1] = "(sm)";
+       end
+       if session.ip and session.ip:match(":") then
+               line[#line+1] = "(IPv6)";
+       end
+       return table.concat(line, " ");
+end
+
 def_env.c2s = {};
 
 local function show_c2s(callback)
@@ -519,14 +537,9 @@ function def_env.c2s:show(match_jid)
                        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
+                               status = session.presence:get_child_text("show") or "available";
                        end
-                       print("   "..jid.." - "..status.."("..priority..")");
+                       print(session_flags(session, { "   "..jid.." - "..status.."("..priority..")" }));
                end             
        end);
        return true, "Total: "..count.." clients";
@@ -565,96 +578,80 @@ function def_env.c2s:close(match_jid)
        return true, "Total: "..count.." sessions closed";
 end
 
-local function session_flags(session, line)
-       if session.cert_identity_status == "valid" then
-               line[#line+1] = "(secure)";
-       elseif session.secure then
-               line[#line+1] = "(encrypted)";
-       end
-       if session.compressed then
-               line[#line+1] = "(compressed)";
-       end
-       if session.smacks then
-               line[#line+1] = "(sm)";
-       end
-       if session.conn and session.conn:ip():match(":") then
-               line[#line+1] = "(IPv6)";
-       end
-       return table.concat(line, " ");
-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;
+       local s2s_list = { };
        
-       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(session_flags(session, {"   ", host, "->", remotehost}));
-                               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));
+       local s2s_sessions = module:shared"/*/s2s/sessions";
+       for _, session in pairs(s2s_sessions) do
+               local remotehost, localhost, direction;
+               if session.direction == "outgoing" then
+                       direction = "->";
+                       count_out = count_out + 1;
+                       remotehost, localhost = session.to_host or "?", session.from_host or "?";
+               else
+                       direction = "<-";
+                       count_in = count_in + 1;
+                       remotehost, localhost = session.from_host or "?", session.to_host or "?";
+               end
+               local sess_lines = { l = localhost, r = remotehost,
+                       session_flags(session, { "", direction, remotehost or "?",
+                               "["..session.type..tostring(session):match("[a-f0-9]*$").."]" })};
+
+               if (not match_jid) or remotehost:match(match_jid) or localhost:match(match_jid) then
+                       table.insert(s2s_list, sess_lines);
+                       local print = function (s) table.insert(sess_lines, "        "..s); end
+                       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.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");
+                                       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     
-               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(session_flags(session, {"   ", host, "<-", session.from_host or "(unknown)"}));
-                               if session.type == "s2sin_unauthed" then
-                                               print("        Connection not yet authenticated");
-                               end
+                       if session.type == "s2sin_unauthed" then
+                               print("Connection not yet authenticated");
+                       elseif session.type == "s2sin" then
                                for name in pairs(session.hosts) do
                                        if name ~= session.from_host then
-                                               print("        also hosts "..tostring(name));
+                                               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
+
+       -- Sort by local host, then remote host
+       table.sort(s2s_list, function(a,b)
+               if a.l == b.l then return a.r < b.r; end
+               return a.l < b.l;
+       end);
+       local lasthost;
+       for _, sess_lines in ipairs(s2s_list) do
+               if sess_lines.l ~= lasthost then print(sess_lines.l); lasthost=sess_lines.l end
+               for _, line in ipairs(sess_lines) do print(line); end
        end
-       
        return true, "Total: "..count_out.." outgoing, "..count_in.." incoming connections";
 end
 
@@ -686,14 +683,9 @@ end
 function def_env.s2s:showcert(domain)
        local ser = require "util.serialization".serialize;
        local print = self.session.print;
-       local domain_sessions = set.new(array.collect(keys(incoming_s2s)))
-               /function(session) return session.from_host == domain and session or nil; end;
-       for local_host in values(prosody.hosts) do
-               local s2sout = local_host.s2sout;
-               if s2sout and s2sout[domain] then
-                       domain_sessions:add(s2sout[domain]);
-               end
-       end
+       local s2s_sessions = module:shared"/*/s2s/sessions";
+       local domain_sessions = set.new(array.collect(values(s2s_sessions)))
+               /function(session) return (session.to_host == domain or session.from_host == domain) and session or nil; end;
        local cert_set = {};
        for session in domain_sessions do
                local conn = session.conn;
@@ -784,76 +776,38 @@ end
 
 function def_env.s2s:close(from, to)
        local print, count = self.session.print, 0;
+       local s2s_sessions = module:shared"/*/s2s/sessions";
        
-       if not (from and to) then
+       local match_id;
+       if from and not to then
+               match_id, from = from;
+       elseif not 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
+       for _, session in pairs(s2s_sessions) do
+               local id = session.type..tostring(session):match("[a-f0-9]+$");
+               if (match_id and match_id == id)
+               or (session.from_host == from and session.to_host == to) then
+                       print(("Closing connection from %s to %s [%s]"):format(session.from_host, session.to_host, id));
                        (session.close or s2smanager.destroy_session)(session);
-                       count = count + 1;
-                       print("Closed outgoing session from "..from.." to "..to);
+                       count = count + 1 ;
                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
 
 function def_env.s2s:closeall(host)
         local count = 0;
-
-        if not host or type(host) ~= "string" then return false, "wrong syntax: please use s2s:closeall('hostname.tld')"; end
-        if hosts[host] then
-                for session in pairs(incoming_s2s) do
-                        if session.to_host == host then
-                                (session.close or s2smanager.destroy_session)(session);
+       local s2s_sessions = module:shared"/*/s2s/sessions";
+       for _,session in pairs(s2s_sessions) do
+               if not host or session.from_host == host or session.to_host == host then
+                       session:close();
                                 count = count + 1;
                         end
                 end
-                for _, session in pairs(hosts[host].s2sout) do
-                        (session.close or s2smanager.destroy_session)(session);
-                        count = count + 1;
-                end
-        else
-                for session in pairs(incoming_s2s) do
-                       if session.from_host == host then
-                               (session.close or s2smanager.destroy_session)(session);
-                               count = count + 1;
-                       end
-               end
-               for _, h in pairs(hosts) do
-                       if h.s2sout[host] then
-                               (h.s2sout[host].close or s2smanager.destroy_session)(h.s2sout[host]);
-                               count = count + 1;
-                       end
-               end
-        end
-
        if count == 0 then return false, "No sessions to close.";
        else return true, "Closed "..count.." s2s session"..((count == 1 and "") or "s"); end
 end
index b88cd9691ad71bb74733505a4d7e20e8dc87adfe..9a612ae0882652d45d9c885776a7fd0d81c3bac1 100644 (file)
@@ -1,7 +1,7 @@
 -- 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.
 --
@@ -35,24 +35,10 @@ local BOSH_DEFAULT_REQUESTS = module:get_option_number("bosh_max_requests", 2);
 local bosh_max_wait = module:get_option_number("bosh_max_wait", 120);
 
 local consider_bosh_secure = module:get_option_boolean("consider_bosh_secure");
-
-local default_headers = { ["Content-Type"] = "text/xml; charset=utf-8" };
-
 local cross_domain = module:get_option("cross_domain_bosh", false);
-if cross_domain then
-       default_headers["Access-Control-Allow-Methods"] = "GET, POST, OPTIONS";
-       default_headers["Access-Control-Allow-Headers"] = "Content-Type";
-       default_headers["Access-Control-Max-Age"] = "7200";
-
-       if cross_domain == true then
-               default_headers["Access-Control-Allow-Origin"] = "*";
-       elseif type(cross_domain) == "table" then
-               cross_domain = table.concat(cross_domain, ", ");
-       end
-       if type(cross_domain) == "string" then
-               default_headers["Access-Control-Allow-Origin"] = cross_domain;
-       end
-end
+
+if cross_domain == true then cross_domain = "*"; end
+if type(cross_domain) == "table" then cross_domain = table.concat(cross_domain, ", "); end
 
 local trusted_proxies = module:get_option_set("trusted_proxies", {"127.0.0.1"})._items;
 
@@ -77,7 +63,7 @@ local os_time = os.time;
 local sessions, inactive_sessions = module:shared("sessions", "inactive_sessions");
 
 -- Used to respond to idle sessions (those with waiting requests)
-local waiting_requests = {};
+local waiting_requests = module:shared("waiting_requests");
 function on_destroy_request(request)
        log("debug", "Request destroyed: %s", tostring(request));
        waiting_requests[request] = nil;
@@ -100,11 +86,20 @@ function on_destroy_request(request)
        end
 end
 
-function handle_OPTIONS(request)
-       local headers = {};
-       for k,v in pairs(default_headers) do headers[k] = v; end
-       headers["Content-Type"] = nil;
-       return { headers = headers, body = "" };
+local function set_cross_domain_headers(response)
+       local headers = response.headers;
+       headers.access_control_allow_methods = "GET, POST, OPTIONS";
+       headers.access_control_allow_headers = "Content-Type";
+       headers.access_control_max_age = "7200";
+       headers.access_control_allow_origin = cross_domain;
+       return response;
+end
+
+function handle_OPTIONS(event)
+       if cross_domain and event.request.headers.origin then
+               set_cross_domain_headers(event.response);
+       end
+       return "";
 end
 
 function handle_POST(event)
@@ -117,13 +112,23 @@ function handle_POST(event)
        local context = { request = request, response = response, notopen = true };
        local stream = new_xmpp_stream(context, stream_callbacks);
        response.context = context;
+
+       local headers = response.headers;
+       headers.content_type = "text/xml; charset=utf-8";
+
+       if cross_domain and event.request.headers.origin then
+               set_cross_domain_headers(response);
+       end
        
        -- stream:feed() calls the stream_callbacks, so all stanzas in
        -- the body are processed in this next line before it returns.
        -- In particular, the streamopened() stream callback is where
        -- much of the session logic happens, because it's where we first
        -- get to see the 'sid' of this request.
-       stream:feed(body);
+       if not stream:feed(body) then
+               module:log("warn", "Error parsing BOSH payload")
+               return 400;
+       end
        
        -- Stanzas (if any) in the request have now been processed, and
        -- we take care of the high-level BOSH logic here, including
@@ -139,9 +144,6 @@ function handle_POST(event)
                local r = session.requests;
                log("debug", "Session %s has %d out of %d requests open", context.sid, #r, session.bosh_hold);
                log("debug", "and there are %d things in the send_buffer:", #session.send_buffer);
-               for i, thing in ipairs(session.send_buffer) do
-                       log("debug", "    %s", tostring(thing));
-               end
                if #r > session.bosh_hold then
                        -- We are holding too many requests, send what's in the buffer,
                        log("debug", "We are holding too many requests, so...");
@@ -177,6 +179,8 @@ function handle_POST(event)
                        return true; -- Inform http server we shall reply later
                end
        end
+       module:log("warn", "Unable to associate request with a session (incomplete request?)");
+       return 400;
 end
 
 
@@ -215,10 +219,9 @@ local function bosh_close_stream(session, reason)
 
        local response_body = tostring(close_reply);
        for _, held_request in ipairs(session.requests) do
-               held_request.headers = default_headers;
                held_request:send(response_body);
        end
-       sessions[session.sid]  = nil;
+       sessions[session.sid] = nil;
        inactive_sessions[session] = nil;
        sm_destroy_session(session);
 end
@@ -277,7 +280,6 @@ function stream_callbacks.streamopened(context, attr)
                        local oldest_request = r[1];
                        if oldest_request and not session.bosh_processing then
                                log("debug", "We have an open request, so sending on that");
-                               oldest_request.headers = default_headers;
                                local body_attr = { xmlns = "http://jabber.org/protocol/httpbind",
                                        ["xmlns:stream"] = "http://etherx.jabber.org/streams";
                                        type = session.bosh_terminate and "terminate" or nil;
@@ -309,7 +311,6 @@ function stream_callbacks.streamopened(context, attr)
        if not session then
                -- Unknown sid
                log("info", "Client tried to use sid '%s' which we don't know about", sid);
-               response.headers = default_headers;
                response:send(tostring(st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", condition = "item-not-found" })));
                context.notopen = nil;
                return;
@@ -347,7 +348,7 @@ function stream_callbacks.streamopened(context, attr)
                local features = st.stanza("stream:features");
                hosts[session.host].events.fire_event("stream-features", { origin = session, features = features });
                fire_event("stream-features", session, features);
-               session.send(tostring(features));
+               session.send(features);
                session.notopen = nil;
        end
 end
@@ -365,8 +366,8 @@ function stream_callbacks.handlestanza(context, stanza)
        end
 end
 
-function stream_callbacks.streamclosed(request)
-       local session = sessions[request.sid];
+function stream_callbacks.streamclosed(context)
+       local session = sessions[context.sid];
        if session then
                session.bosh_processing = false;
                if #session.send_buffer > 0 then
@@ -379,7 +380,6 @@ function stream_callbacks.error(context, error)
        log("debug", "Error parsing BOSH request payload; %s", error);
        if not context.sid then
                local response = context.response;
-               response.headers = default_headers;
                response.status_code = 400;
                response:send();
                return;
@@ -393,7 +393,7 @@ function stream_callbacks.error(context, error)
        end
 end
 
-local dead_sessions = {};
+local dead_sessions = module:shared("dead_sessions");
 function on_timer()
        -- log("debug", "Checking for requests soon to timeout...");
        -- Identify requests timing out within the next few seconds
index 1d2dd6ddf4c07b25e7aebcaaf090a4ea0104d542..91bde5742f844f6019797129f7e15a64412da32c 100644 (file)
@@ -50,7 +50,7 @@ function stream_callbacks.streamopened(session, attr)
        session.streamid = uuid_generate();
        (session.log or session)("debug", "Client sent opening <stream:stream> to %s", session.host);
 
-       if not hosts[session.host] then
+       if not hosts[session.host] or not hosts[session.host].modules.c2s then
                -- We don't serve this host...
                session:close{ condition = "host-unknown", text = "This server does not serve "..tostring(session.host)};
                return;
@@ -262,10 +262,27 @@ function listener.ondisconnect(conn, err)
        end
 end
 
+function listener.onreadtimeout(conn)
+       local session = sessions[conn];
+       if session then
+               return (hosts[session.host] or prosody).events.fire_event("c2s-read-timeout", { session = session });
+       end
+end
+
+local function keepalive(event)
+       return event.session.send(' ');
+end
+
 function listener.associate_session(conn, session)
        sessions[conn] = session;
 end
 
+function module.add_host(module)
+       module:hook("c2s-read-timeout", keepalive, -1);
+end
+
+module:hook("c2s-read-timeout", keepalive, -1);
+
 module:hook("server-stopping", function(event)
        local reason = event.reason;
        for _, session in pairs(sessions) do
index 72c9a34c85ca87ec954e2e1c0dec6ebf45f4ad1c..06a4bb1e53cad8361fd7b97f1adaabc485d80236 100644 (file)
@@ -32,7 +32,9 @@ do -- validate disco_items
        end
 end
 
-module:add_identity("server", "im", module:get_option_string("name", "Prosody")); -- FIXME should be in the non-existing mod_router
+if module:get_host_type() == "normal" then
+       module:add_identity("server", "im", module:get_option_string("name", "Prosody")); -- FIXME should be in the non-existing mod_router
+end
 module:add_feature("http://jabber.org/protocol/disco#info");
 module:add_feature("http://jabber.org/protocol/disco#items");
 
@@ -97,7 +99,18 @@ module:hook("iq/host/http://jabber.org/protocol/disco#info:query", function(even
        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?
+       if node and node ~= "" and node ~= "http://prosody.im#"..get_server_caps_hash() then
+               local reply = st.reply(stanza):tag('query', {xmlns='http://jabber.org/protocol/disco#info', node=node});
+               local event = { origin = origin, stanza = stanza, reply = reply, node = node, exists = false};
+               local ret = module:fire_event("host-disco-info-node", event);
+               if ret ~= nil then return ret; end
+               if event.exists then
+                       origin.send(reply);
+               else
+                       origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Node does not exist"));
+               end
+               return true;
+       end
        local reply_query = get_server_disco_info();
        reply_query.node = node;
        local reply = st.reply(stanza):add_child(reply_query);
@@ -108,9 +121,21 @@ module:hook("iq/host/http://jabber.org/protocol/disco#items:query", function(eve
        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?
-
+       if node and node ~= "" then
+               local reply = st.reply(stanza):tag('query', {xmlns='http://jabber.org/protocol/disco#items', node=node});
+               local event = { origin = origin, stanza = stanza, reply = reply, node = node, exists = false};
+               local ret = module:fire_event("host-disco-items-node", event);
+               if ret ~= nil then return ret; end
+               if event.exists then
+                       origin.send(reply);
+               else
+                       origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Node does not exist"));
+               end
+               return true;
+       end
        local reply = st.reply(stanza):query("http://jabber.org/protocol/disco#items");
+       local ret = module:fire_event("host-disco-items", { origin = origin, stanza = stanza, reply = reply });
+       if ret ~= nil then return ret; end
        for jid, name in pairs(get_children(module.host)) do
                reply:tag("item", {jid = jid, name = name~=true and name or nil}):up();
        end
@@ -133,12 +158,24 @@ module:hook("iq/bare/http://jabber.org/protocol/disco#info:query", function(even
        local origin, stanza = event.origin, event.stanza;
        if stanza.attr.type ~= "get" then return; end
        local node = stanza.tags[1].attr.node;
-       if node and node ~= "" then return; end -- TODO fire event?
        local username = jid_split(stanza.attr.to) or origin.username;
        if not stanza.attr.to or is_contact_subscribed(username, module.host, jid_bare(stanza.attr.from)) then
+               if node and node ~= "" then
+                       local reply = st.reply(stanza):tag('query', {xmlns='http://jabber.org/protocol/disco#info', node=node});
+                       if not reply.attr.from then reply.attr.from = origin.username.."@"..origin.host; end -- COMPAT To satisfy Psi when querying own account
+                       local event = { origin = origin, stanza = stanza, reply = reply, node = node, exists = false};
+                       local ret = module:fire_event("account-disco-info-node", event);
+                       if ret ~= nil then return ret; end
+                       if event.exists then
+                               origin.send(reply);
+                       else
+                               origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Node does not exist"));
+                       end
+                       return true;
+               end
                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", { origin = origin, reply = reply });
                origin.send(reply);
                return true;
        end
@@ -147,12 +184,24 @@ module:hook("iq/bare/http://jabber.org/protocol/disco#items:query", function(eve
        local origin, stanza = event.origin, event.stanza;
        if stanza.attr.type ~= "get" then return; end
        local node = stanza.tags[1].attr.node;
-       if node and node ~= "" then return; end -- TODO fire event?
        local username = jid_split(stanza.attr.to) or origin.username;
        if not stanza.attr.to or is_contact_subscribed(username, module.host, jid_bare(stanza.attr.from)) then
+               if node and node ~= "" then
+                       local reply = st.reply(stanza):tag('query', {xmlns='http://jabber.org/protocol/disco#items', node=node});
+                       if not reply.attr.from then reply.attr.from = origin.username.."@"..origin.host; end -- COMPAT To satisfy Psi when querying own account
+                       local event = { origin = origin, stanza = stanza, reply = reply, node = node, exists = false};
+                       local ret = module:fire_event("account-disco-items-node", event);
+                       if ret ~= nil then return ret; end
+                       if event.exists then
+                               origin.send(reply);
+                       else
+                               origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Node does not exist"));
+                       end
+                       return true;
+               end
                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", { origin = origin, stanza = stanza, reply = reply });
                origin.send(reply);
                return true;
        end
index 915bec58b56b28a1e6a6a1f0250c1f25bf0b3ad1..59fd50aaf630799568e1b50d81cd632003922a2e 100644 (file)
@@ -19,7 +19,7 @@ local base_path = module:get_option_string("http_files_dir", module:get_option_s
 local dir_indices = module:get_option("http_index_files", { "index.html", "index.htm" });
 local directory_index = module:get_option_boolean("http_dir_listing");
 
-local mime_map = module:shared("mime").types;
+local mime_map = module:shared("/*/http_files/mime").types;
 if not mime_map then
        mime_map = {
                html = "text/html", htm = "text/html",
index e88bfd9f9cb1ff6a957fcbfbd173b36f056b7815..778f83ed9c15d1ffbabb7b2b6de1864f89abddf7 100644 (file)
@@ -263,19 +263,19 @@ module:hook("iq-result/bare/disco", function(event)
 end);
 
 module:hook("account-disco-info", function(event)
-       local stanza = event.stanza;
-       stanza:tag('identity', {category='pubsub', type='pep'}):up();
-       stanza:tag('feature', {var='http://jabber.org/protocol/pubsub#publish'}):up();
+       local reply = event.reply;
+       reply:tag('identity', {category='pubsub', type='pep'}):up();
+       reply:tag('feature', {var='http://jabber.org/protocol/pubsub#publish'}):up();
 end);
 
 module:hook("account-disco-items", function(event)
-       local stanza = event.stanza;
-       local bare = stanza.attr.to;
+       local reply = event.reply;
+       local bare = reply.attr.to;
        local user_data = data[bare];
 
        if user_data then
                for node, _ in pairs(user_data) do
-                       stanza:tag('item', {jid=bare, node=node}):up(); -- TODO we need to handle queries to these nodes
+                       reply:tag('item', {jid=bare, node=node}):up(); -- TODO we need to handle queries to these nodes
                end
        end
 end);
diff --git a/plugins/mod_pubsub.lua b/plugins/mod_pubsub.lua
deleted file mode 100644 (file)
index 926ed4f..0000000
+++ /dev/null
@@ -1,463 +0,0 @@
-local pubsub = require "util.pubsub";
-local st = require "util.stanza";
-local jid_bare = require "util.jid".bare;
-local uuid_generate = require "util.uuid".generate;
-local usermanager = require "core.usermanager";
-
-local xmlns_pubsub = "http://jabber.org/protocol/pubsub";
-local xmlns_pubsub_errors = "http://jabber.org/protocol/pubsub#errors";
-local xmlns_pubsub_event = "http://jabber.org/protocol/pubsub#event";
-local xmlns_pubsub_owner = "http://jabber.org/protocol/pubsub#owner";
-
-local autocreate_on_publish = module:get_option_boolean("autocreate_on_publish", false);
-local autocreate_on_subscribe = module:get_option_boolean("autocreate_on_subscribe", false);
-local pubsub_disco_name = module:get_option("name");
-if type(pubsub_disco_name) ~= "string" then pubsub_disco_name = "Prosody PubSub Service"; end
-
-local service;
-
-local handlers = {};
-
-function handle_pubsub_iq(event)
-       local origin, stanza = event.origin, event.stanza;
-       local pubsub = stanza.tags[1];
-       local action = pubsub.tags[1];
-       if not action then
-               return origin.send(st.error_reply(stanza, "cancel", "bad-request"));
-       end
-       local handler = handlers[stanza.attr.type.."_"..action.name];
-       if handler then
-               handler(origin, stanza, action);
-               return true;
-       end
-end
-
-local pubsub_errors = {
-       ["conflict"] = { "cancel", "conflict" };
-       ["invalid-jid"] = { "modify", "bad-request", nil, "invalid-jid" };
-       ["jid-required"] = { "modify", "bad-request", nil, "jid-required" };
-       ["nodeid-required"] = { "modify", "bad-request", nil, "nodeid-required" };
-       ["item-not-found"] = { "cancel", "item-not-found" };
-       ["not-subscribed"] = { "modify", "unexpected-request", nil, "not-subscribed" };
-       ["forbidden"] = { "cancel", "forbidden" };
-};
-function pubsub_error_reply(stanza, error)
-       local e = pubsub_errors[error];
-       local reply = st.error_reply(stanza, unpack(e, 1, 3));
-       if e[4] then
-               reply:tag(e[4], { xmlns = xmlns_pubsub_errors }):up();
-       end
-       return reply;
-end
-
-function handlers.get_items(origin, stanza, items)
-       local node = items.attr.node;
-       local item = items:get_child("item");
-       local id = item and item.attr.id;
-       
-       if not node then
-               return origin.send(pubsub_error_reply(stanza, "nodeid-required"));
-       end
-       local ok, results = service:get_items(node, stanza.attr.from, id);
-       if not ok then
-               return origin.send(pubsub_error_reply(stanza, results));
-       end
-       
-       local data = st.stanza("items", { node = node });
-       for _, entry in pairs(results) do
-               data:add_child(entry);
-       end
-       local reply;
-       if data then
-               reply = st.reply(stanza)
-                       :tag("pubsub", { xmlns = xmlns_pubsub })
-                               :add_child(data);
-       else
-               reply = pubsub_error_reply(stanza, "item-not-found");
-       end
-       return origin.send(reply);
-end
-
-function handlers.get_subscriptions(origin, stanza, subscriptions)
-       local node = subscriptions.attr.node;
-       local ok, ret = service:get_subscriptions(node, stanza.attr.from, stanza.attr.from);
-       if not ok then
-               return origin.send(pubsub_error_reply(stanza, ret));
-       end
-       local reply = st.reply(stanza)
-               :tag("pubsub", { xmlns = xmlns_pubsub })
-                       :tag("subscriptions");
-       for _, sub in ipairs(ret) do
-               reply:tag("subscription", { node = sub.node, jid = sub.jid, subscription = 'subscribed' }):up();
-       end
-       return origin.send(reply);
-end
-
-function handlers.set_create(origin, stanza, create)
-       local node = create.attr.node;
-       local ok, ret, reply;
-       if node then
-               ok, ret = service:create(node, stanza.attr.from);
-               if ok then
-                       reply = st.reply(stanza);
-               else
-                       reply = pubsub_error_reply(stanza, ret);
-               end
-       else
-               repeat
-                       node = uuid_generate();
-                       ok, ret = service:create(node, stanza.attr.from);
-               until ok or ret ~= "conflict";
-               if ok then
-                       reply = st.reply(stanza)
-                               :tag("pubsub", { xmlns = xmlns_pubsub })
-                                       :tag("create", { node = node });
-               else
-                       reply = pubsub_error_reply(stanza, ret);
-               end
-       end
-       return origin.send(reply);
-end
-
-function handlers.set_delete(origin, stanza, delete)
-       local node = delete.attr.node;
-
-       local reply, notifier;
-       if not node then
-               return origin.send(pubsub_error_reply(stanza, "nodeid-required"));
-       end
-       local ok, ret = service:delete(node, stanza.attr.from);
-       if ok then
-               reply = st.reply(stanza);
-       else
-               reply = pubsub_error_reply(stanza, ret);
-       end
-       return origin.send(reply);
-end
-
-function handlers.set_subscribe(origin, stanza, subscribe)
-       local node, jid = subscribe.attr.node, subscribe.attr.jid;
-       if not (node and jid) then
-               return origin.send(pubsub_error_reply(stanza, jid and "nodeid-required" or "invalid-jid"));
-       end
-       --[[
-       local options_tag, options = stanza.tags[1]:get_child("options"), nil;
-       if options_tag then
-               options = options_form:data(options_tag.tags[1]);
-       end
-       --]]
-       local options_tag, options; -- FIXME
-       local ok, ret = service:add_subscription(node, stanza.attr.from, jid, options);
-       local reply;
-       if ok then
-               reply = st.reply(stanza)
-                       :tag("pubsub", { xmlns = xmlns_pubsub })
-                               :tag("subscription", {
-                                       node = node,
-                                       jid = jid,
-                                       subscription = "subscribed"
-                               }):up();
-               if options_tag then
-                       reply:add_child(options_tag);
-               end
-       else
-               reply = pubsub_error_reply(stanza, ret);
-       end
-       origin.send(reply);
-end
-
-function handlers.set_unsubscribe(origin, stanza, unsubscribe)
-       local node, jid = unsubscribe.attr.node, unsubscribe.attr.jid;
-       if not (node and jid) then
-               return origin.send(pubsub_error_reply(stanza, jid and "nodeid-required" or "invalid-jid"));
-       end
-       local ok, ret = service:remove_subscription(node, stanza.attr.from, jid);
-       local reply;
-       if ok then
-               reply = st.reply(stanza);
-       else
-               reply = pubsub_error_reply(stanza, ret);
-       end
-       return origin.send(reply);
-end
-
-function handlers.set_publish(origin, stanza, publish)
-       local node = publish.attr.node;
-       if not node then
-               return origin.send(pubsub_error_reply(stanza, "nodeid-required"));
-       end
-       local item = publish:get_child("item");
-       local id = (item and item.attr.id);
-       if not id then
-               id = uuid_generate();
-               if item then
-                       item.attr.id = id;
-               end
-       end
-       local ok, ret = service:publish(node, stanza.attr.from, id, item);
-       local reply;
-       if ok then
-               reply = st.reply(stanza)
-                       :tag("pubsub", { xmlns = xmlns_pubsub })
-                               :tag("publish", { node = node })
-                                       :tag("item", { id = id });
-       else
-               reply = pubsub_error_reply(stanza, ret);
-       end
-       return origin.send(reply);
-end
-
-function handlers.set_retract(origin, stanza, retract)
-       local node, notify = retract.attr.node, retract.attr.notify;
-       notify = (notify == "1") or (notify == "true");
-       local item = retract:get_child("item");
-       local id = item and item.attr.id
-       if not (node and id) then
-               return origin.send(pubsub_error_reply(stanza, node and "item-not-found" or "nodeid-required"));
-       end
-       local reply, notifier;
-       if notify then
-               notifier = st.stanza("retract", { id = id });
-       end
-       local ok, ret = service:retract(node, stanza.attr.from, id, notifier);
-       if ok then
-               reply = st.reply(stanza);
-       else
-               reply = pubsub_error_reply(stanza, ret);
-       end
-       return origin.send(reply);
-end
-
-function handlers.set_purge(origin, stanza, purge)
-       local node, notify = purge.attr.node, purge.attr.notify;
-       notify = (notify == "1") or (notify == "true");
-       local reply;
-       if not node then
-               return origin.send(pubsub_error_reply(stanza, "nodeid-required"));
-       end
-       local ok, ret = service:purge(node, stanza.attr.from, notify);
-       if ok then
-               reply = st.reply(stanza);
-       else
-               reply = pubsub_error_reply(stanza, ret);
-       end
-       return origin.send(reply);
-end
-
-function simple_broadcast(kind, node, jids, item)
-       if item then
-               item = st.clone(item);
-               item.attr.xmlns = nil; -- Clear the pubsub namespace
-       end
-       local message = st.message({ from = module.host, type = "headline" })
-               :tag("event", { xmlns = xmlns_pubsub_event })
-                       :tag(kind, { node = node })
-                               :add_child(item);
-       for jid in pairs(jids) do
-               module:log("debug", "Sending notification to %s", jid);
-               message.attr.to = jid;
-               module:send(message);
-       end
-end
-
-module:hook("iq/host/"..xmlns_pubsub..":pubsub", handle_pubsub_iq);
-module:hook("iq/host/"..xmlns_pubsub_owner..":pubsub", handle_pubsub_iq);
-
-local disco_info;
-
-local feature_map = {
-       create = { "create-nodes", "instant-nodes", "item-ids" };
-       retract = { "delete-items", "retract-items" };
-       purge = { "purge-nodes" };
-       publish = { "publish", autocreate_on_publish and "auto-create" };
-       delete = { "delete-nodes" };
-       get_items = { "retrieve-items" };
-       add_subscription = { "subscribe" };
-       get_subscriptions = { "retrieve-subscriptions" };
-};
-
-local function add_disco_features_from_service(disco, service)
-       for method, features in pairs(feature_map) do
-               if service[method] then
-                       for _, feature in ipairs(features) do
-                               if feature then
-                                       disco:tag("feature", { var = xmlns_pubsub.."#"..feature }):up();
-                               end
-                       end
-               end
-       end
-       for affiliation in pairs(service.config.capabilities) do
-               if affiliation ~= "none" and affiliation ~= "owner" then
-                       disco:tag("feature", { var = xmlns_pubsub.."#"..affiliation.."-affiliation" }):up();
-               end
-       end
-end
-
-local function build_disco_info(service)
-       local disco_info = st.stanza("query", { xmlns = "http://jabber.org/protocol/disco#info" })
-               :tag("identity", { category = "pubsub", type = "service", name = pubsub_disco_name }):up()
-               :tag("feature", { var = "http://jabber.org/protocol/pubsub" }):up();
-       add_disco_features_from_service(disco_info, service);
-       return disco_info;
-end
-
-module:hook("iq-get/host/http://jabber.org/protocol/disco#info:query", function (event)
-       local origin, stanza = event.origin, event.stanza;
-       local node = stanza.tags[1].attr.node;
-       if not node then
-               return origin.send(st.reply(stanza):add_child(disco_info));
-       else
-               local ok, ret = service:get_nodes(stanza.attr.from);
-               if ok and not ret[node] then
-                       ok, ret = false, "item-not-found";
-               end
-               if not ok then
-                       return origin.send(pubsub_error_reply(stanza, ret));
-               end
-               local reply = st.reply(stanza)
-                       :tag("query", { xmlns = "http://jabber.org/protocol/disco#info", node = node })
-                               :tag("identity", { category = "pubsub", type = "leaf" });
-               return origin.send(reply);
-       end
-end);
-
-local function handle_disco_items_on_node(event)
-       local stanza, origin = event.stanza, event.origin;
-       local query = stanza.tags[1];
-       local node = query.attr.node;
-       local ok, ret = service:get_items(node, stanza.attr.from);
-       if not ok then
-               return origin.send(pubsub_error_reply(stanza, ret));
-       end
-       
-       local reply = st.reply(stanza)
-               :tag("query", { xmlns = "http://jabber.org/protocol/disco#items", node = node });
-       
-       for id, item in pairs(ret) do
-               reply:tag("item", { jid = module.host, name = id }):up();
-       end
-       
-       return origin.send(reply);
-end
-
-
-module:hook("iq-get/host/http://jabber.org/protocol/disco#items:query", function (event)
-       if event.stanza.tags[1].attr.node then
-               return handle_disco_items_on_node(event);
-       end
-       local ok, ret = service:get_nodes(event.stanza.attr.from);
-       if not ok then
-               event.origin.send(pubsub_error_reply(event.stanza, ret));
-       else
-               local reply = st.reply(event.stanza)
-                       :tag("query", { xmlns = "http://jabber.org/protocol/disco#items" });
-               for node, node_obj in pairs(ret) do
-                       reply:tag("item", { jid = module.host, node = node, name = node_obj.config.name }):up();
-               end
-               event.origin.send(reply);
-       end
-       return true;
-end);
-
-local admin_aff = module:get_option_string("default_admin_affiliation", "owner");
-local function get_affiliation(jid)
-       local bare_jid = jid_bare(jid);
-       if bare_jid == module.host or usermanager.is_admin(bare_jid, module.host) then
-               return admin_aff;
-       end
-end
-
-function set_service(new_service)
-       service = new_service;
-       module.environment.service = service;
-       disco_info = build_disco_info(service);
-end
-
-function module.save()
-       return { service = service };
-end
-
-function module.restore(data)
-       set_service(data.service);
-end
-
-set_service(pubsub.new({
-       capabilities = {
-               none = {
-                       create = false;
-                       publish = false;
-                       retract = false;
-                       get_nodes = true;
-                       
-                       subscribe = true;
-                       unsubscribe = true;
-                       get_subscription = true;
-                       get_subscriptions = true;
-                       get_items = true;
-                       
-                       subscribe_other = false;
-                       unsubscribe_other = false;
-                       get_subscription_other = false;
-                       get_subscriptions_other = false;
-                       
-                       be_subscribed = true;
-                       be_unsubscribed = true;
-                       
-                       set_affiliation = false;
-               };
-               publisher = {
-                       create = false;
-                       publish = true;
-                       retract = true;
-                       get_nodes = true;
-                       
-                       subscribe = true;
-                       unsubscribe = true;
-                       get_subscription = true;
-                       get_subscriptions = true;
-                       get_items = true;
-                       
-                       subscribe_other = false;
-                       unsubscribe_other = false;
-                       get_subscription_other = false;
-                       get_subscriptions_other = false;
-                       
-                       be_subscribed = true;
-                       be_unsubscribed = true;
-                       
-                       set_affiliation = false;
-               };
-               owner = {
-                       create = true;
-                       publish = true;
-                       retract = true;
-                       delete = true;
-                       get_nodes = true;
-                       
-                       subscribe = true;
-                       unsubscribe = true;
-                       get_subscription = true;
-                       get_subscriptions = true;
-                       get_items = true;
-                       
-                       
-                       subscribe_other = true;
-                       unsubscribe_other = true;
-                       get_subscription_other = true;
-                       get_subscriptions_other = true;
-                       
-                       be_subscribed = true;
-                       be_unsubscribed = true;
-                       
-                       set_affiliation = true;
-               };
-       };
-       
-       autocreate_on_publish = autocreate_on_publish;
-       autocreate_on_subscribe = autocreate_on_subscribe;
-       
-       broadcaster = simple_broadcast;
-       get_affiliation = get_affiliation;
-       
-       normalize_jid = jid_bare;
-}));
diff --git a/plugins/mod_pubsub/mod_pubsub.lua b/plugins/mod_pubsub/mod_pubsub.lua
new file mode 100644 (file)
index 0000000..81a66f8
--- /dev/null
@@ -0,0 +1,229 @@
+local pubsub = require "util.pubsub";
+local st = require "util.stanza";
+local jid_bare = require "util.jid".bare;
+local usermanager = require "core.usermanager";
+
+local xmlns_pubsub = "http://jabber.org/protocol/pubsub";
+local xmlns_pubsub_event = "http://jabber.org/protocol/pubsub#event";
+local xmlns_pubsub_owner = "http://jabber.org/protocol/pubsub#owner";
+
+local autocreate_on_publish = module:get_option_boolean("autocreate_on_publish", false);
+local autocreate_on_subscribe = module:get_option_boolean("autocreate_on_subscribe", false);
+local pubsub_disco_name = module:get_option("name");
+if type(pubsub_disco_name) ~= "string" then pubsub_disco_name = "Prosody PubSub Service"; end
+
+local service;
+
+local lib_pubsub = module:require "pubsub";
+local handlers = lib_pubsub.handlers;
+local pubsub_error_reply = lib_pubsub.pubsub_error_reply;
+
+module:depends("disco");
+module:add_identity("pubsub", "service", pubsub_disco_name);
+module:add_feature("http://jabber.org/protocol/pubsub");
+
+function handle_pubsub_iq(event)
+       local origin, stanza = event.origin, event.stanza;
+       local pubsub = stanza.tags[1];
+       local action = pubsub.tags[1];
+       if not action then
+               return origin.send(st.error_reply(stanza, "cancel", "bad-request"));
+       end
+       local handler = handlers[stanza.attr.type.."_"..action.name];
+       if handler then
+               handler(origin, stanza, action, service);
+               return true;
+       end
+end
+
+function simple_broadcast(kind, node, jids, item)
+       if item then
+               item = st.clone(item);
+               item.attr.xmlns = nil; -- Clear the pubsub namespace
+       end
+       local message = st.message({ from = module.host, type = "headline" })
+               :tag("event", { xmlns = xmlns_pubsub_event })
+                       :tag(kind, { node = node })
+                               :add_child(item);
+       for jid in pairs(jids) do
+               module:log("debug", "Sending notification to %s", jid);
+               message.attr.to = jid;
+               module:send(message);
+       end
+end
+
+module:hook("iq/host/"..xmlns_pubsub..":pubsub", handle_pubsub_iq);
+module:hook("iq/host/"..xmlns_pubsub_owner..":pubsub", handle_pubsub_iq);
+
+local feature_map = {
+       create = { "create-nodes", "instant-nodes", "item-ids" };
+       retract = { "delete-items", "retract-items" };
+       purge = { "purge-nodes" };
+       publish = { "publish", autocreate_on_publish and "auto-create" };
+       delete = { "delete-nodes" };
+       get_items = { "retrieve-items" };
+       add_subscription = { "subscribe" };
+       get_subscriptions = { "retrieve-subscriptions" };
+};
+
+local function add_disco_features_from_service(service)
+       for method, features in pairs(feature_map) do
+               if service[method] then
+                       for _, feature in ipairs(features) do
+                               if feature then
+                                       module:add_feature(xmlns_pubsub.."#"..feature);
+                               end
+                       end
+               end
+       end
+       for affiliation in pairs(service.config.capabilities) do
+               if affiliation ~= "none" and affiliation ~= "owner" then
+                       module:add_feature(xmlns_pubsub.."#"..affiliation.."-affiliation");
+               end
+       end
+end
+
+module:hook("host-disco-info-node", function (event)
+       local stanza, origin, reply, node = event.stanza, event.origin, event.reply, event.node;
+       local ok, ret = service:get_nodes(stanza.attr.from);
+       if ok and not ret[node] then
+               return;
+       end
+       if not ok then
+               return origin.send(pubsub_error_reply(stanza, ret));
+       end
+       event.exists = true;
+       reply:tag("identity", { category = "pubsub", type = "leaf" });
+end);
+
+module:hook("host-disco-items-node", function (event)
+       local stanza, origin, reply, node = event.stanza, event.origin, event.reply, event.node;
+       local ok, ret = service:get_items(node, stanza.attr.from);
+       if not ok then
+               return origin.send(pubsub_error_reply(stanza, ret));
+       end
+
+       for id, item in pairs(ret) do
+               reply:tag("item", { jid = module.host, name = id }):up();
+       end
+       event.exists = true;
+end);
+
+
+module:hook("host-disco-items", function (event)
+       local stanza, origin, reply = event.stanza, event.origin, event.reply;
+       local ok, ret = service:get_nodes(event.stanza.attr.from);
+       if not ok then
+               return origin.send(pubsub_error_reply(event.stanza, ret));
+       end
+       for node, node_obj in pairs(ret) do
+               reply:tag("item", { jid = module.host, node = node, name = node_obj.config.name }):up();
+       end
+end);
+
+local admin_aff = module:get_option_string("default_admin_affiliation", "owner");
+local function get_affiliation(jid)
+       local bare_jid = jid_bare(jid);
+       if bare_jid == module.host or usermanager.is_admin(bare_jid, module.host) then
+               return admin_aff;
+       end
+end
+
+function set_service(new_service)
+       service = new_service;
+       module.environment.service = service;
+       add_disco_features_from_service(service);
+end
+
+function module.save()
+       return { service = service };
+end
+
+function module.restore(data)
+       set_service(data.service);
+end
+
+function module.load()
+       if module.reloading then return; end
+
+       set_service(pubsub.new({
+               capabilities = {
+                       none = {
+                               create = false;
+                               publish = false;
+                               retract = false;
+                               get_nodes = true;
+
+                               subscribe = true;
+                               unsubscribe = true;
+                               get_subscription = true;
+                               get_subscriptions = true;
+                               get_items = true;
+
+                               subscribe_other = false;
+                               unsubscribe_other = false;
+                               get_subscription_other = false;
+                               get_subscriptions_other = false;
+
+                               be_subscribed = true;
+                               be_unsubscribed = true;
+
+                               set_affiliation = false;
+                       };
+                       publisher = {
+                               create = false;
+                               publish = true;
+                               retract = true;
+                               get_nodes = true;
+
+                               subscribe = true;
+                               unsubscribe = true;
+                               get_subscription = true;
+                               get_subscriptions = true;
+                               get_items = true;
+
+                               subscribe_other = false;
+                               unsubscribe_other = false;
+                               get_subscription_other = false;
+                               get_subscriptions_other = false;
+
+                               be_subscribed = true;
+                               be_unsubscribed = true;
+
+                               set_affiliation = false;
+                       };
+                       owner = {
+                               create = true;
+                               publish = true;
+                               retract = true;
+                               delete = true;
+                               get_nodes = true;
+
+                               subscribe = true;
+                               unsubscribe = true;
+                               get_subscription = true;
+                               get_subscriptions = true;
+                               get_items = true;
+
+
+                               subscribe_other = true;
+                               unsubscribe_other = true;
+                               get_subscription_other = true;
+                               get_subscriptions_other = true;
+
+                               be_subscribed = true;
+                               be_unsubscribed = true;
+
+                               set_affiliation = true;
+                       };
+               };
+
+               autocreate_on_publish = autocreate_on_publish;
+               autocreate_on_subscribe = autocreate_on_subscribe;
+
+               broadcaster = simple_broadcast;
+               get_affiliation = get_affiliation;
+
+               normalize_jid = jid_bare;
+       }));
+end
diff --git a/plugins/mod_pubsub/pubsub.lib.lua b/plugins/mod_pubsub/pubsub.lib.lua
new file mode 100644 (file)
index 0000000..2b015e3
--- /dev/null
@@ -0,0 +1,225 @@
+local st = require "util.stanza";
+local uuid_generate = require "util.uuid".generate;
+
+local xmlns_pubsub = "http://jabber.org/protocol/pubsub";
+local xmlns_pubsub_errors = "http://jabber.org/protocol/pubsub#errors";
+
+local _M = {};
+
+local handlers = {};
+_M.handlers = handlers;
+
+local pubsub_errors = {
+       ["conflict"] = { "cancel", "conflict" };
+       ["invalid-jid"] = { "modify", "bad-request", nil, "invalid-jid" };
+       ["jid-required"] = { "modify", "bad-request", nil, "jid-required" };
+       ["nodeid-required"] = { "modify", "bad-request", nil, "nodeid-required" };
+       ["item-not-found"] = { "cancel", "item-not-found" };
+       ["not-subscribed"] = { "modify", "unexpected-request", nil, "not-subscribed" };
+       ["forbidden"] = { "cancel", "forbidden" };
+};
+local function pubsub_error_reply(stanza, error)
+       local e = pubsub_errors[error];
+       local reply = st.error_reply(stanza, unpack(e, 1, 3));
+       if e[4] then
+               reply:tag(e[4], { xmlns = xmlns_pubsub_errors }):up();
+       end
+       return reply;
+end
+_M.pubsub_error_reply = pubsub_error_reply;
+
+function handlers.get_items(origin, stanza, items, service)
+       local node = items.attr.node;
+       local item = items:get_child("item");
+       local id = item and item.attr.id;
+
+       if not node then
+               return origin.send(pubsub_error_reply(stanza, "nodeid-required"));
+       end
+       local ok, results = service:get_items(node, stanza.attr.from, id);
+       if not ok then
+               return origin.send(pubsub_error_reply(stanza, results));
+       end
+
+       local data = st.stanza("items", { node = node });
+       for _, entry in pairs(results) do
+               data:add_child(entry);
+       end
+       local reply;
+       if data then
+               reply = st.reply(stanza)
+                       :tag("pubsub", { xmlns = xmlns_pubsub })
+                               :add_child(data);
+       else
+               reply = pubsub_error_reply(stanza, "item-not-found");
+       end
+       return origin.send(reply);
+end
+
+function handlers.get_subscriptions(origin, stanza, subscriptions, service)
+       local node = subscriptions.attr.node;
+       local ok, ret = service:get_subscriptions(node, stanza.attr.from, stanza.attr.from);
+       if not ok then
+               return origin.send(pubsub_error_reply(stanza, ret));
+       end
+       local reply = st.reply(stanza)
+               :tag("pubsub", { xmlns = xmlns_pubsub })
+                       :tag("subscriptions");
+       for _, sub in ipairs(ret) do
+               reply:tag("subscription", { node = sub.node, jid = sub.jid, subscription = 'subscribed' }):up();
+       end
+       return origin.send(reply);
+end
+
+function handlers.set_create(origin, stanza, create, service)
+       local node = create.attr.node;
+       local ok, ret, reply;
+       if node then
+               ok, ret = service:create(node, stanza.attr.from);
+               if ok then
+                       reply = st.reply(stanza);
+               else
+                       reply = pubsub_error_reply(stanza, ret);
+               end
+       else
+               repeat
+                       node = uuid_generate();
+                       ok, ret = service:create(node, stanza.attr.from);
+               until ok or ret ~= "conflict";
+               if ok then
+                       reply = st.reply(stanza)
+                               :tag("pubsub", { xmlns = xmlns_pubsub })
+                                       :tag("create", { node = node });
+               else
+                       reply = pubsub_error_reply(stanza, ret);
+               end
+       end
+       return origin.send(reply);
+end
+
+function handlers.set_delete(origin, stanza, delete, service)
+       local node = delete.attr.node;
+
+       local reply, notifier;
+       if not node then
+               return origin.send(pubsub_error_reply(stanza, "nodeid-required"));
+       end
+       local ok, ret = service:delete(node, stanza.attr.from);
+       if ok then
+               reply = st.reply(stanza);
+       else
+               reply = pubsub_error_reply(stanza, ret);
+       end
+       return origin.send(reply);
+end
+
+function handlers.set_subscribe(origin, stanza, subscribe, service)
+       local node, jid = subscribe.attr.node, subscribe.attr.jid;
+       if not (node and jid) then
+               return origin.send(pubsub_error_reply(stanza, jid and "nodeid-required" or "invalid-jid"));
+       end
+       --[[
+       local options_tag, options = stanza.tags[1]:get_child("options"), nil;
+       if options_tag then
+               options = options_form:data(options_tag.tags[1]);
+       end
+       --]]
+       local options_tag, options; -- FIXME
+       local ok, ret = service:add_subscription(node, stanza.attr.from, jid, options);
+       local reply;
+       if ok then
+               reply = st.reply(stanza)
+                       :tag("pubsub", { xmlns = xmlns_pubsub })
+                               :tag("subscription", {
+                                       node = node,
+                                       jid = jid,
+                                       subscription = "subscribed"
+                               }):up();
+               if options_tag then
+                       reply:add_child(options_tag);
+               end
+       else
+               reply = pubsub_error_reply(stanza, ret);
+       end
+       origin.send(reply);
+end
+
+function handlers.set_unsubscribe(origin, stanza, unsubscribe, service)
+       local node, jid = unsubscribe.attr.node, unsubscribe.attr.jid;
+       if not (node and jid) then
+               return origin.send(pubsub_error_reply(stanza, jid and "nodeid-required" or "invalid-jid"));
+       end
+       local ok, ret = service:remove_subscription(node, stanza.attr.from, jid);
+       local reply;
+       if ok then
+               reply = st.reply(stanza);
+       else
+               reply = pubsub_error_reply(stanza, ret);
+       end
+       return origin.send(reply);
+end
+
+function handlers.set_publish(origin, stanza, publish, service)
+       local node = publish.attr.node;
+       if not node then
+               return origin.send(pubsub_error_reply(stanza, "nodeid-required"));
+       end
+       local item = publish:get_child("item");
+       local id = (item and item.attr.id);
+       if not id then
+               id = uuid_generate();
+               if item then
+                       item.attr.id = id;
+               end
+       end
+       local ok, ret = service:publish(node, stanza.attr.from, id, item);
+       local reply;
+       if ok then
+               reply = st.reply(stanza)
+                       :tag("pubsub", { xmlns = xmlns_pubsub })
+                               :tag("publish", { node = node })
+                                       :tag("item", { id = id });
+       else
+               reply = pubsub_error_reply(stanza, ret);
+       end
+       return origin.send(reply);
+end
+
+function handlers.set_retract(origin, stanza, retract, service)
+       local node, notify = retract.attr.node, retract.attr.notify;
+       notify = (notify == "1") or (notify == "true");
+       local item = retract:get_child("item");
+       local id = item and item.attr.id
+       if not (node and id) then
+               return origin.send(pubsub_error_reply(stanza, node and "item-not-found" or "nodeid-required"));
+       end
+       local reply, notifier;
+       if notify then
+               notifier = st.stanza("retract", { id = id });
+       end
+       local ok, ret = service:retract(node, stanza.attr.from, id, notifier);
+       if ok then
+               reply = st.reply(stanza);
+       else
+               reply = pubsub_error_reply(stanza, ret);
+       end
+       return origin.send(reply);
+end
+
+function handlers.set_purge(origin, stanza, purge, service)
+       local node, notify = purge.attr.node, purge.attr.notify;
+       notify = (notify == "1") or (notify == "true");
+       local reply;
+       if not node then
+               return origin.send(pubsub_error_reply(stanza, "nodeid-required"));
+       end
+       local ok, ret = service:purge(node, stanza.attr.from, notify);
+       if ok then
+               reply = st.reply(stanza);
+       else
+               reply = pubsub_error_reply(stanza, ret);
+       end
+       return origin.send(reply);
+end
+
+return _M;
index 141a4997966a7448a4b78f0f9a9b929e5206a354..3cdb48b32b22e79fea6d169fe159df0c3a21df77 100644 (file)
@@ -72,7 +72,7 @@ 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;
+       local session, features = event.origin, event.features;
 
        -- Advertise registration to unauthorized clients only.
        if not(allow_registration) or session.type ~= "c2s_unauthed" then
@@ -115,8 +115,8 @@ local function handle_registration_stanza(event)
                        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();
+                       local username = nodeprep(query:get_child_text("username"));
+                       local password = query:get_child_text("password");
                        if username and password then
                                if username == session.username then
                                        if usermanager_set_password(username, password, session.host) then
index 5a2af96843e67ebea28591315e331527744addc7..5e50e88b6207ca3085067560430bdf4c1b071656 100644 (file)
@@ -135,6 +135,10 @@ function route_to_new_session(event)
        return true;
 end
 
+local function keepalive(event)
+       return event.session.sends2s(' ');
+end
+
 function module.add_host(module)
        if module:get_option_boolean("disallow_s2s", false) then
                module:log("warn", "The 'disallow_s2s' config option is deprecated, please see http://prosody.im/doc/s2s#disabling");
@@ -143,6 +147,7 @@ function module.add_host(module)
        module:hook("route/remote", route_to_existing_session, -1);
        module:hook("route/remote", route_to_new_session, -10);
        module:hook("s2s-authenticated", make_authenticated, -1);
+       module:hook("s2s-read-timeout", keepalive, -1);
 end
 
 -- Stream is authorised, and ready for normal stanzas
@@ -590,6 +595,7 @@ function listener.onconnect(conn)
        else -- Outgoing session connected
                session:open_stream(session.from_host, session.to_host);
        end
+       session.ip = conn:ip();
 end
 
 function listener.onincoming(conn, data)
@@ -616,7 +622,6 @@ function listener.ondisconnect(conn, err)
                if err and session.direction == "outgoing" and session.notopen then
                        (session.log or log)("debug", "s2s connection attempt failed: %s", err);
                        if s2sout.attempt_connection(session, err) then
-                               (session.log or log)("debug", "...so we're going to try another target");
                                return; -- Session lives for now
                        end
                end
@@ -625,6 +630,13 @@ function listener.ondisconnect(conn, err)
        end
 end
 
+function listener.onreadtimeout(conn)
+       local session = sessions[conn];
+       if session then
+               return (hosts[session.host] or prosody).events.fire_event("s2s-read-timeout", { session = session });
+       end
+end
+
 function listener.register_outgoing(conn, session)
        session.direction = "outgoing";
        sessions[conn] = session;
index 80b56abbb41b8b6482ecf1846ea9cb75d2142a43..1af8dbe9e134bbe81fc8bb80997d323be826d8b0 100644 (file)
@@ -23,20 +23,47 @@ local s2s_feature = st.stanza("starttls", starttls_attr);
 if secure_auth_only then c2s_feature:tag("required"):up(); end
 if secure_s2s_only then s2s_feature:tag("required"):up(); end
 
-local global_ssl_ctx = prosody.global_ssl_ctx;
-
 local hosts = prosody.hosts;
 local host = hosts[module.host];
 
+local ssl_ctx_c2s, ssl_ctx_s2sout, ssl_ctx_s2sin;
+do
+       local function get_ssl_cfg(typ)
+               local cfg_key = (typ and typ.."_" or "").."ssl";
+               local ssl_config = config.rawget(module.host, cfg_key);
+               if not ssl_config then
+                       local base_host = module.host:match("%.(.*)");
+                       ssl_config = config.get(base_host, cfg_key);
+               end
+               return ssl_config or typ and get_ssl_cfg();
+       end
+
+       local ssl_config, err = get_ssl_cfg("c2s");
+       ssl_ctx_c2s, err = create_context(host.host, "server", ssl_config); -- for incoming client connections
+       if err then module:log("error", "Error creating context for c2s: %s", err); end
+
+       ssl_config = get_ssl_cfg("s2s");
+       ssl_ctx_s2sin, err = create_context(host.host, "server", ssl_config); -- for incoming server connections
+       ssl_ctx_s2sout = create_context(host.host, "client", ssl_config); -- for outgoing server connections
+       if err then module:log("error", "Error creating context for s2s: %s", err); end -- Both would have the same issue
+end
+
 local function can_do_tls(session)
+       if not session.conn.starttls then
+               return false;
+       elseif session.ssl_ctx then
+               return true;
+       end
        if session.type == "c2s_unauthed" then
-               return session.conn.starttls and host.ssl_ctx_in;
+               session.ssl_ctx = ssl_ctx_c2s;
        elseif session.type == "s2sin_unauthed" and allow_s2s_tls then
-               return session.conn.starttls and host.ssl_ctx_in;
+               session.ssl_ctx = ssl_ctx_s2sin;
        elseif session.direction == "outgoing" and allow_s2s_tls then
-               return session.conn.starttls and host.ssl_ctx;
+               session.ssl_ctx = ssl_ctx_s2sout;
+       else
+               return false;
        end
-       return false;
+       return session.ssl_ctx;
 end
 
 -- Hook <starttls/>
@@ -45,9 +72,7 @@ module:hook("stanza/urn:ietf:params:xml:ns:xmpp-tls:starttls", function(event)
        if can_do_tls(origin) then
                (origin.sends2s or origin.send)(starttls_proceed);
                origin:reset_stream();
-               local host = origin.to_host or origin.host;
-               local ssl_ctx = host and hosts[host].ssl_ctx_in or global_ssl_ctx;
-               origin.conn:starttls(ssl_ctx);
+               origin.conn:starttls(origin.ssl_ctx);
                origin.log("debug", "TLS negotiation started for %s...", origin.type);
                origin.secure = false;
        else
@@ -85,23 +110,7 @@ end, 500);
 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(session.ssl_ctx);
        session.secure = false;
        return true;
 end);
-
-function module.load()
-       local ssl_config = config.rawget(module.host, "ssl");
-       if not ssl_config then
-               local base_host = module.host:match("%.(.*)");
-               ssl_config = config.get(base_host, "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 0f1beb0e5f1f6801ce8de79299c2903764ac4009..a9480465c0417adf5c3f043b03ee2c9c6936393c 100644 (file)
@@ -40,6 +40,10 @@ local room_configs = module:open_store("config");
 -- Configurable options
 muclib.set_max_history_length(module:get_option_number("max_history_messages"));
 
+module:depends("disco");
+module:add_identity("conference", "text", muc_name);
+module:add_feature("http://jabber.org/protocol/muc");
+
 local function is_admin(jid)
        return um_is_admin(jid, module.host);
 end
@@ -107,20 +111,15 @@ local host_room = muc_new_room(muc_host);
 host_room.route_stanza = room_route_stanza;
 host_room.save = room_save;
 
-local function get_disco_info(stanza)
-       return st.iq({type='result', id=stanza.attr.id, from=muc_host, to=stanza.attr.from}):query("http://jabber.org/protocol/disco#info")
-               :tag("identity", {category='conference', type='text', name=muc_name}):up()
-               :tag("feature", {var="http://jabber.org/protocol/muc"}); -- TODO cache disco reply
-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");
+module:hook("host-disco-items", function(event)
+       local reply = event.reply;
+       module:log("debug", "host-disco-items called");
        for jid, room in pairs(rooms) do
-               if not room:is_hidden() then
+               if not room:get_hidden() then
                        reply:tag("item", {jid=jid, name=room:get_name()}):up();
                end
        end
-       return reply; -- TODO cache disco reply
-end
+end);
 
 local function handle_to_domain(event)
        local origin, stanza = event.origin, event.stanza;
@@ -129,11 +128,7 @@ local function handle_to_domain(event)
        if stanza.name == "iq" and type == "get" then
                local xmlns = stanza.tags[1].attr.xmlns;
                local node = stanza.tags[1].attr.node;
-               if xmlns == "http://jabber.org/protocol/disco#info" and not node then
-                       origin.send(get_disco_info(stanza));
-               elseif xmlns == "http://jabber.org/protocol/disco#items" and not node then
-                       origin.send(get_disco_items(stanza));
-               elseif xmlns == "http://jabber.org/protocol/muc#unique" then
+               if xmlns == "http://jabber.org/protocol/muc#unique" then
                        origin.send(st.reply(stanza):tag("unique", {xmlns = xmlns}):text(uuid_gen())); -- FIXME Random UUIDs can theoretically have collisions
                else
                        origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); -- TODO disco/etc
@@ -219,7 +214,8 @@ function shutdown_component()
        if not saved then
                local stanza = st.presence({type = "unavailable"})
                        :tag("x", {xmlns = "http://jabber.org/protocol/muc#user"})
-                               :tag("item", { affiliation='none', role='none' }):up();
+                               :tag("item", { affiliation='none', role='none' }):up()
+                               :tag("status", { code = "332"}):up();
                for roomjid, room in pairs(rooms) do
                        shutdown_room(room, stanza);
                end
@@ -228,3 +224,39 @@ function shutdown_component()
 end
 module.unload = shutdown_component;
 module:hook_global("server-stopping", shutdown_component);
+
+-- Ad-hoc commands
+module:depends("adhoc")
+local t_concat = table.concat;
+local keys = require "util.iterators".keys;
+local adhoc_new = module:require "adhoc".new;
+local adhoc_initial = require "util.adhoc".new_initial_data_form;
+local dataforms_new = require "util.dataforms".new;
+
+local destroy_rooms_layout = dataforms_new {
+       title = "Destroy rooms";
+       instructions = "Select the rooms to destroy";
+
+       { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/muc#destroy" };
+       { name = "rooms", type = "list-multi", required = true, label = "Rooms to destroy:"};
+};
+
+local destroy_rooms_handler = adhoc_initial(destroy_rooms_layout, function()
+       return { rooms = array.collect(keys(rooms)):sort() };
+end, function(fields, errors)
+       if errors then
+               local errmsg = {};
+               for name, err in pairs(errors) do
+                       errmsg[#errmsg + 1] = name .. ": " .. err;
+               end
+               return { status = "completed", error = { message = t_concat(errmsg, "\n") } };
+       end
+       for _, room in ipairs(fields.rooms) do
+               rooms[room]:destroy();
+               rooms[room] = nil;
+       end
+       return { status = "completed", info = "The following rooms were destroyed:\n"..t_concat(fields.rooms, "\n") };
+end);
+local destroy_rooms_desc = adhoc_new("Destroy Rooms", "http://prosody.im/protocol/muc#destroy", destroy_rooms_handler, "admin");
+
+module:provides("adhoc", destroy_rooms_desc);
index 1f725cf79a838cc9c4ec13dfdb9c18e157d28c10..483b0812b0d7e538c90a64b435f9bda0938158c6 100644 (file)
@@ -27,28 +27,16 @@ local muc_domain = nil; --module:get_host();
 local default_history_length, max_history_length = 20, math.huge;
 
 ------------
-local function filter_xmlns_from_array(array, filters)
-       local count = 0;
-       for i=#array,1,-1 do
-               local attr = array[i].attr;
-               if filters[attr and attr.xmlns] then
-                       t_remove(array, i);
-                       count = count + 1;
-               end
-       end
-       return count;
-end
-local function filter_xmlns_from_stanza(stanza, filters)
-       if filters then
-               if filter_xmlns_from_array(stanza.tags, filters) ~= 0 then
-                       return stanza, filter_xmlns_from_array(stanza, filters);
-               end
+local presence_filters = {["http://jabber.org/protocol/muc"]=true;["http://jabber.org/protocol/muc#user"]=true};
+local function presence_filter(tag)
+       if presence_filters[tag.attr.xmlns] then
+               return nil;
        end
-       return stanza, 0;
+       return tag;
 end
-local presence_filters = {["http://jabber.org/protocol/muc"]=true;["http://jabber.org/protocol/muc#user"]=true};
+
 local function get_filtered_presence(stanza)
-       return filter_xmlns_from_stanza(st.clone(stanza):reset(), presence_filters);
+       return st.clone(stanza):maptags(presence_filter);
 end
 local kickable_error_conditions = {
        ["gone"] = true;
@@ -72,17 +60,6 @@ local function is_kickable_error(stanza)
        local cond = get_error_condition(stanza);
        return kickable_error_conditions[cond] and cond;
 end
-local function getUsingPath(stanza, path, getText)
-       local tag = stanza;
-       for _, name in ipairs(path) do
-               if type(tag) ~= 'table' then return; end
-               tag = tag:child_with_name(name);
-       end
-       if tag and getText then tag = table.concat(tag); end
-       return tag;
-end
-local function getTag(stanza, path) return getUsingPath(stanza, path); end
-local function getText(stanza, path) return getUsingPath(stanza, path, true); end
 -----------
 
 local room_mt = {};
@@ -98,8 +75,8 @@ function room_mt:get_default_role(affiliation)
        elseif affiliation == "member" then
                return "participant";
        elseif not affiliation then
-               if not self:is_members_only() then
-                       return self:is_moderated() and "visitor" or "participant";
+               if not self:get_members_only() then
+                       return self:get_moderated() and "visitor" or "participant";
                end
        end
 end
@@ -218,10 +195,10 @@ function room_mt:get_disco_info(stanza)
                :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:get_moderated() and "muc_moderated" or "muc_unmoderated"}):up()
+               :tag("feature", {var=self:get_members_only() and "muc_membersonly" or "muc_open"}):up()
+               :tag("feature", {var=self:get_persistent() and "muc_persistent" or "muc_temporary"}):up()
+               :tag("feature", {var=self:get_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" },
@@ -238,7 +215,6 @@ function room_mt:get_disco_items(stanza)
        return reply;
 end
 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;
@@ -296,7 +272,7 @@ function room_mt:set_moderated(moderated)
                if self.save then self:save(true); end
        end
 end
-function room_mt:is_moderated()
+function room_mt:get_moderated()
        return self._data.moderated;
 end
 function room_mt:set_members_only(members_only)
@@ -306,7 +282,7 @@ function room_mt:set_members_only(members_only)
                if self.save then self:save(true); end
        end
 end
-function room_mt:is_members_only()
+function room_mt:get_members_only()
        return self._data.members_only;
 end
 function room_mt:set_persistent(persistent)
@@ -316,7 +292,7 @@ function room_mt:set_persistent(persistent)
                if self.save then self:save(true); end
        end
 end
-function room_mt:is_persistent()
+function room_mt:get_persistent()
        return self._data.persistent;
 end
 function room_mt:set_hidden(hidden)
@@ -326,9 +302,15 @@ function room_mt:set_hidden(hidden)
                if self.save then self:save(true); end
        end
 end
-function room_mt:is_hidden()
+function room_mt:get_hidden()
        return self._data.hidden;
 end
+function room_mt:get_public()
+       return not self:get_hidden();
+end
+function room_mt:set_public(public)
+       return self:set_hidden(not public);
+end
 function room_mt:set_changesubject(changesubject)
        changesubject = changesubject and true or nil;
        if self._data.changesubject ~= changesubject then
@@ -351,6 +333,19 @@ function room_mt:set_historylength(length)
 end
 
 
+local valid_whois = { moderators = true, anyone = true };
+
+function room_mt:set_whois(whois)
+       if valid_whois[whois] and self._data.whois ~= whois then
+               self._data.whois = whois;
+               if self.save then self:save(true); end
+       end
+end
+
+function room_mt:get_whois()
+       return self._data.whois;
+end
+
 local function construct_stanza_id(room, stanza)
        local from_jid, to_nick = stanza.attr.from, stanza.attr.to;
        local from_nick = room._jid_nick[from_jid];
@@ -575,11 +570,11 @@ end
 
 function room_mt:send_form(origin, stanza)
        origin.send(st.reply(stanza):query("http://jabber.org/protocol/muc#owner")
-               :add_child(self:get_form_layout():form())
+               :add_child(self:get_form_layout(stanza.attr.from):form())
        );
 end
 
-function room_mt:get_form_layout()
+function room_mt:get_form_layout(actor)
        local form = dataform.new({
                title = "Configuration for "..self.jid,
                instructions = "Complete and submit this form to configure the room.",
@@ -604,13 +599,13 @@ function room_mt:get_form_layout()
                        name = 'muc#roomconfig_persistentroom',
                        type = 'boolean',
                        label = 'Make Room Persistent?',
-                       value = self:is_persistent()
+                       value = self:get_persistent()
                },
                {
                        name = 'muc#roomconfig_publicroom',
                        type = 'boolean',
                        label = 'Make Room Publicly Searchable?',
-                       value = not self:is_hidden()
+                       value = not self:get_hidden()
                },
                {
                        name = 'muc#roomconfig_changesubject',
@@ -637,13 +632,13 @@ function room_mt:get_form_layout()
                        name = 'muc#roomconfig_moderatedroom',
                        type = 'boolean',
                        label = 'Make Room Moderated?',
-                       value = self:is_moderated()
+                       value = self:get_moderated()
                },
                {
                        name = 'muc#roomconfig_membersonly',
                        type = 'boolean',
                        label = 'Make Room Members-Only?',
-                       value = self:is_members_only()
+                       value = self:get_members_only()
                },
                {
                        name = 'muc#roomconfig_historylength',
@@ -652,14 +647,9 @@ function room_mt:get_form_layout()
                        value = tostring(self:get_historylength())
                }
        });
-       return module:fire_event("muc-config-form", { room = self, form = form }) or form;
+       return module:fire_event("muc-config-form", { room = self, actor = actor, form = form }) or form;
 end
 
-local valid_whois = {
-       moderators = true,
-       anyone = true,
-}
-
 function room_mt:process_form(origin, stanza)
        local query = stanza.tags[1];
        local form;
@@ -668,84 +658,46 @@ function room_mt:process_form(origin, stanza)
        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);
+       local fields = self:get_form_layout(stanza.attr.from):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 event = { room = self, fields = fields, changed = dirty };
-       module:fire_event("muc-config-submitted", event);
-       dirty = event.changed or dirty;
-
-       local name = fields['muc#roomconfig_roomname'];
-       if name ~= self:get_name() then
-               self:set_name(name);
-       end
+       local changed = {};
 
-       local description = fields['muc#roomconfig_roomdesc'];
-       if description ~= self:get_description() then
-               self:set_description(description);
+       local function handle_option(name, field, allowed)
+               local new = fields[field];
+               if new == nil then return; end
+               if allowed and not allowed[new] then return; end
+               if new == self["get_"..name](self) then return; end
+               changed[name] = true;
+               self["set_"..name](self, new);
        end
 
-       local persistent = fields['muc#roomconfig_persistentroom'];
-       dirty = dirty or (self:is_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")
-
-       local historylength = tonumber(fields['muc#roomconfig_historylength']);
-       dirty = dirty or (historylength and (self:get_historylength() ~= historylength));
-       module:log('debug', 'historylength=%s', historylength)
+       local event = { room = self, fields = fields, changed = changed, stanza = stanza, origin = origin, update_option = handle_option };
+       module:fire_event("muc-config-submitted", event);
 
-
-       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'"));
-           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);
-       self:set_historylength(historylength);
+       handle_option("name", "muc#roomconfig_roomname");
+       handle_option("description", "muc#roomconfig_roomdesc");
+       handle_option("persistent", "muc#roomconfig_persistentroom");
+       handle_option("moderated", "muc#roomconfig_moderatedroom");
+       handle_option("members_only", "muc#roomconfig_membersonly");
+       handle_option("public", "muc#roomconfig_publicroom");
+       handle_option("changesubject", "muc#roomconfig_changesubject");
+       handle_option("historylength", "muc#roomconfig_historylength");
+       handle_option("whois", "muc#roomconfig_whois", valid_whois);
+       handle_option("password", "muc#roomconfig_roomsecret");
 
        if self.save then self:save(true); end
        origin.send(st.reply(stanza));
 
-       if dirty or whois_changed then
+       if next(changed) then
                local msg = st.message({type='groupchat', from=self.jid})
                        :tag('x', {xmlns='http://jabber.org/protocol/muc#user'}):up()
-
-               if dirty then
-                       msg.tags[1]:tag('status', {code = '104'}):up();
-               end
-               if whois_changed then
-                       local code = (whois == 'moderators') and "173" or "172";
+                               :tag('status', {code = '104'}):up();
+               if changed.whois then
+                       local code = (self:get_whois() == 'moderators') and "173" or "172";
                        msg.tags[1]:tag('status', {code = code}):up();
                end
-
                self:broadcast_message(msg, false)
        end
 end
@@ -881,7 +833,7 @@ function room_mt:handle_to_room(origin, stanza) -- presence changes and groupcha
                        origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
                end
        elseif stanza.name == "message" and type == "groupchat" then
-               local from, to = stanza.attr.from, stanza.attr.to;
+               local from = stanza.attr.from;
                local current_nick = self._jid_nick[from];
                local occupant = self._occupants[current_nick];
                if not occupant then -- not in room
@@ -891,11 +843,11 @@ function room_mt:handle_to_room(origin, stanza) -- presence changes and groupcha
                else
                        local from = stanza.attr.from;
                        stanza.attr.from = current_nick;
-                       local subject = getText(stanza, {"subject"});
+                       local subject = stanza:get_child_text("subject");
                        if subject then
                                if occupant.role == "moderator" or
                                        ( self._data.changesubject and occupant.role == "participant" ) then -- and participant
-                                       self:set_subject(current_nick, subject); -- TODO use broadcast_message_stanza
+                                       self:set_subject(current_nick, subject);
                                else
                                        stanza.attr.from = from;
                                        origin.send(st.error_reply(stanza, "auth", "forbidden"));
@@ -943,7 +895,7 @@ function room_mt:handle_to_room(origin, stanza) -- presence changes and groupcha
                                        :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
+                               if self:get_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
diff --git a/prosody b/prosody
index 9a88eac0b227e65406608276a3f4e861fe78ef94..1a0f6ff2d76119f7907785187a94e0541be72079 100755 (executable)
--- a/prosody
+++ b/prosody
@@ -264,12 +264,6 @@ function init_global_state()
                prosody.events.fire_event("server-stopping", {reason = reason});
                server.setquitting(true);
        end
-
-       -- Load SSL settings from config, and create a ctx table
-       local certmanager = require "core.certmanager";
-       local global_ssl_ctx = certmanager.create_context("*", "server");
-       prosody.global_ssl_ctx = global_ssl_ctx;
-
 end
 
 function read_version()
index 230329325b39eaf702f3114bf4b15fbce6188040..30221da92e23be54076842114f3063f3c8d0caa1 100644 (file)
@@ -4,7 +4,7 @@
 -- 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
+-- when you have finished by running: prosodyctl check config
 -- If there are any errors, it will let you know what and where
 -- they are, otherwise it will keep quiet.
 --
@@ -24,7 +24,7 @@ admins = { }
 
 -- Enable use of libevent for better performance under high load
 -- For more information see: http://prosody.im/doc/libevent
---use_libevent = true;
+--use_libevent = true
 
 -- This is the list of modules Prosody will load on startup.
 -- It looks for mod_modulename.lua in the plugins folder, so make sure that exists too.
@@ -70,7 +70,7 @@ modules_enabled = {
                --"watchregistrations"; -- Alert admins of registrations
                --"motd"; -- Send a message to users when they log in
                --"legacyauth"; -- Legacy authentication. Only used by some old clients and bots.
-};
+}
 
 -- These modules are auto-loaded, but should you want
 -- to disable them then uncomment them here:
@@ -78,11 +78,11 @@ modules_disabled = {
        -- "offline"; -- Store offline messages
        -- "c2s"; -- Handle client connections
        -- "s2s"; -- Handle server-to-server connections
-};
+}
 
 -- Disable account creation by default, for security
 -- For more information see http://prosody.im/doc/creating_accounts
-allow_registration = false;
+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
index 247b099a94f73a30109a7139e45103a9fe32332f..aa6f2073d5708225d3a81ec388e4ee6204e7184f 100755 (executable)
@@ -274,11 +274,12 @@ local commands = {};
 local command = arg[1];
 
 function commands.adduser(arg)
+       local jid_split = require "util.jid".split;
        if not arg[1] or arg[1] == "--help" then
                show_usage([[adduser JID]], [[Create the specified user account in Prosody]]);
                return 1;
        end
-       local user, host = arg[1]:match("([^@]+)@(.+)");
+       local user, host = jid_split(arg[1]);
        if not user and host then
                show_message [[Failed to understand JID, please supply the JID you want to create]]
                show_usage [[adduser user@host]]
@@ -313,11 +314,12 @@ function commands.adduser(arg)
 end
 
 function commands.passwd(arg)
+       local jid_split = require "util.jid".split;
        if not arg[1] or arg[1] == "--help" then
                show_usage([[passwd JID]], [[Set the password for the specified user account in Prosody]]);
                return 1;
        end
-       local user, host = arg[1]:match("([^@]+)@(.+)");
+       local user, host = jid_split(arg[1]);
        if not user and host then
                show_message [[Failed to understand JID, please supply the JID you want to set the password for]]
                show_usage [[passwd user@host]]
@@ -352,11 +354,12 @@ function commands.passwd(arg)
 end
 
 function commands.deluser(arg)
+       local jid_split = require "util.jid".split;
        if not arg[1] or arg[1] == "--help" then
                show_usage([[deluser JID]], [[Permanently remove the specified user account from Prosody]]);
                return 1;
        end
-       local user, host = arg[1]:match("([^@]+)@(.+)");
+       local user, host = jid_split(arg[1]);
        if not user and host then
                show_message [[Failed to understand JID, please supply the JID you want to set the password for]]
                show_usage [[passwd user@host]]
@@ -776,6 +779,332 @@ function commands.cert(arg)
        show_usage("cert config|request|generate|key", "Helpers for generating X.509 certificates and keys.")
 end
 
+function commands.check(arg)
+       if arg[1] == "--help" then
+               show_usage([[check]], [[Perform basic checks on your Prosody installation]]);
+               return 1;
+       end
+       local what = table.remove(arg, 1);
+       local array, set = require "util.array", require "util.set";
+       local it = require "util.iterators";
+       local ok = true;
+       if not what or what == "config" then
+               print("Checking config...");
+               local known_global_options = set.new({
+                       "pidfile", "log", "plugin_paths", "prosody_user", "prosody_group", "daemonize",
+                       "umask", "prosodyctl_timeout", "use_ipv6", "use_libevent", "network_settings"
+               });
+               local config = config.getconfig();
+               -- Check that we have any global options (caused by putting a host at the top)
+               if it.count(it.filter("log", pairs(config["*"]))) == 0 then
+                       ok = false;
+                       print("");
+                       print("    No global options defined. Perhaps you have put a host definition at the top")
+                       print("    of the config file? They should be at the bottom, see http://prosody.im/doc/configure#overview");
+               end
+               -- Check for global options under hosts
+               local global_options = set.new(it.to_array(it.keys(config["*"])));
+               for host, options in it.filter("*", pairs(config)) do
+                       local host_options = set.new(it.to_array(it.keys(options)));
+                       local misplaced_options = set.intersection(host_options, known_global_options);
+                       for name in pairs(options) do
+                               if name:match("^interfaces?")
+                               or name:match("_ports?$") or name:match("_interfaces?$")
+                               or name:match("_ssl$") then
+                                       misplaced_options:add(name);
+                               end
+                       end
+                       if not misplaced_options:empty() then
+                               ok = false;
+                               print("");
+                               local n = it.count(misplaced_options);
+                               print("    You have "..n.." option"..(n>1 and "s " or " ").."set under "..host.." that should be");
+                               print("    in the global section of the config file, above any VirtualHost or Component definitions,")
+                               print("    see http://prosody.im/doc/configure#overview for more information.")
+                               print("");
+                               print("    You need to move the following option"..(n>1 and "s" or "")..": "..table.concat(it.to_array(misplaced_options), ", "));
+                       end
+                       local subdomain = host:match("^[^.]+");
+                       if not(host_options:contains("component_module")) and (subdomain == "jabber" or subdomain == "xmpp"
+                          or subdomain == "chat" or subdomain == "im") then
+                               print("");
+                               print("    Suggestion: If "..host.. " is a new host with no real users yet, consider renaming it now to");
+                               print("     "..host:gsub("^[^.]+%.", "")..". You can use SRV records to redirect XMPP clients and servers to "..host..".");
+                               print("     For more information see: http://prosody.im/doc/dns");
+                       end
+               end
+               
+               print("Done.\n");
+       end
+       if not what or what == "dns" then
+               local dns = require "net.dns";
+               local ip = require "util.ip";
+               local c2s_ports = set.new(config.get("*", "c2s_ports") or {5222});
+               local s2s_ports = set.new(config.get("*", "s2s_ports") or {5269});
+               
+               local c2s_srv_required, s2s_srv_required;
+               if not c2s_ports:contains(5222) then
+                       c2s_srv_required = true;
+               end
+               if not s2s_ports:contains(5269) then
+                       s2s_srv_required = true;
+               end
+               
+               local problem_hosts = set.new();
+               
+               local external_addresses, internal_addresses = set.new(), set.new();
+               
+               local fqdn = socket.dns.tohostname(socket.dns.gethostname());
+               if fqdn then
+                       local res = dns.lookup(fqdn, "A");
+                       if res then
+                               for _, record in ipairs(res) do
+                                       external_addresses:add(record.a);
+                               end
+                       end
+                       local res = dns.lookup(fqdn, "AAAA");
+                       if res then
+                               for _, record in ipairs(res) do
+                                       external_addresses:add(record.aaaa);
+                               end
+                       end
+               end
+               
+               local local_addresses = socket.local_addresses and socket.local_addresses() or {};
+               
+               for addr in it.values(local_addresses) do
+                       if not ip.new_ip(addr).private then
+                               external_addresses:add(addr);
+                       else
+                               internal_addresses:add(addr);
+                       end
+               end
+               
+               if external_addresses:empty() then
+                       print("");
+                       print("   Failed to determine the external addresses of this server. Checks may be inaccurate.");
+                       c2s_srv_required, s2s_srv_required = true, true;
+               end
+               
+               local v6_supported = not not socket.tcp6;
+               
+               for host, host_options in it.filter("*", pairs(config.getconfig())) do
+                       local all_targets_ok, some_targets_ok = true, false;
+                       
+                       local is_component = not not host_options.component_module;
+                       print("Checking DNS for "..(is_component and "component" or "host").." "..host.."...");
+                       local target_hosts = set.new();
+                       if not is_component then
+                               local res = dns.lookup("_xmpp-client._tcp."..host..".", "SRV");
+                               if res then
+                                       for _, record in ipairs(res) do
+                                               target_hosts:add(record.srv.target);
+                                               if not c2s_ports:contains(record.srv.port) then
+                                                       print("    SRV target "..record.srv.target.." contains unknown client port: "..record.srv.port);
+                                               end
+                                       end
+                               else
+                                       if c2s_srv_required then
+                                               print("    No _xmpp-client SRV record found for "..host..", but it looks like you need one.");
+                                               all_targst_ok = false;
+                                       else
+                                               target_hosts:add(host);
+                                       end
+                               end
+                       end
+                       local res = dns.lookup("_xmpp-server._tcp."..host..".", "SRV");
+                       if res then
+                               for _, record in ipairs(res) do
+                                       target_hosts:add(record.srv.target);
+                                       if not s2s_ports:contains(record.srv.port) then
+                                               print("    SRV target "..record.srv.target.." contains unknown server port: "..record.srv.port);
+                                       end
+                               end
+                       else
+                               if s2s_srv_required then
+                                       print("    No _xmpp-server SRV record found for "..host..", but it looks like you need one.");
+                                       all_targets_ok = false;
+                               else
+                                       target_hosts:add(host);
+                               end
+                       end
+                       if target_hosts:empty() then
+                               target_hosts:add(host);
+                       end
+                       
+                       if target_hosts:contains("localhost") then
+                               print("    Target 'localhost' cannot be accessed from other servers");
+                               target_hosts:remove("localhost");
+                       end
+                       
+                       local modules = set.new(it.to_array(it.values(host_options.modules_enabled)))
+                                       + set.new(it.to_array(it.values(config.get("*", "modules_enabled"))))
+                                       + set.new({ config.get(host, "component_module") });
+
+                       if modules:contains("proxy65") then
+                               local proxy65_target = config.get(host, "proxy65_address") or host;
+                               local A, AAAA = dns.lookup(proxy65_target, "A"), dns.lookup(proxy65_target, "AAAA");
+                               local prob = {};
+                               if not A then
+                                       table.insert(prob, "A");
+                               end
+                               if v6_supported and not AAAA then
+                                       table.insert(prob, "AAAA");
+                               end
+                               if #prob > 0 then
+                                       print("    File transfer proxy "..proxy65_target.." has no "..table.concat(prob, "/").." record. Create one or set 'proxy65_address' to the correct host/IP.");
+                               end
+                       end
+                       
+                       for host in target_hosts do
+                               local host_ok_v4, host_ok_v6;
+                               local res = dns.lookup(host, "A");
+                               if res then
+                                       for _, record in ipairs(res) do
+                                               if external_addresses:contains(record.a) then
+                                                       some_targets_ok = true;
+                                                       host_ok_v4 = true;
+                                               elseif internal_addresses:contains(record.a) then
+                                                       host_ok_v4 = true;
+                                                       some_targets_ok = true;
+                                                       print("    "..host.." A record points to internal address, external connections might fail");
+                                               else
+                                                       print("    "..host.." A record points to unknown address "..record.a);
+                                                       all_targets_ok = false;
+                                               end
+                                       end
+                               end
+                               local res = dns.lookup(host, "AAAA");
+                               if res then
+                                       for _, record in ipairs(res) do
+                                               if external_addresses:contains(record.aaaa) then
+                                                       some_targets_ok = true;
+                                                       host_ok_v6 = true;
+                                               elseif internal_addresses:contains(record.aaaa) then
+                                                       host_ok_v6 = true;
+                                                       some_targets_ok = true;
+                                                       print("    "..host.." AAAA record points to internal address, external connections might fail");
+                                               else
+                                                       print("    "..host.." AAAA record points to unknown address "..record.aaaa);
+                                                       all_targets_ok = false;
+                                               end
+                                       end
+                               end
+                               
+                               local bad_protos = {}
+                               if not host_ok_v4 then
+                                       table.insert(bad_protos, "IPv4");
+                               end
+                               if not host_ok_v6 then
+                                       table.insert(bad_protos, "IPv6");
+                               end
+                               if #bad_protos > 0 then
+                                       print("    Host "..host.." does not seem to resolve to this server ("..table.concat(bad_protos, "/")..")");
+                               end
+                               if host_ok_v6 and not v6_supported then
+                                       print("    Host "..host.." has AAAA records, but your version of LuaSocket does not support IPv6.");
+                                       print("      Please see http://prosody.im/doc/ipv6 for more information.");
+                               end
+                       end
+                       if not all_targets_ok then
+                               print("    "..(some_targets_ok and "Only some" or "No").." targets for "..host.." appear to resolve to this server.");
+                               if is_component then
+                                       print("    DNS records are necessary if you want users on other servers to access this component.");
+                               end
+                               problem_hosts:add(host);
+                       end
+                       print("");
+               end
+               if not problem_hosts:empty() then
+                       print("");
+                       print("For more information about DNS configuration please see http://prosody.im/doc/dns");
+                       print("");
+                       ok = false;
+               end
+       end
+       if not what or what == "certs" then
+               local cert_ok;
+               print"Checking certificates..."
+               local x509_verify_identity = require"util.x509".verify_identity;
+               local ssl = dependencies.softreq"ssl";
+               -- local datetime_parse = require"util.datetime".parse_x509;
+               local load_cert = ssl and ssl.x509 and ssl.x509.load;
+               -- or ssl.cert_from_pem
+               if not ssl then
+                       print("LuaSec not available, can't perform certificate checks")
+                       if what == "certs" then cert_ok = false end
+               elseif not load_cert then
+                       print("This version of LuaSec (" .. ssl._VERSION .. ") does not support certificate checking");
+                       cert_ok = false
+               else
+                       for host in pairs(hosts) do
+                               if host ~= "*" then -- Should check global certs too.
+                                       print("Checking certificate for "..host);
+                                       -- First, let's find out what certificate this host uses.
+                                       local ssl_config = config.rawget(host, "ssl");
+                                       if not ssl_config then
+                                               local base_host = host:match("%.(.*)");
+                                               ssl_config = config.get(base_host, "ssl");
+                                       end
+                                       if not ssl_config then
+                                               print("  No 'ssl' option defined for "..host)
+                                               cert_ok = false
+                                       elseif not ssl_config.certificate then
+                                               print("  No 'certificate' set in ssl option for "..host)
+                                               cert_ok = false
+                                       elseif not ssl_config.key then
+                                               print("  No 'key' set in ssl option for "..host)
+                                               cert_ok = false
+                                       else
+                                               local key, err = io.open(ssl_config.key); -- Permissions check only
+                                               if not key then
+                                                       print("    Could not open "..ssl_config.key..": "..err);
+                                                       cert_ok = false
+                                               else
+                                                       key:close();
+                                               end
+                                               local cert_fh, err = io.open(ssl_config.certificate); -- Load the file.
+                                               if not cert_fh then
+                                                       print("    Could not open "..ssl_config.certificate..": "..err);
+                                                       cert_ok = false
+                                               else
+                                                       print("  Certificate: "..ssl_config.certificate)
+                                                       local cert = load_cert(cert_fh:read"*a"); cert_fh = cert_fh:close();
+                                                       if not cert:validat(os.time()) then
+                                                               print("    Certificate has expired.")
+                                                               cert_ok = false
+                                                       end
+                                                       if config.get(host, "component_module") == nil
+                                                       and not x509_verify_identity(host, "_xmpp-client", cert) then
+                                                               print("    Not vaild for client connections to "..host..".")
+                                                               cert_ok = false
+                                                       end
+                                                       if (not (config.get(name, "anonymous_login")
+                                                               or config.get(name, "authentication") == "anonymous"))
+                                                       and not x509_verify_identity(host, "_xmpp-client", cert) then
+                                                               print("    Not vaild for server-to-server connections to "..host..".")
+                                                               cert_ok = false
+                                                       end
+                                               end
+                                       end
+                               end
+                       end
+                       if cert_ok == false then
+                               print("")
+                               print("For more information about certificates please see http://prosody.im/doc/certificates");
+                               ok = false
+                       end
+               end
+               print("")
+       end
+       if not ok then
+               print("Problems found, see above.");
+       else
+               print("All checks passed, congratulations!");
+       end
+       return ok and 0 or 2;
+end
+
 ---------------------
 
 if command and command:match("^mod_") then -- Is a command in a module
index db727ce157739265964312557af7ce937af5c19f..b6728061e7ed9332cfb62a53c1495ebb259df917 100644 (file)
@@ -12,12 +12,12 @@ function run_all_tests()
        package.loaded["net.connlisteners"] = { get = function () return {} end };
        dotest "util.jid"
        dotest "util.multitable"
-       dotest "util.rfc3484"
-       dotest "net.http"
-       dotest "core.modulemanager"
+       dotest "util.rfc6724"
+       dotest "util.http"
        dotest "core.stanza_router"
        dotest "core.s2smanager"
        dotest "core.configmanager"
+       dotest "util.ip"
        dotest "util.stanza"
        dotest "util.sasl.scram"
        
@@ -136,15 +136,21 @@ function dotest(unitname)
        end
        
        local oldmodule, old_M = _fakeG.module, _fakeG._M;
-       _fakeG.module = function () _M = _G end
+       _fakeG.module = function () _M = unit end
        setfenv(chunk, unit);
-       local success, err = pcall(chunk);
+       local success, ret = pcall(chunk);
        _fakeG.module, _fakeG._M = oldmodule, old_M;
        if not success then
                print("WARNING: ", "Failed to initialise module: "..unitname, err);
                return;
        end
        
+       if type(ret) == "table" then
+               for k,v in pairs(ret) do
+                       unit[k] = v;
+               end
+       end
+
        for name, f in pairs(unit) do
                local test = rawget(tests, name);
                if type(f) ~= "function" then
index 132dfc7456bd98360556bf3c4ec52328c6f908e4..d79199658eace9f21aff6181de872afea36600ea 100644 (file)
@@ -9,27 +9,23 @@
 
 
 function get(get, config)
-       config.set("example.com", "test", "testkey", 123);
-       assert_equal(get("example.com", "test", "testkey"), 123, "Retrieving a set key");
+       config.set("example.com", "testkey", 123);
+       assert_equal(get("example.com", "testkey"), 123, "Retrieving a set key");
 
-       config.set("*", "test", "testkey1", 321);
-       assert_equal(get("*", "test", "testkey1"), 321, "Retrieving a set global key");
-       assert_equal(get("example.com", "test", "testkey1"), 321, "Retrieving a set key of undefined host, of which only a globally set one exists");
+       config.set("*", "testkey1", 321);
+       assert_equal(get("*", "testkey1"), 321, "Retrieving a set global key");
+       assert_equal(get("example.com", "testkey1"), 321, "Retrieving a set key of undefined host, of which only a globally set one exists");
        
-       config.set("example.com", "test", ""); -- Creates example.com host in config
-       assert_equal(get("example.com", "test", "testkey1"), 321, "Retrieving a set key, of which only a globally set one exists");
+       config.set("example.com", ""); -- Creates example.com host in config
+       assert_equal(get("example.com", "testkey1"), 321, "Retrieving a set key, of which only a globally set one exists");
        
        assert_equal(get(), nil, "No parameters to get()");
        assert_equal(get("undefined host"), nil, "Getting for undefined host");
-       assert_equal(get("undefined host", "undefined section"), nil, "Getting for undefined host & section");
-       assert_equal(get("undefined host", "undefined section", "undefined key"), nil, "Getting for undefined host & section & key");
-
-       assert_equal(get("example.com", "undefined section", "testkey"), nil, "Defined host, undefined section");
+       assert_equal(get("undefined host", "undefined key"), nil, "Getting for undefined host & key");
 end
 
 function set(set, u)
-       assert_equal(set("*"), false, "Set with no section/key");
-       assert_equal(set("*", "set_test"), false, "Set with no key");
+       assert_equal(set("*"), false, "Set with no key");
 
        assert_equal(set("*", "set_test", "testkey"), true, "Setting a nil global value");
        assert_equal(set("*", "set_test", "testkey", 123), true, "Setting a global value");
diff --git a/tests/test_core_modulemanager.lua b/tests/test_core_modulemanager.lua
deleted file mode 100644 (file)
index 9498875..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
--- Prosody IM
--- Copyright (C) 2008-2010 Matthew Wild
--- Copyright (C) 2008-2010 Waqas Hussain
--- 
--- This project is MIT/X11 licensed. Please see the
--- COPYING file in the source package for more information.
---
-
-local config = require "core.configmanager";
-local helpers = require "util.helpers";
-local set = require "util.set";
-
-function load_modules_for_host(load_modules_for_host, mm)
-       local test_num = 0;
-       local function test_load(global_modules_enabled, global_modules_disabled, host_modules_enabled, host_modules_disabled, expected_modules)
-               test_num = test_num + 1;
-               -- Prepare
-               hosts = { ["example.com"] = {} };
-               config.set("*", "core", "modules_enabled", global_modules_enabled);
-               config.set("*", "core", "modules_disabled", global_modules_disabled);
-               config.set("example.com", "core", "modules_enabled", host_modules_enabled);
-               config.set("example.com", "core", "modules_disabled", host_modules_disabled);
-               
-               expected_modules = set.new(expected_modules);
-               expected_modules:add_list(helpers.get_upvalue(load_modules_for_host, "autoload_modules"));
-               
-               local loaded_modules = set.new();
-               function mm.load(host, module)
-                       assert_equal(host, "example.com", test_num..": Host isn't example.com but "..tostring(host));
-                       assert_equal(expected_modules:contains(module), true, test_num..": Loading unexpected module '"..tostring(module).."'");
-                       loaded_modules:add(module);
-               end
-               load_modules_for_host("example.com");
-               assert_equal((expected_modules - loaded_modules):empty(), true, test_num..": Not all modules loaded: "..tostring(expected_modules - loaded_modules));
-       end
-       
-       test_load({ "one", "two", "three" }, nil, nil, nil, { "one", "two", "three" });
-       test_load({ "one", "two", "three" }, {}, nil, nil, { "one", "two", "three" });
-       test_load({ "one", "two", "three" }, { "two" }, nil, nil, { "one", "three" });
-       test_load({ "one", "two", "three" }, { "three" }, nil, nil, { "one", "two" });
-       test_load({ "one", "two", "three" }, nil, nil, { "three" }, { "one", "two" });
-       test_load({ "one", "two", "three" }, nil, { "three" }, { "three" }, { "one", "two", "three" });
-
-       test_load({ "one", "two" }, nil, { "three" }, nil, { "one", "two", "three" });
-       test_load({ "one", "two", "three" }, nil, { "three" }, nil, { "one", "two", "three" });
-       test_load({ "one", "two", "three" }, { "three" }, { "three" }, nil, { "one", "two", "three" });
-       test_load({ "one", "two" }, { "three" }, { "three" }, nil, { "one", "two", "three" });
-end
index b49c7da6cd2827790d538ee66c40e6dabbf30c2c..7194d201dd75b99ed3cd4cc31c6e4900e83d6107 100644 (file)
@@ -6,6 +6,9 @@
 -- COPYING file in the source package for more information.
 --
 
+env = {
+       prosody = { events = require "util.events".new() };
+};
 
 function compare_srv_priorities(csp)
        local r1 = { priority = 10, weight = 0 }
diff --git a/tests/test_net_http.lua b/tests/test_net_http.lua
deleted file mode 100644 (file)
index e68f96e..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
--- Prosody IM
--- Copyright (C) 2008-2010 Matthew Wild
--- Copyright (C) 2008-2010 Waqas Hussain
--- 
--- This project is MIT/X11 licensed. Please see the
--- COPYING file in the source package for more information.
---
-
-function urlencode(urlencode)
-       assert_equal(urlencode("helloworld123"), "helloworld123", "Normal characters not escaped");
-       assert_equal(urlencode("hello world"), "hello%20world", "Spaces escaped");
-       assert_equal(urlencode("This & that = something"), "This%20%26%20that%20%3d%20something", "Important URL chars escaped");
-end
-
-function urldecode(urldecode)
-       assert_equal("helloworld123", urldecode("helloworld123"), "Normal characters not escaped");
-       assert_equal("hello world", urldecode("hello%20world"), "Spaces escaped");
-       assert_equal("This & that = something", urldecode("This%20%26%20that%20%3d%20something"), "Important URL chars escaped");
-       assert_equal("This & that = something", urldecode("This%20%26%20that%20%3D%20something"), "Important URL chars escaped");
-end
-
-function formencode(formencode)
-       assert_equal(formencode({ { name = "one", value = "1"}, { name = "two", value = "2" } }), "one=1&two=2", "Form encoded");
-       assert_equal(formencode({ { name = "one two", value = "1"}, { name = "two one&", value = "2" } }), "one+two=1&two+one%26=2", "Form encoded");
-end
-
-function formdecode(formdecode)
-       local t = formdecode("one=1&two=2");
-       assert_table(t[1]);
-       assert_equal(t[1].name, "one"); assert_equal(t[1].value, "1");
-       assert_table(t[2]);
-       assert_equal(t[2].name, "two"); assert_equal(t[2].value, "2");
-
-       local t = formdecode("one+two=1&two+one%26=2");
-       assert_equal(t[1].name, "one two"); assert_equal(t[1].value, "1");
-       assert_equal(t[2].name, "two one&"); assert_equal(t[2].value, "2");
-end
diff --git a/tests/test_util_http.lua b/tests/test_util_http.lua
new file mode 100644 (file)
index 0000000..e68f96e
--- /dev/null
@@ -0,0 +1,37 @@
+-- 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.
+--
+
+function urlencode(urlencode)
+       assert_equal(urlencode("helloworld123"), "helloworld123", "Normal characters not escaped");
+       assert_equal(urlencode("hello world"), "hello%20world", "Spaces escaped");
+       assert_equal(urlencode("This & that = something"), "This%20%26%20that%20%3d%20something", "Important URL chars escaped");
+end
+
+function urldecode(urldecode)
+       assert_equal("helloworld123", urldecode("helloworld123"), "Normal characters not escaped");
+       assert_equal("hello world", urldecode("hello%20world"), "Spaces escaped");
+       assert_equal("This & that = something", urldecode("This%20%26%20that%20%3d%20something"), "Important URL chars escaped");
+       assert_equal("This & that = something", urldecode("This%20%26%20that%20%3D%20something"), "Important URL chars escaped");
+end
+
+function formencode(formencode)
+       assert_equal(formencode({ { name = "one", value = "1"}, { name = "two", value = "2" } }), "one=1&two=2", "Form encoded");
+       assert_equal(formencode({ { name = "one two", value = "1"}, { name = "two one&", value = "2" } }), "one+two=1&two+one%26=2", "Form encoded");
+end
+
+function formdecode(formdecode)
+       local t = formdecode("one=1&two=2");
+       assert_table(t[1]);
+       assert_equal(t[1].name, "one"); assert_equal(t[1].value, "1");
+       assert_table(t[2]);
+       assert_equal(t[2].name, "two"); assert_equal(t[2].value, "2");
+
+       local t = formdecode("one+two=1&two+one%26=2");
+       assert_equal(t[1].name, "one two"); assert_equal(t[1].value, "1");
+       assert_equal(t[2].name, "two one&"); assert_equal(t[2].value, "2");
+end
diff --git a/tests/test_util_ip.lua b/tests/test_util_ip.lua
new file mode 100644 (file)
index 0000000..410f1da
--- /dev/null
@@ -0,0 +1,89 @@
+
+function match(match, _M)
+       local _ = _M.new_ip;
+       local ip = _"10.20.30.40";
+       assert_equal(match(ip, _"10.0.0.0", 8), true);
+       assert_equal(match(ip, _"10.0.0.0", 16), false);
+       assert_equal(match(ip, _"10.0.0.0", 24), false);
+       assert_equal(match(ip, _"10.0.0.0", 32), false);
+
+       assert_equal(match(ip, _"10.20.0.0", 8), true);
+       assert_equal(match(ip, _"10.20.0.0", 16), true);
+       assert_equal(match(ip, _"10.20.0.0", 24), false);
+       assert_equal(match(ip, _"10.20.0.0", 32), false);
+
+       assert_equal(match(ip, _"0.0.0.0", 32), false);
+       assert_equal(match(ip, _"0.0.0.0", 0), true);
+       assert_equal(match(ip, _"0.0.0.0"), false);
+
+       assert_equal(match(ip, _"10.0.0.0", 255), false, "excessive number of bits");
+       assert_equal(match(ip, _"10.0.0.0", -8), true, "negative number of bits");
+       assert_equal(match(ip, _"10.0.0.0", -32), true, "negative number of bits");
+       assert_equal(match(ip, _"10.0.0.0", 0), true, "zero bits");
+       assert_equal(match(ip, _"10.0.0.0"), false, "no specified number of bits (differing ip)");
+       assert_equal(match(ip, _"10.20.30.40"), true, "no specified number of bits (same ip)");
+
+       assert_equal(match(_"127.0.0.1", _"127.0.0.1"), true, "simple ip");
+
+       assert_equal(match(_"8.8.8.8", _"8.8.0.0", 16), true);
+       assert_equal(match(_"8.8.4.4", _"8.8.0.0", 16), true);
+end
+
+function parse_cidr(parse_cidr, _M)
+       local new_ip = _M.new_ip;
+       
+       assert_equal(new_ip"0.0.0.0", new_ip"0.0.0.0")
+       
+       local function assert_cidr(cidr, ip, bits)
+               local parsed_ip, parsed_bits = parse_cidr(cidr);
+               assert_equal(new_ip(ip), parsed_ip, cidr.." parsed ip is "..ip);
+               assert_equal(bits, parsed_bits, cidr.." parsed bits is "..tostring(bits));
+       end
+       assert_cidr("0.0.0.0", "0.0.0.0", nil);
+       assert_cidr("127.0.0.1", "127.0.0.1", nil);
+       assert_cidr("127.0.0.1/0", "127.0.0.1", 0);
+       assert_cidr("127.0.0.1/8", "127.0.0.1", 8);
+       assert_cidr("127.0.0.1/32", "127.0.0.1", 32);
+       assert_cidr("127.0.0.1/256", "127.0.0.1", 256);
+       assert_cidr("::/48", "::", 48);
+end
+
+function new_ip(new_ip)
+       local v4, v6 = "IPv4", "IPv6";
+       local function assert_proto(s, proto)
+               local ip = new_ip(s);
+               if proto then
+                       assert_equal(ip and ip.proto, proto, "protocol is correct for "..("%q"):format(s));
+               else
+                       assert_equal(ip, nil, "address is invalid");
+               end
+       end
+       assert_proto("127.0.0.1", v4);
+       assert_proto("::1", v6);
+       assert_proto("", nil);
+       assert_proto("abc", nil);
+       assert_proto("   ", nil);
+end
+
+function commonPrefixLength(cpl, _M)
+       local new_ip = _M.new_ip;
+       local function assert_cpl6(a, b, len, v4)
+               local ipa, ipb = new_ip(a), new_ip(b);
+               if v4 then len = len+96; end
+               assert_equal(cpl(ipa, ipb), len, "common prefix length of "..a.." and "..b.." is "..len);
+               assert_equal(cpl(ipb, ipa), len, "common prefix length of "..b.." and "..a.." is "..len);
+       end
+       local function assert_cpl4(a, b, len)
+               return assert_cpl6(a, b, len, "IPv4");
+       end
+       assert_cpl4("0.0.0.0", "0.0.0.0", 32);
+       assert_cpl4("255.255.255.255", "0.0.0.0", 0);
+       assert_cpl4("255.255.255.255", "255.255.0.0", 16);
+       assert_cpl4("255.255.255.255", "255.255.255.255", 32);
+       assert_cpl4("255.255.255.255", "255.255.255.255", 32);
+
+       assert_cpl6("::1", "::1", 128);
+       assert_cpl6("abcd::1", "abcd::1", 128);
+       assert_cpl6("abcd::abcd", "abcd::", 112);
+       assert_cpl6("abcd::abcd", "abcd::abcd:abcd", 96);
+end
diff --git a/tests/test_util_rfc3484.lua b/tests/test_util_rfc3484.lua
deleted file mode 100644 (file)
index 18ae310..0000000
+++ /dev/null
@@ -1,51 +0,0 @@
--- Prosody IM
--- Copyright (C) 2011 Florian Zeitz
---
--- This project is MIT/X11 licensed. Please see the
--- COPYING file in the source package for more information.
---
-
-function source(source)
-       local new_ip = require"util.ip".new_ip;
-       assert_equal(source(new_ip("2001::1", "IPv6"), {new_ip("3ffe::1", "IPv6"), new_ip("fe80::1", "IPv6")}).addr, "3ffe::1", "prefer appropriate scope");
-       assert_equal(source(new_ip("2001::1", "IPv6"), {new_ip("fe80::1", "IPv6"), new_ip("fec0::1", "IPv6")}).addr, "fec0::1", "prefer appropriate scope");
-       assert_equal(source(new_ip("fec0::1", "IPv6"), {new_ip("fe80::1", "IPv6"), new_ip("2001::1", "IPv6")}).addr, "2001::1", "prefer appropriate scope");
-       assert_equal(source(new_ip("ff05::1", "IPv6"), {new_ip("fe80::1", "IPv6"), new_ip("fec0::1", "IPv6"), new_ip("2001::1", "IPv6")}).addr, "fec0::1", "prefer appropriate scope");
-       assert_equal(source(new_ip("2001::1", "IPv6"), {new_ip("2001::1", "IPv6"), new_ip("2002::1", "IPv6")}).addr, "2001::1", "prefer same address");
-       assert_equal(source(new_ip("fec0::1", "IPv6"), {new_ip("fec0::2", "IPv6"), new_ip("2001::1", "IPv6")}).addr, "fec0::2", "prefer appropriate scope");
-       assert_equal(source(new_ip("2001::1", "IPv6"), {new_ip("2001::2", "IPv6"), new_ip("3ffe::2", "IPv6")}).addr, "2001::2", "longest matching prefix");
-       assert_equal(source(new_ip("2002:836b:2179::1", "IPv6"), {new_ip("2002:836b:2179::d5e3:7953:13eb:22e8", "IPv6"), new_ip("2001::2", "IPv6")}).addr, "2002:836b:2179::d5e3:7953:13eb:22e8", "prefer matching label");
-end
-
-function destination(dest)
-       local order;
-       local new_ip = require"util.ip".new_ip;
-       order = dest({new_ip("2001::1", "IPv6"), new_ip("131.107.65.121", "IPv4")}, {new_ip("2001::2", "IPv6"), new_ip("fe80::1", "IPv6"), new_ip("169.254.13.78", "IPv4")})
-       assert_equal(order[1].addr, "2001::1", "prefer matching scope");
-       assert_equal(order[2].addr, "131.107.65.121", "prefer matching scope")
-
-       order = dest({new_ip("2001::1", "IPv6"), new_ip("131.107.65.121", "IPv4")}, {new_ip("fe80::1", "IPv6"), new_ip("131.107.65.117", "IPv4")})
-       assert_equal(order[1].addr, "131.107.65.121", "prefer matching scope")
-       assert_equal(order[2].addr, "2001::1", "prefer matching scope")
-
-       order = dest({new_ip("2001::1", "IPv6"), new_ip("10.1.2.3", "IPv4")}, {new_ip("2001::2", "IPv6"), new_ip("fe80::1", "IPv6"), new_ip("10.1.2.4", "IPv4")})
-       assert_equal(order[1].addr, "2001::1", "prefer higher precedence");
-       assert_equal(order[2].addr, "10.1.2.3", "prefer higher precedence");
-
-       order = dest({new_ip("2001::1", "IPv6"), new_ip("fec0::1", "IPv6"), new_ip("fe80::1", "IPv6")}, {new_ip("2001::2", "IPv6"), new_ip("fec0::1", "IPv6"), new_ip("fe80::2", "IPv6")})
-       assert_equal(order[1].addr, "fe80::1", "prefer smaller scope");
-       assert_equal(order[2].addr, "fec0::1", "prefer smaller scope");
-       assert_equal(order[3].addr, "2001::1", "prefer smaller scope");
-
-       order = dest({new_ip("2001::1", "IPv6"), new_ip("3ffe::1", "IPv6")}, {new_ip("2001::2", "IPv6"), new_ip("3f44::2", "IPv6"), new_ip("fe80::2", "IPv6")})
-       assert_equal(order[1].addr, "2001::1", "longest matching prefix");
-       assert_equal(order[2].addr, "3ffe::1", "longest matching prefix");
-
-       order = dest({new_ip("2002:836b:4179::1", "IPv6"), new_ip("2001::1", "IPv6")}, {new_ip("2002:836b:4179::2", "IPv6"), new_ip("fe80::2", "IPv6")})
-       assert_equal(order[1].addr, "2002:836b:4179::1", "prefer matching label");
-       assert_equal(order[2].addr, "2001::1", "prefer matching label");
-
-       order = dest({new_ip("2002:836b:4179::1", "IPv6"), new_ip("2001::1", "IPv6")}, {new_ip("2002:836b:4179::2", "IPv6"), new_ip("2001::2", "IPv6"), new_ip("fe80::2", "IPv6")})
-       assert_equal(order[1].addr, "2001::1", "prefer higher precedence");
-       assert_equal(order[2].addr, "2002:836b:4179::1", "prefer higher precedence");
-end
diff --git a/tests/test_util_rfc6724.lua b/tests/test_util_rfc6724.lua
new file mode 100644 (file)
index 0000000..bb73e92
--- /dev/null
@@ -0,0 +1,97 @@
+-- Prosody IM
+-- Copyright (C) 2011-2013 Florian Zeitz
+--
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+function source(source)
+       local new_ip = require"util.ip".new_ip;
+       assert_equal(source(new_ip("2001:db8:1::1", "IPv6"),
+                       {new_ip("2001:db8:3::1", "IPv6"), new_ip("fe80::1", "IPv6")}).addr,
+               "2001:db8:3::1",
+               "prefer appropriate scope");
+       assert_equal(source(new_ip("ff05::1", "IPv6"),
+                       {new_ip("2001:db8:3::1", "IPv6"), new_ip("fe80::1", "IPv6")}).addr,
+               "2001:db8:3::1",
+               "prefer appropriate scope");
+       assert_equal(source(new_ip("2001:db8:1::1", "IPv6"),
+                       {new_ip("2001:db8:1::1", "IPv6"), new_ip("2001:db8:2::1", "IPv6")}).addr,
+               "2001:db8:1::1",
+               "prefer same address"); -- "2001:db8:1::1" should be marked "deprecated" here, we don't handle that right now
+       assert_equal(source(new_ip("fe80::1", "IPv6"),
+                       {new_ip("fe80::2", "IPv6"), new_ip("2001:db8:1::1", "IPv6")}).addr,
+               "fe80::2",
+               "prefer appropriate scope"); -- "fe80::2" should be marked "deprecated" here, we don't handle that right now
+       assert_equal(source(new_ip("2001:db8:1::1", "IPv6"),
+                       {new_ip("2001:db8:1::2", "IPv6"), new_ip("2001:db8:3::2", "IPv6")}).addr,
+               "2001:db8:1::2",
+               "longest matching prefix");
+--[[ "2001:db8:1::2" should be a care-of address and "2001:db8:3::2" a home address, we can't handle this and would fail
+       assert_equal(source(new_ip("2001:db8:1::1", "IPv6"),
+                       {new_ip("2001:db8:1::2", "IPv6"), new_ip("2001:db8:3::2", "IPv6")}).addr,
+               "2001:db8:3::2",
+               "prefer home address");
+]]
+       assert_equal(source(new_ip("2002:c633:6401::1", "IPv6"),
+                       {new_ip("2002:c633:6401::d5e3:7953:13eb:22e8", "IPv6"), new_ip("2001:db8:1::2", "IPv6")}).addr,
+               "2002:c633:6401::d5e3:7953:13eb:22e8",
+               "prefer matching label"); -- "2002:c633:6401::d5e3:7953:13eb:22e8" should be marked "temporary" here, we don't handle that right now
+       assert_equal(source(new_ip("2001:db8:1::d5e3:0:0:1", "IPv6"),
+                       {new_ip("2001:db8:1::2", "IPv6"), new_ip("2001:db8:1::d5e3:7953:13eb:22e8", "IPv6")}).addr,
+               "2001:db8:1::d5e3:7953:13eb:22e8",
+               "prefer temporary address") -- "2001:db8:1::2" should be marked "public" and "2001:db8:1::d5e3:7953:13eb:22e8" should be marked "temporary" here, we don't handle that right now
+end
+
+function destination(dest)
+       local order;
+       local new_ip = require"util.ip".new_ip;
+       order = dest({new_ip("2001:db8:1::1", "IPv6"), new_ip("198.51.100.121", "IPv4")},
+               {new_ip("2001:db8:1::2", "IPv6"), new_ip("fe80::1", "IPv6"), new_ip("169.254.13.78", "IPv4")})
+       assert_equal(order[1].addr, "2001:db8:1::1", "prefer matching scope");
+       assert_equal(order[2].addr, "198.51.100.121", "prefer matching scope");
+
+       order = dest({new_ip("2001:db8:1::1", "IPv6"), new_ip("198.51.100.121", "IPv4")},
+               {new_ip("fe80::1", "IPv6"), new_ip("198.51.100.117", "IPv4")})
+       assert_equal(order[1].addr, "198.51.100.121", "prefer matching scope");
+       assert_equal(order[2].addr, "2001:db8:1::1", "prefer matching scope");
+
+       order = dest({new_ip("2001:db8:1::1", "IPv6"), new_ip("10.1.2.3", "IPv4")},
+               {new_ip("2001:db8:1::2", "IPv6"), new_ip("fe80::1", "IPv6"), new_ip("10.1.2.4", "IPv4")})
+       assert_equal(order[1].addr, "2001:db8:1::1", "prefer higher precedence");
+       assert_equal(order[2].addr, "10.1.2.3", "prefer higher precedence");
+
+       order = dest({new_ip("2001:db8:1::1", "IPv6"), new_ip("fe80::1", "IPv6")},
+               {new_ip("2001:db8:1::2", "IPv6"), new_ip("fe80::2", "IPv6")})
+       assert_equal(order[1].addr, "fe80::1", "prefer smaller scope");
+       assert_equal(order[2].addr, "2001:db8:1::1", "prefer smaller scope");
+
+--[[ "2001:db8:1::2" and "fe80::2" should be marked "care-of address", while "2001:db8:3::1" should be marked "home address", we can't currently handle this and would fail the test
+       order = dest({new_ip("2001:db8:1::1", "IPv6"), new_ip("fe80::1", "IPv6")},
+               {new_ip("2001:db8:1::2", "IPv6"), new_ip("2001:db8:3::1", "IPv6"), new_ip("fe80::2", "IPv6")})
+       assert_equal(order[1].addr, "2001:db8:1::1", "prefer home address");
+       assert_equal(order[2].addr, "fe80::1", "prefer home address");
+]]
+
+--[[ "fe80::2" should be marked "deprecated", we can't currently handle this and would fail the test
+       order = dest({new_ip("2001:db8:1::1", "IPv6"), new_ip("fe80::1", "IPv6")},
+               {new_ip("2001:db8:1::2", "IPv6"), new_ip("fe80::2", "IPv6")})
+       assert_equal(order[1].addr, "2001:db8:1::1", "avoid deprecated addresses");
+       assert_equal(order[2].addr, "fe80::1", "avoid deprecated addresses");
+]]
+
+       order = dest({new_ip("2001:db8:1::1", "IPv6"), new_ip("2001:db8:3ffe::1", "IPv6")},
+               {new_ip("2001:db8:1::2", "IPv6"), new_ip("2001:db8:3f44::2", "IPv6"), new_ip("fe80::2", "IPv6")})
+       assert_equal(order[1].addr, "2001:db8:1::1", "longest matching prefix");
+       assert_equal(order[2].addr, "2001:db8:3ffe::1", "longest matching prefix");
+
+       order = dest({new_ip("2002:c633:6401::1", "IPv6"), new_ip("2001:db8:1::1", "IPv6")},
+               {new_ip("2002:c633:6401::2", "IPv6"), new_ip("fe80::2", "IPv6")})
+       assert_equal(order[1].addr, "2002:c633:6401::1", "prefer matching label");
+       assert_equal(order[2].addr, "2001:db8:1::1", "prefer matching label");
+
+       order = dest({new_ip("2002:c633:6401::1", "IPv6"), new_ip("2001:db8:1::1", "IPv6")},
+               {new_ip("2002:c633:6401::2", "IPv6"), new_ip("2001:db8:1::2", "IPv6"), new_ip("fe80::2", "IPv6")})
+       assert_equal(order[1].addr, "2001:db8:1::1", "prefer higher precedence");
+       assert_equal(order[2].addr, "2002:c633:6401::1", "prefer higher precedence");
+end
index c11e41d9e602676fba2f935600ad2b71f4b917ba..ff3004c273e9f32caa012f0d961efc49af93aafe 100755 (executable)
 
 package.path = package.path ..";../?.lua";
 
-if arg[0]:match("[/\\]") then
-       package.path = package.path .. ";"..arg[0]:gsub("[^/\\]*$", "?.lua");
+local my_name = arg[0];
+if my_name:match("[/\\]") then
+       package.path = package.path..";"..my_name:gsub("[^/\\]+$", "../?.lua");
+       package.path = package.path..";"..my_name:gsub("[^/\\]+$", "?.lua");
+       package.cpath = package.cpath..";"..my_name:gsub("[^/\\]+$", "../?.so");
 end
 
 local erlparse = require "erlparse";
@@ -229,10 +232,10 @@ local help = "/? -? ? /h -h /help -help --help";
 if not arg or help:find(arg, 1, true) then
        print([[ejabberd db dump importer for Prosody
 
-  Usage: ejabberd2prosody.lua filename.txt
+  Usage: ]]..my_name..[[ filename.txt
 
 The file can be generated from ejabberd using:
-  sudo ./bin/ejabberdctl dump filename.txt
+  sudo ejabberdctl dump filename.txt
 
 Note: The path of ejabberdctl depends on your ejabberd installation, and ejabberd needs to be running for ejabberdctl to work.]]);
        os.exit(1);
index 4372064386c37ba8d0494faf0fca6037fb3ff7b1..d80b9e469831098efe33bea5f4f4a6430e843dfc 100644 (file)
 prosody = {};
 
 package.path = package.path ..";../?.lua";
+
+local my_name = arg[0];
+if my_name:match("[/\\]") then
+       package.path = package.path..";"..my_name:gsub("[^/\\]+$", "../?.lua");
+       package.cpath = package.cpath..";"..my_name:gsub("[^/\\]+$", "../?.so");
+end
+
+
 local serialize = require "util.serialization".serialize;
 local st = require "util.stanza";
 local parse_xml = require "util.xml".parse;
index b85d2c20c96ec29aec67187e90b88735cc5a262c..d6a6753f0c30f6b52dd654a5690645b64bf9ce46 100644 (file)
@@ -428,7 +428,14 @@ end
 end
 
 -- import modules
-package.path = [[C:\Documents and Settings\Waqas\Desktop\mercurial\prosody-hg\?.lua;]]..package.path;
+package.path = package.path.."..\?.lua;";
+
+local my_name = arg[0];
+if my_name:match("[/\\]") then
+       package.path = package.path..";"..my_name:gsub("[^/\\]+$", "../?.lua");
+       package.cpath = package.cpath..";"..my_name:gsub("[^/\\]+$", "../?.so");
+end
+
 
 -- ugly workaround for getting datamanager to work outside of prosody :(
 prosody = { };
index bdea9a6301b271b3a390276f91e943b0cf24ff32..5ef47602a9b3b7254ef82accbbb7e3fc74ba76ee 100644 (file)
@@ -9,6 +9,12 @@
 package.path = package.path..";../?.lua";
 package.cpath = package.cpath..";../?.so"; -- needed for util.pposix used in datamanager
 
+local my_name = arg[0];
+if my_name:match("[/\\]") then
+       package.path = package.path..";"..my_name:gsub("[^/\\]+$", "../?.lua");
+       package.cpath = package.cpath..";"..my_name:gsub("[^/\\]+$", "../?.so");
+end
+
 -- ugly workaround for getting datamanager to work outside of prosody :(
 prosody = { };
 prosody.platform = "unknown";
index b5156f45f8193085b4ff5334ea747c1d18e4e510..0862b0c14ea6a536e65f00114c8fabe00223b51f 100755 (executable)
 package.path = package.path..";../?.lua";
 package.cpath = package.cpath..";../?.so"; -- needed for util.pposix used in datamanager
 
+local my_name = arg[0];
+if my_name:match("[/\\]") then
+       package.path = package.path..";"..my_name:gsub("[^/\\]+$", "../?.lua");
+       package.cpath = package.cpath..";"..my_name:gsub("[^/\\]+$", "../?.so");
+end
+
 -- ugly workaround for getting datamanager to work outside of prosody :(
 prosody = { };
 prosody.platform = "unknown";
index 856bf03498261860db5796afccac66258c33beae..62649c9b8567d6afe1aacc85d0aa91b7948245db 100644 (file)
@@ -12,7 +12,17 @@ local ip_mt = { __index = function (ip, key) return (ip_methods[key])(ip); end,
 local hex2bits = { ["0"] = "0000", ["1"] = "0001", ["2"] = "0010", ["3"] = "0011", ["4"] = "0100", ["5"] = "0101", ["6"] = "0110", ["7"] = "0111", ["8"] = "1000", ["9"] = "1001", ["A"] = "1010", ["B"] = "1011", ["C"] = "1100", ["D"] = "1101", ["E"] = "1110", ["F"] = "1111" };
 
 local function new_ip(ipStr, proto)
-       if proto ~= "IPv4" and proto ~= "IPv6" then
+       if not proto then
+               local sep = ipStr:match("^%x+(.)");
+               if sep == ":" or (not(sep) and ipStr:sub(1,1) == ":") then
+                       proto = "IPv6"
+               elseif sep == "." then
+                       proto = "IPv4"
+               end
+               if not proto then
+                       return nil, "invalid address";
+               end
+       elseif proto ~= "IPv4" and proto ~= "IPv6" then
                return nil, "invalid protocol";
        end
        if proto == "IPv6" and ipStr:find('.', 1, true) then
@@ -192,5 +202,43 @@ function ip_methods:scope()
        return value;
 end
 
+function ip_methods:private()
+       local private = self.scope ~= 0xE;
+       if not private and self.proto == "IPv4" then
+               local ip = self.addr;
+               local fields = {};
+               ip:gsub("([^.]*).?", function (c) fields[#fields + 1] = tonumber(c) end);
+               if fields[1] == 127 or fields[1] == 10 or (fields[1] == 192 and fields[2] == 168)
+               or (fields[1] == 172 and (fields[2] >= 16 or fields[2] <= 32)) then
+                       private = true;
+               end
+       end
+       self.private = private;
+       return private;
+end
+
+local function parse_cidr(cidr)
+       local bits;
+       local ip_len = cidr:find("/", 1, true);
+       if ip_len then
+               bits = tonumber(cidr:sub(ip_len+1, -1));
+               cidr = cidr:sub(1, ip_len-1);
+       end
+       return new_ip(cidr), bits;
+end
+
+local function match(ipA, ipB, bits)
+       local common_bits = commonPrefixLength(ipA, ipB);
+       if not bits then
+               return ipA == ipB;
+       end
+       if bits and ipB.proto == "IPv4" then
+               common_bits = common_bits - 96; -- v6 mapped addresses always share these bits
+       end
+       return common_bits >= bits;
+end
+
 return {new_ip = new_ip,
-       commonPrefixLength = commonPrefixLength};
+       commonPrefixLength = commonPrefixLength,
+       parse_cidr = parse_cidr,
+       match=match};
index 1f6aacb81a4d36e77ba23429ff2001f6cdf14ad3..4b429163bfbdbcf963cf0d267c1d3da7b1015ac7 100644 (file)
 
 local it = {};
 
+local t_insert = table.insert;
+local select, unpack, next = select, unpack, next;
+local function pack(...) return { n = select("#", ...), ... }; end
+
 -- Reverse an iterator
 function it.reverse(f, s, var)
        local results = {};
@@ -19,7 +23,7 @@ function it.reverse(f, s, var)
                local ret = { f(s, var) };
                var = ret[1];
                if var == nil then break; end
-               table.insert(results, 1, ret);
+               t_insert(results, 1, ret);
        end
        
        -- Then return our reverse one
@@ -55,12 +59,12 @@ function it.unique(f, s, var)
        
        return function ()
                while true do
-                       local ret = { f(s, var) };
+                       local ret = pack(f(s, var));
                        var = ret[1];
                        if var == nil then break; end
                        if not set[var] then
                                set[var] = true;
-                               return var;
+                               return unpack(ret, 1, ret.n);
                        end
                end
        end;
@@ -71,8 +75,7 @@ function it.count(f, s, var)
        local x = 0;
        
        while true do
-               local ret = { f(s, var) };
-               var = ret[1];
+               var = f(s, var);
                if var == nil then break; end
                x = x + 1;
        end
@@ -104,7 +107,7 @@ end
 function it.tail(n, f, s, var)
        local results, count = {}, 0;
        while true do
-               local ret = { f(s, var) };
+               local ret = pack(f(s, var));
                var = ret[1];
                if var == nil then break; end
                results[(count%n)+1] = ret;
@@ -117,9 +120,24 @@ function it.tail(n, f, s, var)
        return function ()
                pos = pos + 1;
                if pos > n then return nil; end
-               return unpack(results[((count-1+pos)%n)+1]);
+               local ret = results[((count-1+pos)%n)+1];
+               return unpack(ret, 1, ret.n);
        end
-       --return reverse(head(n, reverse(f, s, var)));
+       --return reverse(head(n, reverse(f, s, var))); -- !
+end
+
+function it.filter(filter, f, s, var)
+       if type(filter) ~= "function" then
+               local filter_value = filter;
+               function filter(x) return x ~= filter_value; end
+       end
+       return function (s, var)
+               local ret;
+               repeat ret = pack(f(s, var));
+                       var = ret[1];
+               until var == nil or filter(unpack(ret, 1, ret.n));
+               return unpack(ret, 1, ret.n);
+       end, s, var;
 end
 
 local function _ripairs_iter(t, key) if key > 1 then return key-1, t[key-1]; end end
@@ -139,7 +157,7 @@ function it.to_array(f, s, var)
        while true do
                var = f(s, var);
                if var == nil then break; end
-               table.insert(t, var);
+               t_insert(t, var);
        end
        return t;
 end
index afb3861b08f90d70db33d9657da4df77efe43036..d0da943556f8bd1c18dcc1bf753c2233c5399eec 100644 (file)
@@ -92,5 +92,6 @@ 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);
+require "util.sasl.external"  .init(registerMechanism);
 
 return _M;
diff --git a/util/sasl/external.lua b/util/sasl/external.lua
new file mode 100644 (file)
index 0000000..4c5c434
--- /dev/null
@@ -0,0 +1,25 @@
+local saslprep = require "util.encodings".stringprep.saslprep;
+
+module "sasl.external"
+
+local function external(self, message)
+       message = saslprep(message);
+       local state
+       self.username, state = self.profile.external(message);
+
+       if state == false then
+               return "failure", "account-disabled";
+       elseif state == nil  then
+               return "failure", "not-authorized";
+       elseif state == "expired" then
+               return "false", "credentials-expired";
+       end
+
+       return "success";
+end
+
+function init(registerMechanism)
+       registerMechanism("EXTERNAL", {"external"}, external);
+end
+
+return _M;