aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/portmanager.lua4
-rw-r--r--core/s2smanager.lua12
-rw-r--r--core/sessionmanager.lua6
-rw-r--r--core/stanza_router.lua2
-rw-r--r--net/adns.lua10
-rw-r--r--net/connect.lua2
-rw-r--r--net/http.lua4
-rw-r--r--net/http/files.lua2
-rw-r--r--net/resolvers/service.lua6
-rw-r--r--net/server_epoll.lua92
-rw-r--r--net/websocket.lua6
-rw-r--r--plugins/mod_admin_telnet.lua69
-rw-r--r--plugins/mod_blocklist.lua2
-rw-r--r--plugins/mod_bosh.lua16
-rw-r--r--plugins/mod_c2s.lua4
-rw-r--r--plugins/mod_component.lua14
-rw-r--r--plugins/mod_groups.lua6
-rw-r--r--plugins/mod_limits.lua22
-rw-r--r--plugins/mod_mam/mod_mam.lua24
-rw-r--r--plugins/mod_muc_mam.lua10
-rw-r--r--plugins/mod_pep.lua2
-rw-r--r--plugins/mod_pep_simple.lua4
-rw-r--r--plugins/mod_proxy65.lua2
-rw-r--r--plugins/mod_pubsub/mod_pubsub.lua2
-rw-r--r--plugins/mod_pubsub/pubsub.lib.lua21
-rw-r--r--plugins/mod_s2s/mod_s2s.lua56
-rw-r--r--plugins/mod_s2s/s2sout.lib.lua349
-rw-r--r--plugins/mod_saslauth.lua3
-rw-r--r--plugins/mod_stanza_debug.lua5
-rw-r--r--plugins/mod_vcard_legacy.lua13
-rw-r--r--plugins/mod_websocket.lua3
-rw-r--r--plugins/muc/language.lib.lua1
-rwxr-xr-xprosodyctl10
-rw-r--r--spec/util_array_spec.lua154
-rw-r--r--spec/util_error_spec.lua68
-rw-r--r--util-src/poll.c2
-rw-r--r--util/error.lua2
-rw-r--r--util/serialization.lua7
-rw-r--r--util/session.lua4
-rw-r--r--util/sql.lua8
-rw-r--r--util/stanza.lua6
-rw-r--r--util/startup.lua4
-rw-r--r--util/xmppstream.lua6
43 files changed, 526 insertions, 519 deletions
diff --git a/core/portmanager.lua b/core/portmanager.lua
index 9eb40abf..55868c34 100644
--- a/core/portmanager.lua
+++ b/core/portmanager.lua
@@ -9,7 +9,7 @@ local set = require "util.set";
local table = table;
local setmetatable, rawset, rawget = setmetatable, rawset, rawget;
-local type, tonumber, tostring, ipairs = type, tonumber, tostring, ipairs;
+local type, tonumber, ipairs = type, tonumber, ipairs;
local pairs = pairs;
local prosody = prosody;
@@ -103,7 +103,7 @@ local function activate(service_name)
for port in bind_ports do
local port_number = tonumber(port);
if not port_number then
- log("error", "Invalid port number specified for service '%s': %s", service_info.name, tostring(port));
+ log("error", "Invalid port number specified for service '%s': %s", service_info.name, port);
elseif #active_services:search(nil, interface, port_number) > 0 then
log("error", "Multiple services configured to listen on the same port ([%s]:%d): %s, %s", interface, port,
active_services:search(nil, interface, port)[1][1].service.name or "<unnamed>", service_name or "<unnamed>");
diff --git a/core/s2smanager.lua b/core/s2smanager.lua
index 684bb94e..ccdf4932 100644
--- a/core/s2smanager.lua
+++ b/core/s2smanager.lua
@@ -9,8 +9,7 @@
local hosts = prosody.hosts;
-local tostring, pairs, setmetatable
- = tostring, pairs, setmetatable;
+local pairs, setmetatable = pairs, setmetatable;
local logger_init = require "util.logger".init;
local sessionlib = require "util.session";
@@ -75,8 +74,8 @@ local function retire_session(session, reason)
session.destruction_reason = reason;
- function session.send(data) log("debug", "Discarding data sent to resting session: %s", tostring(data)); end
- function session.data(data) log("debug", "Discarding data received from resting session: %s", tostring(data)); end
+ function session.send(data) log("debug", "Discarding data sent to resting session: %s", data); end
+ function session.data(data) log("debug", "Discarding data received from resting session: %s", data); end
session.thread = { run = function (_, data) return session.data(data) end };
session.sends2s = session.send;
return setmetatable(session, resting_session);
@@ -84,9 +83,8 @@ end
local function destroy_session(session, reason)
if session.destroyed then return; end
- (session.log or log)("debug", "Destroying "..tostring(session.direction)
- .." session "..tostring(session.from_host).."->"..tostring(session.to_host)
- ..(reason and (": "..reason) or ""));
+ local log = session.log or log;
+ log("debug", "Destroying %s session %s->%s%s%s", session.direction, session.from_host, session.to_host, reason and ": " or "", reason or "");
if session.direction == "outgoing" then
hosts[session.from_host].s2sout[session.to_host] = nil;
diff --git a/core/sessionmanager.lua b/core/sessionmanager.lua
index 55f096b9..29779c3c 100644
--- a/core/sessionmanager.lua
+++ b/core/sessionmanager.lua
@@ -44,7 +44,7 @@ local function new_session(conn)
if t then
local ret, err = w(conn, t);
if not ret then
- session.log("debug", "Error writing to connection: %s", tostring(err));
+ session.log("debug", "Error writing to connection: %s", err);
return false, err;
end
end
@@ -85,8 +85,8 @@ local function retire_session(session)
end
end
- function session.send(data) log("debug", "Discarding data sent to resting session: %s", tostring(data)); return false; end
- function session.data(data) log("debug", "Discarding data received from resting session: %s", tostring(data)); end
+ function session.send(data) log("debug", "Discarding data sent to resting session: %s", data); return false; end
+ function session.data(data) log("debug", "Discarding data received from resting session: %s", data); end
session.thread = { run = function (_, data) return session.data(data) end };
return setmetatable(session, resting_session);
end
diff --git a/core/stanza_router.lua b/core/stanza_router.lua
index f5a34f59..d3caeb5d 100644
--- a/core/stanza_router.lua
+++ b/core/stanza_router.lua
@@ -199,7 +199,7 @@ function core_route_stanza(origin, stanza)
else
local host_session = hosts[from_host];
if not host_session then
- log("error", "No hosts[from_host] (please report): %s", tostring(stanza));
+ log("error", "No hosts[from_host] (please report): %s", stanza);
else
local xmlns = stanza.attr.xmlns;
stanza.attr.xmlns = nil;
diff --git a/net/adns.lua b/net/adns.lua
index 4fa01f8a..d7266958 100644
--- a/net/adns.lua
+++ b/net/adns.lua
@@ -11,7 +11,7 @@ local new_resolver = require "net.dns".resolver;
local log = require "util.logger".init("adns");
-local coroutine, tostring, pcall = coroutine, tostring, pcall;
+local coroutine, pcall = coroutine, pcall;
local setmetatable = setmetatable;
local function dummy_send(sock, data, i, j) return (j-i)+1; end -- luacheck: ignore 212
@@ -73,11 +73,11 @@ function async_resolver_methods:lookup(handler, qname, qtype, qclass)
handler(peek);
return;
end
- log("debug", "Records for %s not in cache, sending query (%s)...", qname, tostring(coroutine.running()));
+ log("debug", "Records for %s not in cache, sending query (%s)...", qname, coroutine.running());
local ok, err = resolver:query(qname, qtype, qclass);
if ok then
coroutine.yield(setmetatable({ resolver, qclass or "IN", qtype or "A", qname, coroutine.running()}, query_mt)); -- Wait for reply
- log("debug", "Reply for %s (%s)", qname, tostring(coroutine.running()));
+ log("debug", "Reply for %s (%s)", qname, coroutine.running());
end
if ok then
ok, err = pcall(handler, resolver:peek(qname, qtype, qclass));
@@ -86,13 +86,13 @@ function async_resolver_methods:lookup(handler, qname, qtype, qclass)
ok, err = pcall(handler, nil, err);
end
if not ok then
- log("error", "Error in DNS response handler: %s", tostring(err));
+ log("error", "Error in DNS response handler: %s", err);
end
end)(resolver:peek(qname, qtype, qclass));
end
function query_methods:cancel(call_handler, reason) -- luacheck: ignore 212/reason
- log("warn", "Cancelling DNS lookup for %s", tostring(self[4]));
+ log("warn", "Cancelling DNS lookup for %s", self[4]);
self[1].cancel(self[2], self[3], self[4], self[5], call_handler);
end
diff --git a/net/connect.lua b/net/connect.lua
index b812ffcd..d4de6fb4 100644
--- a/net/connect.lua
+++ b/net/connect.lua
@@ -38,7 +38,7 @@ local function attempt_connection(p)
p:log("debug", "Next target to try is %s:%d", ip, port);
local conn, err = server.addclient(ip, port, pending_connection_listeners, p.options.pattern or "*a", p.options.sslctx, conn_type, extra);
if not conn then
- log("debug", "Connection attempt failed immediately: %s", tostring(err));
+ log("debug", "Connection attempt failed immediately: %s", err);
p.last_error = err or "unknown reason";
return attempt_connection(p);
end
diff --git a/net/http.lua b/net/http.lua
index fe5250ac..0e03fb3a 100644
--- a/net/http.lua
+++ b/net/http.lua
@@ -40,7 +40,7 @@ local listener = { default_port = 80, default_mode = "*a" };
local function handleerr(err) log("error", "Traceback[http]: %s", traceback(tostring(err), 2)); return err; end
local function log_if_failed(req, ret, ...)
if not ret then
- log("error", "Request '%s': error in callback: %s", req.id, tostring((...)));
+ log("error", "Request '%s': error in callback: %s", req.id, (...));
if not req.suppress_errors then
error(...);
end
@@ -150,7 +150,7 @@ function listener.onincoming(conn, data)
local request = requests[conn];
if not request then
- log("warn", "Received response from connection %s with no request attached!", tostring(conn));
+ log("warn", "Received response from connection %s with no request attached!", conn);
return;
end
diff --git a/net/http/files.lua b/net/http/files.lua
index 090b15c8..7ff81fc8 100644
--- a/net/http/files.lua
+++ b/net/http/files.lua
@@ -94,7 +94,7 @@ local function serve(opts)
if data and data.etag == etag then
response_headers.content_type = data.content_type;
data = data.data;
- cache:get(orig_path, data);
+ cache:set(orig_path, data);
elseif attr.mode == "directory" and path then
if full_path:sub(-1) ~= "/" then
local dir_path = { is_absolute = true, is_directory = true };
diff --git a/net/resolvers/service.lua b/net/resolvers/service.lua
index d1b8556c..62d269e5 100644
--- a/net/resolvers/service.lua
+++ b/net/resolvers/service.lua
@@ -33,7 +33,11 @@ function methods:next(cb)
-- Resolve DNS to target list
local dns_resolver = adns.resolver();
- dns_resolver:lookup(function (answer)
+ dns_resolver:lookup(function (answer, err)
+ if not answer and not err then
+ -- net.adns returns nil if there are zero records or nxdomain
+ answer = {};
+ end
if answer then
if #answer == 0 then
if self.extra and self.extra.default_port then
diff --git a/net/server_epoll.lua b/net/server_epoll.lua
index 5f62d931..49ad48ea 100644
--- a/net/server_epoll.lua
+++ b/net/server_epoll.lua
@@ -9,12 +9,12 @@
local t_insert = table.insert;
local t_concat = table.concat;
local setmetatable = setmetatable;
-local tostring = tostring;
local pcall = pcall;
local type = type;
local next = next;
local pairs = pairs;
-local log = require "util.logger".init("server_epoll");
+local logger = require "util.logger";
+local log = logger.init("server_epoll");
local socket = require "socket";
local luasec = require "ssl";
local gettime = require "util.time".now;
@@ -23,6 +23,7 @@ local createtable = require "util.table".create;
local inet = require "util.net";
local inet_pton = inet.pton;
local _SOCKETINVALID = socket._SOCKETINVALID or -1;
+local new_id = require "util.id".medium;
local poller = require "util.poll"
local EEXIST = poller.EEXIST;
@@ -61,6 +62,10 @@ local default_config = { __index = {
-- Maximum and minimum amount of time to sleep waiting for events (adjusted for pending timers)
max_wait = 86400;
min_wait = 1e-06;
+
+ -- EXPERIMENTAL
+ -- Whether to kill connections in case of callback errors.
+ fatal_errors = false;
}};
local cfg = default_config.__index;
@@ -141,6 +146,15 @@ function interface_mt:__tostring()
return ("FD %d"):format(self:getfd());
end
+interface.log = log;
+function interface:debug(msg, ...) --luacheck: ignore 212/self
+ self.log("debug", msg, ...);
+end
+
+function interface:error(msg, ...) --luacheck: ignore 212/self
+ self.log("error", msg, ...);
+end
+
-- Replace the listener and tell the old one
function interface:setlistener(listeners, data)
self:on("detach");
@@ -151,18 +165,23 @@ end
-- Call a listener callback
function interface:on(what, ...)
if not self.listeners then
- log("error", "%s has no listeners", self);
+ self:debug("Interface is missing listener callbacks");
return;
end
local listener = self.listeners["on"..what];
if not listener then
- -- log("debug", "Missing listener 'on%s'", what); -- uncomment for development and debugging
+ -- self:debug("Missing listener 'on%s'", what); -- uncomment for development and debugging
return;
end
local ok, err = pcall(listener, self, ...);
if not ok then
- log("error", "Error calling on%s: %s", what, err);
- return;
+ if cfg.fatal_errors then
+ self:debug("Closing due to error calling on%s: %s", what, err);
+ self:destroy();
+ else
+ self:debug("Error calling on%s: %s", what, err);
+ end
+ return nil, err;
end
return err;
end
@@ -273,15 +292,15 @@ function interface:add(r, w)
local ok, err, errno = poll:add(fd, r, w);
if not ok then
if errno == EEXIST then
- log("debug", "%s already registered!", self);
+ self:debug("FD already registered in poller! (EEXIST)");
return self:set(r, w); -- So try to change its flags
end
- log("error", "Could not register %s: %s(%d)", self, err, errno);
+ self:debug("Could not register in poller: %s(%d)", err, errno);
return ok, err;
end
self._wantread, self._wantwrite = r, w;
fds[fd] = self;
- log("debug", "Watching %s", self);
+ self:debug("Registered in poller");
return true;
end
@@ -294,7 +313,7 @@ function interface:set(r, w)
if w == nil then w = self._wantwrite; end
local ok, err, errno = poll:set(fd, r, w);
if not ok then
- log("error", "Could not update poller state %s: %s(%d)", self, err, errno);
+ self:debug("Could not update poller state: %s(%d)", err, errno);
return ok, err;
end
self._wantread, self._wantwrite = r, w;
@@ -311,12 +330,12 @@ function interface:del()
end
local ok, err, errno = poll:del(fd);
if not ok and errno ~= ENOENT then
- log("error", "Could not unregister %s: %s(%d)", self, err, errno);
+ self:debug("Could not unregister: %s(%d)", err, errno);
return ok, err;
end
self._wantread, self._wantwrite = nil, nil;
fds[fd] = nil;
- log("debug", "Unwatched %s", self);
+ self:debug("Unregistered from poller");
return true;
end
@@ -358,6 +377,14 @@ function interface:onreadable()
end
end
if not self.conn then return; end
+ if self._limit and (data or partial) then
+ local cost = self._limit * #(data or partial);
+ if cost > cfg.min_wait then
+ self:setreadtimeout(false);
+ self:pausefor(cost);
+ return;
+ end
+ end
if self._wantread and self.conn:dirty() then
self:setreadtimeout(false);
self:pausefor(cfg.read_retry_delay);
@@ -424,10 +451,10 @@ function interface:close()
if self.writebuffer and self.writebuffer[1] then
self:set(false, true); -- Flush final buffer contents
self.write, self.send = noop, noop; -- No more writing
- log("debug", "Close %s after writing", self);
+ self:debug("Close after writing");
self.ondrain = interface.close;
else
- log("debug", "Close %s now", self);
+ self:debug("Closing now");
self.write, self.send = noop, noop;
self.close = noop;
self:on("disconnect");
@@ -456,7 +483,7 @@ function interface:starttls(tls_ctx)
if tls_ctx then self.tls_ctx = tls_ctx; end
self.starttls = false;
if self.writebuffer and self.writebuffer[1] then
- log("debug", "Start TLS on %s after write", self);
+ self:debug("Start TLS after write");
self.ondrain = interface.starttls;
self:set(nil, true); -- make sure wantwrite is set
else
@@ -466,7 +493,7 @@ function interface:starttls(tls_ctx)
self.onwritable = interface.tlshandskake;
self.onreadable = interface.tlshandskake;
self:set(true, true);
- log("debug", "Prepare to start TLS on %s", self);
+ self:debug("Prepared to start TLS");
end
end
@@ -475,12 +502,12 @@ function interface:tlshandskake()
self:setreadtimeout(false);
if not self._tls then
self._tls = true;
- log("debug", "Start TLS on %s now", self);
+ self:debug("Starting TLS now");
self:del();
local ok, conn, err = pcall(luasec.wrap, self.conn, self.tls_ctx);
if not ok then
conn, err = ok, conn;
- log("error", "Failed to initialize TLS: %s", err);
+ self:debug("Failed to initialize TLS: %s", err);
end
if not conn then
self:on("disconnect", err);
@@ -504,22 +531,22 @@ function interface:tlshandskake()
end
local ok, err = self.conn:dohandshake();
if ok then
- log("debug", "TLS handshake on %s complete", self);
+ self:debug("TLS handshake complete");
self.onwritable = nil;
self.onreadable = nil;
self:on("status", "ssl-handshake-complete");
self:setwritetimeout();
self:set(true, true);
elseif err == "wantread" then
- log("debug", "TLS handshake on %s to wait until readable", self);
+ self:debug("TLS handshake to wait until readable");
self:set(true, false);
self:setreadtimeout(cfg.ssl_handshake_timeout);
elseif err == "wantwrite" then
- log("debug", "TLS handshake on %s to wait until writable", self);
+ self:debug("TLS handshake to wait until writable");
self:set(false, true);
self:setwritetimeout(cfg.ssl_handshake_timeout);
else
- log("debug", "TLS handshake error on %s: %s", self, err);
+ self:debug("TLS handshake error: %s", err);
self:on("disconnect", err);
self:destroy();
end
@@ -536,6 +563,7 @@ local function wrapsocket(client, server, read_size, listeners, tls_ctx) -- luas
writebuffer = {};
tls_ctx = tls_ctx or (server and server.tls_ctx);
tls_direct = server and server.tls_direct;
+ log = logger.init(("conn%s"):format(new_id()));
}, interface_mt);
conn:updatenames();
@@ -559,12 +587,12 @@ end
function interface:onacceptable()
local conn, err = self.conn:accept();
if not conn then
- log("debug", "Error accepting new client: %s, server will be paused for %ds", err, cfg.accept_retry_interval);
+ self:debug("Error accepting new client: %s, server will be paused for %ds", err, cfg.accept_retry_interval);
self:pausefor(cfg.accept_retry_interval);
return;
end
local client = wrapsocket(conn, self, nil, self.listeners);
- log("debug", "New connection %s", tostring(client));
+ client:debug("New connection %s on server %s", client, self);
client:init();
if self.tls_direct then
client:starttls(self.tls_ctx);
@@ -589,6 +617,7 @@ end
-- Pause connection for some time
function interface:pausefor(t)
+ self:debug("Pause for %fs", t);
if self._pausefor then
self._pausefor:close();
end
@@ -603,6 +632,14 @@ function interface:pausefor(t)
end);
end
+function interface:setlimit(Bps)
+ if Bps > 0 then
+ self._limit = 1/Bps;
+ else
+ self._limit = nil;
+ end
+end
+
function interface:pause_writes()
self._write_lock = true;
self:setwritetimeout(false);
@@ -639,7 +676,9 @@ local function listen(addr, port, listeners, config)
hosts = config and config.sni_hosts;
sockname = addr;
sockport = port;
+ log = logger.init(("serv%s"):format(new_id()));
}, interface_mt);
+ server:debug("Server %s created", server);
server:add(true, false);
return server;
end
@@ -686,6 +725,7 @@ local function addclient(addr, port, listeners, read_size, tls_ctx, typ)
return nil, "invalid socket type";
end
local conn, err = create();
+ if not conn then return conn, err; end
local ok, err = conn:settimeout(0);
if not ok then return ok, err; end
local ok, err = conn:setpeername(addr, port);
@@ -696,6 +736,7 @@ local function addclient(addr, port, listeners, read_size, tls_ctx, typ)
if tls_ctx then
client:starttls(tls_ctx);
end
+ client:debug("Client %s created", client);
return client, conn;
end
@@ -714,6 +755,7 @@ local function watchfd(fd, onreadable, onwritable)
end;
-- Otherwise it'll need to be something LuaSocket-compatible
end
+ conn.log = logger.init(("fdwatch%s"):format(new_id()));
conn:add(onreadable, onwritable);
return conn;
end;
@@ -804,6 +846,7 @@ return {
-- libevent emulation
event = { EV_READ = "r", EV_WRITE = "w", EV_READWRITE = "rw", EV_LEAVE = -1 };
addevent = function (fd, mode, callback)
+ log("warn", "Using deprecated libevent emulation, please update code to use watchfd API instead");
local function onevent(self)
local ret = self:callback();
if ret == -1 then
@@ -823,6 +866,7 @@ return {
fds[fd] = nil;
end;
}, interface_mt);
+ conn.log = logger.init(("fdwatch%d"):format(conn:getfd()));
local ok, err = conn:add(mode == "r" or mode == "rw", mode == "w" or mode == "rw");
if not ok then return ok, err; end
return conn;
diff --git a/net/websocket.lua b/net/websocket.lua
index 469c6a58..fb16b8bb 100644
--- a/net/websocket.lua
+++ b/net/websocket.lua
@@ -113,7 +113,7 @@ function websocket_listeners.onincoming(conn, buffer, err) -- luacheck: ignore 2
frame.MASK = true; -- RFC 6455 6.1.5: If the data is being sent by the client, the frame(s) MUST be masked
conn:write(frames.build(frame));
elseif frame.opcode == 0xA then -- Pong frame
- log("debug", "Received unexpected pong frame: " .. tostring(frame.data));
+ log("debug", "Received unexpected pong frame: %s", frame.data);
else
return fail(s, 1002, "Reserved opcode");
end
@@ -131,7 +131,7 @@ end
function websocket_methods:close(code, reason)
if self.readyState < 2 then
code = code or 1000;
- log("debug", "closing WebSocket with code %i: %s" , code , tostring(reason));
+ log("debug", "closing WebSocket with code %i: %s" , code , reason);
self.readyState = 2;
local conn = self.conn;
conn:write(frames.build_close(code, reason, true));
@@ -245,7 +245,7 @@ local function connect(url, ex, listeners)
or (protocol and not protocol[r.headers["sec-websocket-protocol"]])
then
s.readyState = 3;
- log("warn", "WebSocket connection to %s failed: %s", url, tostring(b));
+ log("warn", "WebSocket connection to %s failed: %s", url, b);
if s.onerror then s:onerror("connecting-failed"); end
return;
end
diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua
index fa03840b..b6cdfe82 100644
--- a/plugins/mod_admin_telnet.lua
+++ b/plugins/mod_admin_telnet.lua
@@ -62,6 +62,9 @@ end
function runner_callbacks:error(err)
module:log("error", "Traceback[telnet]: %s", err);
+
+ self.data.print("Fatal error while running command, it did not complete");
+ self.data.print("Error: "..tostring(err));
end
@@ -114,6 +117,11 @@ function console:process_line(session, line)
session.env._ = line;
+ if not useglobalenv and commands[line:lower()] then
+ commands[line:lower()](session, line);
+ return;
+ end
+
local chunkname = "=console";
local env = (useglobalenv and redirect_output(_G, session)) or session.env or nil
local chunk, err = envload("return "..line, chunkname, env);
@@ -128,18 +136,7 @@ function console:process_line(session, line)
end
end
- local ranok, taskok, message = pcall(chunk);
-
- if not (ranok or message or useglobalenv) and commands[line:lower()] then
- commands[line:lower()](session, line);
- return;
- end
-
- if not ranok then
- session.print("Fatal error while running command, it did not complete");
- session.print("Error: "..taskok);
- return;
- end
+ local taskok, message = chunk();
if not message then
session.print("Result: "..tostring(taskok));
@@ -242,6 +239,7 @@ function commands.help(session, data)
print [[server - Uptime, version, shutting down, etc.]]
print [[port - Commands to manage ports the server is listening on]]
print [[dns - Commands to manage and inspect the internal DNS resolver]]
+ print [[xmpp - Commands for sending XMPP stanzas]]
print [[config - Reloading the configuration, etc.]]
print [[console - Help regarding the console itself]]
elseif section == "c2s" then
@@ -249,6 +247,7 @@ function commands.help(session, data)
print [[c2s:show_insecure() - Show all unencrypted client connections]]
print [[c2s:show_secure() - Show all encrypted client connections]]
print [[c2s:show_tls() - Show TLS cipher info for encrypted sessions]]
+ print [[c2s:count() - Count sessions without listing them]]
print [[c2s:close(jid) - Close all sessions for the specified JID]]
print [[c2s:closeall() - Close all active c2s connections ]]
elseif section == "s2s" then
@@ -284,6 +283,8 @@ function commands.help(session, data)
print [[dns:setnameserver(nameserver) - Replace the list of name servers with the supplied one]]
print [[dns:purge() - Clear the DNS cache]]
print [[dns:cache() - Show cached records]]
+ elseif section == "xmpp" then
+ print [[xmpp:ping(localhost, remotehost) -- Sends a ping to a remote XMPP server and reports the response]]
elseif section == "config" then
print [[config:reload() - Reload the server configuration. Modules may need to be reloaded for changes to take effect.]]
elseif section == "console" then
@@ -595,10 +596,16 @@ local function get_jid(session)
return jid_join("["..ip.."]:"..clientport, session.host or "["..serverip.."]:"..serverport);
end
-local function show_c2s(callback)
- local c2s = array.collect(values(module:shared"/*/c2s/sessions"));
+local function get_c2s()
+ local c2s = array.collect(values(prosody.full_sessions));
+ c2s:append(array.collect(values(module:shared"/*/c2s/sessions")));
c2s:append(array.collect(values(module:shared"/*/bosh/sessions")));
- c2s:sort(function(a, b)
+ c2s:unique();
+ return c2s;
+end
+
+local function show_c2s(callback)
+ get_c2s():sort(function(a, b)
if a.host == b.host then
if a.username == b.username then
return (a.resource or "") > (b.resource or "");
@@ -612,9 +619,8 @@ local function show_c2s(callback)
end
function def_env.c2s:count()
- local c2s_count = iterators.count(values(module:shared"/*/c2s/sessions"))
- local bosh_count = iterators.count(values(module:shared"/*/bosh/sessions"))
- return true, "Total: ".. c2s_count + bosh_count .." clients";
+ local c2s = get_c2s();
+ return true, "Total: ".. #c2s .." clients";
end
function def_env.c2s:show(match_jid, annotate)
@@ -660,23 +666,32 @@ function def_env.c2s:show_tls(match_jid)
return self:show(match_jid, tls_info);
end
-function def_env.c2s:close(match_jid)
+local function build_reason(text, condition)
+ if text or condition then
+ return {
+ text = text,
+ condition = condition or "undefined-condition",
+ };
+ end
+end
+
+function def_env.c2s:close(match_jid, text, condition)
local count = 0;
show_c2s(function (jid, session)
if jid == match_jid or jid_bare(jid) == match_jid then
count = count + 1;
- session:close();
+ session:close(build_reason(text, condition));
end
end);
return true, "Total: "..count.." sessions closed";
end
-function def_env.c2s:closeall()
+function def_env.c2s:closeall(text, condition)
local count = 0;
--luacheck: ignore 212/jid
show_c2s(function (jid, session)
count = count + 1;
- session:close();
+ session:close(build_reason(text, condition));
end);
return true, "Total: "..count.." sessions closed";
end
@@ -881,7 +896,7 @@ function def_env.s2s:showcert(domain)
.." presented by "..domain..".");
end
-function def_env.s2s:close(from, to)
+function def_env.s2s:close(from, to, text, condition)
local print, count = self.session.print, 0;
local s2s_sessions = module:shared"/*/s2s/sessions";
@@ -895,23 +910,23 @@ function def_env.s2s:close(from, to)
end
for _, session in pairs(s2s_sessions) do
- local id = session.type..tostring(session):match("[a-f0-9]+$");
+ local id = session.id or (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);
+ (session.close or s2smanager.destroy_session)(session, build_reason(text, condition));
count = count + 1 ;
end
end
return true, "Closed "..count.." s2s session"..((count == 1 and "") or "s");
end
-function def_env.s2s:closeall(host)
+function def_env.s2s:closeall(host, text, condition)
local count = 0;
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();
+ session:close(build_reason(text, condition));
count = count + 1;
end
end
diff --git a/plugins/mod_blocklist.lua b/plugins/mod_blocklist.lua
index cf8aad80..dad06b62 100644
--- a/plugins/mod_blocklist.lua
+++ b/plugins/mod_blocklist.lua
@@ -67,7 +67,7 @@ local function migrate_privacy_list(username)
if item.type == "jid" and item.action == "deny" then
local jid = jid_prep(item.value);
if not jid then
- module:log("warn", "Invalid JID in privacy store for user '%s' not migrated: %s", username, tostring(item.value));
+ module:log("warn", "Invalid JID in privacy store for user '%s' not migrated: %s", username, item.value);
else
migrated_data[jid] = true;
end
diff --git a/plugins/mod_bosh.lua b/plugins/mod_bosh.lua
index d4e980f2..f4d7eba2 100644
--- a/plugins/mod_bosh.lua
+++ b/plugins/mod_bosh.lua
@@ -78,7 +78,7 @@ end);
-- Used to respond to idle sessions (those with waiting requests)
function on_destroy_request(request)
- log("debug", "Request destroyed: %s", tostring(request));
+ log("debug", "Request destroyed: %s", request);
local session = sessions[request.context.sid];
if session then
local requests = session.requests;
@@ -115,7 +115,7 @@ function session_timeout(now, session, context, reason) -- luacheck: ignore 212/
end
function handle_POST(event)
- log("debug", "Handling new request %s: %s\n----------", tostring(event.request), tostring(event.request.body));
+ log("debug", "Handling new request %s: %s\n----------", event.request, event.request.body);
local request, response = event.request, event.response;
response.on_destroy = on_destroy_request;
@@ -224,7 +224,7 @@ local function bosh_reset_stream(session) session.notopen = true; end
local stream_xmlns_attr = { xmlns = "urn:ietf:params:xml:ns:xmpp-streams" };
local function bosh_close_stream(session, reason)
- (session.log or log)("info", "BOSH client disconnected: %s", tostring((reason and reason.condition or reason) or "session close"));
+ (session.log or log)("info", "BOSH client disconnected: %s", (reason and reason.condition or reason) or "session close");
local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate",
["xmlns:stream"] = xmlns_streams });
@@ -249,7 +249,7 @@ local function bosh_close_stream(session, reason)
close_reply = reason;
end
end
- log("info", "Disconnecting client, <stream:error> is: %s", tostring(close_reply));
+ log("info", "Disconnecting client, <stream:error> is: %s", close_reply);
end
local response_body = tostring(close_reply);
@@ -275,7 +275,7 @@ function stream_callbacks.streamopened(context, attr)
local to_host = nameprep(attr.to);
local wait = tonumber(attr.wait);
if not to_host then
- log("debug", "BOSH client tried to connect to invalid host: %s", tostring(attr.to));
+ log("debug", "BOSH client tried to connect to invalid host: %s", attr.to);
report_bad_host();
local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate",
["xmlns:stream"] = xmlns_streams, condition = "improper-addressing" });
@@ -283,7 +283,7 @@ function stream_callbacks.streamopened(context, attr)
return;
end
if not rid or (not attr.wait or not wait or wait < 0 or wait % 1 ~= 0) then
- log("debug", "BOSH client sent invalid rid or wait attributes: rid=%s, wait=%s", tostring(attr.rid), tostring(attr.wait));
+ log("debug", "BOSH client sent invalid rid or wait attributes: rid=%s, wait=%s", attr.rid, attr.wait);
local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate",
["xmlns:stream"] = xmlns_streams, condition = "bad-request" });
response:send(tostring(close_reply));
@@ -329,7 +329,7 @@ function stream_callbacks.streamopened(context, attr)
s.attr.xmlns = "jabber:client";
end
s = filter("stanzas/out", s);
- --log("debug", "Sending BOSH data: %s", tostring(s));
+ --log("debug", "Sending BOSH data: %s", s);
if not s then return true end
t_insert(session.send_buffer, tostring(s));
@@ -432,7 +432,7 @@ function stream_callbacks.streamopened(context, attr)
end
end
-local function handleerr(err) log("error", "Traceback[bosh]: %s", traceback(tostring(err), 2)); end
+local function handleerr(err) log("error", "Traceback[bosh]: %s", traceback(err, 2)); end
function runner_callbacks:error(err) -- luacheck: ignore 212/self
return handleerr(err);
diff --git a/plugins/mod_c2s.lua b/plugins/mod_c2s.lua
index bfec1055..53af34f0 100644
--- a/plugins/mod_c2s.lua
+++ b/plugins/mod_c2s.lua
@@ -127,7 +127,7 @@ function stream_callbacks.error(session, error, data)
session.log("debug", "Invalid opening stream header (%s)", (data:gsub("^([^\1]+)\1", "{%1}")));
session:close("invalid-namespace");
elseif error == "parse-error" then
- (session.log or log)("debug", "Client XML parse error: %s", tostring(data));
+ (session.log or log)("debug", "Client XML parse error: %s", data);
session:close("not-well-formed");
elseif error == "stream-error" then
local condition, text = "undefined-condition";
@@ -289,7 +289,7 @@ function listener.onconnect(conn)
if data then
local ok, err = stream:feed(data);
if not ok then
- log("debug", "Received invalid XML (%s) %d bytes: %q", tostring(err), #data, data:sub(1, 300));
+ log("debug", "Received invalid XML (%s) %d bytes: %q", err, #data, data:sub(1, 300));
session:close("not-well-formed");
end
end
diff --git a/plugins/mod_component.lua b/plugins/mod_component.lua
index b1ffc81d..afcfc68c 100644
--- a/plugins/mod_component.lua
+++ b/plugins/mod_component.lua
@@ -167,11 +167,11 @@ local xmlns_xmpp_streams = "urn:ietf:params:xml:ns:xmpp-streams";
function stream_callbacks.error(session, error, data)
if session.destroyed then return; end
- module:log("warn", "Error processing component stream: %s", tostring(error));
+ module:log("warn", "Error processing component stream: %s", error);
if error == "no-stream" then
session:close("invalid-namespace");
elseif error == "parse-error" then
- session.log("warn", "External component %s XML parse error: %s", tostring(session.host), tostring(data));
+ session.log("warn", "External component %s XML parse error: %s", session.host, data);
session:close("not-well-formed");
elseif error == "stream-error" then
local condition, text = "undefined-condition";
@@ -208,7 +208,7 @@ function stream_callbacks.streamclosed(session)
session:close();
end
-local function handleerr(err) log("error", "Traceback[component]: %s", traceback(tostring(err), 2)); end
+local function handleerr(err) log("error", "Traceback[component]: %s", traceback(err, 2)); end
function stream_callbacks.handlestanza(session, stanza)
-- Namespaces are icky.
if not stanza.attr.xmlns and stanza.name == "handshake" then
@@ -268,10 +268,10 @@ local function session_close(session, reason)
if reason.extra then
stanza:add_child(reason.extra);
end
- module:log("info", "Disconnecting component, <stream:error> is: %s", tostring(stanza));
+ module:log("info", "Disconnecting component, <stream:error> is: %s", stanza);
session.send(stanza);
elseif reason.name then -- a stanza
- module:log("info", "Disconnecting component, <stream:error> is: %s", tostring(reason));
+ module:log("info", "Disconnecting component, <stream:error> is: %s", reason);
session.send(reason);
end
end
@@ -312,7 +312,7 @@ function listener.onconnect(conn)
function session.data(_, data)
local ok, err = stream:feed(data);
if ok then return; end
- log("debug", "Received invalid XML (%s) %d bytes: %q", tostring(err), #data, data:sub(1, 300));
+ log("debug", "Received invalid XML (%s) %d bytes: %q", err, #data, data:sub(1, 300));
session:close("not-well-formed");
end
@@ -327,7 +327,7 @@ end
function listener.ondisconnect(conn, err)
local session = sessions[conn];
if session then
- (session.log or log)("info", "component disconnected: %s (%s)", tostring(session.host), tostring(err));
+ (session.log or log)("info", "component disconnected: %s (%s)", session.host, err);
if session.host then
module:context(session.host):fire_event("component-disconnected", { session = session, reason = err });
end
diff --git a/plugins/mod_groups.lua b/plugins/mod_groups.lua
index 646b7408..0c44f481 100644
--- a/plugins/mod_groups.lua
+++ b/plugins/mod_groups.lua
@@ -25,7 +25,7 @@ function inject_roster_contacts(event)
local function import_jids_to_roster(group_name)
for jid in pairs(groups[group_name]) do
-- Add them to roster
- --module:log("debug", "processing jid %s in group %s", tostring(jid), tostring(group_name));
+ --module:log("debug", "processing jid %s in group %s", jid, group_name);
if jid ~= bare_jid then
if not roster[jid] then roster[jid] = {}; end
roster[jid].subscription = "both";
@@ -99,7 +99,7 @@ function module.load()
end
members[false][#members[false]+1] = curr_group; -- Is a public group
end
- module:log("debug", "New group: %s", tostring(curr_group));
+ module:log("debug", "New group: %s", curr_group);
groups[curr_group] = groups[curr_group] or {};
else
-- Add JID
@@ -108,7 +108,7 @@ function module.load()
local jid;
jid = jid_prep(entryjid:match("%S+"));
if jid then
- module:log("debug", "New member of %s: %s", tostring(curr_group), tostring(jid));
+ module:log("debug", "New member of %s: %s", curr_group, jid);
groups[curr_group][jid] = name or false;
members[jid] = members[jid] or {};
members[jid][#members[jid]+1] = curr_group;
diff --git a/plugins/mod_limits.lua b/plugins/mod_limits.lua
index 7ae8bb34..a1a3b2c0 100644
--- a/plugins/mod_limits.lua
+++ b/plugins/mod_limits.lua
@@ -32,7 +32,7 @@ local function parse_burst(burst, sess_type)
end
local n_burst = tonumber(burst);
if not n_burst then
- module:log("error", "Unable to parse burst for %s: %q, using default burst interval (%ds)", sess_type, tostring(burst), default_burst);
+ module:log("error", "Unable to parse burst for %s: %q, using default burst interval (%ds)", sess_type, burst, default_burst);
end
return n_burst or default_burst;
end
@@ -84,8 +84,13 @@ local function filter_hook(session)
local session_type = session.type:match("^[^_]+");
local filter_set, opts = type_filters[session_type], limits[session_type];
if opts then
- session.throttle = throttle.create(opts.bytes_per_second * opts.burst_seconds, opts.burst_seconds);
- filters.add_filter(session, "bytes/in", filter_set.bytes_in, 1000);
+ if session.conn and session.conn.setlimit then
+ session.conn:setlimit(opts.bytes_per_second);
+ -- Currently no burst support
+ else
+ session.throttle = throttle.create(opts.bytes_per_second * opts.burst_seconds, opts.burst_seconds);
+ filters.add_filter(session, "bytes/in", filter_set.bytes_in, 1000);
+ end
end
end
@@ -106,9 +111,14 @@ function module.add_host(module)
local session_type = session.type:match("^[^_]+");
local jid = session.username .. "@" .. session.host;
if unlimited_jids:contains(jid) then
- local filter_set = type_filters[session_type];
- filters.remove_filter(session, "bytes/in", filter_set.bytes_in);
- session.throttle = nil;
+ if session.conn and session.conn.setlimit then
+ session.conn:setlimit(0);
+ -- Currently no burst support
+ else
+ local filter_set = type_filters[session_type];
+ filters.remove_filter(session, "bytes/in", filter_set.bytes_in);
+ session.throttle = nil;
+ end
end
end);
end
diff --git a/plugins/mod_mam/mod_mam.lua b/plugins/mod_mam/mod_mam.lua
index 4e0cf531..cfa92ff5 100644
--- a/plugins/mod_mam/mod_mam.lua
+++ b/plugins/mod_mam/mod_mam.lua
@@ -118,10 +118,12 @@ module:hook("iq-set/self/"..xmlns_mam..":query", function(event)
qstart, qend = vstart, vend;
end
- module:log("debug", "Archive query, id %s with %s from %s until %s",
- tostring(qid), qwith or "anyone",
- qstart and timestamp(qstart) or "the dawn of time",
- qend and timestamp(qend) or "now");
+ module:log("debug", "Archive query by %s id=%s with=%s when=%s...%s",
+ origin.username,
+ qid or stanza.attr.id,
+ qwith or "*",
+ qstart and timestamp(qstart) or "",
+ qend and timestamp(qend) or "");
-- RSM stuff
local qset = rsm.get(query);
@@ -129,6 +131,9 @@ module:hook("iq-set/self/"..xmlns_mam..":query", function(event)
local reverse = qset and qset.before or false;
local before, after = qset and qset.before, qset and qset.after;
if type(before) ~= "string" then before = nil; end
+ if qset then
+ module:log("debug", "Archive query id=%s rsm=%q", qid or stanza.attr.id, qset);
+ end
-- Load all the data!
local data, err = archive:find(origin.username, {
@@ -141,6 +146,7 @@ module:hook("iq-set/self/"..xmlns_mam..":query", function(event)
});
if not data then
+ module:log("debug", "Archive query id=%s failed: %s", qid or stanza.attr.id, err);
if err == "item-not-found" then
origin.send(st.error_reply(stanza, "modify", "item-not-found"));
else
@@ -194,13 +200,13 @@ module:hook("iq-set/self/"..xmlns_mam..":query", function(event)
first, last = last, first;
end
- -- That's all folks!
- module:log("debug", "Archive query %s completed", tostring(qid));
-
origin.send(st.reply(stanza)
:tag("fin", { xmlns = xmlns_mam, queryid = qid, complete = complete })
:add_child(rsm.generate {
first = first, last = last, count = total }));
+
+ -- That's all folks!
+ module:log("debug", "Archive query id=%s completed, %d items returned", qid or stanza.attr.id, complete and count or count - 1);
return true;
end);
@@ -218,13 +224,13 @@ local function shall_store(user, who)
end
local prefs = get_prefs(user);
local rule = prefs[who];
- module:log("debug", "%s's rule for %s is %s", user, who, tostring(rule));
+ module:log("debug", "%s's rule for %s is %s", user, who, rule);
if rule ~= nil then
return rule;
end
-- Below could be done by a metatable
local default = prefs[false];
- module:log("debug", "%s's default rule is %s", user, tostring(default));
+ module:log("debug", "%s's default rule is %s", user, default);
if default == "roster" then
return has_in_roster(user, who);
end
diff --git a/plugins/mod_muc_mam.lua b/plugins/mod_muc_mam.lua
index eb93a386..7fc9fabf 100644
--- a/plugins/mod_muc_mam.lua
+++ b/plugins/mod_muc_mam.lua
@@ -251,7 +251,7 @@ module:hook("iq-set/bare/"..xmlns_mam..":query", function(event)
end
-- That's all folks!
- module:log("debug", "Archive query %s completed", tostring(qid));
+ module:log("debug", "Archive query %s completed", qid);
origin.send(st.reply(stanza)
:tag("fin", { xmlns = xmlns_mam, queryid = qid, complete = complete })
@@ -291,7 +291,7 @@ module:hook("muc-get-history", function (event)
local data, err = archive:find(jid_split(room_jid), query);
if not data then
- module:log("error", "Could not fetch history: %s", tostring(err));
+ module:log("error", "Could not fetch history: %s", err);
return
end
@@ -317,7 +317,7 @@ module:hook("muc-get-history", function (event)
maxchars = maxchars - chars;
end
history[i], i = item, i+1;
- -- module:log("debug", tostring(item));
+ -- module:log("debug", item);
end
function event.next_stanza()
i = i - 1;
@@ -428,7 +428,9 @@ end
module:add_feature(xmlns_mam);
module:hook("muc-disco#info", function(event)
- event.reply:tag("feature", {var=xmlns_mam}):up();
+ if archiving_enabled(event.room) then
+ event.reply:tag("feature", {var=xmlns_mam}):up();
+ end
end);
-- Cleanup
diff --git a/plugins/mod_pep.lua b/plugins/mod_pep.lua
index 54f0451d..c0a85a9d 100644
--- a/plugins/mod_pep.lua
+++ b/plugins/mod_pep.lua
@@ -183,12 +183,12 @@ local function on_node_creation(event)
end
function get_pep_service(username)
- module:log("debug", "get_pep_service(%q)", username);
local user_bare = jid_join(username, host);
local service = services[username];
if service then
return service;
end
+ module:log("debug", "Creating pubsub service for user %q", username);
service = pubsub.new({
pep_username = username;
node_defaults = {
diff --git a/plugins/mod_pep_simple.lua b/plugins/mod_pep_simple.lua
index f91e5448..11268ab7 100644
--- a/plugins/mod_pep_simple.lua
+++ b/plugins/mod_pep_simple.lua
@@ -230,13 +230,13 @@ module:hook("iq/bare/http://jabber.org/protocol/pubsub:pubsub", function(event)
return true;
else --invalid request
session.send(st.error_reply(stanza, 'modify', 'bad-request'));
- module:log("debug", "Invalid request: %s", tostring(payload));
+ module:log("debug", "Invalid request: %s", payload);
return true;
end
else --no presence subscription
session.send(st.error_reply(stanza, 'auth', 'not-authorized')
:tag('presence-subscription-required', {xmlns='http://jabber.org/protocol/pubsub#errors'}));
- module:log("debug", "Unauthorized request: %s", tostring(payload));
+ module:log("debug", "Unauthorized request: %s", payload);
return true;
end
end
diff --git a/plugins/mod_proxy65.lua b/plugins/mod_proxy65.lua
index 00833772..29c821e2 100644
--- a/plugins/mod_proxy65.lua
+++ b/plugins/mod_proxy65.lua
@@ -117,7 +117,7 @@ function module.add_host(module)
if jid_compare(jid, acl) then allow = true; break; end
end
if allow then break; end
- module:log("warn", "Denying use of proxy for %s", tostring(stanza.attr.from));
+ module:log("warn", "Denying use of proxy for %s", stanza.attr.from);
origin.send(st.error_reply(stanza, "auth", "forbidden"));
return true;
end
diff --git a/plugins/mod_pubsub/mod_pubsub.lua b/plugins/mod_pubsub/mod_pubsub.lua
index 05f80365..faf08cb2 100644
--- a/plugins/mod_pubsub/mod_pubsub.lua
+++ b/plugins/mod_pubsub/mod_pubsub.lua
@@ -82,7 +82,6 @@ function simple_broadcast(kind, node, jids, item, actor, node_obj)
end
local summary;
- -- Compose a sensible textual representation of at least Atom payloads
if item and item.tags[1] then
local payload = item.tags[1];
summary = module:fire_event("pubsub-summary/"..payload.attr.xmlns, {
@@ -116,6 +115,7 @@ function is_item_stanza(item)
return st.is_stanza(item) and item.attr.xmlns == xmlns_pubsub and item.name == "item";
end
+-- Compose a textual representation of Atom payloads
module:hook("pubsub-summary/http://www.w3.org/2005/Atom", function (event)
local payload = event.payload;
local title = payload:get_child_text("title");
diff --git a/plugins/mod_pubsub/pubsub.lib.lua b/plugins/mod_pubsub/pubsub.lib.lua
index 50ef7ddf..d59e3d85 100644
--- a/plugins/mod_pubsub/pubsub.lib.lua
+++ b/plugins/mod_pubsub/pubsub.lib.lua
@@ -185,6 +185,14 @@ local node_metadata_form = dataform {
type = "text-single";
name = "pubsub#type";
};
+ {
+ type = "text-single";
+ name = "pubsub#access_model";
+ };
+ {
+ type = "text-single";
+ name = "pubsub#publish_model";
+ };
};
local service_method_feature_map = {
@@ -258,6 +266,8 @@ function _M.handle_disco_info_node(event, service)
["pubsub#title"] = node_obj.config.title;
["pubsub#description"] = node_obj.config.description;
["pubsub#type"] = node_obj.config.payload_type;
+ ["pubsub#access_model"] = node_obj.config.access_model;
+ ["pubsub#publish_model"] = node_obj.config.publish_model;
}, "result"));
end
end
@@ -318,14 +328,9 @@ function handlers.get_items(origin, stanza, items, service)
for _, id in ipairs(results) do
data:add_child(results[id]);
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
+ local reply = st.reply(stanza)
+ :tag("pubsub", { xmlns = xmlns_pubsub })
+ :add_child(data);
origin.send(reply);
return true;
end
diff --git a/plugins/mod_s2s/mod_s2s.lua b/plugins/mod_s2s/mod_s2s.lua
index f0fdc5fb..ea19f7ad 100644
--- a/plugins/mod_s2s/mod_s2s.lua
+++ b/plugins/mod_s2s/mod_s2s.lua
@@ -27,8 +27,8 @@ local s2s_destroy_session = require "core.s2smanager".destroy_session;
local uuid_gen = require "util.uuid".generate;
local fire_global_event = prosody.events.fire_event;
local runner = require "util.async".runner;
-
-local s2sout = module:require("s2sout");
+local connect = require "net.connect".connect;
+local service = require "net.resolvers.service";
local connect_timeout = module:get_option_number("s2s_timeout", 90);
local stream_close_timeout = module:get_option_number("s2s_close_timeout", 5);
@@ -45,6 +45,8 @@ local sessions = module:shared("sessions");
local runner_callbacks = {};
+local listener = {};
+
local log = module._log;
module:hook("stats-update", function ()
@@ -77,12 +79,19 @@ local function bounce_sendq(session, reason)
(session.log or log)("error", "Attempting to close the dummy origin of s2s error replies, please report this! Traceback: %s", traceback());
end;
};
+ -- FIXME Allow for more specific error conditions
+ -- TODO use util.error ?
+ local error_type = "cancel";
+ local condition = "remote-server-not-found";
+ if session.had_stream then -- set when a stream is opened by the remote
+ error_type, condition = "wait", "remote-server-timeout";
+ end
for i, data in ipairs(sendq) do
local reply = data[2];
if reply and not(reply.attr.xmlns) and bouncy_stanzas[reply.name] then
reply.attr.type = "error";
- reply:tag("error", {type = "cancel", by = session.from_host})
- :tag("remote-server-not-found", {xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas"}):up();
+ reply:tag("error", {type = error_type, by = session.from_host})
+ :tag(condition, {xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas"}):up();
if reason then
reply:tag("text", {xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas"})
:text("Server-to-server connection failed: "..reason):up();
@@ -127,7 +136,7 @@ function route_to_existing_session(event)
elseif host.type == "local" or host.type == "component" then
log("error", "Trying to send a stanza to ourselves??")
log("error", "Traceback: %s", traceback());
- log("error", "Stanza: %s", tostring(stanza));
+ log("error", "Stanza: %s", stanza);
return false;
else
-- FIXME
@@ -147,17 +156,13 @@ function route_to_new_session(event)
local from_host, to_host, stanza = event.from_host, event.to_host, event.stanza;
log("debug", "opening a new outgoing connection for this stanza");
local host_session = s2s_new_outgoing(from_host, to_host);
+ host_session.version = 1;
-- Store in buffer
host_session.bounce_sendq = bounce_sendq;
host_session.sendq = { {tostring(stanza), stanza.attr.type ~= "error" and stanza.attr.type ~= "result" and st.reply(stanza)} };
- log("debug", "stanza [%s] queued until connection complete", tostring(stanza.name));
- s2sout.initiate_connection(host_session);
- if (not host_session.connecting) and (not host_session.conn) then
- log("warn", "Connection to %s failed already, destroying session...", to_host);
- s2s_destroy_session(host_session, "Connection failed");
- return false;
- end
+ log("debug", "stanza [%s] queued until connection complete", stanza.name);
+ connect(service.new(to_host, "xmpp-server", "tcp", { default_port = 5269 }), listener, nil, { session = host_session });
return true;
end
@@ -301,6 +306,7 @@ end
function stream_callbacks._streamopened(session, attr)
session.version = tonumber(attr.version) or 0;
+ session.had_stream = true; -- Had a stream opened at least once
-- TODO: Rename session.secure to session.encrypted
if session.secure == false then
@@ -471,8 +477,6 @@ function stream_callbacks.error(session, error, data)
end
end
-local listener = {};
-
--- Session methods
local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'};
local function session_close(session, reason, remote_reason)
@@ -595,7 +599,7 @@ local function initialize_session(session)
if data then
local ok, err = stream:feed(data);
if ok then return; end
- log("debug", "Received invalid XML (%s) %d bytes: %q", tostring(err), #data, data:sub(1, 300));
+ log("debug", "Received invalid XML (%s) %d bytes: %q", err, #data, data:sub(1, 300));
session:close("not-well-formed");
end
end
@@ -671,11 +675,16 @@ function listener.ondisconnect(conn, err)
local session = sessions[conn];
if session then
sessions[conn] = nil;
+ (session.log or log)("debug", "s2s disconnected: %s->%s (%s)", session.from_host, session.to_host, err or "connection closed");
+ s2s_destroy_session(session, err);
+ end
+end
+
+function listener.onfail(data, err)
+ local session = data and data.session;
+ if session then
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
- return; -- Session lives for now
- end
end
(session.log or log)("debug", "s2s disconnected: %s->%s (%s)", session.from_host, session.to_host, err or "connection closed");
s2s_destroy_session(session, err);
@@ -699,6 +708,15 @@ function listener.ondetach(conn)
sessions[conn] = nil;
end
+function listener.onattach(conn, data)
+ local session = data and data.session;
+ if session then
+ session.conn = conn;
+ sessions[conn] = session;
+ initialize_session(session);
+ end
+end
+
function check_auth_policy(event)
local host, session = event.host, event.session;
local must_secure = secure_auth;
@@ -722,8 +740,6 @@ end
module:hook("s2s-check-certificate", check_auth_policy, -1);
-s2sout.set_listener(listener);
-
module:hook("server-stopping", function(event)
local reason = event.reason;
for _, session in pairs(sessions) do
diff --git a/plugins/mod_s2s/s2sout.lib.lua b/plugins/mod_s2s/s2sout.lib.lua
deleted file mode 100644
index 34e322d2..00000000
--- a/plugins/mod_s2s/s2sout.lib.lua
+++ /dev/null
@@ -1,349 +0,0 @@
--- Prosody IM
--- Copyright (C) 2008-2010 Matthew Wild
--- Copyright (C) 2008-2010 Waqas Hussain
---
--- This project is MIT/X11 licensed. Please see the
--- COPYING file in the source package for more information.
---
-
---- Module containing all the logic for connecting to a remote server
-
--- luacheck: ignore 432/err
-
-local portmanager = require "core.portmanager";
-local wrapclient = require "net.server".wrapclient;
-local initialize_filters = require "util.filters".initialize;
-local idna_to_ascii = require "util.encodings".idna.to_ascii;
-local new_ip = require "util.ip".new_ip;
-local rfc6724_dest = require "util.rfc6724".destination;
-local socket = require "socket";
-local adns = require "net.adns";
-local t_insert, t_sort, ipairs = table.insert, table.sort, ipairs;
-local local_addresses = require "util.net".local_addresses;
-
-local s2s_destroy_session = require "core.s2smanager".destroy_session;
-
-local default_mode = module:get_option("network_default_read_size", 4096);
-
-local log = module._log;
-
-local sources = {};
-local has_ipv4, has_ipv6;
-
-local dns_timeout = module:get_option_number("dns_timeout", 15);
-local resolvers = module:get_option_set("s2s_dns_resolvers")
-
-local s2sout = {};
-
-local s2s_listener;
-
-
-function s2sout.set_listener(listener)
- s2s_listener = listener;
-end
-
-local function compare_srv_priorities(a,b)
- return a.priority < b.priority or (a.priority == b.priority and a.weight > b.weight);
-end
-
-function s2sout.initiate_connection(host_session)
- local log = host_session.log or log;
-
- initialize_filters(host_session);
- host_session.version = 1;
-
- host_session.resolver = adns.resolver();
- host_session.resolver._resolver:settimeout(dns_timeout);
- if resolvers then
- for resolver in resolvers do
- host_session.resolver._resolver:addnameserver(resolver);
- end
- end
-
- -- Kick the connection attempting machine into life
- if not s2sout.attempt_connection(host_session) then
- -- Intentionally not returning here, the
- -- session is needed, connected or not
- s2s_destroy_session(host_session);
- end
-
- if not host_session.sends2s then
- -- A sends2s which buffers data (until the stream is opened)
- -- note that data in this buffer will be sent before the stream is authed
- -- and will not be ack'd in any way, successful or otherwise
- local buffer;
- function host_session.sends2s(data)
- if not buffer then
- buffer = {};
- host_session.send_buffer = buffer;
- end
- log("debug", "Buffering data on unconnected s2sout to %s", host_session.to_host);
- buffer[#buffer+1] = data;
- log("debug", "Buffered item %d: %s", #buffer, data);
- end
- end
-end
-
-function s2sout.attempt_connection(host_session, err)
- local to_host = host_session.to_host;
- local connect_host, connect_port = to_host and idna_to_ascii(to_host), 5269;
- local log = host_session.log or log;
-
- if not connect_host then
- return false;
- end
-
- if not err then -- This is our first attempt
- log("debug", "First attempt to connect to %s, starting with SRV lookup...", to_host);
- host_session.connecting = true;
- host_session.resolver:lookup(function (answer)
- local srv_hosts = { answer = answer };
- host_session.srv_hosts = srv_hosts;
- host_session.srv_choice = 0;
- host_session.connecting = nil;
- if answer and #answer > 0 then
- log("debug", "%s has SRV records, handling...", to_host);
- for _, record in ipairs(answer) do
- t_insert(srv_hosts, record.srv);
- end
- if #srv_hosts == 1 and srv_hosts[1].target == "." then
- log("debug", "%s does not provide a XMPP service", to_host);
- s2s_destroy_session(host_session, err); -- Nothing to see here
- return;
- end
- t_sort(srv_hosts, compare_srv_priorities);
-
- local srv_choice = srv_hosts[1];
- host_session.srv_choice = 1;
- if srv_choice then
- connect_host, connect_port = srv_choice.target or to_host, srv_choice.port or connect_port;
- log("debug", "Best record found, will connect to %s:%d", connect_host, connect_port);
- end
- else
- log("debug", "%s has no SRV records, falling back to A/AAAA", to_host);
- end
- -- Try with SRV, or just the plain hostname if no SRV
- local ok, err = s2sout.try_connect(host_session, connect_host, connect_port);
- if not ok then
- if not s2sout.attempt_connection(host_session, err) then
- -- No more attempts will be made
- s2s_destroy_session(host_session, err);
- end
- end
- end, "_xmpp-server._tcp."..connect_host..".", "SRV");
-
- return true; -- Attempt in progress
- elseif host_session.ip_hosts then
- return s2sout.try_connect(host_session, connect_host, connect_port, err);
- elseif host_session.srv_hosts and #host_session.srv_hosts > host_session.srv_choice then -- Not our first attempt, and we also have SRV
- host_session.srv_choice = host_session.srv_choice + 1;
- local srv_choice = host_session.srv_hosts[host_session.srv_choice];
- connect_host, connect_port = srv_choice.target or to_host, srv_choice.port or connect_port;
- host_session.log("info", "Connection failed (%s). Attempt #%d: This time to %s:%d", err, host_session.srv_choice, connect_host, connect_port);
- else
- host_session.log("info", "Failed in all attempts to connect to %s", host_session.to_host);
- -- We're out of options
- return false;
- end
-
- if not (connect_host and connect_port) then
- -- Likely we couldn't resolve DNS
- log("warn", "Hmm, we're without a host (%s) and port (%s) to connect to for %s, giving up :(", connect_host, connect_port, to_host);
- return false;
- end
-
- return s2sout.try_connect(host_session, connect_host, connect_port);
-end
-
-function s2sout.try_next_ip(host_session)
- host_session.connecting = nil;
- host_session.ip_choice = host_session.ip_choice + 1;
- local ip = host_session.ip_hosts[host_session.ip_choice];
- local ok, err= s2sout.make_connect(host_session, ip.ip, ip.port);
- if not ok then
- if not s2sout.attempt_connection(host_session, err or "closed") then
- err = err and (": "..err) or "";
- s2s_destroy_session(host_session, "Connection failed"..err);
- end
- end
-end
-
-function s2sout.try_connect(host_session, connect_host, connect_port, err)
- host_session.connecting = true;
- local log = host_session.log or log;
-
- if not err then
- local IPs = {};
- host_session.ip_hosts = IPs;
- -- luacheck: ignore 231/handle4 231/handle6
- local handle4, handle6;
- local have_other_result = not(has_ipv4) or not(has_ipv6) or false;
-
- if has_ipv4 then
- handle4 = host_session.resolver:lookup(function (reply, err)
- handle4 = nil;
-
- if reply and reply[#reply] and reply[#reply].a then
- for _, ip in ipairs(reply) do
- log("debug", "DNS reply for %s gives us %s", connect_host, ip.a);
- IPs[#IPs+1] = new_ip(ip.a, "IPv4");
- end
- elseif err then
- log("debug", "Error in DNS lookup: %s", err);
- end
-
- if have_other_result then
- if #IPs > 0 then
- rfc6724_dest(host_session.ip_hosts, sources);
- for i = 1, #IPs do
- IPs[i] = {ip = IPs[i], port = connect_port};
- end
- host_session.ip_choice = 0;
- s2sout.try_next_ip(host_session);
- else
- log("debug", "DNS lookup failed to get a response for %s", connect_host);
- host_session.ip_hosts = nil;
- if not s2sout.attempt_connection(host_session, "name resolution failed") then -- Retry if we can
- log("debug", "No other records to try for %s - destroying", host_session.to_host);
- err = err and (": "..err) or "";
- s2s_destroy_session(host_session, "DNS resolution failed"..err); -- End of the line, we can't
- end
- end
- else
- have_other_result = true;
- end
- end, connect_host, "A", "IN");
- else
- have_other_result = true;
- end
-
- if has_ipv6 then
- handle6 = host_session.resolver:lookup(function (reply, err)
- handle6 = nil;
-
- if reply and reply[#reply] and reply[#reply].aaaa then
- for _, ip in ipairs(reply) do
- log("debug", "DNS reply for %s gives us %s", connect_host, ip.aaaa);
- IPs[#IPs+1] = new_ip(ip.aaaa, "IPv6");
- end
- elseif err then
- log("debug", "Error in DNS lookup: %s", err);
- end
-
- if have_other_result then
- if #IPs > 0 then
- rfc6724_dest(host_session.ip_hosts, sources);
- for i = 1, #IPs do
- IPs[i] = {ip = IPs[i], port = connect_port};
- end
- host_session.ip_choice = 0;
- s2sout.try_next_ip(host_session);
- else
- log("debug", "DNS lookup failed to get a response for %s", connect_host);
- host_session.ip_hosts = nil;
- if not s2sout.attempt_connection(host_session, "name resolution failed") then -- Retry if we can
- log("debug", "No other records to try for %s - destroying", host_session.to_host);
- err = err and (": "..err) or "";
- s2s_destroy_session(host_session, "DNS resolution failed"..err); -- End of the line, we can't
- end
- end
- else
- have_other_result = true;
- end
- end, connect_host, "AAAA", "IN");
- else
- have_other_result = true;
- end
- return true;
- elseif host_session.ip_hosts and #host_session.ip_hosts > host_session.ip_choice then -- Not our first attempt, and we also have IPs left to try
- s2sout.try_next_ip(host_session);
- else
- log("debug", "Out of IP addresses, trying next SRV record (if any)");
- host_session.ip_hosts = nil;
- if not s2sout.attempt_connection(host_session, "out of IP addresses") then -- Retry if we can
- log("debug", "No other records to try for %s - destroying", host_session.to_host);
- err = err and (": "..err) or "";
- s2s_destroy_session(host_session, "Connecting failed"..err); -- End of the line, we can't
- return false;
- end
- end
-
- return true;
-end
-
-function s2sout.make_connect(host_session, connect_host, connect_port)
- local log = host_session.log or log;
- log("debug", "Beginning new connection attempt to %s ([%s]:%d)", host_session.to_host, connect_host.addr, connect_port);
-
- -- Reset secure flag in case this is another
- -- connection attempt after a failed STARTTLS
- host_session.secure = nil;
- host_session.encrypted = nil;
-
- local conn, handler;
- local proto = connect_host.proto;
- if proto == "IPv4" then
- conn, handler = socket.tcp();
- elseif proto == "IPv6" and socket.tcp6 then
- conn, handler = socket.tcp6();
- else
- handler = "Unsupported protocol: "..tostring(proto);
- end
-
- if not conn then
- log("warn", "Failed to create outgoing connection, system error: %s", handler);
- return false, handler;
- end
-
- conn:settimeout(0);
- local success, err = conn:connect(connect_host.addr, connect_port);
- if not success and err ~= "timeout" then
- log("warn", "s2s connect() to %s (%s:%d) failed: %s", host_session.to_host, connect_host.addr, connect_port, err);
- return false, err;
- end
-
- conn = wrapclient(conn, connect_host.addr, connect_port, s2s_listener, default_mode);
- host_session.conn = conn;
-
- -- Register this outgoing connection so that xmppserver_listener knows about it
- -- otherwise it will assume it is a new incoming connection
- s2s_listener.register_outgoing(conn, host_session);
-
- log("debug", "Connection attempt in progress...");
- return true;
-end
-
-module:hook_global("service-added", function (event)
- if event.name ~= "s2s" then return end
-
- local s2s_sources = portmanager.get_active_services():get("s2s");
- if not s2s_sources then
- module:log_status("warn", "s2s not listening on any ports, outgoing connections may fail");
- return;
- end
- for source, _ in pairs(s2s_sources) do
- if source == "*" or source == "0.0.0.0" then
- for _, addr in ipairs(local_addresses("ipv4", true)) do
- sources[#sources + 1] = new_ip(addr, "IPv4");
- end
- elseif source == "::" then
- for _, addr in ipairs(local_addresses("ipv6", true)) do
- sources[#sources + 1] = new_ip(addr, "IPv6");
- end
- else
- sources[#sources + 1] = new_ip(source, (source:find(":") and "IPv6") or "IPv4");
- end
- end
- for i = 1,#sources do
- if sources[i].proto == "IPv6" then
- has_ipv6 = true;
- elseif sources[i].proto == "IPv4" then
- has_ipv4 = true;
- end
- end
- if not (has_ipv4 or has_ipv6) then
- module:log("warn", "No local IPv4 or IPv6 addresses detected, outgoing connections may fail");
- end
-end);
-
-return s2sout;
diff --git a/plugins/mod_saslauth.lua b/plugins/mod_saslauth.lua
index 3145cf9b..89313de1 100644
--- a/plugins/mod_saslauth.lua
+++ b/plugins/mod_saslauth.lua
@@ -14,7 +14,6 @@ local sm_make_authenticated = require "core.sessionmanager".make_authenticated;
local base64 = require "util.encodings".base64;
local usermanager_get_sasl_handler = require "core.usermanager".get_sasl_handler;
-local tostring = tostring;
local secure_auth_only = module:get_option_boolean("c2s_require_encryption", module:get_option_boolean("require_encryption", false));
local allow_unencrypted_plain_auth = module:get_option_boolean("allow_unencrypted_plain_auth", false)
@@ -77,7 +76,7 @@ local function sasl_process_cdata(session, stanza)
local status, ret, err_msg = session.sasl_handler:process(text);
status, ret, err_msg = handle_status(session, status, ret, err_msg);
local s = build_reply(status, ret, err_msg);
- log("debug", "sasl reply: %s", tostring(s));
+ log("debug", "sasl reply: %s", s);
session.send(s);
return true;
end
diff --git a/plugins/mod_stanza_debug.lua b/plugins/mod_stanza_debug.lua
index 6dedb6f7..af98670c 100644
--- a/plugins/mod_stanza_debug.lua
+++ b/plugins/mod_stanza_debug.lua
@@ -1,18 +1,17 @@
module:set_global();
-local tostring = tostring;
local filters = require "util.filters";
local function log_send(t, session)
if t and t ~= "" and t ~= " " then
- session.log("debug", "SEND: %s", tostring(t));
+ session.log("debug", "SEND: %s", t);
end
return t;
end
local function log_recv(t, session)
if t and t ~= "" and t ~= " " then
- session.log("debug", "RECV: %s", tostring(t));
+ session.log("debug", "RECV: %s", t);
end
return t;
end
diff --git a/plugins/mod_vcard_legacy.lua b/plugins/mod_vcard_legacy.lua
index ab2c4490..89ef1a4f 100644
--- a/plugins/mod_vcard_legacy.lua
+++ b/plugins/mod_vcard_legacy.lua
@@ -105,6 +105,15 @@ module:hook("iq-get/bare/vcard-temp:vCard", function (event)
vcard_temp:tag("WORK"):up();
end
vcard_temp:up();
+ elseif tag.name == "impp" then
+ local uri = tag:get_child_text("uri");
+ if uri and uri:sub(1, 5) == "xmpp:" then
+ vcard_temp:text_tag("JABBERID", uri:sub(6))
+ end
+ elseif tag.name == "org" then
+ vcard_temp:tag("ORG")
+ :text_tag("ORGNAME", tag:get_child_text("text"))
+ :up();
end
end
end
@@ -216,6 +225,10 @@ function vcard_to_pep(vcard_temp)
vcard4:text_tag("text", "work");
end
vcard4:up():up():up();
+ elseif tag.name == "JABBERID" then
+ vcard4:tag("impp")
+ :text_tag("uri", "xmpp:" .. tag:get_text())
+ :up();
elseif tag.name == "PHOTO" then
local avatar_type = tag:get_child_text("TYPE");
local avatar_payload = tag:get_child_text("BINVAL");
diff --git a/plugins/mod_websocket.lua b/plugins/mod_websocket.lua
index 008f6823..c94ea84a 100644
--- a/plugins/mod_websocket.lua
+++ b/plugins/mod_websocket.lua
@@ -80,7 +80,7 @@ local function session_close(session, reason)
stream_error = reason;
end
end
- log("debug", "Disconnecting client, <stream:error> is: %s", tostring(stream_error));
+ log("debug", "Disconnecting client, <stream:error> is: %s", stream_error);
session.send(stream_error);
end
@@ -272,6 +272,7 @@ function handle_request(event)
end);
add_filter(session, "stanzas/out", function(stanza)
+ stanza = st.clone(stanza);
local attr = stanza.attr;
attr.xmlns = attr.xmlns or xmlns_client;
if stanza.name:find("^stream:") then
diff --git a/plugins/muc/language.lib.lua b/plugins/muc/language.lib.lua
index ee80806b..2ee2ba0f 100644
--- a/plugins/muc/language.lib.lua
+++ b/plugins/muc/language.lib.lua
@@ -32,6 +32,7 @@ local function add_form_option(event)
label = "Language tag for room (e.g. 'en', 'de', 'fr' etc.)";
type = "text-single";
desc = "Indicate the primary language spoken in this room";
+ datatype = "xs:language";
value = get_language(event.room) or "";
});
end
diff --git a/prosodyctl b/prosodyctl
index 664deb51..7261b326 100755
--- a/prosodyctl
+++ b/prosodyctl
@@ -246,7 +246,15 @@ function commands.start(arg)
end
--luacheck: ignore 411/ret
- local ok, ret = prosodyctl.start(prosody.paths.source, arg[-1]);
+ local lua;
+ do
+ local i = 0;
+ repeat
+ i = i - 1;
+ until arg[i-1] == nil
+ lua = arg[i];
+ end
+ local ok, ret = prosodyctl.start(prosody.paths.source, lua);
if ok then
local daemonize = configmanager.get("*", "daemonize");
if daemonize == nil then
diff --git a/spec/util_array_spec.lua b/spec/util_array_spec.lua
new file mode 100644
index 00000000..acd2e882
--- /dev/null
+++ b/spec/util_array_spec.lua
@@ -0,0 +1,154 @@
+local array = require "util.array";
+describe("util.array", function ()
+ describe("creation", function ()
+ describe("from tablle", function ()
+ it("works", function ()
+ local a = array({"a", "b", "c"});
+ assert.same({"a", "b", "c"}, a);
+ end);
+ end);
+
+ describe("from iterator", function ()
+ it("works", function ()
+ -- collects the first value, ie the keys
+ local a = array(ipairs({true, true, true}));
+ assert.same({1, 2, 3}, a);
+ end);
+ end);
+
+ describe("collect", function ()
+ it("works", function ()
+ -- collects the first value, ie the keys
+ local a = array.collect(ipairs({true, true, true}));
+ assert.same({1, 2, 3}, a);
+ end);
+ end);
+
+ end);
+
+ describe("metatable", function ()
+ describe("operator", function ()
+ describe("addition", function ()
+ it("works", function ()
+ local a = array({ "a", "b" });
+ local b = array({ "c", "d" });
+ assert.same({"a", "b", "c", "d"}, a + b);
+ end);
+ end);
+
+ describe("equality", function ()
+ it("works", function ()
+ local a1 = array({ "a", "b" });
+ local a2 = array({ "a", "b" });
+ local b = array({ "c", "d" });
+ assert.truthy(a1 == a2);
+ assert.falsy(a1 == b);
+ end);
+ end);
+
+ describe("division", function ()
+ it("works", function ()
+ local a = array({ "a", "b", "c" });
+ local b = a / function (i) if i ~= "b" then return i .. "x" end end;
+ assert.same({ "ax", "cx" }, b);
+ end);
+ end);
+
+ end);
+ end);
+
+ describe("methods", function ()
+ describe("map", function ()
+ it("works", function ()
+ local a = array({ "a", "b", "c" });
+ local b = a:map(string.upper);
+ assert.same({ "A", "B", "C" }, b);
+ end);
+ end);
+
+ describe("filter", function ()
+ it("works", function ()
+ local a = array({ "a", "b", "c" });
+ a:filter(function (i) return i ~= "b" end);
+ assert.same({ "a", "c" }, a);
+ end);
+ end);
+
+ describe("sort", function ()
+ it("works", function ()
+ local a = array({ 5, 4, 3, 1, 2, });
+ a:sort();
+ assert.same({ 1, 2, 3, 4, 5, }, a);
+ end);
+ end);
+
+ describe("unique", function ()
+ it("works", function ()
+ local a = array({ "a", "b", "c", "c", "a", "b" });
+ a:unique();
+ assert.same({ "a", "b", "c" }, a);
+ end);
+ end);
+
+ describe("pluck", function ()
+ it("works", function ()
+ local a = array({ { a = 1, b = -1 }, { a = 2, b = -2 }, });
+ a:pluck("a");
+ assert.same({ 1, 2 }, a);
+ end);
+ end);
+
+
+ describe("reverse", function ()
+ it("works", function ()
+ local a = array({ "a", "b", "c" });
+ a:reverse();
+ assert.same({ "c", "b", "a" }, a);
+ end);
+ end);
+
+ -- TODO :shuffle
+
+ describe("append", function ()
+ it("works", function ()
+ local a = array({ "a", "b", "c" });
+ a:append(array({ "d", "e", }));
+ assert.same({ "a", "b", "c", "d", "e" }, a);
+ end);
+ end);
+
+ describe("push", function ()
+ it("works", function ()
+ local a = array({ "a", "b", "c" });
+ a:push("d"):push("e");
+ assert.same({ "a", "b", "c", "d", "e" }, a);
+ end);
+ end);
+
+ describe("pop", function ()
+ it("works", function ()
+ local a = array({ "a", "b", "c" });
+ assert.equal("c", a:pop());
+ assert.same({ "a", "b", }, a);
+ end);
+ end);
+
+ describe("concat", function ()
+ it("works", function ()
+ local a = array({ "a", "b", "c" });
+ assert.equal("a,b,c", a:concat(","));
+ end);
+ end);
+
+ describe("length", function ()
+ it("works", function ()
+ local a = array({ "a", "b", "c" });
+ assert.equal(3, a:length());
+ end);
+ end);
+
+ end);
+
+ -- TODO The various array.foo(array ina, array outa) functions
+end);
+
diff --git a/spec/util_error_spec.lua b/spec/util_error_spec.lua
new file mode 100644
index 00000000..d8534c2b
--- /dev/null
+++ b/spec/util_error_spec.lua
@@ -0,0 +1,68 @@
+local errors = require "util.error"
+
+describe("util.error", function ()
+ describe("new()", function ()
+ it("works", function ()
+ local err = errors.new("bork", "bork bork");
+ assert.not_nil(err);
+ assert.equal("cancel", err.type);
+ assert.equal("undefined-condition", err.condition);
+ assert.same("bork bork", err.context);
+ end);
+
+ describe("templates", function ()
+ it("works", function ()
+ local templates = {
+ ["fail"] = {
+ type = "wait",
+ condition = "internal-server-error",
+ };
+ };
+ local err = errors.new("fail", { traceback = "in some file, somewhere" }, templates);
+ assert.equal("wait", err.type);
+ assert.equal("internal-server-error", err.condition);
+ assert.same({ traceback = "in some file, somewhere" }, err.context);
+ end);
+ end);
+
+ end);
+
+ describe("is_err()", function ()
+ it("works", function ()
+ assert.truthy(errors.is_err(errors.new()));
+ assert.falsy(errors.is_err("not an error"));
+ end);
+ end);
+
+ describe("coerce", function ()
+ it("works", function ()
+ local ok, err = errors.coerce(nil, "it dun goofed");
+ assert.is_nil(ok);
+ assert.truthy(errors.is_err(err))
+ end);
+ end);
+
+ describe("from_stanza", function ()
+ it("works", function ()
+ local st = require "util.stanza";
+ local m = st.message({ type = "chat" });
+ local e = st.error_reply(m, "modify", "bad-request");
+ local err = errors.from_stanza(e);
+ assert.truthy(errors.is_err(err));
+ assert.equal("modify", err.type);
+ assert.equal("bad-request", err.condition);
+ assert.equal(e, err.context.stanza);
+ end);
+ end);
+
+ describe("__tostring", function ()
+ it("doesn't throw", function ()
+ assert.has_no.errors(function ()
+ -- See 6f317e51544d
+ tostring(errors.new());
+ end);
+ end);
+ end);
+
+end);
+
diff --git a/util-src/poll.c b/util-src/poll.c
index 1e7b6da3..21cb9581 100644
--- a/util-src/poll.c
+++ b/util-src/poll.c
@@ -172,6 +172,7 @@ static int Lset(lua_State *L) {
lua_pushnil(L);
lua_pushstring(L, strerror(ENOENT));
lua_pushinteger(L, ENOENT);
+ return 3;
}
if(!lua_isnoneornil(L, 3)) {
@@ -229,6 +230,7 @@ static int Ldel(lua_State *L) {
lua_pushnil(L);
lua_pushstring(L, strerror(ENOENT));
lua_pushinteger(L, ENOENT);
+ return 3;
}
FD_CLR(fd, &state->wantread);
diff --git a/util/error.lua b/util/error.lua
index 344dd274..23551fe2 100644
--- a/util/error.lua
+++ b/util/error.lua
@@ -1,7 +1,7 @@
local error_mt = { __name = "error" };
function error_mt:__tostring()
- return ("error<%s:%s:%s>"):format(self.type, self.condition, self.text);
+ return ("error<%s:%s:%s>"):format(self.type, self.condition, self.text or "");
end
local function is_err(e)
diff --git a/util/serialization.lua b/util/serialization.lua
index 60e341cf..d70e92ba 100644
--- a/util/serialization.lua
+++ b/util/serialization.lua
@@ -267,10 +267,15 @@ local function deserialize(str)
return ret;
end
+local default = new();
return {
new = new;
serialize = function (x, opt)
- return new(opt)(x);
+ if opt == nil then
+ return default(x);
+ else
+ return new(opt)(x);
+ end
end;
deserialize = deserialize;
};
diff --git a/util/session.lua b/util/session.lua
index b9c6bec7..25b22faf 100644
--- a/util/session.lua
+++ b/util/session.lua
@@ -31,7 +31,7 @@ local function set_send(session)
local conn = session.conn;
if not conn then
function session.send(data)
- session.log("debug", "Discarding data sent to unconnected session: %s", tostring(data));
+ session.log("debug", "Discarding data sent to unconnected session: %s", data);
return false;
end
return session;
@@ -47,7 +47,7 @@ local function set_send(session)
if t then
local ret, err = w(conn, t);
if not ret then
- session.log("debug", "Error writing to connection: %s", tostring(err));
+ session.log("debug", "Error writing to connection: %s", err);
return false, err;
end
end
diff --git a/util/sql.lua b/util/sql.lua
index 00c7b57f..4406d7ff 100644
--- a/util/sql.lua
+++ b/util/sql.lua
@@ -201,18 +201,18 @@ function engine:_transaction(func, ...)
if not ok then return ok, err; end
end
--assert(not self.__transaction, "Recursive transactions not allowed");
- log("debug", "SQL transaction begin [%s]", tostring(func));
+ log("debug", "SQL transaction begin [%s]", func);
self.__transaction = true;
local success, a, b, c = xpcall(func, handleerr, ...);
self.__transaction = nil;
if success then
- log("debug", "SQL transaction success [%s]", tostring(func));
+ log("debug", "SQL transaction success [%s]", func);
local ok, err = self.conn:commit();
-- LuaDBI doesn't actually return an error message here, just a boolean
if not ok then return ok, err or "commit failed"; end
return success, a, b, c;
else
- log("debug", "SQL transaction failure [%s]: %s", tostring(func), a.err);
+ log("debug", "SQL transaction failure [%s]: %s", func, a.err);
if self.conn then self.conn:rollback(); end
return success, a.err;
end
@@ -224,7 +224,7 @@ function engine:transaction(...)
if not conn or not conn:ping() then
log("debug", "Database connection was closed. Will reconnect and retry.");
self.conn = nil;
- log("debug", "Retrying SQL transaction [%s]", tostring((...)));
+ log("debug", "Retrying SQL transaction [%s]", (...));
ok, ret = self:_transaction(...);
log("debug", "SQL transaction retry %s", ok and "succeeded" or "failed");
else
diff --git a/util/stanza.lua b/util/stanza.lua
index 7fe5c7ae..55c38c73 100644
--- a/util/stanza.lua
+++ b/util/stanza.lua
@@ -98,7 +98,7 @@ function stanza_mt:query(xmlns)
end
function stanza_mt:body(text, attr)
- return self:tag("body", attr):text(text);
+ return self:text_tag("body", text, attr);
end
function stanza_mt:text_tag(name, text, attr, namespaces)
@@ -417,7 +417,7 @@ local function message(attr, body)
if not body then
return new_stanza("message", attr);
else
- return new_stanza("message", attr):tag("body"):text(body):up();
+ return new_stanza("message", attr):text_tag("body", body);
end
end
local function iq(attr)
@@ -449,7 +449,7 @@ local function error_reply(orig, error_type, condition, error_message)
t.attr.type = "error";
t:tag("error", {type = error_type}) --COMPAT: Some day xmlns:stanzas goes here
:tag(condition, xmpp_stanzas_attr):up();
- if error_message then t:tag("text", xmpp_stanzas_attr):text(error_message):up(); end
+ if error_message then t:text_tag("text", error_message, xmpp_stanzas_attr); end
return t; -- stanza ready for adding app-specific errors
end
diff --git a/util/startup.lua b/util/startup.lua
index 02e5f012..580410c9 100644
--- a/util/startup.lua
+++ b/util/startup.lua
@@ -257,9 +257,9 @@ function startup.add_global_prosody_functions()
local ok, level, err = config.load(prosody.config_file);
if not ok then
if level == "parser" then
- log("error", "There was an error parsing the configuration file: %s", tostring(err));
+ log("error", "There was an error parsing the configuration file: %s", err);
elseif level == "file" then
- log("error", "Couldn't read the config file when trying to reload: %s", tostring(err));
+ log("error", "Couldn't read the config file when trying to reload: %s", err);
end
else
prosody.events.fire_event("config-reloaded", {
diff --git a/util/xmppstream.lua b/util/xmppstream.lua
index 58cbd18e..6aa1def3 100644
--- a/util/xmppstream.lua
+++ b/util/xmppstream.lua
@@ -64,6 +64,8 @@ local function new_sax_handlers(session, stream_callbacks, cb_handleprogress)
local stream_default_ns = stream_callbacks.default_ns;
+ local stream_lang = "en";
+
local stack = {};
local chardata, stanza = {};
local stanza_size = 0;
@@ -101,6 +103,7 @@ local function new_sax_handlers(session, stream_callbacks, cb_handleprogress)
if session.notopen then
if tagname == stream_tag then
non_streamns_depth = 0;
+ stream_lang = attr["xml:lang"] or stream_lang;
if cb_streamopened then
if lxp_supports_bytecount then
cb_handleprogress(stanza_size);
@@ -178,6 +181,9 @@ local function new_sax_handlers(session, stream_callbacks, cb_handleprogress)
cb_handleprogress(stanza_size);
end
stanza_size = 0;
+ if stanza.attr["xml:lang"] == nil then
+ stanza.attr["xml:lang"] = stream_lang;
+ end
if tagname ~= stream_error_tag then
cb_handlestanza(session, stanza);
else