From 7abfe39cc3a0949c8bde868dd3811bf33149897d Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 28 Oct 2018 16:49:28 +0100 Subject: net.server_select: Deprecate connection:lock_read() method --- net/server_select.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/server_select.lua b/net/server_select.lua index bc86742c..1c016633 100644 --- a/net/server_select.lua +++ b/net/server_select.lua @@ -456,8 +456,8 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport maxreadlen = readlen or maxreadlen return bufferlen, maxreadlen, maxsendlen end - --TODO: Deprecate handler.lock_read = function (self, switch) + out_error( "server.lua, lock_read() is deprecated, use pause() and resume()" ) if switch == true then local tmp = _readlistlen _readlistlen = removesocket( _readlist, socket, _readlistlen ) -- cgit v1.2.3 From eff5acbce1361ea628ac44fe8cb980ed876c6661 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 28 Oct 2018 16:51:58 +0100 Subject: net.server_event: Deprecate :lock_read here too --- net/server_event.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/net/server_event.lua b/net/server_event.lua index 11bd6a29..ca80c3f2 100644 --- a/net/server_event.lua +++ b/net/server_event.lua @@ -253,6 +253,7 @@ end --TODO: Deprecate function interface_mt:lock_read(switch) + log("warn", ":lock_read is deprecated, use :pasue() and :resume()"); if switch then return self:pause(); else -- cgit v1.2.3 From 9fe357101e6a870bc94ec27577abc022c62bc6da Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 28 Oct 2018 16:53:10 +0100 Subject: net.server_select: Move code from :lock_read into :pause and :resume --- net/server_select.lua | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/net/server_select.lua b/net/server_select.lua index 1c016633..51a74c94 100644 --- a/net/server_select.lua +++ b/net/server_select.lua @@ -459,26 +459,28 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport handler.lock_read = function (self, switch) out_error( "server.lua, lock_read() is deprecated, use pause() and resume()" ) if switch == true then - local tmp = _readlistlen - _readlistlen = removesocket( _readlist, socket, _readlistlen ) - _readtimes[ handler ] = nil - if _readlistlen ~= tmp then - noread = true - end + return self:pause() elseif switch == false then - if noread then - noread = false - _readlistlen = addsocket(_readlist, socket, _readlistlen) - _readtimes[ handler ] = _currenttime - end + return self:resume() end return noread end handler.pause = function (self) - return self:lock_read(true); + local tmp = _readlistlen + _readlistlen = removesocket( _readlist, socket, _readlistlen ) + _readtimes[ handler ] = nil + if _readlistlen ~= tmp then + noread = true + end + return noread; end handler.resume = function (self) - return self:lock_read(false); + if noread then + noread = false + _readlistlen = addsocket(_readlist, socket, _readlistlen) + _readtimes[ handler ] = _currenttime + end + return noread; end handler.lock = function( self, switch ) handler.lock_read (switch) -- cgit v1.2.3 From 38ea739022f4adeca620b4c8bb736bacb60e1d06 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 28 Oct 2018 16:54:08 +0100 Subject: server_select: Fix :lock method This always unlocks reading. I don't believe this is used anywhere. server_event does not implement this. --- net/server_select.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/server_select.lua b/net/server_select.lua index 51a74c94..d74da130 100644 --- a/net/server_select.lua +++ b/net/server_select.lua @@ -483,7 +483,7 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport return noread; end handler.lock = function( self, switch ) - handler.lock_read (switch) + handler.lock_read (self, switch) if switch == true then handler.write = idfalse local tmp = _sendlistlen -- cgit v1.2.3 From 55e3a7a8aacae7cc401753da0f32947b0eab5d16 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 28 Oct 2018 16:55:21 +0100 Subject: net.server_select: Deprecate :lock method Exists only in server_select and I found nothing using it --- net/server_select.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/net/server_select.lua b/net/server_select.lua index d74da130..e30ac8fe 100644 --- a/net/server_select.lua +++ b/net/server_select.lua @@ -483,6 +483,7 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport return noread; end handler.lock = function( self, switch ) + out_error( "server.lua, lock() is deprecated" ) handler.lock_read (self, switch) if switch == true then handler.write = idfalse -- cgit v1.2.3 From 556eddb7913324503e77bfdce49b8edb55cbc59f Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 28 Oct 2018 17:08:50 +0100 Subject: net.server_select: Replace use of deprecated :lock_read in server.link --- net/server_select.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/server_select.lua b/net/server_select.lua index e30ac8fe..475f05b8 100644 --- a/net/server_select.lua +++ b/net/server_select.lua @@ -719,7 +719,7 @@ local function link(sender, receiver, buffersize) function receiver.sendbuffer() _sendbuffer(); if sender_locked and receiver.bufferlen() < buffersize then - sender:lock_read(false); -- Unlock now + sender:resume(); -- Unlock now sender_locked = nil; end end @@ -729,7 +729,7 @@ local function link(sender, receiver, buffersize) _readbuffer(); if not sender_locked and receiver.bufferlen() >= buffersize then sender_locked = true; - sender:lock_read(true); + sender:pause(); end end sender:set_mode("*a"); -- cgit v1.2.3 From 5834d45f487f3875987620843914c47a3824feb7 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 28 Oct 2018 17:11:18 +0100 Subject: net.server_select: Still allow buffering outgoing data on write-locked connections --- net/server_select.lua | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/net/server_select.lua b/net/server_select.lua index 475f05b8..745e1f49 100644 --- a/net/server_select.lua +++ b/net/server_select.lua @@ -424,9 +424,8 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport bufferlen = bufferlen + #data if bufferlen > maxsendlen then _closelist[ handler ] = "send buffer exceeded" -- cannot close the client at the moment, have to wait to the end of the cycle - handler.write = idfalse -- don't write anymore return false - elseif socket and not _sendlist[ socket ] then + elseif not nosend and socket and not _sendlist[ socket ] then _sendlistlen = addsocket(_sendlist, socket, _sendlistlen) end bufferqueuelen = bufferqueuelen + 1 @@ -486,7 +485,6 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport out_error( "server.lua, lock() is deprecated" ) handler.lock_read (self, switch) if switch == true then - handler.write = idfalse local tmp = _sendlistlen _sendlistlen = removesocket( _sendlist, socket, _sendlistlen ) _writetimes[ handler ] = nil @@ -494,7 +492,6 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport nosend = true end elseif switch == false then - handler.write = write if nosend then nosend = false write( "" ) -- cgit v1.2.3 From 3899c7ac4b50242ccfc78edc6d5e3d6c3b954008 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 25 Oct 2018 15:12:59 +0200 Subject: net.server: Add an API for holding writes of outgoing data --- net/server_epoll.lua | 20 ++++++++++++++++++-- net/server_event.lua | 13 +++++++++++++ net/server_select.lua | 31 +++++++++++++++++++------------ 3 files changed, 50 insertions(+), 14 deletions(-) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index 4b40c7d5..cdf3e8fe 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -409,8 +409,10 @@ function interface:write(data) else self.writebuffer = { data }; end - self:setwritetimeout(); - self:set(nil, true); + if not self._write_lock then + self:setwritetimeout(); + self:set(nil, true); + end return #data; end interface.send = interface.write; @@ -590,6 +592,20 @@ function interface:pausefor(t) end); end +function interface:pause_writes() + self._write_lock = true; + self:setwritetimeout(false); + self:set(nil, false); +end + +function interface:resume_writes() + self._write_lock = nil; + if self.writebuffer[1] then + self:setwritetimeout(); + self:set(nil, true); + end +end + -- Connected! function interface:onconnect() if self.conn and not self.peername and self.conn.getpeername then diff --git a/net/server_event.lua b/net/server_event.lua index ca80c3f2..70757e03 100644 --- a/net/server_event.lua +++ b/net/server_event.lua @@ -273,6 +273,19 @@ function interface_mt:resume() end end +function interface_mt:pause_writes() + return self:_lock(self.nointerface, self.noreading, true); +end + +function interface_mt:resume_writes() + self:_lock(self.nointerface, self.noreading, false); + if self.writecallback and not self.eventwrite then + self.eventwrite = addevent( base, self.conn, EV_WRITE, self.writecallback, cfg.WRITE_TIMEOUT ); -- register callback + return true; + end +end + + function interface_mt:counter(c) if c then self._connections = self._connections + c diff --git a/net/server_select.lua b/net/server_select.lua index 745e1f49..693cee5e 100644 --- a/net/server_select.lua +++ b/net/server_select.lua @@ -485,20 +485,27 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport out_error( "server.lua, lock() is deprecated" ) handler.lock_read (self, switch) if switch == true then - local tmp = _sendlistlen - _sendlistlen = removesocket( _sendlist, socket, _sendlistlen ) - _writetimes[ handler ] = nil - if _sendlistlen ~= tmp then - nosend = true - end + handler.pause_writes (self) elseif switch == false then - if nosend then - nosend = false - write( "" ) - end + handler.resume_writes (self) end return noread, nosend end + handler.pause_writes = function (self) + local tmp = _sendlistlen + _sendlistlen = removesocket( _sendlist, socket, _sendlistlen ) + _writetimes[ handler ] = nil + if _sendlistlen ~= tmp then + nosend = true + end + end + handler.resume_writes = function (self) + if nosend then + nosend = false + write( "" ) + end + end + local _readbuffer = function( ) -- this function reads data local buffer, err, part = receive( socket, pattern ) -- receive buffer with "pattern" if not err or (err == "wantread" or err == "timeout") then -- received something @@ -716,7 +723,7 @@ local function link(sender, receiver, buffersize) function receiver.sendbuffer() _sendbuffer(); if sender_locked and receiver.bufferlen() < buffersize then - sender:resume(); -- Unlock now + sender:lock_read(false); -- Unlock now sender_locked = nil; end end @@ -726,7 +733,7 @@ local function link(sender, receiver, buffersize) _readbuffer(); if not sender_locked and receiver.bufferlen() >= buffersize then sender_locked = true; - sender:pause(); + sender:lock_read(true); end end sender:set_mode("*a"); -- cgit v1.2.3 From 1f9b825c34e068f951cf4154ceb71580aea23eb0 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 28 Oct 2018 18:22:17 +0100 Subject: net.server_epoll: Reschedule delayed timers relative to current time This should normally never happen, but can be reproduced by suspending the process a while. --- net/server_epoll.lua | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index cdf3e8fe..ce8996a8 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -106,9 +106,13 @@ local function runtimers(next_delay, min_wait) end local new_timeout = f(now); if new_timeout then - -- Schedule for 'delay' from the time actually scheduled, - -- not from now, in order to prevent timer drift. - timer[1] = t + new_timeout; + -- Schedule for 'delay' from the time actually scheduled, not from now, + -- in order to prevent timer drift, unless it already drifted way out of sync. + if (t + new_timeout) > ( now - new_timeout ) then + timer[1] = t + new_timeout; + else + timer[1] = now + new_timeout; + end resort_timers = true; else t_remove(timers, i); -- cgit v1.2.3 From fb768f193f73d360a61758b5a46e14d81c967151 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 29 Oct 2018 02:13:09 +0100 Subject: net.server_epoll: Use method to update peername on connect --- net/server_epoll.lua | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index ce8996a8..f7e5ae49 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -612,9 +612,7 @@ end -- Connected! function interface:onconnect() - if self.conn and not self.peername and self.conn.getpeername then - self.peername, self.peerport = self.conn:getpeername(); - end + self:updatenames(); self.onconnect = noop; self:on("connect"); end -- cgit v1.2.3 From 2a701f2d8b5b024bccbe10ebfcb0e4f1f3ffddf2 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 1 Nov 2018 23:58:41 +0100 Subject: mod_pep: Remove incorrect features advertised on the bare host --- plugins/mod_pep.lua | 3 --- 1 file changed, 3 deletions(-) diff --git a/plugins/mod_pep.lua b/plugins/mod_pep.lua index 1d8c55bf..cb775fb5 100644 --- a/plugins/mod_pep.lua +++ b/plugins/mod_pep.lua @@ -250,9 +250,6 @@ end module:hook("iq/bare/"..xmlns_pubsub..":pubsub", handle_pubsub_iq); module:hook("iq/bare/"..xmlns_pubsub_owner..":pubsub", handle_pubsub_iq); -module:add_identity("pubsub", "pep", module:get_option_string("name", "Prosody")); -module:add_feature("http://jabber.org/protocol/pubsub#publish"); - local function get_caps_hash_from_presence(stanza, current) local t = stanza.attr.type; if not t then -- cgit v1.2.3 From eb0947ba4f77172073a21be7c20e17e5f016c203 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 10 Nov 2018 15:50:32 +0100 Subject: MUC: Fix spelling in comments --- plugins/muc/muc.lib.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 4060535a..d9fa37f5 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -39,7 +39,7 @@ function room_mt:__tostring() end function room_mt.save() - -- overriden by mod_muc.lua + -- overridden by mod_muc.lua end function room_mt:get_occupant_jid(real_jid) @@ -279,7 +279,7 @@ function room_mt:publicise_occupant_status(occupant, x, nick, actor, reason) self_p = st.clone(base_presence):add_child(self_x); end - -- General populance + -- General populace for occupant_nick, n_occupant in self:each_occupant() do if occupant_nick ~= occupant.nick then local pr; @@ -609,7 +609,7 @@ function room_mt:handle_normal_presence(origin, stanza) x:tag("status", {code = "303";}):up(); x:tag("status", {code = "110";}):up(); self:route_stanza(generated_unavail:add_child(x)); - dest_nick = nil; -- set dest_nick to nil; so general populance doesn't see it for whole orig_occupant + dest_nick = nil; -- set dest_nick to nil; so general populace doesn't see it for whole orig_occupant end end @@ -966,7 +966,7 @@ function room_mt:handle_admin_query_get_command(origin, stanza) local _aff_rank = valid_affiliations[_aff or "none"]; local _rol = item.attr.role; if _aff and _aff_rank and not _rol then - -- You need to be at least an admin, and be requesting info about your affifiliation or lower + -- You need to be at least an admin, and be requesting info about your affiliation or lower -- e.g. an admin can't ask for a list of owners local affiliation_rank = valid_affiliations[affiliation or "none"]; if (affiliation_rank >= valid_affiliations.admin and affiliation_rank >= _aff_rank) @@ -1291,7 +1291,7 @@ function room_mt:set_affiliation(actor, jid, affiliation, reason, data) -- Outcast can be by host. is_host_only and affiliation == "outcast" and select(2, jid_split(occupant.bare_jid)) == host ) then - -- need to publcize in all cases; as affiliation in has changed. + -- need to publicize in all cases; as affiliation in has changed. occupants_updated[occupant] = occupant.role; if occupant.role ~= role and ( is_downgrade or -- cgit v1.2.3 From cf22878c983c7ebd6cf2a6bef90ed44b7295298d Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 27 Nov 2018 17:01:47 +0100 Subject: MUC: Move check for explicit room join earlier in room creation flow --- plugins/muc/mod_muc.lua | 2 +- plugins/muc/muc.lib.lua | 7 ------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index 954bae92..89e67744 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -453,7 +453,7 @@ for event_name, method in pairs { if room == nil then -- Watch presence to create rooms - if stanza.attr.type == nil and stanza.name == "presence" then + if stanza.attr.type == nil and stanza.name == "presence" and stanza:get_child("x", "http://jabber.org/protocol/muc") then room = muclib.new_room(room_jid); return room:handle_first_presence(origin, stanza); elseif stanza.attr.type ~= "error" then diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 96f58023..0009e9b2 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -428,13 +428,6 @@ module:hook("muc-occupant-pre-change", function(event) end, 1); function room_mt:handle_first_presence(origin, stanza) - if not stanza:get_child("x", "http://jabber.org/protocol/muc") then - module:log("debug", "Room creation without , possibly desynced"); - - origin.send(st.error_reply(stanza, "cancel", "item-not-found")); - return true; - end - local real_jid = stanza.attr.from; local dest_jid = stanza.attr.to; local bare_jid = jid_bare(real_jid); -- cgit v1.2.3 From d007771f8da390816640fa3839b29dcbaa5862d2 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 28 Nov 2018 20:36:53 +0100 Subject: util.format: Tweak how nil values are handled Because [] seems exsessive --- spec/util_format_spec.lua | 2 ++ util/format.lua | 9 ++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/spec/util_format_spec.lua b/spec/util_format_spec.lua index 7e6a0c6e..8a2e9312 100644 --- a/spec/util_format_spec.lua +++ b/spec/util_format_spec.lua @@ -5,6 +5,8 @@ describe("util.format", function() it("should work", function() assert.equal("hello", format("%s", "hello")); assert.equal("", format("%s")); + assert.equal("", format("%d")); + assert.equal("", format("%q")); assert.equal(" []", format("", nil)); assert.equal("true", format("%s", true)); assert.equal("[true]", format("%d", true)); diff --git a/util/format.lua b/util/format.lua index c5e513fa..16c57bc6 100644 --- a/util/format.lua +++ b/util/format.lua @@ -28,13 +28,12 @@ local function format(formatstring, ...) if spec ~= "%%" then i = i + 1; local arg = args[i]; - if arg == nil then -- special handling for nil - arg = "" - args[i] = ""; - end local option = spec:sub(-1); - if option == "q" or option == "s" then -- arg should be string + if arg == nil then + args[i] = "nil"; + spec = "<%s>"; + elseif option == "q" or option == "s" then -- arg should be string args[i] = tostring(arg); elseif type(arg) ~= "number" then -- arg isn't number as expected? args[i] = tostring(arg); -- cgit v1.2.3 From 0974ed9811f7d814df095ee089a701807c03cafa Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 29 Nov 2018 16:16:09 +0100 Subject: configure: Split list of possible suffixes into a line per Lua version --- configure | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/configure b/configure index 4307997c..92f078f4 100755 --- a/configure +++ b/configure @@ -404,7 +404,9 @@ then then suffixes="5.3 53 -5.3 -53" else - suffixes="5.1 51 -5.1 -51 5.2 52 -5.2 -52 5.3 53 -5.3 -53" + suffixes="5.1 51 -5.1 -51" + suffixes="$suffixes 5.2 52 -5.2 -52" + suffixes="$suffixes 5.3 53 -5.3 -53" fi for suffix in "" $suffixes do -- cgit v1.2.3 From 1331a867a7c3083e1824dc92b9fca8171a677d8a Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 29 Nov 2018 16:19:39 +0100 Subject: configure: Recognise 5.4 as a valid Lua version --- configure | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/configure b/configure index 92f078f4..c484a280 100755 --- a/configure +++ b/configure @@ -237,7 +237,7 @@ do --lua-version|--with-lua-version) [ -n "$value" ] || die "Missing value in flag $key." LUA_VERSION="$value" - [ "$LUA_VERSION" = "5.1" ] || [ "$LUA_VERSION" = "5.2" ] || [ "$LUA_VERSION" = "5.3" ] || die "Invalid Lua version in flag $key." + [ "$LUA_VERSION" = "5.1" ] || [ "$LUA_VERSION" = "5.2" ] || [ "$LUA_VERSION" = "5.3" ] || [ "$LUA_VERSION" = "5.4" ] || die "Invalid Lua version in flag $key." LUA_VERSION_SET=yes ;; --with-lua) @@ -340,7 +340,7 @@ then fi detect_lua_version() { - detected_lua=$("$1" -e 'print(_VERSION:match(" (5%.[123])$"))' 2> /dev/null) + detected_lua=$("$1" -e 'print(_VERSION:match(" (5%.[1234])$"))' 2> /dev/null) if [ "$detected_lua" != "nil" ] then if [ "$LUA_VERSION_SET" != "yes" ] @@ -403,10 +403,14 @@ then elif [ "$LUA_VERSION_SET" = "yes" ] && [ "$LUA_VERSION" = "5.3" ] then suffixes="5.3 53 -5.3 -53" + elif [ "$LUA_VERSION_SET" = "yes" ] && [ "$LUA_VERSION" = "5.4" ] + then + suffixes="5.4 54 -5.4 -54" else suffixes="5.1 51 -5.1 -51" suffixes="$suffixes 5.2 52 -5.2 -52" suffixes="$suffixes 5.3 53 -5.3 -53" + suffixes="$suffixes 5.4 54 -5.4 -54" fi for suffix in "" $suffixes do -- cgit v1.2.3 From 874cf01e2b01cf96392763957ef1df9f77dedc24 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 29 Nov 2018 16:53:22 +0100 Subject: net.websocket.frames: Prefer Lua 5.2 built-in bit module over LuaJIT version When running on Lua 5.2 this makes sense since bit32 is usually already loaded. It's sensible to prefer this going forward in case of incompatibilities between the two variants. --- net/websocket/frames.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/websocket/frames.lua b/net/websocket/frames.lua index ba25d261..b5aebb40 100644 --- a/net/websocket/frames.lua +++ b/net/websocket/frames.lua @@ -9,7 +9,7 @@ local softreq = require "util.dependencies".softreq; local random_bytes = require "util.random".bytes; -local bit = assert(softreq"bit" or softreq"bit32", +local bit = assert(softreq"bit32" or softreq"bit", "No bit module found. See https://prosody.im/doc/depends#bitop"); local band = bit.band; local bor = bit.bor; -- cgit v1.2.3 From e999fee5c39278f767f7f508a2c0c964ec964cd1 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 30 Nov 2018 23:58:55 +0100 Subject: tests: Add scansion test for #689 about keeping the full subscription request stanza --- spec/scansion/keep_full_sub_req.scs | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 spec/scansion/keep_full_sub_req.scs diff --git a/spec/scansion/keep_full_sub_req.scs b/spec/scansion/keep_full_sub_req.scs new file mode 100644 index 00000000..318bbb60 --- /dev/null +++ b/spec/scansion/keep_full_sub_req.scs @@ -0,0 +1,37 @@ +# server MUST keep a record of the complete presence stanza comprising the subscription request (#689) + +[Client] Alice + jid: pars-a@localhost + password: password + +[Client] Bob + jid: pars-b@localhost + password: password + +--------- + +Alice connects + +Alice sends: + + + + +Alice disconnects + +Bob connects + +Bob sends: + + +Bob receives: + + + +Bob receives: + + + + +Bob disconnects + -- cgit v1.2.3 From 296555762ed5a515998d4b85d6d7fd4d9bfb62e5 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 1 Dec 2018 18:02:58 +0100 Subject: spec/keep_full_sub_req: Add missing type attribute --- spec/scansion/keep_full_sub_req.scs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/scansion/keep_full_sub_req.scs b/spec/scansion/keep_full_sub_req.scs index 318bbb60..3b80b8b2 100644 --- a/spec/scansion/keep_full_sub_req.scs +++ b/spec/scansion/keep_full_sub_req.scs @@ -29,7 +29,7 @@ Bob receives: Bob receives: - + -- cgit v1.2.3 From 8aac387431c7dad47c6afffc4bfcc024d3147950 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 1 Dec 2018 18:07:56 +0100 Subject: spec/keep_full_sub_req: Verify that the presence subscription stays the same after a reconnect --- spec/scansion/keep_full_sub_req.scs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/spec/scansion/keep_full_sub_req.scs b/spec/scansion/keep_full_sub_req.scs index 3b80b8b2..5a5e1fdf 100644 --- a/spec/scansion/keep_full_sub_req.scs +++ b/spec/scansion/keep_full_sub_req.scs @@ -27,6 +27,23 @@ Bob sends: Bob receives: +Bob receives: + + + + +Bob disconnects + +# Works if they reconnect too + +Bob connects + +Bob sends: + + +Bob receives: + + Bob receives: -- cgit v1.2.3 From c295aedbfeeab6ebacb9b5bece085b8a532a7e9d Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 1 Dec 2018 18:12:01 +0100 Subject: spec/keep_full_sub_req: Make the second connect a differenct device (workaround for scansion issue) scansion threw an error when a client connected again --- spec/scansion/keep_full_sub_req.scs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/spec/scansion/keep_full_sub_req.scs b/spec/scansion/keep_full_sub_req.scs index 5a5e1fdf..244c1d55 100644 --- a/spec/scansion/keep_full_sub_req.scs +++ b/spec/scansion/keep_full_sub_req.scs @@ -8,6 +8,10 @@ jid: pars-b@localhost password: password +[Client] Bob's phone + jid: pars-b@localhost/phone + password: password + --------- Alice connects @@ -36,19 +40,19 @@ Bob disconnects # Works if they reconnect too -Bob connects +Bob's phone connects -Bob sends: +Bob's phone sends: -Bob receives: - +Bob's phone receives: + -Bob receives: +Bob's phone receives: -Bob disconnects +Bob's phone disconnects -- cgit v1.2.3 From 3836d03c37dbd7f3dcd07eb68dab3ca5f0290329 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Sat, 1 Dec 2018 22:13:24 +0000 Subject: rostermanager, mod_presence: Store stanza for incoming subscription requests (fixes #689) (thanks Zash, Ge0rG) --- core/rostermanager.lua | 6 +++--- plugins/mod_presence.lua | 8 +++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/core/rostermanager.lua b/core/rostermanager.lua index 61b08002..2d616e4b 100644 --- a/core/rostermanager.lua +++ b/core/rostermanager.lua @@ -263,15 +263,15 @@ end function is_contact_pending_in(username, host, jid) local roster = load_roster(username, host); - return roster[false].pending[jid]; + return roster[false].pending[jid] ~= nil; end -local function set_contact_pending_in(username, host, jid) +local function set_contact_pending_in(username, host, jid, stanza) local roster = load_roster(username, host); local item = roster[jid]; if item and (item.subscription == "from" or item.subscription == "both") then return; -- false end - roster[false].pending[jid] = true; + roster[false].pending[jid] = st.is_stanza(stanza) and st.preserialize(stanza) or true; return save_roster(username, host, roster, jid); end function is_contact_pending_out(username, host, jid) diff --git a/plugins/mod_presence.lua b/plugins/mod_presence.lua index 5056a3a3..51254c63 100644 --- a/plugins/mod_presence.lua +++ b/plugins/mod_presence.lua @@ -80,8 +80,10 @@ function handle_normal_presence(origin, stanza) res.presence.attr.to = nil; end end - for jid in pairs(roster[false].pending) do -- resend incoming subscription requests - origin.send(st.presence({type="subscribe", from=jid})); -- TODO add to attribute? Use original? + for jid, pending_request in pairs(roster[false].pending) do -- resend incoming subscription requests + local subscribe = st.clone(st.deserialize(pending_request)); + subscribe.attr.type, subscribe.attr.from = "subscribe", jid; + origin.send(subscribe); end local request = st.presence({type="subscribe", from=origin.username.."@"..origin.host}); for jid, item in pairs(roster) do -- resend outgoing subscription requests @@ -225,7 +227,7 @@ function handle_inbound_presence_subscriptions_and_probes(origin, stanza, from_b else core_post_stanza(hosts[host], st.presence({from=to_bare, to=from_bare, type="unavailable"}), true); -- acknowledging receipt if not rostermanager.is_contact_pending_in(node, host, from_bare) then - if rostermanager.set_contact_pending_in(node, host, from_bare) then + if rostermanager.set_contact_pending_in(node, host, from_bare, stanza) then sessionmanager.send_to_available_resources(node, host, stanza); end -- TODO else return error, unable to save end -- cgit v1.2.3 From c083a55ca54508f51320cc412c544c481ba25fd1 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 2 Dec 2018 17:20:44 +0100 Subject: mod_presence: Remove unnecessary stanza clone call --- plugins/mod_presence.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_presence.lua b/plugins/mod_presence.lua index 51254c63..1ea837e8 100644 --- a/plugins/mod_presence.lua +++ b/plugins/mod_presence.lua @@ -81,7 +81,7 @@ function handle_normal_presence(origin, stanza) end end for jid, pending_request in pairs(roster[false].pending) do -- resend incoming subscription requests - local subscribe = st.clone(st.deserialize(pending_request)); + local subscribe = st.deserialize(pending_request); subscribe.attr.type, subscribe.attr.from = "subscribe", jid; origin.send(subscribe); end -- cgit v1.2.3 From e3d678dd679de143a9dd46fa77360f4874ecdf60 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 2 Dec 2018 17:22:26 +0100 Subject: mod_presence: Handle older boolean subscription request data (thanks Martin) --- plugins/mod_presence.lua | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/plugins/mod_presence.lua b/plugins/mod_presence.lua index 1ea837e8..5aed5854 100644 --- a/plugins/mod_presence.lua +++ b/plugins/mod_presence.lua @@ -81,9 +81,13 @@ function handle_normal_presence(origin, stanza) end end for jid, pending_request in pairs(roster[false].pending) do -- resend incoming subscription requests - local subscribe = st.deserialize(pending_request); - subscribe.attr.type, subscribe.attr.from = "subscribe", jid; - origin.send(subscribe); + if type(pending_request) == "table" then + local subscribe = st.deserialize(pending_request); + subscribe.attr.type, subscribe.attr.from = "subscribe", jid; + origin.send(subscribe); + else + origin.send(st.presence({type="subscribe", from=jid})); + end end local request = st.presence({type="subscribe", from=origin.username.."@"..origin.host}); for jid, item in pairs(roster) do -- resend outgoing subscription requests -- cgit v1.2.3 From d2b0158dcc2b91122089072feccaaaa34dd9bf61 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 3 Dec 2018 19:38:19 +0000 Subject: configure: Also look for lua.h in a directory with the same suffix as the interpreter (FreeBSD-friendly) --- configure | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/configure b/configure index c484a280..4a816364 100755 --- a/configure +++ b/configure @@ -469,24 +469,37 @@ if [ -f "$lua_h" ] then echo "lua.h found in $lua_h" else - v_dir="$LUA_INCDIR/lua/$LUA_VERSION" - lua_h="$v_dir/lua.h" - if [ -f "$lua_h" ] - then + for postfix in "$LUA_VERSION" "$LUA_SUFFIX"; do + if ! [ "$postfix" = "" ]; then + v_dir="$LUA_INCDIR/lua/$postfix"; + else + v_dir="$LUA_INCDIR/lua"; + fi + lua_h="$v_dir/lua.h" + if [ -f "$lua_h" ] + then echo "lua.h found in $lua_h" LUA_INCDIR="$v_dir" - else - d_dir="$LUA_INCDIR/lua$LUA_VERSION" + break; + else + d_dir="$LUA_INCDIR/lua$postfix" lua_h="$d_dir/lua.h" if [ -f "$lua_h" ] then - echo "lua.h found in $lua_h (Debian/Ubuntu)" - LUA_INCDIR="$d_dir" + echo "lua.h found in $lua_h" + LUA_INCDIR="$d_dir" + break; else - echo "lua.h not found (looked in $LUA_INCDIR, $v_dir, $d_dir)" - die "You may want to use the flag --with-lua or --with-lua-include. See --help." + lua_h_search="$lua_h_search\n $v_dir/lua.h\n $d_dir/lua.h" fi - fi + fi + done + if [ ! -f "$lua_h" ]; then + echo "lua.h not found. Looked for:" + echo "$lua_h_search" | uniq + echo + die "You may want to use the flag --with-lua or --with-lua-include. See --help." + fi fi if [ "$lua_interp_found" = "yes" ] -- cgit v1.2.3 From f1ada80c232b4c2ddcd0b4f3eaec9594a993bcec Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 3 Dec 2018 23:06:41 +0000 Subject: configure: Refactor header search to make it more portable --- configure | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/configure b/configure index 4a816364..dec7b60d 100755 --- a/configure +++ b/configure @@ -463,12 +463,13 @@ then LUA_LIBDIR="$LUA_DIR/lib" fi -echo_n "Checking Lua includes... " lua_h="$LUA_INCDIR/lua.h" +echo_n "Looking for lua.h at $lua_h..." if [ -f "$lua_h" ] then - echo "lua.h found in $lua_h" + echo found else + echo "not found" for postfix in "$LUA_VERSION" "$LUA_SUFFIX"; do if ! [ "$postfix" = "" ]; then v_dir="$LUA_INCDIR/lua/$postfix"; @@ -476,27 +477,29 @@ else v_dir="$LUA_INCDIR/lua"; fi lua_h="$v_dir/lua.h" + echo_n "Looking for lua.h at $lua_h..." if [ -f "$lua_h" ] then - echo "lua.h found in $lua_h" LUA_INCDIR="$v_dir" + echo found break; else + echo "not found" d_dir="$LUA_INCDIR/lua$postfix" lua_h="$d_dir/lua.h" + echo_n "Looking for lua.h at $lua_h..." if [ -f "$lua_h" ] then - echo "lua.h found in $lua_h" + echo found LUA_INCDIR="$d_dir" break; else - lua_h_search="$lua_h_search\n $v_dir/lua.h\n $d_dir/lua.h" + echo "not found" fi fi done if [ ! -f "$lua_h" ]; then - echo "lua.h not found. Looked for:" - echo "$lua_h_search" | uniq + echo "lua.h not found." echo die "You may want to use the flag --with-lua or --with-lua-include. See --help." fi -- cgit v1.2.3 From 4ef9902ea95ef9d99d0e98dd85dc6a124d4c3894 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Tue, 4 Dec 2018 12:11:15 +0000 Subject: util.time: Bump POSIX_C_SOURCE to ensure visibility of CLOCK_MONOTONIC on FreeBSD (fixes #1253) --- util-src/time.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util-src/time.c b/util-src/time.c index bfad52ee..bc6b5b1c 100644 --- a/util-src/time.c +++ b/util-src/time.c @@ -1,5 +1,5 @@ #ifndef _POSIX_C_SOURCE -#define _POSIX_C_SOURCE 199309L +#define _POSIX_C_SOURCE 200809L #endif #include -- cgit v1.2.3 From f68b6612f8690450a115a653cdfb13b44988ab53 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Tue, 4 Dec 2018 12:11:58 +0000 Subject: util.pposix: Don't define POSIX_C_SOURCE on FreeBSD to ensure visibility of initgroups() --- util-src/pposix.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/util-src/pposix.c b/util-src/pposix.c index 5c926603..169343b8 100644 --- a/util-src/pposix.c +++ b/util-src/pposix.c @@ -25,14 +25,18 @@ #define _DEFAULT_SOURCE #endif #endif + #if defined(__APPLE__) #ifndef _DARWIN_C_SOURCE #define _DARWIN_C_SOURCE #endif #endif + +#if ! defined(__FreeBSD__) #ifndef _POSIX_C_SOURCE #define _POSIX_C_SOURCE 200809L #endif +#endif #include #include -- cgit v1.2.3 From 64b7335fd7d557dd269a74be95bbe375a096e335 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Tue, 4 Dec 2018 16:19:08 +0000 Subject: makefile: Add lint target (to match GNUMakefile) --- makefile | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/makefile b/makefile index d19ec24d..1b8e99b5 100644 --- a/makefile +++ b/makefile @@ -19,6 +19,8 @@ INSTALL_EXEC=$(INSTALL) -m755 MKDIR=install -d MKDIR_PRIVATE=$(MKDIR) -m750 +LUACHECK=luacheck + .PHONY: all test clean install all: prosody.install prosodyctl.install prosody.cfg.lua.install prosody.version @@ -68,6 +70,11 @@ clean: rm -f prosody.version $(MAKE) clean -C util-src +lint: + $(LUACHECK) -q $$(HGPLAIN= hg files -I '**.lua') prosody prosodyctl + @echo $$(sed -n '/^\tlocal exclude_files/,/^}/p;' .luacheckrc | sed '1d;$d' | wc -l) files ignored + shellcheck configure + test: busted --lua=$(RUNWITH) -- cgit v1.2.3 From bd19b153479f2f54ce4357398091a8513afd8506 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Tue, 4 Dec 2018 16:19:58 +0000 Subject: makefile: Allow configuring path to busted (to match GNUMakefile) --- makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/makefile b/makefile index 1b8e99b5..0b1c8788 100644 --- a/makefile +++ b/makefile @@ -20,6 +20,7 @@ MKDIR=install -d MKDIR_PRIVATE=$(MKDIR) -m750 LUACHECK=luacheck +BUSTED=busted .PHONY: all test clean install @@ -76,7 +77,7 @@ lint: shellcheck configure test: - busted --lua=$(RUNWITH) + $(BUSTED) --lua=$(RUNWITH) prosody.install: prosody -- cgit v1.2.3 From c250892998c3734ed355b60ff3975279eaef7a9d Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 4 Dec 2018 19:49:31 +0100 Subject: MUC/subject: Don't consider messages with or (fixes #667) --- plugins/muc/subject.lib.lua | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/plugins/muc/subject.lib.lua b/plugins/muc/subject.lib.lua index 938abf61..c8b99cc7 100644 --- a/plugins/muc/subject.lib.lua +++ b/plugins/muc/subject.lib.lua @@ -94,6 +94,12 @@ module:hook("muc-occupant-groupchat", function(event) local stanza = event.stanza; local subject = stanza:get_child("subject"); if subject then + if stanza:get_child("body") or stanza:get_child("thread") then + -- Note: A message with a and a or a and + -- a is a legitimate message, but it SHALL NOT be interpreted + -- as a subject change. + return; + end local room = event.room; local occupant = event.occupant; -- Role check for subject changes -- cgit v1.2.3 From c28be4a630e1e6872438a5822de098295b55b925 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 6 Dec 2018 17:54:50 +0100 Subject: MUC: Add test case for #667 --- spec/scansion/muc_subject_issue_667.scs | 79 +++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 spec/scansion/muc_subject_issue_667.scs diff --git a/spec/scansion/muc_subject_issue_667.scs b/spec/scansion/muc_subject_issue_667.scs new file mode 100644 index 00000000..68f4c17a --- /dev/null +++ b/spec/scansion/muc_subject_issue_667.scs @@ -0,0 +1,79 @@ +# #667 MUC message with subject and body SHALL NOT be interpreted as a subject change + +[Client] Romeo + password: password + jid: romeo@localhost + +----- + +Romeo connects + +# and creates a room +Romeo sends: + + + + +Romeo receives: + + + + + + + + +Romeo receives: + + + + +Romeo sends: + + Greetings + Hello everyone + + +Romeo receives: + + Greetings + Hello everyone + + +Romeo sends: + + Something to talk about + + +Romeo receives: + + Something to talk about + + +Romeo sends: + + + + +Romeo receives: + + + + + + + +# These have delay tags but we ignore those for now +Romeo receives: + + Greetings + Hello everyone + + +Romeo receives: + + Something to talk about + + +Romeo disconnects + -- cgit v1.2.3 From c899c8d357a046e72d4e88c2e05a893c34af2650 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 8 Dec 2018 16:35:00 +0100 Subject: moduleapi: Use pack from util.table --- core/moduleapi.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/moduleapi.lua b/core/moduleapi.lua index 10f9f04d..d2aa1e8c 100644 --- a/core/moduleapi.lua +++ b/core/moduleapi.lua @@ -20,7 +20,7 @@ local error, setmetatable, type = error, setmetatable, type; local ipairs, pairs, select = ipairs, pairs, select; local tonumber, tostring = tonumber, tostring; local require = require; -local pack = table.pack or function(...) return {n=select("#",...), ...}; end -- table.pack is only in 5.2 +local pack = table.pack or require "util.table".pack; -- table.pack is only in 5.2 local unpack = table.unpack or unpack; --luacheck: ignore 113 -- renamed in 5.2 local prosody = prosody; -- cgit v1.2.3 From 01deb521fd669fab333f5af103f176122fa225e2 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 8 Dec 2018 16:35:39 +0100 Subject: util.format: Use pack from util.table --- util/format.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/util/format.lua b/util/format.lua index 16c57bc6..6c46384a 100644 --- a/util/format.lua +++ b/util/format.lua @@ -3,12 +3,13 @@ -- local tostring = tostring; -local select = select; local unpack = table.unpack or unpack; -- luacheck: ignore 113/unpack +local pack = require "util.table".pack; -- TODO table.pack in 5.2+ local type = type; local function format(formatstring, ...) - local args, args_length = { ... }, select('#', ...); + local args = pack(...); + local args_length = args.n; -- format specifier spec: -- 1. Start: '%%' -- cgit v1.2.3 From 76d4ce39f52ce13fc9676296fe7fbef0d6424a32 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 8 Dec 2018 16:36:05 +0100 Subject: util.iterators: Use pack from table.pack --- util/iterators.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/util/iterators.lua b/util/iterators.lua index 302cca36..c03c2fd6 100644 --- a/util/iterators.lua +++ b/util/iterators.lua @@ -11,9 +11,9 @@ local it = {}; local t_insert = table.insert; -local select, next = select, next; -local unpack = table.unpack or unpack; --luacheck: ignore 113 143 -local pack = table.pack or function (...) return { n = select("#", ...), ... }; end -- luacheck: ignore 143 +local next = next; +local unpack = table.unpack or unpack; --luacheck: ignore 113 +local pack = table.pack or require "util.table".pack; local type = type; local table, setmetatable = table, setmetatable; -- cgit v1.2.3 From fa9d2ec96308906179bf39bff1442662b1beff16 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 8 Dec 2018 17:00:30 +0100 Subject: luacheckrc: Set Lua standard to 5.3 with 5.2 compat enabled --- .luacheckrc | 1 + 1 file changed, 1 insertion(+) diff --git a/.luacheckrc b/.luacheckrc index ce3d377b..3192768c 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -2,6 +2,7 @@ cache = true codes = true ignore = { "411/err", "421/err", "411/ok", "421/ok", "211/_ENV", "431/log", "143/table", "113/unpack" } +std = "lua53c" max_line_length = 150 read_globals = { -- cgit v1.2.3 From 2d56ac0394838f6fe199566bd5a8f2be15c27a8e Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 8 Dec 2018 17:07:28 +0100 Subject: lint: No longer ignore access to the deprecated global 'unpack' _G.unpack is deprecated in Lua 5.2 --- .luacheckrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.luacheckrc b/.luacheckrc index 3192768c..96882c73 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -1,6 +1,6 @@ cache = true codes = true -ignore = { "411/err", "421/err", "411/ok", "421/ok", "211/_ENV", "431/log", "143/table", "113/unpack" } +ignore = { "411/err", "421/err", "411/ok", "421/ok", "211/_ENV", "431/log", "143/table", } std = "lua53c" max_line_length = 150 -- cgit v1.2.3 From 2b289f34f929a69424a22bb0de3b668a58ba80cd Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 8 Dec 2018 17:09:55 +0100 Subject: various: Don't rely on _G.unpack existing --- net/resolvers/basic.lua | 1 + net/resolvers/manual.lua | 1 + net/resolvers/service.lua | 1 + net/websocket/frames.lua | 1 + plugins/mod_admin_telnet.lua | 1 + plugins/mod_pep_simple.lua | 1 + plugins/mod_storage_sql.lua | 2 +- spec/core_storagemanager_spec.lua | 2 +- 8 files changed, 8 insertions(+), 2 deletions(-) diff --git a/net/resolvers/basic.lua b/net/resolvers/basic.lua index 9a3c9952..56f9c77d 100644 --- a/net/resolvers/basic.lua +++ b/net/resolvers/basic.lua @@ -1,5 +1,6 @@ local adns = require "net.adns"; local inet_pton = require "util.net".pton; +local unpack = table.unpack or unpack; -- luacheck: ignore 113 local methods = {}; local resolver_mt = { __index = methods }; diff --git a/net/resolvers/manual.lua b/net/resolvers/manual.lua index c0d4e5d5..dbc40256 100644 --- a/net/resolvers/manual.lua +++ b/net/resolvers/manual.lua @@ -1,5 +1,6 @@ local methods = {}; local resolver_mt = { __index = methods }; +local unpack = table.unpack or unpack; -- luacheck: ignore 113 -- Find the next target to connect to, and -- pass it to cb() diff --git a/net/resolvers/service.lua b/net/resolvers/service.lua index b5a2d821..d1b8556c 100644 --- a/net/resolvers/service.lua +++ b/net/resolvers/service.lua @@ -1,5 +1,6 @@ local adns = require "net.adns"; local basic = require "net.resolvers.basic"; +local unpack = table.unpack or unpack; -- luacheck: ignore 113 local methods = {}; local resolver_mt = { __index = methods }; diff --git a/net/websocket/frames.lua b/net/websocket/frames.lua index b5aebb40..c3333020 100644 --- a/net/websocket/frames.lua +++ b/net/websocket/frames.lua @@ -16,6 +16,7 @@ local bor = bit.bor; local bxor = bit.bxor; local lshift = bit.lshift; local rshift = bit.rshift; +local unpack = table.unpack or unpack; -- luacheck: ignore 113 local t_concat = table.concat; local s_byte = string.byte; diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 1cbe27a4..8a3508db 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -22,6 +22,7 @@ local prosody = _G.prosody; local console_listener = { default_port = 5582; default_mode = "*a"; interface = "127.0.0.1" }; +local unpack = table.unpack or unpack; -- luacheck: ignore 113 local iterators = require "util.iterators"; local keys, values = iterators.keys, iterators.values; local jid_bare, jid_split, jid_join = import("util.jid", "bare", "prepped_split", "join"); diff --git a/plugins/mod_pep_simple.lua b/plugins/mod_pep_simple.lua index f0b5d7ef..f91e5448 100644 --- a/plugins/mod_pep_simple.lua +++ b/plugins/mod_pep_simple.lua @@ -14,6 +14,7 @@ local is_contact_subscribed = require "core.rostermanager".is_contact_subscribed local pairs = pairs; local next = next; local type = type; +local unpack = table.unpack or unpack; -- luacheck: ignore 113 local calculate_hash = require "util.caps".calculate_hash; local core_post_stanza = prosody.core_post_stanza; local bare_sessions = prosody.bare_sessions; diff --git a/plugins/mod_storage_sql.lua b/plugins/mod_storage_sql.lua index 56cef569..5c0c0208 100644 --- a/plugins/mod_storage_sql.lua +++ b/plugins/mod_storage_sql.lua @@ -11,7 +11,7 @@ local is_stanza = require"util.stanza".is_stanza; local t_concat = table.concat; local noop = function() end -local unpack = table.unpack or unpack; +local unpack = table.unpack or unpack; -- luacheck: ignore 113 local function iterator(result) return function(result_) local row = result_(); diff --git a/spec/core_storagemanager_spec.lua b/spec/core_storagemanager_spec.lua index a0a8b5ef..fd2f8742 100644 --- a/spec/core_storagemanager_spec.lua +++ b/spec/core_storagemanager_spec.lua @@ -1,4 +1,4 @@ -local unpack = table.unpack or unpack; +local unpack = table.unpack or unpack; -- luacheck: ignore 113 local server = require "net.server_select"; package.loaded["net.server"] = server; -- cgit v1.2.3 From 5a608450d505944cbac268f28e48751c8fa3ee10 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 8 Dec 2018 17:10:51 +0100 Subject: lint: Remove use of the 143 error code Does not appear to be invoked by anything --- net/websocket/frames.lua | 4 ++-- util/import.lua | 2 +- util/multitable.lua | 2 +- util/serialization.lua | 1 - 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/net/websocket/frames.lua b/net/websocket/frames.lua index c3333020..86752109 100644 --- a/net/websocket/frames.lua +++ b/net/websocket/frames.lua @@ -22,8 +22,8 @@ local t_concat = table.concat; local s_byte = string.byte; local s_char= string.char; local s_sub = string.sub; -local s_pack = string.pack; -- luacheck: ignore 143 -local s_unpack = string.unpack; -- luacheck: ignore 143 +local s_pack = string.pack; +local s_unpack = string.unpack; if not s_pack and softreq"struct" then s_pack = softreq"struct".pack; diff --git a/util/import.lua b/util/import.lua index 8ecfe43c..1007bc0a 100644 --- a/util/import.lua +++ b/util/import.lua @@ -8,7 +8,7 @@ -local unpack = table.unpack or unpack; --luacheck: ignore 113 143 +local unpack = table.unpack or unpack; --luacheck: ignore 113 local t_insert = table.insert; function _G.import(module, ...) local m = package.loaded[module] or require(module); diff --git a/util/multitable.lua b/util/multitable.lua index 8d32ed8a..4f2cd972 100644 --- a/util/multitable.lua +++ b/util/multitable.lua @@ -9,7 +9,7 @@ local select = select; local t_insert = table.insert; local pairs, next, type = pairs, next, type; -local unpack = table.unpack or unpack; --luacheck: ignore 113 143 +local unpack = table.unpack or unpack; --luacheck: ignore 113 local _ENV = nil; -- luacheck: std none diff --git a/util/serialization.lua b/util/serialization.lua index dd6a2a2b..7ae77a3a 100644 --- a/util/serialization.lua +++ b/util/serialization.lua @@ -20,7 +20,6 @@ local pcall = pcall; local envload = require"util.envload".envload; local pos_inf, neg_inf = math.huge, -math.huge; --- luacheck: ignore 143/math local m_type = math.type or function (n) return n % 1 == 0 and n <= 9007199254740992 and n >= -9007199254740992 and "integer" or "float"; end; -- cgit v1.2.3 From 177420df39c60de47eb47bc5ed574e2ccf082ec4 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 12 Oct 2018 01:29:34 +0200 Subject: util.format: Serialize values for the %q format Improves eg debug logs --- spec/util_format_spec.lua | 1 + util/format.lua | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/spec/util_format_spec.lua b/spec/util_format_spec.lua index 8a2e9312..b9652d19 100644 --- a/spec/util_format_spec.lua +++ b/spec/util_format_spec.lua @@ -11,6 +11,7 @@ describe("util.format", function() assert.equal("true", format("%s", true)); assert.equal("[true]", format("%d", true)); assert.equal("% [true]", format("%%", true)); + assert.equal("{ }", format("%q", { })); end); end); end); diff --git a/util/format.lua b/util/format.lua index 6c46384a..c31f599f 100644 --- a/util/format.lua +++ b/util/format.lua @@ -6,6 +6,7 @@ local tostring = tostring; local unpack = table.unpack or unpack; -- luacheck: ignore 113/unpack local pack = require "util.table".pack; -- TODO table.pack in 5.2+ local type = type; +local dump = require "util.serialization".new("debug"); local function format(formatstring, ...) local args = pack(...); @@ -34,7 +35,10 @@ local function format(formatstring, ...) if arg == nil then args[i] = "nil"; spec = "<%s>"; - elseif option == "q" or option == "s" then -- arg should be string + elseif option == "q" then + args[i] = dump(arg); + spec = "%s"; + elseif option == "s" then args[i] = tostring(arg); elseif type(arg) ~= "number" then -- arg isn't number as expected? args[i] = tostring(arg); -- cgit v1.2.3 From 5e2c950296e59fc524cbe9a943d8b2b3b85ca22a Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 8 Dec 2018 17:13:39 +0100 Subject: luacheckrc: No longer ignore access to undefined fields on table lib --- .luacheckrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.luacheckrc b/.luacheckrc index 96882c73..3e3fb2b5 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -1,6 +1,6 @@ cache = true codes = true -ignore = { "411/err", "421/err", "411/ok", "421/ok", "211/_ENV", "431/log", "143/table", } +ignore = { "411/err", "421/err", "411/ok", "421/ok", "211/_ENV", "431/log", } std = "lua53c" max_line_length = 150 -- cgit v1.2.3 From 726a7996dd944551c5a4007872ae06dd7f3facae Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 8 Dec 2018 21:17:39 +0100 Subject: net.server_epoll: Call onconnect right after accept()ing a new client --- net/server_epoll.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index 13c8315a..3088b55b 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -577,6 +577,8 @@ function interface:onacceptable() client:init(); if self.tls_direct then client:starttls(self.tls_ctx); + else + client:onconnect(); end end -- cgit v1.2.3 From e6e285898bd7dab34cf8c4c0ac5a748334f65ff0 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 8 Dec 2018 21:28:48 +0100 Subject: net.server_epoll: Bail on callback error An error calling a callback would be considered a truthy return value, which is not right. --- net/server_epoll.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index 3088b55b..b2165b1d 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -180,6 +180,7 @@ function interface:on(what, ...) local ok, err = pcall(listener, self, ...); if not ok then log("error", "Error calling on%s: %s", what, err); + return; end return err; end -- cgit v1.2.3 From 619990cf1f9ce75c60252b54da31ba7597fe57b8 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 9 Dec 2018 20:53:33 +0100 Subject: net.connlisteners: Remove deprecated stub module This was deprecated in 0.9.x Removing so auto-completion chooses net/connect.lua instead of net/conn --- net/connlisteners.lua | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 net/connlisteners.lua diff --git a/net/connlisteners.lua b/net/connlisteners.lua deleted file mode 100644 index 9b8f88c3..00000000 --- a/net/connlisteners.lua +++ /dev/null @@ -1,18 +0,0 @@ --- COMPAT w/pre-0.9 -local log = require "util.logger".init("net.connlisteners"); -local traceback = debug.traceback; - -local _ENV = nil; --- luacheck: std none - -local function fail() - log("error", "Attempt to use legacy connlisteners API. For more info see https://prosody.im/doc/developers/network"); - log("error", "Legacy connlisteners API usage, %s", traceback("", 2)); -end - -return { - register = fail; - get = fail; - start = fail; - -- epic fail -}; -- cgit v1.2.3 From b1c3c4bc382df869fab3783a1ba35261e81420a6 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 11 Dec 2018 23:24:14 +0100 Subject: spec/scansion/prosody.cfg.lua: Update a comment from prosody.cfg.lua.dist for easier comparisons --- spec/scansion/prosody.cfg.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/scansion/prosody.cfg.lua b/spec/scansion/prosody.cfg.lua index 94861449..170371e1 100644 --- a/spec/scansion/prosody.cfg.lua +++ b/spec/scansion/prosody.cfg.lua @@ -14,7 +14,7 @@ modules_enabled = { -- Not essential, but recommended "carbons"; -- Keep multiple clients in sync - "pep"; -- Enables users to publish their mood, activity, playing music and more + "pep"; -- Enables users to publish their avatar, mood, activity, playing music and more "private"; -- Private XML storage (for room bookmarks, etc.) "blocklist"; -- Allow users to block communications with other users "vcard"; -- Allow users to set vCards -- cgit v1.2.3 From 35c3393bca3c7ce6f64ef22f0c2bfa133e4367e1 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 11 Dec 2018 23:25:16 +0100 Subject: spec/scansion/prosody.cfg.lua: Replace mod_vcard with mod_vcard4 and mod_vcard_legacy as in default config --- spec/scansion/prosody.cfg.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/scansion/prosody.cfg.lua b/spec/scansion/prosody.cfg.lua index 170371e1..e8872bff 100644 --- a/spec/scansion/prosody.cfg.lua +++ b/spec/scansion/prosody.cfg.lua @@ -17,7 +17,8 @@ modules_enabled = { "pep"; -- Enables users to publish their avatar, mood, activity, playing music and more "private"; -- Private XML storage (for room bookmarks, etc.) "blocklist"; -- Allow users to block communications with other users - "vcard"; -- Allow users to set vCards + "vcard4"; -- User profiles (stored in PEP) + "vcard_legacy"; -- Conversion between legacy vCard and PEP Avatar, vcard -- Nice to have "version"; -- Replies to server version requests -- cgit v1.2.3 From a270e6d5c8798dfad2fcd624f7ebb34ec72238a2 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 11 Dec 2018 23:26:16 +0100 Subject: spec/scansion/prosody.cfg.lua: Add remaining modules listened in prosody.cfg.lua.dist for easier comparisons --- spec/scansion/prosody.cfg.lua | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/spec/scansion/prosody.cfg.lua b/spec/scansion/prosody.cfg.lua index e8872bff..8d6e7c0a 100644 --- a/spec/scansion/prosody.cfg.lua +++ b/spec/scansion/prosody.cfg.lua @@ -27,6 +27,11 @@ modules_enabled = { "ping"; -- Replies to XMPP pings with pongs "register"; -- Allow users to register on this server using a client and change passwords --"mam"; -- Store messages in an archive and allow users to access it + --"csi_simple"; -- Simple Mobile optimizations + + -- Admin interfaces + --"admin_adhoc"; -- Allows administration via an XMPP client that supports ad-hoc commands + --"admin_telnet"; -- Opens telnet console interface on localhost port 5582 -- HTTP modules --"bosh"; -- Enable BOSH clients, aka "Jabber over HTTP" -- cgit v1.2.3 From 149e748141ceef12a3fff9ba985a880b7e09c568 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 16 Dec 2018 02:56:11 +0100 Subject: core.rostermanager: Cache rosters of offline users for faster access (fixes #1233) --- core/rostermanager.lua | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/core/rostermanager.lua b/core/rostermanager.lua index 2d616e4b..d551a1b1 100644 --- a/core/rostermanager.lua +++ b/core/rostermanager.lua @@ -12,6 +12,7 @@ local log = require "util.logger".init("rostermanager"); local new_id = require "util.id".short; +local new_cache = require "util.cache".new; local pairs = pairs; local tostring = tostring; @@ -111,6 +112,23 @@ local function load_roster(username, host) else -- Attempt to load roster for non-loaded user log("debug", "load_roster: loading for offline user: %s", jid); end + local roster_cache = hosts[host] and hosts[host].roster_cache; + if not roster_cache then + if hosts[host] then + roster_cache = new_cache(1024); + hosts[host].roster_cache = roster_cache; + end + else + roster = roster_cache:get(jid); + if roster then + log("debug", "load_roster: cache hit"); + roster_cache:set(jid, roster); + if user then user.roster = roster; end + return roster; + else + log("debug", "load_roster: cache miss, loading from storage"); + end + end local roster_store = storagemanager.open(host, "roster", "keyval"); local data, err = roster_store:get(username); roster = data or {}; @@ -134,6 +152,10 @@ local function load_roster(username, host) if not err then hosts[host].events.fire_event("roster-load", { username = username, host = host, roster = roster }); end + if roster_cache and not user then + log("debug", "load_roster: caching loaded roster"); + roster_cache:set(jid, roster); + end return roster, err; end -- cgit v1.2.3 From 09d88cefa5738c1ec7a60c50c73685c2378335d4 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 16 Dec 2018 22:49:58 +0100 Subject: MUC: Add another message to #667 test --- spec/scansion/muc_subject_issue_667.scs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/spec/scansion/muc_subject_issue_667.scs b/spec/scansion/muc_subject_issue_667.scs index 68f4c17a..859a0bfd 100644 --- a/spec/scansion/muc_subject_issue_667.scs +++ b/spec/scansion/muc_subject_issue_667.scs @@ -50,6 +50,16 @@ Romeo receives: Something to talk about +Romeo sends: + + Lorem ipsum dolor sit amet + + +Romeo receives: + + Lorem ipsum dolor sit amet + + Romeo sends: @@ -70,6 +80,11 @@ Romeo receives: Hello everyone +Romeo receives: + + Lorem ipsum dolor sit amet + + Romeo receives: Something to talk about -- cgit v1.2.3 From 826c511cac78d521e6f336a658f50d66cb10d78a Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 16 Dec 2018 22:53:56 +0100 Subject: MUC: Add descriptive comments to #667 test --- spec/scansion/muc_subject_issue_667.scs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/spec/scansion/muc_subject_issue_667.scs b/spec/scansion/muc_subject_issue_667.scs index 859a0bfd..417f957a 100644 --- a/spec/scansion/muc_subject_issue_667.scs +++ b/spec/scansion/muc_subject_issue_667.scs @@ -23,11 +23,13 @@ Romeo receives: +# the default (empty) subject Romeo receives: +# this should be treated as a normal message Romeo sends: Greetings @@ -40,6 +42,7 @@ Romeo receives: Hello everyone +# this is a subject change Romeo sends: Something to talk about @@ -50,6 +53,7 @@ Romeo receives: Something to talk about +# a message without Romeo sends: Lorem ipsum dolor sit amet @@ -60,11 +64,13 @@ Romeo receives: Lorem ipsum dolor sit amet +# Resync Romeo sends: +# Presences Romeo receives: @@ -73,6 +79,7 @@ Romeo receives: +# History # These have delay tags but we ignore those for now Romeo receives: @@ -85,6 +92,7 @@ Romeo receives: Lorem ipsum dolor sit amet +# Finally, the topic Romeo receives: Something to talk about -- cgit v1.2.3 From 24a020bbaa7d9d99d26330e2639f9249511f84cd Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 16 Dec 2018 22:59:14 +0100 Subject: MUC: Test that subject is still empty after sending a non-subject change message with a subject (#667) --- spec/scansion/muc_subject_issue_667.scs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/spec/scansion/muc_subject_issue_667.scs b/spec/scansion/muc_subject_issue_667.scs index 417f957a..74980073 100644 --- a/spec/scansion/muc_subject_issue_667.scs +++ b/spec/scansion/muc_subject_issue_667.scs @@ -42,6 +42,33 @@ Romeo receives: Hello everyone +# Resync +Romeo sends: + + + + +# Presences +Romeo receives: + + + + + + + +Romeo receives: + + Greetings + Hello everyone + + +# the still empty subject +Romeo receives: + + + + # this is a subject change Romeo sends: -- cgit v1.2.3 From 738a1171dc1415544b2289591578670333250d9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20=E2=80=9Cpep=E2=80=9D=20Buquet?= Date: Tue, 18 Dec 2018 20:23:33 +0000 Subject: admin_telnet: show when bidi is used on s2s --- plugins/mod_admin_telnet.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 8a3508db..63136d63 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -521,6 +521,9 @@ local function session_flags(session, line) if session.remote then line[#line+1] = "(remote)"; end + if session.is_bidi then + line[#line+1] = "(bidi)"; + end return table.concat(line, " "); end -- cgit v1.2.3 From 5b8df5ea61bf95400c856a65a9e7e24c45bbc17b Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 23 Dec 2018 02:50:22 +0100 Subject: mod_pubsub: Add semicolon (code style) --- plugins/mod_pubsub/mod_pubsub.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_pubsub/mod_pubsub.lua b/plugins/mod_pubsub/mod_pubsub.lua index 40adcafe..1edc721b 100644 --- a/plugins/mod_pubsub/mod_pubsub.lua +++ b/plugins/mod_pubsub/mod_pubsub.lua @@ -73,7 +73,7 @@ function simple_broadcast(kind, node, jids, item, actor, node_obj) local msg_type = node_obj and node_obj.config.message_type or "headline"; local message = st.message({ from = module.host, type = msg_type, id = id }) :tag("event", { xmlns = xmlns_pubsub_event }) - :tag(kind, { node = node }) + :tag(kind, { node = node }); if item then message:add_child(item); -- cgit v1.2.3 From 41426ee8d8bb478eb08840412359e4f1e1464832 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 23 Dec 2018 02:53:12 +0100 Subject: mod_pep: Move broadcaster code around to be more like in mod_pubsub This eases comparing and contrasting these two modules. --- plugins/mod_pep.lua | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/plugins/mod_pep.lua b/plugins/mod_pep.lua index cb775fb5..40385616 100644 --- a/plugins/mod_pep.lua +++ b/plugins/mod_pep.lua @@ -136,9 +136,6 @@ local function get_broadcaster(username) if kind == "retract" then kind = "items"; -- XEP-0060 signals retraction in an container end - local message = st.message({ from = user_bare, type = "headline" }) - :tag("event", { xmlns = xmlns_pubsub_event }) - :tag(kind, { node = node }); if item then item = st.clone(item); item.attr.xmlns = nil; -- Clear the pubsub namespace @@ -147,6 +144,12 @@ local function get_broadcaster(username) item:maptags(function () return nil; end); end end + end + + local message = st.message({ from = user_bare, type = "headline" }) + :tag("event", { xmlns = xmlns_pubsub_event }) + :tag(kind, { node = node }); + if item then message:add_child(item); end for jid in pairs(jids) do -- cgit v1.2.3 From aef3d7a500f54e8d44303b082ade3f5a14883efd Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 23 Dec 2018 02:54:39 +0100 Subject: mod_pep: Add some spacing between blocks in broadcaster to improve readability --- plugins/mod_pep.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/mod_pep.lua b/plugins/mod_pep.lua index 40385616..6275d47c 100644 --- a/plugins/mod_pep.lua +++ b/plugins/mod_pep.lua @@ -136,6 +136,7 @@ local function get_broadcaster(username) if kind == "retract" then kind = "items"; -- XEP-0060 signals retraction in an container end + if item then item = st.clone(item); item.attr.xmlns = nil; -- Clear the pubsub namespace @@ -149,9 +150,11 @@ local function get_broadcaster(username) local message = st.message({ from = user_bare, type = "headline" }) :tag("event", { xmlns = xmlns_pubsub_event }) :tag(kind, { node = node }); + if item then message:add_child(item); end + for jid in pairs(jids) do module:log("debug", "Sending notification to %s from %s: %s", jid, user_bare, tostring(item)); message.attr.to = jid; -- cgit v1.2.3 From 6eb576e9d9282dc9cb27b0a44472183280abaafd Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 23 Dec 2018 02:56:10 +0100 Subject: mod_pep: Set an 'id' on notifications mod_pubsub got this in f2d35eee69c9 --- plugins/mod_pep.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/mod_pep.lua b/plugins/mod_pep.lua index 6275d47c..5e3f43f2 100644 --- a/plugins/mod_pep.lua +++ b/plugins/mod_pep.lua @@ -8,6 +8,7 @@ local calculate_hash = require "util.caps".calculate_hash; local is_contact_subscribed = require "core.rostermanager".is_contact_subscribed; local cache = require "util.cache"; local set = require "util.set"; +local new_id = require "util.id".medium; local xmlns_pubsub = "http://jabber.org/protocol/pubsub"; local xmlns_pubsub_event = "http://jabber.org/protocol/pubsub#event"; @@ -147,7 +148,8 @@ local function get_broadcaster(username) end end - local message = st.message({ from = user_bare, type = "headline" }) + local id = new_id(); + local message = st.message({ from = user_bare, type = "headline", id = id }) :tag("event", { xmlns = xmlns_pubsub_event }) :tag(kind, { node = node }); -- cgit v1.2.3 From 27112c0d94020fc1e24ed2b8c1673042f7a02798 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 23 Dec 2018 03:05:58 +0100 Subject: mod_pubsub: Change order of luacheck directives to match arguments they apply to --- plugins/mod_pubsub/mod_pubsub.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_pubsub/mod_pubsub.lua b/plugins/mod_pubsub/mod_pubsub.lua index 1edc721b..abc4fee8 100644 --- a/plugins/mod_pubsub/mod_pubsub.lua +++ b/plugins/mod_pubsub/mod_pubsub.lua @@ -99,7 +99,7 @@ function simple_broadcast(kind, node, jids, item, actor, node_obj) end local max_max_items = module:get_option_number("pubsub_max_items", 256); -function check_node_config(node, actor, new_config) -- luacheck: ignore 212/actor 212/node +function check_node_config(node, actor, new_config) -- luacheck: ignore 212/node 212/actor if (new_config["max_items"] or 1) > max_max_items then return false; end -- cgit v1.2.3 From 1900ae8261698d59245f589289b88a384bf743cb Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 23 Dec 2018 03:06:35 +0100 Subject: mod_pubsub: Split line in config check to improve readability Also makes it easier to compare with mod_pep --- plugins/mod_pubsub/mod_pubsub.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/mod_pubsub/mod_pubsub.lua b/plugins/mod_pubsub/mod_pubsub.lua index abc4fee8..0036b48f 100644 --- a/plugins/mod_pubsub/mod_pubsub.lua +++ b/plugins/mod_pubsub/mod_pubsub.lua @@ -103,7 +103,8 @@ function check_node_config(node, actor, new_config) -- luacheck: ignore 212/node if (new_config["max_items"] or 1) > max_max_items then return false; end - if new_config["access_model"] ~= "whitelist" and new_config["access_model"] ~= "open" then + if new_config["access_model"] ~= "whitelist" + and new_config["access_model"] ~= "open" then return false; end return true; -- cgit v1.2.3 From b90dce4c204a7fd58ec00361cd75546ea32d585b Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 23 Dec 2018 14:52:52 +0100 Subject: util.table: Add test for pack() --- spec/util_table_spec.lua | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 spec/util_table_spec.lua diff --git a/spec/util_table_spec.lua b/spec/util_table_spec.lua new file mode 100644 index 00000000..97266fe2 --- /dev/null +++ b/spec/util_table_spec.lua @@ -0,0 +1,10 @@ +local u_table = require "util.table"; +describe("util.table", function () + describe("pack()", function () + it("works", function () + assert.same({ "lorem", "ipsum", "dolor", "sit", "amet", n = 5 }, u_table.pack("lorem", "ipsum", "dolor", "sit", "amet")); + end); + end); +end); + + -- cgit v1.2.3 From 9ff2b47bcc4a5c1c026ecdba8fc3b6c818af183e Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 23 Dec 2018 15:01:37 +0100 Subject: util.table: Add test for create() --- spec/util_table_spec.lua | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/spec/util_table_spec.lua b/spec/util_table_spec.lua index 97266fe2..76f54b69 100644 --- a/spec/util_table_spec.lua +++ b/spec/util_table_spec.lua @@ -1,5 +1,12 @@ local u_table = require "util.table"; describe("util.table", function () + describe("create()", function () + it("works", function () + -- Can't test the allocated sizes of the table, so what you gonna do? + assert.is.table(u_table.create(1,1)); + end); + end); + describe("pack()", function () it("works", function () assert.same({ "lorem", "ipsum", "dolor", "sit", "amet", n = 5 }, u_table.pack("lorem", "ipsum", "dolor", "sit", "amet")); -- cgit v1.2.3 From 4da406588e5177c0b663f2658336888b29795d13 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 24 Dec 2018 03:00:27 +0100 Subject: net.adns: Silence individual luacheck warnings instead of ignoring entire file --- .luacheckrc | 1 - net/adns.lua | 16 +++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/.luacheckrc b/.luacheckrc index 3e3fb2b5..5035f446 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -132,7 +132,6 @@ if os.getenv("PROSODY_STRICT_LINT") ~= "1" then "fallbacks/bit.lua"; "fallbacks/lxp.lua"; - "net/adns.lua"; "net/cqueues.lua"; "net/dns.lua"; "net/server_select.lua"; diff --git a/net/adns.lua b/net/adns.lua index 560e4b53..4fa01f8a 100644 --- a/net/adns.lua +++ b/net/adns.lua @@ -14,7 +14,7 @@ local log = require "util.logger".init("adns"); local coroutine, tostring, pcall = coroutine, tostring, pcall; local setmetatable = setmetatable; -local function dummy_send(sock, data, i, j) return (j-i)+1; end +local function dummy_send(sock, data, i, j) return (j-i)+1; end -- luacheck: ignore 212 local _ENV = nil; -- luacheck: std none @@ -29,8 +29,7 @@ local function new_async_socket(sock, resolver) local peername = ""; local listener = {}; local handler = {}; - local err; - function listener.onincoming(conn, data) + function listener.onincoming(conn, data) -- luacheck: ignore 212/conn if data then resolver:feed(handler, data); end @@ -46,9 +45,12 @@ local function new_async_socket(sock, resolver) resolver:servfail(conn); -- Let the magic commence end end - handler, err = server.wrapclient(sock, "dns", 53, listener); - if not handler then - return nil, err; + do + local err; + handler, err = server.wrapclient(sock, "dns", 53, listener); + if not handler then + return nil, err; + end end handler.settimeout = function () end @@ -89,7 +91,7 @@ function async_resolver_methods:lookup(handler, qname, qtype, qclass) end)(resolver:peek(qname, qtype, qclass)); end -function query_methods:cancel(call_handler, reason) +function query_methods:cancel(call_handler, reason) -- luacheck: ignore 212/reason log("warn", "Cancelling DNS lookup for %s", tostring(self[4])); self[1].cancel(self[2], self[3], self[4], self[5], call_handler); end -- cgit v1.2.3 From 20429527b1c455e2c6784d717c48d69e80b1138b Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 28 Dec 2018 20:49:01 +0100 Subject: util.stanza: Require a type attribute for iq stanzas --- spec/util_stanza_spec.lua | 19 +++++++++++++++---- util/stanza.lua | 8 +++++++- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/spec/util_stanza_spec.lua b/spec/util_stanza_spec.lua index 6fbae41a..18e39554 100644 --- a/spec/util_stanza_spec.lua +++ b/spec/util_stanza_spec.lua @@ -95,20 +95,31 @@ describe("util.stanza", function() describe("#iq()", function() it("should create an iq stanza", function() - local i = st.iq({ id = "foo" }); + local i = st.iq({ type = "get", id = "foo" }); assert.are.equal("iq", i.name); assert.are.equal("foo", i.attr.id); + assert.are.equal("get", i.attr.type); end); - it("should reject stanzas with no id", function () + it("should reject stanzas with no attributes", function () assert.has.error_match(function () st.iq(); - end, "id attribute"); + end, "attributes"); + end); + + it("should reject stanzas with no id", function () assert.has.error_match(function () - st.iq({ foo = "bar" }); + st.iq({ type = "get" }); end, "id attribute"); end); + + it("should reject stanzas with no type", function () + assert.has.error_match(function () + st.iq({ id = "foo" }); + end, "type attribute"); + + end); end); describe("#presence()", function () diff --git a/util/stanza.lua b/util/stanza.lua index a90d56b3..e9847ca6 100644 --- a/util/stanza.lua +++ b/util/stanza.lua @@ -423,9 +423,15 @@ local function message(attr, body) end end local function iq(attr) - if not (attr and attr.id) then + if not attr then + error("iq stanzas require id and type attributes"); + end + if not attr.id then error("iq stanzas require an id attribute"); end + if not attr.type then + error("iq stanzas require a type attribute"); + end return new_stanza("iq", attr); end -- cgit v1.2.3 From f017415defc1a3764412a1edc0759e1a4b9aeea5 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 28 Dec 2018 20:51:31 +0100 Subject: core.moduleapi: Add a promise-based API for tracking IQ stanzas (fixes #714) --- core/moduleapi.lua | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/core/moduleapi.lua b/core/moduleapi.lua index d2aa1e8c..f7aa7216 100644 --- a/core/moduleapi.lua +++ b/core/moduleapi.lua @@ -361,6 +361,71 @@ function api:send(stanza, origin) return core_post_stanza(origin or hosts[self.host], stanza); end +function api:send_iq(stanza, origin, timeout) + local iq_cache = self._iq_cache; + if not iq_cache then + iq_cache = require "util.cache".new(256, function (_, iq) + iq.reject("evicted"); + self:unhook(iq.result_event, iq.result_handler); + self:unhook(iq.error_event, iq.error_handler); + end); + self._iq_cache = iq_cache; + end + return require "util.promise".new(function (resolve, reject) + local event_type; + if stanza.attr.from == self.host then + event_type = "host"; + else -- assume bare since we can't hook full jids + event_type = "bare"; + end + local result_event = "iq-result/"..event_type.."/"..stanza.attr.id; + local error_event = "iq-error/"..event_type.."/"..stanza.attr.id; + local cache_key = event_type.."/"..stanza.attr.id; + + local function result_handler(event) + if event.stanza.attr.from == stanza.attr.to then + resolve(event); + return true; + end + end + + local function error_handler(event) + if event.stanza.attr.from == stanza.attr.to then + reject(event); + return true; + end + end + + if iq_cache:get(cache_key) then + error("choose another iq stanza id attribute") + end + + self:hook(result_event, result_handler); + self:hook(error_event, error_handler); + + local timeout_handle = self:add_timer(timeout or 120, function () + reject("timeout"); + self:unhook(result_event, result_handler); + self:unhook(error_event, error_handler); + iq_cache:set(cache_key, nil); + end); + + local ok = iq_cache:set(cache_key, { + reject = reject, resolve = resolve, + timeout_handle = timeout_handle, + result_event = result_event, error_event = error_event, + result_handler = result_handler, error_handler = error_handler; + }); + + if not ok then + reject("cache insertion failure"); + return; + end + + self:send(stanza, origin); + end); +end + function api:broadcast(jids, stanza, iter) for jid in (iter or it.values)(jids) do local new_stanza = st.clone(stanza); -- cgit v1.2.3 From 9a412b02e9ab54e2201986bae39e5c7c1d664d3d Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 28 Dec 2018 20:56:01 +0100 Subject: mod_admin_telnet: Invert host existence check Simplifies and reduces indentation --- plugins/mod_admin_telnet.lua | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 63136d63..5ba88b84 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -1067,13 +1067,12 @@ def_env.xmpp = {}; local st = require "util.stanza"; function def_env.xmpp:ping(localhost, remotehost) - if prosody.hosts[localhost] then - module:send(st.iq{ from=localhost, to=remotehost, type="get", id="ping" } - :tag("ping", {xmlns="urn:xmpp:ping"}), prosody.hosts[localhost]); - return true, "Sent ping"; - else + if not prosody.hosts[localhost] then return nil, "No such host"; end + module:send(st.iq{ from=localhost, to=remotehost, type="get", id="ping" } + :tag("ping", {xmlns="urn:xmpp:ping"}), prosody.hosts[localhost]); + return true, "Sent ping"; end def_env.dns = {}; -- cgit v1.2.3 From 851f33034886b3d25d698497139cb51bf40ed506 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 27 Dec 2018 02:53:34 +0100 Subject: mod_admin_telnet: Enable async processing using util.async --- plugins/mod_admin_telnet.lua | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 5ba88b84..bb97a09b 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -31,6 +31,7 @@ local cert_verify_identity = require "util.x509".verify_identity; local envload = require "util.envload".envload; local envloadfile = require "util.envload".envloadfile; local has_pposix, pposix = pcall(require, "util.pposix"); +local async = require "util.async"; local commands = module:shared("commands") local def_env = module:shared("env"); @@ -48,6 +49,21 @@ end console = {}; +local runner_callbacks = {}; + +function runner_callbacks:ready() + self.data.conn:resume(); +end + +function runner_callbacks:waiting() + self.data.conn:pause(); +end + +function runner_callbacks:error(err) + module:log("error", "Traceback[telnet]: %s", err); +end + + function console:new_session(conn) local w = function(s) conn:write(s:gsub("\n", "\r\n")); end; local session = { conn = conn; @@ -63,6 +79,11 @@ function console:new_session(conn) }; session.env = setmetatable({}, default_env_mt); + session.thread = async.runner(function (line) + console:process_line(session, line); + session.send(string.char(0)); + end, runner_callbacks, session); + -- Load up environment with helper objects for name, t in pairs(def_env) do if type(t) == "table" then @@ -151,8 +172,7 @@ function console_listener.onincoming(conn, data) for line in data:gmatch("[^\n]*[\n\004]") do if session.closed then return end - console:process_line(session, line); - session.send(string.char(0)); + session.thread:run(line); end session.partial_data = data:match("[^\n]+$"); end -- cgit v1.2.3 From f1f0c276bc41aa4290f06a7b308671d88ee54050 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 28 Dec 2018 20:59:10 +0100 Subject: mod_admin_telnet: Make xmpp:ping command wait and report the reply --- plugins/mod_admin_telnet.lua | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index bb97a09b..ee6a4176 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -1086,13 +1086,28 @@ end def_env.xmpp = {}; local st = require "util.stanza"; -function def_env.xmpp:ping(localhost, remotehost) +local new_id = require "util.id".medium; +function def_env.xmpp:ping(localhost, remotehost, timeout) if not prosody.hosts[localhost] then return nil, "No such host"; end - module:send(st.iq{ from=localhost, to=remotehost, type="get", id="ping" } - :tag("ping", {xmlns="urn:xmpp:ping"}), prosody.hosts[localhost]); - return true, "Sent ping"; + local iq = st.iq{ from=localhost, to=remotehost, type="get", id=new_id()} + :tag("ping", {xmlns="urn:xmpp:ping"}); + local ret, err; + local wait, done = async.waiter(); + module:context(localhost):send_iq(iq, nil, timeout) + :next(function (ret_) ret = ret_; end, + function (err_) err = err_; end) + :finally(done); + wait(); + if ret then + return true, "pong from " .. ret.stanza.attr.from; + elseif type(err) == "table" and st.is_stanza(err.stanza) then + local t, cond, text = err.stanza:get_error(); + return false, text or cond or t; + else + return false, tostring(err); + end end def_env.dns = {}; -- cgit v1.2.3 From 15d5dffa63b71c75bcba23046ac20e4a7f0e3a58 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 10 Mar 2018 19:58:41 +0100 Subject: spec: Stub tests for util.interpolation --- spec/util_interpolation_spec.lua | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 spec/util_interpolation_spec.lua diff --git a/spec/util_interpolation_spec.lua b/spec/util_interpolation_spec.lua new file mode 100644 index 00000000..88d9f844 --- /dev/null +++ b/spec/util_interpolation_spec.lua @@ -0,0 +1,17 @@ +local template = [[ +{greet!}, {name?world}! +]]; +local expect1 = [[ +Hello, WORLD! +]]; +local expect2 = [[ +Hello, world! +]]; + +describe("util.interpolation", function () + it("renders", function () + local render = require "util.interpolation".new("%b{}", string.upper); + assert.equal(expect1, render(template, { greet = "Hello", name = "world" })); + assert.equal(expect2, render(template, { greet = "Hello" })); + end); +end); -- cgit v1.2.3 From 4fd11623ddc55ce3bbdaf1984834455afef78279 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 24 Nov 2018 02:24:48 +0100 Subject: mod_saslauth: Improve log message when no SASL mechanisms offered (thanks hexa) --- plugins/mod_saslauth.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/mod_saslauth.lua b/plugins/mod_saslauth.lua index fba84ef8..ba30b9e6 100644 --- a/plugins/mod_saslauth.lua +++ b/plugins/mod_saslauth.lua @@ -275,7 +275,8 @@ module:hook("stream-features", function(event) if mechanisms[1] then features:add_child(mechanisms); elseif not next(sasl_mechanisms) then - log("warn", "No available SASL mechanisms, verify that the configured authentication module is working"); + local authmod = module:get_option_string("authentication", "internal_plain"); + log("error", "No available SASL mechanisms, verify that the configured authentication module '%s' is loaded and configured correctly", authmod); else log("warn", "All available authentication mechanisms are either disabled or not suitable for an insecure connection"); end -- cgit v1.2.3 From b9cac1a3fff4d900c66635d7e5bdcf902f52a34c Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 28 Dec 2018 00:13:03 +0100 Subject: mod_c2s: Improve log message in case there are no stream features on offer (thanks hexa) --- plugins/mod_c2s.lua | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/plugins/mod_c2s.lua b/plugins/mod_c2s.lua index 8e31a968..36e6a152 100644 --- a/plugins/mod_c2s.lua +++ b/plugins/mod_c2s.lua @@ -106,7 +106,13 @@ function stream_callbacks.streamopened(session, attr) if features.tags[1] or session.full_jid then send(features); else - (session.log or log)("warn", "No stream features to offer"); + if session.secure then + -- Normally STARTTLS would be offered + (session.log or log)("warn", "No stream features to offer on secure session. Check authentication settings."); + else + -- Here SASL should be offered + (session.log or log)("warn", "No stream features to offer on insecure session. Check encryption and security settings."); + end session:close{ condition = "undefined-condition", text = "No stream features to proceed with" }; end end -- cgit v1.2.3 From e6b7c91ebc9484c268fd5f0632abf4eb475ad7d6 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 28 Dec 2018 00:04:26 +0100 Subject: mod_tls: Keep TLS context errors and repeat them again for each session --- plugins/mod_tls.lua | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/plugins/mod_tls.lua b/plugins/mod_tls.lua index 029ddd1d..4ead60dc 100644 --- a/plugins/mod_tls.lua +++ b/plugins/mod_tls.lua @@ -35,9 +35,10 @@ local host = hosts[module.host]; local ssl_ctx_c2s, ssl_ctx_s2sout, ssl_ctx_s2sin; local ssl_cfg_c2s, ssl_cfg_s2sout, ssl_cfg_s2sin; +local err_c2s, err_s2sin, err_s2sout; function module.load() - local NULL, err = {}; + local NULL = {}; local modhost = module.host; local parent = modhost:match("%.(.*)$"); @@ -52,14 +53,14 @@ function module.load() local parent_s2s = rawgetopt(parent, "s2s_ssl") or NULL; local host_s2s = rawgetopt(modhost, "s2s_ssl") or parent_s2s; - ssl_ctx_c2s, err, ssl_cfg_c2s = create_context(host.host, "server", host_c2s, host_ssl, global_c2s); -- for incoming client connections - if not ssl_ctx_c2s then module:log("error", "Error creating context for c2s: %s", err); end + ssl_ctx_c2s, err_c2s, ssl_cfg_c2s = create_context(host.host, "server", host_c2s, host_ssl, global_c2s); -- for incoming client connections + if not ssl_ctx_c2s then module:log("error", "Error creating context for c2s: %s", err_c2s); end - ssl_ctx_s2sout, err, ssl_cfg_s2sout = create_context(host.host, "client", host_s2s, host_ssl, global_s2s); -- for outgoing server connections - if not ssl_ctx_s2sout then module:log("error", "Error creating contexts for s2sout: %s", err); end + ssl_ctx_s2sout, err_s2sout, ssl_cfg_s2sout = create_context(host.host, "client", host_s2s, host_ssl, global_s2s); -- for outgoing server connections + if not ssl_ctx_s2sout then module:log("error", "Error creating contexts for s2sout: %s", err_s2sout); end - ssl_ctx_s2sin, err, ssl_cfg_s2sin = create_context(host.host, "server", host_s2s, host_ssl, global_s2s); -- for incoming server connections - if not ssl_ctx_s2sin then module:log("error", "Error creating contexts for s2sin: %s", err); end + ssl_ctx_s2sin, err_s2sin, ssl_cfg_s2sin = create_context(host.host, "server", host_s2s, host_ssl, global_s2s); -- for incoming server connections + if not ssl_ctx_s2sin then module:log("error", "Error creating contexts for s2sin: %s", err_s2sin); end end module:hook_global("config-reloaded", module.load); @@ -74,12 +75,21 @@ local function can_do_tls(session) return session.ssl_ctx; end if session.type == "c2s_unauthed" then + if not ssl_ctx_c2s and c2s_require_encryption then + session.log("error", "No TLS context available for c2s. Earlier error was: %s", err_c2s); + end session.ssl_ctx = ssl_ctx_c2s; session.ssl_cfg = ssl_cfg_c2s; elseif session.type == "s2sin_unauthed" and allow_s2s_tls then + if not ssl_ctx_s2sin and s2s_require_encryption then + session.log("error", "No TLS context available for s2sin. Earlier error was: %s", err_s2sin); + end session.ssl_ctx = ssl_ctx_s2sin; session.ssl_cfg = ssl_cfg_s2sin; elseif session.direction == "outgoing" and allow_s2s_tls then + if not ssl_ctx_s2sout and s2s_require_encryption then + session.log("error", "No TLS context available for s2sout. Earlier error was: %s", err_s2sout); + end session.ssl_ctx = ssl_ctx_s2sout; session.ssl_cfg = ssl_cfg_s2sout; else -- cgit v1.2.3 From 5eb327274aa1ab27ec45b49e419943c264bd237d Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 29 Dec 2018 03:21:13 +0100 Subject: mod_admin_telnet: Validate hostnames in xmpp:ping command Attempt to ping some invalid hostnames cause weird behavior --- plugins/mod_admin_telnet.lua | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index ee6a4176..f3731c8a 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -1088,8 +1088,17 @@ def_env.xmpp = {}; local st = require "util.stanza"; local new_id = require "util.id".medium; function def_env.xmpp:ping(localhost, remotehost, timeout) - if not prosody.hosts[localhost] then - return nil, "No such host"; + localhost = select(2, jid_split(localhost)); + remotehost = select(2, jid_split(remotehost)); + if not localhost then + return nil, "Invalid sender hostname"; + elseif not prosody.hosts[localhost] then + return nil, "No such local host"; + end + if not remotehost then + return nil, "Invalid destination hostname"; + elseif prosody.hosts[remotehost] then + return nil, "Both hosts are local"; end local iq = st.iq{ from=localhost, to=remotehost, type="get", id=new_id()} :tag("ping", {xmlns="urn:xmpp:ping"}); -- cgit v1.2.3 From 92445d93df5e5f7d6933c706dd8e60f4e54e0d7c Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 30 Dec 2018 03:20:37 +0100 Subject: luacheckrc: Teach luacheck about the new module:send_iq() API --- .luacheckrc | 1 + 1 file changed, 1 insertion(+) diff --git a/.luacheckrc b/.luacheckrc index 5035f446..b2fa7cdb 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -80,6 +80,7 @@ files["plugins/"] = { "module.remove_item", "module.require", "module.send", + "module.send_iq", "module.set_global", "module.shared", "module.unhook", -- cgit v1.2.3 From 2d28fb93b4a1cf562cd5eb314279e0bb7e349499 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 30 Dec 2018 03:24:54 +0100 Subject: util.promise: Remove references to callbacks after settling promise This is to help the garbage collector. --- util/promise.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/util/promise.lua b/util/promise.lua index 07c9c4dc..0b182b54 100644 --- a/util/promise.lua +++ b/util/promise.lua @@ -49,6 +49,9 @@ local function promise_settle(promise, new_state, new_next, cbs, value) for _, cb in ipairs(cbs) do cb(value); end + -- No need to keep references to callbacks + promise._pending_on_fulfilled = nil; + promise._pending_on_rejected = nil; return true; end -- cgit v1.2.3 From 464121c5b7092f2521d21be390b01173e28fbd00 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Sun, 30 Dec 2018 12:55:58 +0000 Subject: util.error: Add new util library for structured errors --- util/error.lua | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 util/error.lua diff --git a/util/error.lua b/util/error.lua new file mode 100644 index 00000000..ed61793f --- /dev/null +++ b/util/error.lua @@ -0,0 +1,40 @@ +local error_mt = { __name = "error" }; + +function error_mt:__tostring() + return ("error<%s:%s:%s>"):format(self.type, self.condition, self.text); +end + +local function is_err(e) + return getmetatable(e) == error_mt; +end + +local function new(e, context, registry) + local template = (registry and registry[e]) or e or {}; + return setmetatable({ + type = template.type or "cancel"; + condition = template.condition or "undefined-condition"; + text = template.text; + + context = context or template.context or { _error_id = e }; + }, error_mt); +end + +local function coerce(ok, err, ...) + if ok or is_err(err) then + return ok, err, ...; + end + + local new_err = setmetatable({ + native = err; + + type = "cancel"; + condition = "undefined-condition"; + }, error_mt); + return ok, new_err, ...; +end + +return { + new = new; + coerce = coerce; + is_err = is_err; +} -- cgit v1.2.3 From 0fe56344ca10575746f969992b63b4173395eed2 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 30 Dec 2018 14:26:58 +0100 Subject: core.moduleapi: Move util imports to top --- core/moduleapi.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/moduleapi.lua b/core/moduleapi.lua index f7aa7216..c7fff11f 100644 --- a/core/moduleapi.lua +++ b/core/moduleapi.lua @@ -14,6 +14,8 @@ local pluginloader = require "util.pluginloader"; local timer = require "util.timer"; local resolve_relative_path = require"util.paths".resolve_relative_path; local st = require "util.stanza"; +local cache = require "util.cache"; +local promise = require "util.promise"; local t_insert, t_remove, t_concat = table.insert, table.remove, table.concat; local error, setmetatable, type = error, setmetatable, type; @@ -364,14 +366,14 @@ end function api:send_iq(stanza, origin, timeout) local iq_cache = self._iq_cache; if not iq_cache then - iq_cache = require "util.cache".new(256, function (_, iq) + iq_cache = cache.new(256, function (_, iq) iq.reject("evicted"); self:unhook(iq.result_event, iq.result_handler); self:unhook(iq.error_event, iq.error_handler); end); self._iq_cache = iq_cache; end - return require "util.promise".new(function (resolve, reject) + return promise.new(function (resolve, reject) local event_type; if stanza.attr.from == self.host then event_type = "host"; -- cgit v1.2.3 From f102941562aa2228e1949261c91045ecbf71c18d Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 30 Dec 2018 16:03:15 +0100 Subject: core.moduleapi: Use util.error for :send_iq errors --- core/moduleapi.lua | 26 +++++++++++++++++++++----- plugins/mod_admin_telnet.lua | 3 --- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/core/moduleapi.lua b/core/moduleapi.lua index c7fff11f..57aa4e9f 100644 --- a/core/moduleapi.lua +++ b/core/moduleapi.lua @@ -15,6 +15,7 @@ local timer = require "util.timer"; local resolve_relative_path = require"util.paths".resolve_relative_path; local st = require "util.stanza"; local cache = require "util.cache"; +local errutil = require "util.error"; local promise = require "util.promise"; local t_insert, t_remove, t_concat = table.insert, table.remove, table.concat; @@ -367,7 +368,10 @@ function api:send_iq(stanza, origin, timeout) local iq_cache = self._iq_cache; if not iq_cache then iq_cache = cache.new(256, function (_, iq) - iq.reject("evicted"); + iq.reject(errutil.new({ + type = "wait", condition = "resource-constraint", + text = "evicted from iq tracking cache" + })); self:unhook(iq.result_event, iq.result_handler); self:unhook(iq.error_event, iq.error_handler); end); @@ -393,20 +397,29 @@ function api:send_iq(stanza, origin, timeout) local function error_handler(event) if event.stanza.attr.from == stanza.attr.to then - reject(event); + local error_type, condition, text = event.stanza:get_error(); + local err = errutil.new({ type = error_type, condition = condition, text = text }, event); + reject(err); return true; end end if iq_cache:get(cache_key) then - error("choose another iq stanza id attribute") + reject(errutil.new({ + type = "modify", condition = "conflict", + text = "iq stanza id attribute already used", + })); + return; end self:hook(result_event, result_handler); self:hook(error_event, error_handler); local timeout_handle = self:add_timer(timeout or 120, function () - reject("timeout"); + reject(errutil.new({ + type = "wait", condition = "remote-server-timeout", + text = "IQ stanza timed out", + })); self:unhook(result_event, result_handler); self:unhook(error_event, error_handler); iq_cache:set(cache_key, nil); @@ -420,7 +433,10 @@ function api:send_iq(stanza, origin, timeout) }); if not ok then - reject("cache insertion failure"); + reject(errutil.new({ + type = "wait", condition = "internal-server-error", + text = "Could not store IQ tracking data" + })); return; end diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index f3731c8a..5bb9361e 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -1111,9 +1111,6 @@ function def_env.xmpp:ping(localhost, remotehost, timeout) wait(); if ret then return true, "pong from " .. ret.stanza.attr.from; - elseif type(err) == "table" and st.is_stanza(err.stanza) then - local t, cond, text = err.stanza:get_error(); - return false, text or cond or t; else return false, tostring(err); end -- cgit v1.2.3 From 3b3af4805c4323fe915ea4ac5aa09110a19e7676 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 30 Dec 2018 20:30:59 +0100 Subject: util.error: Add a function for creating an error object from an error stanza --- util/error.lua | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/util/error.lua b/util/error.lua index ed61793f..344dd274 100644 --- a/util/error.lua +++ b/util/error.lua @@ -33,8 +33,20 @@ local function coerce(ok, err, ...) return ok, new_err, ...; end +local function from_stanza(stanza, context) + local error_type, condition, text = stanza:get_error(); + return setmetatable({ + type = error_type or "cancel"; + condition = condition or "undefined-condition"; + text = text; + + context = context or { stanza = stanza }; + }, error_mt); +end + return { new = new; coerce = coerce; is_err = is_err; + from_stanza = from_stanza; } -- cgit v1.2.3 From a89dd30b7e9e04e3b4fb89efc45245970353608f Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 30 Dec 2018 20:35:20 +0100 Subject: core.moduleapi: Use convenience function for creating error object from stanza --- core/moduleapi.lua | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/core/moduleapi.lua b/core/moduleapi.lua index 57aa4e9f..c6193cfd 100644 --- a/core/moduleapi.lua +++ b/core/moduleapi.lua @@ -397,9 +397,7 @@ function api:send_iq(stanza, origin, timeout) local function error_handler(event) if event.stanza.attr.from == stanza.attr.to then - local error_type, condition, text = event.stanza:get_error(); - local err = errutil.new({ type = error_type, condition = condition, text = text }, event); - reject(err); + reject(errutil.from_stanza(event.stanza), event); return true; end end -- cgit v1.2.3 From c68690726162f0ab0efb62f1cb455001c06b0fa7 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 3 Jan 2019 17:25:43 +0100 Subject: mod_mam: Perform message expiry based on building an index by date For each day, store a set of users that have new messages. To expire messages, we collect the union of sets of users from dates that fall outside the cleanup range. The previous algoritm did not work well with many users, especially with the default settings. --- plugins/mod_mam/mod_mam.lua | 70 +++++++++++++++++++++++++-------------------- 1 file changed, 39 insertions(+), 31 deletions(-) diff --git a/plugins/mod_mam/mod_mam.lua b/plugins/mod_mam/mod_mam.lua index 94bedbb1..18f84752 100644 --- a/plugins/mod_mam/mod_mam.lua +++ b/plugins/mod_mam/mod_mam.lua @@ -33,7 +33,7 @@ local is_stanza = st.is_stanza; local tostring = tostring; local time_now = os.time; local m_min = math.min; -local timestamp, timestamp_parse = require "util.datetime".datetime, require "util.datetime".parse; +local timestamp, timestamp_parse, datestamp = import( "util.datetime", "datetime", "parse", "date"); local default_max_items, max_max_items = 20, module:get_option_number("max_archive_query_results", 50); local strip_tags = module:get_option_set("dont_archive_namespaces", { "http://jabber.org/protocol/chatstates" }); @@ -46,13 +46,8 @@ if not archive.find then end local use_total = module:get_option_boolean("mam_include_total", true); -local cleanup; - -local function schedule_cleanup(username) - if cleanup and not cleanup[username] then - table.insert(cleanup, username); - cleanup[username] = true; - end +function schedule_cleanup() + -- replaced by non-noop later if cleanup is enabled end -- Handle prefs. @@ -96,7 +91,6 @@ module:hook("iq-set/self/"..xmlns_mam..":query", function(event) local qid = query.attr.queryid; get_prefs(origin.username, true); - schedule_cleanup(origin.username); -- Search query parameters local qwith, qstart, qend; @@ -212,6 +206,7 @@ end local function shall_store(user, who) -- TODO Cache this? if not um.user_exists(user, host) then + module:log("debug", "%s@%s does not exist", user, host) return false; end local prefs = get_prefs(user); @@ -329,6 +324,9 @@ module:hook("pre-message/full", strip_stanza_id_after_other_events, -1); local cleanup_after = module:get_option_string("archive_expires_after", "1w"); local cleanup_interval = module:get_option_number("archive_cleanup_interval", 4 * 60 * 60); if cleanup_after ~= "never" then + local cleanup_storage = module:open_store("archive_cleanup"); + local cleanup_map = module:open_store("archive_cleanup", "map"); + local day = 86400; local multipliers = { d = day, w = day * 7, m = 31 * day, y = 365.2425 * day }; local n, m = cleanup_after:lower():match("(%d+)%s*([dwmy]?)"); @@ -346,33 +344,43 @@ if cleanup_after ~= "never" then return false; end - -- Set of known users to do message expiry for - -- Populated either below or when new messages are added - cleanup = {}; + -- For each day, store a set of users that have new messages. To expire + -- messages, we collect the union of sets of users from dates that fall + -- outside the cleanup range. - -- Iterating over users is not supported by all authentication modules - -- Catch and ignore error if not supported - pcall(function () - -- If this works, then we schedule cleanup for all known users on startup - for user in um.users(module.host) do - schedule_cleanup(user); - end - end); + function schedule_cleanup(username, date) + cleanup_map:set(date or datestamp(), username, true); + end - -- At odd intervals, delete old messages for one user - module:add_timer(math.random(10, 60), function() - local user = table.remove(cleanup, 1); - if user then - module:log("debug", "Removing old messages for user %q", user); + cleanup_runner = require "util.async".runner(function () + local users = {}; + local cut_off = datestamp(os.time() - cleanup_after); + for date in cleanup_storage:users() do + if date < cut_off then + module:log("debug", "Messages from %q should be expired", date); + local messages_this_day = cleanup_storage:get(date); + if messages_this_day then + for user in pairs(messages_this_day) do + users[user] = true; + end + cleanup_storage:set(date, nil); + end + end + end + local sum, num_users = 0, 0; + for user in pairs(users) do local ok, err = archive:delete(user, { ["end"] = os.time() - cleanup_after; }) - if not ok then - module:log("warn", "Could not expire archives for user %s: %s", user, err); - elseif type(ok) == "number" then - module:log("debug", "Removed %d messages", ok); + if ok then + num_users = num_users + 1; + sum = sum + tonumber(ok) or 0; end - cleanup[user] = nil; end - return math.random(cleanup_interval, cleanup_interval * 2); + module:log("info", "Deleted expired %d messages for %d users", sum, num_users); + end); + + cleanup_task = module:add_timer(1, function () + cleanup_runner:run(true); + return cleanup_interval; end); else module:log("debug", "Archive expiry disabled"); -- cgit v1.2.3 From 3738686f06f0156da85df269050f3f754c7603cd Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 4 Jan 2019 10:14:55 +0100 Subject: mod_mam: Fix word order in log message --- plugins/mod_mam/mod_mam.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_mam/mod_mam.lua b/plugins/mod_mam/mod_mam.lua index 18f84752..35a4b9a0 100644 --- a/plugins/mod_mam/mod_mam.lua +++ b/plugins/mod_mam/mod_mam.lua @@ -375,7 +375,7 @@ if cleanup_after ~= "never" then sum = sum + tonumber(ok) or 0; end end - module:log("info", "Deleted expired %d messages for %d users", sum, num_users); + module:log("info", "Deleted %d expired messages for %d users", sum, num_users); end); cleanup_task = module:add_timer(1, function () -- cgit v1.2.3 From 11b2a79872902ae26905c006a2171aaecbcb4300 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 4 Jan 2019 13:38:30 +0100 Subject: mod_admin_telnet: Remove the long gone 'section' argument in the undocumented config:get command --- plugins/mod_admin_telnet.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 5bb9361e..4c049b95 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -495,9 +495,9 @@ function def_env.config:load(filename, format) return true, "Config loaded"; end -function def_env.config:get(host, section, key) +function def_env.config:get(host, key) local config_get = require "core.configmanager".get - return true, tostring(config_get(host, section, key)); + return true, tostring(config_get(host, key)); end function def_env.config:reload() -- cgit v1.2.3 From 5fb717bbcec4af6aee2bc709f97fbea7b88f3fe6 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 4 Jan 2019 13:39:13 +0100 Subject: mod_admin_telnet: config:get: Assume the global section if only one argument is given --- plugins/mod_admin_telnet.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 4c049b95..c6b67b95 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -496,6 +496,9 @@ function def_env.config:load(filename, format) end function def_env.config:get(host, key) + if key == nil then + host, key = "*", host; + end local config_get = require "core.configmanager".get return true, tostring(config_get(host, key)); end -- cgit v1.2.3 From d020a0b57782846653d6145d388143df5b616c64 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 4 Jan 2019 13:41:39 +0100 Subject: mod_admin_telnet: Serialize config values (table: 0x123abc isn't useful) --- plugins/mod_admin_telnet.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index c6b67b95..cd9f8078 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -32,6 +32,7 @@ local envload = require "util.envload".envload; local envloadfile = require "util.envload".envloadfile; local has_pposix, pposix = pcall(require, "util.pposix"); local async = require "util.async"; +local serialize = require "util.serialization".new({ fatal = false, unquoted = true}); local commands = module:shared("commands") local def_env = module:shared("env"); @@ -500,7 +501,7 @@ function def_env.config:get(host, key) host, key = "*", host; end local config_get = require "core.configmanager".get - return true, tostring(config_get(host, key)); + return true, serialize(config_get(host, key)); end function def_env.config:reload() -- cgit v1.2.3 From 51c4d0a0e4d0b1d83bdcfc779bcc9e83be4f3d08 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 4 Jan 2019 15:13:52 +0100 Subject: mod_admin_telnet: Sort stats by name --- plugins/mod_admin_telnet.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index cd9f8078..5ce504f8 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -1543,7 +1543,7 @@ function def_env.stats:show(filter) local stats, changed, extra = require "core.statsmanager".get_stats(); local available, displayed = 0, 0; local displayed_stats = new_stats_context(self); - for name, value in pairs(stats) do + for name, value in iterators.sorted_pairs(stats) do available = available + 1; if not filter or name:match(filter) then displayed = displayed + 1; -- cgit v1.2.3 From 2ac699495592895c1cde86cb0ba2dc25c254a4eb Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 6 Jan 2019 09:34:59 +0100 Subject: mod_mam: Measure how long it takes to run the message expiry job job --- plugins/mod_mam/mod_mam.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/mod_mam/mod_mam.lua b/plugins/mod_mam/mod_mam.lua index 35a4b9a0..5ba04d68 100644 --- a/plugins/mod_mam/mod_mam.lua +++ b/plugins/mod_mam/mod_mam.lua @@ -351,8 +351,10 @@ if cleanup_after ~= "never" then function schedule_cleanup(username, date) cleanup_map:set(date or datestamp(), username, true); end + local cleanup_time = module:measure("cleanup", "times"); cleanup_runner = require "util.async".runner(function () + local cleanup_done = cleanup_time(); local users = {}; local cut_off = datestamp(os.time() - cleanup_after); for date in cleanup_storage:users() do @@ -376,6 +378,7 @@ if cleanup_after ~= "never" then end end module:log("info", "Deleted %d expired messages for %d users", sum, num_users); + cleanup_done(); end); cleanup_task = module:add_timer(1, function () -- cgit v1.2.3 From bdfc36fc8caa83c2919c0df1f46b91232af09096 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 6 Jan 2019 09:44:55 +0100 Subject: mod_mam: Handle expiry of messages that expire in the middle of the cut-off day --- plugins/mod_mam/mod_mam.lua | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/plugins/mod_mam/mod_mam.lua b/plugins/mod_mam/mod_mam.lua index 5ba04d68..d2ca709b 100644 --- a/plugins/mod_mam/mod_mam.lua +++ b/plugins/mod_mam/mod_mam.lua @@ -358,14 +358,18 @@ if cleanup_after ~= "never" then local users = {}; local cut_off = datestamp(os.time() - cleanup_after); for date in cleanup_storage:users() do - if date < cut_off then + if date <= cut_off then module:log("debug", "Messages from %q should be expired", date); local messages_this_day = cleanup_storage:get(date); if messages_this_day then for user in pairs(messages_this_day) do users[user] = true; end - cleanup_storage:set(date, nil); + if date < cut_off then + -- Messages from the same day as the cut-off might not have expired yet, + -- but all earlier will have, so clear storage for those days. + cleanup_storage:set(date, nil); + end end end end -- cgit v1.2.3 From 37374ae95e615cc02da0c64a43a7a30b7156fea4 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 6 Jan 2019 10:39:33 +0100 Subject: util.http: Pre-generate urlencoding mappings (optimization) Function calls are more expensive than table lookups --- util/http.lua | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/util/http.lua b/util/http.lua index cfb89193..1730d4d4 100644 --- a/util/http.lua +++ b/util/http.lua @@ -6,24 +6,25 @@ -- local format, char = string.format, string.char; -local pairs, ipairs, tonumber = pairs, ipairs, tonumber; +local pairs, ipairs = pairs, ipairs; local t_insert, t_concat = table.insert, table.concat; +local url_codes = {}; +for i = 0, 255 do + local c = char(i); + local u = format("%%%02x", i); + url_codes[c] = u; + url_codes[u] = c; +end local function urlencode(s) - return s and (s:gsub("[^a-zA-Z0-9.~_-]", function (c) return format("%%%02x", c:byte()); end)); + return s and (s:gsub("[^a-zA-Z0-9.~_-]", url_codes)); end local function urldecode(s) - return s and (s:gsub("%%(%x%x)", function (c) return char(tonumber(c,16)); end)); + return s and (s:gsub("%%%x%x", url_codes)); end local function _formencodepart(s) - return s and (s:gsub("%W", function (c) - if c ~= " " then - return format("%%%02x", c:byte()); - else - return "+"; - end - end)); + return s and (urlencode(s):gsub("%%20", "+")); end local function formencode(form) -- cgit v1.2.3 From c3c38cd2b33d37dfe980c4e21c9b369c138f20c5 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 6 Jan 2019 10:42:45 +0100 Subject: mod_http_errors: Normalize CSS --- plugins/mod_http_errors.lua | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/plugins/mod_http_errors.lua b/plugins/mod_http_errors.lua index 13473219..2bb13298 100644 --- a/plugins/mod_http_errors.lua +++ b/plugins/mod_http_errors.lua @@ -26,21 +26,24 @@ local html = [[ {title} -- cgit v1.2.3 From 90f8c75467e26c7a9dc74a55c615304ee4a3569e Mon Sep 17 00:00:00 2001 From: Jonas Wielicki Date: Sun, 6 Jan 2019 11:28:54 +0100 Subject: MUC: add ID to message if no ID is present --- plugins/muc/muc.lib.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index bb79cda6..a34e912b 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -23,6 +23,7 @@ local resourceprep = require "util.encodings".stringprep.resourceprep; local st = require "util.stanza"; local base64 = require "util.encodings".base64; local md5 = require "util.hashes".md5; +local id = require "util.id"; local log = module._log; @@ -1037,6 +1038,9 @@ end function room_mt:handle_groupchat_to_room(origin, stanza) local from = stanza.attr.from; local occupant = self:get_occupant_by_real_jid(from); + if not stanza.attr.id then + stanza.attr.id = id.medium() + end if module:fire_event("muc-occupant-groupchat", { room = self; origin = origin; stanza = stanza; from = from; occupant = occupant; }) then return true; end -- cgit v1.2.3 From f0550233fa24e17773e0c7cc21885cd195ad8c1f Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 6 Jan 2019 12:20:57 +0100 Subject: MUC: Rename import to avoid name clash [luacheck] --- plugins/muc/muc.lib.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index a34e912b..7259dde2 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -23,7 +23,7 @@ local resourceprep = require "util.encodings".stringprep.resourceprep; local st = require "util.stanza"; local base64 = require "util.encodings".base64; local md5 = require "util.hashes".md5; -local id = require "util.id"; +local new_id = require "util.id".medium; local log = module._log; @@ -1039,7 +1039,7 @@ function room_mt:handle_groupchat_to_room(origin, stanza) local from = stanza.attr.from; local occupant = self:get_occupant_by_real_jid(from); if not stanza.attr.id then - stanza.attr.id = id.medium() + stanza.attr.id = new_id() end if module:fire_event("muc-occupant-groupchat", { room = self; origin = origin; stanza = stanza; from = from; occupant = occupant; -- cgit v1.2.3 From 45b1245e12ee56a85bb8fb942f98673902fd706f Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 10 Jan 2019 14:27:01 +0100 Subject: GNUmakefile: Add target for running scansion --- GNUmakefile | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/GNUmakefile b/GNUmakefile index 6c134679..dd199997 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -21,6 +21,7 @@ MKDIR_PRIVATE=$(MKDIR) -m750 LUACHECK=luacheck BUSTED=busted +SCANSION=scansion .PHONY: all test coverage clean install @@ -71,6 +72,11 @@ clean: test: $(BUSTED) --lua=$(RUNWITH) +integration-test: all + $(RUNWITH) prosodyctl --config ./spec/scansion/prosody.cfg.lua start + $(SCANSION) -d ./spec/scansion + $(RUNWITH) prosodyctl --config ./spec/scansion/prosody.cfg.lua stop + coverage: -rm -- luacov.* $(BUSTED) --lua=$(RUNWITH) -c -- cgit v1.2.3 From e31667cf151d9d81cf2ab16eece72e559983fb52 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 10 Jan 2019 14:54:34 +0100 Subject: prosodyctl: Pass the original argv table to subcommands (with first argument removed) This preserves eg arg[-1] where you might find the path to the Lua executable, which can be useful. --- prosodyctl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/prosodyctl b/prosodyctl index 76de45a2..ecf00baa 100755 --- a/prosodyctl +++ b/prosodyctl @@ -83,7 +83,7 @@ local jid_split = require "util.jid".prepped_split; local prosodyctl_timeout = (configmanager.get("*", "prosodyctl_timeout") or 5) * 2; ----------------------- local commands = {}; -local command = arg[1]; +local command = table.remove(arg, 1); function commands.adduser(arg) if not arg[1] or arg[1] == "--help" then @@ -1365,7 +1365,7 @@ local command_runner = async.runner(function () os.exit(0); end - os.exit(commands[command]({ select(2, unpack(arg)) })); + os.exit(commands[command](arg)); end, watchers); command_runner:run(true); -- cgit v1.2.3 From 8d8c6bd619ded1b202ebd19bda33bde475c4c37d Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 10 Jan 2019 14:57:26 +0100 Subject: util.prosodyctl: Allow passing path to Lua runtime to the start() function By default the shebang is used. Being able to override it is useful in cases where the shebang does not match the configured runtime. --- util/prosodyctl.lua | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/util/prosodyctl.lua b/util/prosodyctl.lua index 5f0c4d12..9b627bde 100644 --- a/util/prosodyctl.lua +++ b/util/prosodyctl.lua @@ -229,7 +229,8 @@ local function isrunning() return true, signal.kill(pid, 0) == 0; end -local function start(source_dir) +local function start(source_dir, lua) + lua = lua and lua .. " " or ""; local ok, ret = isrunning(); if not ok then return ok, ret; @@ -238,9 +239,9 @@ local function start(source_dir) return false, "already-running"; end if not source_dir then - os.execute("./prosody"); + os.execute(lua .. "./prosody"); else - os.execute(source_dir.."/../../bin/prosody"); + os.execute(lua .. source_dir.."/../../bin/prosody"); end return true; end -- cgit v1.2.3 From 1e4a0ebba24834980440a61636601732b762e397 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 10 Jan 2019 15:25:38 +0100 Subject: prosodyctl: Use the same runtime for starting prosody Improves the experience with the `make integration-test` command --- prosodyctl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prosodyctl b/prosodyctl index ecf00baa..0ff03a7b 100755 --- a/prosodyctl +++ b/prosodyctl @@ -222,7 +222,7 @@ function commands.start(arg) end --luacheck: ignore 411/ret - local ok, ret = prosodyctl.start(prosody.paths.source); + local ok, ret = prosodyctl.start(prosody.paths.source, arg[-1]); if ok then local daemonize = configmanager.get("*", "daemonize"); if daemonize == nil then -- cgit v1.2.3 From 4b6a1153f46fbb1c14ca7a67cc82701572227811 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 14 Jan 2019 00:17:02 +0100 Subject: mod_storage_memory: Implement :user iteration API --- plugins/mod_storage_memory.lua | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/plugins/mod_storage_memory.lua b/plugins/mod_storage_memory.lua index ed04a5fb..3a9de1cc 100644 --- a/plugins/mod_storage_memory.lua +++ b/plugins/mod_storage_memory.lua @@ -22,6 +22,10 @@ local function _purge_store(self, username) return true; end +local function _users(self) + return next, self.store, nil; +end + local keyval_store = {}; keyval_store.__index = keyval_store; @@ -39,9 +43,13 @@ end keyval_store.purge = _purge_store; +keyval_store.users = _users; + local archive_store = {}; archive_store.__index = archive_store; +archive_store.users = _users; + function archive_store:append(username, key, value, when, with) if is_stanza(value) then value = st.preserialize(value); -- cgit v1.2.3 From cf984835d120a714e2ed4337f8522e935cf85498 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 15 Jan 2019 20:08:30 +0100 Subject: mod_c2s, mod_s2s, mod_component: Log invalid XML escaped (fixes #734) See 6ed0d6224d64 --- plugins/mod_c2s.lua | 2 +- plugins/mod_component.lua | 2 +- plugins/mod_s2s/mod_s2s.lua | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/plugins/mod_c2s.lua b/plugins/mod_c2s.lua index 36e6a152..8d7b92fe 100644 --- a/plugins/mod_c2s.lua +++ b/plugins/mod_c2s.lua @@ -290,7 +290,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: %s", tostring(err), #data, data:sub(1, 300):gsub("[\r\n]+", " "):gsub("[%z\1-\31]", "_")); + log("debug", "Received invalid XML (%s) %d bytes: %q", tostring(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 b41204a2..b8c87dee 100644 --- a/plugins/mod_component.lua +++ b/plugins/mod_component.lua @@ -310,7 +310,7 @@ function listener.onconnect(conn) function session.data(_, data) local ok, err = stream:feed(data); if ok then return; end - module:log("debug", "Received invalid XML (%s) %d bytes: %s", tostring(err), #data, data:sub(1, 300):gsub("[\r\n]+", " "):gsub("[%z\1-\31]", "_")); + log("debug", "Received invalid XML (%s) %d bytes: %q", tostring(err), #data, data:sub(1, 300)); session:close("not-well-formed"); end diff --git a/plugins/mod_s2s/mod_s2s.lua b/plugins/mod_s2s/mod_s2s.lua index aae37b7f..79308847 100644 --- a/plugins/mod_s2s/mod_s2s.lua +++ b/plugins/mod_s2s/mod_s2s.lua @@ -595,8 +595,7 @@ local function initialize_session(session) if data then local ok, err = stream:feed(data); if ok then return; end - log("warn", "Received invalid XML: %s", data); - log("warn", "Problem was: %s", err); + log("debug", "Received invalid XML (%s) %d bytes: %q", tostring(err), #data, data:sub(1, 300)); session:close("not-well-formed"); end end -- cgit v1.2.3 From 3e30870220e8c617cda3a06b1c9d9054b346283c Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 16 Jan 2019 13:53:04 +0100 Subject: util.http: Fix decoding of uppercase URL encoded chars Broken in 1af5106a2c34 --- spec/util_http_spec.lua | 5 +++++ util/http.lua | 1 + 2 files changed, 6 insertions(+) diff --git a/spec/util_http_spec.lua b/spec/util_http_spec.lua index 0f51a86c..d38645f7 100644 --- a/spec/util_http_spec.lua +++ b/spec/util_http_spec.lua @@ -28,6 +28,11 @@ describe("util.http", function() it("should decode important URL characters", function() assert.are.equal("This & that = something", http.urldecode("This%20%26%20that%20%3d%20something"), "Important URL chars escaped"); end); + + it("should decode both lower and uppercase", function () + assert.are.equal("This & that = {something}.", http.urldecode("This%20%26%20that%20%3D%20%7Bsomething%7D%2E"), "Important URL chars escaped"); + end); + end); describe("#formencode()", function() diff --git a/util/http.lua b/util/http.lua index 1730d4d4..3852f91c 100644 --- a/util/http.lua +++ b/util/http.lua @@ -15,6 +15,7 @@ for i = 0, 255 do local u = format("%%%02x", i); url_codes[c] = u; url_codes[u] = c; + url_codes[u:upper()] = c; end local function urlencode(s) return s and (s:gsub("[^a-zA-Z0-9.~_-]", url_codes)); -- cgit v1.2.3 From b2c3b2f740d777f1e04df40494f2be0637f946a6 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 16 Jan 2019 14:20:16 +0100 Subject: mod_admin_telnet: sttas:show: Use format option that allows float numbers string.format("%d", 0.5) causes an error on Lua 5.3 --- plugins/mod_admin_telnet.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 5ce504f8..7fae8983 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -1255,7 +1255,7 @@ local function format_stat(type, value, ref_value) --do return tostring(value) end if type == "duration" then if ref_value < 0.001 then - return ("%d µs"):format(value*1000000); + return ("%g µs"):format(value*1000000); elseif ref_value < 0.9 then return ("%0.2f ms"):format(value*1000); end -- cgit v1.2.3 From 5268b2c180c1bce00117232541a979a909ff2eb3 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 16 Jan 2019 20:01:38 +0100 Subject: core.s2smanager: Add stub reset_stream method to destroyed sessions Fixes traceback if connection is closed from the 's2s-authenticated' event --- core/s2smanager.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/s2smanager.lua b/core/s2smanager.lua index 58269c49..0ba5e7c6 100644 --- a/core/s2smanager.lua +++ b/core/s2smanager.lua @@ -50,6 +50,9 @@ local resting_session = { -- Resting, not dead close = function (session) session.log("debug", "Attempt to close already-closed session"); end; + reset_stream = function (session) + session.log("debug", "Attempt to reset stream of already-closed session"); + end; filter = function (type, data) return data; end; --luacheck: ignore 212/type }; resting_session.__index = resting_session; -- cgit v1.2.3 From 87639540e4ea43c57eb3d31b78e0b5acaf68f97a Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 4 Oct 2018 12:22:12 +0200 Subject: mod_http: Solve CORS problems once and for all This blindly allows any cross-site requests. Future work should add an API to allow each HTTP app some influence over this for each HTTP path --- plugins/mod_http.lua | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/plugins/mod_http.lua b/plugins/mod_http.lua index a1d409bd..07d1094b 100644 --- a/plugins/mod_http.lua +++ b/plugins/mod_http.lua @@ -22,6 +22,11 @@ server.set_default_host(module:get_option_string("http_default_host")); server.set_option("body_size_limit", module:get_option_number("http_max_content_size")); server.set_option("buffer_size_limit", module:get_option_number("http_max_buffer_size")); +-- CORS settigs +local opt_methods = module:get_option_set("access_control_allow_methods", { "GET", "POST", "PUT", "OPTIONS" }); +local opt_headers = module:get_option_set("access_control_allow_headers", { "Content-Type" }); +local opt_max_age = module:get_option_number("access_control_max_age", 2 * 60 * 60); + local function get_http_event(host, app_path, key) local method, path = key:match("^(%S+)%s+(.+)$"); if not method then -- No path specified, default to "" (base path) @@ -83,6 +88,13 @@ function moduleapi.http_url(module, app_name, default_path) return "http://disabled.invalid/"; end +local function apply_cors_headers(response, methods, headers, max_age, origin) + response.headers.access_control_allow_methods = tostring(methods); + response.headers.access_control_allow_headers = tostring(headers); + response.headers.access_control_max_age = tostring(max_age) + response.headers.access_control_allow_origin = origin or "*"; +end + function module.add_host(module) local host = module.host; if host ~= "*" then @@ -101,6 +113,12 @@ function module.add_host(module) end apps[app_name] = apps[app_name] or {}; local app_handlers = apps[app_name]; + + local function cors_handler(event_data) + local request, response = event_data.request, event_data.response; + apply_cors_headers(response, opt_methods, opt_headers, opt_max_age, request.headers.origin); + end + for key, handler in pairs(event.item.route or {}) do local event_name = get_http_event(host, app_path, key); if event_name then @@ -121,6 +139,7 @@ function module.add_host(module) if not app_handlers[event_name] then app_handlers[event_name] = handler; module:hook_object_event(server, event_name, handler); + module:hook_object_event(server, event_name, cors_handler, 1); else module:log("warn", "App %s added handler twice for '%s', ignoring", app_name, event_name); end -- cgit v1.2.3 From 467260e6f51942bc4a113bc0ca23808002289147 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 4 Oct 2018 12:23:06 +0200 Subject: mod_bosh: Drop CORS code in favor of than in mod_http This deprecates the cross_domain_bosh setting. As a compat measure, if it is set, mod_http_crossdomain is loaded. --- plugins/mod_bosh.lua | 30 +++++------------------------- prosodyctl | 2 +- 2 files changed, 6 insertions(+), 26 deletions(-) diff --git a/plugins/mod_bosh.lua b/plugins/mod_bosh.lua index d4701148..82615161 100644 --- a/plugins/mod_bosh.lua +++ b/plugins/mod_bosh.lua @@ -44,10 +44,12 @@ local bosh_max_polling = module:get_option_number("bosh_max_polling", 5); local bosh_max_wait = module:get_option_number("bosh_max_wait", 120); local consider_bosh_secure = module:get_option_boolean("consider_bosh_secure"); -local cross_domain = module:get_option("cross_domain_bosh", false); +local cross_domain = module:get_option("cross_domain_bosh"); -if cross_domain == true then cross_domain = "*"; end -if type(cross_domain) == "table" then cross_domain = table.concat(cross_domain, ", "); end +if cross_domain ~= nil then + module:log("info", "The 'cross_domain_bosh' option has been deprecated"); + module:depends("http_crossdomain"); +end local t_insert, t_remove, t_concat = table.insert, table.remove, table.concat; @@ -91,22 +93,6 @@ function check_inactive(now, session, context, reason) -- luacheck: ignore 212/n end end -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) log("debug", "Handling new request %s: %s\n----------", tostring(event.request), tostring(event.request.body)); @@ -121,10 +107,6 @@ function handle_POST(event) local headers = response.headers; headers.content_type = "text/xml; charset=utf-8"; - if cross_domain and 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 @@ -511,8 +493,6 @@ module:provides("http", { route = { ["GET"] = GET_response; ["GET /"] = GET_response; - ["OPTIONS"] = handle_OPTIONS; - ["OPTIONS /"] = handle_OPTIONS; ["POST"] = handle_POST; ["POST /"] = handle_POST; }; diff --git a/prosodyctl b/prosodyctl index 0ff03a7b..4d91cfe5 100755 --- a/prosodyctl +++ b/prosodyctl @@ -806,7 +806,7 @@ function commands.check(arg) print("Checking config..."); local deprecated = set.new({ "bosh_ports", "disallow_s2s", "no_daemonize", "anonymous_login", "require_encryption", - "vcard_compatibility", + "vcard_compatibility", "cross_domain_bosh", }); local known_global_options = set.new({ "pidfile", "log", "plugin_paths", "prosody_user", "prosody_group", "daemonize", -- cgit v1.2.3 From 7fefafa8f6cc312b41f69d8149d5a926657bc9fb Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 4 Oct 2018 12:24:08 +0200 Subject: mod_websocket: Drop CORS code in favor of that in mod_http Like for mod_bosh, deprecates consider_websocket_secure and depend on mod_http_crossdomain if it is set. --- plugins/mod_websocket.lua | 38 ++++---------------------------------- prosodyctl | 2 +- 2 files changed, 5 insertions(+), 35 deletions(-) diff --git a/plugins/mod_websocket.lua b/plugins/mod_websocket.lua index a668b4fa..da0ce8a6 100644 --- a/plugins/mod_websocket.lua +++ b/plugins/mod_websocket.lua @@ -29,18 +29,11 @@ local t_concat = table.concat; local stream_close_timeout = module:get_option_number("c2s_close_timeout", 5); local consider_websocket_secure = module:get_option_boolean("consider_websocket_secure"); -local cross_domain = module:get_option_set("cross_domain_websocket", {}); -if cross_domain:contains("*") or cross_domain:contains(true) then - cross_domain = true; +local cross_domain = module:get_option("cross_domain_websocket"); +if cross_domain ~= nil then + module:log("info", "The 'cross_domain_websocket' option has been deprecated"); + module:depends("http_crossdomain"); end - -local function check_origin(origin) - if cross_domain == true then - return true; - end - return cross_domain:contains(origin); -end - local xmlns_framing = "urn:ietf:params:xml:ns:xmpp-framing"; local xmlns_streams = "http://etherx.jabber.org/streams"; local xmlns_client = "jabber:client"; @@ -158,11 +151,6 @@ function handle_request(event) return 501; end - if not check_origin(request.headers.origin or "") then - module:log("debug", "Origin %s is not allowed by 'cross_domain_websocket'", request.headers.origin or "(missing header)"); - return 403; - end - local function websocket_close(code, message) conn:write(build_close(code, message)); conn:close(); @@ -329,22 +317,4 @@ module:provides("http", { function module.add_host(module) module:hook("c2s-read-timeout", keepalive, -0.9); - - if cross_domain ~= true then - local url = require "socket.url"; - local ws_url = module:http_url("websocket", "xmpp-websocket"); - local url_components = url.parse(ws_url); - -- The 'Origin' consists of the base URL without path - url_components.path = nil; - local this_origin = url.build(url_components); - local local_cross_domain = module:get_option_set("cross_domain_websocket", { this_origin }); - -- Don't add / remove something added by another host - -- This might be weird with random load order - local_cross_domain:exclude(cross_domain); - cross_domain:include(local_cross_domain); - module:log("debug", "cross_domain = %s", tostring(cross_domain)); - function module.unload() - cross_domain:exclude(local_cross_domain); - end - end end diff --git a/prosodyctl b/prosodyctl index 4d91cfe5..efb98386 100755 --- a/prosodyctl +++ b/prosodyctl @@ -806,7 +806,7 @@ function commands.check(arg) print("Checking config..."); local deprecated = set.new({ "bosh_ports", "disallow_s2s", "no_daemonize", "anonymous_login", "require_encryption", - "vcard_compatibility", "cross_domain_bosh", + "vcard_compatibility", "cross_domain_bosh", "cross_domain_websocket" }); local known_global_options = set.new({ "pidfile", "log", "plugin_paths", "prosody_user", "prosody_group", "daemonize", -- cgit v1.2.3 From 19d344e092421bd84cd52de74bcd6b7b1e9a0a13 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 18 Jan 2019 02:03:40 +0100 Subject: mod_http: Set up to handle OPTIONS Lower priority to allow http modules to handle it themselves, should they wish to --- plugins/mod_http.lua | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/plugins/mod_http.lua b/plugins/mod_http.lua index 07d1094b..01f20f76 100644 --- a/plugins/mod_http.lua +++ b/plugins/mod_http.lua @@ -119,9 +119,15 @@ function module.add_host(module) apply_cors_headers(response, opt_methods, opt_headers, opt_max_age, request.headers.origin); end + local function options_handler(event_data) + cors_handler(event_data); + return ""; + end + for key, handler in pairs(event.item.route or {}) do local event_name = get_http_event(host, app_path, key); if event_name then + local options_event_name = event_name:gsub("^%S+", "OPTIONS"); if type(handler) ~= "function" then local data = handler; handler = function () return data; end @@ -140,6 +146,7 @@ function module.add_host(module) app_handlers[event_name] = handler; module:hook_object_event(server, event_name, handler); module:hook_object_event(server, event_name, cors_handler, 1); + module:hook_object_event(server, options_event_name, options_handler, -1); else module:log("warn", "App %s added handler twice for '%s', ignoring", app_name, event_name); end -- cgit v1.2.3 From 3434e4560f79c834411e0c1d117a96e8b94ff4db Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 17 Jan 2019 20:42:38 +0100 Subject: mod_http: Determine CORS methods to whitelist from actual methods used --- plugins/mod_http.lua | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/plugins/mod_http.lua b/plugins/mod_http.lua index 01f20f76..829c2d02 100644 --- a/plugins/mod_http.lua +++ b/plugins/mod_http.lua @@ -14,6 +14,7 @@ local moduleapi = require "core.moduleapi"; local url_parse = require "socket.url".parse; local url_build = require "socket.url".build; local normalize_path = require "util.http".normalize_path; +local set = require "util.set"; local server = require "net.http.server"; @@ -23,7 +24,7 @@ server.set_option("body_size_limit", module:get_option_number("http_max_content_ server.set_option("buffer_size_limit", module:get_option_number("http_max_buffer_size")); -- CORS settigs -local opt_methods = module:get_option_set("access_control_allow_methods", { "GET", "POST", "PUT", "OPTIONS" }); +local opt_methods = module:get_option_set("access_control_allow_methods", { "GET", "OPTIONS" }); local opt_headers = module:get_option_set("access_control_allow_headers", { "Content-Type" }); local opt_max_age = module:get_option_number("access_control_max_age", 2 * 60 * 60); @@ -114,9 +115,11 @@ function module.add_host(module) apps[app_name] = apps[app_name] or {}; local app_handlers = apps[app_name]; + local app_methods = opt_methods; + local function cors_handler(event_data) local request, response = event_data.request, event_data.response; - apply_cors_headers(response, opt_methods, opt_headers, opt_max_age, request.headers.origin); + apply_cors_headers(response, app_methods, opt_headers, opt_max_age, request.headers.origin); end local function options_handler(event_data) @@ -127,6 +130,10 @@ function module.add_host(module) for key, handler in pairs(event.item.route or {}) do local event_name = get_http_event(host, app_path, key); if event_name then + local method = event_name:match("^%S+"); + if not app_methods:contains(method) then + app_methods = app_methods + set.new{ method }; + end local options_event_name = event_name:gsub("^%S+", "OPTIONS"); if type(handler) ~= "function" then local data = handler; -- cgit v1.2.3 From d254f7e101f76154ad5fb1a22964b74cba93d675 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 18 Jan 2019 18:30:41 +0100 Subject: prosodyctl: Fix module.command invocation (thanks woffs) The first argument is already removed once since c7727c13260f --- prosodyctl | 2 -- 1 file changed, 2 deletions(-) diff --git a/prosodyctl b/prosodyctl index efb98386..a628ed2b 100755 --- a/prosodyctl +++ b/prosodyctl @@ -1300,8 +1300,6 @@ local command_runner = async.runner(function () end end - table.remove(arg, 1); - local module = modulemanager.get_module("*", module_name); if not module then show_message("Failed to load module '"..module_name.."': Unknown error"); -- cgit v1.2.3 From 2612e75c8a206c174071c66a6fe938b07349e55a Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 19 Jan 2019 20:03:04 +0100 Subject: mod_bosh, mod_websocket: Remove accidentally included dependency on non-existant module --- plugins/mod_bosh.lua | 1 - plugins/mod_websocket.lua | 1 - 2 files changed, 2 deletions(-) diff --git a/plugins/mod_bosh.lua b/plugins/mod_bosh.lua index 82615161..082ed961 100644 --- a/plugins/mod_bosh.lua +++ b/plugins/mod_bosh.lua @@ -48,7 +48,6 @@ local cross_domain = module:get_option("cross_domain_bosh"); if cross_domain ~= nil then module:log("info", "The 'cross_domain_bosh' option has been deprecated"); - module:depends("http_crossdomain"); end local t_insert, t_remove, t_concat = table.insert, table.remove, table.concat; diff --git a/plugins/mod_websocket.lua b/plugins/mod_websocket.lua index da0ce8a6..008f6823 100644 --- a/plugins/mod_websocket.lua +++ b/plugins/mod_websocket.lua @@ -32,7 +32,6 @@ local consider_websocket_secure = module:get_option_boolean("consider_websocket_ local cross_domain = module:get_option("cross_domain_websocket"); if cross_domain ~= nil then module:log("info", "The 'cross_domain_websocket' option has been deprecated"); - module:depends("http_crossdomain"); end local xmlns_framing = "urn:ietf:params:xml:ns:xmpp-framing"; local xmlns_streams = "http://etherx.jabber.org/streams"; -- cgit v1.2.3 From de09c462e53b412b30463d55400328b4316a4c45 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 20 Jan 2019 20:24:17 +0100 Subject: mod_mam: Fix operator precedence (thanks mimi89999) --- plugins/mod_mam/mod_mam.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_mam/mod_mam.lua b/plugins/mod_mam/mod_mam.lua index d2ca709b..67bf177e 100644 --- a/plugins/mod_mam/mod_mam.lua +++ b/plugins/mod_mam/mod_mam.lua @@ -378,7 +378,7 @@ if cleanup_after ~= "never" then local ok, err = archive:delete(user, { ["end"] = os.time() - cleanup_after; }) if ok then num_users = num_users + 1; - sum = sum + tonumber(ok) or 0; + sum = sum + (tonumber(ok) or 0); end end module:log("info", "Deleted %d expired messages for %d users", sum, num_users); -- cgit v1.2.3 From 19d302f3c477bc8aa22b68d78cd4a4e686dfa556 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 21 Jan 2019 21:30:54 +0100 Subject: TODO: Remove statistics since this was done in 0.10 --- TODO | 1 - 1 file changed, 1 deletion(-) diff --git a/TODO b/TODO index d22d20f4..9ec820b2 100644 --- a/TODO +++ b/TODO @@ -1,5 +1,4 @@ == 1.0 == - Roster providers -- Statistics - Clustering - World domination -- cgit v1.2.3 From 54bfc5180fd94c731b4f021ed1ea142079fc1511 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 22 Jan 2019 09:21:23 +0100 Subject: core.statsmanager: Do a final collection on shutdown --- core/statsmanager.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/core/statsmanager.lua b/core/statsmanager.lua index 237b1dd5..50798ad0 100644 --- a/core/statsmanager.lua +++ b/core/statsmanager.lua @@ -97,6 +97,7 @@ if stats then end timer.add_task(stats_interval, collect); prosody.events.add_handler("server-started", function () collect() end, -1); + prosody.events.add_handler("server-stopped", function () collect() end, -1); else log("debug", "Statistics enabled using %s provider, collection is disabled", stats_provider_name); end -- cgit v1.2.3 From df36d51cd7e63834fce8d0d38f3547d873195c95 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 24 Jan 2019 05:48:55 +0100 Subject: GNUmakefile: Stop Prosody in case of failure in integration-test Normally make skips the remaning steps in the rule if one fails. This collects the status code and re-returns it after stopping the running Prosody instance. --- GNUmakefile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/GNUmakefile b/GNUmakefile index dd199997..977e91c6 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -74,8 +74,9 @@ test: integration-test: all $(RUNWITH) prosodyctl --config ./spec/scansion/prosody.cfg.lua start - $(SCANSION) -d ./spec/scansion - $(RUNWITH) prosodyctl --config ./spec/scansion/prosody.cfg.lua stop + $(SCANSION) -d ./spec/scansion; R=$$? \ + $(RUNWITH) prosodyctl --config ./spec/scansion/prosody.cfg.lua stop \ + exit $$R coverage: -rm -- luacov.* -- cgit v1.2.3 From a40e044c0327b838e4a4e161e92798ed3ceadcf5 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 9 Feb 2019 20:34:00 +0100 Subject: net.server_epoll: Separate timeout for initial connection attempts server_event has this separation already --- net/server_epoll.lua | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index 807e0b4c..a80b33a9 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -41,6 +41,9 @@ local default_config = { __index = { -- How long to wait for a socket to become writable after queuing data to send send_timeout = 60; + -- How long to wait for a socket to become writable after creation + connect_timeout = 20; + -- Some number possibly influencing how many pending connections can be accepted tcp_backlog = 128; @@ -585,7 +588,7 @@ end -- Initialization function interface:init() - self:setwritetimeout(); + self:setwritetimeout(cfg.connect_timeout); return self:add(true, true); end -- cgit v1.2.3 From ce03153c84ca4f2ef38daa09ab078d6e1a092469 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 9 Feb 2019 20:54:35 +0100 Subject: net.server_epoll: Increase send_timeout to 3 minutes (to match server_event) The separate connect_timeout means we can afford a longer send_timeout --- net/server_epoll.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index a80b33a9..fdf006f6 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -39,7 +39,7 @@ local default_config = { __index = { read_timeout = 14 * 60; -- How long to wait for a socket to become writable after queuing data to send - send_timeout = 60; + send_timeout = 180; -- How long to wait for a socket to become writable after creation connect_timeout = 20; -- cgit v1.2.3 From 42f4b6b225edf608828ca0716934c41a690964db Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 8 Dec 2018 18:02:56 +0100 Subject: prosodyctl: about: Report the current operating system according to uname --- prosodyctl | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/prosodyctl b/prosodyctl index a628ed2b..f6812e23 100755 --- a/prosodyctl +++ b/prosodyctl @@ -363,6 +363,13 @@ function commands.about(arg) .."\n "; end))); print(""); + local have_pposix, pposix = pcall(require, "util.pposix"); + if have_pposix and pposix.uname then + print("# Operating system"); + local uname, err = pposix.uname(); + print(uname and uname.sysname .. " " .. uname.release or "Unknown POSIX", err or ""); + print(""); + end print("# Lua environment"); print("Lua version: ", _G._VERSION); print(""); -- cgit v1.2.3 From 10e58af1abd74cd1bb668fc75b506b57fd77d86f Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 24 Feb 2019 16:18:30 +0100 Subject: MUC: Factor out role change permission check into its own method I would like to invert this logic so that it checks if the role change is allowed instead of checking if it is not allowed as it does now, in order to make it easier to understand. --- plugins/muc/muc.lib.lua | 45 +++++++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 9b168e93..2b6a7d76 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1368,6 +1368,30 @@ function room_mt:get_role(nick) return occupant and occupant.role or nil; end +function room_mt:may_set_role(actor, occupant, role) + -- Can't do anything to other owners or admins + local occupant_affiliation = self:get_affiliation(occupant.bare_jid); + if occupant_affiliation == "owner" or occupant_affiliation == "admin" then + return nil, "cancel", "not-allowed"; + end + + -- If you are trying to give or take moderator role you need to be an owner or admin + if occupant.role == "moderator" or role == "moderator" then + local actor_affiliation = self:get_affiliation(actor); + if actor_affiliation ~= "owner" and actor_affiliation ~= "admin" then + return nil, "cancel", "not-allowed"; + end + end + + -- Need to be in the room and a moderator + local actor_occupant = self:get_occupant_by_real_jid(actor); + if not actor_occupant or actor_occupant.role ~= "moderator" then + return nil, "cancel", "not-allowed"; + end + + return true; +end + function room_mt:set_role(actor, occupant_jid, role, reason) if not actor then return nil, "modify", "not-acceptable"; end @@ -1382,24 +1406,9 @@ function room_mt:set_role(actor, occupant_jid, role, reason) if actor == true then actor = nil -- So we can pass it safely to 'publicise_occupant_status' below else - -- Can't do anything to other owners or admins - local occupant_affiliation = self:get_affiliation(occupant.bare_jid); - if occupant_affiliation == "owner" or occupant_affiliation == "admin" then - return nil, "cancel", "not-allowed"; - end - - -- If you are trying to give or take moderator role you need to be an owner or admin - if occupant.role == "moderator" or role == "moderator" then - local actor_affiliation = self:get_affiliation(actor); - if actor_affiliation ~= "owner" and actor_affiliation ~= "admin" then - return nil, "cancel", "not-allowed"; - end - end - - -- Need to be in the room and a moderator - local actor_occupant = self:get_occupant_by_real_jid(actor); - if not actor_occupant or actor_occupant.role ~= "moderator" then - return nil, "cancel", "not-allowed"; + local allowed, err, condition = self:may_set_role(actor, occupant, role) + if not allowed then + return allowed, err, condition; end end -- cgit v1.2.3 From 8518868d4192e48f1f7529970dca3bae4364f43b Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 24 Feb 2019 16:30:11 +0100 Subject: MUC: Fire an event to allow affecting decision of whether to allow a role change --- plugins/muc/muc.lib.lua | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 2b6a7d76..a8d3d790 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1369,6 +1369,18 @@ function room_mt:get_role(nick) end function room_mt:may_set_role(actor, occupant, role) + local event = { + room = self, + actor = actor, + occupant = occupant, + role = role, + }; + + module:fire_event("muc-pre-set-role", event); + if event.allowed ~= nil then + return event.allowed, event.error, event.condition; + end + -- Can't do anything to other owners or admins local occupant_affiliation = self:get_affiliation(occupant.bare_jid); if occupant_affiliation == "owner" or occupant_affiliation == "admin" then -- cgit v1.2.3 From 3cb132326dd6489e14a26071f87d68bf277a5a70 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 13 Sep 2018 21:16:37 +0200 Subject: net.server: New API for creating server listeners server.listen(interface, port, listeners, options); --- net/server_epoll.lua | 18 ++++++++++++++---- net/server_event.lua | 22 ++++++++++++++++------ net/server_select.lua | 30 ++++++++++++++++++++++-------- 3 files changed, 52 insertions(+), 18 deletions(-) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index fdf006f6..5609f058 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -637,7 +637,7 @@ function interface:onconnect() self:on("connect"); end -local function addserver(addr, port, listeners, read_size, tls_ctx) +local function listen(addr, port, listeners, config) local conn, err = socket.bind(addr, port, cfg.tcp_backlog); if not conn then return conn, err; end conn:settimeout(0); @@ -645,10 +645,10 @@ local function addserver(addr, port, listeners, read_size, tls_ctx) conn = conn; created = gettime(); listeners = listeners; - read_size = read_size; + read_size = config and config.read_size; onreadable = interface.onacceptable; - tls_ctx = tls_ctx; - tls_direct = tls_ctx and true or false; + tls_ctx = config and config.tls_ctx; + tls_direct = config and config.tls_direct; sockname = addr; sockport = port; }, interface_mt); @@ -656,6 +656,15 @@ local function addserver(addr, port, listeners, read_size, tls_ctx) return server; end +-- COMPAT +local function addserver(addr, port, listeners, read_size, tls_ctx) + return listen(addr, port, listeners, { + read_size = read_size; + tls_ctx = tls_ctx; + tls_direct = tls_ctx and true or false; + }); +end + -- COMPAT local function wrapclient(conn, addr, port, listeners, read_size, tls_ctx) local client = wrapsocket(conn, nil, read_size, listeners, tls_ctx); @@ -792,6 +801,7 @@ return { addserver = addserver; addclient = addclient; add_task = addtimer; + listen = listen; at = at; loop = loop; closeall = closeall; diff --git a/net/server_event.lua b/net/server_event.lua index 70757e03..b78bf412 100644 --- a/net/server_event.lua +++ b/net/server_event.lua @@ -649,7 +649,7 @@ local function handleclient( client, ip, port, server, pattern, listener, sslctx return interface end -local function handleserver( server, addr, port, pattern, listener, sslctx ) -- creates an server interface +local function handleserver( server, addr, port, pattern, listener, sslctx, startssl ) -- creates an server interface debug "creating server interface..." local interface = { _connections = 0; @@ -695,7 +695,7 @@ local function handleserver( server, addr, port, pattern, listener, sslctx ) -- interface._connections = interface._connections + 1 -- increase connection count local clientinterface = handleclient( client, client_ip, client_port, interface, pattern, listener, sslctx ) --vdebug( "client id:", clientinterface, "startssl:", startssl ) - if has_luasec and sslctx then + if has_luasec and startssl then clientinterface:starttls(sslctx, true) else clientinterface:_start_session( true ) @@ -714,9 +714,9 @@ local function handleserver( server, addr, port, pattern, listener, sslctx ) -- return interface end -local function addserver( addr, port, listener, pattern, sslctx, startssl ) -- TODO: check arguments - --vdebug( "creating new tcp server with following parameters:", addr or "nil", port or "nil", sslctx or "nil", startssl or "nil") - if sslctx and not has_luasec then +local function listen(addr, port, listener, config) + config = config or {} + if config.sslctx and not has_luasec then debug "fatal error: luasec not found" return nil, "luasec not found" end @@ -725,11 +725,20 @@ local function addserver( addr, port, listener, pattern, sslctx, startssl ) -- debug( "creating server socket on "..addr.." port "..port.." failed:", err ) return nil, err end - local interface = handleserver( server, addr, port, pattern, listener, sslctx, startssl ) -- new server handler + local interface = handleserver( server, addr, port, config.read_size, listener, config.tls_ctx, config.tls_direct) -- new server handler debug( "new server created with id:", tostring(interface)) return interface end +local function addserver( addr, port, listener, pattern, sslctx ) -- TODO: check arguments + --vdebug( "creating new tcp server with following parameters:", addr or "nil", port or "nil", sslctx or "nil", startssl or "nil") + return listen( addr, port, listener, { + read_size = pattern, + tls_ctx = sslctx, + tls_direct = not not sslctx, + }); +end + local function wrapclient( client, ip, port, listeners, pattern, sslctx ) local interface = handleclient( client, ip, port, nil, pattern, listeners, sslctx ) interface:_start_connection(sslctx) @@ -890,6 +899,7 @@ return { event_base = base, addevent = newevent, addserver = addserver, + listen = listen, addclient = addclient, wrapclient = wrapclient, setquitting = setquitting, diff --git a/net/server_select.lua b/net/server_select.lua index f616116e..d82936e6 100644 --- a/net/server_select.lua +++ b/net/server_select.lua @@ -68,6 +68,7 @@ local idfalse local closeall local addsocket local addserver +local listen local addtimer local getserver local wrapserver @@ -157,7 +158,7 @@ _maxsslhandshake = 30 -- max handshake round-trips ----------------------------------// PRIVATE //-- -wrapserver = function( listeners, socket, ip, serverport, pattern, sslctx ) -- this function wraps a server -- FIXME Make sure FD < _maxfd +wrapserver = function( listeners, socket, ip, serverport, pattern, sslctx, ssldirect ) -- this function wraps a server -- FIXME Make sure FD < _maxfd if socket:getfd() >= _maxfd then out_error("server.lua: Disallowed FD number: "..socket:getfd()) @@ -244,13 +245,13 @@ wrapserver = function( listeners, socket, ip, serverport, pattern, sslctx ) -- t local client, err = accept( socket ) -- try to accept if client then local ip, clientport = client:getpeername( ) - local handler, client, err = wrapconnection( handler, listeners, client, ip, serverport, clientport, pattern, sslctx ) -- wrap new client socket + local handler, client, err = wrapconnection( handler, listeners, client, ip, serverport, clientport, pattern, sslctx, ssldirect ) -- wrap new client socket if err then -- error while wrapping ssl socket return false end connections = connections + 1 out_put( "server.lua: accepted new client connection from ", tostring(ip), ":", tostring(clientport), " to ", tostring(serverport)) - if dispatch and not sslctx then -- SSL connections will notify onconnect when handshake completes + if dispatch and not ssldirect then -- SSL connections will notify onconnect when handshake completes return dispatch( handler ); end return; @@ -264,7 +265,7 @@ wrapserver = function( listeners, socket, ip, serverport, pattern, sslctx ) -- t return handler end -wrapconnection = function( server, listeners, socket, ip, serverport, clientport, pattern, sslctx ) -- this function wraps a client to a handler object +wrapconnection = function( server, listeners, socket, ip, serverport, clientport, pattern, sslctx, ssldirect ) -- this function wraps a client to a handler object if socket:getfd() >= _maxfd then out_error("server.lua: Disallowed FD number: "..socket:getfd()) -- PROTIP: Switch to libevent @@ -666,7 +667,7 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport _socketlist[ socket ] = handler _readlistlen = addsocket(_readlist, socket, _readlistlen) - if sslctx and has_luasec then + if sslctx and ssldirect and has_luasec then out_put "server.lua: auto-starting ssl negotiation..." handler.autostart_ssl = true; local ok, err = handler:starttls(sslctx); @@ -741,9 +742,13 @@ end ----------------------------------// PUBLIC //-- -addserver = function( addr, port, listeners, pattern, sslctx ) -- this function provides a way for other scripts to reg a server +listen = function ( addr, port, listeners, config ) addr = addr or "*" + config = config or {} local err + local sslctx = config.tls_ctx; + local ssldirect = config.tls_direct; + local pattern = config.read_size; if type( listeners ) ~= "table" then err = "invalid listener table" elseif type ( addr ) ~= "string" then @@ -764,7 +769,7 @@ addserver = function( addr, port, listeners, pattern, sslctx ) -- this function out_error( "server.lua, [", addr, "]:", port, ": ", err ) return nil, err end - local handler, err = wrapserver( listeners, server, addr, port, pattern, sslctx ) -- wrap new server socket + local handler, err = wrapserver( listeners, server, addr, port, pattern, sslctx, ssldirect ) -- wrap new server socket if not handler then server:close( ) return nil, err @@ -777,6 +782,14 @@ addserver = function( addr, port, listeners, pattern, sslctx ) -- this function return handler end +addserver = function( addr, port, listeners, pattern, sslctx ) -- this function provides a way for other scripts to reg a server + return listen(addr, port, listeners, { + read_size = pattern; + tls_ctx = sslctx; + tls_direct = sslctx and true or false; + }); +end + getserver = function ( addr, port ) return _server[ addr..":"..port ]; end @@ -985,7 +998,7 @@ end --// EXPERIMENTAL //-- local wrapclient = function( socket, ip, serverport, listeners, pattern, sslctx ) - local handler, socket, err = wrapconnection( nil, listeners, socket, ip, serverport, "clientport", pattern, sslctx ) + local handler, socket, err = wrapconnection( nil, listeners, socket, ip, serverport, "clientport", pattern, sslctx, sslctx) if not handler then return nil, err end _socketlist[ socket ] = handler if not sslctx then @@ -1121,6 +1134,7 @@ return { stats = stats, closeall = closeall, addserver = addserver, + listen = listen, getserver = getserver, setlogger = setlogger, getsettings = getsettings, -- cgit v1.2.3 From 781d8a4868990c95aca244c18335555fabaec85b Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 13 Sep 2018 21:17:37 +0200 Subject: core.portmanager: Use server.listen API --- core/portmanager.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/portmanager.lua b/core/portmanager.lua index 1ed37da0..cf836634 100644 --- a/core/portmanager.lua +++ b/core/portmanager.lua @@ -126,7 +126,11 @@ local function activate(service_name) end if not err then -- Start listening on interface+port - local handler, err = server.addserver(interface, port_number, listener, mode, ssl); + local handler, err = server.listen(interface, port_number, listener, { + read_size = mode, + tls_ctx = ssl, + tls_direct = service_info.encryption == "ssl"; + }); if not handler then log("error", "Failed to open server port %d on %s, %s", port_number, interface, error_to_friendly_message(service_name, port_number, err)); -- cgit v1.2.3 From 59f517457b5542d8533d43734f99036451d16d8b Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 10 Oct 2018 17:22:08 +0200 Subject: core.portmanager: Reduce scope of variable Not sure why it was all the way out there, seems like there would have been unexpected behaviour from that --- core/portmanager.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/portmanager.lua b/core/portmanager.lua index cf836634..db41e1ea 100644 --- a/core/portmanager.lua +++ b/core/portmanager.lua @@ -95,7 +95,7 @@ local function activate(service_name) } bind_ports = set.new(type(bind_ports) ~= "table" and { bind_ports } or bind_ports ); - local mode, ssl = listener.default_mode or default_mode; + local mode = listener.default_mode or default_mode; local hooked_ports = {}; for interface in bind_interfaces do @@ -107,7 +107,7 @@ local function activate(service_name) 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 "", service_name or ""); else - local err; + local ssl, err; -- Create SSL context for this service/port if service_info.encryption == "ssl" then local global_ssl_config = config.get("*", "ssl") or {}; -- cgit v1.2.3 From 9c9d32e7e69af9aa59c1937b91bc41525d584144 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 4 Mar 2019 13:13:37 +0100 Subject: mod_muc_mam: Validate that the FORM_TYPE of a query is as expected --- plugins/mod_muc_mam.lua | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/plugins/mod_muc_mam.lua b/plugins/mod_muc_mam.lua index 166a5c71..963e5255 100644 --- a/plugins/mod_muc_mam.lua +++ b/plugins/mod_muc_mam.lua @@ -21,6 +21,7 @@ local jid_bare = require "util.jid".bare; local jid_split = require "util.jid".split; local jid_prep = require "util.jid".prep; local dataform = require "util.dataforms".new; +local get_form_type = require "util.dataforms".get_type; local mod_muc = module:depends"muc"; local get_room_from_jid = mod_muc.get_room_from_jid; @@ -131,7 +132,11 @@ module:hook("iq-set/bare/"..xmlns_mam..":query", function(event) local qstart, qend; local form = query:get_child("x", "jabber:x:data"); if form then - local err; + local form_type, err = get_form_type(form); + if form_type ~= xmlns_mam then + origin.send(st.error_reply(stanza, "modify", "bad-request", "Unexpected FORM_TYPE, expected '"..xmlns_mam.."'")); + return true; + end form, err = query_form:data(form); if err then origin.send(st.error_reply(stanza, "modify", "bad-request", select(2, next(err)))); -- cgit v1.2.3 From 99e25a9093b7a1cc66b7784e3b805b60f092f9da Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 10 Mar 2019 18:05:08 +0100 Subject: doc/net.server: Document the new server.listen() API --- doc/net.server.lua | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/doc/net.server.lua b/doc/net.server.lua index 7342c549..aa9a4a9d 100644 --- a/doc/net.server.lua +++ b/doc/net.server.lua @@ -160,6 +160,26 @@ Returns: local function addserver(address, port, listeners, pattern, sslctx) end +--[[ Binds and listens on the given address and port +Mostly the same as addserver but with all optional arguments in a table + +Arguments: + - address: address to bind to, may be "*" to bind all addresses. will be resolved if it is a string. + - port: port to bind (as number) + - listeners: a table of listeners + - config: table of extra settings + - read_size: the amount of bytes to read or a read pattern + - tls_ctx: is a valid luasec constructor + - tls_direct: boolean true for direct TLS, false (or nil) for starttls + +Returns: + - handle + - nil, "an error message": on failure (e.g. out of file descriptors) +]] +local function listen(address, port, listeners, config) +end + + --[[ Wraps a lua-socket socket client socket in a handle. The socket must be already connected to the remote end. If `sslctx` is given, a SSL session will be negotiated before listeners are called. @@ -255,4 +275,5 @@ return { closeall = closeall; hook_signal = hook_signal; watchfd = watchfd; + listen = listen; } -- cgit v1.2.3 From a1ef28548caaf3dc474b4c638aee917e2ca1563d Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 10 Mar 2019 19:35:34 +0100 Subject: net.server_epoll: Add support for SNI (#409) --- net/server_epoll.lua | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index 5609f058..3c8b2613 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -509,6 +509,13 @@ function interface:tlshandskake() end conn:settimeout(0); self.conn = conn; + if conn.sni then + if self.servername then + conn:sni(self.servername); + elseif self._server and self._server.hosts then + conn:sni(self._server.hosts, true); + end + end self:on("starttls"); self.ondrain = nil; self.onwritable = interface.tlshandskake; @@ -649,6 +656,7 @@ local function listen(addr, port, listeners, config) onreadable = interface.onacceptable; tls_ctx = config and config.tls_ctx; tls_direct = config and config.tls_direct; + hosts = config and config.sni_hosts; sockname = addr; sockport = port; }, interface_mt); -- cgit v1.2.3 From 5313a0c5c608dccdee0b663f2072be05203980c0 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 10 Oct 2018 17:23:03 +0200 Subject: core.portmanager: Record TLS config for each port --- core/portmanager.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/portmanager.lua b/core/portmanager.lua index db41e1ea..17758a36 100644 --- a/core/portmanager.lua +++ b/core/portmanager.lua @@ -107,12 +107,12 @@ local function activate(service_name) 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 "", service_name or ""); else - local ssl, err; + local ssl, cfg, err; -- Create SSL context for this service/port if service_info.encryption == "ssl" then local global_ssl_config = config.get("*", "ssl") or {}; local prefix_ssl_config = config.get("*", config_prefix.."ssl") or global_ssl_config; - ssl, err = certmanager.create_context(service_info.name.." port "..port, "server", + ssl, err, cfg = certmanager.create_context(service_info.name.." port "..port, "server", prefix_ssl_config[interface], prefix_ssl_config[port], prefix_ssl_config, @@ -130,6 +130,7 @@ local function activate(service_name) read_size = mode, tls_ctx = ssl, tls_direct = service_info.encryption == "ssl"; + sni_hosts = {}, }); if not handler then log("error", "Failed to open server port %d on %s, %s", port_number, interface, @@ -140,6 +141,7 @@ local function activate(service_name) active_services:add(service_name, interface, port_number, { server = handler; service = service_info; + tls_cfg = cfg; }); end end -- cgit v1.2.3 From 2878ed99a5780d3de6714f1f8141fa6f1661f2cb Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 14 Sep 2018 01:30:56 +0200 Subject: core.portmanager: Collect per-host certificates for SNI --- core/portmanager.lua | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/core/portmanager.lua b/core/portmanager.lua index 17758a36..5aef07d7 100644 --- a/core/portmanager.lua +++ b/core/portmanager.lua @@ -10,6 +10,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 pairs = pairs; local prosody = prosody; local fire_event = prosody.events.fire_event; @@ -227,15 +228,55 @@ end -- Event handlers +local function add_sni_host(host, service) + -- local global_ssl_config = config.get(host, "ssl") or {}; + for name, interface, port, n, active_service --luacheck: ignore 213 + in active_services:iter(service, nil, nil, nil) do + if active_service.server.hosts and active_service.tls_cfg then + -- local config_prefix = (active_service.config_prefix or name).."_"; + -- if config_prefix == "_" then + -- config_prefix = ""; + -- end + -- local prefix_ssl_config = config.get(host, config_prefix.."ssl") or global_ssl_config; + -- FIXME only global 'ssl' settings are mixed in here + -- TODO per host and per service settings should be merged in, + -- without overriding the per-host certificate + local ssl, err, cfg = certmanager.create_context(host, "server"); + if ssl then + active_service.server.hosts[host] = ssl; + if not active_service.tls_cfg.certificate then + active_service.server.tls_ctx = ssl; + active_service.tls_cfg = cfg; + end + else + log("error", "err = %q", err); + end + end + end +end + prosody.events.add_handler("item-added/net-provider", function (event) local item = event.item; register_service(item.name, item); + for host in pairs(prosody.hosts) do + add_sni_host(host, item.name); + end end); prosody.events.add_handler("item-removed/net-provider", function (event) local item = event.item; unregister_service(item.name, item); end); +prosody.events.add_handler("host-activated", add_sni_host); +prosody.events.add_handler("host-deactivated", function (host) + for name, interface, port, n, active_service --luacheck: ignore 213 + in active_services:iter(nil, nil, nil, nil) do + if active_service.tls_cfg then + active_service.server.hosts[host] = nil; + end + end +end); + return { activate = activate; deactivate = deactivate; -- cgit v1.2.3 From af5e6fcb649c43591524e5f00bf178428718123a Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 10 Mar 2019 19:32:54 +0100 Subject: net.server_event: Add SNI support (#409) Snippet adapted from server_epoll --- net/server_event.lua | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/net/server_event.lua b/net/server_event.lua index b78bf412..6c9b941d 100644 --- a/net/server_event.lua +++ b/net/server_event.lua @@ -164,6 +164,15 @@ function interface_mt:_start_ssl(call_onconnect) -- old socket will be destroyed debug( "fatal error while ssl wrapping:", err ) return false end + + if self.conn.sni then + if self.servername then + self.conn:sni(self.servername); + elseif self._server and self._server.hosts then + self.conn:sni(self._server.hosts, true); + end + end + self.conn:settimeout( 0 ) -- set non blocking local handshakecallback = coroutine_wrap(function( event ) local _, err @@ -665,6 +674,7 @@ local function handleserver( server, addr, port, pattern, listener, sslctx, star _ip = addr, _port = port, _pattern = pattern, _sslctx = sslctx; + hosts = {}; } interface.id = tostring(interface):match("%x+$"); interface.readcallback = function( event ) -- server handler, called on incoming connections -- cgit v1.2.3 From 5fb7d2d35a2c24a9152931d29d614c2aa8714c7e Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 10 Mar 2019 19:32:33 +0100 Subject: net.server_select: SNI support (#409) --- net/server_select.lua | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/net/server_select.lua b/net/server_select.lua index d82936e6..b52cc6d7 100644 --- a/net/server_select.lua +++ b/net/server_select.lua @@ -184,6 +184,7 @@ wrapserver = function( listeners, socket, ip, serverport, pattern, sslctx, ssldi handler.sslctx = function( ) return sslctx end + handler.hosts = {} -- sni handler.remove = function( ) connections = connections - 1 if handler then @@ -627,11 +628,20 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport out_put( "server.lua: attempting to start tls on " .. tostring( socket ) ) local oldsocket, err = socket socket, err = ssl_wrap( socket, sslctx ) -- wrap socket + if not socket then out_put( "server.lua: error while starting tls on client: ", tostring(err or "unknown error") ) return nil, err -- fatal error end + if socket.sni then + if self.servername then + socket:sni(self.servername); + elseif self.server() and self.server().hosts then + socket:sni(self.server().hosts, true); + end + end + socket:settimeout( 0 ) -- add the new socket to our system -- cgit v1.2.3 From 9f65ce71893ef10485442ee209472a38865da081 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 10 Mar 2019 19:58:28 +0100 Subject: core.certmanager: Do not ask for client certificates by default Since it's mostly only mod_s2s that needs to request client certificates it makes some sense to have mod_s2s ask for this, instead of having eg mod_http ask to disable it. --- core/certmanager.lua | 2 +- plugins/mod_http.lua | 3 --- plugins/mod_s2s/mod_s2s.lua | 3 +++ 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/certmanager.lua b/core/certmanager.lua index 5282a6f5..63f314f8 100644 --- a/core/certmanager.lua +++ b/core/certmanager.lua @@ -106,7 +106,7 @@ local core_defaults = { capath = "/etc/ssl/certs"; depth = 9; protocol = "tlsv1+"; - verify = (ssl_x509 and { "peer", "client_once", }) or "none"; + verify = "none"; options = { cipher_server_preference = luasec_has.options.cipher_server_preference; no_ticket = luasec_has.options.no_ticket; diff --git a/plugins/mod_http.lua b/plugins/mod_http.lua index 829c2d02..17ea27e1 100644 --- a/plugins/mod_http.lua +++ b/plugins/mod_http.lua @@ -228,9 +228,6 @@ module:provides("net", { listener = server.listener; default_port = 5281; encryption = "ssl"; - ssl_config = { - verify = "none"; - }; multiplex = { pattern = "^[A-Z]"; }; diff --git a/plugins/mod_s2s/mod_s2s.lua b/plugins/mod_s2s/mod_s2s.lua index 79308847..b0d551fe 100644 --- a/plugins/mod_s2s/mod_s2s.lua +++ b/plugins/mod_s2s/mod_s2s.lua @@ -738,6 +738,9 @@ module:provides("net", { listener = listener; default_port = 5269; encryption = "starttls"; + ssl_config = { + verify = { "peer", "client_once", }; + }; multiplex = { pattern = "^<.*:stream.*%sxmlns%s*=%s*(['\"])jabber:server%1.*>"; }; -- cgit v1.2.3 From 5d2608e150b7a739c0b1658fd2e9031af9ad2991 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 11 Mar 2019 13:00:51 +0100 Subject: net.server: Only add alternate SNI contexts if at least one is provided Fixes use of when a client sends SNI, which would send no certificate otherwise. --- net/server_epoll.lua | 2 +- net/server_event.lua | 2 +- net/server_select.lua | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index 3c8b2613..4bdc2e21 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -512,7 +512,7 @@ function interface:tlshandskake() if conn.sni then if self.servername then conn:sni(self.servername); - elseif self._server and self._server.hosts then + elseif self._server and type(self._server.hosts) == "table" and next(self._server.hosts) ~= nil then conn:sni(self._server.hosts, true); end end diff --git a/net/server_event.lua b/net/server_event.lua index 6c9b941d..2bee614a 100644 --- a/net/server_event.lua +++ b/net/server_event.lua @@ -168,7 +168,7 @@ function interface_mt:_start_ssl(call_onconnect) -- old socket will be destroyed if self.conn.sni then if self.servername then self.conn:sni(self.servername); - elseif self._server and self._server.hosts then + elseif self._server and type(self._server.hosts) == "table" and next(self._server.hosts) ~= nil then self.conn:sni(self._server.hosts, true); end end diff --git a/net/server_select.lua b/net/server_select.lua index b52cc6d7..4b156409 100644 --- a/net/server_select.lua +++ b/net/server_select.lua @@ -637,7 +637,7 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport if socket.sni then if self.servername then socket:sni(self.servername); - elseif self.server() and self.server().hosts then + elseif self._server and type(self._server.hosts) == "table" and next(self._server.hosts) ~= nil then socket:sni(self.server().hosts, true); end end -- cgit v1.2.3 From b246b00f85b1973058f8b607190a72168380dbc3 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 11 Mar 2019 13:07:59 +0100 Subject: mod_tls: Restore querying for certificates on s2s The 'ssl_config' setting in the mod_s2s network service is not used. Only direct TLS ports use this currently. --- plugins/mod_s2s/mod_s2s.lua | 2 +- plugins/mod_tls.lua | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/plugins/mod_s2s/mod_s2s.lua b/plugins/mod_s2s/mod_s2s.lua index b0d551fe..f0fdc5fb 100644 --- a/plugins/mod_s2s/mod_s2s.lua +++ b/plugins/mod_s2s/mod_s2s.lua @@ -738,7 +738,7 @@ module:provides("net", { listener = listener; default_port = 5269; encryption = "starttls"; - ssl_config = { + ssl_config = { -- FIXME This is not used atm, see mod_tls verify = { "peer", "client_once", }; }; multiplex = { diff --git a/plugins/mod_tls.lua b/plugins/mod_tls.lua index 4ead60dc..d8bf02c4 100644 --- a/plugins/mod_tls.lua +++ b/plugins/mod_tls.lua @@ -53,13 +53,17 @@ function module.load() local parent_s2s = rawgetopt(parent, "s2s_ssl") or NULL; local host_s2s = rawgetopt(modhost, "s2s_ssl") or parent_s2s; + local request_client_certs = { verify = { "peer", "client_once", }; }; + ssl_ctx_c2s, err_c2s, ssl_cfg_c2s = create_context(host.host, "server", host_c2s, host_ssl, global_c2s); -- for incoming client connections if not ssl_ctx_c2s then module:log("error", "Error creating context for c2s: %s", err_c2s); end - ssl_ctx_s2sout, err_s2sout, ssl_cfg_s2sout = create_context(host.host, "client", host_s2s, host_ssl, global_s2s); -- for outgoing server connections + -- for outgoing server connections + ssl_ctx_s2sout, err_s2sout, ssl_cfg_s2sout = create_context(host.host, "client", host_s2s, host_ssl, global_s2s, request_client_certs); if not ssl_ctx_s2sout then module:log("error", "Error creating contexts for s2sout: %s", err_s2sout); end - ssl_ctx_s2sin, err_s2sin, ssl_cfg_s2sin = create_context(host.host, "server", host_s2s, host_ssl, global_s2s); -- for incoming server connections + -- for incoming server connections + ssl_ctx_s2sin, err_s2sin, ssl_cfg_s2sin = create_context(host.host, "server", host_s2s, host_ssl, global_s2s, request_client_certs); if not ssl_ctx_s2sin then module:log("error", "Error creating contexts for s2sin: %s", err_s2sin); end end -- cgit v1.2.3 From 50f89a9f96e4a37cb367c732fefd9ae40a6d82f9 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 12 Mar 2019 23:13:51 +0100 Subject: net.server_epoll: Optimize timer handling --- net/server_epoll.lua | 83 +++++++++++++++++++--------------------------------- 1 file changed, 30 insertions(+), 53 deletions(-) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index 4bdc2e21..4037f7ab 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -6,9 +6,7 @@ -- -local t_sort = table.sort; local t_insert = table.insert; -local t_remove = table.remove; local t_concat = table.concat; local setmetatable = setmetatable; local tostring = tostring; @@ -20,6 +18,7 @@ local log = require "util.logger".init("server_epoll"); local socket = require "socket"; local luasec = require "ssl"; local gettime = require "util.time".now; +local indexedbheap = require "util.indexedbheap"; local createtable = require "util.table".create; local inet = require "util.net"; local inet_pton = inet.pton; @@ -69,22 +68,24 @@ local fds = createtable(10, 0); -- FD -> conn -- Timer and scheduling -- -local timers = {}; +local timers = indexedbheap.create(); local function noop() end local function closetimer(t) t[1] = 0; t[2] = noop; + timers:remove(t.id); end --- Set to true when timers have changed -local resort_timers = false; +local function reschedule(t, time) + t[1] = time; + timers:reprioritize(t.id, time); +end -- Add absolute timer local function at(time, f) - local timer = { time, f, close = closetimer }; - t_insert(timers, timer); - resort_timers = true; + local timer = { time, f, close = closetimer, reschedule = reschedule, id = nil }; + timer.id = timers:insert(timer, time); return timer; end @@ -97,54 +98,32 @@ end -- Return time until next timeout local function runtimers(next_delay, min_wait) -- Any timers at all? - if not timers[1] then - return next_delay; - end + local now = gettime(); + local peek = timers:peek(); + while peek do - if resort_timers then - -- Sort earliest timers to the end - t_sort(timers, function (a, b) return a[1] > b[1]; end); - resort_timers = false; - end - - -- Iterate from the end and remove completed timers - for i = #timers, 1, -1 do - local timer = timers[i]; - local t, f = timer[1], timer[2]; - -- Get time for every iteration to increase accuracy - local now = gettime(); - if t > now then - -- This timer should not fire yet - local diff = t - now; - if diff < next_delay then - next_delay = diff; - end + if peek > now then + next_delay = peek - now; break; end - local new_timeout = f(now); - if new_timeout then - -- Schedule for 'delay' from the time actually scheduled, not from now, - -- in order to prevent timer drift, unless it already drifted way out of sync. - if (t + new_timeout) > ( now - new_timeout ) then - timer[1] = t + new_timeout; - else - timer[1] = now + new_timeout; - end - resort_timers = true; - else - t_remove(timers, i); + + local _, timer, id = timers:pop(); + local ok, ret = pcall(timer[2], now); + if ok and type(ret) == "number" then + local next_time = now+ret; + timer[1] = next_time; + timers:insert(timer, next_time); end - end - if resort_timers or next_delay < min_wait then - -- Timers may be added from within a timer callback. - -- Those would not be considered for next_delay, - -- and we might sleep for too long, so instead - -- we return a shorter timeout so we can - -- properly sort all new timers. - next_delay = min_wait; + peek = timers:peek(); + end + if peek == nil then + return next_delay; end + if next_delay < min_wait then + return min_wait; + end return next_delay; end @@ -251,8 +230,7 @@ function interface:setreadtimeout(t) end t = t or cfg.read_timeout; if self._readtimeout then - self._readtimeout[1] = gettime() + t; - resort_timers = true; + self._readtimeout:reschedule(gettime() + t); else self._readtimeout = addtimer(t, function () if self:on("readtimeout") then @@ -276,8 +254,7 @@ function interface:setwritetimeout(t) end t = t or cfg.send_timeout; if self._writetimeout then - self._writetimeout[1] = gettime() + t; - resort_timers = true; + self._writetimeout:reschedule(gettime() + t); else self._writetimeout = addtimer(t, function () self:on("disconnect", "write timeout"); -- cgit v1.2.3 From 0666d291f625da2f5dbc8065c05f099d1487874c Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Thu, 14 Mar 2019 16:13:14 +0000 Subject: doc/coding_style.{txt,md}: Update coding style guide --- HACKERS | 2 +- doc/coding_style.md | 805 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 806 insertions(+), 1 deletion(-) create mode 100644 doc/coding_style.md diff --git a/HACKERS b/HACKERS index cae98091..240de63d 100644 --- a/HACKERS +++ b/HACKERS @@ -5,7 +5,7 @@ involved you can join us on our mailing list and discussion rooms. More information on these at https://prosody.im/discuss Patches are welcome, though before sending we would appreciate if you read -docs/coding_style.txt for guidelines on how to format your code, and other tips. +docs/coding_style.md for guidelines on how to format your code, and other tips. Documentation for developers can be found at https://prosody.im/doc/developers diff --git a/doc/coding_style.md b/doc/coding_style.md new file mode 100644 index 00000000..6f34d371 --- /dev/null +++ b/doc/coding_style.md @@ -0,0 +1,805 @@ + +# Prosody Coding Style Guide + +This style guides lists the coding conventions used in the +[Prosody](https://prosody.im/) project. It is based heavily on the [style guide used by the LuaRocks project](https://github.com/luarocks/lua-style-guide). + +## Indentation and formatting + +* Prosody code is indented with tabs at the start of the line, a single + tab per logical indent level: + +```lua +for i, pkg in ipairs(packages) do + for name, version in pairs(pkg) do + if name == searched then + print(version) + end + end +end +``` + +Tab width is configurable in editors, so never assume a particular width. +Specically this means you should not mix tabs and spaces, or use tabs for +alignment of items at different indentation levels. + +* Use LF (Unix) line endings. + +## Comments + +* Comments are encouraged where necessary to explain non-obvious code. + +* In general comments should be used to explain 'why', not 'how' + +### Comment tags + +A comment may be prefixed with one of the following tags: + +* **FIXME**: Indicates a serious problem with the code that should be addressed +* **TODO**: Indicates an open task, feature request or code restructuring that + is primarily of interest to developers (otherwise it should be in the + issue tracker). +* **COMPAT**: Must be used on all code that is present only for backwards-compatibility, + and may be removed one day. For example code that is added to support old + or buggy third-party software or dependencies. + +**Example:** + +```lua +-- TODO: implement method +local function something() + -- FIXME: check conditions +end + +``` + +## Variable names + +* Variable names with larger scope should be more descriptive than those with +smaller scope. One-letter variable names should be avoided except for very +small scopes (less than ten lines) or for iterators. + +* `i` should be used only as a counter variable in for loops (either numeric for +or `ipairs`). + +* Prefer more descriptive names than `k` and `v` when iterating with `pairs`, +unless you are writing a function that operates on generic tables. + +* Use `_` for ignored variables (e.g. in for loops:) + +```lua +for _, item in ipairs(items) do + do_something_with_item(item) +end +``` + +* Generally all identifiers (variables and function names) should use `snake_case`, + i.e. lowercase words joined by `_`. + +```lua +-- bad +local OBJEcttsssss = {} +local thisIsMyObject = {} +local c = function() + -- ...stuff... +end + +-- good +local this_is_my_object = {} + +local function do_that_thing() + -- ...stuff... +end +``` + +> **Rationale:** The standard library uses lowercase APIs, with `joinedlowercase` +names, but this does not scale too well for more complex APIs. `snake_case` +tends to look good enough and not too out-of-place along side the standard +APIs. + +```lua +for _, name in pairs(names) do + -- ...stuff... +end +``` + +* Prefer using `is_` when naming boolean functions: + +```lua +-- bad +local function evil(alignment) + return alignment < 100 +end + +-- good +local function is_evil(alignment) + return alignment < 100 +end +``` + +* `UPPER_CASE` is to be used sparingly, with "constants" only. + +> **Rationale:** "Sparingly", since Lua does not have real constants. This +notation is most useful in libraries that bind C libraries, when bringing over +constants from C. + +* Do not use uppercase names starting with `_`, they are reserved by Lua. + +## Tables + +* When creating a table, prefer populating its fields all at once, if possible: + +```lua +local player = { name = "Jack", class = "Rogue" } +} +``` + +* Items should be separated by commas. If there are many items, put each + key/value on a separate line and use a semi-colon after each item (including + the last one): + +```lua +local player = { + name = "Jack"; + class = "Rogue"; +} +``` + +> **Rationale:** This makes the structure of your tables more evident at a glance. +Trailing commas make it quicker to add new fields and produces shorter diffs. + +* Use plain `key` syntax whenever possible, use `["key"]` syntax when using names +that can't be represented as identifiers and avoid mixing representations in +a declaration: + +```lua +local mytable = { + ["1394-E"] = val1, + ["UTF-8"] = val2, + ["and"] = val2, +} +``` + +## Strings + +* Use `"double quotes"` for strings; use `'single quotes'` when writing strings +that contain double quotes. + +```lua +local name = "Prosody" +local sentence = 'The name of the program is "Prosody"' +``` + +> **Rationale:** Double quotes are used as string delimiters in a larger number of +programming languages. Single quotes are useful for avoiding escaping when +using double quotes in literals. + +## Line lengths + +* There are no hard or soft limits on line lengths. Line lengths are naturally +limited by using one statement per line. If that still produces lines that are +too long (e.g. an expression that produces a line over 256-characters long, +for example), this means the expression is too complex and would do better +split into subexpressions with reasonable names. + +> **Rationale:** No one works on VT100 terminals anymore. If line lengths are a proxy +for code complexity, we should address code complexity instead of using line +breaks to fit mind-bending statements over multiple lines. + +## Function declaration syntax + +* Prefer function syntax over variable syntax. This helps differentiate between +named and anonymous functions. + +```lua +-- bad +local nope = function(name, options) + -- ...stuff... +end + +-- good +local function yup(name, options) + -- ...stuff... +end +``` + +* Perform validation early and return as early as possible. + +```lua +-- bad +local function is_good_name(name, options, arg) + local is_good = #name > 3 + is_good = is_good and #name < 30 + + -- ...stuff... + + return is_good +end + +-- good +local function is_good_name(name, options, args) + if #name < 3 or #name > 30 then + return false + end + + -- ...stuff... + + return true +end +``` + +## Function calls + +* Even though Lua allows it, generally you should not omit parentheses + for functions that take a unique string literal argument. + +```lua +-- bad +local data = get_data"KRP"..tostring(area_number) +-- good +local data = get_data("KRP"..tostring(area_number)) +local data = get_data("KRP")..tostring(area_number) +``` + +> **Rationale:** It is not obvious at a glace what the precedence rules are +when omitting the parentheses in a function call. Can you quickly tell which +of the two "good" examples in equivalent to the "bad" one? (It's the second +one). + +* You should not omit parenthesis for functions that take a unique table +argument on a single line. You may do so for table arguments that span several +lines. + +```lua +local an_instance = a_module.new { + a_parameter = 42, + another_parameter = "yay", +} +``` + +> **Rationale:** The use as in `a_module.new` above occurs alone in a statement, +so there are no precedence issues. + +## Table attributes + +* Use dot notation when accessing known properties. + +```lua +local luke = { + jedi = true, + age = 28, +} + +-- bad +local is_jedi = luke["jedi"] + +-- good +local is_jedi = luke.jedi +``` + +* Use subscript notation `[]` when accessing properties with a variable or if using a table as a list. + +```lua +local vehicles = load_vehicles_from_disk("vehicles.dat") + +if vehicles["Porsche"] then + porsche_handler(vehicles["Porsche"]) + vehicles["Porsche"] = nil +end +for name, cars in pairs(vehicles) do + regular_handler(cars) +end +``` + +> **Rationale:** Using dot notation makes it clearer that the given key is meant +to be used as a record/object field. + +## Functions in tables + +* When declaring modules and classes, declare functions external to the table definition: + +```lua +local my_module = {} + +function my_module.a_function(x) + -- code +end +``` + +* When declaring metatables, declare function internal to the table definition. + +```lua +local version_mt = { + __eq = function(a, b) + -- code + end; + __lt = function(a, b) + -- code + end; +} +``` + +> **Rationale:** Metatables contain special behavior that affect the tables +they're assigned (and are used implicitly at the call site), so it's good to +be able to get a view of the complete behavior of the metatable at a glance. + +This is not as important for objects and modules, which usually have way more +code, and which don't fit in a single screen anyway, so nesting them inside +the table does not gain much: when scrolling a longer file, it is more evident +that `check_version` is a method of `Api` if it says `function Api:check_version()` +than if it says `check_version = function()` under some indentation level. + +## Variable declaration + +* Always use `local` to declare variables. + +```lua +-- bad +superpower = get_superpower() + +-- good +local superpower = get_superpower() +``` + +> **Rationale:** Not doing so will result in global variables to avoid polluting +the global namespace. + +## Variable scope + +* Assign variables with the smallest possible scope. + +```lua +-- bad +local function good() + local name = get_name() + + test() + print("doing stuff..") + + --...other stuff... + + if name == "test" then + return false + end + + return name +end + +-- good +local bad = function() + test() + print("doing stuff..") + + --...other stuff... + + local name = get_name() + + if name == "test" then + return false + end + + return name +end +``` + +> **Rationale:** Lua has proper lexical scoping. Declaring the function later means that its +scope is smaller, so this makes it easier to check for the effects of a variable. + +## Conditional expressions + +* False and nil are falsy in conditional expressions. Use shortcuts when you +can, unless you need to know the difference between false and nil. + +```lua +-- bad +if name ~= nil then + -- ...stuff... +end + +-- good +if name then + -- ...stuff... +end +``` + +* Avoid designing APIs which depend on the difference between `nil` and `false`. + +* Use the `and`/`or` idiom for the pseudo-ternary operator when it results in +more straightforward code. When nesting expressions, use parentheses to make it +easier to scan visually: + +```lua +local function default_name(name) + -- return the default "Waldo" if name is nil + return name or "Waldo" +end + +local function brew_coffee(machine) + return (machine and machine.is_loaded) and "coffee brewing" or "fill your water" +end +``` + +Note that the `x and y or z` as a substitute for `x ? y : z` does not work if +`y` may be `nil` or `false` so avoid it altogether for returning booleans or +values which may be nil. + +## Blocks + +* Use single-line blocks only for `then return`, `then break` and `function return` (a.k.a "lambda") constructs: + +```lua +-- good +if test then break end + +-- good +if not ok then return nil, "this failed for this reason: " .. reason end + +-- good +use_callback(x, function(k) return k.last end) + +-- good +if test then + return false +end + +-- bad +if test < 1 and do_complicated_function(test) == false or seven == 8 and nine == 10 then do_other_complicated_function() end + +-- good +if test < 1 and do_complicated_function(test) == false or seven == 8 and nine == 10 then + do_other_complicated_function() + return false +end +``` + +* Separate statements onto multiple lines. Do not use semicolons as statement terminators. + +```lua +-- bad +local whatever = "sure"; +a = 1; b = 2 + +-- good +local whatever = "sure" +a = 1 +b = 2 +``` + +## Spacing + +* Use a space after `--`. + +```lua +--bad +-- good +``` + +* Always put a space after commas and between operators and assignment signs: + +```lua +-- bad +local x = y*9 +local numbers={1,2,3} +numbers={1 , 2 , 3} +numbers={1 ,2 ,3} +local strings = { "hello" + , "Lua" + , "world" + } +dog.set( "attr",{ + age="1 year", + breed="Bernese Mountain Dog" +}) + +-- good +local x = y * 9 +local numbers = {1, 2, 3} +local strings = { + "hello"; + "Lua"; + "world"; +} +dog.set("attr", { + age = "1 year", + breed = "Bernese Mountain Dog", +}) +``` + +* Indent tables and functions according to the start of the line, not the construct: + +```lua +-- bad +local my_table = { + "hello", + "world", + } +using_a_callback(x, function(...) + print("hello") + end) + +-- good +local my_table = { + "hello"; + "world"; +} +using_a_callback(x, function(...) + print("hello") +end) +``` + +> **Rationale:** This keep indentation levels aligned at predictable places. You don't +need to realign the entire block if something in the first line changes (such as +replacing `x` with `xy` in the `using_a_callback` example above). + +* The concatenation operator gets a pass for avoiding spaces: + +```lua +-- okay +local message = "Hello, "..user.."! This is your day # "..day.." in our platform!" +``` + +> **Rationale:** Being at the baseline, the dots already provide some visual spacing. + +* No spaces after the name of a function in a declaration or in its arguments: + +```lua +-- bad +local function hello ( name, language ) + -- code +end + +-- good +local function hello(name, language) + -- code +end +``` + +* Add blank lines between functions: + +```lua +-- bad +local function foo() + -- code +end +local function bar() + -- code +end + +-- good +local function foo() + -- code +end + +local function bar() + -- code +end +``` + +* Avoid aligning variable declarations: + +```lua +-- bad +local a = 1 +local long_identifier = 2 + +-- good +local a = 1 +local long_identifier = 2 +``` + +> **Rationale:** This produces extra diffs which add noise to `git blame`. + +* Alignment is occasionally useful when logical correspondence is to be highlighted: + +```lua +-- okay +sys_command(form, UI_FORM_UPDATE_NODE, "a", FORM_NODE_HIDDEN, false) +sys_command(form, UI_FORM_UPDATE_NODE, "sample", FORM_NODE_VISIBLE, false) +``` + +## Typing + +* In non-performance critical code, it can be useful to add type-checking assertions +for function arguments: + +```lua +function manif.load_manifest(repo_url, lua_version) + assert(type(repo_url) == "string") + assert(type(lua_version) == "string" or not lua_version) + + -- ... +end +``` + +* Use the standard functions for type conversion, avoid relying on coercion: + +```lua +-- bad +local total_score = review_score .. "" + +-- good +local total_score = tostring(review_score) +``` + +## Errors + +* Functions that can fail for reasons that are expected (e.g. I/O) should +return `nil` and a (string) error message on error, possibly followed by other +return values such as an error code. + +* On errors such as API misuse, an error should be thrown, either with `error()` +or `assert()`. + +## Modules + +Follow [these guidelines](http://hisham.hm/2014/01/02/how-to-write-lua-modules-in-a-post-module-world/) for writing modules. In short: + +* Always require a module into a local variable named after the last component of the module’s full name. + +```lua +local bar = require("foo.bar") -- requiring the module + +bar.say("hello") -- using the module +``` + +* Don’t rename modules arbitrarily: + +```lua +-- bad +local skt = require("socket") +``` + +> **Rationale:** Code is much harder to read if we have to keep going back to the top +to check how you chose to call a module. + +* Start a module by declaring its table using the same all-lowercase local +name that will be used to require it. You may use an LDoc comment to identify +the whole module path. + +```lua +--- @module foo.bar +local bar = {} +``` + +* Try to use names that won't clash with your local variables. For instance, don't +name your module something like “size”. + +* Use `local function` to declare _local_ functions only: that is, functions +that won’t be accessible from outside the module. + +That is, `local function helper_foo()` means that `helper_foo` is really local. + +* Public functions are declared in the module table, with dot syntax: + +```lua +function bar.say(greeting) + print(greeting) +end +``` + +> **Rationale:** Visibility rules are made explicit through syntax. + +* Do not set any globals in your module and always return a table in the end. + +* If you would like your module to be used as a function, you may set the +`__call` metamethod on the module table instead. + +> **Rationale:** Modules should return tables in order to be amenable to have their +contents inspected via the Lua interactive interpreter or other tools. + +* Requiring a module should cause no side-effect other than loading other +modules and returning the module table. + +* A module should not have state. If a module needs configuration, turn + it into a factory. For example, do not make something like this: + +```lua +-- bad +local mp = require "MessagePack" +mp.set_integer("unsigned") +``` + +and do something like this instead: + +```lua +-- good +local messagepack = require("messagepack") +local mpack = messagepack.new({integer = "unsigned"}) +``` + +* The invocation of require may omit parentheses around the module name: + +```lua +local bla = require "bla" +``` + +## Metatables, classes and objects + +If creating a new type of object that has a metatable and methods, the +metatable and methods table should be separate, and the metatable name +should end with `_mt`. + +```lua +local mytype_methods = {}; +local mytype_mt = { __index = mytype_methods }; + +function mytype_methods:add_new_thing(thing) +end + +local function new() + return setmetatable({}, mytype_mt); +end + +return { new = new }; +``` + +* Use the method notation when invoking methods: + +``` +-- bad +my_object.my_method(my_object) + +-- good +my_object:my_method() +``` + +> **Rationale:** This makes it explicit that the intent is to use the function as a method. + +* Do not rely on the `__gc` metamethod to release resources other than memory. +If your object manage resources such as files, add a `close` method to their +APIs and do not auto-close via `__gc`. Auto-closing via `__gc` would entice +users of your module to not close resources as soon as possible. (Note that +the standard `io` library does not follow this recommendation, and users often +forget that not closing files immediately can lead to "too many open files" +errors when the program runs for a while.) + +> **Rationale:** The garbage collector performs automatic *memory* management, +dealing with memory only. There is no guarantees as to when the garbage +collector will be invoked, and memory pressure does not correlate to pressure +on other resources. + +## File structure + +* Lua files should be named in all lowercase. + +* Tests should be in a top-level `spec` directory. Prosody uses +[Busted](http://olivinelabs.com/busted/) for testing. + +## Static checking + +All code should pass [luacheck](https://github.com/mpeterv/luacheck) using +the `.luacheckrc` provided in the Prosody repository, and using miminal +inline exceptions. + +* luacheck warnings of class 211, 212, 213 (unused variable, argument or loop +variable) may be ignored, if the unused variable was added explicitly: for +example, sometimes it is useful, for code understandability, to spell out what +the keys and values in a table are, even if you're only using one of them. +Another example is a function that needs to follow a given signature for API +reasons (e.g. a callback that follows a given format) but doesn't use some of +its arguments; it's better to spell out in the argument what the API the +function implements is, instead of adding `_` variables. + +``` +local foo, bar = some_function() --luacheck: ignore 212/foo +print(bar) +``` + +* luacheck warning 542 (empty if branch) can also be ignored, when a sequence +of `if`/`elseif`/`else` blocks implements a "switch/case"-style list of cases, +and one of the cases is meant to mean "pass". For example: + +```lua +if warning >= 600 and warning <= 699 then + print("no whitespace warnings") +elseif warning == 542 then --luacheck: ignore 542 + -- pass +else + print("got a warning: "..warning) +end +``` + +> **Rationale:** This avoids writing negated conditions in the final fallback +case, and it's easy to add another case to the construct without having to +edit the fallback. + -- cgit v1.2.3 From 8c6de8ceba4bffa55a022b141d6ce70aae0d6d65 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Thu, 14 Mar 2019 16:18:00 +0000 Subject: Actually remove coding_style.txt --- doc/coding_style.txt | 33 --------------------------------- 1 file changed, 33 deletions(-) delete mode 100644 doc/coding_style.txt diff --git a/doc/coding_style.txt b/doc/coding_style.txt deleted file mode 100644 index af44da1a..00000000 --- a/doc/coding_style.txt +++ /dev/null @@ -1,33 +0,0 @@ -This file describes some coding styles to try and adhere to when contributing to this project. -Please try to follow, and feel free to fix code you see not following this standard. - -== Indentation == - - 1 tab indentation for all blocks - -== Spacing == - -No space between function names and parenthesis and parenthesis and parameters: - - function foo(bar, baz) - -Single space between braces and key/value pairs in table constructors: - - { foo = "bar", bar = "foo" } - -== Local variable naming == - -In this project there are many places where use of globals is restricted, and locals used for faster access. - -Local versions of standard functions should follow the below form: - - math.random -> m_random - string.char -> s_char - -== Miscellaneous == - -Single-statement blocks may be written on one line when short - - if foo then bar(); end - -'do' and 'then' keywords should be placed at the end of the line, and never on a line by themself. -- cgit v1.2.3 From d22354c8aafecdb0fef59165c3318f00817c855c Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 16 Mar 2019 18:43:11 +0100 Subject: configure: Separate flags related to compiler warnings This should make it more obvious that these are related --- configure | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/configure b/configure index dec7b60d..516ebaec 100755 --- a/configure +++ b/configure @@ -23,7 +23,8 @@ EXCERTS="yes" PRNG= PRNGLIBS= -CFLAGS="-fPIC -Wall -pedantic -std=c99" +CFLAGS="-fPIC -std=c99" +CFLAGS="$CFLAGS -Wall -pedantic" LDFLAGS="-shared" IDN_LIBRARY="idn" -- cgit v1.2.3 From cfbebf9baa57a17a4d47b5c32b77a755a08869b6 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 16 Mar 2019 18:51:02 +0100 Subject: configure: Enable more compiler warnings --- configure | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure b/configure index 516ebaec..1343d34b 100755 --- a/configure +++ b/configure @@ -24,7 +24,7 @@ PRNG= PRNGLIBS= CFLAGS="-fPIC -std=c99" -CFLAGS="$CFLAGS -Wall -pedantic" +CFLAGS="$CFLAGS -Wall -pedantic -Wextra -Wshadow -Wformat=2" LDFLAGS="-shared" IDN_LIBRARY="idn" -- cgit v1.2.3 From 62f33cd891e824a8d9c5a99c5d1a51af6c23835d Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 18 Mar 2019 09:50:23 +0000 Subject: MUC: Update error message for consistency --- plugins/muc/muc.lib.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index a8d3d790..c828d17d 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -498,7 +498,7 @@ function room_mt:handle_normal_presence(origin, stanza) if orig_occupant == nil and not muc_x and stanza.attr.type == nil then module:log("debug", "Attempted join without , possibly desynced"); origin.send(st.error_reply(stanza, "cancel", "item-not-found", - "You must join the room before sending presence updates")); + "You are not currently connected to this chat")); return true; end -- cgit v1.2.3 From cffb6e6e7bf6c2178ab8dafd092f59c0b521d69b Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 17 Mar 2019 20:40:01 +0100 Subject: util.serialization: Optimize handling of last table separator Fewer next() calls and a step towards allowing use of a different iterator. --- util/serialization.lua | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/util/serialization.lua b/util/serialization.lua index 7ae77a3a..c64bfec1 100644 --- a/util/serialization.lua +++ b/util/serialization.lua @@ -163,7 +163,9 @@ local function new(opt) local indent = s_rep(indentwith, d); local numkey = 1; local ktyp, vtyp; + local had_items = false; for k,v in next,t do + had_items = true; o[l], l = itemstart, l + 1; o[l], l = indent, l + 1; ktyp, vtyp = type(k), type(v); @@ -194,14 +196,10 @@ local function new(opt) else o[l], l = ser(v), l + 1; end - -- last item? - if next(t, k) ~= nil then - o[l], l = itemsep, l + 1; - else - o[l], l = itemlast, l + 1; - end + o[l], l = itemsep, l + 1; end - if next(t) ~= nil then + if had_items then + o[l - 1] = itemlast; o[l], l = s_rep(indentwith, d-1), l + 1; end o[l], l = tend, l +1; -- cgit v1.2.3 From 94ceae0f0b8560fe26ec148cbb8d8237739efc3e Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 17 Mar 2019 21:16:27 +0100 Subject: util.serialization: Allow overriding table iterator Could be useful to eg swap it out with sorted_pairs to get a stable serialization. Default to next() wrapper to avoid metatable tricks from pairs(). --- util/serialization.lua | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/util/serialization.lua b/util/serialization.lua index c64bfec1..2ead8c12 100644 --- a/util/serialization.lua +++ b/util/serialization.lua @@ -33,6 +33,10 @@ local function to_hex(s) return (s_gsub(s, ".", char_to_hex)); end +local function rawpairs(t) + return next, t, nil; +end + local function fatal_error(obj, why) error("Can't serialize "..type(obj) .. (why and ": ".. why or "")); end @@ -122,6 +126,7 @@ local function new(opt) local freeze = opt.freeze; local maxdepth = opt.maxdepth or 127; local multirefs = opt.multiref; + local table_pairs = opt.table_iterator or rawpairs; -- serialize one table, recursively -- t - table being serialized @@ -164,7 +169,7 @@ local function new(opt) local numkey = 1; local ktyp, vtyp; local had_items = false; - for k,v in next,t do + for k,v in table_pairs(t) do had_items = true; o[l], l = itemstart, l + 1; o[l], l = indent, l + 1; -- cgit v1.2.3 From 34f85c79c0f3127ed03d10d8d94d1e9a152798ed Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 17 Mar 2019 21:25:33 +0100 Subject: util.serialization: Use util.hex --- util/serialization.lua | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/util/serialization.lua b/util/serialization.lua index 2ead8c12..60e341cf 100644 --- a/util/serialization.lua +++ b/util/serialization.lua @@ -16,6 +16,8 @@ local s_char = string.char; local s_match = string.match; local t_concat = table.concat; +local to_hex = require "util.hex".to; + local pcall = pcall; local envload = require"util.envload".envload; @@ -24,15 +26,6 @@ local m_type = math.type or function (n) return n % 1 == 0 and n <= 9007199254740992 and n >= -9007199254740992 and "integer" or "float"; end; -local char_to_hex = {}; -for i = 0,255 do - char_to_hex[s_char(i)] = s_format("%02x", i); -end - -local function to_hex(s) - return (s_gsub(s, ".", char_to_hex)); -end - local function rawpairs(t) return next, t, nil; end -- cgit v1.2.3 From 23577330fd9826da26a2ab0a6a3f1d6b82e5dfb8 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Tue, 19 Mar 2019 09:04:40 +0000 Subject: moduleapi: New API for modules to set a status --- core/moduleapi.lua | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/core/moduleapi.lua b/core/moduleapi.lua index c6193cfd..2db7433a 100644 --- a/core/moduleapi.lua +++ b/core/moduleapi.lua @@ -17,6 +17,8 @@ local st = require "util.stanza"; local cache = require "util.cache"; local errutil = require "util.error"; local promise = require "util.promise"; +local time_now = require "util.time".now; +local format = require "util.format".format; local t_insert, t_remove, t_concat = table.insert, table.remove, table.concat; local error, setmetatable, type = error, setmetatable, type; @@ -513,4 +515,33 @@ function api:measure_global_event(event_name, stat_name) return self:measure_object_event(prosody.events.wrappers, event_name, stat_name); end +local status_priorities = { error = 3, warn = 2, info = 1, core = 0 }; + +function api:set_status(status_type, status_message, override) + local priority = status_priorities[status_type]; + if not priority then + self:log("error", "set_status: Invalid status type '%s', assuming 'info'"); + status_type, priority = "info", status_priorities.info; + end + local current_priority = status_priorities[self.status_type] or 0; + -- By default an 'error' status can only be overwritten by another 'error' status + if (current_priority >= status_priorities.error and priority < current_priority and override ~= true) + or (override == false and current_priority > priority) then + self:log("debug", "Ignoring status"); + return; + end + self.status_type, self.status_message, self.status_time = status_type, status_message, time_now(); + self:log("debug", "New status: %s", status_type); + self:fire_event("module-status/updated", { name = self.name }); +end + +function api:log_status(level, msg, ...) + self:set_status(level, format(msg, ...)); + return self:log(level, msg, ...); +end + +function api:get_status() + return self.status_type, self.status_message, self.status_time; +end + return api; -- cgit v1.2.3 From cf15c2a1e0ddada23688ad289a5b407a334d61e1 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Tue, 19 Mar 2019 09:05:15 +0000 Subject: modulemanager: Set module status on successful or failed module load --- core/modulemanager.lua | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/modulemanager.lua b/core/modulemanager.lua index 17602459..0d24381a 100644 --- a/core/modulemanager.lua +++ b/core/modulemanager.lua @@ -169,6 +169,7 @@ local function do_load_module(host, module_name, state) local mod, err = pluginloader.load_code(module_name, nil, pluginenv); if not mod then log("error", "Unable to load module '%s': %s", module_name or "nil", err or "nil"); + api_instance:set_status("error", "Failed to load (see log)"); return nil, err; end @@ -182,6 +183,7 @@ local function do_load_module(host, module_name, state) ok, err = call_module_method(pluginenv, "load"); if not ok then log("warn", "Error loading module '%s' on '%s': %s", module_name, host, err or "nil"); + api_instance:set_status("warn", "Error during load (see log)"); end end api_instance.reloading, api_instance.saved_state = nil, nil; @@ -204,6 +206,9 @@ local function do_load_module(host, module_name, state) if not ok then modulemap[api_instance.host][module_name] = nil; log("error", "Error initializing module '%s' on '%s': %s", module_name, host, err or "nil"); + api_instance:set_status("warn", "Error during load (see log)"); + else + api_instance:set_status("core", "Loaded", false); end return ok and pluginenv, err; end -- cgit v1.2.3 From ab545f19a339c76afe6d24912a79e05ee5f4d94c Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Tue, 19 Mar 2019 09:05:37 +0000 Subject: mod_admin_telnet: Show module status in module:list() --- plugins/mod_admin_telnet.lua | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 7fae8983..34cc4dc6 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -480,7 +480,12 @@ function def_env.module:list(hosts) end else for _, name in ipairs(modules) do - print(" "..name); + local status, status_text = modulemanager.get_module(host, name).module:get_status(); + local status_summary = ""; + if status == "warn" or status == "error" then + status_summary = (" (%s: %s)"):format(status, status_text); + end + print((" %s%s"):format(name, status_summary)); end end end -- cgit v1.2.3 From d89b760be1cc44c166a587b8b32b67536c92c9d9 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Tue, 19 Mar 2019 09:07:36 +0000 Subject: .luacheckrc: Update to reflect new module API methods --- .luacheckrc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.luacheckrc b/.luacheckrc index b2fa7cdb..f0cb93a8 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -34,7 +34,6 @@ files["plugins/"] = { "module.name", "module.host", "module._log", - "module.log", "module.event_handlers", "module.reloading", "module.saved_state", @@ -65,12 +64,15 @@ files["plugins/"] = { "module.get_option_scalar", "module.get_option_set", "module.get_option_string", + "module.get_status", "module.handle_items", "module.hook", "module.hook_global", "module.hook_object_event", "module.hook_tag", "module.load_resource", + "module.log", + "module.log_status", "module.measure", "module.measure_event", "module.measure_global_event", @@ -82,6 +84,7 @@ files["plugins/"] = { "module.send", "module.send_iq", "module.set_global", + "module.set_status", "module.shared", "module.unhook", "module.unhook_object_event", -- cgit v1.2.3 From c6efcf09bec3f89c768ebab216c69ce116b091e6 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Tue, 19 Mar 2019 09:08:06 +0000 Subject: mod_component: Set module status to indicate whether component is connected --- plugins/mod_component.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/mod_component.lua b/plugins/mod_component.lua index b8c87dee..b1ffc81d 100644 --- a/plugins/mod_component.lua +++ b/plugins/mod_component.lua @@ -49,6 +49,7 @@ function module.add_host(module) local send; local function on_destroy(session, err) --luacheck: ignore 212/err + module:set_status("warn", err and ("Disconnected: "..err) or "Disconnected"); env.connected = false; env.session = false; send = nil; @@ -102,6 +103,7 @@ function module.add_host(module) module:log("info", "External component successfully authenticated"); session.send(st.stanza("handshake")); module:fire_event("component-authenticated", { session = session }); + module:set_status("info", "Connected"); return true; end -- cgit v1.2.3 From 755b5076441531a8c8b6f2b2ab831800768402ef Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Tue, 19 Mar 2019 09:08:33 +0000 Subject: mod_s2s: Set warning status if not listening on any ports --- plugins/mod_s2s/s2sout.lib.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_s2s/s2sout.lib.lua b/plugins/mod_s2s/s2sout.lib.lua index 5f765da8..34e322d2 100644 --- a/plugins/mod_s2s/s2sout.lib.lua +++ b/plugins/mod_s2s/s2sout.lib.lua @@ -318,7 +318,7 @@ module:hook_global("service-added", function (event) local s2s_sources = portmanager.get_active_services():get("s2s"); if not s2s_sources then - module:log("warn", "s2s not listening on any ports, outgoing connections may fail"); + module:log_status("warn", "s2s not listening on any ports, outgoing connections may fail"); return; end for source, _ in pairs(s2s_sources) do -- cgit v1.2.3 From 992497531e9000cc9139a159cd21ea808e1b636e Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Tue, 19 Mar 2019 09:08:56 +0000 Subject: mod_muc_mam: Set error status if loaded on incorrect host type --- plugins/mod_muc_mam.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_muc_mam.lua b/plugins/mod_muc_mam.lua index d414a449..7d429482 100644 --- a/plugins/mod_muc_mam.lua +++ b/plugins/mod_muc_mam.lua @@ -4,7 +4,7 @@ -- This file is MIT/X11 licensed. if module:get_host_type() ~= "component" then - module:log("error", "mod_%s should be loaded only on a MUC component, not normal hosts", module.name); + module:log_status("error", "mod_%s should be loaded only on a MUC component, not normal hosts", module.name); return; end -- cgit v1.2.3 From 4901e830856ee4d0c0bec09a72b742c9d3c234d0 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Wed, 20 Mar 2019 12:18:34 +0000 Subject: util.startup: Give function a more generic name so it can apply to all warnings --- util/startup.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/util/startup.lua b/util/startup.lua index c101c290..4d3c6e4e 100644 --- a/util/startup.lua +++ b/util/startup.lua @@ -96,7 +96,7 @@ function startup.init_logging() end); end -function startup.log_dependency_warnings() +function startup.log_startup_warnings() dependencies.log_warnings(); end @@ -518,7 +518,7 @@ function startup.prosodyctl() startup.read_version(); startup.switch_user(); startup.check_dependencies(); - startup.log_dependency_warnings(); + startup.log_startup_warnings(); startup.check_unwriteable(); startup.load_libraries(); startup.init_http_client(); @@ -543,7 +543,7 @@ function startup.prosody() startup.add_global_prosody_functions(); startup.read_version(); startup.log_greeting(); - startup.log_dependency_warnings(); + startup.log_startup_warnings(); startup.load_secondary_libraries(); startup.init_http_client(); startup.init_data_store(); -- cgit v1.2.3 From 76ebc7778e97a310ebd456c4da884496f8b428a0 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Wed, 20 Mar 2019 12:19:43 +0000 Subject: configmanager: Add support for returning warnings --- core/configmanager.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core/configmanager.lua b/core/configmanager.lua index 1e67da9b..579db3b0 100644 --- a/core/configmanager.lua +++ b/core/configmanager.lua @@ -9,7 +9,7 @@ local _G = _G; local setmetatable, rawget, rawset, io, os, error, dofile, type, pairs = setmetatable, rawget, rawset, io, os, error, dofile, type, pairs; -local format, math_max = string.format, math.max; +local format, math_max, t_insert = string.format, math.max, table.insert; local envload = require"util.envload".envload; local deps = require"util.dependencies"; @@ -102,6 +102,7 @@ do local pcall = _G.pcall; parser = {}; function parser.load(data, config_file, config_table) + local warnings = {}; local env; -- The ' = true' are needed so as not to set off __newindex when we assign the functions below env = setmetatable({ @@ -217,7 +218,7 @@ do return nil, err; end - return true; + return true, warnings; end end -- cgit v1.2.3 From 1e6c93ec05b5aba14b05c01a3a8cac39722a9849 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Wed, 20 Mar 2019 12:20:51 +0000 Subject: configmanager: Emit warning for duplicated config options --- core/configmanager.lua | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/core/configmanager.lua b/core/configmanager.lua index 579db3b0..41034df8 100644 --- a/core/configmanager.lua +++ b/core/configmanager.lua @@ -16,6 +16,7 @@ local deps = require"util.dependencies"; local resolve_relative_path = require"util.paths".resolve_relative_path; local glob_to_pattern = require"util.paths".glob_to_pattern; local path_sep = package.config:sub(1,1); +local get_traceback_table = require "util.debug".get_traceback_table; local encodings = deps.softreq"util.encodings"; local nameprep = encodings and encodings.stringprep.nameprep or function (host) return host:lower(); end @@ -100,8 +101,17 @@ end -- Built-in Lua parser do local pcall = _G.pcall; + local function get_line_number(config_file) + local tb = get_traceback_table(nil, 2); + for i = 1, #tb do + if tb[i].info.short_src == config_file then + return tb[i].info.currentline; + end + end + end parser = {}; function parser.load(data, config_file, config_table) + local set_options = {}; -- set_options[host.."/"..option_name] = true (when the option has been set already in this file) local warnings = {}; local env; -- The ' = true' are needed so as not to set off __newindex when we assign the functions below @@ -116,6 +126,12 @@ do return rawget(_G, k); end, __newindex = function (_, k, v) + local host = env.__currenthost or "*"; + local option_path = host.."/"..k; + if set_options[option_path] then + t_insert(warnings, ("%s:%d: Duplicate option '%s'"):format(config_file, get_line_number(config_file), k)); + end + set_options[option_path] = true; set(config_table, env.__currenthost or "*", k, v); end }); -- cgit v1.2.3 From 7dfdcd5e09abe4165e13e6a741f8e04cf3e6082d Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Wed, 20 Mar 2019 12:45:08 +0000 Subject: configmanager: Pass through warnings from included files --- core/configmanager.lua | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/core/configmanager.lua b/core/configmanager.lua index 41034df8..090a6a0a 100644 --- a/core/configmanager.lua +++ b/core/configmanager.lua @@ -7,8 +7,8 @@ -- local _G = _G; -local setmetatable, rawget, rawset, io, os, error, dofile, type, pairs = - setmetatable, rawget, rawset, io, os, error, dofile, type, pairs; +local setmetatable, rawget, rawset, io, os, error, dofile, type, pairs, ipairs = + setmetatable, rawget, rawset, io, os, error, dofile, type, pairs, ipairs; local format, math_max, t_insert = string.format, math.max, table.insert; local envload = require"util.envload".envload; @@ -212,6 +212,11 @@ do if f then local ret, err = parser.load(f:read("*a"), file, config_table); if not ret then error(err:gsub("%[string.-%]", file), 0); end + if err then + for _, warning in ipairs(err) do + t_insert(warnings, warning); + end + end end if not f then error("Error loading included "..file..": "..err, 0); end return f, err; -- cgit v1.2.3 From 6fc745f13a6ac47780c67a7e9efceb9b8785ada4 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Wed, 20 Mar 2019 12:45:58 +0000 Subject: util.startup: Log configuration warnings at startup --- util/startup.lua | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/util/startup.lua b/util/startup.lua index 4d3c6e4e..966f2934 100644 --- a/util/startup.lua +++ b/util/startup.lua @@ -7,6 +7,7 @@ local logger = require "util.logger"; local log = logger.init("startup"); local config = require "core.configmanager"; +local config_warnings; local dependencies = require "util.dependencies"; @@ -64,6 +65,8 @@ function startup.read_config() print("**************************"); print(""); os.exit(1); + elseif err and #err > 0 then + config_warnings = err; end prosody.config_loaded = true; end @@ -98,6 +101,9 @@ end function startup.log_startup_warnings() dependencies.log_warnings(); + for _, warning in ipairs(config_warnings) do + log("warn", "Configuration warning: %s", warning); + end end function startup.sanity_check() -- cgit v1.2.3 From 20f878694972c7a5d6202b8cf62aa8ae97fe780f Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Wed, 20 Mar 2019 13:44:29 +0000 Subject: util.startup: Don't die if there are no config warnings to log (thanks buildbot) --- util/startup.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/util/startup.lua b/util/startup.lua index 966f2934..7a1a95aa 100644 --- a/util/startup.lua +++ b/util/startup.lua @@ -101,8 +101,10 @@ end function startup.log_startup_warnings() dependencies.log_warnings(); - for _, warning in ipairs(config_warnings) do - log("warn", "Configuration warning: %s", warning); + if config_warnings then + for _, warning in ipairs(config_warnings) do + log("warn", "Configuration warning: %s", warning); + end end end -- cgit v1.2.3 From d7761bd914bd38e43de12c248196bc81307c71c5 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Fri, 20 Oct 2017 12:53:53 +0200 Subject: mod_storage_internal,_sql: Add limit to number of items in an archive store (fixes #733) --- plugins/mod_storage_internal.lua | 35 +++++++++++++++++++++++++++++++++++ plugins/mod_storage_sql.lua | 39 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/plugins/mod_storage_internal.lua b/plugins/mod_storage_internal.lua index 42b451bd..d812c0e9 100644 --- a/plugins/mod_storage_internal.lua +++ b/plugins/mod_storage_internal.lua @@ -1,3 +1,4 @@ +local cache = require "util.cache"; local datamanager = require "core.storagemanager".olddm; local array = require "util.array"; local datetime = require "util.datetime"; @@ -7,6 +8,9 @@ local id = require "util.id".medium; local host = module.host; +local archive_item_limit = module:get_option_number("storage_archive_item_limit", 1000); +local archive_item_count_cache = cache.new(module:get_option("storage_archive_item_limit_cache_size", 1000)); + local driver = {}; function driver:open(store, typ) @@ -54,28 +58,56 @@ function archive:append(username, key, value, when, with) value.attr.stamp = datetime.datetime(when); value.attr.stamp_legacy = datetime.legacy(when); + local item_count = archive_item_count_cache:get(username); + if key then local items, err = datamanager.list_load(username, host, self.store); if not items and err then return items, err; end + + -- Check the quota + item_count = items and #items or 0; + archive_item_count_cache:set(username, item_count); + if item_count >= archive_item_limit then + module:log("debug", "%s reached or over quota, not adding to store", username); + return nil, "quota-limit"; + end + if items then + -- Filter out any item with the same key as the one being added items = array(items); items:filter(function (item) return item.key ~= key; end); + value.key = key; items:push(value); local ok, err = datamanager.list_store(username, host, self.store, items); if not ok then return ok, err; end + archive_item_count_cache:set(username, #items); return key; end else + if not item_count then -- Item count not cached? + -- We need to load the list to get the number of items currently stored + local items, err = datamanager.list_load(username, host, self.store); + if not items and err then return items, err; end + item_count = items and #items or 0; + archive_item_count_cache:set(username, item_count); + end + if item_count >= archive_item_limit then + module:log("debug", "%s reached or over quota, not adding to store", username); + return nil, "quota-limit"; + end key = id(); end + module:log("debug", "%s has %d items out of %d limit", username, item_count, archive_item_limit); + value.key = key; local ok, err = datamanager.list_append(username, host, self.store, value); if not ok then return ok, err; end + archive_item_count_cache:set(username, item_count+1); return key; end @@ -158,6 +190,7 @@ end function archive:delete(username, query) if not query or next(query) == nil then + archive_item_count_cache:set(username, nil); return datamanager.list_store(username, host, self.store, nil); end local items, err = datamanager.list_load(username, host, self.store); @@ -165,6 +198,7 @@ function archive:delete(username, query) if err then return items, err; end + archive_item_count_cache:set(username, 0); -- Store is empty return 0; end @@ -214,6 +248,7 @@ function archive:delete(username, query) end local ok, err = datamanager.list_store(username, host, self.store, items); if not ok then return ok, err; end + archive_item_count_cache:set(username, #items); return count; end diff --git a/plugins/mod_storage_sql.lua b/plugins/mod_storage_sql.lua index 5c0c0208..4fe2a262 100644 --- a/plugins/mod_storage_sql.lua +++ b/plugins/mod_storage_sql.lua @@ -1,6 +1,7 @@ -- luacheck: ignore 212/self +local cache = require "util.cache"; local json = require "util.json"; local sql = require "util.sql"; local xml_parse = require "util.xml".parse; @@ -148,6 +149,9 @@ end --- Archive store API +local archive_item_limit = module:get_option_number("storage_archive_item_limit", 1000); +local archive_item_count_cache = cache.new(module:get_option("storage_archive_item_limit_cache_size", 1000)); + -- luacheck: ignore 512 431/user 431/store local map_store = {}; map_store.__index = map_store; @@ -231,6 +235,32 @@ archive_store.caps = { }; archive_store.__index = archive_store function archive_store:append(username, key, value, when, with) + local item_count = archive_item_count_cache:get(username); + if not item_count then + local ok, ret = engine:transaction(function() + local count_sql = [[ + SELECT COUNT(*) FROM "prosodyarchive" + WHERE "host"=? AND "user"=? AND "store"=?; + ]]; + local result = engine:select(count_sql, host, user, store); + if result then + for row in result do + item_count = row[1]; + end + end + end); + if not ok or not item_count then + module:log("error", "Failed while checking quota for %s: %s", username, ret); + return nil, "Failure while checking quota"; + end + archive_item_count_cache:set(username, item_count); + end + + module:log("debug", "%s has %d items out of %d limit", username, item_count, archive_item_limit); + if item_count >= archive_item_limit then + return nil, "quota-limit"; + end + local user,store = username,self.store; when = when or os.time(); with = with or ""; @@ -245,12 +275,18 @@ function archive_store:append(username, key, value, when, with) VALUES (?,?,?,?,?,?,?,?); ]]; if key then - engine:delete(delete_sql, host, user or "", store, key); + local result, err = engine:delete(delete_sql, host, user or "", store, key); + if result then + item_count = item_count - result:affected(); + archive_item_count_cache:set(username, item_count); + end else + item_count = item_count + 1; key = uuid.generate(); end local t, encoded_value = assert(serialize(value)); engine:insert(insert_sql, host, user or "", store, when, with, key, t, encoded_value); + archive_item_count_cache:set(username, item_count+1); return key; end); if not ok then return ok, ret; end @@ -422,6 +458,7 @@ function archive_store:delete(username, query) end return engine:delete(sql_query, unpack(args)); end); + archive_item_count_cache:set(username, nil); return ok and stmt:affected(), stmt; end -- cgit v1.2.3 From 0028ea46e2aed1e0522da59b3d31912afea2c54a Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 22 Mar 2019 18:01:24 +0100 Subject: mod_storage_internal,_sql: Expose archive capabilities feature set This was planned to be added long ago but was forgotten. --- plugins/mod_storage_internal.lua | 6 ++++++ plugins/mod_storage_sql.lua | 2 ++ 2 files changed, 8 insertions(+) diff --git a/plugins/mod_storage_internal.lua b/plugins/mod_storage_internal.lua index d812c0e9..c21fe0dc 100644 --- a/plugins/mod_storage_internal.lua +++ b/plugins/mod_storage_internal.lua @@ -47,6 +47,12 @@ end local archive = {}; driver.archive = { __index = archive }; +archive.caps = { + total = true; + quota = archive_item_limit; + truncate = true; +}; + function archive:append(username, key, value, when, with) when = when or now(); if not st.is_stanza(value) then diff --git a/plugins/mod_storage_sql.lua b/plugins/mod_storage_sql.lua index 4fe2a262..82c5c3fe 100644 --- a/plugins/mod_storage_sql.lua +++ b/plugins/mod_storage_sql.lua @@ -232,6 +232,8 @@ end local archive_store = {} archive_store.caps = { total = true; + quota = archive_item_limit; + truncate = true; }; archive_store.__index = archive_store function archive_store:append(username, key, value, when, with) -- cgit v1.2.3 From 9eb4885f38891261621cd18aa206883851acbaab Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 22 Mar 2019 18:02:27 +0100 Subject: mod_storage_internal,_sql: Key item count cache on both username and store --- plugins/mod_storage_internal.lua | 19 +++++++++++-------- plugins/mod_storage_sql.lua | 13 ++++++++----- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/plugins/mod_storage_internal.lua b/plugins/mod_storage_internal.lua index c21fe0dc..52ca4da8 100644 --- a/plugins/mod_storage_internal.lua +++ b/plugins/mod_storage_internal.lua @@ -5,6 +5,7 @@ local datetime = require "util.datetime"; local st = require "util.stanza"; local now = require "util.time".now; local id = require "util.id".medium; +local jid_join = require "util.jid".join; local host = module.host; @@ -64,7 +65,8 @@ function archive:append(username, key, value, when, with) value.attr.stamp = datetime.datetime(when); value.attr.stamp_legacy = datetime.legacy(when); - local item_count = archive_item_count_cache:get(username); + local cache_key = jid_join(username, host, self.store); + local item_count = archive_item_count_cache:get(cache_key); if key then local items, err = datamanager.list_load(username, host, self.store); @@ -72,7 +74,7 @@ function archive:append(username, key, value, when, with) -- Check the quota item_count = items and #items or 0; - archive_item_count_cache:set(username, item_count); + archive_item_count_cache:set(cache_key, item_count); if item_count >= archive_item_limit then module:log("debug", "%s reached or over quota, not adding to store", username); return nil, "quota-limit"; @@ -89,7 +91,7 @@ function archive:append(username, key, value, when, with) items:push(value); local ok, err = datamanager.list_store(username, host, self.store, items); if not ok then return ok, err; end - archive_item_count_cache:set(username, #items); + archive_item_count_cache:set(cache_key, #items); return key; end else @@ -98,7 +100,7 @@ function archive:append(username, key, value, when, with) local items, err = datamanager.list_load(username, host, self.store); if not items and err then return items, err; end item_count = items and #items or 0; - archive_item_count_cache:set(username, item_count); + archive_item_count_cache:set(cache_key, item_count); end if item_count >= archive_item_limit then module:log("debug", "%s reached or over quota, not adding to store", username); @@ -113,7 +115,7 @@ function archive:append(username, key, value, when, with) local ok, err = datamanager.list_append(username, host, self.store, value); if not ok then return ok, err; end - archive_item_count_cache:set(username, item_count+1); + archive_item_count_cache:set(cache_key, item_count+1); return key; end @@ -195,8 +197,9 @@ function archive:dates(username) end function archive:delete(username, query) + local cache_key = jid_join(username, host, self.store); if not query or next(query) == nil then - archive_item_count_cache:set(username, nil); + archive_item_count_cache:set(cache_key, nil); return datamanager.list_store(username, host, self.store, nil); end local items, err = datamanager.list_load(username, host, self.store); @@ -204,7 +207,7 @@ function archive:delete(username, query) if err then return items, err; end - archive_item_count_cache:set(username, 0); + archive_item_count_cache:set(cache_key, 0); -- Store is empty return 0; end @@ -254,7 +257,7 @@ function archive:delete(username, query) end local ok, err = datamanager.list_store(username, host, self.store, items); if not ok then return ok, err; end - archive_item_count_cache:set(username, #items); + archive_item_count_cache:set(cache_key, #items); return count; end diff --git a/plugins/mod_storage_sql.lua b/plugins/mod_storage_sql.lua index 82c5c3fe..3028bb72 100644 --- a/plugins/mod_storage_sql.lua +++ b/plugins/mod_storage_sql.lua @@ -7,6 +7,7 @@ local sql = require "util.sql"; local xml_parse = require "util.xml".parse; local uuid = require "util.uuid"; local resolve_relative_path = require "util.paths".resolve_relative_path; +local jid_join = require "util.jid".join; local is_stanza = require"util.stanza".is_stanza; local t_concat = table.concat; @@ -237,7 +238,8 @@ archive_store.caps = { }; archive_store.__index = archive_store function archive_store:append(username, key, value, when, with) - local item_count = archive_item_count_cache:get(username); + local cache_key = jid_join(username, host, self.store); + local item_count = archive_item_count_cache:get(cache_key); if not item_count then local ok, ret = engine:transaction(function() local count_sql = [[ @@ -255,7 +257,7 @@ function archive_store:append(username, key, value, when, with) module:log("error", "Failed while checking quota for %s: %s", username, ret); return nil, "Failure while checking quota"; end - archive_item_count_cache:set(username, item_count); + archive_item_count_cache:set(cache_key, item_count); end module:log("debug", "%s has %d items out of %d limit", username, item_count, archive_item_limit); @@ -280,7 +282,7 @@ function archive_store:append(username, key, value, when, with) local result, err = engine:delete(delete_sql, host, user or "", store, key); if result then item_count = item_count - result:affected(); - archive_item_count_cache:set(username, item_count); + archive_item_count_cache:set(cache_key, item_count); end else item_count = item_count + 1; @@ -288,7 +290,7 @@ function archive_store:append(username, key, value, when, with) end local t, encoded_value = assert(serialize(value)); engine:insert(insert_sql, host, user or "", store, when, with, key, t, encoded_value); - archive_item_count_cache:set(username, item_count+1); + archive_item_count_cache:set(cache_key, item_count+1); return key; end); if not ok then return ok, ret; end @@ -460,7 +462,8 @@ function archive_store:delete(username, query) end return engine:delete(sql_query, unpack(args)); end); - archive_item_count_cache:set(username, nil); + local cache_key = jid_join(username, host, self.store); + archive_item_count_cache:set(cache_key, nil); return ok and stmt:affected(), stmt; end -- cgit v1.2.3 From 9dce1de7674543e1daa3664c75a709845b1330fb Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 7 Nov 2017 18:58:52 +0100 Subject: mod_mam: Trim archive when quota has been exceeded --- plugins/mod_mam/mod_mam.lua | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/plugins/mod_mam/mod_mam.lua b/plugins/mod_mam/mod_mam.lua index 88cbaa08..5be4bc24 100644 --- a/plugins/mod_mam/mod_mam.lua +++ b/plugins/mod_mam/mod_mam.lua @@ -40,6 +40,10 @@ local strip_tags = module:get_option_set("dont_archive_namespaces", { "http://ja local archive_store = module:get_option_string("archive_store", "archive"); local archive = module:open_store(archive_store, "archive"); +local cleanup_after = module:get_option_string("archive_expires_after", "1w"); +local cleanup_interval = module:get_option_number("archive_cleanup_interval", 4 * 60 * 60); +local archive_item_limit = module:get_option_number("storage_archive_item_limit", archive.caps and archive.caps.quota or 1000); + if not archive.find then error("mod_"..(archive._provided_by or archive.name and "storage_"..archive.name).." does not support archiving\n" .."See https://prosody.im/doc/storage and https://prosody.im/doc/archiving for more information"); @@ -295,7 +299,20 @@ local function message_handler(event, c2s) log("debug", "Archiving stanza: %s", stanza:top_tag()); -- And stash it - local ok = archive:append(store_user, nil, clone_for_storage, time_now(), with); + local time = time_now(); + local ok, err = archive:append(store_user, nil, clone_for_storage, time, with); + if not ok and err == "quota-limit" then + if archive.caps and archive.caps.truncate then + module:log("debug", "User '%s' over quota, trimming archive", store_user); + local truncated = archive:delete(store_user, { + truncate = archive_item_limit - 1; + ["end"] = type(cleanup_after) == "number" and (os.time() - cleanup_after) or nil; + }); + if truncated then + ok, err = archive:append(store_user, nil, clone_for_storage, time, with); + end + end + end if ok then local clone_for_other_handlers = st.clone(stanza); local id = ok; @@ -321,8 +338,6 @@ end module:hook("pre-message/bare", strip_stanza_id_after_other_events, -1); module:hook("pre-message/full", strip_stanza_id_after_other_events, -1); -local cleanup_after = module:get_option_string("archive_expires_after", "1w"); -local cleanup_interval = module:get_option_number("archive_cleanup_interval", 4 * 60 * 60); if cleanup_after ~= "never" then local cleanup_storage = module:open_store("archive_cleanup"); local cleanup_map = module:open_store("archive_cleanup", "map"); -- cgit v1.2.3 From 0681ffe60631d573ad8529de7eaa187cb21a20b1 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 20 Mar 2019 12:14:45 +0100 Subject: mod_storage_memory: Add support for archive item limits --- plugins/mod_storage_memory.lua | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/plugins/mod_storage_memory.lua b/plugins/mod_storage_memory.lua index 745e394b..8e1cf879 100644 --- a/plugins/mod_storage_memory.lua +++ b/plugins/mod_storage_memory.lua @@ -8,6 +8,8 @@ local new_id = require "util.id".medium; local auto_purge_enabled = module:get_option_boolean("storage_memory_temporary", false); local auto_purge_stores = module:get_option_set("storage_memory_temporary_stores", {}); +local archive_item_limit = module:get_option_number("storage_archive_item_limit", 1000); + local memory = setmetatable({}, { __index = function(t, k) local store = module:shared(k) @@ -51,6 +53,12 @@ archive_store.__index = archive_store; archive_store.users = _users; +archive_store.caps = { + total = true; + quota = archive_item_limit; + truncate = true; +}; + function archive_store:append(username, key, value, when, with) if is_stanza(value) then value = st.preserialize(value); @@ -70,6 +78,8 @@ function archive_store:append(username, key, value, when, with) end if a[key] then table.remove(a, a[key]); + elseif #a >= archive_item_limit then + return nil, "quota-limit"; end local i = #a+1; a[i] = v; -- cgit v1.2.3 From c4b5bfdc5f2da4cdbe085a5a95c6418f802ec56e Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 22 Mar 2019 02:04:34 +0100 Subject: mod_storage_internal: Increase default quota to 10 000 Performance doesn't seem great but 10k should be far enough from limits inherited by the Lua parser. 1000 messages seemed pretty close to what an active user might produce in one week. --- plugins/mod_storage_internal.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_storage_internal.lua b/plugins/mod_storage_internal.lua index 52ca4da8..cb88f10f 100644 --- a/plugins/mod_storage_internal.lua +++ b/plugins/mod_storage_internal.lua @@ -9,7 +9,7 @@ local jid_join = require "util.jid".join; local host = module.host; -local archive_item_limit = module:get_option_number("storage_archive_item_limit", 1000); +local archive_item_limit = module:get_option_number("storage_archive_item_limit", 10000); local archive_item_count_cache = cache.new(module:get_option("storage_archive_item_limit_cache_size", 1000)); local driver = {}; -- cgit v1.2.3 From 3e5243f2d2df3e4933a9bd7fdaf13c848265178e Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 22 Mar 2019 02:09:38 +0100 Subject: mod_storage_sql: Don't increment counter twice (fixes accounting error) --- plugins/mod_storage_sql.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/mod_storage_sql.lua b/plugins/mod_storage_sql.lua index 3028bb72..154daf06 100644 --- a/plugins/mod_storage_sql.lua +++ b/plugins/mod_storage_sql.lua @@ -285,7 +285,6 @@ function archive_store:append(username, key, value, when, with) archive_item_count_cache:set(cache_key, item_count); end else - item_count = item_count + 1; key = uuid.generate(); end local t, encoded_value = assert(serialize(value)); -- cgit v1.2.3 From 61edbdb90f50b71c6a13f24e7621b561fef9b3ac Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 22 Mar 2019 02:10:46 +0100 Subject: mod_storage_sql: Fix to use currently queried store Was using the previously queried store due to this being cached in an upvalue. --- plugins/mod_storage_sql.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/mod_storage_sql.lua b/plugins/mod_storage_sql.lua index 154daf06..325fab94 100644 --- a/plugins/mod_storage_sql.lua +++ b/plugins/mod_storage_sql.lua @@ -238,7 +238,8 @@ archive_store.caps = { }; archive_store.__index = archive_store function archive_store:append(username, key, value, when, with) - local cache_key = jid_join(username, host, self.store); + local user,store = username,self.store; + local cache_key = jid_join(username, host, store); local item_count = archive_item_count_cache:get(cache_key); if not item_count then local ok, ret = engine:transaction(function() @@ -265,7 +266,6 @@ function archive_store:append(username, key, value, when, with) return nil, "quota-limit"; end - local user,store = username,self.store; when = when or os.time(); with = with or ""; local ok, ret = engine:transaction(function() -- cgit v1.2.3 From 56a9e395ade713122fd2251a64232abd270cadbe Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 22 Mar 2019 02:12:02 +0100 Subject: mod_storage_sql: Skip cache write This would cause the cache to be wrong in case the the later INSERT fails and the transaction is aborted. --- plugins/mod_storage_sql.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/mod_storage_sql.lua b/plugins/mod_storage_sql.lua index 325fab94..35a16870 100644 --- a/plugins/mod_storage_sql.lua +++ b/plugins/mod_storage_sql.lua @@ -282,7 +282,6 @@ function archive_store:append(username, key, value, when, with) local result, err = engine:delete(delete_sql, host, user or "", store, key); if result then item_count = item_count - result:affected(); - archive_item_count_cache:set(cache_key, item_count); end else key = uuid.generate(); -- cgit v1.2.3 From 5029870d3eee5bbe50c012fe1e7f4ee62e62ee24 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 22 Mar 2019 02:13:27 +0100 Subject: mod_storage_sql: Cache total count if it's calculated as part of the current query --- plugins/mod_storage_sql.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/mod_storage_sql.lua b/plugins/mod_storage_sql.lua index 35a16870..b3bd5171 100644 --- a/plugins/mod_storage_sql.lua +++ b/plugins/mod_storage_sql.lua @@ -384,6 +384,9 @@ function archive_store:find(username, query) total = row[1]; end end + if query.start == nil and query.with == nil and query["end"] == nil and query.key == nil then + archive_item_count_cache:set(cache_key, total); + end if query.limit == 0 then -- Skip the real query return noop, total; end -- cgit v1.2.3 From 9393931a25367390465b480b8dbaa38e9f199b54 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 22 Mar 2019 02:18:54 +0100 Subject: mod_storage_sql: Return cached count if only this is queried for --- plugins/mod_storage_sql.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/mod_storage_sql.lua b/plugins/mod_storage_sql.lua index b3bd5171..8c03da01 100644 --- a/plugins/mod_storage_sql.lua +++ b/plugins/mod_storage_sql.lua @@ -362,7 +362,11 @@ end function archive_store:find(username, query) query = query or {}; local user,store = username,self.store; - local total; + local cache_key = jid_join(username, host, self.store); + local total = archive_item_count_cache:get(cache_key); + if total ~= nil and query.limit == 0 and query.start == nil and query.with == nil and query["end"] == nil and query.key == nil then + return noop, total; + end local ok, result = engine:transaction(function() local sql_query = [[ SELECT "key", "type", "value", "when", "with" -- cgit v1.2.3 From 2fed4a88c282ca6aa62d9641f3360a021ec0cebe Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 22 Mar 2019 02:22:21 +0100 Subject: mod_mam: On quota hit, separately delete by time and by item count This is to work around a possible SQL issue where offsets and time stamps don't interact correctly. --- plugins/mod_mam/mod_mam.lua | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/plugins/mod_mam/mod_mam.lua b/plugins/mod_mam/mod_mam.lua index 5be4bc24..632de9ea 100644 --- a/plugins/mod_mam/mod_mam.lua +++ b/plugins/mod_mam/mod_mam.lua @@ -302,11 +302,19 @@ local function message_handler(event, c2s) local time = time_now(); local ok, err = archive:append(store_user, nil, clone_for_storage, time, with); if not ok and err == "quota-limit" then - if archive.caps and archive.caps.truncate then - module:log("debug", "User '%s' over quota, trimming archive", store_user); + if type(cleanup_after) == "number" then + module:log("debug", "User '%s' over quota, cleaning archive", store_user); + local cleaned = archive:delete(store_user, { + ["end"] = (os.time() - cleanup_after); + }); + if cleaned then + ok, err = archive:append(store_user, nil, clone_for_storage, time, with); + end + end + if not ok and (archive.caps and archive.caps.truncate) then + module:log("debug", "User '%s' over quota, truncating archive", store_user); local truncated = archive:delete(store_user, { truncate = archive_item_limit - 1; - ["end"] = type(cleanup_after) == "number" and (os.time() - cleanup_after) or nil; }); if truncated then ok, err = archive:append(store_user, nil, clone_for_storage, time, with); -- cgit v1.2.3 From 5bb703f07f52906b1280daaac164a3886f09a373 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 22 Mar 2019 02:24:48 +0100 Subject: mod_storage_internal: Include store name when reporting quota status --- plugins/mod_storage_internal.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_storage_internal.lua b/plugins/mod_storage_internal.lua index cb88f10f..c87d01be 100644 --- a/plugins/mod_storage_internal.lua +++ b/plugins/mod_storage_internal.lua @@ -109,7 +109,7 @@ function archive:append(username, key, value, when, with) key = id(); end - module:log("debug", "%s has %d items out of %d limit", username, item_count, archive_item_limit); + module:log("debug", "%s has %d items out of %d limit in store %s", username, item_count, archive_item_limit, self.store); value.key = key; -- cgit v1.2.3 From 8cc789c7968e0f578eba70be1be13b1645e52914 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 22 Mar 2019 16:30:53 +0100 Subject: mod_storage_sql: No archive item limit by default --- plugins/mod_storage_sql.lua | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/plugins/mod_storage_sql.lua b/plugins/mod_storage_sql.lua index 8c03da01..6b26759f 100644 --- a/plugins/mod_storage_sql.lua +++ b/plugins/mod_storage_sql.lua @@ -150,7 +150,7 @@ end --- Archive store API -local archive_item_limit = module:get_option_number("storage_archive_item_limit", 1000); +local archive_item_limit = module:get_option_number("storage_archive_item_limit"); local archive_item_count_cache = cache.new(module:get_option("storage_archive_item_limit_cache_size", 1000)); -- luacheck: ignore 512 431/user 431/store @@ -261,9 +261,11 @@ function archive_store:append(username, key, value, when, with) archive_item_count_cache:set(cache_key, item_count); end - module:log("debug", "%s has %d items out of %d limit", username, item_count, archive_item_limit); - if item_count >= archive_item_limit then - return nil, "quota-limit"; + if archive_item_limit then + module:log("debug", "%s has %d items out of %d limit", username, item_count, archive_item_limit); + if item_count >= archive_item_limit then + return nil, "quota-limit"; + end end when = when or os.time(); -- cgit v1.2.3 From dc241cf18e1976c79296b8857fdd5b5bb50d43c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20=E2=80=9Cpep=E2=80=9D=20Buquet?= Date: Sat, 23 Mar 2019 01:57:12 +0000 Subject: net/server_event: fix typo in comment --- net/server_event.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/server_event.lua b/net/server_event.lua index 2bee614a..42c9af2e 100644 --- a/net/server_event.lua +++ b/net/server_event.lua @@ -658,7 +658,7 @@ local function handleclient( client, ip, port, server, pattern, listener, sslctx return interface end -local function handleserver( server, addr, port, pattern, listener, sslctx, startssl ) -- creates an server interface +local function handleserver( server, addr, port, pattern, listener, sslctx, startssl ) -- creates a server interface debug "creating server interface..." local interface = { _connections = 0; -- cgit v1.2.3 From 24581c47db0db9739d89339034c4d4e58bcdf8a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20=E2=80=9Cpep=E2=80=9D=20Buquet?= Date: Sat, 23 Mar 2019 02:27:45 +0000 Subject: doc/coding_style: remove superfulous bracket in example --- doc/coding_style.md | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/coding_style.md b/doc/coding_style.md index 6f34d371..af1a2502 100644 --- a/doc/coding_style.md +++ b/doc/coding_style.md @@ -131,7 +131,6 @@ constants from C. ```lua local player = { name = "Jack", class = "Rogue" } -} ``` * Items should be separated by commas. If there are many items, put each -- cgit v1.2.3 From 7aab0c40a49c5f6a2008646f636b19290d2abfe4 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 23 Mar 2019 03:56:55 +0100 Subject: doc/coding_style: Trim trailing whitespace --- doc/coding_style.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/coding_style.md b/doc/coding_style.md index af1a2502..17b7c037 100644 --- a/doc/coding_style.md +++ b/doc/coding_style.md @@ -330,7 +330,7 @@ than if it says `check_version = function()` under some indentation level. ## Variable declaration -* Always use `local` to declare variables. +* Always use `local` to declare variables. ```lua -- bad @@ -446,8 +446,8 @@ if test < 1 and do_complicated_function(test) == false or seven == 8 and nine == -- good if test < 1 and do_complicated_function(test) == false or seven == 8 and nine == 10 then - do_other_complicated_function() - return false + do_other_complicated_function() + return false end ``` @@ -466,7 +466,7 @@ b = 2 ## Spacing -* Use a space after `--`. +* Use a space after `--`. ```lua --bad -- cgit v1.2.3 From 95314bb2be435c6b7527675d08c53ea8809b0690 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 23 Mar 2019 04:00:55 +0100 Subject: doc/coding_style: The codebase uses semicolons --- doc/coding_style.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/coding_style.md b/doc/coding_style.md index 17b7c037..6ca527fa 100644 --- a/doc/coding_style.md +++ b/doc/coding_style.md @@ -451,17 +451,17 @@ if test < 1 and do_complicated_function(test) == false or seven == 8 and nine == end ``` -* Separate statements onto multiple lines. Do not use semicolons as statement terminators. +* Separate statements onto multiple lines. Use semicolons as statement terminators. ```lua -- bad -local whatever = "sure"; -a = 1; b = 2 +local whatever = "sure" +a = 1 b = 2 -- good -local whatever = "sure" -a = 1 -b = 2 +local whatever = "sure"; +a = 1; +b = 2; ``` ## Spacing -- cgit v1.2.3 From a274eacbbc62164a6567faeaa8d18ea5993a133f Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Sat, 23 Mar 2019 08:47:55 +0000 Subject: util.queue: Add 'consume()' convenience iterator --- spec/util_queue_spec.lua | 37 +++++++++++++++++++++++++++++++++++++ util/queue.lua | 3 +++ 2 files changed, 40 insertions(+) diff --git a/spec/util_queue_spec.lua b/spec/util_queue_spec.lua index 7cd3d695..d73f523d 100644 --- a/spec/util_queue_spec.lua +++ b/spec/util_queue_spec.lua @@ -100,4 +100,41 @@ describe("util.queue", function() end); end); + describe("consume()", function () + it("should work", function () + local q = queue.new(10); + for i = 1, 5 do + q:push(i); + end + local c = 0; + for i in q:consume() do + assert(i == c + 1); + assert(q:count() == (5-i)); + c = i; + end + end); + + it("should work even if items are pushed in the loop", function () + local q = queue.new(10); + for i = 1, 5 do + q:push(i); + end + local c = 0; + for i in q:consume() do + assert(i == c + 1); + if c < 3 then + assert(q:count() == (5-i)); + else + assert(q:count() == (6-i)); + end + + c = i; + + if c == 3 then + q:push(6); + end + end + assert.equal(c, 6); + end); + end); end); diff --git a/util/queue.lua b/util/queue.lua index 728e905f..e63b3f1c 100644 --- a/util/queue.lua +++ b/util/queue.lua @@ -64,6 +64,9 @@ local function new(size, allow_wrapping) return pos+1, t._items[read_pos]; end, self, 0; end; + consume = function (self) + return self.pop, self; + end; }; end -- cgit v1.2.3 From 3c50aa4902aada8eccbdd32f359a0cd6a52aceae Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Sat, 23 Mar 2019 08:52:57 +0000 Subject: util.queue: Update :items() to consistently use private data directly It will perform better this way, and we were accessing private variables already within the iterator. --- core/loggingmanager.lua | 21 ++++++++++++++++++++- util/queue.lua | 9 ++++----- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/core/loggingmanager.lua b/core/loggingmanager.lua index cfa8246a..b510617f 100644 --- a/core/loggingmanager.lua +++ b/core/loggingmanager.lua @@ -18,6 +18,9 @@ local getstyle, getstring = require "util.termcolours".getstyle, require "util.t local config = require "core.configmanager"; local logger = require "util.logger"; +local have_pposix, pposix = pcall(require, "util.pposix"); +have_pposix = have_pposix and pposix._VERSION == "0.4.4"; + local _ENV = nil; -- luacheck: std none @@ -45,7 +48,8 @@ local function add_rule(sink_config) local sink = sink_maker(sink_config); -- Set sink for all chosen levels - for level in pairs(get_levels(sink_config.levels or logging_levels)) do + local levels = get_levels(sink_config.levels or logging_levels); + for level in pairs(levels) do logger.add_level_sink(level, sink); end end @@ -232,6 +236,21 @@ local function log_to_console(sink_config) end log_sink_types.console = log_to_console; +if have_pposix then + local syslog_opened; + local function log_to_syslog(sink_config) -- luacheck: ignore 212/sink_config + if not syslog_opened then + pposix.syslog_open(sink_config.syslog_name or "prosody", sink_config.syslog_facility or config.get("*", "syslog_facility")); + syslog_opened = true; + end + local syslog = pposix.syslog_log; + return function (name, level, message, ...) + syslog(level, name, format(message, ...)); + end; + end + log_sink_types.syslog = log_to_syslog; +end + local function register_sink_type(name, sink_maker) local old_sink_maker = log_sink_types[name]; log_sink_types[name] = sink_maker; diff --git a/util/queue.lua b/util/queue.lua index e63b3f1c..66ed098b 100644 --- a/util/queue.lua +++ b/util/queue.lua @@ -52,16 +52,15 @@ local function new(size, allow_wrapping) return t[tail]; end; items = function (self) - --luacheck: ignore 431/t - return function (t, pos) - if pos >= t:count() then + return function (_, pos) + if pos >= items then return nil; end local read_pos = tail + pos; - if read_pos > t.size then + if read_pos > self.size then read_pos = (read_pos%size); end - return pos+1, t._items[read_pos]; + return pos+1, t[read_pos]; end, self, 0; end; consume = function (self) -- cgit v1.2.3 From 582fa3f46f105fe55447d607dceb43b5ac61d440 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 25 Feb 2019 15:48:28 +0100 Subject: mod_storage_internal: Implement a summary API returning message counts per contact --- doc/storage.tld | 3 +++ plugins/mod_storage_internal.lua | 10 ++++++++++ 2 files changed, 13 insertions(+) diff --git a/doc/storage.tld b/doc/storage.tld index f1d33e58..057649a4 100644 --- a/doc/storage.tld +++ b/doc/storage.tld @@ -47,6 +47,9 @@ interface archive_store -- Array of dates which do have messages (Optional?) dates : ( self, string? ) -> ({ string }) | (nil, string) + + -- Map of counts per "with" field + summary : ( self, string?, archive_query? ) -> ( { string : integer } ) | (nil, string) end -- This represents moduleapi diff --git a/plugins/mod_storage_internal.lua b/plugins/mod_storage_internal.lua index c87d01be..aa5c3c8a 100644 --- a/plugins/mod_storage_internal.lua +++ b/plugins/mod_storage_internal.lua @@ -196,6 +196,16 @@ function archive:dates(username) return array(items):pluck("when"):map(datetime.date):unique(); end +function archive:summary(username, query) + local iter, err = self:find(username, query) + if not iter then return iter, err; end + local summary = {}; + for _, _, _, with in iter do + summary[with] = (summary[with] or 0) + 1; + end + return summary; +end + function archive:delete(username, query) local cache_key = jid_join(username, host, self.store); if not query or next(query) == nil then -- cgit v1.2.3 From a32b5ceb4576f8fefeea0f68aae1f81dd942125d Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 25 Feb 2019 15:51:55 +0100 Subject: mod_storage_sql: Implement archive summary API --- plugins/mod_storage_sql.lua | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/plugins/mod_storage_sql.lua b/plugins/mod_storage_sql.lua index 6b26759f..ffe48ab8 100644 --- a/plugins/mod_storage_sql.lua +++ b/plugins/mod_storage_sql.lua @@ -419,6 +419,41 @@ function archive_store:find(username, query) end, total; end +function archive_store:summary(username, query) + query = query or {}; + local user,store = username,self.store; + local ok, result = engine:transaction(function() + local sql_query = [[ + SELECT DISTINCT "with", COUNT(*) + FROM "prosodyarchive" + WHERE %s + GROUP BY "with" + ORDER BY "sort_id" %s%s; + ]]; + local args = { host, user or "", store, }; + local where = { "\"host\" = ?", "\"user\" = ?", "\"store\" = ?", }; + + archive_where(query, args, where); + + archive_where_id_range(query, args, where); + + if query.limit then + args[#args+1] = query.limit; + end + + sql_query = sql_query:format(t_concat(where, " AND "), query.reverse + and "DESC" or "ASC", query.limit and " LIMIT ?" or ""); + return engine:select(sql_query, unpack(args)); + end); + if not ok then return ok, result end + local summary = {}; + for row in result do + local with, count = row[1], row[2]; + summary[with] = count; + end + return summary; +end + function archive_store:delete(username, query) query = query or {}; local user,store = username,self.store; -- cgit v1.2.3 From 9b96017ca92a16719c614484e7c314bcd7b0ba80 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 23 Mar 2019 22:05:08 +0100 Subject: mod_storage_memory: Implement archive summary API --- plugins/mod_storage_memory.lua | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/plugins/mod_storage_memory.lua b/plugins/mod_storage_memory.lua index 8e1cf879..41180aba 100644 --- a/plugins/mod_storage_memory.lua +++ b/plugins/mod_storage_memory.lua @@ -147,6 +147,16 @@ function archive_store:find(username, query) end, count; end +function archive:summary(username, query) + local iter, err = self:find(username, query) + if not iter then return iter, err; end + local summary = {}; + for _, _, _, with in iter do + summary[with] = (summary[with] or 0) + 1; + end + return summary; +end + function archive_store:delete(username, query) if not query or next(query) == nil then -- cgit v1.2.3 From 170c49b52dec97673c1cb473038e0c538e239b2c Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 23 Mar 2019 22:05:42 +0100 Subject: mod_storage_memory: Fix copypaste mistake --- plugins/mod_storage_memory.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_storage_memory.lua b/plugins/mod_storage_memory.lua index 41180aba..dde2d571 100644 --- a/plugins/mod_storage_memory.lua +++ b/plugins/mod_storage_memory.lua @@ -147,7 +147,7 @@ function archive_store:find(username, query) end, count; end -function archive:summary(username, query) +function archive_store:summary(username, query) local iter, err = self:find(username, query) if not iter then return iter, err; end local summary = {}; -- cgit v1.2.3 From 5ba20f8a9b1e93a99e38aedf9ec83a4f18f330f3 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 4 Jan 2019 10:20:51 +0100 Subject: util.x509: Add function that extracts usable names from a certificate --- util/x509.lua | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/util/x509.lua b/util/x509.lua index 15cc4d3c..1cdf07dc 100644 --- a/util/x509.lua +++ b/util/x509.lua @@ -20,6 +20,7 @@ local nameprep = require "util.encodings".stringprep.nameprep; local idna_to_ascii = require "util.encodings".idna.to_ascii; +local idna_to_unicode = require "util.encodings".idna.to_unicode; local base64 = require "util.encodings".base64; local log = require "util.logger".init("x509"); local s_format = string.format; @@ -216,6 +217,32 @@ local function verify_identity(host, service, cert) return false end +-- TODO Support other SANs +local function get_identities(cert) --> set of names + if cert.setencode then + cert:setencode("utf8"); + end + + local names = {}; + + local ext = cert:extensions(); + local sans = ext[oid_subjectaltname]; + if sans and sans["dNSName"] then + for i = 1, #sans["dNSName"] do + names[ idna_to_unicode(sans["dNSName"][i]) ] = true; + end + end + + local subject = cert:subject(); + for i = 1, #subject do + local dn = subject[i]; + if dn.oid == oid_commonname and nameprep(dn.value) then + names[dn.value] = true; + end + end + return names; +end + local pat = "%-%-%-%-%-BEGIN ([A-Z ]+)%-%-%-%-%-\r?\n".. "([0-9A-Za-z+/=\r\n]*)\r?\n%-%-%-%-%-END %1%-%-%-%-%-"; @@ -237,6 +264,7 @@ end return { verify_identity = verify_identity; + get_identities = get_identities; pem2der = pem2der; der2pem = der2pem; }; -- cgit v1.2.3 From ee0fd8f1d90b736597baff3d7e1fd7dd1d28240b Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 24 Mar 2019 08:18:19 +0000 Subject: sessionmanager: Split byte-level sending into separate session.rawsend --- core/sessionmanager.lua | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/core/sessionmanager.lua b/core/sessionmanager.lua index 2843001a..9a2456f2 100644 --- a/core/sessionmanager.lua +++ b/core/sessionmanager.lua @@ -32,20 +32,26 @@ local function new_session(conn) local session = { conn = conn, type = "c2s_unauthed", conntime = gettime() }; local filter = initialize_filters(session); local w = conn.write; + + function session.rawsend(t) + t = filter("bytes/out", tostring(t)); + if t then + local ret, err = w(conn, t); + if not ret then + session.log("debug", "Error writing to connection: %s", tostring(err)); + return false, err; + end + end + return true; + end + session.send = function (t) session.log("debug", "Sending[%s]: %s", session.type, t.top_tag and t:top_tag() or t:match("^[^>]*>?")); if t.name then t = filter("stanzas/out", t); end if t then - t = filter("bytes/out", tostring(t)); - if t then - local ret, err = w(conn, t); - if not ret then - session.log("debug", "Error writing to connection: %s", tostring(err)); - return false, err; - end - end + return session.rawsend(t); end return true; end -- cgit v1.2.3 From 8e68b0dd1adfcd7932f368a0b00dd2019c95db38 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 24 Nov 2018 02:25:44 +0100 Subject: mod_csi_simple: Use write locks in net.server if available --- plugins/mod_csi_simple.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/mod_csi_simple.lua b/plugins/mod_csi_simple.lua index da2dd953..abe65fce 100644 --- a/plugins/mod_csi_simple.lua +++ b/plugins/mod_csi_simple.lua @@ -86,7 +86,9 @@ end, -1); module:hook("csi-client-inactive", function (event) local session = event.origin; - if session.pump then + if session.conn and session.conn and session.conn.pause_writes then + session.conn:pause_writes(); + elseif session.pump then session.pump:pause(); else local bare_jid = jid.join(session.username, session.host); @@ -115,6 +117,8 @@ module:hook("csi-client-active", function (event) local session = event.origin; if session.pump then session.pump:resume(); + elseif session.conn and session.conn and session.conn.resume_writes then + session.conn:resume_writes(); end end); -- cgit v1.2.3 From 9e7035be7282a7902989904cec6aeec879814f49 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 24 Mar 2019 18:30:51 +0100 Subject: mod_c2s: Fire an event when outgoing buffers have been emptied --- plugins/mod_c2s.lua | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/plugins/mod_c2s.lua b/plugins/mod_c2s.lua index 8d7b92fe..7c6d95f7 100644 --- a/plugins/mod_c2s.lua +++ b/plugins/mod_c2s.lua @@ -332,6 +332,13 @@ function listener.onreadtimeout(conn) end end +function listener.ondrain(conn) + local session = sessions[conn]; + if session then + return (hosts[session.host] or prosody).events.fire_event("c2s-ondrain", { session = session }); + end +end + local function keepalive(event) local session = event.session; if not session.notopen then -- cgit v1.2.3 From e5885c928a79604dea999d24cf57104150b55898 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 24 Mar 2019 18:32:50 +0100 Subject: mod_csi_simple: Break out stanza timestamping into a function for future reuse --- plugins/mod_csi_simple.lua | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/plugins/mod_csi_simple.lua b/plugins/mod_csi_simple.lua index abe65fce..6bd6c0bf 100644 --- a/plugins/mod_csi_simple.lua +++ b/plugins/mod_csi_simple.lua @@ -84,6 +84,14 @@ module:hook("csi-is-stanza-important", function (event) return true; end, -1); +local function with_timestamp(stanza, from) + if st.is_stanza(stanza) and stanza.attr.xmlns == nil and stanza.name ~= "iq" then + stanza = st.clone(stanza); + stanza:add_direct_child(st.stanza("delay", {xmlns = "urn:xmpp:delay", from = from, stamp = dt.datetime()})); + end + return stanza; +end + module:hook("csi-client-inactive", function (event) local session = event.origin; if session.conn and session.conn and session.conn.pause_writes then @@ -102,11 +110,7 @@ module:hook("csi-client-inactive", function (event) pump:flush(); send(stanza); else - if st.is_stanza(stanza) and stanza.attr.xmlns == nil and stanza.name ~= "iq" then - stanza = st.clone(stanza); - stanza:add_direct_child(st.stanza("delay", {xmlns = "urn:xmpp:delay", from = bare_jid, stamp = dt.datetime()})); - end - pump:push(stanza); + pump:push(with_timestamp(stanza, bare_jid)); end return true; end -- cgit v1.2.3 From 643c317b1627da95e839bab0e397d89ab6f6a589 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 24 Mar 2019 18:33:38 +0100 Subject: mod_csi_simple: Count buffered items and flush when it reaches configured limit In this mode, stanzas have been serialized to strings in the internal net.server buffer, so it is difficult to count them after the fact. --- plugins/mod_csi_simple.lua | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/plugins/mod_csi_simple.lua b/plugins/mod_csi_simple.lua index 6bd6c0bf..5c829179 100644 --- a/plugins/mod_csi_simple.lua +++ b/plugins/mod_csi_simple.lua @@ -10,6 +10,7 @@ local jid = require "util.jid"; local st = require "util.stanza"; local dt = require "util.datetime"; local new_queue = require "util.queue".new; +local filters = require "util.filters"; local function new_pump(output, ...) -- luacheck: ignore 212/self @@ -92,10 +93,22 @@ local function with_timestamp(stanza, from) return stanza; end +local function manage_buffer(stanza, session) + local ctr = session.csi_counter or 0; + if ctr >= queue_size or module:fire_event("csi-is-stanza-important", { stanza = stanza, session = session }) then + session.conn:resume_writes(); + else + stanza = with_timestamp(stanza, jid.join(session.username, session.host)) + end + session.csi_counter = ctr + 1; + return stanza; +end + module:hook("csi-client-inactive", function (event) local session = event.origin; if session.conn and session.conn and session.conn.pause_writes then session.conn:pause_writes(); + filters.add_filter(session, "stanzas/out", manage_buffer); elseif session.pump then session.pump:pause(); else @@ -122,7 +135,16 @@ module:hook("csi-client-active", function (event) if session.pump then session.pump:resume(); elseif session.conn and session.conn and session.conn.resume_writes then + filters.remove_filter(session, "stanzas/out", manage_buffer); session.conn:resume_writes(); end end); + +module:hook("c2s-ondrain", function (event) + local session = event.session; + if session.state == "inactive" and session.conn and session.conn and session.conn.pause_writes then + session.csi_counter = 0; + session.conn:pause_writes(); + end +end); -- cgit v1.2.3 From 141c5d3fbe257cf63249eeec528dd36b3266b25e Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 24 Mar 2019 18:58:53 +0100 Subject: mod_csi_simple: Trigger buffer flush on seeing incoming data I.e. the client sent us something, which means its network / radio is active. --- plugins/mod_csi_simple.lua | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/plugins/mod_csi_simple.lua b/plugins/mod_csi_simple.lua index 5c829179..07a8cfb3 100644 --- a/plugins/mod_csi_simple.lua +++ b/plugins/mod_csi_simple.lua @@ -104,11 +104,17 @@ local function manage_buffer(stanza, session) return stanza; end +local function flush_buffer(data, session) + session.conn:resume_writes(); + return data; +end + module:hook("csi-client-inactive", function (event) local session = event.origin; if session.conn and session.conn and session.conn.pause_writes then session.conn:pause_writes(); filters.add_filter(session, "stanzas/out", manage_buffer); + filters.add_filter(session, "bytes/in", flush_buffer); elseif session.pump then session.pump:pause(); else @@ -136,6 +142,7 @@ module:hook("csi-client-active", function (event) session.pump:resume(); elseif session.conn and session.conn and session.conn.resume_writes then filters.remove_filter(session, "stanzas/out", manage_buffer); + filters.remove_filter(session, "bytes/in", flush_buffer); session.conn:resume_writes(); end end); -- cgit v1.2.3 From 3a1498ebd335a15a987e3087f84089620b5507c5 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 24 Mar 2019 19:02:40 +0100 Subject: mod_csi_simple: Also flush buffer in "pump" mode --- plugins/mod_csi_simple.lua | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/plugins/mod_csi_simple.lua b/plugins/mod_csi_simple.lua index 07a8cfb3..ee7e01c9 100644 --- a/plugins/mod_csi_simple.lua +++ b/plugins/mod_csi_simple.lua @@ -109,9 +109,15 @@ local function flush_buffer(data, session) return data; end +local function flush_pump(data, session) + session.pump:flush(); + return data; +end + module:hook("csi-client-inactive", function (event) local session = event.origin; if session.conn and session.conn and session.conn.pause_writes then + session.log("info", "Native net.server buffer management mode"); session.conn:pause_writes(); filters.add_filter(session, "stanzas/out", manage_buffer); filters.add_filter(session, "bytes/in", flush_buffer); @@ -124,6 +130,7 @@ module:hook("csi-client-inactive", function (event) local pump = new_pump(session.send, queue_size); pump:pause(); session.pump = pump; + filters.add_filter(session, "bytes/in", flush_pump); function session.send(stanza) if session.state == "active" or module:fire_event("csi-is-stanza-important", { stanza = stanza, session = session }) then pump:flush(); -- cgit v1.2.3 From 20eaa5d17bcff2a0f861b48f23ec3b3d4290f583 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 24 Mar 2019 19:07:39 +0100 Subject: net.server_event: Allow writing into buffer of write-locked connections Check for 'nointerface' flag instead, whatever that means. --- net/server_event.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/server_event.lua b/net/server_event.lua index 42c9af2e..fde79d86 100644 --- a/net/server_event.lua +++ b/net/server_event.lua @@ -304,7 +304,7 @@ end -- Public methods function interface_mt:write(data) - if self.nowriting then return nil, "locked" end + if self.nointerface then return nil, "locked"; end --vdebug( "try to send data to client, id/data:", self.id, data ) data = tostring( data ) local len = #data @@ -316,7 +316,7 @@ function interface_mt:write(data) end t_insert(self.writebuffer, data) -- new buffer self.writebufferlen = total - if not self.eventwrite then -- register new write event + if not self.eventwrite and not self.nowriting then -- register new write event --vdebug( "register new write event" ) self.eventwrite = addevent( base, self.conn, EV_WRITE, self.writecallback, cfg.WRITE_TIMEOUT ) end -- cgit v1.2.3 From e8f72c6d4f6bc28e54f93702eb4825de8c81229e Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 24 Mar 2019 20:12:22 +0100 Subject: net.server_select: Fix write pause/resume functions Nothing would happen if the write buffer was empty. Also simplified the code because it took too long to understand what `if _sendlistlen ~= tmp then` did. --- net/server_select.lua | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/net/server_select.lua b/net/server_select.lua index 4b156409..5d554655 100644 --- a/net/server_select.lua +++ b/net/server_select.lua @@ -497,14 +497,12 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport local tmp = _sendlistlen _sendlistlen = removesocket( _sendlist, socket, _sendlistlen ) _writetimes[ handler ] = nil - if _sendlistlen ~= tmp then - nosend = true - end + nosend = true end handler.resume_writes = function (self) - if nosend then - nosend = false - write( "" ) + nosend = false + if bufferlen > 0 then + _sendlistlen = addsocket(_sendlist, socket, _sendlistlen) end end -- cgit v1.2.3 From 6c89a86e0df62c51e0fb12596d3999324245c9fe Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 24 Mar 2019 20:22:01 +0100 Subject: mod_csi_simple: Remove old "pump" queue/buffer method, handled in net.server now --- plugins/mod_csi_simple.lua | 63 ++-------------------------------------------- 1 file changed, 2 insertions(+), 61 deletions(-) diff --git a/plugins/mod_csi_simple.lua b/plugins/mod_csi_simple.lua index ee7e01c9..0fa0d083 100644 --- a/plugins/mod_csi_simple.lua +++ b/plugins/mod_csi_simple.lua @@ -9,42 +9,8 @@ module:depends"csi" local jid = require "util.jid"; local st = require "util.stanza"; local dt = require "util.datetime"; -local new_queue = require "util.queue".new; local filters = require "util.filters"; -local function new_pump(output, ...) - -- luacheck: ignore 212/self - local q = new_queue(...); - local flush = true; - function q:pause() - flush = false; - end - function q:resume() - flush = true; - return q:flush(); - end - local push = q.push; - function q:push(item) - local ok = push(self, item); - if not ok then - q:flush(); - output(item, self); - elseif flush then - return q:flush(); - end - return true; - end - function q:flush() - local item = self:pop(); - while item do - output(item, self); - item = self:pop(); - end - return true; - end - return q; -end - local queue_size = module:get_option_number("csi_queue_size", 256); module:hook("csi-is-stanza-important", function (event) @@ -109,45 +75,20 @@ local function flush_buffer(data, session) return data; end -local function flush_pump(data, session) - session.pump:flush(); - return data; -end - module:hook("csi-client-inactive", function (event) local session = event.origin; if session.conn and session.conn and session.conn.pause_writes then - session.log("info", "Native net.server buffer management mode"); session.conn:pause_writes(); filters.add_filter(session, "stanzas/out", manage_buffer); filters.add_filter(session, "bytes/in", flush_buffer); - elseif session.pump then - session.pump:pause(); else - local bare_jid = jid.join(session.username, session.host); - local send = session.send; - session._orig_send = send; - local pump = new_pump(session.send, queue_size); - pump:pause(); - session.pump = pump; - filters.add_filter(session, "bytes/in", flush_pump); - function session.send(stanza) - if session.state == "active" or module:fire_event("csi-is-stanza-important", { stanza = stanza, session = session }) then - pump:flush(); - send(stanza); - else - pump:push(with_timestamp(stanza, bare_jid)); - end - return true; - end + session.log("warn", "Session connection does not support write pausing"); end end); module:hook("csi-client-active", function (event) local session = event.origin; - if session.pump then - session.pump:resume(); - elseif session.conn and session.conn and session.conn.resume_writes then + if session.conn and session.conn and session.conn.resume_writes then filters.remove_filter(session, "stanzas/out", manage_buffer); filters.remove_filter(session, "bytes/in", flush_buffer); session.conn:resume_writes(); -- cgit v1.2.3 From 1e77bb6ed2ac00b1e5f64f39da41ec140fe425ce Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 24 Mar 2019 20:41:25 +0100 Subject: mod_csi_simple: Separate out functions to enable/disable optimizations This allows reusing this logic outside the events. Letting the functions be module globals makes it easier to access from eg the telnet console. --- plugins/mod_csi_simple.lua | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/plugins/mod_csi_simple.lua b/plugins/mod_csi_simple.lua index 0fa0d083..14abbc68 100644 --- a/plugins/mod_csi_simple.lua +++ b/plugins/mod_csi_simple.lua @@ -75,8 +75,7 @@ local function flush_buffer(data, session) return data; end -module:hook("csi-client-inactive", function (event) - local session = event.origin; +function enable_optimizations(session) if session.conn and session.conn and session.conn.pause_writes then session.conn:pause_writes(); filters.add_filter(session, "stanzas/out", manage_buffer); @@ -84,15 +83,24 @@ module:hook("csi-client-inactive", function (event) else session.log("warn", "Session connection does not support write pausing"); end -end); +end -module:hook("csi-client-active", function (event) - local session = event.origin; +function disble_optimizations(session) if session.conn and session.conn and session.conn.resume_writes then filters.remove_filter(session, "stanzas/out", manage_buffer); filters.remove_filter(session, "bytes/in", flush_buffer); session.conn:resume_writes(); end +end + +module:hook("csi-client-inactive", function (event) + local session = event.origin; + enable_optimizations(session); +end); + +module:hook("csi-client-active", function (event) + local session = event.origin; + disble_optimizations(session); end); -- cgit v1.2.3 From e887ed5cad52d2853ba66dfe1a7ec0762f4624df Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 24 Mar 2019 20:43:15 +0100 Subject: mod_csi_simple: Disable optimizations on unload and re-enable on load --- plugins/mod_csi_simple.lua | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/plugins/mod_csi_simple.lua b/plugins/mod_csi_simple.lua index 14abbc68..d0de222e 100644 --- a/plugins/mod_csi_simple.lua +++ b/plugins/mod_csi_simple.lua @@ -111,3 +111,24 @@ module:hook("c2s-ondrain", function (event) session.conn:pause_writes(); end end); + +function module.load() + for _, user_session in pairs(prosody.hosts[module.host].sessions) do + for _, session in pairs(user_session.sessions) do + if session.state == "inactive" then + enable_optimizations(session); + end + end + end +end + +function module.unload() + for _, user_session in pairs(prosody.hosts[module.host].sessions) do + for _, session in pairs(user_session.sessions) do + if session.state == "inactive" then + disble_optimizations(session); + end + end + end +end + -- cgit v1.2.3 From 684a26f5be5a29e4d8eb82af0f9b2d9a87f98267 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 24 Mar 2019 20:53:49 +0100 Subject: mod_csi_simple: Add some debug logging --- plugins/mod_csi_simple.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/mod_csi_simple.lua b/plugins/mod_csi_simple.lua index d0de222e..ff1fa86c 100644 --- a/plugins/mod_csi_simple.lua +++ b/plugins/mod_csi_simple.lua @@ -71,6 +71,7 @@ local function manage_buffer(stanza, session) end local function flush_buffer(data, session) + session.log("debug", "Client sent something, flushing buffer once"); session.conn:resume_writes(); return data; end @@ -109,6 +110,7 @@ module:hook("c2s-ondrain", function (event) if session.state == "inactive" and session.conn and session.conn and session.conn.pause_writes then session.csi_counter = 0; session.conn:pause_writes(); + session.log("debug", "Buffer flushed, resuming inactive mode"); end end); -- cgit v1.2.3 From ba9e50592452854fe03cc12b1c376c17b8091c80 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 24 Mar 2019 22:01:36 +0100 Subject: mod_csi_simple: Improve debug logs by mentioing why the buffer gets flushed --- plugins/mod_csi_simple.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/mod_csi_simple.lua b/plugins/mod_csi_simple.lua index ff1fa86c..2dda1c42 100644 --- a/plugins/mod_csi_simple.lua +++ b/plugins/mod_csi_simple.lua @@ -61,7 +61,11 @@ end local function manage_buffer(stanza, session) local ctr = session.csi_counter or 0; - if ctr >= queue_size or module:fire_event("csi-is-stanza-important", { stanza = stanza, session = session }) then + if ctr >= queue_size then + session.log("debug", "Queue size limit hit, flushing buffer"); + session.conn:resume_writes(); + elseif module:fire_event("csi-is-stanza-important", { stanza = stanza, session = session }) then + session.log("debug", "Important stanza, flushing buffer"); session.conn:resume_writes(); else stanza = with_timestamp(stanza, jid.join(session.username, session.host)) -- cgit v1.2.3 From 5074566d79830e5a6ecba2b13fd9cdad2e1fb902 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 25 Mar 2019 10:32:39 +0000 Subject: mod_csi_simple: Fix type in function name --- plugins/mod_csi_simple.lua | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/plugins/mod_csi_simple.lua b/plugins/mod_csi_simple.lua index 2dda1c42..c79c56fc 100644 --- a/plugins/mod_csi_simple.lua +++ b/plugins/mod_csi_simple.lua @@ -90,7 +90,7 @@ function enable_optimizations(session) end end -function disble_optimizations(session) +function disable_optimizations(session) if session.conn and session.conn and session.conn.resume_writes then filters.remove_filter(session, "stanzas/out", manage_buffer); filters.remove_filter(session, "bytes/in", flush_buffer); @@ -105,7 +105,7 @@ end); module:hook("csi-client-active", function (event) local session = event.origin; - disble_optimizations(session); + disable_optimizations(session); end); @@ -132,9 +132,8 @@ function module.unload() for _, user_session in pairs(prosody.hosts[module.host].sessions) do for _, session in pairs(user_session.sessions) do if session.state == "inactive" then - disble_optimizations(session); + disable_optimizations(session); end end end end - -- cgit v1.2.3 From 096ebc3bcfde419b5c9c387f08d9c41c4d65b847 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 25 Mar 2019 15:20:28 +0100 Subject: mod_csi_simple: Include queue size in debug messages --- plugins/mod_csi_simple.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/mod_csi_simple.lua b/plugins/mod_csi_simple.lua index c79c56fc..a9148618 100644 --- a/plugins/mod_csi_simple.lua +++ b/plugins/mod_csi_simple.lua @@ -62,10 +62,10 @@ end local function manage_buffer(stanza, session) local ctr = session.csi_counter or 0; if ctr >= queue_size then - session.log("debug", "Queue size limit hit, flushing buffer"); + session.log("debug", "Queue size limit hit, flushing buffer (queue size is %d)", session.csi_counter); session.conn:resume_writes(); elseif module:fire_event("csi-is-stanza-important", { stanza = stanza, session = session }) then - session.log("debug", "Important stanza, flushing buffer"); + session.log("debug", "Important stanza, flushing buffer (queue size is %d)", session.csi_counter); session.conn:resume_writes(); else stanza = with_timestamp(stanza, jid.join(session.username, session.host)) @@ -75,7 +75,7 @@ local function manage_buffer(stanza, session) end local function flush_buffer(data, session) - session.log("debug", "Client sent something, flushing buffer once"); + session.log("debug", "Client sent something, flushing buffer once (queue size is %d)", session.csi_counter); session.conn:resume_writes(); return data; end @@ -112,9 +112,9 @@ end); module:hook("c2s-ondrain", function (event) local session = event.session; if session.state == "inactive" and session.conn and session.conn and session.conn.pause_writes then - session.csi_counter = 0; session.conn:pause_writes(); - session.log("debug", "Buffer flushed, resuming inactive mode"); + session.log("debug", "Buffer flushed, resuming inactive mode (queue size was %d)", session.csi_counter); + session.csi_counter = 0; end end); -- cgit v1.2.3 From e1b559853fb0f6e0967124a135e1d380d02316d9 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 25 Mar 2019 14:37:43 +0000 Subject: util.stanza: Fix :top_tag() handling of namespaced attributes --- spec/util_stanza_spec.lua | 31 ++++++++++++++++++++++++ util/stanza.lua | 62 +++++++++++++++++++++++------------------------ 2 files changed, 61 insertions(+), 32 deletions(-) diff --git a/spec/util_stanza_spec.lua b/spec/util_stanza_spec.lua index 18e39554..38503ab7 100644 --- a/spec/util_stanza_spec.lua +++ b/spec/util_stanza_spec.lua @@ -381,4 +381,35 @@ describe("util.stanza", function() end); end); end); + + describe("top_tag", function () + local xml_parse = require "util.xml".parse; + it("works", function () + local s = st.message({type="chat"}, "Hello"); + local top_tag = s:top_tag(); + assert.is_string(top_tag); + assert.not_equal("/>", top_tag:sub(-2, -1)); + assert.equal(">", top_tag:sub(-1, -1)); + local s2 = xml_parse(top_tag..""); + assert(st.is_stanza(s2)); + assert.equal("message", s2.name); + assert.equal(0, #s2); + assert.equal(0, #s2.tags); + assert.equal("chat", s2.attr.type); + end); + + it("works with namespaced attributes", function () + local s = xml_parse[[]]; + local top_tag = s:top_tag(); + assert.is_string(top_tag); + assert.not_equal("/>", top_tag:sub(-2, -1)); + assert.equal(">", top_tag:sub(-1, -1)); + local s2 = xml_parse(top_tag..""); + assert(st.is_stanza(s2)); + assert.equal("message", s2.name); + assert.equal(0, #s2); + assert.equal(0, #s2.tags); + assert.equal("true", s2.attr["my-awesome-ns\1bar"]); + end); + end); end); diff --git a/util/stanza.lua b/util/stanza.lua index e9847ca6..7fe5c7ae 100644 --- a/util/stanza.lua +++ b/util/stanza.lua @@ -270,6 +270,34 @@ function stanza_mt:find(path) until not self end +local function _clone(stanza, only_top) + local attr, tags = {}, {}; + for k,v in pairs(stanza.attr) do attr[k] = v; end + local old_namespaces, namespaces = stanza.namespaces; + if old_namespaces then + namespaces = {}; + for k,v in pairs(old_namespaces) do namespaces[k] = v; end + end + local new = { name = stanza.name, attr = attr, namespaces = namespaces, tags = tags }; + if not only_top then + for i=1,#stanza do + local child = stanza[i]; + if child.name then + child = _clone(child); + t_insert(tags, child); + end + t_insert(new, child); + end + end + return setmetatable(new, stanza_mt); +end + +local function clone(stanza, only_top) + if not is_stanza(stanza) then + error("bad argument to clone: expected stanza, got "..type(stanza)); + end + return _clone(stanza, only_top); +end local escape_table = { ["'"] = "'", ["\""] = """, ["<"] = "<", [">"] = ">", ["&"] = "&" }; local function xml_escape(str) return (s_gsub(str, "['&<>\"]", escape_table)); end @@ -310,11 +338,8 @@ function stanza_mt.__tostring(t) end function stanza_mt.top_tag(t) - local attr_string = ""; - if t.attr then - for k, v in pairs(t.attr) do if type(k) == "string" then attr_string = attr_string .. s_format(" %s='%s'", k, xml_escape(tostring(v))); end end - end - return s_format("<%s%s>", t.name, attr_string); + local top_tag_clone = clone(t, true); + return tostring(top_tag_clone):sub(1,-3)..">"; end function stanza_mt.get_text(t) @@ -388,33 +413,6 @@ local function deserialize(serialized) end end -local function _clone(stanza) - local attr, tags = {}, {}; - for k,v in pairs(stanza.attr) do attr[k] = v; end - local old_namespaces, namespaces = stanza.namespaces; - if old_namespaces then - namespaces = {}; - for k,v in pairs(old_namespaces) do namespaces[k] = v; end - end - local new = { name = stanza.name, attr = attr, namespaces = namespaces, tags = tags }; - for i=1,#stanza do - local child = stanza[i]; - if child.name then - child = _clone(child); - t_insert(tags, child); - end - t_insert(new, child); - end - return setmetatable(new, stanza_mt); -end - -local function clone(stanza) - if not is_stanza(stanza) then - error("bad argument to clone: expected stanza, got "..type(stanza)); - end - return _clone(stanza); -end - local function message(attr, body) if not body then return new_stanza("message", attr); -- cgit v1.2.3 From de724221378ba5772c9cfdb2d40c43619da8166f Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Tue, 26 Mar 2019 13:51:06 +0000 Subject: Backed out changeset 3eea63a68e0f Commit included intended changes to loggingmanager --- core/loggingmanager.lua | 21 +-------------------- util/queue.lua | 9 +++++---- 2 files changed, 6 insertions(+), 24 deletions(-) diff --git a/core/loggingmanager.lua b/core/loggingmanager.lua index b510617f..cfa8246a 100644 --- a/core/loggingmanager.lua +++ b/core/loggingmanager.lua @@ -18,9 +18,6 @@ local getstyle, getstring = require "util.termcolours".getstyle, require "util.t local config = require "core.configmanager"; local logger = require "util.logger"; -local have_pposix, pposix = pcall(require, "util.pposix"); -have_pposix = have_pposix and pposix._VERSION == "0.4.4"; - local _ENV = nil; -- luacheck: std none @@ -48,8 +45,7 @@ local function add_rule(sink_config) local sink = sink_maker(sink_config); -- Set sink for all chosen levels - local levels = get_levels(sink_config.levels or logging_levels); - for level in pairs(levels) do + for level in pairs(get_levels(sink_config.levels or logging_levels)) do logger.add_level_sink(level, sink); end end @@ -236,21 +232,6 @@ local function log_to_console(sink_config) end log_sink_types.console = log_to_console; -if have_pposix then - local syslog_opened; - local function log_to_syslog(sink_config) -- luacheck: ignore 212/sink_config - if not syslog_opened then - pposix.syslog_open(sink_config.syslog_name or "prosody", sink_config.syslog_facility or config.get("*", "syslog_facility")); - syslog_opened = true; - end - local syslog = pposix.syslog_log; - return function (name, level, message, ...) - syslog(level, name, format(message, ...)); - end; - end - log_sink_types.syslog = log_to_syslog; -end - local function register_sink_type(name, sink_maker) local old_sink_maker = log_sink_types[name]; log_sink_types[name] = sink_maker; diff --git a/util/queue.lua b/util/queue.lua index 66ed098b..e63b3f1c 100644 --- a/util/queue.lua +++ b/util/queue.lua @@ -52,15 +52,16 @@ local function new(size, allow_wrapping) return t[tail]; end; items = function (self) - return function (_, pos) - if pos >= items then + --luacheck: ignore 431/t + return function (t, pos) + if pos >= t:count() then return nil; end local read_pos = tail + pos; - if read_pos > self.size then + if read_pos > t.size then read_pos = (read_pos%size); end - return pos+1, t[read_pos]; + return pos+1, t._items[read_pos]; end, self, 0; end; consume = function (self) -- cgit v1.2.3 From e5d74b77aec0f9c63104384ef1f550d57959fbce Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Tue, 26 Mar 2019 13:54:14 +0000 Subject: util.queue: Update :items() to consistently use private data directly It will perform better this way, and we were accessing private variables already within the iterator. Replaces 3eea63a68e0f --- util/queue.lua | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/util/queue.lua b/util/queue.lua index e63b3f1c..66ed098b 100644 --- a/util/queue.lua +++ b/util/queue.lua @@ -52,16 +52,15 @@ local function new(size, allow_wrapping) return t[tail]; end; items = function (self) - --luacheck: ignore 431/t - return function (t, pos) - if pos >= t:count() then + return function (_, pos) + if pos >= items then return nil; end local read_pos = tail + pos; - if read_pos > t.size then + if read_pos > self.size then read_pos = (read_pos%size); end - return pos+1, t._items[read_pos]; + return pos+1, t[read_pos]; end, self, 0; end; consume = function (self) -- cgit v1.2.3 From 2ab785fd9fc6d669b6bd0c24333dc27af86153af Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Tue, 26 Mar 2019 14:48:21 +0000 Subject: loggingmanager, mod_posix: Move syslog to core, fixes #541 (in a way) --- core/loggingmanager.lua | 19 +++++++++++++++++++ plugins/mod_posix.lua | 13 ------------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/core/loggingmanager.lua b/core/loggingmanager.lua index cfa8246a..85a6380b 100644 --- a/core/loggingmanager.lua +++ b/core/loggingmanager.lua @@ -18,6 +18,9 @@ local getstyle, getstring = require "util.termcolours".getstyle, require "util.t local config = require "core.configmanager"; local logger = require "util.logger"; +local have_pposix, pposix = pcall(require, "util.pposix"); +have_pposix = have_pposix and pposix._VERSION == "0.4.0"; + local _ENV = nil; -- luacheck: std none @@ -232,6 +235,22 @@ local function log_to_console(sink_config) end log_sink_types.console = log_to_console; +if have_pposix then + local syslog_opened; + local function log_to_syslog(sink_config) -- luacheck: ignore 212/sink_config + if not syslog_opened then + local facility = sink_config.syslog_facility or config.get("*", "syslog_facility"); + pposix.syslog_open(sink_config.syslog_name or "prosody", facility); + syslog_opened = true; + end + local syslog = pposix.syslog_log; + return function (name, level, message, ...) + syslog(level, name, format(message, ...)); + end; + end + log_sink_types.syslog = log_to_syslog; +end + local function register_sink_type(name, sink_maker) local old_sink_maker = log_sink_types[name]; log_sink_types[name] = sink_maker; diff --git a/plugins/mod_posix.lua b/plugins/mod_posix.lua index 23df4d23..8367ae9e 100644 --- a/plugins/mod_posix.lua +++ b/plugins/mod_posix.lua @@ -113,19 +113,6 @@ local function write_pidfile() end end -local syslog_opened; -function syslog_sink_maker(config) -- luacheck: ignore 212/config - if not syslog_opened then - pposix.syslog_open("prosody", module:get_option_string("syslog_facility")); - syslog_opened = true; - end - local syslog = pposix.syslog_log; - return function (name, level, message, ...) - syslog(level, name, format(message, ...)); - end; -end -require "core.loggingmanager".register_sink_type("syslog", syslog_sink_maker); - local daemonize = module:get_option("daemonize", prosody.installed); local function remove_log_sinks() -- cgit v1.2.3 From 28322869e86df2d2d094acfe83a09d6dc81732c4 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Tue, 26 Mar 2019 14:59:42 +0000 Subject: mod_posix: Remove unnecessary import of util.format (thanks luacheck and buildbot) --- plugins/mod_posix.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/mod_posix.lua b/plugins/mod_posix.lua index 8367ae9e..a2a60dd0 100644 --- a/plugins/mod_posix.lua +++ b/plugins/mod_posix.lua @@ -20,7 +20,6 @@ if not have_signal then module:log("warn", "Couldn't load signal library, won't respond to SIGTERM"); end -local format = require "util.format".format; local lfs = require "lfs"; local stat = lfs.attributes; -- cgit v1.2.3 From 3616d69edbd95a27b6edc042fb4fc512b584b71b Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Tue, 26 Mar 2019 17:22:25 +0000 Subject: moduleapi: Remove overly-verbose debug logging on module status change --- core/moduleapi.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/core/moduleapi.lua b/core/moduleapi.lua index 2db7433a..e9e4c6d3 100644 --- a/core/moduleapi.lua +++ b/core/moduleapi.lua @@ -531,7 +531,6 @@ function api:set_status(status_type, status_message, override) return; end self.status_type, self.status_message, self.status_time = status_type, status_message, time_now(); - self:log("debug", "New status: %s", status_type); self:fire_event("module-status/updated", { name = self.name }); end -- cgit v1.2.3 From 36ad587977a55c0042e6aae283b10acbb50a87df Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Tue, 26 Mar 2019 17:22:56 +0000 Subject: moduleapi: Log suppressed status priority and message when not overriding --- core/moduleapi.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/moduleapi.lua b/core/moduleapi.lua index e9e4c6d3..b81bbeb2 100644 --- a/core/moduleapi.lua +++ b/core/moduleapi.lua @@ -527,7 +527,7 @@ function api:set_status(status_type, status_message, override) -- By default an 'error' status can only be overwritten by another 'error' status if (current_priority >= status_priorities.error and priority < current_priority and override ~= true) or (override == false and current_priority > priority) then - self:log("debug", "Ignoring status"); + self:log("debug", "moduleapi: ignoring status [prio %d override %s]: %s", priority, override, status_message); return; end self.status_type, self.status_message, self.status_time = status_type, status_message, time_now(); -- cgit v1.2.3 From cb800a7c5d2517ef0bae478fb6a4990a72b04d37 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 29 Mar 2019 22:45:54 +0100 Subject: core.s2smanager: Rename variable to be same in two functions --- core/s2smanager.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/s2smanager.lua b/core/s2smanager.lua index 0ba5e7c6..fbe0458b 100644 --- a/core/s2smanager.lua +++ b/core/s2smanager.lua @@ -26,10 +26,10 @@ local _ENV = nil; -- luacheck: std none local function new_incoming(conn) - local session = { conn = conn, type = "s2sin_unauthed", direction = "incoming", hosts = {} }; - session.log = logger_init("s2sin"..tostring(session):match("[a-f0-9]+$")); - incoming_s2s[session] = true; - return session; + local host_session = { conn = conn, type = "s2sin_unauthed", direction = "incoming", hosts = {} }; + host_session.log = logger_init("s2sin"..tostring(host_session):match("[a-f0-9]+$")); + incoming_s2s[host_session] = true; + return host_session; end local function new_outgoing(from_host, to_host) -- cgit v1.2.3 From 281c3a42c832e7f61b33cc7492cdd5b23d46a8c6 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 29 Mar 2019 22:37:12 +0100 Subject: core.s2smanager: Spread out session tables over multiple lines Improves readability --- core/s2smanager.lua | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/core/s2smanager.lua b/core/s2smanager.lua index fbe0458b..e5540e1d 100644 --- a/core/s2smanager.lua +++ b/core/s2smanager.lua @@ -26,15 +26,26 @@ local _ENV = nil; -- luacheck: std none local function new_incoming(conn) - local host_session = { conn = conn, type = "s2sin_unauthed", direction = "incoming", hosts = {} }; + local host_session = { + conn = conn, + type = "s2sin_unauthed", + direction = "incoming", + hosts = {}, + }; host_session.log = logger_init("s2sin"..tostring(host_session):match("[a-f0-9]+$")); incoming_s2s[host_session] = true; return host_session; end local function new_outgoing(from_host, to_host) - local host_session = { to_host = to_host, from_host = from_host, host = from_host, - notopen = true, type = "s2sout_unauthed", direction = "outgoing" }; + local host_session = { + to_host = to_host, + from_host = from_host, + host = from_host, + notopen = true, + type = "s2sout_unauthed", + direction = "outgoing", + }; hosts[from_host].s2sout[to_host] = host_session; local conn_name = "s2sout"..tostring(host_session):match("[a-f0-9]*$"); host_session.log = logger_init(conn_name); -- cgit v1.2.3 From 1ec8b4510430479da2cb404f03fd1067dae45c82 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 29 Mar 2019 22:40:53 +0100 Subject: core.s2smanager: Use util.session to create sessions --- core/s2smanager.lua | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/core/s2smanager.lua b/core/s2smanager.lua index e5540e1d..46dcd108 100644 --- a/core/s2smanager.lua +++ b/core/s2smanager.lua @@ -13,6 +13,7 @@ local tostring, pairs, setmetatable = tostring, pairs, setmetatable; local logger_init = require "util.logger".init; +local sessionlib = require "util.session"; local log = logger_init("s2smanager"); @@ -26,29 +27,26 @@ local _ENV = nil; -- luacheck: std none local function new_incoming(conn) - local host_session = { - conn = conn, - type = "s2sin_unauthed", - direction = "incoming", - hosts = {}, - }; - host_session.log = logger_init("s2sin"..tostring(host_session):match("[a-f0-9]+$")); + local host_session = sessionlib.new("s2sin"); + sessionlib.set_id(host_session); + sessionlib.set_logger(host_session); + sessionlib.set_conn(host_session, conn); + host_session.direction = "incoming"; + host_session.session.hosts = {}; incoming_s2s[host_session] = true; return host_session; end local function new_outgoing(from_host, to_host) - local host_session = { - to_host = to_host, - from_host = from_host, - host = from_host, - notopen = true, - type = "s2sout_unauthed", - direction = "outgoing", - }; + local host_session = sessionlib.new("s2sout"); + sessionlib.set_id(host_session); + sessionlib.set_logger(host_session); + host_session.to_host = to_host; + host_session.from_host = from_host; + host_session.host = from_host; + host_session.notopen = true; + host_session.direction = "outgoing"; hosts[from_host].s2sout[to_host] = host_session; - local conn_name = "s2sout"..tostring(host_session):match("[a-f0-9]*$"); - host_session.log = logger_init(conn_name); return host_session; end -- cgit v1.2.3 From ae1009d110d6abd42ba1f8c1698811ff947e99f1 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 29 Mar 2019 23:05:08 +0100 Subject: core.sessionmanager: Use util.session to create sessions --- core/sessionmanager.lua | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/core/sessionmanager.lua b/core/sessionmanager.lua index 9a2456f2..f5af1185 100644 --- a/core/sessionmanager.lua +++ b/core/sessionmanager.lua @@ -21,6 +21,7 @@ local config_get = require "core.configmanager".get; local resourceprep = require "util.encodings".stringprep.resourceprep; local nodeprep = require "util.encodings".stringprep.nodeprep; local generate_identifier = require "util.id".short; +local sessionlib = require "util.session"; local initialize_filters = require "util.filters".initialize; local gettime = require "socket".gettime; @@ -29,7 +30,12 @@ local _ENV = nil; -- luacheck: std none local function new_session(conn) - local session = { conn = conn, type = "c2s_unauthed", conntime = gettime() }; + local session = sessionlib.new("c2s"); + sessionlib.set_id(session); + sessionlib.set_logger(session); + sessionlib.set_conn(session, conn); + + session.conntime = gettime(); local filter = initialize_filters(session); local w = conn.write; -- cgit v1.2.3 From e7fce52802163ca1c042bd1a369b81c775a72980 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 30 Mar 2019 09:04:33 +0100 Subject: core.s2smanager: Fix previous commit (Thanks Martin) --- core/s2smanager.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/s2smanager.lua b/core/s2smanager.lua index 46dcd108..684bb94e 100644 --- a/core/s2smanager.lua +++ b/core/s2smanager.lua @@ -32,7 +32,7 @@ local function new_incoming(conn) sessionlib.set_logger(host_session); sessionlib.set_conn(host_session, conn); host_session.direction = "incoming"; - host_session.session.hosts = {}; + host_session.hosts = {}; incoming_s2s[host_session] = true; return host_session; end -- cgit v1.2.3 From 643032e8b38abbb96d52c763cd5e3093d9b895ce Mon Sep 17 00:00:00 2001 From: marc0s Date: Sat, 30 Mar 2019 18:44:34 +0100 Subject: doc/coding_style: apply consistent semi-colon usage Make all "good" statements in the coding style document use consistent statement-separator semi-colon --- doc/coding_style.md | 122 ++++++++++++++++++++++++++-------------------------- 1 file changed, 61 insertions(+), 61 deletions(-) diff --git a/doc/coding_style.md b/doc/coding_style.md index 6ca527fa..f9a10ece 100644 --- a/doc/coding_style.md +++ b/doc/coding_style.md @@ -13,7 +13,7 @@ This style guides lists the coding conventions used in the for i, pkg in ipairs(packages) do for name, version in pairs(pkg) do if name == searched then - print(version) + print(version); end end end @@ -69,7 +69,7 @@ unless you are writing a function that operates on generic tables. ```lua for _, item in ipairs(items) do - do_something_with_item(item) + do_something_with_item(item); end ``` @@ -85,7 +85,7 @@ local c = function() end -- good -local this_is_my_object = {} +local this_is_my_object = {}; local function do_that_thing() -- ...stuff... @@ -113,7 +113,7 @@ end -- good local function is_evil(alignment) - return alignment < 100 + return alignment < 100; end ``` @@ -130,7 +130,7 @@ constants from C. * When creating a table, prefer populating its fields all at once, if possible: ```lua -local player = { name = "Jack", class = "Rogue" } +local player = { name = "Jack", class = "Rogue" }; ``` * Items should be separated by commas. If there are many items, put each @@ -145,7 +145,7 @@ local player = { ``` > **Rationale:** This makes the structure of your tables more evident at a glance. -Trailing commas make it quicker to add new fields and produces shorter diffs. +Trailing semi-colons make it quicker to add new fields and produces shorter diffs. * Use plain `key` syntax whenever possible, use `["key"]` syntax when using names that can't be represented as identifiers and avoid mixing representations in @@ -153,9 +153,9 @@ a declaration: ```lua local mytable = { - ["1394-E"] = val1, - ["UTF-8"] = val2, - ["and"] = val2, + ["1394-E"] = val1; + ["UTF-8"] = val2; + ["and"] = val2; } ``` @@ -165,8 +165,8 @@ local mytable = { that contain double quotes. ```lua -local name = "Prosody" -local sentence = 'The name of the program is "Prosody"' +local name = "Prosody"; +local sentence = 'The name of the program is "Prosody"'; ``` > **Rationale:** Double quotes are used as string delimiters in a larger number of @@ -218,12 +218,12 @@ end -- good local function is_good_name(name, options, args) if #name < 3 or #name > 30 then - return false + return false; end -- ...stuff... - return true + return true; end ``` @@ -236,8 +236,8 @@ end -- bad local data = get_data"KRP"..tostring(area_number) -- good -local data = get_data("KRP"..tostring(area_number)) -local data = get_data("KRP")..tostring(area_number) +local data = get_data("KRP"..tostring(area_number)); +local data = get_data("KRP")..tostring(area_number); ``` > **Rationale:** It is not obvious at a glace what the precedence rules are @@ -251,8 +251,8 @@ lines. ```lua local an_instance = a_module.new { - a_parameter = 42, - another_parameter = "yay", + a_parameter = 42; + another_parameter = "yay"; } ``` @@ -265,15 +265,15 @@ so there are no precedence issues. ```lua local luke = { - jedi = true, - age = 28, + jedi = true; + age = 28; } -- bad local is_jedi = luke["jedi"] -- good -local is_jedi = luke.jedi +local is_jedi = luke.jedi; ``` * Use subscript notation `[]` when accessing properties with a variable or if using a table as a list. @@ -282,11 +282,11 @@ local is_jedi = luke.jedi local vehicles = load_vehicles_from_disk("vehicles.dat") if vehicles["Porsche"] then - porsche_handler(vehicles["Porsche"]) - vehicles["Porsche"] = nil + porsche_handler(vehicles["Porsche"]); + vehicles["Porsche"] = nil; end for name, cars in pairs(vehicles) do - regular_handler(cars) + regular_handler(cars); end ``` @@ -298,7 +298,7 @@ to be used as a record/object field. * When declaring modules and classes, declare functions external to the table definition: ```lua -local my_module = {} +local my_module = {}; function my_module.a_function(x) -- code @@ -337,7 +337,7 @@ than if it says `check_version = function()` under some indentation level. superpower = get_superpower() -- good -local superpower = get_superpower() +local superpower = get_superpower(); ``` > **Rationale:** Not doing so will result in global variables to avoid polluting @@ -366,18 +366,18 @@ end -- good local bad = function() - test() - print("doing stuff..") + test(); + print("doing stuff.."); --...other stuff... - local name = get_name() + local name = get_name(); if name == "test" then - return false + return false; end - return name + return name; end ``` @@ -410,11 +410,11 @@ easier to scan visually: ```lua local function default_name(name) -- return the default "Waldo" if name is nil - return name or "Waldo" + return name or "Waldo"; end local function brew_coffee(machine) - return (machine and machine.is_loaded) and "coffee brewing" or "fill your water" + return (machine and machine.is_loaded) and "coffee brewing" or "fill your water"; end ``` @@ -434,7 +434,7 @@ if test then break end if not ok then return nil, "this failed for this reason: " .. reason end -- good -use_callback(x, function(k) return k.last end) +use_callback(x, function(k) return k.last end); -- good if test then @@ -446,8 +446,8 @@ if test < 1 and do_complicated_function(test) == false or seven == 8 and nine == -- good if test < 1 and do_complicated_function(test) == false or seven == 8 and nine == 10 then - do_other_complicated_function() - return false + do_other_complicated_function(); + return false; end ``` @@ -491,17 +491,17 @@ dog.set( "attr",{ }) -- good -local x = y * 9 -local numbers = {1, 2, 3} +local x = y * 9; +local numbers = {1, 2, 3}; local strings = { "hello"; "Lua"; "world"; } dog.set("attr", { - age = "1 year", - breed = "Bernese Mountain Dog", -}) + age = "1 year"; + breed = "Bernese Mountain Dog"; +}); ``` * Indent tables and functions according to the start of the line, not the construct: @@ -522,7 +522,7 @@ local my_table = { "world"; } using_a_callback(x, function(...) - print("hello") + print("hello"); end) ``` @@ -534,7 +534,7 @@ replacing `x` with `xy` in the `using_a_callback` example above). ```lua -- okay -local message = "Hello, "..user.."! This is your day # "..day.." in our platform!" +local message = "Hello, "..user.."! This is your day # "..day.." in our platform!"; ``` > **Rationale:** Being at the baseline, the dots already provide some visual spacing. @@ -582,8 +582,8 @@ local a = 1 local long_identifier = 2 -- good -local a = 1 -local long_identifier = 2 +local a = 1; +local long_identifier = 2; ``` > **Rationale:** This produces extra diffs which add noise to `git blame`. @@ -592,8 +592,8 @@ local long_identifier = 2 ```lua -- okay -sys_command(form, UI_FORM_UPDATE_NODE, "a", FORM_NODE_HIDDEN, false) -sys_command(form, UI_FORM_UPDATE_NODE, "sample", FORM_NODE_VISIBLE, false) +sys_command(form, UI_FORM_UPDATE_NODE, "a", FORM_NODE_HIDDEN, false); +sys_command(form, UI_FORM_UPDATE_NODE, "sample", FORM_NODE_VISIBLE, false); ``` ## Typing @@ -603,8 +603,8 @@ for function arguments: ```lua function manif.load_manifest(repo_url, lua_version) - assert(type(repo_url) == "string") - assert(type(lua_version) == "string" or not lua_version) + assert(type(repo_url) == "string"); + assert(type(lua_version) == "string" or not lua_version); -- ... end @@ -617,7 +617,7 @@ end local total_score = review_score .. "" -- good -local total_score = tostring(review_score) +local total_score = tostring(review_score); ``` ## Errors @@ -636,9 +636,9 @@ Follow [these guidelines](http://hisham.hm/2014/01/02/how-to-write-lua-modules-i * Always require a module into a local variable named after the last component of the module’s full name. ```lua -local bar = require("foo.bar") -- requiring the module +local bar = require("foo.bar"); -- requiring the module -bar.say("hello") -- using the module +bar.say("hello"); -- using the module ``` * Don’t rename modules arbitrarily: @@ -657,7 +657,7 @@ the whole module path. ```lua --- @module foo.bar -local bar = {} +local bar = {}; ``` * Try to use names that won't clash with your local variables. For instance, don't @@ -672,7 +672,7 @@ That is, `local function helper_foo()` means that `helper_foo` is really local. ```lua function bar.say(greeting) - print(greeting) + print(greeting); end ``` @@ -702,14 +702,14 @@ and do something like this instead: ```lua -- good -local messagepack = require("messagepack") -local mpack = messagepack.new({integer = "unsigned"}) +local messagepack = require("messagepack"); +local mpack = messagepack.new({integer = "unsigned"}); ``` * The invocation of require may omit parentheses around the module name: ```lua -local bla = require "bla" +local bla = require "bla"; ``` ## Metatables, classes and objects @@ -739,7 +739,7 @@ return { new = new }; my_object.my_method(my_object) -- good -my_object:my_method() +my_object:my_method(); ``` > **Rationale:** This makes it explicit that the intent is to use the function as a method. @@ -780,8 +780,8 @@ its arguments; it's better to spell out in the argument what the API the function implements is, instead of adding `_` variables. ``` -local foo, bar = some_function() --luacheck: ignore 212/foo -print(bar) +local foo, bar = some_function(); --luacheck: ignore 212/foo +print(bar); ``` * luacheck warning 542 (empty if branch) can also be ignored, when a sequence @@ -790,11 +790,11 @@ and one of the cases is meant to mean "pass". For example: ```lua if warning >= 600 and warning <= 699 then - print("no whitespace warnings") + print("no whitespace warnings"); elseif warning == 542 then --luacheck: ignore 542 -- pass else - print("got a warning: "..warning) + print("got a warning: "..warning); end ``` -- cgit v1.2.3 From ce1056dc43fb98b6df1b025e621c28cf4cabf7b9 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 1 Apr 2019 21:15:47 +0200 Subject: .luacheckrc: Correct indentation of 'exclude_files' list --- .luacheckrc | 60 ++++++++++++++++++++++++++++++------------------------------ 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/.luacheckrc b/.luacheckrc index f0cb93a8..f6760ee3 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -131,42 +131,42 @@ if os.getenv("PROSODY_STRICT_LINT") ~= "1" then unused_secondaries = false local exclude_files = { - "doc/net.server.lua"; + "doc/net.server.lua"; - "fallbacks/bit.lua"; - "fallbacks/lxp.lua"; + "fallbacks/bit.lua"; + "fallbacks/lxp.lua"; - "net/cqueues.lua"; - "net/dns.lua"; - "net/server_select.lua"; + "net/cqueues.lua"; + "net/dns.lua"; + "net/server_select.lua"; - "plugins/mod_storage_sql1.lua"; + "plugins/mod_storage_sql1.lua"; - "spec/core_configmanager_spec.lua"; - "spec/core_moduleapi_spec.lua"; - "spec/net_http_parser_spec.lua"; - "spec/util_events_spec.lua"; - "spec/util_http_spec.lua"; - "spec/util_ip_spec.lua"; - "spec/util_multitable_spec.lua"; - "spec/util_rfc6724_spec.lua"; - "spec/util_throttle_spec.lua"; - "spec/util_xmppstream_spec.lua"; + "spec/core_configmanager_spec.lua"; + "spec/core_moduleapi_spec.lua"; + "spec/net_http_parser_spec.lua"; + "spec/util_events_spec.lua"; + "spec/util_http_spec.lua"; + "spec/util_ip_spec.lua"; + "spec/util_multitable_spec.lua"; + "spec/util_rfc6724_spec.lua"; + "spec/util_throttle_spec.lua"; + "spec/util_xmppstream_spec.lua"; - "tools/ejabberd2prosody.lua"; - "tools/ejabberdsql2prosody.lua"; - "tools/erlparse.lua"; - "tools/jabberd14sql2prosody.lua"; - "tools/migration/migrator.cfg.lua"; - "tools/migration/migrator/jabberd14.lua"; - "tools/migration/migrator/mtools.lua"; - "tools/migration/migrator/prosody_files.lua"; - "tools/migration/migrator/prosody_sql.lua"; - "tools/migration/prosody-migrator.lua"; - "tools/openfire2prosody.lua"; - "tools/xep227toprosody.lua"; + "tools/ejabberd2prosody.lua"; + "tools/ejabberdsql2prosody.lua"; + "tools/erlparse.lua"; + "tools/jabberd14sql2prosody.lua"; + "tools/migration/migrator.cfg.lua"; + "tools/migration/migrator/jabberd14.lua"; + "tools/migration/migrator/mtools.lua"; + "tools/migration/migrator/prosody_files.lua"; + "tools/migration/migrator/prosody_sql.lua"; + "tools/migration/prosody-migrator.lua"; + "tools/openfire2prosody.lua"; + "tools/xep227toprosody.lua"; - "util/sasl/digest-md5.lua"; + "util/sasl/digest-md5.lua"; } for _, file in ipairs(exclude_files) do files[file] = { only = {} } -- cgit v1.2.3 From a6571e3ef33936cb4f2b99b582148accc60af846 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 2 Apr 2019 21:17:28 +0200 Subject: mod_limits: Fix indentation Appears to have been messed up in 60e113f3682f --- plugins/mod_limits.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/mod_limits.lua b/plugins/mod_limits.lua index 914d5c44..32bc75a9 100644 --- a/plugins/mod_limits.lua +++ b/plugins/mod_limits.lua @@ -51,18 +51,18 @@ end local default_filter_set = {}; function default_filter_set.bytes_in(bytes, session) - local sess_throttle = session.throttle; - if sess_throttle then - local ok, balance, outstanding = sess_throttle:poll(#bytes, true); + local sess_throttle = session.throttle; + if sess_throttle then + local ok, balance, outstanding = sess_throttle:poll(#bytes, true); if not ok then - session.log("debug", "Session over rate limit (%d) with %d (by %d), pausing", sess_throttle.max, #bytes, outstanding); + session.log("debug", "Session over rate limit (%d) with %d (by %d), pausing", sess_throttle.max, #bytes, outstanding); outstanding = ceil(outstanding); session.conn:pause(); -- Read no more data from the connection until there is no outstanding data local outstanding_data = bytes:sub(-outstanding); bytes = bytes:sub(1, #bytes-outstanding); timer.add_task(limits_resolution, function () if not session.conn then return; end - if sess_throttle:peek(#outstanding_data) then + if sess_throttle:peek(#outstanding_data) then session.log("debug", "Resuming paused session"); session.conn:resume(); end -- cgit v1.2.3 From 1b534392de5c8430ee88417c5f6fff8acce1da62 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 2 Apr 2019 20:38:51 +0200 Subject: mod_limits: Allow configuring a list of unrestricted JIDs (fixes #1323) --- plugins/mod_limits.lua | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/plugins/mod_limits.lua b/plugins/mod_limits.lua index 32bc75a9..56e3faaf 100644 --- a/plugins/mod_limits.lua +++ b/plugins/mod_limits.lua @@ -96,3 +96,20 @@ end function module.unload() filters.remove_filter_hook(filter_hook); end + +function module.add_host(module) + local unlimited_jids = module:get_option_inherited_set("unlimited_jids", {}); + + if not unlimited_jids:empy() then + module:hook("authentication-success", function (event) + local session = event.session; + 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; + end + end); + end +end -- cgit v1.2.3 From e632119ffa88909b7f3be5b59e47373204e75aa8 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 2 Apr 2019 21:22:20 +0200 Subject: mod_limits: Fix typo --- plugins/mod_limits.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_limits.lua b/plugins/mod_limits.lua index 56e3faaf..7ae8bb34 100644 --- a/plugins/mod_limits.lua +++ b/plugins/mod_limits.lua @@ -100,7 +100,7 @@ end function module.add_host(module) local unlimited_jids = module:get_option_inherited_set("unlimited_jids", {}); - if not unlimited_jids:empy() then + if not unlimited_jids:empty() then module:hook("authentication-success", function (event) local session = event.session; local session_type = session.type:match("^[^_]+"); -- cgit v1.2.3 From 10705cd0535b328d98003ad98cd2d1384fc8a370 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 3 Apr 2019 01:02:36 +0200 Subject: README: Remove mailing list where issue tracker changes went in the Google Code days --- README | 3 --- 1 file changed, 3 deletions(-) diff --git a/README b/README index 797a2175..f679402a 100644 --- a/README +++ b/README @@ -26,9 +26,6 @@ Mailing lists: Development discussion: https://groups.google.com/group/prosody-dev - Issue tracker changes: - https://groups.google.com/group/prosody-issues - ## Installation See the accompanying INSTALL file for help on building Prosody from source. Alternatively -- cgit v1.2.3 From 085e1728975a2bc34e5945f479cc89665bd4e44c Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 3 Apr 2019 01:03:23 +0200 Subject: README: Add link to current issue tracker --- README | 1 + 1 file changed, 1 insertion(+) diff --git a/README b/README index f679402a..4b52186b 100644 --- a/README +++ b/README @@ -12,6 +12,7 @@ rapidly prototype new protocols. Homepage: https://prosody.im/ Download: https://prosody.im/download Documentation: https://prosody.im/doc/ +Issue tracker: https://issues.prosody.im/ Jabber/XMPP Chat: Address: -- cgit v1.2.3 From 049663349e8123008c98a08d6016cf6c9c7e30a2 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 3 Apr 2019 01:09:21 +0200 Subject: CONTRIBUTING: Add a short file referencing the online doc/contributing page --- CONTRIBUTING | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 CONTRIBUTING diff --git a/CONTRIBUTING b/CONTRIBUTING new file mode 100644 index 00000000..2e732c73 --- /dev/null +++ b/CONTRIBUTING @@ -0,0 +1,9 @@ +Thanks for your interest in contributing to the project! + +There are many ways to contribute, such as helping improve the +documentation, reporting bugs, spreading the word or testing the latest +development version. + +You can find more information on how to contribute at + +See also the HACKERS and README files. -- cgit v1.2.3 From 392eac3c621a11d30d9e42803faaf93cdb48d83d Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 3 Apr 2019 17:20:57 +0200 Subject: util.session: Fix session id not include unauthed forever --- util/session.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/util/session.lua b/util/session.lua index b2a726ce..b9c6bec7 100644 --- a/util/session.lua +++ b/util/session.lua @@ -4,12 +4,13 @@ local logger = require "util.logger"; local function new_session(typ) local session = { type = typ .. "_unauthed"; + base_type = typ; }; return session; end local function set_id(session) - local id = session.type .. tostring(session):match("%x+$"):lower(); + local id = session.base_type .. tostring(session):match("%x+$"):lower(); session.id = id; return session; end -- cgit v1.2.3 From 6f6ac910564bf6ecdc0e6b70cf06bd84a24868fb Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 5 Apr 2019 16:10:51 +0200 Subject: net.http.files: Copy of mod_http_files The intent is to make it easier to reuse and simplify mod_http_files. Currently modules will use the serve() function exported by mod_http_files in order to serve their own files. This makes it unclear whether mod_http_files should be doing anything on its own. Moving the logic into a separate module should help here, as well as make re-use outside of prosody easier. --- net/http/files.lua | 198 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 198 insertions(+) create mode 100644 net/http/files.lua diff --git a/net/http/files.lua b/net/http/files.lua new file mode 100644 index 00000000..1dae0d6d --- /dev/null +++ b/net/http/files.lua @@ -0,0 +1,198 @@ +-- Prosody IM +-- Copyright (C) 2008-2010 Matthew Wild +-- Copyright (C) 2008-2010 Waqas Hussain +-- +-- This project is MIT/X11 licensed. Please see the +-- COPYING file in the source package for more information. +-- + +module:depends("http"); +local server = require"net.http.server"; +local lfs = require "lfs"; + +local os_date = os.date; +local open = io.open; +local stat = lfs.attributes; +local build_path = require"socket.url".build_path; +local path_sep = package.config:sub(1,1); + +local base_path = module:get_option_path("http_files_dir", module:get_option_path("http_path")); +local cache_size = module:get_option_number("http_files_cache_size", 128); +local cache_max_file_size = module:get_option_number("http_files_cache_max_file_size", 4096); +local dir_indices = module:get_option_array("http_index_files", { "index.html", "index.htm" }); +local directory_index = module:get_option_boolean("http_dir_listing"); + +local mime_map = module:shared("/*/http_files/mime").types; +if not mime_map then + mime_map = { + html = "text/html", htm = "text/html", + xml = "application/xml", + txt = "text/plain", + css = "text/css", + js = "application/javascript", + png = "image/png", + gif = "image/gif", + jpeg = "image/jpeg", jpg = "image/jpeg", + svg = "image/svg+xml", + }; + module:shared("/*/http_files/mime").types = mime_map; + + local mime_types, err = open(module:get_option_path("mime_types_file", "/etc/mime.types", "config"), "r"); + if mime_types then + local mime_data = mime_types:read("*a"); + mime_types:close(); + setmetatable(mime_map, { + __index = function(t, ext) + local typ = mime_data:match("\n(%S+)[^\n]*%s"..(ext:lower()).."%s") or "application/octet-stream"; + t[ext] = typ; + return typ; + end + }); + end +end + +local forbidden_chars_pattern = "[/%z]"; +if prosody.platform == "windows" then + forbidden_chars_pattern = "[/%z\001-\031\127\"*:<>?|]" +end + +local urldecode = require "util.http".urldecode; +function sanitize_path(path) + if not path then return end + local out = {}; + + local c = 0; + for component in path:gmatch("([^/]+)") do + component = urldecode(component); + if component:find(forbidden_chars_pattern) then + return nil; + elseif component == ".." then + if c <= 0 then + return nil; + end + out[c] = nil; + c = c - 1; + elseif component ~= "." then + c = c + 1; + out[c] = component; + end + end + if path:sub(-1,-1) == "/" then + out[c+1] = ""; + end + return "/"..table.concat(out, "/"); +end + +local cache = require "util.cache".new(cache_size); + +function serve(opts) + if type(opts) ~= "table" then -- assume path string + opts = { path = opts }; + end + -- luacheck: ignore 431 + local base_path = opts.path; + local dir_indices = opts.index_files or dir_indices; + local directory_index = opts.directory_index; + local function serve_file(event, path) + local request, response = event.request, event.response; + local sanitized_path = sanitize_path(path); + if path and not sanitized_path then + return 400; + end + path = sanitized_path; + local orig_path = sanitize_path(request.path); + local full_path = base_path .. (path or ""):gsub("/", path_sep); + local attr = stat(full_path:match("^.*[^\\/]")); -- Strip trailing path separator because Windows + if not attr then + return 404; + end + + local request_headers, response_headers = request.headers, response.headers; + + local last_modified = os_date('!%a, %d %b %Y %H:%M:%S GMT', attr.modification); + response_headers.last_modified = last_modified; + + local etag = ('"%02x-%x-%x-%x"'):format(attr.dev or 0, attr.ino or 0, attr.size or 0, attr.modification or 0); + response_headers.etag = etag; + + local if_none_match = request_headers.if_none_match + local if_modified_since = request_headers.if_modified_since; + if etag == if_none_match + or (not if_none_match and last_modified == if_modified_since) then + return 304; + end + + local data = cache:get(orig_path); + if data and data.etag == etag then + response_headers.content_type = data.content_type; + data = data.data; + elseif attr.mode == "directory" and path then + if full_path:sub(-1) ~= "/" then + local dir_path = { is_absolute = true, is_directory = true }; + for dir in orig_path:gmatch("[^/]+") do dir_path[#dir_path+1]=dir; end + response_headers.location = build_path(dir_path); + return 301; + end + for i=1,#dir_indices do + if stat(full_path..dir_indices[i], "mode") == "file" then + return serve_file(event, path..dir_indices[i]); + end + end + + if directory_index then + data = server._events.fire_event("directory-index", { path = request.path, full_path = full_path }); + end + if not data then + return 403; + end + cache:set(orig_path, { data = data, content_type = mime_map.html; etag = etag; }); + response_headers.content_type = mime_map.html; + + else + local f, err = open(full_path, "rb"); + if not f then + module:log("debug", "Could not open %s. Error was %s", full_path, err); + return 403; + end + local ext = full_path:match("%.([^./]+)$"); + local content_type = ext and mime_map[ext]; + response_headers.content_type = content_type; + if attr.size > cache_max_file_size then + response_headers.content_length = attr.size; + module:log("debug", "%d > cache_max_file_size", attr.size); + return response:send_file(f); + else + data = f:read("*a"); + f:close(); + end + cache:set(orig_path, { data = data; content_type = content_type; etag = etag }); + end + + return response:send(data); + end + + return serve_file; +end + +function wrap_route(routes) + for route,handler in pairs(routes) do + if type(handler) ~= "function" then + routes[route] = serve(handler); + end + end + return routes; +end + +if base_path then + module:provides("http", { + route = { + ["GET /*"] = serve { + path = base_path; + directory_index = directory_index; + } + }; + }); +else + module:log("debug", "http_files_dir not set, assuming use by some other module"); +end + -- cgit v1.2.3 From 3ea6ca719574ce8a1bfa9532c5526da05f3ff5d9 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 5 Apr 2019 17:09:03 +0200 Subject: net.http.files: Make into standalone library --- net/http/files.lua | 78 ++++++++++-------------------------------------------- 1 file changed, 14 insertions(+), 64 deletions(-) diff --git a/net/http/files.lua b/net/http/files.lua index 1dae0d6d..0b898dc6 100644 --- a/net/http/files.lua +++ b/net/http/files.lua @@ -6,9 +6,10 @@ -- COPYING file in the source package for more information. -- -module:depends("http"); local server = require"net.http.server"; local lfs = require "lfs"; +local new_cache = require "util.cache".new; +local log = require "util.logger".init("net.http.files"); local os_date = os.date; local open = io.open; @@ -16,48 +17,14 @@ local stat = lfs.attributes; local build_path = require"socket.url".build_path; local path_sep = package.config:sub(1,1); -local base_path = module:get_option_path("http_files_dir", module:get_option_path("http_path")); -local cache_size = module:get_option_number("http_files_cache_size", 128); -local cache_max_file_size = module:get_option_number("http_files_cache_max_file_size", 4096); -local dir_indices = module:get_option_array("http_index_files", { "index.html", "index.htm" }); -local directory_index = module:get_option_boolean("http_dir_listing"); - -local mime_map = module:shared("/*/http_files/mime").types; -if not mime_map then - mime_map = { - html = "text/html", htm = "text/html", - xml = "application/xml", - txt = "text/plain", - css = "text/css", - js = "application/javascript", - png = "image/png", - gif = "image/gif", - jpeg = "image/jpeg", jpg = "image/jpeg", - svg = "image/svg+xml", - }; - module:shared("/*/http_files/mime").types = mime_map; - - local mime_types, err = open(module:get_option_path("mime_types_file", "/etc/mime.types", "config"), "r"); - if mime_types then - local mime_data = mime_types:read("*a"); - mime_types:close(); - setmetatable(mime_map, { - __index = function(t, ext) - local typ = mime_data:match("\n(%S+)[^\n]*%s"..(ext:lower()).."%s") or "application/octet-stream"; - t[ext] = typ; - return typ; - end - }); - end -end local forbidden_chars_pattern = "[/%z]"; -if prosody.platform == "windows" then +if package.config:sub(1,1) == "\\" then forbidden_chars_pattern = "[/%z\001-\031\127\"*:<>?|]" end local urldecode = require "util.http".urldecode; -function sanitize_path(path) +local function sanitize_path(path) --> util.paths or util.http? if not path then return end local out = {}; @@ -83,15 +50,16 @@ function sanitize_path(path) return "/"..table.concat(out, "/"); end -local cache = require "util.cache".new(cache_size); - -function serve(opts) +local function serve(opts) if type(opts) ~= "table" then -- assume path string opts = { path = opts }; end + local mime_map = opts.mime_map or { html = "text/html" }; + local cache = new_cache(opts.cache_size or 256); + local cache_max_file_size = tonumber(opts.cache_max_file_size) or 1024 -- luacheck: ignore 431 local base_path = opts.path; - local dir_indices = opts.index_files or dir_indices; + local dir_indices = opts.index_files or { "index.html", "index.htm" }; local directory_index = opts.directory_index; local function serve_file(event, path) local request, response = event.request, event.response; @@ -151,7 +119,7 @@ function serve(opts) else local f, err = open(full_path, "rb"); if not f then - module:log("debug", "Could not open %s. Error was %s", full_path, err); + log("debug", "Could not open %s. Error was %s", full_path, err); return 403; end local ext = full_path:match("%.([^./]+)$"); @@ -159,7 +127,7 @@ function serve(opts) response_headers.content_type = content_type; if attr.size > cache_max_file_size then response_headers.content_length = attr.size; - module:log("debug", "%d > cache_max_file_size", attr.size); + log("debug", "%d > cache_max_file_size", attr.size); return response:send_file(f); else data = f:read("*a"); @@ -174,25 +142,7 @@ function serve(opts) return serve_file; end -function wrap_route(routes) - for route,handler in pairs(routes) do - if type(handler) ~= "function" then - routes[route] = serve(handler); - end - end - return routes; -end - -if base_path then - module:provides("http", { - route = { - ["GET /*"] = serve { - path = base_path; - directory_index = directory_index; - } - }; - }); -else - module:log("debug", "http_files_dir not set, assuming use by some other module"); -end +return { + serve = serve; +} -- cgit v1.2.3 From cb2eedde50f919165117c8dba312ba820db77f73 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 5 Apr 2019 17:12:19 +0200 Subject: mod_http_files: Use net.http.files --- plugins/mod_http_files.lua | 169 +++++++++------------------------------------ 1 file changed, 33 insertions(+), 136 deletions(-) diff --git a/plugins/mod_http_files.lua b/plugins/mod_http_files.lua index 1dae0d6d..acb99501 100644 --- a/plugins/mod_http_files.lua +++ b/plugins/mod_http_files.lua @@ -7,14 +7,9 @@ -- module:depends("http"); -local server = require"net.http.server"; -local lfs = require "lfs"; -local os_date = os.date; local open = io.open; -local stat = lfs.attributes; -local build_path = require"socket.url".build_path; -local path_sep = package.config:sub(1,1); +local fileserver = require"net.http.files"; local base_path = module:get_option_path("http_files_dir", module:get_option_path("http_path")); local cache_size = module:get_option_number("http_files_cache_size", 128); @@ -51,148 +46,50 @@ if not mime_map then end end -local forbidden_chars_pattern = "[/%z]"; -if prosody.platform == "windows" then - forbidden_chars_pattern = "[/%z\001-\031\127\"*:<>?|]" -end - -local urldecode = require "util.http".urldecode; -function sanitize_path(path) - if not path then return end - local out = {}; - - local c = 0; - for component in path:gmatch("([^/]+)") do - component = urldecode(component); - if component:find(forbidden_chars_pattern) then - return nil; - elseif component == ".." then - if c <= 0 then - return nil; - end - out[c] = nil; - c = c - 1; - elseif component ~= "." then - c = c + 1; - out[c] = component; - end - end - if path:sub(-1,-1) == "/" then - out[c+1] = ""; - end - return "/"..table.concat(out, "/"); -end - -local cache = require "util.cache".new(cache_size); - +-- COMPAT -- TODO deprecate function serve(opts) if type(opts) ~= "table" then -- assume path string opts = { path = opts }; end - -- luacheck: ignore 431 - local base_path = opts.path; - local dir_indices = opts.index_files or dir_indices; - local directory_index = opts.directory_index; - local function serve_file(event, path) - local request, response = event.request, event.response; - local sanitized_path = sanitize_path(path); - if path and not sanitized_path then - return 400; - end - path = sanitized_path; - local orig_path = sanitize_path(request.path); - local full_path = base_path .. (path or ""):gsub("/", path_sep); - local attr = stat(full_path:match("^.*[^\\/]")); -- Strip trailing path separator because Windows - if not attr then - return 404; - end - - local request_headers, response_headers = request.headers, response.headers; - - local last_modified = os_date('!%a, %d %b %Y %H:%M:%S GMT', attr.modification); - response_headers.last_modified = last_modified; - - local etag = ('"%02x-%x-%x-%x"'):format(attr.dev or 0, attr.ino or 0, attr.size or 0, attr.modification or 0); - response_headers.etag = etag; - - local if_none_match = request_headers.if_none_match - local if_modified_since = request_headers.if_modified_since; - if etag == if_none_match - or (not if_none_match and last_modified == if_modified_since) then - return 304; - end - - local data = cache:get(orig_path); - if data and data.etag == etag then - response_headers.content_type = data.content_type; - data = data.data; - elseif attr.mode == "directory" and path then - if full_path:sub(-1) ~= "/" then - local dir_path = { is_absolute = true, is_directory = true }; - for dir in orig_path:gmatch("[^/]+") do dir_path[#dir_path+1]=dir; end - response_headers.location = build_path(dir_path); - return 301; - end - for i=1,#dir_indices do - if stat(full_path..dir_indices[i], "mode") == "file" then - return serve_file(event, path..dir_indices[i]); - end - end - - if directory_index then - data = server._events.fire_event("directory-index", { path = request.path, full_path = full_path }); - end - if not data then - return 403; - end - cache:set(orig_path, { data = data, content_type = mime_map.html; etag = etag; }); - response_headers.content_type = mime_map.html; - - else - local f, err = open(full_path, "rb"); - if not f then - module:log("debug", "Could not open %s. Error was %s", full_path, err); - return 403; - end - local ext = full_path:match("%.([^./]+)$"); - local content_type = ext and mime_map[ext]; - response_headers.content_type = content_type; - if attr.size > cache_max_file_size then - response_headers.content_length = attr.size; - module:log("debug", "%d > cache_max_file_size", attr.size); - return response:send_file(f); - else - data = f:read("*a"); - f:close(); - end - cache:set(orig_path, { data = data; content_type = content_type; etag = etag }); - end - - return response:send(data); + if opts.directory_index == nil then + opts.directory_index = directory_index; end - - return serve_file; + if opts.mime_map == nil then + opts.mime_map = mime_map; + end + if opts.cache_size == nil then + opts.cache_size = cache_size; + end + if opts.cache_max_file_size == nil then + opts.cache_max_file_size = cache_max_file_size; + end + if opts.index_files == nil then + opts.index_files = dir_indices; + end + -- TODO Crank up to warning + module:log("debug", "Use of mod_http_files.serve() should be updated to use net.http.files"); + return fileserver.serve(opts); end function wrap_route(routes) + module:log("debug", "Use of mod_http_files.wrap_route() should be updated to use net.http.files"); for route,handler in pairs(routes) do if type(handler) ~= "function" then - routes[route] = serve(handler); + routes[route] = fileserver.serve(handler); end end return routes; end -if base_path then - module:provides("http", { - route = { - ["GET /*"] = serve { - path = base_path; - directory_index = directory_index; - } - }; - }); -else - module:log("debug", "http_files_dir not set, assuming use by some other module"); -end - +module:provides("http", { + route = { + ["GET /*"] = fileserver.serve({ + path = base_path; + directory_index = directory_index; + mime_map = mime_map; + cache_size = cache_size; + cache_max_file_size = cache_max_file_size; + index_files = dir_indices; + }); + }; +}); -- cgit v1.2.3 From 92bb509c8afff767492044972a29000e3e4cbfbd Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 5 Apr 2019 18:18:23 +0200 Subject: mod_http_files: Try to determine which module using serve() needs updating --- plugins/mod_http_files.lua | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/plugins/mod_http_files.lua b/plugins/mod_http_files.lua index acb99501..c357ddfc 100644 --- a/plugins/mod_http_files.lua +++ b/plugins/mod_http_files.lua @@ -46,6 +46,12 @@ if not mime_map then end end +local function get_calling_module() + local info = debug.getinfo(3, "S"); + if not info then return "An unknown module"; end + return info.source:match"mod_[^/\\.]+" or info.short_src; +end + -- COMPAT -- TODO deprecate function serve(opts) if type(opts) ~= "table" then -- assume path string @@ -67,12 +73,12 @@ function serve(opts) opts.index_files = dir_indices; end -- TODO Crank up to warning - module:log("debug", "Use of mod_http_files.serve() should be updated to use net.http.files"); + module:log("debug", "%s should be updated to use 'net.http.files' insead of mod_http_files", get_calling_module()); return fileserver.serve(opts); end function wrap_route(routes) - module:log("debug", "Use of mod_http_files.wrap_route() should be updated to use net.http.files"); + module:log("debug", "%s should be updated to use 'net.http.files' insead of mod_http_files", get_calling_module()); for route,handler in pairs(routes) do if type(handler) ~= "function" then routes[route] = fileserver.serve(handler); -- cgit v1.2.3 From b8f3a149da84f707c1d1c1237b7aa005d4d97d9b Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 13 Apr 2019 23:55:34 +0200 Subject: util.poll: Minimize scope of methods File scope is enough --- util-src/poll.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/util-src/poll.c b/util-src/poll.c index 0ca0cf28..1e7b6da3 100644 --- a/util-src/poll.c +++ b/util-src/poll.c @@ -59,7 +59,7 @@ typedef struct Lpoll_state { /* * Add an FD to be watched */ -int Ladd(lua_State *L) { +static int Ladd(lua_State *L) { struct Lpoll_state *state = luaL_checkudata(L, 1, STATE_MT); int fd = luaL_checkinteger(L, 2); @@ -137,7 +137,7 @@ int Ladd(lua_State *L) { /* * Set events to watch for, readable and/or writable */ -int Lset(lua_State *L) { +static int Lset(lua_State *L) { struct Lpoll_state *state = luaL_checkudata(L, 1, STATE_MT); int fd = luaL_checkinteger(L, 2); @@ -200,7 +200,7 @@ int Lset(lua_State *L) { /* * Remove FDs */ -int Ldel(lua_State *L) { +static int Ldel(lua_State *L) { struct Lpoll_state *state = luaL_checkudata(L, 1, STATE_MT); int fd = luaL_checkinteger(L, 2); @@ -247,7 +247,7 @@ int Ldel(lua_State *L) { /* * Check previously manipulated event state for FDs ready for reading or writing */ -int Lpushevent(lua_State *L, struct Lpoll_state *state) { +static int Lpushevent(lua_State *L, struct Lpoll_state *state) { #ifdef USE_EPOLL if(state->processed > 0) { @@ -281,7 +281,7 @@ int Lpushevent(lua_State *L, struct Lpoll_state *state) { /* * Wait for event */ -int Lwait(lua_State *L) { +static int Lwait(lua_State *L) { struct Lpoll_state *state = luaL_checkudata(L, 1, STATE_MT); int ret = Lpushevent(L, state); @@ -344,7 +344,7 @@ int Lwait(lua_State *L) { /* * Return Epoll FD */ -int Lgetfd(lua_State *L) { +static int Lgetfd(lua_State *L) { struct Lpoll_state *state = luaL_checkudata(L, 1, STATE_MT); lua_pushinteger(L, state->epoll_fd); return 1; @@ -353,7 +353,7 @@ int Lgetfd(lua_State *L) { /* * Close epoll FD */ -int Lgc(lua_State *L) { +static int Lgc(lua_State *L) { struct Lpoll_state *state = luaL_checkudata(L, 1, STATE_MT); if(state->epoll_fd == -1) { @@ -375,7 +375,7 @@ int Lgc(lua_State *L) { /* * String representation */ -int Ltos(lua_State *L) { +static int Ltos(lua_State *L) { struct Lpoll_state *state = luaL_checkudata(L, 1, STATE_MT); lua_pushfstring(L, "%s: %p", STATE_MT, state); return 1; @@ -384,7 +384,7 @@ int Ltos(lua_State *L) { /* * Create a new context */ -int Lnew(lua_State *L) { +static int Lnew(lua_State *L) { /* Allocate state */ Lpoll_state *state = lua_newuserdata(L, sizeof(Lpoll_state)); luaL_setmetatable(L, STATE_MT); -- cgit v1.2.3 From e70c9f1828ba93e493e940672c16fee0370bd02a Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 19 Apr 2019 12:41:38 +0200 Subject: util.hashes: Remove redundant semicolon --- util-src/hashes.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util-src/hashes.c b/util-src/hashes.c index 82f5876e..5cf8b5e7 100644 --- a/util-src/hashes.c +++ b/util-src/hashes.c @@ -215,7 +215,7 @@ LUALIB_API int luaopen_util_hashes(lua_State *L) { luaL_checkversion(L); #endif lua_newtable(L); - luaL_setfuncs(L, Reg, 0);; + luaL_setfuncs(L, Reg, 0); lua_pushliteral(L, "-3.14"); lua_setfield(L, -2, "version"); return 1; -- cgit v1.2.3 From 2ab687396c5b2bd4b461adf0e7edc7be296c8218 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 19 Apr 2019 12:46:24 +0200 Subject: util.hmac: Reflow code --- util/hmac.lua | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/util/hmac.lua b/util/hmac.lua index 2c4cc6ef..f3caae33 100644 --- a/util/hmac.lua +++ b/util/hmac.lua @@ -10,6 +10,8 @@ local hashes = require "util.hashes" -return { md5 = hashes.hmac_md5, - sha1 = hashes.hmac_sha1, - sha256 = hashes.hmac_sha256 }; +return { + md5 = hashes.hmac_md5, + sha1 = hashes.hmac_sha1, + sha256 = hashes.hmac_sha256, +}; -- cgit v1.2.3 From df0fbd05149ac66b910e644a136667315218d57d Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 19 Apr 2019 12:47:49 +0200 Subject: util.hmac: Expose hmac-sha-512 too All these are provided by util.hashes so why not? --- util/hmac.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/util/hmac.lua b/util/hmac.lua index f3caae33..4cad17cc 100644 --- a/util/hmac.lua +++ b/util/hmac.lua @@ -14,4 +14,5 @@ return { md5 = hashes.hmac_md5, sha1 = hashes.hmac_sha1, sha256 = hashes.hmac_sha256, + sha512 = hashes.hmac_sha512, }; -- cgit v1.2.3 From d00874434414a06801a3b8d0be756176d1eb778b Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 11 Apr 2019 00:41:48 +0200 Subject: util.hmac: Generate test cases from RFC 4231 --- spec/util_hmac_spec.lua | 103 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 spec/util_hmac_spec.lua diff --git a/spec/util_hmac_spec.lua b/spec/util_hmac_spec.lua new file mode 100644 index 00000000..abcc92f1 --- /dev/null +++ b/spec/util_hmac_spec.lua @@ -0,0 +1,103 @@ +-- Test cases from RFC 4231 + +local hmac = require "util.hmac"; +local hex = require "util.hex"; + +describe("Test case 1", function () + local Key = hex.from("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"); + local Data = hex.from("4869205468657265"); + describe("HMAC-SHA-256", function () + it("works", function() + assert.equal("b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7", hmac.sha256(Key, Data, true)) + end); + end); + describe("HMAC-SHA-512", function () + it("works", function() + assert.equal("87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cdedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854", hmac.sha512(Key, Data, true)) + end); + end); +end); +describe("Test case 2", function () + local Key = hex.from("4a656665"); + local Data = hex.from("7768617420646f2079612077616e7420666f72206e6f7468696e673f"); + describe("HMAC-SHA-256", function () + it("works", function() + assert.equal("5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843", hmac.sha256(Key, Data, true)) + end); + end); + describe("HMAC-SHA-512", function () + it("works", function() + assert.equal("164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7ea2505549758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b636e070a38bce737", hmac.sha512(Key, Data, true)) + end); + end); +end); +describe("Test case 3", function () + local Key = hex.from("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + local Data = hex.from("dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"); + describe("HMAC-SHA-256", function () + it("works", function() + assert.equal("773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe", hmac.sha256(Key, Data, true)) + end); + end); + describe("HMAC-SHA-512", function () + it("works", function() + assert.equal("fa73b0089d56a284efb0f0756c890be9b1b5dbdd8ee81a3655f83e33b2279d39bf3e848279a722c806b485a47e67c807b946a337bee8942674278859e13292fb", hmac.sha512(Key, Data, true)) + end); + end); +end); +describe("Test case 4", function () + local Key = hex.from("0102030405060708090a0b0c0d0e0f10111213141516171819"); + local Data = hex.from("cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"); + describe("HMAC-SHA-256", function () + it("works", function() + assert.equal("82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b", hmac.sha256(Key, Data, true)) + end); + end); + describe("HMAC-SHA-512", function () + it("works", function() + assert.equal("b0ba465637458c6990e5a8c5f61d4af7e576d97ff94b872de76f8050361ee3dba91ca5c11aa25eb4d679275cc5788063a5f19741120c4f2de2adebeb10a298dd", hmac.sha512(Key, Data, true)) + end); + end); +end); +describe("Test case 5", function () + local Key = hex.from("0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c"); + local Data = hex.from("546573742057697468205472756e636174696f6e"); + describe("HMAC-SHA-256", function () + it("works", function() + assert.equal("a3b6167473100ee06e0c796c2955552b", hmac.sha256(Key, Data, true):sub(1,128/4)) + end); + end); + describe("HMAC-SHA-512", function () + it("works", function() + assert.equal("415fad6271580a531d4179bc891d87a6", hmac.sha512(Key, Data, true):sub(1,128/4)) + end); + end); +end); +describe("Test case 6", function () + local Key = hex.from("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + local Data = hex.from("54657374205573696e67204c6172676572205468616e20426c6f636b2d53697a65204b6579202d2048617368204b6579204669727374"); + describe("HMAC-SHA-256", function () + it("works", function() + assert.equal("60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54", hmac.sha256(Key, Data, true)) + end); + end); + describe("HMAC-SHA-512", function () + it("works", function() + assert.equal("80b24263c7c1a3ebb71493c1dd7be8b49b46d1f41b4aeec1121b013783f8f3526b56d037e05f2598bd0fd2215d6a1e5295e64f73f63f0aec8b915a985d786598", hmac.sha512(Key, Data, true)) + end); + end); +end); +describe("Test case 7", function () + local Key = hex.from("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + local Data = hex.from("5468697320697320612074657374207573696e672061206c6172676572207468616e20626c6f636b2d73697a65206b657920616e642061206c6172676572207468616e20626c6f636b2d73697a6520646174612e20546865206b6579206e6565647320746f20626520686173686564206265666f7265206265696e6720757365642062792074686520484d414320616c676f726974686d2e"); + describe("HMAC-SHA-256", function () + it("works", function() + assert.equal("9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f51535c3a35e2", hmac.sha256(Key, Data, true)) + end); + end); + describe("HMAC-SHA-512", function () + it("works", function() + assert.equal("e37b6a775dc87dbaa4dfa9f96e5e3ffddebd71f8867289865df5a32d20cdc944b6022cac3c4982b10d5eeb55c3e4de15134676fb6de0446065c97440fa8c6a58", hmac.sha512(Key, Data, true)) + end); + end); +end); -- cgit v1.2.3 From 53f4351d9a70341bfa9083be3552b7f0d1f7ffbe Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 19 Apr 2019 13:17:49 +0200 Subject: util.hmac: Ignore long hex lines in tests --- spec/util_hmac_spec.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/spec/util_hmac_spec.lua b/spec/util_hmac_spec.lua index abcc92f1..a2125c3a 100644 --- a/spec/util_hmac_spec.lua +++ b/spec/util_hmac_spec.lua @@ -1,5 +1,8 @@ -- Test cases from RFC 4231 +-- Yes, the lines are long, it's annoying to split the long hex things. +-- luacheck: ignore 631 + local hmac = require "util.hmac"; local hex = require "util.hex"; -- cgit v1.2.3 From 43bb3d5756a77f7d67a470ca1c69c979a2dba612 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 19 Apr 2019 14:12:28 +0200 Subject: util.hashes: Add test vectors from RFC 6070 for PBKDF2 (aka SCRAM Hi()) Number 4 is disabled by default beacuse of how long time it takes --- .busted | 2 +- spec/util_hashes_spec.lua | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 spec/util_hashes_spec.lua diff --git a/.busted b/.busted index f5f9472e..0206c093 100644 --- a/.busted +++ b/.busted @@ -2,7 +2,7 @@ return { _all = { }, default = { - ["exclude-tags"] = "mod_bosh,storage"; + ["exclude-tags"] = "mod_bosh,storage,SLOW"; }; bosh = { tags = "mod_bosh"; diff --git a/spec/util_hashes_spec.lua b/spec/util_hashes_spec.lua new file mode 100644 index 00000000..1e6187bb --- /dev/null +++ b/spec/util_hashes_spec.lua @@ -0,0 +1,37 @@ +-- Test vectors from RFC 6070 +local hashes = require "util.hashes"; +local hex = require "util.hex"; + +-- Also see spec for util.hmac where HMAC test cases reside + +describe("PBKDF2-SHA1", function () + it("test vector 1", function () + local P = "password" + local S = "salt" + local c = 1 + local DK = "0c60c80f961f0e71f3a9b524af6012062fe037a6"; + assert.equal(DK, hex.to(hashes.scram_Hi_sha1(P, S, c))); + end); + it("test vector 2", function () + local P = "password" + local S = "salt" + local c = 2 + local DK = "ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957"; + assert.equal(DK, hex.to(hashes.scram_Hi_sha1(P, S, c))); + end); + it("test vector 3", function () + local P = "password" + local S = "salt" + local c = 4096 + local DK = "4b007901b765489abead49d926f721d065a429c1"; + assert.equal(DK, hex.to(hashes.scram_Hi_sha1(P, S, c))); + end); + it("test vector 4 #SLOW", function () + local P = "password" + local S = "salt" + local c = 16777216 + local DK = "eefe3d61cd4da4e4e9945b3d6ba2158c2634e984"; + assert.equal(DK, hex.to(hashes.scram_Hi_sha1(P, S, c))); + end); +end); + -- cgit v1.2.3 From 5bc034dcf55c76081728e75c3b53457d5dcaa20d Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 13 Jan 2019 13:57:14 +0100 Subject: util.hashes: Use PBKDF2 from libcrypto --- util-src/hashes.c | 53 +++++++++-------------------------------------------- 1 file changed, 9 insertions(+), 44 deletions(-) diff --git a/util-src/hashes.c b/util-src/hashes.c index 8de4ef5b..ac6cac7e 100644 --- a/util-src/hashes.c +++ b/util-src/hashes.c @@ -26,6 +26,7 @@ typedef unsigned __int32 uint32_t; #include #include #include +#include #if (LUA_VERSION_NUM == 501) #define luaL_setfuncs(L, R, N) luaL_register(L, NULL, R) @@ -137,54 +138,18 @@ MAKE_HMAC_FUNCTION(Lhmac_sha512, EVP_sha512, SHA512_DIGEST_LENGTH, SHA512_CTX) MAKE_HMAC_FUNCTION(Lhmac_md5, EVP_md5, MD5_DIGEST_LENGTH, MD5_CTX) static int LscramHi(lua_State *L) { - union xory { - unsigned char bytes[SHA_DIGEST_LENGTH]; - uint32_t quadbytes[SHA_DIGEST_LENGTH / 4]; - }; - int i; - SHA_CTX ctx, ctxo; - unsigned char Ust[SHA_DIGEST_LENGTH]; - union xory Und; - union xory res; - size_t str_len, salt_len; - struct hash_desc desc; - const char *str = luaL_checklstring(L, 1, &str_len); - const char *salt = luaL_checklstring(L, 2, &salt_len); - char *salt2; - const int iter = luaL_checkinteger(L, 3); - - desc.Init = (int (*)(void *))SHA1_Init; - desc.Update = (int (*)(void *, const void *, size_t))SHA1_Update; - desc.Final = (int (*)(unsigned char *, void *))SHA1_Final; - desc.digestLength = SHA_DIGEST_LENGTH; - desc.ctx = &ctx; - desc.ctxo = &ctxo; - - salt2 = malloc(salt_len + 4); + unsigned char out[SHA_DIGEST_LENGTH]; - if(salt2 == NULL) { - return luaL_error(L, "Out of memory in scramHi"); - } - - memcpy(salt2, salt, salt_len); - memcpy(salt2 + salt_len, "\0\0\0\1", 4); - hmac(&desc, str, str_len, salt2, salt_len + 4, Ust); - free(salt2); - - memcpy(res.bytes, Ust, sizeof(res)); - - for(i = 1; i < iter; i++) { - int j; - hmac(&desc, str, str_len, (char *)Ust, sizeof(Ust), Und.bytes); - - for(j = 0; j < SHA_DIGEST_LENGTH / 4; j++) { - res.quadbytes[j] ^= Und.quadbytes[j]; - } + size_t pass_len, salt_len; + const char *pass = luaL_checklstring(L, 1, &pass_len); + const unsigned char *salt = (unsigned char *)luaL_checklstring(L, 2, &salt_len); + const int iter = luaL_checkinteger(L, 3); - memcpy(Ust, Und.bytes, sizeof(Ust)); + if(PKCS5_PBKDF2_HMAC(pass, pass_len, salt, salt_len, iter, EVP_sha1(), SHA_DIGEST_LENGTH, out) == 0) { + return luaL_error(L, "PKCS5_PBKDF2_HMAC() failed"); } - lua_pushlstring(L, (char *)res.bytes, SHA_DIGEST_LENGTH); + lua_pushlstring(L, (char *)out, SHA_DIGEST_LENGTH); return 1; } -- cgit v1.2.3 From 86219e253244ae0aab1b27195410affee3c22ab2 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 19 Apr 2019 13:24:32 +0200 Subject: util.hashes: Remove now unused hmac() function --- util-src/hashes.c | 38 -------------------------------------- 1 file changed, 38 deletions(-) diff --git a/util-src/hashes.c b/util-src/hashes.c index ac6cac7e..75eb116d 100644 --- a/util-src/hashes.c +++ b/util-src/hashes.c @@ -76,44 +76,6 @@ struct hash_desc { void *ctx, *ctxo; }; -static void hmac(struct hash_desc *desc, const char *key, size_t key_len, - const char *msg, size_t msg_len, unsigned char *result) { - union xory { - unsigned char bytes[64]; - uint32_t quadbytes[16]; - }; - - int i; - unsigned char hashedKey[64]; /* Maximum used digest length */ - union xory k_ipad, k_opad; - - if(key_len > 64) { - desc->Init(desc->ctx); - desc->Update(desc->ctx, key, key_len); - desc->Final(hashedKey, desc->ctx); - key = (const char *)hashedKey; - key_len = desc->digestLength; - } - - memcpy(k_ipad.bytes, key, key_len); - memset(k_ipad.bytes + key_len, 0, 64 - key_len); - memcpy(k_opad.bytes, k_ipad.bytes, 64); - - for(i = 0; i < 16; i++) { - k_ipad.quadbytes[i] ^= HMAC_IPAD; - k_opad.quadbytes[i] ^= HMAC_OPAD; - } - - desc->Init(desc->ctx); - desc->Update(desc->ctx, k_ipad.bytes, 64); - desc->Init(desc->ctxo); - desc->Update(desc->ctxo, k_opad.bytes, 64); - desc->Update(desc->ctx, msg, msg_len); - desc->Final(result, desc->ctx); - desc->Update(desc->ctxo, result, desc->digestLength); - desc->Final(result, desc->ctxo); -} - #define MAKE_HMAC_FUNCTION(myFunc, evp, size, type) \ static int myFunc(lua_State *L) { \ unsigned char hash[size], result[2*size]; \ -- cgit v1.2.3 From 5a6c7ae67300e26d483c9956f36b3a29d47e6502 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 13 Jan 2019 13:57:18 +0100 Subject: util.hashes: Rename PBKDF2 function It's not SCRAM-specific --- util-src/hashes.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/util-src/hashes.c b/util-src/hashes.c index 75eb116d..7e790e9f 100644 --- a/util-src/hashes.c +++ b/util-src/hashes.c @@ -99,7 +99,7 @@ MAKE_HMAC_FUNCTION(Lhmac_sha256, EVP_sha256, SHA256_DIGEST_LENGTH, SHA256_CTX) MAKE_HMAC_FUNCTION(Lhmac_sha512, EVP_sha512, SHA512_DIGEST_LENGTH, SHA512_CTX) MAKE_HMAC_FUNCTION(Lhmac_md5, EVP_md5, MD5_DIGEST_LENGTH, MD5_CTX) -static int LscramHi(lua_State *L) { +static int Lpbkdf2_sha1(lua_State *L) { unsigned char out[SHA_DIGEST_LENGTH]; size_t pass_len, salt_len; @@ -127,7 +127,8 @@ static const luaL_Reg Reg[] = { { "hmac_sha256", Lhmac_sha256 }, { "hmac_sha512", Lhmac_sha512 }, { "hmac_md5", Lhmac_md5 }, - { "scram_Hi_sha1", LscramHi }, + { "scram_Hi_sha1", Lpbkdf2_sha1 }, /* COMPAT */ + { "pbkdf2_hmac_sha1", Lpbkdf2_sha1 }, { NULL, NULL } }; -- cgit v1.2.3 From ffc422e2f6d8806dee551491feaac71f54adf0d7 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 13 Jan 2019 13:59:26 +0100 Subject: util.hashes: Add PBKDF2-HMAC-SHA256 --- util-src/hashes.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/util-src/hashes.c b/util-src/hashes.c index 7e790e9f..4c48b26f 100644 --- a/util-src/hashes.c +++ b/util-src/hashes.c @@ -116,6 +116,23 @@ static int Lpbkdf2_sha1(lua_State *L) { return 1; } + +static int Lpbkdf2_sha256(lua_State *L) { + unsigned char out[SHA256_DIGEST_LENGTH]; + + size_t pass_len, salt_len; + const char *pass = luaL_checklstring(L, 1, &pass_len); + const unsigned char *salt = (unsigned char *)luaL_checklstring(L, 2, &salt_len); + const int iter = luaL_checkinteger(L, 3); + + if(PKCS5_PBKDF2_HMAC(pass, pass_len, salt, salt_len, iter, EVP_sha256(), SHA256_DIGEST_LENGTH, out) == 0) { + return luaL_error(L, "PKCS5_PBKDF2_HMAC() failed"); + } + + lua_pushlstring(L, (char *)out, SHA_DIGEST_LENGTH); + return 1; +} + static const luaL_Reg Reg[] = { { "sha1", Lsha1 }, { "sha224", Lsha224 }, @@ -129,6 +146,7 @@ static const luaL_Reg Reg[] = { { "hmac_md5", Lhmac_md5 }, { "scram_Hi_sha1", Lpbkdf2_sha1 }, /* COMPAT */ { "pbkdf2_hmac_sha1", Lpbkdf2_sha1 }, + { "pbkdf2_hmac_sha256", Lpbkdf2_sha256 }, { NULL, NULL } }; -- cgit v1.2.3 From 165ee3a5ef1247468e98d0d4cba6fc43e15f92d7 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 20 Apr 2019 15:11:04 +0200 Subject: util.hashes: Allow specifying output key length This is not needed for SCRAM but PBKDF2 takes this argument. --- spec/util_hashes_spec.lua | 16 ++++++++++++++++ util-src/hashes.c | 21 ++++++++++++--------- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/spec/util_hashes_spec.lua b/spec/util_hashes_spec.lua index 1e6187bb..9099145a 100644 --- a/spec/util_hashes_spec.lua +++ b/spec/util_hashes_spec.lua @@ -33,5 +33,21 @@ describe("PBKDF2-SHA1", function () local DK = "eefe3d61cd4da4e4e9945b3d6ba2158c2634e984"; assert.equal(DK, hex.to(hashes.scram_Hi_sha1(P, S, c))); end); + it("test vector 5", function () + local P = "passwordPASSWORDpassword" + local S = "saltSALTsaltSALTsaltSALTsaltSALTsalt" + local c = 4096 + local dkLen = 25 + local DK = "3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038" + assert.equal(DK, hex.to(hashes.scram_Hi_sha1(P, S, c, dkLen))); + end); + it("works", function () + local P = "pass\0word" + local S = "sa\0lt" + local c = 4096 + local dkLen = 16 + local DK = "56fa6aa75548099dcc37d7f03425e0c3" + assert.equal(DK, hex.to(hashes.scram_Hi_sha1(P, S, c, dkLen))); + end); end); diff --git a/util-src/hashes.c b/util-src/hashes.c index 4c48b26f..3fb849b9 100644 --- a/util-src/hashes.c +++ b/util-src/hashes.c @@ -100,36 +100,39 @@ MAKE_HMAC_FUNCTION(Lhmac_sha512, EVP_sha512, SHA512_DIGEST_LENGTH, SHA512_CTX) MAKE_HMAC_FUNCTION(Lhmac_md5, EVP_md5, MD5_DIGEST_LENGTH, MD5_CTX) static int Lpbkdf2_sha1(lua_State *L) { - unsigned char out[SHA_DIGEST_LENGTH]; - size_t pass_len, salt_len; const char *pass = luaL_checklstring(L, 1, &pass_len); const unsigned char *salt = (unsigned char *)luaL_checklstring(L, 2, &salt_len); const int iter = luaL_checkinteger(L, 3); + const size_t len = luaL_optinteger(L, 4, SHA_DIGEST_LENGTH); + + luaL_Buffer b; + unsigned char *out = (unsigned char *)luaL_buffinitsize(L, &b, len); - if(PKCS5_PBKDF2_HMAC(pass, pass_len, salt, salt_len, iter, EVP_sha1(), SHA_DIGEST_LENGTH, out) == 0) { + if(PKCS5_PBKDF2_HMAC(pass, pass_len, salt, salt_len, iter, EVP_sha1(), len, out) == 0) { return luaL_error(L, "PKCS5_PBKDF2_HMAC() failed"); } - lua_pushlstring(L, (char *)out, SHA_DIGEST_LENGTH); - + luaL_pushresultsize(&b, len); return 1; } static int Lpbkdf2_sha256(lua_State *L) { - unsigned char out[SHA256_DIGEST_LENGTH]; - size_t pass_len, salt_len; const char *pass = luaL_checklstring(L, 1, &pass_len); const unsigned char *salt = (unsigned char *)luaL_checklstring(L, 2, &salt_len); const int iter = luaL_checkinteger(L, 3); + const int len = luaL_optinteger(L, 4, SHA256_DIGEST_LENGTH); + + luaL_Buffer b; + unsigned char *out = (unsigned char *)luaL_buffinitsize(L, &b, len); - if(PKCS5_PBKDF2_HMAC(pass, pass_len, salt, salt_len, iter, EVP_sha256(), SHA256_DIGEST_LENGTH, out) == 0) { + if(PKCS5_PBKDF2_HMAC(pass, pass_len, salt, salt_len, iter, EVP_sha256(), len, out) == 0) { return luaL_error(L, "PKCS5_PBKDF2_HMAC() failed"); } - lua_pushlstring(L, (char *)out, SHA_DIGEST_LENGTH); + luaL_pushresultsize(&b, len); return 1; } -- cgit v1.2.3 From 90d4d6bda8161d7fdc730f822db13a1a8639aee4 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 21 Apr 2019 00:59:36 +0200 Subject: Backed out changeset 61bc5c52c941 luaL_buffinitsize is only available in Lua 5.2+ --- spec/util_hashes_spec.lua | 16 ---------------- util-src/hashes.c | 21 +++++++++------------ 2 files changed, 9 insertions(+), 28 deletions(-) diff --git a/spec/util_hashes_spec.lua b/spec/util_hashes_spec.lua index 9099145a..1e6187bb 100644 --- a/spec/util_hashes_spec.lua +++ b/spec/util_hashes_spec.lua @@ -33,21 +33,5 @@ describe("PBKDF2-SHA1", function () local DK = "eefe3d61cd4da4e4e9945b3d6ba2158c2634e984"; assert.equal(DK, hex.to(hashes.scram_Hi_sha1(P, S, c))); end); - it("test vector 5", function () - local P = "passwordPASSWORDpassword" - local S = "saltSALTsaltSALTsaltSALTsaltSALTsalt" - local c = 4096 - local dkLen = 25 - local DK = "3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038" - assert.equal(DK, hex.to(hashes.scram_Hi_sha1(P, S, c, dkLen))); - end); - it("works", function () - local P = "pass\0word" - local S = "sa\0lt" - local c = 4096 - local dkLen = 16 - local DK = "56fa6aa75548099dcc37d7f03425e0c3" - assert.equal(DK, hex.to(hashes.scram_Hi_sha1(P, S, c, dkLen))); - end); end); diff --git a/util-src/hashes.c b/util-src/hashes.c index 3fb849b9..4c48b26f 100644 --- a/util-src/hashes.c +++ b/util-src/hashes.c @@ -100,39 +100,36 @@ MAKE_HMAC_FUNCTION(Lhmac_sha512, EVP_sha512, SHA512_DIGEST_LENGTH, SHA512_CTX) MAKE_HMAC_FUNCTION(Lhmac_md5, EVP_md5, MD5_DIGEST_LENGTH, MD5_CTX) static int Lpbkdf2_sha1(lua_State *L) { + unsigned char out[SHA_DIGEST_LENGTH]; + size_t pass_len, salt_len; const char *pass = luaL_checklstring(L, 1, &pass_len); const unsigned char *salt = (unsigned char *)luaL_checklstring(L, 2, &salt_len); const int iter = luaL_checkinteger(L, 3); - const size_t len = luaL_optinteger(L, 4, SHA_DIGEST_LENGTH); - - luaL_Buffer b; - unsigned char *out = (unsigned char *)luaL_buffinitsize(L, &b, len); - if(PKCS5_PBKDF2_HMAC(pass, pass_len, salt, salt_len, iter, EVP_sha1(), len, out) == 0) { + if(PKCS5_PBKDF2_HMAC(pass, pass_len, salt, salt_len, iter, EVP_sha1(), SHA_DIGEST_LENGTH, out) == 0) { return luaL_error(L, "PKCS5_PBKDF2_HMAC() failed"); } - luaL_pushresultsize(&b, len); + lua_pushlstring(L, (char *)out, SHA_DIGEST_LENGTH); + return 1; } static int Lpbkdf2_sha256(lua_State *L) { + unsigned char out[SHA256_DIGEST_LENGTH]; + size_t pass_len, salt_len; const char *pass = luaL_checklstring(L, 1, &pass_len); const unsigned char *salt = (unsigned char *)luaL_checklstring(L, 2, &salt_len); const int iter = luaL_checkinteger(L, 3); - const int len = luaL_optinteger(L, 4, SHA256_DIGEST_LENGTH); - - luaL_Buffer b; - unsigned char *out = (unsigned char *)luaL_buffinitsize(L, &b, len); - if(PKCS5_PBKDF2_HMAC(pass, pass_len, salt, salt_len, iter, EVP_sha256(), len, out) == 0) { + if(PKCS5_PBKDF2_HMAC(pass, pass_len, salt, salt_len, iter, EVP_sha256(), SHA256_DIGEST_LENGTH, out) == 0) { return luaL_error(L, "PKCS5_PBKDF2_HMAC() failed"); } - luaL_pushresultsize(&b, len); + lua_pushlstring(L, (char *)out, SHA_DIGEST_LENGTH); return 1; } -- cgit v1.2.3 From 196ac28ab5b002063f2b25dc60bd809c707b8ab2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Wed, 17 Apr 2019 10:11:22 -0700 Subject: mod_admin_telnet: Adds c2s:closeall() (Fixes #1315) --- plugins/mod_admin_telnet.lua | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 34cc4dc6..c66630ca 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -250,6 +250,7 @@ function commands.help(session, data) print [[c2s:show_secure() - Show all encrypted client connections]] print [[c2s:show_tls() - Show TLS cipher info for encrypted sessions]] print [[c2s:close(jid) - Close all sessions for the specified JID]] + print [[c2s:closeall() - Close all active c2s connections ]] elseif section == "s2s" then print [[s2s:show(domain) - Show all s2s connections for the given domain (or all if no domain given)]] print [[s2s:show_tls(domain) - Show TLS cipher info for encrypted sessions]] @@ -661,6 +662,16 @@ function def_env.c2s:close(match_jid) return true, "Total: "..count.." sessions closed"; end +function def_env.c2s:closeall() + local count = 0; + --luacheck: ignore 212/jid + show_c2s(function (jid, session) + count = count + 1; + session:close(); + end); + return true, "Total: "..count.." sessions closed"; +end + def_env.s2s = {}; function def_env.s2s:show(match_jid, annotate) -- cgit v1.2.3 From 43aed81cb51776952ce8c6d211369bc49a05aa67 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 24 Apr 2019 22:40:38 +0200 Subject: util.encodings: Add binding to confusables skeleton function in ICU --- util-src/encodings.c | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/util-src/encodings.c b/util-src/encodings.c index e55a3f44..0d723913 100644 --- a/util-src/encodings.c +++ b/util-src/encodings.c @@ -268,6 +268,7 @@ static const luaL_Reg Reg_utf8[] = { #include #include #include +#include static int icu_stringprep_prep(lua_State *L, const UStringPrepProfile *profile) { size_t input_len; @@ -321,6 +322,7 @@ UStringPrepProfile *icu_nameprep; UStringPrepProfile *icu_nodeprep; UStringPrepProfile *icu_resourceprep; UStringPrepProfile *icu_saslprep; +USpoofChecker *icu_spoofcheck; /* initialize global ICU stringprep profiles */ void init_icu() { @@ -330,6 +332,8 @@ void init_icu() { icu_nodeprep = usprep_openByType(USPREP_RFC3920_NODEPREP, &err); icu_resourceprep = usprep_openByType(USPREP_RFC3920_RESOURCEPREP, &err); icu_saslprep = usprep_openByType(USPREP_RFC4013_SASLPREP, &err); + icu_spoofcheck = uspoof_open(&err); + uspoof_setChecks(icu_spoofcheck, USPOOF_CONFUSABLE, &err); if(U_FAILURE(err)) { fprintf(stderr, "[c] util.encodings: error: %s\n", u_errorName((UErrorCode)err)); @@ -477,6 +481,40 @@ static int Lidna_to_unicode(lua_State *L) { /** idna.to_unicode(s) */ } } +static int Lskeleton(lua_State *L) { + size_t len; + int32_t ulen, dest_len, output_len; + const char *s = luaL_checklstring(L, 1, &len); + UErrorCode err = U_ZERO_ERROR; + UChar ustr[1024]; + UChar dest[1024]; + char output[1024]; + + u_strFromUTF8(ustr, 1024, &ulen, s, len, &err); + + if(U_FAILURE(err)) { + lua_pushnil(L); + return 1; + } + + dest_len = uspoof_getSkeleton(icu_spoofcheck, 0, ustr, ulen, dest, 1024, &err); + + if(U_FAILURE(err)) { + lua_pushnil(L); + return 1; + } + + u_strToUTF8(output, 1024, &output_len, dest, dest_len, &err); + + if(U_SUCCESS(err)) { + lua_pushlstring(L, output, output_len); + return 1; + } + + lua_pushnil(L); + return 1; +} + #else /* USE_STRINGPREP_ICU */ /****************** libidn ********************/ @@ -558,6 +596,13 @@ LUALIB_API int luaopen_util_encodings(lua_State *L) { luaL_setfuncs(L, Reg_utf8, 0); lua_setfield(L, -2, "utf8"); +#ifdef USE_STRINGPREP_ICU + lua_newtable(L); + lua_pushcfunction(L, Lskeleton); + lua_setfield(L, -2, "skeleton"); + lua_setfield(L, -2, "confusable"); +#endif + lua_pushliteral(L, "-3.14"); lua_setfield(L, -2, "version"); return 1; -- cgit v1.2.3 From 8276936dc113921af1db030b7ab707242de07a6b Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 29 Apr 2019 03:05:25 +0200 Subject: CHANGES: New in trunk so far --- CHANGES | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGES b/CHANGES index 136b7d2b..f5771512 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,14 @@ +TRUNK +===== + +- Module statuses +- SNI support (not completely finished) +- Improved message expiry in MAM +- CORS handling now provided by mod\_http +- CSI improvements +- mod\_limits: Exempted JIDs +- Archive quotas + 0.11.0 ====== -- cgit v1.2.3 From 56728cf598b1f64862604d00a964fb47d96bc91c Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 29 Apr 2019 03:07:35 +0200 Subject: CHANGES: Remove MAM change that got rebased to 0.11 --- CHANGES | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGES b/CHANGES index f5771512..e2e90f4a 100644 --- a/CHANGES +++ b/CHANGES @@ -3,7 +3,6 @@ TRUNK - Module statuses - SNI support (not completely finished) -- Improved message expiry in MAM - CORS handling now provided by mod\_http - CSI improvements - mod\_limits: Exempted JIDs -- cgit v1.2.3 From 183b42baa0da20b06ff3429bcc75a8ce01676a1b Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 29 Apr 2019 15:53:52 +0200 Subject: util.encodings: Add compat with ICU before version 58 --- util-src/encodings.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/util-src/encodings.c b/util-src/encodings.c index 0d723913..3b7f322d 100644 --- a/util-src/encodings.c +++ b/util-src/encodings.c @@ -324,6 +324,11 @@ UStringPrepProfile *icu_resourceprep; UStringPrepProfile *icu_saslprep; USpoofChecker *icu_spoofcheck; +#if (U_ICU_VERSION_MAJOR_NUM < 58) +/* COMPAT */ +#define USPOOF_CONFUSABLE (USPOOF_SINGLE_SCRIPT_CONFUSABLE | USPOOF_MIXED_SCRIPT_CONFUSABLE | USPOOF_WHOLE_SCRIPT_CONFUSABLE) +#endif + /* initialize global ICU stringprep profiles */ void init_icu() { UErrorCode err = U_ZERO_ERROR; -- cgit v1.2.3 From e40cf146149c412e17d789d1b0a0132117494747 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 6 Aug 2012 15:35:27 +0200 Subject: mod_mimicking: Prevents registration of confusable usernames (by Florob) (fixes #1347) --- CHANGES | 1 + plugins/mod_mimicking.lua | 49 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 plugins/mod_mimicking.lua diff --git a/CHANGES b/CHANGES index e2e90f4a..8c65f4ec 100644 --- a/CHANGES +++ b/CHANGES @@ -7,6 +7,7 @@ TRUNK - CSI improvements - mod\_limits: Exempted JIDs - Archive quotas +- mod\_mimicking 0.11.0 ====== diff --git a/plugins/mod_mimicking.lua b/plugins/mod_mimicking.lua new file mode 100644 index 00000000..9004957a --- /dev/null +++ b/plugins/mod_mimicking.lua @@ -0,0 +1,49 @@ +-- Prosody IM +-- Copyright (C) 2012 Florian Zeitz +-- +-- This project is MIT/X11 licensed. Please see the +-- COPYING file in the source package for more information. +-- + +local skeleton = require "util.confusable".skeleton; +local datamanager = require "util.datamanager"; +local usage = require "util.prosodyctl".show_usage; +local warn = require "util.prosodyctl".show_warning; +local users = require "usermanager".users; + +module:hook("user-registered", function(user) + datamanager.store(skeleton(user.username), user.host, "skeletons", {username = user.username}); +end); + +module:hook("user-deregistered", function(user) + datamanager.store(skeleton(user.username), user.host, "skeletons", nil); +end); + +module:hook("registration-attempt", function(user) + if datamanager.load(skeleton(user.username), user.host, "skeletons") then + user.allowed = false; + end +end); + +function module.command(arg) + if (arg[1] ~= "bootstrap" or not arg[2]) then + usage("mod_mimicking bootstrap ", "Initialize skeleton database"); + return; + end + + local host = arg[2]; + + local host_session = prosody.hosts[host]; + if not host_session then + return "No such host"; + end + local provider = host_session.users; + if not(provider) or provider.name == "null" then + usermanager.initialize_host(host); + end + storagemanager.initialize_host(host); + + for user in users(host) do + datamanager.store(skeleton(user), host, "skeletons", {username = user}); + end +end -- cgit v1.2.3 From ad90995e3f3cee91596566be5d99c625cb3a62a4 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 6 Jul 2019 17:47:06 +0200 Subject: mod_pubsub: Expose pubsub#access_model and pubsub#publish_model (fixes #1387) --- plugins/mod_pubsub/pubsub.lib.lua | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/plugins/mod_pubsub/pubsub.lib.lua b/plugins/mod_pubsub/pubsub.lib.lua index 50ef7ddf..83c81bb7 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 -- cgit v1.2.3 From 236825ba8ce7cb811457a52ab9e18e31c2231a93 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 7 Jul 2019 19:15:35 +0200 Subject: MUC: Advertise language field as such via XEP-0122 This lets clients know that the field is a language field and should be in RFC 5646 format. Field validation code in util.dataforms left for future commit. --- plugins/muc/language.lib.lua | 1 + 1 file changed, 1 insertion(+) 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 -- cgit v1.2.3 From de37e6cb589e0124b93a38721c8ad809ba762b16 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 29 Jun 2019 16:54:39 +0200 Subject: prosodyctl: Fix extraction of interpreter from arg when additional arguments (fixes #1386) Interpreter goes into the lowest negative index. See http://www.lua.org/manual/5.2/manual.html#7 --- prosodyctl | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/prosodyctl b/prosodyctl index f5786ff9..3809beff 100755 --- a/prosodyctl +++ b/prosodyctl @@ -222,7 +222,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 -- cgit v1.2.3 From c6ca3b473e60f56a20113a3ab9d8a33231aae0e5 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 29 Jun 2019 19:19:38 +0200 Subject: net.http.files: Fix cache handling Typo that broke the LRU-ness of the caching --- net/http/files.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 }; -- cgit v1.2.3 From 37a74662f01498e2723be58d2a565ab36eff9fe9 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 9 Jul 2019 15:12:32 +0200 Subject: mod_pep: Only log when creating new pubsub services Once upon a time get_pep_service() would get called with random bare JIDs and remote hostnames, which is why it was logged this way. This seems to have been fixed, so it's not as useful anymore. It's still useful to know when it creates a new service object. --- plugins/mod_pep.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 = { -- cgit v1.2.3 From f9f7ac859acc839ac4b7ff95de811717ab2afd7e Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 10 May 2019 01:28:09 +0200 Subject: mod_admin_telnet: Check for simple commands before executing in sandbox This makes fixing yield over pcall boundry issue easier since it would have jumped to the thread error handler instead of proceeding to checking for simple commands. --- plugins/mod_admin_telnet.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index fa03840b..50753f04 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -114,6 +114,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); @@ -130,11 +135,6 @@ function console:process_line(session, line) 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); -- cgit v1.2.3 From a7e58a0c50dd7bf8d5321b50467707e93effc994 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 10 May 2019 01:29:26 +0200 Subject: mod_admin_telnet: Move error handling to thread callback (fixes #1391) Avoids yielding over pcall boundry, fixes xmpp:ping() command on Lua 5.1 --- plugins/mod_admin_telnet.lua | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 50753f04..55ed89b8 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 @@ -133,13 +136,7 @@ function console:process_line(session, line) end end - local ranok, taskok, message = pcall(chunk); - - 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)); -- cgit v1.2.3 From 078178a3bac5c0ed69d629119d1c84bac3eda480 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 10 Jul 2019 17:04:36 +0200 Subject: util.error: Fix traceback due to missing text field --- util/error.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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) -- cgit v1.2.3 From 7860ab4b12a1cc13cdb5fc736cfa08f800f87890 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 10 Jul 2019 19:12:19 +0200 Subject: mod_pubsub: Move a comment to where it makes sense This code has moved but the comment did not follow it. --- plugins/mod_pubsub/mod_pubsub.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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"); -- cgit v1.2.3 From 6ab914c24a5bc606f13f69cd5d358074e0e1f788 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 11 Jul 2019 23:22:59 +0200 Subject: mod_mam: Make log message more compact --- plugins/mod_mam/mod_mam.lua | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/plugins/mod_mam/mod_mam.lua b/plugins/mod_mam/mod_mam.lua index 4e0cf531..0b3b0b82 100644 --- a/plugins/mod_mam/mod_mam.lua +++ b/plugins/mod_mam/mod_mam.lua @@ -118,10 +118,11 @@ 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 id=%s with=%s when=%s...%s", + qid or "-", + qwith or "*", + qstart and timestamp(qstart) or "", + qend and timestamp(qend) or ""); -- RSM stuff local qset = rsm.get(query); -- cgit v1.2.3 From 219d20f1ba270123c0f739fa2e5f10b3af6b61a3 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 11 Jul 2019 23:30:58 +0200 Subject: mod_mam: Include username that performed query Not always easy to find from surrounding logs --- plugins/mod_mam/mod_mam.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/mod_mam/mod_mam.lua b/plugins/mod_mam/mod_mam.lua index 0b3b0b82..0c50bd2a 100644 --- a/plugins/mod_mam/mod_mam.lua +++ b/plugins/mod_mam/mod_mam.lua @@ -118,7 +118,8 @@ module:hook("iq-set/self/"..xmlns_mam..":query", function(event) qstart, qend = vstart, vend; end - module:log("debug", "Archive query id=%s with=%s when=%s...%s", + module:log("debug", "Archive query by %s id=%s with=%s when=%s...%s", + origin.username, qid or "-", qwith or "*", qstart and timestamp(qstart) or "", -- cgit v1.2.3 From b396fc61d55211c7ebd43b90677a2b5c24302bd7 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 11 Jul 2019 23:26:02 +0200 Subject: mod_mam: Use stanza id in log message as fallback if no query id --- plugins/mod_mam/mod_mam.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_mam/mod_mam.lua b/plugins/mod_mam/mod_mam.lua index 0c50bd2a..cc06b617 100644 --- a/plugins/mod_mam/mod_mam.lua +++ b/plugins/mod_mam/mod_mam.lua @@ -120,7 +120,7 @@ module:hook("iq-set/self/"..xmlns_mam..":query", function(event) module:log("debug", "Archive query by %s id=%s with=%s when=%s...%s", origin.username, - qid or "-", + qid or stanza.attr.id, qwith or "*", qstart and timestamp(qstart) or "", qend and timestamp(qend) or ""); -- cgit v1.2.3 From c733e10ba2d06213e1eb45d2e31baf3f9919d592 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 11 Jul 2019 23:31:13 +0200 Subject: mod_mam: Include query id in final log message Should make it easier to find related log messages --- plugins/mod_mam/mod_mam.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_mam/mod_mam.lua b/plugins/mod_mam/mod_mam.lua index cc06b617..5c796f9e 100644 --- a/plugins/mod_mam/mod_mam.lua +++ b/plugins/mod_mam/mod_mam.lua @@ -197,7 +197,7 @@ module:hook("iq-set/self/"..xmlns_mam..":query", function(event) end -- That's all folks! - module:log("debug", "Archive query %s completed", tostring(qid)); + module:log("debug", "Archive query id=%s completed", qid or stanza.attr.id); origin.send(st.reply(stanza) :tag("fin", { xmlns = xmlns_mam, queryid = qid, complete = complete }) -- cgit v1.2.3 From 460efc6b3e2713183e1f3e1fdd0018b90c08c83f Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 11 Jul 2019 23:31:51 +0200 Subject: mod_mam: Log more details in final log message Saves you from counting messages sent --- plugins/mod_mam/mod_mam.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_mam/mod_mam.lua b/plugins/mod_mam/mod_mam.lua index 5c796f9e..613d045a 100644 --- a/plugins/mod_mam/mod_mam.lua +++ b/plugins/mod_mam/mod_mam.lua @@ -197,7 +197,7 @@ module:hook("iq-set/self/"..xmlns_mam..":query", function(event) end -- That's all folks! - module:log("debug", "Archive query id=%s completed", qid or stanza.attr.id); + module:log("debug", "Archive query id=%s completed, %d items returned", qid or stanza.attr.id, #results); origin.send(st.reply(stanza) :tag("fin", { xmlns = xmlns_mam, queryid = qid, complete = complete }) -- cgit v1.2.3 From 006983308dd9300755cf992b2699d73744033a42 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 11 Jul 2019 23:27:01 +0200 Subject: mod_mam: Log query failure reason The storage engine will usually make a lot of noise for serious errors, but not always. --- plugins/mod_mam/mod_mam.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/mod_mam/mod_mam.lua b/plugins/mod_mam/mod_mam.lua index 613d045a..a3bf9c42 100644 --- a/plugins/mod_mam/mod_mam.lua +++ b/plugins/mod_mam/mod_mam.lua @@ -143,6 +143,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 -- cgit v1.2.3 From c8528f9004a6053ad1a999e2ecc90d07267d5abd Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 11 Jul 2019 23:27:45 +0200 Subject: mod_mam: Log RSM parameters Helps when trying to figure out why some queries fail, ie when paging using an archive id that doesn't exist. --- plugins/mod_mam/mod_mam.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/mod_mam/mod_mam.lua b/plugins/mod_mam/mod_mam.lua index a3bf9c42..82bc6b5f 100644 --- a/plugins/mod_mam/mod_mam.lua +++ b/plugins/mod_mam/mod_mam.lua @@ -131,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, { -- cgit v1.2.3 From 3a4f54f014d4bb0714ef910c92500a8a4202c158 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 11 Jul 2019 23:29:59 +0200 Subject: mod_mam: Move final log message to end of query procedure --- plugins/mod_mam/mod_mam.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/mod_mam/mod_mam.lua b/plugins/mod_mam/mod_mam.lua index 82bc6b5f..2f6ab531 100644 --- a/plugins/mod_mam/mod_mam.lua +++ b/plugins/mod_mam/mod_mam.lua @@ -200,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 id=%s completed, %d items returned", qid or stanza.attr.id, #results); - 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, #results); return true; end); -- cgit v1.2.3 From 41c458eac5f9df61ab8af811c74c8d89e7f38e98 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 13 Jul 2019 19:57:43 +0200 Subject: mod_mam: Report correct count of results for forward queries #results is only correct for backwards queries, the table is unused for forward queries. --- plugins/mod_mam/mod_mam.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_mam/mod_mam.lua b/plugins/mod_mam/mod_mam.lua index 2f6ab531..855e1974 100644 --- a/plugins/mod_mam/mod_mam.lua +++ b/plugins/mod_mam/mod_mam.lua @@ -206,7 +206,7 @@ module:hook("iq-set/self/"..xmlns_mam..":query", function(event) 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, #results); + 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); -- cgit v1.2.3 From d06300c456ad2209a500e64a6efac1094d58bdea Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 25 May 2019 16:09:26 +0200 Subject: net.server_epoll: Return listener error message --- net/server_epoll.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index 5f62d931..4061d755 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -162,7 +162,7 @@ function interface:on(what, ...) local ok, err = pcall(listener, self, ...); if not ok then log("error", "Error calling on%s: %s", what, err); - return; + return nil, err; end return err; end -- cgit v1.2.3 From 2b596d7bd69f1c67a53c8166cc7b59cda3efce17 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 25 May 2019 16:14:31 +0200 Subject: net.server_epoll: Add experimental option to close connections in case of listener error Sometimes such errors leave sessions in an inconsistent state, so it might be better to close them early. --- net/server_epoll.lua | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index 4061d755..251f91f7 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -61,6 +61,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; @@ -162,6 +166,10 @@ function interface:on(what, ...) local ok, err = pcall(listener, self, ...); if not ok then log("error", "Error calling on%s: %s", what, err); + if cfg.fatal_errors then + log("debug", "Closing %s due to error in listener", self); + self:destroy(); + end return nil, err; end return err; -- cgit v1.2.3 From d9649edc2c6a2c22d2cdc5c8fe577deb510580bf Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 23 Jul 2019 18:06:34 +0200 Subject: net.server_epoll: Return errors from creating sockets Prevents error from attempting to index nil conn on such failure. Silences luacheck warning about the 'err' variable being unused --- net/server_epoll.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index 251f91f7..f296dd37 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -694,6 +694,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); -- cgit v1.2.3 From 3effd36ff0bb97de406768b838cf51c9d7c0af41 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 26 Jul 2019 20:25:15 +0200 Subject: mod_admin_telnet: Include both c2s connections and sessions in c2s:show() This way both incomplete connections and hibernating c2s sessions are shown. --- plugins/mod_admin_telnet.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 55ed89b8..af0ac9e7 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -593,8 +593,10 @@ local function get_jid(session) end local function show_c2s(callback) - local c2s = array.collect(values(module:shared"/*/c2s/sessions")); + 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:unique(); c2s:sort(function(a, b) if a.host == b.host then if a.username == b.username then -- cgit v1.2.3 From 65b25e80b00cd99a78a3d8ee9b5a916795e53774 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 26 Jul 2019 21:05:13 +0200 Subject: mod_admin_telnet: Factor out function for collecting all c2s sessions for easier reuse --- plugins/mod_admin_telnet.lua | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index af0ac9e7..4e34455c 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -592,12 +592,16 @@ local function get_jid(session) return jid_join("["..ip.."]:"..clientport, session.host or "["..serverip.."]:"..serverport); end -local function show_c2s(callback) +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:unique(); - c2s:sort(function(a, b) + 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 ""); -- cgit v1.2.3 From 54e39ab881929efad96e45c115e7d857b42f456b Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 26 Jul 2019 21:06:47 +0200 Subject: mod_admin_telnet: Make c2s:count() consistent with c2s:show() Both now operate on the same complete set of c2s sessions --- plugins/mod_admin_telnet.lua | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 4e34455c..a7ba41b8 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -615,9 +615,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) -- cgit v1.2.3 From 1ede2571be5c8c7fa75c1be64b9951b5898915e8 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 26 Jul 2019 21:10:42 +0200 Subject: mod_admin_telnet: Add c2s:count() to help --- plugins/mod_admin_telnet.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index a7ba41b8..40160765 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -246,6 +246,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 -- cgit v1.2.3 From a8db3548e41edddef094c82ec1b8029834a5c47f Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 26 Jul 2019 21:13:17 +0200 Subject: mod_admin_telnet: Add xmpp:ping to help --- plugins/mod_admin_telnet.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 40160765..7f324999 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -239,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 @@ -282,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 -- cgit v1.2.3 From e6a6668fb5ec2eb87ec51bc23613d5c5046549ed Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 24 Jul 2019 16:50:06 +0200 Subject: net.server_epoll: Deprecate libevent emulation layer --- net/server_epoll.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index f296dd37..b6f377fd 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -813,6 +813,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 -- cgit v1.2.3 From 0cd5fc425d98ae6bf94b4e06a3a2d8196a9ddaf4 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 26 Jul 2019 21:21:48 +0200 Subject: net.server_epoll: Overhaul logging with one log sink per connection --- net/server_epoll.lua | 64 +++++++++++++++++++++++++++++++++------------------- 1 file changed, 41 insertions(+), 23 deletions(-) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index b6f377fd..1ae92d87 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -14,7 +14,8 @@ 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 +24,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; @@ -145,6 +147,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"); @@ -155,20 +166,21 @@ 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); if cfg.fatal_errors then - log("debug", "Closing %s due to error in listener", self); + 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 @@ -281,15 +293,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 @@ -302,7 +314,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; @@ -319,12 +331,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 @@ -432,10 +444,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"); @@ -464,7 +476,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 @@ -474,7 +486,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 @@ -483,12 +495,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); @@ -512,22 +524,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 @@ -544,6 +556,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(); @@ -567,12 +580,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); @@ -647,7 +660,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 @@ -705,6 +720,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 @@ -723,6 +739,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; @@ -833,6 +850,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; -- cgit v1.2.3 From 3ecb639c7160f1b26eea1df1eca19aedd91257bf Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 25 Jul 2019 12:26:07 +0200 Subject: mod_pubsub: Eliminate dead code `data` is a stanza and always truthy --- plugins/mod_pubsub/pubsub.lib.lua | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/plugins/mod_pubsub/pubsub.lib.lua b/plugins/mod_pubsub/pubsub.lib.lua index 83c81bb7..d59e3d85 100644 --- a/plugins/mod_pubsub/pubsub.lib.lua +++ b/plugins/mod_pubsub/pubsub.lib.lua @@ -328,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 -- cgit v1.2.3 From ad9b431b8c24e909e2f2bc693b60bf11dd130372 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 26 Jul 2019 21:23:53 +0200 Subject: net.server_epoll: Remove unused local [luacheck] --- net/server_epoll.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index 1ae92d87..ccf46928 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -9,7 +9,6 @@ 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; -- cgit v1.2.3 From 1e86443ee73e3bf570eed9d8472f2940fbdb2c4e Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 28 Jul 2019 01:17:44 +0200 Subject: util.xmppstream: Inherit xml:lang from stream to stanzas (fixes #1401) --- util/xmppstream.lua | 6 ++++++ 1 file changed, 6 insertions(+) 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 -- cgit v1.2.3 From 49b3e1479cef50bc69f72b5e32ff115c7975c062 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 28 Jul 2019 01:39:47 +0200 Subject: mod_admin_telnet: Allow specifying a reason when closing sessions (#1400) --- plugins/mod_admin_telnet.lua | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 7f324999..df702b91 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -666,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 @@ -887,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"; @@ -905,19 +914,19 @@ function def_env.s2s:close(from, to) 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 -- cgit v1.2.3 From 9e57e0279736a1ec952d4915b02735b14ddb6d19 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 28 Jul 2019 01:43:10 +0200 Subject: mod_admin_telnet: Use already generated session id Don't need to construct it from components again --- plugins/mod_admin_telnet.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index df702b91..b6cdfe82 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -910,7 +910,7 @@ function def_env.s2s:close(from, to, text, condition) 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)); -- cgit v1.2.3 From d8ab361f15526623860ccd6ab9ca90451f638736 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 28 Jul 2019 16:17:23 +0200 Subject: util.poll: Add missing return statements in fallback mode These allowed modifying or deleting select() state for unregistered FDs. During normal usage this should never happen. Modifying one that isn't set might cause weirdness but deleting an already deleted FD isn't a problem. --- util-src/poll.c | 2 ++ 1 file changed, 2 insertions(+) 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); -- cgit v1.2.3 From 5c48dbda51bac9bf58dff9be6bc19524734ac42a Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 18 Dec 2016 17:39:16 +0000 Subject: server_epoll: Add native support for per socket bandwith limits --- net/server_epoll.lua | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index ccf46928..49ad48ea 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -377,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); @@ -609,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 @@ -623,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); -- cgit v1.2.3 From 9df663276b0cd3821e18a2c626cec15677ddc62d Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 29 Mar 2019 00:58:22 +0100 Subject: mod_limits: Use rate limiting in net.server if provided This should be simpler and more efficient, as well avoid problems caused by using filters. --- plugins/mod_limits.lua | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/plugins/mod_limits.lua b/plugins/mod_limits.lua index 7ae8bb34..3c7f4d40 100644 --- a/plugins/mod_limits.lua +++ b/plugins/mod_limits.lua @@ -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 -- cgit v1.2.3 From 56b07603dd018dc6cfd67bf0b34bb5539455a761 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 29 Jul 2019 00:51:03 +0200 Subject: util.array: Add tests --- spec/util_array_spec.lua | 154 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 spec/util_array_spec.lua 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); + -- cgit v1.2.3 From ab82920e54c61272770c93051deeb29396171228 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 29 Jul 2019 17:26:03 +0200 Subject: util.error: Add tests --- spec/util_error_spec.lua | 68 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 spec/util_error_spec.lua 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); + -- cgit v1.2.3 From cc653ef0d999ba46ad59cdd80be633a9f0d15557 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 29 Jul 2019 23:15:30 +0200 Subject: core.s2smanager: Remove use of tostring in logging This is now performed by loggingmanager --- core/s2smanager.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/s2smanager.lua b/core/s2smanager.lua index 684bb94e..48bf0544 100644 --- a/core/s2smanager.lua +++ b/core/s2smanager.lua @@ -75,8 +75,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); -- cgit v1.2.3 From 7bce7c9fbe91d12069da3821f732114eabeb1814 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 30 Jul 2019 02:14:50 +0200 Subject: core.s2smanager: Rewrite log line to use formatting instead of concatenation Makes it more in line with logging elsewhere. Potentially avoids or at least delays creation of new string. --- core/s2smanager.lua | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/core/s2smanager.lua b/core/s2smanager.lua index 48bf0544..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"; @@ -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; -- cgit v1.2.3 From d7770bba849e88e4ece97c65ebaabadbb78c8755 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 30 Jul 2019 02:18:59 +0200 Subject: core.stanza_router: Remove tostring call from logging Taken care of by loggingmanager now --- core/stanza_router.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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; -- cgit v1.2.3 From 8a87b70092c75b6fac191cf9f79787f7235bf60d Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 30 Jul 2019 02:19:27 +0200 Subject: core.sessionmanager: Remove tostring call from logging Taken care of by loggingmanager now --- core/sessionmanager.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 -- cgit v1.2.3 From 73beabd065c7cc08b1da06938e7df166c1f35c69 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 30 Jul 2019 02:19:54 +0200 Subject: core.portmanager: Remove tostring call from logging Taken care of by loggingmanager now --- core/portmanager.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/portmanager.lua b/core/portmanager.lua index 9eb40abf..7ba13dfd 100644 --- a/core/portmanager.lua +++ b/core/portmanager.lua @@ -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 "", service_name or ""); -- cgit v1.2.3 From e3276738b31e4afc377b3a8991b9cf771b7d5309 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 30 Jul 2019 02:20:27 +0200 Subject: core.portmanager: Remove unused local [luacheck] --- core/portmanager.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/portmanager.lua b/core/portmanager.lua index 7ba13dfd..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; -- cgit v1.2.3 From 57b3a1dc873aa16a723e8ee90349ca3d94cc9f46 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 30 Jul 2019 02:23:06 +0200 Subject: util.startup: Remove tostring call from logging Taken care of by loggingmanager now --- util/startup.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/util/startup.lua b/util/startup.lua index 7a1a95aa..ab595526 100644 --- a/util/startup.lua +++ b/util/startup.lua @@ -251,9 +251,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", { -- cgit v1.2.3 From 358ade297948443295224b6400e06447636c0e32 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 30 Jul 2019 02:23:44 +0200 Subject: util.sql: Remove tostring call from logging Taken care of by loggingmanager now --- util/sql.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 -- cgit v1.2.3 From 0e16eeb216d52e4cce2457fae31e1618a8b6e446 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 30 Jul 2019 02:24:06 +0200 Subject: util.session: Remove tostring call from logging Taken care of by loggingmanager now --- util/session.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 -- cgit v1.2.3 From 40b1e3e0ed1223517f29bcf136fc08a6f33b17f2 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 30 Jul 2019 02:29:36 +0200 Subject: plugins: Remove tostring call from logging Taken care of by loggingmanager now Mass-rewrite using lua pattern like `tostring%b()` --- plugins/mod_blocklist.lua | 2 +- plugins/mod_bosh.lua | 16 ++++++++-------- plugins/mod_c2s.lua | 4 ++-- plugins/mod_component.lua | 14 +++++++------- plugins/mod_groups.lua | 6 +++--- plugins/mod_limits.lua | 2 +- plugins/mod_mam/mod_mam.lua | 4 ++-- plugins/mod_muc_mam.lua | 6 +++--- plugins/mod_pep_simple.lua | 4 ++-- plugins/mod_proxy65.lua | 2 +- plugins/mod_s2s/mod_s2s.lua | 6 +++--- plugins/mod_saslauth.lua | 3 +-- plugins/mod_stanza_debug.lua | 5 ++--- plugins/mod_websocket.lua | 2 +- 14 files changed, 37 insertions(+), 39 deletions(-) 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, is: %s", tostring(close_reply)); + log("info", "Disconnecting client, 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, is: %s", tostring(stanza)); + module:log("info", "Disconnecting component, is: %s", stanza); session.send(stanza); elseif reason.name then -- a stanza - module:log("info", "Disconnecting component, is: %s", tostring(reason)); + module:log("info", "Disconnecting component, 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 3c7f4d40..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 diff --git a/plugins/mod_mam/mod_mam.lua b/plugins/mod_mam/mod_mam.lua index 855e1974..cfa92ff5 100644 --- a/plugins/mod_mam/mod_mam.lua +++ b/plugins/mod_mam/mod_mam.lua @@ -224,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..387f6a5d 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; 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_s2s/mod_s2s.lua b/plugins/mod_s2s/mod_s2s.lua index f0fdc5fb..dd19f350 100644 --- a/plugins/mod_s2s/mod_s2s.lua +++ b/plugins/mod_s2s/mod_s2s.lua @@ -127,7 +127,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 @@ -151,7 +151,7 @@ function route_to_new_session(event) -- 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)); + log("debug", "stanza [%s] queued until connection complete", 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); @@ -595,7 +595,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 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_websocket.lua b/plugins/mod_websocket.lua index 4ef9a07f..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, is: %s", tostring(stream_error)); + log("debug", "Disconnecting client, is: %s", stream_error); session.send(stream_error); end -- cgit v1.2.3 From 75bfec7731e0d42ad5733a62b9772db9c72aeef9 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 30 Jul 2019 02:35:17 +0200 Subject: net.*: Remove tostring call from logging Taken care of by loggingmanager now --- net/adns.lua | 8 ++++---- net/connect.lua | 2 +- net/http.lua | 4 ++-- net/websocket.lua | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/net/adns.lua b/net/adns.lua index 4fa01f8a..5050c23b 100644 --- a/net/adns.lua +++ b/net/adns.lua @@ -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/websocket.lua b/net/websocket.lua index 469c6a58..bb26c100 100644 --- a/net/websocket.lua +++ b/net/websocket.lua @@ -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 -- cgit v1.2.3 From 2fb3c3041d82abe4b51cd03f210881c52fd53b03 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 30 Jul 2019 02:35:28 +0200 Subject: net.websocket: Fix log call to pass data via format string instead of concatenation --- net/websocket.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/websocket.lua b/net/websocket.lua index bb26c100..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 -- cgit v1.2.3 From bf79ad07b26e43fe579a8761865f61e74cc682b9 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 30 Jul 2019 02:36:15 +0200 Subject: net.adns: Remove unused local [luacheck] --- net/adns.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/adns.lua b/net/adns.lua index 5050c23b..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 -- cgit v1.2.3 From 45a8e8ea7f8f4342a2ca2f8c0ea7d2bbb6591f8d Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 1 Aug 2019 05:25:34 +0200 Subject: mod_s2s: Distinguish between high and low level errors in bounces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `remote-server-not-found` is reported for problems occurring without a reply `` having been opened, e.g. DNS records were not found or no TCP stream could be established to a functioning XMPP entity. `remote-server-timeout` is reported for problems that occurring after a stream has been opened, such as configuration problems, inability to perform TLS or unsuccessful certificate validation. Related: #770 --- plugins/mod_s2s/mod_s2s.lua | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/plugins/mod_s2s/mod_s2s.lua b/plugins/mod_s2s/mod_s2s.lua index dd19f350..012c5341 100644 --- a/plugins/mod_s2s/mod_s2s.lua +++ b/plugins/mod_s2s/mod_s2s.lua @@ -77,12 +77,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(); @@ -301,6 +308,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 -- cgit v1.2.3 From 6e624e01c69504684b4afc4e4b1499da2d957c3d Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 2 Aug 2019 08:56:29 +0200 Subject: util.stanza: Use :text_tag internally everywhere May allow future changes in a single place. --- util/stanza.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 -- cgit v1.2.3 From 03acf1ffe1b8abc3b457075bd46be5ac1b491f2c Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 19 Jan 2019 22:01:54 +0100 Subject: mod_vcard_legacy: Add support for JABBERID - impp/uri conversion --- plugins/mod_vcard_legacy.lua | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/plugins/mod_vcard_legacy.lua b/plugins/mod_vcard_legacy.lua index ab2c4490..45988fa8 100644 --- a/plugins/mod_vcard_legacy.lua +++ b/plugins/mod_vcard_legacy.lua @@ -105,6 +105,11 @@ 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 end end end @@ -216,6 +221,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"); -- cgit v1.2.3 From fabd896892e40eda501c12704025df7cb7c16654 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 2 Aug 2019 21:57:57 +0200 Subject: mod_vcard_legacy: Complete roundtrip support for ORG/ORGNAME vcard-temp -> vcard4 worked previously but not the other way around --- plugins/mod_vcard_legacy.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/mod_vcard_legacy.lua b/plugins/mod_vcard_legacy.lua index 45988fa8..89ef1a4f 100644 --- a/plugins/mod_vcard_legacy.lua +++ b/plugins/mod_vcard_legacy.lua @@ -110,6 +110,10 @@ module:hook("iq-get/bare/vcard-temp:vCard", function (event) 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 -- cgit v1.2.3 From 5976d3fc664719dfddb539c660465caa961588a6 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 10 Aug 2019 16:01:42 +0200 Subject: mod_muc_mam: Conditionally advertise MAM feature on rooms (fixes #1407) --- plugins/mod_muc_mam.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/mod_muc_mam.lua b/plugins/mod_muc_mam.lua index 387f6a5d..7fc9fabf 100644 --- a/plugins/mod_muc_mam.lua +++ b/plugins/mod_muc_mam.lua @@ -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 -- cgit v1.2.3 From c788104e048f868c0448df797d2020fba1fa0934 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 10 Nov 2018 13:37:32 +0100 Subject: mod_s2s: Use net.connect instead of s2sout.lib for outgoing s2s connections --- plugins/mod_s2s/mod_s2s.lua | 38 +++-- plugins/mod_s2s/s2sout.lib.lua | 349 ----------------------------------------- 2 files changed, 23 insertions(+), 364 deletions(-) delete mode 100644 plugins/mod_s2s/s2sout.lib.lua diff --git a/plugins/mod_s2s/mod_s2s.lua b/plugins/mod_s2s/mod_s2s.lua index 012c5341..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 () @@ -154,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", 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 + connect(service.new(to_host, "xmpp-server", "tcp", { default_port = 5269 }), listener, nil, { session = host_session }); return true; end @@ -479,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) @@ -679,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); @@ -707,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; @@ -730,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; -- cgit v1.2.3 From 5da92eb0973e60d13b3240dee2ec9f09335923d0 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 29 Apr 2019 02:40:39 +0200 Subject: mod_mimicking: Import skeleton() from current location --- plugins/mod_mimicking.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/mod_mimicking.lua b/plugins/mod_mimicking.lua index 9004957a..da03967f 100644 --- a/plugins/mod_mimicking.lua +++ b/plugins/mod_mimicking.lua @@ -1,11 +1,15 @@ -- Prosody IM -- Copyright (C) 2012 Florian Zeitz +-- Copyright (C) 2019 Kim Alvefur -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- -local skeleton = require "util.confusable".skeleton; +local encodings = require "util.encodings"; +assert(encodings.confusable, "This module requires that Prosody be built with ICU"); +local skeleton = encodings.confusable.skeleton; + local datamanager = require "util.datamanager"; local usage = require "util.prosodyctl".show_usage; local warn = require "util.prosodyctl".show_warning; -- cgit v1.2.3 From 0ec982877caed14891aa11b308ae7fb3cb032acd Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 29 Apr 2019 02:37:35 +0200 Subject: mod_mimicking: Hook the correct event names --- plugins/mod_mimicking.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/mod_mimicking.lua b/plugins/mod_mimicking.lua index da03967f..8b8d6ee3 100644 --- a/plugins/mod_mimicking.lua +++ b/plugins/mod_mimicking.lua @@ -19,11 +19,11 @@ module:hook("user-registered", function(user) datamanager.store(skeleton(user.username), user.host, "skeletons", {username = user.username}); end); -module:hook("user-deregistered", function(user) +module:hook("user-deleted", function(user) datamanager.store(skeleton(user.username), user.host, "skeletons", nil); end); -module:hook("registration-attempt", function(user) +module:hook("user-registering", function(user) if datamanager.load(skeleton(user.username), user.host, "skeletons") then user.allowed = false; end -- cgit v1.2.3 From fe0ba7ac7ce6f9696d4b94c07676a279920758f8 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 29 Apr 2019 02:38:55 +0200 Subject: mod_mimicking: Use new storage API --- plugins/mod_mimicking.lua | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/plugins/mod_mimicking.lua b/plugins/mod_mimicking.lua index 8b8d6ee3..0da68059 100644 --- a/plugins/mod_mimicking.lua +++ b/plugins/mod_mimicking.lua @@ -10,21 +10,22 @@ local encodings = require "util.encodings"; assert(encodings.confusable, "This module requires that Prosody be built with ICU"); local skeleton = encodings.confusable.skeleton; -local datamanager = require "util.datamanager"; local usage = require "util.prosodyctl".show_usage; local warn = require "util.prosodyctl".show_warning; local users = require "usermanager".users; +local skeletons = module:open_store("skeletons"); + module:hook("user-registered", function(user) - datamanager.store(skeleton(user.username), user.host, "skeletons", {username = user.username}); + skeletons:set(skeleton(user.username), { username = user.username }); end); module:hook("user-deleted", function(user) - datamanager.store(skeleton(user.username), user.host, "skeletons", nil); + skeletons:set(skeleton(user.username), nil); end); module:hook("user-registering", function(user) - if datamanager.load(skeleton(user.username), user.host, "skeletons") then + if skeletons:get(skeleton(user.username)) then user.allowed = false; end end); -- cgit v1.2.3 From 7e8621d4b5918ef20d00ba87f79fb895d3dff448 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 29 Apr 2019 02:40:14 +0200 Subject: mod_mimicking: Update command to work with current code --- plugins/mod_mimicking.lua | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/plugins/mod_mimicking.lua b/plugins/mod_mimicking.lua index 0da68059..a8d6803e 100644 --- a/plugins/mod_mimicking.lua +++ b/plugins/mod_mimicking.lua @@ -11,10 +11,15 @@ assert(encodings.confusable, "This module requires that Prosody be built with IC local skeleton = encodings.confusable.skeleton; local usage = require "util.prosodyctl".show_usage; -local warn = require "util.prosodyctl".show_warning; -local users = require "usermanager".users; +local usermanager = require "core.usermanager"; +local storagemanager = require "core.storagemanager"; -local skeletons = module:open_store("skeletons"); +local skeletons +function module.load() + if module.host ~= "*" then + skeletons = module:open_store("skeletons"); + end +end module:hook("user-registered", function(user) skeletons:set(skeleton(user.username), { username = user.username }); @@ -42,13 +47,13 @@ function module.command(arg) if not host_session then return "No such host"; end - local provider = host_session.users; - if not(provider) or provider.name == "null" then - usermanager.initialize_host(host); - end + storagemanager.initialize_host(host); + usermanager.initialize_host(host); + + skeletons = storagemanager.open(host, "skeletons"); - for user in users(host) do - datamanager.store(skeleton(user), host, "skeletons", {username = user}); + for user in usermanager.users(host) do + skeletons:set(skeleton(user), { username = user }); end end -- cgit v1.2.3 From d9d997c8619b47534f0a18ce67cc9a90621e43b9 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 29 Apr 2019 17:26:56 +0200 Subject: mod_mimicking: Use more intuitive term "mimicry index" for skeletons Fits better with the module name too. --- plugins/mod_mimicking.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_mimicking.lua b/plugins/mod_mimicking.lua index a8d6803e..43d165d2 100644 --- a/plugins/mod_mimicking.lua +++ b/plugins/mod_mimicking.lua @@ -37,7 +37,7 @@ end); function module.command(arg) if (arg[1] ~= "bootstrap" or not arg[2]) then - usage("mod_mimicking bootstrap ", "Initialize skeleton database"); + usage("mod_mimicking bootstrap ", "Initialize username mimicry index"); return; end -- cgit v1.2.3 From cdea7586e4fc49b4a16b972f69adeeea1dd9136e Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 29 Apr 2019 17:27:08 +0200 Subject: mod_mimicking: Improve error handling --- plugins/mod_mimicking.lua | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/plugins/mod_mimicking.lua b/plugins/mod_mimicking.lua index 43d165d2..b586a70c 100644 --- a/plugins/mod_mimicking.lua +++ b/plugins/mod_mimicking.lua @@ -22,22 +22,34 @@ function module.load() end module:hook("user-registered", function(user) - skeletons:set(skeleton(user.username), { username = user.username }); + local skel = skeleton(user.username); + local ok, err = skeletons:set(skel, { username = user.username }); + if not ok then + module:log("error", "Unable to store mimicry data (%q => %q): %s", user.username, skel, err); + end end); module:hook("user-deleted", function(user) - skeletons:set(skeleton(user.username), nil); + local skel = skeleton(user.username); + local ok, err = skeletons:set(skel, nil); + if not ok and err then + module:log("error", "Unable to clear mimicry data (%q): %s", skel, err); + end end); module:hook("user-registering", function(user) - if skeletons:get(skeleton(user.username)) then + local existing, err = skeletons:get(skeleton(user.username)); + if existing then + module:log("debug", "Attempt to register username '%s' which could be confused with '%s'", user.username, existing.username); user.allowed = false; + elseif err then + module:log("error", "Unable to check if new username '%s' can be confused with any existing user: %s", err); end end); function module.command(arg) if (arg[1] ~= "bootstrap" or not arg[2]) then - usage("mod_mimicking bootstrap ", "Initialize username mimicry index"); + usage("mod_mimicking bootstrap ", "Initialize username mimicry database"); return; end @@ -53,7 +65,21 @@ function module.command(arg) skeletons = storagemanager.open(host, "skeletons"); + local count = 0; for user in usermanager.users(host) do - skeletons:set(skeleton(user), { username = user }); + local skel = skeleton(user); + local existing, err = skeletons:get(skel); + if existing and existing.username ~= user then + module:log("warn", "Existing usernames '%s' and '%s' are confusable", existing.username, user); + elseif err then + module:log("error", "Error checking for existing mimicry data (%q = %q): %s", user, skel, err); + end + local ok, err = skeletons:set(skel, { username = user }); + if ok then + count = count + 1; + elseif err then + module:log("error", "Unable to store mimicry data (%q => %q): %s", user, skel, err); + end end + module:log("info", "%d usernames indexed", count); end -- cgit v1.2.3 From bbc0bd0b8aa6cca380e0e4b81a9ebc6051224b20 Mon Sep 17 00:00:00 2001 From: Arc Riley Date: Thu, 2 May 2019 16:33:14 -0700 Subject: mod_admin_telnet: include BOSH connections in c2s session commands (#998) --- plugins/mod_admin_telnet.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index c66630ca..5ee25771 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -591,6 +591,7 @@ end local function show_c2s(callback) local c2s = array.collect(values(module:shared"/*/c2s/sessions")); + c2s:append(values(module:shared"/*/bosh/sessions")); c2s:sort(function(a, b) if a.host == b.host then if a.username == b.username then -- cgit v1.2.3 From ed8b36a84b47e6ab21347d6cf9c616a69e0b3f7d Mon Sep 17 00:00:00 2001 From: Arc Riley Date: Thu, 2 May 2019 17:28:49 -0700 Subject: mod_admin_telnet: added "(bosh)" and "(websocket)" connection flags (#998) --- plugins/mod_admin_telnet.lua | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 5ee25771..248d6b07 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -554,6 +554,12 @@ local function session_flags(session, line) if session.is_bidi then line[#line+1] = "(bidi)"; end + if session.bosh_version then + line[#line+1] = "(bosh)"; + end + if session.websocket_request then + line[#line+1] = "(websocket)"; + end return table.concat(line, " "); end -- cgit v1.2.3 From a16b70c96df75d562734f84e5dbda7bae2d41eed Mon Sep 17 00:00:00 2001 From: Arc Riley Date: Thu, 2 May 2019 17:44:21 -0700 Subject: mod_admin_telnet: include BOSH connections in c2s:count (#998) --- plugins/mod_admin_telnet.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 248d6b07..0fbd3ff9 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -612,7 +612,9 @@ local function show_c2s(callback) end function def_env.c2s:count() - return true, "Total: ".. iterators.count(values(module:shared"/*/c2s/sessions")) .." clients"; + 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"; end function def_env.c2s:show(match_jid, annotate) -- cgit v1.2.3 From 5b40b117152aaf7f5a55e2dab65a5cdf98910726 Mon Sep 17 00:00:00 2001 From: Arc Riley Date: Fri, 3 May 2019 04:10:31 -0700 Subject: mod_bosh: Added metrics for active/inactive sessions, new BOSH sessions, BOSH errors, and timeouts (finishes #998) --- plugins/mod_bosh.lua | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/plugins/mod_bosh.lua b/plugins/mod_bosh.lua index 082ed961..d4e980f2 100644 --- a/plugins/mod_bosh.lua +++ b/plugins/mod_bosh.lua @@ -55,6 +55,27 @@ local t_insert, t_remove, t_concat = table.insert, table.remove, table.concat; -- All sessions, and sessions that have no requests open local sessions = module:shared("sessions"); +local measure_active = module:measure("active_sessions", "amount"); +local measure_inactive = module:measure("inactive_sessions", "amount"); +local report_bad_host = module:measure("bad_host", "rate"); +local report_bad_sid = module:measure("bad_sid", "rate"); +local report_new_sid = module:measure("new_sid", "rate"); +local report_timeout = module:measure("timeout", "rate"); + +module:hook("stats-update", function () + local active = 0; + local inactive = 0; + for _, session in pairs(sessions) do + if #session.requests > 0 then + active = active + 1; + else + inactive = inactive + 1; + end + end + measure_active(active); + measure_inactive(inactive); +end); + -- Used to respond to idle sessions (those with waiting requests) function on_destroy_request(request) log("debug", "Request destroyed: %s", tostring(request)); @@ -74,7 +95,7 @@ function on_destroy_request(request) if session.inactive_timer then session.inactive_timer:stop(); end - session.inactive_timer = module:add_timer(max_inactive, check_inactive, session, request.context, + session.inactive_timer = module:add_timer(max_inactive, session_timeout, session, request.context, "BOSH client silent for over "..max_inactive.." seconds"); (session.log or log)("debug", "BOSH session marked as inactive (for %ds)", max_inactive); end @@ -85,8 +106,9 @@ function on_destroy_request(request) end end -function check_inactive(now, session, context, reason) -- luacheck: ignore 212/now +function session_timeout(now, session, context, reason) -- luacheck: ignore 212/now if not session.destroyed then + report_timeout(); sessions[context.sid] = nil; sm_destroy_session(session, reason); end @@ -186,6 +208,7 @@ function handle_POST(event) return; end module:log("warn", "Unable to associate request with a session (incomplete request?)"); + report_bad_sid(); local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", ["xmlns:stream"] = xmlns_streams, condition = "item-not-found" }); return tostring(close_reply) .. "\n"; @@ -253,6 +276,7 @@ function stream_callbacks.streamopened(context, attr) local wait = tonumber(attr.wait); if not to_host then log("debug", "BOSH client tried to connect to invalid host: %s", tostring(attr.to)); + report_bad_host(); local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", ["xmlns:stream"] = xmlns_streams, condition = "improper-addressing" }); response:send(tostring(close_reply)); @@ -290,6 +314,7 @@ function stream_callbacks.streamopened(context, attr) session.log("debug", "BOSH session created for request from %s", session.ip); log("info", "New BOSH session, assigned it sid '%s'", sid); + report_new_sid(); module:fire_event("bosh-session", { session = session, request = request }); @@ -344,6 +369,7 @@ 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); + report_bad_sid(); response:send(tostring(st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", condition = "item-not-found" }))); context.notopen = nil; return; -- cgit v1.2.3 From f65c017ee107f86b353d5931e85a70e8c6067f1f Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 3 May 2019 20:54:24 +0200 Subject: Fix various spelling mistakes [codespell] --- net/server_select.lua | 4 ++-- plugins/mod_blocklist.lua | 2 +- plugins/mod_saslauth.lua | 2 +- spec/util_throttle_spec.lua | 2 +- util/datamanager.lua | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/net/server_select.lua b/net/server_select.lua index 5d554655..e14c126e 100644 --- a/net/server_select.lua +++ b/net/server_select.lua @@ -124,7 +124,7 @@ local _maxsslhandshake _server = { } -- key = port, value = table; list of listening servers _readlist = { } -- array with sockets to read from -_sendlist = { } -- arrary with sockets to write to +_sendlist = { } -- array with sockets to write to _timerlist = { } -- array of timer functions _socketlist = { } -- key = socket, value = wrapped socket (handlers) _readtimes = { } -- key = handler, value = timestamp of last data reading @@ -150,7 +150,7 @@ _checkinterval = 30 -- interval in secs to check idle clients _sendtimeout = 60000 -- allowed send idle time in secs _readtimeout = 14 * 60 -- allowed read idle time in secs -local is_windows = package.config:sub(1,1) == "\\" -- check the directory separator, to detemine whether this is Windows +local is_windows = package.config:sub(1,1) == "\\" -- check the directory separator, to determine whether this is Windows _maxfd = (is_windows and math.huge) or luasocket._SETSIZE or 1024 -- max fd number, limit to 1024 by default to prevent glibc buffer overflow, but not on Windows _maxselectlen = luasocket._SETSIZE or 1024 -- But this still applies on Windows diff --git a/plugins/mod_blocklist.lua b/plugins/mod_blocklist.lua index 8aca7332..2193a093 100644 --- a/plugins/mod_blocklist.lua +++ b/plugins/mod_blocklist.lua @@ -159,7 +159,7 @@ local function edit_blocklist(event) local blocklist = cache[username] or get_blocklist(username); local new_blocklist = { - -- We set the [false] key to someting as a signal not to migrate privacy lists + -- We set the [false] key to something as a signal not to migrate privacy lists [false] = blocklist[false] or { created = now; }; }; if type(blocklist[false]) == "table" then diff --git a/plugins/mod_saslauth.lua b/plugins/mod_saslauth.lua index ba30b9e6..3145cf9b 100644 --- a/plugins/mod_saslauth.lua +++ b/plugins/mod_saslauth.lua @@ -248,7 +248,7 @@ module:hook("stream-features", function(event) local sasl_handler = usermanager_get_sasl_handler(module.host, origin) origin.sasl_handler = sasl_handler; if origin.encrypted then - -- check wether LuaSec has the nifty binding to the function needed for tls-unique + -- check whether LuaSec has the nifty binding to the function needed for tls-unique -- FIXME: would be nice to have this check only once and not for every socket if sasl_handler.add_cb_handler then local socket = origin.conn:socket(); diff --git a/spec/util_throttle_spec.lua b/spec/util_throttle_spec.lua index 75daf1b9..985afae8 100644 --- a/spec/util_throttle_spec.lua +++ b/spec/util_throttle_spec.lua @@ -88,7 +88,7 @@ describe("util.throttle", function() later(0.1); a:update(); end - assert(math.abs(a.balance - 1) < 0.0001); -- incremental updates cause rouding errors + assert(math.abs(a.balance - 1) < 0.0001); -- incremental updates cause rounding errors end); end); diff --git a/util/datamanager.lua b/util/datamanager.lua index cf96887b..b52c77fa 100644 --- a/util/datamanager.lua +++ b/util/datamanager.lua @@ -24,7 +24,7 @@ local t_concat = table.concat; local envloadfile = require"util.envload".envloadfile; local serialize = require "util.serialization".serialize; local lfs = require "lfs"; --- Extract directory seperator from package.config (an undocumented string that comes with lua) +-- Extract directory separator from package.config (an undocumented string that comes with lua) local path_separator = assert ( package.config:match ( "^([^\n]+)" ) , "package.config not in standard form" ) local prosody = prosody; -- cgit v1.2.3 From a371d01137d44a43cbeabe2590174fc36053bb2a Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 4 May 2019 04:48:40 +0200 Subject: net.http.files: Bump cache hits so they stay cached It's not an LRU cache unless this is done. --- net/http/files.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/net/http/files.lua b/net/http/files.lua index 0b898dc6..090b15c8 100644 --- a/net/http/files.lua +++ b/net/http/files.lua @@ -94,6 +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); elseif attr.mode == "directory" and path then if full_path:sub(-1) ~= "/" then local dir_path = { is_absolute = true, is_directory = true }; -- cgit v1.2.3 From 637b52a13e2c9e655a2595a4d8e274280e2fd612 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 5 May 2019 08:12:16 +0200 Subject: mod_storage_internal,memory: Only return total count if requested --- plugins/mod_storage_internal.lua | 17 +++++++++++------ plugins/mod_storage_memory.lua | 17 ++++++++++++----- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/plugins/mod_storage_internal.lua b/plugins/mod_storage_internal.lua index aa5c3c8a..f921747e 100644 --- a/plugins/mod_storage_internal.lua +++ b/plugins/mod_storage_internal.lua @@ -124,11 +124,14 @@ function archive:find(username, query) if not items then if err then return items, err; - else - return function () end, 0; + elseif query then + if query.total then + return function () end, 0; + end end + return function () end; end - local count = #items; + local count = nil; local i = 0; if query then items = array(items); @@ -152,11 +155,13 @@ function archive:find(username, query) return item.when <= query["end"]; end); end - count = #items; + if query.total then + count = #items; + end if query.reverse then items:reverse(); if query.before then - for j = 1, count do + for j = 1, #items do if (items[j].key or tostring(j)) == query.before then i = j; break; @@ -164,7 +169,7 @@ function archive:find(username, query) end end elseif query.after then - for j = 1, count do + for j = 1, #items do if (items[j].key or tostring(j)) == query.after then i = j; break; diff --git a/plugins/mod_storage_memory.lua b/plugins/mod_storage_memory.lua index dde2d571..4655cb3a 100644 --- a/plugins/mod_storage_memory.lua +++ b/plugins/mod_storage_memory.lua @@ -90,9 +90,14 @@ end function archive_store:find(username, query) local items = self.store[username or NULL]; if not items then - return function () end, 0; + if query then + if query.total then + return function () end, 0; + end + end + return function () end; end - local count = #items; + local count = nil; local i = 0; if query then items = array():append(items); @@ -116,11 +121,13 @@ function archive_store:find(username, query) return item.when <= query["end"]; end); end - count = #items; + if query.total then + count = #items; + end if query.reverse then items:reverse(); if query.before then - for j = 1, count do + for j = 1, #items do if (items[j].key or tostring(j)) == query.before then i = j; break; @@ -128,7 +135,7 @@ function archive_store:find(username, query) end end elseif query.after then - for j = 1, count do + for j = 1, #items do if (items[j].key or tostring(j)) == query.after then i = j; break; -- cgit v1.2.3 From e6706dee7ff715d50e8ec03762bbf6f8625cb8a8 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 5 May 2019 14:52:34 +0200 Subject: mod_muc_mam: Handle archive quotas Same as in mod_mam --- plugins/mod_muc_mam.lua | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/plugins/mod_muc_mam.lua b/plugins/mod_muc_mam.lua index 35eabd3b..fffe23e7 100644 --- a/plugins/mod_muc_mam.lua +++ b/plugins/mod_muc_mam.lua @@ -33,6 +33,9 @@ local m_min = math.min; local timestamp, timestamp_parse, datestamp = import( "util.datetime", "datetime", "parse", "date"); local default_max_items, max_max_items = 20, module:get_option_number("max_archive_query_results", 50); +local cleanup_after = module:get_option_string("muc_log_expires_after", "1w"); +local cleanup_interval = module:get_option_number("muc_log_cleanup_interval", 4 * 60 * 60); + local default_history_length = 20; local max_history_length = module:get_option_number("max_history_messages", math.huge); @@ -50,6 +53,8 @@ local log_by_default = module:get_option_boolean("muc_log_by_default", true); local archive_store = "muc_log"; local archive = module:open_store(archive_store, "archive"); +local archive_item_limit = module:get_option_number("storage_archive_item_limit", archive.caps and archive.caps.quota or 1000); + if archive.name == "null" or not archive.find then if not archive.find then module:log("error", "Attempt to open archive storage returned a driver without archive API support"); @@ -64,12 +69,15 @@ end local function archiving_enabled(room) if log_all_rooms then + module:log("debug", "Archiving all rooms"); return true; end local enabled = room._data.archiving; if enabled == nil then + module:log("debug", "Default is %s (for %s)", log_by_default, room.jid); return log_by_default; end + module:log("debug", "Logging in room %s is %s", room.jid, enabled); return enabled; end @@ -357,7 +365,29 @@ local function save_to_history(self, stanza) end -- And stash it - local id = archive:append(room_node, nil, stored_stanza, time_now(), with); + local time = time_now(); + local id, err = archive:append(room_node, nil, stored_stanza, time, with); + + if not id and err == "quota-limit" then + if type(cleanup_after) == "number" then + module:log("debug", "Room '%s' over quota, cleaning archive", room_node); + local cleaned = archive:delete(room_node, { + ["end"] = (os.time() - cleanup_after); + }); + if cleaned then + id, err = archive:append(room_node, nil, stored_stanza, time, with); + end + end + if not id and (archive.caps and archive.caps.truncate) then + module:log("debug", "User '%s' over quota, truncating archive", room_node); + local truncated = archive:delete(room_node, { + truncate = archive_item_limit - 1; + }); + if truncated then + id, err = archive:append(room_node, nil, stored_stanza, time, with); + end + end + end if id then schedule_cleanup(room_node); @@ -399,9 +429,6 @@ end); -- Cleanup -local cleanup_after = module:get_option_string("muc_log_expires_after", "1w"); -local cleanup_interval = module:get_option_number("muc_log_cleanup_interval", 4 * 60 * 60); - if cleanup_after ~= "never" then local cleanup_storage = module:open_store("muc_log_cleanup"); local cleanup_map = module:open_store("muc_log_cleanup", "map"); -- cgit v1.2.3 From ef95de88710184351f130090685c5bc7ae7fbcaf Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 5 May 2019 16:07:16 +0200 Subject: mod_storage_internal: Add support for iterating over users in archive stores May help with writing a better migrator --- plugins/mod_storage_internal.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/mod_storage_internal.lua b/plugins/mod_storage_internal.lua index f921747e..96780b33 100644 --- a/plugins/mod_storage_internal.lua +++ b/plugins/mod_storage_internal.lua @@ -211,6 +211,10 @@ function archive:summary(username, query) return summary; end +function archive:users() + return datamanager.users(host, self.store, "list"); +end + function archive:delete(username, query) local cache_key = jid_join(username, host, self.store); if not query or next(query) == nil then -- cgit v1.2.3 From 40040cdfbca7a934bb5e1256a9fdd403350eeaae Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 5 May 2019 16:26:01 +0200 Subject: mod_storage_sql: Add support for iterating over users in archive stores --- plugins/mod_storage_sql.lua | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/plugins/mod_storage_sql.lua b/plugins/mod_storage_sql.lua index ffe48ab8..596687ae 100644 --- a/plugins/mod_storage_sql.lua +++ b/plugins/mod_storage_sql.lua @@ -509,6 +509,19 @@ function archive_store:delete(username, query) return ok and stmt:affected(), stmt; end +function archive_store:users() + local ok, result = engine:transaction(function() + local select_sql = [[ + SELECT DISTINCT "user" + FROM "prosodyarchive" + WHERE "host"=? AND "store"=?; + ]]; + return engine:select(select_sql, host, self.store); + end); + if not ok then error(result); end + return iterator(result); +end + local stores = { keyval = keyval_store; map = map_store; -- cgit v1.2.3 From 6041d3e4e83ca60fb16fb4f61d83084a76632da9 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 5 May 2019 21:32:34 +0200 Subject: migrator: Rewrite to use storage modules This allows migrating to and from any storage module that supports the right methods. Based on experimental mod_migrate work. --- CHANGES | 1 + tools/migration/Makefile | 7 +- tools/migration/migrator.cfg.lua | 34 +++++- tools/migration/migrator/mtools.lua | 58 --------- tools/migration/migrator/prosody_files.lua | 144 ---------------------- tools/migration/migrator/prosody_sql.lua | 190 ----------------------------- tools/migration/prosody-migrator.lua | 148 ++++++++++++++++------ 7 files changed, 143 insertions(+), 439 deletions(-) delete mode 100644 tools/migration/migrator/mtools.lua delete mode 100644 tools/migration/migrator/prosody_files.lua delete mode 100644 tools/migration/migrator/prosody_sql.lua diff --git a/CHANGES b/CHANGES index 8c65f4ec..8b6675a5 100644 --- a/CHANGES +++ b/CHANGES @@ -8,6 +8,7 @@ TRUNK - mod\_limits: Exempted JIDs - Archive quotas - mod\_mimicking +- Rewritten migrator 0.11.0 ====== diff --git a/tools/migration/Makefile b/tools/migration/Makefile index 713831d2..dc14e0da 100644 --- a/tools/migration/Makefile +++ b/tools/migration/Makefile @@ -12,16 +12,13 @@ INSTALLEDCONFIG = $(SYSCONFDIR) INSTALLEDMODULES = $(LIBDIR)/prosody/modules INSTALLEDDATA = $(DATADIR) -SOURCE_FILES = migrator/*.lua - -all: prosody-migrator.install migrator.cfg.lua.install prosody-migrator.lua $(SOURCE_FILES) +all: prosody-migrator.install migrator.cfg.lua.install prosody-migrator.lua install: prosody-migrator.install migrator.cfg.lua.install - install -d $(BIN) $(CONFIG) $(SOURCE) $(SOURCE)/migrator + install -d $(BIN) $(CONFIG) $(SOURCE) install -d $(MAN)/man1 install -d $(SOURCE)/migrator install -m755 ./prosody-migrator.install $(BIN)/prosody-migrator - install -m644 $(SOURCE_FILES) $(SOURCE)/migrator test -e $(CONFIG)/migrator.cfg.lua || install -m644 migrator.cfg.lua.install $(CONFIG)/migrator.cfg.lua clean: diff --git a/tools/migration/migrator.cfg.lua b/tools/migration/migrator.cfg.lua index fa37f2a3..c26fa869 100644 --- a/tools/migration/migrator.cfg.lua +++ b/tools/migration/migrator.cfg.lua @@ -1,12 +1,38 @@ local data_path = "../../data"; +local vhost = { + "accounts", + "account_details", + "roster", + "vcard", + "private", + "blocklist", + "privacy", + "archive-archive", + "offline-archive", + "pubsub_nodes", + -- "pubsub_*-archive", + "pep", + -- "pep_*-archive", +} +local muc = { + "persistent", + "config", + "state", + "muc_log-archive", +}; + input { - type = "prosody_files"; + hosts = { + ["example.com"] = vhost; + ["conference.example.com"] = muc; + }; + type = "internal"; path = data_path; } output { - type = "prosody_sql"; + type = "sql"; driver = "SQLite3"; database = data_path.."/prosody.sqlite"; } @@ -14,11 +40,11 @@ output { --[[ input { - type = "prosody_files"; + type = "internal"; path = data_path; } output { - type = "prosody_sql"; + type = "sql"; driver = "SQLite3"; database = data_path.."/prosody.sqlite"; } diff --git a/tools/migration/migrator/mtools.lua b/tools/migration/migrator/mtools.lua deleted file mode 100644 index cfbfcce8..00000000 --- a/tools/migration/migrator/mtools.lua +++ /dev/null @@ -1,58 +0,0 @@ - - -local print = print; -local t_insert = table.insert; -local t_sort = table.sort; - - -local function sorted(params) - - local reader = params.reader; -- iterator to get items from - local sorter = params.sorter; -- sorting function - local filter = params.filter; -- filter function - - local cache = {}; - for item in reader do - if filter then item = filter(item); end - if item then t_insert(cache, item); end - end - if sorter then - t_sort(cache, sorter); - end - local i = 0; - return function() - i = i + 1; - return cache[i]; - end; - -end - -local function merged(reader, merger) - - local item1 = reader(); - local merged = { item1 }; - return function() - while true do - if not item1 then return nil; end - local item2 = reader(); - if not item2 then item1 = nil; return merged; end - if merger(item1, item2) then - --print("merged") - item1 = item2; - t_insert(merged, item1); - else - --print("unmerged", merged) - item1 = item2; - local tmp = merged; - merged = { item1 }; - return tmp; - end - end - end; - -end - -return { - sorted = sorted; - merged = merged; -} diff --git a/tools/migration/migrator/prosody_files.lua b/tools/migration/migrator/prosody_files.lua deleted file mode 100644 index 4de09273..00000000 --- a/tools/migration/migrator/prosody_files.lua +++ /dev/null @@ -1,144 +0,0 @@ - -local print = print; -local assert = assert; -local setmetatable = setmetatable; -local tonumber = tonumber; -local char = string.char; -local coroutine = coroutine; -local lfs = require "lfs"; -local loadfile = loadfile; -local pcall = pcall; -local mtools = require "migrator.mtools"; -local next = next; -local pairs = pairs; -local json = require "util.json"; -local os_getenv = os.getenv; -local error = error; - -prosody = {}; -local dm = require "util.datamanager" - - -local function is_dir(path) return lfs.attributes(path, "mode") == "directory"; end -local function is_file(path) return lfs.attributes(path, "mode") == "file"; end -local function clean_path(path) - return path:gsub("\\", "/"):gsub("//+", "/"):gsub("^~", os_getenv("HOME") or "~"); -end -local encode, decode; do - local urlcodes = setmetatable({}, { __index = function (t, k) t[k] = char(tonumber("0x"..k)); return t[k]; end }); - decode = function (s) return s and (s:gsub("+", " "):gsub("%%([a-fA-F0-9][a-fA-F0-9])", urlcodes)); end - encode = function (s) return s and (s:gsub("%W", function (c) return format("%%%02x", c:byte()); end)); end -end -local function decode_dir(x) - if x:gsub("%%%x%x", ""):gsub("[a-zA-Z0-9]", "") == "" then - return decode(x); - end -end -local function decode_file(x) - if x:match(".%.dat$") and x:gsub("%.dat$", ""):gsub("%%%x%x", ""):gsub("[a-zA-Z0-9]", "") == "" then - return decode(x:gsub("%.dat$", "")); - end -end -local function prosody_dir(path, ondir, onfile, ...) - for x in lfs.dir(path) do - local xpath = path.."/"..x; - if decode_dir(x) and is_dir(xpath) then - ondir(xpath, x, ...); - elseif decode_file(x) and is_file(xpath) then - onfile(xpath, x, ...); - end - end -end - -local function handle_root_file(path, name) - --print("root file: ", decode_file(name)) - coroutine.yield { user = nil, host = nil, store = decode_file(name) }; -end -local function handle_host_file(path, name, host) - --print("host file: ", decode_dir(host).."/"..decode_file(name)) - coroutine.yield { user = nil, host = decode_dir(host), store = decode_file(name) }; -end -local function handle_store_file(path, name, store, host) - --print("store file: ", decode_file(name).."@"..decode_dir(host).."/"..decode_dir(store)) - coroutine.yield { user = decode_file(name), host = decode_dir(host), store = decode_dir(store) }; -end -local function handle_host_store(path, name, host) - prosody_dir(path, function() end, handle_store_file, name, host); -end -local function handle_host_dir(path, name) - prosody_dir(path, handle_host_store, handle_host_file, name); -end -local function handle_root_dir(path) - prosody_dir(path, handle_host_dir, handle_root_file); -end - -local function decode_user(item) - local userdata = { - user = item[1].user; - host = item[1].host; - stores = {}; - }; - for i=1,#item do -- loop over stores - local result = {}; - local store = item[i]; - userdata.stores[store.store] = store.data; - store.user = nil; store.host = nil; store.store = nil; - end - return userdata; -end - -local function reader(input) - local path = clean_path(assert(input.path, "no input.path specified")); - assert(is_dir(path), "input.path is not a directory"); - local iter = coroutine.wrap(function()handle_root_dir(path);end); - -- get per-user stores, sorted - local iter = mtools.sorted { - reader = function() - local x = iter(); - while x do - dm.set_data_path(path); - local err; - x.data, err = dm.load(x.user, x.host, x.store); - if x.data == nil and err then - local p = dm.getpath(x.user, x.host, x.store); - print(("Error loading data at path %s for %s@%s (%s store): %s") - :format(p, x.user or "", x.host or "", x.store or "", err or "")); - else - return x; - end - x = iter(); - end - end; - sorter = function(a, b) - local a_host, a_user, a_store = a.host or "", a.user or "", a.store or ""; - local b_host, b_user, b_store = b.host or "", b.user or "", b.store or ""; - return a_host > b_host or (a_host==b_host and a_user > b_user) or (a_host==b_host and a_user==b_user and a_store > b_store); - end; - }; - -- merge stores to get users - iter = mtools.merged(iter, function(a, b) - return (a.host == b.host and a.user == b.user); - end); - - return function() - local x = iter(); - return x and decode_user(x); - end -end - -local function writer(output) - local path = clean_path(assert(output.path, "no output.path specified")); - assert(is_dir(path), "output.path is not a directory"); - return function(item) - if not item then return; end -- end of input - dm.set_data_path(path); - for store, data in pairs(item.stores) do - assert(dm.store(item.user, item.host, store, data)); - end - end -end - -return { - reader = reader; - writer = writer; -} diff --git a/tools/migration/migrator/prosody_sql.lua b/tools/migration/migrator/prosody_sql.lua deleted file mode 100644 index 6df2b025..00000000 --- a/tools/migration/migrator/prosody_sql.lua +++ /dev/null @@ -1,190 +0,0 @@ - -local assert = assert; -local have_DBI = pcall(require,"DBI"); -local print = print; -local type = type; -local next = next; -local pairs = pairs; -local t_sort = table.sort; -local json = require "util.json"; -local mtools = require "migrator.mtools"; -local tostring = tostring; -local tonumber = tonumber; - -if not have_DBI then - error("LuaDBI (required for SQL support) was not found, please see https://prosody.im/doc/depends#luadbi", 0); -end - -local sql = require "util.sql"; - -local function create_table(engine, name) -- luacheck: ignore 431/engine - local Table, Column, Index = sql.Table, sql.Column, sql.Index; - - local ProsodyTable = Table { - name= name or "prosody"; - Column { name="host", type="TEXT", nullable=false }; - Column { name="user", type="TEXT", nullable=false }; - Column { name="store", type="TEXT", nullable=false }; - Column { name="key", type="TEXT", nullable=false }; - Column { name="type", type="TEXT", nullable=false }; - Column { name="value", type="MEDIUMTEXT", nullable=false }; - Index { name="prosody_index", "host", "user", "store", "key" }; - }; - engine:transaction(function() - ProsodyTable:create(engine); - end); - -end - -local function serialize(value) - local t = type(value); - if t == "string" or t == "boolean" or t == "number" then - return t, tostring(value); - elseif t == "table" then - local value,err = json.encode(value); - if value then return "json", value; end - return nil, err; - end - return nil, "Unhandled value type: "..t; -end -local function deserialize(t, value) - if t == "string" then return value; - elseif t == "boolean" then - if value == "true" then return true; - elseif value == "false" then return false; end - elseif t == "number" then return tonumber(value); - elseif t == "json" then - return json.decode(value); - end -end - -local function decode_user(item) - local userdata = { - user = item[1][1].user; - host = item[1][1].host; - stores = {}; - }; - for i=1,#item do -- loop over stores - local result = {}; - local store = item[i]; - for i=1,#store do -- loop over store data - local row = store[i]; - local k = row.key; - local v = deserialize(row.type, row.value); - if k and v then - if k ~= "" then result[k] = v; elseif type(v) == "table" then - for a,b in pairs(v) do - result[a] = b; - end - end - end - userdata.stores[store[1].store] = result; - end - end - return userdata; -end - -local function needs_upgrade(engine, params) - if params.driver == "MySQL" then - local success = engine:transaction(function() - local result = engine:execute("SHOW COLUMNS FROM prosody WHERE Field='value' and Type='text'"); - assert(result:rowcount() == 0); - - -- COMPAT w/pre-0.10: Upgrade table to UTF-8 if not already - local check_encoding_query = [[ - SELECT "COLUMN_NAME","COLUMN_TYPE","TABLE_NAME" - FROM "information_schema"."columns" - WHERE "TABLE_NAME" LIKE 'prosody%%' AND ( "CHARACTER_SET_NAME"!='%s' OR "COLLATION_NAME"!='%s_bin' ); - ]]; - check_encoding_query = check_encoding_query:format(engine.charset, engine.charset); - local result = engine:execute(check_encoding_query); - assert(result:rowcount() == 0) - end); - if not success then - -- Upgrade required - return true; - end - end - return false; -end - -local function reader(input) - local engine = assert(sql:create_engine(input, function (engine) -- luacheck: ignore 431/engine - if needs_upgrade(engine, input) then - error("Old database format detected. Please run: prosodyctl mod_storage_sql upgrade"); - end - end)); - local keys = {"host", "user", "store", "key", "type", "value"}; - assert(engine:connect()); - local f,s,val = assert(engine:select("SELECT \"host\", \"user\", \"store\", \"key\", \"type\", \"value\" FROM \"prosody\";")); - -- get SQL rows, sorted - local iter = mtools.sorted { - reader = function() val = f(s, val); return val; end; - filter = function(x) - for i=1,#keys do - x[ keys[i] ] = x[i]; - end - if x.host == "" then x.host = nil; end - if x.user == "" then x.user = nil; end - if x.store == "" then x.store = nil; end - return x; - end; - sorter = function(a, b) - local a_host, a_user, a_store = a.host or "", a.user or "", a.store or ""; - local b_host, b_user, b_store = b.host or "", b.user or "", b.store or ""; - return a_host > b_host or (a_host==b_host and a_user > b_user) or (a_host==b_host and a_user==b_user and a_store > b_store); - end; - }; - -- merge rows to get stores - iter = mtools.merged(iter, function(a, b) - return (a.host == b.host and a.user == b.user and a.store == b.store); - end); - -- merge stores to get users - iter = mtools.merged(iter, function(a, b) - return (a[1].host == b[1].host and a[1].user == b[1].user); - end); - return function() - local x = iter(); - return x and decode_user(x); - end; -end - -local function writer(output, iter) - local engine = assert(sql:create_engine(output, function (engine) -- luacheck: ignore 431/engine - if needs_upgrade(engine, output) then - error("Old database format detected. Please run: prosodyctl mod_storage_sql upgrade"); - end - create_table(engine); - end)); - assert(engine:connect()); - assert(engine:delete("DELETE FROM \"prosody\"")); - local insert_sql = "INSERT INTO \"prosody\" (\"host\",\"user\",\"store\",\"key\",\"type\",\"value\") VALUES (?,?,?,?,?,?)"; - - return function(item) - if not item then assert(engine.conn:commit()) return end -- end of input - local host = item.host or ""; - local user = item.user or ""; - for store, data in pairs(item.stores) do - -- TODO transactions - local extradata = {}; - for key, value in pairs(data) do - if type(key) == "string" and key ~= "" then - local t, value = assert(serialize(value)); - local ok, err = assert(engine:insert(insert_sql, host, user, store, key, t, value)); - else - extradata[key] = value; - end - end - if next(extradata) ~= nil then - local t, extradata = assert(serialize(extradata)); - local ok, err = assert(engine:insert(insert_sql, host, user, store, "", t, extradata)); - end - end - end; -end - - -return { - reader = reader; - writer = writer; -} diff --git a/tools/migration/prosody-migrator.lua b/tools/migration/prosody-migrator.lua index 1219d891..3cc10ecf 100644 --- a/tools/migration/prosody-migrator.lua +++ b/tools/migration/prosody-migrator.lua @@ -1,19 +1,43 @@ #!/usr/bin/env lua -CFG_SOURCEDIR=os.getenv("PROSODY_SRCDIR"); -CFG_CONFIGDIR=os.getenv("PROSODY_CFGDIR"); +CFG_SOURCEDIR=CFG_SOURCEDIR or os.getenv("PROSODY_SRCDIR"); +CFG_CONFIGDIR=CFG_CONFIGDIR or os.getenv("PROSODY_CFGDIR"); +CFG_PLUGINDIR=CFG_PLUGINDIR or os.getenv("PROSODY_PLUGINDIR"); +CFG_DATADIR=CFG_DATADIR or os.getenv("PROSODY_DATADIR"); --- Substitute ~ with path to home directory in paths -if CFG_CONFIGDIR then - CFG_CONFIGDIR = CFG_CONFIGDIR:gsub("^~", os.getenv("HOME")); +-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- + +local function is_relative(path) + local path_sep = package.config:sub(1,1); + return ((path_sep == "/" and path:sub(1,1) ~= "/") + or (path_sep == "\\" and (path:sub(1,1) ~= "/" and path:sub(2,3) ~= ":\\"))) end +-- Tell Lua where to find our libraries if CFG_SOURCEDIR then - CFG_SOURCEDIR = CFG_SOURCEDIR:gsub("^~", os.getenv("HOME")); + local function filter_relative_paths(path) + if is_relative(path) then return ""; end + end + local function sanitise_paths(paths) + return (paths:gsub("[^;]+;?", filter_relative_paths):gsub(";;+", ";")); + end + package.path = sanitise_paths(CFG_SOURCEDIR.."/?.lua;"..package.path); + package.cpath = sanitise_paths(CFG_SOURCEDIR.."/?.so;"..package.cpath); +end + +-- Substitute ~ with path to home directory in data path +if CFG_DATADIR then + if os.getenv("HOME") then + CFG_DATADIR = CFG_DATADIR:gsub("^~", os.getenv("HOME")); + end end local default_config = (CFG_CONFIGDIR or ".").."/migrator.cfg.lua"; +local startup = require "util.startup"; +startup.prosodyctl(); +-- TODO startup.migrator ? + -- Command-line parsing local options = {}; local i = 1; @@ -29,13 +53,6 @@ while arg[i] do end end -if CFG_SOURCEDIR then - package.path = CFG_SOURCEDIR.."/?.lua;"..package.path; - package.cpath = CFG_SOURCEDIR.."/?.so;"..package.cpath; -else - package.path = "../../?.lua;"..package.path - package.cpath = "../../?.so;"..package.cpath -end local envloadfile = require "util.envload".envloadfile; @@ -69,24 +86,14 @@ if not config[to_store] then print("Error: Output store '"..to_store.."' not found in the config file."); end -function load_store_handler(name) - local store_type = config[name].type; - if not store_type then - print("Error: "..name.." store type not specified in the config file"); - return false; - else - local ok, err = pcall(require, "migrator."..store_type); - if not ok then - print(("Error: Failed to initialize '%s' store:\n\t%s") - :format(name, err)); - return false; - end +for store, conf in pairs(config) do -- COMPAT + if conf.type == "prosody_files" then + conf.type = "internal"; + elseif conf.type == "prosody_sql" then + conf.type = "sql"; end - return true; end -have_err = have_err or not(load_store_handler(from_store, "input") and load_store_handler(to_store, "output")); - if have_err then print(""); print("Usage: "..arg[0].." FROM_STORE TO_STORE"); @@ -101,17 +108,82 @@ if have_err then os.exit(1); end -local itype = config[from_store].type; -local otype = config[to_store].type; -local reader = require("migrator."..itype).reader(config[from_store]); -local writer = require("migrator."..otype).writer(config[to_store]); +local async = require "util.async"; +local server = require "net.server"; +local watchers = { + error = function (_, err) + error(err); + end; + waiting = function () + server.loop(); + end; +}; + +local cm = require "core.configmanager"; +local hm = require "core.hostmanager"; +local sm = require "core.storagemanager"; +local um = require "core.usermanager"; + +local function users(store, host) + if store.users then + return store:users(); + else + return um.users(host); + end +end -local json = require "util.json"; +local function prepare_config(host, conf) + if conf.type == "internal" then + sm.olddm.set_data_path(conf.path or prosody.paths.data); + elseif conf.type == "sql" then + cm.set(host, "sql", conf); + end +end -io.stderr:write("Migrating...\n"); -for x in reader do - --print(json.encode(x)) - writer(x); +local function get_driver(host, conf) + prepare_config(host, conf); + return assert(sm.load_driver(host, conf.type)); end -writer(nil); -- close + +local migration_runner = async.runner(function (job) + for host, stores in pairs(job.input.hosts) do + prosody.hosts[host] = startup.make_host(host); + sm.initialize_host(host); + um.initialize_host(host); + + local input_driver = get_driver(host, job.input); + + local output_driver = get_driver(host, job.output); + + for _, store in ipairs(stores) do + local p, typ = store:match("()%-(%w+)$"); + if typ then store = store:sub(1, p-1); else typ = "keyval"; end + log("info", "Migrating host %s store %s (%s)", host, store, typ); + + local origin = assert(input_driver:open(store, typ)); + local destination = assert(output_driver:open(store, typ)); + + if typ == "keyval" then -- host data + local data, err = origin:get(nil); + assert(not err, err); + assert(destination:set(nil, data)); + end + + for user in users(origin, host) do + if typ == "keyval" then + local data, err = origin:get(user); + assert(not err, err); + assert(destination:set(user, data)); + else + error("Don't know how to migrate data of type '"..typ.."'."); + end + end + end + end +end, watchers); + +io.stderr:write("Migrating...\n"); + +migration_runner:run({ input = config[from_store], output = config[to_store] }); + io.stderr:write("Done!\n"); -- cgit v1.2.3 From 037fc25b4c87c9ee1168635335c3ac5ee3af7353 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 5 May 2019 21:31:15 +0200 Subject: migrator: Add support for archives (fixes #651) --- tools/migration/prosody-migrator.lua | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tools/migration/prosody-migrator.lua b/tools/migration/prosody-migrator.lua index 3cc10ecf..5c4800ad 100644 --- a/tools/migration/prosody-migrator.lua +++ b/tools/migration/prosody-migrator.lua @@ -174,6 +174,12 @@ local migration_runner = async.runner(function (job) local data, err = origin:get(user); assert(not err, err); assert(destination:set(user, data)); + elseif typ == "archive" then + local iter, err = origin:find(user); + assert(iter, err); + for id, item, when, with in iter do + assert(destination:append(user, id, item, when, with)); + end else error("Don't know how to migrate data of type '"..typ.."'."); end -- cgit v1.2.3 From a9d08911dc2534d7cbfc898c7a0b4fb3fab88cf4 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 13 May 2019 10:03:46 +0100 Subject: util.hashring: Implementation of hashring data structure --- util/hashring.lua | 88 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 util/hashring.lua diff --git a/util/hashring.lua b/util/hashring.lua new file mode 100644 index 00000000..322bc005 --- /dev/null +++ b/util/hashring.lua @@ -0,0 +1,88 @@ +local function generate_ring(nodes, num_replicas, hash) + local new_ring = {}; + for _, node_name in ipairs(nodes) do + for replica = 1, num_replicas do + local replica_hash = hash(node_name..":"..replica); + new_ring[replica_hash] = node_name; + table.insert(new_ring, replica_hash); + end + end + table.sort(new_ring); + return new_ring; +end + +local hashring_methods = {}; +local hashring_mt = { + __index = function (self, k) + -- Automatically build self.ring if it's missing + if k == "ring" then + local ring = generate_ring(self.nodes, self.num_replicas, self.hash); + rawset(self, "ring", ring); + return ring; + end + return rawget(hashring_methods, k); + end +}; + +local function new(num_replicas, hash_function) + return setmetatable({ nodes = {}, num_replicas = num_replicas, hash = hash_function }, hashring_mt); +end; + +function hashring_methods:add_node(name) + self.ring = nil; + self.nodes[name] = true; + table.insert(self.nodes, name); + return true; +end + +function hashring_methods:add_nodes(nodes) + self.ring = nil; + for _, node_name in ipairs(nodes) do + if not self.nodes[node_name] then + self.nodes[node_name] = true; + table.insert(self.nodes, node_name); + end + end + return true; +end + +function hashring_methods:remove_node(node_name) + self.ring = nil; + if self.nodes[node_name] then + for i, stored_node_name in ipairs(self.nodes) do + if node_name == stored_node_name then + self.nodes[node_name] = nil; + table.remove(self.nodes, i); + return true; + end + end + end + return false; +end + +function hashring_methods:remove_nodes(nodes) + self.ring = nil; + for _, node_name in ipairs(nodes) do + self:remove_node(node_name); + end +end + +function hashring_methods:clone() + local clone_hashring = new(self.num_replicas, self.hash); + clone_hashring:add_nodes(self.nodes); + return clone_hashring; +end + +function hashring_methods:get_node(key) + local key_hash = self.hash(key); + for _, replica_hash in ipairs(self.ring) do + if key_hash < replica_hash then + return self.ring[replica_hash]; + end + end + return self.ring[self.ring[1]]; +end + +return { + new = new; +} -- cgit v1.2.3 From 63c03ce6ef0cdb5aa5640e1e71069ec8a1396247 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 13 May 2019 11:30:45 +0200 Subject: util.encodings: Declare absence of arguments [-Wstrict-prototypes] --- util-src/encodings.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util-src/encodings.c b/util-src/encodings.c index 3b7f322d..5e7032cf 100644 --- a/util-src/encodings.c +++ b/util-src/encodings.c @@ -330,7 +330,7 @@ USpoofChecker *icu_spoofcheck; #endif /* initialize global ICU stringprep profiles */ -void init_icu() { +void init_icu(void) { UErrorCode err = U_ZERO_ERROR; utrace_setLevel(UTRACE_VERBOSE); icu_nameprep = usprep_openByType(USPREP_RFC3491_NAMEPREP, &err); -- cgit v1.2.3 From 46822c54b1e6ba4e37b88e7184147fbf57f99782 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 13 May 2019 10:36:03 +0100 Subject: util.hashring: Add tests --- spec/util_hashring_spec.lua | 85 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 spec/util_hashring_spec.lua diff --git a/spec/util_hashring_spec.lua b/spec/util_hashring_spec.lua new file mode 100644 index 00000000..d8801774 --- /dev/null +++ b/spec/util_hashring_spec.lua @@ -0,0 +1,85 @@ +local hashring = require "util.hashring"; + +describe("util.hashring", function () + + local sha256 = require "util.hashes".sha256; + + local ring = hashring.new(128, sha256); + + it("should fail to get a node that does not exist", function () + assert.is_nil(ring:get_node("foo")) + end); + + it("should support adding nodes", function () + ring:add_node("node1"); + end); + + it("should return a single node for all keys if only one node exists", function () + for i = 1, 100 do + assert.is_equal("node1", ring:get_node(tostring(i))) + end + end); + + it("should support adding a second node", function () + ring:add_node("node2"); + end); + + it("should fail to remove a non-existent node", function () + assert.is_falsy(ring:remove_node("node3")); + end); + + it("should succeed to remove a node", function () + assert.is_truthy(ring:remove_node("node1")); + end); + + it("should return the only node for all keys", function () + for i = 1, 100 do + assert.is_equal("node2", ring:get_node(tostring(i))) + end + end); + + it("should support adding multiple nodes", function () + ring:add_nodes({ "node1", "node3", "node4", "node5" }); + end); + + it("should disrupt a minimal number of keys on node removal", function () + local orig_ring = ring:clone(); + local node_tallies = {}; + + local n = 1000; + + for i = 1, n do + local key = tostring(i); + local node = ring:get_node(key); + node_tallies[node] = (node_tallies[node] or 0) + 1; + end + + --[[ + for node, key_count in pairs(node_tallies) do + print(node, key_count, ("%.2f%%"):format((key_count/n)*100)); + end + ]] + + ring:remove_node("node5"); + + local disrupted_keys = 0; + for i = 1, n do + local key = tostring(i); + if orig_ring:get_node(key) ~= ring:get_node(key) then + disrupted_keys = disrupted_keys + 1; + end + end + assert.is_equal(node_tallies["node5"], disrupted_keys); + end); + + it("should support removing multiple nodes", function () + ring:remove_nodes({"node2", "node3", "node4", "node5"}); + end); + + it("should return a single node for all keys if only one node remains", function () + for i = 1, 100 do + assert.is_equal("node1", ring:get_node(tostring(i))) + end + end); + +end); -- cgit v1.2.3 From e65093b5dee9d09e23ac6d651b76648cce91214e Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 13 May 2019 11:52:16 +0200 Subject: configure: Handle ostype preset after argument processing --- configure | 138 ++++++++++++++++++++++++++++++++------------------------------ 1 file changed, 71 insertions(+), 67 deletions(-) diff --git a/configure b/configure index c8ad1115..33d86909 100755 --- a/configure +++ b/configure @@ -153,74 +153,8 @@ do SYSCONFDIR_SET=yes ;; --ostype) - # TODO make this a switch? OSPRESET="$value" - if [ "$OSPRESET" = "debian" ]; then - if [ "$LUA_SUFFIX_SET" != "yes" ]; then - LUA_SUFFIX="5.1"; - LUA_SUFFIX_SET=yes - fi - if [ "$RUNWITH_SET" != "yes" ]; then - RUNWITH="lua$LUA_SUFFIX"; - RUNWITH_SET=yes - fi - LUA_INCDIR="/usr/include/lua$LUA_SUFFIX" - LUA_INCDIR_SET=yes - CFLAGS="$CFLAGS -ggdb" - fi - if [ "$OSPRESET" = "macosx" ]; then - LUA_INCDIR=/usr/local/include; - LUA_INCDIR_SET=yes - LUA_LIBDIR=/usr/local/lib - LUA_LIBDIR_SET=yes - CFLAGS="$CFLAGS -mmacosx-version-min=10.3" - LDFLAGS="-bundle -undefined dynamic_lookup" - fi - if [ "$OSPRESET" = "linux" ]; then - LUA_INCDIR=/usr/local/include; - LUA_INCDIR_SET=yes - LUA_LIBDIR=/usr/local/lib - LUA_LIBDIR_SET=yes - CFLAGS="$CFLAGS -ggdb" - fi - if [ "$OSPRESET" = "freebsd" ] || [ "$OSPRESET" = "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" - LUA_SUFFIX="51" - LUA_SUFFIX_SET=yes - LUA_DIR=/usr/local - LUA_DIR_SET=yes - CC=cc - LD=ld - fi - if [ "$OSPRESET" = "openbsd" ]; then - LUA_INCDIR="/usr/local/include"; - LUA_INCDIR_SET="yes" - fi - if [ "$OSPRESET" = "netbsd" ]; then - LUA_INCDIR="/usr/pkg/include/lua-5.1" - LUA_INCDIR_SET=yes - LUA_LIBDIR="/usr/pkg/lib/lua/5.1" - LUA_LIBDIR_SET=yes - CFLAGS="-Wall -fPIC -I/usr/pkg/include" - LDFLAGS="-L/usr/pkg/lib -Wl,-rpath,/usr/pkg/lib -shared" - fi - if [ "$OSPRESET" = "pkg-config" ]; then - if [ "$LUA_SUFFIX_SET" != "yes" ]; then - LUA_SUFFIX="5.1"; - LUA_SUFFIX_SET=yes - fi - LUA_CF="$(pkg-config --cflags-only-I lua$LUA_SUFFIX)" - LUA_CF="${LUA_CF#*-I}" - LUA_CF="${LUA_CF%% *}" - if [ "$LUA_CF" != "" ]; then - LUA_INCDIR="$LUA_CF" - LUA_INCDIR_SET=yes - fi - CFLAGS="$CFLAGS" - fi + OSPRESET_SET="yes" ;; --libdir) LIBDIR="$value" @@ -319,6 +253,76 @@ do shift done +if [ "$OSPRESET_SET" = "yes" ]; then + # TODO make this a switch? + if [ "$OSPRESET" = "debian" ]; then + if [ "$LUA_SUFFIX_SET" != "yes" ]; then + LUA_SUFFIX="5.1"; + LUA_SUFFIX_SET=yes + fi + if [ "$RUNWITH_SET" != "yes" ]; then + RUNWITH="lua$LUA_SUFFIX"; + RUNWITH_SET=yes + fi + LUA_INCDIR="/usr/include/lua$LUA_SUFFIX" + LUA_INCDIR_SET=yes + CFLAGS="$CFLAGS -ggdb" + fi + if [ "$OSPRESET" = "macosx" ]; then + LUA_INCDIR=/usr/local/include; + LUA_INCDIR_SET=yes + LUA_LIBDIR=/usr/local/lib + LUA_LIBDIR_SET=yes + CFLAGS="$CFLAGS -mmacosx-version-min=10.3" + LDFLAGS="-bundle -undefined dynamic_lookup" + fi + if [ "$OSPRESET" = "linux" ]; then + LUA_INCDIR=/usr/local/include; + LUA_INCDIR_SET=yes + LUA_LIBDIR=/usr/local/lib + LUA_LIBDIR_SET=yes + CFLAGS="$CFLAGS -ggdb" + fi + if [ "$OSPRESET" = "freebsd" ] || [ "$OSPRESET" = "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" + LUA_SUFFIX="51" + LUA_SUFFIX_SET=yes + LUA_DIR=/usr/local + LUA_DIR_SET=yes + CC=cc + LD=ld + fi + if [ "$OSPRESET" = "openbsd" ]; then + LUA_INCDIR="/usr/local/include"; + LUA_INCDIR_SET="yes" + fi + if [ "$OSPRESET" = "netbsd" ]; then + LUA_INCDIR="/usr/pkg/include/lua-5.1" + LUA_INCDIR_SET=yes + LUA_LIBDIR="/usr/pkg/lib/lua/5.1" + LUA_LIBDIR_SET=yes + CFLAGS="-Wall -fPIC -I/usr/pkg/include" + LDFLAGS="-L/usr/pkg/lib -Wl,-rpath,/usr/pkg/lib -shared" + fi + if [ "$OSPRESET" = "pkg-config" ]; then + if [ "$LUA_SUFFIX_SET" != "yes" ]; then + LUA_SUFFIX="5.1"; + LUA_SUFFIX_SET=yes + fi + LUA_CF="$(pkg-config --cflags-only-I lua$LUA_SUFFIX)" + LUA_CF="${LUA_CF#*-I}" + LUA_CF="${LUA_CF%% *}" + if [ "$LUA_CF" != "" ]; then + LUA_INCDIR="$LUA_CF" + LUA_INCDIR_SET=yes + fi + CFLAGS="$CFLAGS" + fi +fi + if [ "$PREFIX_SET" = "yes" ] && [ ! "$SYSCONFDIR_SET" = "yes" ] then if [ "$PREFIX" = "/usr" ] -- cgit v1.2.3 From 371260638d19941a80979052d0fac875927659e0 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 13 May 2019 11:59:00 +0200 Subject: configure: Remove preset settings that are autodiscovered These are likely wrong if other flags have been given. --- configure | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/configure b/configure index 33d86909..c9737ae6 100755 --- a/configure +++ b/configure @@ -256,16 +256,6 @@ done if [ "$OSPRESET_SET" = "yes" ]; then # TODO make this a switch? if [ "$OSPRESET" = "debian" ]; then - if [ "$LUA_SUFFIX_SET" != "yes" ]; then - LUA_SUFFIX="5.1"; - LUA_SUFFIX_SET=yes - fi - if [ "$RUNWITH_SET" != "yes" ]; then - RUNWITH="lua$LUA_SUFFIX"; - RUNWITH_SET=yes - fi - LUA_INCDIR="/usr/include/lua$LUA_SUFFIX" - LUA_INCDIR_SET=yes CFLAGS="$CFLAGS -ggdb" fi if [ "$OSPRESET" = "macosx" ]; then @@ -277,10 +267,6 @@ if [ "$OSPRESET_SET" = "yes" ]; then LDFLAGS="-bundle -undefined dynamic_lookup" fi if [ "$OSPRESET" = "linux" ]; then - LUA_INCDIR=/usr/local/include; - LUA_INCDIR_SET=yes - LUA_LIBDIR=/usr/local/lib - LUA_LIBDIR_SET=yes CFLAGS="$CFLAGS -ggdb" fi if [ "$OSPRESET" = "freebsd" ] || [ "$OSPRESET" = "openbsd" ]; then -- cgit v1.2.3 From 61d02f359982574f857674bc314ac121f2735189 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 13 May 2019 12:00:28 +0200 Subject: configure: Respect previously set paths in macosx preset --- configure | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/configure b/configure index c9737ae6..2b58efe5 100755 --- a/configure +++ b/configure @@ -259,10 +259,14 @@ if [ "$OSPRESET_SET" = "yes" ]; then CFLAGS="$CFLAGS -ggdb" fi if [ "$OSPRESET" = "macosx" ]; then - LUA_INCDIR=/usr/local/include; - LUA_INCDIR_SET=yes - LUA_LIBDIR=/usr/local/lib - LUA_LIBDIR_SET=yes + if [ "$LUA_INCDIR_SET" != "yes" ]; then + LUA_INCDIR=/usr/local/include; + LUA_INCDIR_SET=yes + fi + if [ "$LUA_LIBDIR_SET" != "yes" ]; then + LUA_LIBDIR=/usr/local/lib + LUA_LIBDIR_SET=yes + fi CFLAGS="$CFLAGS -mmacosx-version-min=10.3" LDFLAGS="-bundle -undefined dynamic_lookup" fi -- cgit v1.2.3 From b772308c93a1c78995a073964ebf963f759ef81c Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 5 Mar 2019 00:12:30 +0100 Subject: mod_storage_internal: Return error if 'before' or 'after' are not found (partial fix for #1325) --- plugins/mod_storage_internal.lua | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/plugins/mod_storage_internal.lua b/plugins/mod_storage_internal.lua index 96780b33..fdce3c98 100644 --- a/plugins/mod_storage_internal.lua +++ b/plugins/mod_storage_internal.lua @@ -161,20 +161,30 @@ function archive:find(username, query) if query.reverse then items:reverse(); if query.before then + local found = false; for j = 1, #items do if (items[j].key or tostring(j)) == query.before then + found = true; i = j; break; end end + if not found then + return nil, "item-not-found"; + end end elseif query.after then + local found = false; for j = 1, #items do if (items[j].key or tostring(j)) == query.after then + found = true; i = j; break; end end + if not found then + return nil, "item-not-found"; + end end if query.limit and #items - i > query.limit then items[i+query.limit+1] = nil; -- cgit v1.2.3 From f456f0c03e519a3e53caaf233f7c9f1a54e2ade8 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 5 Mar 2019 00:16:41 +0100 Subject: mod_storage_memory: Return error if 'before' or 'after' are not found (partial fix for #1325) --- plugins/mod_storage_memory.lua | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/plugins/mod_storage_memory.lua b/plugins/mod_storage_memory.lua index 4655cb3a..2fae8828 100644 --- a/plugins/mod_storage_memory.lua +++ b/plugins/mod_storage_memory.lua @@ -127,20 +127,30 @@ function archive_store:find(username, query) if query.reverse then items:reverse(); if query.before then + local found = false; for j = 1, #items do if (items[j].key or tostring(j)) == query.before then + found = true; i = j; break; end end + if not found then + return nil, "item-not-found"; + end end elseif query.after then + local found = false; for j = 1, #items do if (items[j].key or tostring(j)) == query.after then + found = true; i = j; break; end end + if not found then + return nil, "item-not-found"; + end end if query.limit and #items - i > query.limit then items[i+query.limit+1] = nil; -- cgit v1.2.3 From a770a8430662ca624d81bdf4e23e8da5356c82ac Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 23 Mar 2019 00:51:10 +0100 Subject: mod_storage_sql: Look up archive IDs in separate queries (fixes #1325) This is probably not good for performance. --- plugins/mod_storage_sql.lua | 58 ++++++++++++++++++++++----------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/plugins/mod_storage_sql.lua b/plugins/mod_storage_sql.lua index f0a8fee0..5da5e448 100644 --- a/plugins/mod_storage_sql.lua +++ b/plugins/mod_storage_sql.lua @@ -327,38 +327,36 @@ local function archive_where(query, args, where) end end local function archive_where_id_range(query, args, where) - local args_len = #args -- Before or after specific item, exclusive + local id_lookup_sql = [[ + SELECT "sort_id" + FROM "prosodyarchive" + WHERE "key" = ? AND "host" = ? AND "user" = ? AND "store" = ? + LIMIT 1; + ]]; if query.after then -- keys better be unique! - where[#where+1] = [[ - "sort_id" > COALESCE( - ( - SELECT "sort_id" - FROM "prosodyarchive" - WHERE "key" = ? AND "host" = ? AND "user" = ? AND "store" = ? - LIMIT 1 - ), 0) - ]]; - args[args_len+1], args[args_len+2], args[args_len+3], args[args_len+4] = query.after, args[1], args[2], args[3]; - args_len = args_len + 4 + local after_id = nil; + for row in engine:select(id_lookup_sql, query.after, host, user or "", store) do + after_id = row[1]; + end + if not after_id then + return nil, "item-not-found"; + end + where[#where+1] = '"sort_id" > ?'; + args[#args+1] = after_id; end if query.before then - where[#where+1] = [[ - "sort_id" < COALESCE( - ( - SELECT "sort_id" - FROM "prosodyarchive" - WHERE "key" = ? AND "host" = ? AND "user" = ? AND "store" = ? - LIMIT 1 - ), - ( - SELECT MAX("sort_id")+1 - FROM "prosodyarchive" - ) - ) - ]] - args[args_len+1], args[args_len+2], args[args_len+3], args[args_len+4] = query.before, args[1], args[2], args[3]; + local before_id = nil; + for row in engine:select(id_lookup_sql, query.after, host, user or "", store) do + before_id = row[1]; + end + if not before_id then + return nil, "item-not-found"; + end + where[#where+1] = '"sort_id" < ?'; + args[#args+1] = before_id; end + return true; end function archive_store:find(username, query) @@ -398,7 +396,8 @@ function archive_store:find(username, query) end end - archive_where_id_range(query, args, where); + local ok, err = archive_where_id_range(query, args, where); + if not ok then return ok, err; end if query.limit then args[#args+1] = query.limit; @@ -466,7 +465,8 @@ function archive_store:delete(username, query) table.remove(where, 2); end archive_where(query, args, where); - archive_where_id_range(query, args, where); + local ok, err = archive_where_id_range(query, args, where); + if not ok then return ok, err; end if query.truncate == nil then sql_query = sql_query:format(t_concat(where, " AND ")); else -- cgit v1.2.3 From fcda870911ff3a9ae66a3051c0e1a281f7b99772 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 5 May 2019 07:16:05 +0200 Subject: mod_mam: Propagate item-not-found to client (fixes #1325) --- plugins/mod_mam/mod_mam.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/mod_mam/mod_mam.lua b/plugins/mod_mam/mod_mam.lua index 632de9ea..317ddac1 100644 --- a/plugins/mod_mam/mod_mam.lua +++ b/plugins/mod_mam/mod_mam.lua @@ -142,7 +142,11 @@ module:hook("iq-set/self/"..xmlns_mam..":query", function(event) }); if not data then - origin.send(st.error_reply(stanza, "cancel", "internal-server-error", err)); + if err == "item-not-found" then + origin.send(st.error_reply(stanza, "modify", "item-not-found")); + else + origin.send(st.error_reply(stanza, "cancel", "internal-server-error")); + end return true; end local total = tonumber(err); -- cgit v1.2.3 From d88db76a0669d3613fc6ce1123943e9bd8927395 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 5 May 2019 07:16:03 +0200 Subject: mod_muc_mam: Propagate item-not-found to client (fixes #1325) --- plugins/mod_muc_mam.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/mod_muc_mam.lua b/plugins/mod_muc_mam.lua index fffe23e7..bba7f422 100644 --- a/plugins/mod_muc_mam.lua +++ b/plugins/mod_muc_mam.lua @@ -189,7 +189,11 @@ module:hook("iq-set/bare/"..xmlns_mam..":query", function(event) }); if not data then - origin.send(st.error_reply(stanza, "cancel", "internal-server-error")); + if err == "item-not-found" then + origin.send(st.error_reply(stanza, "modify", "item-not-found")); + else + origin.send(st.error_reply(stanza, "cancel", "internal-server-error")); + end return true; end local total = tonumber(err); -- cgit v1.2.3 From 63333b90631e5b3d7c955ec3c0c2e9eae204faf7 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 5 May 2019 07:22:18 +0200 Subject: mod_storage_memory: Return correct error even if no archive data available --- plugins/mod_storage_memory.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/mod_storage_memory.lua b/plugins/mod_storage_memory.lua index 2fae8828..376ae277 100644 --- a/plugins/mod_storage_memory.lua +++ b/plugins/mod_storage_memory.lua @@ -91,6 +91,9 @@ function archive_store:find(username, query) local items = self.store[username or NULL]; if not items then if query then + if query.before or query.after then + return nil, "item-not-found"; + end if query.total then return function () end, 0; end -- cgit v1.2.3 From 99f6c695075dc4a93deb5aea4e8acaf635d48fdb Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 5 May 2019 07:24:12 +0200 Subject: mod_storage_internal: Return appropriate error even with empty archive --- plugins/mod_storage_internal.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/mod_storage_internal.lua b/plugins/mod_storage_internal.lua index fdce3c98..2556224d 100644 --- a/plugins/mod_storage_internal.lua +++ b/plugins/mod_storage_internal.lua @@ -125,6 +125,9 @@ function archive:find(username, query) if err then return items, err; elseif query then + if query.before or query.after then + return nil, "item-not-found"; + end if query.total then return function () end, 0; end -- cgit v1.2.3 From 6fcaa64f6139a5093a328b2458ea4ac74a153d51 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 26 May 2019 15:04:16 +0200 Subject: mod_csi_simple: Disable optimizations on disconnect (fixes #1358) The connection becomes invalid here, regardless of 3rd party modules that might keep the session alive. --- plugins/mod_csi_simple.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/mod_csi_simple.lua b/plugins/mod_csi_simple.lua index a9148618..13002ea8 100644 --- a/plugins/mod_csi_simple.lua +++ b/plugins/mod_csi_simple.lua @@ -108,6 +108,10 @@ module:hook("csi-client-active", function (event) disable_optimizations(session); end); +module:hook("pre-resource-unbind", function (event) + local session = event.session; + disable_optimizations(session); +end); module:hook("c2s-ondrain", function (event) local session = event.session; -- cgit v1.2.3 From 5a2a81bfe97c366e6da39442534b4e58ba64ae71 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 28 May 2019 00:46:24 +0200 Subject: mod_storage_sql: Correctly return item-not-found error `return ok, err` comes out as `transaction_ok, ok, err` --- plugins/mod_storage_sql.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/mod_storage_sql.lua b/plugins/mod_storage_sql.lua index 5da5e448..cfc8450c 100644 --- a/plugins/mod_storage_sql.lua +++ b/plugins/mod_storage_sql.lua @@ -367,7 +367,7 @@ function archive_store:find(username, query) if total ~= nil and query.limit == 0 and query.start == nil and query.with == nil and query["end"] == nil and query.key == nil then return noop, total; end - local ok, result = engine:transaction(function() + local ok, result, err = engine:transaction(function() local sql_query = [[ SELECT "key", "type", "value", "when", "with" FROM "prosodyarchive" @@ -407,7 +407,8 @@ function archive_store:find(username, query) and "DESC" or "ASC", query.limit and " LIMIT ?" or ""); return engine:select(sql_query, unpack(args)); end); - if not ok then return ok, result end + if not ok then return ok, result; end + if not result then return nil, err; end return function() local row = result(); if row ~= nil then -- cgit v1.2.3 From 7b63f8d95dcc99df5508a05c60fe472dfc2a4282 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 28 May 2019 00:47:50 +0200 Subject: mod_storage_sql: Fix to use correct arguments to archive id lookup --- plugins/mod_storage_sql.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/mod_storage_sql.lua b/plugins/mod_storage_sql.lua index cfc8450c..ad2de840 100644 --- a/plugins/mod_storage_sql.lua +++ b/plugins/mod_storage_sql.lua @@ -336,7 +336,7 @@ local function archive_where_id_range(query, args, where) ]]; if query.after then -- keys better be unique! local after_id = nil; - for row in engine:select(id_lookup_sql, query.after, host, user or "", store) do + for row in engine:select(id_lookup_sql, query.after, args[1], args[2], args[3]) do after_id = row[1]; end if not after_id then @@ -347,7 +347,7 @@ local function archive_where_id_range(query, args, where) end if query.before then local before_id = nil; - for row in engine:select(id_lookup_sql, query.after, host, user or "", store) do + for row in engine:select(id_lookup_sql, query.after, args[1], args[2], args[3]) do before_id = row[1]; end if not before_id then -- cgit v1.2.3 From 2a65eae651302a17c925e0340e59e73976aa07fb Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 28 May 2019 00:56:30 +0200 Subject: mod_storage_sql: Ignore shadowed error variable [luacheck] --- plugins/mod_storage_sql.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_storage_sql.lua b/plugins/mod_storage_sql.lua index ad2de840..518e2654 100644 --- a/plugins/mod_storage_sql.lua +++ b/plugins/mod_storage_sql.lua @@ -153,7 +153,7 @@ end local archive_item_limit = module:get_option_number("storage_archive_item_limit"); local archive_item_count_cache = cache.new(module:get_option("storage_archive_item_limit_cache_size", 1000)); --- luacheck: ignore 512 431/user 431/store +-- luacheck: ignore 512 431/user 431/store 431/err local map_store = {}; map_store.__index = map_store; map_store.remove = {}; -- cgit v1.2.3 From 236abc4afed5321e0da406e369a8b23dac6fef83 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 30 May 2019 13:41:05 +0200 Subject: util.format: Handle formats expecting an integer in Lua 5.3+ (fixes #1371) --- spec/util_format_spec.lua | 1 + util/format.lua | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/spec/util_format_spec.lua b/spec/util_format_spec.lua index b9652d19..82b70205 100644 --- a/spec/util_format_spec.lua +++ b/spec/util_format_spec.lua @@ -12,6 +12,7 @@ describe("util.format", function() assert.equal("[true]", format("%d", true)); assert.equal("% [true]", format("%%", true)); assert.equal("{ }", format("%q", { })); + assert.equal("[1.5]", format("%d", 1.5)); end); end); end); diff --git a/util/format.lua b/util/format.lua index c31f599f..857bb694 100644 --- a/util/format.lua +++ b/util/format.lua @@ -7,6 +7,9 @@ local unpack = table.unpack or unpack; -- luacheck: ignore 113/unpack local pack = require "util.table".pack; -- TODO table.pack in 5.2+ local type = type; local dump = require "util.serialization".new("debug"); +local num_type = math.type; + +local expects_integer = num_type and { c = true, d = true, i = true, o = true, u = true, X = true, x = true, } or {}; local function format(formatstring, ...) local args = pack(...); @@ -43,6 +46,9 @@ local function format(formatstring, ...) elseif type(arg) ~= "number" then -- arg isn't number as expected? args[i] = tostring(arg); spec = "[%s]"; + elseif expects_integer[option] and num_type(arg) ~= "integer" then + args[i] = tostring(arg); + spec = "[%s]"; end end return spec; -- cgit v1.2.3 From 2661a6f5a32731100287df9564c45cbe2406e0f0 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 30 May 2019 13:54:11 +0200 Subject: util.format: Handle integer formats the same way on Lua versions without integer support --- spec/util_format_spec.lua | 1 + util/format.lua | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/spec/util_format_spec.lua b/spec/util_format_spec.lua index 82b70205..50509630 100644 --- a/spec/util_format_spec.lua +++ b/spec/util_format_spec.lua @@ -13,6 +13,7 @@ describe("util.format", function() assert.equal("% [true]", format("%%", true)); assert.equal("{ }", format("%q", { })); assert.equal("[1.5]", format("%d", 1.5)); + assert.equal("[7.3786976294838e+19]", format("%d", 73786976294838206464)); end); end); end); diff --git a/util/format.lua b/util/format.lua index 857bb694..1ce670f3 100644 --- a/util/format.lua +++ b/util/format.lua @@ -7,9 +7,12 @@ local unpack = table.unpack or unpack; -- luacheck: ignore 113/unpack local pack = require "util.table".pack; -- TODO table.pack in 5.2+ local type = type; local dump = require "util.serialization".new("debug"); -local num_type = math.type; +local num_type = math.type or function (n) + return n % 1 == 0 and n <= 9007199254740992 and n >= -9007199254740992 and "integer" or "float"; +end -local expects_integer = num_type and { c = true, d = true, i = true, o = true, u = true, X = true, x = true, } or {}; +-- In Lua 5.3+ these formats throw an error if given a float +local expects_integer = { c = true, d = true, i = true, o = true, u = true, X = true, x = true, }; local function format(formatstring, ...) local args = pack(...); -- cgit v1.2.3 From 8ad384af08177fe49e1760128f19bacbef73b301 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 31 May 2019 17:01:22 +0200 Subject: prosody: Log shutdown reason --- prosody | 1 + 1 file changed, 1 insertion(+) diff --git a/prosody b/prosody index 204fb36d..f0061bf1 100755 --- a/prosody +++ b/prosody @@ -90,6 +90,7 @@ end loop(); prosody.log("info", "Shutting down..."); +prosody.log("debug", "Shutdown reason is: %s", prosody.shutdown_reason or "not specified"); cleanup(); prosody.events.fire_event("server-stopped"); prosody.log("info", "Shutdown complete"); -- cgit v1.2.3 From 132dc809762a15b4c1c638ea0345bf7358caa833 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 31 May 2019 18:50:13 +0200 Subject: prosody: Log shutdown reason (in past tense) as the very last thing --- prosody | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prosody b/prosody index f0061bf1..ced319e0 100755 --- a/prosody +++ b/prosody @@ -90,9 +90,9 @@ end loop(); prosody.log("info", "Shutting down..."); -prosody.log("debug", "Shutdown reason is: %s", prosody.shutdown_reason or "not specified"); cleanup(); prosody.events.fire_event("server-stopped"); prosody.log("info", "Shutdown complete"); +prosody.log("debug", "Shutdown reason was: %s", prosody.shutdown_reason or "not specified"); os.exit(prosody.shutdown_code); -- cgit v1.2.3 From b6b8c443ca10aea6aae5f8bb0596b8b4a9030f42 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 31 May 2019 18:50:34 +0200 Subject: prosody: Also log status code passed to exit() Sometimes you're just too lazy to `echo $?` --- prosody | 1 + 1 file changed, 1 insertion(+) diff --git a/prosody b/prosody index ced319e0..e82318d1 100755 --- a/prosody +++ b/prosody @@ -95,4 +95,5 @@ prosody.events.fire_event("server-stopped"); prosody.log("info", "Shutdown complete"); prosody.log("debug", "Shutdown reason was: %s", prosody.shutdown_reason or "not specified"); +prosody.log("debug", "Exiting with status code: %d", prosody.shutdown_code or 0); os.exit(prosody.shutdown_code); -- cgit v1.2.3 From 331336cd83ae38f8f963abd61951912127a3c250 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20=E2=80=9Cpep=E2=80=9D=20Buquet?= Date: Sat, 1 Jun 2019 15:00:35 +0200 Subject: core/sessionmanager: Remove unnecessary fallback in make_authenticated --- core/sessionmanager.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/sessionmanager.lua b/core/sessionmanager.lua index f5af1185..55f096b9 100644 --- a/core/sessionmanager.lua +++ b/core/sessionmanager.lua @@ -129,7 +129,7 @@ local function make_authenticated(session, username) if session.type == "c2s_unauthed" then session.type = "c2s_unbound"; end - session.log("info", "Authenticated as %s@%s", username or "(unknown)", session.host or "(unknown)"); + session.log("info", "Authenticated as %s@%s", username, session.host or "(unknown)"); return true; end -- cgit v1.2.3 From 2bb05d010d9b237a088bd9b4c997451407191d3f Mon Sep 17 00:00:00 2001 From: Michel Le Bihan Date: Mon, 3 Jun 2019 20:51:15 +0200 Subject: mod_admin_telnet: Collect array from Bosh connections when appending to connection list Fixes #1356 --- plugins/mod_admin_telnet.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 0fbd3ff9..fa03840b 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -597,7 +597,7 @@ end local function show_c2s(callback) local c2s = array.collect(values(module:shared"/*/c2s/sessions")); - c2s:append(values(module:shared"/*/bosh/sessions")); + c2s:append(array.collect(values(module:shared"/*/bosh/sessions"))); c2s:sort(function(a, b) if a.host == b.host then if a.username == b.username then -- cgit v1.2.3 From 66bb4ab4494853abfad5a53854355f03d4b7ae6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Fri, 7 Jun 2019 11:36:13 +0100 Subject: prosodyctl: Created a custom function, 'test', that prints back a welcoming message --- prosodyctl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/prosodyctl b/prosodyctl index f5786ff9..db7b0dc4 100755 --- a/prosodyctl +++ b/prosodyctl @@ -85,6 +85,10 @@ local prosodyctl_timeout = (configmanager.get("*", "prosodyctl_timeout") or 5) * local commands = {}; local command = table.remove(arg, 1); +function commands.test() + show_message [[Well, hello there!]] +end + function commands.adduser(arg) if not arg[1] or arg[1] == "--help" then show_usage([[adduser JID]], [[Create the specified user account in Prosody]]); -- cgit v1.2.3 From 83ca8aef547856ab0caead6f9f999f642818de95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Fri, 7 Jun 2019 11:46:19 +0100 Subject: prosodyctl: The 'test' function now also prints which plugins are enabled --- prosodyctl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/prosodyctl b/prosodyctl index db7b0dc4..4036c59a 100755 --- a/prosodyctl +++ b/prosodyctl @@ -87,6 +87,8 @@ local command = table.remove(arg, 1); function commands.test() show_message [[Well, hello there!]] + --show_message [[Enabled Plugins: ], modulemanager.get_modules_for_host()] + show_warning("Trying to peek at the plugin directory: '%s'", modulemanager.get_modules_for_host()) end function commands.adduser(arg) -- cgit v1.2.3 From cf25afe5b143eea8b1ca7d74462965d6e8184b2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Fri, 7 Jun 2019 13:00:40 +0100 Subject: prosodyctl: Added the 'local_plugins' command function, which prints back a list of locally available plugins --- prosodyctl | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/prosodyctl b/prosodyctl index 4036c59a..00d24b1c 100755 --- a/prosodyctl +++ b/prosodyctl @@ -87,10 +87,25 @@ local command = table.remove(arg, 1); function commands.test() show_message [[Well, hello there!]] - --show_message [[Enabled Plugins: ], modulemanager.get_modules_for_host()] show_warning("Trying to peek at the plugin directory: '%s'", modulemanager.get_modules_for_host()) end +function commands.local_plugins() + local directory = "./plugins" + local i, t, popen = 0, {}, io.popen + local pfile = popen('ls -a "'..directory..'"') + for filename in pfile:lines() do + if filename == "." or filename == ".." then + i = i + 1 + else + i = i + 1 + t[i] = filename + show_warning("%s", t[i]) + end + end + pfile:close() +end + function commands.adduser(arg) if not arg[1] or arg[1] == "--help" then show_usage([[adduser JID]], [[Create the specified user account in Prosody]]); -- cgit v1.2.3 From 8025f98427a23a64f3dd303aedde6fcc8d5ea688 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Fri, 7 Jun 2019 13:09:01 +0100 Subject: prosodyctl: Renamed the command function 'test' to 'enabled_plugins', which now only shows the plugins, in a list --- prosodyctl | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/prosodyctl b/prosodyctl index 00d24b1c..a7300973 100755 --- a/prosodyctl +++ b/prosodyctl @@ -85,9 +85,10 @@ local prosodyctl_timeout = (configmanager.get("*", "prosodyctl_timeout") or 5) * local commands = {}; local command = table.remove(arg, 1); -function commands.test() - show_message [[Well, hello there!]] - show_warning("Trying to peek at the plugin directory: '%s'", modulemanager.get_modules_for_host()) +function commands.enabled_plugins() + for module in modulemanager.get_modules_for_host() do + show_warning("%s", module) + end end function commands.local_plugins() -- cgit v1.2.3 From 9935e64396d1c225083a1d8585027183a1cf0151 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 16 Jun 2019 22:02:53 +0200 Subject: mod_pep: Log node name instead of payload Having the node logged is more useful and less problematic for privacy --- plugins/mod_pep.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_pep.lua b/plugins/mod_pep.lua index f06f1753..54f0451d 100644 --- a/plugins/mod_pep.lua +++ b/plugins/mod_pep.lua @@ -159,7 +159,7 @@ local function get_broadcaster(username) end for jid in pairs(jids) do - module:log("debug", "Sending notification to %s from %s: %s", jid, user_bare, tostring(item)); + module:log("debug", "Sending notification to %s from %s for node %s", jid, user_bare, node); message.attr.to = jid; module:send(message); end -- cgit v1.2.3 From 693a64eedfe149f36b2e3b6374dd41fbfd41cad0 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 19 Jun 2019 08:51:39 +0200 Subject: MUC: Reflow event tables to improve readability Also makes it easier to read diffs of added fields. --- plugins/muc/muc.lib.lua | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index c828d17d..34961558 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -391,7 +391,11 @@ function room_mt:handle_kickable(origin, stanza) -- luacheck: ignore 212 end self:publicise_occupant_status(new_occupant or occupant, x); if is_last_session then - module:fire_event("muc-occupant-left", {room = self; nick = occupant.nick; occupant = occupant;}); + module:fire_event("muc-occupant-left", { + room = self; + nick = occupant.nick; + occupant = occupant; + }); end return true; end @@ -868,7 +872,11 @@ function room_mt:clear(x) end for occupant in pairs(occupants_updated) do self:publicise_occupant_status(occupant, x); - module:fire_event("muc-occupant-left", { room = self; nick = occupant.nick; occupant = occupant;}); + module:fire_event("muc-occupant-left", { + room = self; + nick = occupant.nick; + occupant = occupant; + }); end end @@ -1316,7 +1324,11 @@ function room_mt:set_affiliation(actor, jid, affiliation, reason, data) for occupant, old_role in pairs(occupants_updated) do self:publicise_occupant_status(occupant, x, nil, actor, reason); if occupant.role == nil then - module:fire_event("muc-occupant-left", {room = self; nick = occupant.nick; occupant = occupant;}); + module:fire_event("muc-occupant-left", { + room = self; + nick = occupant.nick; + occupant = occupant; + }); elseif is_semi_anonymous and (old_role == "moderator" and occupant.role ~= "moderator") or (old_role ~= "moderator" and occupant.role == "moderator") then -- Has gained or lost moderator status @@ -1432,7 +1444,11 @@ function room_mt:set_role(actor, occupant_jid, role, reason) self:save_occupant(occupant); self:publicise_occupant_status(occupant, x, nil, actor, reason); if role == nil then - module:fire_event("muc-occupant-left", {room = self; nick = occupant.nick; occupant = occupant;}); + module:fire_event("muc-occupant-left", { + room = self; + nick = occupant.nick; + occupant = occupant; + }); end return true; end -- cgit v1.2.3 From 587c2382b0d7940927a0f6b7b87809eca890f9b7 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 19 Jun 2019 19:16:09 +0200 Subject: util.dependencies: Increase Lua version to warn about to 5.4 No significant problems have been encountered with Lua 5.3 itself, so apart from some odd problems in LuaExpat it seems about time to declare it ready. --- util/dependencies.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/dependencies.lua b/util/dependencies.lua index 7c7b938e..84e2dd5c 100644 --- a/util/dependencies.lua +++ b/util/dependencies.lua @@ -140,7 +140,7 @@ local function check_dependencies() end local function log_warnings() - if _VERSION > "Lua 5.2" then + if _VERSION > "Lua 5.3" then prosody.log("warn", "Support for %s is experimental, please report any issues", _VERSION); end local ssl = softreq"ssl"; -- cgit v1.2.3 From 992d18aa62af23bbb5849e728a9b298b556e2cb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Thu, 20 Jun 2019 19:17:47 +0100 Subject: prosodyctl: Implemented the 'list' command, which is a bridge to 'luarocks list' --- prosodyctl | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/prosodyctl b/prosodyctl index a7300973..d726c028 100755 --- a/prosodyctl +++ b/prosodyctl @@ -85,6 +85,19 @@ local prosodyctl_timeout = (configmanager.get("*", "prosodyctl_timeout") or 5) * local commands = {}; local command = table.remove(arg, 1); +function commands.list(arg) + -- Need to think about the case with many flags + local flag="--tree=" + -- I'm considering the flag is the first, but there can be many flags + if arg[1] and arg[1]:sub(1, #flag) == flag then + local dir = arg[1]:match("=(.+)$") + -- These extra double brackets allow us to correctly process names with spaces + os.execute("luarocks list --tree=".."'"..dir.."'") + else + os.execute("luarocks list --tree="..prosody.paths.data.."/rocks") + end +end + function commands.enabled_plugins() for module in modulemanager.get_modules_for_host() do show_warning("%s", module) -- cgit v1.2.3 From 57f55730e7837846a0065088885fa101b1a67404 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 20 Jun 2019 21:44:43 +0200 Subject: mod_blocklist: Add comment to clarify some logic --- plugins/mod_blocklist.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/mod_blocklist.lua b/plugins/mod_blocklist.lua index 18d28be2..cf8aad80 100644 --- a/plugins/mod_blocklist.lua +++ b/plugins/mod_blocklist.lua @@ -189,6 +189,7 @@ local function edit_blocklist(event) if is_blocking then for jid in pairs(send_unavailable) do + -- Check that this JID isn't already blocked, i.e. this is not a change if not blocklist[jid] then for _, session in pairs(sessions[username].sessions) do if session.presence then -- cgit v1.2.3 From 1fda45e5860bbfd748ec163b4f7ebe03d1ba7c7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Fri, 21 Jun 2019 19:03:04 +0100 Subject: prosodyctl: Implemented a command bridge to the 'luarocks-admin add' command, called 'admin_add' --- prosodyctl | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/prosodyctl b/prosodyctl index d726c028..efe6394e 100755 --- a/prosodyctl +++ b/prosodyctl @@ -87,7 +87,7 @@ local command = table.remove(arg, 1); function commands.list(arg) -- Need to think about the case with many flags - local flag="--tree=" + local flag = "--tree=" -- I'm considering the flag is the first, but there can be many flags if arg[1] and arg[1]:sub(1, #flag) == flag then local dir = arg[1]:match("=(.+)$") @@ -98,6 +98,22 @@ function commands.list(arg) end end +function commands.admin_add(arg) + local modules, tree, server, refresh = "", "", "", "" + for i, _ in ipairs(arg) do + if arg[i]:sub(1, #"--tree=") == "--tree=" then + tree = arg[i].." " + elseif arg[i]:sub(1, #"--server=") == "--server=" then + server = arg[i].." " + elseif arg[i]:sub(1, #"--no-refresh") == "--no-refresh" then + refresh = arg[i].." " + else + modules=modules..arg[i].." " + end + end + os.execute("luarocks-admin "..tree.."add "..server..refresh..modules) +end + function commands.enabled_plugins() for module in modulemanager.get_modules_for_host() do show_warning("%s", module) -- cgit v1.2.3 From b12ba155400cc80ff269a100e18bcbdd53f86041 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Tue, 25 Jun 2019 00:52:12 +0100 Subject: prosodyctl: Changed the command 'admin_add' to 'admin_operation', which will be called by both add/remove operations --- prosodyctl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/prosodyctl b/prosodyctl index efe6394e..56fa3f24 100755 --- a/prosodyctl +++ b/prosodyctl @@ -98,7 +98,7 @@ function commands.list(arg) end end -function commands.admin_add(arg) +function admin_operation(operation, arg) local modules, tree, server, refresh = "", "", "", "" for i, _ in ipairs(arg) do if arg[i]:sub(1, #"--tree=") == "--tree=" then @@ -111,7 +111,7 @@ function commands.admin_add(arg) modules=modules..arg[i].." " end end - os.execute("luarocks-admin "..tree.."add "..server..refresh..modules) + os.execute("luarocks-admin "..tree..operation..server..refresh..modules) end function commands.enabled_plugins() -- cgit v1.2.3 From 6021b5871292b692043b80334ecbe1133965519d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Tue, 25 Jun 2019 12:02:37 +0100 Subject: prosodyctl: Added the 'admin_add' command --- prosodyctl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/prosodyctl b/prosodyctl index 56fa3f24..1180d039 100755 --- a/prosodyctl +++ b/prosodyctl @@ -114,6 +114,10 @@ function admin_operation(operation, arg) os.execute("luarocks-admin "..tree..operation..server..refresh..modules) end +function commands.admin_add(arg) + admin_operation("add ", arg) +end + function commands.enabled_plugins() for module in modulemanager.get_modules_for_host() do show_warning("%s", module) -- cgit v1.2.3 From 2aa2aff974345346344416ca1f91f0c5190dd93b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Tue, 25 Jun 2019 12:03:20 +0100 Subject: prosodyctl: Added the 'admin_remove' command --- prosodyctl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/prosodyctl b/prosodyctl index 1180d039..75b36e34 100755 --- a/prosodyctl +++ b/prosodyctl @@ -118,6 +118,10 @@ function commands.admin_add(arg) admin_operation("add ", arg) end +function commands.admin_remove(arg) + admin_operation("remove ", arg) +end + function commands.enabled_plugins() for module in modulemanager.get_modules_for_host() do show_warning("%s", module) -- cgit v1.2.3 From 122bde2f2c4816e89bd02be36e0e9f1ee31f7557 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Tue, 25 Jun 2019 13:20:54 +0100 Subject: util.prosodyctl: Moved the 'admin_operation' function from prosodyctl to here --- prosodyctl | 16 ---------------- util/prosodyctl.lua | 17 +++++++++++++++++ 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/prosodyctl b/prosodyctl index 75b36e34..05f711d7 100755 --- a/prosodyctl +++ b/prosodyctl @@ -98,22 +98,6 @@ function commands.list(arg) end end -function admin_operation(operation, arg) - local modules, tree, server, refresh = "", "", "", "" - for i, _ in ipairs(arg) do - if arg[i]:sub(1, #"--tree=") == "--tree=" then - tree = arg[i].." " - elseif arg[i]:sub(1, #"--server=") == "--server=" then - server = arg[i].." " - elseif arg[i]:sub(1, #"--no-refresh") == "--no-refresh" then - refresh = arg[i].." " - else - modules=modules..arg[i].." " - end - end - os.execute("luarocks-admin "..tree..operation..server..refresh..modules) -end - function commands.admin_add(arg) admin_operation("add ", arg) end diff --git a/util/prosodyctl.lua b/util/prosodyctl.lua index 9b627bde..e2bc4369 100644 --- a/util/prosodyctl.lua +++ b/util/prosodyctl.lua @@ -278,6 +278,22 @@ local function reload() return true; end +local function admin_operation(operation, arg) + local modules, tree, server, refresh = "", "", "", "" + for i, _ in ipairs(arg) do + if arg[i]:sub(1, #"--tree=") == "--tree=" then + tree = arg[i].." " + elseif arg[i]:sub(1, #"--server=") == "--server=" then + server = arg[i].." " + elseif arg[i]:sub(1, #"--no-refresh") == "--no-refresh" then + refresh = arg[i].." " + else + modules=modules..arg[i].." " + end + end + os.execute("luarocks-admin "..tree..operation..server..refresh..modules) +end + return { show_message = show_message; show_warning = show_message; @@ -297,4 +313,5 @@ return { start = start; stop = stop; reload = reload; + admin_operation = admin_operation; }; -- cgit v1.2.3 From 79191ed8f28bc8a26b782f3ef3e28459029c9f76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Tue, 25 Jun 2019 13:22:22 +0100 Subject: prosodyctl: Corrected the calls to the recently moved function 'admin_operation' --- prosodyctl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/prosodyctl b/prosodyctl index 05f711d7..2c786b95 100755 --- a/prosodyctl +++ b/prosodyctl @@ -99,11 +99,11 @@ function commands.list(arg) end function commands.admin_add(arg) - admin_operation("add ", arg) + prosodyctl.admin_operation("add ", arg) end function commands.admin_remove(arg) - admin_operation("remove ", arg) + prosodyctl.admin_operation("remove ", arg) end function commands.enabled_plugins() -- cgit v1.2.3 From fce54a8010c647e28274b54e169f92c814855649 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Wed, 26 Jun 2019 16:46:51 +0100 Subject: prosodyctl: Complemented my functions with return calls, when possible --- prosodyctl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/prosodyctl b/prosodyctl index 2c786b95..8cf90048 100755 --- a/prosodyctl +++ b/prosodyctl @@ -10,7 +10,6 @@ -- prosodyctl - command-line controller for Prosody XMPP server -- Will be modified by configure script if run -- - CFG_SOURCEDIR=CFG_SOURCEDIR or os.getenv("PROSODY_SRCDIR"); CFG_CONFIGDIR=CFG_CONFIGDIR or os.getenv("PROSODY_CFGDIR"); CFG_PLUGINDIR=CFG_PLUGINDIR or os.getenv("PROSODY_PLUGINDIR"); @@ -93,17 +92,21 @@ function commands.list(arg) local dir = arg[1]:match("=(.+)$") -- These extra double brackets allow us to correctly process names with spaces os.execute("luarocks list --tree=".."'"..dir.."'") + return 0; else os.execute("luarocks list --tree="..prosody.paths.data.."/rocks") + return 0; end end function commands.admin_add(arg) prosodyctl.admin_operation("add ", arg) + return 0; end function commands.admin_remove(arg) prosodyctl.admin_operation("remove ", arg) + return 0; end function commands.enabled_plugins() @@ -126,6 +129,7 @@ function commands.local_plugins() end end pfile:close() + return 0 end function commands.adduser(arg) -- cgit v1.2.3 From 11a354176f389c17770db9a11f45cd3cdcfe7472 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Thu, 27 Jun 2019 18:00:11 +0100 Subject: prosodyctl: added help support to all my functions --- prosodyctl | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/prosodyctl b/prosodyctl index 8cf90048..7cae908e 100755 --- a/prosodyctl +++ b/prosodyctl @@ -85,6 +85,10 @@ local commands = {}; local command = table.remove(arg, 1); function commands.list(arg) + if not arg[1] or arg[1] == "--help" then + show_usage([[list]], [[Shows installed rocks]]); + return 1; + end -- Need to think about the case with many flags local flag = "--tree=" -- I'm considering the flag is the first, but there can be many flags @@ -109,13 +113,21 @@ function commands.admin_remove(arg) return 0; end -function commands.enabled_plugins() +function commands.enabled_plugins(arg) + if arg[1] == "--help" then + show_usage([[enabled_plugins]], [[Shows plugins currently enabled on prosody]]); + return 1; + end for module in modulemanager.get_modules_for_host() do show_warning("%s", module) end end -function commands.local_plugins() +function commands.local_plugins(arg) + if arg[1] == "--help" then + show_usage([[local_plugins]], [[Shows plugins currently available for prosody, locally]]); + return 1; + end local directory = "./plugins" local i, t, popen = 0, {}, io.popen local pfile = popen('ls -a "'..directory..'"') @@ -1397,7 +1409,8 @@ local command_runner = async.runner(function () print("Where COMMAND may be one of:\n"); local hidden_commands = require "util.set".new{ "register", "unregister", "addplugin" }; - local commands_order = { "adduser", "passwd", "deluser", "start", "stop", "restart", "reload", "about" }; + local commands_order = { "adduser", "passwd", "deluser", "start", "stop", "restart", "reload", "about", "local_plugins", "enabled_plugins", + "admin_add", "admin_remove", "list", }; local done = {}; -- cgit v1.2.3 From 7555016ac24cb681b2c443324652a5cc9df61799 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Thu, 27 Jun 2019 18:01:36 +0100 Subject: util.prosodyctl: Added help support to 'admin_operation' --- util/prosodyctl.lua | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/util/prosodyctl.lua b/util/prosodyctl.lua index e2bc4369..c2235e85 100644 --- a/util/prosodyctl.lua +++ b/util/prosodyctl.lua @@ -279,6 +279,11 @@ local function reload() end local function admin_operation(operation, arg) + if arg[1] == "--help" then + print(" admin-"..operation) + print(" "..operation.."plugins from a server (repository)") + return 1; + end local modules, tree, server, refresh = "", "", "", "" for i, _ in ipairs(arg) do if arg[i]:sub(1, #"--tree=") == "--tree=" then -- cgit v1.2.3 From 7c291a6e154b0325ca4173995810f7b0831f7480 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Tue, 16 Jul 2019 10:05:31 -0700 Subject: prosodyctl: Added the 'get_modules' commands --- prosodyctl | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/prosodyctl b/prosodyctl index 7cae908e..0bde4478 100755 --- a/prosodyctl +++ b/prosodyctl @@ -84,6 +84,35 @@ local prosodyctl_timeout = (configmanager.get("*", "prosodyctl_timeout") or 5) * local commands = {}; local command = table.remove(arg, 1); +-- This function receives no arguments. It clones all the plugins from prosody's plugin repository +function commands.get_modules(arg) + if arg[1] == "--help" then + show_usage([[get_modules]], [[Downloads all available modules]]); + return 1 + end + if os.execute '[ -e "./downloaded_modules" ]' then + print("The modules have already been imported") + print("Do you want to re-import?(Y/N)") + local answer = io.read() + if answer == "Y" then + print("Deleting previous imports") + os.execute("rm -rf downloaded_modules") + print("Downloading plugins") + os.execute("hg clone https://hg.prosody.im/prosody-modules/ downloaded_modules") + print("Done!") + return 0 + else + print("We keep what we have then!") + return 0 + end + else + print("Getting all the available modules") + os.execute("hg clone https://hg.prosody.im/prosody-modules/ downloaded_modules") + print("Done!") + return 0 + end +end + function commands.list(arg) if not arg[1] or arg[1] == "--help" then show_usage([[list]], [[Shows installed rocks]]); -- cgit v1.2.3 From db90fc5da5288330ba1780d387ab7bdafc3210ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Tue, 16 Jul 2019 10:08:12 -0700 Subject: prosodyctl: Added the 'write_rockspec' function --- prosodyctl | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/prosodyctl b/prosodyctl index 0bde4478..baea969a 100755 --- a/prosodyctl +++ b/prosodyctl @@ -113,6 +113,22 @@ function commands.get_modules(arg) end end +-- Function to write rockspecs from a module at working_directory/downloaded_modules +-- Receives the module's name as an argument +-- The rockspec is saved inside its module's folder +function commands.write_rockspec(arg) + if arg[1] == "--help" then + show_usage([[write_rockspec]], [[Picks up a module and writes an initial rockspec]]); + return 1 + end + print("Writing rockspec for "..arg[1]) + os.execute("luarocks write_rockspec "..arg[1].." ./downloaded_modules/"..arg[1]) + print("Rockspec created! Moving it into the ./downloaded_modules/"..arg[1].." folder") + os.execute("mv "..arg[1].."-scm-1.rockspec ./downloaded_modules/"..arg[1]) + print("Done!") + return 0 +end + function commands.list(arg) if not arg[1] or arg[1] == "--help" then show_usage([[list]], [[Shows installed rocks]]); -- cgit v1.2.3 From 8fa4d6e25003773649665a5c9d4924b723a21fb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Tue, 16 Jul 2019 10:26:44 -0700 Subject: prosodyctl: Added the 'make' function --- prosodyctl | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/prosodyctl b/prosodyctl index baea969a..7f7b515e 100755 --- a/prosodyctl +++ b/prosodyctl @@ -129,6 +129,17 @@ function commands.write_rockspec(arg) return 0 end +-- Command to install a rockspec with local sources +-- The module is installed at the plugins folder +function commands.make(arg) + if arg[1] == "--help" then + show_usage([[make]], [[Installs a module with sources available locally]]); + return 1 + end + os.execute("cd downloaded_modules/"..arg[1].." && luarocks --tree='../../plugins' make "..arg[1].."-scm-1.rockspec") + return 0 +end + function commands.list(arg) if not arg[1] or arg[1] == "--help" then show_usage([[list]], [[Shows installed rocks]]); -- cgit v1.2.3 From 5b190cf27618c048a69021dae3415727a50630e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Tue, 16 Jul 2019 10:34:13 -0700 Subject: prosodyctl: Added the 'remove' command --- prosodyctl | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/prosodyctl b/prosodyctl index 7f7b515e..dac0c1f9 100755 --- a/prosodyctl +++ b/prosodyctl @@ -140,6 +140,19 @@ function commands.make(arg) return 0 end +-- Command to remove a rockspec +-- Receives as an argument the name of the plugin to be removed from the plugins folder +function commands.remove(arg) + if arg[1] == "--help" then + show_usage([[make]], [[Removes a module installed in the wroking directory's plugins folder]]); + return 1 + end + print("Removing "..arg[1].." from ./plugins") + os.execute("luarocks --tree='./plugins' remove "..arg[1]) + print("Done!") + return 0 +end + function commands.list(arg) if not arg[1] or arg[1] == "--help" then show_usage([[list]], [[Shows installed rocks]]); -- cgit v1.2.3 From 344f333e82da80a66a357ba5b271043b504ada2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Wed, 17 Jul 2019 03:20:08 -0700 Subject: prosodyctl: Added the 'install' command --- prosodyctl | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/prosodyctl b/prosodyctl index dac0c1f9..f04c1d46 100755 --- a/prosodyctl +++ b/prosodyctl @@ -153,6 +153,16 @@ function commands.remove(arg) return 0 end +function commands.install(arg) + if arg[1] == "--help" then + show_usage([[make]], [[Installs a rockspec/rock from a specified server]]); + return 1 + end + print("Installing module "..arg[1].." locally, from luarocks repo") + os.execute("luarocks --tree='./plugins' install "..arg[1]) + return 0 +end + function commands.list(arg) if not arg[1] or arg[1] == "--help" then show_usage([[list]], [[Shows installed rocks]]); -- cgit v1.2.3 From 184b2a45b80a03728038643d0c5ec1cfd70e617f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Wed, 17 Jul 2019 03:31:02 -0700 Subject: prosodyctl: Improved the 'list' command --- prosodyctl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/prosodyctl b/prosodyctl index f04c1d46..750c84e4 100755 --- a/prosodyctl +++ b/prosodyctl @@ -164,7 +164,7 @@ function commands.install(arg) end function commands.list(arg) - if not arg[1] or arg[1] == "--help" then + if arg[1] == "--help" then show_usage([[list]], [[Shows installed rocks]]); return 1; end @@ -174,10 +174,10 @@ function commands.list(arg) if arg[1] and arg[1]:sub(1, #flag) == flag then local dir = arg[1]:match("=(.+)$") -- These extra double brackets allow us to correctly process names with spaces - os.execute("luarocks list --tree=".."'"..dir.."'") + os.execute("luarocks list --tree='"..dir.."'") return 0; else - os.execute("luarocks list --tree="..prosody.paths.data.."/rocks") + os.execute("luarocks list --tree="..prosody.paths.plugins) return 0; end end -- cgit v1.2.3 From 213539ccd90da28856875c64bd41868cb818f2f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Wed, 17 Jul 2019 03:45:26 -0700 Subject: prosodyctl: The 'install' command can now recognize the flag '--tree' --- prosodyctl | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/prosodyctl b/prosodyctl index 750c84e4..a1dd1614 100755 --- a/prosodyctl +++ b/prosodyctl @@ -158,9 +158,20 @@ function commands.install(arg) show_usage([[make]], [[Installs a rockspec/rock from a specified server]]); return 1 end - print("Installing module "..arg[1].." locally, from luarocks repo") - os.execute("luarocks --tree='./plugins' install "..arg[1]) - return 0 + -- Need to think about the case with many flags + local flag = "--tree=" + -- I'm considering the flag is the first, but there can be many flags + if arg[1] and arg[1]:sub(1, #flag) == flag then + local dir = arg[1]:match("=(.+)$") + print("Installing module "..arg[2].." at "..dir..", from luarocks repo") + -- These extra double brackets allow us to correctly process names with spaces + os.execute("luarocks install --tree='"..dir.."' "..arg[2]) + return 0; + else + print("Installing module "..arg[1].." locally, from luarocks repo") + os.execute("luarocks --tree='"..prosody.paths.plugins.."' install "..arg[1]) + return 0 + end end function commands.list(arg) -- cgit v1.2.3 From efb9164e517ff5d95a6b82fd3cf416535b2fe3af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Wed, 17 Jul 2019 09:03:15 -0700 Subject: prosodyctl: Improved the 'remove' command --- prosodyctl | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/prosodyctl b/prosodyctl index a1dd1614..af1fea54 100755 --- a/prosodyctl +++ b/prosodyctl @@ -147,10 +147,20 @@ function commands.remove(arg) show_usage([[make]], [[Removes a module installed in the wroking directory's plugins folder]]); return 1 end - print("Removing "..arg[1].." from ./plugins") - os.execute("luarocks --tree='./plugins' remove "..arg[1]) - print("Done!") - return 0 + local flag = "--tree=" + -- I'm considering the flag is the first, but there can be many flags + if arg[1] and arg[1]:sub(1, #flag) == flag then + local dir = arg[1]:match("=(.+)$") + print("Removing module "..arg[2].." at "..dir..", from luarocks repo") + -- These extra double brackets allow us to correctly process names with spaces + os.execute("luarocks remove --tree='"..dir.."' "..arg[2]) + return 0; + else + print("Removing "..arg[1].." from ./plugins") + os.execute("luarocks --tree='"..prosody.paths.plugins.."' remove "..arg[1]) + print("Done!") + return 0 + end end function commands.install(arg) -- cgit v1.2.3 From 5fa25a8910bcb012944b3abbc23b5904e8df46a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Wed, 17 Jul 2019 09:12:32 -0700 Subject: prosodyctl: Added missing semicolons to some return calls --- prosodyctl | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/prosodyctl b/prosodyctl index af1fea54..dea588a2 100755 --- a/prosodyctl +++ b/prosodyctl @@ -88,7 +88,7 @@ local command = table.remove(arg, 1); function commands.get_modules(arg) if arg[1] == "--help" then show_usage([[get_modules]], [[Downloads all available modules]]); - return 1 + return 1; end if os.execute '[ -e "./downloaded_modules" ]' then print("The modules have already been imported") @@ -100,16 +100,16 @@ function commands.get_modules(arg) print("Downloading plugins") os.execute("hg clone https://hg.prosody.im/prosody-modules/ downloaded_modules") print("Done!") - return 0 + return 0; else print("We keep what we have then!") - return 0 + return 0; end else print("Getting all the available modules") os.execute("hg clone https://hg.prosody.im/prosody-modules/ downloaded_modules") print("Done!") - return 0 + return 0; end end @@ -119,14 +119,14 @@ end function commands.write_rockspec(arg) if arg[1] == "--help" then show_usage([[write_rockspec]], [[Picks up a module and writes an initial rockspec]]); - return 1 + return 1; end print("Writing rockspec for "..arg[1]) os.execute("luarocks write_rockspec "..arg[1].." ./downloaded_modules/"..arg[1]) print("Rockspec created! Moving it into the ./downloaded_modules/"..arg[1].." folder") os.execute("mv "..arg[1].."-scm-1.rockspec ./downloaded_modules/"..arg[1]) print("Done!") - return 0 + return 0; end -- Command to install a rockspec with local sources @@ -134,10 +134,10 @@ end function commands.make(arg) if arg[1] == "--help" then show_usage([[make]], [[Installs a module with sources available locally]]); - return 1 + return 1; end os.execute("cd downloaded_modules/"..arg[1].." && luarocks --tree='../../plugins' make "..arg[1].."-scm-1.rockspec") - return 0 + return 0; end -- Command to remove a rockspec @@ -145,7 +145,7 @@ end function commands.remove(arg) if arg[1] == "--help" then show_usage([[make]], [[Removes a module installed in the wroking directory's plugins folder]]); - return 1 + return 1; end local flag = "--tree=" -- I'm considering the flag is the first, but there can be many flags @@ -159,14 +159,14 @@ function commands.remove(arg) print("Removing "..arg[1].." from ./plugins") os.execute("luarocks --tree='"..prosody.paths.plugins.."' remove "..arg[1]) print("Done!") - return 0 + return 0; end end function commands.install(arg) if arg[1] == "--help" then show_usage([[make]], [[Installs a rockspec/rock from a specified server]]); - return 1 + return 1; end -- Need to think about the case with many flags local flag = "--tree=" @@ -180,7 +180,7 @@ function commands.install(arg) else print("Installing module "..arg[1].." locally, from luarocks repo") os.execute("luarocks --tree='"..prosody.paths.plugins.."' install "..arg[1]) - return 0 + return 0; end end @@ -219,7 +219,7 @@ function commands.enabled_plugins(arg) return 1; end for module in modulemanager.get_modules_for_host() do - show_warning("%s", module) + show_warning("%s", module) end end @@ -241,7 +241,7 @@ function commands.local_plugins(arg) end end pfile:close() - return 0 + return 0; end function commands.adduser(arg) -- cgit v1.2.3 From cbeea7c2875da04e63c97727be48eaf689fc1745 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Wed, 17 Jul 2019 09:47:37 -0700 Subject: prosodyctl: Removed trailing whitespaces --- prosodyctl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prosodyctl b/prosodyctl index dea588a2..477ddcfe 100755 --- a/prosodyctl +++ b/prosodyctl @@ -219,7 +219,7 @@ function commands.enabled_plugins(arg) return 1; end for module in modulemanager.get_modules_for_host() do - show_warning("%s", module) + show_warning("%s", module) end end -- cgit v1.2.3 From aaac83292f531d4f5b540bc8e35e5e8999ae21cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Sat, 20 Jul 2019 12:41:06 -0700 Subject: prosodyctl: Corrected indentation on my code --- prosodyctl | 212 ++++++++++++++++++++++++++++++------------------------------- 1 file changed, 106 insertions(+), 106 deletions(-) diff --git a/prosodyctl b/prosodyctl index 477ddcfe..244a1a24 100755 --- a/prosodyctl +++ b/prosodyctl @@ -86,137 +86,137 @@ local command = table.remove(arg, 1); -- This function receives no arguments. It clones all the plugins from prosody's plugin repository function commands.get_modules(arg) - if arg[1] == "--help" then + if arg[1] == "--help" then show_usage([[get_modules]], [[Downloads all available modules]]); - return 1; - end - if os.execute '[ -e "./downloaded_modules" ]' then - print("The modules have already been imported") - print("Do you want to re-import?(Y/N)") - local answer = io.read() - if answer == "Y" then - print("Deleting previous imports") - os.execute("rm -rf downloaded_modules") - print("Downloading plugins") - os.execute("hg clone https://hg.prosody.im/prosody-modules/ downloaded_modules") - print("Done!") - return 0; - else - print("We keep what we have then!") - return 0; - end - else - print("Getting all the available modules") - os.execute("hg clone https://hg.prosody.im/prosody-modules/ downloaded_modules") - print("Done!") - return 0; - end + return 1; + end + if os.execute '[ -e "./downloaded_modules" ]' then + print("The modules have already been imported") + print("Do you want to re-import?(Y/N)") + local answer = io.read() + if answer == "Y" then + print("Deleting previous imports") + os.execute("rm -rf downloaded_modules") + print("Downloading plugins") + os.execute("hg clone https://hg.prosody.im/prosody-modules/ downloaded_modules") + print("Done!") + return 0; + else + print("We keep what we have then!") + return 0; + end + else + print("Getting all the available modules") + os.execute("hg clone https://hg.prosody.im/prosody-modules/ downloaded_modules") + print("Done!") + return 0; + end end -- Function to write rockspecs from a module at working_directory/downloaded_modules -- Receives the module's name as an argument -- The rockspec is saved inside its module's folder function commands.write_rockspec(arg) - if arg[1] == "--help" then - show_usage([[write_rockspec]], [[Picks up a module and writes an initial rockspec]]); - return 1; - end - print("Writing rockspec for "..arg[1]) - os.execute("luarocks write_rockspec "..arg[1].." ./downloaded_modules/"..arg[1]) - print("Rockspec created! Moving it into the ./downloaded_modules/"..arg[1].." folder") - os.execute("mv "..arg[1].."-scm-1.rockspec ./downloaded_modules/"..arg[1]) - print("Done!") - return 0; + if arg[1] == "--help" then + show_usage([[write_rockspec]], [[Picks up a module and writes an initial rockspec]]); + return 1; + end + print("Writing rockspec for "..arg[1]) + os.execute("luarocks write_rockspec "..arg[1].." ./downloaded_modules/"..arg[1]) + print("Rockspec created! Moving it into the ./downloaded_modules/"..arg[1].." folder") + os.execute("mv "..arg[1].."-scm-1.rockspec ./downloaded_modules/"..arg[1]) + print("Done!") + return 0; end -- Command to install a rockspec with local sources -- The module is installed at the plugins folder function commands.make(arg) - if arg[1] == "--help" then - show_usage([[make]], [[Installs a module with sources available locally]]); - return 1; - end - os.execute("cd downloaded_modules/"..arg[1].." && luarocks --tree='../../plugins' make "..arg[1].."-scm-1.rockspec") - return 0; + if arg[1] == "--help" then + show_usage([[make]], [[Installs a module with sources available locally]]); + return 1; + end + os.execute("cd downloaded_modules/"..arg[1].." && luarocks --tree='../../plugins' make "..arg[1].."-scm-1.rockspec") + return 0; end -- Command to remove a rockspec -- Receives as an argument the name of the plugin to be removed from the plugins folder function commands.remove(arg) - if arg[1] == "--help" then - show_usage([[make]], [[Removes a module installed in the wroking directory's plugins folder]]); - return 1; - end - local flag = "--tree=" - -- I'm considering the flag is the first, but there can be many flags - if arg[1] and arg[1]:sub(1, #flag) == flag then - local dir = arg[1]:match("=(.+)$") - print("Removing module "..arg[2].." at "..dir..", from luarocks repo") - -- These extra double brackets allow us to correctly process names with spaces - os.execute("luarocks remove --tree='"..dir.."' "..arg[2]) - return 0; - else - print("Removing "..arg[1].." from ./plugins") - os.execute("luarocks --tree='"..prosody.paths.plugins.."' remove "..arg[1]) - print("Done!") - return 0; - end + if arg[1] == "--help" then + show_usage([[make]], [[Removes a module installed in the wroking directory's plugins folder]]); + return 1; + end + local flag = "--tree=" + -- I'm considering the flag is the first, but there can be many flags + if arg[1] and arg[1]:sub(1, #flag) == flag then + local dir = arg[1]:match("=(.+)$") + print("Removing module "..arg[2].." at "..dir..", from luarocks repo") + -- These extra double brackets allow us to correctly process names with spaces + os.execute("luarocks remove --tree='"..dir.."' "..arg[2]) + return 0; + else + print("Removing "..arg[1].." from ./plugins") + os.execute("luarocks --tree='"..prosody.paths.plugins.."' remove "..arg[1]) + print("Done!") + return 0; + end end function commands.install(arg) - if arg[1] == "--help" then - show_usage([[make]], [[Installs a rockspec/rock from a specified server]]); - return 1; - end - -- Need to think about the case with many flags - local flag = "--tree=" - -- I'm considering the flag is the first, but there can be many flags - if arg[1] and arg[1]:sub(1, #flag) == flag then - local dir = arg[1]:match("=(.+)$") - print("Installing module "..arg[2].." at "..dir..", from luarocks repo") - -- These extra double brackets allow us to correctly process names with spaces - os.execute("luarocks install --tree='"..dir.."' "..arg[2]) - return 0; - else - print("Installing module "..arg[1].." locally, from luarocks repo") - os.execute("luarocks --tree='"..prosody.paths.plugins.."' install "..arg[1]) - return 0; - end + if arg[1] == "--help" then + show_usage([[make]], [[Installs a rockspec/rock from a specified server]]); + return 1; + end + -- Need to think about the case with many flags + local flag = "--tree=" + -- I'm considering the flag is the first, but there can be many flags + if arg[1] and arg[1]:sub(1, #flag) == flag then + local dir = arg[1]:match("=(.+)$") + print("Installing module "..arg[2].." at "..dir..", from luarocks repo") + -- These extra double brackets allow us to correctly process names with spaces + os.execute("luarocks install --tree='"..dir.."' "..arg[2]) + return 0; + else + print("Installing module "..arg[1].." locally, from luarocks repo") + os.execute("luarocks --tree='"..prosody.paths.plugins.."' install "..arg[1]) + return 0; + end end function commands.list(arg) - if arg[1] == "--help" then + if arg[1] == "--help" then show_usage([[list]], [[Shows installed rocks]]); return 1; end - -- Need to think about the case with many flags - local flag = "--tree=" - -- I'm considering the flag is the first, but there can be many flags - if arg[1] and arg[1]:sub(1, #flag) == flag then - local dir = arg[1]:match("=(.+)$") - -- These extra double brackets allow us to correctly process names with spaces - os.execute("luarocks list --tree='"..dir.."'") - return 0; - else - os.execute("luarocks list --tree="..prosody.paths.plugins) - return 0; - end + -- Need to think about the case with many flags + local flag = "--tree=" + -- I'm considering the flag is the first, but there can be many flags + if arg[1] and arg[1]:sub(1, #flag) == flag then + local dir = arg[1]:match("=(.+)$") + -- These extra double brackets allow us to correctly process names with spaces + os.execute("luarocks list --tree='"..dir.."'") + return 0; + else + os.execute("luarocks list --tree="..prosody.paths.plugins) + return 0; + end end function commands.admin_add(arg) - prosodyctl.admin_operation("add ", arg) - return 0; + prosodyctl.admin_operation("add ", arg) + return 0; end function commands.admin_remove(arg) - prosodyctl.admin_operation("remove ", arg) - return 0; + prosodyctl.admin_operation("remove ", arg) + return 0; end function commands.enabled_plugins(arg) - if arg[1] == "--help" then - show_usage([[enabled_plugins]], [[Shows plugins currently enabled on prosody]]); - return 1; + if arg[1] == "--help" then + show_usage([[enabled_plugins]], [[Shows plugins currently enabled on prosody]]); + return 1; end for module in modulemanager.get_modules_for_host() do show_warning("%s", module) @@ -224,24 +224,24 @@ function commands.enabled_plugins(arg) end function commands.local_plugins(arg) - if arg[1] == "--help" then - show_usage([[local_plugins]], [[Shows plugins currently available for prosody, locally]]); - return 1; + if arg[1] == "--help" then + show_usage([[local_plugins]], [[Shows plugins currently available for prosody, locally]]); + return 1; end local directory = "./plugins" - local i, t, popen = 0, {}, io.popen - local pfile = popen('ls -a "'..directory..'"') - for filename in pfile:lines() do + local i, t, popen = 0, {}, io.popen + local pfile = popen('ls -a "'..directory..'"') + for filename in pfile:lines() do if filename == "." or filename == ".." then i = i + 1 else i = i + 1 t[i] = filename show_warning("%s", t[i]) - end - end - pfile:close() - return 0; + end + end + pfile:close() + return 0; end function commands.adduser(arg) -- cgit v1.2.3 From c21b60857f9f0f6b15e9f95ee11ec6291b56c574 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Sat, 20 Jul 2019 12:41:31 -0700 Subject: util.prosodyctl: Corrected indentation on my code --- util/prosodyctl.lua | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/util/prosodyctl.lua b/util/prosodyctl.lua index c2235e85..6f7bf065 100644 --- a/util/prosodyctl.lua +++ b/util/prosodyctl.lua @@ -279,24 +279,24 @@ local function reload() end local function admin_operation(operation, arg) - if arg[1] == "--help" then - print(" admin-"..operation) - print(" "..operation.."plugins from a server (repository)") - return 1; - end - local modules, tree, server, refresh = "", "", "", "" - for i, _ in ipairs(arg) do - if arg[i]:sub(1, #"--tree=") == "--tree=" then - tree = arg[i].." " - elseif arg[i]:sub(1, #"--server=") == "--server=" then - server = arg[i].." " - elseif arg[i]:sub(1, #"--no-refresh") == "--no-refresh" then - refresh = arg[i].." " - else - modules=modules..arg[i].." " - end - end - os.execute("luarocks-admin "..tree..operation..server..refresh..modules) + if arg[1] == "--help" then + print(" admin-"..operation) + print(" "..operation.."plugins from a server (repository)") + return 1; + end + local modules, tree, server, refresh = "", "", "", "" + for i, _ in ipairs(arg) do + if arg[i]:sub(1, #"--tree=") == "--tree=" then + tree = arg[i].." " + elseif arg[i]:sub(1, #"--server=") == "--server=" then + server = arg[i].." " + elseif arg[i]:sub(1, #"--no-refresh") == "--no-refresh" then + refresh = arg[i].." " + else + modules=modules..arg[i].." " + end + end + os.execute("luarocks-admin "..tree..operation..server..refresh..modules) end return { -- cgit v1.2.3 From 9da55591971c39bbc5f3c8d24b521be89dc51897 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Tue, 23 Jul 2019 07:21:08 -0700 Subject: prosodyctl: Corrected output printed by the remove command --- prosodyctl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/prosodyctl b/prosodyctl index 244a1a24..4831150c 100755 --- a/prosodyctl +++ b/prosodyctl @@ -151,12 +151,12 @@ function commands.remove(arg) -- I'm considering the flag is the first, but there can be many flags if arg[1] and arg[1]:sub(1, #flag) == flag then local dir = arg[1]:match("=(.+)$") - print("Removing module "..arg[2].." at "..dir..", from luarocks repo") + print("Removing module "..arg[2].." at "..dir) -- These extra double brackets allow us to correctly process names with spaces os.execute("luarocks remove --tree='"..dir.."' "..arg[2]) return 0; else - print("Removing "..arg[1].." from ./plugins") + print("Removing "..arg[1].." from "..prosody.paths.plugins) os.execute("luarocks --tree='"..prosody.paths.plugins.."' remove "..arg[1]) print("Done!") return 0; -- cgit v1.2.3 From 94ba83d8e657ba8c934c1875dac049f2f4f38101 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Tue, 23 Jul 2019 08:36:06 -0700 Subject: prosodyctl: Corrected the outputs from the install command --- prosodyctl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/prosodyctl b/prosodyctl index 4831150c..820a274f 100755 --- a/prosodyctl +++ b/prosodyctl @@ -165,7 +165,7 @@ end function commands.install(arg) if arg[1] == "--help" then - show_usage([[make]], [[Installs a rockspec/rock from a specified server]]); + show_usage([[make]], [[Installs a prosody/luarocks plugin]]); return 1; end -- Need to think about the case with many flags @@ -173,12 +173,12 @@ function commands.install(arg) -- I'm considering the flag is the first, but there can be many flags if arg[1] and arg[1]:sub(1, #flag) == flag then local dir = arg[1]:match("=(.+)$") - print("Installing module "..arg[2].." at "..dir..", from luarocks repo") + print("Installing module "..arg[2].." at "..dir) -- These extra double brackets allow us to correctly process names with spaces os.execute("luarocks install --tree='"..dir.."' "..arg[2]) return 0; else - print("Installing module "..arg[1].." locally, from luarocks repo") + print("Installing module "..arg[1].." at "..prosody.paths.plugins) os.execute("luarocks --tree='"..prosody.paths.plugins.."' install "..arg[1]) return 0; end -- cgit v1.2.3 From d5026a6f633fddc5c459a6e63b3568c073029387 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Tue, 23 Jul 2019 08:48:31 -0700 Subject: prosodyctl: The install command is now also checking a specified remote server --- prosodyctl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/prosodyctl b/prosodyctl index 820a274f..f485f517 100755 --- a/prosodyctl +++ b/prosodyctl @@ -175,11 +175,13 @@ function commands.install(arg) local dir = arg[1]:match("=(.+)$") print("Installing module "..arg[2].." at "..dir) -- These extra double brackets allow us to correctly process names with spaces - os.execute("luarocks install --tree='"..dir.."' "..arg[2]) + os.execute("luarocks --tree='"..dir.."' --server='http://localhost/' install "..arg[2]) return 0; else print("Installing module "..arg[1].." at "..prosody.paths.plugins) - os.execute("luarocks --tree='"..prosody.paths.plugins.."' install "..arg[1]) + -- I've build a local server to upload some new rockspecs, like mod_smacks'. We can replace this server by one from + -- prosody's, where we can oficially disbrute rocks/rockspecs for all modules + os.execute("luarocks --tree='"..prosody.paths.plugins.."' --server='http://localhost/' install "..arg[1]) return 0; end end -- cgit v1.2.3 From 94ddde2e9e756f420fd6f6d271acbc1f7df630b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Tue, 23 Jul 2019 10:24:55 -0700 Subject: util.prosodyctl: Added the show_module_configuration_help function --- util/prosodyctl.lua | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/util/prosodyctl.lua b/util/prosodyctl.lua index 6f7bf065..84f65665 100644 --- a/util/prosodyctl.lua +++ b/util/prosodyctl.lua @@ -39,6 +39,16 @@ local function show_usage(usage, desc) end end +local function show_module_configuration_help(mod_name) + print("Done.") + print("If you installed a prosody plugin, don't forget to add its name under the 'modules_enabled' section inside your configuration file.") + print("Depending on the module, there might be further configuration steps required.") + print("") + print("More info about: ") + print(" modules_enabled: https://prosody.im/doc/modules_enabled") + print(" "..mod_name..": https://modules.prosody.im/"..mod_name..".html") +end + local function getchar(n) local stty_ret = os.execute("stty raw -echo 2>/dev/null"); local ok, char; @@ -303,6 +313,7 @@ return { show_message = show_message; show_warning = show_message; show_usage = show_usage; + show_module_configuration_help = show_module_configuration_help; getchar = getchar; getline = getline; getpass = getpass; -- cgit v1.2.3 From a935d786f3ad27b543eae92d8ac404442c2cc6ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Tue, 23 Jul 2019 10:26:00 -0700 Subject: prosodyctl: The install command now prints output regarding module configuration --- prosodyctl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/prosodyctl b/prosodyctl index f485f517..26d6a850 100755 --- a/prosodyctl +++ b/prosodyctl @@ -76,6 +76,7 @@ local show_usage = prosodyctl.show_usage; local show_yesno = prosodyctl.show_yesno; local show_prompt = prosodyctl.show_prompt; local read_password = prosodyctl.read_password; +local show_module_configuration_help = prosodyctl.show_module_configuration_help; local jid_split = require "util.jid".prepped_split; @@ -176,12 +177,14 @@ function commands.install(arg) print("Installing module "..arg[2].." at "..dir) -- These extra double brackets allow us to correctly process names with spaces os.execute("luarocks --tree='"..dir.."' --server='http://localhost/' install "..arg[2]) + show_module_configuration_help(arg[2]); return 0; else print("Installing module "..arg[1].." at "..prosody.paths.plugins) -- I've build a local server to upload some new rockspecs, like mod_smacks'. We can replace this server by one from -- prosody's, where we can oficially disbrute rocks/rockspecs for all modules os.execute("luarocks --tree='"..prosody.paths.plugins.."' --server='http://localhost/' install "..arg[1]) + show_module_configuration_help(arg[1]); return 0; end end -- cgit v1.2.3 From ecd9e8f5d771da2fffa4721df0880756c2203714 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Tue, 23 Jul 2019 13:27:19 -0700 Subject: prosodyctl: Created a temporary function, that automatically sets up a repo with rockspecs for prosody modules --- prosodyctl | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/prosodyctl b/prosodyctl index 26d6a850..13494bb8 100755 --- a/prosodyctl +++ b/prosodyctl @@ -85,6 +85,43 @@ local prosodyctl_timeout = (configmanager.get("*", "prosodyctl_timeout") or 5) * local commands = {}; local command = table.remove(arg, 1); +function commands.magic(arg) + --print("Getting all the available modules") + --os.execute("hg clone https://hg.prosody.im/prosody-modules/ downloaded_modules") + local i, popen = 0, io.popen + local flag = "mod_" + os.execute("mkdir repository") + local pfile = popen('ls -a "'..arg[1]..'"') + for filename in pfile:lines() do + i = i + 1 + if filename:sub(1, #flag) == flag then + local file = io.open("repository/"..filename.."-scm-1.rockspec", "w") + file:write('package = "'..filename..'"', '\n') + file:write('version = "scm-1"', '\n') + file:write('source = {', '\n') + file:write('\turl = "hg+https://hg.prosody.im/prosody-modules",', '\n') + file:write('\tdir = "prosody-modules"', '\n') + file:write('}', '\n') + file:write('description = {', '\n') + file:write('\thomepage = "https://prosody.im/",', '\n') + file:write('\tlicense = "MIT"', '\n') + file:write('}', '\n') + file:write('dependencies = {', '\n') + file:write('\t"lua >= 5.1"', '\n') + file:write('}', '\n') + file:write('build = {', '\n') + file:write('\ttype = "builtin",', '\n') + file:write('\tmodules = {', '\n') + file:write('\t\t["'..filename..'.'..filename..'"] = "'..filename..'/'..filename..'.lua"', '\n') + file:write('\t}', '\n') + file:write('}', '\n') + file:close() + end + end + pfile:close() + os.execute("cd repository/ && luarocks-admin make_manifest ./ && chmod -R 644 ./*") +end + -- This function receives no arguments. It clones all the plugins from prosody's plugin repository function commands.get_modules(arg) if arg[1] == "--help" then -- cgit v1.2.3 From 447b7f8731db9be3caab0e8500caa2cafe168ac4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Wed, 24 Jul 2019 04:24:29 -0700 Subject: prosodyctl: Removed the development commands magic, get_modules and write_rockspec --- prosodyctl | 82 -------------------------------------------------------------- 1 file changed, 82 deletions(-) diff --git a/prosodyctl b/prosodyctl index 13494bb8..420337da 100755 --- a/prosodyctl +++ b/prosodyctl @@ -85,88 +85,6 @@ local prosodyctl_timeout = (configmanager.get("*", "prosodyctl_timeout") or 5) * local commands = {}; local command = table.remove(arg, 1); -function commands.magic(arg) - --print("Getting all the available modules") - --os.execute("hg clone https://hg.prosody.im/prosody-modules/ downloaded_modules") - local i, popen = 0, io.popen - local flag = "mod_" - os.execute("mkdir repository") - local pfile = popen('ls -a "'..arg[1]..'"') - for filename in pfile:lines() do - i = i + 1 - if filename:sub(1, #flag) == flag then - local file = io.open("repository/"..filename.."-scm-1.rockspec", "w") - file:write('package = "'..filename..'"', '\n') - file:write('version = "scm-1"', '\n') - file:write('source = {', '\n') - file:write('\turl = "hg+https://hg.prosody.im/prosody-modules",', '\n') - file:write('\tdir = "prosody-modules"', '\n') - file:write('}', '\n') - file:write('description = {', '\n') - file:write('\thomepage = "https://prosody.im/",', '\n') - file:write('\tlicense = "MIT"', '\n') - file:write('}', '\n') - file:write('dependencies = {', '\n') - file:write('\t"lua >= 5.1"', '\n') - file:write('}', '\n') - file:write('build = {', '\n') - file:write('\ttype = "builtin",', '\n') - file:write('\tmodules = {', '\n') - file:write('\t\t["'..filename..'.'..filename..'"] = "'..filename..'/'..filename..'.lua"', '\n') - file:write('\t}', '\n') - file:write('}', '\n') - file:close() - end - end - pfile:close() - os.execute("cd repository/ && luarocks-admin make_manifest ./ && chmod -R 644 ./*") -end - --- This function receives no arguments. It clones all the plugins from prosody's plugin repository -function commands.get_modules(arg) - if arg[1] == "--help" then - show_usage([[get_modules]], [[Downloads all available modules]]); - return 1; - end - if os.execute '[ -e "./downloaded_modules" ]' then - print("The modules have already been imported") - print("Do you want to re-import?(Y/N)") - local answer = io.read() - if answer == "Y" then - print("Deleting previous imports") - os.execute("rm -rf downloaded_modules") - print("Downloading plugins") - os.execute("hg clone https://hg.prosody.im/prosody-modules/ downloaded_modules") - print("Done!") - return 0; - else - print("We keep what we have then!") - return 0; - end - else - print("Getting all the available modules") - os.execute("hg clone https://hg.prosody.im/prosody-modules/ downloaded_modules") - print("Done!") - return 0; - end -end - --- Function to write rockspecs from a module at working_directory/downloaded_modules --- Receives the module's name as an argument --- The rockspec is saved inside its module's folder -function commands.write_rockspec(arg) - if arg[1] == "--help" then - show_usage([[write_rockspec]], [[Picks up a module and writes an initial rockspec]]); - return 1; - end - print("Writing rockspec for "..arg[1]) - os.execute("luarocks write_rockspec "..arg[1].." ./downloaded_modules/"..arg[1]) - print("Rockspec created! Moving it into the ./downloaded_modules/"..arg[1].." folder") - os.execute("mv "..arg[1].."-scm-1.rockspec ./downloaded_modules/"..arg[1]) - print("Done!") - return 0; -end - -- Command to install a rockspec with local sources -- The module is installed at the plugins folder function commands.make(arg) -- cgit v1.2.3 From 66afe675789f0161a1eb4a73605ed0e03015323a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Wed, 24 Jul 2019 04:31:39 -0700 Subject: make_repo: This script creates a repository with prosody plugins' rockspecs --- make_repo.lua | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 make_repo.lua diff --git a/make_repo.lua b/make_repo.lua new file mode 100644 index 00000000..5b5e6234 --- /dev/null +++ b/make_repo.lua @@ -0,0 +1,44 @@ +print("Getting all the available modules") +if os.execute '[ -e "./downloaded_modules" ]' then + os.execute("rm -rf downloaded_modules") +end +os.execute("hg clone https://hg.prosody.im/prosody-modules/ downloaded_modules") +local i, popen = 0, io.popen +local flag = "mod_" +if os.execute '[ -e "./repository" ]' then + os.execute("mkdir repository") +end +local pfile = popen('ls -a "downloaded_modules"') +for filename in pfile:lines() do + i = i + 1 + if filename:sub(1, #flag) == flag then + local file = io.open("repository/"..filename.."-scm-1.rockspec", "w") + file:write('package = "'..filename..'"', '\n') + file:write('version = "scm-1"', '\n') + file:write('source = {', '\n') + file:write('\turl = "hg+https://hg.prosody.im/prosody-modules",', '\n') + file:write('\tdir = "prosody-modules"', '\n') + file:write('}', '\n') + file:write('description = {', '\n') + file:write('\thomepage = "https://prosody.im/",', '\n') + file:write('\tlicense = "MIT"', '\n') + file:write('}', '\n') + file:write('dependencies = {', '\n') + file:write('\t"lua >= 5.1"', '\n') + file:write('}', '\n') + file:write('build = {', '\n') + file:write('\ttype = "builtin",', '\n') + file:write('\tmodules = {', '\n') + file:write('\t\t["'..filename..'.'..filename..'"] = "'..filename..'/'..filename..'.lua"', '\n') + file:write('\t}', '\n') + file:write('}', '\n') + file:close() + end +end +pfile:close() +os.execute("cd repository/ && luarocks-admin make_manifest ./ && chmod -R 644 ./*") +print("") +print("Done!. Modules' sources are locally available at ./downloaded_modules") +print("Repository is available at ./repository") +print("The repository contains all of prosody modules' respective rockspecs, as well as manifest files and an html Index") +print("You can now either point your server to this folder, or copy its contents to another configured folder.") -- cgit v1.2.3 From d38ebec46613682f618c932b1ab16b6ca5f9e6cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Wed, 24 Jul 2019 04:42:45 -0700 Subject: prosodyctl: Removed the make, admin_add and admin_remove commands --- prosodyctl | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/prosodyctl b/prosodyctl index 420337da..14db6c29 100755 --- a/prosodyctl +++ b/prosodyctl @@ -85,17 +85,6 @@ local prosodyctl_timeout = (configmanager.get("*", "prosodyctl_timeout") or 5) * local commands = {}; local command = table.remove(arg, 1); --- Command to install a rockspec with local sources --- The module is installed at the plugins folder -function commands.make(arg) - if arg[1] == "--help" then - show_usage([[make]], [[Installs a module with sources available locally]]); - return 1; - end - os.execute("cd downloaded_modules/"..arg[1].." && luarocks --tree='../../plugins' make "..arg[1].."-scm-1.rockspec") - return 0; -end - -- Command to remove a rockspec -- Receives as an argument the name of the plugin to be removed from the plugins folder function commands.remove(arg) @@ -163,16 +152,6 @@ function commands.list(arg) end end -function commands.admin_add(arg) - prosodyctl.admin_operation("add ", arg) - return 0; -end - -function commands.admin_remove(arg) - prosodyctl.admin_operation("remove ", arg) - return 0; -end - function commands.enabled_plugins(arg) if arg[1] == "--help" then show_usage([[enabled_plugins]], [[Shows plugins currently enabled on prosody]]); -- cgit v1.2.3 From c84fa6fd262025b7394f4e51992ffaacdb9c6eb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Wed, 24 Jul 2019 04:44:44 -0700 Subject: util.prosodyctl: Removed the admin_operation command --- util/prosodyctl.lua | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/util/prosodyctl.lua b/util/prosodyctl.lua index 84f65665..5e19937e 100644 --- a/util/prosodyctl.lua +++ b/util/prosodyctl.lua @@ -288,27 +288,6 @@ local function reload() return true; end -local function admin_operation(operation, arg) - if arg[1] == "--help" then - print(" admin-"..operation) - print(" "..operation.."plugins from a server (repository)") - return 1; - end - local modules, tree, server, refresh = "", "", "", "" - for i, _ in ipairs(arg) do - if arg[i]:sub(1, #"--tree=") == "--tree=" then - tree = arg[i].." " - elseif arg[i]:sub(1, #"--server=") == "--server=" then - server = arg[i].." " - elseif arg[i]:sub(1, #"--no-refresh") == "--no-refresh" then - refresh = arg[i].." " - else - modules=modules..arg[i].." " - end - end - os.execute("luarocks-admin "..tree..operation..server..refresh..modules) -end - return { show_message = show_message; show_warning = show_message; @@ -329,5 +308,4 @@ return { start = start; stop = stop; reload = reload; - admin_operation = admin_operation; }; -- cgit v1.2.3 From b291e91de25427e836f74cb41ab9ee644fd510c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Wed, 24 Jul 2019 04:53:02 -0700 Subject: prosodyctl: Removed the admin_add and admin_remove from the commands_order variable --- prosodyctl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/prosodyctl b/prosodyctl index 14db6c29..ae73ed54 100755 --- a/prosodyctl +++ b/prosodyctl @@ -1448,8 +1448,8 @@ local command_runner = async.runner(function () print("Where COMMAND may be one of:\n"); local hidden_commands = require "util.set".new{ "register", "unregister", "addplugin" }; - local commands_order = { "adduser", "passwd", "deluser", "start", "stop", "restart", "reload", "about", "local_plugins", "enabled_plugins", - "admin_add", "admin_remove", "list", }; + local commands_order = { "adduser", "passwd", "deluser", "start", "stop", "restart", "reload", "about", "local_plugins", + "enabled_plugins", "list"}; local done = {}; -- cgit v1.2.3 From e1969065c487c60fa6df682d2aca5614d2dae690 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Wed, 24 Jul 2019 04:59:46 -0700 Subject: prosodyctl: Added the install and remove arguments to the reorganized commands_order variable --- prosodyctl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/prosodyctl b/prosodyctl index ae73ed54..905e00cf 100755 --- a/prosodyctl +++ b/prosodyctl @@ -1448,8 +1448,8 @@ local command_runner = async.runner(function () print("Where COMMAND may be one of:\n"); local hidden_commands = require "util.set".new{ "register", "unregister", "addplugin" }; - local commands_order = { "adduser", "passwd", "deluser", "start", "stop", "restart", "reload", "about", "local_plugins", - "enabled_plugins", "list"}; + local commands_order = { "install", "remove", "list", "enabled_plugins", "local_plugins","adduser", "passwd", "deluser", "start", "stop", "restart", + "reload", "about", "list" }; local done = {}; -- cgit v1.2.3 From f736aec6b2c3062216b6f7481bd80a2444ffc36f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Wed, 24 Jul 2019 05:01:35 -0700 Subject: prosodyctl: Corrected the remove and install commands' order --- prosodyctl | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/prosodyctl b/prosodyctl index 905e00cf..2fd3084d 100755 --- a/prosodyctl +++ b/prosodyctl @@ -85,50 +85,50 @@ local prosodyctl_timeout = (configmanager.get("*", "prosodyctl_timeout") or 5) * local commands = {}; local command = table.remove(arg, 1); --- Command to remove a rockspec --- Receives as an argument the name of the plugin to be removed from the plugins folder -function commands.remove(arg) +function commands.install(arg) if arg[1] == "--help" then - show_usage([[make]], [[Removes a module installed in the wroking directory's plugins folder]]); + show_usage([[make]], [[Installs a prosody/luarocks plugin]]); return 1; end + -- Need to think about the case with many flags local flag = "--tree=" -- I'm considering the flag is the first, but there can be many flags if arg[1] and arg[1]:sub(1, #flag) == flag then local dir = arg[1]:match("=(.+)$") - print("Removing module "..arg[2].." at "..dir) + print("Installing module "..arg[2].." at "..dir) -- These extra double brackets allow us to correctly process names with spaces - os.execute("luarocks remove --tree='"..dir.."' "..arg[2]) + os.execute("luarocks --tree='"..dir.."' --server='http://localhost/' install "..arg[2]) + show_module_configuration_help(arg[2]); return 0; else - print("Removing "..arg[1].." from "..prosody.paths.plugins) - os.execute("luarocks --tree='"..prosody.paths.plugins.."' remove "..arg[1]) - print("Done!") + print("Installing module "..arg[1].." at "..prosody.paths.plugins) + -- I've build a local server to upload some new rockspecs, like mod_smacks'. We can replace this server by one from + -- prosody's, where we can oficially disbrute rocks/rockspecs for all modules + os.execute("luarocks --tree='"..prosody.paths.plugins.."' --server='http://localhost/' install "..arg[1]) + show_module_configuration_help(arg[1]); return 0; end end -function commands.install(arg) +-- Command to remove a rockspec +-- Receives as an argument the name of the plugin to be removed from the plugins folder +function commands.remove(arg) if arg[1] == "--help" then - show_usage([[make]], [[Installs a prosody/luarocks plugin]]); + show_usage([[make]], [[Removes a module installed in the wroking directory's plugins folder]]); return 1; end - -- Need to think about the case with many flags local flag = "--tree=" -- I'm considering the flag is the first, but there can be many flags if arg[1] and arg[1]:sub(1, #flag) == flag then local dir = arg[1]:match("=(.+)$") - print("Installing module "..arg[2].." at "..dir) + print("Removing module "..arg[2].." at "..dir) -- These extra double brackets allow us to correctly process names with spaces - os.execute("luarocks --tree='"..dir.."' --server='http://localhost/' install "..arg[2]) - show_module_configuration_help(arg[2]); + os.execute("luarocks remove --tree='"..dir.."' "..arg[2]) return 0; else - print("Installing module "..arg[1].." at "..prosody.paths.plugins) - -- I've build a local server to upload some new rockspecs, like mod_smacks'. We can replace this server by one from - -- prosody's, where we can oficially disbrute rocks/rockspecs for all modules - os.execute("luarocks --tree='"..prosody.paths.plugins.."' --server='http://localhost/' install "..arg[1]) - show_module_configuration_help(arg[1]); + print("Removing "..arg[1].." from "..prosody.paths.plugins) + os.execute("luarocks --tree='"..prosody.paths.plugins.."' remove "..arg[1]) + print("Done!") return 0; end end -- cgit v1.2.3 From 46ae143d9a6a51b72365b8d2781e7d45d8e332c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Wed, 24 Jul 2019 05:07:57 -0700 Subject: prosodyctl: Removed/rewrote comments at the install, remove and list commands --- prosodyctl | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/prosodyctl b/prosodyctl index 2fd3084d..be6df959 100755 --- a/prosodyctl +++ b/prosodyctl @@ -90,13 +90,11 @@ function commands.install(arg) show_usage([[make]], [[Installs a prosody/luarocks plugin]]); return 1; end - -- Need to think about the case with many flags + -- I'm considering this optional flag comes first local flag = "--tree=" - -- I'm considering the flag is the first, but there can be many flags if arg[1] and arg[1]:sub(1, #flag) == flag then local dir = arg[1]:match("=(.+)$") print("Installing module "..arg[2].." at "..dir) - -- These extra double brackets allow us to correctly process names with spaces os.execute("luarocks --tree='"..dir.."' --server='http://localhost/' install "..arg[2]) show_module_configuration_help(arg[2]); return 0; @@ -110,19 +108,16 @@ function commands.install(arg) end end --- Command to remove a rockspec --- Receives as an argument the name of the plugin to be removed from the plugins folder function commands.remove(arg) if arg[1] == "--help" then show_usage([[make]], [[Removes a module installed in the wroking directory's plugins folder]]); return 1; end local flag = "--tree=" - -- I'm considering the flag is the first, but there can be many flags + -- I'm considering this optional flag comes first if arg[1] and arg[1]:sub(1, #flag) == flag then local dir = arg[1]:match("=(.+)$") print("Removing module "..arg[2].." at "..dir) - -- These extra double brackets allow us to correctly process names with spaces os.execute("luarocks remove --tree='"..dir.."' "..arg[2]) return 0; else @@ -138,12 +133,10 @@ function commands.list(arg) show_usage([[list]], [[Shows installed rocks]]); return 1; end - -- Need to think about the case with many flags local flag = "--tree=" - -- I'm considering the flag is the first, but there can be many flags + -- I'm considering this optional flag comes first if arg[1] and arg[1]:sub(1, #flag) == flag then local dir = arg[1]:match("=(.+)$") - -- These extra double brackets allow us to correctly process names with spaces os.execute("luarocks list --tree='"..dir.."'") return 0; else -- cgit v1.2.3 From 20f233099eaf3122df6acc163bcd119f6b877c89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Wed, 24 Jul 2019 10:37:01 -0700 Subject: util.startup: Now it also loads default or configured paths to custom plugin directories and creates them --- util/startup.lua | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/util/startup.lua b/util/startup.lua index 7a1a95aa..65a131fe 100644 --- a/util/startup.lua +++ b/util/startup.lua @@ -227,6 +227,7 @@ end function startup.setup_plugindir() local custom_plugin_paths = config.get("*", "plugin_paths"); + local installer_plugin_paths = config.get("*", "installer_plugin_paths") or {"custom_plugins"}; if custom_plugin_paths then local path_sep = package.config:sub(3,3); -- path1;path2;path3;defaultpath... @@ -234,6 +235,17 @@ function startup.setup_plugindir() CFG_PLUGINDIR = table.concat(custom_plugin_paths, path_sep)..path_sep..(CFG_PLUGINDIR or "plugins"); prosody.paths.plugins = CFG_PLUGINDIR; end + if installer_plugin_paths then + for path, _ in ipairs(installer_plugin_paths) do + if os.execute('[ -d "'..installer_plugin_paths[path]..'" ]') ~= 0 then + os.execute("mkdir "..installer_plugin_paths[path]) + end + end + local path_sep = package.config:sub(3,3); + -- luacheck: ignore 111 + CFG_PLUGINDIR = table.concat(installer_plugin_paths, path_sep)..path_sep..(CFG_PLUGINDIR or "plugins"); + prosody.paths.plugins = CFG_PLUGINDIR; + end end function startup.chdir() -- cgit v1.2.3 From 27e711c5876b0008fa871b26787c0c134e90d744 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Wed, 24 Jul 2019 10:43:28 -0700 Subject: prosodyctl: The install command now performs its job at a dedicated folder for custom plugins --- prosodyctl | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/prosodyctl b/prosodyctl index be6df959..1c702576 100755 --- a/prosodyctl +++ b/prosodyctl @@ -90,6 +90,12 @@ function commands.install(arg) show_usage([[make]], [[Installs a prosody/luarocks plugin]]); return 1; end + local installer_plugin_path + -- I'm considering that we are using just one path to custom plugins, and it is the first in prosody.paths.plugins, for now + for path in prosody.paths.plugins:gmatch("[^;]+") do + installer_plugin_path = path + break + end -- I'm considering this optional flag comes first local flag = "--tree=" if arg[1] and arg[1]:sub(1, #flag) == flag then @@ -99,10 +105,10 @@ function commands.install(arg) show_module_configuration_help(arg[2]); return 0; else - print("Installing module "..arg[1].." at "..prosody.paths.plugins) + print("Installing module "..arg[1].." at "..installer_plugin_path) -- I've build a local server to upload some new rockspecs, like mod_smacks'. We can replace this server by one from -- prosody's, where we can oficially disbrute rocks/rockspecs for all modules - os.execute("luarocks --tree='"..prosody.paths.plugins.."' --server='http://localhost/' install "..arg[1]) + os.execute("luarocks --tree='"..installer_plugin_path.."' --server='http://localhost/' install "..arg[1]) show_module_configuration_help(arg[1]); return 0; end -- cgit v1.2.3 From cf695f88f1bc15c7ccb69c65aee555579ad80da8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Wed, 24 Jul 2019 10:46:05 -0700 Subject: prosodyctl: Updated the remove command to use the new directory for custom plugins --- prosodyctl | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/prosodyctl b/prosodyctl index 1c702576..33f58fc1 100755 --- a/prosodyctl +++ b/prosodyctl @@ -119,6 +119,11 @@ function commands.remove(arg) show_usage([[make]], [[Removes a module installed in the wroking directory's plugins folder]]); return 1; end + -- I'm considering that we are using just one path to custom plugins, and it is the first in prosody.paths.plugins, for now + for path in prosody.paths.plugins:gmatch("[^;]+") do + installer_plugin_path = path + break + end local flag = "--tree=" -- I'm considering this optional flag comes first if arg[1] and arg[1]:sub(1, #flag) == flag then @@ -127,8 +132,8 @@ function commands.remove(arg) os.execute("luarocks remove --tree='"..dir.."' "..arg[2]) return 0; else - print("Removing "..arg[1].." from "..prosody.paths.plugins) - os.execute("luarocks --tree='"..prosody.paths.plugins.."' remove "..arg[1]) + print("Removing "..arg[1].." from "..installer_plugin_path) + os.execute("luarocks --tree='"..installer_plugin_path.."' remove "..arg[1]) print("Done!") return 0; end -- cgit v1.2.3 From f1a27b4554175c04fbd3c5ffedbc5b9e6ad40967 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Wed, 24 Jul 2019 10:48:20 -0700 Subject: prosodyctl: Updated the list command, to use the new directory for custom plugins --- prosodyctl | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/prosodyctl b/prosodyctl index 33f58fc1..777b2c30 100755 --- a/prosodyctl +++ b/prosodyctl @@ -144,6 +144,11 @@ function commands.list(arg) show_usage([[list]], [[Shows installed rocks]]); return 1; end + -- I'm considering that we are using just one path to custom plugins, and it is the first in prosody.paths.plugins, for now + for path in prosody.paths.plugins:gmatch("[^;]+") do + installer_plugin_path = path + break + end local flag = "--tree=" -- I'm considering this optional flag comes first if arg[1] and arg[1]:sub(1, #flag) == flag then @@ -151,7 +156,7 @@ function commands.list(arg) os.execute("luarocks list --tree='"..dir.."'") return 0; else - os.execute("luarocks list --tree="..prosody.paths.plugins) + os.execute("luarocks list --tree="..installer_plugin_path) return 0; end end -- cgit v1.2.3 From 52f963350f11ac6317bcf2ac1e184a6c0f7b9c68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Wed, 24 Jul 2019 11:20:35 -0700 Subject: util.prosodyctl: Created the helper function get_path_custom_plugins --- util/prosodyctl.lua | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/util/prosodyctl.lua b/util/prosodyctl.lua index 5e19937e..fbeb9540 100644 --- a/util/prosodyctl.lua +++ b/util/prosodyctl.lua @@ -288,6 +288,14 @@ local function reload() return true; end +local function get_path_custom_plugins(plugin_paths) + -- I'm considering that we are using just one path to custom plugins, and it is the first in prosody.paths.plugins, for now + -- luacheck: ignore 512 + for path in plugin_paths:gmatch("[^;]+") do + return path + end +end + return { show_message = show_message; show_warning = show_message; @@ -308,4 +316,5 @@ return { start = start; stop = stop; reload = reload; + get_path_custom_plugins = get_path_custom_plugins; }; -- cgit v1.2.3 From 843f28994734b5791eb1f011dd60de87e2b2fa7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Wed, 24 Jul 2019 11:26:29 -0700 Subject: prosodyctl: Custom plugins paths are now retrieved by an helper function --- prosodyctl | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/prosodyctl b/prosodyctl index 777b2c30..c50900f2 100755 --- a/prosodyctl +++ b/prosodyctl @@ -90,12 +90,7 @@ function commands.install(arg) show_usage([[make]], [[Installs a prosody/luarocks plugin]]); return 1; end - local installer_plugin_path - -- I'm considering that we are using just one path to custom plugins, and it is the first in prosody.paths.plugins, for now - for path in prosody.paths.plugins:gmatch("[^;]+") do - installer_plugin_path = path - break - end + local installer_plugin_path = prosodyctl.get_path_custom_plugins(prosody.paths.plugins) -- I'm considering this optional flag comes first local flag = "--tree=" if arg[1] and arg[1]:sub(1, #flag) == flag then @@ -120,10 +115,7 @@ function commands.remove(arg) return 1; end -- I'm considering that we are using just one path to custom plugins, and it is the first in prosody.paths.plugins, for now - for path in prosody.paths.plugins:gmatch("[^;]+") do - installer_plugin_path = path - break - end + local installer_plugin_path = prosodyctl.get_path_custom_plugins(prosody.paths.plugins) local flag = "--tree=" -- I'm considering this optional flag comes first if arg[1] and arg[1]:sub(1, #flag) == flag then @@ -145,10 +137,7 @@ function commands.list(arg) return 1; end -- I'm considering that we are using just one path to custom plugins, and it is the first in prosody.paths.plugins, for now - for path in prosody.paths.plugins:gmatch("[^;]+") do - installer_plugin_path = path - break - end + local installer_plugin_path = prosodyctl.get_path_custom_plugins(prosody.paths.plugins) local flag = "--tree=" -- I'm considering this optional flag comes first if arg[1] and arg[1]:sub(1, #flag) == flag then -- cgit v1.2.3 From d93db0b931b21e041baf0badb7b5224ebced7a34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Wed, 24 Jul 2019 11:29:55 -0700 Subject: prosodyctl: Removed the local_plugins command --- prosodyctl | 25 ++----------------------- 1 file changed, 2 insertions(+), 23 deletions(-) diff --git a/prosodyctl b/prosodyctl index c50900f2..cf51ca7d 100755 --- a/prosodyctl +++ b/prosodyctl @@ -160,27 +160,6 @@ function commands.enabled_plugins(arg) end end -function commands.local_plugins(arg) - if arg[1] == "--help" then - show_usage([[local_plugins]], [[Shows plugins currently available for prosody, locally]]); - return 1; - end - local directory = "./plugins" - local i, t, popen = 0, {}, io.popen - local pfile = popen('ls -a "'..directory..'"') - for filename in pfile:lines() do - if filename == "." or filename == ".." then - i = i + 1 - else - i = i + 1 - t[i] = filename - show_warning("%s", t[i]) - end - end - pfile:close() - return 0; -end - function commands.adduser(arg) if not arg[1] or arg[1] == "--help" then show_usage([[adduser JID]], [[Create the specified user account in Prosody]]); @@ -1446,8 +1425,8 @@ local command_runner = async.runner(function () print("Where COMMAND may be one of:\n"); local hidden_commands = require "util.set".new{ "register", "unregister", "addplugin" }; - local commands_order = { "install", "remove", "list", "enabled_plugins", "local_plugins","adduser", "passwd", "deluser", "start", "stop", "restart", - "reload", "about", "list" }; + local commands_order = { "install", "remove", "list", "enabled_plugins", "adduser", "passwd", "deluser", "start", "stop", "restart", "reload", + "about", "list" }; local done = {}; -- cgit v1.2.3 From 47421968357f79d18b68ca8999bc1c17b8a786e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Thu, 25 Jul 2019 04:25:40 -0700 Subject: prosodyctl: Removed the list duplicate at the commands_order variable --- prosodyctl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prosodyctl b/prosodyctl index cf51ca7d..54e883b2 100755 --- a/prosodyctl +++ b/prosodyctl @@ -1426,7 +1426,7 @@ local command_runner = async.runner(function () local hidden_commands = require "util.set".new{ "register", "unregister", "addplugin" }; local commands_order = { "install", "remove", "list", "enabled_plugins", "adduser", "passwd", "deluser", "start", "stop", "restart", "reload", - "about", "list" }; + "about" }; local done = {}; -- cgit v1.2.3 From c20cc1a17b2bd0ef75ad2c26e288011902b57206 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Thu, 25 Jul 2019 06:46:04 -0700 Subject: util.startup: Removed unnecessary if clause at startup.set_plugindir --- util/startup.lua | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/util/startup.lua b/util/startup.lua index 65a131fe..54b968f8 100644 --- a/util/startup.lua +++ b/util/startup.lua @@ -235,17 +235,15 @@ function startup.setup_plugindir() CFG_PLUGINDIR = table.concat(custom_plugin_paths, path_sep)..path_sep..(CFG_PLUGINDIR or "plugins"); prosody.paths.plugins = CFG_PLUGINDIR; end - if installer_plugin_paths then - for path, _ in ipairs(installer_plugin_paths) do - if os.execute('[ -d "'..installer_plugin_paths[path]..'" ]') ~= 0 then - os.execute("mkdir "..installer_plugin_paths[path]) - end + for path, _ in ipairs(installer_plugin_paths) do + if os.execute('[ -d "'..installer_plugin_paths[path]..'" ]') ~= 0 then + os.execute("mkdir "..installer_plugin_paths[path]) end - local path_sep = package.config:sub(3,3); - -- luacheck: ignore 111 - CFG_PLUGINDIR = table.concat(installer_plugin_paths, path_sep)..path_sep..(CFG_PLUGINDIR or "plugins"); - prosody.paths.plugins = CFG_PLUGINDIR; end + local path_sep = package.config:sub(3,3); + -- luacheck: ignore 111 + CFG_PLUGINDIR = table.concat(installer_plugin_paths, path_sep)..path_sep..(CFG_PLUGINDIR or "plugins"); + prosody.paths.plugins = CFG_PLUGINDIR; end function startup.chdir() -- cgit v1.2.3 From 449256fa0c00cce042fe8dc1a2b18bcbc0c07258 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Fri, 26 Jul 2019 05:53:18 -0700 Subject: prosodyctl: Corrected the help output of the install and remove commands --- prosodyctl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/prosodyctl b/prosodyctl index 54e883b2..9990a485 100755 --- a/prosodyctl +++ b/prosodyctl @@ -87,7 +87,7 @@ local command = table.remove(arg, 1); function commands.install(arg) if arg[1] == "--help" then - show_usage([[make]], [[Installs a prosody/luarocks plugin]]); + show_usage([[install]], [[Installs a prosody/luarocks plugin]]); return 1; end local installer_plugin_path = prosodyctl.get_path_custom_plugins(prosody.paths.plugins) @@ -111,7 +111,7 @@ end function commands.remove(arg) if arg[1] == "--help" then - show_usage([[make]], [[Removes a module installed in the wroking directory's plugins folder]]); + show_usage([[remove]], [[Removes a module installed in the wroking directory's plugins folder]]); return 1; end -- I'm considering that we are using just one path to custom plugins, and it is the first in prosody.paths.plugins, for now -- cgit v1.2.3 From 63071336691c272dded2c1b7d74e58d78a6ae8ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Fri, 26 Jul 2019 08:39:27 -0700 Subject: util.startup: The .setup_plugindir function now correctly sets a default/specified path for custom plugins --- util/startup.lua | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/util/startup.lua b/util/startup.lua index 54b968f8..363d8465 100644 --- a/util/startup.lua +++ b/util/startup.lua @@ -226,23 +226,29 @@ function startup.setup_datadir() end function startup.setup_plugindir() + --require "lfs".currentdir() + --local current_directory = lfs.currentdir() local custom_plugin_paths = config.get("*", "plugin_paths"); - local installer_plugin_paths = config.get("*", "installer_plugin_paths") or {"custom_plugins"}; + local installer_plugin_path = config.get("*", "installer_plugin_path") or "custom_plugins"; + local path_sep = package.config:sub(3,3); if custom_plugin_paths then - local path_sep = package.config:sub(3,3); -- path1;path2;path3;defaultpath... -- luacheck: ignore 111 CFG_PLUGINDIR = table.concat(custom_plugin_paths, path_sep)..path_sep..(CFG_PLUGINDIR or "plugins"); prosody.paths.plugins = CFG_PLUGINDIR; end - for path, _ in ipairs(installer_plugin_paths) do - if os.execute('[ -d "'..installer_plugin_paths[path]..'" ]') ~= 0 then - os.execute("mkdir "..installer_plugin_paths[path]) - end - end - local path_sep = package.config:sub(3,3); - -- luacheck: ignore 111 - CFG_PLUGINDIR = table.concat(installer_plugin_paths, path_sep)..path_sep..(CFG_PLUGINDIR or "plugins"); + -- Checking if the folder exists. If it doesn't, we create it + --[[if os.execute('[ -d "'..installer_plugin_path..'" ]') ~= 0 then + os.execute("mkdir "..installer_plugin_path) + end]] + --[[if not string.find(package.path, current_directory..installer_plugin_path[path]) then + --os.execute("ls -la "..current_directory..path_sep..installer_plugin_paths[path]) + package.path = package.path..path_sep..current_directory..installer_plugin_path.."/?.lua"..path_sep..path_sep + package.path = package.path..current_directory..installer_plugin_path.."/?/init.lua"..path_sep..path_sep + package.cpath = package.cpath..path_sep..current_directory..installer_plugin_path.."/?.lua" + package.cpath = package.cpath..path_sep..current_directory..installer_plugin_path.."/?/init.lua" + end]] + CFG_PLUGINDIR = installer_plugin_path..path_sep..(CFG_PLUGINDIR or "plugins"); prosody.paths.plugins = CFG_PLUGINDIR; end -- cgit v1.2.3 From 093110478e9ad8bbc539a10b07ce968387255a74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Fri, 26 Jul 2019 08:58:56 -0700 Subject: util.startup: .setup_plugindir now checks if the specified directory for custom plugins exists, and creates it if it doesn't --- util/startup.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/util/startup.lua b/util/startup.lua index 363d8465..9ddb10b5 100644 --- a/util/startup.lua +++ b/util/startup.lua @@ -226,8 +226,8 @@ function startup.setup_datadir() end function startup.setup_plugindir() - --require "lfs".currentdir() - --local current_directory = lfs.currentdir() + --local lfs_currentdir = require "lfs".currentdir() + --local current_directory = lfs_currentdir local custom_plugin_paths = config.get("*", "plugin_paths"); local installer_plugin_path = config.get("*", "installer_plugin_path") or "custom_plugins"; local path_sep = package.config:sub(3,3); @@ -238,9 +238,9 @@ function startup.setup_plugindir() prosody.paths.plugins = CFG_PLUGINDIR; end -- Checking if the folder exists. If it doesn't, we create it - --[[if os.execute('[ -d "'..installer_plugin_path..'" ]') ~= 0 then + if os.execute('[ -d "'..installer_plugin_path..'" ]') ~= 0 then os.execute("mkdir "..installer_plugin_path) - end]] + end --[[if not string.find(package.path, current_directory..installer_plugin_path[path]) then --os.execute("ls -la "..current_directory..path_sep..installer_plugin_paths[path]) package.path = package.path..path_sep..current_directory..installer_plugin_path.."/?.lua"..path_sep..path_sep -- cgit v1.2.3 From ad26c41fa6a4ac22d08b62dbadd13e49156ed305 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Fri, 26 Jul 2019 17:54:37 -0700 Subject: util.startup: Improved how .set_plugindir updates prosody.paths.plugins, package.path and package.cpath --- util/startup.lua | 38 +++++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/util/startup.lua b/util/startup.lua index 9ddb10b5..4601bd85 100644 --- a/util/startup.lua +++ b/util/startup.lua @@ -226,28 +226,44 @@ function startup.setup_datadir() end function startup.setup_plugindir() - --local lfs_currentdir = require "lfs".currentdir() - --local current_directory = lfs_currentdir local custom_plugin_paths = config.get("*", "plugin_paths"); local installer_plugin_path = config.get("*", "installer_plugin_path") or "custom_plugins"; + -- This variable separates different paths, like this "," here -> /usr;/home local path_sep = package.config:sub(3,3); + -- This variable is the separator between directories, in a path, like the "/" here -> /home/path/to/somewhere + local dir_sep = package.config:sub(1,1); if custom_plugin_paths then -- path1;path2;path3;defaultpath... -- luacheck: ignore 111 CFG_PLUGINDIR = table.concat(custom_plugin_paths, path_sep)..path_sep..(CFG_PLUGINDIR or "plugins"); prosody.paths.plugins = CFG_PLUGINDIR; end - -- Checking if the folder exists. If it doesn't, we create it + -- Checks if installer_plugin_path is a relative paths and makes it an absolute path + if installer_plugin_path:sub(1,1) ~= "/" then + -- Works fine when executing prosody from source (configure and make only) + -- Probably wont be the best install directory, when using a package installation + local lfs_currentdir = require "lfs".currentdir(); + local current_directory = lfs_currentdir; + -- Some normalization + installer_plugin_path = installer_plugin_path:gsub("^%.%"..dir_sep.."+", ""); + installer_plugin_path = current_directory..dir_sep..installer_plugin_path; + end + -- Checking if the folder exists. If it doesn't, we create it, but we need permissions to do so if os.execute('[ -d "'..installer_plugin_path..'" ]') ~= 0 then - os.execute("mkdir "..installer_plugin_path) + os.execute("mkdir "..installer_plugin_path); + end + -- Developers may have add these custom paths to their LUA_PATH/LUA_CPATH variables, before running prosody + -- Therefore, I'll just check if the paths we are about to add aren't already at package.(path/cpath) + if not string.match(package.path, installer_plugin_path) then + local lua_version = _VERSION:match(" (.+)$") + -- I'm assuming there's good reason not to hard code any separator + -- This next line is unnecessary, but I think it makes the code more readable and neat + local sub_path = dir_sep.."lua"..dir_sep..lua_version..dir_sep + package.path = package.path..path_sep..installer_plugin_path..dir_sep.."share"..sub_path.."?.lua"; + package.path = package.path..path_sep..installer_plugin_path..dir_sep.."share"..sub_path.."?"..dir_sep.."init.lua"; + package.cpath = package.cpath..path_sep..installer_plugin_path..dir_sep.."lib"..sub_path.."?.lua"; end - --[[if not string.find(package.path, current_directory..installer_plugin_path[path]) then - --os.execute("ls -la "..current_directory..path_sep..installer_plugin_paths[path]) - package.path = package.path..path_sep..current_directory..installer_plugin_path.."/?.lua"..path_sep..path_sep - package.path = package.path..current_directory..installer_plugin_path.."/?/init.lua"..path_sep..path_sep - package.cpath = package.cpath..path_sep..current_directory..installer_plugin_path.."/?.lua" - package.cpath = package.cpath..path_sep..current_directory..installer_plugin_path.."/?/init.lua" - end]] + -- The commands using luarocks need the path to the directory that has the /share and /lib folders. CFG_PLUGINDIR = installer_plugin_path..path_sep..(CFG_PLUGINDIR or "plugins"); prosody.paths.plugins = CFG_PLUGINDIR; end -- cgit v1.2.3 From e8ff1e8966bed06f5e7ca15d738c8ad6db0fec8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Fri, 26 Jul 2019 18:25:58 -0700 Subject: prosody.cfg.lua.dist: Added a field for the installer's path --- prosody.cfg.lua.dist | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/prosody.cfg.lua.dist b/prosody.cfg.lua.dist index f7f7b731..0d74cf66 100644 --- a/prosody.cfg.lua.dist +++ b/prosody.cfg.lua.dist @@ -32,6 +32,10 @@ admins = { } -- will look for modules first. For community modules, see https://modules.prosody.im/ --plugin_paths = {} +-- Single directory for custom prosody plugins and/or Lua libraries installation +-- This path takes priority over plugin_paths, when prosody is searching for modules +--installer_plugin_path = "" + -- This is the list of modules Prosody will load on startup. -- It looks for mod_modulename.lua in the plugins folder, so make sure that exists too. -- Documentation for bundled modules can be found at: https://prosody.im/doc/modules -- cgit v1.2.3 From 8398643365bfd23a8b78c45946915c403882cbee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Mon, 29 Jul 2019 10:32:03 -0700 Subject: make_repo.lua: Moved to /tools/make_repo.lua --- make_repo.lua | 44 -------------------------------------------- tools/make_repo.lua | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 44 deletions(-) delete mode 100644 make_repo.lua create mode 100644 tools/make_repo.lua diff --git a/make_repo.lua b/make_repo.lua deleted file mode 100644 index 5b5e6234..00000000 --- a/make_repo.lua +++ /dev/null @@ -1,44 +0,0 @@ -print("Getting all the available modules") -if os.execute '[ -e "./downloaded_modules" ]' then - os.execute("rm -rf downloaded_modules") -end -os.execute("hg clone https://hg.prosody.im/prosody-modules/ downloaded_modules") -local i, popen = 0, io.popen -local flag = "mod_" -if os.execute '[ -e "./repository" ]' then - os.execute("mkdir repository") -end -local pfile = popen('ls -a "downloaded_modules"') -for filename in pfile:lines() do - i = i + 1 - if filename:sub(1, #flag) == flag then - local file = io.open("repository/"..filename.."-scm-1.rockspec", "w") - file:write('package = "'..filename..'"', '\n') - file:write('version = "scm-1"', '\n') - file:write('source = {', '\n') - file:write('\turl = "hg+https://hg.prosody.im/prosody-modules",', '\n') - file:write('\tdir = "prosody-modules"', '\n') - file:write('}', '\n') - file:write('description = {', '\n') - file:write('\thomepage = "https://prosody.im/",', '\n') - file:write('\tlicense = "MIT"', '\n') - file:write('}', '\n') - file:write('dependencies = {', '\n') - file:write('\t"lua >= 5.1"', '\n') - file:write('}', '\n') - file:write('build = {', '\n') - file:write('\ttype = "builtin",', '\n') - file:write('\tmodules = {', '\n') - file:write('\t\t["'..filename..'.'..filename..'"] = "'..filename..'/'..filename..'.lua"', '\n') - file:write('\t}', '\n') - file:write('}', '\n') - file:close() - end -end -pfile:close() -os.execute("cd repository/ && luarocks-admin make_manifest ./ && chmod -R 644 ./*") -print("") -print("Done!. Modules' sources are locally available at ./downloaded_modules") -print("Repository is available at ./repository") -print("The repository contains all of prosody modules' respective rockspecs, as well as manifest files and an html Index") -print("You can now either point your server to this folder, or copy its contents to another configured folder.") diff --git a/tools/make_repo.lua b/tools/make_repo.lua new file mode 100644 index 00000000..5b5e6234 --- /dev/null +++ b/tools/make_repo.lua @@ -0,0 +1,44 @@ +print("Getting all the available modules") +if os.execute '[ -e "./downloaded_modules" ]' then + os.execute("rm -rf downloaded_modules") +end +os.execute("hg clone https://hg.prosody.im/prosody-modules/ downloaded_modules") +local i, popen = 0, io.popen +local flag = "mod_" +if os.execute '[ -e "./repository" ]' then + os.execute("mkdir repository") +end +local pfile = popen('ls -a "downloaded_modules"') +for filename in pfile:lines() do + i = i + 1 + if filename:sub(1, #flag) == flag then + local file = io.open("repository/"..filename.."-scm-1.rockspec", "w") + file:write('package = "'..filename..'"', '\n') + file:write('version = "scm-1"', '\n') + file:write('source = {', '\n') + file:write('\turl = "hg+https://hg.prosody.im/prosody-modules",', '\n') + file:write('\tdir = "prosody-modules"', '\n') + file:write('}', '\n') + file:write('description = {', '\n') + file:write('\thomepage = "https://prosody.im/",', '\n') + file:write('\tlicense = "MIT"', '\n') + file:write('}', '\n') + file:write('dependencies = {', '\n') + file:write('\t"lua >= 5.1"', '\n') + file:write('}', '\n') + file:write('build = {', '\n') + file:write('\ttype = "builtin",', '\n') + file:write('\tmodules = {', '\n') + file:write('\t\t["'..filename..'.'..filename..'"] = "'..filename..'/'..filename..'.lua"', '\n') + file:write('\t}', '\n') + file:write('}', '\n') + file:close() + end +end +pfile:close() +os.execute("cd repository/ && luarocks-admin make_manifest ./ && chmod -R 644 ./*") +print("") +print("Done!. Modules' sources are locally available at ./downloaded_modules") +print("Repository is available at ./repository") +print("The repository contains all of prosody modules' respective rockspecs, as well as manifest files and an html Index") +print("You can now either point your server to this folder, or copy its contents to another configured folder.") -- cgit v1.2.3 From 1e03cec66433732be858cca013ce8c9f84169c0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Wed, 31 Jul 2019 05:47:36 -0700 Subject: prosodyctl: Swapped prints for the show_message function at the install/remove commands --- prosodyctl | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/prosodyctl b/prosodyctl index 9990a485..72f7cbb5 100755 --- a/prosodyctl +++ b/prosodyctl @@ -95,12 +95,12 @@ function commands.install(arg) local flag = "--tree=" if arg[1] and arg[1]:sub(1, #flag) == flag then local dir = arg[1]:match("=(.+)$") - print("Installing module "..arg[2].." at "..dir) + show_message("Installing module %s at %s", arg[2], dir) os.execute("luarocks --tree='"..dir.."' --server='http://localhost/' install "..arg[2]) show_module_configuration_help(arg[2]); return 0; else - print("Installing module "..arg[1].." at "..installer_plugin_path) + show_message("Installing module %s at %s", arg[1], installer_plugin_path) -- I've build a local server to upload some new rockspecs, like mod_smacks'. We can replace this server by one from -- prosody's, where we can oficially disbrute rocks/rockspecs for all modules os.execute("luarocks --tree='"..installer_plugin_path.."' --server='http://localhost/' install "..arg[1]) @@ -120,13 +120,12 @@ function commands.remove(arg) -- I'm considering this optional flag comes first if arg[1] and arg[1]:sub(1, #flag) == flag then local dir = arg[1]:match("=(.+)$") - print("Removing module "..arg[2].." at "..dir) + show_message("Removing module %s at %s", arg[2], dir) os.execute("luarocks remove --tree='"..dir.."' "..arg[2]) return 0; else - print("Removing "..arg[1].." from "..installer_plugin_path) + show_message("Removing %s from %s", arg[1], installer_plugin_path) os.execute("luarocks --tree='"..installer_plugin_path.."' remove "..arg[1]) - print("Done!") return 0; end end -- cgit v1.2.3 From 372a90aff6bb86406df0e56f65ff0427b7b01172 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Wed, 31 Jul 2019 05:55:32 -0700 Subject: prosodyctl: Removed unnecessary comments from the remove command --- prosodyctl | 1 - 1 file changed, 1 deletion(-) diff --git a/prosodyctl b/prosodyctl index 72f7cbb5..24874ade 100755 --- a/prosodyctl +++ b/prosodyctl @@ -114,7 +114,6 @@ function commands.remove(arg) show_usage([[remove]], [[Removes a module installed in the wroking directory's plugins folder]]); return 1; end - -- I'm considering that we are using just one path to custom plugins, and it is the first in prosody.paths.plugins, for now local installer_plugin_path = prosodyctl.get_path_custom_plugins(prosody.paths.plugins) local flag = "--tree=" -- I'm considering this optional flag comes first -- cgit v1.2.3 From d2524edff49771ad0c131ac85461241eb6cd99f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Wed, 31 Jul 2019 06:42:13 -0700 Subject: util.prosodyctl: Added the check_flags function --- util/prosodyctl.lua | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/util/prosodyctl.lua b/util/prosodyctl.lua index fbeb9540..affa2f6b 100644 --- a/util/prosodyctl.lua +++ b/util/prosodyctl.lua @@ -292,10 +292,20 @@ local function get_path_custom_plugins(plugin_paths) -- I'm considering that we are using just one path to custom plugins, and it is the first in prosody.paths.plugins, for now -- luacheck: ignore 512 for path in plugin_paths:gmatch("[^;]+") do - return path + return path; end end +local function check_flags(arg) + local flag = "--tree="; + -- There might not be any argument when the list command is calling this function + if arg[1] and arg[1]:sub(1, #flag) == flag then + local dir = arg[1]:match("=(.+)$") + return true, arg[2], dir; + end + return false, arg[1] +end + return { show_message = show_message; show_warning = show_message; @@ -317,4 +327,5 @@ return { stop = stop; reload = reload; get_path_custom_plugins = get_path_custom_plugins; + check_flags = check_flags; }; -- cgit v1.2.3 From b60081c28ce425696a3839d10039f23d6250e840 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Wed, 31 Jul 2019 07:24:43 -0700 Subject: util.prosodyctl: Added the call_luarocks function --- util/prosodyctl.lua | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/util/prosodyctl.lua b/util/prosodyctl.lua index affa2f6b..22571061 100644 --- a/util/prosodyctl.lua +++ b/util/prosodyctl.lua @@ -306,6 +306,12 @@ local function check_flags(arg) return false, arg[1] end +local function call_luarocks(operation, mod, dir) + show_message("Installing %s at %s", mod, dir); + os.execute("luarocks --tree='"..dir.."' --server='http://localhost/' "..operation.." "..mod); + show_module_configuration_help(mod); +end + return { show_message = show_message; show_warning = show_message; @@ -328,4 +334,5 @@ return { reload = reload; get_path_custom_plugins = get_path_custom_plugins; check_flags = check_flags; + call_luarocks = call_luarocks; }; -- cgit v1.2.3 From 7e472f4b26c85841e3306cf46b3bfd378dabca25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Wed, 31 Jul 2019 07:31:03 -0700 Subject: prosodyctl: Rewrote the install command, to make it more cleaner --- prosodyctl | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/prosodyctl b/prosodyctl index 24874ade..18db72b7 100755 --- a/prosodyctl +++ b/prosodyctl @@ -76,7 +76,9 @@ local show_usage = prosodyctl.show_usage; local show_yesno = prosodyctl.show_yesno; local show_prompt = prosodyctl.show_prompt; local read_password = prosodyctl.read_password; -local show_module_configuration_help = prosodyctl.show_module_configuration_help; +local check_flags = prosodyctl.check_flags; +local call_luarocks = prosodyctl.call_luarocks; +local get_path_custom_plugins = prosodyctl.get_path_custom_plugins; local jid_split = require "util.jid".prepped_split; @@ -90,21 +92,14 @@ function commands.install(arg) show_usage([[install]], [[Installs a prosody/luarocks plugin]]); return 1; end - local installer_plugin_path = prosodyctl.get_path_custom_plugins(prosody.paths.plugins) - -- I'm considering this optional flag comes first - local flag = "--tree=" - if arg[1] and arg[1]:sub(1, #flag) == flag then - local dir = arg[1]:match("=(.+)$") - show_message("Installing module %s at %s", arg[2], dir) - os.execute("luarocks --tree='"..dir.."' --server='http://localhost/' install "..arg[2]) - show_module_configuration_help(arg[2]); + local operation = "install"; + local tree, mod, dir = check_flags(arg); + if tree then + call_luarocks(operation, mod, dir); return 0; else - show_message("Installing module %s at %s", arg[1], installer_plugin_path) - -- I've build a local server to upload some new rockspecs, like mod_smacks'. We can replace this server by one from - -- prosody's, where we can oficially disbrute rocks/rockspecs for all modules - os.execute("luarocks --tree='"..installer_plugin_path.."' --server='http://localhost/' install "..arg[1]) - show_module_configuration_help(arg[1]); + dir = get_path_custom_plugins(prosody.paths.plugins); + call_luarocks(operation, mod, dir); return 0; end end -- cgit v1.2.3 From 289378113abb5b92d1628369fad66454b58cb738 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Wed, 31 Jul 2019 10:02:42 -0700 Subject: util.prosodyctl: Function now differentiates its output, depending if it is being called by install or remove --- util/prosodyctl.lua | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/util/prosodyctl.lua b/util/prosodyctl.lua index 22571061..a98a89d7 100644 --- a/util/prosodyctl.lua +++ b/util/prosodyctl.lua @@ -307,9 +307,15 @@ local function check_flags(arg) end local function call_luarocks(operation, mod, dir) + if operation == "install" then show_message("Installing %s at %s", mod, dir); - os.execute("luarocks --tree='"..dir.."' --server='http://localhost/' "..operation.." "..mod); + elseif operation == "remove" then + show_message("Removing %s from %s", mod, dir); + end + os.execute("luarocks --tree='"..dir.."' --server='http://localhost/' "..operation.." "..mod); + if operation == "install" then show_module_configuration_help(mod); + end end return { -- cgit v1.2.3 From f74f2d0d17b792139c85d44cbb4a64939bd2347e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Wed, 31 Jul 2019 10:07:22 -0700 Subject: prosodyctl: Rewrote the remove command, to make it cleaner and easier to work with --- prosodyctl | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/prosodyctl b/prosodyctl index 18db72b7..b92b166b 100755 --- a/prosodyctl +++ b/prosodyctl @@ -109,17 +109,14 @@ function commands.remove(arg) show_usage([[remove]], [[Removes a module installed in the wroking directory's plugins folder]]); return 1; end - local installer_plugin_path = prosodyctl.get_path_custom_plugins(prosody.paths.plugins) - local flag = "--tree=" - -- I'm considering this optional flag comes first - if arg[1] and arg[1]:sub(1, #flag) == flag then - local dir = arg[1]:match("=(.+)$") - show_message("Removing module %s at %s", arg[2], dir) - os.execute("luarocks remove --tree='"..dir.."' "..arg[2]) + local operation = "remove"; + local tree, mod, dir = check_flags(arg); + if tree then + call_luarocks(operation, mod, dir); return 0; else - show_message("Removing %s from %s", arg[1], installer_plugin_path) - os.execute("luarocks --tree='"..installer_plugin_path.."' remove "..arg[1]) + dir = get_path_custom_plugins(prosody.paths.plugins); + call_luarocks(operation, mod, dir); return 0; end end -- cgit v1.2.3 From 2b8e9031a41af9046dc19c35e95f8cac0b4e67b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Wed, 31 Jul 2019 10:22:11 -0700 Subject: util.prosodyctl: call_luarocks function is now compatible with the list command --- util/prosodyctl.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/util/prosodyctl.lua b/util/prosodyctl.lua index a98a89d7..7fe87dab 100644 --- a/util/prosodyctl.lua +++ b/util/prosodyctl.lua @@ -312,7 +312,11 @@ local function call_luarocks(operation, mod, dir) elseif operation == "remove" then show_message("Removing %s from %s", mod, dir); end - os.execute("luarocks --tree='"..dir.."' --server='http://localhost/' "..operation.." "..mod); + if operation == "list" then + os.execute("luarocks list --tree='"..dir.."'") + else + os.execute("luarocks --tree='"..dir.."' --server='http://localhost/' "..operation.." "..mod); + end if operation == "install" then show_module_configuration_help(mod); end -- cgit v1.2.3 From 849165ba69da619e45f7d4da5e0ae2554cea35a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Wed, 31 Jul 2019 10:22:40 -0700 Subject: prosodyctl: Rewrote the list command, to make it cleaner and easier to work with --- prosodyctl | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/prosodyctl b/prosodyctl index b92b166b..c0690293 100755 --- a/prosodyctl +++ b/prosodyctl @@ -126,16 +126,14 @@ function commands.list(arg) show_usage([[list]], [[Shows installed rocks]]); return 1; end - -- I'm considering that we are using just one path to custom plugins, and it is the first in prosody.paths.plugins, for now - local installer_plugin_path = prosodyctl.get_path_custom_plugins(prosody.paths.plugins) - local flag = "--tree=" - -- I'm considering this optional flag comes first - if arg[1] and arg[1]:sub(1, #flag) == flag then - local dir = arg[1]:match("=(.+)$") - os.execute("luarocks list --tree='"..dir.."'") + local operation = "list"; + local tree, mod, dir = check_flags(arg); + if tree then + call_luarocks(operation, mod, dir); return 0; else - os.execute("luarocks list --tree="..installer_plugin_path) + dir = get_path_custom_plugins(prosody.paths.plugins); + call_luarocks(operation, mod, dir); return 0; end end -- cgit v1.2.3 From 2049e20696aecd37419fafb8b38c96550b04a442 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Wed, 31 Jul 2019 11:00:20 -0700 Subject: util.prosodyctl: Added the execute_command function --- util/prosodyctl.lua | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/util/prosodyctl.lua b/util/prosodyctl.lua index 7fe87dab..274d611c 100644 --- a/util/prosodyctl.lua +++ b/util/prosodyctl.lua @@ -322,6 +322,19 @@ local function call_luarocks(operation, mod, dir) end end +local function execute_command(arg) + local operation = arg[#arg] + local tree, mod, dir = check_flags(arg); + if tree then + call_luarocks(operation, mod, dir); + return 0; + else + dir = get_path_custom_plugins(prosody.paths.plugins); + call_luarocks(operation, mod, dir); + return 0; + end +end + return { show_message = show_message; show_warning = show_message; @@ -345,4 +358,5 @@ return { get_path_custom_plugins = get_path_custom_plugins; check_flags = check_flags; call_luarocks = call_luarocks; + execute_command = execute_command; }; -- cgit v1.2.3 From 5ba7a798fbf368a845cfc3fd04c3241d29f75d53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Wed, 31 Jul 2019 11:01:36 -0700 Subject: prosodyctl: The install, remove and list commands now work by calling the execute_command function --- prosodyctl | 40 +++++++--------------------------------- 1 file changed, 7 insertions(+), 33 deletions(-) diff --git a/prosodyctl b/prosodyctl index c0690293..57632964 100755 --- a/prosodyctl +++ b/prosodyctl @@ -76,9 +76,7 @@ local show_usage = prosodyctl.show_usage; local show_yesno = prosodyctl.show_yesno; local show_prompt = prosodyctl.show_prompt; local read_password = prosodyctl.read_password; -local check_flags = prosodyctl.check_flags; -local call_luarocks = prosodyctl.call_luarocks; -local get_path_custom_plugins = prosodyctl.get_path_custom_plugins; +local execute_command = prosodyctl.execute_command; local jid_split = require "util.jid".prepped_split; @@ -92,16 +90,8 @@ function commands.install(arg) show_usage([[install]], [[Installs a prosody/luarocks plugin]]); return 1; end - local operation = "install"; - local tree, mod, dir = check_flags(arg); - if tree then - call_luarocks(operation, mod, dir); - return 0; - else - dir = get_path_custom_plugins(prosody.paths.plugins); - call_luarocks(operation, mod, dir); - return 0; - end + table.insert(arg, "install"); + execute_command(arg); end function commands.remove(arg) @@ -109,16 +99,8 @@ function commands.remove(arg) show_usage([[remove]], [[Removes a module installed in the wroking directory's plugins folder]]); return 1; end - local operation = "remove"; - local tree, mod, dir = check_flags(arg); - if tree then - call_luarocks(operation, mod, dir); - return 0; - else - dir = get_path_custom_plugins(prosody.paths.plugins); - call_luarocks(operation, mod, dir); - return 0; - end + table.insert(arg, "remove"); + execute_command(arg); end function commands.list(arg) @@ -126,16 +108,8 @@ function commands.list(arg) show_usage([[list]], [[Shows installed rocks]]); return 1; end - local operation = "list"; - local tree, mod, dir = check_flags(arg); - if tree then - call_luarocks(operation, mod, dir); - return 0; - else - dir = get_path_custom_plugins(prosody.paths.plugins); - call_luarocks(operation, mod, dir); - return 0; - end + table.insert(arg, "list"); + execute_command(arg); end function commands.enabled_plugins(arg) -- cgit v1.2.3 From 5aa4dda0fb32d81ee08d873bd9528ab26aa56c22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Wed, 31 Jul 2019 11:08:43 -0700 Subject: util.prosodyctl: The check_flags function now considers that a module, if given, is specified at the penultimate argument it receives --- util/prosodyctl.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/util/prosodyctl.lua b/util/prosodyctl.lua index 274d611c..bf65e396 100644 --- a/util/prosodyctl.lua +++ b/util/prosodyctl.lua @@ -301,9 +301,9 @@ local function check_flags(arg) -- There might not be any argument when the list command is calling this function if arg[1] and arg[1]:sub(1, #flag) == flag then local dir = arg[1]:match("=(.+)$") - return true, arg[2], dir; + return true, arg[#arg-1], dir; end - return false, arg[1] + return false, arg[#arg-1]; end local function call_luarocks(operation, mod, dir) -- cgit v1.2.3 From 3fe8683f5ec784091353788cf7f1b722747e489a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Wed, 31 Jul 2019 11:13:14 -0700 Subject: prosodyctl: Removed the auxiliary command 'enabled_plugins' --- prosodyctl | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/prosodyctl b/prosodyctl index 57632964..9c94fea4 100755 --- a/prosodyctl +++ b/prosodyctl @@ -112,16 +112,6 @@ function commands.list(arg) execute_command(arg); end -function commands.enabled_plugins(arg) - if arg[1] == "--help" then - show_usage([[enabled_plugins]], [[Shows plugins currently enabled on prosody]]); - return 1; - end - for module in modulemanager.get_modules_for_host() do - show_warning("%s", module) - end -end - function commands.adduser(arg) if not arg[1] or arg[1] == "--help" then show_usage([[adduser JID]], [[Create the specified user account in Prosody]]); @@ -1387,7 +1377,7 @@ local command_runner = async.runner(function () print("Where COMMAND may be one of:\n"); local hidden_commands = require "util.set".new{ "register", "unregister", "addplugin" }; - local commands_order = { "install", "remove", "list", "enabled_plugins", "adduser", "passwd", "deluser", "start", "stop", "restart", "reload", + local commands_order = { "install", "remove", "list", "adduser", "passwd", "deluser", "start", "stop", "restart", "reload", "about" }; local done = {}; -- cgit v1.2.3 From ee4ae338f9f6f4cbb162ff5c77c9873a38be8b33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Thu, 1 Aug 2019 04:16:00 -0700 Subject: util.prosodyctl: Changed a comment --- util/prosodyctl.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/prosodyctl.lua b/util/prosodyctl.lua index bf65e396..c6abc02f 100644 --- a/util/prosodyctl.lua +++ b/util/prosodyctl.lua @@ -289,7 +289,7 @@ local function reload() end local function get_path_custom_plugins(plugin_paths) - -- I'm considering that we are using just one path to custom plugins, and it is the first in prosody.paths.plugins, for now + -- I'm considering that the custom plugins' path is the first one at prosody.paths.plugins -- luacheck: ignore 512 for path in plugin_paths:gmatch("[^;]+") do return path; -- cgit v1.2.3 From 92fafa3343526ed26f66dc81d26167b7e0850f69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Thu, 1 Aug 2019 04:33:05 -0700 Subject: util.startupt: I'm now using the resolve_relative_path function from util/paths at the setup_plugindir function --- util/startup.lua | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/util/startup.lua b/util/startup.lua index 4601bd85..4b3842bb 100644 --- a/util/startup.lua +++ b/util/startup.lua @@ -238,16 +238,8 @@ function startup.setup_plugindir() CFG_PLUGINDIR = table.concat(custom_plugin_paths, path_sep)..path_sep..(CFG_PLUGINDIR or "plugins"); prosody.paths.plugins = CFG_PLUGINDIR; end - -- Checks if installer_plugin_path is a relative paths and makes it an absolute path - if installer_plugin_path:sub(1,1) ~= "/" then - -- Works fine when executing prosody from source (configure and make only) - -- Probably wont be the best install directory, when using a package installation - local lfs_currentdir = require "lfs".currentdir(); - local current_directory = lfs_currentdir; - -- Some normalization - installer_plugin_path = installer_plugin_path:gsub("^%.%"..dir_sep.."+", ""); - installer_plugin_path = current_directory..dir_sep..installer_plugin_path; - end + local current_directory = require "lfs".currentdir(); + installer_plugin_path = config.resolve_relative_path(current_directory, installer_plugin_path); -- Checking if the folder exists. If it doesn't, we create it, but we need permissions to do so if os.execute('[ -d "'..installer_plugin_path..'" ]') ~= 0 then os.execute("mkdir "..installer_plugin_path); -- cgit v1.2.3 From ded9e843e3c7f2fa463310ce15be271d252b76a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Thu, 1 Aug 2019 07:48:11 -0700 Subject: util.startupt: setup_plugindir now uses lfs.mkdir to check/create directories --- util/startup.lua | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/util/startup.lua b/util/startup.lua index 4b3842bb..8c6c8b99 100644 --- a/util/startup.lua +++ b/util/startup.lua @@ -240,10 +240,7 @@ function startup.setup_plugindir() end local current_directory = require "lfs".currentdir(); installer_plugin_path = config.resolve_relative_path(current_directory, installer_plugin_path); - -- Checking if the folder exists. If it doesn't, we create it, but we need permissions to do so - if os.execute('[ -d "'..installer_plugin_path..'" ]') ~= 0 then - os.execute("mkdir "..installer_plugin_path); - end + require "lfs".mkdir(installer_plugin_path) -- Developers may have add these custom paths to their LUA_PATH/LUA_CPATH variables, before running prosody -- Therefore, I'll just check if the paths we are about to add aren't already at package.(path/cpath) if not string.match(package.path, installer_plugin_path) then -- cgit v1.2.3 From a3f41c2b14b3776e6009d90d2fbc024b306620a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Thu, 1 Aug 2019 08:02:26 -0700 Subject: util.startup: Removed/rewrote comments at setup_plugindir --- util/startup.lua | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/util/startup.lua b/util/startup.lua index 8c6c8b99..e7107a9c 100644 --- a/util/startup.lua +++ b/util/startup.lua @@ -228,9 +228,7 @@ end function startup.setup_plugindir() local custom_plugin_paths = config.get("*", "plugin_paths"); local installer_plugin_path = config.get("*", "installer_plugin_path") or "custom_plugins"; - -- This variable separates different paths, like this "," here -> /usr;/home local path_sep = package.config:sub(3,3); - -- This variable is the separator between directories, in a path, like the "/" here -> /home/path/to/somewhere local dir_sep = package.config:sub(1,1); if custom_plugin_paths then -- path1;path2;path3;defaultpath... @@ -241,18 +239,15 @@ function startup.setup_plugindir() local current_directory = require "lfs".currentdir(); installer_plugin_path = config.resolve_relative_path(current_directory, installer_plugin_path); require "lfs".mkdir(installer_plugin_path) - -- Developers may have add these custom paths to their LUA_PATH/LUA_CPATH variables, before running prosody - -- Therefore, I'll just check if the paths we are about to add aren't already at package.(path/cpath) + -- Checking for duplicates + -- The commands using luarocks need the path to the directory that has the /share and /lib folders. if not string.match(package.path, installer_plugin_path) then local lua_version = _VERSION:match(" (.+)$") - -- I'm assuming there's good reason not to hard code any separator - -- This next line is unnecessary, but I think it makes the code more readable and neat local sub_path = dir_sep.."lua"..dir_sep..lua_version..dir_sep package.path = package.path..path_sep..installer_plugin_path..dir_sep.."share"..sub_path.."?.lua"; package.path = package.path..path_sep..installer_plugin_path..dir_sep.."share"..sub_path.."?"..dir_sep.."init.lua"; package.cpath = package.cpath..path_sep..installer_plugin_path..dir_sep.."lib"..sub_path.."?.lua"; end - -- The commands using luarocks need the path to the directory that has the /share and /lib folders. CFG_PLUGINDIR = installer_plugin_path..path_sep..(CFG_PLUGINDIR or "plugins"); prosody.paths.plugins = CFG_PLUGINDIR; end -- cgit v1.2.3 From d2fc41c9e754d20a7c432b10b2db4f840e8fae54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Thu, 1 Aug 2019 08:08:06 -0700 Subject: util.startup: Directly calling lfs.currentdir instead of storing it in a local variable --- util/startup.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/util/startup.lua b/util/startup.lua index e7107a9c..abf985f8 100644 --- a/util/startup.lua +++ b/util/startup.lua @@ -236,8 +236,7 @@ function startup.setup_plugindir() CFG_PLUGINDIR = table.concat(custom_plugin_paths, path_sep)..path_sep..(CFG_PLUGINDIR or "plugins"); prosody.paths.plugins = CFG_PLUGINDIR; end - local current_directory = require "lfs".currentdir(); - installer_plugin_path = config.resolve_relative_path(current_directory, installer_plugin_path); + installer_plugin_path = config.resolve_relative_path(require "lfs".currentdir(), installer_plugin_path); require "lfs".mkdir(installer_plugin_path) -- Checking for duplicates -- The commands using luarocks need the path to the directory that has the /share and /lib folders. -- cgit v1.2.3 From f27cd24e27134ff803e57768d1adac625f154162 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Thu, 1 Aug 2019 08:28:24 -0700 Subject: util.startup: setup_plugindir now also checks package.cpath for duplicates --- util/startup.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/util/startup.lua b/util/startup.lua index abf985f8..6ba81819 100644 --- a/util/startup.lua +++ b/util/startup.lua @@ -240,11 +240,13 @@ function startup.setup_plugindir() require "lfs".mkdir(installer_plugin_path) -- Checking for duplicates -- The commands using luarocks need the path to the directory that has the /share and /lib folders. + local lua_version = _VERSION:match(" (.+)$") + local sub_path = dir_sep.."lua"..dir_sep..lua_version..dir_sep if not string.match(package.path, installer_plugin_path) then - local lua_version = _VERSION:match(" (.+)$") - local sub_path = dir_sep.."lua"..dir_sep..lua_version..dir_sep package.path = package.path..path_sep..installer_plugin_path..dir_sep.."share"..sub_path.."?.lua"; package.path = package.path..path_sep..installer_plugin_path..dir_sep.."share"..sub_path.."?"..dir_sep.."init.lua"; + end + if not string.match(package.path, installer_plugin_path) then package.cpath = package.cpath..path_sep..installer_plugin_path..dir_sep.."lib"..sub_path.."?.lua"; end CFG_PLUGINDIR = installer_plugin_path..path_sep..(CFG_PLUGINDIR or "plugins"); -- cgit v1.2.3 From 579bcb64d580b2e018f5aa4f647d23cb5b2da5e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Thu, 1 Aug 2019 09:12:28 -0700 Subject: util.paths: Added the function 'complement_lua_path' --- util/paths.lua | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/util/paths.lua b/util/paths.lua index 89f4cad9..a8d80a4c 100644 --- a/util/paths.lua +++ b/util/paths.lua @@ -41,4 +41,20 @@ function path_util.join(...) return t_concat({...}, path_sep); end +function path_util.complement_lua_path(installer_plugin_path) + -- Checking for duplicates + -- The commands using luarocks need the path to the directory that has the /share and /lib folders. + local lua_version = _VERSION:match(" (.+)$"); + local path_sep = package.config:sub(3,3); + local dir_sep = package.config:sub(1,1); + local sub_path = dir_sep.."lua"..dir_sep..lua_version..dir_sep; + if not string.match(package.path, installer_plugin_path) then + package.path = package.path..path_sep..installer_plugin_path..dir_sep.."share"..sub_path.."?.lua"; + package.path = package.path..path_sep..installer_plugin_path..dir_sep.."share"..sub_path.."?"..dir_sep.."init.lua"; + end + if not string.match(package.path, installer_plugin_path) then + package.cpath = package.cpath..path_sep..installer_plugin_path..dir_sep.."lib"..sub_path.."?.lua"; + end +end + return path_util; -- cgit v1.2.3 From 2af161560a96cf1b69a9d28ee79388c87a1b7418 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Thu, 1 Aug 2019 09:21:10 -0700 Subject: core.configmanager: Added support to 'complement_lua_path' --- core/configmanager.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/configmanager.lua b/core/configmanager.lua index 090a6a0a..d501b7cf 100644 --- a/core/configmanager.lua +++ b/core/configmanager.lua @@ -15,6 +15,7 @@ local envload = require"util.envload".envload; local deps = require"util.dependencies"; local resolve_relative_path = require"util.paths".resolve_relative_path; local glob_to_pattern = require"util.paths".glob_to_pattern; +local complement_lua_path = require"util.paths".complement_lua_path; local path_sep = package.config:sub(1,1); local get_traceback_table = require "util.debug".get_traceback_table; @@ -26,6 +27,7 @@ local _ENV = nil; -- luacheck: std none _M.resolve_relative_path = resolve_relative_path; -- COMPAT +_M.complement_lua_path = complement_lua_path; local parser = nil; -- cgit v1.2.3 From f088ddc713ec2b830b89546a4bf1b6a1218631e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Thu, 1 Aug 2019 09:22:50 -0700 Subject: util.startup: Now calls a function to complement lua's path/cpath --- util/startup.lua | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/util/startup.lua b/util/startup.lua index 6ba81819..e46f98c9 100644 --- a/util/startup.lua +++ b/util/startup.lua @@ -229,7 +229,6 @@ function startup.setup_plugindir() local custom_plugin_paths = config.get("*", "plugin_paths"); local installer_plugin_path = config.get("*", "installer_plugin_path") or "custom_plugins"; local path_sep = package.config:sub(3,3); - local dir_sep = package.config:sub(1,1); if custom_plugin_paths then -- path1;path2;path3;defaultpath... -- luacheck: ignore 111 @@ -237,18 +236,8 @@ function startup.setup_plugindir() prosody.paths.plugins = CFG_PLUGINDIR; end installer_plugin_path = config.resolve_relative_path(require "lfs".currentdir(), installer_plugin_path); - require "lfs".mkdir(installer_plugin_path) - -- Checking for duplicates - -- The commands using luarocks need the path to the directory that has the /share and /lib folders. - local lua_version = _VERSION:match(" (.+)$") - local sub_path = dir_sep.."lua"..dir_sep..lua_version..dir_sep - if not string.match(package.path, installer_plugin_path) then - package.path = package.path..path_sep..installer_plugin_path..dir_sep.."share"..sub_path.."?.lua"; - package.path = package.path..path_sep..installer_plugin_path..dir_sep.."share"..sub_path.."?"..dir_sep.."init.lua"; - end - if not string.match(package.path, installer_plugin_path) then - package.cpath = package.cpath..path_sep..installer_plugin_path..dir_sep.."lib"..sub_path.."?.lua"; - end + require "lfs".mkdir(installer_plugin_path); + config.complement_lua_path(installer_plugin_path); CFG_PLUGINDIR = installer_plugin_path..path_sep..(CFG_PLUGINDIR or "plugins"); prosody.paths.plugins = CFG_PLUGINDIR; end -- cgit v1.2.3 From cc182d07959a8eb3a55f73a0039c42b256af671e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Thu, 1 Aug 2019 09:29:18 -0700 Subject: util.paths: Refactored a variable, to avoid shadowing --- util/paths.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/util/paths.lua b/util/paths.lua index a8d80a4c..de63111e 100644 --- a/util/paths.lua +++ b/util/paths.lua @@ -45,15 +45,15 @@ function path_util.complement_lua_path(installer_plugin_path) -- Checking for duplicates -- The commands using luarocks need the path to the directory that has the /share and /lib folders. local lua_version = _VERSION:match(" (.+)$"); - local path_sep = package.config:sub(3,3); + local lua_path_sep = package.config:sub(3,3); local dir_sep = package.config:sub(1,1); local sub_path = dir_sep.."lua"..dir_sep..lua_version..dir_sep; if not string.match(package.path, installer_plugin_path) then - package.path = package.path..path_sep..installer_plugin_path..dir_sep.."share"..sub_path.."?.lua"; - package.path = package.path..path_sep..installer_plugin_path..dir_sep.."share"..sub_path.."?"..dir_sep.."init.lua"; + package.path = package.path..lua_path_sep..installer_plugin_path..dir_sep.."share"..sub_path.."?.lua"; + package.path = package.path..lua_path_sep..installer_plugin_path..dir_sep.."share"..sub_path.."?"..dir_sep.."init.lua"; end if not string.match(package.path, installer_plugin_path) then - package.cpath = package.cpath..path_sep..installer_plugin_path..dir_sep.."lib"..sub_path.."?.lua"; + package.cpath = package.cpath..lua_path_sep..installer_plugin_path..dir_sep.."lib"..sub_path.."?.lua"; end end -- cgit v1.2.3 From 9ef4fbdbf875a09014d32caf8797402801095948 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Thu, 1 Aug 2019 09:29:40 -0700 Subject: util.startup: Reorganized code at setup_plugindir --- util/startup.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/util/startup.lua b/util/startup.lua index e46f98c9..a77237d5 100644 --- a/util/startup.lua +++ b/util/startup.lua @@ -229,15 +229,15 @@ function startup.setup_plugindir() local custom_plugin_paths = config.get("*", "plugin_paths"); local installer_plugin_path = config.get("*", "installer_plugin_path") or "custom_plugins"; local path_sep = package.config:sub(3,3); + installer_plugin_path = config.resolve_relative_path(require "lfs".currentdir(), installer_plugin_path); + require "lfs".mkdir(installer_plugin_path); + config.complement_lua_path(installer_plugin_path); if custom_plugin_paths then -- path1;path2;path3;defaultpath... -- luacheck: ignore 111 CFG_PLUGINDIR = table.concat(custom_plugin_paths, path_sep)..path_sep..(CFG_PLUGINDIR or "plugins"); prosody.paths.plugins = CFG_PLUGINDIR; end - installer_plugin_path = config.resolve_relative_path(require "lfs".currentdir(), installer_plugin_path); - require "lfs".mkdir(installer_plugin_path); - config.complement_lua_path(installer_plugin_path); CFG_PLUGINDIR = installer_plugin_path..path_sep..(CFG_PLUGINDIR or "plugins"); prosody.paths.plugins = CFG_PLUGINDIR; end -- cgit v1.2.3 From ce4f9d56d998d47c6466a7ae2edd1149d88bd334 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Fri, 16 Aug 2019 08:38:29 -0700 Subject: prosodyctl: Fixed a typo --- prosodyctl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prosodyctl b/prosodyctl index 9c94fea4..4c8892ac 100755 --- a/prosodyctl +++ b/prosodyctl @@ -96,7 +96,7 @@ end function commands.remove(arg) if arg[1] == "--help" then - show_usage([[remove]], [[Removes a module installed in the wroking directory's plugins folder]]); + show_usage([[remove]], [[Removes a module installed in the working directory's plugins folder]]); return 1; end table.insert(arg, "remove"); -- cgit v1.2.3 From 68f40ef5ff40602553077c333e5f050b1edbb1c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Fri, 16 Aug 2019 09:26:36 -0700 Subject: util.startup: Changed the way util.paths.complement_lua_path was being accessed --- util/startup.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/startup.lua b/util/startup.lua index a77237d5..02e5f012 100644 --- a/util/startup.lua +++ b/util/startup.lua @@ -231,7 +231,7 @@ function startup.setup_plugindir() local path_sep = package.config:sub(3,3); installer_plugin_path = config.resolve_relative_path(require "lfs".currentdir(), installer_plugin_path); require "lfs".mkdir(installer_plugin_path); - config.complement_lua_path(installer_plugin_path); + require"util.paths".complement_lua_path(installer_plugin_path); if custom_plugin_paths then -- path1;path2;path3;defaultpath... -- luacheck: ignore 111 -- cgit v1.2.3 From bb07c44d5a0dbd76fc9c4650a8fdef383d127ad2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Fri, 16 Aug 2019 10:44:10 -0700 Subject: util.paths: Fixed file termination for package.cpath's extra path --- util/paths.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/paths.lua b/util/paths.lua index de63111e..c225108a 100644 --- a/util/paths.lua +++ b/util/paths.lua @@ -53,7 +53,7 @@ function path_util.complement_lua_path(installer_plugin_path) package.path = package.path..lua_path_sep..installer_plugin_path..dir_sep.."share"..sub_path.."?"..dir_sep.."init.lua"; end if not string.match(package.path, installer_plugin_path) then - package.cpath = package.cpath..lua_path_sep..installer_plugin_path..dir_sep.."lib"..sub_path.."?.lua"; + package.cpath = package.cpath..lua_path_sep..installer_plugin_path..dir_sep.."lib"..sub_path.."?.so"; end end -- cgit v1.2.3 From ebfdb94d96f23b2953b69b0991985bf44691f747 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Fri, 16 Aug 2019 13:54:40 -0700 Subject: util.pluginloader: Added a new path to the variable local_names --- util/pluginloader.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/util/pluginloader.lua b/util/pluginloader.lua index 9ab8f245..af0428c4 100644 --- a/util/pluginloader.lua +++ b/util/pluginloader.lua @@ -36,12 +36,13 @@ end local function load_resource(plugin, resource) resource = resource or "mod_"..plugin..".lua"; - + local lua_version = _VERSION:match(" (.+)$"); local names = { "mod_"..plugin..dir_sep..plugin..dir_sep..resource; -- mod_hello/hello/mod_hello.lua "mod_"..plugin..dir_sep..resource; -- mod_hello/mod_hello.lua plugin..dir_sep..resource; -- hello/mod_hello.lua resource; -- mod_hello.lua + "share"..dir_sep.."lua"..dir_sep..lua_version..dir_sep.."mod_"..plugin..dir_sep..resource; }; return load_file(names); -- cgit v1.2.3 From 32b5c6feeb60545096871fd9bd3d2c3fc18b96dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Fri, 16 Aug 2019 14:02:51 -0700 Subject: core.configmanager: Removed code related to complement_lua_path --- core/configmanager.lua | 2 -- 1 file changed, 2 deletions(-) diff --git a/core/configmanager.lua b/core/configmanager.lua index d501b7cf..090a6a0a 100644 --- a/core/configmanager.lua +++ b/core/configmanager.lua @@ -15,7 +15,6 @@ local envload = require"util.envload".envload; local deps = require"util.dependencies"; local resolve_relative_path = require"util.paths".resolve_relative_path; local glob_to_pattern = require"util.paths".glob_to_pattern; -local complement_lua_path = require"util.paths".complement_lua_path; local path_sep = package.config:sub(1,1); local get_traceback_table = require "util.debug".get_traceback_table; @@ -27,7 +26,6 @@ local _ENV = nil; -- luacheck: std none _M.resolve_relative_path = resolve_relative_path; -- COMPAT -_M.complement_lua_path = complement_lua_path; local parser = nil; -- cgit v1.2.3 From 9d8380db119f65c4bb6d5ef5701c35e7b5722e07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Fri, 16 Aug 2019 14:58:29 -0700 Subject: prosodyctl: Install, remove and list commands now use the call_luarocks function --- prosodyctl | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/prosodyctl b/prosodyctl index 4c8892ac..664deb51 100755 --- a/prosodyctl +++ b/prosodyctl @@ -76,7 +76,7 @@ local show_usage = prosodyctl.show_usage; local show_yesno = prosodyctl.show_yesno; local show_prompt = prosodyctl.show_prompt; local read_password = prosodyctl.read_password; -local execute_command = prosodyctl.execute_command; +local call_luarocks = prosodyctl.call_luarocks; local jid_split = require "util.jid".prepped_split; @@ -90,8 +90,7 @@ function commands.install(arg) show_usage([[install]], [[Installs a prosody/luarocks plugin]]); return 1; end - table.insert(arg, "install"); - execute_command(arg); + call_luarocks(arg[1], "install") end function commands.remove(arg) @@ -99,8 +98,7 @@ function commands.remove(arg) show_usage([[remove]], [[Removes a module installed in the working directory's plugins folder]]); return 1; end - table.insert(arg, "remove"); - execute_command(arg); + call_luarocks(arg[1], "remove") end function commands.list(arg) @@ -108,8 +106,7 @@ function commands.list(arg) show_usage([[list]], [[Shows installed rocks]]); return 1; end - table.insert(arg, "list"); - execute_command(arg); + call_luarocks(arg[1], "list") end function commands.adduser(arg) -- cgit v1.2.3 From d323072816a3060dc0d77f7e58a3ebc3c2f90ff5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Fri, 16 Aug 2019 15:01:57 -0700 Subject: util/prosodyctl: Removed the check_flags and execute_command function --- util/prosodyctl.lua | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/util/prosodyctl.lua b/util/prosodyctl.lua index c6abc02f..316831a9 100644 --- a/util/prosodyctl.lua +++ b/util/prosodyctl.lua @@ -296,16 +296,6 @@ local function get_path_custom_plugins(plugin_paths) end end -local function check_flags(arg) - local flag = "--tree="; - -- There might not be any argument when the list command is calling this function - if arg[1] and arg[1]:sub(1, #flag) == flag then - local dir = arg[1]:match("=(.+)$") - return true, arg[#arg-1], dir; - end - return false, arg[#arg-1]; -end - local function call_luarocks(operation, mod, dir) if operation == "install" then show_message("Installing %s at %s", mod, dir); @@ -322,19 +312,6 @@ local function call_luarocks(operation, mod, dir) end end -local function execute_command(arg) - local operation = arg[#arg] - local tree, mod, dir = check_flags(arg); - if tree then - call_luarocks(operation, mod, dir); - return 0; - else - dir = get_path_custom_plugins(prosody.paths.plugins); - call_luarocks(operation, mod, dir); - return 0; - end -end - return { show_message = show_message; show_warning = show_message; @@ -356,7 +333,5 @@ return { stop = stop; reload = reload; get_path_custom_plugins = get_path_custom_plugins; - check_flags = check_flags; call_luarocks = call_luarocks; - execute_command = execute_command; }; -- cgit v1.2.3 From 6ae5f04781c82e8064ceb9acc91ab9cfdb295969 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Fri, 16 Aug 2019 15:03:50 -0700 Subject: util/prosodyctl: call_luarocks now sets a directory variable itself --- util/prosodyctl.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/util/prosodyctl.lua b/util/prosodyctl.lua index 316831a9..163658f3 100644 --- a/util/prosodyctl.lua +++ b/util/prosodyctl.lua @@ -296,7 +296,8 @@ local function get_path_custom_plugins(plugin_paths) end end -local function call_luarocks(operation, mod, dir) +local function call_luarocks(mod, operation) + local dir = get_path_custom_plugins(prosody.paths.plugins); if operation == "install" then show_message("Installing %s at %s", mod, dir); elseif operation == "remove" then -- cgit v1.2.3 From 469ce79a49fabbe0d42115fa7a2f1a27e3bae3f1 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 17 Aug 2019 15:40:52 +0200 Subject: net.resolvers.service: Fix DNS fallback --- net/resolvers/service.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 -- cgit v1.2.3 From 2dd7b5717587d249dbda1335be3b5975a1c96e59 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 21 Aug 2019 23:15:05 +0200 Subject: core.moduleapi: Uppercase "IQ stanza" for consistency It's written like that elsewhere in the send_iq method --- core/moduleapi.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/moduleapi.lua b/core/moduleapi.lua index b81bbeb2..dcdc41a4 100644 --- a/core/moduleapi.lua +++ b/core/moduleapi.lua @@ -407,7 +407,7 @@ function api:send_iq(stanza, origin, timeout) if iq_cache:get(cache_key) then reject(errutil.new({ type = "modify", condition = "conflict", - text = "iq stanza id attribute already used", + text = "IQ stanza id attribute already used", })); return; end -- cgit v1.2.3 From e0125bcb4c3a1c0f58d6fc291580ca4d54d8f331 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 21 Aug 2019 23:18:08 +0200 Subject: core.moduleapi: Restructure send_iq method for more atomic cleanup All cleanup in one spot instead of two, and at the end which fits with cleanup happening afterwards. --- core/moduleapi.lua | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/core/moduleapi.lua b/core/moduleapi.lua index dcdc41a4..0a8adc36 100644 --- a/core/moduleapi.lua +++ b/core/moduleapi.lua @@ -374,22 +374,21 @@ function api:send_iq(stanza, origin, timeout) type = "wait", condition = "resource-constraint", text = "evicted from iq tracking cache" })); - self:unhook(iq.result_event, iq.result_handler); - self:unhook(iq.error_event, iq.error_handler); end); self._iq_cache = iq_cache; end - return promise.new(function (resolve, reject) - local event_type; - if stanza.attr.from == self.host then - event_type = "host"; - else -- assume bare since we can't hook full jids - event_type = "bare"; - end - local result_event = "iq-result/"..event_type.."/"..stanza.attr.id; - local error_event = "iq-error/"..event_type.."/"..stanza.attr.id; - local cache_key = event_type.."/"..stanza.attr.id; + local event_type; + if stanza.attr.from == self.host then + event_type = "host"; + else -- assume bare since we can't hook full jids + event_type = "bare"; + end + local result_event = "iq-result/"..event_type.."/"..stanza.attr.id; + local error_event = "iq-error/"..event_type.."/"..stanza.attr.id; + local cache_key = event_type.."/"..stanza.attr.id; + + local p = promise.new(function (resolve, reject) local function result_handler(event) if event.stanza.attr.from == stanza.attr.to then resolve(event); @@ -420,15 +419,11 @@ function api:send_iq(stanza, origin, timeout) type = "wait", condition = "remote-server-timeout", text = "IQ stanza timed out", })); - self:unhook(result_event, result_handler); - self:unhook(error_event, error_handler); - iq_cache:set(cache_key, nil); end); local ok = iq_cache:set(cache_key, { reject = reject, resolve = resolve, timeout_handle = timeout_handle, - result_event = result_event, error_event = error_event, result_handler = result_handler, error_handler = error_handler; }); @@ -442,6 +437,18 @@ function api:send_iq(stanza, origin, timeout) self:send(stanza, origin); end); + + p:finally(function () + local iq = iq_cache:get(cache_key); + if iq then + self:unhook(result_event, iq.result_handler); + self:unhook(error_event, iq.error_handler); + iq.timeout_handle:stop(); + iq_cache:set(cache_key, nil); + end + end); + + return p; end function api:broadcast(jids, stanza, iter) -- cgit v1.2.3 From 761731222d7836a8567bb6cc1fe1788aa15748a0 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 22 Aug 2019 22:23:04 +0200 Subject: mod_vcard_legacy: Use PEP nickname if vcard4 data is unavailable Last remaining nice feature from mod_profile. Allows setting eg nickname and avatar as completely public while restricting private details in vcard4 to only contacts. --- plugins/mod_vcard_legacy.lua | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/plugins/mod_vcard_legacy.lua b/plugins/mod_vcard_legacy.lua index 89ef1a4f..91497493 100644 --- a/plugins/mod_vcard_legacy.lua +++ b/plugins/mod_vcard_legacy.lua @@ -116,6 +116,14 @@ module:hook("iq-get/bare/vcard-temp:vCard", function (event) :up(); end end + else + local ok, _, nick_item = pep_service:get_last_item("http://jabber.org/protocol/nick", stanza.attr.from); + if ok and nick_item then + local nickname = nick_item:get_child_text("nick", "http://jabber.org/protocol/nick"); + if nickname then + vcard_temp:text_tag("NICKNAME", nickname); + end + end end local meta_ok, avatar_meta = pep_service:get_items("urn:xmpp:avatar:metadata", stanza.attr.from); -- cgit v1.2.3 From 73acfb61b4613c13a96e47e11acdd14ac011961d Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 13 Jan 2019 14:01:31 +0100 Subject: util.sasl.scram: Factor out SHA-1 specific getAuthenticationDatabaseSHA1 This makes the code more generic, allowing SHA-1 to be replaced --- util/sasl/scram.lua | 45 +++++++++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/util/sasl/scram.lua b/util/sasl/scram.lua index 043f328b..0ca20a52 100644 --- a/util/sasl/scram.lua +++ b/util/sasl/scram.lua @@ -14,9 +14,7 @@ local s_match = string.match; local type = type local base64 = require "util.encodings".base64; -local hmac_sha1 = require "util.hashes".hmac_sha1; -local sha1 = require "util.hashes".sha1; -local Hi = require "util.hashes".scram_Hi_sha1; +local hashes = require "util.hashes"; local generate_uuid = require "util.uuid".generate; local saslprep = require "util.encodings".stringprep.saslprep; local nodeprep = require "util.encodings".stringprep.nodeprep; @@ -99,20 +97,22 @@ local function hashprep(hashname) return hashname:lower():gsub("-", "_"); end -local function getAuthenticationDatabaseSHA1(password, salt, iteration_count) - if type(password) ~= "string" or type(salt) ~= "string" or type(iteration_count) ~= "number" then - return false, "inappropriate argument types" - end - if iteration_count < 4096 then - log("warn", "Iteration count < 4096 which is the suggested minimum according to RFC 5802.") +local function get_scram_hasher(H, HMAC, Hi) + return function (password, salt, iteration_count) + if type(password) ~= "string" or type(salt) ~= "string" or type(iteration_count) ~= "number" then + return false, "inappropriate argument types" + end + if iteration_count < 4096 then + log("warn", "Iteration count < 4096 which is the suggested minimum according to RFC 5802.") + end + local salted_password = Hi(password, salt, iteration_count); + local stored_key = H(HMAC(salted_password, "Client Key")) + local server_key = HMAC(salted_password, "Server Key"); + return true, stored_key, server_key end - local salted_password = Hi(password, salt, iteration_count); - local stored_key = sha1(hmac_sha1(salted_password, "Client Key")) - local server_key = hmac_sha1(salted_password, "Server Key"); - return true, stored_key, server_key end -local function scram_gen(hash_name, H_f, HMAC_f) +local function scram_gen(hash_name, H_f, HMAC_f, get_auth_db) local profile_name = "scram_" .. hashprep(hash_name); local function scram_hash(self, message) local support_channel_binding = false; @@ -177,7 +177,7 @@ local function scram_gen(hash_name, H_f, HMAC_f) iteration_count = default_i; local succ; - succ, stored_key, server_key = getAuthenticationDatabaseSHA1(password, salt, iteration_count); + succ, stored_key, server_key = get_auth_db(password, salt, iteration_count); if not succ then log("error", "Generating authentication database failed. Reason: %s", stored_key); return "failure", "temporary-auth-failure"; @@ -247,22 +247,27 @@ local function scram_gen(hash_name, H_f, HMAC_f) return scram_hash; end +local auth_db_getters = {} local function init(registerMechanism) - local function registerSCRAMMechanism(hash_name, hash, hmac_hash) + local function registerSCRAMMechanism(hash_name, hash, hmac_hash, pbkdf2) + local get_auth_db = get_scram_hasher(hash, hmac_hash, pbkdf2); + auth_db_getters[hash_name] = get_auth_db; registerMechanism("SCRAM-"..hash_name, {"plain", "scram_"..(hashprep(hash_name))}, - scram_gen(hash_name:lower(), hash, hmac_hash)); + scram_gen(hash_name:lower(), hash, hmac_hash, get_auth_db)); -- register channel binding equivalent registerMechanism("SCRAM-"..hash_name.."-PLUS", {"plain", "scram_"..(hashprep(hash_name))}, - scram_gen(hash_name:lower(), hash, hmac_hash), {"tls-unique"}); + scram_gen(hash_name:lower(), hash, hmac_hash, get_auth_db), {"tls-unique"}); end - registerSCRAMMechanism("SHA-1", sha1, hmac_sha1); + registerSCRAMMechanism("SHA-1", hashes.sha1, hashes.hmac_sha1, hashes.pbkdf2_hmac_sha1); end return { - getAuthenticationDatabaseSHA1 = getAuthenticationDatabaseSHA1; + get_hash = get_scram_hasher; + hashers = auth_db_getters; + getAuthenticationDatabaseSHA1 = get_scram_hasher(hashes.sha1, hashes.sha256, hashes.pbkdf2_hmac_sha1); init = init; } -- cgit v1.2.3 From 49e8cbabff23741159a457d0eae8262e79e4561c Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 13 Jan 2019 14:02:29 +0100 Subject: util.sasl.scram: Add support for SCRAM-SHA-256 --- util/sasl/scram.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/util/sasl/scram.lua b/util/sasl/scram.lua index 0ca20a52..793382b8 100644 --- a/util/sasl/scram.lua +++ b/util/sasl/scram.lua @@ -263,6 +263,7 @@ local function init(registerMechanism) end registerSCRAMMechanism("SHA-1", hashes.sha1, hashes.hmac_sha1, hashes.pbkdf2_hmac_sha1); + registerSCRAMMechanism("SHA-256", hashes.sha256, hashes.hmac_sha256, hashes.pbkdf2_hmac_sha256); end return { -- cgit v1.2.3 From a746aba7a27bcff70944f07268e7aef519a0223b Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 13 Jan 2019 14:02:56 +0100 Subject: mod_auth_internal_hashed: Add support for optionally using SCRAM-SHA-256 instead of SHA-1 This will currently require a hard reset of all passwords back to plain. This will be least painful on new deployments. --- CHANGES | 1 + plugins/mod_auth_internal_hashed.lua | 13 +++++++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/CHANGES b/CHANGES index 8b6675a5..fe42a997 100644 --- a/CHANGES +++ b/CHANGES @@ -9,6 +9,7 @@ TRUNK - Archive quotas - mod\_mimicking - Rewritten migrator +- SCRAM-SHA-256 0.11.0 ====== diff --git a/plugins/mod_auth_internal_hashed.lua b/plugins/mod_auth_internal_hashed.lua index 35764afb..174e848a 100644 --- a/plugins/mod_auth_internal_hashed.lua +++ b/plugins/mod_auth_internal_hashed.lua @@ -9,7 +9,7 @@ local max = math.max; -local getAuthenticationDatabaseSHA1 = require "util.sasl.scram".getAuthenticationDatabaseSHA1; +local scram_hashers = require "util.sasl.scram".hashers; local usermanager = require "core.usermanager"; local generate_uuid = require "util.uuid".generate; local new_sasl = require "util.sasl".new; @@ -21,7 +21,8 @@ local host = module.host; local accounts = module:open_store("accounts"); - +local hash_name = module:get_option_string("password_hash", "SHA-1"); +local get_auth_db = assert(scram_hashers[hash_name], "SCRAM-"..hash_name.." not supported by SASL library"); -- Default; can be set per-user local default_iteration_count = 4096; @@ -49,7 +50,7 @@ function provider.test_password(username, password) return nil, "Auth failed. Stored salt and iteration count information is not complete."; end - local valid, stored_key, server_key = getAuthenticationDatabaseSHA1(password, credentials.salt, credentials.iteration_count); + local valid, stored_key, server_key = get_auth_db(password, credentials.salt, credentials.iteration_count); local stored_key_hex = to_hex(stored_key); local server_key_hex = to_hex(server_key); @@ -67,7 +68,7 @@ function provider.set_password(username, password) if account then account.salt = generate_uuid(); account.iteration_count = max(account.iteration_count or 0, default_iteration_count); - local valid, stored_key, server_key = getAuthenticationDatabaseSHA1(password, account.salt, account.iteration_count); + local valid, stored_key, server_key = get_auth_db(password, account.salt, account.iteration_count); local stored_key_hex = to_hex(stored_key); local server_key_hex = to_hex(server_key); @@ -98,7 +99,7 @@ function provider.create_user(username, password) return accounts:set(username, {}); end local salt = generate_uuid(); - local valid, stored_key, server_key = getAuthenticationDatabaseSHA1(password, salt, default_iteration_count); + local valid, stored_key, server_key = get_auth_db(password, salt, default_iteration_count); local stored_key_hex = to_hex(stored_key); local server_key_hex = to_hex(server_key); return accounts:set(username, { @@ -116,7 +117,7 @@ function provider.get_sasl_handler() plain_test = function(_, username, password, realm) return usermanager.test_password(username, realm, password), true; end, - scram_sha_1 = function(_, username) + ["scram_"..hash_name:gsub("%-","_"):lower()] = function(_, username) local credentials = accounts:get(username); if not credentials then return; end if credentials.password then -- cgit v1.2.3 From b8ad8ccc88415e201eea3530120ce4fda4fdaf04 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 22 Aug 2019 01:00:31 +0200 Subject: mod_auth_internal_hashed: Precompute SCRAM authentication profile name (thanks MattJ) --- plugins/mod_auth_internal_hashed.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/mod_auth_internal_hashed.lua b/plugins/mod_auth_internal_hashed.lua index 174e848a..be22a8d8 100644 --- a/plugins/mod_auth_internal_hashed.lua +++ b/plugins/mod_auth_internal_hashed.lua @@ -23,6 +23,7 @@ local accounts = module:open_store("accounts"); local hash_name = module:get_option_string("password_hash", "SHA-1"); local get_auth_db = assert(scram_hashers[hash_name], "SCRAM-"..hash_name.." not supported by SASL library"); +local scram_name = "scram_"..hash_name:gsub("%-","_"):lower(); -- Default; can be set per-user local default_iteration_count = 4096; @@ -117,7 +118,7 @@ function provider.get_sasl_handler() plain_test = function(_, username, password, realm) return usermanager.test_password(username, realm, password), true; end, - ["scram_"..hash_name:gsub("%-","_"):lower()] = function(_, username) + [scram_name] = function(_, username) local credentials = accounts:get(username); if not credentials then return; end if credentials.password then -- cgit v1.2.3 From 39cb87a158a017441e129ed8514f83bfbfdae64f Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 23 Aug 2019 01:04:00 +0200 Subject: mod_storage_*: Tweak :summary API to allow future expansion with more fields Eg might want to include last message, timestamp, chat state or other info. --- plugins/mod_storage_internal.lua | 8 +++++--- plugins/mod_storage_memory.lua | 8 +++++--- plugins/mod_storage_sql.lua | 8 +++++--- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/plugins/mod_storage_internal.lua b/plugins/mod_storage_internal.lua index 2556224d..28caa071 100644 --- a/plugins/mod_storage_internal.lua +++ b/plugins/mod_storage_internal.lua @@ -217,11 +217,13 @@ end function archive:summary(username, query) local iter, err = self:find(username, query) if not iter then return iter, err; end - local summary = {}; + local counts = {}; for _, _, _, with in iter do - summary[with] = (summary[with] or 0) + 1; + counts[with] = (counts[with] or 0) + 1; end - return summary; + return { + counts = counts; + }; end function archive:users() diff --git a/plugins/mod_storage_memory.lua b/plugins/mod_storage_memory.lua index 376ae277..cac9bc40 100644 --- a/plugins/mod_storage_memory.lua +++ b/plugins/mod_storage_memory.lua @@ -170,11 +170,13 @@ end function archive_store:summary(username, query) local iter, err = self:find(username, query) if not iter then return iter, err; end - local summary = {}; + local counts = {}; for _, _, _, with in iter do - summary[with] = (summary[with] or 0) + 1; + counts[with] = (counts[with] or 0) + 1; end - return summary; + return { + counts = counts; + }; end diff --git a/plugins/mod_storage_sql.lua b/plugins/mod_storage_sql.lua index 518e2654..d6779946 100644 --- a/plugins/mod_storage_sql.lua +++ b/plugins/mod_storage_sql.lua @@ -446,12 +446,14 @@ function archive_store:summary(username, query) return engine:select(sql_query, unpack(args)); end); if not ok then return ok, result end - local summary = {}; + local counts = {}; for row in result do local with, count = row[1], row[2]; - summary[with] = count; + counts[with] = count; end - return summary; + return { + counts = counts; + }; end function archive_store:delete(username, query) -- cgit v1.2.3 From d3c559bcc89ef91ebecde3cc3b4520873a98ea4d Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 23 Aug 2019 01:10:27 +0200 Subject: mod_storage_*: Include timestamp of latest message in :summary API Clients may want to show a list of conversations ordered by how timestamp of most recent message. The counts allow a badge with unread message counter. --- plugins/mod_storage_internal.lua | 5 ++++- plugins/mod_storage_memory.lua | 5 ++++- plugins/mod_storage_sql.lua | 5 ++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/plugins/mod_storage_internal.lua b/plugins/mod_storage_internal.lua index 28caa071..1ee860d6 100644 --- a/plugins/mod_storage_internal.lua +++ b/plugins/mod_storage_internal.lua @@ -218,11 +218,14 @@ function archive:summary(username, query) local iter, err = self:find(username, query) if not iter then return iter, err; end local counts = {}; - for _, _, _, with in iter do + local latest = {}; + for _, _, when, with in iter do counts[with] = (counts[with] or 0) + 1; + latest[with] = when; end return { counts = counts; + latest = latest; }; end diff --git a/plugins/mod_storage_memory.lua b/plugins/mod_storage_memory.lua index cac9bc40..6a04e003 100644 --- a/plugins/mod_storage_memory.lua +++ b/plugins/mod_storage_memory.lua @@ -171,11 +171,14 @@ function archive_store:summary(username, query) local iter, err = self:find(username, query) if not iter then return iter, err; end local counts = {}; - for _, _, _, with in iter do + local latest = {}; + for _, _, when, with in iter do counts[with] = (counts[with] or 0) + 1; + latest[with] = when; end return { counts = counts; + latest = latest; }; end diff --git a/plugins/mod_storage_sql.lua b/plugins/mod_storage_sql.lua index d6779946..dc960cad 100644 --- a/plugins/mod_storage_sql.lua +++ b/plugins/mod_storage_sql.lua @@ -424,7 +424,7 @@ function archive_store:summary(username, query) local user,store = username,self.store; local ok, result = engine:transaction(function() local sql_query = [[ - SELECT DISTINCT "with", COUNT(*) + SELECT DISTINCT "with", COUNT(*), MAX("when") FROM "prosodyarchive" WHERE %s GROUP BY "with" @@ -447,12 +447,15 @@ function archive_store:summary(username, query) end); if not ok then return ok, result end local counts = {}; + local latest = {}; for row in result do local with, count = row[1], row[2]; counts[with] = count; + latest[with] = row[3]; end return { counts = counts; + latest = latest; }; end -- cgit v1.2.3 From 9d5e31fd481a38bc7e346697cdacb8ef5cbd1ae5 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 23 Aug 2019 01:15:44 +0200 Subject: mod_storage_*: Also include timestmap of first message in :summary API For completeness along with most recent timestamp. Might be nice to be able to order by oldest unread message. --- plugins/mod_storage_internal.lua | 5 +++++ plugins/mod_storage_memory.lua | 5 +++++ plugins/mod_storage_sql.lua | 8 +++++--- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/plugins/mod_storage_internal.lua b/plugins/mod_storage_internal.lua index 1ee860d6..b3702fe1 100644 --- a/plugins/mod_storage_internal.lua +++ b/plugins/mod_storage_internal.lua @@ -218,13 +218,18 @@ function archive:summary(username, query) local iter, err = self:find(username, query) if not iter then return iter, err; end local counts = {}; + local earliest = {}; local latest = {}; for _, _, when, with in iter do counts[with] = (counts[with] or 0) + 1; + if earliest[with] == nil then + earliest[with] = when; + end latest[with] = when; end return { counts = counts; + earliest = earliest; latest = latest; }; end diff --git a/plugins/mod_storage_memory.lua b/plugins/mod_storage_memory.lua index 6a04e003..8beb8c01 100644 --- a/plugins/mod_storage_memory.lua +++ b/plugins/mod_storage_memory.lua @@ -171,13 +171,18 @@ function archive_store:summary(username, query) local iter, err = self:find(username, query) if not iter then return iter, err; end local counts = {}; + local earliest = {}; local latest = {}; for _, _, when, with in iter do counts[with] = (counts[with] or 0) + 1; + if earliest[with] == nil then + earliest[with] = when; + end latest[with] = when; end return { counts = counts; + earliest = earliest; latest = latest; }; end diff --git a/plugins/mod_storage_sql.lua b/plugins/mod_storage_sql.lua index dc960cad..666cee41 100644 --- a/plugins/mod_storage_sql.lua +++ b/plugins/mod_storage_sql.lua @@ -424,7 +424,7 @@ function archive_store:summary(username, query) local user,store = username,self.store; local ok, result = engine:transaction(function() local sql_query = [[ - SELECT DISTINCT "with", COUNT(*), MAX("when") + SELECT DISTINCT "with", COUNT(*), MIN("when"), MAX("when") FROM "prosodyarchive" WHERE %s GROUP BY "with" @@ -447,14 +447,16 @@ function archive_store:summary(username, query) end); if not ok then return ok, result end local counts = {}; - local latest = {}; + local earliest, latest = {}, {}; for row in result do local with, count = row[1], row[2]; counts[with] = count; - latest[with] = row[3]; + earliest[with] = row[3]; + latest[with] = row[4]; end return { counts = counts; + earliest = earliest; latest = latest; }; end -- cgit v1.2.3 From d45b4c026faac9849324a8db0747e2729f19362c Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 23 Aug 2019 01:28:53 +0200 Subject: mod_storage_internal: Include last text message A protocol built on this API now allows showing a list of unread conversations with a counter, ordered by either oldest or newest message, along with the text body itself. --- plugins/mod_storage_internal.lua | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/mod_storage_internal.lua b/plugins/mod_storage_internal.lua index b3702fe1..c8b902cf 100644 --- a/plugins/mod_storage_internal.lua +++ b/plugins/mod_storage_internal.lua @@ -220,17 +220,20 @@ function archive:summary(username, query) local counts = {}; local earliest = {}; local latest = {}; - for _, _, when, with in iter do + local body = {}; + for _, stanza, when, with in iter do counts[with] = (counts[with] or 0) + 1; if earliest[with] == nil then earliest[with] = when; end latest[with] = when; + body[with] = stanza:get_child_text("body") or body[with]; end return { counts = counts; earliest = earliest; latest = latest; + body = body; }; end -- cgit v1.2.3 From df3f84ce546ea55074a11a69aab473012369af8f Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 25 Aug 2019 20:22:35 +0200 Subject: core.certmanager: Move EECDH ciphers before EDH in default cipherstring The original intent of having kEDH before kEECDH was that if a `dhparam` file was specified, this would be interpreted as a preference by the admin for old and well-tested Diffie-Hellman key agreement over newer elliptic curve ones. Otherwise the faster elliptic curve ciphersuites would be preferred. This didn't really work as intended since this affects the ClientHello on outgoing s2s connections, leading to some servers using poorly configured kEDH. With Debian shipping OpenSSL settings that enforce a higher security level, this caused interoperability problems with servers that use DH params smaller than 2048 bits. E.g. jabber.org at the time of this writing has 1024 bit DH params. MattJ says > Curves have won, and OpenSSL is less weird about them now --- core/certmanager.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/certmanager.lua b/core/certmanager.lua index 63f314f8..5d3cc2c1 100644 --- a/core/certmanager.lua +++ b/core/certmanager.lua @@ -123,8 +123,8 @@ local core_defaults = { "P-521", }; ciphers = { -- Enabled ciphers in order of preference: - "HIGH+kEDH", -- Ephemeral Diffie-Hellman key exchange, if a 'dhparam' file is set "HIGH+kEECDH", -- Ephemeral Elliptic curve Diffie-Hellman key exchange + "HIGH+kEDH", -- Ephemeral Diffie-Hellman key exchange, if a 'dhparam' file is set "HIGH", -- Other "High strength" ciphers -- Disabled cipher suites: "!PSK", -- Pre-Shared Key - not used for XMPP -- cgit v1.2.3 From 1295efd944da3e95e66243eab2ac4fe0b4de556e Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 25 Aug 2019 21:31:04 +0200 Subject: MUC: Simplify nickname refresh loop Affiliation data is passed as a loop variable so no need to retrieve it --- plugins/muc/register.lib.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/muc/register.lib.lua b/plugins/muc/register.lib.lua index 21cb3f2f..da106f8c 100644 --- a/plugins/muc/register.lib.lua +++ b/plugins/muc/register.lib.lua @@ -15,8 +15,7 @@ local function get_reserved_nicks(room) end module:log("debug", "Refreshing reserved nicks..."); local reserved_nicks = {}; - for jid in room:each_affiliation() do - local data = room._affiliation_data[jid]; + for jid, _, data in room:each_affiliation() do local nick = data and data.reserved_nickname; module:log("debug", "Refreshed for %s: %s", jid, nick); if nick then -- cgit v1.2.3 From b16782257d441196d7fbab2823ba8fa878c4c056 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 25 Aug 2019 23:12:55 +0200 Subject: Remove COMPAT with temporary luasec fork The changes in the temporary fork were merged into mainline luasec ca 2013 and included in the 0.5 release in 2014. --- core/certmanager.lua | 7 ------- plugins/mod_c2s.lua | 3 --- plugins/mod_s2s/mod_s2s.lua | 1 - plugins/mod_s2s_auth_certs.lua | 3 --- 4 files changed, 14 deletions(-) diff --git a/core/certmanager.lua b/core/certmanager.lua index 5d3cc2c1..f81429ee 100644 --- a/core/certmanager.lua +++ b/core/certmanager.lua @@ -148,13 +148,6 @@ local path_options = { -- These we pass through resolve_path() key = true, certificate = true, cafile = true, capath = true, dhparam = true } -if luasec_version < 5 and ssl_x509 then - -- COMPAT mw/luasec-hg - 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 - local function create_context(host, mode, ...) local cfg = new_config(); cfg:apply(core_defaults); diff --git a/plugins/mod_c2s.lua b/plugins/mod_c2s.lua index 53af34f0..02a0c5eb 100644 --- a/plugins/mod_c2s.lua +++ b/plugins/mod_c2s.lua @@ -97,7 +97,6 @@ function stream_callbacks.streamopened(session, attr) session.compressed = info.compression; else (session.log or log)("info", "Stream encrypted"); - session.compressed = sock.compression and sock:compression(); --COMPAT mw/luasec-hg end end @@ -257,8 +256,6 @@ function listener.onconnect(conn) local sock = conn:socket(); if sock.info then session.compressed = sock:info"compression"; - elseif sock.compression then - session.compressed = sock:compression(); --COMPAT mw/luasec-hg end end diff --git a/plugins/mod_s2s/mod_s2s.lua b/plugins/mod_s2s/mod_s2s.lua index ea19f7ad..5a0d2a49 100644 --- a/plugins/mod_s2s/mod_s2s.lua +++ b/plugins/mod_s2s/mod_s2s.lua @@ -320,7 +320,6 @@ function stream_callbacks._streamopened(session, attr) session.compressed = info.compression; else (session.log or log)("info", "Stream encrypted"); - session.compressed = sock.compression and sock:compression(); --COMPAT mw/luasec-hg end end diff --git a/plugins/mod_s2s_auth_certs.lua b/plugins/mod_s2s_auth_certs.lua index dd0eb3cb..76e134a7 100644 --- a/plugins/mod_s2s_auth_certs.lua +++ b/plugins/mod_s2s_auth_certs.lua @@ -17,9 +17,6 @@ module:hook("s2s-check-certificate", function(event) local chain_valid, errors; if conn.getpeerverification then chain_valid, errors = conn:getpeerverification(); - elseif conn.getpeerchainvalid then -- COMPAT mw/luasec-hg - chain_valid, errors = conn:getpeerchainvalid(); - errors = (not chain_valid) and { { errors } } or nil; else chain_valid, errors = false, { { "Chain verification not supported by this version of LuaSec" } }; end -- cgit v1.2.3 From f39535cfd039d481457c27cee5154bdfb1ff3621 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 25 Aug 2019 23:25:42 +0200 Subject: core.certmanager: Remove unused import [luacheck] --- core/certmanager.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/core/certmanager.lua b/core/certmanager.lua index f81429ee..64528c34 100644 --- a/core/certmanager.lua +++ b/core/certmanager.lua @@ -20,7 +20,6 @@ end local configmanager = require "core.configmanager"; local log = require "util.logger".init("certmanager"); local ssl_context = ssl.context or softreq"ssl.context"; -local ssl_x509 = ssl.x509 or softreq"ssl.x509"; local ssl_newcontext = ssl.newcontext; local new_config = require"util.sslconfig".new; local stat = require "lfs".attributes; -- cgit v1.2.3 From 4cc299fc0214368012a2775b94c746d2290327a0 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 28 Aug 2019 01:41:00 +0200 Subject: net.server_epoll: Add support for opportunistic writes This tries to flush data to the underlying sockets when receiving writes. This should lead to fewer timer objects being around. On the other hand, this leads to more and smaller writes which may translate to more TCP/IP packets being sent, depending on how the kernel handles this. This trades throughput for lower latency. --- net/server_epoll.lua | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index 49ad48ea..ef021851 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -66,6 +66,9 @@ local default_config = { __index = { -- EXPERIMENTAL -- Whether to kill connections in case of callback errors. fatal_errors = false; + + -- Attempt writes instantly + opportunistic_writes = false; }}; local cfg = default_config.__index; @@ -413,6 +416,7 @@ function interface:onwritable() for i = #buffer, 2, -1 do buffer[i] = nil; end + self:set(nil, true); self:setwritetimeout(); end if err == "wantwrite" or err == "timeout" then @@ -439,6 +443,10 @@ function interface:write(data) self.writebuffer = { data }; end if not self._write_lock then + if cfg.opportunistic_writes then + self:onwritable(); + return #data; + end self:setwritetimeout(); self:set(nil, true); end -- cgit v1.2.3 From 59c1712dfe1ba856aa8c3bd3e0dfa5c8db7ccec1 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 31 Aug 2019 16:04:50 +0200 Subject: MUC: Add a test covering basic room creation, messages and destruction --- spec/scansion/muc_create_destroy.scs | 242 +++++++++++++++++++++++++++++++++++ 1 file changed, 242 insertions(+) create mode 100644 spec/scansion/muc_create_destroy.scs diff --git a/spec/scansion/muc_create_destroy.scs b/spec/scansion/muc_create_destroy.scs new file mode 100644 index 00000000..b24ef4a2 --- /dev/null +++ b/spec/scansion/muc_create_destroy.scs @@ -0,0 +1,242 @@ +# MUC creation, basic messages and destruction + +[Client] Romeo + jid: romeo@localhost/mK0dD6Ha + password: password + +[Client] Juliet + jid: juliet@localhost/lVwkim_k + password: password + +----- + +Romeo connects + +Romeo sends: + + + + +Romeo receives: + + + + + + + + + +Romeo receives: + + + + +Romeo sends: + + + + + + +Romeo receives: + + +Juliet connects + +Romeo sends: + + Where are thou my Juliet? + + +Romeo receives: + + Where are thou my Juliet? + + +Juliet sends: + + + + +Juliet receives: + + + + + + + +Juliet receives: + + + + + + + + +Juliet receives: + + Where are thou my Juliet? + + + + +Juliet receives: + + + + +Romeo receives: + + + + + + + +Juliet sends: + + /me jumps out from behind a tree + + +Romeo receives: + + /me jumps out from behind a tree + + +Juliet receives: + + /me jumps out from behind a tree + + +Juliet sends: + + Here I am! + + +Romeo receives: + + Here I am! + + +Juliet receives: + + Here I am! + + +Romeo sends: + + What is this place? + + +Romeo receives: + + What is this place? + + +Juliet receives: + + What is this place? + + +Juliet sends: + + I think we're in a script! + + +Romeo receives: + + I think we're in a script! + + +Juliet receives: + + I think we're in a script! + + +Romeo sends: + + Oh no! Does that mean our love is not real?! + + +Romeo receives: + + Oh no! Does that mean our love is not real?! + + +Juliet receives: + + Oh no! Does that mean our love is not real?! + + +Juliet sends: + + I refuse to accept this! Let's burn this place to the ground! + + +Romeo receives: + + I refuse to accept this! Let's burn this place to the ground! + + +Juliet receives: + + I refuse to accept this! Let's burn this place to the ground! + + +Romeo sends: + + Yes! + + +Romeo receives: + + Yes! + + +Juliet receives: + + Yes! + + +Romeo sends: + + + + We refuse to live in this fantasy! + + + + +Juliet receives: + + + + We refuse to live in this fantasy! + + + + + + +Romeo receives: + + + + We refuse to live in this fantasy! + + + + + + +Romeo receives: + + +Juliet disconnects + +Romeo disconnects + +# recording ended on 2019-08-31T13:45:32Z -- cgit v1.2.3 From 887ccb25c5a26f7d645e626b3ad915cf84793dde Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 31 Aug 2019 16:15:51 +0200 Subject: MUC: Fix delay tag @from in test to be the room JID (#1054 came back) --- spec/scansion/muc_create_destroy.scs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/scansion/muc_create_destroy.scs b/spec/scansion/muc_create_destroy.scs index b24ef4a2..4b16fbf2 100644 --- a/spec/scansion/muc_create_destroy.scs +++ b/spec/scansion/muc_create_destroy.scs @@ -79,8 +79,8 @@ Juliet receives: Juliet receives: Where are thou my Juliet? - - + + Juliet receives: -- cgit v1.2.3 From 9afa1ac496bac9d9eaaf687edbdde3f0c7fb66e1 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 1 Sep 2019 01:55:55 +0200 Subject: net.server: Accept and save an 'extra' field for client connections This lets code attach some extra data to be attached to client connections. --- net/server_epoll.lua | 11 ++++++----- net/server_event.lua | 11 ++++++----- net/server_select.lua | 12 +++++++----- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index ef021851..96e7b201 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -560,7 +560,7 @@ function interface:tlshandskake() end end -local function wrapsocket(client, server, read_size, listeners, tls_ctx) -- luasocket object -> interface object +local function wrapsocket(client, server, read_size, listeners, tls_ctx, extra) -- luasocket object -> interface object client:settimeout(0); local conn = setmetatable({ conn = client; @@ -572,6 +572,7 @@ local function wrapsocket(client, server, read_size, listeners, tls_ctx) -- luas 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())); + extra = extra; }, interface_mt); conn:updatenames(); @@ -701,8 +702,8 @@ local function addserver(addr, port, listeners, read_size, tls_ctx) end -- COMPAT -local function wrapclient(conn, addr, port, listeners, read_size, tls_ctx) - local client = wrapsocket(conn, nil, read_size, listeners, tls_ctx); +local function wrapclient(conn, addr, port, listeners, read_size, tls_ctx, extra) + local client = wrapsocket(conn, nil, read_size, listeners, tls_ctx, extra); if not client.peername then client.peername, client.peerport = addr, port; end @@ -715,7 +716,7 @@ local function wrapclient(conn, addr, port, listeners, read_size, tls_ctx) end -- New outgoing TCP connection -local function addclient(addr, port, listeners, read_size, tls_ctx, typ) +local function addclient(addr, port, listeners, read_size, tls_ctx, typ, extra) local create; if not typ then local n = inet_pton(addr); @@ -738,7 +739,7 @@ local function addclient(addr, port, listeners, read_size, tls_ctx, typ) if not ok then return ok, err; end local ok, err = conn:setpeername(addr, port); if not ok and err ~= "timeout" then return ok, err; end - local client = wrapsocket(conn, nil, read_size, listeners, tls_ctx) + local client = wrapsocket(conn, nil, read_size, listeners, tls_ctx, extra) local ok, err = client:init(); if not ok then return ok, err; end if tls_ctx then diff --git a/net/server_event.lua b/net/server_event.lua index fde79d86..950a7a5c 100644 --- a/net/server_event.lua +++ b/net/server_event.lua @@ -479,7 +479,7 @@ end -- End of client interface methods -local function handleclient( client, ip, port, server, pattern, listener, sslctx ) -- creates an client interface +local function handleclient( client, ip, port, server, pattern, listener, sslctx, extra ) -- creates an client interface --vdebug("creating client interfacce...") local interface = { type = "client"; @@ -515,6 +515,7 @@ local function handleclient( client, ip, port, server, pattern, listener, sslctx _serverport = (server and server:port() or nil), _sslctx = sslctx; -- parameters _usingssl = false; -- client is using ssl; + extra = extra; } if not has_luasec then interface.starttls = false; end interface.id = tostring(interface):match("%x+$"); @@ -749,14 +750,14 @@ local function addserver( addr, port, listener, pattern, sslctx ) -- TODO: chec }); end -local function wrapclient( client, ip, port, listeners, pattern, sslctx ) - local interface = handleclient( client, ip, port, nil, pattern, listeners, sslctx ) +local function wrapclient( client, ip, port, listeners, pattern, sslctx, extra ) + local interface = handleclient( client, ip, port, nil, pattern, listeners, sslctx, extra ) interface:_start_connection(sslctx) return interface, client --function handleclient( client, ip, port, server, pattern, listener, _, sslctx ) -- creates an client interface end -local function addclient( addr, serverport, listener, pattern, sslctx, typ ) +local function addclient( addr, serverport, listener, pattern, sslctx, typ, extra ) if sslctx and not has_luasec then debug "need luasec, but not available" return nil, "luasec not found" @@ -783,7 +784,7 @@ local function addclient( addr, serverport, listener, pattern, sslctx, typ ) local res, err = client:setpeername( addr, serverport ) -- connect if res or ( err == "timeout" ) then local ip, port = client:getsockname( ) - local interface = wrapclient( client, ip, serverport, listener, pattern, sslctx ) + local interface = wrapclient( client, ip, serverport, listener, pattern, sslctx, extra ) debug( "new connection id:", interface.id ) return interface, err else diff --git a/net/server_select.lua b/net/server_select.lua index e14c126e..de183331 100644 --- a/net/server_select.lua +++ b/net/server_select.lua @@ -266,7 +266,7 @@ wrapserver = function( listeners, socket, ip, serverport, pattern, sslctx, ssldi return handler end -wrapconnection = function( server, listeners, socket, ip, serverport, clientport, pattern, sslctx, ssldirect ) -- this function wraps a client to a handler object +wrapconnection = function( server, listeners, socket, ip, serverport, clientport, pattern, sslctx, ssldirect, extra ) -- this function wraps a client to a handler object if socket:getfd() >= _maxfd then out_error("server.lua: Disallowed FD number: "..socket:getfd()) -- PROTIP: Switch to libevent @@ -316,6 +316,8 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport local handler = bufferqueue -- saves a table ^_^ + handler.extra = extra + handler.dispatch = function( ) return dispatch end @@ -1005,8 +1007,8 @@ end --// EXPERIMENTAL //-- -local wrapclient = function( socket, ip, serverport, listeners, pattern, sslctx ) - local handler, socket, err = wrapconnection( nil, listeners, socket, ip, serverport, "clientport", pattern, sslctx, sslctx) +local wrapclient = function( socket, ip, serverport, listeners, pattern, sslctx, extra ) + local handler, socket, err = wrapconnection( nil, listeners, socket, ip, serverport, "clientport", pattern, sslctx, sslctx, extra) if not handler then return nil, err end _socketlist[ socket ] = handler if not sslctx then @@ -1025,7 +1027,7 @@ local wrapclient = function( socket, ip, serverport, listeners, pattern, sslctx return handler, socket end -local addclient = function( address, port, listeners, pattern, sslctx, typ ) +local addclient = function( address, port, listeners, pattern, sslctx, typ, extra ) local err if type( listeners ) ~= "table" then err = "invalid listener table" @@ -1062,7 +1064,7 @@ local addclient = function( address, port, listeners, pattern, sslctx, typ ) client:settimeout( 0 ) local ok, err = client:setpeername( address, port ) if ok or err == "timeout" or err == "Operation already in progress" then - return wrapclient( client, address, port, listeners, pattern, sslctx ) + return wrapclient( client, address, port, listeners, pattern, sslctx, extra ) else return nil, err end -- cgit v1.2.3 From fb692a2cedc46229966f4a84585f292abe850ba4 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 1 Sep 2019 01:58:38 +0200 Subject: net.server: Handle server name (SNI) as extra argument Code added in 75d2874502c3, 9a905888b96c and adc0672b700e uses this field. See #409 and #1408 --- net/server_epoll.lua | 6 ++++++ net/server_event.lua | 1 + net/server_select.lua | 3 +++ 3 files changed, 10 insertions(+) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index 96e7b201..f48086e3 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -575,6 +575,12 @@ local function wrapsocket(client, server, read_size, listeners, tls_ctx, extra) extra = extra; }, interface_mt); + if extra then + if extra.servername then + conn.servername = extra.servername; + end + end + conn:updatenames(); return conn; end diff --git a/net/server_event.lua b/net/server_event.lua index 950a7a5c..2b791291 100644 --- a/net/server_event.lua +++ b/net/server_event.lua @@ -516,6 +516,7 @@ local function handleclient( client, ip, port, server, pattern, listener, sslctx _sslctx = sslctx; -- parameters _usingssl = false; -- client is using ssl; extra = extra; + servername = extra and extra.servername; } if not has_luasec then interface.starttls = false; end interface.id = tostring(interface):match("%x+$"); diff --git a/net/server_select.lua b/net/server_select.lua index de183331..e15f5298 100644 --- a/net/server_select.lua +++ b/net/server_select.lua @@ -317,6 +317,9 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport local handler = bufferqueue -- saves a table ^_^ handler.extra = extra + if extra then + handler.servername = extra.servername + end handler.dispatch = function( ) return dispatch -- cgit v1.2.3 From 92b963270a4e399096f66ac9353a99574915d2e7 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 1 Sep 2019 02:05:33 +0200 Subject: net.http: Pass server name along for SNI (fixes #1408) net.resolver.basic passes this 'extra' field along to server.addclient --- net/http.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/http.lua b/net/http.lua index 0e03fb3a..055fc936 100644 --- a/net/http.lua +++ b/net/http.lua @@ -260,7 +260,7 @@ local function request(self, u, ex, callback) sslctx = ex and ex.sslctx or self.options and self.options.sslctx; end - local http_service = basic_resolver.new(host, port_number); + local http_service = basic_resolver.new(host, port_number, "tcp", { servername = req.host }); connect(http_service, listener, { sslctx = sslctx }, req); self.events.fire_event("request", { http = self, request = req, url = u }); -- cgit v1.2.3 From 5bba716be9cf3b7e345164023dc89b84e7542ea1 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 7 Sep 2019 00:00:40 +0200 Subject: core.certmanager: Lower severity for tls config not having cert This is needed for SNI where certificates are in separate per-hostname contexts, not the main one. If there is a cert, it will still require a corresponding key. --- core/certmanager.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/certmanager.lua b/core/certmanager.lua index 64528c34..b20a0cdb 100644 --- a/core/certmanager.lua +++ b/core/certmanager.lua @@ -169,8 +169,10 @@ local function create_context(host, mode, ...) local user_ssl_config = cfg:final(); if mode == "server" then - if not user_ssl_config.certificate then return nil, "No certificate present in SSL/TLS configuration for "..host; 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 + log("info", "No certificate present in SSL/TLS configuration for %s. SNI will be required.", host); + end + if user_ssl_config.certificate and not user_ssl_config.key then return nil, "No key present in SSL/TLS configuration for "..host; end end for option in pairs(path_options) do -- cgit v1.2.3 From f724d890d28484d3c6d818b93c348019ce1256fb Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 7 Sep 2019 00:46:06 +0200 Subject: tests: Disable TLS in scansion tests They were not using TLS before. With a36af4570b39 TLS context creation will succeed even without a certificate, so TLS will be offered, but since there is no certificate it does not work. --- spec/scansion/prosody.cfg.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/scansion/prosody.cfg.lua b/spec/scansion/prosody.cfg.lua index fd742db6..1c359b27 100644 --- a/spec/scansion/prosody.cfg.lua +++ b/spec/scansion/prosody.cfg.lua @@ -8,7 +8,7 @@ modules_enabled = { -- Generally required "roster"; -- Allow users to have a roster. Recommended ;) "saslauth"; -- Authentication for clients and servers. Recommended if you want to log in. - "tls"; -- Add support for secure TLS on c2s/s2s connections + --"tls"; -- Add support for secure TLS on c2s/s2s connections "dialback"; -- s2s dialback support "disco"; -- Service discovery -- cgit v1.2.3 From 60fddf5c7c7a2272f0235cc5d702f7a89c0e9568 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 7 Sep 2019 15:53:05 +0200 Subject: mod_admin_telnet: Identify bidi-capable s2sout sessions (fixes #1403) --- plugins/mod_admin_telnet.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index b6cdfe82..c184924c 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -552,7 +552,7 @@ local function session_flags(session, line) if session.remote then line[#line+1] = "(remote)"; end - if session.is_bidi then + if session.is_bidi or session.bidi_session then line[#line+1] = "(bidi)"; end if session.bosh_version then -- cgit v1.2.3 From 42861396cd1eeb5a880691e9ee00a75836b2fddb Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 7 Sep 2019 19:00:37 +0200 Subject: mod_s2s: Remove obsolete cleanup code These were added by s2sout.lib --- plugins/mod_s2s/mod_s2s.lua | 7 ------- 1 file changed, 7 deletions(-) diff --git a/plugins/mod_s2s/mod_s2s.lua b/plugins/mod_s2s/mod_s2s.lua index 5a0d2a49..6c4d92ae 100644 --- a/plugins/mod_s2s/mod_s2s.lua +++ b/plugins/mod_s2s/mod_s2s.lua @@ -228,13 +228,6 @@ function mark_connected(session) end session.sendq = nil; end - - if session.resolver then - session.resolver._resolver:closeall() - end - session.resolver = nil; - session.ip_hosts = nil; - session.srv_hosts = nil; end end -- cgit v1.2.3 From 3dcfc5ee71761a90edbd32165725f38ffd323237 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 22 Jul 2019 01:58:57 +0200 Subject: util.bitops: Library to find appropriate bitwise library (closes #1395) --- net/websocket/frames.lua | 3 +-- util/bit53.lua | 7 +++++++ util/bitcompat.lua | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 util/bit53.lua create mode 100644 util/bitcompat.lua diff --git a/net/websocket/frames.lua b/net/websocket/frames.lua index 86752109..5e17df07 100644 --- a/net/websocket/frames.lua +++ b/net/websocket/frames.lua @@ -9,8 +9,7 @@ local softreq = require "util.dependencies".softreq; local random_bytes = require "util.random".bytes; -local bit = assert(softreq"bit32" or softreq"bit", - "No bit module found. See https://prosody.im/doc/depends#bitop"); +local bit = require "util.bitcompat"; local band = bit.band; local bor = bit.bor; local bxor = bit.bxor; diff --git a/util/bit53.lua b/util/bit53.lua new file mode 100644 index 00000000..4b5c2e9c --- /dev/null +++ b/util/bit53.lua @@ -0,0 +1,7 @@ +-- Only the operators needed by net.websocket.frames are provided at this point +return { + band = function (a, b) return a & b end; + bor = function (a, b) return a | b end; + bxor = function (a, b) return a ~ b end; +}; + diff --git a/util/bitcompat.lua b/util/bitcompat.lua new file mode 100644 index 00000000..454181af --- /dev/null +++ b/util/bitcompat.lua @@ -0,0 +1,32 @@ +-- Compatibility layer for bitwise operations + +-- First try the bit32 lib +-- Lua 5.3 has it with compat enabled +-- Lua 5.2 has it by default +if _G.bit32 then + return _G.bit32; +else + -- Lua 5.1 may have it as a standalone module that can be installed + local ok, bitop = pcall(require, "bit32") + if ok then + return bitop; + end +end + +do + -- Lua 5.3 and 5.4 would be able to use native infix operators + local ok, bitop = pcall(require, "util.bit53") + if ok then + return bitop; + end +end + +do + -- Lastly, try the LuaJIT bitop library + local ok, bitop = pcall(require, "bit") + if ok then + return bitop; + end +end + +error "No bit module found. See https://prosody.im/doc/depends#bitop"; -- cgit v1.2.3 From cf236f25f8322327921009ab92e33788e4c48de9 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 8 Sep 2019 16:48:53 +0200 Subject: mod_offline: Add some debug logging to reduce confusion Where did these messages come from??? --- plugins/mod_offline.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/mod_offline.lua b/plugins/mod_offline.lua index 487098d1..04c679cf 100644 --- a/plugins/mod_offline.lua +++ b/plugins/mod_offline.lua @@ -29,6 +29,7 @@ end, -1); module:hook("message/offline/broadcast", function(event) local origin = event.origin; + origin.log("debug", "Broadcasting offline messages"); local node, host = origin.username, origin.host; @@ -38,6 +39,9 @@ module:hook("message/offline/broadcast", function(event) stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = host, stamp = datetime.datetime(when)}):up(); -- XEP-0203 origin.send(stanza); end - offline_messages:delete(node); + local ok = offline_messages:delete(node); + if type(ok) == "number" and ok > 0 then + origin.log("debug", "%d offline messages consumed"); + end return true; end, -1); -- cgit v1.2.3 From 1cebbe481be0dbce23f8c7288e091cfb557225d2 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 7 Sep 2019 17:31:26 +0200 Subject: core.s2smanager: Add [direction] boolean flags to s2s connections This will allow representing connections that go both directions --- core/s2smanager.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/s2smanager.lua b/core/s2smanager.lua index ccdf4932..45993fd2 100644 --- a/core/s2smanager.lua +++ b/core/s2smanager.lua @@ -31,6 +31,7 @@ local function new_incoming(conn) sessionlib.set_logger(host_session); sessionlib.set_conn(host_session, conn); host_session.direction = "incoming"; + host_session.incoming = true; host_session.hosts = {}; incoming_s2s[host_session] = true; return host_session; @@ -45,6 +46,7 @@ local function new_outgoing(from_host, to_host) host_session.host = from_host; host_session.notopen = true; host_session.direction = "outgoing"; + host_session.outgoing = true; hosts[from_host].s2sout[to_host] = host_session; return host_session; end -- cgit v1.2.3 From 2398f3813579f0f95a14008e1e9f217f60d67bde Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 7 Sep 2019 17:32:55 +0200 Subject: core.s2smanager: Add map of names authenticate for remote on s2sout for parity with s2sin Making s2sin and -out look more alike in preparation for bidi support --- core/s2smanager.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/core/s2smanager.lua b/core/s2smanager.lua index 45993fd2..971ccc5c 100644 --- a/core/s2smanager.lua +++ b/core/s2smanager.lua @@ -47,6 +47,7 @@ local function new_outgoing(from_host, to_host) host_session.notopen = true; host_session.direction = "outgoing"; host_session.outgoing = true; + host_session.hosts = {}; hosts[from_host].s2sout[to_host] = host_session; return host_session; end -- cgit v1.2.3 From 6f213da0a0d3c6c292f2ba6baebf1238ef4ea13d Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 7 Sep 2019 17:34:56 +0200 Subject: core.stanza_router: Handle s2s in more direction-agnostic way --- core/stanza_router.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/stanza_router.lua b/core/stanza_router.lua index d3caeb5d..a74f3b6f 100644 --- a/core/stanza_router.lua +++ b/core/stanza_router.lua @@ -111,8 +111,8 @@ function core_process_stanza(origin, stanza) stanza.attr.from = from; end - if (origin.type == "s2sin" or origin.type == "c2s" or origin.type == "component") and xmlns == nil then - if origin.type == "s2sin" and not origin.dummy then + if (origin.type == "s2sin" or origin.type == "s2sout" or origin.type == "c2s" or origin.type == "component") and xmlns == nil then + if (origin.type == "s2sin" or origin.type == "s2sout") and not origin.dummy then local host_status = origin.hosts[from_host]; if not host_status or not host_status.authed then -- remote server trying to impersonate some other server? log("warn", "Received a stanza claiming to be from %s, over a stream authed for %s!", from_host, origin.from_host); -- cgit v1.2.3 From 5c27fe4b793490d6ec80cdbdcc5063ae3b660766 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 7 Sep 2019 17:44:57 +0200 Subject: mod_s2s: Handle authentication of s2sin and s2sout the same way --- plugins/mod_s2s/mod_s2s.lua | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/plugins/mod_s2s/mod_s2s.lua b/plugins/mod_s2s/mod_s2s.lua index 6c4d92ae..d8e631b9 100644 --- a/plugins/mod_s2s/mod_s2s.lua +++ b/plugins/mod_s2s/mod_s2s.lua @@ -249,15 +249,13 @@ function make_authenticated(event) session.type = "s2sout"; elseif session.type == "s2sin_unauthed" then session.type = "s2sin"; - if host then - if not session.hosts[host] then session.hosts[host] = {}; end - session.hosts[host].authed = true; - end - elseif session.type == "s2sin" and host then + elseif session.type ~= "s2sin" and session.type ~= "s2sout" then + return false; + end + + if session.incoming and host then if not session.hosts[host] then session.hosts[host] = {}; end session.hosts[host].authed = true; - else - return false; end session.log("debug", "connection %s->%s is now authenticated for %s", session.from_host, session.to_host, host); -- cgit v1.2.3 From f6ba50139a104e69aad9378756b927ccc7b2e661 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 7 Sep 2019 18:32:29 +0200 Subject: mod_s2s: Add function to send replies on s2sout connections that support incoming traffic --- plugins/mod_s2s/mod_s2s.lua | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/plugins/mod_s2s/mod_s2s.lua b/plugins/mod_s2s/mod_s2s.lua index d8e631b9..5114b1ce 100644 --- a/plugins/mod_s2s/mod_s2s.lua +++ b/plugins/mod_s2s/mod_s2s.lua @@ -208,6 +208,13 @@ function mark_connected(session) if session.type == "s2sout" then fire_global_event("s2sout-established", event_data); hosts[from].events.fire_event("s2sout-established", event_data); + + if session.incoming then + session.send = function(stanza) + return hosts[from].events.fire_event("route/remote", { from_host = from, to_host = to, stanza = stanza }); + end; + end + else local host_session = hosts[to]; session.send = function(stanza) -- cgit v1.2.3 From 4eda545430dc80353a1b168828990e22c73e7a41 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 7 Sep 2019 18:33:16 +0200 Subject: mod_s2s: Insert s2sin into outgoing routing table when bidirectional --- plugins/mod_s2s/mod_s2s.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/mod_s2s/mod_s2s.lua b/plugins/mod_s2s/mod_s2s.lua index 5114b1ce..673c93a2 100644 --- a/plugins/mod_s2s/mod_s2s.lua +++ b/plugins/mod_s2s/mod_s2s.lua @@ -216,6 +216,10 @@ function mark_connected(session) end else + if session.outgoing and not hosts[to].s2sout[from] then + session.log("debug", "Setting up to handle route from %s to %s", to, from); + hosts[to].s2sout[from] = session; -- luacheck: ignore 122 + end local host_session = hosts[to]; session.send = function(stanza) return host_session.events.fire_event("route/remote", { from_host = to, to_host = from, stanza = stanza }); -- cgit v1.2.3 From 55efbf445b422313e209e02bae07db8b35826a62 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 7 Sep 2019 18:54:59 +0200 Subject: mod_s2s: Remove warning about hostname mismatch It triggers on bidi-related routing where this to/from is flipped. Removing since I don't think we have ever seen this potential bug. --- plugins/mod_s2s/mod_s2s.lua | 5 ----- 1 file changed, 5 deletions(-) diff --git a/plugins/mod_s2s/mod_s2s.lua b/plugins/mod_s2s/mod_s2s.lua index 673c93a2..41b1875b 100644 --- a/plugins/mod_s2s/mod_s2s.lua +++ b/plugins/mod_s2s/mod_s2s.lua @@ -139,11 +139,6 @@ function route_to_existing_session(event) log("error", "Stanza: %s", stanza); return false; else - -- FIXME - if host.from_host ~= from_host then - log("error", "WARNING! This might, possibly, be a bug, but it might not..."); - log("error", "We are going to send from %s instead of %s", host.from_host, from_host); - end if host.sends2s(stanza) then return true; end -- cgit v1.2.3 From ef38f2d0622260df46a86ca4a6d000c57e197823 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 8 Sep 2019 19:45:39 +0200 Subject: mod_s2s_bidi: Enables bi-directional streams via XEP-0288 --- core/modulemanager.lua | 2 +- plugins/mod_s2s_bidi.lua | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 plugins/mod_s2s_bidi.lua diff --git a/core/modulemanager.lua b/core/modulemanager.lua index 0d24381a..df6ae787 100644 --- a/core/modulemanager.lua +++ b/core/modulemanager.lua @@ -24,7 +24,7 @@ local setmetatable, rawget = setmetatable, rawget; local ipairs, pairs, type, t_insert = ipairs, pairs, type, table.insert; local autoload_modules = {prosody.platform, "presence", "message", "iq", "offline", "c2s", "s2s", "s2s_auth_certs"}; -local component_inheritable_modules = {"tls", "saslauth", "dialback", "iq", "s2s"}; +local component_inheritable_modules = {"tls", "saslauth", "dialback", "iq", "s2s", "s2s_bidi"}; -- We need this to let modules access the real global namespace local _G = _G; diff --git a/plugins/mod_s2s_bidi.lua b/plugins/mod_s2s_bidi.lua new file mode 100644 index 00000000..67a48d8d --- /dev/null +++ b/plugins/mod_s2s_bidi.lua @@ -0,0 +1,38 @@ +-- Prosody IM +-- Copyright (C) 2019 Kim Alvefur +-- +-- This project is MIT/X11 licensed. Please see the +-- COPYING file in the source package for more information. +-- + +local st = require "util.stanza"; + +local xmlns_bidi_feature = "urn:xmpp:features:bidi" +local xmlns_bidi = "urn:xmpp:bidi"; + +module:hook("s2s-stream-features", function(event) + local origin, features = event.origin, event.features; + if origin.type == "s2sin_unauthed" then + features:tag("bidi", { xmlns = xmlns_bidi_feature }):up(); + end +end); + +module:hook_tag("http://etherx.jabber.org/streams", "features", function (session, stanza) + if session.type == "s2sout_unauthed" then + local bidi = stanza:get_child("bidi", xmlns_bidi_feature); + if bidi then + session.incoming = true; + session.log("debug", "Requesting bidirectional stream"); + session.sends2s(st.stanza("bidi", { xmlns = xmlns_bidi })); + end + end +end, 200); + +module:hook_tag("urn:xmpp:bidi", "bidi", function(session) + if session.type == "s2sin_unauthed" then + session.log("debug", "Requested bidirectional stream"); + session.outgoing = true; + return true; + end +end); + -- cgit v1.2.3 From ab8f003076c8ea8484a70be316d2182d17951ea7 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 8 Sep 2019 18:51:57 +0200 Subject: CHANGES: Add XEP-0288 --- CHANGES | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES b/CHANGES index fe42a997..11f1837d 100644 --- a/CHANGES +++ b/CHANGES @@ -10,6 +10,7 @@ TRUNK - mod\_mimicking - Rewritten migrator - SCRAM-SHA-256 +- Bi-directional server-to-server (XEP-0288) 0.11.0 ====== -- cgit v1.2.3 From 2a9da5b8f04ce7b13d9db194fa01a36ec4b6ac91 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 8 Sep 2019 18:51:15 +0200 Subject: mod_admin_telnet: Identify native bidi sessions --- plugins/mod_admin_telnet.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index c184924c..5c08b8d1 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -552,7 +552,9 @@ local function session_flags(session, line) if session.remote then line[#line+1] = "(remote)"; end - if session.is_bidi or session.bidi_session then + if session.incoming and session.outgoing then + line[#line+1] = "(bidi)"; + elseif session.is_bidi or session.bidi_session then line[#line+1] = "(bidi)"; end if session.bosh_version then -- cgit v1.2.3 From 8a4bec10cab93f6ef4b99cdc7b7f2c1d36f43688 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 27 Aug 2019 20:53:11 +0200 Subject: doap: Add a Description Of A Project file --- doc/doap.xml | 427 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 427 insertions(+) create mode 100644 doc/doap.xml diff --git a/doc/doap.xml b/doc/doap.xml new file mode 100644 index 00000000..47b4ca14 --- /dev/null +++ b/doc/doap.xml @@ -0,0 +1,427 @@ + + + + Prosody IM + Lightweight XMPP server + + 2008-08-22 + + + + + + + + + + + + + + + Lua + C + Linux + macOS + FreeBSD + OpenBSD + NetBSD + + + Matthew Wild + MattJ + https://matthewwild.co.uk/ + + + + + Waqas Hussain + waqas + + + + + Kim Alvefur + Zash + https://www.zash.se/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0.7 + 0.10 + + + + + + + + + + + 0.3 + + + + + + 0.1 + + + + + + 0.8 + + + + + + 0.1 + + + + + + 0.9 + + + + + + 0.7 + + + + + + + + + + + 0.1 + + + + + + 0.1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + via XEP-0163 + + + + + + via XEP-0163 + + + + + + 0.4 + + + + + + + + + + + via XEP-0163 + + + + + + 0.2 + + + + + + 0.10 + + + + + + 0.9 + + + + + + 0.7 + + + + + + 0.6 + 0.10 + + + + + + via XEP-0398 + + + + + + 0.10 + + + + + + + + + + + 0.5 + + + + + + + + + + + via XEP-0163 + + + + + + + + + + + 0.9 + + + + + + + + + + + + + + + + via XEP-0163 + + + + + + 0.10 + + + + + + via XEP-0163 + + + + + + via XEP-0163 + + + + + + via XEP-0163 + + + + + + via XEP-0163 + + + + + + 0.1 + + + + + + 0.1 + + + + + + + + + + + 0.2 + + + + + + required level + + + + + + 0.1 + + + + + + 0.7 + + + + + + 0.4 + + + + + + 0.10 + + + + + + 0.11 + + + + + + Core Server + + + + + + 0.10 + + + + + + 0.9 + refers to inclusion of delay stamp in presence + + + + + + 0.11 + + + + + + via XEP-0163 + + + + + + 0.11 + + + + + + 0.11 + + + + + + -- cgit v1.2.3 From f11e620eb73c131401eb77f6750a2f5e21c95c44 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 9 Sep 2019 19:54:59 +0200 Subject: doap: Add XEP-0288 --- doc/doap.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/doap.xml b/doc/doap.xml index 47b4ca14..ce6cca4c 100644 --- a/doc/doap.xml +++ b/doc/doap.xml @@ -372,6 +372,12 @@ 0.10 + + + + 0.12 + + -- cgit v1.2.3 From 4caae044218601c3abf448776a6c0909f9135bd8 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 10 Sep 2019 18:16:11 +0200 Subject: util.x509: Nameprep commonName once --- util/x509.lua | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/util/x509.lua b/util/x509.lua index 1cdf07dc..c4001ecb 100644 --- a/util/x509.lua +++ b/util/x509.lua @@ -236,8 +236,11 @@ local function get_identities(cert) --> set of names local subject = cert:subject(); for i = 1, #subject do local dn = subject[i]; - if dn.oid == oid_commonname and nameprep(dn.value) then - names[dn.value] = true; + if dn.oid == oid_commonname then + local name = nameprep(dn.value); + if name then + names[name] = true; + end end end return names; -- cgit v1.2.3 From 833d955c39a5bf1036e507f85d9893664efa6c7f Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 10 Sep 2019 18:17:13 +0200 Subject: util.x509: Only collect commonNames that pass idna Weeds out "Example Certificate" and the like, which are uninteresting for this function. --- util/x509.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/x509.lua b/util/x509.lua index c4001ecb..82f8285d 100644 --- a/util/x509.lua +++ b/util/x509.lua @@ -238,7 +238,7 @@ local function get_identities(cert) --> set of names local dn = subject[i]; if dn.oid == oid_commonname then local name = nameprep(dn.value); - if name then + if name and idna_to_ascii(name) then names[name] = true; end end -- cgit v1.2.3 From d24e6c7582a6c09be435908083ed19f13fee9bde Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Wed, 11 Sep 2019 15:10:31 +0100 Subject: mod_http: Add support for configuring CORS Access-Control-Allow-Credentials --- plugins/mod_http.lua | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/plugins/mod_http.lua b/plugins/mod_http.lua index 17ea27e1..654ec6c7 100644 --- a/plugins/mod_http.lua +++ b/plugins/mod_http.lua @@ -26,6 +26,7 @@ server.set_option("buffer_size_limit", module:get_option_number("http_max_buffer -- CORS settigs local opt_methods = module:get_option_set("access_control_allow_methods", { "GET", "OPTIONS" }); local opt_headers = module:get_option_set("access_control_allow_headers", { "Content-Type" }); +local opt_credentials = module:get_option_boolean("access_control_allow_credentials", false); local opt_max_age = module:get_option_number("access_control_max_age", 2 * 60 * 60); local function get_http_event(host, app_path, key) @@ -89,11 +90,14 @@ function moduleapi.http_url(module, app_name, default_path) return "http://disabled.invalid/"; end -local function apply_cors_headers(response, methods, headers, max_age, origin) +local function apply_cors_headers(response, methods, headers, max_age, allow_credentials, origin) response.headers.access_control_allow_methods = tostring(methods); response.headers.access_control_allow_headers = tostring(headers); response.headers.access_control_max_age = tostring(max_age) response.headers.access_control_allow_origin = origin or "*"; + if allow_credentials then + response.headers.access_control_allow_credentials = "true"; + end end function module.add_host(module) @@ -119,7 +123,7 @@ function module.add_host(module) local function cors_handler(event_data) local request, response = event_data.request, event_data.response; - apply_cors_headers(response, app_methods, opt_headers, opt_max_age, request.headers.origin); + apply_cors_headers(response, app_methods, opt_headers, opt_max_age, opt_credentials, request.headers.origin); end local function options_handler(event_data) -- cgit v1.2.3 From 6a6b2fedcef0be190f63cfa1aca2183f3c225377 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 10 Sep 2019 18:41:36 +0200 Subject: util.x509: Return sets of services per identity --- util/x509.lua | 41 ++++++++++++++++++++++++++++++++++------- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/util/x509.lua b/util/x509.lua index 82f8285d..fe6e4b79 100644 --- a/util/x509.lua +++ b/util/x509.lua @@ -23,7 +23,9 @@ local idna_to_ascii = require "util.encodings".idna.to_ascii; local idna_to_unicode = require "util.encodings".idna.to_unicode; local base64 = require "util.encodings".base64; local log = require "util.logger".init("x509"); +local mt = require "util.multitable"; local s_format = string.format; +local ipairs = ipairs; local _ENV = nil; -- luacheck: std none @@ -218,18 +220,43 @@ local function verify_identity(host, service, cert) end -- TODO Support other SANs -local function get_identities(cert) --> set of names +local function get_identities(cert) --> map of names to sets of services if cert.setencode then cert:setencode("utf8"); end - local names = {}; + local names = mt.new(); local ext = cert:extensions(); local sans = ext[oid_subjectaltname]; - if sans and sans["dNSName"] then - for i = 1, #sans["dNSName"] do - names[ idna_to_unicode(sans["dNSName"][i]) ] = true; + if sans then + if sans["dNSName"] then -- Valid for any service + for _, name in ipairs(sans["dNSName"]) do + name = idna_to_unicode(nameprep(name)); + if name then + names:set(name, "*", true); + end + end + end + if sans[oid_xmppaddr] then + for _, name in ipairs(sans[oid_xmppaddr]) do + name = nameprep(name); + if name then + names:set(name, "xmpp-client", true); + names:set(name, "xmpp-server", true); + end + end + end + if sans[oid_dnssrv] then + for _, srvname in ipairs(sans[oid_dnssrv]) do + local srv, name = srvname:match("^_([^.]+)%.(.*)"); + if srv then + name = nameprep(name); + if name then + names:set(name, srv, true); + end + end + end end end @@ -239,11 +266,11 @@ local function get_identities(cert) --> set of names if dn.oid == oid_commonname then local name = nameprep(dn.value); if name and idna_to_ascii(name) then - names[name] = true; + names:set("*", name, true); end end end - return names; + return names.data; end local pat = "%-%-%-%-%-BEGIN ([A-Z ]+)%-%-%-%-%-\r?\n".. -- cgit v1.2.3 From b733d61de629dabbc1678781711b3775b7ce17cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20=E2=80=9Cpep=E2=80=9D=20Buquet?= Date: Thu, 12 Sep 2019 15:17:12 +0200 Subject: Prepare required data folder for integration tests --- GNUmakefile | 1 + 1 file changed, 1 insertion(+) diff --git a/GNUmakefile b/GNUmakefile index 977e91c6..15362f5c 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -73,6 +73,7 @@ test: $(BUSTED) --lua=$(RUNWITH) integration-test: all + $(MKDIR) data $(RUNWITH) prosodyctl --config ./spec/scansion/prosody.cfg.lua start $(SCANSION) -d ./spec/scansion; R=$$? \ $(RUNWITH) prosodyctl --config ./spec/scansion/prosody.cfg.lua stop \ -- cgit v1.2.3 From bb4cb60fb8200e5fa26eaa482422fbbfa71aa11c Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 11 Sep 2019 00:14:59 +0200 Subject: util.encodings: Switch ICU binding to IDNA2008 (fixes #533, #1301) --- util-src/encodings.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/util-src/encodings.c b/util-src/encodings.c index 5e7032cf..f20ba75d 100644 --- a/util-src/encodings.c +++ b/util-src/encodings.c @@ -269,6 +269,7 @@ static const luaL_Reg Reg_utf8[] = { #include #include #include +#include static int icu_stringprep_prep(lua_State *L, const UStringPrepProfile *profile) { size_t input_len; @@ -323,6 +324,7 @@ UStringPrepProfile *icu_nodeprep; UStringPrepProfile *icu_resourceprep; UStringPrepProfile *icu_saslprep; USpoofChecker *icu_spoofcheck; +UIDNA *icu_idna2008; #if (U_ICU_VERSION_MAJOR_NUM < 58) /* COMPAT */ @@ -339,6 +341,7 @@ void init_icu(void) { icu_saslprep = usprep_openByType(USPREP_RFC4013_SASLPREP, &err); icu_spoofcheck = uspoof_open(&err); uspoof_setChecks(icu_spoofcheck, USPOOF_CONFUSABLE, &err); + icu_idna2008 = uidna_openUTS46(UIDNA_USE_STD3_RULES, &err); if(U_FAILURE(err)) { fprintf(stderr, "[c] util.encodings: error: %s\n", u_errorName((UErrorCode)err)); @@ -434,9 +437,10 @@ static int Lidna_to_ascii(lua_State *L) { /** idna.to_ascii(s) */ return 1; } - dest_len = uidna_IDNToASCII(ustr, ulen, dest, 1024, UIDNA_USE_STD3_RULES, NULL, &err); + UIDNAInfo info = UIDNA_INFO_INITIALIZER; + dest_len = uidna_nameToASCII(icu_idna2008, ustr, ulen, dest, 256, &info, &err); - if(U_FAILURE(err)) { + if(U_FAILURE(err) || info.errors) { lua_pushnil(L); return 1; } else { @@ -468,9 +472,10 @@ static int Lidna_to_unicode(lua_State *L) { /** idna.to_unicode(s) */ return 1; } - dest_len = uidna_IDNToUnicode(ustr, ulen, dest, 1024, UIDNA_USE_STD3_RULES, NULL, &err); + UIDNAInfo info = UIDNA_INFO_INITIALIZER; + dest_len = uidna_nameToUnicode(icu_idna2008, ustr, ulen, dest, 1024, &info, &err); - if(U_FAILURE(err)) { + if(U_FAILURE(err) || info.errors) { lua_pushnil(L); return 1; } else { -- cgit v1.2.3 From bf1a0c2f0577c980394830a346a2637694ae2057 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 11 Sep 2019 00:40:30 +0200 Subject: util.encodings: Spell out all IDNA 2008 options ICU has --- util-src/encodings.c | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/util-src/encodings.c b/util-src/encodings.c index f20ba75d..6f2676f2 100644 --- a/util-src/encodings.c +++ b/util-src/encodings.c @@ -341,7 +341,30 @@ void init_icu(void) { icu_saslprep = usprep_openByType(USPREP_RFC4013_SASLPREP, &err); icu_spoofcheck = uspoof_open(&err); uspoof_setChecks(icu_spoofcheck, USPOOF_CONFUSABLE, &err); - icu_idna2008 = uidna_openUTS46(UIDNA_USE_STD3_RULES, &err); + int options = UIDNA_DEFAULT; +#if 0 + /* COMPAT with future Unicode versions */ + options |= UIDNA_ALLOW_UNASSIGNED; +#endif +#if 1 + /* Forbid eg labels starting with _ */ + options |= UIDNA_USE_STD3_RULES; +#endif +#if 0 + /* TODO determine if we need this */ + options |= UIDNA_CHECK_BIDI; +#endif +#if 0 + /* UTS46 makes it sound like these are the responsibility of registrars */ + options |= UIDNA_CHECK_CONTEXTJ; + options |= UIDNA_CHECK_CONTEXTO; +#endif +#if 0 + /* This disables COMPAT with IDNA 2003 */ + options |= UIDNA_NONTRANSITIONAL_TO_ASCII; + options |= UIDNA_NONTRANSITIONAL_TO_UNICODE; +#endif + icu_idna2008 = uidna_openUTS46(options, &err); if(U_FAILURE(err)) { fprintf(stderr, "[c] util.encodings: error: %s\n", u_errorName((UErrorCode)err)); -- cgit v1.2.3 From f9017df5432ff31ec0b21a406104c5654d5a23ad Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 19 Sep 2019 16:13:55 +0200 Subject: doap: Update to newer format --- doc/doap.xml | 754 +++++++++++++++++++++++++++++------------------------------ 1 file changed, 375 insertions(+), 379 deletions(-) diff --git a/doc/doap.xml b/doc/doap.xml index ce6cca4c..4eeafcc3 100644 --- a/doc/doap.xml +++ b/doc/doap.xml @@ -1,5 +1,5 @@ - + Prosody IM Lightweight XMPP server @@ -51,383 +51,379 @@ - - - - - - - - - - - - - - - - - - - - - 0.7 - 0.10 - - - - - - - - - - - 0.3 - - - - - - 0.1 - - - - - - 0.8 - - - - - - 0.1 - - - - - - 0.9 - - - - - - 0.7 - - - - - - - - - - - 0.1 - - - - - - 0.1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - via XEP-0163 - - - - - - via XEP-0163 - - - - - - 0.4 - - - - - - - - - - - via XEP-0163 - - - - - - 0.2 - - - - - - 0.10 - - - - - - 0.9 - - - - - - 0.7 - - - - - - 0.6 - 0.10 - - - - - - via XEP-0398 - - - - - - 0.10 - - - - - - - - - - - 0.5 - - - - - - - - - - - via XEP-0163 - - - - - - - - - - - 0.9 - - - - - - - - - - - - - - - - via XEP-0163 - - - - - - 0.10 - - - - - - via XEP-0163 - - - - - - via XEP-0163 - - - - - - via XEP-0163 - - - - - - via XEP-0163 - - - - - - 0.1 - - - - - - 0.1 - - - - - - - - - - - 0.2 - - - - - - required level - - - - - - 0.1 - - - - - - 0.7 - - - - - - 0.4 - - - - - - 0.10 - - - - - - 0.12 - - - - - - 0.11 - - - - - - Core Server - - - - - - 0.10 - - - - - - 0.9 - refers to inclusion of delay stamp in presence - - - - - - 0.11 - - - - - - via XEP-0163 - - - - - - 0.11 - - - - - - 0.11 - - - - + + + + + + + + + + + + + + + + + + + 0.7 + 0.10 + + + + + + + + + + + 0.3 + + + + + + 0.1 + + + + + + 0.8 + + + + + + 0.1 + + + + + + 0.9 + + + + + + 0.7 + + + + + + + + + + + 0.1 + + + + + + 0.1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + via XEP-0163 + + + + + + via XEP-0163 + + + + + + 0.4 + + + + + + + + + + + via XEP-0163 + + + + + + 0.2 + + + + + + 0.10 + + + + + + 0.9 + + + + + + 0.7 + + + + + + 0.6 + 0.10 + + + + + + via XEP-0398 + + + + + + 0.10 + + + + + + + + + + + 0.5 + + + + + + + + + + + via XEP-0163 + + + + + + + + + + + 0.9 + + + + + + + + + + + + + + + + via XEP-0163 + + + + + + 0.10 + + + + + + via XEP-0163 + + + + + + via XEP-0163 + + + + + + via XEP-0163 + + + + + + via XEP-0163 + + + + + + 0.1 + + + + + + 0.1 + + + + + + + + + + + 0.2 + + + + + + required level + + + + + + 0.1 + + + + + + 0.7 + + + + + + 0.4 + + + + + + 0.10 + + + + + + 0.12 + + + + + + 0.11 + + + + + + Core Server + + + + + + 0.10 + + + + + + 0.9 + refers to inclusion of delay stamp in presence + + + + + + 0.11 + + + + + + via XEP-0163 + + + + + + 0.11 + + + + + + 0.11 + + -- cgit v1.2.3 From bd2dcf7503ec9f262636eb5e98e33fdf71d2a700 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 19 Sep 2019 21:41:04 +0200 Subject: doap: Fix namespace of --- doc/doap.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/doap.xml b/doc/doap.xml index 4eeafcc3..2bc11fd3 100644 --- a/doc/doap.xml +++ b/doc/doap.xml @@ -70,7 +70,7 @@ 0.7 - 0.10 + 0.10 @@ -204,7 +204,7 @@ - 0.10 + 0.10 @@ -223,7 +223,7 @@ 0.6 - 0.10 + 0.10 -- cgit v1.2.3 From 9327a1edc037edc07542c56d221cace8bd8cbdf2 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 19 Sep 2019 22:14:56 +0200 Subject: doap: Add details to XEPs numbered under 100 --- doc/doap.xml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/doc/doap.xml b/doc/doap.xml index 2bc11fd3..5dc61ed0 100644 --- a/doc/doap.xml +++ b/doc/doap.xml @@ -54,16 +54,23 @@ + complete + 2.9 + 0.4 + 0.7 + 0.1 + mod_lastactivity and mod_uptime + complete @@ -76,18 +83,24 @@ + 0.10. + complete + 1.32.0( 0.3 + partial + 1.2 0.1 + complete @@ -100,17 +113,21 @@ 0.1 + complete + 1.15.8 0.9 + partial + 1.8.1 0.7 @@ -123,11 +140,14 @@ 0.1 + 2.4 + complete + 2.5 0.1 @@ -139,6 +159,8 @@ + 1.1 + 0.1 @@ -159,6 +181,8 @@ + 1.1 + 0.1 -- cgit v1.2.3 From ee98d07a437843f597d79ca3333700c5cc4bd4bc Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 19 Sep 2019 22:25:04 +0200 Subject: doap: Add details about most recent XEPs numbered over 300 --- doc/doap.xml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/doc/doap.xml b/doc/doap.xml index 5dc61ed0..57d2246b 100644 --- a/doc/doap.xml +++ b/doc/doap.xml @@ -415,12 +415,15 @@ + 0.6.3 0.10 + mod_mam, mod_muc_mam + 0.2 0.9 refers to inclusion of delay stamp in presence @@ -428,25 +431,32 @@ + 0.3.0 0.11 - via XEP-0163 + via XEP-0163, XEP-0222 + 0.2.1 0.11 + complete + mod_vcard_legacy + 1.0.1 0.11 + complete + Server Optimization -- cgit v1.2.3 From d248f67c60fed92db726da4f2bc5b4d4a3895c7c Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 19 Sep 2019 22:53:00 +0200 Subject: doap: Remove stray '(' --- doc/doap.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/doap.xml b/doc/doap.xml index 57d2246b..af9b2fab 100644 --- a/doc/doap.xml +++ b/doc/doap.xml @@ -90,7 +90,7 @@ - 1.32.0( + 1.32.0 0.3 partial -- cgit v1.2.3 From 52198503001e9e701fc89036972d7374ca14b249 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 21 Sep 2019 17:52:07 +0200 Subject: MUC: Update test for vcard-temp changes Should this XEP-0398 behavior even be covered here? The original lines came from a recording. --- spec/scansion/muc_create_destroy.scs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/spec/scansion/muc_create_destroy.scs b/spec/scansion/muc_create_destroy.scs index 4b16fbf2..9a66c160 100644 --- a/spec/scansion/muc_create_destroy.scs +++ b/spec/scansion/muc_create_destroy.scs @@ -19,7 +19,8 @@ Romeo sends: Romeo receives: - + + @@ -61,7 +62,9 @@ Juliet sends: Juliet receives: - + + + @@ -69,7 +72,9 @@ Juliet receives: Juliet receives: - + + + @@ -90,7 +95,9 @@ Juliet receives: Romeo receives: - + + + -- cgit v1.2.3 From 9f70749716ef88e9fa5017e41d6dfb7b177f6cea Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 21 Sep 2019 18:16:45 +0200 Subject: MUC: Fix XML syntax error in test How did this even happen? --- spec/scansion/muc_create_destroy.scs | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/scansion/muc_create_destroy.scs b/spec/scansion/muc_create_destroy.scs index 9a66c160..fea759a3 100644 --- a/spec/scansion/muc_create_destroy.scs +++ b/spec/scansion/muc_create_destroy.scs @@ -19,6 +19,7 @@ Romeo sends: Romeo receives: + -- cgit v1.2.3 From d2cb312666fa8151900c34d44e91e27d61d2ec2f Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 28 Sep 2019 00:09:24 +0200 Subject: doap: Add some XEP versions --- doc/doap.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/doap.xml b/doc/doap.xml index af9b2fab..9ad71cb6 100644 --- a/doc/doap.xml +++ b/doc/doap.xml @@ -205,6 +205,7 @@ + 1.6 0.4 @@ -314,6 +315,7 @@ + 1.3 0.10 @@ -344,23 +346,27 @@ + 2.0.1 0.1 + 2.0 0.1 + 2.0 + 1.2 0.2 -- cgit v1.2.3 From 067ae06b1a476c0f35edcea59a1ab258dc655f88 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 28 Sep 2019 00:09:29 +0200 Subject: doap: Add some supported-since versions --- doc/doap.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/doap.xml b/doc/doap.xml index 9ad71cb6..a52fd68a 100644 --- a/doc/doap.xml +++ b/doc/doap.xml @@ -188,6 +188,7 @@ + 0.9 @@ -212,6 +213,7 @@ + 0.8 @@ -304,6 +306,7 @@ + 0.9.10 -- cgit v1.2.3 From 272944f4dfb578ad6ed13c5d323cad4c74f3e5d1 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 28 Sep 2019 18:24:28 +0200 Subject: util.sql: Preserve 3rd and 4th return values from transaction (fixes #1434) (thanks mrdoctorwho) --- util/sql.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/util/sql.lua b/util/sql.lua index 4406d7ff..86740b1c 100644 --- a/util/sql.lua +++ b/util/sql.lua @@ -218,14 +218,14 @@ function engine:_transaction(func, ...) end end function engine:transaction(...) - local ok, ret = self:_transaction(...); + local ok, ret, b, c = self:_transaction(...); if not ok then local conn = self.conn; 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]", (...)); - ok, ret = self:_transaction(...); + ok, ret, b, c = self:_transaction(...); log("debug", "SQL transaction retry %s", ok and "succeeded" or "failed"); else log("debug", "SQL connection is up, so not retrying"); @@ -234,7 +234,7 @@ function engine:transaction(...) log("error", "Error in SQL transaction: %s", ret); end end - return ok, ret; + return ok, ret, b, c; end function engine:_create_index(index) local sql = "CREATE INDEX \""..index.name.."\" ON \""..index.table.."\" ("; -- cgit v1.2.3 From 01e73988778bf20b8ff5ded517459c7ead977009 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Sat, 28 Sep 2019 20:00:39 +0200 Subject: mod_pubsub: Remove the unwanted check for @notify on . MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This most likely was copied from the handling of , where it actually is required by the spec (XEP-0060 §7.2.2.1), but this attribute doesn’t exist for purge. --- plugins/mod_pubsub/pubsub.lib.lua | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/plugins/mod_pubsub/pubsub.lib.lua b/plugins/mod_pubsub/pubsub.lib.lua index d59e3d85..a002fbe7 100644 --- a/plugins/mod_pubsub/pubsub.lib.lua +++ b/plugins/mod_pubsub/pubsub.lib.lua @@ -638,14 +638,13 @@ function handlers.set_retract(origin, stanza, retract, service) end function handlers.owner_set_purge(origin, stanza, purge, service) - local node, notify = purge.attr.node, purge.attr.notify; - notify = (notify == "1") or (notify == "true"); + local node = purge.attr.node; local reply; if not node then origin.send(pubsub_error_reply(stanza, "nodeid-required")); return true; end - local ok, ret = service:purge(node, stanza.attr.from, notify); + local ok, ret = service:purge(node, stanza.attr.from, true); if ok then reply = st.reply(stanza); else -- cgit v1.2.3 From fad52c768601de198e5c277d547528bfac1f6166 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 28 Sep 2019 22:59:29 +0200 Subject: mod_csi_simple: Remove duplicated check for connection --- plugins/mod_csi_simple.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/mod_csi_simple.lua b/plugins/mod_csi_simple.lua index 13002ea8..4e2b95e1 100644 --- a/plugins/mod_csi_simple.lua +++ b/plugins/mod_csi_simple.lua @@ -81,7 +81,7 @@ local function flush_buffer(data, session) end function enable_optimizations(session) - if session.conn and session.conn and session.conn.pause_writes then + if session.conn and session.conn.pause_writes then session.conn:pause_writes(); filters.add_filter(session, "stanzas/out", manage_buffer); filters.add_filter(session, "bytes/in", flush_buffer); @@ -91,7 +91,7 @@ function enable_optimizations(session) end function disable_optimizations(session) - if session.conn and session.conn and session.conn.resume_writes then + if session.conn and session.conn.resume_writes then filters.remove_filter(session, "stanzas/out", manage_buffer); filters.remove_filter(session, "bytes/in", flush_buffer); session.conn:resume_writes(); @@ -115,7 +115,7 @@ end); module:hook("c2s-ondrain", function (event) local session = event.session; - if session.state == "inactive" and session.conn and session.conn and session.conn.pause_writes then + if session.state == "inactive" and session.conn and session.conn.pause_writes then session.conn:pause_writes(); session.log("debug", "Buffer flushed, resuming inactive mode (queue size was %d)", session.csi_counter); session.csi_counter = 0; -- cgit v1.2.3 From 2aedce23760830b08c7cdd76b1164f9e6f2fbea9 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 29 Sep 2019 14:50:04 +0200 Subject: net.server_epoll: Make it easy to override handler for incoming data --- net/server_epoll.lua | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index f48086e3..fd9e0416 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -189,6 +189,11 @@ function interface:on(what, ...) return err; end +-- Allow this one to be overridden +function interface:onincoming(...) + return self:on("incoming", ...); +end + -- Return the file descriptor number function interface:getfd() if self.conn then @@ -360,7 +365,7 @@ function interface:onreadable() local data, err, partial = self.conn:receive(self.read_size or cfg.read_size); if data then self:onconnect(); - self:on("incoming", data); + self:onincoming(data); else if err == "wantread" then self:set(true, nil); @@ -371,7 +376,7 @@ function interface:onreadable() end if partial and partial ~= "" then self:onconnect(); - self:on("incoming", partial, err); + self:onincoming(partial, err); end if err ~= "timeout" then self:on("disconnect", err); -- cgit v1.2.3 From 315959a49a675d2f95610828ddcc66d2f1fb535c Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 29 Sep 2019 15:00:28 +0200 Subject: net.server_epoll: Make log tag accessible as a field To allow referencing connections by id instead of tostring form --- net/server_epoll.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index fd9e0416..633b038c 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -567,6 +567,7 @@ end local function wrapsocket(client, server, read_size, listeners, tls_ctx, extra) -- luasocket object -> interface object client:settimeout(0); + local conn_id = ("conn%s"):format(new_id()); local conn = setmetatable({ conn = client; _server = server; @@ -576,7 +577,8 @@ local function wrapsocket(client, server, read_size, listeners, tls_ctx, extra) 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())); + id = conn_id; + log = logger.init(conn_id); extra = extra; }, interface_mt); -- cgit v1.2.3 From 6e360554ffaa391f23a9e0bb93adde3f1bd32020 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 29 Sep 2019 15:02:05 +0200 Subject: net.server_epoll: Add debug logging for various connection events --- net/server_epoll.lua | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index 633b038c..c3354006 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -630,10 +630,12 @@ function interface:init() end function interface:pause() + self:debug("Pause reading"); return self:set(false); end function interface:resume() + self:debug("Resume reading"); return self:set(true); end @@ -663,12 +665,14 @@ function interface:setlimit(Bps) end function interface:pause_writes() + self:debug("Pause writes"); self._write_lock = true; self:setwritetimeout(false); self:set(nil, false); end function interface:resume_writes() + self:debug("Resume writes"); self._write_lock = nil; if self.writebuffer[1] then self:setwritetimeout(); @@ -678,6 +682,7 @@ end -- Connected! function interface:onconnect() + self:debug("Connected"); self:updatenames(); self.onconnect = noop; self:on("connect"); @@ -784,6 +789,7 @@ end; -- Dump all data from one connection into another local function link(from, to) + from:debug("Linking to %s", to.id); from.listeners = setmetatable({ onincoming = function (_, data) from:pause(); -- cgit v1.2.3 From 2c3bd063b9f6b729b2b1592f964444aecc10de5b Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Sun, 29 Sep 2019 15:05:17 +0200 Subject: mod_register_ibr: Add FORM_TYPE as required by XEP-0077. --- plugins/mod_register_ibr.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/mod_register_ibr.lua b/plugins/mod_register_ibr.lua index bbe7581d..e04e6ecd 100644 --- a/plugins/mod_register_ibr.lua +++ b/plugins/mod_register_ibr.lua @@ -25,6 +25,7 @@ end); local account_details = module:open_store("account_details"); local field_map = { + FORM_TYPE = { name = "FORM_TYPE", type = "hidden", value = "jabber:iq:register" }; username = { name = "username", type = "text-single", label = "Username", required = true }; password = { name = "password", type = "text-private", label = "Password", required = true }; nick = { name = "nick", type = "text-single", label = "Nickname" }; @@ -50,6 +51,7 @@ local registration_form = dataform_new{ title = title; instructions = instructions; + field_map.FORM_TYPE; field_map.username; field_map.password; }; -- cgit v1.2.3 From 4f848e3ea0c92e545b9d162d55959e4f281aca70 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 29 Sep 2019 15:18:40 +0200 Subject: net.server_epoll: Add some timeout related logging --- net/server_epoll.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index c3354006..250ce4d0 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -261,8 +261,10 @@ function interface:setreadtimeout(t) else self._readtimeout = addtimer(t, function () if self:on("readtimeout") then + self:debug("Read timeout, handled"); return cfg.read_timeout; else + self:debug("Read timeout, fatal"); self:on("disconnect", "read timeout"); self:destroy(); end @@ -284,6 +286,7 @@ function interface:setwritetimeout(t) self._writetimeout:reschedule(gettime() + t); else self._writetimeout = addtimer(t, function () + self:debug("Write timeout"); self:on("disconnect", "write timeout"); self:destroy(); end); -- cgit v1.2.3 From 5eea3358fd7f8ca8b0edb36769b393bb0f1423ca Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 29 Sep 2019 15:36:23 +0200 Subject: net.server_epoll: Handle read size argument to link --- net/server_epoll.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index 250ce4d0..c2eb7b1c 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -791,7 +791,7 @@ local function watchfd(fd, onreadable, onwritable) end; -- Dump all data from one connection into another -local function link(from, to) +local function link(from, to, read_size) from:debug("Linking to %s", to.id); from.listeners = setmetatable({ onincoming = function (_, data) @@ -804,6 +804,7 @@ local function link(from, to) from:resume(); end, }, {__index=to.listeners}); + from:set_mode(read_size); from:set(true, nil); to:set(nil, true); end -- cgit v1.2.3 From 79b375cb53b2e160d18309c6f88b9fa70a88d2e5 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 29 Sep 2019 15:41:01 +0200 Subject: net.server_epoll: Fix link function to not replace listeners mod_proxy65 calls link twice, once for each direction. This would overwrite the listeners with one that has the previous listeners as metatable.__index, but none of the others. This takes advantage of 94c584d67533 to improve this. --- net/server_epoll.lua | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index c2eb7b1c..d2964888 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -793,17 +793,13 @@ end; -- Dump all data from one connection into another local function link(from, to, read_size) from:debug("Linking to %s", to.id); - from.listeners = setmetatable({ - onincoming = function (_, data) - from:pause(); + function from:onincoming(data) + self:pause(); to:write(data); - end, - }, {__index=from.listeners}); - to.listeners = setmetatable({ - ondrain = function () + end + function to:ondrain() from:resume(); - end, - }, {__index=to.listeners}); + end from:set_mode(read_size); from:set(true, nil); to:set(nil, true); -- cgit v1.2.3 From 2feedcb7d6875ff3ae40c615f3087de8a61c999e Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 29 Sep 2019 15:51:21 +0200 Subject: net.server_epoll: Ignore unused self argument [luacheck] --- net/server_epoll.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index d2964888..3aa37ee6 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -797,7 +797,7 @@ local function link(from, to, read_size) self:pause(); to:write(data); end - function to:ondrain() + function to:ondrain() -- luacheck: ignore 212/self from:resume(); end from:set_mode(read_size); -- cgit v1.2.3 From 1fa149d6c4c0e709d62999f044f28cbb0eff4040 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Sun, 29 Sep 2019 15:26:18 +0200 Subject: mod_register_ibr, mod_register_limits: Add support for custom error type and defined-condition. --- plugins/mod_register_ibr.lua | 2 +- plugins/mod_register_limits.lua | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/plugins/mod_register_ibr.lua b/plugins/mod_register_ibr.lua index e04e6ecd..fe5ede2b 100644 --- a/plugins/mod_register_ibr.lua +++ b/plugins/mod_register_ibr.lua @@ -168,7 +168,7 @@ module:hook("stanza/iq/jabber:iq:register:query", function(event) module:fire_event("user-registering", user); if not user.allowed then log("debug", "Registration disallowed by module: %s", user.reason or "no reason given"); - session.send(st.error_reply(stanza, "modify", "not-acceptable", user.reason)); + session.send(st.error_reply(stanza, user.error_type or "modify", user.error_condition or "not-acceptable", user.reason)); return true; end diff --git a/plugins/mod_register_limits.lua b/plugins/mod_register_limits.lua index 736282a5..55811d74 100644 --- a/plugins/mod_register_limits.lua +++ b/plugins/mod_register_limits.lua @@ -64,15 +64,21 @@ module:hook("user-registering", function (event) log("debug", "Registration disallowed by blacklist"); event.allowed = false; event.reason = "Your IP address is blacklisted"; + event.error_type = "auth"; + event.error_condition = "forbidden"; elseif (whitelist_only and not ip_in_set(whitelisted_ips, ip)) then log("debug", "Registration disallowed by whitelist"); event.allowed = false; event.reason = "Your IP address is not whitelisted"; + event.error_type = "auth"; + event.error_condition = "forbidden"; elseif throttle_max and not ip_in_set(whitelisted_ips, ip) then if not check_throttle(ip) then log("debug", "Registrations over limit for ip %s", ip or "?"); event.allowed = false; event.reason = "Too many registrations from this IP address recently"; + event.error_type = "wait"; + event.error_condition = "policy-violation"; end end end); -- cgit v1.2.3 From 213edf5203123371b0a9fe0efe3606ec6f9575c2 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 29 Sep 2019 16:22:05 +0200 Subject: mod_register_ibr: Reminder to maybe use util.error in the future --- plugins/mod_register_ibr.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/mod_register_ibr.lua b/plugins/mod_register_ibr.lua index fe5ede2b..3f6da004 100644 --- a/plugins/mod_register_ibr.lua +++ b/plugins/mod_register_ibr.lua @@ -168,6 +168,7 @@ module:hook("stanza/iq/jabber:iq:register:query", function(event) module:fire_event("user-registering", user); if not user.allowed then log("debug", "Registration disallowed by module: %s", user.reason or "no reason given"); + -- TODO This could use util.error session.send(st.error_reply(stanza, user.error_type or "modify", user.error_condition or "not-acceptable", user.reason)); return true; end -- cgit v1.2.3 From 2ad505a81f7dbb9be487fd51481d4d82120c26e3 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 29 Sep 2019 17:07:39 +0200 Subject: core.modulemanager: Split lists across multiple lines for improved readability Patches will also be easier to read. --- core/modulemanager.lua | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/core/modulemanager.lua b/core/modulemanager.lua index df6ae787..5a45d6b6 100644 --- a/core/modulemanager.lua +++ b/core/modulemanager.lua @@ -23,8 +23,24 @@ local debug_traceback = debug.traceback; local setmetatable, rawget = setmetatable, rawget; local ipairs, pairs, type, t_insert = ipairs, pairs, type, table.insert; -local autoload_modules = {prosody.platform, "presence", "message", "iq", "offline", "c2s", "s2s", "s2s_auth_certs"}; -local component_inheritable_modules = {"tls", "saslauth", "dialback", "iq", "s2s", "s2s_bidi"}; +local autoload_modules = { + prosody.platform, + "presence", + "message", + "iq", + "offline", + "c2s", + "s2s", + "s2s_auth_certs", +}; +local component_inheritable_modules = { + "tls", + "saslauth", + "dialback", + "iq", + "s2s", + "s2s_bidi", +}; -- We need this to let modules access the real global namespace local _G = _G; -- cgit v1.2.3 From 90d58b619e2533d796e20235cfcd750a55152bf5 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 29 Sep 2019 17:30:54 +0200 Subject: net.server_epoll: Correct indentation --- net/server_epoll.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index 3aa37ee6..ab513e93 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -795,10 +795,10 @@ local function link(from, to, read_size) from:debug("Linking to %s", to.id); function from:onincoming(data) self:pause(); - to:write(data); + to:write(data); end function to:ondrain() -- luacheck: ignore 212/self - from:resume(); + from:resume(); end from:set_mode(read_size); from:set(true, nil); -- cgit v1.2.3 From fc0e6e197d28d5a37b89a12bf9ae3119d0ae69d6 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 29 Sep 2019 17:34:47 +0200 Subject: mod_register_ibr: Distinguish between failure to create account or save extra data --- plugins/mod_register_ibr.lua | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/plugins/mod_register_ibr.lua b/plugins/mod_register_ibr.lua index 3f6da004..2f220658 100644 --- a/plugins/mod_register_ibr.lua +++ b/plugins/mod_register_ibr.lua @@ -179,14 +179,13 @@ module:hook("stanza/iq/jabber:iq:register:query", function(event) return true; end - -- TODO unable to write file, file may be locked, etc, what's the correct error? - local error_reply = st.error_reply(stanza, "wait", "internal-server-error", "Failed to write data to disk."); - if usermanager_create_user(username, password, host) then + local created, err = usermanager_create_user(username, password, host); + if created then data.registered = os.time(); if not account_details:set(username, data) then log("debug", "Could not store extra details"); usermanager_delete_user(username, host); - session.send(error_reply); + session.send(st.error_reply(stanza, "wait", "internal-server-error", "Failed to write data to disk.")); return true; end session.send(st.reply(stanza)); -- user created! @@ -195,8 +194,8 @@ module:hook("stanza/iq/jabber:iq:register:query", function(event) username = username, host = host, source = "mod_register", session = session }); else - log("debug", "Could not create user"); - session.send(error_reply); + log("debug", "Could not create user", err); + session.send(st.error_reply(stanza, "cancel", "feature-not-implemented", err)); end return true; end); -- cgit v1.2.3 From 6b4ed1a30aa2a8b7e07ba7397564f41af53565ba Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 29 Sep 2019 18:42:35 +0200 Subject: util.async: Add function for waiting on promises and unpacking the results --- util/async.lua | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/util/async.lua b/util/async.lua index 20397785..d338071f 100644 --- a/util/async.lua +++ b/util/async.lua @@ -246,9 +246,25 @@ local function ready() return pcall(checkthread); end +local function wait(promise) + local async_wait, async_done = waiter(); + local ret, err = nil, nil; + promise:next( + function (r) ret = r; end, + function (e) err = e; end) + :finally(async_done); + async_wait(); + if ret then + return ret; + else + return nil, err; + end +end + return { ready = ready; waiter = waiter; guarder = guarder; runner = runner; + wait = wait; }; -- cgit v1.2.3 From f304a306dd05b8f99ba5ef26d301ea33731d2007 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 29 Sep 2019 18:44:58 +0200 Subject: mod_admin_telnet: Use new compact function for waiting on promises --- plugins/mod_admin_telnet.lua | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 5c08b8d1..24230257 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -1148,13 +1148,7 @@ function def_env.xmpp:ping(localhost, remotehost, timeout) end local iq = st.iq{ from=localhost, to=remotehost, type="get", id=new_id()} :tag("ping", {xmlns="urn:xmpp:ping"}); - local ret, err; - local wait, done = async.waiter(); - module:context(localhost):send_iq(iq, nil, timeout) - :next(function (ret_) ret = ret_; end, - function (err_) err = err_; end) - :finally(done); - wait(); + local ret, err = async.wait(module:context(localhost):send_iq(iq, nil, timeout)); if ret then return true, "pong from " .. ret.stanza.attr.from; else -- cgit v1.2.3 From a4f4107e5a258eb083d63acdb75f3f39bb39d34c Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 29 Sep 2019 21:12:46 +0200 Subject: util.sasl.scram: Fix old API This function is not directly used by anything in Prosody anymore and should be considered deprecated. --- util/sasl/scram.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/sasl/scram.lua b/util/sasl/scram.lua index 793382b8..9bf1737b 100644 --- a/util/sasl/scram.lua +++ b/util/sasl/scram.lua @@ -269,6 +269,6 @@ end return { get_hash = get_scram_hasher; hashers = auth_db_getters; - getAuthenticationDatabaseSHA1 = get_scram_hasher(hashes.sha1, hashes.sha256, hashes.pbkdf2_hmac_sha1); + getAuthenticationDatabaseSHA1 = get_scram_hasher(hashes.sha1, hashes.hmac_sha1, hashes.pbkdf2_hmac_sha1); -- COMPAT init = init; } -- cgit v1.2.3 From 082761bde34c1a1cca02d574c13d4f8f78637db6 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 29 Sep 2019 23:50:34 +0200 Subject: mod_offline: Log a debug message when message is stored --- plugins/mod_offline.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/mod_offline.lua b/plugins/mod_offline.lua index 04c679cf..dffe8357 100644 --- a/plugins/mod_offline.lua +++ b/plugins/mod_offline.lua @@ -24,7 +24,11 @@ module:hook("message/offline/handle", function(event) node = origin.username; end - return offline_messages:append(node, nil, stanza, os.time(), ""); + local ok = offline_messages:append(node, nil, stanza, os.time(), ""); + if ok then + module:log("debug", "Saved to offline storage: %s", stanza:top_tag()); + end + return ok; end, -1); module:hook("message/offline/broadcast", function(event) -- cgit v1.2.3 From 843104372c0ec065a89d09f25079f68b98314c31 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 30 Sep 2019 08:22:30 +0100 Subject: util.promise: Add some additional tests to cover callback return values --- spec/util_promise_spec.lua | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/spec/util_promise_spec.lua b/spec/util_promise_spec.lua index 65d252f6..0008c6a2 100644 --- a/spec/util_promise_spec.lua +++ b/spec/util_promise_spec.lua @@ -248,6 +248,30 @@ describe("util.promise", function () assert.spy(cb3).was_called(1); assert.spy(cb3).was_called_with("goodbye"); end); + + it("ordinary values", function () + local p = promise.resolve() + local cb = spy.new(function () + return "hello" + end); + local cb2 = spy.new(function () end); + p:next(cb):next(cb2); + assert.spy(cb).was_called(1); + assert.spy(cb2).was_called(1); + assert.spy(cb2).was_called_with("hello"); + end); + + it("nil", function () + local p = promise.resolve() + local cb = spy.new(function () + return + end); + local cb2 = spy.new(function () end); + p:next(cb):next(cb2); + assert.spy(cb).was_called(1); + assert.spy(cb2).was_called(1); + assert.spy(cb2).was_called_with(nil); + end); end); describe("race()", function () -- cgit v1.2.3 From 5cadccc68af00a5d2dcd2ea09c03e713e4fa4739 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 5 Oct 2019 15:22:59 +0200 Subject: net.server_epoll: Return early when attepting to set write lock state to current state Reduces needless duplication of work and log messages.. --- net/server_epoll.lua | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index ab513e93..62db6c86 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -668,6 +668,9 @@ function interface:setlimit(Bps) end function interface:pause_writes() + if self._write_lock then + return + end self:debug("Pause writes"); self._write_lock = true; self:setwritetimeout(false); @@ -675,6 +678,9 @@ function interface:pause_writes() end function interface:resume_writes() + if not self._write_lock then + return + end self:debug("Resume writes"); self._write_lock = nil; if self.writebuffer[1] then -- cgit v1.2.3 From 72b4209cfb6968802358cf5556dfb7be6442e194 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 5 Oct 2019 16:21:41 +0200 Subject: core.s2smanager: Remove bidi-enabled s2sin from outgoing routing table Caused creation of new s2sout instead of proper bidi-enabled s2sin. --- core/s2smanager.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/s2smanager.lua b/core/s2smanager.lua index 971ccc5c..20b7ffea 100644 --- a/core/s2smanager.lua +++ b/core/s2smanager.lua @@ -93,6 +93,9 @@ local function destroy_session(session, reason) hosts[session.from_host].s2sout[session.to_host] = nil; session:bounce_sendq(reason); elseif session.direction == "incoming" then + if session.outgoing then + hosts[session.from_host].s2sout[session.to_host] = nil; + end incoming_s2s[session] = nil; end -- cgit v1.2.3 From 1356b90c3a135009a5b6ba5c56b2f4a407f7fc3a Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 5 Oct 2019 16:50:41 +0200 Subject: mod_csi_simple: Try not to flush buffer while already flushing it Reduces log noice --- plugins/mod_csi_simple.lua | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugins/mod_csi_simple.lua b/plugins/mod_csi_simple.lua index 4e2b95e1..b99aaab3 100644 --- a/plugins/mod_csi_simple.lua +++ b/plugins/mod_csi_simple.lua @@ -75,6 +75,10 @@ local function manage_buffer(stanza, session) end local function flush_buffer(data, session) + if session.csi_flushing then + return data; + end + session.csi_flushing = true; session.log("debug", "Client sent something, flushing buffer once (queue size is %d)", session.csi_counter); session.conn:resume_writes(); return data; @@ -92,6 +96,7 @@ end function disable_optimizations(session) if session.conn and session.conn.resume_writes then + session.csi_flushing = nil; filters.remove_filter(session, "stanzas/out", manage_buffer); filters.remove_filter(session, "bytes/in", flush_buffer); session.conn:resume_writes(); -- cgit v1.2.3 From 2e1a43906f35d4feaa7398eb1f4848fcdda3bd5c Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 5 Oct 2019 16:55:58 +0200 Subject: mod_csi_simple: Always remove session filters when disabling CSI Only guard the actual pausing of outgoing data on the method existing. This prevents the filters from lingering in case something happened to the connection. Removing already removed filters should be a safe noop. --- plugins/mod_csi_simple.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/mod_csi_simple.lua b/plugins/mod_csi_simple.lua index b99aaab3..24a2f1ce 100644 --- a/plugins/mod_csi_simple.lua +++ b/plugins/mod_csi_simple.lua @@ -95,10 +95,10 @@ function enable_optimizations(session) end function disable_optimizations(session) + session.csi_flushing = nil; + filters.remove_filter(session, "stanzas/out", manage_buffer); + filters.remove_filter(session, "bytes/in", flush_buffer); if session.conn and session.conn.resume_writes then - session.csi_flushing = nil; - filters.remove_filter(session, "stanzas/out", manage_buffer); - filters.remove_filter(session, "bytes/in", flush_buffer); session.conn:resume_writes(); end end -- cgit v1.2.3 From fb13625e08b83f4c1886e90963aeda49137c04ab Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 5 Oct 2019 17:09:24 +0200 Subject: core.s2smanager: Fix traceback due to mixup with to/from Forgot to swap to and from in 3123a13cf577 --- core/s2smanager.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/s2smanager.lua b/core/s2smanager.lua index 20b7ffea..7471286c 100644 --- a/core/s2smanager.lua +++ b/core/s2smanager.lua @@ -94,7 +94,7 @@ local function destroy_session(session, reason) session:bounce_sendq(reason); elseif session.direction == "incoming" then if session.outgoing then - hosts[session.from_host].s2sout[session.to_host] = nil; + hosts[session.to_host].s2sout[session.from_host] = nil; end incoming_s2s[session] = nil; end -- cgit v1.2.3 From 153ebfe5d719d50312629bb9475bde5500f1da6e Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 5 Oct 2019 18:10:12 +0200 Subject: net.server_epoll: Log size of partial writes (debug) --- net/server_epoll.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index 62db6c86..991383ec 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -420,6 +420,7 @@ function interface:onwritable() self:ondrain(); -- Be aware of writes in ondrain return; elseif partial then + self:debug("Sent %d out of %d buffered bytes", partial, #data); buffer[1] = data:sub(partial+1); for i = #buffer, 2, -1 do buffer[i] = nil; -- cgit v1.2.3 From 2da7971cb5dae639818e67ba68d37946d948b787 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 6 Oct 2019 16:07:26 +0200 Subject: util.sasl.scram: Avoid implicit coersion of number to string Lua can be compiled without coercion, which would cause an error here. --- util/sasl/scram.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/sasl/scram.lua b/util/sasl/scram.lua index 9bf1737b..1d1590e8 100644 --- a/util/sasl/scram.lua +++ b/util/sasl/scram.lua @@ -190,7 +190,7 @@ local function scram_gen(hash_name, H_f, HMAC_f, get_auth_db) end local nonce = clientnonce .. generate_uuid(); - local server_first_message = "r="..nonce..",s="..base64.encode(salt)..",i="..iteration_count; + local server_first_message = ("r=%s,s=%s,i=%d"):format(nonce, base64.encode(salt), iteration_count); self.state = { gs2_header = gs2_header; gs2_cbind_name = gs2_cbind_name; -- cgit v1.2.3 From 56e9e471e7501bffa576aa5eef51d9bf3c0561b5 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 6 Oct 2019 16:23:43 +0200 Subject: net.server_epoll: Log TLS version and cipher for all completed handshakes The similar logging in mod_c2s and mod_s2s does not cover all connections, like HTTPS or other Direct TLS ports. --- net/server_epoll.lua | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index 991383ec..6c65bcdd 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -548,7 +548,12 @@ function interface:tlshandskake() end local ok, err = self.conn:dohandshake(); if ok then - self:debug("TLS handshake complete"); + if self.conn.info then + local info = self.conn:info(); + self:debug("TLS handshake complete (%s with %s)", info.protocol, info.cipher); + else + self:debug("TLS handshake complete"); + end self.onwritable = nil; self.onreadable = nil; self:on("status", "ssl-handshake-complete"); -- cgit v1.2.3 From e8eae31799c80382afb90ff4c0e888078bfd5494 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 6 Oct 2019 16:34:42 +0200 Subject: doap: Add reference to draft-cridland-xmpp-session This covers the optional element added in 0bbbc9042361 --- doc/doap.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/doap.xml b/doc/doap.xml index a52fd68a..81322722 100644 --- a/doc/doap.xml +++ b/doc/doap.xml @@ -51,6 +51,8 @@ + + -- cgit v1.2.3 From 193bc49cac95c2876afcd223379a93eff9855770 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 6 Oct 2019 19:34:03 +0200 Subject: net.server_epoll: Guard against nil return from TLS info method --- net/server_epoll.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index 6c65bcdd..cccc8b5d 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -548,8 +548,8 @@ function interface:tlshandskake() end local ok, err = self.conn:dohandshake(); if ok then - if self.conn.info then - local info = self.conn:info(); + local info = self.conn.info and self.conn:info(); + if type(info) == "table" then self:debug("TLS handshake complete (%s with %s)", info.protocol, info.cipher); else self:debug("TLS handshake complete"); -- cgit v1.2.3 From 7b43531fa9779ba0ec001e87cbe5004cc8e303f1 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 6 Oct 2019 19:35:35 +0200 Subject: mod_admin_telnet: xmpp:ping: Log ping time --- plugins/mod_admin_telnet.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 24230257..afb4fb4b 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -33,6 +33,7 @@ local envloadfile = require "util.envload".envloadfile; local has_pposix, pposix = pcall(require, "util.pposix"); local async = require "util.async"; local serialize = require "util.serialization".new({ fatal = false, unquoted = true}); +local time = require "util.time"; local commands = module:shared("commands") local def_env = module:shared("env"); @@ -1148,9 +1149,10 @@ function def_env.xmpp:ping(localhost, remotehost, timeout) end local iq = st.iq{ from=localhost, to=remotehost, type="get", id=new_id()} :tag("ping", {xmlns="urn:xmpp:ping"}); + local time_start = time.now(); local ret, err = async.wait(module:context(localhost):send_iq(iq, nil, timeout)); if ret then - return true, "pong from " .. ret.stanza.attr.from; + return true, ("pong from %s in %gs"):format(ret.stanza.attr.from, time.now() - time_start); else return false, tostring(err); end -- cgit v1.2.3 From 5460dfed75e36009063b3f0af2b3f9fd0f920837 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 6 Oct 2019 21:14:53 +0200 Subject: mod_s2s: Close with a stream error in case neither SASL or Dialback are available This both tells the remote server and users who sent any queued stanzas why it failed. --- plugins/mod_s2s/mod_s2s.lua | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/mod_s2s/mod_s2s.lua b/plugins/mod_s2s/mod_s2s.lua index 41b1875b..b9c13ef2 100644 --- a/plugins/mod_s2s/mod_s2s.lua +++ b/plugins/mod_s2s/mod_s2s.lua @@ -184,7 +184,10 @@ function module.add_host(module) return true; elseif not session.dialback_verifying then session.log("warn", "No SASL EXTERNAL offer and Dialback doesn't seem to be enabled, giving up"); - session:close(); + session:close({ + condition = "unsupported-feature", + text = "No viable authentication method offered", + }); return false; end end, -1); -- cgit v1.2.3 From d3a7d673d63cc78564ee231f58751c4ad11c0c1a Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 9 Oct 2019 23:19:42 +0200 Subject: doap: Fix element name typo (node -> note) --- doc/doap.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/doap.xml b/doc/doap.xml index 81322722..5f20efaf 100644 --- a/doc/doap.xml +++ b/doc/doap.xml @@ -196,13 +196,13 @@ - via XEP-0163 + via XEP-0163 - via XEP-0163 + via XEP-0163 @@ -221,7 +221,7 @@ - via XEP-0163 + via XEP-0163 -- cgit v1.2.3 From ab2711ede517c303256e4919203f1c7b2b9b6b87 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 9 Oct 2019 23:20:22 +0200 Subject: doap: Mention support for XEP-0122: Data Forms Validation Added to util.dataforms in a4c52e304e6f. Used in PubSub (cc32aae5c7da) and MUC (13ccc2f05007). --- doc/doap.xml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/doc/doap.xml b/doc/doap.xml index 5f20efaf..76f9cae1 100644 --- a/doc/doap.xml +++ b/doc/doap.xml @@ -236,6 +236,14 @@ 0.10 + + + + 1.0.2 + 0.11 + partial + + -- cgit v1.2.3 From 4394dbf275eb840fe8f4568eca3957560c42c3d0 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Thu, 10 Oct 2019 16:58:02 +0200 Subject: util.statistics: Add a total count for rate counters, counting from server start. --- util/statistics.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/util/statistics.lua b/util/statistics.lua index 39954652..0ec88e21 100644 --- a/util/statistics.lua +++ b/util/statistics.lua @@ -57,12 +57,14 @@ local function new_registry(config) end; end; rate = function (name) - local since, n = time(), 0; + local since, n, total = time(), 0, 0; registry[name..":rate"] = function () + total = total + n; local t = time(); local stats = { rate = n/(t-since); count = n; + total = total; }; since, n = t, 0; return "rate", stats.rate, stats; -- cgit v1.2.3 From 8340ca2b183123ba1c1b2d78a155c727f70a9b10 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 10 Oct 2019 20:46:27 +0200 Subject: mod_http: Unhook CORS related event handlers Prevents CORS related handlers from being left over on reload. BC: `mod_http.apps[app_name][event_name]` is now a table instead of the main handler function. --- plugins/mod_http.lua | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/plugins/mod_http.lua b/plugins/mod_http.lua index 654ec6c7..8ef06dc2 100644 --- a/plugins/mod_http.lua +++ b/plugins/mod_http.lua @@ -154,7 +154,11 @@ function module.add_host(module) module:hook_object_event(server, event_name:sub(1, -2), redir_handler, -1); end if not app_handlers[event_name] then - app_handlers[event_name] = handler; + app_handlers[event_name] = { + main = handler; + cors = cors_handler; + options = options_handler; + }; module:hook_object_event(server, event_name, handler); module:hook_object_event(server, event_name, cors_handler, 1); module:hook_object_event(server, options_event_name, options_handler, -1); @@ -176,8 +180,11 @@ function module.add_host(module) local function http_app_removed(event) local app_handlers = apps[event.item.name]; apps[event.item.name] = nil; - for event_name, handler in pairs(app_handlers) do - module:unhook_object_event(server, event_name, handler); + for event_name, handlers in pairs(app_handlers) do + module:unhook_object_event(server, event_name, handlers.main); + module:unhook_object_event(server, event_name, handlers.cors); + local options_event_name = event_name:gsub("^%S+", "OPTIONS"); + module:unhook_object_event(server, options_event_name, handlers.options); end end -- cgit v1.2.3 From d9639ab531002b59206e1cc00f8e1f58c5a6c2ae Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 10 Oct 2019 23:39:32 +0200 Subject: doap: Fix missing '0' in URL to XEP-0288 --- doc/doap.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/doap.xml b/doc/doap.xml index 76f9cae1..8bc1c4fb 100644 --- a/doc/doap.xml +++ b/doc/doap.xml @@ -415,7 +415,7 @@ - + 0.12 -- cgit v1.2.3 From f9807ebbf057058cb6ef18966dc4800d874bb32a Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 10 Oct 2019 23:41:59 +0200 Subject: doap: Note about where XEP-0227 is used --- doc/doap.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/doap.xml b/doc/doap.xml index 8bc1c4fb..0e8c4c11 100644 --- a/doc/doap.xml +++ b/doc/doap.xml @@ -399,6 +399,7 @@ 0.7 + Used in migrator tools -- cgit v1.2.3 From a16efe359c2f3ee0d565cc85cb344771135fbc88 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 11 Oct 2019 00:27:53 +0200 Subject: doap: Referece SCRAM Added in 0.7.0, first commit 41d42d253a1d --- doc/doap.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/doap.xml b/doc/doap.xml index 0e8c4c11..8407888e 100644 --- a/doc/doap.xml +++ b/doc/doap.xml @@ -46,6 +46,7 @@ https://www.zash.se/ + -- cgit v1.2.3 From eb9557dc286e0e2fbfeafda19b982f3da7faeef8 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 11 Oct 2019 00:29:05 +0200 Subject: doap: Note about XEP-0237: Roster Versioning being merged into rfc6121 --- doc/doap.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/doap.xml b/doc/doap.xml index 8407888e..e916ab6a 100644 --- a/doc/doap.xml +++ b/doc/doap.xml @@ -407,6 +407,7 @@ 0.4 + implied by rfc6121 -- cgit v1.2.3 From 116ded720bcd9614b34be1eb3b4b794bb3a5c50b Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 11 Oct 2019 00:29:53 +0200 Subject: doap: Add detials for XEP-0280, 0288 and 0292 --- doc/doap.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doc/doap.xml b/doc/doap.xml index e916ab6a..787de9b4 100644 --- a/doc/doap.xml +++ b/doc/doap.xml @@ -413,19 +413,26 @@ + 0.12.1 + complete 0.10 + 1.0.1 + complete 0.12 + 0.10 + complete 0.11 + mod_vcard4, mod_vcard_legacy -- cgit v1.2.3 From 1ef09f8698c54bc48aa67bd54bb7c12cf6a63803 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 11 Oct 2019 00:52:29 +0200 Subject: doap: Even more XEP details --- doc/doap.xml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/doc/doap.xml b/doc/doap.xml index 787de9b4..e1474c14 100644 --- a/doc/doap.xml +++ b/doc/doap.xml @@ -260,6 +260,7 @@ + 2.0 0.6 0.10 @@ -267,29 +268,36 @@ + 1.1 + 0.11 via XEP-0398 + 1.0.1 0.10 + 1.0.1 + 0.1 + 1.2.1 0.5 + 1.0 @@ -301,11 +309,14 @@ + 1.2 + 0.4 + 1.1 0.9 @@ -317,6 +328,8 @@ + 1.0 + complete 0.9.10 @@ -330,6 +343,7 @@ 1.3 + complete 0.10 -- cgit v1.2.3 From 6fd6ad9958fb23a8956a21810113c5da99a40b6a Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 11 Oct 2019 00:58:33 +0200 Subject: doap: Add XEP-0380 since mod_csi_simple takes that into consideration --- doc/doap.xml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/doc/doap.xml b/doc/doap.xml index e1474c14..42e98f77 100644 --- a/doc/doap.xml +++ b/doc/doap.xml @@ -478,6 +478,14 @@ 0.11 + + + + 0.3.0 + 0.11 + Used in context of XEP-0352 + + -- cgit v1.2.3 From 89a6f8d8c108bdf633d7d6589c260de833c56bf5 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 12 Oct 2019 18:27:02 +0200 Subject: net.http.server: Ensure HEAD requests are sent with empty body --- net/http/server.lua | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/net/http/server.lua b/net/http/server.lua index 9b63d516..47680455 100644 --- a/net/http/server.lua +++ b/net/http/server.lua @@ -194,8 +194,11 @@ function handle_request(conn, request, finish_cb) response_conn_header = httpversion == "1.1" and "close" or nil end + local is_head_request = request.method == "HEAD"; + local response = { request = request; + is_head_request = is_head_request; status_code = 200; headers = { date = date_header, connection = response_conn_header }; persistent = persistent; @@ -291,16 +294,29 @@ local function prepare_header(response) return output; end _M.prepare_header = prepare_header; +function _M.send_head_response(response) + if response.finished then return; end + local output = prepare_header(response); + response.conn:write(t_concat(output)); + response:done(); +end function _M.send_response(response, body) if response.finished then return; end body = body or response.body or ""; response.headers.content_length = #body; + if response.is_head_request then + return _M.send_head_response(response) + end local output = prepare_header(response); t_insert(output, body); response.conn:write(t_concat(output)); response:done(); end function _M.send_file(response, f) + if response.is_head_request then + if f.close then f:close(); end + return _M.send_head_response(response); + end if response.finished then return; end local chunked = not response.headers.content_length; if chunked then response.headers.transfer_encoding = "chunked"; end -- cgit v1.2.3 From 8e485ec3200359adcc00731a371b4455d24bb562 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 12 Oct 2019 18:27:54 +0200 Subject: net.http.server: Re-fire unhandled HEAD requsts as GET events (fixes #1447) BC: This overloads the GET event. Previous commit ensures HEAD requests are sent without a body. --- CHANGES | 1 + net/http/server.lua | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/CHANGES b/CHANGES index 11f1837d..bddf32c6 100644 --- a/CHANGES +++ b/CHANGES @@ -11,6 +11,7 @@ TRUNK - Rewritten migrator - SCRAM-SHA-256 - Bi-directional server-to-server (XEP-0288) +- Built-in HTTP server now handles HEAD requests 0.11.0 ====== diff --git a/net/http/server.lua b/net/http/server.lua index 47680455..b649ff5f 100644 --- a/net/http/server.lua +++ b/net/http/server.lua @@ -229,6 +229,11 @@ function handle_request(conn, request, finish_cb) local payload = { request = request, response = response }; log("debug", "Firing event: %s", global_event); local result = events.fire_event(global_event, payload); + if result == nil and is_head_request then + local global_head_event = "GET "..request.path:match("[^?]*"); + log("debug", "Firing event: %s", global_head_event); + result = events.fire_event(global_head_event, payload); + end if result == nil then if not hosts[host] then if hosts[default_host] then @@ -249,6 +254,12 @@ function handle_request(conn, request, finish_cb) local host_event = request.method.." "..host..request.path:match("[^?]*"); log("debug", "Firing event: %s", host_event); result = events.fire_event(host_event, payload); + + if result == nil and is_head_request then + local host_head_event = "GET "..host..request.path:match("[^?]*"); + log("debug", "Firing event: %s", host_head_event); + result = events.fire_event(host_head_event, payload); + end end if result ~= nil then if result ~= true then -- cgit v1.2.3 From bf5f096225ec159e50e38b6631114553a03adce9 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 12 Oct 2019 19:16:43 +0200 Subject: mod_websocket: Guard against upgrading to websocket from a HEAD request --- plugins/mod_websocket.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_websocket.lua b/plugins/mod_websocket.lua index c94ea84a..386a4d60 100644 --- a/plugins/mod_websocket.lua +++ b/plugins/mod_websocket.lua @@ -136,7 +136,7 @@ function handle_request(event) conn.starttls = false; -- Prevent mod_tls from believing starttls can be done - if not request.headers.sec_websocket_key then + if not request.headers.sec_websocket_key or request.method ~= "GET" then response.headers.content_type = "text/html"; return [[Websocket

It works! Now point your WebSocket client to this URL to connect to Prosody.

-- cgit v1.2.3 From 30a72c72a392fdbee1e91946c9a72ed88a20b232 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 12 Oct 2019 19:30:29 +0200 Subject: net.http.server: Explicitly convert number to string, avoiding implicit coercion --- net/http/server.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/http/server.lua b/net/http/server.lua index b649ff5f..bf24c97e 100644 --- a/net/http/server.lua +++ b/net/http/server.lua @@ -314,7 +314,7 @@ end function _M.send_response(response, body) if response.finished then return; end body = body or response.body or ""; - response.headers.content_length = #body; + response.headers.content_length = ("%d"):format(#body); if response.is_head_request then return _M.send_head_response(response) end -- cgit v1.2.3 From 9e6dce07bf7a9836eb8123bf198c8671d423157f Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 12 Oct 2019 19:31:48 +0200 Subject: net.http.files: Explicitly convert number to string, avoiding implicit coercion --- net/http/files.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/http/files.lua b/net/http/files.lua index 7ff81fc8..650c6f47 100644 --- a/net/http/files.lua +++ b/net/http/files.lua @@ -127,7 +127,7 @@ local function serve(opts) local content_type = ext and mime_map[ext]; response_headers.content_type = content_type; if attr.size > cache_max_file_size then - response_headers.content_length = attr.size; + response_headers.content_length = ("%d"):format(attr.size); log("debug", "%d > cache_max_file_size", attr.size); return response:send_file(f); else -- cgit v1.2.3 From 73d6c64bd7b061a8790919daac19e09cd00fa5e1 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 12 Oct 2019 19:51:29 +0200 Subject: net.server_epoll: Move a log message to improve ordering It was weird that it said "Prepared to start TLS" before "Client .. created" --- net/server_epoll.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index cccc8b5d..3b134312 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -775,10 +775,10 @@ local function addclient(addr, port, listeners, read_size, tls_ctx, typ, extra) local client = wrapsocket(conn, nil, read_size, listeners, tls_ctx, extra) local ok, err = client:init(); if not ok then return ok, err; end + client:debug("Client %s created", client); if tls_ctx then client:starttls(tls_ctx); end - client:debug("Client %s created", client); return client, conn; end -- cgit v1.2.3 From 3585385ab60126105f7e0c7900399ec05c56a781 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 12 Oct 2019 20:07:50 +0200 Subject: net.server_epoll: Fix to get remote IP on direct TLS connections A Direct TLS connection (eg HTTPS) gets turned into a LuaSec handle before the :updatenames call done in the :connect method. LuaSec does not expose the :getpeername and :getsockname methods, so the addresses remain obscured, making debugging trickier since the actual IP addrerss connected to does not show up. --- net/server_epoll.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index 3b134312..7a414901 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -521,6 +521,7 @@ function interface:tlshandskake() self._tls = true; self:debug("Starting TLS now"); self:del(); + self:updatenames(); -- Can't getpeer/sockname after wrap() local ok, conn, err = pcall(luasec.wrap, self.conn, self.tls_ctx); if not ok then conn, err = ok, conn; -- cgit v1.2.3 From 3e8be00bbec2bfc691a2fd7fdb4dde33d39a55d4 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 12 Oct 2019 20:12:04 +0200 Subject: net.server_epoll: Handle getpeer/sockname returning a normal error These will sometimes return nil, "Transport not connected" but not throw a hard error. This shouldn't be treated as success. --- net/server_epoll.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index 7a414901..3745b426 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -605,11 +605,11 @@ end function interface:updatenames() local conn = self.conn; local ok, peername, peerport = pcall(conn.getpeername, conn); - if ok then + if ok and peername then self.peername, self.peerport = peername, peerport; end local ok, sockname, sockport = pcall(conn.getsockname, conn); - if ok then + if ok and sockname then self.sockname, self.sockport = sockname, sockport; end end -- cgit v1.2.3 From 066ee6e7810f638be65445ef7161ee41b6e33f14 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 12 Oct 2019 20:15:31 +0200 Subject: server_epoll: Log full string represestation when connected Since they may have been unknown when the connection was created. --- net/server_epoll.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index 3745b426..9feba360 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -698,8 +698,8 @@ end -- Connected! function interface:onconnect() - self:debug("Connected"); self:updatenames(); + self:debug("Connected (%s)", self); self.onconnect = noop; self:on("connect"); end -- cgit v1.2.3 From c5a70f10627bcdbc1b9f67e055574bbedc801fac Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 12 Oct 2019 20:22:07 +0200 Subject: net.server_epoll: Save IP and port from connection creation call Might come out of :getpeername different later but at least it's something. --- net/server_epoll.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index 9feba360..a2052875 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -775,6 +775,10 @@ local function addclient(addr, port, listeners, read_size, tls_ctx, typ, extra) if not ok and err ~= "timeout" then return ok, err; end local client = wrapsocket(conn, nil, read_size, listeners, tls_ctx, extra) local ok, err = client:init(); + if not client.peername then + -- otherwise not set until connected + client.peername, client.peerport = addr, port; + end if not ok then return ok, err; end client:debug("Client %s created", client); if tls_ctx then -- cgit v1.2.3 From bf566284b130f8be7b5d25bfe883de882f1ff7bb Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 13 Oct 2019 00:32:58 +0200 Subject: mod_saslauth: Remove commented-out debug log line --- plugins/mod_saslauth.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/mod_saslauth.lua b/plugins/mod_saslauth.lua index 89313de1..52de1434 100644 --- a/plugins/mod_saslauth.lua +++ b/plugins/mod_saslauth.lua @@ -66,7 +66,6 @@ local function sasl_process_cdata(session, stanza) local text = stanza[1]; if text then text = base64.decode(text); - --log("debug", "AUTH: %s", text:gsub("[%z\001-\008\011\012\014-\031]", " ")); if not text then session.sasl_handler = nil; session.send(build_reply("failure", "incorrect-encoding")); -- cgit v1.2.3 From 8eb15f32e17e630b8965813e09d7dc73a9539985 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 13 Oct 2019 00:33:35 +0200 Subject: mod_saslauth: Remove useless debug log line Fairly useless to only log half of SASL messages. Use mod_stanza_debug instead to get the full exchange. --- plugins/mod_saslauth.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/mod_saslauth.lua b/plugins/mod_saslauth.lua index 52de1434..251c0c4e 100644 --- a/plugins/mod_saslauth.lua +++ b/plugins/mod_saslauth.lua @@ -75,7 +75,6 @@ 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", s); session.send(s); return true; end -- cgit v1.2.3 From 76c7cb9560f56dd3a24b26ff72182cd34a2a350c Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 15 Oct 2019 00:13:52 +0200 Subject: doc/doap: Claim support for XEP-0268 via mod_csi_simple mod_csi_simple tries to follow the advice in XEP-0268. Notably, since 7d78b24d8449 it also does this: > If the server receives data, the phones radio is already on, therefore > you should flush any pending data as soon as possible after receiving > data from a client --- doc/doap.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doc/doap.xml b/doc/doap.xml index 42e98f77..3d8de106 100644 --- a/doc/doap.xml +++ b/doc/doap.xml @@ -424,6 +424,13 @@ implied by rfc6121
+ + + + 0.11 + mod_csi_simple + + -- cgit v1.2.3 From 945ec3c73e6dfc66767e7283af345f5d0eaa2b8b Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 15 Oct 2019 00:21:01 +0200 Subject: doc/doap: Claim support for XEP-0368 by way of legacy_ssl_ports > Server support of XEP-0368 means having the ability to accept direct TLS connections. This is what legacy_ssl_ports does. First trace of it seems to be 8458be0941e7 --- doc/doap.xml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/doc/doap.xml b/doc/doap.xml index 3d8de106..ed6fc7bb 100644 --- a/doc/doap.xml +++ b/doc/doap.xml @@ -485,6 +485,15 @@ 0.11 + + + + 1.1.0 + partial + 0.2 + legacy_ssl_ports + + -- cgit v1.2.3 From a375a343326a80bab8d98d86118772d1e0791be1 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 15 Oct 2019 21:37:19 +0200 Subject: mod_saslauth: Log (debug) messages about channel binding --- plugins/mod_saslauth.lua | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugins/mod_saslauth.lua b/plugins/mod_saslauth.lua index 251c0c4e..cfaa1f9c 100644 --- a/plugins/mod_saslauth.lua +++ b/plugins/mod_saslauth.lua @@ -250,11 +250,16 @@ module:hook("stream-features", function(event) if sasl_handler.add_cb_handler then local socket = origin.conn:socket(); if socket.getpeerfinished then + log("debug", "Channel binding 'tls-unique' supported"); sasl_handler:add_cb_handler("tls-unique", tls_unique); + else + log("debug", "Channel binding 'tls-unique' not supported (by LuaSec?)"); end sasl_handler["userdata"] = { ["tls-unique"] = socket; }; + else + log("debug", "Channel binding not supported by SASL handler"); end end local mechanisms = st.stanza("mechanisms", mechanisms_attr); -- cgit v1.2.3 From 668089d57cc81ec71f421eb76eb6ea71af7a2d15 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 15 Oct 2019 21:58:10 +0200 Subject: mod_saslauth: Use the power of Set Theory to mange sets of SASL mechanisms This makes sets of excluded mechanisms easily available for use later. --- plugins/mod_saslauth.lua | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/plugins/mod_saslauth.lua b/plugins/mod_saslauth.lua index cfaa1f9c..3d3620cf 100644 --- a/plugins/mod_saslauth.lua +++ b/plugins/mod_saslauth.lua @@ -12,6 +12,7 @@ local st = require "util.stanza"; local sm_bind_resource = require "core.sessionmanager".bind_resource; local sm_make_authenticated = require "core.sessionmanager".make_authenticated; local base64 = require "util.encodings".base64; +local set = require "util.set"; local usermanager_get_sasl_handler = require "core.usermanager".get_sasl_handler; @@ -264,15 +265,32 @@ module:hook("stream-features", function(event) end local mechanisms = st.stanza("mechanisms", mechanisms_attr); local sasl_mechanisms = sasl_handler:mechanisms() + local available_mechanisms = set.new(); for mechanism in pairs(sasl_mechanisms) do - if disabled_mechanisms:contains(mechanism) then - log("debug", "Not offering disabled mechanism %s", mechanism); - elseif not origin.secure and insecure_mechanisms:contains(mechanism) then - log("debug", "Not offering mechanism %s on insecure connection", mechanism); - else - log("debug", "Offering mechanism %s", mechanism); + available_mechanisms:add(mechanism); + end + log("debug", "SASL mechanisms supported by handler: %s", available_mechanisms); + + local usable_mechanisms = available_mechanisms - disabled_mechanisms; + + local available_disabled = set.intersection(available_mechanisms, disabled_mechanisms); + if not available_disabled:empty() then + log("debug", "Not offering disabled mechanisms: %s", available_disabled); + end + + local available_insecure = set.intersection(available_mechanisms, insecure_mechanisms); + if not origin.secure and not available_insecure:empty() then + log("debug", "Session is not secure, not offering insecure mechanisms: %s", available_insecure); + usable_mechanisms = usable_mechanisms - insecure_mechanisms; + end + + if not usable_mechanisms:empty() then + log("debug", "Offering usable mechanisms: %s", usable_mechanisms); + for mechanism in available_mechanisms do mechanisms:tag("mechanism"):text(mechanism):up(); end + features:add_child(mechanisms); + return; end if mechanisms[1] then features:add_child(mechanisms); -- cgit v1.2.3 From 57a86c0b0c206495acf6a36fc22f2506ea438f3f Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 15 Oct 2019 22:05:51 +0200 Subject: mod_saslauth: Improve logging of why no SASL mechanisms were offered --- plugins/mod_saslauth.lua | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/plugins/mod_saslauth.lua b/plugins/mod_saslauth.lua index 3d3620cf..be57e8d8 100644 --- a/plugins/mod_saslauth.lua +++ b/plugins/mod_saslauth.lua @@ -292,14 +292,26 @@ module:hook("stream-features", function(event) features:add_child(mechanisms); return; end - if mechanisms[1] then - features:add_child(mechanisms); - elseif not next(sasl_mechanisms) then - local authmod = module:get_option_string("authentication", "internal_plain"); + + local authmod = module:get_option_string("authentication", "internal_plain"); + if available_mechanisms:empty() then log("error", "No available SASL mechanisms, verify that the configured authentication module '%s' is loaded and configured correctly", authmod); - else - log("warn", "All available authentication mechanisms are either disabled or not suitable for an insecure connection"); + return; + end + + if not origin.secure and not available_insecure:empty() then + if not available_disabled:empty() then + log("error", "All SASL mechanisms provided by authentication module '%s' are forbidden on insecure connections (%s) or disabled (%s)", + authmod, available_insecure, available_disabled); + else + log("error", "All SASL mechanisms provided by authentication module '%s' are forbidden on insecure connections (%s)", + authmod, available_insecure); + end + elseif not available_disabled:empty() then + log("error", "All SASL mechanisms provided by authentication module '%s' are disabled (%s)", + authmod, available_disabled); end + else features:tag("bind", bind_attr):tag("required"):up():up(); features:tag("session", xmpp_session_attr):tag("optional"):up():up(); -- cgit v1.2.3 From 4d28443876e2122d89bf41a2e57b34a6d1d4e813 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 15 Oct 2019 23:38:29 +0200 Subject: mod_saslauth: Demote "no SASL mechanisms" error back to warning This gets printed before TLS if c2s_require_encryption = false, in which case it is just annoying. --- plugins/mod_saslauth.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/mod_saslauth.lua b/plugins/mod_saslauth.lua index be57e8d8..9e9091d3 100644 --- a/plugins/mod_saslauth.lua +++ b/plugins/mod_saslauth.lua @@ -295,20 +295,20 @@ module:hook("stream-features", function(event) local authmod = module:get_option_string("authentication", "internal_plain"); if available_mechanisms:empty() then - log("error", "No available SASL mechanisms, verify that the configured authentication module '%s' is loaded and configured correctly", authmod); + log("warn", "No available SASL mechanisms, verify that the configured authentication module '%s' is loaded and configured correctly", authmod); return; end if not origin.secure and not available_insecure:empty() then if not available_disabled:empty() then - log("error", "All SASL mechanisms provided by authentication module '%s' are forbidden on insecure connections (%s) or disabled (%s)", + log("warn", "All SASL mechanisms provided by authentication module '%s' are forbidden on insecure connections (%s) or disabled (%s)", authmod, available_insecure, available_disabled); else - log("error", "All SASL mechanisms provided by authentication module '%s' are forbidden on insecure connections (%s)", + log("warn", "All SASL mechanisms provided by authentication module '%s' are forbidden on insecure connections (%s)", authmod, available_insecure); end elseif not available_disabled:empty() then - log("error", "All SASL mechanisms provided by authentication module '%s' are disabled (%s)", + log("warn", "All SASL mechanisms provided by authentication module '%s' are disabled (%s)", authmod, available_disabled); end -- cgit v1.2.3 From 21b0efc6ad3d7b757bebc45330ca9e40dcc158b2 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 20 Oct 2019 14:54:57 +0200 Subject: MUC: Validate registration dataform more carefully --- plugins/muc/register.lib.lua | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/plugins/muc/register.lib.lua b/plugins/muc/register.lib.lua index da106f8c..cfbdfb59 100644 --- a/plugins/muc/register.lib.lua +++ b/plugins/muc/register.lib.lua @@ -136,7 +136,19 @@ local function handle_register_iq(room, origin, stanza) return true; end local form_tag = query:get_child("x", "jabber:x:data"); - local reg_data = form_tag and registration_form:data(form_tag); + if not form_tag then + origin.send(st.error_reply(stanza, "modify", "bad-request", "Missing dataform")); + return true; + end + local form_type, err = dataforms.get_type(form_tag); + if not form_type then + origin.send(st.error_reply(stanza, "modify", "bad-request", "Error with form: "..err)); + return true; + elseif form_type ~= "http://jabber.org/protocol/muc#register" then + origin.send(st.error_reply(stanza, "modify", "bad-request", "Error in form")); + return true; + end + local reg_data = registration_form:data(form_tag); if not reg_data then origin.send(st.error_reply(stanza, "modify", "bad-request", "Error in form")); return true; -- cgit v1.2.3 From 8cfba1f03ef4376d52e2d7edfac240a78c07c758 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 20 Oct 2019 16:47:20 +0200 Subject: doap: Copy longer description from README --- doc/doap.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/doap.xml b/doc/doap.xml index ed6fc7bb..80393805 100644 --- a/doc/doap.xml +++ b/doc/doap.xml @@ -3,7 +3,7 @@ Prosody IM Lightweight XMPP server - + Prosody is a server for Jabber/XMPP written in Lua. It aims to be easy to use and light on resources. For developers, it aims to give a flexible system on which to rapidly develop added functionality or rapidly prototype new protocols. 2008-08-22 -- cgit v1.2.3 From 68b32f4c91d4c291699248549004d7f77957566d Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 20 Oct 2019 16:57:28 +0200 Subject: doap: Trim a trailing '.' from a version/branch name for consistency --- doc/doap.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/doap.xml b/doc/doap.xml index 80393805..c94b0e5f 100644 --- a/doc/doap.xml +++ b/doc/doap.xml @@ -86,7 +86,7 @@ - 0.10. + 0.10 complete -- cgit v1.2.3 From 04cb4aa218ec524bd0852e87ca72f8cd7f1d9033 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 20 Oct 2019 18:46:46 +0200 Subject: doap: mod_csi_simple tries to follow XEP-0286: Mobile Considerations, not XEP-0268 --- doc/doap.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/doap.xml b/doc/doap.xml index c94b0e5f..bfad0b62 100644 --- a/doc/doap.xml +++ b/doc/doap.xml @@ -426,7 +426,7 @@ - + 0.11 mod_csi_simple -- cgit v1.2.3 From 4caf87fa9d93fb224dbc5492a5f20adb845d27c5 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 20 Oct 2019 18:48:10 +0200 Subject: doap: Sort by XEP number --- doc/doap.xml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/doc/doap.xml b/doc/doap.xml index bfad0b62..d97d39d7 100644 --- a/doc/doap.xml +++ b/doc/doap.xml @@ -424,13 +424,6 @@ implied by rfc6121 - - - - 0.11 - mod_csi_simple - - @@ -439,6 +432,13 @@ 0.10 + + + + 0.11 + mod_csi_simple + + -- cgit v1.2.3 From 8b19b4435f6feded2bfffa4ee5a11716579ec7ea Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 20 Oct 2019 20:53:41 +0200 Subject: util.interpolation: Test #1452 --- spec/util_interpolation_spec.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/spec/util_interpolation_spec.lua b/spec/util_interpolation_spec.lua index 88d9f844..9fcad469 100644 --- a/spec/util_interpolation_spec.lua +++ b/spec/util_interpolation_spec.lua @@ -1,5 +1,5 @@ local template = [[ -{greet!}, {name?world}! +{greet!?Hi}, {name?world}! ]]; local expect1 = [[ Hello, WORLD! @@ -7,11 +7,15 @@ Hello, WORLD! local expect2 = [[ Hello, world! ]]; +local expect3 = [[ +Hi, YOU! +]]; describe("util.interpolation", function () it("renders", function () local render = require "util.interpolation".new("%b{}", string.upper); assert.equal(expect1, render(template, { greet = "Hello", name = "world" })); assert.equal(expect2, render(template, { greet = "Hello" })); + assert.equal(expect3, render(template, { name = "you" })); end); end); -- cgit v1.2.3 From c0a72783661d8008519a0283ac7f107661f89c6b Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 20 Oct 2019 20:56:29 +0200 Subject: util.interpolation: Test array syntax --- spec/util_interpolation_spec.lua | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/spec/util_interpolation_spec.lua b/spec/util_interpolation_spec.lua index 9fcad469..4106e10b 100644 --- a/spec/util_interpolation_spec.lua +++ b/spec/util_interpolation_spec.lua @@ -10,6 +10,13 @@ Hello, world! local expect3 = [[ Hi, YOU! ]]; +local template_array = [[ +{foo#{idx}. {item} +}]] +local expect_array = [[ +1. HELLO +2. WORLD +]] describe("util.interpolation", function () it("renders", function () @@ -17,5 +24,6 @@ describe("util.interpolation", function () assert.equal(expect1, render(template, { greet = "Hello", name = "world" })); assert.equal(expect2, render(template, { greet = "Hello" })); assert.equal(expect3, render(template, { name = "you" })); + assert.equal(expect_array, render(template_array, { foo = { "Hello", "World" } })); end); end); -- cgit v1.2.3 From 9889cb8bb4c614939a4a82624b0e7e3539b80b63 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 20 Oct 2019 20:58:19 +0200 Subject: util.interpolation: Test map syntax --- spec/util_interpolation_spec.lua | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/spec/util_interpolation_spec.lua b/spec/util_interpolation_spec.lua index 4106e10b..eb49e53b 100644 --- a/spec/util_interpolation_spec.lua +++ b/spec/util_interpolation_spec.lua @@ -17,6 +17,12 @@ local expect_array = [[ 1. HELLO 2. WORLD ]] +local template_map = [[ +{foo%{idx}: {item!} +}]] +local expect_map = [[ +FOO: bar +]] describe("util.interpolation", function () it("renders", function () @@ -25,5 +31,6 @@ describe("util.interpolation", function () assert.equal(expect2, render(template, { greet = "Hello" })); assert.equal(expect3, render(template, { name = "you" })); assert.equal(expect_array, render(template_array, { foo = { "Hello", "World" } })); + assert.equal(expect_map, render(template_map, { foo = { foo = "bar" } })); end); end); -- cgit v1.2.3 From 190c1e7772fe11c353ce0c0e001490e29734a63a Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Sun, 20 Oct 2019 21:58:16 +0200 Subject: MUC: Add controls for whose presence is broadcast (closes #1335) Committed by Zash --- plugins/muc/mod_muc.lua | 7 +++ plugins/muc/muc.lib.lua | 23 +++++++-- plugins/muc/presence_broadcast.lib.lua | 87 ++++++++++++++++++++++++++++++++++ 3 files changed, 112 insertions(+), 5 deletions(-) create mode 100644 plugins/muc/presence_broadcast.lib.lua diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index 89e67744..e55bd6a2 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -86,6 +86,12 @@ room_mt.get_registered_nick = register.get_registered_nick; room_mt.get_registered_jid = register.get_registered_jid; room_mt.handle_register_iq = register.handle_register_iq; +local presence_broadcast = module:require "muc/presence_broadcast"; +room_mt.get_presence_broadcast = presence_broadcast.get; +room_mt.set_presence_broadcast = presence_broadcast.set; +room_mt.get_valid_broadcast_roles = presence_broadcast.get_valid_broadcast_roles; + + local jid_split = require "util.jid".split; local jid_bare = require "util.jid".bare; local st = require "util.stanza"; @@ -263,6 +269,7 @@ local function set_room_defaults(room, lang) room:set_changesubject(module:get_option_boolean("muc_room_default_change_subject", room:get_changesubject())); room:set_historylength(module:get_option_number("muc_room_default_history_length", room:get_historylength())); room:set_language(lang or module:get_option_string("muc_room_default_language")); + room:set_presence_broadcast(module:get_option("muc_room_default_presence_broadcast", room:get_presence_broadcast())); end function create_room(room_jid, config) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index d84f4ac1..200f69f9 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -218,13 +218,13 @@ end -- Broadcasts an occupant's presence to the whole room -- Takes the x element that goes into the stanzas -function room_mt:publicise_occupant_status(occupant, x, nick, actor, reason) +function room_mt:publicise_occupant_status(occupant, x, nick, actor, reason, prev_role, force_unavailable) local base_x = x.base or x; -- Build real jid and (optionally) occupant jid template presences local base_presence do -- Try to use main jid's presence local pr = occupant:get_presence(); - if pr and (occupant.role ~= nil or pr.attr.type == "unavailable") then + if pr and (occupant.role ~= nil or pr.attr.type == "unavailable") and not force_unavailable then base_presence = st.clone(pr); else -- user is leaving but didn't send a leave presence. make one for them base_presence = st.presence {from = occupant.nick; type = "unavailable";}; @@ -280,6 +280,8 @@ function room_mt:publicise_occupant_status(occupant, x, nick, actor, reason) self_p = st.clone(base_presence):add_child(self_x); end + local broadcast_roles = self:get_presence_broadcast(); + -- General populace for occupant_nick, n_occupant in self:each_occupant() do if occupant_nick ~= occupant.nick then @@ -291,7 +293,13 @@ function room_mt:publicise_occupant_status(occupant, x, nick, actor, reason) else pr = get_anon_p(); end - self:route_to_occupant(n_occupant, pr); + if broadcast_roles[occupant.role or "none"] or force_unavailable then + self:route_to_occupant(n_occupant, pr); + elseif prev_role and broadcast_roles[prev_role] then + pr.attr.type = 'unavailable'; + self:route_to_occupant(n_occupant, pr); + end + end end @@ -315,6 +323,7 @@ function room_mt:send_occupant_list(to, filter) local to_bare = jid_bare(to); local is_anonymous = false; local whois = self:get_whois(); + local broadcast_roles = self:get_presence_broadcast(); if whois ~= "anyone" then local affiliation = self:get_affiliation(to); if affiliation ~= "admin" and affiliation ~= "owner" then @@ -331,7 +340,9 @@ function room_mt:send_occupant_list(to, filter) local pres = st.clone(occupant:get_presence()); pres.attr.to = to; pres:add_child(x); - self:route_stanza(pres); + if to_bare == occupant.bare_jid or broadcast_roles[occupant.role or "none"] then + self:route_stanza(pres); + end end end end @@ -1442,9 +1453,11 @@ function room_mt:set_role(actor, occupant_jid, role, reason) if not role then x:tag("status", {code = "307"}):up(); end + + local prev_role = occupant.role; occupant.role = role; self:save_occupant(occupant); - self:publicise_occupant_status(occupant, x, nil, actor, reason); + self:publicise_occupant_status(occupant, x, nil, actor, reason, prev_role); if role == nil then module:fire_event("muc-occupant-left", { room = self; diff --git a/plugins/muc/presence_broadcast.lib.lua b/plugins/muc/presence_broadcast.lib.lua new file mode 100644 index 00000000..ace614b3 --- /dev/null +++ b/plugins/muc/presence_broadcast.lib.lua @@ -0,0 +1,87 @@ +-- Prosody IM +-- Copyright (C) 2008-2010 Matthew Wild +-- Copyright (C) 2008-2010 Waqas Hussain +-- Copyright (C) 2014 Daurnimator +-- +-- This project is MIT/X11 licensed. Please see the +-- COPYING file in the source package for more information. +-- + +local st = require "util.stanza"; + +local valid_roles = { "visitor", "participant", "moderator" }; +local default_broadcast = { + none = true; + visitor = true; + participant = true; + moderator = true; +}; + +local function get_presence_broadcast(room) + return room._data.presence_broadcast or default_broadcast; +end + +local function set_presence_broadcast(room, broadcast_roles) + broadcast_roles = broadcast_roles or default_broadcast; + + -- Ensure that unavailable presence is always sent when role changes to none + broadcast_roles.none = true; + + local changed = false; + local old_broadcast_roles = get_presence_broadcast(room); + for _, role in ipairs(valid_roles) do + if old_broadcast_roles[role] ~= broadcast_roles[role] then + changed = true; + end + end + + if not changed then return false; end + + room._data.presence_broadcast = broadcast_roles; + + for _, occupant in room:each_occupant() do + local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user";}); + local role = occupant.role or "none"; + if broadcast_roles[role] and not old_broadcast_roles[role] then + -- Presence broadcast is now enabled, so announce existing user + room:publicise_occupant_status(occupant, x); + elseif old_broadcast_roles[role] and not broadcast_roles[role] then + -- Presence broadcast is now disabled, so mark existing user as unavailable + room:publicise_occupant_status(occupant, x, nil, nil, nil, nil, true); + end + end + + return true; +end + +module:hook("muc-config-form", function(event) + local values = {}; + for role, value in pairs(get_presence_broadcast(event.room)) do + if value then + values[#values + 1] = role; + end + end + + table.insert(event.form, { + name = "muc#roomconfig_presencebroadcast"; + type = "list-multi"; + label = "Roles for which Presence is Broadcasted"; + value = values; + options = valid_roles; + }); +end, 70-7); + +module:hook("muc-config-submitted/muc#roomconfig_presencebroadcast", function(event) + local broadcast_roles = {}; + for _, role in ipairs(event.value) do + broadcast_roles[role] = true; + end + if set_presence_broadcast(event.room, broadcast_roles) then + event.status_codes["104"] = true; + end +end); + +return { + get = get_presence_broadcast; + set = set_presence_broadcast; +}; -- cgit v1.2.3 From 76810faa9b99cc21ef7bf81e272b0ce0c89fe757 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 20 Oct 2019 23:46:47 +0200 Subject: CHANGES: Add a line for #1335 --- CHANGES | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES b/CHANGES index bddf32c6..e451d3f5 100644 --- a/CHANGES +++ b/CHANGES @@ -12,6 +12,7 @@ TRUNK - SCRAM-SHA-256 - Bi-directional server-to-server (XEP-0288) - Built-in HTTP server now handles HEAD requests +- MUC presence broadcast controls 0.11.0 ====== -- cgit v1.2.3 From 7207a107fd582334d32af3d847ef8e939d136e97 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 20 Oct 2019 23:47:48 +0200 Subject: MUC: Advertise history related fields as integers via XEP-0122 This takes advantage of data type validation and conversion done in util.dataforms. --- plugins/muc/history.lib.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/muc/history.lib.lua b/plugins/muc/history.lib.lua index 0d69c97d..f9ddabbf 100644 --- a/plugins/muc/history.lib.lua +++ b/plugins/muc/history.lib.lua @@ -48,16 +48,18 @@ module:hook("muc-config-form", function(event) table.insert(event.form, { name = "muc#roomconfig_historylength"; type = "text-single"; + datatype = "xs:integer"; label = "Maximum number of history messages returned by room"; desc = "Specify the maximum number of previous messages that should be sent to users when they join the room"; - value = tostring(get_historylength(event.room)); + value = get_historylength(event.room); }); table.insert(event.form, { name = 'muc#roomconfig_defaulthistorymessages', type = 'text-single', + datatype = "xs:integer"; label = 'Default number of history messages returned by room', desc = "Specify the number of previous messages sent to new users when they join the room"; - value = tostring(get_defaulthistorymessages(event.room)) + value = get_defaulthistorymessages(event.room); }); end, 70-5); -- cgit v1.2.3 From b5b9b70c88a1287f034bceccdd953fe805bc78c6 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Sun, 27 Oct 2019 14:45:57 +0000 Subject: util.pubsub, pubsub.lib and tests: Add text to precondition-not-met error (fixes #1455) --- plugins/mod_pubsub/pubsub.lib.lua | 4 + spec/scansion/pubsub_preconditions.scs | 234 +++++++++++++++++++++++++++++++++ spec/util_pubsub_spec.lua | 2 +- util/pubsub.lua | 12 +- 4 files changed, 248 insertions(+), 4 deletions(-) create mode 100644 spec/scansion/pubsub_preconditions.scs diff --git a/plugins/mod_pubsub/pubsub.lib.lua b/plugins/mod_pubsub/pubsub.lib.lua index a002fbe7..23695211 100644 --- a/plugins/mod_pubsub/pubsub.lib.lua +++ b/plugins/mod_pubsub/pubsub.lib.lua @@ -7,6 +7,7 @@ local st = require "util.stanza"; local it = require "util.iterators"; local uuid_generate = require "util.uuid".generate; local dataform = require"util.dataforms".new; +local errors = require "util.error"; local xmlns_pubsub = "http://jabber.org/protocol/pubsub"; local xmlns_pubsub_errors = "http://jabber.org/protocol/pubsub#errors"; @@ -34,6 +35,9 @@ local pubsub_errors = { }; local function pubsub_error_reply(stanza, error) local e = pubsub_errors[error]; + if not e and errors.is_err(error) then + e = { error.type, error.condition, error.text, error.pubsub_condition }; + end local reply = st.error_reply(stanza, t_unpack(e, 1, 3)); if e[4] then reply:tag(e[4], { xmlns = xmlns_pubsub_errors }):up(); diff --git a/spec/scansion/pubsub_preconditions.scs b/spec/scansion/pubsub_preconditions.scs new file mode 100644 index 00000000..25afaa8d --- /dev/null +++ b/spec/scansion/pubsub_preconditions.scs @@ -0,0 +1,234 @@ +# Pubsub preconditions are enforced + +[Client] Romeo + password: password + jid: jqpcrbq2@localhost + +----- + +Romeo connects + +Romeo sends: + + + + + + + + + + +Romeo receives: + + + + + + + + +Romeo sends: + + + + + + +Romeo receives: + + + + + + http://jabber.org/protocol/pubsub#node_config + + + + + + + 1 + + + 1 + + + + + + + + presence + + + + + + publishers + + + 1 + + + 1 + + + + + headline + + + 1 + + + 1 + + + + + + +Romeo sends: + + + + + + http://jabber.org/protocol/pubsub#node_config + + + Nice tunes + + + + + + 1 + + + 1 + + + + + + + + presence + + + + + + publishers + + + 1 + + + 1 + + + + + headline + + + 1 + + + 1 + + + + + + +Romeo receives: + + +Romeo sends: + + + + +Romeo receives: + + + + + + +Romeo sends: + + + + + + + + + + + http://jabber.org/protocol/pubsub#publish-options + + + whitelist + + + + + + +Romeo receives: + + + + Field does not match: access_model + + + + +Romeo disconnects + diff --git a/spec/util_pubsub_spec.lua b/spec/util_pubsub_spec.lua index c982fb36..a48bd400 100644 --- a/spec/util_pubsub_spec.lua +++ b/spec/util_pubsub_spec.lua @@ -107,7 +107,7 @@ describe("util.pubsub", function () it("fails to publish to a node with differing config", function () local ok, err = service:publish("node", true, "1", "item 2", { myoption = false }); assert.falsy(ok); - assert.equals("precondition-not-met", err); + assert.equals("precondition-not-met", err.pubsub_condition); end); it("allows to publish to a node with differing config when only defaults are suggested", function () diff --git a/util/pubsub.lua b/util/pubsub.lua index e5e0cb7c..8a07c669 100644 --- a/util/pubsub.lua +++ b/util/pubsub.lua @@ -1,5 +1,6 @@ local events = require "util.events"; local cache = require "util.cache"; +local errors = require "util.error"; local service_mt = {}; @@ -510,7 +511,7 @@ local function check_preconditions(node_config, required_config) end for config_field, value in pairs(required_config) do if node_config[config_field] ~= value then - return false; + return false, config_field; end end return true; @@ -546,8 +547,13 @@ function service:publish(node, actor, id, item, requested_config) --> ok, err node_obj = self.nodes[node]; elseif requested_config and not requested_config._defaults_only then -- Check that node has the requested config before we publish - if not check_preconditions(node_obj.config, requested_config) then - return false, "precondition-not-met"; + local ok, field = check_preconditions(node_obj.config, requested_config); + if not ok then + local err = errors.new({ + type = "cancel", condition = "conflict", text = "Field does not match: "..field; + }); + err.pubsub_condition = "precondition-not-met"; + return false, err; end end if not self.config.itemcheck(item) then -- cgit v1.2.3 From 41a40ab74b17a6a7fea165e5c63b76131b2e36e7 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 24 Apr 2019 15:01:00 +0200 Subject: util.encodings: Optional strict flag to stringprep --- util-src/encodings.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/util-src/encodings.c b/util-src/encodings.c index 6f2676f2..be34032e 100644 --- a/util-src/encodings.c +++ b/util-src/encodings.c @@ -276,6 +276,7 @@ static int icu_stringprep_prep(lua_State *L, const UStringPrepProfile *profile) int32_t unprepped_len, prepped_len, output_len; const char *input; char output[1024]; + int flags = USPREP_ALLOW_UNASSIGNED; UChar unprepped[1024]; /* Temporary unicode buffer (1024 characters) */ UChar prepped[1024]; @@ -294,6 +295,11 @@ static int icu_stringprep_prep(lua_State *L, const UStringPrepProfile *profile) return 1; } + /* strict */ + if(lua_toboolean(L, 2)) { + flags = 0; + } + u_strFromUTF8(unprepped, 1024, &unprepped_len, input, input_len, &err); if(U_FAILURE(err)) { @@ -301,7 +307,7 @@ static int icu_stringprep_prep(lua_State *L, const UStringPrepProfile *profile) return 1; } - prepped_len = usprep_prepare(profile, unprepped, unprepped_len, prepped, 1024, USPREP_ALLOW_UNASSIGNED, NULL, &err); + prepped_len = usprep_prepare(profile, unprepped, unprepped_len, prepped, 1024, flags, NULL, &err); if(U_FAILURE(err)) { lua_pushnil(L); @@ -397,6 +403,7 @@ static int stringprep_prep(lua_State *L, const Stringprep_profile *profile) { const char *s; char string[1024]; int ret; + Stringprep_profile_flags flags = 0; if(!lua_isstring(L, 1)) { lua_pushnil(L); @@ -405,13 +412,18 @@ static int stringprep_prep(lua_State *L, const Stringprep_profile *profile) { s = check_utf8(L, 1, &len); + /* strict */ + if(lua_toboolean(L, 2)) { + flags = STRINGPREP_NO_UNASSIGNED; + } + if(s == NULL || len >= 1024 || len != strlen(s)) { lua_pushnil(L); return 1; /* TODO return error message */ } strcpy(string, s); - ret = stringprep(string, 1024, (Stringprep_profile_flags)0, profile); + ret = stringprep(string, 1024, flags, profile); if(ret == STRINGPREP_OK) { lua_pushstring(L, string); -- cgit v1.2.3 From 31d6d1a42b9928648961f418dedfa778e60bc0ba Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 9 Sep 2019 22:15:04 +0200 Subject: util.jid: Add a 'strict' flag for jidprep calls --- util/jid.lua | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/util/jid.lua b/util/jid.lua index ec31f180..1ddf33d4 100644 --- a/util/jid.lua +++ b/util/jid.lua @@ -45,20 +45,20 @@ local function bare(jid) return host; end -local function prepped_split(jid) +local function prepped_split(jid, strict) local node, host, resource = split(jid); if host and host ~= "." then if sub(host, -1, -1) == "." then -- Strip empty root label host = sub(host, 1, -2); end - host = nameprep(host); + host = nameprep(host, strict); if not host then return; end if node then - node = nodeprep(node); + node = nodeprep(node, strict); if not node then return; end end if resource then - resource = resourceprep(resource); + resource = resourceprep(resource, strict); if not resource then return; end end return node, host, resource; @@ -77,8 +77,8 @@ local function join(node, host, resource) return host; end -local function prep(jid) - local node, host, resource = prepped_split(jid); +local function prep(jid, strict) + local node, host, resource = prepped_split(jid, strict); return join(node, host, resource); end -- cgit v1.2.3 From 42aeda373c62b60224608b188b4a776afe534511 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 30 Oct 2019 16:22:44 +0100 Subject: util.encodings: Strictly verify that the 'strict' *prep argument is a boolean This is to prevent mistakes like nodeprep(username:gsub("a","b")) from unintentionally invoking strict mode. --- util-src/encodings.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/util-src/encodings.c b/util-src/encodings.c index be34032e..4fe83c64 100644 --- a/util-src/encodings.c +++ b/util-src/encodings.c @@ -296,8 +296,11 @@ static int icu_stringprep_prep(lua_State *L, const UStringPrepProfile *profile) } /* strict */ - if(lua_toboolean(L, 2)) { - flags = 0; + if(!lua_isnoneornil(L, 2)) { + luaL_checktype(L, 2, LUA_TBOOLEAN); + if(lua_toboolean(L, 2)) { + flags = 0; + } } u_strFromUTF8(unprepped, 1024, &unprepped_len, input, input_len, &err); @@ -413,8 +416,11 @@ static int stringprep_prep(lua_State *L, const Stringprep_profile *profile) { s = check_utf8(L, 1, &len); /* strict */ - if(lua_toboolean(L, 2)) { - flags = STRINGPREP_NO_UNASSIGNED; + if(!lua_isnoneornil(L, 2)) { + luaL_checktype(L, 2, LUA_TBOOLEAN); + if(lua_toboolean(L, 2)) { + flags = STRINGPREP_NO_UNASSIGNED; + } } if(s == NULL || len >= 1024 || len != strlen(s)) { -- cgit v1.2.3 From c09d6decbebbaec23defaed760dcfc63a3c9ec32 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 9 Sep 2019 22:32:01 +0200 Subject: core.stanza_router: Do strict jidprep on c2s Be conservative in what you let your clients send, be liberal in what you let in via s2s. Being strict on s2s leads to interop problems and poor experiences, ie users being ejected from MUCs if something invalid enters. By starting with tightening up input into the network, we may be able to gradually approach a point where no invalid JIDs are allowed. --- core/stanza_router.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/stanza_router.lua b/core/stanza_router.lua index a74f3b6f..9d3ab113 100644 --- a/core/stanza_router.lua +++ b/core/stanza_router.lua @@ -83,7 +83,7 @@ function core_process_stanza(origin, stanza) if full_sessions[to] or bare_sessions[to] or hosts[to] then node, host = jid_split(to); -- TODO only the host is needed, optimize else - node, host, resource = jid_prepped_split(to); + node, host, resource = jid_prepped_split(to, origin.type == "c2s"); if not host then log("warn", "Received stanza with invalid destination JID: %s", to); if stanza.attr.type ~= "error" and stanza.attr.type ~= "result" then -- cgit v1.2.3 From 2834c8aaff68fc71a6ccba394c1c3ccbe06846a1 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 23 Sep 2019 19:13:14 +0200 Subject: MUC: Enforce strict resourceprep on nicknames (bye bye robot face) --- plugins/muc/muc.lib.lua | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 200f69f9..3a228aae 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -444,6 +444,22 @@ module:hook("muc-occupant-pre-change", function(event) end end, 1); +module:hook("muc-occupant-pre-join", function(event) + local nick = jid_resource(event.occupant.nick); + if not resourceprep(nick, true) then -- strict + event.origin.send(st.error_reply(event.stanza, "modify", "jid-malformed", "Nickname must pass strict validation")); + return true; + end +end, 2); + +module:hook("muc-occupant-pre-change", function(event) + local nick = jid_resource(event.dest_occupant.nick); + if not resourceprep(nick, true) then -- strict + event.origin.send(st.error_reply(event.stanza, "modify", "jid-malformed", "Nickname must pass strict validation")); + return true; + end +end, 2); + function room_mt:handle_first_presence(origin, stanza) local real_jid = stanza.attr.from; local dest_jid = stanza.attr.to; -- cgit v1.2.3 From cf05074f0e625bd001fb79c44efd787c0d8f333f Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 30 Oct 2019 17:33:52 +0100 Subject: Backed out changeset 64ddcbc9a328 as it would prevent communicating with valid remote JIDs that aren't valid under STRINGPREP / Unicode 3.2 --- core/stanza_router.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/stanza_router.lua b/core/stanza_router.lua index 9d3ab113..a74f3b6f 100644 --- a/core/stanza_router.lua +++ b/core/stanza_router.lua @@ -83,7 +83,7 @@ function core_process_stanza(origin, stanza) if full_sessions[to] or bare_sessions[to] or hosts[to] then node, host = jid_split(to); -- TODO only the host is needed, optimize else - node, host, resource = jid_prepped_split(to, origin.type == "c2s"); + node, host, resource = jid_prepped_split(to); if not host then log("warn", "Received stanza with invalid destination JID: %s", to); if stanza.attr.type ~= "error" and stanza.attr.type ~= "result" then -- cgit v1.2.3 From 1d400b6d80c689a3d6fcfcde0d588e18941f00d2 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 1 Nov 2019 18:09:13 +0100 Subject: mod_register_ibr: Allow registartion rejection reason as util.error object --- plugins/mod_register_ibr.lua | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/plugins/mod_register_ibr.lua b/plugins/mod_register_ibr.lua index 2f220658..32e6f710 100644 --- a/plugins/mod_register_ibr.lua +++ b/plugins/mod_register_ibr.lua @@ -168,8 +168,15 @@ module:hook("stanza/iq/jabber:iq:register:query", function(event) module:fire_event("user-registering", user); if not user.allowed then log("debug", "Registration disallowed by module: %s", user.reason or "no reason given"); - -- TODO This could use util.error - session.send(st.error_reply(stanza, user.error_type or "modify", user.error_condition or "not-acceptable", user.reason)); + local error_type, error_condition, reason; + local err = user.error; + if err then + error_type, error_condition, reason = err.type, err.condition, err.text; + else + -- COMPAT pre-util.error + error_type, error_condition, reason = user.error_type, user.error_condition, user.reason; + end + session.send(st.error_reply(stanza, error_type or "modify", error_condition or "not-acceptable", reason)); return true; end -- cgit v1.2.3 From 9f13e20ffef42d61a3f72a3b11a68125557667c3 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 1 Nov 2019 18:11:58 +0100 Subject: mod_register_limits: Use util.error for managing rejection reasons --- plugins/mod_register_limits.lua | 37 ++++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/plugins/mod_register_limits.lua b/plugins/mod_register_limits.lua index 55811d74..fc9bf27a 100644 --- a/plugins/mod_register_limits.lua +++ b/plugins/mod_register_limits.lua @@ -13,6 +13,7 @@ local ip_util = require "util.ip"; local new_ip = ip_util.new_ip; local match_ip = ip_util.match; local parse_cidr = ip_util.parse_cidr; +local errors = require "util.error"; local min_seconds_between_registrations = module:get_option_number("min_seconds_between_registrations"); local whitelist_only = module:get_option_boolean("whitelist_registration_only"); @@ -54,6 +55,24 @@ local function ip_in_set(set, ip) return false; end +local err_registry = { + blacklisted = { + text = "Your IP address is blacklisted"; + type = "auth"; + condition = "forbidden"; + }; + not_whitelisted = { + text = "Your IP address is not whitelisted"; + type = "auth"; + condition = "forbidden"; + }; + throttled = { + reason = "Too many registrations from this IP address recently"; + type = "wait"; + condition = "policy-violation"; + }; +} + module:hook("user-registering", function (event) local session = event.session; local ip = event.ip or session and session.ip; @@ -63,22 +82,22 @@ module:hook("user-registering", function (event) elseif ip_in_set(blacklisted_ips, ip) then log("debug", "Registration disallowed by blacklist"); event.allowed = false; - event.reason = "Your IP address is blacklisted"; - event.error_type = "auth"; - event.error_condition = "forbidden"; + event.error = errors.new("blacklisted", err_registry, event); elseif (whitelist_only and not ip_in_set(whitelisted_ips, ip)) then log("debug", "Registration disallowed by whitelist"); event.allowed = false; - event.reason = "Your IP address is not whitelisted"; - event.error_type = "auth"; - event.error_condition = "forbidden"; + event.error = errors.new("not_whitelisted", err_registry, event); elseif throttle_max and not ip_in_set(whitelisted_ips, ip) then if not check_throttle(ip) then log("debug", "Registrations over limit for ip %s", ip or "?"); event.allowed = false; - event.reason = "Too many registrations from this IP address recently"; - event.error_type = "wait"; - event.error_condition = "policy-violation"; + event.error = errors.new("throttle", err_registry, event); end end + if event.error then + -- COMPAT pre-util.error + event.reason = event.error.text; + event.error_type = event.error.type; + event.error_condition = event.error.condition; + end end); -- cgit v1.2.3 From 03adb505558555cd2449bea0ae83c0a89c82a399 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 1 Nov 2019 18:31:12 +0100 Subject: util.error: Add well-known field 'code' in error templates Intended to be for HTTP-ish numeric status codes --- spec/util_error_spec.lua | 2 ++ util/error.lua | 1 + 2 files changed, 3 insertions(+) diff --git a/spec/util_error_spec.lua b/spec/util_error_spec.lua index d8534c2b..ca053285 100644 --- a/spec/util_error_spec.lua +++ b/spec/util_error_spec.lua @@ -16,11 +16,13 @@ describe("util.error", function () ["fail"] = { type = "wait", condition = "internal-server-error", + code = 555; }; }; local err = errors.new("fail", { traceback = "in some file, somewhere" }, templates); assert.equal("wait", err.type); assert.equal("internal-server-error", err.condition); + assert.equal(555, err.code); assert.same({ traceback = "in some file, somewhere" }, err.context); end); end); diff --git a/util/error.lua b/util/error.lua index 23551fe2..9ebfa6ab 100644 --- a/util/error.lua +++ b/util/error.lua @@ -14,6 +14,7 @@ local function new(e, context, registry) type = template.type or "cancel"; condition = template.condition or "undefined-condition"; text = template.text; + code = template.code or 500; context = context or template.context or { _error_id = e }; }, error_mt); -- cgit v1.2.3 From 8de5e91be6f1c6e5fb1009d4a90dfea73b17cb48 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 1 Nov 2019 22:08:38 +0100 Subject: MUC: Strictly validate room JID on creation This should prevent any MUCs with invalid JID (according to current normalization routine) --- plugins/muc/mod_muc.lua | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index e55bd6a2..166249cc 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -93,6 +93,7 @@ room_mt.get_valid_broadcast_roles = presence_broadcast.get_valid_broadcast_roles local jid_split = require "util.jid".split; +local jid_prep = require "util.jid".prep; local jid_bare = require "util.jid".bare; local st = require "util.stanza"; local cache = require "util.cache"; @@ -273,6 +274,9 @@ local function set_room_defaults(room, lang) end function create_room(room_jid, config) + if jid_bare(room_jid) ~= room_jid or not jid_prep(room_jid, true) then + return nil, "invalid-jid"; + end local exists = get_room_from_jid(room_jid); if exists then return nil, "room-exists"; @@ -460,6 +464,10 @@ for event_name, method in pairs { if room == nil then -- Watch presence to create rooms + if not jid_prep(room_jid, true) then + origin.send(st.error_reply(stanza, "modify", "jid-malformed")); + return true; + end if stanza.attr.type == nil and stanza.name == "presence" and stanza:get_child("x", "http://jabber.org/protocol/muc") then room = muclib.new_room(room_jid); return room:handle_first_presence(origin, stanza); -- cgit v1.2.3 From 45936787cb44aaef09f568cc2d74c4de6113579b Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 1 Nov 2019 22:53:14 +0100 Subject: util.prosodyctl: Enforce strict JID validation on user creation This is where 64ddcbc9a328 should have started. By preventing creation of users with invalid JIDs, it will slowly become safer to enforce strict validation on everything. --- util/prosodyctl.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/prosodyctl.lua b/util/prosodyctl.lua index 163658f3..586802d3 100644 --- a/util/prosodyctl.lua +++ b/util/prosodyctl.lua @@ -134,7 +134,7 @@ end -- Server control local function adduser(params) - local user, host, password = nodeprep(params.user), nameprep(params.host), params.password; + local user, host, password = nodeprep(params.user, true), nameprep(params.host), params.password; if not user then return false, "invalid-username"; elseif not host then -- cgit v1.2.3 From 64a2f6c1920e46288b323efcf9ce445fde61226a Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 1 Nov 2019 23:11:43 +0100 Subject: mod_register_ibr: Enforce strict JID validation --- plugins/mod_register_ibr.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_register_ibr.lua b/plugins/mod_register_ibr.lua index 32e6f710..6de9bc33 100644 --- a/plugins/mod_register_ibr.lua +++ b/plugins/mod_register_ibr.lua @@ -155,7 +155,7 @@ module:hook("stanza/iq/jabber:iq:register:query", function(event) return true; end - local username, password = nodeprep(data.username), data.password; + local username, password = nodeprep(data.username, true), data.password; data.username, data.password = nil, nil; local host = module.host; if not username or username == "" then -- cgit v1.2.3 From f6983a3d514a29fb8eab260f65373523450046cf Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 1 Nov 2019 23:16:58 +0100 Subject: prosodyctl: Print friendly version of error messages --- prosodyctl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prosodyctl b/prosodyctl index 82b5faaa..a4724a19 100755 --- a/prosodyctl +++ b/prosodyctl @@ -144,7 +144,7 @@ function commands.adduser(arg) if ok then return 0; end - show_message(msg) + show_message(error_messages[msg]) return 1; end -- cgit v1.2.3 From 2b035680860450dd6ce9d3b853c26bd900752f8f Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 1 Nov 2019 23:18:29 +0100 Subject: net.http.codes: Avoid implicit number -> string coercion --- net/http/codes.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/http/codes.lua b/net/http/codes.lua index 8098b5c3..4327f151 100644 --- a/net/http/codes.lua +++ b/net/http/codes.lua @@ -82,5 +82,5 @@ local response_codes = { -- [512-599] = "Unassigned"; }; -for k,v in pairs(response_codes) do response_codes[k] = k.." "..v; end +for k,v in pairs(response_codes) do response_codes[k] = ("%03d %s"):format(k, v); end return setmetatable(response_codes, { __index = function(_, k) return k.." Unassigned"; end }) -- cgit v1.2.3 From b679ffe808759514c5dc12151ea7ff28c17c43a1 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 2 Nov 2019 13:09:54 +0100 Subject: util.encodings: Don't ignore non-strings passed to stringprep functions If you manage to pass a table or something weird to these, you deserve to know. --- util-src/encodings.c | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/util-src/encodings.c b/util-src/encodings.c index 4fe83c64..367182b6 100644 --- a/util-src/encodings.c +++ b/util-src/encodings.c @@ -283,12 +283,7 @@ static int icu_stringprep_prep(lua_State *L, const UStringPrepProfile *profile) UErrorCode err = U_ZERO_ERROR; - if(!lua_isstring(L, 1)) { - lua_pushnil(L); - return 1; - } - - input = lua_tolstring(L, 1, &input_len); + input = luaL_checklstring(L, 1, &input_len); if(input_len >= 1024) { lua_pushnil(L); @@ -408,11 +403,6 @@ static int stringprep_prep(lua_State *L, const Stringprep_profile *profile) { int ret; Stringprep_profile_flags flags = 0; - if(!lua_isstring(L, 1)) { - lua_pushnil(L); - return 1; - } - s = check_utf8(L, 1, &len); /* strict */ -- cgit v1.2.3 From 522916d7c71c35d61d03ff25dc5d8e726f6bf9b3 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 2 Nov 2019 13:24:33 +0100 Subject: core.sessionmanager: Fix traceback from passing nil to resourceprep --- core/sessionmanager.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/sessionmanager.lua b/core/sessionmanager.lua index 29779c3c..a62db906 100644 --- a/core/sessionmanager.lua +++ b/core/sessionmanager.lua @@ -150,7 +150,7 @@ local function bind_resource(session, resource) resource = event_payload.resource; end - resource = resourceprep(resource); + resource = resourceprep(resource or ""); resource = resource ~= "" and resource or generate_identifier(); --FIXME: Randomly-generated resources must be unique per-user, and never conflict with existing -- cgit v1.2.3 From fb0fee842496d9cf89464479406352161f2888bd Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 2 Nov 2019 14:22:06 +0100 Subject: MUC: Make nickname field in registration form required Prevents traceback from resourceprep(nil) muc#register_roomnick is also required in XEP-0045 --- plugins/muc/register.lib.lua | 2 +- spec/scansion/muc_register.scs | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/plugins/muc/register.lib.lua b/plugins/muc/register.lib.lua index 4ae393c7..bf8cd877 100644 --- a/plugins/muc/register.lib.lua +++ b/plugins/muc/register.lib.lua @@ -53,7 +53,7 @@ end); local registration_form = dataforms.new { { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/muc#register" }, - { name = "muc#register_roomnick", type = "text-single", label = "Nickname"}, + { name = "muc#register_roomnick", type = "text-single", required = true, label = "Nickname"}, }; local function enforce_nick_policy(event) diff --git a/spec/scansion/muc_register.scs b/spec/scansion/muc_register.scs index e1eaf4e0..a077cd76 100644 --- a/spec/scansion/muc_register.scs +++ b/spec/scansion/muc_register.scs @@ -100,7 +100,9 @@ Juliet receives: http://jabber.org/protocol/muc#register - + + +
@@ -339,7 +341,9 @@ Romeo receives: http://jabber.org/protocol/muc#register - + + +
-- cgit v1.2.3 From 4b69a96d83465e0b04b4b07043f28516da0afc92 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 2 Nov 2019 13:55:38 +0100 Subject: core.configmanager: Ensure Hosts are given names Prevents traceback from nameprep(nil) --- core/configmanager.lua | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/core/configmanager.lua b/core/configmanager.lua index 090a6a0a..8ed74e3c 100644 --- a/core/configmanager.lua +++ b/core/configmanager.lua @@ -138,6 +138,9 @@ do rawset(env, "__currenthost", "*") -- Default is global function env.VirtualHost(name) + if not name then + error("Host must have a name", 2); + end name = nameprep(name); if rawget(config_table, name) and rawget(config_table[name], "component_module") then error(format("Host %q clashes with previously defined %s Component %q, for services use a sub-domain like conference.%s", @@ -156,6 +159,9 @@ do env.Host, env.host = env.VirtualHost, env.VirtualHost; function env.Component(name) + if not name then + error("Component must have a name", 2); + end name = nameprep(name); if rawget(config_table, name) and rawget(config_table[name], "defined") and not rawget(config_table[name], "component_module") then -- cgit v1.2.3 From f563584fca3f6dff5ee1c98c5679018645f10ac0 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 2 Nov 2019 13:56:13 +0100 Subject: core.configmanager: Handle nameprep validation errors --- core/configmanager.lua | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/core/configmanager.lua b/core/configmanager.lua index 8ed74e3c..ae0a274a 100644 --- a/core/configmanager.lua +++ b/core/configmanager.lua @@ -141,7 +141,11 @@ do if not name then error("Host must have a name", 2); end - name = nameprep(name); + local prepped_name = nameprep(name); + if not prepped_name then + error(format("Name of Host %q contains forbidden characters", name), 0); + end + name = prepped_name; if rawget(config_table, name) and rawget(config_table[name], "component_module") then error(format("Host %q clashes with previously defined %s Component %q, for services use a sub-domain like conference.%s", name, config_table[name].component_module:gsub("^%a+$", { component = "external", muc = "MUC"}), name, name), 0); @@ -162,7 +166,11 @@ do if not name then error("Component must have a name", 2); end - name = nameprep(name); + local prepped_name = nameprep(name); + if not prepped_name then + error(format("Name of Component %q contains forbidden characters", name), 0); + end + name = prepped_name; if rawget(config_table, name) and rawget(config_table[name], "defined") and not rawget(config_table[name], "component_module") then error(format("Component %q clashes with previously defined Host %q, for services use a sub-domain like conference.%s", -- cgit v1.2.3 From c66adb1b89d584c2ca07579c81e54043a3193a18 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 2 Nov 2019 15:27:53 +0100 Subject: mod_dialback: Abort early if request is missing addressing attributes Prevents traceback from passing nil to nameprep() --- plugins/mod_dialback.lua | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugins/mod_dialback.lua b/plugins/mod_dialback.lua index eddc3209..dc843498 100644 --- a/plugins/mod_dialback.lua +++ b/plugins/mod_dialback.lua @@ -93,6 +93,11 @@ module:hook("stanza/jabber:server:dialback:result", function(event) -- he wants to be identified through dialback -- We need to check the key with the Authoritative server local attr = stanza.attr; + if not attr.to or not attr.from then + origin.log("debug", "Missing Dialback addressing (from=%q, to=%q)", attr.from, attr.to); + origin:close("improper-addressing"); + return true; + end local to, from = nameprep(attr.to), nameprep(attr.from); if not hosts[to] then -- cgit v1.2.3 From 6164878eb1f3b5b72b0cc3803e6e6b5d7b3a9309 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 2 Nov 2019 15:29:13 +0100 Subject: mod_bosh: Abort early if request is missing hostname Prevents traceback from passing nil to nameprep() --- plugins/mod_bosh.lua | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/plugins/mod_bosh.lua b/plugins/mod_bosh.lua index f4d7eba2..b45a9dc2 100644 --- a/plugins/mod_bosh.lua +++ b/plugins/mod_bosh.lua @@ -272,6 +272,15 @@ function stream_callbacks.streamopened(context, attr) -- New session request context.notopen = nil; -- Signals that we accept this opening tag + if not attr.to then + log("debug", "BOSH client tried to connect without specifying a host"); + report_bad_host(); + local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", + ["xmlns:stream"] = xmlns_streams, condition = "improper-addressing" }); + response:send(tostring(close_reply)); + return; + end + local to_host = nameprep(attr.to); local wait = tonumber(attr.wait); if not to_host then -- cgit v1.2.3 From b1516ea8f0c1121b47eae70e9f18755298026a60 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 2 Nov 2019 15:39:01 +0100 Subject: core.sessionmanager: Require that client-requested resources pass strict resourceprep --- core/sessionmanager.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/sessionmanager.lua b/core/sessionmanager.lua index a62db906..6c005fcd 100644 --- a/core/sessionmanager.lua +++ b/core/sessionmanager.lua @@ -150,7 +150,7 @@ local function bind_resource(session, resource) resource = event_payload.resource; end - resource = resourceprep(resource or ""); + resource = resourceprep(resource or "", true); resource = resource ~= "" and resource or generate_identifier(); --FIXME: Randomly-generated resources must be unique per-user, and never conflict with existing -- cgit v1.2.3 From e131bbd3a4cc84f05d44be877ed2201031e2f810 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 2 Nov 2019 15:39:40 +0100 Subject: mod_c2s: Validate that a 'to' attribute exists at all Prevents traceback from nameprep(nil) --- plugins/mod_c2s.lua | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugins/mod_c2s.lua b/plugins/mod_c2s.lua index 02a0c5eb..aec0370d 100644 --- a/plugins/mod_c2s.lua +++ b/plugins/mod_c2s.lua @@ -56,6 +56,11 @@ local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'}; function stream_callbacks.streamopened(session, attr) local send = session.send; + if not attr.to then + session:close{ condition = "improper-addressing", + text = "A 'to' attribute is required on stream headers" }; + return; + end local host = nameprep(attr.to); if not host then session:close{ condition = "improper-addressing", -- cgit v1.2.3 From fd7ac7b72e6337637c7d02ab5858067357be542d Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 2 Nov 2019 15:40:20 +0100 Subject: mod_dialback: Fix potential traceback in case of missing addressing Not tested. Assuming nothing good comes from continuing the program flow after this. The connection should get closed and the event gets aborted by a traceback anyways. --- plugins/mod_dialback.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/mod_dialback.lua b/plugins/mod_dialback.lua index dc843498..f580d948 100644 --- a/plugins/mod_dialback.lua +++ b/plugins/mod_dialback.lua @@ -107,6 +107,7 @@ module:hook("stanza/jabber:server:dialback:result", function(event) return true; elseif not from then origin:close("improper-addressing"); + return true; end if dwd and origin.secure then -- cgit v1.2.3 From b24814cbe469216a4321635a48e3f9077d313f49 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 2 Nov 2019 15:43:17 +0100 Subject: mod_s2s: Only nameprep stream to/from addresses if they are present Prevents traceback from nameprep(nil) --- plugins/mod_s2s/mod_s2s.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/mod_s2s/mod_s2s.lua b/plugins/mod_s2s/mod_s2s.lua index b9c13ef2..42998c30 100644 --- a/plugins/mod_s2s/mod_s2s.lua +++ b/plugins/mod_s2s/mod_s2s.lua @@ -327,7 +327,9 @@ function stream_callbacks._streamopened(session, attr) -- Send a reply stream header -- Validate to/from - local to, from = nameprep(attr.to), nameprep(attr.from); + local to, from = attr.to, attr.from; + if to then to = nameprep(attr.to); end + if from then from = nameprep(attr.from); end if not to and attr.to then -- COMPAT: Some servers do not reliably set 'to' (especially on stream restarts) session:close({ condition = "improper-addressing", text = "Invalid 'to' address" }); return; -- cgit v1.2.3 From 1783288951625b5eee9f28d81bc31145530cf774 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 2 Nov 2019 15:43:56 +0100 Subject: mod_user_account_management: Apply username normalization later Prevents traceback from nodeprep(nil) --- plugins/mod_user_account_management.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/mod_user_account_management.lua b/plugins/mod_user_account_management.lua index 615c1ed6..130ed089 100644 --- a/plugins/mod_user_account_management.lua +++ b/plugins/mod_user_account_management.lua @@ -53,9 +53,10 @@ local function handle_registration_stanza(event) 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_text("username")); + local username = query:get_child_text("username"); local password = query:get_child_text("password"); if username and password then + username = nodeprep(username); if username == session.username then if usermanager_set_password(username, password, session.host, session.resource) then session.send(st.reply(stanza)); -- cgit v1.2.3 From 1a78e0a7ac93b3d0b4251a865eb994bcd3e5eaba Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 2 Nov 2019 16:02:37 +0100 Subject: mod_admin_telnet: Show s2s authentication method (probably) used --- plugins/mod_admin_telnet.lua | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index afb4fb4b..2bbd367b 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -538,6 +538,12 @@ local function session_flags(session, line) if session.cert_identity_status == "valid" then line[#line+1] = "(authenticated)"; end + if session.dialback_key then + line[#line+1] = "(dialback)"; + end + if session.external_auth then + line[#line+1] = "(SASL)"; + end if session.secure then line[#line+1] = "(encrypted)"; end -- cgit v1.2.3 From 3cbece6dc346c22f5cd9f07a0921a84daec0cdee Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 3 Nov 2019 17:43:14 +0100 Subject: doap: Reorder tags for consistency It's nice when `grep version.1 -B1` shows XEPs with 1.x versions --- doc/doap.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/doap.xml b/doc/doap.xml index d97d39d7..935b5eb4 100644 --- a/doc/doap.xml +++ b/doc/doap.xml @@ -57,8 +57,8 @@ - complete 2.9 + complete -- cgit v1.2.3 From ac663e1b7846213022f9d142174edbf7b3167d09 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 3 Nov 2019 17:44:14 +0100 Subject: doap: Add version of XEP-0012 --- doc/doap.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/doap.xml b/doc/doap.xml index 935b5eb4..d1d4ad8b 100644 --- a/doc/doap.xml +++ b/doc/doap.xml @@ -71,6 +71,7 @@ + 2.0 0.1 mod_lastactivity and mod_uptime complete -- cgit v1.2.3 From 6a73014b9acdf69a7c415ac8f5fa173f83a68362 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 1 Nov 2019 22:25:54 +0100 Subject: net.http.server: Factor out handling of event response for easier reuse --- net/http/server.lua | 65 ++++++++++++++++++++++++++++------------------------- 1 file changed, 34 insertions(+), 31 deletions(-) diff --git a/net/http/server.lua b/net/http/server.lua index bf24c97e..991ef970 100644 --- a/net/http/server.lua +++ b/net/http/server.lua @@ -170,6 +170,38 @@ local headerfix = setmetatable({}, { end }); +local function handle_result(request, response, result) + if result == nil then + result = 404; + end + + if result == true then + return; + end + + local body; + local result_type = type(result); + if result_type == "number" then + response.status_code = result; + if result >= 400 then + body = events.fire_event("http-error", { request = request, response = response, code = result }); + end + elseif result_type == "string" then + body = result; + elseif result_type == "table" then + for k, v in pairs(result) do + if k ~= "headers" then + response[k] = v; + else + for header_name, header_value in pairs(v) do + response.headers[header_name] = header_value; + end + end + end + end + response:send(body); +end + function _M.hijack_response(response, listener) -- luacheck: ignore error("TODO"); end @@ -261,39 +293,10 @@ function handle_request(conn, request, finish_cb) result = events.fire_event(host_head_event, payload); end end - if result ~= nil then - if result ~= true then - local body; - local result_type = type(result); - if result_type == "number" then - response.status_code = result; - if result >= 400 then - payload.code = result; - body = events.fire_event("http-error", payload); - end - elseif result_type == "string" then - body = result; - elseif result_type == "table" then - for k, v in pairs(result) do - if k ~= "headers" then - response[k] = v; - else - for header_name, header_value in pairs(v) do - response.headers[header_name] = header_value; - end - end - end - end - response:send(body); - end - return; - end - -- if handler not called, return 404 - response.status_code = 404; - payload.code = 404; - response:send(events.fire_event("http-error", payload)); + return handle_result(request, response, result); end + local function prepare_header(response) local status_line = "HTTP/"..response.request.httpversion.." "..(response.status or codes[response.status_code]); local headers = response.headers; -- cgit v1.2.3 From fbab8ed06ac738e6aec96db67141f987b9d3c058 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 1 Nov 2019 22:28:39 +0100 Subject: net.http.server: Tail call because tail call! --- net/http/server.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/http/server.lua b/net/http/server.lua index 991ef970..0682d6ed 100644 --- a/net/http/server.lua +++ b/net/http/server.lua @@ -199,7 +199,7 @@ local function handle_result(request, response, result) end end end - response:send(body); + return response:send(body); end function _M.hijack_response(response, listener) -- luacheck: ignore -- cgit v1.2.3 From fdfe1e0b2bf8cea4e2e512fa8e25bface008a59b Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 1 Nov 2019 22:30:35 +0100 Subject: net.http.server: Handle util.error objects from http handlers --- net/http/server.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/http/server.lua b/net/http/server.lua index 0682d6ed..0d49dbce 100644 --- a/net/http/server.lua +++ b/net/http/server.lua @@ -13,6 +13,7 @@ local traceback = debug.traceback; local tostring = tostring; local cache = require "util.cache"; local codes = require "net.http.codes"; +local errors = require "util.error"; local blocksize = 2^16; local _M = {}; @@ -188,6 +189,8 @@ local function handle_result(request, response, result) end elseif result_type == "string" then body = result; + elseif errors.is_err(result) then + body = events.fire_event("http-error", { request = request, response = response, code = result.code, error = result }); elseif result_type == "table" then for k, v in pairs(result) do if k ~= "headers" then -- cgit v1.2.3 From 5cac177270b40336e9820619e9c6ab3973ad95b4 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 1 Nov 2019 22:31:15 +0100 Subject: net.http.server: Handle promises from http handlers --- net/http/server.lua | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/net/http/server.lua b/net/http/server.lua index 0d49dbce..dc64728f 100644 --- a/net/http/server.lua +++ b/net/http/server.lua @@ -13,6 +13,7 @@ local traceback = debug.traceback; local tostring = tostring; local cache = require "util.cache"; local codes = require "net.http.codes"; +local promise = require "util.promise"; local errors = require "util.error"; local blocksize = 2^16; @@ -191,6 +192,13 @@ local function handle_result(request, response, result) body = result; elseif errors.is_err(result) then body = events.fire_event("http-error", { request = request, response = response, code = result.code, error = result }); + elseif promise.is_promise(result) then + result:next(function (ret) + handle_result(request, response, ret); + end, function (err) + handle_result(request, response, err); + end); + return true; elseif result_type == "table" then for k, v in pairs(result) do if k ~= "headers" then -- cgit v1.2.3 From 58990598f283f24f99dd5899669acc310b5437b1 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 5 Nov 2019 01:34:13 +0100 Subject: net.http.server: Treat promise rejection without value as a HTTP 500 error --- net/http/server.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/http/server.lua b/net/http/server.lua index dc64728f..47f064a5 100644 --- a/net/http/server.lua +++ b/net/http/server.lua @@ -196,7 +196,7 @@ local function handle_result(request, response, result) result:next(function (ret) handle_result(request, response, ret); end, function (err) - handle_result(request, response, err); + handle_result(request, response, err or 500); end); return true; elseif result_type == "table" then -- cgit v1.2.3 From a9980f81fadf325ed8f609ae3787c061dba63f94 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 7 Nov 2019 00:20:54 +0100 Subject: util.array: Fix typo in test --- spec/util_array_spec.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/util_array_spec.lua b/spec/util_array_spec.lua index acd2e882..04d0cc28 100644 --- a/spec/util_array_spec.lua +++ b/spec/util_array_spec.lua @@ -1,7 +1,7 @@ local array = require "util.array"; describe("util.array", function () describe("creation", function () - describe("from tablle", function () + describe("from table", function () it("works", function () local a = array({"a", "b", "c"}); assert.same({"a", "b", "c"}, a); -- cgit v1.2.3 From 5164f0f45c0a4e68aea9887ee13e2900b19cdf57 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 7 Nov 2019 17:07:02 +0100 Subject: mod_admin_adhoc: Add some flags to s2s listing command These are present in mod_admin_telnet and relevant to s2s --- plugins/mod_admin_adhoc.lua | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/plugins/mod_admin_adhoc.lua b/plugins/mod_admin_adhoc.lua index 37e77ab0..4fea5a73 100644 --- a/plugins/mod_admin_adhoc.lua +++ b/plugins/mod_admin_adhoc.lua @@ -392,6 +392,12 @@ local function session_flags(session, line) if session.cert_identity_status == "valid" then flags[#flags+1] = "authenticated"; end + if session.dialback_key then + flags[#flags+1] = "dialback"; + end + if session.external_auth then + flags[#flags+1] = "SASL"; + end if session.secure then flags[#flags+1] = "encrypted"; end @@ -404,6 +410,12 @@ local function session_flags(session, line) if session.ip and session.ip:match(":") then flags[#flags+1] = "IPv6"; end + if session.incoming and session.outgoing then + flags[#flags+1] = "bidi"; + elseif session.is_bidi or session.bidi_session then + flags[#flags+1] = "bidi"; + end + line[#line+1] = "("..t_concat(flags, ", ")..")"; return t_concat(line, " "); -- cgit v1.2.3 From c63beda273f7180dcf4cff13db3921840818d2c6 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 7 Nov 2019 19:23:42 +0100 Subject: mod_ping: Remove ad-hoc command 17:27:40 Zash: the Ping thing is absolutely worthless 17:27:55 The command provided by mod_ping? 17:27:59 To own server? 17:28:14 the Ping command in mod_admin_web, whatever it maps to 17:28:29 > Pong > 2019-11-07T16:28:16Z What am I supposed to do with that result? 17:28:29 Yeah, mod_ping provides that 17:28:41 Is it a ping to my own server? Where's the RTT? 17:28:48 Dunno if it's useful for more than verifying that the adhoc command system works 17:29:02 (it lags, but there is no indication of how much) 17:29:14 It can't really test that itself 17:29:52 Anyone opposed to deleting it? 17:30:42 Half the module 17:42:47 Zash, I'm fine with removing it --- plugins/mod_ping.lua | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/plugins/mod_ping.lua b/plugins/mod_ping.lua index 5fff58d1..df24c495 100644 --- a/plugins/mod_ping.lua +++ b/plugins/mod_ping.lua @@ -16,18 +16,3 @@ end module:hook("iq-get/bare/urn:xmpp:ping:ping", ping_handler); module:hook("iq-get/host/urn:xmpp:ping:ping", ping_handler); - --- Ad-hoc command - -local datetime = require "util.datetime".datetime; - -function ping_command_handler (self, data, state) -- luacheck: ignore 212 - local now = datetime(); - return { info = "Pong\n"..now, status = "completed" }; -end - -module:depends "adhoc"; -local adhoc_new = module:require "adhoc".new; -local descriptor = adhoc_new("Ping", "ping", ping_command_handler); -module:provides("adhoc", descriptor); - -- cgit v1.2.3 From c900a5c54439d358628540fbb697d41303b28d10 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 8 Nov 2019 19:25:57 +0100 Subject: util.paths: Don't treat path as pattern, fix traceback (thanks Menel87) --- util/paths.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/util/paths.lua b/util/paths.lua index c225108a..036f315a 100644 --- a/util/paths.lua +++ b/util/paths.lua @@ -48,11 +48,11 @@ function path_util.complement_lua_path(installer_plugin_path) local lua_path_sep = package.config:sub(3,3); local dir_sep = package.config:sub(1,1); local sub_path = dir_sep.."lua"..dir_sep..lua_version..dir_sep; - if not string.match(package.path, installer_plugin_path) then + if not string.find(package.path, installer_plugin_path, 1, true) then package.path = package.path..lua_path_sep..installer_plugin_path..dir_sep.."share"..sub_path.."?.lua"; package.path = package.path..lua_path_sep..installer_plugin_path..dir_sep.."share"..sub_path.."?"..dir_sep.."init.lua"; end - if not string.match(package.path, installer_plugin_path) then + if not string.find(package.path, installer_plugin_path, 1, true) then package.cpath = package.cpath..lua_path_sep..installer_plugin_path..dir_sep.."lib"..sub_path.."?.so"; end end -- cgit v1.2.3 From 6666a4c8147fd5ba7e58fdc0fef64e8c0ff83be3 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 8 Nov 2019 23:03:47 +0100 Subject: mod_s2s: Allow passing bounce reason as an util.error object (see #770) This argument is currently unused in s2smanager. --- plugins/mod_s2s/mod_s2s.lua | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/plugins/mod_s2s/mod_s2s.lua b/plugins/mod_s2s/mod_s2s.lua index 42998c30..9db13cb7 100644 --- a/plugins/mod_s2s/mod_s2s.lua +++ b/plugins/mod_s2s/mod_s2s.lua @@ -29,6 +29,7 @@ local fire_global_event = prosody.events.fire_event; local runner = require "util.async".runner; local connect = require "net.connect".connect; local service = require "net.resolvers.service"; +local errors = require "util.error"; local connect_timeout = module:get_option_number("s2s_timeout", 90); local stream_close_timeout = module:get_option_number("s2s_close_timeout", 5); @@ -83,18 +84,24 @@ local function bounce_sendq(session, reason) -- TODO use util.error ? local error_type = "cancel"; local condition = "remote-server-not-found"; + local reason_text; if session.had_stream then -- set when a stream is opened by the remote error_type, condition = "wait", "remote-server-timeout"; end + if errors.is_err(reason) then + error_type, condition, reason_text = reason.type, reason.condition, reason.text; + elseif type(reason) == "string" then + reason_text = reason; + 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 = error_type, by = session.from_host}) :tag(condition, {xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas"}):up(); - if reason then + if reason_text then reply:tag("text", {xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas"}) - :text("Server-to-server connection failed: "..reason):up(); + :text("Server-to-server connection failed: "..reason_text):up(); end core_process_stanza(dummy, reply); end -- cgit v1.2.3 From 829aaea2fd758250b0978c3dd04e1f310023b0ed Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 9 Nov 2019 00:23:08 +0100 Subject: util.startup: Split plugin installer path setup into a separate function --- util/startup.lua | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/util/startup.lua b/util/startup.lua index 62fd5579..c9d06109 100644 --- a/util/startup.lua +++ b/util/startup.lua @@ -228,17 +228,22 @@ end function startup.setup_plugindir() local custom_plugin_paths = config.get("*", "plugin_paths"); - local installer_plugin_path = config.get("*", "installer_plugin_path") or "custom_plugins"; local path_sep = package.config:sub(3,3); - installer_plugin_path = config.resolve_relative_path(require "lfs".currentdir(), installer_plugin_path); - require "lfs".mkdir(installer_plugin_path); - require"util.paths".complement_lua_path(installer_plugin_path); if custom_plugin_paths then -- path1;path2;path3;defaultpath... -- luacheck: ignore 111 CFG_PLUGINDIR = table.concat(custom_plugin_paths, path_sep)..path_sep..(CFG_PLUGINDIR or "plugins"); prosody.paths.plugins = CFG_PLUGINDIR; end +end + +function startup.setup_plugin_install_path() + local installer_plugin_path = config.get("*", "installer_plugin_path") or "custom_plugins"; + local path_sep = package.config:sub(3,3); + installer_plugin_path = config.resolve_relative_path(require "lfs".currentdir(), installer_plugin_path); + require "lfs".mkdir(installer_plugin_path); + require"util.paths".complement_lua_path(installer_plugin_path); + -- luacheck: ignore 111 CFG_PLUGINDIR = installer_plugin_path..path_sep..(CFG_PLUGINDIR or "plugins"); prosody.paths.plugins = CFG_PLUGINDIR; end @@ -534,6 +539,7 @@ function startup.prosodyctl() startup.force_console_logging(); startup.init_logging(); startup.setup_plugindir(); + startup.setup_plugin_install_path(); startup.setup_datadir(); startup.chdir(); startup.read_version(); @@ -559,6 +565,7 @@ function startup.prosody() startup.init_logging(); startup.load_libraries(); startup.setup_plugindir(); + startup.setup_plugin_install_path(); startup.setup_datadir(); startup.chdir(); startup.add_global_prosody_functions(); -- cgit v1.2.3 From f10cf81e372393d6b74a837e818f238a7249d66c Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 9 Nov 2019 00:26:56 +0100 Subject: util.startup: Disable plugin installer path creation for now (see comments) --- util/startup.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/util/startup.lua b/util/startup.lua index c9d06109..8e6d89e6 100644 --- a/util/startup.lua +++ b/util/startup.lua @@ -240,7 +240,9 @@ end function startup.setup_plugin_install_path() local installer_plugin_path = config.get("*", "installer_plugin_path") or "custom_plugins"; local path_sep = package.config:sub(3,3); + -- TODO Figure out what this should be relative to, because CWD could be anywhere installer_plugin_path = config.resolve_relative_path(require "lfs".currentdir(), installer_plugin_path); + -- TODO Can probably move directory creation to the install command require "lfs".mkdir(installer_plugin_path); require"util.paths".complement_lua_path(installer_plugin_path); -- luacheck: ignore 111 @@ -539,7 +541,7 @@ function startup.prosodyctl() startup.force_console_logging(); startup.init_logging(); startup.setup_plugindir(); - startup.setup_plugin_install_path(); + -- startup.setup_plugin_install_path(); startup.setup_datadir(); startup.chdir(); startup.read_version(); @@ -565,7 +567,7 @@ function startup.prosody() startup.init_logging(); startup.load_libraries(); startup.setup_plugindir(); - startup.setup_plugin_install_path(); + -- startup.setup_plugin_install_path(); startup.setup_datadir(); startup.chdir(); startup.add_global_prosody_functions(); -- cgit v1.2.3 From 2848e38b7e8a03b26b0aaab6e363403a19298abd Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 9 Nov 2019 13:58:25 +0100 Subject: util.dependencies: Avoid missing bitop false positive on Lua 5.4 --- util/dependencies.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/dependencies.lua b/util/dependencies.lua index 84e2dd5c..22b66d7c 100644 --- a/util/dependencies.lua +++ b/util/dependencies.lua @@ -90,7 +90,7 @@ local function check_dependencies() }, "SSL/TLS support will not be available"); end - local bit = _G.bit32 or softreq"bit"; + local bit = softreq"util.bitcompat"; if not bit then missingdep("lua-bitops", { -- cgit v1.2.3 From ecb6218ce7e485970e1bc56f9464df13e739f878 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 13 Nov 2019 22:34:25 +0100 Subject: server_event: Remove duplicated code (thanks waqas) readcallback() calls onreadtimeout() and runs the exact same code if onreadtimeout() doesn't return true, which it doesn't do. --- net/server_event.lua | 4 ---- 1 file changed, 4 deletions(-) diff --git a/net/server_event.lua b/net/server_event.lua index 2b791291..a8279c42 100644 --- a/net/server_event.lua +++ b/net/server_event.lua @@ -463,10 +463,6 @@ 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 -- cgit v1.2.3 From 5af4eb8be2082c6b22f66037bb54d539f78ba942 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 15 Nov 2019 16:49:31 +0100 Subject: doap: Sort XEPs by number --- doc/doap.xml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/doc/doap.xml b/doc/doap.xml index d1d4ad8b..673e0c23 100644 --- a/doc/doap.xml +++ b/doc/doap.xml @@ -228,22 +228,22 @@ - - 0.2 + + 1.0.2 + 0.11 + partial - - 0.10 + + 0.2 - - 1.0.2 - 0.11 - partial + + 0.10 -- cgit v1.2.3 From fd9ccf20d5b652dbad1f37cecd540661f4642ee6 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 16 Nov 2019 16:39:45 +0100 Subject: mod_http: Soften dependency on mod_http_errors This allows disabling mod_http_errors by adding it to moduless_disabled and ensures mod_http loads even if the error pages aren't as pretty. --- plugins/mod_http.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/mod_http.lua b/plugins/mod_http.lua index 8ef06dc2..081aa7e9 100644 --- a/plugins/mod_http.lua +++ b/plugins/mod_http.lua @@ -7,7 +7,9 @@ -- module:set_global(); -module:depends("http_errors"); +pcall(function () + module:depends("http_errors"); +end); local portmanager = require "core.portmanager"; local moduleapi = require "core.moduleapi"; -- cgit v1.2.3 From e0e0b24f003bcebc564fcebeb745717a4e08b5b7 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 17 Nov 2019 15:06:22 +0100 Subject: util.interpolation: Test template filters --- spec/util_interpolation_spec.lua | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/spec/util_interpolation_spec.lua b/spec/util_interpolation_spec.lua index eb49e53b..21d1abe0 100644 --- a/spec/util_interpolation_spec.lua +++ b/spec/util_interpolation_spec.lua @@ -17,6 +17,15 @@ local expect_array = [[ 1. HELLO 2. WORLD ]] +local template_func_pipe = [[ +{foo|sort#{idx}. {item} +}]] +local expect_func_pipe = [[ +1. A +2. B +3. C +4. D +]] local template_map = [[ {foo%{idx}: {item!} }]] @@ -26,11 +35,12 @@ FOO: bar describe("util.interpolation", function () it("renders", function () - local render = require "util.interpolation".new("%b{}", string.upper); + local render = require "util.interpolation".new("%b{}", string.upper, { sort = function (t) table.sort(t) return t end }); assert.equal(expect1, render(template, { greet = "Hello", name = "world" })); assert.equal(expect2, render(template, { greet = "Hello" })); assert.equal(expect3, render(template, { name = "you" })); assert.equal(expect_array, render(template_array, { foo = { "Hello", "World" } })); + assert.equal(expect_func_pipe, render(template_func_pipe, { foo = { "c", "a", "d", "b", } })); assert.equal(expect_map, render(template_map, { foo = { foo = "bar" } })); end); end); -- cgit v1.2.3 From a7579e93f5833154aef8d67f068064d71b5dc731 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 17 Nov 2019 15:32:28 +0100 Subject: util.interpolation: Add commented test case for passing nil to filter --- spec/util_interpolation_spec.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/util_interpolation_spec.lua b/spec/util_interpolation_spec.lua index 21d1abe0..76000d94 100644 --- a/spec/util_interpolation_spec.lua +++ b/spec/util_interpolation_spec.lua @@ -41,6 +41,7 @@ describe("util.interpolation", function () assert.equal(expect3, render(template, { name = "you" })); assert.equal(expect_array, render(template_array, { foo = { "Hello", "World" } })); assert.equal(expect_func_pipe, render(template_func_pipe, { foo = { "c", "a", "d", "b", } })); + -- assert.equal("", render(template_func_pipe, { foo = nil })); -- FIXME assert.equal(expect_map, render(template_map, { foo = { foo = "bar" } })); end); end); -- cgit v1.2.3 From 2a7715e94b907989fcd58e4b86980c5ae4cbf7b1 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 17 Nov 2019 17:59:56 +0100 Subject: mod_csi_simple: Make sure to disable optimizations before mod_smacks (thanks pep.) --- plugins/mod_csi_simple.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_csi_simple.lua b/plugins/mod_csi_simple.lua index 24a2f1ce..4a87f06c 100644 --- a/plugins/mod_csi_simple.lua +++ b/plugins/mod_csi_simple.lua @@ -116,7 +116,7 @@ end); module:hook("pre-resource-unbind", function (event) local session = event.session; disable_optimizations(session); -end); +end, 1); module:hook("c2s-ondrain", function (event) local session = event.session; -- cgit v1.2.3 From 0455a31dbc1cdc4a918753292157cf3a254b322d Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 17 Nov 2019 23:08:45 +0100 Subject: mod_muc_mam: Copy debug log improvements from mod_mam --- plugins/mod_muc_mam.lua | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/plugins/mod_muc_mam.lua b/plugins/mod_muc_mam.lua index 7fc9fabf..1df93a18 100644 --- a/plugins/mod_muc_mam.lua +++ b/plugins/mod_muc_mam.lua @@ -166,10 +166,11 @@ module:hook("iq-set/bare/"..xmlns_mam..":query", function(event) qstart, qend = vstart, vend; end - module:log("debug", "Archive query id %s from %s until %s)", - tostring(qid), - qstart and timestamp(qstart) or "the dawn of time", - qend and timestamp(qend) or "now"); + module:log("debug", "Archive query by %s id=%s when=%s...%s", + origin.username, + qid or stanza.attr.id, + qstart and timestamp(qstart) or "", + qend and timestamp(qend) or ""); -- RSM stuff local qset = rsm.get(query); @@ -178,6 +179,9 @@ module:hook("iq-set/bare/"..xmlns_mam..":query", function(event) 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(room_node, { @@ -189,6 +193,7 @@ module:hook("iq-set/bare/"..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 @@ -250,13 +255,14 @@ module:hook("iq-set/bare/"..xmlns_mam..":query", function(event) first, last = last, first; end - -- That's all folks! - module:log("debug", "Archive query %s completed", 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); -- cgit v1.2.3 From b87fbcd1e0fb88b63a77d8d0db06e56db6149d0e Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 19 Oct 2019 20:10:14 +0200 Subject: net.server_epoll: Clarify a debug message Writing what? The data that's been buffered for writing --- net/server_epoll.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index a2052875..8f3d28db 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -468,7 +468,7 @@ 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 - self:debug("Close after writing"); + self:debug("Close after writing remaining buffered data"); self.ondrain = interface.close; else self:debug("Closing now"); -- cgit v1.2.3 From c9b83c46682de98ed66585e50c2119f6453620db Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 19 Oct 2019 20:11:21 +0200 Subject: net.server_epoll: Improve read timeout debug messages --- net/server_epoll.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index 8f3d28db..3a2dcbad 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -261,10 +261,10 @@ function interface:setreadtimeout(t) else self._readtimeout = addtimer(t, function () if self:on("readtimeout") then - self:debug("Read timeout, handled"); + self:debug("Read timeout handled"); return cfg.read_timeout; else - self:debug("Read timeout, fatal"); + self:debug("Read timeout not handled, disconnecting"); self:on("disconnect", "read timeout"); self:destroy(); end -- cgit v1.2.3 From 7b8100dc081e37bd45c1baaf100e2580fe302d06 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 17 Nov 2019 23:27:48 +0100 Subject: net.server_epoll: Save log tag in a field on FD watchers too As with 0e1701197722 --- net/server_epoll.lua | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index 3a2dcbad..d6174a4e 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -383,7 +383,7 @@ function interface:onreadable() end if err ~= "timeout" then self:on("disconnect", err); - self:destroy() + self:close() return; end end @@ -802,7 +802,8 @@ 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.id = new_id(); + conn.log = logger.init(("fdwatch%s"):format(conn.id)); conn:add(onreadable, onwritable); return conn; end; @@ -911,7 +912,8 @@ return { fds[fd] = nil; end; }, interface_mt); - conn.log = logger.init(("fdwatch%d"):format(conn:getfd())); + conn.id = conn:getfd(); + conn.log = logger.init(("fdwatch%d"):format(conn.id)); 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; -- cgit v1.2.3 From 7dc9926e110024b70c497481538615a5f89e31c4 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 17 Nov 2019 23:45:44 +0100 Subject: Back out c8aa66595072: Extra changes accidentally included --- net/server_epoll.lua | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index d6174a4e..3a2dcbad 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -383,7 +383,7 @@ function interface:onreadable() end if err ~= "timeout" then self:on("disconnect", err); - self:close() + self:destroy() return; end end @@ -802,8 +802,7 @@ local function watchfd(fd, onreadable, onwritable) end; -- Otherwise it'll need to be something LuaSocket-compatible end - conn.id = new_id(); - conn.log = logger.init(("fdwatch%s"):format(conn.id)); + conn.log = logger.init(("fdwatch%s"):format(new_id())); conn:add(onreadable, onwritable); return conn; end; @@ -912,8 +911,7 @@ return { fds[fd] = nil; end; }, interface_mt); - conn.id = conn:getfd(); - conn.log = logger.init(("fdwatch%d"):format(conn.id)); + 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; -- cgit v1.2.3 From 970ac1aa6c95fd9d8d1f2de01388dcc5113ef296 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 17 Nov 2019 23:47:31 +0100 Subject: net.server_epoll: Save log tag in a field on FD watchers too As with 0e1701197722 --- net/server_epoll.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index 3a2dcbad..d44149f3 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -802,7 +802,8 @@ 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.id = new_id(); + conn.log = logger.init(("fdwatch%s"):format(conn.id)); conn:add(onreadable, onwritable); return conn; end; @@ -911,7 +912,8 @@ return { fds[fd] = nil; end; }, interface_mt); - conn.log = logger.init(("fdwatch%d"):format(conn:getfd())); + conn.id = conn:getfd(); + conn.log = logger.init(("fdwatch%d"):format(conn.id)); 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; -- cgit v1.2.3 From 4216970602aaf459c0f3b0f408eae7d6239f6861 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 18 Nov 2019 20:37:40 +0100 Subject: mod_s2s: Wait for remote to close any connection allowing incoming stanzas Ie both s2sin and bidi-enabled s2sout. --- plugins/mod_s2s/mod_s2s.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_s2s/mod_s2s.lua b/plugins/mod_s2s/mod_s2s.lua index 9db13cb7..7ee88f3e 100644 --- a/plugins/mod_s2s/mod_s2s.lua +++ b/plugins/mod_s2s/mod_s2s.lua @@ -533,7 +533,7 @@ local function session_close(session, reason, remote_reason) -- Authenticated incoming stream may still be sending us stanzas, so wait for from remote local conn = session.conn; - if reason == nil and not session.notopen and session.type == "s2sin" then + if reason == nil and not session.notopen and session.incoming then add_task(stream_close_timeout, function () if not session.destroyed then session.log("warn", "Failed to receive a stream close response, closing connection anyway..."); -- cgit v1.2.3 From bc6a329167cc1b7440db97358268005d54ac107c Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 19 Nov 2019 17:38:38 +0100 Subject: prosody.cfg.lua.dist: Remove mention of syslog near mod_posix The syslog sink was moved out of mod_posix into core.loggingmanager in 1460c4966262 See #541 --- prosody.cfg.lua.dist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prosody.cfg.lua.dist b/prosody.cfg.lua.dist index 0d74cf66..c6a210e5 100644 --- a/prosody.cfg.lua.dist +++ b/prosody.cfg.lua.dist @@ -92,7 +92,7 @@ modules_disabled = { -- "offline"; -- Store offline messages -- "c2s"; -- Handle client connections -- "s2s"; -- Handle server-to-server connections - -- "posix"; -- POSIX functionality, sends server to background, enables syslog, etc. + -- "posix"; -- POSIX functionality, sends server to background, etc. } -- Disable account creation by default, for security -- cgit v1.2.3 From c890928e76f664800358903303dacc2a59a17d54 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 20 Nov 2019 19:22:55 +0100 Subject: util.termcolours: Use explicit number formatting instead of coercion on concatenation --- util/termcolours.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/termcolours.lua b/util/termcolours.lua index 829d84af..2c13d0ff 100644 --- a/util/termcolours.lua +++ b/util/termcolours.lua @@ -83,7 +83,7 @@ end setmetatable(stylemap, { __index = function(_, style) if type(style) == "string" and style:find("%x%x%x%x%x%x") == 1 then local g = style:sub(7) == " background" and "48;5;" or "38;5;"; - return g .. color(hex2rgb(style)); + return format("%s%d", g, color(hex2rgb(style))); end end } ); -- cgit v1.2.3 From e130b377970996b9eb3d36c74db121835ed0a57e Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 20 Nov 2019 21:31:46 +0100 Subject: mod_admin_telnet: Show SNI name in show_tls() if available --- plugins/mod_admin_telnet.lua | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 2bbd367b..cef79d25 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -584,6 +584,12 @@ local function tls_info(session, line) else line[#line+1] = "(cipher info unavailable)"; end + if sock.getsniname then + local name = sock:getsniname(); + if name then + line[#line+1] = ("(SNI:%q)"):format(name); + end + end else line[#line+1] = "(insecure)"; end -- cgit v1.2.3 From 3aee8e24a6873fce6b7ab30f5b87d17089e0d5a6 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 23 Nov 2019 01:29:03 +0100 Subject: s2s: Allow passing a custom error for bouncing queued stanzas (#770) Since stream errors and stanza errors are different --- core/s2smanager.lua | 4 ++-- plugins/mod_s2s/mod_s2s.lua | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/core/s2smanager.lua b/core/s2smanager.lua index 7471286c..7f156f12 100644 --- a/core/s2smanager.lua +++ b/core/s2smanager.lua @@ -84,14 +84,14 @@ local function retire_session(session, reason) return setmetatable(session, resting_session); end -local function destroy_session(session, reason) +local function destroy_session(session, reason, bounce_reason) if session.destroyed then return; end 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; - session:bounce_sendq(reason); + session:bounce_sendq(bounce_reason or reason); elseif session.direction == "incoming" then if session.outgoing then hosts[session.to_host].s2sout[session.from_host] = nil; diff --git a/plugins/mod_s2s/mod_s2s.lua b/plugins/mod_s2s/mod_s2s.lua index 7ee88f3e..6bb444f5 100644 --- a/plugins/mod_s2s/mod_s2s.lua +++ b/plugins/mod_s2s/mod_s2s.lua @@ -487,7 +487,7 @@ end --- Session methods local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'}; -local function session_close(session, reason, remote_reason) +local function session_close(session, reason, remote_reason, bounce_reason) local log = session.log or log; if session.conn then if session.notopen then @@ -537,12 +537,12 @@ local function session_close(session, reason, remote_reason) add_task(stream_close_timeout, function () if not session.destroyed then session.log("warn", "Failed to receive a stream close response, closing connection anyway..."); - s2s_destroy_session(session, reason); + s2s_destroy_session(session, reason, bounce_reason); conn:close(); end end); else - s2s_destroy_session(session, reason); + s2s_destroy_session(session, reason, bounce_reason); conn:close(); -- Close immediately, as this is an outgoing connection or is not authed end end -- cgit v1.2.3 From 55c130d1e410cdab6e35312f848d4f4a33682699 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 23 Nov 2019 01:32:53 +0100 Subject: mod_s2s: Add error text for error replies on some s2s failures (#770) --- plugins/mod_s2s/mod_s2s.lua | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/plugins/mod_s2s/mod_s2s.lua b/plugins/mod_s2s/mod_s2s.lua index 6bb444f5..e7ed8797 100644 --- a/plugins/mod_s2s/mod_s2s.lua +++ b/plugins/mod_s2s/mod_s2s.lua @@ -194,7 +194,7 @@ function module.add_host(module) session:close({ condition = "unsupported-feature", text = "No viable authentication method offered", - }); + }, nil, "No viable authentication method offered by remote server"); return false; end end, -1); @@ -255,7 +255,7 @@ function make_authenticated(event) condition = "policy-violation", text = "Encrypted server-to-server communication is required but was not " ..((session.direction == "outgoing" and "offered") or "used") - }); + }, nil, "Could not establish encrypted connection to remote server"); end end if hosts[host] then @@ -608,7 +608,7 @@ local function initialize_session(session) local ok, err = stream:feed(data); if ok then return; end log("debug", "Received invalid XML (%s) %d bytes: %q", err, #data, data:sub(1, 300)); - session:close("not-well-formed"); + session:close("not-well-formed", nil, "Received invalid XML from remote server"); end end @@ -738,9 +738,10 @@ function check_auth_policy(event) if must_secure and (session.cert_chain_status ~= "valid" or session.cert_identity_status ~= "valid") then module:log("warn", "Forbidding insecure connection to/from %s", host or session.ip or "(unknown host)"); if session.direction == "incoming" then - session:close({ condition = "not-authorized", text = "Your server's certificate is invalid, expired, or not trusted by "..session.to_host }); + session:close({ condition = "not-authorized", text = "Your server's certificate is invalid, expired, or not trusted by "..session.to_host }, + nil, "Remote server's certificate is invalid, expired, or not trusted"); else -- Close outgoing connections without warning - session:close(false); + session:close(false, nil, "Remote server's certificate is invalid, expired, or not trusted"); end return false; end -- cgit v1.2.3 From aa5d88fe4437d08b509164568150bafc16222ce5 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 17 Nov 2019 23:53:51 +0100 Subject: mod_csi: Only advertise CSI to clients if something is handling CSI events --- plugins/mod_csi.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_csi.lua b/plugins/mod_csi.lua index 84476cac..870df418 100644 --- a/plugins/mod_csi.lua +++ b/plugins/mod_csi.lua @@ -3,7 +3,7 @@ local xmlns_csi = "urn:xmpp:csi:0"; local csi_feature = st.stanza("csi", { xmlns = xmlns_csi }); module:hook("stream-features", function (event) - if event.origin.username then + if event.origin.username and prosody.hosts[module.host].events._handlers["csi-client-active"] then event.features:add_child(csi_feature); end end); -- cgit v1.2.3 From 7c3ba237b9d0f3eb829b90e9e9344e5790481de3 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 23 Nov 2019 17:09:48 +0100 Subject: mod_csi: Set module status based on whether a CSI handler module appears to be loaded --- plugins/mod_csi.lua | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/plugins/mod_csi.lua b/plugins/mod_csi.lua index 870df418..e84ee032 100644 --- a/plugins/mod_csi.lua +++ b/plugins/mod_csi.lua @@ -21,3 +21,12 @@ end module:hook("stanza/"..xmlns_csi..":active", refire_event("csi-client-active")); module:hook("stanza/"..xmlns_csi..":inactive", refire_event("csi-client-inactive")); +function module.load() + if prosody.hosts[module.host].events._handlers["csi-client-active"] then + module:set_status("core", "CSI handler module loaded"); + else + module:set_status("warn", "No CSI handler module loaded"); + end +end +module:hook("module-loaded", module.load); +module:hook("module-unloaded", module.load); -- cgit v1.2.3 From 8703eaa89a6cf42d6fe4d8e1083bbb029afcf7e9 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 23 Nov 2019 17:15:34 +0100 Subject: mod_csi: Cache CSI module availability to improve readabilty --- plugins/mod_csi.lua | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/mod_csi.lua b/plugins/mod_csi.lua index e84ee032..458ff491 100644 --- a/plugins/mod_csi.lua +++ b/plugins/mod_csi.lua @@ -2,8 +2,9 @@ local st = require "util.stanza"; local xmlns_csi = "urn:xmpp:csi:0"; local csi_feature = st.stanza("csi", { xmlns = xmlns_csi }); +local csi_handler_available = nil; module:hook("stream-features", function (event) - if event.origin.username and prosody.hosts[module.host].events._handlers["csi-client-active"] then + if event.origin.username and csi_handler_available then event.features:add_child(csi_feature); end end); @@ -23,8 +24,10 @@ module:hook("stanza/"..xmlns_csi..":inactive", refire_event("csi-client-inactive function module.load() if prosody.hosts[module.host].events._handlers["csi-client-active"] then + csi_handler_available = true; module:set_status("core", "CSI handler module loaded"); else + csi_handler_available = false; module:set_status("warn", "No CSI handler module loaded"); end end -- cgit v1.2.3 From dfa2bdffc083faa597509d7f35125c41f6ffbc9d Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 17 Nov 2019 16:16:53 +0100 Subject: mod_http_errors: Show a friendly page instead of 404 on top level --- plugins/mod_http_errors.lua | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/plugins/mod_http_errors.lua b/plugins/mod_http_errors.lua index 2bb13298..2158b403 100644 --- a/plugins/mod_http_errors.lua +++ b/plugins/mod_http_errors.lua @@ -75,3 +75,15 @@ module:hook_object_event(server, "http-error", function (event) end return get_page(event.code, (show_private and event.private_message) or event.message); end); + +module:hook_object_event(server, "http-error", function (event) + local request, response = event.request, event.response; + if request and response and request.path == "/" and response.status_code == 404 then + response.headers.content_type = "text/html; charset=utf-8"; + return render(html, { + title = "Prosody is running!"; + message = "Welcome to the XMPP world!"; + }); + end +end, 1); + -- cgit v1.2.3 From 7ed435c0548a7c735c74234f081e267302970be6 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 24 Nov 2019 00:02:48 +0100 Subject: MUC: Add testcase for #1466 --- spec/scansion/muc_nickname_change.scs | 127 ++++++++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 spec/scansion/muc_nickname_change.scs diff --git a/spec/scansion/muc_nickname_change.scs b/spec/scansion/muc_nickname_change.scs new file mode 100644 index 00000000..73f81203 --- /dev/null +++ b/spec/scansion/muc_nickname_change.scs @@ -0,0 +1,127 @@ +# MUC: Change nickname +# Make sure a role is not reset, see #1466 + +[Client] Romeo + jid: user@localhost + password: password + +[Client] Juliet + jid: user2@localhost + password: password + +----- + +Romeo connects + +Romeo sends: + + + + +Romeo receives: + + + + + + + + +Romeo receives: + + +Romeo sends: + + + + + http://jabber.org/protocol/muc#roomconfig + + + 1 + + + + + +Romeo receives: + + +Juliet connects + +Juliet sends: + + + + +Juliet receives: + + + + + + +Juliet receives: + + + + + + + + +Juliet receives: + + +Romeo receives: + + + + + + +Romeo sends: + + + + + http://jabber.org/protocol/muc#roomconfig + + + 0 + + + + + +Romeo receives: + + +Juliet receives: + + + + + + +Juliet sends: + + + +Juliet receives: + + + + + + + + +Juliet receives: + + + + + + + -- cgit v1.2.3 From ae03335f0be444d9bc6e54326a616bf10456f358 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 24 Nov 2019 04:43:14 +0100 Subject: net.resolvers.service: Pass IP literals directly to basic resolver IP literals will not work with SRV records anyways. Fixes s2s with IP literals. --- net/resolvers/service.lua | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/net/resolvers/service.lua b/net/resolvers/service.lua index 65b086d2..504fb421 100644 --- a/net/resolvers/service.lua +++ b/net/resolvers/service.lua @@ -1,5 +1,6 @@ local adns = require "net.adns"; local basic = require "net.resolvers.basic"; +local inet_pton = require "util.net".pton; local idna_to_ascii = require "util.encodings".idna.to_ascii; local unpack = table.unpack or unpack; -- luacheck: ignore 113 @@ -69,6 +70,14 @@ function methods:next(cb) end local function new(hostname, service, conn_type, extra) + local is_ip = inet_pton(hostname); + if not is_ip and hostname:sub(1,1) == '[' then + is_ip = inet_pton(hostname:sub(2,-2)); + end + if is_ip and extra and extra.default_port then + return basic.new(hostname, extra.default_port, conn_type, extra); + end + return setmetatable({ hostname = idna_to_ascii(hostname); service = service; -- cgit v1.2.3 From b340e5e4628e2f5c136fe1f4300821d89ed4cb94 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 25 Nov 2019 20:44:05 +0100 Subject: util.stanza: Check that argument to reply is a stanza --- spec/util_stanza_spec.lua | 6 ++++++ util/stanza.lua | 3 +++ 2 files changed, 9 insertions(+) diff --git a/spec/util_stanza_spec.lua b/spec/util_stanza_spec.lua index 38503ab7..7d710888 100644 --- a/spec/util_stanza_spec.lua +++ b/spec/util_stanza_spec.lua @@ -170,6 +170,12 @@ describe("util.stanza", function() assert.are.equal(r.attr.type, "result"); assert.are.equal(#r.tags, 0, "A reply should not include children of the original stanza"); end); + + it("should reject not-stanzas", function () + assert.has.error_match(function () + st.reply(not "a stanza"); + end, "expected stanza"); + end); end); describe("#error_reply()", function() diff --git a/util/stanza.lua b/util/stanza.lua index 55c38c73..f46d5b30 100644 --- a/util/stanza.lua +++ b/util/stanza.lua @@ -434,6 +434,9 @@ local function iq(attr) end local function reply(orig) + if not is_stanza(orig) then + error("bad argument to reply: expected stanza, got "..type(orig)); + end return new_stanza(orig.name, orig.attr and { to = orig.attr.from, -- cgit v1.2.3 From 11095184a53cc144e37560b7bbbf4de19ce669c7 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 25 Nov 2019 20:46:55 +0100 Subject: util.stanza: Remove redundant check for attrs A stanza can't not have attrs if created the correct way --- spec/util_stanza_spec.lua | 7 +++++++ util/stanza.lua | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/spec/util_stanza_spec.lua b/spec/util_stanza_spec.lua index 7d710888..3b9084fa 100644 --- a/spec/util_stanza_spec.lua +++ b/spec/util_stanza_spec.lua @@ -176,6 +176,13 @@ describe("util.stanza", function() st.reply(not "a stanza"); end, "expected stanza"); end); + + it("should reject not-stanzas", function () + assert.has.error_match(function () + st.reply({name="x"}); + end, "expected stanza"); + end); + end); describe("#error_reply()", function() diff --git a/util/stanza.lua b/util/stanza.lua index f46d5b30..919d983c 100644 --- a/util/stanza.lua +++ b/util/stanza.lua @@ -438,7 +438,7 @@ local function reply(orig) error("bad argument to reply: expected stanza, got "..type(orig)); end return new_stanza(orig.name, - orig.attr and { + { to = orig.attr.from, from = orig.attr.to, id = orig.attr.id, -- cgit v1.2.3 From 52b7181979bcee9000acf1c90e85934976e4da6a Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 25 Nov 2019 20:52:01 +0100 Subject: util.stanza: Check that argument to error_reply is a stanza --- spec/util_stanza_spec.lua | 6 ++++++ util/stanza.lua | 3 +++ 2 files changed, 9 insertions(+) diff --git a/spec/util_stanza_spec.lua b/spec/util_stanza_spec.lua index 3b9084fa..23cdbeb9 100644 --- a/spec/util_stanza_spec.lua +++ b/spec/util_stanza_spec.lua @@ -214,6 +214,12 @@ describe("util.stanza", function() assert.are.equal(#r.tags, 1); assert.are.equal(r.tags[1].tags[1].name, "service-unavailable"); end); + + it("should reject not-stanzas", function () + assert.has.error_match(function () + st.error_reply(not "a stanza", "modify", "bad-request"); + end, "expected stanza"); + end); end); describe("should reject #invalid", function () diff --git a/util/stanza.lua b/util/stanza.lua index 919d983c..24fd4fea 100644 --- a/util/stanza.lua +++ b/util/stanza.lua @@ -448,6 +448,9 @@ end local xmpp_stanzas_attr = { xmlns = xmlns_stanzas }; local function error_reply(orig, error_type, condition, error_message) + if not is_stanza(orig) then + error("bad argument to error_reply: expected stanza, got "..type(orig)); + end local t = reply(orig); t.attr.type = "error"; t:tag("error", {type = error_type}) --COMPAT: Some day xmlns:stanzas goes here -- cgit v1.2.3 From 54da2ab6f7fec145d05ac8682c50a776bb993880 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 25 Nov 2019 20:52:03 +0100 Subject: util.stanza: Check that argument to error_reply is NOT a stanza of type error Replying to an error is Very Bad --- spec/util_stanza_spec.lua | 10 ++++++++++ util/stanza.lua | 2 ++ 2 files changed, 12 insertions(+) diff --git a/spec/util_stanza_spec.lua b/spec/util_stanza_spec.lua index 23cdbeb9..04458303 100644 --- a/spec/util_stanza_spec.lua +++ b/spec/util_stanza_spec.lua @@ -220,6 +220,16 @@ describe("util.stanza", function() st.error_reply(not "a stanza", "modify", "bad-request"); end, "expected stanza"); end); + + it("should reject stanzas of type error", function () + assert.has.error_match(function () + st.error_reply(st.message({type="error"}), "cancel", "conflict"); + end, "got stanza of type error"); + assert.has.error_match(function () + st.error_reply(st.error_reply(st.message({type="chat"}), "modify", "forbidden"), "cancel", "service-unavailable"); + end, "got stanza of type error"); + end); + end); describe("should reject #invalid", function () diff --git a/util/stanza.lua b/util/stanza.lua index 24fd4fea..8ff1aae3 100644 --- a/util/stanza.lua +++ b/util/stanza.lua @@ -450,6 +450,8 @@ local xmpp_stanzas_attr = { xmlns = xmlns_stanzas }; local function error_reply(orig, error_type, condition, error_message) if not is_stanza(orig) then error("bad argument to error_reply: expected stanza, got "..type(orig)); + elseif orig.attr.type == "error" then + error("bad argument to error_reply: got stanza of type error which must not be replied to"); end local t = reply(orig); t.attr.type = "error"; -- cgit v1.2.3 From be23b274f685f5eac73b79231491f4f0141f1c1c Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 25 Nov 2019 20:59:36 +0100 Subject: util.stanza: Support the 'by' attribute on errors This is to be used when the entity generating the error is not the same as the one the stanza was directed to, e.g. an intermediate server. --- spec/util_stanza_spec.lua | 3 ++- util/stanza.lua | 7 +++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/spec/util_stanza_spec.lua b/spec/util_stanza_spec.lua index 04458303..b754f59d 100644 --- a/spec/util_stanza_spec.lua +++ b/spec/util_stanza_spec.lua @@ -191,13 +191,14 @@ describe("util.stanza", function() local s = st.stanza("s", { to = "touser", from = "fromuser", id = "123" }) :tag("child1"); -- Make reply stanza - local r = st.error_reply(s, "cancel", "service-unavailable"); + local r = st.error_reply(s, "cancel", "service-unavailable", nil, "host"); assert.are.equal(r.name, s.name); assert.are.equal(r.id, s.id); assert.are.equal(r.attr.to, s.attr.from); assert.are.equal(r.attr.from, s.attr.to); assert.are.equal(#r.tags, 1); assert.are.equal(r.tags[1].tags[1].name, "service-unavailable"); + assert.are.equal(r.tags[1].attr.by, "host"); end); it("should work for ", function() diff --git a/util/stanza.lua b/util/stanza.lua index 8ff1aae3..8e46f90f 100644 --- a/util/stanza.lua +++ b/util/stanza.lua @@ -447,7 +447,7 @@ local function reply(orig) end local xmpp_stanzas_attr = { xmlns = xmlns_stanzas }; -local function error_reply(orig, error_type, condition, error_message) +local function error_reply(orig, error_type, condition, error_message, error_by) if not is_stanza(orig) then error("bad argument to error_reply: expected stanza, got "..type(orig)); elseif orig.attr.type == "error" then @@ -455,7 +455,10 @@ local function error_reply(orig, error_type, condition, error_message) end local t = reply(orig); t.attr.type = "error"; - t:tag("error", {type = error_type}) --COMPAT: Some day xmlns:stanzas goes here + if t.attr.from == error_by then + error_by = nil; + end + t:tag("error", {type = error_type, by = error_by}) --COMPAT: Some day xmlns:stanzas goes here :tag(condition, xmpp_stanzas_attr):up(); if error_message then t:text_tag("text", error_message, xmpp_stanzas_attr); end return t; -- stanza ready for adding app-specific errors -- cgit v1.2.3 From 057fbeaf06876cfbfbba24bbc317de313fa9e84b Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 25 Nov 2019 23:47:49 +0100 Subject: MUC: Indicate origin of password related errors --- plugins/muc/password.lib.lua | 2 +- spec/scansion/muc_password.scs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/muc/password.lib.lua b/plugins/muc/password.lib.lua index 1f4b2add..6695c0cf 100644 --- a/plugins/muc/password.lib.lua +++ b/plugins/muc/password.lib.lua @@ -50,7 +50,7 @@ module:hook("muc-occupant-pre-join", function(event) if get_password(room) ~= password then local from, to = stanza.attr.from, stanza.attr.to; module:log("debug", "%s couldn't join due to invalid password: %s", from, to); - local reply = st.error_reply(stanza, "auth", "not-authorized"):up(); + local reply = st.error_reply(stanza, "auth", "not-authorized", nil, room.jid):up(); reply.tags[1].attr.code = "401"; event.origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); return true; diff --git a/spec/scansion/muc_password.scs b/spec/scansion/muc_password.scs index 82611183..63c821e0 100644 --- a/spec/scansion/muc_password.scs +++ b/spec/scansion/muc_password.scs @@ -58,7 +58,7 @@ Juliet sends: Juliet receives: - + -- cgit v1.2.3 From c26f8beac4075c8d031ed9a620fac77653d1bb07 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 25 Nov 2019 23:51:41 +0100 Subject: MUC: Indicate origin of registration related errors --- plugins/muc/register.lib.lua | 6 +++--- spec/scansion/muc_register.scs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/muc/register.lib.lua b/plugins/muc/register.lib.lua index bf8cd877..f0a15dd4 100644 --- a/plugins/muc/register.lib.lua +++ b/plugins/muc/register.lib.lua @@ -66,7 +66,7 @@ local function enforce_nick_policy(event) local reserved_by = get_registered_jid(room, requested_nick); if reserved_by and reserved_by ~= jid_bare(stanza.attr.from) then module:log("debug", "%s attempted to use nick %s reserved by %s", stanza.attr.from, requested_nick, reserved_by); - local reply = st.error_reply(stanza, "cancel", "conflict"):up(); + local reply = st.error_reply(stanza, "cancel", "conflict", nil, room.jid):up(); origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); return true; end @@ -79,7 +79,7 @@ local function enforce_nick_policy(event) event.occupant.nick = jid_bare(event.occupant.nick) .. "/" .. nick; elseif event.dest_occupant.nick ~= jid_bare(event.dest_occupant.nick) .. "/" .. nick then module:log("debug", "Attempt by %s to join as %s, but their reserved nick is %s", stanza.attr.from, requested_nick, nick); - local reply = st.error_reply(stanza, "cancel", "not-acceptable"):up(); + local reply = st.error_reply(stanza, "cancel", "not-acceptable", nil, room.jid):up(); origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); return true; end @@ -103,7 +103,7 @@ local function handle_register_iq(room, origin, stanza) local user_jid = jid_bare(stanza.attr.from) local affiliation = room:get_affiliation(user_jid); if affiliation == "outcast" then - origin.send(st.error_reply(stanza, "auth", "forbidden")); + origin.send(st.error_reply(stanza, "auth", "forbidden", room.jid)); return true; elseif not (affiliation or allow_unaffiliated) then origin.send(st.error_reply(stanza, "auth", "registration-required")); diff --git a/spec/scansion/muc_register.scs b/spec/scansion/muc_register.scs index a077cd76..5d1f5f3e 100644 --- a/spec/scansion/muc_register.scs +++ b/spec/scansion/muc_register.scs @@ -177,7 +177,7 @@ Rosaline sends: Rosaline receives: - + @@ -288,7 +288,7 @@ Rosaline sends: Rosaline receives: - + -- cgit v1.2.3 From 093ef6bb23e47af21bcb5428386ace5013604424 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 25 Nov 2019 23:52:45 +0100 Subject: MUC: Indicate that the room is the origin of various errors where 'from' is an occupant JID --- plugins/muc/members_only.lib.lua | 4 ++-- plugins/muc/mod_muc.lua | 2 +- plugins/muc/muc.lib.lua | 29 ++++++++++++++++------------- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/plugins/muc/members_only.lib.lua b/plugins/muc/members_only.lib.lua index 4194c5c7..79077153 100644 --- a/plugins/muc/members_only.lib.lua +++ b/plugins/muc/members_only.lib.lua @@ -113,7 +113,7 @@ module:hook("muc-occupant-pre-join", function(event) local stanza = event.stanza; local affiliation = room:get_affiliation(stanza.attr.from); if valid_affiliations[affiliation or "none"] <= valid_affiliations.none then - local reply = st.error_reply(stanza, "auth", "registration-required"):up(); + local reply = st.error_reply(stanza, "auth", "registration-required", nil, room.jid):up(); reply.tags[1].attr.code = "407"; event.origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); return true; @@ -131,7 +131,7 @@ module:hook("muc-pre-invite", function(event) local inviter_affiliation = room:get_affiliation(stanza.attr.from) or "none"; local required_affiliation = room._data.allow_member_invites and "member" or "admin"; if valid_affiliations[inviter_affiliation] < valid_affiliations[required_affiliation] then - event.origin.send(st.error_reply(stanza, "auth", "forbidden")); + event.origin.send(st.error_reply(stanza, "auth", "forbidden", nil, room.jid)); return true; end end diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index 166249cc..9481c977 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -191,7 +191,7 @@ end local function handle_broken_room(room, origin, stanza) module:log("debug", "Returning error from broken room %s", room.jid); - origin.send(st.error_reply(stanza, "wait", "internal-server-error")); + origin.send(st.error_reply(stanza, "wait", "internal-server-error", nil, room.jid)); return true; end diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 4b462631..ed6011e1 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -422,7 +422,7 @@ module:hook("muc-occupant-pre-join", function(event) local room, stanza = event.room, event.stanza; local affiliation = room:get_affiliation(stanza.attr.from); if affiliation == "outcast" then - local reply = st.error_reply(stanza, "auth", "forbidden"):up(); + local reply = st.error_reply(stanza, "auth", "forbidden", nil, room.jid):up(); reply.tags[1].attr.code = "403"; event.origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); return true; @@ -430,24 +430,27 @@ module:hook("muc-occupant-pre-join", function(event) end, -10); module:hook("muc-occupant-pre-join", function(event) + local room = event.room; local nick = jid_resource(event.occupant.nick); if not nick:find("%S") then - event.origin.send(st.error_reply(event.stanza, "modify", "not-allowed", "Invisible Nicknames are forbidden")); + event.origin.send(st.error_reply(event.stanza, "modify", "not-allowed", "Invisible Nicknames are forbidden", room.jid)); return true; end end, 1); module:hook("muc-occupant-pre-change", function(event) + local room = event.room; if not jid_resource(event.dest_occupant.nick):find("%S") then - event.origin.send(st.error_reply(event.stanza, "modify", "not-allowed", "Invisible Nicknames are forbidden")); + event.origin.send(st.error_reply(event.stanza, "modify", "not-allowed", "Invisible Nicknames are forbidden", room.jid)); return true; end end, 1); module:hook("muc-occupant-pre-join", function(event) + local room = event.room; local nick = jid_resource(event.occupant.nick); if not resourceprep(nick, true) then -- strict - event.origin.send(st.error_reply(event.stanza, "modify", "jid-malformed", "Nickname must pass strict validation")); + event.origin.send(st.error_reply(event.stanza, "modify", "jid-malformed", "Nickname must pass strict validation", room.jid)); return true; end end, 2); @@ -455,7 +458,7 @@ end, 2); module:hook("muc-occupant-pre-change", function(event) local nick = jid_resource(event.dest_occupant.nick); if not resourceprep(nick, true) then -- strict - event.origin.send(st.error_reply(event.stanza, "modify", "jid-malformed", "Nickname must pass strict validation")); + event.origin.send(st.error_reply(event.stanza, "modify", "jid-malformed", "Nickname must pass strict validation", room.jid)); return true; end end, 2); @@ -530,7 +533,7 @@ function room_mt:handle_normal_presence(origin, stanza) if orig_occupant == nil and not muc_x and stanza.attr.type == nil then module:log("debug", "Attempted join without , possibly desynced"); origin.send(st.error_reply(stanza, "cancel", "item-not-found", - "You are not currently connected to this chat")); + "You are not currently connected to this chat", self.jid)); return true; end @@ -592,7 +595,7 @@ function room_mt:handle_normal_presence(origin, stanza) and bare_jid ~= jid_bare(dest_occupant.bare_jid) then -- new nick or has different bare real jid log("debug", "%s couldn't join due to nick conflict: %s", real_jid, dest_occupant.nick); - local reply = st.error_reply(stanza, "cancel", "conflict"):up(); + local reply = st.error_reply(stanza, "cancel", "conflict", nil, self.jid):up(); reply.tags[1].attr.code = "409"; origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); return true; @@ -721,7 +724,7 @@ function room_mt:handle_presence_to_occupant(origin, stanza) return self:handle_normal_presence(origin, stanza); elseif type ~= 'result' then -- bad type if type ~= 'visible' and type ~= 'invisible' then -- COMPAT ejabberd can broadcast or forward XEP-0018 presences - origin.send(st.error_reply(stanza, "modify", "bad-request")); -- FIXME correct error? + origin.send(st.error_reply(stanza, "modify", "bad-request", nil, self.jid)); -- FIXME correct error? end end return true; @@ -756,11 +759,11 @@ function room_mt:handle_iq_to_occupant(origin, stanza) else -- Type is "get" or "set" local current_nick = self:get_occupant_jid(from); if not current_nick then - origin.send(st.error_reply(stanza, "cancel", "not-acceptable", "You are not currently connected to this chat")); + origin.send(st.error_reply(stanza, "cancel", "not-acceptable", "You are not currently connected to this chat", self.jid)); return true; end if not occupant then -- recipient not in room - origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Recipient not in room")); + origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Recipient not in room", self.jid)); return true; end -- XEP-0410 MUC Self-Ping #1220 @@ -789,12 +792,12 @@ function room_mt:handle_message_to_occupant(origin, stanza) local type = stanza.attr.type; if not current_nick then -- not in room if type ~= "error" then - origin.send(st.error_reply(stanza, "cancel", "not-acceptable", "You are not currently connected to this chat")); + origin.send(st.error_reply(stanza, "cancel", "not-acceptable", "You are not currently connected to this chat", self.jid)); end return true; end if type == "groupchat" then -- groupchat messages not allowed in PM - origin.send(st.error_reply(stanza, "modify", "bad-request")); + origin.send(st.error_reply(stanza, "modify", "bad-request", nil, self.jid)); return true; elseif type == "error" and is_kickable_error(stanza) then log("debug", "%s kicked from %s for sending an error message", current_nick, self.jid); @@ -803,7 +806,7 @@ function room_mt:handle_message_to_occupant(origin, stanza) local o_data = self:get_occupant_by_nick(to); if not o_data then - origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Recipient not in room")); + origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Recipient not in room", self.jid)); return true; end log("debug", "%s sent private message stanza to %s (%s)", from, to, o_data.jid); -- cgit v1.2.3 From b2e9ee4b6bf0950e73252277c9d15c629b2308e0 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 26 Nov 2019 00:02:13 +0100 Subject: MUC: Indicate the component as origin of various errors where there's no room A room that doesn't exist can't return an error, can it? --- plugins/muc/lock.lib.lua | 2 +- plugins/muc/mod_muc.lua | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/plugins/muc/lock.lib.lua b/plugins/muc/lock.lib.lua index 062ab615..32f2647b 100644 --- a/plugins/muc/lock.lib.lua +++ b/plugins/muc/lock.lib.lua @@ -43,7 +43,7 @@ end module:hook("muc-occupant-pre-join", function(event) if not event.is_new_room and is_locked(event.room) then -- Deny entry module:log("debug", "Room is locked, denying entry"); - event.origin.send(st.error_reply(event.stanza, "cancel", "item-not-found")); + event.origin.send(st.error_reply(event.stanza, "cancel", "item-not-found", nil, module.host)); return true; end end, -30); diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index 9481c977..fc39d89f 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -355,7 +355,7 @@ end, 1); module:hook("muc-room-pre-create", function(event) local origin, stanza = event.origin, event.stanza; if not track_room(event.room) then - origin.send(st.error_reply(stanza, "wait", "resource-constraint")); + origin.send(st.error_reply(stanza, "wait", "resource-constraint", nil, module.host)); return true; end end, -1000); @@ -406,7 +406,7 @@ do restrict_room_creation == "local" and select(2, jid_split(user_jid)) == host_suffix ) then - origin.send(st.error_reply(stanza, "cancel", "not-allowed", "Room creation is restricted")); + origin.send(st.error_reply(stanza, "cancel", "not-allowed", "Room creation is restricted", module.host)); return true; end end); @@ -451,7 +451,7 @@ for event_name, method in pairs { room = nil; else if stanza.attr.type ~= "error" then - local reply = st.error_reply(stanza, "cancel", "gone", room._data.reason) + local reply = st.error_reply(stanza, "cancel", "gone", room._data.reason, module.host) if room._data.newjid then local uri = "xmpp:"..room._data.newjid.."?join"; reply:get_child("error"):child_with_name("gone"):text(uri); @@ -465,20 +465,20 @@ for event_name, method in pairs { if room == nil then -- Watch presence to create rooms if not jid_prep(room_jid, true) then - origin.send(st.error_reply(stanza, "modify", "jid-malformed")); + origin.send(st.error_reply(stanza, "modify", "jid-malformed", nil, module.host)); return true; end if stanza.attr.type == nil and stanza.name == "presence" and stanza:get_child("x", "http://jabber.org/protocol/muc") then room = muclib.new_room(room_jid); return room:handle_first_presence(origin, stanza); elseif stanza.attr.type ~= "error" then - origin.send(st.error_reply(stanza, "cancel", "item-not-found")); + origin.send(st.error_reply(stanza, "cancel", "item-not-found", nil, module.host)); return true; else return; end elseif room == false then -- Error loading room - origin.send(st.error_reply(stanza, "wait", "resource-constraint")); + origin.send(st.error_reply(stanza, "wait", "resource-constraint", nil, module.host)); return true; end return room[method](room, origin, stanza); -- cgit v1.2.3 From 47fd3433fc4ac4d203d2ae9c045e2c5e4356a4f2 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 26 Nov 2019 00:09:51 +0100 Subject: MUC: Add missing reference to room (thanks buildbot) [luacheck] --- plugins/muc/muc.lib.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index ed6011e1..6b5f6068 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -456,6 +456,7 @@ module:hook("muc-occupant-pre-join", function(event) end, 2); module:hook("muc-occupant-pre-change", function(event) + local room = event.room; local nick = jid_resource(event.dest_occupant.nick); if not resourceprep(nick, true) then -- strict event.origin.send(st.error_reply(event.stanza, "modify", "jid-malformed", "Nickname must pass strict validation", room.jid)); -- cgit v1.2.3 From e8f099babcb585312a64d604e4c79f003c3b562b Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 26 Nov 2019 00:12:51 +0100 Subject: net.connect: Add some TODO comments --- net/connect.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/net/connect.lua b/net/connect.lua index d4de6fb4..0e11bd3e 100644 --- a/net/connect.lua +++ b/net/connect.lua @@ -2,6 +2,10 @@ local server = require "net.server"; local log = require "util.logger".init("net.connect"); local new_id = require "util.id".short; +-- TODO Respect use_ipv4, use_ipv6 +-- FIXME Error propagation from resolvers doesn't work +-- TODO Try to share DNS resolver object and close it afterwards + local pending_connection_methods = {}; local pending_connection_mt = { __name = "pending_connection"; -- cgit v1.2.3 From 09cdcfc0a16645fba2af58682352163853063224 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Tue, 26 Nov 2019 15:29:01 +0000 Subject: net.websocket: Fix traceback in case of ondisconnect being called twice We want to figure out what situations the double ondisconnect happens in, and aim to fix the root cause in the future. --- net/websocket.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/net/websocket.lua b/net/websocket.lua index fb16b8bb..193cd556 100644 --- a/net/websocket.lua +++ b/net/websocket.lua @@ -23,6 +23,7 @@ local websockets = {}; local websocket_listeners = {}; function websocket_listeners.ondisconnect(conn, err) local s = websockets[conn]; + if not s then return; end websockets[conn] = nil; if s.close_timer then timer.stop(s.close_timer); -- cgit v1.2.3 From f864eaf14f4725aea640dd8020ec4809b4cae73b Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 27 Nov 2019 23:23:25 +0100 Subject: mod_s2s_auth_certs: Save chain validation errors for later use --- plugins/mod_s2s_auth_certs.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/mod_s2s_auth_certs.lua b/plugins/mod_s2s_auth_certs.lua index 76e134a7..37519aa1 100644 --- a/plugins/mod_s2s_auth_certs.lua +++ b/plugins/mod_s2s_auth_certs.lua @@ -27,6 +27,7 @@ module:hook("s2s-check-certificate", function(event) log("debug", "certificate error(s) at depth %d: %s", depth-1, table.concat(t, ", ")) end session.cert_chain_status = "invalid"; + session.cert_chain_errors = errors; else log("debug", "certificate chain validation result: valid"); session.cert_chain_status = "valid"; -- cgit v1.2.3 From 2934eccd99592b9f1b500e9dea0eeea49b0ffbde Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 27 Nov 2019 23:26:59 +0100 Subject: mod_s2s: Improve error in bounces due to cert validation problems --- plugins/mod_s2s/mod_s2s.lua | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/plugins/mod_s2s/mod_s2s.lua b/plugins/mod_s2s/mod_s2s.lua index e7ed8797..4d79a825 100644 --- a/plugins/mod_s2s/mod_s2s.lua +++ b/plugins/mod_s2s/mod_s2s.lua @@ -30,6 +30,7 @@ local runner = require "util.async".runner; local connect = require "net.connect".connect; local service = require "net.resolvers.service"; local errors = require "util.error"; +local set = require "util.set"; local connect_timeout = module:get_option_number("s2s_timeout", 90); local stream_close_timeout = module:get_option_number("s2s_close_timeout", 5); @@ -725,6 +726,25 @@ function listener.onattach(conn, data) end end +-- Complete the sentence "Your certificate " with what's wrong +local function friendly_cert_error(session) --> string + if session.cert_chain_status == "invalid" then + if session.cert_chain_errors then + local cert_errors = set.new(session.cert_chain_errors[1]); + if cert_errors:contains("certificate has expired") then + return "has expired"; + elseif cert_errors:contains("self signed certificate") then + return "is self-signed"; + end + end + return "is not trusted"; -- for some other reason + elseif session.cert_identity_status == "invalid" then + return "is not valid for this name"; + end + -- this should normally be unreachable except if no s2s auth module was loaded + return "could not be validated"; +end + function check_auth_policy(event) local host, session = event.host, event.session; local must_secure = secure_auth; @@ -737,11 +757,12 @@ function check_auth_policy(event) if must_secure and (session.cert_chain_status ~= "valid" or session.cert_identity_status ~= "valid") then module:log("warn", "Forbidding insecure connection to/from %s", host or session.ip or "(unknown host)"); + local reason = friendly_cert_error(session); if session.direction == "incoming" then - session:close({ condition = "not-authorized", text = "Your server's certificate is invalid, expired, or not trusted by "..session.to_host }, - nil, "Remote server's certificate is invalid, expired, or not trusted"); + session:close({ condition = "not-authorized", text = "Your server's certificate "..reason }, + nil, "Remote server's certificate "..reason); else -- Close outgoing connections without warning - session:close(false, nil, "Remote server's certificate is invalid, expired, or not trusted"); + session:close(false, nil, "Remote server's certificate "..reason); end return false; end -- cgit v1.2.3 From 976a86ee4645baf3e4ef8e0f8e6066fc62e8f379 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 28 Nov 2019 17:32:15 +0100 Subject: mod_s2s: Send stream errors for cert problems on outgoing connections Rationale in comment. --- plugins/mod_s2s/mod_s2s.lua | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/plugins/mod_s2s/mod_s2s.lua b/plugins/mod_s2s/mod_s2s.lua index 4d79a825..6419ea67 100644 --- a/plugins/mod_s2s/mod_s2s.lua +++ b/plugins/mod_s2s/mod_s2s.lua @@ -758,12 +758,13 @@ function check_auth_policy(event) if must_secure and (session.cert_chain_status ~= "valid" or session.cert_identity_status ~= "valid") then module:log("warn", "Forbidding insecure connection to/from %s", host or session.ip or "(unknown host)"); local reason = friendly_cert_error(session); - if session.direction == "incoming" then - session:close({ condition = "not-authorized", text = "Your server's certificate "..reason }, - nil, "Remote server's certificate "..reason); - else -- Close outgoing connections without warning - session:close(false, nil, "Remote server's certificate "..reason); - end + -- XEP-0178 recommends closing outgoing connections without warning + -- but does not give a rationale for this. + -- In practice most cases are configuration mistakes or forgotten + -- certificate renewals. We think it's better to let the other party + -- know about the problem so that they can fix it. + session:close({ condition = "not-authorized", text = "Your server's certificate "..reason }, + nil, "Remote server's certificate "..reason); return false; end end -- cgit v1.2.3 From 61228e919ce13247321c04b6b32020d2652d58ba Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 28 Nov 2019 18:30:30 +0100 Subject: mod_s2s: Abort outgoing connections earlier when TLS requirement isn't satisfied This ensures the closure reason is accurate and not reported as an authentication or other problem --- plugins/mod_s2s/mod_s2s.lua | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/plugins/mod_s2s/mod_s2s.lua b/plugins/mod_s2s/mod_s2s.lua index 6419ea67..0fd022cd 100644 --- a/plugins/mod_s2s/mod_s2s.lua +++ b/plugins/mod_s2s/mod_s2s.lua @@ -190,6 +190,13 @@ function module.add_host(module) -- so the stream is ready for stanzas. RFC 6120 Section 4.3 mark_connected(session); return true; + elseif require_encryption and not session.secure then + session.log("warn", "Encrypted server-to-server communication is required but was not offered by %s", session.to_host); + session:close({ + condition = "policy-violation", + text = "Encrypted server-to-server communication is required but was not offered", + }, nil, "Could not establish encrypted connection to remote server"); + return false; elseif not session.dialback_verifying then session.log("warn", "No SASL EXTERNAL offer and Dialback doesn't seem to be enabled, giving up"); session:close({ -- cgit v1.2.3 From 53cde4a8a80379f244e09332114ea51964e172e1 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 28 Nov 2019 18:57:17 +0100 Subject: mod_s2s_bidi: Ignore unencrypted connections if s2s_require_encryption is set Prevents some weirdness in cases where no authentication is done --- plugins/mod_s2s_bidi.lua | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/plugins/mod_s2s_bidi.lua b/plugins/mod_s2s_bidi.lua index 67a48d8d..28e047de 100644 --- a/plugins/mod_s2s_bidi.lua +++ b/plugins/mod_s2s_bidi.lua @@ -10,15 +10,17 @@ local st = require "util.stanza"; local xmlns_bidi_feature = "urn:xmpp:features:bidi" local xmlns_bidi = "urn:xmpp:bidi"; +local require_encryption = module:get_option_boolean("s2s_require_encryption", false); + module:hook("s2s-stream-features", function(event) local origin, features = event.origin, event.features; - if origin.type == "s2sin_unauthed" then + if origin.type == "s2sin_unauthed" and (not require_encryption or origin.secure) then features:tag("bidi", { xmlns = xmlns_bidi_feature }):up(); end end); module:hook_tag("http://etherx.jabber.org/streams", "features", function (session, stanza) - if session.type == "s2sout_unauthed" then + if session.type == "s2sout_unauthed" and (not require_encryption or session.secure) then local bidi = stanza:get_child("bidi", xmlns_bidi_feature); if bidi then session.incoming = true; @@ -29,7 +31,7 @@ module:hook_tag("http://etherx.jabber.org/streams", "features", function (sessio end, 200); module:hook_tag("urn:xmpp:bidi", "bidi", function(session) - if session.type == "s2sin_unauthed" then + if session.type == "s2sin_unauthed" and (not require_encryption or session.secure) then session.log("debug", "Requested bidirectional stream"); session.outgoing = true; return true; -- cgit v1.2.3 From 35d07425e3e09a6d6ec4ca997271bf39f59f15f6 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 29 Nov 2019 18:15:23 +0100 Subject: mod_s2s: Prevent unhandled stanza handler from complaining about stream features on aborted connections I have no idea why I wrote return false in e5945fb5b71f --- plugins/mod_s2s/mod_s2s.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/mod_s2s/mod_s2s.lua b/plugins/mod_s2s/mod_s2s.lua index 0fd022cd..7cd90a84 100644 --- a/plugins/mod_s2s/mod_s2s.lua +++ b/plugins/mod_s2s/mod_s2s.lua @@ -196,14 +196,14 @@ function module.add_host(module) condition = "policy-violation", text = "Encrypted server-to-server communication is required but was not offered", }, nil, "Could not establish encrypted connection to remote server"); - return false; + return true; elseif not session.dialback_verifying then session.log("warn", "No SASL EXTERNAL offer and Dialback doesn't seem to be enabled, giving up"); session:close({ condition = "unsupported-feature", text = "No viable authentication method offered", }, nil, "No viable authentication method offered by remote server"); - return false; + return true; end end, -1); end -- cgit v1.2.3 From 6fd9868ed5e4ea8fd8521db50d863131d8d95a88 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 29 Nov 2019 21:30:08 +0100 Subject: mod_http: Log served URLs at 'info' level These are similar to the "activated service" messages from portmanager and similarily useful for the service admin to know even if they're not debugging anything. --- plugins/mod_http.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_http.lua b/plugins/mod_http.lua index 081aa7e9..e6ef89f5 100644 --- a/plugins/mod_http.lua +++ b/plugins/mod_http.lua @@ -173,7 +173,7 @@ function module.add_host(module) end local services = portmanager.get_active_services(); if services:get("https") or services:get("http") then - module:log("debug", "Serving '%s' at %s", app_name, module:http_url(app_name, app_path)); + module:log("info", "Serving '%s' at %s", app_name, module:http_url(app_name, app_path)); else module:log("warn", "Not listening on any ports, '%s' will be unreachable", app_name); end -- cgit v1.2.3 From 593c04436f5052d8ed201dc6a2f29e52ff5ab622 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 21 Nov 2019 00:16:20 +0100 Subject: mod_admin_telnet: Display ALPN in show_tls() if supported and available --- plugins/mod_admin_telnet.lua | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index cef79d25..f3ab9597 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -590,6 +590,12 @@ local function tls_info(session, line) line[#line+1] = ("(SNI:%q)"):format(name); end end + if sock.getalpn then + local proto = sock:getalpn(); + if proto then + line[#line+1] = ("(ALPN:%q)"):format(proto); + end + end else line[#line+1] = "(insecure)"; end -- cgit v1.2.3 From b65b591c7a19f17f73097e3120f0d13c7dd49886 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 29 Nov 2019 23:22:29 +0100 Subject: core.portmanager: Don't set the first TLS context with a cert as main context Don't think this works and it's apparently acceptable to require SNI these days. --- core/portmanager.lua | 4 ---- 1 file changed, 4 deletions(-) diff --git a/core/portmanager.lua b/core/portmanager.lua index 55868c34..e94720dd 100644 --- a/core/portmanager.lua +++ b/core/portmanager.lua @@ -245,10 +245,6 @@ local function add_sni_host(host, service) local ssl, err, cfg = certmanager.create_context(host, "server"); if ssl then active_service.server.hosts[host] = ssl; - if not active_service.tls_cfg.certificate then - active_service.server.tls_ctx = ssl; - active_service.tls_cfg = cfg; - end else log("error", "err = %q", err); end -- cgit v1.2.3 From 1f33d9c6bb1310ba153580cfdb8d1d36a838f2b7 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 29 Nov 2019 23:24:14 +0100 Subject: core.portmanager: Fix TLS context inheritance for SNI hosts (completes SNI support) --- core/certmanager.lua | 1 + core/portmanager.lua | 15 +++++---------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/core/certmanager.lua b/core/certmanager.lua index b20a0cdb..663cebb4 100644 --- a/core/certmanager.lua +++ b/core/certmanager.lua @@ -252,4 +252,5 @@ return { create_context = create_context; reload_ssl_config = reload_ssl_config; find_cert = find_cert; + find_host_cert = find_host_cert; }; diff --git a/core/portmanager.lua b/core/portmanager.lua index e94720dd..fced3f8f 100644 --- a/core/portmanager.lua +++ b/core/portmanager.lua @@ -230,19 +230,14 @@ end -- Event handlers local function add_sni_host(host, service) - -- local global_ssl_config = config.get(host, "ssl") or {}; for name, interface, port, n, active_service --luacheck: ignore 213 in active_services:iter(service, nil, nil, nil) do if active_service.server.hosts and active_service.tls_cfg then - -- local config_prefix = (active_service.config_prefix or name).."_"; - -- if config_prefix == "_" then - -- config_prefix = ""; - -- end - -- local prefix_ssl_config = config.get(host, config_prefix.."ssl") or global_ssl_config; - -- FIXME only global 'ssl' settings are mixed in here - -- TODO per host and per service settings should be merged in, - -- without overriding the per-host certificate - local ssl, err, cfg = certmanager.create_context(host, "server"); + local config_prefix = (active_service.config_prefix or name).."_"; + if config_prefix == "_" then config_prefix = ""; end + local prefix_ssl_config = config.get(host, config_prefix.."ssl"); + local autocert = certmanager.find_host_cert(host); + local ssl, err, cfg = certmanager.create_context(host, "server", prefix_ssl_config, autocert, active_service.tls_cfg); if ssl then active_service.server.hosts[host] = ssl; else -- cgit v1.2.3 From e354f1abd8ca2fb0fc2c25c1c61c1866cc41d5eb Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 29 Nov 2019 23:25:59 +0100 Subject: net.http: Set ALPN on requests Shouldn't hurt. Revert if it turns out it does. Supported in LuaSec 0.8. Should be ignored otherwise. --- net/http.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/http.lua b/net/http.lua index 055fc936..8ca30db2 100644 --- a/net/http.lua +++ b/net/http.lua @@ -285,7 +285,7 @@ local function new(options) end local default_http = new({ - sslctx = { mode = "client", protocol = "sslv23", options = { "no_sslv2", "no_sslv3" } }; + sslctx = { mode = "client", protocol = "sslv23", options = { "no_sslv2", "no_sslv3" }, alpn = "http/1.1" }; suppress_errors = true; }); -- cgit v1.2.3 From 0fdb85997abd2be59252595b1fec9e46389da586 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 29 Nov 2019 23:27:51 +0100 Subject: mod_net_multiplex: Add support for using ALPN Potentially a bit more efficient since it can jump to the selected protocol on connect instead of waiting for some data to look at. Adds a 'protocol' field to net providers for this purpose. --- CHANGES | 1 + doc/doap.xml | 1 + plugins/mod_c2s.lua | 1 + plugins/mod_http.lua | 1 + plugins/mod_net_multiplex.lua | 40 +++++++++++++++++++++++++++++++++++++--- plugins/mod_s2s/mod_s2s.lua | 1 + 6 files changed, 42 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index e451d3f5..2e6caf0d 100644 --- a/CHANGES +++ b/CHANGES @@ -13,6 +13,7 @@ TRUNK - Bi-directional server-to-server (XEP-0288) - Built-in HTTP server now handles HEAD requests - MUC presence broadcast controls +- ALPN support in mod\_net\_multiplex 0.11.0 ====== diff --git a/doc/doap.xml b/doc/doap.xml index 673e0c23..1633e2f3 100644 --- a/doc/doap.xml +++ b/doc/doap.xml @@ -51,6 +51,7 @@ + diff --git a/plugins/mod_c2s.lua b/plugins/mod_c2s.lua index aec0370d..aecf2210 100644 --- a/plugins/mod_c2s.lua +++ b/plugins/mod_c2s.lua @@ -374,6 +374,7 @@ module:provides("net", { default_port = 5222; encryption = "starttls"; multiplex = { + protocol = "xmpp-client"; pattern = "^<.*:stream.*%sxmlns%s*=%s*(['\"])jabber:client%1.*>"; }; }); diff --git a/plugins/mod_http.lua b/plugins/mod_http.lua index e6ef89f5..c3e19bb3 100644 --- a/plugins/mod_http.lua +++ b/plugins/mod_http.lua @@ -242,6 +242,7 @@ module:provides("net", { default_port = 5281; encryption = "ssl"; multiplex = { + protocol = "http/1.1"; pattern = "^[A-Z]"; }; }); diff --git a/plugins/mod_net_multiplex.lua b/plugins/mod_net_multiplex.lua index 8ef77883..2bf1f88d 100644 --- a/plugins/mod_net_multiplex.lua +++ b/plugins/mod_net_multiplex.lua @@ -1,22 +1,38 @@ module:set_global(); +local array = require "util.array"; local max_buffer_len = module:get_option_number("multiplex_buffer_size", 1024); local portmanager = require "core.portmanager"; local available_services = {}; +local service_by_protocol = {}; +local available_protocols = array(); local function add_service(service) local multiplex_pattern = service.multiplex and service.multiplex.pattern; + local protocol_name = service.multiplex and service.multiplex.protocol; + if protocol_name then + module:log("debug", "Adding multiplex service %q with protocol %q", service.name, protocol_name); + service_by_protocol[protocol_name] = service; + available_protocols:push(protocol_name); + end if multiplex_pattern then module:log("debug", "Adding multiplex service %q with pattern %q", service.name, multiplex_pattern); available_services[service] = multiplex_pattern; - else + elseif not protocol_name then module:log("debug", "Service %q is not multiplex-capable", service.name); end + module:log("info", "available_protocols = %q", available_protocols); end module:hook("service-added", function (event) add_service(event.service); end); -module:hook("service-removed", function (event) available_services[event.service] = nil; end); +module:hook("service-removed", function (event) + available_services[event.service] = nil; + if event.service.multiplex and event.service.multiplex.protocol then + available_protocols:filter(function (p) return p ~= event.service.multiplex.protocol end); + service_by_protocol[event.service.multiplex.protocol] = nil; + end +end); for _, services in pairs(portmanager.get_registered_services()) do for _, service in ipairs(services) do @@ -28,7 +44,20 @@ local buffers = {}; local listener = { default_mode = "*a" }; -function listener.onconnect() +function listener.onconnect(conn) + local sock = conn:socket(); + if sock.getalpn then + local selected_proto = sock:getalpn(); + module:log("debug", "ALPN selected is %s", selected_proto); + local service = service_by_protocol[selected_proto]; + if service then + module:log("debug", "Routing incoming connection to %s", service.name); + local next_listener = service.listener; + conn:setlistener(next_listener); + local onconnect = next_listener.onconnect; + if onconnect then return onconnect(conn) end + end + end end function listener.onincoming(conn, data) @@ -68,5 +97,10 @@ module:provides("net", { name = "multiplex_ssl"; config_prefix = "ssl"; encryption = "ssl"; + ssl_config = { + alpn = function () + return available_protocols; + end; + }; listener = listener; }); diff --git a/plugins/mod_s2s/mod_s2s.lua b/plugins/mod_s2s/mod_s2s.lua index 7cd90a84..7f6546e9 100644 --- a/plugins/mod_s2s/mod_s2s.lua +++ b/plugins/mod_s2s/mod_s2s.lua @@ -796,6 +796,7 @@ module:provides("net", { verify = { "peer", "client_once", }; }; multiplex = { + protocol = "xmpp-server"; pattern = "^<.*:stream.*%sxmlns%s*=%s*(['\"])jabber:server%1.*>"; }; }); -- cgit v1.2.3 From a95af210d143f3c16c6512a53c154a80baa4f98d Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 29 Nov 2019 23:41:41 +0100 Subject: mod_net_multiplex: Tweak debug logging for ALPN case --- plugins/mod_net_multiplex.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/mod_net_multiplex.lua b/plugins/mod_net_multiplex.lua index 2bf1f88d..3de81ddc 100644 --- a/plugins/mod_net_multiplex.lua +++ b/plugins/mod_net_multiplex.lua @@ -48,10 +48,9 @@ function listener.onconnect(conn) local sock = conn:socket(); if sock.getalpn then local selected_proto = sock:getalpn(); - module:log("debug", "ALPN selected is %s", selected_proto); local service = service_by_protocol[selected_proto]; if service then - module:log("debug", "Routing incoming connection to %s", service.name); + module:log("debug", "Routing incoming connection to %s based on ALPN %q", service.name, selected_proto); local next_listener = service.listener; conn:setlistener(next_listener); local onconnect = next_listener.onconnect; -- cgit v1.2.3 From b1aadaced9b8012b55c8b9ee158bfaf6f24ea147 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 30 Nov 2019 14:00:13 +0100 Subject: tests: Disable s2s in scansion tests These are all c2s tests, no need to have s2s enabled. --- spec/scansion/prosody.cfg.lua | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/spec/scansion/prosody.cfg.lua b/spec/scansion/prosody.cfg.lua index 1c359b27..3f804c4e 100644 --- a/spec/scansion/prosody.cfg.lua +++ b/spec/scansion/prosody.cfg.lua @@ -9,7 +9,7 @@ modules_enabled = { "roster"; -- Allow users to have a roster. Recommended ;) "saslauth"; -- Authentication for clients and servers. Recommended if you want to log in. --"tls"; -- Add support for secure TLS on c2s/s2s connections - "dialback"; -- s2s dialback support + --"dialback"; -- s2s dialback support "disco"; -- Service discovery -- Not essential, but recommended @@ -53,6 +53,9 @@ modules_enabled = { --"scansion_record"; -- Records things that happen in scansion test case format } +modules_disabled = { + "s2s"; +} certificate = "certs" allow_registration = false -- cgit v1.2.3 From 46142132167a7d73193739ba7a832bec610223b0 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 30 Nov 2019 16:40:04 +0100 Subject: core.portmanager: Complete error message for SNI TLS context problems --- core/portmanager.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/portmanager.lua b/core/portmanager.lua index fced3f8f..99656e3e 100644 --- a/core/portmanager.lua +++ b/core/portmanager.lua @@ -241,7 +241,7 @@ local function add_sni_host(host, service) if ssl then active_service.server.hosts[host] = ssl; else - log("error", "err = %q", err); + log("error", "Error creating TLS context for SNI host %s: %s", host, err); end end end -- cgit v1.2.3 From c02ddf92ec53dd2c0473ef98427ceb4020d1f001 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 30 Nov 2019 19:34:40 +0100 Subject: mod_carbons: Improve performance by delaying creation of carbon payload If there are no other sessions which also enabled carbons then the carbons wrapper is not used and the potentially expensive clone operation was a waste of cycles. --- plugins/mod_carbons.lua | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/plugins/mod_carbons.lua b/plugins/mod_carbons.lua index 1dcd4a07..0f8c7c60 100644 --- a/plugins/mod_carbons.lua +++ b/plugins/mod_carbons.lua @@ -74,17 +74,7 @@ local function message_handler(event, c2s) return end - -- Create the carbon copy and wrap it as per the Stanza Forwarding XEP - local copy = st.clone(stanza); - if c2s and not orig_to then - stanza.attr.to = bare_from; - end - copy.attr.xmlns = "jabber:client"; - local carbon = st.message{ from = bare_jid, type = orig_type, } - :tag(c2s and "sent" or "received", { xmlns = xmlns_carbons }) - :tag("forwarded", { xmlns = xmlns_forward }) - :add_child(copy):reset(); - + local carbon; user_sessions = user_sessions and user_sessions.sessions; for _, session in pairs(user_sessions) do -- Carbons are sent to resources that have enabled it @@ -93,6 +83,20 @@ local function message_handler(event, c2s) and session ~= target_session -- and isn't among the top resources that would receive the message per standard routing rules and (c2s or session.priority ~= top_priority) then + if not carbon then + -- Create the carbon copy and wrap it as per the Stanza Forwarding XEP + local copy = st.clone(stanza); + if c2s and not orig_to then + stanza.attr.to = bare_from; + end + copy.attr.xmlns = "jabber:client"; + carbon = st.message{ from = bare_jid, type = orig_type, } + :tag(c2s and "sent" or "received", { xmlns = xmlns_carbons }) + :tag("forwarded", { xmlns = xmlns_forward }) + :add_child(copy):reset(); + + end + carbon.attr.to = session.full_jid; module:log("debug", "Sending carbon to %s", session.full_jid); session.send(carbon); -- cgit v1.2.3 From 3d63c139e666e1a89f5caa0eb96afc20ac43fc2a Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 30 Nov 2019 21:56:21 +0100 Subject: mod_admin_telnet: Sort hosts Groups by domain in DNS hierarchy order or something. Why not split on '.' you ask? Well becasue that's not what I typed here. Also "[^.]" is longer than "%P". --- plugins/mod_admin_telnet.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index f3ab9597..8427f811 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -964,11 +964,15 @@ function def_env.host:deactivate(hostname, reason) return hostmanager.deactivate(hostname, reason); end +local function compare_hosts(a, b) + return a:gsub("%P", string.reverse):reverse() < b:gsub("%P", string.reverse):reverse(); +end + function def_env.host:list() local print = self.session.print; local i = 0; local type; - for host, host_session in iterators.sorted_pairs(prosody.hosts) do + for host, host_session in iterators.sorted_pairs(prosody.hosts, compare_hosts) do i = i + 1; type = host_session.type; if type == "local" then -- cgit v1.2.3 From 39cc0ec4510c0e3d80324c7085fa1a938c1f30f8 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 30 Nov 2019 23:29:15 +0100 Subject: mod_s2s: Improve log message about forbidding insecure connections This new wording generator is nice. --- plugins/mod_s2s/mod_s2s.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_s2s/mod_s2s.lua b/plugins/mod_s2s/mod_s2s.lua index 7f6546e9..7700db85 100644 --- a/plugins/mod_s2s/mod_s2s.lua +++ b/plugins/mod_s2s/mod_s2s.lua @@ -763,8 +763,8 @@ function check_auth_policy(event) end if must_secure and (session.cert_chain_status ~= "valid" or session.cert_identity_status ~= "valid") then - module:log("warn", "Forbidding insecure connection to/from %s", host or session.ip or "(unknown host)"); local reason = friendly_cert_error(session); + module:log("warn", "Forbidding insecure connection to/from %s because its certificate %s", host or session.ip or "(unknown host)", reason); -- XEP-0178 recommends closing outgoing connections without warning -- but does not give a rationale for this. -- In practice most cases are configuration mistakes or forgotten -- cgit v1.2.3 From 56500e6e054e18fef1ec47f53e7b58bcc0841e95 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 30 Nov 2019 23:33:39 +0100 Subject: mod_s2s: Log from session logger Helps locating all messages related to a specific session --- plugins/mod_s2s/mod_s2s.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_s2s/mod_s2s.lua b/plugins/mod_s2s/mod_s2s.lua index 7700db85..d0176cea 100644 --- a/plugins/mod_s2s/mod_s2s.lua +++ b/plugins/mod_s2s/mod_s2s.lua @@ -764,7 +764,7 @@ function check_auth_policy(event) if must_secure and (session.cert_chain_status ~= "valid" or session.cert_identity_status ~= "valid") then local reason = friendly_cert_error(session); - module:log("warn", "Forbidding insecure connection to/from %s because its certificate %s", host or session.ip or "(unknown host)", reason); + session.log("warn", "Forbidding insecure connection to/from %s because its certificate %s", host or session.ip or "(unknown host)", reason); -- XEP-0178 recommends closing outgoing connections without warning -- but does not give a rationale for this. -- In practice most cases are configuration mistakes or forgotten -- cgit v1.2.3 From 33ba4c2dbad8a92dcd7d9a94cc32bbb82b688b93 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 1 Dec 2019 01:20:34 +0100 Subject: mod_s2s: Improve TLS handshake error messages This should make it clearer that it's about the TLS handshake. Otherwise it's something like "unsupported protocol" or "no shared ciphers" that might not be that obvious. --- plugins/mod_s2s/mod_s2s.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/mod_s2s/mod_s2s.lua b/plugins/mod_s2s/mod_s2s.lua index d0176cea..82f6a95d 100644 --- a/plugins/mod_s2s/mod_s2s.lua +++ b/plugins/mod_s2s/mod_s2s.lua @@ -692,6 +692,10 @@ function listener.ondisconnect(conn, err) 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"); + if session.secure == false and err then + -- TODO util.error-ify this + err = "Error during negotiation of encrypted connection: "..err; + end s2s_destroy_session(session, err); end end -- cgit v1.2.3 From be74ecddca91607c3305ae11c18461935c3d4384 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 1 Dec 2019 01:21:58 +0100 Subject: net.server_select: Remove prefix added to TLS handshaker errors For consistency. None of the other implementations do this. --- net/server_select.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/server_select.lua b/net/server_select.lua index e15f5298..9cd3463e 100644 --- a/net/server_select.lua +++ b/net/server_select.lua @@ -611,7 +611,7 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport coroutine_yield( ) -- handshake not finished end end - err = "ssl handshake error: " .. ( err or "handshake too long" ); + err = ( err or "handshake too long" ); out_put( "server.lua: ", err ); _ = handler and handler:force_close(err) return false, err -- handshake failed -- cgit v1.2.3 From 62b9a7f53fcbce310f19f224edc45418885ee5c0 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 1 Dec 2019 02:04:24 +0100 Subject: mod_net_multiplex: Remove debug message This was something I added during development and set to info level for visibility. --- plugins/mod_net_multiplex.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/mod_net_multiplex.lua b/plugins/mod_net_multiplex.lua index 3de81ddc..849b22ee 100644 --- a/plugins/mod_net_multiplex.lua +++ b/plugins/mod_net_multiplex.lua @@ -23,7 +23,6 @@ local function add_service(service) elseif not protocol_name then module:log("debug", "Service %q is not multiplex-capable", service.name); end - module:log("info", "available_protocols = %q", available_protocols); end module:hook("service-added", function (event) add_service(event.service); end); module:hook("service-removed", function (event) -- cgit v1.2.3 From a62ff5dc64f7034477ca073900e917ca6398c2a2 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 1 Dec 2019 12:19:36 +0100 Subject: mod_s2s: Use stanza type check instead of duck typing --- plugins/mod_s2s/mod_s2s.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_s2s/mod_s2s.lua b/plugins/mod_s2s/mod_s2s.lua index 82f6a95d..64ca7709 100644 --- a/plugins/mod_s2s/mod_s2s.lua +++ b/plugins/mod_s2s/mod_s2s.lua @@ -521,7 +521,7 @@ local function session_close(session, reason, remote_reason, bounce_reason) log("debug", "Disconnecting %s[%s], is: %s", session.host or session.ip or "(unknown host)", session.type, stanza); session.sends2s(stanza); - elseif reason.name then -- a stanza + elseif st.is_stanza(reason) then log("debug", "Disconnecting %s->%s[%s], is: %s", session.from_host or "(unknown host)", session.to_host or "(unknown host)", session.type, reason); -- cgit v1.2.3 From 91415f5a71021cc24053de17fca1c5337ebd7249 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 1 Dec 2019 12:21:26 +0100 Subject: mod_s2s: Refactor stream error handling on close Deduplicates the 3 log calls that log the same thing but subtly differently. The first one would say "Disconnecting localhost" and the last one didn't log the IP. --- plugins/mod_s2s/mod_s2s.lua | 33 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/plugins/mod_s2s/mod_s2s.lua b/plugins/mod_s2s/mod_s2s.lua index 64ca7709..a3eec9f9 100644 --- a/plugins/mod_s2s/mod_s2s.lua +++ b/plugins/mod_s2s/mod_s2s.lua @@ -507,26 +507,21 @@ local function session_close(session, reason, remote_reason, bounce_reason) end if reason then -- nil == no err, initiated by us, false == initiated by remote if type(reason) == "string" then -- assume stream error - log("debug", "Disconnecting %s[%s], is: %s", session.host or session.ip or "(unknown host)", session.type, reason); - session.sends2s(st.stanza("stream:error"):tag(reason, {xmlns = 'urn:ietf:params:xml:ns:xmpp-streams' })); - elseif type(reason) == "table" then - if reason.condition then - local stanza = st.stanza("stream:error"):tag(reason.condition, stream_xmlns_attr):up(); - if reason.text then - stanza:tag("text", stream_xmlns_attr):text(reason.text):up(); - end - if reason.extra then - stanza:add_child(reason.extra); - end - log("debug", "Disconnecting %s[%s], is: %s", - session.host or session.ip or "(unknown host)", session.type, stanza); - session.sends2s(stanza); - elseif st.is_stanza(reason) then - log("debug", "Disconnecting %s->%s[%s], is: %s", - session.from_host or "(unknown host)", session.to_host or "(unknown host)", - session.type, reason); - session.sends2s(reason); + reason = st.stanza("stream:error"):tag(reason, {xmlns = 'urn:ietf:params:xml:ns:xmpp-streams' }); + elseif type(reason) == "table" and not st.is_stanza(reason) then + local stanza = st.stanza("stream:error"):tag(reason.condition or "undefined-condition", stream_xmlns_attr):up(); + if reason.text then + stanza:tag("text", stream_xmlns_attr):text(reason.text):up(); end + if reason.extra then + stanza:add_child(reason.extra); + end + end + if st.is_stanza(reason) then + -- to and from are never unknown on outgoing connections + log("debug", "Disconnecting %s->%s[%s], is: %s", + session.from_host or "(unknown host)" or session.ip, session.to_host or "(unknown host)", session.type, reason); + session.sends2s(reason); end end -- cgit v1.2.3 From d209c0c6777ae4cdf2c427cd53bdb4bcb79f85b3 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Sun, 1 Dec 2019 23:34:45 +0100 Subject: =?UTF-8?q?util.encodings:=20Don=E2=80=99t=20export=20unneeded=20s?= =?UTF-8?q?ymbols?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reduces the binary size from 22704 B to 18592 B. --- util-src/encodings.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/util-src/encodings.c b/util-src/encodings.c index 367182b6..6f975dfc 100644 --- a/util-src/encodings.c +++ b/util-src/encodings.c @@ -216,7 +216,7 @@ static const char *utf8_decode(const char *o, int *val) { * Check that a string is valid UTF-8 * Returns NULL if not */ -const char *check_utf8(lua_State *L, int idx, size_t *l) { +static const char *check_utf8(lua_State *L, int idx, size_t *l) { size_t pos, len; const char *s = luaL_checklstring(L, idx, &len); pos = 0; @@ -323,12 +323,12 @@ static int icu_stringprep_prep(lua_State *L, const UStringPrepProfile *profile) } } -UStringPrepProfile *icu_nameprep; -UStringPrepProfile *icu_nodeprep; -UStringPrepProfile *icu_resourceprep; -UStringPrepProfile *icu_saslprep; -USpoofChecker *icu_spoofcheck; -UIDNA *icu_idna2008; +static UStringPrepProfile *icu_nameprep; +static UStringPrepProfile *icu_nodeprep; +static UStringPrepProfile *icu_resourceprep; +static UStringPrepProfile *icu_saslprep; +static USpoofChecker *icu_spoofcheck; +static UIDNA *icu_idna2008; #if (U_ICU_VERSION_MAJOR_NUM < 58) /* COMPAT */ @@ -336,7 +336,7 @@ UIDNA *icu_idna2008; #endif /* initialize global ICU stringprep profiles */ -void init_icu(void) { +static void init_icu(void) { UErrorCode err = U_ZERO_ERROR; utrace_setLevel(UTRACE_VERBOSE); icu_nameprep = usprep_openByType(USPREP_RFC3491_NAMEPREP, &err); -- cgit v1.2.3 From 8613dc17392a43ab434c6e5f8b431cb17d2cb9ea Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Sun, 1 Dec 2019 23:34:49 +0100 Subject: util.encodings: Remove redundant cast --- util-src/encodings.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util-src/encodings.c b/util-src/encodings.c index 6f975dfc..996a6d4c 100644 --- a/util-src/encodings.c +++ b/util-src/encodings.c @@ -371,7 +371,7 @@ static void init_icu(void) { icu_idna2008 = uidna_openUTS46(options, &err); if(U_FAILURE(err)) { - fprintf(stderr, "[c] util.encodings: error: %s\n", u_errorName((UErrorCode)err)); + fprintf(stderr, "[c] util.encodings: error: %s\n", u_errorName(err)); } } -- cgit v1.2.3 From a149dda0e3b1c4a37ba1e6019930952374a3992c Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Sun, 1 Dec 2019 20:25:20 +0100 Subject: util.*.c: Add static qualifiers everywhere --- util-src/hashes.c | 4 ++-- util-src/net.c | 2 +- util-src/pposix.c | 52 +++++++++++++++++++++++++-------------------------- util-src/ringbuffer.c | 31 +++++++++++++----------------- util-src/signal.c | 4 ++-- util-src/time.c | 6 +++--- 6 files changed, 47 insertions(+), 52 deletions(-) diff --git a/util-src/hashes.c b/util-src/hashes.c index 4c48b26f..51c7611c 100644 --- a/util-src/hashes.c +++ b/util-src/hashes.c @@ -35,8 +35,8 @@ typedef unsigned __int32 uint32_t; #define HMAC_IPAD 0x36363636 #define HMAC_OPAD 0x5c5c5c5c -const char *hex_tab = "0123456789abcdef"; -void toHex(const unsigned char *in, int length, unsigned char *out) { +static const char *hex_tab = "0123456789abcdef"; +static void toHex(const unsigned char *in, int length, unsigned char *out) { int i; for(i = 0; i < length; i++) { diff --git a/util-src/net.c b/util-src/net.c index 5f706d81..d9c6b914 100644 --- a/util-src/net.c +++ b/util-src/net.c @@ -36,7 +36,7 @@ /* Enumerate all locally configured IP addresses */ -const char *const type_strings[] = { +static const char *const type_strings[] = { "both", "ipv4", "ipv6", diff --git a/util-src/pposix.c b/util-src/pposix.c index d64b6fc6..2ebf0444 100644 --- a/util-src/pposix.c +++ b/util-src/pposix.c @@ -137,7 +137,7 @@ static int lc_daemonize(lua_State *L) { /* Syslog support */ -const char *const facility_strings[] = { +static const char *const facility_strings[] = { "auth", #if !(defined(sun) || defined(__sun)) "authpriv", @@ -163,7 +163,7 @@ const char *const facility_strings[] = { "uucp", NULL }; -int facility_constants[] = { +static int facility_constants[] = { LOG_AUTH, #if !(defined(sun) || defined(__sun)) LOG_AUTHPRIV, @@ -199,9 +199,9 @@ int facility_constants[] = { constant. " -- syslog manpage */ -char *syslog_ident = NULL; +static char *syslog_ident = NULL; -int lc_syslog_open(lua_State *L) { +static int lc_syslog_open(lua_State *L) { int facility = luaL_checkoption(L, 2, "daemon", facility_strings); facility = facility_constants[facility]; @@ -217,7 +217,7 @@ int lc_syslog_open(lua_State *L) { return 0; } -const char *const level_strings[] = { +static const char *const level_strings[] = { "debug", "info", "notice", @@ -225,7 +225,7 @@ const char *const level_strings[] = { "error", NULL }; -int level_constants[] = { +static int level_constants[] = { LOG_DEBUG, LOG_INFO, LOG_NOTICE, @@ -233,7 +233,7 @@ int level_constants[] = { LOG_CRIT, -1 }; -int lc_syslog_log(lua_State *L) { +static int lc_syslog_log(lua_State *L) { int level = level_constants[luaL_checkoption(L, 1, "notice", level_strings)]; if(lua_gettop(L) == 3) { @@ -245,7 +245,7 @@ int lc_syslog_log(lua_State *L) { return 0; } -int lc_syslog_close(lua_State *L) { +static int lc_syslog_close(lua_State *L) { (void)L; closelog(); @@ -257,7 +257,7 @@ int lc_syslog_close(lua_State *L) { return 0; } -int lc_syslog_setmask(lua_State *L) { +static int lc_syslog_setmask(lua_State *L) { int level_idx = luaL_checkoption(L, 1, "notice", level_strings); int mask = 0; @@ -271,24 +271,24 @@ int lc_syslog_setmask(lua_State *L) { /* getpid */ -int lc_getpid(lua_State *L) { +static int lc_getpid(lua_State *L) { lua_pushinteger(L, getpid()); return 1; } /* UID/GID functions */ -int lc_getuid(lua_State *L) { +static int lc_getuid(lua_State *L) { lua_pushinteger(L, getuid()); return 1; } -int lc_getgid(lua_State *L) { +static int lc_getgid(lua_State *L) { lua_pushinteger(L, getgid()); return 1; } -int lc_setuid(lua_State *L) { +static int lc_setuid(lua_State *L) { int uid = -1; if(lua_gettop(L) < 1) { @@ -346,7 +346,7 @@ int lc_setuid(lua_State *L) { return 2; } -int lc_setgid(lua_State *L) { +static int lc_setgid(lua_State *L) { int gid = -1; if(lua_gettop(L) < 1) { @@ -404,7 +404,7 @@ int lc_setgid(lua_State *L) { return 2; } -int lc_initgroups(lua_State *L) { +static int lc_initgroups(lua_State *L) { int ret; gid_t gid; struct passwd *p; @@ -468,7 +468,7 @@ int lc_initgroups(lua_State *L) { return 2; } -int lc_umask(lua_State *L) { +static int lc_umask(lua_State *L) { char old_mode_string[7]; mode_t old_mode = umask(strtoul(luaL_checkstring(L, 1), NULL, 8)); @@ -479,7 +479,7 @@ int lc_umask(lua_State *L) { return 1; } -int lc_mkdir(lua_State *L) { +static int lc_mkdir(lua_State *L) { int ret = mkdir(luaL_checkstring(L, 1), S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IXOTH); /* mode 775 */ @@ -504,7 +504,7 @@ int lc_mkdir(lua_State *L) { * Example usage: * pposix.setrlimit("NOFILE", 1000, 2000) */ -int string2resource(const char *s) { +static int string2resource(const char *s) { if(!strcmp(s, "CORE")) { return RLIMIT_CORE; } @@ -554,7 +554,7 @@ int string2resource(const char *s) { return -1; } -rlim_t arg_to_rlimit(lua_State *L, int idx, rlim_t current) { +static rlim_t arg_to_rlimit(lua_State *L, int idx, rlim_t current) { switch(lua_type(L, idx)) { case LUA_TSTRING: @@ -575,7 +575,7 @@ rlim_t arg_to_rlimit(lua_State *L, int idx, rlim_t current) { } } -int lc_setrlimit(lua_State *L) { +static int lc_setrlimit(lua_State *L) { struct rlimit lim; int arguments = lua_gettop(L); int rid = -1; @@ -614,7 +614,7 @@ int lc_setrlimit(lua_State *L) { return 1; } -int lc_getrlimit(lua_State *L) { +static int lc_getrlimit(lua_State *L) { int arguments = lua_gettop(L); const char *resource = NULL; int rid = -1; @@ -659,13 +659,13 @@ int lc_getrlimit(lua_State *L) { return 3; } -int lc_abort(lua_State *L) { +static int lc_abort(lua_State *L) { (void)L; abort(); return 0; } -int lc_uname(lua_State *L) { +static int lc_uname(lua_State *L) { struct utsname uname_info; if(uname(&uname_info) != 0) { @@ -692,7 +692,7 @@ int lc_uname(lua_State *L) { return 1; } -int lc_setenv(lua_State *L) { +static int lc_setenv(lua_State *L) { const char *var = luaL_checkstring(L, 1); const char *value; @@ -721,7 +721,7 @@ int lc_setenv(lua_State *L) { } #ifdef WITH_MALLINFO -int lc_meminfo(lua_State *L) { +static int lc_meminfo(lua_State *L) { struct mallinfo info = mallinfo(); lua_createtable(L, 0, 5); /* This is the total size of memory allocated with sbrk by malloc, in bytes. */ @@ -749,7 +749,7 @@ int lc_meminfo(lua_State *L) { * Attempt to allocate space first * Truncate to original size on failure */ -int lc_atomic_append(lua_State *L) { +static int lc_atomic_append(lua_State *L) { int err; size_t len; diff --git a/util-src/ringbuffer.c b/util-src/ringbuffer.c index 8f9013f7..3e17cdf5 100644 --- a/util-src/ringbuffer.c +++ b/util-src/ringbuffer.c @@ -15,23 +15,18 @@ typedef struct { char buffer[]; } ringbuffer; -char readchar(ringbuffer *b) { - b->blen--; - return b->buffer[(b->rpos++) % b->alen]; -} - -void writechar(ringbuffer *b, char c) { +static void writechar(ringbuffer *b, char c) { b->blen++; b->buffer[(b->wpos++) % b->alen] = c; } /* make sure position counters stay within the allocation */ -void modpos(ringbuffer *b) { +static void modpos(ringbuffer *b) { b->rpos = b->rpos % b->alen; b->wpos = b->wpos % b->alen; } -int find(ringbuffer *b, const char *s, size_t l) { +static int find(ringbuffer *b, const char *s, size_t l) { size_t i, j; int m; @@ -64,7 +59,7 @@ int find(ringbuffer *b, const char *s, size_t l) { * Find first position of a substring in buffer * (buffer, string) -> number */ -int rb_find(lua_State *L) { +static int rb_find(lua_State *L) { size_t l, m; ringbuffer *b = luaL_checkudata(L, 1, "ringbuffer_mt"); const char *s = luaL_checklstring(L, 2, &l); @@ -82,7 +77,7 @@ int rb_find(lua_State *L) { * Move read position forward without returning the data * (buffer, number) -> boolean */ -int rb_discard(lua_State *L) { +static int rb_discard(lua_State *L) { ringbuffer *b = luaL_checkudata(L, 1, "ringbuffer_mt"); size_t r = luaL_checkinteger(L, 2); @@ -103,7 +98,7 @@ int rb_discard(lua_State *L) { * Read bytes from buffer * (buffer, number, boolean?) -> string */ -int rb_read(lua_State *L) { +static int rb_read(lua_State *L) { ringbuffer *b = luaL_checkudata(L, 1, "ringbuffer_mt"); size_t r = luaL_checkinteger(L, 2); int peek = lua_toboolean(L, 3); @@ -135,7 +130,7 @@ int rb_read(lua_State *L) { * Read buffer until first occurrence of a substring * (buffer, string) -> string */ -int rb_readuntil(lua_State *L) { +static int rb_readuntil(lua_State *L) { size_t l, m; ringbuffer *b = luaL_checkudata(L, 1, "ringbuffer_mt"); const char *s = luaL_checklstring(L, 2, &l); @@ -154,7 +149,7 @@ int rb_readuntil(lua_State *L) { * Write bytes into the buffer * (buffer, string) -> integer */ -int rb_write(lua_State *L) { +static int rb_write(lua_State *L) { size_t l, w = 0; ringbuffer *b = luaL_checkudata(L, 1, "ringbuffer_mt"); const char *s = luaL_checklstring(L, 2, &l); @@ -177,31 +172,31 @@ int rb_write(lua_State *L) { return 1; } -int rb_tostring(lua_State *L) { +static int rb_tostring(lua_State *L) { ringbuffer *b = luaL_checkudata(L, 1, "ringbuffer_mt"); lua_pushfstring(L, "ringbuffer: %p %d/%d", b, b->blen, b->alen); return 1; } -int rb_length(lua_State *L) { +static int rb_length(lua_State *L) { ringbuffer *b = luaL_checkudata(L, 1, "ringbuffer_mt"); lua_pushinteger(L, b->blen); return 1; } -int rb_size(lua_State *L) { +static int rb_size(lua_State *L) { ringbuffer *b = luaL_checkudata(L, 1, "ringbuffer_mt"); lua_pushinteger(L, b->alen); return 1; } -int rb_free(lua_State *L) { +static int rb_free(lua_State *L) { ringbuffer *b = luaL_checkudata(L, 1, "ringbuffer_mt"); lua_pushinteger(L, b->alen - b->blen); return 1; } -int rb_new(lua_State *L) { +static int rb_new(lua_State *L) { size_t size = luaL_optinteger(L, 1, sysconf(_SC_PAGESIZE)); ringbuffer *b = lua_newuserdata(L, sizeof(ringbuffer) + size); diff --git a/util-src/signal.c b/util-src/signal.c index c696a3a2..835a601a 100644 --- a/util-src/signal.c +++ b/util-src/signal.c @@ -164,8 +164,8 @@ static lua_Hook Hsig = NULL; static int Hmask = 0; static int Hcount = 0; -int signals[MAX_PENDING_SIGNALS]; -int nsig = 0; +static int signals[MAX_PENDING_SIGNALS]; +static int nsig = 0; static void sighook(lua_State *L, lua_Debug *ar) { (void)ar; diff --git a/util-src/time.c b/util-src/time.c index bc6b5b1c..afef3df5 100644 --- a/util-src/time.c +++ b/util-src/time.c @@ -5,18 +5,18 @@ #include #include -lua_Number tv2number(struct timespec *tv) { +static lua_Number tv2number(struct timespec *tv) { return tv->tv_sec + tv->tv_nsec * 1e-9; } -int lc_time_realtime(lua_State *L) { +static int lc_time_realtime(lua_State *L) { struct timespec t; clock_gettime(CLOCK_REALTIME, &t); lua_pushnumber(L, tv2number(&t)); return 1; } -int lc_time_monotonic(lua_State *L) { +static int lc_time_monotonic(lua_State *L) { struct timespec t; clock_gettime(CLOCK_MONOTONIC, &t); lua_pushnumber(L, tv2number(&t)); -- cgit v1.2.3 From 4053cdb8483f896e9d36d360e4df9735b9ea00ad Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 2 Dec 2019 16:00:16 +0100 Subject: mod_saslauth: Advertise correct set of mechanisms Mistakenly iterates over the set of all supported mechanisms instead of the one without insecure mechanisms if the connection is insecure. Not a problem if c2s_require_encryption is true Introduced in 56a0f68b7797 --- plugins/mod_saslauth.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_saslauth.lua b/plugins/mod_saslauth.lua index 9e9091d3..30d74b9e 100644 --- a/plugins/mod_saslauth.lua +++ b/plugins/mod_saslauth.lua @@ -286,7 +286,7 @@ module:hook("stream-features", function(event) if not usable_mechanisms:empty() then log("debug", "Offering usable mechanisms: %s", usable_mechanisms); - for mechanism in available_mechanisms do + for mechanism in usable_mechanisms do mechanisms:tag("mechanism"):text(mechanism):up(); end features:add_child(mechanisms); -- cgit v1.2.3 From 9f46aa4d48b2d43b1529f980dda2439e865cd908 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 3 Dec 2019 17:29:43 +0100 Subject: mod_s2s: Fix mistake in 28755107c2f4 --- plugins/mod_s2s/mod_s2s.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/mod_s2s/mod_s2s.lua b/plugins/mod_s2s/mod_s2s.lua index a3eec9f9..8663702f 100644 --- a/plugins/mod_s2s/mod_s2s.lua +++ b/plugins/mod_s2s/mod_s2s.lua @@ -516,6 +516,7 @@ local function session_close(session, reason, remote_reason, bounce_reason) if reason.extra then stanza:add_child(reason.extra); end + reason = stanza; end if st.is_stanza(reason) then -- to and from are never unknown on outgoing connections -- cgit v1.2.3 From 2d5eaff749da055df6a29f20a169759f0bc96479 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 4 Dec 2019 22:37:20 +0100 Subject: mod_s2s: Invert condition to return early and reduce indentation --- plugins/mod_s2s/mod_s2s.lua | 52 ++++++++++++++++++++++----------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/plugins/mod_s2s/mod_s2s.lua b/plugins/mod_s2s/mod_s2s.lua index 8663702f..e5bfbe7c 100644 --- a/plugins/mod_s2s/mod_s2s.lua +++ b/plugins/mod_s2s/mod_s2s.lua @@ -123,33 +123,33 @@ function route_to_existing_session(event) return false; end local host = hosts[from_host].s2sout[to_host]; - if host then - -- We have a connection to this host already - if host.type == "s2sout_unauthed" and (stanza.name ~= "db:verify" or not host.dialback_key) then - (host.log or log)("debug", "trying to send over unauthed s2sout to "..to_host); - - -- Queue stanza until we are able to send it - local queued_item = { - tostring(stanza), - stanza.attr.type ~= "error" and stanza.attr.type ~= "result" and st.reply(stanza); - }; - if host.sendq then - t_insert(host.sendq, queued_item); - else - -- luacheck: ignore 122 - host.sendq = { queued_item }; - end - host.log("debug", "stanza [%s] queued ", stanza.name); - return true; - elseif host.type == "local" or host.type == "component" then - log("error", "Trying to send a stanza to ourselves??") - log("error", "Traceback: %s", traceback()); - log("error", "Stanza: %s", stanza); - return false; + if not host then return end + + -- We have a connection to this host already + if host.type == "s2sout_unauthed" and (stanza.name ~= "db:verify" or not host.dialback_key) then + (host.log or log)("debug", "trying to send over unauthed s2sout to "..to_host); + + -- Queue stanza until we are able to send it + local queued_item = { + tostring(stanza), + stanza.attr.type ~= "error" and stanza.attr.type ~= "result" and st.reply(stanza); + }; + if host.sendq then + t_insert(host.sendq, queued_item); else - if host.sends2s(stanza) then - return true; - end + -- luacheck: ignore 122 + host.sendq = { queued_item }; + end + host.log("debug", "stanza [%s] queued ", stanza.name); + return true; + elseif host.type == "local" or host.type == "component" then + log("error", "Trying to send a stanza to ourselves??") + log("error", "Traceback: %s", traceback()); + log("error", "Stanza: %s", stanza); + return false; + else + if host.sends2s(stanza) then + return true; end end end -- cgit v1.2.3 From 40ffc88bad91782bf8564994826b528609004751 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 7 Dec 2019 17:05:00 +0100 Subject: net.connect: Add some TODOs and FIXMEs And mention issue numbers: #1246, #1428 and #1429 --- net/connect.lua | 4 +++- net/resolvers/basic.lua | 4 ++++ net/resolvers/service.lua | 3 +++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/net/connect.lua b/net/connect.lua index 0e11bd3e..fe70ce36 100644 --- a/net/connect.lua +++ b/net/connect.lua @@ -3,8 +3,10 @@ local log = require "util.logger".init("net.connect"); local new_id = require "util.id".short; -- TODO Respect use_ipv4, use_ipv6 +-- TODO #1246 Happy Eyeballs -- FIXME Error propagation from resolvers doesn't work --- TODO Try to share DNS resolver object and close it afterwards +-- FIXME #1428 Reuse DNS resolver object between service and basic resolver +-- FIXME #1429 Close DNS resolver object when done local pending_connection_methods = {}; local pending_connection_mt = { diff --git a/net/resolvers/basic.lua b/net/resolvers/basic.lua index c1931dab..ef69e6dc 100644 --- a/net/resolvers/basic.lua +++ b/net/resolvers/basic.lua @@ -7,6 +7,10 @@ local unpack = table.unpack or unpack; -- luacheck: ignore 113 local methods = {}; local resolver_mt = { __index = methods }; +-- TODO Respect use_ipv4, use_ipv6 +-- FIXME #1428 Reuse DNS resolver object (from service resolver) +-- FIXME #1429 Close DNS resolver object when done + -- Find the next target to connect to, and -- pass it to cb() function methods:next(cb) diff --git a/net/resolvers/service.lua b/net/resolvers/service.lua index 504fb421..b4300d08 100644 --- a/net/resolvers/service.lua +++ b/net/resolvers/service.lua @@ -4,6 +4,9 @@ local inet_pton = require "util.net".pton; local idna_to_ascii = require "util.encodings".idna.to_ascii; local unpack = table.unpack or unpack; -- luacheck: ignore 113 +-- FIXME #1428 Reuse DNS resolver object (pass to basic resorver) +-- FIXME #1429 Close DNS resolver object when done + local methods = {}; local resolver_mt = { __index = methods }; -- cgit v1.2.3 From 9d5c3cb856ad15611be84d5324caa8413175f4fe Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 7 Dec 2019 17:39:29 +0100 Subject: net.connect: Mention RFC 6724 regression Default Address Selection algorithm is not applied, resulting in a strong bias towards IPv4. --- net/connect.lua | 1 + net/resolvers/basic.lua | 1 + 2 files changed, 2 insertions(+) diff --git a/net/connect.lua b/net/connect.lua index fe70ce36..6d399dda 100644 --- a/net/connect.lua +++ b/net/connect.lua @@ -4,6 +4,7 @@ local new_id = require "util.id".short; -- TODO Respect use_ipv4, use_ipv6 -- TODO #1246 Happy Eyeballs +-- FIXME RFC 6724 -- FIXME Error propagation from resolvers doesn't work -- FIXME #1428 Reuse DNS resolver object between service and basic resolver -- FIXME #1429 Close DNS resolver object when done diff --git a/net/resolvers/basic.lua b/net/resolvers/basic.lua index ef69e6dc..41ebda80 100644 --- a/net/resolvers/basic.lua +++ b/net/resolvers/basic.lua @@ -8,6 +8,7 @@ local methods = {}; local resolver_mt = { __index = methods }; -- TODO Respect use_ipv4, use_ipv6 +-- FIXME RFC 6724 -- FIXME #1428 Reuse DNS resolver object (from service resolver) -- FIXME #1429 Close DNS resolver object when done -- cgit v1.2.3 From d84a2484dce365fa5047eaf746930f9b6a7dccc2 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 7 Dec 2019 19:05:10 +0100 Subject: net.server_epoll: Add debug logging for delayed reading In :onreadable, if there is still buffered incoming data after reading from the socket (as indicated by the :dirty method, usually because LuaSocket has an 8k buffer that's full but it read a smaller amount), another attempt to read is scheduled via this :pausefor method. This is also called from some other places where it would be pointless to read because there shouldn't be any data. In the delayed read case, this should report that the socket is "dirty". If it reports that the socket is "clean" then the question is where the buffer contents went? If this doesn't get logged after the scheduled time (0.000001s by default) then this would suggests a problem with timer or scheduling. --- net/server_epoll.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index d44149f3..17faa848 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -660,6 +660,7 @@ function interface:pausefor(t) self._pausefor = addtimer(t, function () self._pausefor = nil; self:set(true); + self:debug("Resuming after pause, connection is %s", not self.conn and "missing" or self.conn:dirty() and "dirty" or "clean"); if self.conn and self.conn:dirty() then self:onreadable(); end -- cgit v1.2.3 From 0de6ce740e87fd4f8a8c797cdbbefbd626b3f4e5 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 7 Dec 2019 22:51:43 +0100 Subject: mod_saslauth: Collect SASL EXTERNAL failures into an util.error object Will be easier than that concatenated string to extract info out of for use elsewhere. --- plugins/mod_saslauth.lua | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/plugins/mod_saslauth.lua b/plugins/mod_saslauth.lua index 30d74b9e..b5604883 100644 --- a/plugins/mod_saslauth.lua +++ b/plugins/mod_saslauth.lua @@ -13,6 +13,7 @@ local sm_bind_resource = require "core.sessionmanager".bind_resource; local sm_make_authenticated = require "core.sessionmanager".make_authenticated; local base64 = require "util.encodings".base64; local set = require "util.set"; +local errors = require "util.error"; local usermanager_get_sasl_handler = require "core.usermanager".get_sasl_handler; @@ -102,13 +103,19 @@ module:hook_tag(xmlns_sasl, "failure", function (session, stanza) break; end end - if text and condition then - condition = condition .. ": " .. text; - end - module:log("info", "SASL EXTERNAL with %s failed: %s", session.to_host, condition); + local err = errors.new({ + -- TODO type = what? + text = text, + condition = condition, + }, { + session = session, + stanza = stanza, + }); + + module:log("info", "SASL EXTERNAL with %s failed: %s", session.to_host, err); session.external_auth = "failed" - session.external_auth_failure_reason = condition; + session.external_auth_failure_reason = err; end, 500) module:hook_tag(xmlns_sasl, "failure", function (session, stanza) -- luacheck: ignore 212/stanza -- cgit v1.2.3 From 9589da30b6968a355552a1e8be4b7baeaa905c4a Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 7 Dec 2019 22:55:51 +0100 Subject: mod_saslauth: Set a nicer bounce error explaining SASL EXTERNAL failures Better than the previous string concatenation of SASL failure condition and optional text sent by the remote server. Would be nice to have a text per condition, other than the probably most common 'not-authorized'. --- plugins/mod_saslauth.lua | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/mod_saslauth.lua b/plugins/mod_saslauth.lua index b5604883..ecce8361 100644 --- a/plugins/mod_saslauth.lua +++ b/plugins/mod_saslauth.lua @@ -120,7 +120,10 @@ end, 500) module:hook_tag(xmlns_sasl, "failure", function (session, stanza) -- luacheck: ignore 212/stanza session.log("debug", "No fallback from SASL EXTERNAL failure, giving up"); - session:close(nil, session.external_auth_failure_reason); + session:close(nil, session.external_auth_failure_reason, errors.new({ + type = "wait", condition = "remote-server-timeout", + text = "Could not authenticate to remote server", + }, { session = session, sasl_failure = session.external_auth_failure_reason, })); return true; end, 90) -- cgit v1.2.3 From 655294f93e53175928cf6523b82952134e268495 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 8 Dec 2019 13:38:48 +0100 Subject: mod_admin_telnet: Avoid using LuaSocket for timestamps Using util.time will make it easier to move away from LuaSocket if we ever wanted to do that. --- plugins/mod_admin_telnet.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 8427f811..60f92cda 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -1265,7 +1265,6 @@ function def_env.debug:events(host, event) end function def_env.debug:timers() - local socket = require "socket"; local print = self.session.print; local add_task = require"util.timer".add_task; local h, params = add_task.h, add_task.params; @@ -1293,7 +1292,7 @@ function def_env.debug:timers() if h then local next_time = h:peek(); if next_time then - return true, os.date("Next event at %F %T (in %%.6fs)", next_time):format(next_time - socket.gettime()); + return true, os.date("Next event at %F %T (in %%.6fs)", next_time):format(next_time - time.now()); end end return true; -- cgit v1.2.3 From 158be3a8f16ff6f713d216e450e23a23e1f577b2 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 8 Dec 2019 13:40:42 +0100 Subject: net.server_epoll: Remove unused function for adding timer at absolute time This won't make sense if we switch to monotonic time --- net/server_epoll.lua | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index 17faa848..8af6f484 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -90,18 +90,14 @@ local function reschedule(t, time) timers:reprioritize(t.id, time); end --- Add absolute timer -local function at(time, f) +-- Add relative timer +local function addtimer(timeout, f) + local time = gettime() + timeout; local timer = { time, f, close = closetimer, reschedule = reschedule, id = nil }; timer.id = timers:insert(timer, time); return timer; end --- Add relative timer -local function addtimer(timeout, f) - return at(gettime() + timeout, f); -end - -- Run callbacks of expired timers -- Return time until next timeout local function runtimers(next_delay, min_wait) @@ -879,7 +875,6 @@ return { addclient = addclient; add_task = addtimer; listen = listen; - at = at; loop = loop; closeall = closeall; setquitting = setquitting; -- cgit v1.2.3 From 654d9817ad2211686f3d25d79a72cfe9266f8b58 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 8 Dec 2019 14:20:56 +0100 Subject: net.server_epoll: Change timer rescheduling method to match util.timer Relative to current time instead of absolute time, in preparation for switching to monotonic time. --- net/server_epoll.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index 8af6f484..77619b69 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -86,6 +86,7 @@ local function closetimer(t) end local function reschedule(t, time) + time = gettime() + time; t[1] = time; timers:reprioritize(t.id, time); end @@ -253,7 +254,7 @@ function interface:setreadtimeout(t) end t = t or cfg.read_timeout; if self._readtimeout then - self._readtimeout:reschedule(gettime() + t); + self._readtimeout:reschedule(t); else self._readtimeout = addtimer(t, function () if self:on("readtimeout") then @@ -279,7 +280,7 @@ function interface:setwritetimeout(t) end t = t or cfg.send_timeout; if self._writetimeout then - self._writetimeout:reschedule(gettime() + t); + self._writetimeout:reschedule(t); else self._writetimeout = addtimer(t, function () self:debug("Write timeout"); -- cgit v1.2.3 From 9a041bb926ec6596572911b864dd3002d7798dad Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 8 Dec 2019 14:26:32 +0100 Subject: net.server_epoll: Use monotonic time for scheduling Timer API of passing wallclock time remains --- net/server_epoll.lua | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index 77619b69..46fcbf02 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -17,7 +17,8 @@ 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; +local realtime = require "util.time".now; +local monotonic = require "util.time".monotonic; local indexedbheap = require "util.indexedbheap"; local createtable = require "util.table".create; local inet = require "util.net"; @@ -86,14 +87,14 @@ local function closetimer(t) end local function reschedule(t, time) - time = gettime() + time; + time = monotonic() + time; t[1] = time; timers:reprioritize(t.id, time); end -- Add relative timer local function addtimer(timeout, f) - local time = gettime() + timeout; + local time = monotonic() + timeout; local timer = { time, f, close = closetimer, reschedule = reschedule, id = nil }; timer.id = timers:insert(timer, time); return timer; @@ -103,19 +104,20 @@ end -- Return time until next timeout local function runtimers(next_delay, min_wait) -- Any timers at all? - local now = gettime(); + local elapsed = monotonic(); + local now = realtime(); local peek = timers:peek(); while peek do - if peek > now then - next_delay = peek - now; + if peek > elapsed then + next_delay = peek - elapsed; break; end local _, timer, id = timers:pop(); local ok, ret = pcall(timer[2], now); if ok and type(ret) == "number" then - local next_time = now+ret; + local next_time = elapsed+ret; timer[1] = next_time; timers:insert(timer, next_time); end @@ -578,7 +580,7 @@ local function wrapsocket(client, server, read_size, listeners, tls_ctx, extra) local conn = setmetatable({ conn = client; _server = server; - created = gettime(); + created = realtime(); listeners = listeners; read_size = read_size or (server and server.read_size); writebuffer = {}; @@ -708,7 +710,7 @@ local function listen(addr, port, listeners, config) conn:settimeout(0); local server = setmetatable({ conn = conn; - created = gettime(); + created = realtime(); listeners = listeners; read_size = config and config.read_size; onreadable = interface.onacceptable; -- cgit v1.2.3 From a55d83ceb62bf0e8280829629782bc367ab185b8 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 8 Dec 2019 17:00:45 +0100 Subject: util.error: Write down some thoughts in comments --- util/error.lua | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/util/error.lua b/util/error.lua index 9ebfa6ab..461bf385 100644 --- a/util/error.lua +++ b/util/error.lua @@ -8,6 +8,13 @@ local function is_err(e) return getmetatable(e) == error_mt; end +-- Do we want any more well-known fields? +-- Or could we just copy all fields from `e`? +-- Sometimes you want variable details in the `text`, how to handle that? +-- Translations? +-- Should the `type` be restricted to the stanza error types or free-form? +-- What to set `type` to for stream errors or SASL errors? Those don't have a 'type' attr. + local function new(e, context, registry) local template = (registry and registry[e]) or e or {}; return setmetatable({ -- cgit v1.2.3 From 5ba23c972b8453806af6aeca573df96a366c68ef Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 8 Dec 2019 17:48:37 +0100 Subject: util.x509: Fix recording of CommonNames in get_identities Don't worry, this function is not used by anything yet, this isn't a security issue. It'll be used by Prosody to pick the correct certificate for itself in the future. The `names` multitable is a collection of (name, service) pairs but it put them in the wrong order here. --- util/x509.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/x509.lua b/util/x509.lua index fe6e4b79..342dafde 100644 --- a/util/x509.lua +++ b/util/x509.lua @@ -266,7 +266,7 @@ local function get_identities(cert) --> map of names to sets of services if dn.oid == oid_commonname then local name = nameprep(dn.value); if name and idna_to_ascii(name) then - names:set("*", name, true); + names:set(name, "*", true); end end end -- cgit v1.2.3 From 8d23fdfc77ff7ba88e749662dd3eaeefbf951f47 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 9 Dec 2019 11:57:10 +0000 Subject: configmanager tests: Split long line --- spec/core_configmanager_spec.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spec/core_configmanager_spec.lua b/spec/core_configmanager_spec.lua index afb7d492..7958ec6b 100644 --- a/spec/core_configmanager_spec.lua +++ b/spec/core_configmanager_spec.lua @@ -9,7 +9,9 @@ describe("core.configmanager", function() configmanager.set("*", "testkey1", 321); assert.are.equal(321, configmanager.get("*", "testkey1"), "Retrieving a set global key"); - assert.are.equal(321, configmanager.get("example.com", "testkey1"), "Retrieving a set key of undefined host, of which only a globally set one exists"); + assert.are.equal(321, configmanager.get("example.com", "testkey1"), + "Retrieving a set key of undefined host, of which only a globally set one exists" + ); configmanager.set("example.com", ""); -- Creates example.com host in config assert.are.equal(321, configmanager.get("example.com", "testkey1"), "Retrieving a set key, of which only a globally set one exists"); -- cgit v1.2.3 From 6866201d71f4419d6e0bc0169aa6c9f79e3c4695 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 9 Dec 2019 12:42:22 +0000 Subject: .luacheckrc: Remove passing file from ignore list --- .luacheckrc | 1 - 1 file changed, 1 deletion(-) diff --git a/.luacheckrc b/.luacheckrc index f6760ee3..2f3cc1dc 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -142,7 +142,6 @@ if os.getenv("PROSODY_STRICT_LINT") ~= "1" then "plugins/mod_storage_sql1.lua"; - "spec/core_configmanager_spec.lua"; "spec/core_moduleapi_spec.lua"; "spec/net_http_parser_spec.lua"; "spec/util_events_spec.lua"; -- cgit v1.2.3 From 75ff6ee528124979ae6a5a3b2f12d6f3d76eadd3 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 9 Dec 2019 12:43:32 +0000 Subject: net.http.parser tests: Expand tests to include validation of results --- spec/net_http_parser_spec.lua | 108 +++++++++++++++++++++++++++++++++--------- 1 file changed, 86 insertions(+), 22 deletions(-) diff --git a/spec/net_http_parser_spec.lua b/spec/net_http_parser_spec.lua index 6bba087c..8310a451 100644 --- a/spec/net_http_parser_spec.lua +++ b/spec/net_http_parser_spec.lua @@ -1,16 +1,68 @@ -local httpstreams = { [[ +local http_parser = require "net.http.parser"; + +local function test_stream(stream, expect) + local success_cb = spy.new(function (packet) + assert.is_table(packet); + assert.is_equal(expect.body, packet.body); + end); + + stream = stream:gsub("\n", "\r\n"); + local parser = http_parser.new(success_cb, error, stream:sub(1,4) == "HTTP" and "client" or "server") + for chunk in stream:gmatch("..?.?") do + parser:feed(chunk); + end + + assert.spy(success_cb).was_called(expect.count or 1); +end + + +describe("net.http.parser", function() + describe("parser", function() + it("should handle requests with no content-length or body", function () + test_stream( +[[ GET / HTTP/1.1 Host: example.com -]], [[ +]], + { + body = ""; + } + ); + end); + + it("should handle responses with empty body", function () + test_stream( +[[ HTTP/1.1 200 OK Content-Length: 0 -]], [[ +]], + { + body = ""; + } + ); + end); + + it("should handle simple responses", function () + test_stream( + +[[ HTTP/1.1 200 OK Content-Length: 7 Hello +]], + { + body = "Hello\r\n", count = 1; + } + ); + end); + + it("should handle chunked encoding in responses", function () + test_stream( + +[[ HTTP/1.1 200 OK Transfer-Encoding: chunked @@ -25,28 +77,40 @@ o 0 -]] -} +]], + { + body = "Hello", count = 1; + } + ); + end); + it("should handle a stream of responses", function () + test_stream( -local http_parser = require "net.http.parser"; +[[ +HTTP/1.1 200 OK +Content-Length: 5 -describe("net.http.parser", function() - describe("#new()", function() - it("should work", function() - for _, stream in ipairs(httpstreams) do - local success; - local function success_cb(packet) - success = true; - end - stream = stream:gsub("\n", "\r\n"); - local parser = http_parser.new(success_cb, error, stream:sub(1,4) == "HTTP" and "client" or "server") - for chunk in stream:gmatch("..?.?") do - parser:feed(chunk); - end - - assert.is_true(success); - end +Hello +HTTP/1.1 200 OK +Transfer-Encoding: chunked + +1 +H +1 +e +2 +ll +1 +o +0 + + +]], + { + body = "Hello", count = 2; + } + ); end); end); end); -- cgit v1.2.3 From fbb0bbe1b4a9bda0dcfd247fb9412124396c332f Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 9 Dec 2019 12:44:43 +0000 Subject: .luacheckrc: Remove passing spec/ files from ignore list --- .luacheckrc | 3 --- 1 file changed, 3 deletions(-) diff --git a/.luacheckrc b/.luacheckrc index 2f3cc1dc..1a2516ca 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -143,14 +143,11 @@ if os.getenv("PROSODY_STRICT_LINT") ~= "1" then "plugins/mod_storage_sql1.lua"; "spec/core_moduleapi_spec.lua"; - "spec/net_http_parser_spec.lua"; - "spec/util_events_spec.lua"; "spec/util_http_spec.lua"; "spec/util_ip_spec.lua"; "spec/util_multitable_spec.lua"; "spec/util_rfc6724_spec.lua"; "spec/util_throttle_spec.lua"; - "spec/util_xmppstream_spec.lua"; "tools/ejabberd2prosody.lua"; "tools/ejabberdsql2prosody.lua"; -- cgit v1.2.3 From 6890707e43ca5daa277d19b79acb99b7e93f397a Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 9 Dec 2019 16:39:48 +0100 Subject: util.sasl.scram: Ignore unused authzid variable (strict lint) It would be nice if authzid was passed down into the stack and could be used by plugins for things. --- util/sasl/scram.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/util/sasl/scram.lua b/util/sasl/scram.lua index 1d1590e8..e2ce00f5 100644 --- a/util/sasl/scram.lua +++ b/util/sasl/scram.lua @@ -125,6 +125,7 @@ local function scram_gen(hash_name, H_f, HMAC_f, get_auth_db) local client_first_message = message; -- TODO: fail if authzid is provided, since we don't support them yet + -- luacheck: ignore 211/authzid local gs2_header, gs2_cbind_flag, gs2_cbind_name, authzid, client_first_message_bare, username, clientnonce = s_match(client_first_message, "^(([pny])=?([^,]*),([^,]*),)(m?=?[^,]*,?n=([^,]*),r=([^,]*),?.*)$"); -- cgit v1.2.3 From ffcb8303ccdc3dc6c0cceae7c7a9ba8d2de5aa65 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 10 Dec 2019 17:43:26 +0100 Subject: mod_s2s: Fix name conflict introduced in c7864f970969 --- plugins/mod_s2s/mod_s2s.lua | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/plugins/mod_s2s/mod_s2s.lua b/plugins/mod_s2s/mod_s2s.lua index e5bfbe7c..6912c9a4 100644 --- a/plugins/mod_s2s/mod_s2s.lua +++ b/plugins/mod_s2s/mod_s2s.lua @@ -506,23 +506,23 @@ local function session_close(session, reason, remote_reason, bounce_reason) end end if reason then -- nil == no err, initiated by us, false == initiated by remote + local stream_error; if type(reason) == "string" then -- assume stream error - reason = st.stanza("stream:error"):tag(reason, {xmlns = 'urn:ietf:params:xml:ns:xmpp-streams' }); + stream_error = st.stanza("stream:error"):tag(reason, {xmlns = 'urn:ietf:params:xml:ns:xmpp-streams' }); elseif type(reason) == "table" and not st.is_stanza(reason) then - local stanza = st.stanza("stream:error"):tag(reason.condition or "undefined-condition", stream_xmlns_attr):up(); + stream_error = st.stanza("stream:error"):tag(reason.condition or "undefined-condition", stream_xmlns_attr):up(); if reason.text then - stanza:tag("text", stream_xmlns_attr):text(reason.text):up(); + stream_error:tag("text", stream_xmlns_attr):text(reason.text):up(); end if reason.extra then - stanza:add_child(reason.extra); + stream_error:add_child(reason.extra); end - reason = stanza; end - if st.is_stanza(reason) then + if st.is_stanza(stream_error) then -- to and from are never unknown on outgoing connections log("debug", "Disconnecting %s->%s[%s], is: %s", session.from_host or "(unknown host)" or session.ip, session.to_host or "(unknown host)", session.type, reason); - session.sends2s(reason); + session.sends2s(stream_error); end end -- cgit v1.2.3 From 87d0125802ce38410e5d429c90760802dec0397c Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 14 Dec 2019 20:28:44 +0100 Subject: util.error: Move default for numeric error code to net.http.server Stanza errors can also have numbers but these are a legacy thing and rarely used, except in MUC. HTTP errors on the other hand always have a number. --- net/http/server.lua | 2 +- util/error.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/net/http/server.lua b/net/http/server.lua index 47f064a5..f4f67d18 100644 --- a/net/http/server.lua +++ b/net/http/server.lua @@ -191,7 +191,7 @@ local function handle_result(request, response, result) elseif result_type == "string" then body = result; elseif errors.is_err(result) then - body = events.fire_event("http-error", { request = request, response = response, code = result.code, error = result }); + body = events.fire_event("http-error", { request = request, response = response, code = result.code or 500, error = result }); elseif promise.is_promise(result) then result:next(function (ret) handle_result(request, response, ret); diff --git a/util/error.lua b/util/error.lua index 461bf385..ca960dd9 100644 --- a/util/error.lua +++ b/util/error.lua @@ -21,7 +21,7 @@ local function new(e, context, registry) type = template.type or "cancel"; condition = template.condition or "undefined-condition"; text = template.text; - code = template.code or 500; + code = template.code; context = context or template.context or { _error_id = e }; }, error_mt); -- cgit v1.2.3 From 71d99b65fa50afebb0241199d4000a8a7c50e1d1 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 14 Dec 2019 22:43:12 +0100 Subject: util.sasl: Add stub tests Random uncommitted file I found when cleaning out my work dir --- spec/util_sasl_spec.lua | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 spec/util_sasl_spec.lua diff --git a/spec/util_sasl_spec.lua b/spec/util_sasl_spec.lua new file mode 100644 index 00000000..368291b3 --- /dev/null +++ b/spec/util_sasl_spec.lua @@ -0,0 +1,43 @@ +local sasl = require "util.sasl"; + +-- profile * mechanism +-- callbacks could use spies instead + +describe("util.sasl", function () + describe("plain_test profile", function () + local profile = { + plain_test = function (_, username, password, realm) + assert.equals("user", username) + assert.equals("pencil", password) + assert.equals("sasl.test", realm) + return true, true; + end; + }; + it("works with PLAIN", function () + local plain = sasl.new("sasl.test", profile); + assert.truthy(plain:select("PLAIN")); + assert.truthy(plain:process("\000user\000pencil")); + assert.equals("user", plain.username); + end); + end); + + describe("plain profile", function () + local profile = { + plain = function (_, username, realm) + assert.equals("user", username) + assert.equals("sasl.test", realm) + return "pencil", true; + end; + }; + + it("works with PLAIN", function () + local plain = sasl.new("sasl.test", profile); + assert.truthy(plain:select("PLAIN")); + assert.truthy(plain:process("\000user\000pencil")); + assert.equals("user", plain.username); + end); + + -- TODO SCRAM + end); +end); + -- cgit v1.2.3 From 4e34c40ece0eb19f85720d63b1b0372f59e5616d Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 14 Dec 2019 22:47:41 +0100 Subject: util.stanza: Accept util.error object to error_reply If we're moving towards util.error as the standard error container then this makes sense. This may allow for future extensibility without needing a lot of optional arguments. --- spec/util_stanza_spec.lua | 17 +++++++++++++++++ util/stanza.lua | 3 +++ 2 files changed, 20 insertions(+) diff --git a/spec/util_stanza_spec.lua b/spec/util_stanza_spec.lua index b754f59d..d38a609f 100644 --- a/spec/util_stanza_spec.lua +++ b/spec/util_stanza_spec.lua @@ -1,5 +1,6 @@ local st = require "util.stanza"; +local errors = require "util.error"; describe("util.stanza", function() describe("#preserialize()", function() @@ -231,6 +232,22 @@ describe("util.stanza", function() end, "got stanza of type error"); end); + it("should accept util.error objects", function () + local s = st.message({ to = "touser", from = "fromuser", id = "123", type = "chat" }, "Hello"); + local e = errors.new({ type = "modify", condition = "not-acceptable", text = "Bork bork bork" }); + local r = st.error_reply(s, e); + + assert.are.equal(r.name, s.name); + assert.are.equal(r.id, s.id); + assert.are.equal(r.attr.to, s.attr.from); + assert.are.equal(r.attr.from, s.attr.to); + assert.are.equal(r.attr.type, "error"); + assert.are.equal(r.tags[1].name, "error"); + assert.are.equal(r.tags[1].attr.type, e.type); + assert.are.equal(r.tags[1].tags[1].name, e.condition); + assert.are.equal(r.tags[1].tags[2]:get_text(), e.text); + end); + end); describe("should reject #invalid", function () diff --git a/util/stanza.lua b/util/stanza.lua index 8e46f90f..f5cd5668 100644 --- a/util/stanza.lua +++ b/util/stanza.lua @@ -458,6 +458,9 @@ local function error_reply(orig, error_type, condition, error_message, error_by) if t.attr.from == error_by then error_by = nil; end + if type(error_type) == "table" then -- an util.error or similar object + error_type, condition, error_message = error_type.type, error_type.condition, error_type.text; + end t:tag("error", {type = error_type, by = error_by}) --COMPAT: Some day xmlns:stanzas goes here :tag(condition, xmpp_stanzas_attr):up(); if error_message then t:text_tag("text", error_message, xmpp_stanzas_attr); end -- cgit v1.2.3 From d7570eee7e9f2ff351a58ee541395522d5cf7385 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 15 Dec 2019 20:43:02 +0100 Subject: mod_admin_telnet: Fix host sorting Reversing each %P is a noop --- plugins/mod_admin_telnet.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 60f92cda..940c3533 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -965,7 +965,7 @@ function def_env.host:deactivate(hostname, reason) end local function compare_hosts(a, b) - return a:gsub("%P", string.reverse):reverse() < b:gsub("%P", string.reverse):reverse(); + return a:gsub("%P+", string.reverse):reverse() < b:gsub("%P+", string.reverse):reverse(); end function def_env.host:list() -- cgit v1.2.3 From d0cd5469d272364bede91196edbb783e3fe1f769 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 15 Dec 2019 20:44:10 +0100 Subject: mod_admin_telnet: Sort by complete labels Might as well. --- plugins/mod_admin_telnet.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 940c3533..6dc7c5c3 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -965,7 +965,7 @@ function def_env.host:deactivate(hostname, reason) end local function compare_hosts(a, b) - return a:gsub("%P+", string.reverse):reverse() < b:gsub("%P+", string.reverse):reverse(); + return a:gsub("[^.]+", string.reverse):reverse() < b:gsub("[^.]+", string.reverse):reverse(); end function def_env.host:list() -- cgit v1.2.3 From d146d6b8acb261c4a2e1f6d721fdd44243805fd3 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 15 Dec 2019 21:42:42 +0100 Subject: mod_admin_telnet: Merge hostname comparison functions Missed that there existed one already when writing the one for host:list --- plugins/mod_admin_telnet.lua | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 6dc7c5c3..6ed36258 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -433,7 +433,7 @@ end local function _sort_hosts(a, b) if a == "*" then return true elseif b == "*" then return false - else return a < b; end + else return a:gsub("[^.]+", string.reverse):reverse() < b:gsub("[^.]+", string.reverse):reverse(); end end function def_env.module:reload(name, hosts) @@ -964,15 +964,11 @@ function def_env.host:deactivate(hostname, reason) return hostmanager.deactivate(hostname, reason); end -local function compare_hosts(a, b) - return a:gsub("[^.]+", string.reverse):reverse() < b:gsub("[^.]+", string.reverse):reverse(); -end - function def_env.host:list() local print = self.session.print; local i = 0; local type; - for host, host_session in iterators.sorted_pairs(prosody.hosts, compare_hosts) do + for host, host_session in iterators.sorted_pairs(prosody.hosts, _sort_hosts) do i = i + 1; type = host_session.type; if type == "local" then -- cgit v1.2.3 From 7b64b46af18c1ff2816547f6f6f0f046fdabf88f Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 15 Dec 2019 22:07:24 +0100 Subject: mod_admin_telnet: Refactor internal function for listing hosts Splits out a function that doesn't deal with modules for reuse elsewhere --- plugins/mod_admin_telnet.lua | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 6ed36258..fffab4cf 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -364,7 +364,7 @@ end def_env.module = {}; -local function get_hosts_set(hosts, module) +local function get_hosts_set(hosts) if type(hosts) == "table" then if hosts[1] then return set.new(hosts); @@ -374,17 +374,23 @@ local function get_hosts_set(hosts, module) elseif type(hosts) == "string" then return set.new { hosts }; elseif hosts == nil then - local hosts_set = set.new(array.collect(keys(prosody.hosts))) - / function (host) return (prosody.hosts[host].type == "local" or module and modulemanager.is_loaded(host, module)) and host or nil; end; - if module and modulemanager.get_module("*", module) then - hosts_set:add("*"); - end - return hosts_set; + return set.new(array.collect(keys(prosody.hosts))); + end +end + +-- Hosts with a module or all virtualhosts if no module given +-- matching modules_enabled in the global section +local function get_hosts_with_module(hosts, module) + local hosts_set = get_hosts_set(hosts) + / function (host) return (prosody.hosts[host].type == "local" or module and modulemanager.is_loaded(host, module)) and host or nil; end; + if module and modulemanager.get_module("*", module) then + hosts_set:add("*"); end + return hosts_set; end function def_env.module:load(name, hosts, config) - hosts = get_hosts_set(hosts); + hosts = get_hosts_with_module(hosts); -- Load the module for each host local ok, err, count, mod = true, nil, 0; @@ -411,7 +417,7 @@ function def_env.module:load(name, hosts, config) end function def_env.module:unload(name, hosts) - hosts = get_hosts_set(hosts, name); + hosts = get_hosts_with_module(hosts, name); -- Unload the module for each host local ok, err, count = true, nil, 0; @@ -437,7 +443,7 @@ local function _sort_hosts(a, b) end function def_env.module:reload(name, hosts) - hosts = array.collect(get_hosts_set(hosts, name)):sort(_sort_hosts) + hosts = array.collect(get_hosts_with_module(hosts, name)):sort(_sort_hosts) -- Reload the module for each host local ok, err, count = true, nil, 0; -- cgit v1.2.3 From 567e4183b5e2ba4fe92ef0ef86b34327f036e9f1 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 15 Dec 2019 22:08:20 +0100 Subject: mod_admin_telnet: Sort hosts in module:list --- plugins/mod_admin_telnet.lua | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index fffab4cf..fbaa80d0 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -466,16 +466,7 @@ function def_env.module:reload(name, hosts) end function def_env.module:list(hosts) - if hosts == nil then - hosts = array.collect(keys(prosody.hosts)); - table.insert(hosts, 1, "*"); - end - if type(hosts) == "string" then - hosts = { hosts }; - end - if type(hosts) ~= "table" then - return false, "Please supply a host or a list of hosts you would like to see"; - end + hosts = array.collect(set.new({ not hosts and "*" or nil }) + get_hosts_set(hosts)):sort(_sort_hosts); local print = self.session.print; for _, host in ipairs(hosts) do -- cgit v1.2.3 From 13622b141e299ba3df60b82153084f34bdcb8e6f Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 15 Dec 2019 21:44:58 +0100 Subject: mod_admin_telnet: Use existing host comparison when comparing JIDs --- plugins/mod_admin_telnet.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index fbaa80d0..a454f568 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -630,7 +630,7 @@ local function show_c2s(callback) end return (a.username or "") > (b.username or ""); end - return (a.host or "") > (b.host or ""); + return _sort_hosts(a.host or "", b.host or ""); end):map(function (session) callback(get_jid(session), session) end); -- cgit v1.2.3 From fac877feaa6c394469614b4604f1723b6bb3b681 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 15 Dec 2019 22:15:52 +0100 Subject: mod_admin_telnet: Use common sort function in s2s:show --- plugins/mod_admin_telnet.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index a454f568..9468be62 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -781,8 +781,8 @@ function def_env.s2s:show(match_jid, annotate) -- 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; + if a.l == b.l then return _sort_hosts(a.r, b.r); end + return _sort_hosts(a.l, b.l); end); local lasthost; for _, sess_lines in ipairs(s2s_list) do -- cgit v1.2.3 From 62971ce69b890efea8e9d5469fd45a6d1eec236e Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 16 Dec 2019 01:52:56 +0100 Subject: doap: More PEP payloads --- doc/doap.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/doap.xml b/doc/doap.xml index 1633e2f3..20686e03 100644 --- a/doc/doap.xml +++ b/doc/doap.xml @@ -159,6 +159,7 @@ + via XEP-0163 @@ -171,6 +172,7 @@ + via XEP-0163 -- cgit v1.2.3 From 6b533ad772fb32d00a2f87cb53a87ee9f3a6d689 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 16 Dec 2019 02:02:47 +0100 Subject: util.dataforms: Improve descriptions in tests --- spec/util_dataforms_spec.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/spec/util_dataforms_spec.lua b/spec/util_dataforms_spec.lua index 89759035..085128d1 100644 --- a/spec/util_dataforms_spec.lua +++ b/spec/util_dataforms_spec.lua @@ -110,7 +110,7 @@ describe("util.dataforms", function () xform = some_form:form(); end); - it("works", function () + it("XML serialization looks like it should", function () assert.truthy(xform); assert.truthy(st.is_stanza(xform)); assert.equal("x", xform.name); @@ -316,7 +316,7 @@ describe("util.dataforms", function () end); describe(":data", function () - it("works", function () + it("returns something", function () assert.truthy(some_form:data(xform)); end); end); @@ -402,7 +402,7 @@ describe("util.dataforms", function () end); end); - describe("validation", function () + describe("datatype validation", function () local f = dataforms.new { { name = "number", @@ -411,12 +411,12 @@ describe("util.dataforms", function () }, }; - it("works", function () + it("integer roundtrip works", function () local d = f:data(f:form({number = 1})); assert.equal(1, d.number); end); - it("works", function () + it("integer error handling works", function () local d,e = f:data(f:form({number = "nan"})); assert.not_equal(1, d.number); assert.table(e); -- cgit v1.2.3 From ad26a3b047ab0ae2efa0d61553e32f77da2ccac4 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 17 Dec 2019 00:34:39 +0100 Subject: mod_s2s: Remove obsolete pre-connect buffer Originally added in c500d4cb7855 Dead code since the net.connect switch in 756b8821007a --- plugins/mod_s2s/mod_s2s.lua | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/plugins/mod_s2s/mod_s2s.lua b/plugins/mod_s2s/mod_s2s.lua index 6912c9a4..00d8f5d9 100644 --- a/plugins/mod_s2s/mod_s2s.lua +++ b/plugins/mod_s2s/mod_s2s.lua @@ -438,20 +438,6 @@ function stream_callbacks._streamopened(session, attr) end end - -- Send unauthed buffer - -- (stanzas which are fine to send before dialback) - -- Note that this is *not* the stanza queue (which - -- we can only send if auth succeeds) :) - local send_buffer = session.send_buffer; - if send_buffer and #send_buffer > 0 then - log("debug", "Sending s2s send_buffer now..."); - for i, data in ipairs(send_buffer) do - session.sends2s(tostring(data)); - send_buffer[i] = nil; - end - end - session.send_buffer = nil; - -- If server is pre-1.0, don't wait for features, just do dialback if session.version < 1.0 then if not session.dialback_verifying then -- cgit v1.2.3 From 173990157fad6d4507e8ce2dc214e7bf35a17822 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Thu, 19 Dec 2019 10:03:16 +0000 Subject: rostermanager, mod_presence: Support for subscription preapproval (fixes #686) --- core/rostermanager.lua | 23 +++++++++-- plugins/mod_presence.lua | 12 +++++- spec/scansion/presence_preapproval.scs | 74 ++++++++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+), 5 deletions(-) create mode 100644 spec/scansion/presence_preapproval.scs diff --git a/core/rostermanager.lua b/core/rostermanager.lua index d551a1b1..7b104339 100644 --- a/core/rostermanager.lua +++ b/core/rostermanager.lua @@ -301,6 +301,11 @@ function is_contact_pending_out(username, host, jid) local item = roster[jid]; return item and item.ask; end +local function is_contact_preapproved(username, host, jid) + local roster = load_roster(username, host); + local item = roster[jid]; + return item and (item.approved == "true"); +end local function set_contact_pending_out(username, host, jid) -- subscribe local roster = load_roster(username, host); local item = roster[jid]; @@ -331,9 +336,10 @@ local function unsubscribe(username, host, jid) return save_roster(username, host, roster, jid); end local function subscribed(username, host, jid) + local roster = load_roster(username, host); + local item = roster[jid]; + if is_contact_pending_in(username, host, jid) then - local roster = load_roster(username, host); - local item = roster[jid]; if not item then -- FIXME should roster item be auto-created? item = {subscription = "none", groups = {}}; roster[jid] = item; @@ -345,7 +351,17 @@ local function subscribed(username, host, jid) end roster[false].pending[jid] = nil; return save_roster(username, host, roster, jid); - end -- TODO else implement optional feature pre-approval (ask = subscribed) + elseif not item or item.subscription == "none" or item.subscription == "to" then + -- Contact is not subscribed and has not sent a subscription request. + -- We store a pre-approval as per RFC6121 3.4 + if not item then + item = {subscription = "none", groups = {}}; + roster[jid] = item; + end + item.approved = "true"; + log("debug", "Storing preapproval for %s", jid); + return save_roster(username, host, roster, jid); + end end local function unsubscribed(username, host, jid) local roster = load_roster(username, host); @@ -403,6 +419,7 @@ return { set_contact_pending_in = set_contact_pending_in; is_contact_pending_out = is_contact_pending_out; set_contact_pending_out = set_contact_pending_out; + is_contact_preapproved = is_contact_preapproved; unsubscribe = unsubscribe; subscribed = subscribed; unsubscribed = unsubscribed; diff --git a/plugins/mod_presence.lua b/plugins/mod_presence.lua index f7f458ca..b874277c 100644 --- a/plugins/mod_presence.lua +++ b/plugins/mod_presence.lua @@ -181,8 +181,10 @@ function handle_outbound_presence_subscriptions_and_probes(origin, stanza, from_ if rostermanager.subscribed(node, host, to_bare) then rostermanager.roster_push(node, host, to_bare); end - core_post_stanza(origin, stanza); - send_presence_of_available_resources(node, host, to_bare, origin); + if rostermanager.is_contact_subscribed(node, host, to_bare) then + core_post_stanza(origin, stanza); + send_presence_of_available_resources(node, host, to_bare, origin); + end if rostermanager.is_user_subscribed(node, host, to_bare) then core_post_stanza(origin, st.presence({ type = "probe", from = from_bare, to = to_bare })); end @@ -229,6 +231,12 @@ function handle_inbound_presence_subscriptions_and_probes(origin, stanza, from_b if 0 == send_presence_of_available_resources(node, host, from_bare, origin) then core_post_stanza(hosts[host], st.presence({from=to_bare, to=from_bare, type="unavailable"}), true); -- TODO send last activity end + elseif rostermanager.is_contact_preapproved(node, host, from_bare) then + if not rostermanager.is_contact_pending_in(node, host, from_bare) then + if rostermanager.set_contact_pending_in(node, host, from_bare, stanza) then + core_post_stanza(hosts[host], st.presence({from=to_bare, to=from_bare, type="subscribed"}), true); + end -- TODO else return error, unable to save + end else core_post_stanza(hosts[host], st.presence({from=to_bare, to=from_bare, type="unavailable"}), true); -- acknowledging receipt if not rostermanager.is_contact_pending_in(node, host, from_bare) then diff --git a/spec/scansion/presence_preapproval.scs b/spec/scansion/presence_preapproval.scs new file mode 100644 index 00000000..ce6158d2 --- /dev/null +++ b/spec/scansion/presence_preapproval.scs @@ -0,0 +1,74 @@ +# server supports contact subscription pre-approval (RFC 6121 3.4) + +[Client] Alice + jid: preappove-a@localhost + password: password + +[Client] Bob + jid: preapprove-b@localhost + password: password + +--------- + +Alice connects + +Alice sends: + + +Alice receives: + + +Alice sends: + + +Bob connects + +Bob sends: + + + + +Bob receives: + + + + + +Bob sends: + + +Bob receives: + + +Bob sends: + + +Bob receives: + + + + + + + + +Bob receives: + + +Bob disconnects + +Alice sends: + + + + +Alice receives: + + + + + + +Alice disconnects + +Bob disconnects -- cgit v1.2.3 From 228ec67d9790822defd4cf7d64bb1de6f479f0aa Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 19 Dec 2019 16:22:12 +0100 Subject: scansion: Trim trailing whitespace in tests --- spec/scansion/basic_message.scs | 6 +++--- spec/scansion/keep_full_sub_req.scs | 2 +- spec/scansion/muc_members_only_change.scs | 2 +- spec/scansion/muc_register.scs | 2 +- spec/scansion/presence_preapproval.scs | 4 ++-- spec/scansion/pubsub_basic.scs | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/spec/scansion/basic_message.scs b/spec/scansion/basic_message.scs index fb21c465..1258dbf5 100644 --- a/spec/scansion/basic_message.scs +++ b/spec/scansion/basic_message.scs @@ -79,7 +79,7 @@ Juliet's phone receives: Hello Juliet, are you there? - + # Romeo sends another bare-JID message, it should be delivered # instantly to Juliet's phone @@ -92,7 +92,7 @@ Romeo sends: Juliet's phone receives: Oh, hi! - + # Juliet's laptop goes online, but with a negative priority @@ -122,7 +122,7 @@ Romeo sends: Juliet's phone receives: How are you? - + # Romeo sends direct to Juliet's full JID, and she should receive it diff --git a/spec/scansion/keep_full_sub_req.scs b/spec/scansion/keep_full_sub_req.scs index 244c1d55..41ffec0d 100644 --- a/spec/scansion/keep_full_sub_req.scs +++ b/spec/scansion/keep_full_sub_req.scs @@ -30,7 +30,7 @@ Bob sends: Bob receives: - + Bob receives: diff --git a/spec/scansion/muc_members_only_change.scs b/spec/scansion/muc_members_only_change.scs index dc40b5a0..a708dbfb 100644 --- a/spec/scansion/muc_members_only_change.scs +++ b/spec/scansion/muc_members_only_change.scs @@ -94,7 +94,7 @@ Romeo sends: - + # As a non-member, Juliet must now be removed from the room Romeo receives: diff --git a/spec/scansion/muc_register.scs b/spec/scansion/muc_register.scs index 5d1f5f3e..81612829 100644 --- a/spec/scansion/muc_register.scs +++ b/spec/scansion/muc_register.scs @@ -328,7 +328,7 @@ Romeo receives: # Romeo updates his own registration - + Romeo sends: diff --git a/spec/scansion/presence_preapproval.scs b/spec/scansion/presence_preapproval.scs index ce6158d2..e34ac7cf 100644 --- a/spec/scansion/presence_preapproval.scs +++ b/spec/scansion/presence_preapproval.scs @@ -39,7 +39,7 @@ Bob sends: Bob receives: - + Bob sends: @@ -50,7 +50,7 @@ Bob receives: - + Bob receives: diff --git a/spec/scansion/pubsub_basic.scs b/spec/scansion/pubsub_basic.scs index d983ff66..0adf6857 100644 --- a/spec/scansion/pubsub_basic.scs +++ b/spec/scansion/pubsub_basic.scs @@ -32,7 +32,7 @@ Juliet connects -- -- -- --- +-- -- Juliet receives: -- -- cgit v1.2.3 From 2a75869d2721f9235ea882484cb1a8ea83f792b7 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Sun, 22 Dec 2019 08:42:12 +0000 Subject: MUC: Improve presence broadcast form field label --- plugins/muc/presence_broadcast.lib.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/muc/presence_broadcast.lib.lua b/plugins/muc/presence_broadcast.lib.lua index ace614b3..613e6403 100644 --- a/plugins/muc/presence_broadcast.lib.lua +++ b/plugins/muc/presence_broadcast.lib.lua @@ -65,7 +65,7 @@ module:hook("muc-config-form", function(event) table.insert(event.form, { name = "muc#roomconfig_presencebroadcast"; type = "list-multi"; - label = "Roles for which Presence is Broadcasted"; + label = "Only show participants with roles:"; value = values; options = valid_roles; }); -- cgit v1.2.3 From 6183e7a303d4fed16b368b55dcd0fddc34b20739 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 22 Dec 2019 20:10:20 +0100 Subject: mod_admin_telnet: Include config:get() in help text --- plugins/mod_admin_telnet.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 9468be62..f298ad2f 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -288,6 +288,7 @@ function commands.help(session, data) 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.]] + print [[config:get([host,] option) - Show the value of a config option.]] elseif section == "console" then print [[Hey! Welcome to Prosody's admin console.]] print [[First thing, if you're ever wondering how to get out, simply type 'quit'.]] -- cgit v1.2.3 From b3259cf52e03c1b81c823de04a3471ba60f080c6 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 20 Dec 2019 22:23:22 +0100 Subject: luacheck: Don't not warn about not using secondary return values Brace for ONE BILLION WARNINGS!!! --- .luacheckrc | 1 - 1 file changed, 1 deletion(-) diff --git a/.luacheckrc b/.luacheckrc index 1a2516ca..6e8481de 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -128,7 +128,6 @@ files["prosody.cfg.lua"] = { if os.getenv("PROSODY_STRICT_LINT") ~= "1" then -- These files have not yet been brought up to standard -- Do not add more files here, but do help us fix these! - unused_secondaries = false local exclude_files = { "doc/net.server.lua"; -- cgit v1.2.3 From 6d3006307dd0bd38861102dcd2d51cb75e100224 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 20 Dec 2019 22:31:27 +0100 Subject: core.stanza_router: Extract host part of JIDs directly [luacheck] Silences warning about unused return values --- core/stanza_router.lua | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/core/stanza_router.lua b/core/stanza_router.lua index a74f3b6f..dab83803 100644 --- a/core/stanza_router.lua +++ b/core/stanza_router.lua @@ -12,6 +12,7 @@ local hosts = _G.prosody.hosts; local tostring = tostring; local st = require "util.stanza"; local jid_split = require "util.jid".split; +local jid_host = require "util.jid".host; local jid_prepped_split = require "util.jid".prepped_split; local full_sessions = _G.prosody.full_sessions; @@ -81,7 +82,7 @@ function core_process_stanza(origin, stanza) local to_bare, from_bare; if to then if full_sessions[to] or bare_sessions[to] or hosts[to] then - node, host = jid_split(to); -- TODO only the host is needed, optimize + host = jid_host(to); else node, host, resource = jid_prepped_split(to); if not host then @@ -186,8 +187,8 @@ function core_post_stanza(origin, stanza, preevents) end function core_route_stanza(origin, stanza) - local node, host, resource = jid_split(stanza.attr.to); - local from_node, from_host, from_resource = jid_split(stanza.attr.from); + local host = jid_host(stanza.attr.to); + local from_host = jid_host(stanza.attr.from); -- Auto-detect origin if not specified origin = origin or hosts[from_host]; -- cgit v1.2.3 From 5e83a19bc8899ee4282a8370596b3bc43b6e34cc Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 20 Dec 2019 22:33:24 +0100 Subject: core.stanza_router: Silence warning about unused err_message [luacheck] --- core/stanza_router.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/stanza_router.lua b/core/stanza_router.lua index dab83803..1d8db3e7 100644 --- a/core/stanza_router.lua +++ b/core/stanza_router.lua @@ -28,7 +28,7 @@ local function handle_unhandled_stanza(host, origin, stanza) --luacheck: ignore local st_type = stanza.attr.type; if st_type == "error" or (name == "iq" and st_type == "result") then if st_type == "error" then - local err_type, err_condition, err_message = stanza:get_error(); + local err_type, err_condition, err_message = stanza:get_error(); -- luacheck: ignore 211/err_message log("debug", "Discarding unhandled error %s (%s, %s) from %s: %s", name, err_type, err_condition or "unknown condition", origin_type, stanza:top_tag()); else -- cgit v1.2.3 From 3f89e9373b5978f18bed6b62bc1dceb3887364d8 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 20 Dec 2019 22:38:45 +0100 Subject: core.modulemanager: Silence warning about unused err variable [luacheck] --- core/modulemanager.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/modulemanager.lua b/core/modulemanager.lua index 456f09f6..e23f1e55 100644 --- a/core/modulemanager.lua +++ b/core/modulemanager.lua @@ -246,7 +246,8 @@ local function do_reload_module(host, name) local saved; if module_has_method(mod, "save") then - local ok, ret, err = call_module_method(mod, "save"); + -- FIXME What goes in 'err' here? + local ok, ret, err = call_module_method(mod, "save"); -- luacheck: ignore 211/err if ok then saved = ret; else -- cgit v1.2.3 From 0bbfb60aaead427610dc593d646c906387af3468 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 23 Dec 2019 21:01:55 +0100 Subject: util.datamanager: Ignore unused 'errno' variable [luacheck] --- util/datamanager.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/util/datamanager.lua b/util/datamanager.lua index b52c77fa..1c578fee 100644 --- a/util/datamanager.lua +++ b/util/datamanager.lua @@ -157,7 +157,8 @@ end local function atomic_store(filename, data) local scratch = filename.."~"; - local f, ok, msg, errno; + local f, ok, msg, errno; -- luacheck: ignore errno + -- TODO return util.error with code=errno? f, msg, errno = io_open(scratch, "w"); if not f then -- cgit v1.2.3 From bbb1aae80fb294dbfcc0986ae049f91edcd5357d Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 23 Dec 2019 21:02:19 +0100 Subject: util.startup: Ignore unused errno variable [luacheck] --- util/startup.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/startup.lua b/util/startup.lua index 8e6d89e6..dd757dd1 100644 --- a/util/startup.lua +++ b/util/startup.lua @@ -448,7 +448,7 @@ function startup.switch_user() print("Warning: Couldn't switch to Prosody user/group '"..tostring(desired_user).."'/'"..tostring(desired_group).."': "..tostring(err)); else -- Make sure the Prosody user can read the config - local conf, err, errno = io.open(prosody.config_file); + local conf, err, errno = io.open(prosody.config_file); --luacheck: ignore 211/errno if conf then conf:close(); else -- cgit v1.2.3 From 613e3eb3dc54e3ed75353fbd4a2a0902985ec357 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 23 Dec 2019 21:09:38 +0100 Subject: util.mercurial: Ignore an unused error variable [luacheck] --- util/mercurial.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/mercurial.lua b/util/mercurial.lua index 3f75c4c1..0f2b1d04 100644 --- a/util/mercurial.lua +++ b/util/mercurial.lua @@ -19,7 +19,7 @@ function hg.check_id(path) hg_changelog:close(); end else - local hg_archival,e = io.open(path.."/.hg_archival.txt"); + local hg_archival,e = io.open(path.."/.hg_archival.txt"); -- luacheck: ignore 211/e if hg_archival then local repo = hg_archival:read("*l"); local node = hg_archival:read("*l"); -- cgit v1.2.3 From b0361006e16aa8d861e85aea5a4ab3bc163dc229 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 23 Dec 2019 21:13:27 +0100 Subject: util.sql: Handle failure to detect connection encoding Silences a luacheck warning about an unused variable --- util/sql.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/util/sql.lua b/util/sql.lua index 86740b1c..9d1c86ca 100644 --- a/util/sql.lua +++ b/util/sql.lua @@ -335,6 +335,9 @@ function engine:set_encoding() -- to UTF-8 local ok, actual_charset = self:transaction(function () return self:select"SHOW SESSION VARIABLES LIKE 'character_set_client'"; end); + if not ok then + return false, "Failed to detect connection encoding"; + end local charset_ok = true; for row in actual_charset do if row[2] ~= charset then -- cgit v1.2.3 From f8e36355eef43f02211b3e7cdb501c3ed3d36b8d Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 23 Dec 2019 21:15:01 +0100 Subject: util.dependencies: Pass require error to error formatting function For future use there. Silences luacheck warnings about unused 'err' --- util/dependencies.lua | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/util/dependencies.lua b/util/dependencies.lua index 22b66d7c..ede8c6ac 100644 --- a/util/dependencies.lua +++ b/util/dependencies.lua @@ -13,7 +13,8 @@ if not softreq "luarocks.loader" then -- LuaRocks 2.x softreq "luarocks.require"; -- LuaRocks <1.x end -local function missingdep(name, sources, msg) +local function missingdep(name, sources, msg, err) -- luacheck: ignore err + -- TODO print something about the underlying error, useful for debugging print(""); print("**************************"); print("Prosody was unable to find "..tostring(name)); @@ -44,25 +45,25 @@ local function check_dependencies() local fatal; - local lxp = softreq "lxp" + local lxp, err = softreq "lxp" if not lxp then missingdep("luaexpat", { ["Debian/Ubuntu"] = "sudo apt-get install lua-expat"; ["luarocks"] = "luarocks install luaexpat"; ["Source"] = "http://matthewwild.co.uk/projects/luaexpat/"; - }); + }, nil, err); fatal = true; end - local socket = softreq "socket" + local socket, err = softreq "socket" if not socket then missingdep("luasocket", { ["Debian/Ubuntu"] = "sudo apt-get install lua-socket"; ["luarocks"] = "luarocks install luasocket"; ["Source"] = "http://www.tecgraf.puc-rio.br/~diego/professional/luasocket/"; - }); + }, nil, err); fatal = true; elseif not socket.tcp4 then -- COMPAT LuaSocket before being IP-version agnostic @@ -76,28 +77,28 @@ local function check_dependencies() ["luarocks"] = "luarocks install luafilesystem"; ["Debian/Ubuntu"] = "sudo apt-get install lua-filesystem"; ["Source"] = "http://www.keplerproject.org/luafilesystem/"; - }); + }, nil, err); fatal = true; end - local ssl = softreq "ssl" + local ssl, err = softreq "ssl" if not ssl then missingdep("LuaSec", { ["Debian/Ubuntu"] = "sudo apt-get install lua-sec"; ["luarocks"] = "luarocks install luasec"; ["Source"] = "https://github.com/brunoos/luasec"; - }, "SSL/TLS support will not be available"); + }, "SSL/TLS support will not be available", err); end - local bit = softreq"util.bitcompat"; + local bit, err = softreq"util.bitcompat"; if not bit then missingdep("lua-bitops", { ["Debian/Ubuntu"] = "sudo apt-get install lua-bitop"; ["luarocks"] = "luarocks install luabitop"; ["Source"] = "http://bitop.luajit.org/"; - }, "WebSocket support will not be available"); + }, "WebSocket support will not be available", err); end local encodings, err = softreq "util.encodings" -- cgit v1.2.3 From 14d66d84121f2ff5a90da95d6b713d91910a9aa7 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 23 Dec 2019 21:16:00 +0100 Subject: util.prosodyctl: Silence luacheck warnings --- util/prosodyctl.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/util/prosodyctl.lua b/util/prosodyctl.lua index 586802d3..0787b8c3 100644 --- a/util/prosodyctl.lua +++ b/util/prosodyctl.lua @@ -210,7 +210,7 @@ local function getpid() return false, "pidfile-read-failed", err; end - local locked, err = lfs.lock(file, "w"); + local locked, err = lfs.lock(file, "w"); -- luacheck: ignore 211/err if locked then file:close(); return false, "pidfile-not-locked"; @@ -227,7 +227,7 @@ local function getpid() end local function isrunning() - local ok, pid, err = getpid(); + local ok, pid, err = getpid(); -- luacheck: ignore 211/err if not ok then if pid == "pidfile-read-failed" or pid == "pidfile-not-locked" then -- Report as not running, since we can't open the pidfile -- cgit v1.2.3 From 0c9d47f6ff0bfae6f16040c64c15f041794bc08e Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 23 Dec 2019 21:16:10 +0100 Subject: util.pubsub: Silence luacheck warnings, leaving notes on future proper fix --- util/pubsub.lua | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/util/pubsub.lua b/util/pubsub.lua index 2e9df0e7..cfac7a68 100644 --- a/util/pubsub.lua +++ b/util/pubsub.lua @@ -281,7 +281,8 @@ function service:set_affiliation(node, actor, jid, affiliation) --> ok, err node_obj.affiliations[jid] = affiliation; if self.config.nodestore then - local ok, err = save_node_to_store(self, node_obj); + -- TODO pass the error from storage to caller eg wrapped in an util.error + local ok, err = save_node_to_store(self, node_obj); -- luacheck: ignore 211/err if not ok then node_obj.affiliations[jid] = old_affiliation; return ok, "internal-server-error"; @@ -345,7 +346,8 @@ function service:add_subscription(node, actor, jid, options) --> ok, err end if self.config.nodestore then - local ok, err = save_node_to_store(self, node_obj); + -- TODO pass the error from storage to caller eg wrapped in an util.error + local ok, err = save_node_to_store(self, node_obj); -- luacheck: ignore 211/err if not ok then node_obj.subscribers[jid] = old_subscription; self.subscriptions[normal_jid][jid][node] = old_subscription and true or nil; @@ -397,7 +399,8 @@ function service:remove_subscription(node, actor, jid) --> ok, err end if self.config.nodestore then - local ok, err = save_node_to_store(self, node_obj); + -- TODO pass the error from storage to caller eg wrapped in an util.error + local ok, err = save_node_to_store(self, node_obj); -- luacheck: ignore 211/err if not ok then node_obj.subscribers[jid] = old_subscription; self.subscriptions[normal_jid][jid][node] = old_subscription and true or nil; @@ -455,7 +458,8 @@ function service:create(node, actor, options) --> ok, err }; if self.config.nodestore then - local ok, err = save_node_to_store(self, self.nodes[node]); + -- TODO pass the error from storage to caller eg wrapped in an util.error + local ok, err = save_node_to_store(self, self.nodes[node]); -- luacheck: ignore 211/err if not ok then self.nodes[node] = nil; return ok, "internal-server-error"; @@ -774,7 +778,8 @@ function service:set_node_config(node, actor, new_config) --> ok, err node_obj.config = new_config; if self.config.nodestore then - local ok, err = save_node_to_store(self, node_obj); + -- TODO pass the error from storage to caller eg wrapped in an util.error + local ok, err = save_node_to_store(self, node_obj); -- luacheck: ignore 211/err if not ok then node_obj.config = old_config; return ok, "internal-server-error"; -- cgit v1.2.3 From c4c6a44c3089cafa59bdea640e0a2e90951a9058 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 23 Dec 2019 21:26:51 +0100 Subject: core.portmanager: Ignore unused return variable [luacheck] --- core/portmanager.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/core/portmanager.lua b/core/portmanager.lua index 99656e3e..0712f5ac 100644 --- a/core/portmanager.lua +++ b/core/portmanager.lua @@ -237,6 +237,7 @@ local function add_sni_host(host, service) if config_prefix == "_" then config_prefix = ""; end local prefix_ssl_config = config.get(host, config_prefix.."ssl"); local autocert = certmanager.find_host_cert(host); + -- luacheck: ignore 211/cfg local ssl, err, cfg = certmanager.create_context(host, "server", prefix_ssl_config, autocert, active_service.tls_cfg); if ssl then active_service.server.hosts[host] = ssl; -- cgit v1.2.3 From 83c028be30d1467f255756c081c1261ff294a522 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 23 Dec 2019 21:27:34 +0100 Subject: core.statsmanager: Ignore unused variable [luacheck] --- core/statsmanager.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/core/statsmanager.lua b/core/statsmanager.lua index 50798ad0..d3f1d4f5 100644 --- a/core/statsmanager.lua +++ b/core/statsmanager.lua @@ -79,6 +79,7 @@ if stats then if stats.get_stats then changed_stats, stats_extra = {}, {}; for stat_name, getter in pairs(stats.get_stats()) do + -- luacheck: ignore 211/type local type, value, extra = getter(); local old_value = latest_stats[stat_name]; latest_stats[stat_name] = value; -- cgit v1.2.3 From 248ec3f83401528152c3b34dd81e994563706fea Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 23 Dec 2019 21:29:34 +0100 Subject: net.http.parser: Silence warning about unused variable [luacheck] --- net/http/parser.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/http/parser.lua b/net/http/parser.lua index 4e4ae9fb..62c09aef 100644 --- a/net/http/parser.lua +++ b/net/http/parser.lua @@ -63,7 +63,8 @@ function httpstream.new(success_cb, error_cb, parser_type, options_cb) if buftable then buf, buftable = t_concat(buf), false; end local index = buf:find("\r\n\r\n", nil, true); if not index then return; end -- not enough data - local method, path, httpversion, status_code, reason_phrase; + -- FIXME was reason_phrase meant to be passed on somewhere? + local method, path, httpversion, status_code, reason_phrase; -- luacheck: ignore reason_phrase local first_line; local headers = {}; for line in buf:sub(1,index+1):gmatch("([^\r\n]+)\r\n") do -- parse request -- cgit v1.2.3 From e0bcb4d7d43745ce677edabc1cebe6a53644723d Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 23 Dec 2019 21:33:10 +0100 Subject: tests: Silence [luacheck] warnings --- spec/core_storagemanager_spec.lua | 6 ++++++ spec/util_async_spec.lua | 2 ++ spec/util_pubsub_spec.lua | 6 ++++++ 3 files changed, 14 insertions(+) diff --git a/spec/core_storagemanager_spec.lua b/spec/core_storagemanager_spec.lua index fd2f8742..c3534cd5 100644 --- a/spec/core_storagemanager_spec.lua +++ b/spec/core_storagemanager_spec.lua @@ -119,6 +119,7 @@ describe("storagemanager", function () describe("can be queried", function () it("for all items", function () + -- luacheck: ignore 211/err local data, err = archive:find("user", {}); assert.truthy(data); local count = 0; @@ -135,6 +136,7 @@ describe("storagemanager", function () end); it("by JID", function () + -- luacheck: ignore 211/err local data, err = archive:find("user", { with = "contact@example.com"; }); @@ -153,6 +155,7 @@ describe("storagemanager", function () end); it("by time (end)", function () + -- luacheck: ignore 211/err local data, err = archive:find("user", { ["end"] = test_time; }); @@ -171,6 +174,7 @@ describe("storagemanager", function () end); it("by time (start)", function () + -- luacheck: ignore 211/err local data, err = archive:find("user", { ["start"] = test_time; }); @@ -189,6 +193,7 @@ describe("storagemanager", function () end); it("by time (start+end)", function () + -- luacheck: ignore 211/err local data, err = archive:find("user", { ["start"] = test_time; ["end"] = test_time+1; @@ -239,6 +244,7 @@ describe("storagemanager", function () end); it("can be purged", function () + -- luacheck: ignore 211/err local ok, err = archive:delete("user"); assert.truthy(ok); local data, err = archive:find("user", { diff --git a/spec/util_async_spec.lua b/spec/util_async_spec.lua index d2de8c94..8123503b 100644 --- a/spec/util_async_spec.lua +++ b/spec/util_async_spec.lua @@ -544,6 +544,8 @@ describe("util.async", function() assert.equal(r1.state, "ready"); end); + -- luacheck: ignore 211/rf + -- FIXME what's rf? it("should support multiple done() calls", function () local processed_item; local wait, done; diff --git a/spec/util_pubsub_spec.lua b/spec/util_pubsub_spec.lua index a48bd400..75f893e3 100644 --- a/spec/util_pubsub_spec.lua +++ b/spec/util_pubsub_spec.lua @@ -101,6 +101,7 @@ describe("util.pubsub", function () assert(service:publish("node", true, "1", "item 1", { myoption = true })); local ok, config = assert(service:get_node_config("node", true)); + assert.truthy(ok); assert.equals(true, config.myoption); end); @@ -229,6 +230,7 @@ describe("util.pubsub", function () end); it("should be the default", function () local ok, config = service:get_node_config("test", true); + assert.truthy(ok); assert.equal("open", config.access_model); end); it("should allow anyone to subscribe", function () @@ -250,6 +252,7 @@ describe("util.pubsub", function () end); it("should be present in the configuration", function () local ok, config = service:get_node_config("test", true); + assert.truthy(ok); assert.equal("whitelist", config.access_model); end); it("should not allow anyone to subscribe", function () @@ -294,6 +297,7 @@ describe("util.pubsub", function () end); it("should be the default", function () local ok, config = service:get_node_config("test", true); + assert.truthy(ok); assert.equal("publishers", config.publish_model); end); it("should not allow anyone to publish", function () @@ -304,6 +308,7 @@ describe("util.pubsub", function () end); it("should allow publishers to publish", function () assert(service:set_affiliation("test", true, "mypublisher", "publisher")); + -- luacheck: ignore 211/err local ok, err = service:publish("test", "mypublisher", "item1", "foo"); assert.is_true(ok); end); @@ -342,6 +347,7 @@ describe("util.pubsub", function () end); it("should allow publishers to publish without a subscription", function () assert(service:set_affiliation("test", true, "mypublisher", "publisher")); + -- luacheck: ignore 211/err local ok, err = service:publish("test", "mypublisher", "item1", "foo"); assert.is_true(ok); end); -- cgit v1.2.3 From b843b92fe0a352f75529dd11ac83bd40be545d3b Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 23 Dec 2019 21:34:34 +0100 Subject: mod_adhoc: Remove unused variable [luacheck] --- plugins/adhoc/mod_adhoc.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/adhoc/mod_adhoc.lua b/plugins/adhoc/mod_adhoc.lua index bf1775b4..f6553773 100644 --- a/plugins/adhoc/mod_adhoc.lua +++ b/plugins/adhoc/mod_adhoc.lua @@ -8,7 +8,7 @@ local it = require "util.iterators"; local st = require "util.stanza"; local is_admin = require "core.usermanager".is_admin; -local jid_split = require "util.jid".split; +local jid_host = require "util.jid".host; local adhoc_handle_cmd = module:require "adhoc".handle_cmd; local xmlns_cmd = "http://jabber.org/protocol/commands"; local commands = {}; @@ -21,7 +21,7 @@ module:hook("host-disco-info-node", function (event) local from = stanza.attr.from; local privileged = is_admin(from, stanza.attr.to); local global_admin = is_admin(from); - local username, hostname = jid_split(from); + local hostname = jid_host(from); local command = commands[node]; if (command.permission == "admin" and privileged) or (command.permission == "global_admin" and global_admin) @@ -52,7 +52,7 @@ module:hook("host-disco-items-node", function (event) local from = stanza.attr.from; local admin = is_admin(from, stanza.attr.to); local global_admin = is_admin(from); - local username, hostname = jid_split(from); + local hostname = jid_host(from); for node, command in it.sorted_pairs(commands) do if (command.permission == "admin" and admin) or (command.permission == "global_admin" and global_admin) @@ -74,7 +74,7 @@ module:hook("iq-set/host/"..xmlns_cmd..":command", function (event) local from = stanza.attr.from; local admin = is_admin(from, stanza.attr.to); local global_admin = is_admin(from); - local username, hostname = jid_split(from); + local hostname = jid_host(from); if (command.permission == "admin" and not admin) or (command.permission == "global_admin" and not global_admin) or (command.permission == "local_user" and hostname ~= module.host) then -- cgit v1.2.3 From 4340a5d1d4073972f85898d7ff0eece205fe5f9b Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 23 Dec 2019 21:37:16 +0100 Subject: mod_admin_adhoc: Remove unused JID resource variables [luacheck] --- plugins/mod_admin_adhoc.lua | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/mod_admin_adhoc.lua b/plugins/mod_admin_adhoc.lua index 4fea5a73..674b3339 100644 --- a/plugins/mod_admin_adhoc.lua +++ b/plugins/mod_admin_adhoc.lua @@ -59,7 +59,7 @@ local add_user_command_handler = adhoc_simple(add_user_layout, function(fields, if err then return generate_error_message(err); end - local username, host, resource = jid.split(fields.accountjid); + local username, host = jid.split(fields.accountjid); if module_host ~= host then return { status = "completed", error = { message = "Trying to add a user on " .. host .. " but command was sent to " .. module_host}}; end @@ -94,7 +94,7 @@ local change_user_password_command_handler = adhoc_simple(change_user_password_l if err then return generate_error_message(err); end - local username, host, resource = jid.split(fields.accountjid); + local username, host = jid.split(fields.accountjid); if module_host ~= host then return { status = "completed", @@ -136,7 +136,7 @@ local delete_user_command_handler = adhoc_simple(delete_user_layout, function(fi local failed = {}; local succeeded = {}; for _, aJID in ipairs(fields.accountjids) do - local username, host, resource = jid.split(aJID); + local username, host = jid.split(aJID); if (host == module_host) and usermanager_user_exists(username, host) and usermanager_delete_user(username, host) then module:log("debug", "User %s has been deleted", aJID); succeeded[#succeeded+1] = aJID; @@ -180,7 +180,7 @@ local end_user_session_handler = adhoc_simple(end_user_session_layout, function( local failed = {}; local succeeded = {}; for _, aJID in ipairs(fields.accountjids) do - local username, host, resource = jid.split(aJID); + local username, host = jid.split(aJID); if (host == module_host) and usermanager_user_exists(username, host) and disconnect_user(aJID) then succeeded[#succeeded+1] = aJID; else @@ -212,7 +212,7 @@ local get_user_password_handler = adhoc_simple(get_user_password_layout, functio if err then return generate_error_message(err); end - local user, host, resource = jid.split(fields.accountjid); + local user, host = jid.split(fields.accountjid); local accountjid; local password; if host ~= module_host then @@ -243,7 +243,7 @@ local get_user_roster_handler = adhoc_simple(get_user_roster_layout, function(fi return generate_error_message(err); end - local user, host, resource = jid.split(fields.accountjid); + local user, host = jid.split(fields.accountjid); if host ~= module_host then return { status = "completed", error = { message = "Tried to get roster for a user on " .. host .. " but command was sent to " .. module_host } }; elseif not usermanager_user_exists(user, host) then -- cgit v1.2.3 From d7df11baf30a0308a8ed1c40b365051a19c74f92 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 23 Dec 2019 21:38:19 +0100 Subject: mod_admin_telnet: Silence luacheck warnings --- plugins/mod_admin_telnet.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index f298ad2f..7df63da9 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -125,6 +125,7 @@ function console:process_line(session, line) local chunkname = "=console"; local env = (useglobalenv and redirect_output(_G, session)) or session.env or nil + -- luacheck: ignore 311/err local chunk, err = envload("return "..line, chunkname, env); if not chunk then chunk, err = envload(line, chunkname, env); @@ -1427,7 +1428,7 @@ end function stats_methods:cfgraph() for _, stat_info in ipairs(self) do - local name, type, value, data = unpack(stat_info, 1, 4); + local name, type, value, data = unpack(stat_info, 1, 4); -- luacheck: ignore 211 local function print(s) table.insert(stat_info.output, s); end @@ -1493,7 +1494,7 @@ end function stats_methods:histogram() for _, stat_info in ipairs(self) do - local name, type, value, data = unpack(stat_info, 1, 4); + local name, type, value, data = unpack(stat_info, 1, 4); -- luacheck: ignore 211 local function print(s) table.insert(stat_info.output, s); end @@ -1593,6 +1594,7 @@ local function new_stats_context(self) end function def_env.stats:show(filter) + -- luacheck: ignore 211/changed local stats, changed, extra = require "core.statsmanager".get_stats(); local available, displayed = 0, 0; local displayed_stats = new_stats_context(self); -- cgit v1.2.3 From a2cfaf8c28ec3f90ec0f548b240091053d5571c6 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 23 Dec 2019 21:43:34 +0100 Subject: mod_announce: Silence luacheck warning about unused variable --- plugins/mod_announce.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/mod_announce.lua b/plugins/mod_announce.lua index 970a273a..c742ebb8 100644 --- a/plugins/mod_announce.lua +++ b/plugins/mod_announce.lua @@ -38,6 +38,7 @@ end -- Old -based jabberd-style announcement sending function handle_announcement(event) local stanza = event.stanza; + -- luacheck: ignore 211/node local node, host, resource = jid.split(stanza.attr.to); if resource ~= "announce/online" then -- cgit v1.2.3 From c3d1266e34e25c71692c109ee597160d9d7231f3 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 23 Dec 2019 21:51:52 +0100 Subject: net.server_epoll: Remove an unused variable [luacheck] --- net/server_epoll.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index 46fcbf02..e2c5f977 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -114,7 +114,7 @@ local function runtimers(next_delay, min_wait) break; end - local _, timer, id = timers:pop(); + local _, timer = timers:pop(); local ok, ret = pcall(timer[2], now); if ok and type(ret) == "number" then local next_time = elapsed+ret; -- cgit v1.2.3 From 61273591cdd37e19284a070389ceb7bf040a5a1f Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 23 Dec 2019 21:52:28 +0100 Subject: net.server_event: Silence luacheck warnings --- net/server_event.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/server_event.lua b/net/server_event.lua index a8279c42..f7e1f448 100644 --- a/net/server_event.lua +++ b/net/server_event.lua @@ -692,6 +692,7 @@ local function handleserver( server, addr, port, pattern, listener, sslctx, star end end --vdebug("max connection check ok, accepting...") + -- luacheck: ignore 231/err local client, err = server:accept() -- try to accept; TODO: check err while client do if interface._connections >= cfg.MAX_CONNECTIONS then @@ -780,6 +781,7 @@ local function addclient( addr, serverport, listener, pattern, sslctx, typ, extr client:settimeout( 0 ) -- set nonblocking local res, err = client:setpeername( addr, serverport ) -- connect if res or ( err == "timeout" ) then + -- luacheck: ignore 211/port local ip, port = client:getsockname( ) local interface = wrapclient( client, ip, serverport, listener, pattern, sslctx, extra ) debug( "new connection id:", interface.id ) -- cgit v1.2.3 From dd7a972108d1632d28e08fd92a5aa8f728eb5943 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 23 Dec 2019 21:52:50 +0100 Subject: mod_http_files: Log something if unable to load MIME database Not that much to worry about, the most common file types are included in the code above. --- plugins/mod_http_files.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/mod_http_files.lua b/plugins/mod_http_files.lua index c357ddfc..4d0b14cd 100644 --- a/plugins/mod_http_files.lua +++ b/plugins/mod_http_files.lua @@ -33,7 +33,9 @@ if not mime_map then module:shared("/*/http_files/mime").types = mime_map; local mime_types, err = open(module:get_option_path("mime_types_file", "/etc/mime.types", "config"), "r"); - if mime_types then + if not mime_types then + module:log("debug", "Could not open MIME database: %s", err); + else local mime_data = mime_types:read("*a"); mime_types:close(); setmetatable(mime_map, { -- cgit v1.2.3 From e660a65d2856bbc5c20c41c8b0d4a722b187880c Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 23 Dec 2019 21:55:41 +0100 Subject: mod_vcard_legacy: Ignore an unused variable [luacheck] --- plugins/mod_vcard_legacy.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_vcard_legacy.lua b/plugins/mod_vcard_legacy.lua index 6bae3ee5..a6ff47d0 100644 --- a/plugins/mod_vcard_legacy.lua +++ b/plugins/mod_vcard_legacy.lua @@ -38,7 +38,7 @@ local simple_map = { module:hook("iq-get/bare/vcard-temp:vCard", function (event) local origin, stanza = event.origin, event.stanza; local pep_service = mod_pep.get_pep_service(jid_split(stanza.attr.to) or origin.username); - local ok, id, vcard4_item = pep_service:get_last_item("urn:xmpp:vcard4", stanza.attr.from); + local ok, _, vcard4_item = pep_service:get_last_item("urn:xmpp:vcard4", stanza.attr.from); local vcard_temp = st.stanza("vCard", { xmlns = "vcard-temp" }); if ok and vcard4_item then -- cgit v1.2.3 From 72a8c497ec2920ec0ab01ce2f5d01b514da58b8a Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 23 Dec 2019 22:23:29 +0100 Subject: MUC: Remove some unused variables [luacheck] --- plugins/muc/muc.lib.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 6b5f6068..7226a7bf 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -385,7 +385,7 @@ function room_mt:handle_kickable(origin, stanza) -- luacheck: ignore 212 local real_jid = stanza.attr.from; local occupant = self:get_occupant_by_real_jid(real_jid); if occupant == nil then return nil; end - local type, condition, text = stanza:get_error(); + local _, condition, text = stanza:get_error(); local error_message = "Kicked: "..(condition and condition:gsub("%-", " ") or "presence error"); if text and self:get_whois() == "anyone" then error_message = error_message..": "..text; @@ -1254,7 +1254,7 @@ function room_mt:route_stanza(stanza) -- luacheck: ignore 212 end function room_mt:get_affiliation(jid) - local node, host, resource = jid_split(jid); + local node, host = jid_split(jid); -- Affiliations are granted, revoked, and maintained based on the user's bare JID. local bare = node and node.."@"..host or host; local result = self._affiliations[bare]; @@ -1277,7 +1277,7 @@ end function room_mt:set_affiliation(actor, jid, affiliation, reason, data) if not actor then return nil, "modify", "not-acceptable"; end; - local node, host, resource = jid_split(jid); + local node, host = jid_split(jid); if not host then return nil, "modify", "not-acceptable"; end jid = jid_join(node, host); -- Bare local is_host_only = node == nil; @@ -1571,7 +1571,7 @@ function _M.restore_room(frozen, state) else -- New storage format for jid, data in pairs(frozen) do - local node, host, resource = jid_split(jid); + local _, host, resource = jid_split(jid); if host:sub(1,1) ~= "_" and not resource and type(data) == "string" then -- bare jid: affiliation room._affiliations[jid] = data; -- cgit v1.2.3 From 7c8ad7bede58bd7a37da11eec8fef6ed83dc37f0 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 23 Dec 2019 22:24:31 +0100 Subject: mod_limits: Remove an unused variable Hope this isn't meant to be used. 'outstanding' seems to be the more useful value anyways? --- plugins/mod_limits.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_limits.lua b/plugins/mod_limits.lua index a1a3b2c0..024ab686 100644 --- a/plugins/mod_limits.lua +++ b/plugins/mod_limits.lua @@ -53,7 +53,7 @@ local default_filter_set = {}; function default_filter_set.bytes_in(bytes, session) local sess_throttle = session.throttle; if sess_throttle then - local ok, balance, outstanding = sess_throttle:poll(#bytes, true); + local ok, _, outstanding = sess_throttle:poll(#bytes, true); if not ok then session.log("debug", "Session over rate limit (%d) with %d (by %d), pausing", sess_throttle.max, #bytes, outstanding); outstanding = ceil(outstanding); -- cgit v1.2.3 From 2d7c3d090b88245b323b9b1f474b587a030d6e3e Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 23 Dec 2019 22:27:25 +0100 Subject: mod_vcard: Remove unused variable [luacheck] --- plugins/mod_vcard.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_vcard.lua b/plugins/mod_vcard.lua index b1a4c6e8..c3d6fb8b 100644 --- a/plugins/mod_vcard.lua +++ b/plugins/mod_vcard.lua @@ -19,7 +19,7 @@ local function handle_vcard(event) if stanza.attr.type == "get" then local vCard; if to then - local node, host = jid_split(to); + local node = jid_split(to); vCard = st.deserialize(vcards:get(node)); -- load vCard for user or server else vCard = st.deserialize(vcards:get(session.username));-- load user's own vCard -- cgit v1.2.3 From a95900e81c072dc7956addc70b1664695a18261e Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 23 Dec 2019 22:28:04 +0100 Subject: MUC: Make note to handle configuration form errors [luacheck] --- plugins/muc/muc.lib.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 7226a7bf..399b090e 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -844,10 +844,12 @@ function room_mt:process_form(origin, stanza) if form.attr.type == "cancel" then origin.send(st.reply(stanza)); elseif form.attr.type == "submit" then + -- luacheck: ignore 231/errors local fields, errors, present; if form.tags[1] == nil then -- Instant room fields, present = {}, {}; else + -- FIXME handle form errors fields, errors, present = 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")); -- cgit v1.2.3 From 9861fc79f11197e65bd9837a3cda5c2a6c321d8a Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 23 Dec 2019 22:28:42 +0100 Subject: mod_pubsub: Ignore an unused variable [luacheck] --- plugins/mod_pubsub/pubsub.lib.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/mod_pubsub/pubsub.lib.lua b/plugins/mod_pubsub/pubsub.lib.lua index 23695211..0938dbbc 100644 --- a/plugins/mod_pubsub/pubsub.lib.lua +++ b/plugins/mod_pubsub/pubsub.lib.lua @@ -810,6 +810,7 @@ local function archive_itemstore(archive, config, user, node) end module:log("debug", "Listed items %s", data); return it.reverse(function() + -- luacheck: ignore 211/when local id, payload, when, publisher = data(); if id == nil then return; -- cgit v1.2.3 From 1bd2db0a6c060dc2faf3421811c3eaffee498233 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 23 Dec 2019 22:29:13 +0100 Subject: mod_pep_simple: Ignore unused variable [luacheck] --- plugins/mod_pep_simple.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/mod_pep_simple.lua b/plugins/mod_pep_simple.lua index 11268ab7..e686b99b 100644 --- a/plugins/mod_pep_simple.lua +++ b/plugins/mod_pep_simple.lua @@ -85,6 +85,7 @@ local function publish_all(user, recipient, session) if d and notify then for node in pairs(notify) do if d[node] then + -- luacheck: ignore id local id, item = unpack(d[node]); session.send(st.message({from=user, to=recipient, type='headline'}) :tag('event', {xmlns='http://jabber.org/protocol/pubsub#event'}) -- cgit v1.2.3 From 77daebcbc2b412173ac9a41dd5327ff269902f90 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 23 Dec 2019 22:30:21 +0100 Subject: mod_presence: Ignore an unused variable [luacheck] Not sure if it should be unused, hence the TODO --- plugins/mod_presence.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/mod_presence.lua b/plugins/mod_presence.lua index b874277c..e69c31a5 100644 --- a/plugins/mod_presence.lua +++ b/plugins/mod_presence.lua @@ -192,6 +192,8 @@ function handle_outbound_presence_subscriptions_and_probes(origin, stanza, from_ -- 1. send unavailable -- 2. route stanza -- 3. roster push (subscription = from or both) + -- luacheck: ignore 211/pending_in + -- Is pending_in meant to be used? local success, pending_in, subscribed = rostermanager.unsubscribed(node, host, to_bare); if success then if subscribed then -- cgit v1.2.3 From 4b548a129b90bbd17606bfcfe79bda47731ebe5d Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 23 Dec 2019 23:11:28 +0100 Subject: mod_legacyauth: Report failure from sessionmanager (mostly invalid username) --- plugins/mod_legacyauth.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/mod_legacyauth.lua b/plugins/mod_legacyauth.lua index 0f41d3e7..941806d3 100644 --- a/plugins/mod_legacyauth.lua +++ b/plugins/mod_legacyauth.lua @@ -78,8 +78,10 @@ module:hook("stanza/iq/jabber:iq:auth:query", function(event) session:close(); -- FIXME undo resource bind and auth instead of closing the session? return true; end + session.send(st.reply(stanza)); + else + session.send(st.error_reply(stanza, "auth", "not-authorized", err)); end - session.send(st.reply(stanza)); else session.send(st.error_reply(stanza, "auth", "not-authorized")); end -- cgit v1.2.3 From 5b06f8946f51c90fe3dd4ed9cfba91e5ad83f1b7 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 23 Dec 2019 23:28:44 +0100 Subject: mod_muc_mam: Handle form identification error (e.g. not a form at all) --- plugins/mod_muc_mam.lua | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/mod_muc_mam.lua b/plugins/mod_muc_mam.lua index 1df93a18..fd98e205 100644 --- a/plugins/mod_muc_mam.lua +++ b/plugins/mod_muc_mam.lua @@ -145,7 +145,10 @@ module:hook("iq-set/bare/"..xmlns_mam..":query", function(event) local form = query:get_child("x", "jabber:x:data"); if form then local form_type, err = get_form_type(form); - if form_type ~= xmlns_mam then + if not form_type then + origin.send(st.error_reply(stanza, "modify", "bad-request", "Invalid dataform: "..err)); + return true; + elseif form_type ~= xmlns_mam then origin.send(st.error_reply(stanza, "modify", "bad-request", "Unexpected FORM_TYPE, expected '"..xmlns_mam.."'")); return true; end -- cgit v1.2.3 From 1c3988b1719e164b38c75eff73aa3df52c531ae7 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 23 Dec 2019 23:30:45 +0100 Subject: mod_mam: More careful validation of MAM query form Adapted from mod_muc_mam --- plugins/mod_mam/mod_mam.lua | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/plugins/mod_mam/mod_mam.lua b/plugins/mod_mam/mod_mam.lua index e9528d52..018aef77 100644 --- a/plugins/mod_mam/mod_mam.lua +++ b/plugins/mod_mam/mod_mam.lua @@ -25,6 +25,7 @@ local jid_bare = require "util.jid".bare; local jid_split = require "util.jid".split; local jid_prepped_split = require "util.jid".prepped_split; local dataform = require "util.dataforms".new; +local get_form_type = require "util.dataforms".get_type; local host = module.host; local rm_load_roster = require "core.rostermanager".load_roster; @@ -101,7 +102,14 @@ module:hook("iq-set/self/"..xmlns_mam..":query", function(event) local qwith, qstart, qend; local form = query:get_child("x", "jabber:x:data"); if form then - local err; + local form_type, err = get_form_type(form); + if not form_type then + origin.send(st.error_reply(stanza, "modify", "bad-request", "Invalid dataform: "..err)); + return true; + elseif form_type ~= xmlns_mam then + origin.send(st.error_reply(stanza, "modify", "bad-request", "Unexpected FORM_TYPE, expected '"..xmlns_mam.."'")); + return true; + end form, err = query_form:data(form); if err then origin.send(st.error_reply(stanza, "modify", "bad-request", select(2, next(err)))); -- cgit v1.2.3 From 8537138d41a6bca5e855040c8928ef2aae59802f Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 23 Dec 2019 23:31:09 +0100 Subject: mod_muc_mam: Remove unused variable [luacheck] --- plugins/mod_muc_mam.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_muc_mam.lua b/plugins/mod_muc_mam.lua index fd98e205..2e26893a 100644 --- a/plugins/mod_muc_mam.lua +++ b/plugins/mod_muc_mam.lua @@ -354,7 +354,7 @@ end, 0); -- Handle messages local function save_to_history(self, stanza) - local room_node, room_host = jid_split(self.jid); + local room_node = jid_split(self.jid); local stored_stanza = stanza; -- cgit v1.2.3 From 96c1406bcdea0d0e98b70fb968d5d315790cc391 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 24 Dec 2019 00:49:43 +0100 Subject: mod_storage_sql: Remove unused and not actually returned return value [luacheck] The :delete throws an error, it does not return one like this. --- plugins/mod_storage_sql.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_storage_sql.lua b/plugins/mod_storage_sql.lua index 666cee41..8172b853 100644 --- a/plugins/mod_storage_sql.lua +++ b/plugins/mod_storage_sql.lua @@ -281,7 +281,7 @@ function archive_store:append(username, key, value, when, with) VALUES (?,?,?,?,?,?,?,?); ]]; if key then - local result, err = engine:delete(delete_sql, host, user or "", store, key); + local result = engine:delete(delete_sql, host, user or "", store, key); if result then item_count = item_count - result:affected(); end -- cgit v1.2.3 From e10171d44d362918cc0d4886e37018cd14843d0d Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 20 Dec 2019 22:47:34 +0100 Subject: mod_adhoc: Improve permission setting (fix #1482) BC Rename 'user' permission mode to 'any' for clarity, too easily mistaken for what the 'local_user' setting does. It is also removed as a default and made a required argument. --- plugins/adhoc/adhoc.lib.lua | 8 +++++++- plugins/adhoc/mod_adhoc.lua | 4 ++-- plugins/mod_uptime.lua | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/plugins/adhoc/adhoc.lib.lua b/plugins/adhoc/adhoc.lib.lua index 0b910299..0c61a636 100644 --- a/plugins/adhoc/adhoc.lib.lua +++ b/plugins/adhoc/adhoc.lib.lua @@ -21,7 +21,13 @@ local function _cmdtag(desc, status, sessionid, action) end function _M.new(name, node, handler, permission) - return { name = name, node = node, handler = handler, cmdtag = _cmdtag, permission = (permission or "user") }; + if not permission then + error "adhoc.new() expects a permission argument, none given" + end + if permission == "user" then + error "the permission mode 'user' has been renamed 'any', please update your code" + end + return { name = name, node = node, handler = handler, cmdtag = _cmdtag, permission = permission }; end function _M.handle_cmd(command, origin, stanza) diff --git a/plugins/adhoc/mod_adhoc.lua b/plugins/adhoc/mod_adhoc.lua index f6553773..188d05e8 100644 --- a/plugins/adhoc/mod_adhoc.lua +++ b/plugins/adhoc/mod_adhoc.lua @@ -26,7 +26,7 @@ module:hook("host-disco-info-node", function (event) if (command.permission == "admin" and privileged) or (command.permission == "global_admin" and global_admin) or (command.permission == "local_user" and hostname == module.host) - or (command.permission == "user") then + or (command.permission == "any") then reply:tag("identity", { name = command.name, category = "automation", type = "command-node" }):up(); reply:tag("feature", { var = xmlns_cmd }):up(); @@ -57,7 +57,7 @@ module:hook("host-disco-items-node", function (event) if (command.permission == "admin" and admin) or (command.permission == "global_admin" and global_admin) or (command.permission == "local_user" and hostname == module.host) - or (command.permission == "user") then + or (command.permission == "any") then reply:tag("item", { name = command.name, node = node, jid = module:get_host() }); reply:up(); diff --git a/plugins/mod_uptime.lua b/plugins/mod_uptime.lua index ccd8e511..035f7e9b 100644 --- a/plugins/mod_uptime.lua +++ b/plugins/mod_uptime.lua @@ -42,6 +42,6 @@ function uptime_command_handler () return { info = uptime_text(), status = "completed" }; end -local descriptor = adhoc_new("Get uptime", "uptime", uptime_command_handler); +local descriptor = adhoc_new("Get uptime", "uptime", uptime_command_handler, "any"); module:provides("adhoc", descriptor); -- cgit v1.2.3 From d20b12c208ec42372f5cd24ac1334e347762085a Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 28 Dec 2019 06:18:58 +0100 Subject: net.server_epoll: Avoid concatenating buffer with single item Saves creating a string that'll be identical to buffer[1] anyways, as well as a C function call. Depending on Lua version and length of the string, this could be reusing an interned string, but a longer one would probably be duplicated for no reason. Having exactly one item in the buffer seems like it would be fairly common, but I have not done an extensive study. If opportunistic writes are enabled then it will be even more likely. This special case could be optimized like this in table.concat but it does not look like it is. --- net/server_epoll.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index e2c5f977..4ad142ce 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -408,7 +408,7 @@ function interface:onwritable() self:onconnect(); if not self.conn then return; end -- could have been closed in onconnect local buffer = self.writebuffer; - local data = t_concat(buffer); + local data = #buffer == 1 and buffer[1] or t_concat(buffer); local ok, err, partial = self.conn:send(data); if ok then self:set(nil, false); -- cgit v1.2.3 From d8c2888045cb5bd396bd17dc42fbaf2355688683 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 26 Dec 2019 01:52:14 +0100 Subject: util.pubsub: Cover subscription filter in a partial test I'm not sure I understand spies well enough to test that the arguments and return values are as expected. Better than nothing at least. --- spec/util_pubsub_spec.lua | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/spec/util_pubsub_spec.lua b/spec/util_pubsub_spec.lua index 75f893e3..23cdf2a0 100644 --- a/spec/util_pubsub_spec.lua +++ b/spec/util_pubsub_spec.lua @@ -483,4 +483,30 @@ describe("util.pubsub", function () end); + describe("subscriber filter", function () + it("works", function () + local filter = spy.new(function (subs) + return {["modified"] = true}; + end); + local broadcaster = spy.new(function (notif_type, node_name, subscribers, item) -- luacheck: ignore 212 + end); + local service = pubsub.new({ + subscriber_filter = filter; + broadcaster = broadcaster; + }); + + local ok = service:create("node", true); + assert.truthy(ok); + + local ok = service:add_subscription("node", true, "someone"); + assert.truthy(ok); + + local ok = service:publish("node", true, "1", "item"); + assert.truthy(ok); + -- TODO how to match table arguments? + assert.spy(filter).was_called(); + assert.spy(broadcaster).was_called(); + end); + end); + end); -- cgit v1.2.3 From 30d0c690a3eef472603c991a9e1d067c8a851f7d Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 29 Dec 2019 01:11:55 +0100 Subject: util.pubsub: Ignore unused argument in tests [luacheck] --- spec/util_pubsub_spec.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/util_pubsub_spec.lua b/spec/util_pubsub_spec.lua index 23cdf2a0..f876089e 100644 --- a/spec/util_pubsub_spec.lua +++ b/spec/util_pubsub_spec.lua @@ -485,7 +485,7 @@ describe("util.pubsub", function () describe("subscriber filter", function () it("works", function () - local filter = spy.new(function (subs) + local filter = spy.new(function (subs) -- luacheck: ignore 212/subs return {["modified"] = true}; end); local broadcaster = spy.new(function (notif_type, node_name, subscribers, item) -- luacheck: ignore 212 -- cgit v1.2.3 From 30f7e379d4b316c197c837dce2c47b73b8b77d1f Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 29 Dec 2019 22:20:51 +0100 Subject: mod_http_errors: Use text from util.errror object if included This makes util.error objects useful for more than just an error code container. --- plugins/mod_http_errors.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_http_errors.lua b/plugins/mod_http_errors.lua index 2158b403..e151a68e 100644 --- a/plugins/mod_http_errors.lua +++ b/plugins/mod_http_errors.lua @@ -73,7 +73,7 @@ module:hook_object_event(server, "http-error", function (event) if event.response then event.response.headers.content_type = "text/html; charset=utf-8"; end - return get_page(event.code, (show_private and event.private_message) or event.message); + return get_page(event.code, (show_private and event.private_message) or event.message or (event.error and event.error.text)); end); module:hook_object_event(server, "http-error", function (event) -- cgit v1.2.3 From cdd6144dcc649f612c86f786c4e7fe8a12653582 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 30 Dec 2019 09:53:10 +0100 Subject: core.moduleapi: Fix error context in :send_iq API It got passed as argument to reject() instead of the util.error function and was lost. --- core/moduleapi.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/moduleapi.lua b/core/moduleapi.lua index 0a8adc36..dc1f899c 100644 --- a/core/moduleapi.lua +++ b/core/moduleapi.lua @@ -398,7 +398,7 @@ function api:send_iq(stanza, origin, timeout) local function error_handler(event) if event.stanza.attr.from == stanza.attr.to then - reject(errutil.from_stanza(event.stanza), event); + reject(errutil.from_stanza(event.stanza, event)); return true; end end -- cgit v1.2.3 From adc4440fd83a3b9124f91ad38eb8aa2c933284cc Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 30 Dec 2019 09:54:49 +0100 Subject: core.moduleapi: Rename local name for util.error for consistency It's called 'errors' everywhere else except here. --- core/moduleapi.lua | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core/moduleapi.lua b/core/moduleapi.lua index dc1f899c..5e8438a8 100644 --- a/core/moduleapi.lua +++ b/core/moduleapi.lua @@ -15,7 +15,7 @@ local timer = require "util.timer"; local resolve_relative_path = require"util.paths".resolve_relative_path; local st = require "util.stanza"; local cache = require "util.cache"; -local errutil = require "util.error"; +local errors = require "util.error"; local promise = require "util.promise"; local time_now = require "util.time".now; local format = require "util.format".format; @@ -370,7 +370,7 @@ function api:send_iq(stanza, origin, timeout) local iq_cache = self._iq_cache; if not iq_cache then iq_cache = cache.new(256, function (_, iq) - iq.reject(errutil.new({ + iq.reject(errors.new({ type = "wait", condition = "resource-constraint", text = "evicted from iq tracking cache" })); @@ -398,13 +398,13 @@ function api:send_iq(stanza, origin, timeout) local function error_handler(event) if event.stanza.attr.from == stanza.attr.to then - reject(errutil.from_stanza(event.stanza, event)); + reject(errors.from_stanza(event.stanza, event)); return true; end end if iq_cache:get(cache_key) then - reject(errutil.new({ + reject(errors.new({ type = "modify", condition = "conflict", text = "IQ stanza id attribute already used", })); @@ -415,7 +415,7 @@ function api:send_iq(stanza, origin, timeout) self:hook(error_event, error_handler); local timeout_handle = self:add_timer(timeout or 120, function () - reject(errutil.new({ + reject(errors.new({ type = "wait", condition = "remote-server-timeout", text = "IQ stanza timed out", })); @@ -428,7 +428,7 @@ function api:send_iq(stanza, origin, timeout) }); if not ok then - reject(errutil.new({ + reject(errors.new({ type = "wait", condition = "internal-server-error", text = "Could not store IQ tracking data" })); -- cgit v1.2.3 From 48bb417812710633ce1340e1f0f91366905b2a4f Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 1 Jan 2020 01:22:57 +0100 Subject: net.http.parser: Add TODO related to #726 --- net/http/parser.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/net/http/parser.lua b/net/http/parser.lua index 62c09aef..b328bdfe 100644 --- a/net/http/parser.lua +++ b/net/http/parser.lua @@ -93,6 +93,7 @@ function httpstream.new(success_cb, error_cb, parser_type, options_cb) chunked = have_body and headers["transfer-encoding"] == "chunked"; len = tonumber(headers["content-length"]); -- TODO check for invalid len if len and len > bodylimit then error = true; return error_cb("content-length-limit-exceeded"); end + -- TODO ask a callback whether to proceed in case of large requests or Expect: 100-continue if client then -- FIXME handle '100 Continue' response (by skipping it) if not have_body then len = 0; end -- cgit v1.2.3 From 0563faba739b930c600b52e13aca95101cdb8653 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 2 Jan 2020 13:17:03 +0100 Subject: net.websocket.frames: Add test case for masked data ASCI is pretty neat in how lower case alphabet XOR space is upper case --- spec/net_websocket_frames_spec.lua | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/spec/net_websocket_frames_spec.lua b/spec/net_websocket_frames_spec.lua index d4df3a54..6922d523 100644 --- a/spec/net_websocket_frames_spec.lua +++ b/spec/net_websocket_frames_spec.lua @@ -32,6 +32,17 @@ describe("net.websocket.frames", function () ["RSV2"] = false; ["RSV3"] = false; }; + masked_data = { + ["opcode"] = 0; + ["length"] = 5; + ["data"] = "hello"; + ["FIN"] = true; + ["MASK"] = true; + ["RSV1"] = false; + ["RSV2"] = false; + ["RSV3"] = false; + ["key"] = { 0x20, 0x20, 0x20, 0x20, }; + }; } describe("build", function () @@ -40,6 +51,7 @@ describe("net.websocket.frames", function () assert.equal("\0\0", build(test_frames.simple_empty)); assert.equal("\0\5hello", build(test_frames.simple_data)); assert.equal("\128\0", build(test_frames.simple_fin)); + assert.equal("\128\133 HELLO", build(test_frames.masked_data)); end); end); @@ -49,6 +61,7 @@ describe("net.websocket.frames", function () assert.same(test_frames.simple_empty, parse("\0\0")); assert.same(test_frames.simple_data, parse("\0\5hello")); assert.same(test_frames.simple_fin, parse("\128\0")); + assert.same(test_frames.masked_data, parse("\128\133 HELLO")); end); end); -- cgit v1.2.3 From cd9906cb2d3d5ec8801a5d2d5a832a3cb5009259 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 2 Jan 2020 13:17:43 +0100 Subject: net.websocket.frames: Add ping and pong test cases --- spec/net_websocket_frames_spec.lua | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/spec/net_websocket_frames_spec.lua b/spec/net_websocket_frames_spec.lua index 6922d523..7eed73c9 100644 --- a/spec/net_websocket_frames_spec.lua +++ b/spec/net_websocket_frames_spec.lua @@ -43,6 +43,26 @@ describe("net.websocket.frames", function () ["RSV3"] = false; ["key"] = { 0x20, 0x20, 0x20, 0x20, }; }; + ping = { + ["opcode"] = 0x9; + ["length"] = 4; + ["data"] = "ping"; + ["FIN"] = true; + ["MASK"] = false; + ["RSV1"] = false; + ["RSV2"] = false; + ["RSV3"] = false; + }; + pong = { + ["opcode"] = 0xa; + ["length"] = 4; + ["data"] = "pong"; + ["FIN"] = true; + ["MASK"] = false; + ["RSV1"] = false; + ["RSV2"] = false; + ["RSV3"] = false; + }; } describe("build", function () @@ -52,6 +72,8 @@ describe("net.websocket.frames", function () assert.equal("\0\5hello", build(test_frames.simple_data)); assert.equal("\128\0", build(test_frames.simple_fin)); assert.equal("\128\133 HELLO", build(test_frames.masked_data)); + assert.equal("\137\4ping", build(test_frames.ping)); + assert.equal("\138\4pong", build(test_frames.pong)); end); end); @@ -62,6 +84,8 @@ describe("net.websocket.frames", function () assert.same(test_frames.simple_data, parse("\0\5hello")); assert.same(test_frames.simple_fin, parse("\128\0")); assert.same(test_frames.masked_data, parse("\128\133 HELLO")); + assert.same(test_frames.ping, parse("\137\4ping")); + assert.same(test_frames.pong, parse("\138\4pong")); end); end); -- cgit v1.2.3 From 71138a9fd9d1b1e5767409be50d1547359e0d186 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 4 Jan 2020 14:05:10 +0100 Subject: mod_muc_mam: Measure how long a cleanup run takes (like mod_mam) --- plugins/mod_muc_mam.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/mod_muc_mam.lua b/plugins/mod_muc_mam.lua index fee75e33..e7506bbb 100644 --- a/plugins/mod_muc_mam.lua +++ b/plugins/mod_muc_mam.lua @@ -481,7 +481,10 @@ if cleanup_after ~= "never" then end end + local cleanup_time = module:measure("cleanup", "times"); + cleanup_runner = require "util.async".runner(function () + local cleanup_done = cleanup_time(); local rooms = {}; local cut_off = datestamp(os.time() - cleanup_after); for date in cleanup_storage:users() do @@ -512,6 +515,7 @@ if cleanup_after ~= "never" then end end module:log("info", "Deleted %d expired messages for %d rooms", sum, num_rooms); + cleanup_done(); end); cleanup_task = module:add_timer(1, function () -- cgit v1.2.3 From 25ce791b75a3f6facb3c7c3d48df4a86805f997f Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 5 Jan 2020 02:29:31 +0100 Subject: net.server_epoll: Collect full traceback from errors in listeners --- net/server_epoll.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index 4ad142ce..ff69bfbf 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -13,6 +13,7 @@ local pcall = pcall; local type = type; local next = next; local pairs = pairs; +local traceback = debug.traceback; local logger = require "util.logger"; local log = logger.init("server_epoll"); local socket = require "socket"; @@ -25,6 +26,7 @@ local inet = require "util.net"; local inet_pton = inet.pton; local _SOCKETINVALID = socket._SOCKETINVALID or -1; local new_id = require "util.id".medium; +local xpcall = require "util.xpcall".xpcall; local poller = require "util.poll" local EEXIST = poller.EEXIST; @@ -175,7 +177,7 @@ function interface:on(what, ...) -- self:debug("Missing listener 'on%s'", what); -- uncomment for development and debugging return; end - local ok, err = pcall(listener, self, ...); + local ok, err = xpcall(listener, traceback, self, ...); if not ok then if cfg.fatal_errors then self:debug("Closing due to error calling on%s: %s", what, err); -- cgit v1.2.3 From deedb80ea43834999a49e509bed8d55b0810d95b Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 12 Jan 2020 20:35:04 +0100 Subject: net.server_epoll: Add option for reducing debug logging Sometimes all these things just drown out the logs you are interested in with low-level socket noise. Enabled since it's still new and experimental. --- net/server_epoll.lua | 39 +++++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index ff69bfbf..b5c69a6d 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -66,6 +66,10 @@ local default_config = { __index = { max_wait = 86400; min_wait = 1e-06; + -- Enable extra noisy debug logging + -- TODO disable once considered stable + verbose = true; + -- EXPERIMENTAL -- Whether to kill connections in case of callback errors. fatal_errors = false; @@ -155,6 +159,13 @@ function interface:debug(msg, ...) --luacheck: ignore 212/self self.log("debug", msg, ...); end +interface.noise = interface.debug; +function interface:noise(msg, ...) --luacheck: ignore 212/self + if cfg.verbose then + return self:debug(msg, ...); + end +end + function interface:error(msg, ...) --luacheck: ignore 212/self self.log("error", msg, ...); end @@ -174,7 +185,7 @@ function interface:on(what, ...) end local listener = self.listeners["on"..what]; if not listener then - -- self:debug("Missing listener 'on%s'", what); -- uncomment for development and debugging + self:noise("Missing listener 'on%s'", what); -- uncomment for development and debugging return; end local ok, err = xpcall(listener, traceback, self, ...); @@ -262,7 +273,7 @@ function interface:setreadtimeout(t) else self._readtimeout = addtimer(t, function () if self:on("readtimeout") then - self:debug("Read timeout handled"); + self:noise("Read timeout handled"); return cfg.read_timeout; else self:debug("Read timeout not handled, disconnecting"); @@ -287,7 +298,7 @@ function interface:setwritetimeout(t) self._writetimeout:reschedule(t); else self._writetimeout = addtimer(t, function () - self:debug("Write timeout"); + self:noise("Write timeout"); self:on("disconnect", "write timeout"); self:destroy(); end); @@ -312,7 +323,7 @@ function interface:add(r, w) end self._wantread, self._wantwrite = r, w; fds[fd] = self; - self:debug("Registered in poller"); + self:noise("Registered in poller"); return true; end @@ -347,7 +358,7 @@ function interface:del() end self._wantread, self._wantwrite = nil, nil; fds[fd] = nil; - self:debug("Unregistered from poller"); + self:noise("Unregistered from poller"); return true; end @@ -562,15 +573,15 @@ function interface:tlshandskake() self:setwritetimeout(); self:set(true, true); elseif err == "wantread" then - self:debug("TLS handshake to wait until readable"); + self:noise("TLS handshake to wait until readable"); self:set(true, false); self:setreadtimeout(cfg.ssl_handshake_timeout); elseif err == "wantwrite" then - self:debug("TLS handshake to wait until writable"); + self:noise("TLS handshake to wait until writable"); self:set(false, true); self:setwritetimeout(cfg.ssl_handshake_timeout); else - self:debug("TLS handshake error: %s", err); + self:error("TLS handshake error: %s", err); self:on("disconnect", err); self:destroy(); end @@ -641,18 +652,18 @@ function interface:init() end function interface:pause() - self:debug("Pause reading"); + self:noise("Pause reading"); return self:set(false); end function interface:resume() - self:debug("Resume reading"); + self:noise("Resume reading"); return self:set(true); end -- Pause connection for some time function interface:pausefor(t) - self:debug("Pause for %fs", t); + self:noise("Pause for %fs", t); if self._pausefor then self._pausefor:close(); end @@ -661,7 +672,7 @@ function interface:pausefor(t) self._pausefor = addtimer(t, function () self._pausefor = nil; self:set(true); - self:debug("Resuming after pause, connection is %s", not self.conn and "missing" or self.conn:dirty() and "dirty" or "clean"); + self:noise("Resuming after pause, connection is %s", not self.conn and "missing" or self.conn:dirty() and "dirty" or "clean"); if self.conn and self.conn:dirty() then self:onreadable(); end @@ -680,7 +691,7 @@ function interface:pause_writes() if self._write_lock then return end - self:debug("Pause writes"); + self:noise("Pause writes"); self._write_lock = true; self:setwritetimeout(false); self:set(nil, false); @@ -690,7 +701,7 @@ function interface:resume_writes() if not self._write_lock then return end - self:debug("Resume writes"); + self:noise("Resume writes"); self._write_lock = nil; if self.writebuffer[1] then self:setwritetimeout(); -- cgit v1.2.3 From 4a424901c6baefd64fb99acdb019517f6b77d856 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 12 Jan 2020 20:36:04 +0100 Subject: net.server_epoll: Log errors caught in listeners on 'error' level --- net/server_epoll.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index b5c69a6d..74cec7a2 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -191,7 +191,7 @@ function interface:on(what, ...) local ok, err = xpcall(listener, traceback, self, ...); if not ok then if cfg.fatal_errors then - self:debug("Closing due to error calling on%s: %s", what, err); + self:error("Closing due to error calling on%s: %s", what, err); self:destroy(); else self:debug("Error calling on%s: %s", what, err); -- cgit v1.2.3 From 20ad50db57c447158df7162a1f48c3f6ec244553 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 12 Jan 2020 20:36:21 +0100 Subject: net.server_epoll: Log error about missing *all* callbacks at 'error' level --- net/server_epoll.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index 74cec7a2..897bd111 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -180,7 +180,7 @@ end -- Call a listener callback function interface:on(what, ...) if not self.listeners then - self:debug("Interface is missing listener callbacks"); + self:error("Interface is missing listener callbacks"); return; end local listener = self.listeners["on"..what]; -- cgit v1.2.3 From a0bb180c36219f617f01d42b4a49c5f38b839582 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 14 Jan 2020 23:39:47 +0100 Subject: util.array: Add a test case for a behavior change in Lua 5.3 In Lua 5.1 and 5.2 the __eq metamethod is not invoked if the other argument is of a different metatable, but in Lua 5.3 it is. --- spec/util_array_spec.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/util_array_spec.lua b/spec/util_array_spec.lua index 04d0cc28..1d9da947 100644 --- a/spec/util_array_spec.lua +++ b/spec/util_array_spec.lua @@ -43,6 +43,7 @@ describe("util.array", function () local b = array({ "c", "d" }); assert.truthy(a1 == a2); assert.falsy(a1 == b); + assert.falsy(a1 == { "a", "b" }, "Behavior of metatables changed in Lua 5.3"); end); end); -- cgit v1.2.3 From f38173be79e56e6033ab77f5af9d35603f540ddf Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 15 Jan 2020 21:14:06 +0100 Subject: util.json: Test util.array integration This is to expose how [] == json.null due to a change in Lua 5.3 with how the equality metamethod is chosen. --- spec/util_json_spec.lua | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/spec/util_json_spec.lua b/spec/util_json_spec.lua index 43360540..f07cd525 100644 --- a/spec/util_json_spec.lua +++ b/spec/util_json_spec.lua @@ -1,5 +1,6 @@ local json = require "util.json"; +local array = require "util.array"; describe("util.json", function() describe("#encode()", function() @@ -67,4 +68,13 @@ describe("util.json", function() end end); end) + + describe("util.array integration", function () + it("works", function () + assert.equal("[]", json.encode(array())); + assert.equal("[1,2,3]", json.encode(array({1,2,3}))); + assert.equal(getmetatable(array()), getmetatable(json.decode("[]"))); + end); + end); + end); -- cgit v1.2.3 From 34098ab9da64b314b430d82b3530035670039f79 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 15 Jan 2020 21:08:01 +0100 Subject: util.array: Fix equality metamethod in Lua 5.3 Lua 5.2 only used the __eq metamethod if both operands have the same __eq, but Lua 5.3 will pick one from either operands that has one as long as both are tables. This results in array() == {} and all sorts of odd behavior, including array() == util.json.null. I think [array() == {}] should have the same semantics as {} == {} --- util/array.lua | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/util/array.lua b/util/array.lua index 0b60a4fd..32d2d6a5 100644 --- a/util/array.lua +++ b/util/array.lua @@ -10,6 +10,7 @@ local t_insert, t_sort, t_remove, t_concat = table.insert, table.sort, table.remove, table.concat; local setmetatable = setmetatable; +local getmetatable = getmetatable; local math_random = math.random; local math_floor = math.floor; local pairs, ipairs = pairs, ipairs; @@ -40,6 +41,10 @@ function array_mt.__add(a1, a2) end function array_mt.__eq(a, b) + if getmetatable(a) ~= array_mt or getmetatable(b) ~= array_mt then + -- Lua 5.3+ calls this if both operands are tables, even if metatables differ + return false; + end if #a == #b then for i = 1, #a do if a[i] ~= b[i] then -- cgit v1.2.3 From 78eb3b5935a53e1a4e1217555f438674ca40e2ba Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 15 Jan 2020 21:16:08 +0100 Subject: util.ip: Fix equality metamethod for Lua 5.3 --- util/ip.lua | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/util/ip.lua b/util/ip.lua index d1808225..d039e475 100644 --- a/util/ip.lua +++ b/util/ip.lua @@ -19,8 +19,14 @@ local ip_mt = { return ret; end, __tostring = function (ip) return ip.addr; end, - __eq = function (ipA, ipB) return ipA.packed == ipB.packed; end }; +ip_mt.__eq = function (ipA, ipB) + if getmetatable(ipA) ~= ip_mt or getmetatable(ipB) ~= ip_mt then + -- Lua 5.3+ calls this if both operands are tables, even if metatables differ + return false; + end + return ipA.packed == ipB.packed; +end local hex2bits = { ["0"] = "0000", ["1"] = "0001", ["2"] = "0010", ["3"] = "0011", -- cgit v1.2.3 From f4af8004e39ff6f0066fda7dd7eb5457371e1e64 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 15 Jan 2020 21:18:30 +0100 Subject: util.set: Fix equality metamethod in Lua 5.3 --- util/set.lua | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/util/set.lua b/util/set.lua index 02fabc6a..827a9158 100644 --- a/util/set.lua +++ b/util/set.lua @@ -8,6 +8,7 @@ local ipairs, pairs, setmetatable, next, tostring = ipairs, pairs, setmetatable, next, tostring; +local getmetatable = getmetatable; local t_concat = table.concat; local _ENV = nil; @@ -146,6 +147,11 @@ function set_mt.__div(set, func) return new_set; end function set_mt.__eq(set1, set2) + if getmetatable(set1) ~= set_mt or getmetatable(set2) ~= set_mt then + -- Lua 5.3+ calls this if both operands are tables, even if metatables differ + return false; + end + set1, set2 = set1._items, set2._items; for item in pairs(set1) do if not set2[item] then -- cgit v1.2.3 From 3db27c369600393497d626c7b1a3e8c2c0365338 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 16 Jan 2020 16:30:26 +0100 Subject: core.moduleapi: Hook correct event type in some cases In rare cases, module.host can be a bare JID, in which case this test did the wrong thing. --- core/moduleapi.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/moduleapi.lua b/core/moduleapi.lua index 5e8438a8..87c337d6 100644 --- a/core/moduleapi.lua +++ b/core/moduleapi.lua @@ -19,6 +19,7 @@ local errors = require "util.error"; local promise = require "util.promise"; local time_now = require "util.time".now; local format = require "util.format".format; +local jid_node = require "util.jid".node; local t_insert, t_remove, t_concat = table.insert, table.remove, table.concat; local error, setmetatable, type = error, setmetatable, type; @@ -379,7 +380,7 @@ function api:send_iq(stanza, origin, timeout) end local event_type; - if stanza.attr.from == self.host then + if not jid_node(stanza.attr.from) then event_type = "host"; else -- assume bare since we can't hook full jids event_type = "bare"; -- cgit v1.2.3 From d3a6636c1c070199a5d70a8db4959a78b8d85f1e Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Fri, 24 Jan 2020 13:48:49 +0000 Subject: net.connect: Add API to create custom connect()s with options, incl. use_ipv[46] --- net/connect.lua | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/net/connect.lua b/net/connect.lua index 6d399dda..2d929087 100644 --- a/net/connect.lua +++ b/net/connect.lua @@ -2,13 +2,17 @@ local server = require "net.server"; local log = require "util.logger".init("net.connect"); local new_id = require "util.id".short; --- TODO Respect use_ipv4, use_ipv6 -- TODO #1246 Happy Eyeballs -- FIXME RFC 6724 -- FIXME Error propagation from resolvers doesn't work -- FIXME #1428 Reuse DNS resolver object between service and basic resolver -- FIXME #1429 Close DNS resolver object when done +local default_connector_options = { + use_ipv4 = true; + use_ipv6 = true; +}; + local pending_connection_methods = {}; local pending_connection_mt = { __name = "pending_connection"; @@ -78,19 +82,24 @@ function pending_connection_listeners.ondisconnect(conn, reason) attempt_connection(p); end -local function connect(target_resolver, listeners, options, data) - local p = setmetatable({ - id = new_id(); - target_resolver = target_resolver; - listeners = assert(listeners); - options = options or {}; - data = data; - }, pending_connection_mt); +local function new_connector(connector_options) + local function connect(target_resolver, listeners, options, data) + local p = setmetatable({ + id = new_id(); + target_resolver = target_resolver; + listeners = assert(listeners); + options = options or {}; + data = data; + connector_options = connector_options or default_connector_options; + }, pending_connection_mt); - p:log("debug", "Starting connection process"); - attempt_connection(p); + p:log("debug", "Starting connection process"); + attempt_connection(p); + end + return connect; end return { - connect = connect; + connect = new_connector(default_connector_options); + new_connector = new_connector; }; -- cgit v1.2.3 From 9782727301205d85ca58a88f9ab356600bda4013 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Fri, 24 Jan 2020 13:49:33 +0000 Subject: mod_s2s: Pass use_ipv4/ipv6 from config to connector config --- plugins/mod_s2s/mod_s2s.lua | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/plugins/mod_s2s/mod_s2s.lua b/plugins/mod_s2s/mod_s2s.lua index 00d8f5d9..2d45c750 100644 --- a/plugins/mod_s2s/mod_s2s.lua +++ b/plugins/mod_s2s/mod_s2s.lua @@ -27,7 +27,7 @@ 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 connect = require "net.connect".connect; +local new_connector = require "net.connect".new_connector; local service = require "net.resolvers.service"; local errors = require "util.error"; local set = require "util.set"; @@ -51,6 +51,11 @@ local listener = {}; local log = module._log; +local connect = new_connector({ + use_ipv4 = module:get_option_boolean("use_ipv4", true); + use_ipv6 = module:get_option_boolean("use_ipv6", true); +}); + module:hook("stats-update", function () local count = 0; local ipv6 = 0; -- cgit v1.2.3 From 5ac8804435ba7378a39d6fd213da35c57165c483 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Fri, 24 Jan 2020 13:50:02 +0000 Subject: net.adns: Add :lookup_promise() method --- net/adns.lua | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/net/adns.lua b/net/adns.lua index d7266958..bf6c11ab 100644 --- a/net/adns.lua +++ b/net/adns.lua @@ -8,6 +8,7 @@ local server = require "net.server"; local new_resolver = require "net.dns".resolver; +local promise = require "util.promise"; local log = require "util.logger".init("adns"); @@ -91,6 +92,18 @@ function async_resolver_methods:lookup(handler, qname, qtype, qclass) end)(resolver:peek(qname, qtype, qclass)); end +function async_resolver_methods:lookup_promise(qname, qtype, qclass) + return promise.new(function (resolve, reject) + local function handler(answer) + if not answer then + return reject(); + end + resolve(answer); + end + self:lookup(handler, qname, qtype, qclass); + end); +end + function query_methods:cancel(call_handler, reason) -- luacheck: ignore 212/reason log("warn", "Cancelling DNS lookup for %s", self[4]); self[1].cancel(self[2], self[3], self[4], self[5], call_handler); -- cgit v1.2.3 From 634408ca294da12888871740d296fb0a955f13ae Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Fri, 24 Jan 2020 16:21:30 +0000 Subject: mod_websocket: Fire event on session creation (thanks Aaron van Meerten) --- plugins/mod_websocket.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/mod_websocket.lua b/plugins/mod_websocket.lua index 101039e1..4d3e79bb 100644 --- a/plugins/mod_websocket.lua +++ b/plugins/mod_websocket.lua @@ -292,6 +292,8 @@ function handle_request(event) response.headers.sec_webSocket_accept = base64(sha1(request.headers.sec_websocket_key .. "258EAFA5-E914-47DA-95CA-C5AB0DC85B11")); response.headers.sec_webSocket_protocol = "xmpp"; + module:fire_event("websocket-session", { session = session, request = request }); + session.log("debug", "Sending WebSocket handshake"); return ""; -- cgit v1.2.3 From b37a36b1be0a8a91e851bcffe1f3e84c9f0f6151 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 24 Jan 2020 23:29:14 +0100 Subject: mod_admin_telnet: Use promise based DNS resolving Mostly done for testing this new API --- plugins/mod_admin_telnet.lua | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 5ffd40e0..fba10faf 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -1189,14 +1189,12 @@ end function def_env.dns:lookup(name, typ, class) local resolver = get_resolver(self.session); - local ret = "Query sent"; - local print = self.session.print; - local function handler(...) - ret = "Got response"; - print(...); + local ret, err = async.wait(resolver:lookup_promise(name, typ, class)); + if ret then + return true, ret; + elseif err then + return false, err; end - resolver:lookup(handler, name, typ, class); - return true, ret; end function def_env.dns:addnameserver(...) -- cgit v1.2.3 From b20f96afc1ea09e70fb7ffa04757940096e989bc Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Sat, 25 Jan 2020 14:03:30 +0000 Subject: net.resolvers.basic: Obey use_ipv4/use_ipv6 --- net/resolvers/basic.lua | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/net/resolvers/basic.lua b/net/resolvers/basic.lua index 41ebda80..7d326c44 100644 --- a/net/resolvers/basic.lua +++ b/net/resolvers/basic.lua @@ -42,23 +42,28 @@ function methods:next(cb) -- Resolve DNS to target list local dns_resolver = adns.resolver(); - dns_resolver:lookup(function (answer) - if answer then - for _, record in ipairs(answer) do - table.insert(targets, { self.conn_type.."4", record.a, self.port, self.extra }); + + if self.connector_options.use_ipv4 ~= false then + dns_resolver:lookup(function (answer) + if answer then + for _, record in ipairs(answer) do + table.insert(targets, { self.conn_type.."4", record.a, self.port, self.extra }); + end end - end - ready(); - end, self.hostname, "A", "IN"); + ready(); + end, self.hostname, "A", "IN"); + end - dns_resolver:lookup(function (answer) - if answer then - for _, record in ipairs(answer) do - table.insert(targets, { self.conn_type.."6", record.aaaa, self.port, self.extra }); + if self.connector_options.use_ipv6 ~= false then + dns_resolver:lookup(function (answer) + if answer then + for _, record in ipairs(answer) do + table.insert(targets, { self.conn_type.."6", record.aaaa, self.port, self.extra }); + end end - end - ready(); - end, self.hostname, "AAAA", "IN"); + ready(); + end, self.hostname, "AAAA", "IN"); + end end local function new(hostname, port, conn_type, extra) -- cgit v1.2.3 From 6e108728a10ae420bd4c9734af21d12fdaf335bc Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Sat, 25 Jan 2020 14:25:21 +0000 Subject: Backed out changeset 74d66b1be989 (not optimal API) --- plugins/mod_s2s/mod_s2s.lua | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/plugins/mod_s2s/mod_s2s.lua b/plugins/mod_s2s/mod_s2s.lua index 2d45c750..00d8f5d9 100644 --- a/plugins/mod_s2s/mod_s2s.lua +++ b/plugins/mod_s2s/mod_s2s.lua @@ -27,7 +27,7 @@ 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 new_connector = require "net.connect".new_connector; +local connect = require "net.connect".connect; local service = require "net.resolvers.service"; local errors = require "util.error"; local set = require "util.set"; @@ -51,11 +51,6 @@ local listener = {}; local log = module._log; -local connect = new_connector({ - use_ipv4 = module:get_option_boolean("use_ipv4", true); - use_ipv6 = module:get_option_boolean("use_ipv6", true); -}); - module:hook("stats-update", function () local count = 0; local ipv6 = 0; -- cgit v1.2.3 From c5fcad823ae67e81cb2b9b674d70fb6290117218 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Sat, 25 Jan 2020 14:25:29 +0000 Subject: Backed out changeset 44ef46e1a951 (not optimal API) --- net/connect.lua | 33 ++++++++++++--------------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/net/connect.lua b/net/connect.lua index 2d929087..6d399dda 100644 --- a/net/connect.lua +++ b/net/connect.lua @@ -2,17 +2,13 @@ local server = require "net.server"; local log = require "util.logger".init("net.connect"); local new_id = require "util.id".short; +-- TODO Respect use_ipv4, use_ipv6 -- TODO #1246 Happy Eyeballs -- FIXME RFC 6724 -- FIXME Error propagation from resolvers doesn't work -- FIXME #1428 Reuse DNS resolver object between service and basic resolver -- FIXME #1429 Close DNS resolver object when done -local default_connector_options = { - use_ipv4 = true; - use_ipv6 = true; -}; - local pending_connection_methods = {}; local pending_connection_mt = { __name = "pending_connection"; @@ -82,24 +78,19 @@ function pending_connection_listeners.ondisconnect(conn, reason) attempt_connection(p); end -local function new_connector(connector_options) - local function connect(target_resolver, listeners, options, data) - local p = setmetatable({ - id = new_id(); - target_resolver = target_resolver; - listeners = assert(listeners); - options = options or {}; - data = data; - connector_options = connector_options or default_connector_options; - }, pending_connection_mt); +local function connect(target_resolver, listeners, options, data) + local p = setmetatable({ + id = new_id(); + target_resolver = target_resolver; + listeners = assert(listeners); + options = options or {}; + data = data; + }, pending_connection_mt); - p:log("debug", "Starting connection process"); - attempt_connection(p); - end - return connect; + p:log("debug", "Starting connection process"); + attempt_connection(p); end return { - connect = new_connector(default_connector_options); - new_connector = new_connector; + connect = connect; }; -- cgit v1.2.3 From d1fbb9197f3546153fc52428115a725eb1399a4c Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Sat, 25 Jan 2020 14:38:17 +0000 Subject: net.resolvers.basic: Obey extra.use_ipv4/use_ipv6 --- net/resolvers/basic.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/resolvers/basic.lua b/net/resolvers/basic.lua index 7d326c44..438448b2 100644 --- a/net/resolvers/basic.lua +++ b/net/resolvers/basic.lua @@ -43,7 +43,7 @@ function methods:next(cb) -- Resolve DNS to target list local dns_resolver = adns.resolver(); - if self.connector_options.use_ipv4 ~= false then + if not self.extra or self.extra.use_ipv4 ~= false then dns_resolver:lookup(function (answer) if answer then for _, record in ipairs(answer) do @@ -54,7 +54,7 @@ function methods:next(cb) end, self.hostname, "A", "IN"); end - if self.connector_options.use_ipv6 ~= false then + if not self.extra or self.extra.use_ipv6 ~= false then dns_resolver:lookup(function (answer) if answer then for _, record in ipairs(answer) do -- cgit v1.2.3 From 165901fe8023f03c5e4b8083a6f588c306775d78 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Sat, 25 Jan 2020 14:38:42 +0000 Subject: mod_s2s: Pass use_ipv4/use_ipv6 from config to service resolver --- plugins/mod_s2s/mod_s2s.lua | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/plugins/mod_s2s/mod_s2s.lua b/plugins/mod_s2s/mod_s2s.lua index 00d8f5d9..4c509ba9 100644 --- a/plugins/mod_s2s/mod_s2s.lua +++ b/plugins/mod_s2s/mod_s2s.lua @@ -51,6 +51,12 @@ local listener = {}; local log = module._log; +local s2s_service_options = { + default_port = 5269; + use_ipv4 = module:get_option_boolean("use_ipv4", true); + use_ipv6 = module:get_option_boolean("use_ipv6", true); +}; + module:hook("stats-update", function () local count = 0; local ipv6 = 0; @@ -165,7 +171,7 @@ function route_to_new_session(event) 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", stanza.name); - connect(service.new(to_host, "xmpp-server", "tcp", { default_port = 5269 }), listener, nil, { session = host_session }); + connect(service.new(to_host, "xmpp-server", "tcp", s2s_service_options), listener, nil, { session = host_session }); return true; end -- cgit v1.2.3 From 6137789b45618e04d261ef8fba40ad69ebda874f Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 26 Jan 2020 14:35:35 +0100 Subject: net.resolvers.basic: Fix continuing if IPv6 or Legacy IP is disabled The code expects ready() to be called twice, but with IPv4 or v6 disabled it would only be called once. --- net/resolvers/basic.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/net/resolvers/basic.lua b/net/resolvers/basic.lua index 438448b2..75c23a58 100644 --- a/net/resolvers/basic.lua +++ b/net/resolvers/basic.lua @@ -52,6 +52,8 @@ function methods:next(cb) end ready(); end, self.hostname, "A", "IN"); + else + ready(); end if not self.extra or self.extra.use_ipv6 ~= false then @@ -63,6 +65,8 @@ function methods:next(cb) end ready(); end, self.hostname, "AAAA", "IN"); + else + ready(); end end -- cgit v1.2.3 From a15d977f4446660e899dc8c3bafb984163a2de27 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 26 Jan 2020 16:40:21 +0100 Subject: util.prosodyctl: Pass command line flag to force daemonization on start Part of the deprecation of the 'daemonize' config option. Further, it is a bit weird to run `prosodyctl start` and get Prosody running in the foreground. --- util/prosodyctl.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/util/prosodyctl.lua b/util/prosodyctl.lua index 0787b8c3..ea697ffc 100644 --- a/util/prosodyctl.lua +++ b/util/prosodyctl.lua @@ -249,9 +249,9 @@ local function start(source_dir, lua) return false, "already-running"; end if not source_dir then - os.execute(lua .. "./prosody"); + os.execute(lua .. "./prosody -D"); else - os.execute(lua .. source_dir.."/../../bin/prosody"); + os.execute(lua .. source_dir.."/../../bin/prosody -D"); end return true; end -- cgit v1.2.3 From a0dffb53e17d10eea3625dd49751e6cc3ed6d234 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 26 Jan 2020 16:42:56 +0100 Subject: mod_posix: Add deprecation warning for the 'daemonize' option --- plugins/mod_posix.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/mod_posix.lua b/plugins/mod_posix.lua index bcef2c1d..5177aaa5 100644 --- a/plugins/mod_posix.lua +++ b/plugins/mod_posix.lua @@ -116,7 +116,11 @@ local daemonize = prosody.opts.daemonize; if daemonize == nil then -- Fall back to config file if not specified on command-line - daemonize = module:get_option("daemonize", prosody.installed); + daemonize = module:get_option_boolean("daemonize", nil); + if daemonize ~= nil then + module:log("warn", "The 'daemonize' option has been deprecated, specify -D or -F on the command line instead."); + -- TODO: Write some docs and include a link in the warning. + end end local function remove_log_sinks() -- cgit v1.2.3 From 5d10f5303be4464df34bb482cdbb561615ad7445 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 26 Jan 2020 16:52:32 +0100 Subject: prosodyctl: Add 'daemonize' to deprecated options known by check command --- prosodyctl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prosodyctl b/prosodyctl index cc3ca3a5..964285a0 100755 --- a/prosodyctl +++ b/prosodyctl @@ -871,7 +871,7 @@ function commands.check(arg) print("Checking config..."); local deprecated = set.new({ "bosh_ports", "disallow_s2s", "no_daemonize", "anonymous_login", "require_encryption", - "vcard_compatibility", "cross_domain_bosh", "cross_domain_websocket" + "vcard_compatibility", "cross_domain_bosh", "cross_domain_websocket", "daemonize", }); local known_global_options = set.new({ "pidfile", "log", "plugin_paths", "prosody_user", "prosody_group", "daemonize", -- cgit v1.2.3 From 98ab88a9fff327e8a1f76ad41da29a149ba014c8 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 26 Jan 2020 16:53:03 +0100 Subject: scansion tests: Remove daemonize option, not needed --- spec/scansion/prosody.cfg.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/scansion/prosody.cfg.lua b/spec/scansion/prosody.cfg.lua index 3f804c4e..6a72584d 100644 --- a/spec/scansion/prosody.cfg.lua +++ b/spec/scansion/prosody.cfg.lua @@ -80,7 +80,6 @@ mam_smart_enable = true -- For advanced logging see https://prosody.im/doc/logging log = "*console" -daemonize = true pidfile = "prosody.pid" VirtualHost "localhost" -- cgit v1.2.3 From 447a846d4019e43cac4333173f58041281bfc36c Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 26 Jan 2020 16:55:28 +0100 Subject: CHANGES: Mention deprecation of 'daemonize' option --- CHANGES | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES b/CHANGES index 2e6caf0d..29c4cf86 100644 --- a/CHANGES +++ b/CHANGES @@ -14,6 +14,7 @@ TRUNK - Built-in HTTP server now handles HEAD requests - MUC presence broadcast controls - ALPN support in mod\_net\_multiplex +- `daemonize` option deprecated 0.11.0 ====== -- cgit v1.2.3 From ebb79437a842551b2cc8e403aaeccdc642260fa0 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 26 Jan 2020 19:52:48 +0100 Subject: mod_s2s: Comment on the various 'reason' arguments passed to :close --- plugins/mod_s2s/mod_s2s.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/mod_s2s/mod_s2s.lua b/plugins/mod_s2s/mod_s2s.lua index 4c509ba9..39b23ee5 100644 --- a/plugins/mod_s2s/mod_s2s.lua +++ b/plugins/mod_s2s/mod_s2s.lua @@ -487,6 +487,9 @@ end --- Session methods local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'}; +-- reason: stream error to send to the remote server +-- remote_reason: stream error received from the remote server +-- bounce_reason: stanza error to pass to bounce_sendq beacuse stream- and stanza errors are different local function session_close(session, reason, remote_reason, bounce_reason) local log = session.log or log; if session.conn then -- cgit v1.2.3 From 1f437623ad987328bcd6ca34b551f14415985b32 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 27 Jan 2020 21:54:59 +0000 Subject: usermanager, mod_authz_internal: Move admin-checking functionality into a module. Fixes #517 (ish). Note: Removes the ability for mod_auth_* providers to determine user admin status. Such modules will need to have their is_admin methods ported to be a mod_authz_* provider. --- core/usermanager.lua | 62 ++++++++++++++++++++++-------------------- plugins/mod_authz_internal.lua | 16 +++++++++++ 2 files changed, 48 insertions(+), 30 deletions(-) create mode 100644 plugins/mod_authz_internal.lua diff --git a/core/usermanager.lua b/core/usermanager.lua index bb5669cf..ec98d12f 100644 --- a/core/usermanager.lua +++ b/core/usermanager.lua @@ -9,12 +9,13 @@ local modulemanager = require "core.modulemanager"; local log = require "util.logger".init("usermanager"); local type = type; -local ipairs = ipairs; local jid_bare = require "util.jid".bare; +local jid_split = require "util.jid".split; local jid_prep = require "util.jid".prep; local config = require "core.configmanager"; local sasl_new = require "util.sasl".new; local storagemanager = require "core.storagemanager"; +local set = require "util.set"; local prosody = _G.prosody; local hosts = prosody.hosts; @@ -34,6 +35,22 @@ local function new_null_provider() }); end +local global_admins_config = config.get("*", "admins"); +if type(global_admins_config) ~= "table" then + global_admins_config = nil; -- TODO: factor out moduleapi magic config handling and use it here +end +local global_admins = set.new(global_admins_config) / jid_prep; + +local admin_role = { ["prosody:admin"] = true }; +local global_authz_provider = { + get_user_roles = function (user) end; --luacheck: ignore 212/user + get_jid_roles = function (jid) + if global_admins:contains(jid) then + return admin_role; + end + end; +}; + local provider_mt = { __index = new_null_provider() }; local function initialize_host(host) @@ -66,6 +83,11 @@ local function initialize_host(host) if auth_provider ~= "null" then modulemanager.load(host, "auth_"..auth_provider); end + + local authz_provider_name = config.get(host, "authorization") or "internal"; + + local authz_mod = modulemanager.load(host, "authz_"..authz_provider_name); + host_session.authz = authz_mod or global_authz_provider; end; prosody.events.add_handler("host-activated", initialize_host, 100); @@ -120,38 +142,18 @@ local function is_admin(jid, host) jid = jid_bare(jid); host = host or "*"; - local host_admins = config.get(host, "admins"); - local global_admins = config.get("*", "admins"); - - if host_admins and host_admins ~= global_admins then - if type(host_admins) == "table" then - for _,admin in ipairs(host_admins) do - if jid_prep(admin) == jid then - return true; - end - end - elseif host_admins then - log("error", "Option 'admins' for host '%s' is not a list", host); - end - end + local actor_user, actor_host = jid_split(jid); + local roles; - if global_admins then - if type(global_admins) == "table" then - for _,admin in ipairs(global_admins) do - if jid_prep(admin) == jid then - return true; - end - end - elseif global_admins then - log("error", "Global option 'admins' is not a list"); - end - end + local authz_provider = (host ~= "*" and hosts[host].authz) or global_authz_provider; - -- Still not an admin, check with auth provider - if host ~= "*" and hosts[host].users and hosts[host].users.is_admin then - return hosts[host].users.is_admin(jid); + if actor_host == host then -- Local user + roles = authz_provider.get_user_roles(actor_user); + else -- Remote user/JID + roles = authz_provider.get_jid_roles(jid); end - return false; + + return roles and roles["prosody:admin"]; end return { diff --git a/plugins/mod_authz_internal.lua b/plugins/mod_authz_internal.lua new file mode 100644 index 00000000..41b8d9f0 --- /dev/null +++ b/plugins/mod_authz_internal.lua @@ -0,0 +1,16 @@ +local normalize = require "util.jid".prep; +local admin_jids = module:get_option_inherited_set("admins", {}) / normalize; +local host = module.host; + +local admin_role = { ["prosody:admin"] = true }; + +function get_user_roles(user) + return get_jid_roles(user.."@"..host); +end + +function get_jid_roles(jid) + if admin_jids:contains(jid) then + return admin_role; + end + return nil; +end -- cgit v1.2.3 From b177628d3c336437b7098935b2e1040dc254e46a Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 27 Jan 2020 22:09:19 +0000 Subject: usermanager: Load authz providers on components also --- core/usermanager.lua | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/core/usermanager.lua b/core/usermanager.lua index ec98d12f..47d157bf 100644 --- a/core/usermanager.lua +++ b/core/usermanager.lua @@ -55,6 +55,12 @@ local provider_mt = { __index = new_null_provider() }; local function initialize_host(host) local host_session = hosts[host]; + + local authz_provider_name = config.get(host, "authorization") or "internal"; + + local authz_mod = modulemanager.load(host, "authz_"..authz_provider_name); + host_session.authz = authz_mod or global_authz_provider; + if host_session.type ~= "local" then return; end host_session.events.add_handler("item-added/auth-provider", function (event) @@ -84,10 +90,6 @@ local function initialize_host(host) modulemanager.load(host, "auth_"..auth_provider); end - local authz_provider_name = config.get(host, "authorization") or "internal"; - - local authz_mod = modulemanager.load(host, "authz_"..authz_provider_name); - host_session.authz = authz_mod or global_authz_provider; end; prosody.events.add_handler("host-activated", initialize_host, 100); -- cgit v1.2.3 From 34abcc8bd5da00be68279f31b747a341f23d1c11 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 27 Jan 2020 22:28:52 +0000 Subject: mod_authz_internal, usermanager: Rename to mod_authz_config --- core/usermanager.lua | 2 +- plugins/mod_authz_config.lua | 16 ++++++++++++++++ plugins/mod_authz_internal.lua | 16 ---------------- 3 files changed, 17 insertions(+), 17 deletions(-) create mode 100644 plugins/mod_authz_config.lua delete mode 100644 plugins/mod_authz_internal.lua diff --git a/core/usermanager.lua b/core/usermanager.lua index 47d157bf..4a1e18f9 100644 --- a/core/usermanager.lua +++ b/core/usermanager.lua @@ -56,7 +56,7 @@ local provider_mt = { __index = new_null_provider() }; local function initialize_host(host) local host_session = hosts[host]; - local authz_provider_name = config.get(host, "authorization") or "internal"; + local authz_provider_name = config.get(host, "authorization") or "config"; local authz_mod = modulemanager.load(host, "authz_"..authz_provider_name); host_session.authz = authz_mod or global_authz_provider; diff --git a/plugins/mod_authz_config.lua b/plugins/mod_authz_config.lua new file mode 100644 index 00000000..41b8d9f0 --- /dev/null +++ b/plugins/mod_authz_config.lua @@ -0,0 +1,16 @@ +local normalize = require "util.jid".prep; +local admin_jids = module:get_option_inherited_set("admins", {}) / normalize; +local host = module.host; + +local admin_role = { ["prosody:admin"] = true }; + +function get_user_roles(user) + return get_jid_roles(user.."@"..host); +end + +function get_jid_roles(jid) + if admin_jids:contains(jid) then + return admin_role; + end + return nil; +end diff --git a/plugins/mod_authz_internal.lua b/plugins/mod_authz_internal.lua deleted file mode 100644 index 41b8d9f0..00000000 --- a/plugins/mod_authz_internal.lua +++ /dev/null @@ -1,16 +0,0 @@ -local normalize = require "util.jid".prep; -local admin_jids = module:get_option_inherited_set("admins", {}) / normalize; -local host = module.host; - -local admin_role = { ["prosody:admin"] = true }; - -function get_user_roles(user) - return get_jid_roles(user.."@"..host); -end - -function get_jid_roles(jid) - if admin_jids:contains(jid) then - return admin_role; - end - return nil; -end -- cgit v1.2.3 From 028a6e499fc493eaa592411d18f72099cc07a3b4 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Tue, 28 Jan 2020 12:46:59 +0000 Subject: util.startup: expose current process type (prosody/prosodyctl) in the global prosody object --- util/startup.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/util/startup.lua b/util/startup.lua index 9b8d50f8..a799177c 100644 --- a/util/startup.lua +++ b/util/startup.lua @@ -589,6 +589,7 @@ end -- prosodyctl only function startup.prosodyctl() + prosody.process_type = "prosodyctl"; startup.parse_args(); startup.init_global_state(); startup.read_config(); @@ -611,6 +612,7 @@ end function startup.prosody() -- These actions are in a strict order, as many depend on -- previous steps to have already been performed + prosody.process_type = "prosody"; startup.parse_args(); startup.init_global_state(); startup.read_config(); -- cgit v1.2.3 From 668dc534b6bdf67e94042f3bb2c9671d1cacb0f3 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Thu, 30 Jan 2020 14:22:21 +0100 Subject: util.net: Fix signedness warning on ARM MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit net.c:87:56: warning: comparison of integer expressions of different signedness: ‘long unsigned int’ and ‘long int’ [-Wsign-compare] --- util-src/net.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/util-src/net.c b/util-src/net.c index d9c6b914..c3b07815 100644 --- a/util-src/net.c +++ b/util-src/net.c @@ -46,8 +46,8 @@ static const char *const type_strings[] = { static int lc_local_addresses(lua_State *L) { #ifndef _WIN32 /* Link-local IPv4 addresses; see RFC 3927 and RFC 5735 */ - const long ip4_linklocal = htonl(0xa9fe0000); /* 169.254.0.0 */ - const long ip4_mask = htonl(0xffff0000); + const uint32_t ip4_linklocal = htonl(0xa9fe0000); /* 169.254.0.0 */ + const uint32_t ip4_mask = htonl(0xffff0000); struct ifaddrs *addr = NULL, *a; #endif int n = 1; -- cgit v1.2.3 From 0f96c438e5b03eea8d1a1126333b99a3018c0bfb Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 1 Feb 2020 00:33:08 +0100 Subject: net.server_epoll: Different error to distinguish connection timeout This mirrors what server_event does. --- net/server_epoll.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index 897bd111..fa247191 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -299,7 +299,7 @@ function interface:setwritetimeout(t) else self._writetimeout = addtimer(t, function () self:noise("Write timeout"); - self:on("disconnect", "write timeout"); + self:on("disconnect", self._connected and "write timeout" or "connection timeout"); self:destroy(); end); end @@ -711,6 +711,7 @@ end -- Connected! function interface:onconnect() + self._connected = true; self:updatenames(); self:debug("Connected (%s)", self); self.onconnect = noop; -- cgit v1.2.3 From fd3ee50a8c1aa3029d8edecf584bea8acda9edc7 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Wed, 5 Feb 2020 17:40:50 +0000 Subject: stanza_router: Add once-per-routed-stanza event, pre-stanza --- core/stanza_router.lua | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/core/stanza_router.lua b/core/stanza_router.lua index 1d8db3e7..b2712b2f 100644 --- a/core/stanza_router.lua +++ b/core/stanza_router.lua @@ -172,7 +172,14 @@ function core_post_stanza(origin, stanza, preevents) end end - local event_data = {origin=origin, stanza=stanza}; + local event_data = {origin=origin, stanza=stanza, to_self=to_self}; + + local result = hosts[origin.host].events.fire_event("pre-stanza", event_data); + if result ~= nil then + log("debug", "Stanza rejected by pre-stanza handler: %s", event_data.reason or "unknown reason"); + return; + end + if preevents then -- c2s connection if hosts[origin.host].events.fire_event('pre-'..stanza.name..to_type, event_data) then return; end -- do preprocessing end -- cgit v1.2.3 From 3fb671e0a3ebec083770b6ea0bf91b1489ebf833 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Wed, 5 Feb 2020 17:41:14 +0000 Subject: usermanager: Add get_roles() function --- core/usermanager.lua | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/core/usermanager.lua b/core/usermanager.lua index 4a1e18f9..acdc7909 100644 --- a/core/usermanager.lua +++ b/core/usermanager.lua @@ -137,7 +137,7 @@ local function get_provider(host) return hosts[host].users; end -local function is_admin(jid, host) +local function get_roles(jid, host) if host and not hosts[host] then return false; end if type(jid) ~= "string" then return false; end @@ -155,6 +155,11 @@ local function is_admin(jid, host) roles = authz_provider.get_jid_roles(jid); end + return roles; +end + +local function is_admin(jid, host) + local roles = get_roles(jid, host); return roles and roles["prosody:admin"]; end @@ -170,5 +175,6 @@ return { users = users; get_sasl_handler = get_sasl_handler; get_provider = get_provider; + get_roles = get_roles; is_admin = is_admin; }; -- cgit v1.2.3 From e4f830812fe66033ad1344795def93537d159d62 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Wed, 5 Feb 2020 17:41:40 +0000 Subject: portmanager: Don't auto-start network services under prosodyctl --- core/portmanager.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/portmanager.lua b/core/portmanager.lua index 0712f5ac..2f9aa8b2 100644 --- a/core/portmanager.lua +++ b/core/portmanager.lua @@ -170,7 +170,7 @@ end local function register_service(service_name, service_info) table.insert(services[service_name], service_info); - if not active_services:get(service_name) then + if not active_services:get(service_name) and prosody.process_type == "prosody" then log("debug", "No active service for %s, activating...", service_name); local ok, err = activate(service_name); if not ok then -- cgit v1.2.3 From 783af2135e96ad2af8325a32f7f06f7cf51642db Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Wed, 5 Feb 2020 17:56:20 +0000 Subject: sessionmanager: Support passing an auth scope to make_authenticated --- core/sessionmanager.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/sessionmanager.lua b/core/sessionmanager.lua index 6c005fcd..eb515819 100644 --- a/core/sessionmanager.lua +++ b/core/sessionmanager.lua @@ -122,13 +122,14 @@ local function destroy_session(session, err) retire_session(session); end -local function make_authenticated(session, username) +local function make_authenticated(session, username, scope) username = nodeprep(username); if not username or #username == 0 then return nil, "Invalid username"; end session.username = username; if session.type == "c2s_unauthed" then session.type = "c2s_unbound"; end + session.auth_scope = scope; session.log("info", "Authenticated as %s@%s", username, session.host or "(unknown)"); return true; end -- cgit v1.2.3 From cfa4181982f768ca5d376db8db45951815429665 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Wed, 5 Feb 2020 17:56:44 +0000 Subject: mod_saslauth: Pass through any auth scope from the SASL handler to sessionmanager.make_authenticated() --- plugins/mod_saslauth.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_saslauth.lua b/plugins/mod_saslauth.lua index ecce8361..2a5edcb2 100644 --- a/plugins/mod_saslauth.lua +++ b/plugins/mod_saslauth.lua @@ -49,7 +49,7 @@ local function handle_status(session, status, ret, err_msg) module:fire_event("authentication-failure", { session = session, condition = ret, text = err_msg }); session.sasl_handler = session.sasl_handler:clean_clone(); elseif status == "success" then - local ok, err = sm_make_authenticated(session, session.sasl_handler.username); + local ok, err = sm_make_authenticated(session, session.sasl_handler.username, session.sasl_handler.scope); if ok then module:fire_event("authentication-success", { session = session }); session.sasl_handler = nil; -- cgit v1.2.3 From 3827fd9716252e9b74bef848fd39879dd2699950 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 5 Feb 2020 23:29:39 +0100 Subject: mod_admin_telnet: Avoid indexing missing socket (thanks tmolitor) if `sock` was nil it would still proceed with SNI and ALPN checks --- plugins/mod_admin_telnet.lua | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index fba10faf..2485e698 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -580,20 +580,20 @@ local function tls_info(session, line) if sock and sock.info then local info = sock:info(); line[#line+1] = ("(%s with %s)"):format(info.protocol, info.cipher); - else - line[#line+1] = "(cipher info unavailable)"; - end - if sock.getsniname then - local name = sock:getsniname(); - if name then - line[#line+1] = ("(SNI:%q)"):format(name); + if sock.getsniname then + local name = sock:getsniname(); + if name then + line[#line+1] = ("(SNI:%q)"):format(name); + end end - end - if sock.getalpn then - local proto = sock:getalpn(); - if proto then - line[#line+1] = ("(ALPN:%q)"):format(proto); + if sock.getalpn then + local proto = sock:getalpn(); + if proto then + line[#line+1] = ("(ALPN:%q)"):format(proto); + end end + else + line[#line+1] = "(cipher info unavailable)"; end else line[#line+1] = "(insecure)"; -- cgit v1.2.3 From 428e4fa83263db49891c64389ba404d819902cb1 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Wed, 5 Feb 2020 22:53:59 +0000 Subject: stanza_router: only fire pre-stanza if firing other preevents (e.g. for c2s sessions) --- core/stanza_router.lua | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core/stanza_router.lua b/core/stanza_router.lua index b2712b2f..774c077e 100644 --- a/core/stanza_router.lua +++ b/core/stanza_router.lua @@ -174,13 +174,13 @@ function core_post_stanza(origin, stanza, preevents) local event_data = {origin=origin, stanza=stanza, to_self=to_self}; - local result = hosts[origin.host].events.fire_event("pre-stanza", event_data); - if result ~= nil then - log("debug", "Stanza rejected by pre-stanza handler: %s", event_data.reason or "unknown reason"); - return; - end - if preevents then -- c2s connection + local result = hosts[origin.host].events.fire_event("pre-stanza", event_data); + if result ~= nil then + log("debug", "Stanza rejected by pre-stanza handler: %s", event_data.reason or "unknown reason"); + return; + end + if hosts[origin.host].events.fire_event('pre-'..stanza.name..to_type, event_data) then return; end -- do preprocessing end local h = hosts[to_bare] or hosts[host or origin.host]; -- cgit v1.2.3 From 798995ef1a78548aae4c76290064da064494acf4 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 13 Feb 2020 23:03:03 +0100 Subject: mod_s2s: Fix typo in comment [codespell] --- plugins/mod_s2s/mod_s2s.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_s2s/mod_s2s.lua b/plugins/mod_s2s/mod_s2s.lua index 39b23ee5..8feb6403 100644 --- a/plugins/mod_s2s/mod_s2s.lua +++ b/plugins/mod_s2s/mod_s2s.lua @@ -489,7 +489,7 @@ end local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'}; -- reason: stream error to send to the remote server -- remote_reason: stream error received from the remote server --- bounce_reason: stanza error to pass to bounce_sendq beacuse stream- and stanza errors are different +-- bounce_reason: stanza error to pass to bounce_sendq because stream- and stanza errors are different local function session_close(session, reason, remote_reason, bounce_reason) local log = session.log or log; if session.conn then -- cgit v1.2.3 From 61b6b340b1fb3c3bfc01e3cb22058cc8a2c52064 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 15 Feb 2020 16:43:18 +0100 Subject: net.server_epoll: Reduce log level of TLS handshake errors to debug These are triggered all the time by random HTTPS connections, so they are mostly just useless noise. When you actually do need them, you probably have debug logging enabled too, since these messages are fairly useless without more context. --- net/server_epoll.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index fa247191..b281d463 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -581,7 +581,7 @@ function interface:tlshandskake() self:set(false, true); self:setwritetimeout(cfg.ssl_handshake_timeout); else - self:error("TLS handshake error: %s", err); + self:debug("TLS handshake error: %s", err); self:on("disconnect", err); self:destroy(); end -- cgit v1.2.3 From dce92c437ae10cc79abacc7960482065299fd230 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 16 Feb 2020 23:48:31 +0100 Subject: net.resolvers.service: Fix resolving of targets with multiple IPs Each basic resolver was only used once and not kept around to try any IP addresses but the first one found. --- net/resolvers/service.lua | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/net/resolvers/service.lua b/net/resolvers/service.lua index b4300d08..f74338db 100644 --- a/net/resolvers/service.lua +++ b/net/resolvers/service.lua @@ -14,14 +14,17 @@ local resolver_mt = { __index = methods }; -- pass it to cb() function methods:next(cb) if self.targets then - if #self.targets == 0 then - cb(nil); - return; + if not self.resolver then + if #self.targets == 0 then + cb(nil); + return; + end + local next_target = table.remove(self.targets, 1); + self.resolver = basic.new(unpack(next_target, 1, 4)); end - local next_target = table.remove(self.targets, 1); - self.resolver = basic.new(unpack(next_target, 1, 4)); self.resolver:next(function (...) if ... == nil then + self.resolver = nil; self:next(cb); else cb(...); -- cgit v1.2.3 From e19909198725b6c338dcb2d6714849505faafefc Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 19 Feb 2020 21:38:00 +0100 Subject: util.startup: Break out command line argument parsing into util.argparse This will allow using it from other places such as prosodyctl sub-commands and plugins --- util/argparse.lua | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ util/startup.lua | 54 +++++---------------------------------------------- 2 files changed, 63 insertions(+), 49 deletions(-) create mode 100644 util/argparse.lua diff --git a/util/argparse.lua b/util/argparse.lua new file mode 100644 index 00000000..928fc3eb --- /dev/null +++ b/util/argparse.lua @@ -0,0 +1,58 @@ +local function parse(arg, config) + local short_params = config and config.short_params or {}; + local value_params = config and config.value_params or {}; + + local parsed_opts = {}; + + if #arg == 0 then + return parsed_opts; + end + while true do + local raw_param = arg[1]; + if not raw_param then + break; + end + + local prefix = raw_param:match("^%-%-?"); + if not prefix then + break; + elseif prefix == "--" and raw_param == "--" then + table.remove(arg, 1); + break; + end + local param = table.remove(arg, 1):sub(#prefix+1); + if #param == 1 and short_params then + param = short_params[param]; + end + + if not param then + print("Unknown command-line option: "..tostring(param)); + print("Perhaps you meant to use prosodyctl instead?"); + os.exit(1); + end + + local param_k, param_v; + if value_params[param] then + param_k, param_v = param, table.remove(arg, 1); + if not param_v then + print("Expected a value to follow command-line option: "..raw_param); + os.exit(1); + end + else + param_k, param_v = param:match("^([^=]+)=(.+)$"); + if not param_k then + if param:match("^no%-") then + param_k, param_v = param:sub(4), false; + else + param_k, param_v = param, true; + end + end + end + parsed_opts[param_k] = param_v; + end + return parsed_opts; +end + +return { + parse = parse; +} diff --git a/util/startup.lua b/util/startup.lua index a799177c..d45855f2 100644 --- a/util/startup.lua +++ b/util/startup.lua @@ -5,6 +5,7 @@ local startup = {}; local prosody = { events = require "util.events".new() }; local logger = require "util.logger"; local log = logger.init("startup"); +local parse_args = require "util.argparse".parse; local config = require "core.configmanager"; local config_warnings; @@ -17,55 +18,10 @@ local short_params = { D = "daemonize", F = "no-daemonize" }; local value_params = { config = true }; function startup.parse_args() - local parsed_opts = {}; - prosody.opts = parsed_opts; - - if #arg == 0 then - return; - end - while true do - local raw_param = arg[1]; - if not raw_param then - break; - end - - local prefix = raw_param:match("^%-%-?"); - if not prefix then - break; - elseif prefix == "--" and raw_param == "--" then - table.remove(arg, 1); - break; - end - local param = table.remove(arg, 1):sub(#prefix+1); - if #param == 1 then - param = short_params[param]; - end - - if not param then - print("Unknown command-line option: "..tostring(param)); - print("Perhaps you meant to use prosodyctl instead?"); - os.exit(1); - end - - local param_k, param_v; - if value_params[param] then - param_k, param_v = param, table.remove(arg, 1); - if not param_v then - print("Expected a value to follow command-line option: "..raw_param); - os.exit(1); - end - else - param_k, param_v = param:match("^([^=]+)=(.+)$"); - if not param_k then - if param:match("^no%-") then - param_k, param_v = param:sub(4), false; - else - param_k, param_v = param, true; - end - end - end - parsed_opts[param_k] = param_v; - end + prosody.opts = parse_args(arg, { + short_params = short_params, + value_params = value_params, + }); end function startup.read_config() -- cgit v1.2.3 From 0b3056c44881ef8692cdb09e520a9368b29dc601 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 21 Feb 2020 23:30:47 +0100 Subject: mod_ping: Fix double response to internal ping When responding to a ping from elsewhere in the same Prosody the send function will be host_send from core.hostmanager, which does not return anything. Tailcalling it therefore lets the iq event fall trough to handle_unhandled_stanza in core.stanza_router, which responds with an error. This error also goes into handle_unhandled_stanza which discards it. Noticed because I have a module that points out when a stanza error reply is created without a text argument. --- plugins/mod_ping.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/mod_ping.lua b/plugins/mod_ping.lua index df24c495..b6ccc928 100644 --- a/plugins/mod_ping.lua +++ b/plugins/mod_ping.lua @@ -11,7 +11,8 @@ local st = require "util.stanza"; module:add_feature("urn:xmpp:ping"); local function ping_handler(event) - return event.origin.send(st.reply(event.stanza)); + event.origin.send(st.reply(event.stanza)); + return true; end module:hook("iq-get/bare/urn:xmpp:ping:ping", ping_handler); -- cgit v1.2.3 From 23cd82ba5fd0a00d85fb7d6a708723419fdc0cb4 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 22 Feb 2020 18:23:38 +0100 Subject: mod_admin_telnet: Reflow hosts filter for readability --- plugins/mod_admin_telnet.lua | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 2485e698..730053fe 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -384,7 +384,12 @@ end -- matching modules_enabled in the global section local function get_hosts_with_module(hosts, module) local hosts_set = get_hosts_set(hosts) - / function (host) return (prosody.hosts[host].type == "local" or module and modulemanager.is_loaded(host, module)) and host or nil; end; + / function (host) + if prosody.hosts[host].type == "local" or module and modulemanager.is_loaded(host, module) then + return host; + end; + return nil; + end; if module and modulemanager.get_module("*", module) then hosts_set:add("*"); end -- cgit v1.2.3 From 3947003b7e86a084cc725b5c68d7b4e8724e3312 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 22 Feb 2020 18:32:50 +0100 Subject: mod_admin_telnet: Fix host selection filter, fixes loading on components get_hosts_with_module(a component, mod) would still filter out components since they don't have type="component" instead of "local" Introduced in 4d3549e64489 --- plugins/mod_admin_telnet.lua | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 730053fe..2688209b 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -385,10 +385,24 @@ end local function get_hosts_with_module(hosts, module) local hosts_set = get_hosts_set(hosts) / function (host) - if prosody.hosts[host].type == "local" or module and modulemanager.is_loaded(host, module) then - return host; + if module then + -- Module given, filter in hosts with this module loaded + if modulemanager.is_loaded(host, module) then + return host; + else + return nil; + end + end + if not hosts then + -- No hosts given, filter in VirtualHosts + if prosody.hosts[host].type == "local" then + return host; + else + return nil + end end; - return nil; + -- No module given, but hosts are, don't filter at all + return host; end; if module and modulemanager.get_module("*", module) then hosts_set:add("*"); -- cgit v1.2.3 From 8d04879adfbe5d4039a14c5bd10e95ee4b051566 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Sun, 23 Feb 2020 12:38:43 +0000 Subject: usermanager, mod_authz_*: Merge mod_authz_config and mod_authz_internal into the latter --- core/usermanager.lua | 2 +- plugins/mod_authz_config.lua | 16 ---------------- plugins/mod_authz_internal.lua | 22 ++++++++++++++++++++++ 3 files changed, 23 insertions(+), 17 deletions(-) delete mode 100644 plugins/mod_authz_config.lua create mode 100644 plugins/mod_authz_internal.lua diff --git a/core/usermanager.lua b/core/usermanager.lua index acdc7909..aced0379 100644 --- a/core/usermanager.lua +++ b/core/usermanager.lua @@ -56,7 +56,7 @@ local provider_mt = { __index = new_null_provider() }; local function initialize_host(host) local host_session = hosts[host]; - local authz_provider_name = config.get(host, "authorization") or "config"; + local authz_provider_name = config.get(host, "authorization") or "internal"; local authz_mod = modulemanager.load(host, "authz_"..authz_provider_name); host_session.authz = authz_mod or global_authz_provider; diff --git a/plugins/mod_authz_config.lua b/plugins/mod_authz_config.lua deleted file mode 100644 index 41b8d9f0..00000000 --- a/plugins/mod_authz_config.lua +++ /dev/null @@ -1,16 +0,0 @@ -local normalize = require "util.jid".prep; -local admin_jids = module:get_option_inherited_set("admins", {}) / normalize; -local host = module.host; - -local admin_role = { ["prosody:admin"] = true }; - -function get_user_roles(user) - return get_jid_roles(user.."@"..host); -end - -function get_jid_roles(jid) - if admin_jids:contains(jid) then - return admin_role; - end - return nil; -end diff --git a/plugins/mod_authz_internal.lua b/plugins/mod_authz_internal.lua new file mode 100644 index 00000000..0f6e4873 --- /dev/null +++ b/plugins/mod_authz_internal.lua @@ -0,0 +1,22 @@ +local normalize = require "util.jid".prep; +local admin_jids = module:get_option_inherited_set("admins", {}) / normalize; +local host = module.host; +local role_store = module:open_store("roles"); + +local admin_role = { ["prosody:admin"] = true }; + +function get_user_roles(user) + if admin_jids:contains(user.."@"..host) then + return admin_role; + end + return role_store:get(user); +end + +function get_jid_roles(jid) + if admin_jids:contains(jid) then + return admin_role; + end + return nil; +end + + -- cgit v1.2.3 From 0bcbbed75359c7460463e6f33b81a74dcb1b801f Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 24 Feb 2020 01:24:25 +0100 Subject: util.jwt: Basic JSON Web Token library supporting HS256 tokens --- spec/util_jwt_spec.lua | 20 ++++++++++++++++++++ util/jwt.lua | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 spec/util_jwt_spec.lua create mode 100644 util/jwt.lua diff --git a/spec/util_jwt_spec.lua b/spec/util_jwt_spec.lua new file mode 100644 index 00000000..5e688f7b --- /dev/null +++ b/spec/util_jwt_spec.lua @@ -0,0 +1,20 @@ +local jwt = require "util.jwt"; + +describe("util.jwt", function () + it("validates", function () + local key = "secret"; + local token = jwt.sign(key, { payload = "this" }); + assert.string(token); + local ok, parsed = jwt.verify(key, token); + assert.truthy(ok) + assert.same({ payload = "this" }, parsed); + end); + it("rejects invalid", function () + local key = "secret"; + local token = jwt.sign("wrong", { payload = "this" }); + assert.string(token); + local ok, err = jwt.verify(key, token); + assert.falsy(ok) + end); +end); + diff --git a/util/jwt.lua b/util/jwt.lua new file mode 100644 index 00000000..2b172d38 --- /dev/null +++ b/util/jwt.lua @@ -0,0 +1,50 @@ +local s_gsub = string.gsub; +local json = require "util.json"; +local hashes = require "util.hashes"; +local base64_encode = require "util.encodings".base64.encode; +local base64_decode = require "util.encodings".base64.decode; + +local b64url_rep = { ["+"] = "-", ["/"] = "_", ["="] = "", ["-"] = "+", ["_"] = "/" }; +local function b64url(data) + return (s_gsub(base64_encode(data), "[+/=]", b64url_rep)); +end +local function unb64url(data) + return base64_decode(s_gsub(data, "[-_]", b64url_rep).."=="); +end + +local static_header = b64url('{"alg":"HS256","typ":"JWT"}') .. '.'; + +local function sign(key, payload) + local encoded_payload = json.encode(payload); + local signed = static_header .. b64url(encoded_payload); + local signature = hashes.hmac_sha256(key, signed); + return signed .. "." .. b64url(signature); +end + +local jwt_pattern = "^(([A-Za-z0-9-_]+)%.([A-Za-z0-9-_]+))%.([A-Za-z0-9-_]+)$" +local function verify(key, blob) + local signed, bheader, bpayload, signature = string.match(blob, jwt_pattern); + if not signed then + return nil, "invalid-encoding"; + end + local header = json.decode(unb64url(bheader)); + if not header or type(header) ~= "table" then + return nil, "invalid-header"; + elseif header.alg ~= "HS256" then + return nil, "unsupported-algorithm"; + end + if b64url(hashes.hmac_sha256(key, signed)) ~= signature then + return false, "signature-mismatch"; + end + local payload, err = json.decode(unb64url(bpayload)); + if err ~= nil then + return nil, "json-decode-error"; + end + return true, payload; +end + +return { + sign = sign; + verify = verify; +}; + -- cgit v1.2.3 From d9c8eeb88981ca0a617e545674105b7b9b3ec4ae Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 24 Feb 2020 09:10:28 +0100 Subject: util.jwt: Remove unused return value from tests [luacheck] --- spec/util_jwt_spec.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/util_jwt_spec.lua b/spec/util_jwt_spec.lua index 5e688f7b..b391a870 100644 --- a/spec/util_jwt_spec.lua +++ b/spec/util_jwt_spec.lua @@ -13,7 +13,7 @@ describe("util.jwt", function () local key = "secret"; local token = jwt.sign("wrong", { payload = "this" }); assert.string(token); - local ok, err = jwt.verify(key, token); + local ok = jwt.verify(key, token); assert.falsy(ok) end); end); -- cgit v1.2.3 From 4a2e73392898afb5f8b380bdef86e438761b3d77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20=E2=80=9Cpep=E2=80=9D=20Buquet?= Date: Mon, 24 Feb 2020 14:16:45 +0100 Subject: mod_muc: add muc-private-message event This seems to be the one place handling MUC-PMs. This event is added so that plugins (such as muc_occupant_id) can edit them without having to redo the work. --- plugins/muc/muc.lib.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 399b090e..8adb0046 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -814,7 +814,9 @@ function room_mt:handle_message_to_occupant(origin, stanza) stanza = muc_util.filter_muc_x(st.clone(stanza)); stanza:tag("x", { xmlns = "http://jabber.org/protocol/muc#user" }):up(); stanza.attr.from = current_nick; - self:route_to_occupant(o_data, stanza) + if module:fire_event("muc-private-message", { room = self, origin = origin, stanza = stanza }) ~= false then + self:route_to_occupant(o_data, stanza) + end -- TODO: Remove x tag? stanza.attr.from = from; return true; -- cgit v1.2.3 From 1969b96da192e7d8e3233883bdea256942c73a7b Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 24 Feb 2020 18:38:09 +0100 Subject: mod_admin_telnet: Allow passing list of hosts to http:list() Lets you select what hosts to list http services on. In particular, this enables listing global http services, which was not possible before. --- plugins/mod_admin_telnet.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 2688209b..4dc32e47 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -1241,10 +1241,10 @@ end def_env.http = {}; -function def_env.http:list() +function def_env.http:list(hosts) local print = self.session.print; - for host in pairs(prosody.hosts) do + for host in get_hosts_set(hosts) do local http_apps = modulemanager.get_items("http-provider", host); if #http_apps > 0 then local http_host = module:context(host):get_option_string("http_host"); -- cgit v1.2.3 From 522e9778eaf37c99ef15cdebdeee58cedfa5af5c Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 24 Feb 2020 20:21:46 +0100 Subject: net.dns: Handle being loaded outside of Prosody `if timer ...` suggests that this was intended, but it did not work because net.timer depends on net.server which refuses to be loaded outside of Prosody. --- net/dns.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/dns.lua b/net/dns.lua index 3902f95c..193067e3 100644 --- a/net/dns.lua +++ b/net/dns.lua @@ -13,7 +13,7 @@ local socket = require "socket"; -local timer = require "util.timer"; +local have_timer, timer = pcall(require, "util.timer"); local new_ip = require "util.ip".new_ip; local have_util_net, util_net = pcall(require, "util.net"); @@ -871,7 +871,7 @@ function resolver:query(qname, qtype, qclass) -- - - - - - - - - - -- query set(self.wanted, qclass, qtype, qname, co, true); end - if timer and self.timeout then + if have_timer and self.timeout then local num_servers = #self.server; local i = 1; timer.add_task(self.timeout, function () -- cgit v1.2.3 From 80a38cc8731fcb38f9ed7f4aa20c8e9a2071467f Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 26 Feb 2020 00:59:35 +0100 Subject: util.adhoc: Allow passing dataforms in initial command This might not be quite legal per XEP-0050, but makes it possible to call simpler commands without keeping state across another roundtrip. --- util/adhoc.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/util/adhoc.lua b/util/adhoc.lua index d81b8242..19c94c34 100644 --- a/util/adhoc.lua +++ b/util/adhoc.lua @@ -2,7 +2,7 @@ local function new_simple_form(form, result_handler) return function(self, data, state) - if state then + if state or data.form then if data.action == "cancel" then return { status = "canceled" }; end @@ -16,7 +16,7 @@ end local function new_initial_data_form(form, initial_data, result_handler) return function(self, data, state) - if state then + if state or data.form then if data.action == "cancel" then return { status = "canceled" }; end -- cgit v1.2.3 From bd69308bf1e9cc6a84620d74a51b7621d55db685 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Wed, 26 Feb 2020 17:56:23 +0000 Subject: mod_authtokens: New module for managing auth tokens --- plugins/mod_authtokens.lua | 81 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 plugins/mod_authtokens.lua diff --git a/plugins/mod_authtokens.lua b/plugins/mod_authtokens.lua new file mode 100644 index 00000000..8e516924 --- /dev/null +++ b/plugins/mod_authtokens.lua @@ -0,0 +1,81 @@ +local id = require "util.id"; +local jid = require "util.jid"; +local base64 = require "util.encodings".base64; + +local token_store = module:open_store("auth_tokens", "map"); + +function create_jid_token(actor_jid, token_jid, token_scope, token_ttl) + token_jid = jid.prep(token_jid); + if not actor_jid or token_jid ~= actor_jid and not jid.compare(token_jid, actor_jid) then + return nil, "not-authorized"; + end + + local token_username, token_host, token_resource = jid.split(token_jid); + + if token_host ~= module.host then + return nil, "invalid-host"; + end + + local token_info = { + owner = actor_jid; + expires = token_ttl and (os.time() + token_ttl) or nil; + jid = token_jid; + session = { + username = token_username; + host = token_host; + resource = token_resource; + + auth_scope = token_scope; + }; + }; + + local token_id = id.long(); + local token = base64.encode("1;"..token_username.."@"..token_host..";"..token_id); + token_store:set(token_username, token_id, token_info); + + return token, token_info; +end + +local function parse_token(encoded_token) + local token = base64.decode(encoded_token); + if not token then return nil; end + local token_jid, token_id = token:match("^1;([^;]+);(.+)$"); + if not token_jid then return nil; end + local token_user, token_host = jid.split(token_jid); + return token_id, token_user, token_host; +end + +function get_token_info(token) + local token_id, token_user, token_host = parse_token(token); + if not token_id then + return nil, "invalid-token-format"; + end + if token_host ~= module.host then + return nil, "invalid-host"; + end + + local token_info, err = token_store:get(token_user, token_id); + if not token_info then + if err then + return nil, "internal-error"; + end + return nil, "not-authorized"; + end + + if token_info.expires and token_info.expires < os.time() then + return nil, "not-authorized"; + end + + return token_info +end + +function revoke_token(token) + local token_id, token_user, token_host = parse_token(token); + if not token_id then + return nil, "invalid-token-format"; + end + if token_host ~= module.host then + return nil, "invalid-host"; + end + return token_store:set(token_user, token_id, nil); +end -- cgit v1.2.3 From f6a365c970146c2bef638f29eb5cc1b8587a6f98 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Wed, 26 Feb 2020 22:46:15 +0000 Subject: mod_authtokens: Rename to mod_tokenauth for consistency with mod_saslauth --- plugins/mod_authtokens.lua | 81 ---------------------------------------------- plugins/mod_tokenauth.lua | 81 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 81 deletions(-) delete mode 100644 plugins/mod_authtokens.lua create mode 100644 plugins/mod_tokenauth.lua diff --git a/plugins/mod_authtokens.lua b/plugins/mod_authtokens.lua deleted file mode 100644 index 8e516924..00000000 --- a/plugins/mod_authtokens.lua +++ /dev/null @@ -1,81 +0,0 @@ -local id = require "util.id"; -local jid = require "util.jid"; -local base64 = require "util.encodings".base64; - -local token_store = module:open_store("auth_tokens", "map"); - -function create_jid_token(actor_jid, token_jid, token_scope, token_ttl) - token_jid = jid.prep(token_jid); - if not actor_jid or token_jid ~= actor_jid and not jid.compare(token_jid, actor_jid) then - return nil, "not-authorized"; - end - - local token_username, token_host, token_resource = jid.split(token_jid); - - if token_host ~= module.host then - return nil, "invalid-host"; - end - - local token_info = { - owner = actor_jid; - expires = token_ttl and (os.time() + token_ttl) or nil; - jid = token_jid; - session = { - username = token_username; - host = token_host; - resource = token_resource; - - auth_scope = token_scope; - }; - }; - - local token_id = id.long(); - local token = base64.encode("1;"..token_username.."@"..token_host..";"..token_id); - token_store:set(token_username, token_id, token_info); - - return token, token_info; -end - -local function parse_token(encoded_token) - local token = base64.decode(encoded_token); - if not token then return nil; end - local token_jid, token_id = token:match("^1;([^;]+);(.+)$"); - if not token_jid then return nil; end - local token_user, token_host = jid.split(token_jid); - return token_id, token_user, token_host; -end - -function get_token_info(token) - local token_id, token_user, token_host = parse_token(token); - if not token_id then - return nil, "invalid-token-format"; - end - if token_host ~= module.host then - return nil, "invalid-host"; - end - - local token_info, err = token_store:get(token_user, token_id); - if not token_info then - if err then - return nil, "internal-error"; - end - return nil, "not-authorized"; - end - - if token_info.expires and token_info.expires < os.time() then - return nil, "not-authorized"; - end - - return token_info -end - -function revoke_token(token) - local token_id, token_user, token_host = parse_token(token); - if not token_id then - return nil, "invalid-token-format"; - end - if token_host ~= module.host then - return nil, "invalid-host"; - end - return token_store:set(token_user, token_id, nil); -end diff --git a/plugins/mod_tokenauth.lua b/plugins/mod_tokenauth.lua new file mode 100644 index 00000000..8e516924 --- /dev/null +++ b/plugins/mod_tokenauth.lua @@ -0,0 +1,81 @@ +local id = require "util.id"; +local jid = require "util.jid"; +local base64 = require "util.encodings".base64; + +local token_store = module:open_store("auth_tokens", "map"); + +function create_jid_token(actor_jid, token_jid, token_scope, token_ttl) + token_jid = jid.prep(token_jid); + if not actor_jid or token_jid ~= actor_jid and not jid.compare(token_jid, actor_jid) then + return nil, "not-authorized"; + end + + local token_username, token_host, token_resource = jid.split(token_jid); + + if token_host ~= module.host then + return nil, "invalid-host"; + end + + local token_info = { + owner = actor_jid; + expires = token_ttl and (os.time() + token_ttl) or nil; + jid = token_jid; + session = { + username = token_username; + host = token_host; + resource = token_resource; + + auth_scope = token_scope; + }; + }; + + local token_id = id.long(); + local token = base64.encode("1;"..token_username.."@"..token_host..";"..token_id); + token_store:set(token_username, token_id, token_info); + + return token, token_info; +end + +local function parse_token(encoded_token) + local token = base64.decode(encoded_token); + if not token then return nil; end + local token_jid, token_id = token:match("^1;([^;]+);(.+)$"); + if not token_jid then return nil; end + local token_user, token_host = jid.split(token_jid); + return token_id, token_user, token_host; +end + +function get_token_info(token) + local token_id, token_user, token_host = parse_token(token); + if not token_id then + return nil, "invalid-token-format"; + end + if token_host ~= module.host then + return nil, "invalid-host"; + end + + local token_info, err = token_store:get(token_user, token_id); + if not token_info then + if err then + return nil, "internal-error"; + end + return nil, "not-authorized"; + end + + if token_info.expires and token_info.expires < os.time() then + return nil, "not-authorized"; + end + + return token_info +end + +function revoke_token(token) + local token_id, token_user, token_host = parse_token(token); + if not token_id then + return nil, "invalid-token-format"; + end + if token_host ~= module.host then + return nil, "invalid-host"; + end + return token_store:set(token_user, token_id, nil); +end -- cgit v1.2.3 From 92c65ed9af11d0e314db670b37af1119a6eb67e6 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 28 Feb 2020 14:13:04 +0100 Subject: mod_tokenauth: Handle tokens issued to bare hosts (eg components) --- plugins/mod_tokenauth.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_tokenauth.lua b/plugins/mod_tokenauth.lua index 8e516924..b023d9f8 100644 --- a/plugins/mod_tokenauth.lua +++ b/plugins/mod_tokenauth.lua @@ -30,7 +30,7 @@ function create_jid_token(actor_jid, token_jid, token_scope, token_ttl) }; local token_id = id.long(); - local token = base64.encode("1;"..token_username.."@"..token_host..";"..token_id); + local token = base64.encode("1;"..jid.join(token_username, token_host)..";"..token_id); token_store:set(token_username, token_id, token_info); return token, token_info; -- cgit v1.2.3 From 40bb67c03bba8f3a7d1c0fed751c5411fb5f270d Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Fri, 28 Feb 2020 21:55:40 +0000 Subject: mod_tokenauth: Track creation time of tokens --- plugins/mod_tokenauth.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/mod_tokenauth.lua b/plugins/mod_tokenauth.lua index b023d9f8..c04a1aa4 100644 --- a/plugins/mod_tokenauth.lua +++ b/plugins/mod_tokenauth.lua @@ -18,6 +18,7 @@ function create_jid_token(actor_jid, token_jid, token_scope, token_ttl) local token_info = { owner = actor_jid; + created = os.time(); expires = token_ttl and (os.time() + token_ttl) or nil; jid = token_jid; session = { -- cgit v1.2.3 From 1d5af5f0a385ffbe4c46569d8fd8696c8c989a60 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Wed, 11 Mar 2020 14:36:56 +0000 Subject: storagemanager: Add tests for map stores --- spec/core_storagemanager_spec.lua | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/spec/core_storagemanager_spec.lua b/spec/core_storagemanager_spec.lua index c3534cd5..9ecb0cb4 100644 --- a/spec/core_storagemanager_spec.lua +++ b/spec/core_storagemanager_spec.lua @@ -90,6 +90,44 @@ describe("storagemanager", function () end); end); + describe("map stores", function () + -- These tests rely on being executed in order, disable any order + -- randomization for this block + randomize(false); + + local store, kv_store; + it("may be opened", function () + store = assert(sm.open(test_host, "test-map", "map")); + end); + + it("may be opened as a keyval store", function () + kv_store = assert(sm.open(test_host, "test-map", "keyval")); + end); + + it("may set a specific key for a user", function () + assert(store:set("user9999", "foo", "bar")); + assert.same(kv_store:get("user9999"), { foo = "bar" }); + end); + + it("may get a specific key for a user", function () + assert.equal("bar", store:get("user9999", "foo")); + end); + + it("may remove data for a user", function () + assert(store:set("user9999", "foo", nil)); + do + local ret, err = store:get("user9999", "foo"); + assert.is_nil(ret); + assert.is_nil(err); + end + do + local ret, err = kv_store:get("user9999"); + assert.is_nil(ret); + assert.is_nil(err); + end + end); + end); + describe("archive stores", function () randomize(false); -- cgit v1.2.3 From 33b9b2b91ec31f328dcd080832c3caf7a24cb93a Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Wed, 11 Mar 2020 15:57:53 +0000 Subject: mod_storage_sql: Add map_store:find_key() and map_store:delete_key() (+ tests) --- plugins/mod_storage_sql.lua | 44 +++++++++++++++++++++++ spec/core_storagemanager_spec.lua | 74 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 116 insertions(+), 2 deletions(-) diff --git a/plugins/mod_storage_sql.lua b/plugins/mod_storage_sql.lua index 4a2f0d90..a75b832d 100644 --- a/plugins/mod_storage_sql.lua +++ b/plugins/mod_storage_sql.lua @@ -230,6 +230,50 @@ function map_store:set_keys(username, keydatas) return result; end +function map_store:find_key(key) + if type(key) ~= "string" or key == "" then + return nil, "find_key only supports non-empty string keys"; + end + local ok, result = engine:transaction(function() + local query = [[ + SELECT "user", "type", "value" + FROM "prosody" + WHERE "host"=? AND "store"=? AND "key"=? + ]]; + + local data; + for row in engine:select(query, host, self.store, key) do + local key_data, err = deserialize(row[2], row[3]); + assert(key_data ~= nil, err); + if data == nil then + data = {}; + end + data[row[1]] = key_data; + end + + return data; + + end); + if not ok then return nil, result; end + return result; +end + +function map_store:delete_key(key) + if type(key) ~= "string" or key == "" then + return nil, "delete_key only supports non-empty string keys"; + end + local ok, result = engine:transaction(function() + local delete_sql = [[ + DELETE FROM "prosody" + WHERE "host"=? AND "store"=? AND "key"=?; + ]]; + engine:delete(delete_sql, host, self.store, key); + return true; + end); + if not ok then return nil, result; end + return result; +end + local archive_store = {} archive_store.caps = { total = true; diff --git a/spec/core_storagemanager_spec.lua b/spec/core_storagemanager_spec.lua index 9ecb0cb4..69afd87a 100644 --- a/spec/core_storagemanager_spec.lua +++ b/spec/core_storagemanager_spec.lua @@ -62,6 +62,8 @@ describe("storagemanager", function () sm.initialize_host(test_host); assert(mm.load(test_host, "storage_"..backend_config.storage)); + local sql_it = backend_config.sql and it or pending; + describe("key-value stores", function () -- These tests rely on being executed in order, disable any order -- randomization for this block @@ -113,15 +115,83 @@ describe("storagemanager", function () assert.equal("bar", store:get("user9999", "foo")); end); - it("may remove data for a user", function () + sql_it("may find all users with a specific key", function () + assert.is_function(store.find_key); + assert(store:set("user9999b", "bar", "bar")); + assert(store:set("user9999c", "foo", "blah")); + local ret, err = store:find_key("foo"); + assert.is_nil(err); + assert.same({ user9999 = "bar", user9999c = "blah" }, ret); + end); + + sql_it("rejects empty or non-string keys to find_key", function () + assert.is_function(store.find_key); + do + local ret, err = store:find_key(""); + assert.is_nil(ret); + assert.is_not_nil(err); + end + do + local ret, err = store:find_key(true); + assert.is_nil(ret); + assert.is_not_nil(err); + end + end); + + sql_it("rejects empty or non-string keys to delete_key", function () + assert.is_function(store.delete_key); + do + local ret, err = store:delete_key(""); + assert.is_nil(ret); + assert.is_not_nil(err); + end + do + local ret, err = store:delete_key(true); + assert.is_nil(ret); + assert.is_not_nil(err); + end + end); + + sql_it("may delete all instances of a specific key", function () + assert.is_function(store.delete_key); + assert(store:set("user9999b", "foo", "hello")); + + local ret, err = store:delete_key("bar"); + -- Ensure key was deleted + do + local ret, err = store:get("user9999b", "bar"); + assert.is_nil(ret); + assert.is_nil(err); + end + -- Ensure other users/keys are intact + do + local ret, err = store:get("user9999", "foo"); + assert.equal("bar", ret); + assert.is_nil(err); + end + do + local ret, err = store:get("user9999b", "foo"); + assert.equal("hello", ret); + assert.is_nil(err); + end + do + local ret, err = store:get("user9999c", "foo"); + assert.equal("blah", ret); + assert.is_nil(err); + end + end); + + it("may remove data for a specific key for a user", function () assert(store:set("user9999", "foo", nil)); do local ret, err = store:get("user9999", "foo"); assert.is_nil(ret); assert.is_nil(err); end + + assert(store:set("user9999b", "foo", nil)); do - local ret, err = kv_store:get("user9999"); + local ret, err = store:get("user9999b", "foo"); assert.is_nil(ret); assert.is_nil(err); end -- cgit v1.2.3 From e35a4db4984013d82c90bd99fa1cbb15396fa415 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Wed, 11 Mar 2020 16:07:36 +0000 Subject: storagemanager: Fix unused variable in tests [luacheck] --- spec/core_storagemanager_spec.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/core_storagemanager_spec.lua b/spec/core_storagemanager_spec.lua index 69afd87a..dd80f4b8 100644 --- a/spec/core_storagemanager_spec.lua +++ b/spec/core_storagemanager_spec.lua @@ -64,6 +64,7 @@ describe("storagemanager", function () local sql_it = backend_config.sql and it or pending; + describe("key-value stores", function () -- These tests rely on being executed in order, disable any order -- randomization for this block @@ -156,7 +157,7 @@ describe("storagemanager", function () assert.is_function(store.delete_key); assert(store:set("user9999b", "foo", "hello")); - local ret, err = store:delete_key("bar"); + assert(store:delete_key("bar")); -- Ensure key was deleted do local ret, err = store:get("user9999b", "bar"); -- cgit v1.2.3 From 81f5c3e319792bec5d53c30334f3f54537aeb32b Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Wed, 11 Mar 2020 16:29:57 +0000 Subject: storagemanager: Add support for :find_key() and :delete_key() to map store shim --- core/storagemanager.lua | 33 +++++++++++++++++++++++++++++++++ spec/core_storagemanager_spec.lua | 11 ++++------- 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/core/storagemanager.lua b/core/storagemanager.lua index dea71733..14de1314 100644 --- a/core/storagemanager.lua +++ b/core/storagemanager.lua @@ -167,6 +167,39 @@ local map_shim_mt = { return self.keyval_store:set(username, current); end; remove = {}; + find_key = function (self, key) + if type(key) ~= "string" or key == "" then + return nil, "find_key only supports non-empty string keys"; + end + local ret; + for username in self.keyval_store:users() do + local key_data = self:get(username, key); + if key_data then + if not ret then + ret = {}; + end + ret[username] = key_data; + end + end + return ret; + end; + delete_key = function (self, key) + if type(key) ~= "string" or key == "" then + return nil, "delete_key only supports non-empty string keys"; + end + local data = { [key] = self.remove }; + local last_err; + for username in self.keyval_store:users() do + local ok, err = self:set_keys(username, data); + if not ok then + last_err = err; + end + end + if last_err then + return nil, last_err; + end + return true; + end; }; } diff --git a/spec/core_storagemanager_spec.lua b/spec/core_storagemanager_spec.lua index dd80f4b8..848f7910 100644 --- a/spec/core_storagemanager_spec.lua +++ b/spec/core_storagemanager_spec.lua @@ -62,9 +62,6 @@ describe("storagemanager", function () sm.initialize_host(test_host); assert(mm.load(test_host, "storage_"..backend_config.storage)); - local sql_it = backend_config.sql and it or pending; - - describe("key-value stores", function () -- These tests rely on being executed in order, disable any order -- randomization for this block @@ -116,7 +113,7 @@ describe("storagemanager", function () assert.equal("bar", store:get("user9999", "foo")); end); - sql_it("may find all users with a specific key", function () + it("may find all users with a specific key", function () assert.is_function(store.find_key); assert(store:set("user9999b", "bar", "bar")); assert(store:set("user9999c", "foo", "blah")); @@ -125,7 +122,7 @@ describe("storagemanager", function () assert.same({ user9999 = "bar", user9999c = "blah" }, ret); end); - sql_it("rejects empty or non-string keys to find_key", function () + it("rejects empty or non-string keys to find_key", function () assert.is_function(store.find_key); do local ret, err = store:find_key(""); @@ -139,7 +136,7 @@ describe("storagemanager", function () end end); - sql_it("rejects empty or non-string keys to delete_key", function () + it("rejects empty or non-string keys to delete_key", function () assert.is_function(store.delete_key); do local ret, err = store:delete_key(""); @@ -153,7 +150,7 @@ describe("storagemanager", function () end end); - sql_it("may delete all instances of a specific key", function () + it("may delete all instances of a specific key", function () assert.is_function(store.delete_key); assert(store:set("user9999b", "foo", "hello")); -- cgit v1.2.3 From cb6148d155ea02a68e40b8afb5861451750499ad Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Wed, 11 Mar 2020 16:32:41 +0000 Subject: storagemanager, mod_storage_sql: Rename methods to :get_all() and :delete_all() --- core/storagemanager.lua | 8 ++++---- plugins/mod_storage_sql.lua | 8 ++++---- spec/core_storagemanager_spec.lua | 24 ++++++++++++------------ 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/core/storagemanager.lua b/core/storagemanager.lua index 14de1314..856acad3 100644 --- a/core/storagemanager.lua +++ b/core/storagemanager.lua @@ -167,9 +167,9 @@ local map_shim_mt = { return self.keyval_store:set(username, current); end; remove = {}; - find_key = function (self, key) + get_all = function (self, key) if type(key) ~= "string" or key == "" then - return nil, "find_key only supports non-empty string keys"; + return nil, "get_all only supports non-empty string keys"; end local ret; for username in self.keyval_store:users() do @@ -183,9 +183,9 @@ local map_shim_mt = { end return ret; end; - delete_key = function (self, key) + delete_all = function (self, key) if type(key) ~= "string" or key == "" then - return nil, "delete_key only supports non-empty string keys"; + return nil, "delete_all only supports non-empty string keys"; end local data = { [key] = self.remove }; local last_err; diff --git a/plugins/mod_storage_sql.lua b/plugins/mod_storage_sql.lua index a75b832d..accfa2df 100644 --- a/plugins/mod_storage_sql.lua +++ b/plugins/mod_storage_sql.lua @@ -230,9 +230,9 @@ function map_store:set_keys(username, keydatas) return result; end -function map_store:find_key(key) +function map_store:get_all(key) if type(key) ~= "string" or key == "" then - return nil, "find_key only supports non-empty string keys"; + return nil, "get_all only supports non-empty string keys"; end local ok, result = engine:transaction(function() local query = [[ @@ -258,9 +258,9 @@ function map_store:find_key(key) return result; end -function map_store:delete_key(key) +function map_store:delete_all(key) if type(key) ~= "string" or key == "" then - return nil, "delete_key only supports non-empty string keys"; + return nil, "delete_all only supports non-empty string keys"; end local ok, result = engine:transaction(function() local delete_sql = [[ diff --git a/spec/core_storagemanager_spec.lua b/spec/core_storagemanager_spec.lua index 848f7910..b228c5fd 100644 --- a/spec/core_storagemanager_spec.lua +++ b/spec/core_storagemanager_spec.lua @@ -114,47 +114,47 @@ describe("storagemanager", function () end); it("may find all users with a specific key", function () - assert.is_function(store.find_key); + assert.is_function(store.get_all); assert(store:set("user9999b", "bar", "bar")); assert(store:set("user9999c", "foo", "blah")); - local ret, err = store:find_key("foo"); + local ret, err = store:get_all("foo"); assert.is_nil(err); assert.same({ user9999 = "bar", user9999c = "blah" }, ret); end); - it("rejects empty or non-string keys to find_key", function () - assert.is_function(store.find_key); + it("rejects empty or non-string keys to get_all", function () + assert.is_function(store.get_all); do - local ret, err = store:find_key(""); + local ret, err = store:get_all(""); assert.is_nil(ret); assert.is_not_nil(err); end do - local ret, err = store:find_key(true); + local ret, err = store:get_all(true); assert.is_nil(ret); assert.is_not_nil(err); end end); - it("rejects empty or non-string keys to delete_key", function () - assert.is_function(store.delete_key); + it("rejects empty or non-string keys to delete_all", function () + assert.is_function(store.delete_all); do - local ret, err = store:delete_key(""); + local ret, err = store:delete_all(""); assert.is_nil(ret); assert.is_not_nil(err); end do - local ret, err = store:delete_key(true); + local ret, err = store:delete_all(true); assert.is_nil(ret); assert.is_not_nil(err); end end); it("may delete all instances of a specific key", function () - assert.is_function(store.delete_key); + assert.is_function(store.delete_all); assert(store:set("user9999b", "foo", "hello")); - assert(store:delete_key("bar")); + assert(store:delete_all("bar")); -- Ensure key was deleted do local ret, err = store:get("user9999b", "bar"); -- cgit v1.2.3 From e30cbeae993803583e1f2ed2085c9be5b5fb6f63 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Thu, 12 Mar 2020 14:10:12 +0000 Subject: MUC: Pass previous role to :publicise_occupant_status() whenever possible Currently there is what amounts to a hack in presence_broadcast.lib.lua to make it always broadcast presence with roles of "none". This is to ensure that if you previously saw available presence for someone, you will also see the unavailable presence (which always has role="none"). The correct approach is to take into account what the previous role was ( i.e. answer the question: "Was the available presence for this occupant a role for which presence broadcast is enabled?). The logic is already in place to do this correctly, but most call sites do not provide the previous role (prev_role argument) of the occupant, which causes it to not be used. In its place the hack to always broadcast presence of role="none" has allowed things to continue to work. The intention is that a subsequent commit will remove the unconditional broadcast of role="none". --- plugins/muc/muc.lib.lua | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 8adb0046..2a7952cf 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -392,6 +392,7 @@ function room_mt:handle_kickable(origin, stanza) -- luacheck: ignore 212 end occupant:set_session(real_jid, st.presence({type="unavailable"}) :tag('status'):text(error_message)); + local orig_role = occupant.role; local is_last_session = occupant.jid == real_jid; if is_last_session then occupant.role = nil; @@ -401,7 +402,7 @@ function room_mt:handle_kickable(origin, stanza) -- luacheck: ignore 212 if is_last_session then x:tag("status", {code = "333"}); end - self:publicise_occupant_status(new_occupant or occupant, x); + self:publicise_occupant_status(new_occupant or occupant, x, nil, nil, nil, orig_role); if is_last_session then module:fire_event("muc-occupant-left", { room = self; @@ -605,6 +606,7 @@ function room_mt:handle_normal_presence(origin, stanza) -- Send presence stanza about original occupant if orig_occupant ~= nil and orig_occupant ~= dest_occupant then local orig_x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user";}); + local orig_role = orig_occupant.role; local dest_nick; if dest_occupant == nil then -- Session is leaving log("debug", "session %s is leaving occupant %s", real_jid, orig_occupant.nick); @@ -647,7 +649,7 @@ function room_mt:handle_normal_presence(origin, stanza) end self:save_occupant(orig_occupant); - self:publicise_occupant_status(orig_occupant, orig_x, dest_nick); + self:publicise_occupant_status(orig_occupant, orig_x, dest_nick, nil, nil, orig_role); if is_last_orig_session then module:fire_event("muc-occupant-left", { @@ -679,7 +681,7 @@ function room_mt:handle_normal_presence(origin, stanza) if nick_changed then self_x:tag("status", {code="210"}):up(); end - self:publicise_occupant_status(dest_occupant, {base=dest_x,self=self_x}); + self:publicise_occupant_status(dest_occupant, {base=dest_x,self=self_x}, nil, nil, nil, orig_occupant and orig_occupant.role or nil); if orig_occupant ~= nil and orig_occupant ~= dest_occupant and not is_last_orig_session then -- If user is swapping and wasn't last original session @@ -1362,7 +1364,7 @@ function room_mt:set_affiliation(actor, jid, affiliation, reason, data) if next(occupants_updated) ~= nil then for occupant, old_role in pairs(occupants_updated) do - self:publicise_occupant_status(occupant, x, nil, actor, reason); + self:publicise_occupant_status(occupant, x, nil, actor, reason, old_role); if occupant.role == nil then module:fire_event("muc-occupant-left", { room = self; -- cgit v1.2.3 From 0cb40c7b0516d2371428113ae99961cb6368291f Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Thu, 12 Mar 2020 14:13:22 +0000 Subject: MUC: Don't unconditionally broadcast presence with role="none" Detailed explanation in de607875d4bd. A presence with role="none" (which is always type="unavailable") should only be broadcast if available presence was previously broadcast for that occupant. --- plugins/muc/presence_broadcast.lib.lua | 4 ---- 1 file changed, 4 deletions(-) diff --git a/plugins/muc/presence_broadcast.lib.lua b/plugins/muc/presence_broadcast.lib.lua index 613e6403..72e0d76b 100644 --- a/plugins/muc/presence_broadcast.lib.lua +++ b/plugins/muc/presence_broadcast.lib.lua @@ -11,7 +11,6 @@ local st = require "util.stanza"; local valid_roles = { "visitor", "participant", "moderator" }; local default_broadcast = { - none = true; visitor = true; participant = true; moderator = true; @@ -24,9 +23,6 @@ end local function set_presence_broadcast(room, broadcast_roles) broadcast_roles = broadcast_roles or default_broadcast; - -- Ensure that unavailable presence is always sent when role changes to none - broadcast_roles.none = true; - local changed = false; local old_broadcast_roles = get_presence_broadcast(room); for _, role in ipairs(valid_roles) do -- cgit v1.2.3 From 572526d384fa79688a189c2e5d37614036b7ffd3 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Thu, 12 Mar 2020 14:35:34 +0000 Subject: MUC: Pass previous role to :publicise_occupant_status() when destroying a MUC --- plugins/muc/muc.lib.lua | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 2a7952cf..42f41831 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -908,12 +908,13 @@ function room_mt:clear(x) x = x or st.stanza("x", {xmlns='http://jabber.org/protocol/muc#user'}); local occupants_updated = {}; for nick, occupant in self:each_occupant() do -- luacheck: ignore 213 + local prev_role = occupant.role; occupant.role = nil; self:save_occupant(occupant); - occupants_updated[occupant] = true; + occupants_updated[occupant] = prev_role; end - for occupant in pairs(occupants_updated) do - self:publicise_occupant_status(occupant, x); + for occupant, prev_role in pairs(occupants_updated) do + self:publicise_occupant_status(occupant, x, nil, nil, nil, prev_role); module:fire_event("muc-occupant-left", { room = self; nick = occupant.nick; -- cgit v1.2.3 From 64bb781dfec9b44618da58991aab292b1e360be1 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Thu, 12 Mar 2020 16:01:31 +0000 Subject: MUC: Support for broadcasting unavailable presence for affiliated offline users Activated when muc#roomconfig_presencebroadcast includes the "none" role. --- plugins/muc/muc.lib.lua | 25 +- plugins/muc/presence_broadcast.lib.lua | 2 +- spec/scansion/muc_show_offline.scs | 544 +++++++++++++++++++++++++++++++++ 3 files changed, 568 insertions(+), 3 deletions(-) create mode 100644 spec/scansion/muc_show_offline.scs diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 42f41831..5f45498a 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -333,7 +333,9 @@ function room_mt:send_occupant_list(to, filter) end end end + local broadcast_bare_jids = {}; -- Track which bare JIDs we have sent presence for for occupant_jid, occupant in self:each_occupant() do + broadcast_bare_jids[occupant.bare_jid] = true; if filter == nil or filter(occupant_jid, occupant) then local x = st.stanza("x", {xmlns='http://jabber.org/protocol/muc#user'}); self:build_item_list(occupant, x, is_anonymous and to_bare ~= occupant.bare_jid); -- can always see your own jids @@ -345,6 +347,25 @@ function room_mt:send_occupant_list(to, filter) end end end + if broadcast_roles.none then + -- Broadcast stanzas for affiliated users not currently in the MUC + for affiliated_jid, affiliation, affiliation_data in self:each_affiliation() do + local nick = affiliation_data and affiliation_data.reserved_nickname; + if (nick or not is_anonymous) and not broadcast_bare_jids[affiliated_jid] + and (filter == nil or filter(affiliated_jid, nil)) then + local from = nick and (self.jid.."/"..nick) or self.jid; + local pres = st.presence({ to = to, from = from, type = "unavailable" }) + :tag("x", { xmlns = 'http://jabber.org/protocol/muc#user' }) + :tag("item", { + affiliation = affiliation; + role = "none"; + nick = nick; + jid = not is_anonymous and affiliated_jid or nil }):up() + :up(); + self:route_stanza(pres); + end + end + end end function room_mt:get_disco_info(stanza) @@ -670,7 +691,7 @@ function room_mt:handle_normal_presence(origin, stanza) -- Send occupant list to newly joined or desynced user self:send_occupant_list(real_jid, function(nick, occupant) -- luacheck: ignore 212 -- Don't include self - return occupant:get_presence(real_jid) == nil; + return (not occupant) or occupant:get_presence(real_jid) == nil; end) end local dest_x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user";}); @@ -1378,7 +1399,7 @@ function room_mt:set_affiliation(actor, jid, affiliation, reason, data) -- Send everyone else's presences (as jid visibility has changed) for real_jid in occupant:each_session() do self:send_occupant_list(real_jid, function(occupant_jid, occupant) --luacheck: ignore 212 433 - return occupant.bare_jid ~= jid; + return (not occupant) or occupant.bare_jid ~= jid; end); end end diff --git a/plugins/muc/presence_broadcast.lib.lua b/plugins/muc/presence_broadcast.lib.lua index 72e0d76b..82a89fee 100644 --- a/plugins/muc/presence_broadcast.lib.lua +++ b/plugins/muc/presence_broadcast.lib.lua @@ -9,7 +9,7 @@ local st = require "util.stanza"; -local valid_roles = { "visitor", "participant", "moderator" }; +local valid_roles = { "none", "visitor", "participant", "moderator" }; local default_broadcast = { visitor = true; participant = true; diff --git a/spec/scansion/muc_show_offline.scs b/spec/scansion/muc_show_offline.scs new file mode 100644 index 00000000..755a4774 --- /dev/null +++ b/spec/scansion/muc_show_offline.scs @@ -0,0 +1,544 @@ +# MUC: Room registration and presence broadcast of unavailable members + +[Client] Romeo + jid: user@localhost + password: password + +[Client] Juliet + jid: user2@localhost + password: password + +[Client] Rosaline + jid: user3@localhost + password: password + +----- + +Romeo connects + +Romeo sends: + + + + +Romeo receives: + + + + + + + + +Romeo receives: + + +# Submit config form +Romeo sends: + + + + + http://jabber.org/protocol/muc#roomconfig + + + none + participant + moderator + + + + + +Romeo receives: + + + +Romeo sends: + + + + + + +Romeo receives: + + + + + + +Romeo receives: + + +# Juliet connects, and joins the room +Juliet connects + +Juliet sends: + + + + +Juliet receives: + + +Juliet receives: + + +Juliet receives: + + +Romeo receives: + + +# Juliet retrieves the registration form + +Juliet sends: + + + + +Juliet receives: + + + + + http://jabber.org/protocol/muc#register + + + + + + + + +Juliet sends: + + + + + http://jabber.org/protocol/muc#register + + + Juliet + + + + + +Juliet receives: + + + + + + + +Juliet receives: + + +# Juliet discovers her reserved nick + +Juliet sends: + + + + +Juliet receives: + + + + + + +# Juliet leaves the room: + +Juliet sends: + + +Juliet receives: + + + + + + + +Romeo receives: + + + + + + +# Rosaline connect and tries to join the room as Juliet + +Rosaline connects + +Rosaline sends: + + + + +Rosaline receives: + + + + + + + +# In a heated moment, Juliet unregisters from the room + +Juliet sends: + + + + + + +Juliet receives: + + +# Romeo is notified of Juliet's sad decision + +Romeo receives: + + + + + + +# Rosaline attempts once more to sneak into the room, disguised as Juliet + +Rosaline sends: + + + + +Rosaline receives: + + + + + + +Rosaline receives: + + + + + + + +Romeo receives: + + + + + + +# On discovering the ruse, Romeo restores Juliet's nick and status within the room + +Romeo sends: + + + + + + +# Rosaline is evicted from the room + +Romeo receives: + + + + + This nickname is reserved + + + + +# An out-of-room affiliation change is received for Juliet + +Romeo receives: + + + + + + +Romeo receives: + + +Rosaline receives: + + + + + This nickname is reserved + + + + + +# Rosaline, frustrated, attempts to get back into the room... + +Rosaline sends: + + + + +# ...but once again, is denied + +Rosaline receives: + + + + + + + +# Juliet, however, quietly joins the room with success + +Juliet sends: + + + + +Juliet receives: + + +Juliet receives: + + +Juliet receives: + + +Romeo receives: + + +# Romeo checks whether he has reserved his own nick yet + +Romeo sends: + + + + +# But no nick is returned, as he hasn't registered yet! + +Romeo receives: + + + + +# Romeo updates his own registration + +Romeo sends: + + + + +Romeo receives: + + + + + http://jabber.org/protocol/muc#register + + + + + + + + +Romeo sends: + + + + + http://jabber.org/protocol/muc#register + + + Romeo + + + + + +Romeo receives: + + + + + + + +Romeo receives: + + +Juliet receives: + + + + + + +# Romeo discovers his reserved nick + +Romeo sends: + + + + +Romeo receives: + + + + + + +# To check the status of the room is as expected, Romeo requests the member list + +Romeo sends: + + + + + + +Romeo receives: + + + + + + +Juliet sends: + + +Juliet receives: + + +Romeo receives: + + +# Rosaline joins as herself + +Rosaline sends: + + + + +Rosaline receives: + + +Rosaline receives: + + + + + + +Rosaline receives: + + +Rosaline receives: + + +Romeo receives: + + + + + + +# Rosaline tries to register her own nickname, but unaffiliated +# registration is disabled by default + +Rosaline sends: + + + + +Rosaline receives: + + + + + + +Rosaline sends: + + + + + http://jabber.org/protocol/muc#register + + + Romeo + + + + + +Rosaline receives: + + + + + + +# Romeo reserves her nickname for her + +Romeo sends: + + + + + + +Romeo receives: + + + + + + + + +Romeo receives: + + +Rosaline receives: + + + + + + + + + +# Romeo sets their their own nickname via admin query (see #1273) +Romeo sends: + + + + + + +Romeo receives: + + + + + + + + + +Romeo receives: + + -- cgit v1.2.3 From 818722afb83b3c61c49e75fae338a4140414f84f Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Thu, 12 Mar 2020 16:10:44 +0000 Subject: MUC: Switch to new storage format by default Changing the default setting of `new_muc_storage_format` from false to true. The code supports reading both formats since 0.11, but servers with MUCs stored using the new format will not be able to downgrade to 0.10 or earlier. The new format is clearer (less nesting for the most commonly-accessed data), and combined with the new map-store methods, allows for some operations to become more efficient (such as finding out which MUCs on a service a given user is affiliated with). --- plugins/muc/muc.lib.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 5f45498a..2c9139d1 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1536,7 +1536,7 @@ function _M.new_room(jid, config) }, room_mt); end -local new_format = module:get_option_boolean("new_muc_storage_format", false); +local new_format = module:get_option_boolean("new_muc_storage_format", true); function room_mt:freeze(live) local frozen, state; -- cgit v1.2.3 From 33ee094ac8dca4c4edea0b32d1b984c8b46e63a9 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Thu, 12 Mar 2020 20:32:07 +0000 Subject: MUC: Persist affiliation_data in new MUC format! --- plugins/muc/muc.lib.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 2c9139d1..4f265cf8 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1544,6 +1544,7 @@ function room_mt:freeze(live) frozen = { _jid = self.jid; _data = self._data; + _affiliation_data = self._affiliation_data; }; for user, affiliation in pairs(self._affiliations) do frozen[user] = affiliation; -- cgit v1.2.3 From fcc035aa365f0e57199f5a5be47337e169a0bd29 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 15 Mar 2020 20:35:07 +0100 Subject: README: Update link to web chat At some point the web chat moved to /chat and then to this subdomain --- README | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README b/README index 4b52186b..647327ac 100644 --- a/README +++ b/README @@ -18,7 +18,7 @@ Jabber/XMPP Chat: Address: prosody@conference.prosody.im Web interface: - https://prosody.im/webchat + https://chat.prosody.im/ Mailing lists: User support and discussion: -- cgit v1.2.3 From b816830a99f237f89de8160eb2dc8f6808a05d6f Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Wed, 18 Mar 2020 17:42:56 +0000 Subject: MUC: Add initial hats support (broadcast only) Based on the currently-deferred XEP-0317. The protocol differs a little (because XEP-0317 is incomplete), therefore currently we use a custom namespace. The plan is to update and finish XEP-0317. --- plugins/muc/hats.lib.lua | 23 +++++++++++++++++++++++ plugins/muc/mod_muc.lua | 1 + 2 files changed, 24 insertions(+) create mode 100644 plugins/muc/hats.lib.lua diff --git a/plugins/muc/hats.lib.lua b/plugins/muc/hats.lib.lua new file mode 100644 index 00000000..5b0429b5 --- /dev/null +++ b/plugins/muc/hats.lib.lua @@ -0,0 +1,23 @@ +local st = require "util.stanza"; + +local xmlns_hats = "xmpp:prosody.im/protocol/hats:1"; + +module:hook("muc-broadcast-presence", function (event) + -- Strip any hats claimed by the client (to prevent spoofing) + event.stanza:remove_children("hats", xmlns_hats); + + local aff_data = event.room:get_affiliation_data(event.occupant.bare_jid); + local hats = aff_data and aff_data.hats; + if not hats then return; end + local hats_el; + for hat_id, hat_data in pairs(hats) do + if hat_data.active then + if not hats_el then + hats_el = st.stanza("hats", { xmlns = xmlns_hats }); + end + hats_el:tag("hat", { uri = hat_id, title = hat_data.title }):up(); + end + end + if not hats_el then return; end + event.stanza:add_direct_child(hats_el); +end); diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index 697b0081..93b06f72 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -105,6 +105,7 @@ module:depends("disco"); module:add_identity("conference", "text", module:get_option_string("name", "Prosody Chatrooms")); module:add_feature("http://jabber.org/protocol/muc"); module:depends "muc_unique" +module:require "muc/hats"; module:require "muc/lock"; local function is_admin(jid) -- cgit v1.2.3 From c0b0c818027d7070e7d1539a31e5d9fce2cb9f73 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 19 Mar 2020 00:10:15 +0100 Subject: mod_presence: Advertise support for Subscription Pre-Approval MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit RFC 6121 §3.4 says: > If a server supports subscription pre-approvals, then it MUST > advertise the following stream feature during stream negotiation. The feature itself (#686) was added in f0e9e5bda415 --- plugins/mod_presence.lua | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/plugins/mod_presence.lua b/plugins/mod_presence.lua index e69c31a5..d6e2b2b7 100644 --- a/plugins/mod_presence.lua +++ b/plugins/mod_presence.lua @@ -30,6 +30,14 @@ local recalc_resource_map = require "util.presence".recalc_resource_map; local ignore_presence_priority = module:get_option_boolean("ignore_presence_priority", false); +local pre_approval_stream_feature = st.stanza("sub", {xmlns="urn:xmpp:features:pre-approval"}); +module:hook("stream-features", function(event) + local origin, features = event.origin, event.features; + if origin.username then + features:add_child(pre_approval_stream_feature); + end +end); + function handle_normal_presence(origin, stanza) if ignore_presence_priority then local priority = stanza:get_child("priority"); -- cgit v1.2.3 From 8f5d92c15e9a26ca360454df6cab03f21c8eba39 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Thu, 19 Mar 2020 14:12:40 +0000 Subject: usermanager: Fix traceback when checking admin status of host-only JIDs (fixes #1508) --- core/usermanager.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/usermanager.lua b/core/usermanager.lua index aced0379..11707450 100644 --- a/core/usermanager.lua +++ b/core/usermanager.lua @@ -149,7 +149,7 @@ local function get_roles(jid, host) local authz_provider = (host ~= "*" and hosts[host].authz) or global_authz_provider; - if actor_host == host then -- Local user + if actor_user and actor_host == host then -- Local user roles = authz_provider.get_user_roles(actor_user); else -- Remote user/JID roles = authz_provider.get_jid_roles(jid); -- cgit v1.2.3 From 494774b629c17ccbe7e4575c3c4114a401d5cc8a Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 19 Mar 2020 17:43:08 +0100 Subject: doap: Add XEP-0317: Hats See 76bb806cdd4b --- doc/doap.xml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/doc/doap.xml b/doc/doap.xml index 20686e03..116682b0 100644 --- a/doc/doap.xml +++ b/doc/doap.xml @@ -474,6 +474,15 @@ mod_mam, mod_muc_mam + + + + 0.1 + planned + 0.12 + muc/hats + + -- cgit v1.2.3 From 96f172c685a3073f9ac29560bc95620afb365b47 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 20 Mar 2020 18:52:41 +0100 Subject: MUC: Add test for destroying a room by ad-hoc command Testing ad-hoc commands was not easily doable before 49312378ba1d relaxed the need for state and an extra roundtrip to execute commands --- spec/scansion/muc_create_destroy.scs | 67 ++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/spec/scansion/muc_create_destroy.scs b/spec/scansion/muc_create_destroy.scs index fea759a3..195200bb 100644 --- a/spec/scansion/muc_create_destroy.scs +++ b/spec/scansion/muc_create_destroy.scs @@ -8,6 +8,10 @@ jid: juliet@localhost/lVwkim_k password: password +[Client] Admin + jid: admin@localhost/DfNgg9VE + password: password + ----- Romeo connects @@ -245,6 +249,69 @@ Romeo receives: Juliet disconnects +Romeo sends: + + + + +Romeo receives: + + + + + + + + + + + +Romeo receives: + + + + +Romeo sends: + + + + + + +Romeo receives: + + +Admin connects + +Admin sends: + + + + + elsewhere@conference.localhost + + + + + +Romeo receives: + + + + + + + + Romeo disconnects +Admin receives: + + + The following rooms were destroyed: elsewhere@conference.localhost + + + +Admin disconnects + # recording ended on 2019-08-31T13:45:32Z -- cgit v1.2.3 From de1efbb3dfb4a103abbae1040261751ef7e4366a Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 21 Mar 2020 00:00:50 +0100 Subject: MUC: Add ad-hoc command setting affiliation in a room (fixes #1174) This gives service admins a way to set an arbitrary affiliation in any room. Enables various administrative use cases such as room ownership reassignment or recovery. Reduces the need for the admins-as-owners feature, as this can be used by admins to make themselves owner in any room when needed, instead of being owners all the time. --- plugins/muc/mod_muc.lua | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index 93b06f72..d911de08 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -499,6 +499,7 @@ do -- Ad-hoc commands local t_concat = table.concat; local adhoc_new = module:require "adhoc".new; local adhoc_initial = require "util.adhoc".new_initial_data_form; + local adhoc_simple = require "util.adhoc".new_simple_form; local array = require "util.array"; local dataforms_new = require "util.dataforms".new; @@ -529,4 +530,46 @@ do -- Ad-hoc commands "http://prosody.im/protocol/muc#destroy", destroy_rooms_handler, "admin"); module:provides("adhoc", destroy_rooms_desc); + + + local set_affiliation_layout = dataforms_new { + -- FIXME wordsmith title, instructions, labels etc + title = "Set affiliation"; + + { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/muc#set-affiliation" }; + { name = "room", type = "jid-single", required = true, label = "Room"}; + { name = "jid", type = "jid-single", required = true, label = "JID"}; + { name = "affiliation", type = "list-single", required = true, label = "Affiliation", + options = { "owner"; "admin"; "member"; "none"; "outcast"; }, + }; + { name = "reason", type = "text-single", "Reason", } + }; + + local set_affiliation_handler = adhoc_simple(set_affiliation_layout, function (fields, errors) + if errors then + local errmsg = {}; + for field, err in pairs(errors) do + errmsg[#errmsg + 1] = field .. ": " .. err; + end + return { status = "completed", error = { message = t_concat(errmsg, "\n") } }; + end + + local room = get_room_from_jid(fields.room); + if not room then + return { status = "canceled", error = { message = "No such room"; }; }; + end + local ok, err, condition = room:set_affiliation(true, fields.jid, fields.affiliation, fields.reason); + + if not ok then + return { status = "canceled", error = { message = "Affiliation change failed: "..err..":"..condition; }; }; + end + + return { status = "completed", info = "Affiliation updated", + }; + end); + + local set_affiliation_desc = adhoc_new("Set affiliation in room", + "http://prosody.im/protocol/muc#set-affiliation", set_affiliation_handler, "admin"); + + module:provides("adhoc", set_affiliation_desc); end -- cgit v1.2.3 From f1d66f402e2f4547aec796764fc926023f5b143e Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 29 Mar 2020 18:35:26 +0200 Subject: doap: Add UTR-39 (mod_mimicking) Supported in ICU, with a binding in util.encodings. mod_mimicking uses this to prevent similarity looking JIDs from being registered. Planned to be used in MUC as well. --- doc/doap.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/doap.xml b/doc/doap.xml index 116682b0..c23334ae 100644 --- a/doc/doap.xml +++ b/doc/doap.xml @@ -55,6 +55,7 @@ + -- cgit v1.2.3 From 7e181e31e0fdce18e0ce326f612edb2e9428fc13 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 22 Mar 2020 22:32:26 +0100 Subject: moduleapi: Fix handling of replies to :send_iq from internal modules Unclear exactly why, but replies to some queries to local modules would be discarded by stanza_router. This appears to fix it. --- core/moduleapi.lua | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/core/moduleapi.lua b/core/moduleapi.lua index 87c337d6..71239a0c 100644 --- a/core/moduleapi.lua +++ b/core/moduleapi.lua @@ -436,7 +436,16 @@ function api:send_iq(stanza, origin, timeout) return; end - self:send(stanza, origin); + local wrapped_origin = setmetatable({ + -- XXX Needed in some cases for replies to work correctly when sending queries internally. + send = function (stanza) + resolve({ stanza = stanza }); + end; + }, { + __index = origin or hosts[self.host]; + }); + + self:send(stanza, wrapped_origin); end); p:finally(function () -- cgit v1.2.3 From 38742f7b507df3687d7d068ff620611a84d874ca Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 1 Apr 2020 22:32:50 +0200 Subject: moduleapi: Rename argument to silence luacheck --- core/moduleapi.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/moduleapi.lua b/core/moduleapi.lua index 71239a0c..021db4c8 100644 --- a/core/moduleapi.lua +++ b/core/moduleapi.lua @@ -438,8 +438,8 @@ function api:send_iq(stanza, origin, timeout) local wrapped_origin = setmetatable({ -- XXX Needed in some cases for replies to work correctly when sending queries internally. - send = function (stanza) - resolve({ stanza = stanza }); + send = function (reply) + resolve({ stanza = reply }); end; }, { __index = origin or hosts[self.host]; -- cgit v1.2.3 From a4b069589cd555050a9a6e71b185e3a540f8c42d Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Fri, 10 Apr 2020 20:20:14 +0200 Subject: spec: Add test cases for util.http.contains_token --- spec/util_http_spec.lua | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/spec/util_http_spec.lua b/spec/util_http_spec.lua index d38645f7..c6087450 100644 --- a/spec/util_http_spec.lua +++ b/spec/util_http_spec.lua @@ -89,4 +89,23 @@ describe("util.http", function() assert.equal("/foo/", http.normalize_path("/foo/", true)); end); end); + + describe("contains_token", function () + it("is present in field", function () + assert.is_true(http.contains_token("foo", "foo")); + assert.is_true(http.contains_token("foo, bar", "foo")); + assert.is_true(http.contains_token("foo,bar", "foo")); + assert.is_true(http.contains_token("bar, foo,baz", "foo")); + end); + + it("is absent from field", function () + assert.is_false(http.contains_token("bar", "foo")); + assert.is_false(http.contains_token("fooo", "foo")); + assert.is_false(http.contains_token("foo o,bar", "foo")); + end); + + it("is weird", function () + assert.is_(http.contains_token("fo o", "foo")); + end); + end); end); -- cgit v1.2.3 From 007c8b7e64a93a79684b62d8ec888fe2fe1100e4 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Sat, 11 Apr 2020 16:41:52 +0100 Subject: MUC: Add new event 'muc-build-occupant-presence' for plugins to extend occupant presence --- plugins/muc/muc.lib.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 4f265cf8..3b126522 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -237,6 +237,7 @@ function room_mt:publicise_occupant_status(occupant, x, nick, actor, reason, pre occupant = occupant; nick = nick; actor = actor; reason = reason; } + module:fire_event("muc-build-occupant-presence", event); module:fire_event("muc-broadcast-presence", event); -- Allow muc-broadcast-presence listeners to change things @@ -342,6 +343,7 @@ function room_mt:send_occupant_list(to, filter) local pres = st.clone(occupant:get_presence()); pres.attr.to = to; pres:add_child(x); + module:fire_event("muc-build-occupant-presence", { room = self, occupant = occupant, stanza = pres }); if to_bare == occupant.bare_jid or broadcast_roles[occupant.role or "none"] then self:route_stanza(pres); end -- cgit v1.2.3 From c439358e74c1c970efba55a317a7e6b65d0c38af Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Sat, 11 Apr 2020 16:43:57 +0100 Subject: MUC: Add API for adding 'filtered namespaces' to be stripped from inbound presence --- plugins/muc/util.lib.lua | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/plugins/muc/util.lib.lua b/plugins/muc/util.lib.lua index 53a83fae..0877150f 100644 --- a/plugins/muc/util.lib.lua +++ b/plugins/muc/util.lib.lua @@ -41,18 +41,22 @@ function _M.is_kickable_error(stanza) return kickable_error_conditions[cond]; end -local muc_x_filters = { - ["http://jabber.org/protocol/muc"] = true; - ["http://jabber.org/protocol/muc#user"] = true; -} -local function muc_x_filter(tag) - if muc_x_filters[tag.attr.xmlns] then +local filtered_namespaces = module:shared("filtered-namespaces"); +filtered_namespaces["http://jabber.org/protocol/muc"] = true; +filtered_namespaces["http://jabber.org/protocol/muc#user"] = true; + +local function muc_ns_filter(tag) + if filtered_namespaces[tag.attr.xmlns] then return nil; end return tag; end function _M.filter_muc_x(stanza) - return stanza:maptags(muc_x_filter); + return stanza:maptags(muc_ns_filter); +end + +function _M.add_filtered_namespace(xmlns) + filtered_namespaces[xmlns] = true; end function _M.only_with_min_role(role) -- cgit v1.2.3 From 9a93e48a90837674d400f915722a9335751e7d07 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Sat, 11 Apr 2020 16:45:27 +0100 Subject: MUC: Switch hats to new presence APIs --- plugins/muc/hats.lib.lua | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/plugins/muc/hats.lib.lua b/plugins/muc/hats.lib.lua index 5b0429b5..77051af2 100644 --- a/plugins/muc/hats.lib.lua +++ b/plugins/muc/hats.lib.lua @@ -1,11 +1,12 @@ local st = require "util.stanza"; +local muc_util = module:require "muc/util"; local xmlns_hats = "xmpp:prosody.im/protocol/hats:1"; -module:hook("muc-broadcast-presence", function (event) - -- Strip any hats claimed by the client (to prevent spoofing) - event.stanza:remove_children("hats", xmlns_hats); +-- Strip any hats claimed by the client (to prevent spoofing) +muc_util.add_filtered_namespace(xmlns_hats); +module:hook("muc-build-occupant-presence", function (event) local aff_data = event.room:get_affiliation_data(event.occupant.bare_jid); local hats = aff_data and aff_data.hats; if not hats then return; end -- cgit v1.2.3 From 1f92aa928062b406778e78ab9c1030b8d6be5bed Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 11 Apr 2020 17:59:39 +0200 Subject: spec: Include a hacky moduleapi stub to allow test to proceed --- spec/muc_util_spec.lua | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/spec/muc_util_spec.lua b/spec/muc_util_spec.lua index cef68e80..3b2da4d0 100644 --- a/spec/muc_util_spec.lua +++ b/spec/muc_util_spec.lua @@ -3,11 +3,23 @@ local muc_util; local st = require "util.stanza"; do - local old_pp = package.path; - package.path = "./?.lib.lua;"..package.path; - muc_util = require "plugins.muc.util"; - package.path = old_pp; -end + -- XXX Hack for lack of a mock moduleapi + local env = setmetatable({ + module = { + _shared = {}; + -- Close enough to the real module:shared() for our purposes here + shared = function (self, name) + local t = self._shared[name]; + if t == nil then + t = {}; + self._shared[name] = t; + end + return t; + end; + } + }, { __index = _ENV or _G }); + muc_util = require "util.envload".envloadfile("plugins/muc/util.lib.lua", env)(); + end describe("muc/util", function () describe("filter_muc_x()", function () -- cgit v1.2.3 From 608d6dd75669b3b8bcc526b91dad865ae89af79a Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 11 Apr 2020 19:31:15 +0200 Subject: mod_component: Specify an error source for Component unavailable errors It is somewhat ambiguous where an error really comes from in the case of an external component. Setting by to the bare host at least distinguishes it from JIDs with a node- or resourcepart. --- plugins/mod_component.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_component.lua b/plugins/mod_component.lua index afcfc68c..9ffc496e 100644 --- a/plugins/mod_component.lua +++ b/plugins/mod_component.lua @@ -132,7 +132,7 @@ function module.add_host(module) end module:log("warn", "Component not connected, bouncing error for: %s", stanza:top_tag()); if stanza.attr.type ~= "error" and stanza.attr.type ~= "result" then - event.origin.send(st.error_reply(stanza, "wait", "service-unavailable", "Component unavailable")); + event.origin.send(st.error_reply(stanza, "wait", "service-unavailable", "Component unavailable", module.host)); end end return true; -- cgit v1.2.3 From f0ac29acf0d1d6cd947ac2e20dc6bcbbbddf76f6 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 12 Apr 2020 17:03:05 +0200 Subject: util.stanza: Add method returning stanza with added indentation Adds indentation and line breaks to stanzas, to make stanzas easier to read for humans. --- spec/util_stanza_spec.lua | 8 ++++++++ util/stanza.lua | 30 ++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/spec/util_stanza_spec.lua b/spec/util_stanza_spec.lua index d38a609f..efe3e47e 100644 --- a/spec/util_stanza_spec.lua +++ b/spec/util_stanza_spec.lua @@ -459,4 +459,12 @@ describe("util.stanza", function() assert.equal("true", s2.attr["my-awesome-ns\1bar"]); end); end); + + describe("indent", function () + local s = st.stanza("foo"):text("\n"):tag("bar"):tag("baz"):up():text_tag("cow", "moo"); + assert.equal("\n\t\n\t\t\n\t\tmoo\n\t\n", tostring(s:indent())); + assert.equal("\n \n \n moo\n \n", tostring(s:indent(1, " "))); + assert.equal("\n\t\t\n\t\t\t\n\t\t\tmoo\n\t\t\n\t", tostring(s:indent(2, "\t"))); + end); + end); diff --git a/util/stanza.lua b/util/stanza.lua index f5cd5668..a8a417ab 100644 --- a/util/stanza.lua +++ b/util/stanza.lua @@ -511,6 +511,36 @@ else stanza_mt.pretty_top_tag = stanza_mt.top_tag; end +function stanza_mt.indent(t, level, indent) + if #t == 0 or (#t == 1 and type(t[1]) == "string") then + -- Empty nodes wouldn't have any indentation + -- Text-only nodes are preserved as to not alter the text content + -- Optimization: Skip clone of these since we don't alter them + return t; + end + + indent = indent or "\t"; + level = level or 1; + local tag = clone(t, true); + + for child in t:children() do + if type(child) == "string" then + -- Already indented text would look weird but let's ignore that for now. + if child:find("%S") then + tag:text("\n" .. indent:rep(level)); + tag:text(child); + end + elseif is_stanza(child) then + tag:text("\n" .. indent:rep(level)); + tag:add_direct_child(child:indent(level+1, indent)); + end + end + -- before the closing tag + tag:text("\n" .. indent:rep((level-1))); + + return tag; +end + return { stanza_mt = stanza_mt; stanza = new_stanza; -- cgit v1.2.3 From 6c597cc8d9ba1f6f98cd7f6e4dff697c73cd652c Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 12 Apr 2020 17:30:16 +0200 Subject: mod_scansion_record: Indent stanzas in recordings Improves readability, easier to see structure. --- plugins/mod_scansion_record.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/mod_scansion_record.lua b/plugins/mod_scansion_record.lua index 8d772b4e..0e109a0d 100644 --- a/plugins/mod_scansion_record.lua +++ b/plugins/mod_scansion_record.lua @@ -37,8 +37,7 @@ local function record_event(session, event) end local function record_stanza(stanza, session, verb) - local flattened = tostring(stanza):gsub("><", ">\n\t<"); - -- TODO Proper prettyprinting with indentation + local flattened = tostring(stanza:indent(2, "\t")); record(session.scansion_id.." "..verb..":\n\t"..flattened.."\n\n"); end -- cgit v1.2.3 From 4f2548e8efbb5659ed723cb4e802ef66328ada29 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 12 Apr 2020 22:57:14 +0200 Subject: net.http.server: Use error code from util.error (fixes #1502) Oversight in 955e54e451dc when this was added. --- net/http/server.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/net/http/server.lua b/net/http/server.lua index f4f67d18..c59479cd 100644 --- a/net/http/server.lua +++ b/net/http/server.lua @@ -191,6 +191,7 @@ local function handle_result(request, response, result) elseif result_type == "string" then body = result; elseif errors.is_err(result) then + response.status_code = result.code or 500; body = events.fire_event("http-error", { request = request, response = response, code = result.code or 500, error = result }); elseif promise.is_promise(result) then result:next(function (ret) -- cgit v1.2.3 From 44ed7a0e21e7c4a3380cb97be671f12a4b40f8d5 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 14 Apr 2020 16:51:24 +0200 Subject: util.sasl.digest-md5: Remove, obsolete since 2011 RFC 6331 lists several problems with this outdated authentication mechanism. The code here was also completely ignored by lint checks and has probably not been used for a long time, as it is incompatible with SCRAM-hashed password storage. --- .luacheckrc | 2 - CHANGES | 1 + util/sasl.lua | 1 - util/sasl/digest-md5.lua | 251 ----------------------------------------------- 4 files changed, 1 insertion(+), 254 deletions(-) delete mode 100644 util/sasl/digest-md5.lua diff --git a/.luacheckrc b/.luacheckrc index 6e8481de..bb4568e6 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -160,8 +160,6 @@ if os.getenv("PROSODY_STRICT_LINT") ~= "1" then "tools/migration/prosody-migrator.lua"; "tools/openfire2prosody.lua"; "tools/xep227toprosody.lua"; - - "util/sasl/digest-md5.lua"; } for _, file in ipairs(exclude_files) do files[file] = { only = {} } diff --git a/CHANGES b/CHANGES index 29c4cf86..0aa157a0 100644 --- a/CHANGES +++ b/CHANGES @@ -15,6 +15,7 @@ TRUNK - MUC presence broadcast controls - ALPN support in mod\_net\_multiplex - `daemonize` option deprecated +- SASL DIGEST-MD5 removed 0.11.0 ====== diff --git a/util/sasl.lua b/util/sasl.lua index 50851405..fc2abdf3 100644 --- a/util/sasl.lua +++ b/util/sasl.lua @@ -134,7 +134,6 @@ end -- load the mechanisms require "util.sasl.plain" .init(registerMechanism); -require "util.sasl.digest-md5".init(registerMechanism); require "util.sasl.anonymous" .init(registerMechanism); require "util.sasl.scram" .init(registerMechanism); require "util.sasl.external" .init(registerMechanism); diff --git a/util/sasl/digest-md5.lua b/util/sasl/digest-md5.lua deleted file mode 100644 index 7542a037..00000000 --- a/util/sasl/digest-md5.lua +++ /dev/null @@ -1,251 +0,0 @@ --- sasl.lua v0.4 --- Copyright (C) 2008-2010 Tobias Markmann --- --- All rights reserved. --- --- Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: --- --- * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. --- * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. --- * Neither the name of Tobias Markmann nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. --- --- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -local tostring = tostring; -local type = type; - -local s_gmatch = string.gmatch; -local s_match = string.match; -local t_concat = table.concat; -local t_insert = table.insert; -local to_byte, to_char = string.byte, string.char; - -local md5 = require "util.hashes".md5; -local log = require "util.logger".init("sasl"); -local generate_uuid = require "util.uuid".generate; -local nodeprep = require "util.encodings".stringprep.nodeprep; - -local _ENV = nil; --- luacheck: std none - ---========================= ---SASL DIGEST-MD5 according to RFC 2831 - ---[[ -Supported Authentication Backends - -digest_md5: - function(username, domain, realm, encoding) -- domain and realm are usually the same; for some broken - -- implementations it's not - return digesthash, state; - end - -digest_md5_test: - function(username, domain, realm, encoding, digesthash) - return true or false, state; - end -]] - -local function digest(self, message) - --TODO complete support for authzid - - local function serialize(message) - local data = "" - - -- testing all possible values - if message["realm"] then data = data..[[realm="]]..message.realm..[[",]] end - if message["nonce"] then data = data..[[nonce="]]..message.nonce..[[",]] end - if message["qop"] then data = data..[[qop="]]..message.qop..[[",]] end - if message["charset"] then data = data..[[charset=]]..message.charset.."," end - if message["algorithm"] then data = data..[[algorithm=]]..message.algorithm.."," end - if message["rspauth"] then data = data..[[rspauth=]]..message.rspauth.."," end - data = data:gsub(",$", "") - return data - end - - local function utf8tolatin1ifpossible(passwd) - local i = 1; - while i <= #passwd do - local passwd_i = to_byte(passwd:sub(i, i)); - if passwd_i > 0x7F then - if passwd_i < 0xC0 or passwd_i > 0xC3 then - return passwd; - end - i = i + 1; - passwd_i = to_byte(passwd:sub(i, i)); - if passwd_i < 0x80 or passwd_i > 0xBF then - return passwd; - end - end - i = i + 1; - end - - local p = {}; - local j = 0; - i = 1; - while (i <= #passwd) do - local passwd_i = to_byte(passwd:sub(i, i)); - if passwd_i > 0x7F then - i = i + 1; - local passwd_i_1 = to_byte(passwd:sub(i, i)); - t_insert(p, to_char(passwd_i%4*64 + passwd_i_1%64)); -- I'm so clever - else - t_insert(p, to_char(passwd_i)); - end - i = i + 1; - end - return t_concat(p); - end - local function latin1toutf8(str) - local p = {}; - for ch in s_gmatch(str, ".") do - ch = to_byte(ch); - if (ch < 0x80) then - t_insert(p, to_char(ch)); - elseif (ch < 0xC0) then - t_insert(p, to_char(0xC2, ch)); - else - t_insert(p, to_char(0xC3, ch - 64)); - end - end - return t_concat(p); - end - local function parse(data) - local message = {} - -- COMPAT: %z in the pattern to work around jwchat bug (sends "charset=utf-8\0") - for k, v in s_gmatch(data, [[([%w%-]+)="?([^",%z]*)"?,?]]) do -- FIXME The hacky regex makes me shudder - message[k] = v; - end - return message; - end - - if not self.nonce then - self.nonce = generate_uuid(); - self.step = 0; - self.nonce_count = {}; - end - - self.step = self.step + 1; - if (self.step == 1) then - local challenge = serialize({ nonce = self.nonce, - qop = "auth", - charset = "utf-8", - algorithm = "md5-sess", - realm = self.realm}); - return "challenge", challenge; - elseif (self.step == 2) then - local response = parse(message); - -- check for replay attack - if response["nc"] then - if self.nonce_count[response["nc"]] then return "failure", "not-authorized" end - end - - -- check for username, it's REQUIRED by RFC 2831 - local username = response["username"]; - local _nodeprep = self.profile.nodeprep; - if username and _nodeprep ~= false then - username = (_nodeprep or nodeprep)(username); -- FIXME charset - end - if not username or username == "" then - return "failure", "malformed-request"; - end - self.username = username; - - -- check for nonce, ... - if not response["nonce"] then - return "failure", "malformed-request"; - else - -- check if it's the right nonce - if response["nonce"] ~= tostring(self.nonce) then return "failure", "malformed-request" end - end - - if not response["cnonce"] then return "failure", "malformed-request", "Missing entry for cnonce in SASL message." end - if not response["qop"] then response["qop"] = "auth" end - - if response["realm"] == nil or response["realm"] == "" then - response["realm"] = ""; - elseif response["realm"] ~= self.realm then - return "failure", "not-authorized", "Incorrect realm value"; - end - - local decoder; - if response["charset"] == nil then - decoder = utf8tolatin1ifpossible; - elseif response["charset"] ~= "utf-8" then - return "failure", "incorrect-encoding", "The client's response uses "..response["charset"].." for encoding with isn't supported by sasl.lua. Supported encodings are latin or utf-8."; - end - - local domain = ""; - local protocol = ""; - if response["digest-uri"] then - protocol, domain = response["digest-uri"]:match("(%w+)/(.*)$"); - if protocol == nil or domain == nil then return "failure", "malformed-request" end - else - return "failure", "malformed-request", "Missing entry for digest-uri in SASL message." - end - - --TODO maybe realm support - local Y, state; - if self.profile.plain then - local password, state = self.profile.plain(self, response["username"], self.realm) - if state == nil then return "failure", "not-authorized" - elseif state == false then return "failure", "account-disabled" end - Y = md5(response["username"]..":"..response["realm"]..":"..password); - elseif self.profile["digest-md5"] then - Y, state = self.profile["digest-md5"](self, response["username"], self.realm, response["realm"], response["charset"]) - if state == nil then return "failure", "not-authorized" - elseif state == false then return "failure", "account-disabled" end - elseif self.profile["digest-md5-test"] then - -- TODO - end - --local password_encoding, Y = self.credentials_handler("DIGEST-MD5", response["username"], self.realm, response["realm"], decoder); - --if Y == nil then return "failure", "not-authorized" - --elseif Y == false then return "failure", "account-disabled" end - local A1 = ""; - if response.authzid then - if response.authzid == self.username or response.authzid == self.username.."@"..self.realm then - -- COMPAT - log("warn", "Client is violating RFC 3920 (section 6.1, point 7)."); - A1 = Y..":"..response["nonce"]..":"..response["cnonce"]..":"..response.authzid; - else - return "failure", "invalid-authzid"; - end - else - A1 = Y..":"..response["nonce"]..":"..response["cnonce"]; - end - local A2 = "AUTHENTICATE:"..protocol.."/"..domain; - - local HA1 = md5(A1, true); - local HA2 = md5(A2, true); - - local KD = HA1..":"..response["nonce"]..":"..response["nc"]..":"..response["cnonce"]..":"..response["qop"]..":"..HA2; - local response_value = md5(KD, true); - - if response_value == response["response"] then - -- calculate rspauth - A2 = ":"..protocol.."/"..domain; - - HA1 = md5(A1, true); - HA2 = md5(A2, true); - - KD = HA1..":"..response["nonce"]..":"..response["nc"]..":"..response["cnonce"]..":"..response["qop"]..":"..HA2 - local rspauth = md5(KD, true); - self.authenticated = true; - --TODO: considering sending the rspauth in a success node for saving one roundtrip; allowed according to http://tools.ietf.org/html/draft-saintandre-rfc3920bis-09#section-7.3.6 - return "challenge", serialize({rspauth = rspauth}); - else - return "failure", "not-authorized", "The response provided by the client doesn't match the one we calculated." - end - elseif self.step == 3 then - if self.authenticated ~= nil then return "success" - else return "failure", "malformed-request" end - end -end - -local function init(registerMechanism) - registerMechanism("DIGEST-MD5", {"plain"}, digest); -end - -return { - init = init; -} -- cgit v1.2.3 From 4b33a7aac8b77777c3a59e94b2f82d365e421877 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 18 Apr 2020 16:18:41 +0200 Subject: mod_csi_simple: Allow configuring extra tags indicating importance --- plugins/mod_csi_simple.lua | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/plugins/mod_csi_simple.lua b/plugins/mod_csi_simple.lua index 4a87f06c..e439bf38 100644 --- a/plugins/mod_csi_simple.lua +++ b/plugins/mod_csi_simple.lua @@ -1,4 +1,4 @@ --- Copyright (C) 2016-2018 Kim Alvefur +-- Copyright (C) 2016-2020 Kim Alvefur -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. @@ -13,6 +13,8 @@ local filters = require "util.filters"; local queue_size = module:get_option_number("csi_queue_size", 256); +local important_payloads = module:get_option_set("csi_important_payloads", { }); + module:hook("csi-is-stanza-important", function (event) local stanza = event.stanza; if not st.is_stanza(stanza) then @@ -46,6 +48,11 @@ module:hook("csi-is-stanza-important", function (event) if stanza:get_child("encryption", "urn:xmpp:eme:0") then return true; end + for important in important_payloads do + if stanza:find(important) then + return true; + end + end return false; end return true; -- cgit v1.2.3 From 9036462dcbb26ca029ee4d7709f4d8d2a377734c Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 20 Apr 2020 18:17:57 +0200 Subject: mod_version: Add scansion test Why was this module enabled in the config for tests if it wasn't tested? --- spec/scansion/prosody.cfg.lua | 2 ++ spec/scansion/version.scs | 27 +++++++++++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 spec/scansion/version.scs diff --git a/spec/scansion/prosody.cfg.lua b/spec/scansion/prosody.cfg.lua index 6a72584d..d7444e7a 100644 --- a/spec/scansion/prosody.cfg.lua +++ b/spec/scansion/prosody.cfg.lua @@ -84,6 +84,8 @@ pidfile = "prosody.pid" VirtualHost "localhost" +hide_os_type = true -- absense tested for in version.scs + Component "conference.localhost" "muc" storage = "memory" diff --git a/spec/scansion/version.scs b/spec/scansion/version.scs new file mode 100644 index 00000000..6c841dd9 --- /dev/null +++ b/spec/scansion/version.scs @@ -0,0 +1,27 @@ +# XEP-0092: Software Version / mod_version + +[Client] Romeo + password: password + jid: romeo@localhost/dfaZpuxV + +----- + +Romeo connects + +Romeo sends: + + + + +# Version string would vary so we can't do an exact match atm +# Inclusion of is disabled in the config, it should be absent +Romeo receives: + + + Prosody + + + + + +Romeo disconnects -- cgit v1.2.3 From 5955cc392426300b07c9cbfdb80371a14de6b729 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 20 Apr 2020 18:20:24 +0200 Subject: scansion: Mock time libraries during tests The passage of time does not need test coverage, just look in a mirror. --- spec/scansion/prosody.cfg.lua | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/spec/scansion/prosody.cfg.lua b/spec/scansion/prosody.cfg.lua index d7444e7a..88a5e0c9 100644 --- a/spec/scansion/prosody.cfg.lua +++ b/spec/scansion/prosody.cfg.lua @@ -1,5 +1,16 @@ --luacheck: ignore +-- Mock time functions to simplify tests +function _G.os.time() + return 1219439344; +end +package.preload["util.time"] = function () + return { + now = function () return 1219439344.1; end; + monotonic = function () return 0.1; end; + } +end + admins = { "admin@localhost" } use_libevent = true -- cgit v1.2.3 From 85b09097dc5367e4223595c7debbb92a401cfd12 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 20 Apr 2020 19:22:54 +0200 Subject: mod_uptime: Add scansion test coverage Once the Prosody is up, who cares when it comes down? That's not my department, says scanison. --- spec/scansion/uptime.scs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 spec/scansion/uptime.scs diff --git a/spec/scansion/uptime.scs b/spec/scansion/uptime.scs new file mode 100644 index 00000000..188b9eb5 --- /dev/null +++ b/spec/scansion/uptime.scs @@ -0,0 +1,21 @@ +# XEP-0012: Last Activity / mod_uptime + +[Client] Romeo + jid: romeo@localhost + password: password + +----- + +Romeo connects + +Romeo sends: + + + + +Romeo receives: + + + + +Romeo disconnects -- cgit v1.2.3 From 8be4a3edd7de37c5c06beb3479ec6c84b510f95a Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 20 Apr 2020 18:33:05 +0200 Subject: mod_lastactivity: Add basic scansion test coverage When run on Lua 5.3 produces an issue similar to #1536 --- spec/scansion/lastactivity.scs | 45 ++++++++++++++++++++++++++++++++++++++++++ spec/scansion/prosody.cfg.lua | 1 + 2 files changed, 46 insertions(+) create mode 100644 spec/scansion/lastactivity.scs diff --git a/spec/scansion/lastactivity.scs b/spec/scansion/lastactivity.scs new file mode 100644 index 00000000..44f4e516 --- /dev/null +++ b/spec/scansion/lastactivity.scs @@ -0,0 +1,45 @@ +# XEP-0012: Last Activity / mod_lastactivity + +[Client] Romeo + jid: romeo@localhost + password: password + +----- + +Romeo connects + +Romeo sends: + + Hello + + +Romeo receives: + + Hello + + +Romeo sends: + + Goodbye + + +Romeo receives: + + Goodbye + + +# mod_lastlog saves time + status message from the last unavailable presence + +Romeo sends: + + + + +Romeo receives: + + Goodbye + + +Romeo disconnects + +# recording ended on 2020-04-20T14:39:47Z diff --git a/spec/scansion/prosody.cfg.lua b/spec/scansion/prosody.cfg.lua index 88a5e0c9..b7d6ccd5 100644 --- a/spec/scansion/prosody.cfg.lua +++ b/spec/scansion/prosody.cfg.lua @@ -59,6 +59,7 @@ modules_enabled = { --"motd"; -- Send a message to users when they log in --"legacyauth"; -- Legacy authentication. Only used by some old clients and bots. --"proxy65"; -- Enables a file transfer proxy service which clients behind NAT can use + "lastactivity"; -- Useful for testing --"scansion_record"; -- Records things that happen in scansion test case format -- cgit v1.2.3 From 8f77033238b4cf72377ae0b45d5951c32324892d Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 18 Apr 2020 19:36:26 +0200 Subject: mod_csi_simple: Consider MUC invites important Both mediated invites defined by XEP-0045: Multi-User Chat and direct invites defined by XEP-0249: Direct MUC Invitations --- doc/doap.xml | 8 ++++++++ plugins/mod_csi_simple.lua | 3 +++ 2 files changed, 11 insertions(+) diff --git a/doc/doap.xml b/doc/doap.xml index c23334ae..540cbe51 100644 --- a/doc/doap.xml +++ b/doc/doap.xml @@ -429,6 +429,14 @@ implied by rfc6121 + + + + 1.2 + 0.12 + mod_csi_simple + + diff --git a/plugins/mod_csi_simple.lua b/plugins/mod_csi_simple.lua index e439bf38..cc15f033 100644 --- a/plugins/mod_csi_simple.lua +++ b/plugins/mod_csi_simple.lua @@ -48,6 +48,9 @@ module:hook("csi-is-stanza-important", function (event) if stanza:get_child("encryption", "urn:xmpp:eme:0") then return true; end + if stanza:get_child("x", "jabber:x:conference") or stanza:find("{http://jabber.org/protocol/muc#user}x/invite") then + return true; + end for important in important_payloads do if stanza:find(important) then return true; -- cgit v1.2.3 From 8dceed3e9e3dda2a11e16ab8cf200d97561857d2 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 19 Apr 2020 01:09:21 +0200 Subject: migrator: Inject data- and plugin paths during build Same way as with the prosody and prosodyctl executables --- tools/migration/Makefile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/migration/Makefile b/tools/migration/Makefile index dc14e0da..4d916a9f 100644 --- a/tools/migration/Makefile +++ b/tools/migration/Makefile @@ -28,7 +28,9 @@ clean: prosody-migrator.install: prosody-migrator.lua sed "1s/\blua\b/$(RUNWITH)/; \ s|^CFG_SOURCEDIR=.*;$$|CFG_SOURCEDIR='$(INSTALLEDSOURCE)';|; \ - s|^CFG_CONFIGDIR=.*;$$|CFG_CONFIGDIR='$(INSTALLEDCONFIG)';|;" \ + s|^CFG_CONFIGDIR=.*;$$|CFG_CONFIGDIR='$(INSTALLEDCONFIG)';|; \ + s|^CFG_DATADIR=.*;$$|CFG_DATADIR='$(INSTALLEDDATA)';|; \ + s|^CFG_PLUGINDIR=.*;$$|CFG_PLUGINDIR='$(INSTALLEDMODULES)/';|;" \ < prosody-migrator.lua > prosody-migrator.install migrator.cfg.lua.install: migrator.cfg.lua -- cgit v1.2.3 From 25685a3d1bf426a1f93fffaaf3d847b7e68bc9e9 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 19 Apr 2020 01:17:48 +0200 Subject: migrator: Don't create unused directory I guess this contained the old per-store migrators --- tools/migration/Makefile | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/migration/Makefile b/tools/migration/Makefile index 4d916a9f..fce0f7d9 100644 --- a/tools/migration/Makefile +++ b/tools/migration/Makefile @@ -17,7 +17,6 @@ all: prosody-migrator.install migrator.cfg.lua.install prosody-migrator.lua install: prosody-migrator.install migrator.cfg.lua.install install -d $(BIN) $(CONFIG) $(SOURCE) install -d $(MAN)/man1 - install -d $(SOURCE)/migrator install -m755 ./prosody-migrator.install $(BIN)/prosody-migrator test -e $(CONFIG)/migrator.cfg.lua || install -m644 migrator.cfg.lua.install $(CONFIG)/migrator.cfg.lua -- cgit v1.2.3 From a63e5be1b71a74d78c5d95288c759d70d971f729 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 19 Apr 2020 13:04:12 +0200 Subject: mod_c2s: Swap comments --- plugins/mod_c2s.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/mod_c2s.lua b/plugins/mod_c2s.lua index aecf2210..536b945e 100644 --- a/plugins/mod_c2s.lua +++ b/plugins/mod_c2s.lua @@ -111,10 +111,10 @@ function stream_callbacks.streamopened(session, attr) send(features); else if session.secure then - -- Normally STARTTLS would be offered + -- Here SASL should be offered (session.log or log)("warn", "No stream features to offer on secure session. Check authentication settings."); else - -- Here SASL should be offered + -- Normally STARTTLS would be offered (session.log or log)("warn", "No stream features to offer on insecure session. Check encryption and security settings."); end session:close{ condition = "undefined-condition", text = "No stream features to proceed with" }; -- cgit v1.2.3 From ff9bdefc7e961f217580194146ee6bda8c6b9827 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 20 Apr 2020 11:30:59 +0100 Subject: mod_bosh, mod_websocket: Add config options to override GET responses --- plugins/mod_bosh.lua | 10 ++++++---- plugins/mod_websocket.lua | 10 +++++++--- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/plugins/mod_bosh.lua b/plugins/mod_bosh.lua index b45a9dc2..d4c1ac6a 100644 --- a/plugins/mod_bosh.lua +++ b/plugins/mod_bosh.lua @@ -511,14 +511,16 @@ function stream_callbacks.error(context, error) end end +local GET_response_body = [[ +

It works! Now point your BOSH client to this URL to connect to Prosody.

+

For more information see Prosody: Setting up BOSH.

+ ]]; + local GET_response = { headers = { content_type = "text/html"; }; - body = [[ -

It works! Now point your BOSH client to this URL to connect to Prosody.

-

For more information see Prosody: Setting up BOSH.

- ]]; + body = module:get_option_string("bosh_get_response_body", GET_response_body); }; module:depends("http"); diff --git a/plugins/mod_websocket.lua b/plugins/mod_websocket.lua index 4d3e79bb..1a0c0046 100644 --- a/plugins/mod_websocket.lua +++ b/plugins/mod_websocket.lua @@ -130,6 +130,12 @@ local function filter_open_close(data) return data; end + +local default_get_response_body = [[Websocket +

It works! Now point your WebSocket client to this URL to connect to Prosody.

+]] +local websocket_get_response_body = module:get_option_string("websocket_get_response_body", default_get_response_body) + function handle_request(event) local request, response = event.request, event.response; local conn = response.conn; @@ -138,9 +144,7 @@ function handle_request(event) if not request.headers.sec_websocket_key or request.method ~= "GET" then response.headers.content_type = "text/html"; - return [[Websocket -

It works! Now point your WebSocket client to this URL to connect to Prosody.

- ]]; + return websocket_get_response_body; end local wants_xmpp = contains_token(request.headers.sec_websocket_protocol or "", "xmpp"); -- cgit v1.2.3 From 5817e5f02d048aa6c17d5430d55f11c4a7a40a96 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 21 Apr 2020 00:53:21 +0200 Subject: mod_mam: Factor out "should we store this" into a function Meant to improve readability and ease further improvements to this algorithm. --- plugins/mod_mam/mod_mam.lua | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/plugins/mod_mam/mod_mam.lua b/plugins/mod_mam/mod_mam.lua index 8229eb4e..27b4796d 100644 --- a/plugins/mod_mam/mod_mam.lua +++ b/plugins/mod_mam/mod_mam.lua @@ -263,11 +263,28 @@ local function strip_stanza_id(stanza, user) return stanza; end +local function should_store(stanza) --> boolean, reason: string + local orig_type = stanza.attr.type or "normal"; + -- We store chat messages or normal messages that have a body + if not(orig_type == "chat" or (orig_type == "normal" and stanza:get_child("body")) ) then + return false, "type"; + end + + -- or if hints suggest we shouldn't + if not stanza:get_child("store", "urn:xmpp:hints") then -- No hint telling us we should store + if stanza:get_child("no-permanent-store", "urn:xmpp:hints") + or stanza:get_child("no-store", "urn:xmpp:hints") then -- Hint telling us we should NOT store + return false, "hint"; + end + end + + return true, "default"; +end + -- Handle messages local function message_handler(event, c2s) local origin, stanza = event.origin, event.stanza; local log = c2s and origin.log or module._log; - local orig_type = stanza.attr.type or "normal"; local orig_from = stanza.attr.from; local orig_to = stanza.attr.to or orig_from; -- Stanza without 'to' are treated as if it was to their own bare jid @@ -280,21 +297,12 @@ local function message_handler(event, c2s) -- Filter out that claim to be from us event.stanza = strip_stanza_id(stanza, store_user); - -- We store chat messages or normal messages that have a body - if not(orig_type == "chat" or (orig_type == "normal" and stanza:get_child("body")) ) then - log("debug", "Not archiving stanza: %s (type)", stanza:top_tag()); + local should, why = should_store(stanza); + if not should then + log("debug", "Not archiving stanza: %s (%s)", stanza:top_tag(), why); return; end - -- or if hints suggest we shouldn't - if not stanza:get_child("store", "urn:xmpp:hints") then -- No hint telling us we should store - if stanza:get_child("no-permanent-store", "urn:xmpp:hints") - or stanza:get_child("no-store", "urn:xmpp:hints") then -- Hint telling us we should NOT store - log("debug", "Not archiving stanza: %s (hint)", stanza:top_tag()); - return; - end - end - local clone_for_storage; if not strip_tags:empty() then clone_for_storage = st.clone(stanza); -- cgit v1.2.3 From 962d36d401a1a55346a92fe3b30c6b215583fada Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 21 Apr 2020 00:53:23 +0200 Subject: mod_mam: Log 'why' a stanza is archived Logging of 'why not' is already done. Why not both? Will make more sense when the rules evolve a bit. --- plugins/mod_mam/mod_mam.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_mam/mod_mam.lua b/plugins/mod_mam/mod_mam.lua index 27b4796d..9c00cc99 100644 --- a/plugins/mod_mam/mod_mam.lua +++ b/plugins/mod_mam/mod_mam.lua @@ -323,7 +323,7 @@ local function message_handler(event, c2s) -- Check with the users preferences if shall_store(store_user, with) then - log("debug", "Archiving stanza: %s", stanza:top_tag()); + log("debug", "Archiving stanza: %s (%s)", stanza:top_tag(), why); -- And stash it local time = time_now(); -- cgit v1.2.3 From e5d6376c58d026003fbcee2dff60a7b244debbdb Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 21 Apr 2020 00:56:56 +0200 Subject: mod_mam: Invert check for type This is based on code in mod_csi_simple and aiming towards being more flexible and maintainable than a couple of tests for when not to store. --- plugins/mod_mam/mod_mam.lua | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/plugins/mod_mam/mod_mam.lua b/plugins/mod_mam/mod_mam.lua index 9c00cc99..028c3b8f 100644 --- a/plugins/mod_mam/mod_mam.lua +++ b/plugins/mod_mam/mod_mam.lua @@ -264,11 +264,8 @@ local function strip_stanza_id(stanza, user) end local function should_store(stanza) --> boolean, reason: string - local orig_type = stanza.attr.type or "normal"; - -- We store chat messages or normal messages that have a body - if not(orig_type == "chat" or (orig_type == "normal" and stanza:get_child("body")) ) then - return false, "type"; - end + local st_type = stanza.attr.type or "normal"; + local st_to_full = (stanza.attr.to or ""):find("/"); -- or if hints suggest we shouldn't if not stanza:get_child("store", "urn:xmpp:hints") then -- No hint telling us we should store @@ -277,6 +274,17 @@ local function should_store(stanza) --> boolean, reason: string return false, "hint"; end end + if st_type == "headline" then + -- Headline messages are ephemeral by definition + return false, "headline"; + end + if st_type == "groupchat" and st_to_full then + -- MUC messages always go to the full JID, usually archived by the MUC + return false, "groupchat"; + end + if stanza:get_child("body") then + return true, "body"; + end return true, "default"; end -- cgit v1.2.3 From 0613c5b47f429e34ca3cbe4eec671e2f281321a8 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 21 Apr 2020 01:01:25 +0200 Subject: mod_mam: Rework hints handling Improved readability and early returns definite yes/no answer. --- plugins/mod_mam/mod_mam.lua | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/plugins/mod_mam/mod_mam.lua b/plugins/mod_mam/mod_mam.lua index 028c3b8f..57f399ae 100644 --- a/plugins/mod_mam/mod_mam.lua +++ b/plugins/mod_mam/mod_mam.lua @@ -267,13 +267,6 @@ local function should_store(stanza) --> boolean, reason: string local st_type = stanza.attr.type or "normal"; local st_to_full = (stanza.attr.to or ""):find("/"); - -- or if hints suggest we shouldn't - if not stanza:get_child("store", "urn:xmpp:hints") then -- No hint telling us we should store - if stanza:get_child("no-permanent-store", "urn:xmpp:hints") - or stanza:get_child("no-store", "urn:xmpp:hints") then -- Hint telling us we should NOT store - return false, "hint"; - end - end if st_type == "headline" then -- Headline messages are ephemeral by definition return false, "headline"; @@ -282,6 +275,12 @@ local function should_store(stanza) --> boolean, reason: string -- MUC messages always go to the full JID, usually archived by the MUC return false, "groupchat"; end + if stanza:get_child("no-permanent-store", "urn:xmpp:hints") then + return false, "hint"; + end + if stanza:get_child("store", "urn:xmpp:hints") then + return true, "hint"; + end if stanza:get_child("body") then return true, "body"; end -- cgit v1.2.3 From e84179de9814eded7c4543848d871fd163ac31d2 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 21 Apr 2020 01:17:55 +0200 Subject: mod_mam: Add more positive hints for storage Mostly just lifted from mod_csi_simple --- plugins/mod_mam/mod_mam.lua | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/plugins/mod_mam/mod_mam.lua b/plugins/mod_mam/mod_mam.lua index 57f399ae..385a0ad2 100644 --- a/plugins/mod_mam/mod_mam.lua +++ b/plugins/mod_mam/mod_mam.lua @@ -284,6 +284,18 @@ local function should_store(stanza) --> boolean, reason: string if stanza:get_child("body") then return true, "body"; end + if stanza:get_child("subject") then + -- XXX Who would send a message with a subject but with a body? + return true, "subject"; + end + if stanza:get_child("encryption", "urn:xmpp:eme:0") then + -- Since we can't know what an encrypted message contains, we assume it's important + return true, "encrypted"; + end + if stanza:get_child("x", "jabber:x:conference") + or stanza:find("{http://jabber.org/protocol/muc#user}x/invite") then + return true, "invite"; + end return true, "default"; end -- cgit v1.2.3 From c8db26be662085caef941923286bdcce9f600950 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 21 Apr 2020 01:18:54 +0200 Subject: mod_mam: Store XEP-0184 receipts and requests Happy now Ge0rG? --- plugins/mod_mam/mod_mam.lua | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugins/mod_mam/mod_mam.lua b/plugins/mod_mam/mod_mam.lua index 385a0ad2..6e522d73 100644 --- a/plugins/mod_mam/mod_mam.lua +++ b/plugins/mod_mam/mod_mam.lua @@ -292,6 +292,11 @@ local function should_store(stanza) --> boolean, reason: string -- Since we can't know what an encrypted message contains, we assume it's important return true, "encrypted"; end + if stanza:get_child(nil, "urn:xmpp:receipts") then + -- If it's important enough to ask for a receipt then it's important enough to archive + -- and the same applies to the receipt + return true, "receipt"; + end if stanza:get_child("x", "jabber:x:conference") or stanza:find("{http://jabber.org/protocol/muc#user}x/invite") then return true, "invite"; -- cgit v1.2.3 From 78668f1be3eb2e0c8301995a65fd7aa87e67aefa Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 21 Apr 2020 01:29:58 +0200 Subject: mod_mam: Check sender of error instead of receiver The intent is to capture errors to stanzas sent by the local user, so that they can see why a message failed to be delivered even if the error came after they went offline. --- plugins/mod_mam/mod_mam.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/mod_mam/mod_mam.lua b/plugins/mod_mam/mod_mam.lua index 6e522d73..db0e7f8d 100644 --- a/plugins/mod_mam/mod_mam.lua +++ b/plugins/mod_mam/mod_mam.lua @@ -266,6 +266,9 @@ end local function should_store(stanza) --> boolean, reason: string local st_type = stanza.attr.type or "normal"; local st_to_full = (stanza.attr.to or ""):find("/"); + if st_type == "error" then + st_to_full = (stanza.attr.from or ""):find("/"); + end if st_type == "headline" then -- Headline messages are ephemeral by definition -- cgit v1.2.3 From 617ac066385fea7bc1841252c87ea786bc84eafc Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 21 Apr 2020 19:41:43 +0200 Subject: mod_mam: Prefer not archiving if no interesting payloads are found --- plugins/mod_mam/mod_mam.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/mod_mam/mod_mam.lua b/plugins/mod_mam/mod_mam.lua index db0e7f8d..69282857 100644 --- a/plugins/mod_mam/mod_mam.lua +++ b/plugins/mod_mam/mod_mam.lua @@ -305,7 +305,9 @@ local function should_store(stanza) --> boolean, reason: string return true, "invite"; end - return true, "default"; + -- The IM-NG thing to do here would be to return `not st_to_full` + -- One day ... + return false, "default"; end -- Handle messages -- cgit v1.2.3 From b0dbacb69cac71c39088328f740b7e4b59831b81 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 21 Apr 2020 23:06:55 +0200 Subject: mod_mam: Fix typo in comment If it is with a body then it execution does not get this far --- plugins/mod_mam/mod_mam.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_mam/mod_mam.lua b/plugins/mod_mam/mod_mam.lua index 69282857..6d493131 100644 --- a/plugins/mod_mam/mod_mam.lua +++ b/plugins/mod_mam/mod_mam.lua @@ -288,7 +288,7 @@ local function should_store(stanza) --> boolean, reason: string return true, "body"; end if stanza:get_child("subject") then - -- XXX Who would send a message with a subject but with a body? + -- XXX Who would send a message with a subject but without a body? return true, "subject"; end if stanza:get_child("encryption", "urn:xmpp:eme:0") then -- cgit v1.2.3 From 8c1161cf773fa9eb5371e2bffdb054b97a71dad1 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 22 Apr 2020 18:47:06 +0200 Subject: mod_mam: Respect no-store hint (thanks Ge0rG) no-store is used in an example in XEP-0313, so obviously this is the preferred hint --- plugins/mod_mam/mod_mam.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/mod_mam/mod_mam.lua b/plugins/mod_mam/mod_mam.lua index 6d493131..7ac2a512 100644 --- a/plugins/mod_mam/mod_mam.lua +++ b/plugins/mod_mam/mod_mam.lua @@ -278,7 +278,8 @@ local function should_store(stanza) --> boolean, reason: string -- MUC messages always go to the full JID, usually archived by the MUC return false, "groupchat"; end - if stanza:get_child("no-permanent-store", "urn:xmpp:hints") then + if stanza:get_child("no-store", "urn:xmpp:hints") + or stanza:get_child("no-permanent-store", "urn:xmpp:hints") then return false, "hint"; end if stanza:get_child("store", "urn:xmpp:hints") then -- cgit v1.2.3 From 9bb75c85c478a5da62e697aa6006c570db15eae2 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 22 Apr 2020 18:48:27 +0200 Subject: mod_mam: Keep chat markers (thanks Ge0rG) --- plugins/mod_mam/mod_mam.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/mod_mam/mod_mam.lua b/plugins/mod_mam/mod_mam.lua index 7ac2a512..06a20f4d 100644 --- a/plugins/mod_mam/mod_mam.lua +++ b/plugins/mod_mam/mod_mam.lua @@ -301,6 +301,9 @@ local function should_store(stanza) --> boolean, reason: string -- and the same applies to the receipt return true, "receipt"; end + if stanza:get_child(nil, "urn:xmpp:chat-markers:0") then + return true, "marker"; + end if stanza:get_child("x", "jabber:x:conference") or stanza:find("{http://jabber.org/protocol/muc#user}x/invite") then return true, "invite"; -- cgit v1.2.3 From 92150bd359689a2b3401ae9dcdc8ba51fbaf8e1f Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 22 Apr 2020 18:50:30 +0200 Subject: mod_mam: Save delivery failures (thanks Ge0rG) Makes it possible to learn of delivery failure even if it came bouncing back while you were offline. --- plugins/mod_mam/mod_mam.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/mod_mam/mod_mam.lua b/plugins/mod_mam/mod_mam.lua index 06a20f4d..b74a92ea 100644 --- a/plugins/mod_mam/mod_mam.lua +++ b/plugins/mod_mam/mod_mam.lua @@ -274,6 +274,9 @@ local function should_store(stanza) --> boolean, reason: string -- Headline messages are ephemeral by definition return false, "headline"; end + if st_type == "error" then + return true, "bounce"; + end if st_type == "groupchat" and st_to_full then -- MUC messages always go to the full JID, usually archived by the MUC return false, "groupchat"; -- cgit v1.2.3 From 966c3c56043bb8903acbbd5c8f9ee36bc23d5388 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 22 Apr 2020 18:53:50 +0200 Subject: mod_mam: Make note of Experimental (or Deferred) XEPs Since these XEPs are subject to change we may need come back and double check these in the future. --- plugins/mod_mam/mod_mam.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/mod_mam/mod_mam.lua b/plugins/mod_mam/mod_mam.lua index b74a92ea..a9d389f5 100644 --- a/plugins/mod_mam/mod_mam.lua +++ b/plugins/mod_mam/mod_mam.lua @@ -283,6 +283,7 @@ local function should_store(stanza) --> boolean, reason: string end if stanza:get_child("no-store", "urn:xmpp:hints") or stanza:get_child("no-permanent-store", "urn:xmpp:hints") then + -- XXX Experimental XEP return false, "hint"; end if stanza:get_child("store", "urn:xmpp:hints") then @@ -297,6 +298,7 @@ local function should_store(stanza) --> boolean, reason: string end if stanza:get_child("encryption", "urn:xmpp:eme:0") then -- Since we can't know what an encrypted message contains, we assume it's important + -- XXX Experimental XEP return true, "encrypted"; end if stanza:get_child(nil, "urn:xmpp:receipts") then @@ -305,6 +307,7 @@ local function should_store(stanza) --> boolean, reason: string return true, "receipt"; end if stanza:get_child(nil, "urn:xmpp:chat-markers:0") then + -- XXX Experimental XEP return true, "marker"; end if stanza:get_child("x", "jabber:x:conference") -- cgit v1.2.3 From 16c54b170580328dd57bb35a68281d5e385b82dc Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 22 Apr 2020 21:35:33 +0200 Subject: util.hashes: Use generic name of PBKDF2-HMAC-SHA1 function in tests --- spec/util_hashes_spec.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/spec/util_hashes_spec.lua b/spec/util_hashes_spec.lua index 1e6187bb..5c616a8d 100644 --- a/spec/util_hashes_spec.lua +++ b/spec/util_hashes_spec.lua @@ -4,34 +4,34 @@ local hex = require "util.hex"; -- Also see spec for util.hmac where HMAC test cases reside -describe("PBKDF2-SHA1", function () +describe("PBKDF2-HMAC-SHA1", function () it("test vector 1", function () local P = "password" local S = "salt" local c = 1 local DK = "0c60c80f961f0e71f3a9b524af6012062fe037a6"; - assert.equal(DK, hex.to(hashes.scram_Hi_sha1(P, S, c))); + assert.equal(DK, hex.to(hashes.pbkdf2_hmac_sha1(P, S, c))); end); it("test vector 2", function () local P = "password" local S = "salt" local c = 2 local DK = "ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957"; - assert.equal(DK, hex.to(hashes.scram_Hi_sha1(P, S, c))); + assert.equal(DK, hex.to(hashes.pbkdf2_hmac_sha1(P, S, c))); end); it("test vector 3", function () local P = "password" local S = "salt" local c = 4096 local DK = "4b007901b765489abead49d926f721d065a429c1"; - assert.equal(DK, hex.to(hashes.scram_Hi_sha1(P, S, c))); + assert.equal(DK, hex.to(hashes.pbkdf2_hmac_sha1(P, S, c))); end); it("test vector 4 #SLOW", function () local P = "password" local S = "salt" local c = 16777216 local DK = "eefe3d61cd4da4e4e9945b3d6ba2158c2634e984"; - assert.equal(DK, hex.to(hashes.scram_Hi_sha1(P, S, c))); + assert.equal(DK, hex.to(hashes.pbkdf2_hmac_sha1(P, S, c))); end); end); -- cgit v1.2.3 From 701c5f48110cb004925ba659b7221b18713ad2c7 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 22 Apr 2020 21:38:36 +0200 Subject: util.hashes: Fix output length of PBKDF2-HMAC-SHA256 Somehow it got SHA1's 20 byte output instead of the proper 32 = 256/8 --- spec/util_hashes_spec.lua | 18 ++++++++++++++++++ util-src/hashes.c | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/spec/util_hashes_spec.lua b/spec/util_hashes_spec.lua index 5c616a8d..3639dd4e 100644 --- a/spec/util_hashes_spec.lua +++ b/spec/util_hashes_spec.lua @@ -35,3 +35,21 @@ describe("PBKDF2-HMAC-SHA1", function () end); end); +describe("PBKDF2-HMAC-SHA256", function () + it("test vector 1", function () + local P = "password"; + local S = "salt"; + local c = 1 + local DK = "120fb6cffcf8b32c43e7225256c4f837a86548c92ccc35480805987cb70be17b"; + assert.equal(DK, hex.to(hashes.pbkdf2_hmac_sha256(P, S, c))); + end); + it("test vector 2", function () + local P = "password"; + local S = "salt"; + local c = 2 + local DK = "ae4d0c95af6b46d32d0adff928f06dd02a303f8ef3c251dfd6e2d85a95474c43"; + assert.equal(DK, hex.to(hashes.pbkdf2_hmac_sha256(P, S, c))); + end); +end); + + diff --git a/util-src/hashes.c b/util-src/hashes.c index 51c7611c..84a604ef 100644 --- a/util-src/hashes.c +++ b/util-src/hashes.c @@ -129,7 +129,7 @@ static int Lpbkdf2_sha256(lua_State *L) { return luaL_error(L, "PKCS5_PBKDF2_HMAC() failed"); } - lua_pushlstring(L, (char *)out, SHA_DIGEST_LENGTH); + lua_pushlstring(L, (char *)out, SHA256_DIGEST_LENGTH); return 1; } -- cgit v1.2.3 From bc0e389d47b0a40e1ea871fe4ff583746af928be Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 22 Apr 2020 21:46:56 +0200 Subject: mod_uptime: Encode uptime as decimal, fix #1536 (thanks Martin) --- plugins/mod_uptime.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_uptime.lua b/plugins/mod_uptime.lua index 035f7e9b..8a01fb17 100644 --- a/plugins/mod_uptime.lua +++ b/plugins/mod_uptime.lua @@ -16,7 +16,7 @@ module:add_feature("jabber:iq:last"); module:hook("iq-get/host/jabber:iq:last:query", function(event) local origin, stanza = event.origin, event.stanza; - origin.send(st.reply(stanza):tag("query", {xmlns = "jabber:iq:last", seconds = tostring(os.difftime(os.time(), start_time))})); + origin.send(st.reply(stanza):tag("query", {xmlns = "jabber:iq:last", seconds = tostring(("%d"):format(os.difftime(os.time(), start_time)))})); return true; end); -- cgit v1.2.3 From d649e78d6d7e2977e6e60e26262eac1161631857 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 22 Apr 2020 23:36:25 +0200 Subject: mod_lastactivity: Encode seconds as decimal, not float In Lua 5.3 difftime() takes integers as argument but returns a float, and then tostring() serializes it with a decimal point. This violates XEP-0012. Like #1536 --- plugins/mod_lastactivity.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_lastactivity.lua b/plugins/mod_lastactivity.lua index 575e66be..91d11bd2 100644 --- a/plugins/mod_lastactivity.lua +++ b/plugins/mod_lastactivity.lua @@ -30,7 +30,7 @@ module:hook("iq-get/bare/jabber:iq:last:query", function(event) if not stanza.attr.to or is_contact_subscribed(username, module.host, jid_bare(stanza.attr.from)) then local seconds, text = "0", ""; if map[username] then - seconds = tostring(os.difftime(os.time(), map[username].t)); + seconds = string.format("%d", os.difftime(os.time(), map[username].t)); text = map[username].s; end origin.send(st.reply(stanza):tag('query', {xmlns='jabber:iq:last', seconds=seconds}):text(text)); -- cgit v1.2.3 From ba452d69231d6cbf4c2efb4f5e99df40d9a11ede Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 23 Apr 2020 00:55:34 +0200 Subject: mod_mam: Don't store any groupchat messages The intent was to not store MUC groupchat messages, which are sent from the MUC to local full JIDs, while allowing for potential future account based group chat. However, since this function handles messages in both directions and outgoing MUC messages are sent to the bare room JID, those were stored. --- plugins/mod_mam/mod_mam.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/mod_mam/mod_mam.lua b/plugins/mod_mam/mod_mam.lua index a9d389f5..65859afd 100644 --- a/plugins/mod_mam/mod_mam.lua +++ b/plugins/mod_mam/mod_mam.lua @@ -269,6 +269,8 @@ local function should_store(stanza) --> boolean, reason: string if st_type == "error" then st_to_full = (stanza.attr.from or ""):find("/"); end + -- FIXME pass direction of stanza and use that along with bare/full JID addressing + -- for more accurate MUC / type=groupchat check if st_type == "headline" then -- Headline messages are ephemeral by definition @@ -277,7 +279,7 @@ local function should_store(stanza) --> boolean, reason: string if st_type == "error" then return true, "bounce"; end - if st_type == "groupchat" and st_to_full then + if st_type == "groupchat" then -- MUC messages always go to the full JID, usually archived by the MUC return false, "groupchat"; end -- cgit v1.2.3 From 80aaa484c38af613e61ad2f1af0ca186933ab3b8 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 23 Apr 2020 01:05:34 +0200 Subject: mod_mam: Remove unused variables [luacheck] Logic using full vs bare JID addressing may return in the future. --- plugins/mod_mam/mod_mam.lua | 4 ---- 1 file changed, 4 deletions(-) diff --git a/plugins/mod_mam/mod_mam.lua b/plugins/mod_mam/mod_mam.lua index 65859afd..d61d4883 100644 --- a/plugins/mod_mam/mod_mam.lua +++ b/plugins/mod_mam/mod_mam.lua @@ -265,10 +265,6 @@ end local function should_store(stanza) --> boolean, reason: string local st_type = stanza.attr.type or "normal"; - local st_to_full = (stanza.attr.to or ""):find("/"); - if st_type == "error" then - st_to_full = (stanza.attr.from or ""):find("/"); - end -- FIXME pass direction of stanza and use that along with bare/full JID addressing -- for more accurate MUC / type=groupchat check -- cgit v1.2.3 From 114ff031fcf2aa12596b80bd91dac1b30694b6b8 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Thu, 23 Apr 2020 15:17:42 +0100 Subject: MUC tests: Add missing 'affiliation' attribute --- spec/scansion/muc_show_offline.scs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/scansion/muc_show_offline.scs b/spec/scansion/muc_show_offline.scs index 755a4774..57b75ec7 100644 --- a/spec/scansion/muc_show_offline.scs +++ b/spec/scansion/muc_show_offline.scs @@ -205,7 +205,7 @@ Juliet receives: Romeo receives: - + -- cgit v1.2.3 From da2a13287ee51d1dc1e1db92c3972c72b5d40081 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 23 Apr 2020 18:05:00 +0200 Subject: util.rsm: Add tests Based on examples from XEP-0059 --- spec/util_rsm_spec.lua | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 spec/util_rsm_spec.lua diff --git a/spec/util_rsm_spec.lua b/spec/util_rsm_spec.lua new file mode 100644 index 00000000..c8ddc47d --- /dev/null +++ b/spec/util_rsm_spec.lua @@ -0,0 +1,82 @@ +local rsm = require "util.rsm"; +local xml = require "util.xml"; + +local function strip(s) + return (s:gsub(">%s+<", "><")); +end + +describe("util.rsm", function () + describe("parse", function () + it("works", function () + local test = xml.parse(strip([[ + + 10 + + ]])); + assert.same({ max = 10 }, rsm.parse(test)); + end); + + it("works", function () + local test = xml.parse(strip([[ + + saint@example.org + peterpan@neverland.lit + 800 + + ]])); + assert.same({ first = { index = 0, "saint@example.org" }, last = "peterpan@neverland.lit", count = 800 }, rsm.parse(test)); + end); + + it("works", function () + local test = xml.parse(strip([[ + + 10 + peter@pixyland.org + + ]])); + assert.same({ max = 10, before = "peter@pixyland.org" }, rsm.parse(test)); + end); + + end); + + describe("generate", function () + it("works", function () + local test = xml.parse(strip([[ + + 10 + + ]])); + local res = rsm.generate({ max = 10 }); + assert.same(test:get_child_text("max"), res:get_child_text("max")); + end); + + it("works", function () + local test = xml.parse(strip([[ + + saint@example.org + peterpan@neverland.lit + 800 + + ]])); + local res = rsm.generate({ first = { index = 0, "saint@example.org" }, last = "peterpan@neverland.lit", count = 800 }); + assert.same(test:get_child("first").attr.index, res:get_child("first").attr.index); + assert.same(test:get_child_text("first"), res:get_child_text("first")); + assert.same(test:get_child_text("last"), res:get_child_text("last")); + assert.same(test:get_child_text("count"), res:get_child_text("count")); + end); + + it("works", function () + local test = xml.parse(strip([[ + + 10 + peter@pixyland.org + + ]])); + local res = rsm.generate({ max = 10, before = "peter@pixyland.org" }); + assert.same(test:get_child_text("max"), res:get_child_text("max")); + assert.same(test:get_child_text("before"), res:get_child_text("before")); + end); + + end); +end); + -- cgit v1.2.3 From 93535db346af6b10a1778f91235b7a9429e6ead0 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 23 Apr 2020 18:16:36 +0200 Subject: util.rsm: Fix passing number as attribute --- util/rsm.lua | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/util/rsm.lua b/util/rsm.lua index 40a78fb5..b5afd62a 100644 --- a/util/rsm.lua +++ b/util/rsm.lua @@ -11,9 +11,14 @@ local stanza = require"util.stanza".stanza; local tostring, tonumber = tostring, tonumber; +local s_format = string.format; local type = type; local pairs = pairs; +local function inttostr(n) + return s_format("%d", n); +end + local xmlns_rsm = 'http://jabber.org/protocol/rsm'; local element_parsers = {}; @@ -45,7 +50,7 @@ end local element_generators = setmetatable({ first = function(st, data) if type(data) == "table" then - st:tag("first", { index = data.index }):text(data[1]):up(); + st:tag("first", { index = inttostr(data.index) }):text(data[1]):up(); else st:tag("first"):text(tostring(data)):up(); end -- cgit v1.2.3 From 67372d19bdab286a5900494a0be694aacec7d9b6 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 23 Apr 2020 18:17:54 +0200 Subject: util.rsm: Test that Lua 5.3 floats are not encoded with decimal point The 'max' 'count' and 'index' fields are integers and should be encoded as such on the wire. Care needs to be taken because tostring(1.0) in Lua 5.3+ returns to "1.0" while in previous Lua versions it would return "1". --- spec/util_rsm_spec.lua | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/spec/util_rsm_spec.lua b/spec/util_rsm_spec.lua index c8ddc47d..08fb1b02 100644 --- a/spec/util_rsm_spec.lua +++ b/spec/util_rsm_spec.lua @@ -77,6 +77,13 @@ describe("util.rsm", function () assert.same(test:get_child_text("before"), res:get_child_text("before")); end); + it("handles floats", function () + local r1 = rsm.generate({ max = 10.0, count = 100.0, first = { index = 1.0, "foo" } }); + assert.equal("10", r1:get_child_text("max")); + assert.equal("100", r1:get_child_text("count")); + assert.equal("1", r1:get_child("first").attr.index); + end); + end); end); -- cgit v1.2.3 From f4572c17325ceab3d69a137f2bc508c33638cc94 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 23 Apr 2020 18:40:57 +0200 Subject: util.rsm: Explicitly serialize numbers in correct format --- util/rsm.lua | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/util/rsm.lua b/util/rsm.lua index b5afd62a..aade3c19 100644 --- a/util/rsm.lua +++ b/util/rsm.lua @@ -61,7 +61,13 @@ local element_generators = setmetatable({ else st:tag("before"):text(tostring(data)):up(); end - end + end; + max = function (st, data) + st:tag("max"):text(inttostr(data)):up(); + end; + count = function (st, data) + st:tag("count"):text(inttostr(data)):up(); + end; }, { __index = function(_, name) return function(st, data) -- cgit v1.2.3 From 4d54d3e1352e11519002d917f4d24267d0776bd7 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 23 Apr 2020 18:42:47 +0200 Subject: util.rsm: Don't convert values to strings that should already be strings Causes util.stanza to throw an error, which helps detect mistakes --- util/rsm.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/util/rsm.lua b/util/rsm.lua index aade3c19..ad725d76 100644 --- a/util/rsm.lua +++ b/util/rsm.lua @@ -10,7 +10,7 @@ -- local stanza = require"util.stanza".stanza; -local tostring, tonumber = tostring, tonumber; +local tonumber = tonumber; local s_format = string.format; local type = type; local pairs = pairs; @@ -52,14 +52,14 @@ local element_generators = setmetatable({ if type(data) == "table" then st:tag("first", { index = inttostr(data.index) }):text(data[1]):up(); else - st:tag("first"):text(tostring(data)):up(); + st:tag("first"):text(data):up(); end end; before = function(st, data) if data == true then st:tag("before"):up(); else - st:tag("before"):text(tostring(data)):up(); + st:tag("before"):text(data):up(); end end; max = function (st, data) @@ -71,7 +71,7 @@ local element_generators = setmetatable({ }, { __index = function(_, name) return function(st, data) - st:tag(name):text(tostring(data)):up(); + st:tag(name):text(data):up(); end end; }); -- cgit v1.2.3 From 4923e7f87fd5d1a8ad077ffa1c28c664280af9b2 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 23 Apr 2020 19:00:40 +0200 Subject: mod_register_limits: Fix order of arguments to util.error (fix #1539 p1) (thanks Ge0rG) --- plugins/mod_register_limits.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/mod_register_limits.lua b/plugins/mod_register_limits.lua index fc9bf27a..7a504b32 100644 --- a/plugins/mod_register_limits.lua +++ b/plugins/mod_register_limits.lua @@ -82,16 +82,16 @@ module:hook("user-registering", function (event) elseif ip_in_set(blacklisted_ips, ip) then log("debug", "Registration disallowed by blacklist"); event.allowed = false; - event.error = errors.new("blacklisted", err_registry, event); + event.error = errors.new("blacklisted", event, err_registry); elseif (whitelist_only and not ip_in_set(whitelisted_ips, ip)) then log("debug", "Registration disallowed by whitelist"); event.allowed = false; - event.error = errors.new("not_whitelisted", err_registry, event); + event.error = errors.new("not_whitelisted", event, err_registry); elseif throttle_max and not ip_in_set(whitelisted_ips, ip) then if not check_throttle(ip) then log("debug", "Registrations over limit for ip %s", ip or "?"); event.allowed = false; - event.error = errors.new("throttle", err_registry, event); + event.error = errors.new("throttle", event, err_registry); end end if event.error then -- cgit v1.2.3 From 3820952675423068a69572b9c6fffde4c094acef Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 23 Apr 2020 19:00:48 +0200 Subject: mod_register_limits: Fix typo error name (fix #1539 p2) (thanks Ge0rG) Probably because autocomplete. --- plugins/mod_register_limits.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_register_limits.lua b/plugins/mod_register_limits.lua index 7a504b32..0fe6a98a 100644 --- a/plugins/mod_register_limits.lua +++ b/plugins/mod_register_limits.lua @@ -91,7 +91,7 @@ module:hook("user-registering", function (event) if not check_throttle(ip) then log("debug", "Registrations over limit for ip %s", ip or "?"); event.allowed = false; - event.error = errors.new("throttle", event, err_registry); + event.error = errors.new("throttled", event, err_registry); end end if event.error then -- cgit v1.2.3 From d2aa477111130cd0bd677dc61513c30463a642a5 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 23 Apr 2020 19:02:44 +0200 Subject: mod_register_ibr: Fix reporting of registration rejection reason When the reason is reported as an util.error object the `reason` field is empty and the reason text should be extacted from the error object. --- plugins/mod_register_ibr.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_register_ibr.lua b/plugins/mod_register_ibr.lua index 6de9bc33..e79fc763 100644 --- a/plugins/mod_register_ibr.lua +++ b/plugins/mod_register_ibr.lua @@ -167,7 +167,6 @@ module:hook("stanza/iq/jabber:iq:register:query", function(event) local user = { username = username, password = password, host = host, additional = data, ip = session.ip, session = session, allowed = true } module:fire_event("user-registering", user); if not user.allowed then - log("debug", "Registration disallowed by module: %s", user.reason or "no reason given"); local error_type, error_condition, reason; local err = user.error; if err then @@ -176,6 +175,7 @@ module:hook("stanza/iq/jabber:iq:register:query", function(event) -- COMPAT pre-util.error error_type, error_condition, reason = user.error_type, user.error_condition, user.reason; end + log("debug", "Registration disallowed by module: %s", reason or "no reason given"); session.send(st.error_reply(stanza, error_type or "modify", error_condition or "not-acceptable", reason)); return true; end -- cgit v1.2.3 From 14083d021e63d308c445bb4713a706eb53273e90 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 23 Apr 2020 19:24:27 +0200 Subject: mod_register_limits: Fix text reason field name for 'throttled' Copy-paste mistake presumably --- plugins/mod_register_limits.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_register_limits.lua b/plugins/mod_register_limits.lua index 0fe6a98a..7c80f18b 100644 --- a/plugins/mod_register_limits.lua +++ b/plugins/mod_register_limits.lua @@ -67,7 +67,7 @@ local err_registry = { condition = "forbidden"; }; throttled = { - reason = "Too many registrations from this IP address recently"; + text = "Too many registrations from this IP address recently"; type = "wait"; condition = "policy-violation"; }; -- cgit v1.2.3 From fc3bee71fdc30f8bc253d226124656582189ffa8 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 26 Apr 2020 10:58:22 +0200 Subject: mod_csi_simple: Explicitly consider errors important This was already the case for presence and iq stanzas but not messages. --- plugins/mod_csi_simple.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/mod_csi_simple.lua b/plugins/mod_csi_simple.lua index cc15f033..df7ce045 100644 --- a/plugins/mod_csi_simple.lua +++ b/plugins/mod_csi_simple.lua @@ -23,6 +23,9 @@ module:hook("csi-is-stanza-important", function (event) local st_name = stanza.name; if not st_name then return false; end local st_type = stanza.attr.type; + if st_type == "error" then + return true; + end if st_name == "presence" then if st_type == nil or st_type == "unavailable" then return false; -- cgit v1.2.3 From ea91b4cc7bdc77c297fdd7c0d4b0eb9c8bbfd276 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 26 Apr 2020 11:13:01 +0200 Subject: mod_csi_simple: Consider nonzas important This case was previously handled by fall-trough at the end of the function. --- plugins/mod_csi_simple.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/mod_csi_simple.lua b/plugins/mod_csi_simple.lua index df7ce045..e509bef8 100644 --- a/plugins/mod_csi_simple.lua +++ b/plugins/mod_csi_simple.lua @@ -20,6 +20,10 @@ module:hook("csi-is-stanza-important", function (event) if not st.is_stanza(stanza) then return true; end + if stanza.attr.xmlns ~= nil then + -- stream errors, stream management etc + return true; + end local st_name = stanza.name; if not st_name then return false; end local st_type = stanza.attr.type; -- cgit v1.2.3 From 37a42fa6052f1b55cae4728a71f1bfbdf7743630 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 26 Apr 2020 11:13:25 +0200 Subject: mod_csi_simple: Clarify what might not be stanzas here --- plugins/mod_csi_simple.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/mod_csi_simple.lua b/plugins/mod_csi_simple.lua index e509bef8..bfbcd029 100644 --- a/plugins/mod_csi_simple.lua +++ b/plugins/mod_csi_simple.lua @@ -18,6 +18,7 @@ local important_payloads = module:get_option_set("csi_important_payloads", { }); module:hook("csi-is-stanza-important", function (event) local stanza = event.stanza; if not st.is_stanza(stanza) then + -- whitespace pings etc return true; end if stanza.attr.xmlns ~= nil then -- cgit v1.2.3 From 0085d410b206f1899a2841064a798eac2161d764 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 26 Apr 2020 11:40:48 +0200 Subject: mod_csi_simple: Explicitly mention iq stanzas Should be more obvious that all iq stanzas are considered important. Changes behavior for invalid things in the default namespace. --- plugins/mod_csi_simple.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/mod_csi_simple.lua b/plugins/mod_csi_simple.lua index bfbcd029..d3bf3a9f 100644 --- a/plugins/mod_csi_simple.lua +++ b/plugins/mod_csi_simple.lua @@ -65,8 +65,9 @@ module:hook("csi-is-stanza-important", function (event) end end return false; + elseif st_name == "iq" then + return true; end - return true; end, -1); local function with_timestamp(stanza, from) -- cgit v1.2.3 From e5327bcc31cb602df89d19f9c9c73aa90d4bec37 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 26 Apr 2020 14:28:00 +0200 Subject: mod_mam: Store only incoming errors Unclear if clients normally ever send error messages, but there may be locally generated bounces sent on behalf of local sessions. --- plugins/mod_mam/mod_mam.lua | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/plugins/mod_mam/mod_mam.lua b/plugins/mod_mam/mod_mam.lua index d61d4883..72b7639a 100644 --- a/plugins/mod_mam/mod_mam.lua +++ b/plugins/mod_mam/mod_mam.lua @@ -263,7 +263,7 @@ local function strip_stanza_id(stanza, user) return stanza; end -local function should_store(stanza) --> boolean, reason: string +local function should_store(stanza, c2s) --> boolean, reason: string local st_type = stanza.attr.type or "normal"; -- FIXME pass direction of stanza and use that along with bare/full JID addressing -- for more accurate MUC / type=groupchat check @@ -272,7 +272,8 @@ local function should_store(stanza) --> boolean, reason: string -- Headline messages are ephemeral by definition return false, "headline"; end - if st_type == "error" then + if st_type == "error" and not c2s then + -- Store delivery failure notifications so you know if your own messages were not delivered return true, "bounce"; end if st_type == "groupchat" then @@ -334,7 +335,7 @@ local function message_handler(event, c2s) -- Filter out that claim to be from us event.stanza = strip_stanza_id(stanza, store_user); - local should, why = should_store(stanza); + local should, why = should_store(stanza, c2s); if not should then log("debug", "Not archiving stanza: %s (%s)", stanza:top_tag(), why); return; -- cgit v1.2.3 From 23ae3a4adabb9c38bf33ae597bcae4138f739135 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 26 Apr 2020 14:31:43 +0200 Subject: util.sasl.scram: Mention if clients try PLUS without channel binding This isn't normal, but is it invalid? Likely a client bug in any case. --- util/sasl/scram.lua | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/util/sasl/scram.lua b/util/sasl/scram.lua index e2ce00f5..b3370d4b 100644 --- a/util/sasl/scram.lua +++ b/util/sasl/scram.lua @@ -112,7 +112,7 @@ local function get_scram_hasher(H, HMAC, Hi) end end -local function scram_gen(hash_name, H_f, HMAC_f, get_auth_db) +local function scram_gen(hash_name, H_f, HMAC_f, get_auth_db, expect_cb) local profile_name = "scram_" .. hashprep(hash_name); local function scram_hash(self, message) local support_channel_binding = false; @@ -141,6 +141,10 @@ local function scram_gen(hash_name, H_f, HMAC_f, get_auth_db) if gs2_cbind_flag == "n" then -- "n" -> client doesn't support channel binding. + if expect_cb then + log("debug", "Client unexpectedly doesn't support channel binding"); + -- XXX Is it sensible to abort if the client starts -PLUS but doesn't use channel binding? + end support_channel_binding = false; end @@ -260,7 +264,7 @@ local function init(registerMechanism) -- register channel binding equivalent registerMechanism("SCRAM-"..hash_name.."-PLUS", {"plain", "scram_"..(hashprep(hash_name))}, - scram_gen(hash_name:lower(), hash, hmac_hash, get_auth_db), {"tls-unique"}); + scram_gen(hash_name:lower(), hash, hmac_hash, get_auth_db, true), {"tls-unique"}); end registerSCRAMMechanism("SHA-1", hashes.sha1, hashes.hmac_sha1, hashes.pbkdf2_hmac_sha1); -- cgit v1.2.3 From ce1056565dde6666a85a70733bdc5998bf4c9896 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 26 Apr 2020 19:38:38 +0200 Subject: MUC: Adapt rules for what should be stored from mod_mam This is the subset of mod_mam rules I believe makes sense in MUC. Note that mod_muc_mam does not have its own rules, but uses these. --- plugins/muc/history.lib.lua | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/plugins/muc/history.lib.lua b/plugins/muc/history.lib.lua index f9ddabbf..1b7167af 100644 --- a/plugins/muc/history.lib.lua +++ b/plugins/muc/history.lib.lua @@ -200,7 +200,27 @@ module:hook("muc-broadcast-message", function(event) end); module:hook("muc-message-is-historic", function (event) - return event.stanza:get_child("body"); + local stanza = event.stanza; + if stanza:get_child("no-store", "urn:xmpp:hints") + or stanza:get_child("no-permanent-store", "urn:xmpp:hints") then + -- XXX Experimental XEP + return false, "hint"; + end + if stanza:get_child("store", "urn:xmpp:hints") then + return true, "hint"; + end + if stanza:get_child("body") then + return true; + end + if stanza:get_child("encryption", "urn:xmpp:eme:0") then + -- Since we can't know what an encrypted message contains, we assume it's important + -- XXX Experimental XEP + return true, "encrypted"; + end + if stanza:get_child(nil, "urn:xmpp:chat-markers:0") then + -- XXX Experimental XEP + return true, "marker"; + end end, -1); return { -- cgit v1.2.3 From fe36680fba1a9010a3a9004066a33c7b0612b124 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 26 Apr 2020 20:17:43 +0200 Subject: mod_carbons: Refactor in new style (mod_mam/csi) --- plugins/mod_carbons.lua | 46 +++++++++++++++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/plugins/mod_carbons.lua b/plugins/mod_carbons.lua index 5c17616c..dfd2b3a4 100644 --- a/plugins/mod_carbons.lua +++ b/plugins/mod_carbons.lua @@ -20,6 +20,32 @@ end module:hook("iq-set/self/"..xmlns_carbons..":disable", toggle_carbons); module:hook("iq-set/self/"..xmlns_carbons..":enable", toggle_carbons); +local function should_copy(stanza, c2s, user_bare) + local st_type = stanza.attr.type or "normal"; + if stanza:get_child("private", xmlns_carbons) then + return false, "private"; + end + + if stanza:get_child("no-copy", "urn:xmpp:hints") then + return false, "hint"; + end + + if not c2s and and stanza.attr.to ~= user_bare and stanza:get_child("x", "http://jabber.org/protocol/muc#user") then + -- MUC PMs are normally sent to full JIDs + return false, "muc-pm"; + end + + if st_type == "chat" then + return true, "type"; + end + + if st_type == "normal" and stanza:get_child("body") then + return true, "type"; + end + + return false, "default"; +end + local function message_handler(event, c2s) local origin, stanza = event.origin, event.stanza; local orig_type = stanza.attr.type or "normal"; @@ -28,10 +54,6 @@ local function message_handler(event, c2s) local orig_to = stanza.attr.to; local bare_to = jid_bare(orig_to); - if not(orig_type == "chat" or (orig_type == "normal" and stanza:get_child("body"))) then - return -- Only chat type messages - end - -- Stanza sent by a local client local bare_jid = bare_from; -- JID of the local user local target_session = origin; @@ -56,22 +78,16 @@ local function message_handler(event, c2s) return -- No use in sending carbons to an offline user end - if stanza:get_child("private", xmlns_carbons) then - if not c2s then + local should, why = should_copy(stanza, c2s, bare_jid); + + if not should then + module:log("debug", "Not copying stanza: %s (%s)", stanza:top_tag(), why); + elseif why == "private" and not c2s then stanza:maptags(function(tag) if not ( tag.attr.xmlns == xmlns_carbons and tag.name == "private" ) then return tag; end end); - end - module:log("debug", "Message tagged private, ignoring"); - return - elseif stanza:get_child("no-copy", "urn:xmpp:hints") then - module:log("debug", "Message has no-copy hint, ignoring"); - return - elseif not c2s and bare_jid ~= orig_to and stanza:get_child("x", "http://jabber.org/protocol/muc#user") then - module:log("debug", "MUC PM, ignoring"); - return end local carbon; -- cgit v1.2.3 From bec170ac73c3b7763cb66909c0301d11d316ff47 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 26 Apr 2020 21:11:00 +0200 Subject: mod_carbons: Fix syntax error [luacheck] --- plugins/mod_carbons.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_carbons.lua b/plugins/mod_carbons.lua index dfd2b3a4..09518bf7 100644 --- a/plugins/mod_carbons.lua +++ b/plugins/mod_carbons.lua @@ -30,7 +30,7 @@ local function should_copy(stanza, c2s, user_bare) return false, "hint"; end - if not c2s and and stanza.attr.to ~= user_bare and stanza:get_child("x", "http://jabber.org/protocol/muc#user") then + if not c2s and stanza.attr.to ~= user_bare and stanza:get_child("x", "http://jabber.org/protocol/muc#user") then -- MUC PMs are normally sent to full JIDs return false, "muc-pm"; end -- cgit v1.2.3 From 9e3ef1c01bc14f51a41e5eb413cb05118489bd7f Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 26 Apr 2020 20:23:50 +0200 Subject: mod_carbons: Carbon incoming message delivery failure reports Ensures that all your clients know about sent messages that failed. --- plugins/mod_carbons.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/mod_carbons.lua b/plugins/mod_carbons.lua index 09518bf7..9befc9cb 100644 --- a/plugins/mod_carbons.lua +++ b/plugins/mod_carbons.lua @@ -43,6 +43,10 @@ local function should_copy(stanza, c2s, user_bare) return true, "type"; end + if st_type == "error" and not c2s and not (stanza.attr.from or ""):find("/") then + return true, "bounce"; + end + return false, "default"; end -- cgit v1.2.3 From 608f1d1c58ceac633a29036eb1a6b70103d2e0e0 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 26 Apr 2020 20:24:51 +0200 Subject: mod_carbons: Carbon anything that has been archived by the current user This ensures rules in mod_mam apply to some extent. Messages worth archiving are probably worth sending to other clients. --- plugins/mod_carbons.lua | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/plugins/mod_carbons.lua b/plugins/mod_carbons.lua index 9befc9cb..c5efce39 100644 --- a/plugins/mod_carbons.lua +++ b/plugins/mod_carbons.lua @@ -47,6 +47,12 @@ local function should_copy(stanza, c2s, user_bare) return true, "bounce"; end + for archived in stanza:childtags("stanza-id", "urn:xmpp:sid:0") do + if archived and archived.attr.by == user_bare then + return true, "archived"; + end + end + return false, "default"; end -- cgit v1.2.3 From 70410438020cc0646be1ec469dd88909da607b66 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 27 Apr 2020 14:43:54 +0200 Subject: mod_carbons: Don't copy messages that should not be copied The return statements were lost in d95e083931d1 --- plugins/mod_carbons.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/mod_carbons.lua b/plugins/mod_carbons.lua index c5efce39..67f345f1 100644 --- a/plugins/mod_carbons.lua +++ b/plugins/mod_carbons.lua @@ -92,6 +92,7 @@ local function message_handler(event, c2s) if not should then module:log("debug", "Not copying stanza: %s (%s)", stanza:top_tag(), why); + return; elseif why == "private" and not c2s then stanza:maptags(function(tag) if not ( tag.attr.xmlns == xmlns_carbons and tag.name == "private" ) then -- cgit v1.2.3 From b966c976903eecced5a16826ef2da07cb011809f Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 27 Apr 2020 14:46:15 +0200 Subject: mod_carbons: Check for and strip 'private' tag before stopping This was explicit previously --- plugins/mod_carbons.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/mod_carbons.lua b/plugins/mod_carbons.lua index 67f345f1..fad47a7c 100644 --- a/plugins/mod_carbons.lua +++ b/plugins/mod_carbons.lua @@ -92,13 +92,14 @@ local function message_handler(event, c2s) if not should then module:log("debug", "Not copying stanza: %s (%s)", stanza:top_tag(), why); - return; - elseif why == "private" and not c2s then + if why == "private" and not c2s then stanza:maptags(function(tag) if not ( tag.attr.xmlns == xmlns_carbons and tag.name == "private" ) then return tag; end end); + end + return; end local carbon; -- cgit v1.2.3 From edce14b4a40664a6f10239d7f2ef50421f6a93af Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 29 Apr 2020 22:23:05 +0200 Subject: mod_admin_telnet: Pretty-print values returned from commands This makes it much nicer to inspect Prosody internals. Existing textual status messages from commands are not serialized to preserve existing behavior. Explicit serialization of configuration is kept in order to make it clear that returned strings are serialized strings that would look like what's actually in the config file. The default maxdepth of 2 seems ought to be an okay default, balanced between showing enough structure to continue exploring and DoS-ing your terminal. Thanks to Ge0rG for the motivation to finally do this. --- plugins/mod_admin_telnet.lua | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 2efd424c..5407b737 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -32,7 +32,8 @@ local envload = require "util.envload".envload; local envloadfile = require "util.envload".envloadfile; local has_pposix, pposix = pcall(require, "util.pposix"); local async = require "util.async"; -local serialize = require "util.serialization".new({ fatal = false, unquoted = true}); +local serialization = require "util.serialization"; +local serialize_config = serialization.new ({ fatal = false, unquoted = true}); local time = require "util.time"; local commands = module:shared("commands") @@ -80,6 +81,7 @@ function console:new_session(conn) end w("| "..table.concat(t, "\t").."\n"); end; + serialize = serialization.new({ fatal = false, unquoted = true, maxdepth = 2}); disconnect = function () conn:close(); end; }; session.env = setmetatable({}, default_env_mt); @@ -141,7 +143,10 @@ function console:process_line(session, line) local taskok, message = chunk(); if not message then - session.print("Result: "..tostring(taskok)); + if type(taskok) ~= "string" then + taskok = session.serialize(taskok); + end + session.print("Result: "..taskok); return; elseif (not taskok) and message then session.print("Command completed with a problem"); @@ -149,7 +154,11 @@ function console:process_line(session, line) return; end - session.print("OK: "..tostring(message)); + if type(message) ~= "string" then + message = session.serialize(message); + end + + session.print("OK: "..message); end local sessions = {}; @@ -527,7 +536,7 @@ function def_env.config:get(host, key) host, key = "*", host; end local config_get = require "core.configmanager".get - return true, serialize(config_get(host, key)); + return true, serialize_config(config_get(host, key)); end function def_env.config:reload() -- cgit v1.2.3 From de35ba33a1347a42004b039bce2fc09a284e9dd6 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 29 Apr 2020 22:48:36 +0200 Subject: mod_admin_telnet: Document (in the internal help) debug commands --- plugins/mod_admin_telnet.lua | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 5407b737..71a9420b 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -251,6 +251,7 @@ function commands.help(session, data) 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 [[debug - Commands for debugging the server]] print [[config - Reloading the configuration, etc.]] print [[console - Help regarding the console itself]] elseif section == "c2s" then @@ -299,6 +300,10 @@ function commands.help(session, data) elseif section == "config" then print [[config:reload() - Reload the server configuration. Modules may need to be reloaded for changes to take effect.]] print [[config:get([host,] option) - Show the value of a config option.]] + elseif section == "debug" then + print [[debug:logevents(host) - Enable logging of fired events on host]] + print [[debug:events(host, event) - Show registered event handlers]] + print [[debug:timers() - Show information about scheduled timers]] elseif section == "console" then print [[Hey! Welcome to Prosody's admin console.]] print [[First thing, if you're ever wondering how to get out, simply type 'quit'.]] -- cgit v1.2.3 From 3da0521744c13023ebb07bd261154277d103ab25 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 29 Apr 2020 22:56:35 +0200 Subject: mod_admin_telnet: Document HTTP command in internal help --- plugins/mod_admin_telnet.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 71a9420b..96b5a97b 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -244,6 +244,7 @@ function commands.help(session, data) print [[]] print [[c2s - Commands to manage local client-to-server sessions]] print [[s2s - Commands to manage sessions between this server and others]] + print [[http - Commands to inspect HTTP services]] -- XXX plural but there is only one so far print [[module - Commands to load/reload/unload modules/plugins]] print [[host - Commands to activate, deactivate and list virtual hosts]] print [[user - Commands to create and delete users, and change their passwords]] @@ -267,6 +268,8 @@ function commands.help(session, data) print [[s2s:show_tls(domain) - Show TLS cipher info for encrypted sessions]] print [[s2s:close(from, to) - Close a connection from one domain to another]] print [[s2s:closeall(host) - Close all the incoming/outgoing s2s sessions to specified host]] + elseif section == "http" then + print [[http:list(hosts) - Show HTTP endpoints]] elseif section == "module" then print [[module:load(module, host) - Load the specified module on the specified host (or all hosts if none given)]] print [[module:reload(module, host) - The same, but unloads and loads the module (saving state if the module supports it)]] -- cgit v1.2.3 From c38264dd58ffae438f630380fed2996f4f515afe Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 29 Apr 2020 22:59:01 +0200 Subject: mod_admin_telnet: Add a TODO for someone to find in the future --- plugins/mod_admin_telnet.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 96b5a97b..a27f6d5c 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -303,6 +303,7 @@ function commands.help(session, data) elseif section == "config" then print [[config:reload() - Reload the server configuration. Modules may need to be reloaded for changes to take effect.]] print [[config:get([host,] option) - Show the value of a config option.]] + elseif section == "stats" then -- TODO describe how this works elseif section == "debug" then print [[debug:logevents(host) - Enable logging of fired events on host]] print [[debug:events(host, event) - Show registered event handlers]] -- cgit v1.2.3 From 222299d18c5524cc3ee344e6c24f13dfb4a876dd Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 29 Apr 2020 23:15:01 +0200 Subject: mod_admin_telnet: Add a command to configure pretty-printing settings Sometimes you wanna adjust the maxdepth or something. --- plugins/mod_admin_telnet.lua | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index a27f6d5c..d25a576a 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -330,6 +330,11 @@ end --luacheck: ignore 212/self +def_env.output = {}; +function def_env.output:configure(opts) + self.session.serialize = serialization.new(opts); +end + def_env.server = {}; function def_env.server:insane_reload() -- cgit v1.2.3 From 7369538232b41b98872b5bf45759fc19d6293727 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 29 Apr 2020 23:28:21 +0200 Subject: mod_admin_telnet: Silence luacheck --- plugins/mod_admin_telnet.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index d25a576a..dcc19687 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -303,7 +303,8 @@ function commands.help(session, data) elseif section == "config" then print [[config:reload() - Reload the server configuration. Modules may need to be reloaded for changes to take effect.]] print [[config:get([host,] option) - Show the value of a config option.]] - elseif section == "stats" then -- TODO describe how this works + elseif section == "stats" then -- luacheck: ignore 542 + -- TODO describe how stats:show() works elseif section == "debug" then print [[debug:logevents(host) - Enable logging of fired events on host]] print [[debug:events(host, event) - Show registered event handlers]] -- cgit v1.2.3 From 75a3d7758bda32792bb3abe412b12f8c85bf757f Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 2 May 2020 14:13:02 +0200 Subject: mod_storage_sql: Log database connection parameters when creating engine This is meant to help trace down an issue where Prosody apparently creates multiple conflicting SQL engines, causing problems especially with SQLite3, e.g. #616 #784. --- plugins/mod_storage_sql.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/mod_storage_sql.lua b/plugins/mod_storage_sql.lua index 21556715..30e38d49 100644 --- a/plugins/mod_storage_sql.lua +++ b/plugins/mod_storage_sql.lua @@ -759,9 +759,10 @@ function module.load() if prosody.prosodyctl then return; end local engines = module:shared("/*/sql/connections"); local params = normalize_params(module:get_option("sql", default_params)); - engine = engines[sql.db2uri(params)]; + local db_uri = sql.db2uri(params); + engine = engines[db_uri]; if not engine then - module:log("debug", "Creating new engine"); + module:log("debug", "Creating new engine %s", db_uri); engine = sql:create_engine(params, function (engine) -- luacheck: ignore 431/engine if module:get_option("sql_manage_tables", true) then -- Automatically create table, ignore failure (table probably already exists) -- cgit v1.2.3 From dc8d810f34ba55ec51a238a84c04673efb7fc7a3 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 2 May 2020 20:12:41 +0200 Subject: MUC: Enforce strict resourceprep when registering room nicknames If nickname enforcement is enabled this would otherwise let you bypass the join check in muc.lib by registering an invalid nickname and then joining with any nickname, letting register.lib change it to the invalid registered nick. --- plugins/muc/register.lib.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/muc/register.lib.lua b/plugins/muc/register.lib.lua index f0a15dd4..d917b396 100644 --- a/plugins/muc/register.lib.lua +++ b/plugins/muc/register.lib.lua @@ -152,7 +152,7 @@ local function handle_register_iq(room, origin, stanza) return true; end -- Is the nickname valid? - local desired_nick = resourceprep(reg_data["muc#register_roomnick"]); + local desired_nick = resourceprep(reg_data["muc#register_roomnick"], true); if not desired_nick then origin.send(st.error_reply(stanza, "modify", "bad-request", "Invalid Nickname")); return true; -- cgit v1.2.3 From bfdff4488f72dd5e3595968b2fdb14d054c3977c Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 2 May 2020 20:32:43 +0200 Subject: mod_admin_telnet: Allow configuring pretty printing defaults Mostly just to have the defaults merged so you can e.g. output:configure({maxdepth=1}) --- plugins/mod_admin_telnet.lua | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index dcc19687..93a4587f 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -330,9 +330,18 @@ end -- Anything in def_env will be accessible within the session as a global variable --luacheck: ignore 212/self +local serialize_defaults = module:get_option("console_prettyprint_settings", { fatal = false, unquoted = true, maxdepth = 2}) def_env.output = {}; function def_env.output:configure(opts) + if type(opts) ~= "table" then + opts = { preset = opts }; + end + for k,v in pairs(serialize_defaults) do + if opts[k] == nil then + opts[k] = v; + end + end self.session.serialize = serialization.new(opts); end -- cgit v1.2.3 From f1c4d468e2134147b6aec12910837d05b332b8d6 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 2 May 2020 20:37:49 +0200 Subject: mod_admin_telnet: Reuse existing pretty printing setup Didn't do the configurable defaults thing here because I was going to do this, so that there's only one spot where it's done. --- plugins/mod_admin_telnet.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 93a4587f..792f4f78 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -81,7 +81,7 @@ function console:new_session(conn) end w("| "..table.concat(t, "\t").."\n"); end; - serialize = serialization.new({ fatal = false, unquoted = true, maxdepth = 2}); + serialize = tostring; disconnect = function () conn:close(); end; }; session.env = setmetatable({}, default_env_mt); @@ -98,6 +98,8 @@ function console:new_session(conn) end end + session.env.output:configure(); + return session; end -- cgit v1.2.3 From 95b5facf3bc6ec7b346ea6dff07522fc9aceda4e Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 2 May 2020 20:39:33 +0200 Subject: mod_admin_telnet: Don't pretty-print the normal console stuff Typing e.g. `c2s` would dump out a bunch of stuff that would probably just confuse users. Now you only get pretty-printing when poking around in the internals with `>`. --- plugins/mod_admin_telnet.lua | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 792f4f78..a5657d09 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -145,10 +145,10 @@ function console:process_line(session, line) local taskok, message = chunk(); if not message then - if type(taskok) ~= "string" then + if type(taskok) ~= "string" and useglobalenv then taskok = session.serialize(taskok); end - session.print("Result: "..taskok); + session.print("Result: "..tostring(taskok)); return; elseif (not taskok) and message then session.print("Command completed with a problem"); @@ -156,11 +156,7 @@ function console:process_line(session, line) return; end - if type(message) ~= "string" then - message = session.serialize(message); - end - - session.print("OK: "..message); + session.print("OK: "..tostring(message)); end local sessions = {}; -- cgit v1.2.3 From 783f5430a57d1f543f0eddbc3d6b6a3185be8008 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 2 May 2020 20:41:35 +0200 Subject: mod_admin_telnet: Use tostring as fallback in pretty printing This has some nice effects such as functions, VirtualHosts and other things being printed using their `__tostring` metamethod. --- plugins/mod_admin_telnet.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index a5657d09..3014517c 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -335,6 +335,10 @@ function def_env.output:configure(opts) if type(opts) ~= "table" then opts = { preset = opts }; end + if not opts.fallback then + -- XXX Error message passed to fallback is lost, does it matter? + opts.fallback = tostring; + end for k,v in pairs(serialize_defaults) do if opts[k] == nil then opts[k] = v; -- cgit v1.2.3 From 8055c8f7d721ba1d8aa538255e61a1830377c49e Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 4 May 2020 21:51:30 +0200 Subject: util.pposix,signal: Pass around various OS numbers as integers [Lua 5.3] Passing around PIDs, UIDs etc as integers makes it more sane in Lua 5.3. Getting 1234.0 as PID is silly. Shouldn't change any behavior as these are all integers on the C side and the integral floats are accepted as integers when passed back from Lua into C. --- util-src/pposix.c | 17 ++++++++++------- util-src/signal.c | 45 ++++++++++++++++++++++++--------------------- 2 files changed, 34 insertions(+), 28 deletions(-) diff --git a/util-src/pposix.c b/util-src/pposix.c index 2ebf0444..42b553dd 100644 --- a/util-src/pposix.c +++ b/util-src/pposix.c @@ -61,6 +61,9 @@ #if (LUA_VERSION_NUM == 501) #define luaL_setfuncs(L, R, N) luaL_register(L, NULL, R) #endif +#if (LUA_VERSION_NUM < 503) +#define lua_isinteger(L, n) lua_isnumber(L, n) +#endif #include #if defined(__linux__) @@ -106,7 +109,7 @@ static int lc_daemonize(lua_State *L) { } else if(pid != 0) { /* We are the parent process */ lua_pushboolean(L, 1); - lua_pushnumber(L, pid); + lua_pushinteger(L, pid); return 2; } @@ -295,7 +298,7 @@ static int lc_setuid(lua_State *L) { return 0; } - if(!lua_isnumber(L, 1) && lua_tostring(L, 1)) { + if(!lua_isinteger(L, 1) && lua_tostring(L, 1)) { /* Passed UID is actually a string, so look up the UID */ struct passwd *p; p = getpwnam(lua_tostring(L, 1)); @@ -308,7 +311,7 @@ static int lc_setuid(lua_State *L) { uid = p->pw_uid; } else { - uid = lua_tonumber(L, 1); + uid = lua_tointeger(L, 1); } if(uid > -1) { @@ -353,7 +356,7 @@ static int lc_setgid(lua_State *L) { return 0; } - if(!lua_isnumber(L, 1) && lua_tostring(L, 1)) { + if(!lua_isinteger(L, 1) && lua_tostring(L, 1)) { /* Passed GID is actually a string, so look up the GID */ struct group *g; g = getgrnam(lua_tostring(L, 1)); @@ -366,7 +369,7 @@ static int lc_setgid(lua_State *L) { gid = g->gr_gid; } else { - gid = lua_tonumber(L, 1); + gid = lua_tointeger(L, 1); } if(gid > -1) { @@ -647,13 +650,13 @@ static int lc_getrlimit(lua_State *L) { if(lim.rlim_cur == RLIM_INFINITY) { lua_pushstring(L, "unlimited"); } else { - lua_pushnumber(L, lim.rlim_cur); + lua_pushinteger(L, lim.rlim_cur); } if(lim.rlim_max == RLIM_INFINITY) { lua_pushstring(L, "unlimited"); } else { - lua_pushnumber(L, lim.rlim_max); + lua_pushinteger(L, lim.rlim_max); } return 3; diff --git a/util-src/signal.c b/util-src/signal.c index 835a601a..1a398fa0 100644 --- a/util-src/signal.c +++ b/util-src/signal.c @@ -39,6 +39,9 @@ #if (LUA_VERSION_NUM == 501) #define luaL_setfuncs(L, R, N) luaL_register(L, NULL, R) #endif +#if (LUA_VERSION_NUM < 503) +#define lua_isinteger(L, n) lua_isnumber(L, n) +#endif #ifndef lsig @@ -176,7 +179,7 @@ static void sighook(lua_State *L, lua_Debug *ar) { lua_gettable(L, LUA_REGISTRYINDEX); for(int i = 0; i < nsig; i++) { - lua_pushnumber(L, signals[i]); + lua_pushinteger(L, signals[i]); lua_gettable(L, -2); lua_call(L, 0, 0); }; @@ -223,18 +226,18 @@ static int l_signal(lua_State *L) { t = lua_type(L, 1); if(t == LUA_TNUMBER) { - sig = (int) lua_tonumber(L, 1); + sig = (int) lua_tointeger(L, 1); } else if(t == LUA_TSTRING) { lua_pushstring(L, LUA_SIGNAL); lua_gettable(L, LUA_REGISTRYINDEX); lua_pushvalue(L, 1); lua_gettable(L, -2); - if(!lua_isnumber(L, -1)) { + if(!lua_isinteger(L, -1)) { return luaL_error(L, "invalid signal string"); } - sig = (int) lua_tonumber(L, -1); + sig = (int) lua_tointeger(L, -1); lua_pop(L, 1); /* get rid of number we pushed */ } else { luaL_checknumber(L, 1); /* will always error, with good error msg */ @@ -245,9 +248,9 @@ static int l_signal(lua_State *L) { if(args == 1 || lua_isnil(L, 2)) { /* clear handler */ lua_pushstring(L, LUA_SIGNAL); lua_gettable(L, LUA_REGISTRYINDEX); - lua_pushnumber(L, sig); + lua_pushinteger(L, sig); lua_gettable(L, -2); /* return old handler */ - lua_pushnumber(L, sig); + lua_pushinteger(L, sig); lua_pushnil(L); lua_settable(L, -4); lua_remove(L, -2); /* remove LUA_SIGNAL table */ @@ -258,7 +261,7 @@ static int l_signal(lua_State *L) { lua_pushstring(L, LUA_SIGNAL); lua_gettable(L, LUA_REGISTRYINDEX); - lua_pushnumber(L, sig); + lua_pushinteger(L, sig); lua_pushvalue(L, 2); lua_settable(L, -3); @@ -292,15 +295,15 @@ static int l_signal(lua_State *L) { static int l_raise(lua_State *L) { /* int args = lua_gettop(L); */ int t = 0; /* type */ - lua_Number ret; + lua_Integer ret; luaL_checkany(L, 1); t = lua_type(L, 1); if(t == LUA_TNUMBER) { - ret = (lua_Number) raise((int) lua_tonumber(L, 1)); - lua_pushnumber(L, ret); + ret = (lua_Integer) raise((int) lua_tointeger(L, 1)); + lua_pushinteger(L, ret); } else if(t == LUA_TSTRING) { lua_pushstring(L, LUA_SIGNAL); lua_gettable(L, LUA_REGISTRYINDEX); @@ -311,9 +314,9 @@ static int l_raise(lua_State *L) { return luaL_error(L, "invalid signal string"); } - ret = (lua_Number) raise((int) lua_tonumber(L, -1)); + ret = (lua_Integer) raise((int) lua_tointeger(L, -1)); lua_pop(L, 1); /* get rid of number we pushed */ - lua_pushnumber(L, ret); + lua_pushinteger(L, ret); } else { luaL_checknumber(L, 1); /* will always error, with good error msg */ } @@ -334,7 +337,7 @@ static int l_raise(lua_State *L) { static int l_kill(lua_State *L) { int t; /* type */ - lua_Number ret; /* return value */ + lua_Integer ret; /* return value */ luaL_checknumber(L, 1); /* must be int for pid */ luaL_checkany(L, 2); /* check for a second arg */ @@ -342,9 +345,9 @@ static int l_kill(lua_State *L) { t = lua_type(L, 2); if(t == LUA_TNUMBER) { - ret = (lua_Number) kill((int) lua_tonumber(L, 1), - (int) lua_tonumber(L, 2)); - lua_pushnumber(L, ret); + ret = (lua_Integer) kill((int) lua_tointeger(L, 1), + (int) lua_tointeger(L, 2)); + lua_pushinteger(L, ret); } else if(t == LUA_TSTRING) { lua_pushstring(L, LUA_SIGNAL); lua_gettable(L, LUA_REGISTRYINDEX); @@ -355,10 +358,10 @@ static int l_kill(lua_State *L) { return luaL_error(L, "invalid signal string"); } - ret = (lua_Number) kill((int) lua_tonumber(L, 1), - (int) lua_tonumber(L, -1)); + ret = (lua_Integer) kill((int) lua_tointeger(L, 1), + (int) lua_tointeger(L, -1)); lua_pop(L, 1); /* get rid of number we pushed */ - lua_pushnumber(L, ret); + lua_pushinteger(L, ret); } else { luaL_checknumber(L, 2); /* will always error, with good error msg */ } @@ -396,11 +399,11 @@ int luaopen_util_signal(lua_State *L) { while(lua_signals[i].name != NULL) { /* registry table */ lua_pushstring(L, lua_signals[i].name); - lua_pushnumber(L, lua_signals[i].sig); + lua_pushinteger(L, lua_signals[i].sig); lua_settable(L, -3); /* signal table */ lua_pushstring(L, lua_signals[i].name); - lua_pushnumber(L, lua_signals[i].sig); + lua_pushinteger(L, lua_signals[i].sig); lua_settable(L, -5); i++; } -- cgit v1.2.3 From d146cc6f582196d9d9a79b6d3c2bf030cc26f7b2 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 5 May 2020 22:21:39 +0200 Subject: MAM: Remove 1% of contents when reaching limits, fix #1545 With mod\_storage\_internal this counts out to 100 messages out of 10 000, meaning should not hit the quota limit immediately until that many messages have been added again. --- plugins/mod_mam/mod_mam.lua | 4 +++- plugins/mod_muc_mam.lua | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/plugins/mod_mam/mod_mam.lua b/plugins/mod_mam/mod_mam.lua index 72b7639a..8695fe65 100644 --- a/plugins/mod_mam/mod_mam.lua +++ b/plugins/mod_mam/mod_mam.lua @@ -44,6 +44,8 @@ local archive = module:open_store(archive_store, "archive"); local cleanup_after = module:get_option_string("archive_expires_after", "1w"); local cleanup_interval = module:get_option_number("archive_cleanup_interval", 4 * 60 * 60); local archive_item_limit = module:get_option_number("storage_archive_item_limit", archive.caps and archive.caps.quota or 1000); +local archive_truncate = math.floor(archive_item_limit * 0.99); + if not archive.find then error("mod_"..(archive._provided_by or archive.name and "storage_"..archive.name).." does not support archiving\n" .."See https://prosody.im/doc/storage and https://prosody.im/doc/archiving for more information"); @@ -379,7 +381,7 @@ local function message_handler(event, c2s) if not ok and (archive.caps and archive.caps.truncate) then module:log("debug", "User '%s' over quota, truncating archive", store_user); local truncated = archive:delete(store_user, { - truncate = archive_item_limit - 1; + truncate = archive_truncate; }); if truncated then ok, err = archive:append(store_user, nil, clone_for_storage, time, with); diff --git a/plugins/mod_muc_mam.lua b/plugins/mod_muc_mam.lua index 7eeada6d..a151931f 100644 --- a/plugins/mod_muc_mam.lua +++ b/plugins/mod_muc_mam.lua @@ -54,6 +54,7 @@ local archive_store = "muc_log"; local archive = module:open_store(archive_store, "archive"); local archive_item_limit = module:get_option_number("storage_archive_item_limit", archive.caps and archive.caps.quota or 1000); +local archive_truncate = math.floor(archive_item_limit * 0.99); if archive.name == "null" or not archive.find then if not archive.find then @@ -397,7 +398,7 @@ local function save_to_history(self, stanza) if not id and (archive.caps and archive.caps.truncate) then module:log("debug", "User '%s' over quota, truncating archive", room_node); local truncated = archive:delete(room_node, { - truncate = archive_item_limit - 1; + truncate = archive_truncate; }); if truncated then id, err = archive:append(room_node, nil, stored_stanza, time, with); -- cgit v1.2.3 From 9339ebb8e37a72fae02bd5d928f7ce22beff1a80 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 5 May 2020 23:08:47 +0200 Subject: mod_csi_simple: Don't consider presence errors as important A large share of `` appears to be noise from large public channels and failed presence probes. The later at least should count as presence updates, which are currently considered unimportant. See also 8cecb85e4bc4 which is partly reverted here. The intent there was probably mostly about message (delivery) errors, which should be considered important. --- plugins/mod_csi_simple.lua | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/plugins/mod_csi_simple.lua b/plugins/mod_csi_simple.lua index d3bf3a9f..05650957 100644 --- a/plugins/mod_csi_simple.lua +++ b/plugins/mod_csi_simple.lua @@ -28,18 +28,19 @@ module:hook("csi-is-stanza-important", function (event) local st_name = stanza.name; if not st_name then return false; end local st_type = stanza.attr.type; - if st_type == "error" then - return true; - end if st_name == "presence" then - if st_type == nil or st_type == "unavailable" then + if st_type == nil or st_type == "unavailable" or st_name == "error" then return false; end + -- TODO Some MUC awareness, e.g. check for the 'this relates to you' status code return true; elseif st_name == "message" then if st_type == "headline" then return false; end + if st_type == "error" then + return true; + end if stanza:get_child("sent", "urn:xmpp:carbons:2") then return true; end -- cgit v1.2.3 From 92c97b036113d8b608fd3c85e7deb8712c7004e6 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 6 May 2020 12:48:09 +0200 Subject: mod_carbons: Clarify handling of error bounces The :find bit was hard to understand, this should be clearer. --- plugins/mod_carbons.lua | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/plugins/mod_carbons.lua b/plugins/mod_carbons.lua index fad47a7c..f07e9356 100644 --- a/plugins/mod_carbons.lua +++ b/plugins/mod_carbons.lua @@ -5,10 +5,15 @@ local st = require "util.stanza"; local jid_bare = require "util.jid".bare; +local jid_resource = require "util.jid".resource; local xmlns_carbons = "urn:xmpp:carbons:2"; local xmlns_forward = "urn:xmpp:forward:0"; local full_sessions, bare_sessions = prosody.full_sessions, prosody.bare_sessions; +local function is_bare(jid) + return not jid_resource(jid); +end + local function toggle_carbons(event) local origin, stanza = event.origin, event.stanza; local state = stanza.tags[1].name; @@ -43,7 +48,10 @@ local function should_copy(stanza, c2s, user_bare) return true, "type"; end - if st_type == "error" and not c2s and not (stanza.attr.from or ""):find("/") then + -- Normal outgoing chat messages are sent to=bare JID. This clause should + -- match the error bounces from those, which would have from=bare JID and + -- be incoming (not c2s). + if st_type == "error" and not c2s and is_bare(stanza.attr.from) then return true, "bounce"; end -- cgit v1.2.3 From 7d924c49ac817353980f9505ed83845ce0b8377c Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 6 May 2020 18:03:20 +0200 Subject: net.http: Return a Promise if no callback is given --- net/http.lua | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/net/http.lua b/net/http.lua index 8ca30db2..5fbed4fe 100644 --- a/net/http.lua +++ b/net/http.lua @@ -12,6 +12,8 @@ local httpstream_new = require "net.http.parser".new; local util_http = require "util.http"; local events = require "util.events"; local verify_identity = require"util.x509".verify_identity; +local promise = require "util.promise"; +local errors = require "util.error"; local basic_resolver = require "net.resolvers.basic"; local connect = require "net.connect".connect; @@ -270,7 +272,21 @@ end local function new(options) local http = { options = options; - request = request; + request = function (self, u, ex, callback) + if callback ~= nil then + return request(self, u, ex, callback); + else + return promise.new(function (resolve, reject) + request(self, u, ex, function (body, code, a, b) + if code == 0 then + reject(errors.new(body, { request = a })); + else + resolve({ request = b, response = a }); + end + end); + end); + end + end; new = options and function (new_options) local final_options = {}; for k, v in pairs(options) do final_options[k] = v; end -- cgit v1.2.3 From 8da81e9160c093c79754eb7189a2a9fced8df82a Mon Sep 17 00:00:00 2001 From: JC Brand Date: Wed, 22 Apr 2020 16:04:03 +0200 Subject: Fixes #1533 Hats don't get sent out to own MUC user --- plugins/muc/muc.lib.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index d785a5a2..65ed8731 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -312,6 +312,7 @@ function room_mt:publicise_occupant_status(occupant, x, nick, actor, reason, pre else -- use their own presences as templates for full_jid, pr in occupant:each_session() do + module:fire_event("muc-build-occupant-presence", { room = self, occupant = occupant, stanza = pr }); pr = st.clone(pr); pr.attr.to = full_jid; pr:add_child(self_x); -- cgit v1.2.3 From b00737a6d32cf4e834cb239172280762e4d620f6 Mon Sep 17 00:00:00 2001 From: JC Brand Date: Wed, 22 Apr 2020 16:12:15 +0200 Subject: mod_muc: let event handlers modify cloned presence Updates #1533 --- plugins/muc/muc.lib.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 65ed8731..2f72c9eb 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -312,8 +312,8 @@ function room_mt:publicise_occupant_status(occupant, x, nick, actor, reason, pre else -- use their own presences as templates for full_jid, pr in occupant:each_session() do - module:fire_event("muc-build-occupant-presence", { room = self, occupant = occupant, stanza = pr }); pr = st.clone(pr); + module:fire_event("muc-build-occupant-presence", { room = self, occupant = occupant, stanza = pr }); pr.attr.to = full_jid; pr:add_child(self_x); self:route_stanza(pr); -- cgit v1.2.3 From 44847d620cf0b7f690682e4906dfcbea8cd3ef03 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 7 May 2020 21:55:29 +0200 Subject: mod_csi_simple: Refactor to allow logging reason for buffer flush Same style as mod_mam and mod_carbons allows easy comparison. BC: Log format changes --- plugins/mod_csi_simple.lua | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/plugins/mod_csi_simple.lua b/plugins/mod_csi_simple.lua index 05650957..7b8b3741 100644 --- a/plugins/mod_csi_simple.lua +++ b/plugins/mod_csi_simple.lua @@ -15,8 +15,7 @@ local queue_size = module:get_option_number("csi_queue_size", 256); local important_payloads = module:get_option_set("csi_important_payloads", { }); -module:hook("csi-is-stanza-important", function (event) - local stanza = event.stanza; +function is_important(stanza) --> boolean, reason: string if not st.is_stanza(stanza) then -- whitespace pings etc return true; @@ -69,8 +68,23 @@ module:hook("csi-is-stanza-important", function (event) elseif st_name == "iq" then return true; end +end + +module:hook("csi-is-stanza-important", function (event) + local important, why = is_important(event.stanza); + event.reason = why; + return important; end, -1); +local function should_flush(stanza, session, ctr) --> boolean, reason: string + if ctr >= queue_size then + return true, "queue size limit reached"; + end + local event = { stanza = stanza, session = session }; + local ret = module:fire_event("csi-is-stanza-important", event) + return ret, event.reason; +end + local function with_timestamp(stanza, from) if st.is_stanza(stanza) and stanza.attr.xmlns == nil and stanza.name ~= "iq" then stanza = st.clone(stanza); @@ -81,11 +95,9 @@ end local function manage_buffer(stanza, session) local ctr = session.csi_counter or 0; - if ctr >= queue_size then - session.log("debug", "Queue size limit hit, flushing buffer (queue size is %d)", session.csi_counter); - session.conn:resume_writes(); - elseif module:fire_event("csi-is-stanza-important", { stanza = stanza, session = session }) then - session.log("debug", "Important stanza, flushing buffer (queue size is %d)", session.csi_counter); + local flush, why = should_flush(stanza, session, ctr); + if flush then + session.log("debug", "Flushing buffer (%s; queue size is %d)", why or "important", session.csi_counter); session.conn:resume_writes(); else stanza = with_timestamp(stanza, jid.join(session.username, session.host)) -- cgit v1.2.3 From a9ba50343ca6fc47d8bf50b0714ca943f38b1410 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 7 May 2020 22:56:30 +0200 Subject: mod_csi_simple: Add short reasons to report Should improve quality of debug logs --- plugins/mod_csi_simple.lua | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/plugins/mod_csi_simple.lua b/plugins/mod_csi_simple.lua index 7b8b3741..6274dcb3 100644 --- a/plugins/mod_csi_simple.lua +++ b/plugins/mod_csi_simple.lua @@ -22,42 +22,46 @@ function is_important(stanza) --> boolean, reason: string end if stanza.attr.xmlns ~= nil then -- stream errors, stream management etc - return true; + return true, "nonza"; end local st_name = stanza.name; if not st_name then return false; end local st_type = stanza.attr.type; if st_name == "presence" then if st_type == nil or st_type == "unavailable" or st_name == "error" then - return false; + return false, "presence update"; end -- TODO Some MUC awareness, e.g. check for the 'this relates to you' status code - return true; + return true, "subscription request"; elseif st_name == "message" then if st_type == "headline" then - return false; + -- Headline messages are ephemeral by definition + return false, "headline"; end if st_type == "error" then - return true; + return true, "delivery failure"; end if stanza:get_child("sent", "urn:xmpp:carbons:2") then - return true; + return true, "carbon"; end local forwarded = stanza:find("{urn:xmpp:carbons:2}received/{urn:xmpp:forward:0}/{jabber:client}message"); if forwarded then stanza = forwarded; end if stanza:get_child("body") then - return true; + return true, "body"; end if stanza:get_child("subject") then - return true; + -- Last step of a MUC join + return true, "subject"; end if stanza:get_child("encryption", "urn:xmpp:eme:0") then - return true; + -- Since we can't know what an encrypted message contains, we assume it's important + -- XXX Experimental XEP + return true, "encrypted"; end if stanza:get_child("x", "jabber:x:conference") or stanza:find("{http://jabber.org/protocol/muc#user}x/invite") then - return true; + return true, "invite"; end for important in important_payloads do if stanza:find(important) then -- cgit v1.2.3 From b2baba11247243446e60bea10884d2d2193e9f48 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 7 May 2020 23:02:47 +0200 Subject: mod_csi_simple: Log reasons for not flushing --- plugins/mod_csi_simple.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/mod_csi_simple.lua b/plugins/mod_csi_simple.lua index 6274dcb3..bb8f757a 100644 --- a/plugins/mod_csi_simple.lua +++ b/plugins/mod_csi_simple.lua @@ -104,6 +104,7 @@ local function manage_buffer(stanza, session) session.log("debug", "Flushing buffer (%s; queue size is %d)", why or "important", session.csi_counter); session.conn:resume_writes(); else + session.log("debug", "Holding buffer (%s; queue size is %d)", why or "unimportant", session.csi_counter); stanza = with_timestamp(stanza, jid.join(session.username, session.host)) end session.csi_counter = ctr + 1; -- cgit v1.2.3 From 289898e68f98fe5edfb31ae622c9c3f0d6e1e039 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 8 May 2020 23:54:17 +0200 Subject: mod_s2s: Improve signaling of stream open events Makes it clearer, cleaner and easier to extend. --- plugins/mod_s2s/mod_s2s.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/mod_s2s/mod_s2s.lua b/plugins/mod_s2s/mod_s2s.lua index 8feb6403..2dad864c 100644 --- a/plugins/mod_s2s/mod_s2s.lua +++ b/plugins/mod_s2s/mod_s2s.lua @@ -322,7 +322,7 @@ local xmlns_xmpp_streams = "urn:ietf:params:xml:ns:xmpp-streams"; function stream_callbacks.streamopened(session, attr) -- run _streamopened in async context - session.thread:run({ attr = attr }); + session.thread:run({ stream = "opened", attr = attr }); end function stream_callbacks._streamopened(session, attr) @@ -564,10 +564,10 @@ local function initialize_session(session) local stream = new_xmpp_stream(session, stream_callbacks); session.thread = runner(function (stanza) - if stanza.name == nil then - stream_callbacks._streamopened(session, stanza.attr); - else + if st.is_stanza(stanza) then core_process_stanza(session, stanza); + elseif stanza.stream == "opened" then + stream_callbacks._streamopened(session, stanza.attr); end end, runner_callbacks, session); -- cgit v1.2.3 From a7c0def27f214441fe4881e119194540d291fd75 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 8 May 2020 23:55:51 +0200 Subject: mod_s2s: Run stream close in async context Allows async processing during stream shutdown. Fixes potential ASYNC-01 issues, however no such issues known at the time of this commit. --- plugins/mod_s2s/mod_s2s.lua | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/plugins/mod_s2s/mod_s2s.lua b/plugins/mod_s2s/mod_s2s.lua index 2dad864c..9f7d4d6d 100644 --- a/plugins/mod_s2s/mod_s2s.lua +++ b/plugins/mod_s2s/mod_s2s.lua @@ -455,11 +455,16 @@ function stream_callbacks._streamopened(session, attr) end end -function stream_callbacks.streamclosed(session) +function stream_callbacks._streamclosed(session) (session.log or log)("debug", "Received "); session:close(false); end +function stream_callbacks.streamclosed(session, attr) + -- run _streamclosed in async context + session.thread:run({ stream = "closed", attr = attr }); +end + function stream_callbacks.error(session, error, data) if error == "no-stream" then session.log("debug", "Invalid opening stream header (%s)", (data:gsub("^([^\1]+)\1", "{%1}"))); @@ -568,6 +573,8 @@ local function initialize_session(session) core_process_stanza(session, stanza); elseif stanza.stream == "opened" then stream_callbacks._streamopened(session, stanza.attr); + elseif stanza.stream == "closed" then + stream_callbacks._streamclosed(session, stanza.attr); end end, runner_callbacks, session); -- cgit v1.2.3 From 0747cbea53bce8930724c4d7de735c07e36e6893 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 8 May 2020 23:58:24 +0200 Subject: mod_c2s: Run stream open and close events in async thread, fixes #1103 Enables async processing during stream opening and closing. --- plugins/mod_c2s.lua | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/plugins/mod_c2s.lua b/plugins/mod_c2s.lua index 536b945e..91e37c4a 100644 --- a/plugins/mod_c2s.lua +++ b/plugins/mod_c2s.lua @@ -55,6 +55,11 @@ end); local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'}; function stream_callbacks.streamopened(session, attr) + -- run _streamopened in async context + session.thread:run({ stream = "opened", attr = attr }); +end + +function stream_callbacks._streamopened(session, attr) local send = session.send; if not attr.to then session:close{ condition = "improper-addressing", @@ -121,7 +126,12 @@ function stream_callbacks.streamopened(session, attr) end end -function stream_callbacks.streamclosed(session) +function stream_callbacks.streamclosed(session, attr) + -- run _streamclosed in async context + session.thread:run({ stream = "closed", attr = attr }); +end + +function stream_callbacks._streamclosed(session) session.log("debug", "Received "); session:close(false); end @@ -280,7 +290,13 @@ function listener.onconnect(conn) end session.thread = runner(function (stanza) - core_process_stanza(session, stanza); + if st.is_stanza(stanza) then + core_process_stanza(session, stanza); + elseif stanza.stream == "opened" then + stream_callbacks._streamopened(session, stanza.attr); + elseif stanza.stream == "closed" then + stream_callbacks._streamclosed(session, stanza.attr); + end end, runner_callbacks, session); local filter = session.filter; -- cgit v1.2.3 From 6b456feb2939af36b454f8b53b0bda32f3aa7613 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 9 May 2020 00:07:42 +0200 Subject: spec/scansion/blocking: Don't send stanzas after disconnecting Probably casued by mod_scansion_record catching the unavailable presence generated by Prosody on disconnect. See #1549 --- spec/scansion/blocking.scs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/scansion/blocking.scs b/spec/scansion/blocking.scs index 6a9f199e..5bb5a41b 100644 --- a/spec/scansion/blocking.scs +++ b/spec/scansion/blocking.scs @@ -145,16 +145,16 @@ Juliet receives: # Bye -Juliet disconnects - Juliet sends: +Juliet disconnects + Romeo receives: -Romeo disconnects - Romeo sends: +Romeo disconnects + -- cgit v1.2.3 From 71d6bde69ce86526dda8b0ee7c14b3418c415a7f Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 9 May 2020 00:28:10 +0200 Subject: mod_presence: Send unavailable presence in current thread run `session:dispatch_stanza(pres)` enqueues processing of the stanza in the sessions async thread, but becasue the entire stream close handling is now in that thread it would process the presence after the stream and session was completely closed, leading to weird errors "sent to a resting session". We call core_process_stanza() since this is what :dispatch_stanza calls in the end. --- plugins/mod_presence.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/mod_presence.lua b/plugins/mod_presence.lua index d6e2b2b7..3f9a0c12 100644 --- a/plugins/mod_presence.lua +++ b/plugins/mod_presence.lua @@ -14,6 +14,7 @@ local s_find = string.find; local tonumber = tonumber; local core_post_stanza = prosody.core_post_stanza; +local core_process_stanza = prosody.core_process_stanza; local st = require "util.stanza"; local jid_split = require "util.jid".split; local jid_bare = require "util.jid".bare; @@ -370,7 +371,7 @@ module:hook("resource-unbind", function(event) if err then pres:tag("status"):text("Disconnected: "..err):up(); end - session:dispatch_stanza(pres); + core_process_stanza(session, pres); elseif session.directed then local pres = st.presence{ type = "unavailable", from = session.full_jid }; if err then -- cgit v1.2.3 From 527d16b0b372a6103e23c79eb3cb9ed1588d311e Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 9 May 2020 00:50:59 +0200 Subject: mod_carbons: Describe return types in a comment For similarity with mod_mam, mod_csi_simple --- plugins/mod_carbons.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_carbons.lua b/plugins/mod_carbons.lua index f07e9356..ccd16ad5 100644 --- a/plugins/mod_carbons.lua +++ b/plugins/mod_carbons.lua @@ -25,7 +25,7 @@ end module:hook("iq-set/self/"..xmlns_carbons..":disable", toggle_carbons); module:hook("iq-set/self/"..xmlns_carbons..":enable", toggle_carbons); -local function should_copy(stanza, c2s, user_bare) +local function should_copy(stanza, c2s, user_bare) --> boolean, reason: string local st_type = stanza.attr.type or "normal"; if stanza:get_child("private", xmlns_carbons) then return false, "private"; -- cgit v1.2.3 From 3d5273454b79de0f6c9809cce5ed91f439a91e9f Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 9 May 2020 00:52:07 +0200 Subject: doap: Add XEP-0353 since mod_csi_simple is aware of it --- doc/doap.xml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/doc/doap.xml b/doc/doap.xml index 540cbe51..fa3f8d91 100644 --- a/doc/doap.xml +++ b/doc/doap.xml @@ -507,6 +507,14 @@ 0.11
+ + + + 0.3 + 0.11.6 + triggers buffer flush in mod_csi_simple since 0.11.6 + + -- cgit v1.2.3 From 4443c44e22a58b5cd2d2f16f9748648d5bd8a7e9 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 9 May 2020 00:55:18 +0200 Subject: mod_carbons: Explicitly carbon XEP-0353: Jingle Message Initiation --- doc/doap.xml | 2 +- plugins/mod_carbons.lua | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/doc/doap.xml b/doc/doap.xml index fa3f8d91..0befc21c 100644 --- a/doc/doap.xml +++ b/doc/doap.xml @@ -512,7 +512,7 @@ 0.3 0.11.6 - triggers buffer flush in mod_csi_simple since 0.11.6 + triggers buffer flush in mod_csi_simple since 0.11.6; recognised by mod_carbons since 0.12 diff --git a/plugins/mod_carbons.lua b/plugins/mod_carbons.lua index ccd16ad5..e0f0a711 100644 --- a/plugins/mod_carbons.lua +++ b/plugins/mod_carbons.lua @@ -55,6 +55,11 @@ local function should_copy(stanza, c2s, user_bare) --> boolean, reason: string return true, "bounce"; end + if stanza:get_child(nil, "urn:xmpp:jingle-message:0") then + -- XXX Experimental XEP stuck in Proposed for almost a year at the time of this comment + return true, "jingle call"; + end + for archived in stanza:childtags("stanza-id", "urn:xmpp:sid:0") do if archived and archived.attr.by == user_bare then return true, "archived"; -- cgit v1.2.3 From 1f8daf736d2ebfee0222b2bbccad5ed41eaf16a8 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 9 May 2020 00:57:42 +0200 Subject: mod_mam: Archive XEP-0353: Jingle Message Initiation --- doc/doap.xml | 2 +- plugins/mod_mam/mod_mam.lua | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/doc/doap.xml b/doc/doap.xml index 0befc21c..df7b4a26 100644 --- a/doc/doap.xml +++ b/doc/doap.xml @@ -512,7 +512,7 @@ 0.3 0.11.6 - triggers buffer flush in mod_csi_simple since 0.11.6; recognised by mod_carbons since 0.12 + triggers buffer flush in mod_csi_simple since 0.11.6; recognised by mod_carbons and mod_mam since 0.12
diff --git a/plugins/mod_mam/mod_mam.lua b/plugins/mod_mam/mod_mam.lua index 8695fe65..4fe714eb 100644 --- a/plugins/mod_mam/mod_mam.lua +++ b/plugins/mod_mam/mod_mam.lua @@ -315,6 +315,10 @@ local function should_store(stanza, c2s) --> boolean, reason: string or stanza:find("{http://jabber.org/protocol/muc#user}x/invite") then return true, "invite"; end + if stanza:get_child(nil, "urn:xmpp:jingle-message:0") then + -- XXX Experimental XEP stuck in Proposed for almost a year at the time of this comment + return true, "jingle call"; + end -- The IM-NG thing to do here would be to return `not st_to_full` -- One day ... -- cgit v1.2.3 From fc8a50cd73d43df270a88710d26fec550e33752e Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 9 May 2020 01:01:01 +0200 Subject: mod_csi_simple: Fix unintentional order of rules from merge --- plugins/mod_csi_simple.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/mod_csi_simple.lua b/plugins/mod_csi_simple.lua index 3bad872d..a62b7841 100644 --- a/plugins/mod_csi_simple.lua +++ b/plugins/mod_csi_simple.lua @@ -63,14 +63,14 @@ function is_important(stanza) --> boolean, reason: string if stanza:get_child("x", "jabber:x:conference") or stanza:find("{http://jabber.org/protocol/muc#user}x/invite") then return true, "invite"; end + if stanza:get_child(nil, "urn:xmpp:jingle-message:0") then + return true, "jingle call"; + end for important in important_payloads do if stanza:find(important) then return true; end end - if stanza:get_child(nil, "urn:xmpp:jingle-message:0") then - return true; - end return false; elseif st_name == "iq" then return true; -- cgit v1.2.3 From ade3caf1ad39b977ea3233238ba86a1f8635a334 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 9 May 2020 01:01:03 +0200 Subject: mod_csi_simple: Add comment highlighting that XEP-0353 is experimental To make it easier to find implemented Experimental XEPs later. Also at the time of this commit it has been Proposed as mentinoed in the comment but hopefully that will be resolved soon. --- plugins/mod_csi_simple.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/mod_csi_simple.lua b/plugins/mod_csi_simple.lua index a62b7841..2c3b3042 100644 --- a/plugins/mod_csi_simple.lua +++ b/plugins/mod_csi_simple.lua @@ -64,6 +64,7 @@ function is_important(stanza) --> boolean, reason: string return true, "invite"; end if stanza:get_child(nil, "urn:xmpp:jingle-message:0") then + -- XXX Experimental XEP stuck in Proposed for almost a year at the time of this comment return true, "jingle call"; end for important in important_payloads do -- cgit v1.2.3 From 749ed917c1c1a5d34ffb92b0da4cf1cc3f22cf50 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 9 May 2020 13:33:26 +0200 Subject: mod_csi_simple: Fix treating presence errors as presence updates Autocomplete fail probably. --- plugins/mod_csi_simple.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_csi_simple.lua b/plugins/mod_csi_simple.lua index 2c3b3042..8c6f75c8 100644 --- a/plugins/mod_csi_simple.lua +++ b/plugins/mod_csi_simple.lua @@ -28,7 +28,7 @@ function is_important(stanza) --> boolean, reason: string if not st_name then return false; end local st_type = stanza.attr.type; if st_name == "presence" then - if st_type == nil or st_type == "unavailable" or st_name == "error" then + if st_type == nil or st_type == "unavailable" or st_type == "error" then return false, "presence update"; end -- TODO Some MUC awareness, e.g. check for the 'this relates to you' status code -- cgit v1.2.3 From cabd89913a6d1101defb663f964116c6ec9ba37b Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 9 May 2020 13:50:58 +0200 Subject: mod_csi_simple: Fix flushing when client sent something Forgot to unset the flag afterwards, so it would only work once. The flag is not even needed, it works as intended without it. --- plugins/mod_csi_simple.lua | 5 ----- 1 file changed, 5 deletions(-) diff --git a/plugins/mod_csi_simple.lua b/plugins/mod_csi_simple.lua index 8c6f75c8..a44237d1 100644 --- a/plugins/mod_csi_simple.lua +++ b/plugins/mod_csi_simple.lua @@ -116,10 +116,6 @@ local function manage_buffer(stanza, session) end local function flush_buffer(data, session) - if session.csi_flushing then - return data; - end - session.csi_flushing = true; session.log("debug", "Client sent something, flushing buffer once (queue size is %d)", session.csi_counter); session.conn:resume_writes(); return data; @@ -136,7 +132,6 @@ function enable_optimizations(session) end function disable_optimizations(session) - session.csi_flushing = nil; filters.remove_filter(session, "stanzas/out", manage_buffer); filters.remove_filter(session, "bytes/in", flush_buffer); if session.conn and session.conn.resume_writes then -- cgit v1.2.3 From 859c3650e5ad8a3fd101154a5f435ca2589ca4d6 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 9 May 2020 17:05:46 +0200 Subject: mod_csi_simple: Forget queue counter when disabling optimizations Otherwise it might not start from zero when enabled again. --- plugins/mod_csi_simple.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/mod_csi_simple.lua b/plugins/mod_csi_simple.lua index a44237d1..fb3d80c5 100644 --- a/plugins/mod_csi_simple.lua +++ b/plugins/mod_csi_simple.lua @@ -134,6 +134,7 @@ end function disable_optimizations(session) filters.remove_filter(session, "stanzas/out", manage_buffer); filters.remove_filter(session, "bytes/in", flush_buffer); + session.csi_counter = nil; if session.conn and session.conn.resume_writes then session.conn:resume_writes(); end -- cgit v1.2.3 From e1a3982654d6f10ebaa4b6efda5bf72d55c40413 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 9 May 2020 17:07:43 +0200 Subject: mod_csi_simple: Reset queue counter to zero when enabling For symmetry. --- plugins/mod_csi_simple.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/mod_csi_simple.lua b/plugins/mod_csi_simple.lua index fb3d80c5..a4ce258b 100644 --- a/plugins/mod_csi_simple.lua +++ b/plugins/mod_csi_simple.lua @@ -124,6 +124,7 @@ end function enable_optimizations(session) if session.conn and session.conn.pause_writes then session.conn:pause_writes(); + session.csi_counter = 0; filters.add_filter(session, "stanzas/out", manage_buffer); filters.add_filter(session, "bytes/in", flush_buffer); else -- cgit v1.2.3 From 4053cca7dc5b7356cbf53a0ee4e42420000cef35 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 9 May 2020 17:38:13 +0200 Subject: mod_csi_simple: Change debug message of client-triggered flush for coherence It now matches other the other source of flush reason logging. --- plugins/mod_csi_simple.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_csi_simple.lua b/plugins/mod_csi_simple.lua index a4ce258b..0678b5d4 100644 --- a/plugins/mod_csi_simple.lua +++ b/plugins/mod_csi_simple.lua @@ -116,7 +116,7 @@ local function manage_buffer(stanza, session) end local function flush_buffer(data, session) - session.log("debug", "Client sent something, flushing buffer once (queue size is %d)", session.csi_counter); + session.log("debug", "Flushing buffer (%s; queue size is %d)", "client activity", session.csi_counter); session.conn:resume_writes(); return data; end -- cgit v1.2.3 From edd59660621f044d6402966a4a77ca9867d1f619 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 9 May 2020 17:45:45 +0200 Subject: mod_csi_simple: Record stats of how long buffers are held Telnet command `stats:show("buffer_hold"):histogram()` looks nice! --- plugins/mod_csi_simple.lua | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/plugins/mod_csi_simple.lua b/plugins/mod_csi_simple.lua index 0678b5d4..c0871c8d 100644 --- a/plugins/mod_csi_simple.lua +++ b/plugins/mod_csi_simple.lua @@ -101,10 +101,16 @@ local function with_timestamp(stanza, from) return stanza; end +local measure_buffer_hold = module:measure("buffer_hold", "times"); + local function manage_buffer(stanza, session) local ctr = session.csi_counter or 0; local flush, why = should_flush(stanza, session, ctr); if flush then + if session.csi_measure_buffer_hold then + session.csi_measure_buffer_hold(); + session.csi_measure_buffer_hold = nil; + end session.log("debug", "Flushing buffer (%s; queue size is %d)", why or "important", session.csi_counter); session.conn:resume_writes(); else @@ -117,6 +123,10 @@ end local function flush_buffer(data, session) session.log("debug", "Flushing buffer (%s; queue size is %d)", "client activity", session.csi_counter); + if session.csi_measure_buffer_hold then + session.csi_measure_buffer_hold(); + session.csi_measure_buffer_hold = nil; + end session.conn:resume_writes(); return data; end @@ -124,6 +134,7 @@ end function enable_optimizations(session) if session.conn and session.conn.pause_writes then session.conn:pause_writes(); + session.csi_measure_buffer_hold = measure_buffer_hold(); session.csi_counter = 0; filters.add_filter(session, "stanzas/out", manage_buffer); filters.add_filter(session, "bytes/in", flush_buffer); @@ -136,6 +147,10 @@ function disable_optimizations(session) filters.remove_filter(session, "stanzas/out", manage_buffer); filters.remove_filter(session, "bytes/in", flush_buffer); session.csi_counter = nil; + if session.csi_measure_buffer_hold then + session.csi_measure_buffer_hold(); + session.csi_measure_buffer_hold = nil; + end if session.conn and session.conn.resume_writes then session.conn:resume_writes(); end @@ -160,6 +175,7 @@ module:hook("c2s-ondrain", function (event) local session = event.session; if session.state == "inactive" and session.conn and session.conn.pause_writes then session.conn:pause_writes(); + session.csi_measure_buffer_hold = measure_buffer_hold(); session.log("debug", "Buffer flushed, resuming inactive mode (queue size was %d)", session.csi_counter); session.csi_counter = 0; end -- cgit v1.2.3 From d4c1384269b98e3a0e21b4cf3615a6d116100fd6 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 9 May 2020 17:47:03 +0200 Subject: mod_csi_simple: Collect stats on flush reasons --- plugins/mod_csi_simple.lua | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/plugins/mod_csi_simple.lua b/plugins/mod_csi_simple.lua index c0871c8d..0c428c4a 100644 --- a/plugins/mod_csi_simple.lua +++ b/plugins/mod_csi_simple.lua @@ -103,6 +103,15 @@ end local measure_buffer_hold = module:measure("buffer_hold", "times"); +local flush_reasons = setmetatable({}, { + __index = function (t, reason) + local m = module:measure("flush_reason."..reason:gsub("%W", "_"), "rate"); + t[reason] = m; + return m; + end; + }); + + local function manage_buffer(stanza, session) local ctr = session.csi_counter or 0; local flush, why = should_flush(stanza, session, ctr); @@ -111,6 +120,7 @@ local function manage_buffer(stanza, session) session.csi_measure_buffer_hold(); session.csi_measure_buffer_hold = nil; end + flush_reasons[why or "important"](); session.log("debug", "Flushing buffer (%s; queue size is %d)", why or "important", session.csi_counter); session.conn:resume_writes(); else @@ -123,6 +133,7 @@ end local function flush_buffer(data, session) session.log("debug", "Flushing buffer (%s; queue size is %d)", "client activity", session.csi_counter); + flush_reasons["client activity"](); if session.csi_measure_buffer_hold then session.csi_measure_buffer_hold(); session.csi_measure_buffer_hold = nil; -- cgit v1.2.3 From 9b7ab06ef37d1271d76f2ef9cba6b8f72af199ce Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 10 May 2020 23:06:21 +0200 Subject: mod_csi_simple: Identify raw string data in logging and stats --- plugins/mod_csi_simple.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/mod_csi_simple.lua b/plugins/mod_csi_simple.lua index 0c428c4a..3f3271f0 100644 --- a/plugins/mod_csi_simple.lua +++ b/plugins/mod_csi_simple.lua @@ -16,8 +16,10 @@ local queue_size = module:get_option_number("csi_queue_size", 256); local important_payloads = module:get_option_set("csi_important_payloads", { }); function is_important(stanza) --> boolean, reason: string - if not st.is_stanza(stanza) then + if type(stanza) == "string" then -- whitespace pings etc + return true, "raw data"; + elseif not st.is_stanza(stanza) then return true; end if stanza.attr.xmlns ~= nil then -- cgit v1.2.3 From c34f2c9ebf519ae400018cb81bb0d8141d75af64 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 10 May 2020 23:09:15 +0200 Subject: mod_csi_simple: Report whitespace keepalives Single space character is sent by mod_c2s when a session has been silent for some time. This should account for the vast majority of raw strings passing through here. If this is not the case then having stats to say otherwise will be interesting. --- plugins/mod_csi_simple.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/mod_csi_simple.lua b/plugins/mod_csi_simple.lua index 3f3271f0..679f9b48 100644 --- a/plugins/mod_csi_simple.lua +++ b/plugins/mod_csi_simple.lua @@ -16,8 +16,9 @@ local queue_size = module:get_option_number("csi_queue_size", 256); local important_payloads = module:get_option_set("csi_important_payloads", { }); function is_important(stanza) --> boolean, reason: string - if type(stanza) == "string" then - -- whitespace pings etc + if stanza == " " then + return true, "whitespace keepalive"; + elseif type(stanza) == "string" then return true, "raw data"; elseif not st.is_stanza(stanza) then return true; -- cgit v1.2.3 From e856c00107938f0442342e3550baae4bab3f3901 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 10 May 2020 23:12:33 +0200 Subject: mod_csi_simple: Report whatever's not a stirng and not a stanza This is either dead code or actually a type error, but catching that should be the responsibility of the session.send function. This type check is left since everything after it assumes a stanza object. These last few commits aren't meant to change any behavior and it did mark things not stanzas as important, but those would have been mostly raw strings which are now specially handled. --- plugins/mod_csi_simple.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/mod_csi_simple.lua b/plugins/mod_csi_simple.lua index 679f9b48..d814f083 100644 --- a/plugins/mod_csi_simple.lua +++ b/plugins/mod_csi_simple.lua @@ -21,7 +21,8 @@ function is_important(stanza) --> boolean, reason: string elseif type(stanza) == "string" then return true, "raw data"; elseif not st.is_stanza(stanza) then - return true; + -- This should probably never happen + return true, type(stanza); end if stanza.attr.xmlns ~= nil then -- stream errors, stream management etc -- cgit v1.2.3 From 0cf5e523843ae039f2aa005995cd320d7fe2ff1f Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 11 May 2020 21:41:02 +0200 Subject: spec/storage: Reset build context of test stanza make comparisons easier While building a stanza there's a .last_add field keeping track of where in the XML tree tags are being added. This field does not survive a roundtrip through preserialize / deserialize. :reset() removes this, which simplifes comparisons after such a roundtrip. --- spec/core_storagemanager_spec.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/core_storagemanager_spec.lua b/spec/core_storagemanager_spec.lua index b228c5fd..29ab014f 100644 --- a/spec/core_storagemanager_spec.lua +++ b/spec/core_storagemanager_spec.lua @@ -206,7 +206,8 @@ describe("storagemanager", function () local test_stanza = st.stanza("test", { xmlns = "urn:example:foo" }) :tag("foo"):up() - :tag("foo"):up(); + :tag("foo"):up() + :reset(); local test_time = 1539204123; local test_data = { -- cgit v1.2.3 From 82714e54a8dca7ff60365758dd497c3c9c270633 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 11 May 2020 21:56:19 +0200 Subject: mod_storage_internal: Implement key-value API --- plugins/mod_storage_internal.lua | 40 +++++++++++++++++++++++++++++++++++++++ spec/core_storagemanager_spec.lua | 15 +++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/plugins/mod_storage_internal.lua b/plugins/mod_storage_internal.lua index c8b902cf..586ea10f 100644 --- a/plugins/mod_storage_internal.lua +++ b/plugins/mod_storage_internal.lua @@ -208,6 +208,46 @@ function archive:find(username, query) end, count; end +function archive:get(username, wanted_key) + local iter, err = self:find(username, { key = wanted_key }) + if not iter then return iter, err; end + for key, stanza, when, with in iter do + if key == wanted_key then + return stanza, when, with; + end + end + return nil, "item-not-found"; +end + +function archive:set(username, key, new_value, new_when, new_with) + local items, err = datamanager.list_load(username, host, self.store); + if not items then + if err then + return items, err; + else + return nil, "item-not-found"; + end + end + + for i = 1, #items do + local old_item = items[i]; + if old_item.key == key then + local item = st.preserialize(st.clone(new_value)); + + local when = new_when or item.when or datetime.parse(item.attr.stamp); + item.key = key; + item.when = when; + item.with = new_with or old_item.with; + item.attr.stamp = datetime.datetime(when); + item.attr.stamp_legacy = datetime.legacy(when); + items[i] = item; + return datamanager.list_store(username, host, self.store, items); + end + end + + return nil, "item-not-found"; +end + function archive:dates(username) local items, err = datamanager.list_load(username, host, self.store); if not items then return items, err; end diff --git a/spec/core_storagemanager_spec.lua b/spec/core_storagemanager_spec.lua index 29ab014f..96ccceb6 100644 --- a/spec/core_storagemanager_spec.lua +++ b/spec/core_storagemanager_spec.lua @@ -439,6 +439,21 @@ describe("storagemanager", function () assert.equal(2, count); assert(archive:delete("user-issue1073")); end); + + it("can be treated as a map store", function () + assert.falsy(archive:get("mapuser", "no-such-id")); + assert.falsy(archive:set("mapuser", "no-such-id", test_stanza)); + + local id = archive:append("mapuser", nil, test_stanza, test_time, "contact@example.com"); + assert.same(test_stanza, archive:get("mapuser", id)); + + local replacement_stanza = st.stanza("test", { xmlns = "urn:example:foo" }) + :tag("bar"):up() + :reset(); + assert(archive:set("mapuser", id, replacement_stanza)); + assert.same(replacement_stanza, archive:get("mapuser", id)); + end); + end); end); end -- cgit v1.2.3 From 7d6580d30730c1f7d7147ef8da0f2f5199584fa7 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 11 May 2020 22:32:28 +0200 Subject: doc/storage: Add archive store map-like API --- doc/storage.tld | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/storage.tld b/doc/storage.tld index 057649a4..0fc8ff54 100644 --- a/doc/storage.tld +++ b/doc/storage.tld @@ -50,6 +50,10 @@ interface archive_store -- Map of counts per "with" field summary : ( self, string?, archive_query? ) -> ( { string : integer } ) | (nil, string) + + -- Map-store API + get : ( self, string, string ) -> (stanza, number?, string?) | (nil, string) + set : ( self, string, string, stanza, number?, string? ) -> (boolean) | (nil, string) end -- This represents moduleapi -- cgit v1.2.3 From 84f9f4973c41a8094bc935ca5f08c47d7ac672c7 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 11 May 2020 23:22:25 +0200 Subject: mod_storage_memory: Add map store methods to archive store --- plugins/mod_storage_memory.lua | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/plugins/mod_storage_memory.lua b/plugins/mod_storage_memory.lua index 8beb8c01..67598416 100644 --- a/plugins/mod_storage_memory.lua +++ b/plugins/mod_storage_memory.lua @@ -167,6 +167,37 @@ function archive_store:find(username, query) end, count; end +function archive_store:get(username, wanted_key) + local items = self.store[username or NULL]; + if not items then return nil, "item-not-found"; end + local i = items[wanted_key]; + if not i then return nil, "item-not-found"; end + local item = items[i]; + return item.value(), item.when, item.with; +end + +function archive_store:set(username, wanted_key, new_value, new_when, new_with) + local items = self.store[username or NULL]; + if not items then return nil, "item-not-found"; end + local i = items[wanted_key]; + if not i then return nil, "item-not-found"; end + local item = items[i]; + + if is_stanza(new_value) then + new_value = st.preserialize(new_value); + item.value = envload("return xml"..serialize(new_value), "=(stanza)", { xml = st.deserialize }) + else + item.value = envload("return "..serialize(new_value), "=(data)", {}); + end + if new_when then + item.when = new_when; + end + if new_with then + item.with = new_when; + end + return true; +end + function archive_store:summary(username, query) local iter, err = self:find(username, query) if not iter then return iter, err; end -- cgit v1.2.3 From d6de70d19f5a657070684ca1d3c68dd966412cf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Sch=C3=A4fer?= Date: Thu, 14 May 2020 14:59:59 +0200 Subject: mod_http: Add documentation to the non-obvious logic of get_ip_from_request Because docs are good. --- plugins/mod_http.lua | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/plugins/mod_http.lua b/plugins/mod_http.lua index c3e19bb3..b89ff21c 100644 --- a/plugins/mod_http.lua +++ b/plugins/mod_http.lua @@ -208,6 +208,13 @@ local function get_ip_from_request(request) local ip = request.conn:ip(); local forwarded_for = request.headers.x_forwarded_for; if forwarded_for then + -- This logic looks weird at first, but it makes sense. + -- The for loop will take the last non-trusted-proxy IP from `forwarded_for`. + -- We append the original request IP to the header. Then, since the last IP wins, there are two cases: + -- Case a) The original request IP is *not* in trusted proxies, in which case the X-Forwarded-For header will, effectively, be ineffective; the original request IP will win because it overrides any other IP in the header. + -- Case b) The original request IP is in trusted proxies. In that case, the if branch in the for loop will skip the last IP, causing it to be ignored. The second-to-last IP will be taken instead. + -- Case c) If the second-to-last IP is also a trusted proxy, it will also be ignored, iteratively, up to the last IP which isn’t in trusted proxies. + -- Case d) If all IPs are in trusted proxies, something went obviously wrong and the logic never overwrites `ip`, leaving it at the original request IP. forwarded_for = forwarded_for..", "..ip; for forwarded_ip in forwarded_for:gmatch("[^%s,]+") do if not trusted_proxies[forwarded_ip] then -- cgit v1.2.3 From d689f6c9a103215f7ce2b23aca163c21a63a4ce3 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 14 May 2020 16:55:01 +0200 Subject: mod_http: Tell luacheck to ignore the long comment lines --- plugins/mod_http.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/mod_http.lua b/plugins/mod_http.lua index b89ff21c..3bacae61 100644 --- a/plugins/mod_http.lua +++ b/plugins/mod_http.lua @@ -208,6 +208,7 @@ local function get_ip_from_request(request) local ip = request.conn:ip(); local forwarded_for = request.headers.x_forwarded_for; if forwarded_for then + -- luacheck: ignore 631 -- This logic looks weird at first, but it makes sense. -- The for loop will take the last non-trusted-proxy IP from `forwarded_for`. -- We append the original request IP to the header. Then, since the last IP wins, there are two cases: -- cgit v1.2.3 From d916ce38f6b434552a5ebd2a1751286da9cf2d69 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 15 May 2020 20:55:22 +0200 Subject: mod_storage_internal: Fix keeping old timestamp in archive map API This led to a missing 'when' field on changed items, which would cause a traceack. --- plugins/mod_storage_internal.lua | 2 +- spec/core_storagemanager_spec.lua | 17 ++++++++++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/plugins/mod_storage_internal.lua b/plugins/mod_storage_internal.lua index 586ea10f..472ec642 100644 --- a/plugins/mod_storage_internal.lua +++ b/plugins/mod_storage_internal.lua @@ -234,7 +234,7 @@ function archive:set(username, key, new_value, new_when, new_with) if old_item.key == key then local item = st.preserialize(st.clone(new_value)); - local when = new_when or item.when or datetime.parse(item.attr.stamp); + local when = new_when or old_item.when or datetime.parse(old_item.attr.stamp); item.key = key; item.when = when; item.with = new_with or old_item.with; diff --git a/spec/core_storagemanager_spec.lua b/spec/core_storagemanager_spec.lua index 96ccceb6..a19edbab 100644 --- a/spec/core_storagemanager_spec.lua +++ b/spec/core_storagemanager_spec.lua @@ -445,13 +445,24 @@ describe("storagemanager", function () assert.falsy(archive:set("mapuser", "no-such-id", test_stanza)); local id = archive:append("mapuser", nil, test_stanza, test_time, "contact@example.com"); - assert.same(test_stanza, archive:get("mapuser", id)); + do + local stanza_roundtrip, when, with = archive:get("mapuser", id); + assert.same(test_stanza, stanza_roundtrip, "same stanza is returned"); + assert.equal(test_time, when, "same 'when' is returned"); + assert.equal("contact@example.com", with, "same 'with' is returned"); + end local replacement_stanza = st.stanza("test", { xmlns = "urn:example:foo" }) :tag("bar"):up() :reset(); - assert(archive:set("mapuser", id, replacement_stanza)); - assert.same(replacement_stanza, archive:get("mapuser", id)); + assert(archive:set("mapuser", id, replacement_stanza, test_time+1)); + + do + local replaced, when, with = archive:get("mapuser", id); + assert.same(replacement_stanza, replaced, "replaced stanza is returned"); + assert.equal(test_time+1, when, "modified 'when' is returned"); + assert.equal("contact@example.com", with, "original 'with' is returned"); + end end); end); -- cgit v1.2.3 From 29f51d7e6dd71d1a702410cf24ea59edd7e5afcd Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 16 May 2020 20:46:12 +0200 Subject: mod_admin_telnet: Update existing sessions on reload This removes the need to disconnect and reconnect to the telnet console for changes to take effect. --- plugins/mod_admin_telnet.lua | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 3014517c..a082a851 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -161,6 +161,20 @@ end local sessions = {}; +function module.save() + return { sessions = sessions } +end + +function module.restore(data) + if data.sessions then + for conn in pairs(data.sessions) do + conn:setlistener(console_listener); + local session = console:new_session(conn); + sessions[conn] = session; + end + end +end + function console_listener.onconnect(conn) -- Handle new connection local session = console:new_session(conn); -- cgit v1.2.3 From a166ab719cbdcb89e7bdfca6f0c89239c7942f57 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 22 May 2020 15:20:19 +0200 Subject: net.server_epoll: Log some noise before TLS handshake step This would help pinpoint if a crash happens during the handshake, which has occurred a few times, e.g. like https://github.com/brunoos/luasec/issues/75 --- net/server_epoll.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index b281d463..632958ba 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -559,6 +559,7 @@ function interface:tlshandskake() self.onreadable = interface.tlshandskake; return self:init(); end + self:noise("Continuing TLS handshake"); local ok, err = self.conn:dohandshake(); if ok then local info = self.conn.info and self.conn:info(); -- cgit v1.2.3 From 9783208ba5af9749a111cd70761afeed383d751a Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 22 May 2020 15:36:03 +0200 Subject: net.server_epoll: Fix typo in internal method name --- net/server_epoll.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index 632958ba..82f7ab59 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -519,14 +519,14 @@ function interface:starttls(tls_ctx) if self.ondrain == interface.starttls then self.ondrain = nil; end - self.onwritable = interface.tlshandskake; - self.onreadable = interface.tlshandskake; + self.onwritable = interface.tlshandshake; + self.onreadable = interface.tlshandshake; self:set(true, true); self:debug("Prepared to start TLS"); end end -function interface:tlshandskake() +function interface:tlshandshake() self:setwritetimeout(false); self:setreadtimeout(false); if not self._tls then @@ -555,8 +555,8 @@ function interface:tlshandskake() end self:on("starttls"); self.ondrain = nil; - self.onwritable = interface.tlshandskake; - self.onreadable = interface.tlshandskake; + self.onwritable = interface.tlshandshake; + self.onreadable = interface.tlshandshake; return self:init(); end self:noise("Continuing TLS handshake"); -- cgit v1.2.3 From 836be9b60d1e2b10ec4cdfb3e58281f18c0a31d0 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 27 May 2020 19:44:12 +0200 Subject: scansion: Add test for mod_server_contact_info / XEP-0157 --- spec/scansion/prosody.cfg.lua | 11 +++++++- spec/scansion/server_contact_info.scs | 53 +++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 spec/scansion/server_contact_info.scs diff --git a/spec/scansion/prosody.cfg.lua b/spec/scansion/prosody.cfg.lua index b7d6ccd5..4cf03de8 100644 --- a/spec/scansion/prosody.cfg.lua +++ b/spec/scansion/prosody.cfg.lua @@ -52,7 +52,7 @@ modules_enabled = { -- Other specific functionality --"limits"; -- Enable bandwidth limiting for XMPP connections --"groups"; -- Shared roster support - --"server_contact_info"; -- Publish contact information for this service + "server_contact_info"; -- Publish contact information for this service --"announce"; -- Send announcement to all online users --"welcome"; -- Welcome users who register accounts --"watchregistrations"; -- Alert admins of registrations @@ -65,6 +65,15 @@ modules_enabled = { --"scansion_record"; -- Records things that happen in scansion test case format } +contact_info = { + abuse = { "mailto:abuse@localhost", "xmpp:abuse@localhost" }; + admin = { "mailto:admin@localhost", "xmpp:admin@localhost" }; + feedback = { "http://localhost/feedback.html", "mailto:feedback@localhost", "xmpp:feedback@localhost" }; + sales = { "xmpp:sales@localhost" }; + security = { "xmpp:security@localhost" }; + support = { "https://localhost/support.html", "xmpp:support@localhost" }; +} + modules_disabled = { "s2s"; } diff --git a/spec/scansion/server_contact_info.scs b/spec/scansion/server_contact_info.scs new file mode 100644 index 00000000..15537e11 --- /dev/null +++ b/spec/scansion/server_contact_info.scs @@ -0,0 +1,53 @@ +# XEP-0157: Contact Addresses for XMPP Services +# mod_server_contact_info + +[Client] Romeo + jid: romeo@localhost + password: password + +----- + +Romeo connects + +Romeo sends: + + + + +# Ignore other disco#info features, identities etc + +Romeo receives: + + + + + http://jabber.org/network/serverinfo + + + mailto:abuse@localhost + xmpp:abuse@localhost + + + mailto:admin@localhost + xmpp:admin@localhost + + + http://localhost/feedback.html + mailto:feedback@localhost + xmpp:feedback@localhost + + + xmpp:sales@localhost + + + xmpp:security@localhost + + + https://localhost/support.html + xmpp:support@localhost + + + + + +Romeo disconnects -- cgit v1.2.3 From d22e85debcc0a230f82af5b07e28f4248f63e072 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 31 May 2020 22:25:48 +0200 Subject: mod_c2s,mod_s2s: Use a distinct stream error for hitting stanza size limit Since this is not a real parse error, it should not be reported as such. --- plugins/mod_c2s.lua | 6 +++++- plugins/mod_s2s/mod_s2s.lua | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/plugins/mod_c2s.lua b/plugins/mod_c2s.lua index 91e37c4a..c6a95e9e 100644 --- a/plugins/mod_c2s.lua +++ b/plugins/mod_c2s.lua @@ -308,7 +308,11 @@ function listener.onconnect(conn) local ok, err = stream:feed(data); if not ok then log("debug", "Received invalid XML (%s) %d bytes: %q", err, #data, data:sub(1, 300)); - session:close("not-well-formed"); + if err == "stanza-too-large" then + session:close({ condition = "policy-violation", text = "XML stanza is too big" }); + else + session:close("not-well-formed"); + end end end end diff --git a/plugins/mod_s2s/mod_s2s.lua b/plugins/mod_s2s/mod_s2s.lua index 9f7d4d6d..bd6627b8 100644 --- a/plugins/mod_s2s/mod_s2s.lua +++ b/plugins/mod_s2s/mod_s2s.lua @@ -614,7 +614,11 @@ local function initialize_session(session) local ok, err = stream:feed(data); if ok then return; end log("debug", "Received invalid XML (%s) %d bytes: %q", err, #data, data:sub(1, 300)); - session:close("not-well-formed", nil, "Received invalid XML from remote server"); + if err == "stanza-too-large" then + session:close({ condition = "policy-violation", text = "XML stanza is too big" }, nil, "Received invalid XML from remote server"); + else + session:close("not-well-formed", nil, "Received invalid XML from remote server"); + end end end -- cgit v1.2.3 From 5abc2e6a5ce803060cd2c03182d3ae95bd29f694 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 31 May 2020 22:39:34 +0200 Subject: mod_c2s,mod_s2s: Make stanza size limits configurable --- plugins/mod_c2s.lua | 3 ++- plugins/mod_s2s/mod_s2s.lua | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/mod_c2s.lua b/plugins/mod_c2s.lua index c6a95e9e..ef4bd4b3 100644 --- a/plugins/mod_c2s.lua +++ b/plugins/mod_c2s.lua @@ -26,6 +26,7 @@ local log = module._log; local c2s_timeout = module:get_option_number("c2s_timeout", 300); local stream_close_timeout = module:get_option_number("c2s_close_timeout", 5); local opt_keepalives = module:get_option_boolean("c2s_tcp_keepalives", module:get_option_boolean("tcp_keepalives", true)); +local stanza_size_limit = module:get_option_number("c2s_stanza_size_limit"); -- TODO come up with a sensible default (util.xmppstream defaults to 10M) local measure_connections = module:measure("connections", "amount"); local measure_ipv6 = module:measure("ipv6", "amount"); @@ -280,7 +281,7 @@ function listener.onconnect(conn) session.close = session_close; - local stream = new_xmpp_stream(session, stream_callbacks); + local stream = new_xmpp_stream(session, stream_callbacks, stanza_size_limit); session.stream = stream; session.notopen = true; diff --git a/plugins/mod_s2s/mod_s2s.lua b/plugins/mod_s2s/mod_s2s.lua index bd6627b8..0674f981 100644 --- a/plugins/mod_s2s/mod_s2s.lua +++ b/plugins/mod_s2s/mod_s2s.lua @@ -39,6 +39,7 @@ local secure_auth = module:get_option_boolean("s2s_secure_auth", false); -- One local secure_domains, insecure_domains = module:get_option_set("s2s_secure_domains", {})._items, module:get_option_set("s2s_insecure_domains", {})._items; local require_encryption = module:get_option_boolean("s2s_require_encryption", false); +local stanza_size_limit = module:get_option_number("s2s_stanza_size_limit"); -- TODO come up with a sensible default (util.xmppstream defaults to 10M) local measure_connections = module:measure("connections", "amount"); local measure_ipv6 = module:measure("ipv6", "amount"); @@ -566,7 +567,7 @@ end -- Session initialization logic shared by incoming and outgoing local function initialize_session(session) - local stream = new_xmpp_stream(session, stream_callbacks); + local stream = new_xmpp_stream(session, stream_callbacks, stanza_size_limit); session.thread = runner(function (stanza) if st.is_stanza(stanza) then -- cgit v1.2.3 From 30d3969c22ef22ae5fd455a019ab0879c28f43fb Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 1 Jun 2020 13:38:47 +0100 Subject: net.server_select: Ensure onconnect is always called before onincoming This changes the code to call onconnect when the first data is sucessfully read or written, instead of simply when the socket first becomes writable. A writable socket can mean a connection error, and if the client already sent some data it may get passed to onincoming before processing writable sockets. This fixes the issue. --- net/server_select.lua | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/net/server_select.lua b/net/server_select.lua index 9cd3463e..de2556f0 100644 --- a/net/server_select.lua +++ b/net/server_select.lua @@ -289,6 +289,8 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport local ssl + local pending + local dispatch = listeners.onincoming local status = listeners.onstatus local disconnect = listeners.ondisconnect @@ -343,6 +345,9 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport listeners.onattach(self, data) end end + handler._setpending = function( ) + pending = true + end handler.getstats = function( ) return readtraffic, sendtraffic end @@ -525,6 +530,12 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport _readtraffic = _readtraffic + count _readtimes[ handler ] = _currenttime --out_put( "server.lua: read data '", buffer:gsub("[^%w%p ]", "."), "', error: ", err ) + if pending then -- connection established + pending = nil + if listeners.onconnect then + listeners.onconnect(handler) + end + end return dispatch( handler, buffer, err ) else -- connections was closed or fatal error out_put( "server.lua: client ", tostring(ip), ":", tostring(clientport), " read error: ", tostring(err) ) @@ -535,6 +546,12 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport local _sendbuffer = function( ) -- this function sends data local succ, err, byte, buffer, count; if socket then + if pending then + pending = nil + if listeners.onconnect then + listeners.onconnect(handler); + end + end buffer = table_concat( bufferqueue, "", 1, bufferqueuelen ) succ, err, byte = send( socket, buffer, 1, bufferlen ) count = ( succ or byte or 0 ) * STAT_UNIT @@ -1015,17 +1032,9 @@ local wrapclient = function( socket, ip, serverport, listeners, pattern, sslctx, if not handler then return nil, err end _socketlist[ socket ] = handler if not sslctx then + handler._setpending() _readlistlen = addsocket(_readlist, socket, _readlistlen) _sendlistlen = addsocket(_sendlist, socket, _sendlistlen) - if listeners.onconnect then - -- When socket is writeable, call onconnect - local _sendbuffer = handler.sendbuffer; - handler.sendbuffer = function () - handler.sendbuffer = _sendbuffer; - listeners.onconnect(handler); - return _sendbuffer(); -- Send any queued outgoing data - end - end end return handler, socket end -- cgit v1.2.3 From ce129b96c7684d4270a7fae58ba5a9002837835c Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 1 Jun 2020 13:41:41 +0100 Subject: net.server_select: Pass conn/handler to readbuffer/sendbuffer The internal implementations don't use it, but this causes onreadable and onwritable of watchfd to receive the conn as they do in other backends. --- net/server_select.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/net/server_select.lua b/net/server_select.lua index de2556f0..a2515d59 100644 --- a/net/server_select.lua +++ b/net/server_select.lua @@ -384,7 +384,7 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport _readlistlen = removesocket( _readlist, socket, _readlistlen ) _readtimes[ handler ] = nil if bufferqueuelen ~= 0 then - handler.sendbuffer() -- Try now to send any outstanding data + handler:sendbuffer() -- Try now to send any outstanding data if bufferqueuelen ~= 0 then -- Still not empty, so we'll try again later if handler then handler.write = nil -- ... but no further writing allowed @@ -752,7 +752,7 @@ local function link(sender, receiver, buffersize) local sender_locked; local _sendbuffer = receiver.sendbuffer; function receiver.sendbuffer() - _sendbuffer(); + _sendbuffer(receiver); if sender_locked and receiver.bufferlen() < buffersize then sender:lock_read(false); -- Unlock now sender_locked = nil; @@ -962,7 +962,7 @@ loop = function(once) -- this is the main loop of the program for _, socket in ipairs( read ) do -- receive data local handler = _socketlist[ socket ] if handler then - handler.readbuffer( ) + handler:readbuffer( ) else closesocket( socket ) out_put "server.lua: found no handler and closed socket (readlist)" -- this can happen @@ -971,7 +971,7 @@ loop = function(once) -- this is the main loop of the program for _, socket in ipairs( write ) do -- send data waiting in writequeues local handler = _socketlist[ socket ] if handler then - handler.sendbuffer( ) + handler:sendbuffer( ) else closesocket( socket ) out_put "server.lua: found no handler and closed socket (writelist)" -- this should not happen -- cgit v1.2.3 From 610be29e2a98547251da98091c65ff3111664b69 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 1 Jun 2020 13:43:35 +0100 Subject: net.server: Switch to epoll backend by default (if util.poll is found) --- net/server.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/net/server.lua b/net/server.lua index abbb421d..f5666594 100644 --- a/net/server.lua +++ b/net/server.lua @@ -13,7 +13,11 @@ if not (prosody and prosody.config_loaded) then end local log = require "util.logger".init("net.server"); -local server_type = require "core.configmanager".get("*", "network_backend") or "select"; + +local have_util_poll = pcall(require, "util.poll"); +local default_backend = have_util_poll and "epoll" or "select"; + +local server_type = require "core.configmanager".get("*", "network_backend") or default_backend; if require "core.configmanager".get("*", "use_libevent") then server_type = "event"; -- cgit v1.2.3 From 6ccd66e347747b0fd7b2a193a777b46d913c38ee Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 1 Jun 2020 14:26:11 +0100 Subject: net.server_epoll: Handle missing ports from getsock/peername (as in the case of unix sockets) --- net/server_epoll.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index 82f7ab59..ded42c4d 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -619,11 +619,11 @@ function interface:updatenames() local conn = self.conn; local ok, peername, peerport = pcall(conn.getpeername, conn); if ok and peername then - self.peername, self.peerport = peername, peerport; + self.peername, self.peerport = peername, peerport or 0; end local ok, sockname, sockport = pcall(conn.getsockname, conn); if ok and sockname then - self.sockname, self.sockport = sockname, sockport; + self.sockname, self.sockport = sockname, sockport or 0; end end -- cgit v1.2.3 From 4c6992a00e582dc2c9c6a7ad721cd7e831202a1e Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 1 Jun 2020 15:42:19 +0100 Subject: mod_admin_socket, util.adminstream: New module to manage a local unix domain socket for admin functionality --- plugins/mod_admin_socket.lua | 69 +++++++++++ util/adminstream.lua | 285 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 354 insertions(+) create mode 100644 plugins/mod_admin_socket.lua create mode 100644 util/adminstream.lua diff --git a/plugins/mod_admin_socket.lua b/plugins/mod_admin_socket.lua new file mode 100644 index 00000000..c094aad2 --- /dev/null +++ b/plugins/mod_admin_socket.lua @@ -0,0 +1,69 @@ +module:set_global(); + +local have_unix, unix = pcall(require, "socket.unix"); + +if not have_unix or type(unix) ~= "table" then + module:log_status("error", "LuaSocket unix socket support not available or incompatible, ensure it is up to date"); + return; +end + +local server = require "net.server"; + +local adminstream = require "util.adminstream"; + +local socket_path = module:get_option_string("admin_socket", prosody.paths.data.."/prosody.sock"); + +local sessions = module:shared("sessions"); + +local function fire_admin_event(session, stanza) + local event_data = { + origin = session, stanza = stanza; + }; + local event_name; + if stanza.attr.xmlns then + event_name = "admin/"..stanza.attr.xmlns..":"..stanza.name; + else + event_name = "admin/"..stanza.name; + end + module:log("debug", "Firing %s", event_name); + return module:fire_event(event_name, event_data); +end + +module:hook("server-stopping", function () + for _, session in pairs(sessions) do + session:close("system-shutdown"); + end + os.remove(socket_path); +end); + +--- Unix domain socket management + +local conn, sock; + +local listeners = adminstream.server(sessions, fire_admin_event).listeners; + +local function accept_connection() + module:log("debug", "accepting..."); + local client = sock:accept(); + if not client then return; end + server.wrapclient(client, "unix", 0, listeners, "*a"); +end + +function module.load() + sock = unix.stream(); + sock:settimeout(0); + os.remove(socket_path); + assert(sock:bind(socket_path)); + assert(sock:listen()); + conn = server.watchfd(sock:getfd(), accept_connection); +end + +function module.unload() + if conn then + conn:close(); + end + if sock then + sock:close(); + end + os.remove(socket_path); +end diff --git a/util/adminstream.lua b/util/adminstream.lua new file mode 100644 index 00000000..186cb0e9 --- /dev/null +++ b/util/adminstream.lua @@ -0,0 +1,285 @@ +local st = require "util.stanza"; +local new_xmpp_stream = require "util.xmppstream".new; +local sessionlib = require "util.session"; +local gettime = require "util.time".now; +local runner = require "util.async".runner; +local add_task = require "util.timer".add_task; +local events = require "util.events"; + +local stream_close_timeout = 5; + +local log = require "util.logger".init("adminstream"); + +local xmlns_xmpp_streams = "urn:ietf:params:xml:ns:xmpp-streams"; + +local stream_callbacks = { default_ns = "xmpp:prosody.im/admin" }; + +function stream_callbacks.streamopened(session, attr) + -- run _streamopened in async context + session.thread:run({ stream = "opened", attr = attr }); +end + +function stream_callbacks._streamopened(session, attr) --luacheck: ignore 212/attr + if session.type ~= "client" then + session:open_stream(); + end + session.notopen = nil; +end + +function stream_callbacks.streamclosed(session, attr) + -- run _streamclosed in async context + session.thread:run({ stream = "closed", attr = attr }); +end + +function stream_callbacks._streamclosed(session) + session.log("debug", "Received "); + session:close(false); +end + +function stream_callbacks.error(session, error, data) + if error == "no-stream" then + session.log("debug", "Invalid opening stream header (%s)", (data:gsub("^([^\1]+)\1", "{%1}"))); + session:close("invalid-namespace"); + elseif error == "parse-error" then + session.log("debug", "Client XML parse error: %s", data); + session:close("not-well-formed"); + elseif error == "stream-error" then + local condition, text = "undefined-condition"; + for child in data:childtags(nil, xmlns_xmpp_streams) do + if child.name ~= "text" then + condition = child.name; + else + text = child:get_text(); + end + if condition ~= "undefined-condition" and text then + break; + end + end + text = condition .. (text and (" ("..text..")") or ""); + session.log("info", "Session closed by remote with error: %s", text); + session:close(nil, text); + end +end + +function stream_callbacks.handlestanza(session, stanza) + session.thread:run(stanza); +end + +local runner_callbacks = {}; + +function runner_callbacks:error(err) + self.data.log("error", "Traceback[c2s]: %s", err); +end + +local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'}; + +local function destroy_session(session, reason) + if session.destroyed then return; end + session.destroyed = true; + session.log("debug", "Destroying session: %s", reason or "unknown reason"); +end + +local function session_close(session, reason) + local log = session.log or log; + if session.conn then + if session.notopen then + session:open_stream(); + end + if reason then -- nil == no err, initiated by us, false == initiated by client + local stream_error = st.stanza("stream:error"); + if type(reason) == "string" then -- assume stream error + stream_error:tag(reason, {xmlns = 'urn:ietf:params:xml:ns:xmpp-streams' }); + elseif type(reason) == "table" then + if reason.condition then + stream_error:tag(reason.condition, stream_xmlns_attr):up(); + if reason.text then + stream_error:tag("text", stream_xmlns_attr):text(reason.text):up(); + end + if reason.extra then + stream_error:add_child(reason.extra); + end + elseif reason.name then -- a stanza + stream_error = reason; + end + end + stream_error = tostring(stream_error); + log("debug", "Disconnecting client, is: %s", stream_error); + session.send(stream_error); + end + + session.send(""); + function session.send() return false; end + + local reason_text = (reason and (reason.name or reason.text or reason.condition)) or reason; + session.log("debug", "c2s stream for %s closed: %s", session.full_jid or session.ip or "", reason_text or "session closed"); + + -- Authenticated incoming stream may still be sending us stanzas, so wait for from remote + local conn = session.conn; + if reason_text == nil and not session.notopen and session.type == "c2s" then + -- Grace time to process data from authenticated cleanly-closed stream + add_task(stream_close_timeout, function () + if not session.destroyed then + session.log("warn", "Failed to receive a stream close response, closing connection anyway..."); + destroy_session(session); + conn:close(); + end + end); + else + destroy_session(session, reason_text); + conn:close(); + end + else + local reason_text = (reason and (reason.name or reason.text or reason.condition)) or reason; + destroy_session(session, reason_text); + end +end + +--- Public methods + +local function new_server(sessions, stanza_handler) + local listeners = {}; + + function listeners.onconnect(conn) + log("debug", "New connection"); + local session = sessionlib.new("admin"); + sessionlib.set_id(session); + sessionlib.set_logger(session); + sessionlib.set_conn(session, conn); + + session.conntime = gettime(); + session.type = "admin"; + + local stream = new_xmpp_stream(session, stream_callbacks); + session.stream = stream; + session.notopen = true; + + session.thread = runner(function (stanza) + if st.is_stanza(stanza) then + stanza_handler(session, stanza); + elseif stanza.stream == "opened" then + stream_callbacks._streamopened(session, stanza.attr); + elseif stanza.stream == "closed" then + stream_callbacks._streamclosed(session, stanza.attr); + end + end, runner_callbacks, session); + + function session.data(data) + -- Parse the data, which will store stanzas in session.pending_stanzas + if data then + local ok, err = stream:feed(data); + if not ok then + session.log("debug", "Received invalid XML (%s) %d bytes: %q", err, #data, data:sub(1, 300)); + session:close("not-well-formed"); + end + end + end + + session.close = session_close; + + session.send = function (t) + session.log("debug", "Sending[%s]: %s", session.type, t.top_tag and t:top_tag() or t:match("^[^>]*>?")); + return session.rawsend(tostring(t)); + end + + function session.rawsend(t) + local ret, err = conn:write(t); + if not ret then + session.log("debug", "Error writing to connection: %s", err); + return false, err; + end + return true; + end + + sessions[conn] = session; + end + + function listeners.onincoming(conn, data) + local session = sessions[conn]; + if session then + session.data(data); + end + end + + function listeners.ondisconnect(conn, err) + local session = sessions[conn]; + if session then + session.log("info", "Admin client disconnected: %s", err or "connection closed"); + session.conn = nil; + sessions[conn] = nil; + end + end + return { + listeners = listeners; + }; +end + +local function new_client() + local client = { + type = "client"; + events = events.new(); + log = log; + }; + + local listeners = {}; + + function listeners.onconnect(conn) + log("debug", "Connected"); + client.conn = conn; + + local stream = new_xmpp_stream(client, stream_callbacks); + client.stream = stream; + client.notopen = true; + + client.thread = runner(function (stanza) + if st.is_stanza(stanza) then + client.events.fire_event("received", stanza); + elseif stanza.stream == "opened" then + stream_callbacks._streamopened(client, stanza.attr); + client.events.fire_event("connected"); + elseif stanza.stream == "closed" then + client.events.fire_event("disconnected"); + stream_callbacks._streamclosed(client, stanza.attr); + end + end, runner_callbacks, client); + + client.close = session_close; + + function client.send(t) + client.log("debug", "Sending: %s", t.top_tag and t:top_tag() or t:match("^[^>]*>?")); + return client.rawsend(tostring(t)); + end + + function client.rawsend(t) + local ret, err = conn:write(t); + if not ret then + client.log("debug", "Error writing to connection: %s", err); + return false, err; + end + return true; + end + client.log("debug", "Opening stream..."); + client:open_stream(); + end + + function listeners.onincoming(conn, data) --luacheck: ignore 212/conn + local ok, err = client.stream:feed(data); + if not ok then + client.log("debug", "Received invalid XML (%s) %d bytes: %q", err, #data, data:sub(1, 300)); + client:close("not-well-formed"); + end + end + + function listeners.ondisconnect(conn, err) --luacheck: ignore 212/conn + client.log("info", "Admin client disconnected: %s", err or "connection closed"); + client.conn = nil; + end + + client.listeners = listeners; + + return client; +end + +return { + server = new_server; + client = new_client; +}; -- cgit v1.2.3 From 9daa5c028c5bf48b71e2e03cf0e759ce5368f626 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 1 Jun 2020 15:43:00 +0100 Subject: mod_admin_shell: New module that implements the console interface over an admin socket --- plugins/mod_admin_shell.lua | 1637 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1637 insertions(+) create mode 100644 plugins/mod_admin_shell.lua diff --git a/plugins/mod_admin_shell.lua b/plugins/mod_admin_shell.lua new file mode 100644 index 00000000..d471e32f --- /dev/null +++ b/plugins/mod_admin_shell.lua @@ -0,0 +1,1637 @@ +-- 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. +-- +-- luacheck: ignore 212/self + +module:set_global(); +module:depends("admin_socket"); + +local hostmanager = require "core.hostmanager"; +local modulemanager = require "core.modulemanager"; +local s2smanager = require "core.s2smanager"; +local portmanager = require "core.portmanager"; +local helpers = require "util.helpers"; +local server = require "net.server"; +local st = require "util.stanza"; + +local _G = _G; + +local prosody = _G.prosody; + +local unpack = table.unpack or unpack; -- luacheck: ignore 113 +local iterators = require "util.iterators"; +local keys, values = iterators.keys, iterators.values; +local jid_bare, jid_split, jid_join = import("util.jid", "bare", "prepped_split", "join"); +local set, array = require "util.set", require "util.array"; +local cert_verify_identity = require "util.x509".verify_identity; +local envload = require "util.envload".envload; +local envloadfile = require "util.envload".envloadfile; +local has_pposix, pposix = pcall(require, "util.pposix"); +local async = require "util.async"; +local serialization = require "util.serialization"; +local serialize_config = serialization.new ({ fatal = false, unquoted = true}); +local time = require "util.time"; + +local commands = module:shared("commands") +local def_env = module:shared("env"); +local default_env_mt = { __index = def_env }; + +local function redirect_output(target, session) + local env = setmetatable({ print = session.print }, { __index = function (_, k) return rawget(target, k); end }); + env.dofile = function(name) + local f, err = envloadfile(name, env); + if not f then return f, err; end + return f(); + end; + return env; +end + +console = {}; + +local runner_callbacks = {}; + +function runner_callbacks:error(err) + module:log("error", "Traceback[shell]: %s", err); + + self.data.print("Fatal error while running command, it did not complete"); + self.data.print("Error: "..tostring(err)); +end + +local function send_repl_result(session, line) + return session.send(st.stanza("repl-result"):text(tostring(line))); +end + +function console:new_session(admin_session) + local session = { + send = function (t) + return send_repl_result(admin_session, t); + end; + print = function (...) + local t = {}; + for i=1,select("#", ...) do + t[i] = tostring(select(i, ...)); + end + return send_repl_result(admin_session, table.concat(t, "\t")); + end; + serialize = tostring; + disconnect = function () admin_session:close(); end; + }; + session.env = setmetatable({}, default_env_mt); + + session.thread = async.runner(function (line) + console:process_line(session, line); + end, runner_callbacks, session); + + -- Load up environment with helper objects + for name, t in pairs(def_env) do + if type(t) == "table" then + session.env[name] = setmetatable({ session = session }, { __index = t }); + end + end + + session.env.output:configure(); + + return session; +end + +local function handle_line(event) + local session = event.origin.shell_session; + if not session then + session = console:new_session(event.origin); + event.origin.shell_session = session; + end + local line = event.stanza:get_text(); + local useglobalenv; + + + module:log("debug", "HELLO: %s", line) + if line:match("^>") then + line = line:gsub("^>", ""); + useglobalenv = true; + else + local command = line:match("^%w+") or line:match("%p"); + if commands[command] then + commands[command](session, line); + return; + end + end + + 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 + -- luacheck: ignore 311/err + local chunk, err = envload("return "..line, chunkname, env); + if not chunk then + chunk, err = envload(line, chunkname, env); + if not chunk then + err = err:gsub("^%[string .-%]:%d+: ", ""); + err = err:gsub("^:%d+: ", ""); + err = err:gsub("''", "the end of the line"); + session.print("Sorry, I couldn't understand that... "..err); + return; + end + end + + local taskok, message = chunk(); + + if not message then + if type(taskok) ~= "string" and useglobalenv then + taskok = session.serialize(taskok); + end + session.print("Result: "..tostring(taskok)); + return; + elseif (not taskok) and message then + session.print("Command completed with a problem"); + session.print("Message: "..tostring(message)); + return; + end + + session.print("OK: "..tostring(message)); +end + +module:hook("admin/repl-line", function (event) + local ok, err = pcall(handle_line, event); + if not ok then + event.origin.send(st.stanza("repl-result", { type = "error" }):text(err)); + end +end); + +-- Console commands -- +-- These are simple commands, not valid standalone in Lua + +function commands.help(session, data) + local print = session.print; + local section = data:match("^help (%w+)"); + if not section then + print [[Commands are divided into multiple sections. For help on a particular section, ]] + print [[type: help SECTION (for example, 'help c2s'). Sections are: ]] + print [[]] + print [[c2s - Commands to manage local client-to-server sessions]] + print [[s2s - Commands to manage sessions between this server and others]] + print [[http - Commands to inspect HTTP services]] -- XXX plural but there is only one so far + print [[module - Commands to load/reload/unload modules/plugins]] + print [[host - Commands to activate, deactivate and list virtual hosts]] + print [[user - Commands to create and delete users, and change their passwords]] + 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 [[debug - Commands for debugging the server]] + print [[config - Reloading the configuration, etc.]] + print [[console - Help regarding the console itself]] + elseif section == "c2s" then + print [[c2s:show(jid) - Show all client sessions with the specified JID (or all if no JID given)]] + print [[c2s:show_insecure() - Show all unencrypted client connections]] + print [[c2s:show_secure() - Show all encrypted client connections]] + print [[c2s: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 + print [[s2s:show(domain) - Show all s2s connections for the given domain (or all if no domain given)]] + print [[s2s:show_tls(domain) - Show TLS cipher info for encrypted sessions]] + print [[s2s:close(from, to) - Close a connection from one domain to another]] + print [[s2s:closeall(host) - Close all the incoming/outgoing s2s sessions to specified host]] + elseif section == "http" then + print [[http:list(hosts) - Show HTTP endpoints]] + elseif section == "module" then + print [[module:load(module, host) - Load the specified module on the specified host (or all hosts if none given)]] + print [[module:reload(module, host) - The same, but unloads and loads the module (saving state if the module supports it)]] + print [[module:unload(module, host) - The same, but just unloads the module from memory]] + print [[module:list(host) - List the modules loaded on the specified host]] + elseif section == "host" then + print [[host:activate(hostname) - Activates the specified host]] + print [[host:deactivate(hostname) - Disconnects all clients on this host and deactivates]] + print [[host:list() - List the currently-activated hosts]] + elseif section == "user" then + print [[user:create(jid, password) - Create the specified user account]] + print [[user:password(jid, password) - Set the password for the specified user account]] + print [[user:delete(jid) - Permanently remove the specified user account]] + print [[user:list(hostname, pattern) - List users on the specified host, optionally filtering with a pattern]] + elseif section == "server" then + print [[server:version() - Show the server's version number]] + print [[server:uptime() - Show how long the server has been running]] + print [[server:memory() - Show details about the server's memory usage]] + print [[server:shutdown(reason) - Shut down the server, with an optional reason to be broadcast to all connections]] + elseif section == "port" then + print [[port:list() - Lists all network ports prosody currently listens on]] + print [[port:close(port, interface) - Close a port]] + elseif section == "dns" then + print [[dns:lookup(name, type, class) - Do a DNS lookup]] + print [[dns:addnameserver(nameserver) - Add a nameserver to the list]] + 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.]] + print [[config:get([host,] option) - Show the value of a config option.]] + elseif section == "stats" then -- luacheck: ignore 542 + -- TODO describe how stats:show() works + elseif section == "debug" then + print [[debug:logevents(host) - Enable logging of fired events on host]] + print [[debug:events(host, event) - Show registered event handlers]] + print [[debug:timers() - Show information about scheduled timers]] + elseif section == "console" then + print [[Hey! Welcome to Prosody's admin console.]] + print [[First thing, if you're ever wondering how to get out, simply type 'quit'.]] + print [[Secondly, note that we don't support the full telnet protocol yet (it's coming)]] + print [[so you may have trouble using the arrow keys, etc. depending on your system.]] + print [[]] + print [[For now we offer a couple of handy shortcuts:]] + print [[!! - Repeat the last command]] + print [[!old!new! - repeat the last command, but with 'old' replaced by 'new']] + print [[]] + print [[For those well-versed in Prosody's internals, or taking instruction from those who are,]] + print [[you can prefix a command with > to escape the console sandbox, and access everything in]] + print [[the running server. Great fun, but be careful not to break anything :)]] + end + print [[]] +end + +-- Session environment -- +-- Anything in def_env will be accessible within the session as a global variable + +--luacheck: ignore 212/self +local serialize_defaults = module:get_option("console_prettyprint_settings", { fatal = false, unquoted = true, maxdepth = 2}) + +def_env.output = {}; +function def_env.output:configure(opts) + if type(opts) ~= "table" then + opts = { preset = opts }; + end + if not opts.fallback then + -- XXX Error message passed to fallback is lost, does it matter? + opts.fallback = tostring; + end + for k,v in pairs(serialize_defaults) do + if opts[k] == nil then + opts[k] = v; + end + end + self.session.serialize = serialization.new(opts); +end + +def_env.server = {}; + +function def_env.server:insane_reload() + prosody.unlock_globals(); + dofile "prosody" + prosody = _G.prosody; + return true, "Server reloaded"; +end + +function def_env.server:version() + return true, tostring(prosody.version or "unknown"); +end + +function def_env.server:uptime() + local t = os.time()-prosody.start_time; + local seconds = t%60; + t = (t - seconds)/60; + local minutes = t%60; + t = (t - minutes)/60; + local hours = t%24; + t = (t - hours)/24; + local days = t; + return true, string.format("This server has been running for %d day%s, %d hour%s and %d minute%s (since %s)", + days, (days ~= 1 and "s") or "", hours, (hours ~= 1 and "s") or "", + minutes, (minutes ~= 1 and "s") or "", os.date("%c", prosody.start_time)); +end + +function def_env.server:shutdown(reason) + prosody.shutdown(reason); + return true, "Shutdown initiated"; +end + +local function human(kb) + local unit = "K"; + if kb > 1024 then + kb, unit = kb/1024, "M"; + end + return ("%0.2f%sB"):format(kb, unit); +end + +function def_env.server:memory() + if not has_pposix or not pposix.meminfo then + return true, "Lua is using "..human(collectgarbage("count")); + end + local mem, lua_mem = pposix.meminfo(), collectgarbage("count"); + local print = self.session.print; + print("Process: "..human((mem.allocated+mem.allocated_mmap)/1024)); + print(" Used: "..human(mem.used/1024).." ("..human(lua_mem).." by Lua)"); + print(" Free: "..human(mem.unused/1024).." ("..human(mem.returnable/1024).." returnable)"); + return true, "OK"; +end + +def_env.module = {}; + +local function get_hosts_set(hosts) + if type(hosts) == "table" then + if hosts[1] then + return set.new(hosts); + elseif hosts._items then + return hosts; + end + elseif type(hosts) == "string" then + return set.new { hosts }; + elseif hosts == nil then + return set.new(array.collect(keys(prosody.hosts))); + end +end + +-- Hosts with a module or all virtualhosts if no module given +-- matching modules_enabled in the global section +local function get_hosts_with_module(hosts, module) + local hosts_set = get_hosts_set(hosts) + / function (host) + if module then + -- Module given, filter in hosts with this module loaded + if modulemanager.is_loaded(host, module) then + return host; + else + return nil; + end + end + if not hosts then + -- No hosts given, filter in VirtualHosts + if prosody.hosts[host].type == "local" then + return host; + else + return nil + end + end; + -- No module given, but hosts are, don't filter at all + return host; + end; + if module and modulemanager.get_module("*", module) then + hosts_set:add("*"); + end + return hosts_set; +end + +function def_env.module:load(name, hosts, config) + hosts = get_hosts_with_module(hosts); + + -- Load the module for each host + local ok, err, count, mod = true, nil, 0; + for host in hosts do + if (not modulemanager.is_loaded(host, name)) then + mod, err = modulemanager.load(host, name, config); + if not mod then + ok = false; + if err == "global-module-already-loaded" then + if count > 0 then + ok, err, count = true, nil, 1; + end + break; + end + self.session.print(err or "Unknown error loading module"); + else + count = count + 1; + self.session.print("Loaded for "..mod.module.host); + end + end + end + + return ok, (ok and "Module loaded onto "..count.." host"..(count ~= 1 and "s" or "")) or ("Last error: "..tostring(err)); +end + +function def_env.module:unload(name, hosts) + hosts = get_hosts_with_module(hosts, name); + + -- Unload the module for each host + local ok, err, count = true, nil, 0; + for host in hosts do + if modulemanager.is_loaded(host, name) then + ok, err = modulemanager.unload(host, name); + if not ok then + ok = false; + self.session.print(err or "Unknown error unloading module"); + else + count = count + 1; + self.session.print("Unloaded from "..host); + end + end + end + return ok, (ok and "Module unloaded from "..count.." host"..(count ~= 1 and "s" or "")) or ("Last error: "..tostring(err)); +end + +local function _sort_hosts(a, b) + if a == "*" then return true + elseif b == "*" then return false + else return a:gsub("[^.]+", string.reverse):reverse() < b:gsub("[^.]+", string.reverse):reverse(); end +end + +function def_env.module:reload(name, hosts) + hosts = array.collect(get_hosts_with_module(hosts, name)):sort(_sort_hosts) + + -- Reload the module for each host + local ok, err, count = true, nil, 0; + for _, host in ipairs(hosts) do + if modulemanager.is_loaded(host, name) then + ok, err = modulemanager.reload(host, name); + if not ok then + ok = false; + self.session.print(err or "Unknown error reloading module"); + else + count = count + 1; + if ok == nil then + ok = true; + end + self.session.print("Reloaded on "..host); + end + end + end + return ok, (ok and "Module reloaded on "..count.." host"..(count ~= 1 and "s" or "")) or ("Last error: "..tostring(err)); +end + +function def_env.module:list(hosts) + hosts = array.collect(set.new({ not hosts and "*" or nil }) + get_hosts_set(hosts)):sort(_sort_hosts); + + local print = self.session.print; + for _, host in ipairs(hosts) do + print((host == "*" and "Global" or host)..":"); + local modules = array.collect(keys(modulemanager.get_modules(host) or {})):sort(); + if #modules == 0 then + if prosody.hosts[host] then + print(" No modules loaded"); + else + print(" Host not found"); + end + else + for _, name in ipairs(modules) do + local status, status_text = modulemanager.get_module(host, name).module:get_status(); + local status_summary = ""; + if status == "warn" or status == "error" then + status_summary = (" (%s: %s)"):format(status, status_text); + end + print((" %s%s"):format(name, status_summary)); + end + end + end +end + +def_env.config = {}; +function def_env.config:load(filename, format) + local config_load = require "core.configmanager".load; + local ok, err = config_load(filename, format); + if not ok then + return false, err or "Unknown error loading config"; + end + return true, "Config loaded"; +end + +function def_env.config:get(host, key) + if key == nil then + host, key = "*", host; + end + local config_get = require "core.configmanager".get + return true, serialize_config(config_get(host, key)); +end + +function def_env.config:reload() + local ok, err = prosody.reload_config(); + return ok, (ok and "Config reloaded (you may need to reload modules to take effect)") or tostring(err); +end + +local function common_info(session, line) + if session.id then + line[#line+1] = "["..session.id.."]" + else + line[#line+1] = "["..session.type..(tostring(session):match("%x*$")).."]" + end +end + +local function session_flags(session, line) + line = line or {}; + common_info(session, line); + if session.type == "c2s" then + local status, priority = "unavailable", tostring(session.priority or "-"); + if session.presence then + status = session.presence:get_child_text("show") or "available"; + end + line[#line+1] = status.."("..priority..")"; + end + if session.cert_identity_status == "valid" then + line[#line+1] = "(authenticated)"; + end + if session.dialback_key then + line[#line+1] = "(dialback)"; + end + if session.external_auth then + line[#line+1] = "(SASL)"; + end + if 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 + if session.remote then + line[#line+1] = "(remote)"; + end + if session.incoming and session.outgoing then + line[#line+1] = "(bidi)"; + elseif session.is_bidi or session.bidi_session then + line[#line+1] = "(bidi)"; + end + if session.bosh_version then + line[#line+1] = "(bosh)"; + end + if session.websocket_request then + line[#line+1] = "(websocket)"; + end + return table.concat(line, " "); +end + +local function tls_info(session, line) + line = line or {}; + common_info(session, line); + if session.secure then + local sock = session.conn and session.conn.socket and session.conn:socket(); + if sock then + local info = sock.info and sock:info(); + if info then + line[#line+1] = ("(%s with %s)"):format(info.protocol, info.cipher); + else + -- TLS session might not be ready yet + line[#line+1] = "(cipher info unavailable)"; + end + if sock.getsniname then + local name = sock:getsniname(); + if name then + line[#line+1] = ("(SNI:%q)"):format(name); + end + end + if sock.getalpn then + local proto = sock:getalpn(); + if proto then + line[#line+1] = ("(ALPN:%q)"):format(proto); + end + end + end + else + line[#line+1] = "(insecure)"; + end + return table.concat(line, " "); +end + +def_env.c2s = {}; + +local function get_jid(session) + if session.username then + return session.full_jid or jid_join(session.username, session.host, session.resource); + end + + local conn = session.conn; + local ip = session.ip or "?"; + local clientport = conn and conn:clientport() or "?"; + local serverip = conn and conn.server and conn:server():ip() or "?"; + local serverport = conn and conn:serverport() or "?" + return jid_join("["..ip.."]:"..clientport, session.host or "["..serverip.."]:"..serverport); +end + +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: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 ""); + end + return (a.username or "") > (b.username or ""); + end + return _sort_hosts(a.host or "", b.host or ""); + end):map(function (session) + callback(get_jid(session), session) + end); +end + +function def_env.c2s:count() + local c2s = get_c2s(); + return true, "Total: ".. #c2s .." clients"; +end + +function def_env.c2s:show(match_jid, annotate) + local print, count = self.session.print, 0; + annotate = annotate or session_flags; + local curr_host = false; + show_c2s(function (jid, session) + if curr_host ~= session.host then + curr_host = session.host; + print(curr_host or "(not connected to any host yet)"); + end + if (not match_jid) or jid:match(match_jid) then + count = count + 1; + print(annotate(session, { " ", jid })); + end + end); + return true, "Total: "..count.." clients"; +end + +function def_env.c2s:show_insecure(match_jid) + local print, count = self.session.print, 0; + show_c2s(function (jid, session) + if ((not match_jid) or jid:match(match_jid)) and not session.secure then + count = count + 1; + print(jid); + end + end); + return true, "Total: "..count.." insecure client connections"; +end + +function def_env.c2s:show_secure(match_jid) + local print, count = self.session.print, 0; + show_c2s(function (jid, session) + if ((not match_jid) or jid:match(match_jid)) and session.secure then + count = count + 1; + print(jid); + end + end); + return true, "Total: "..count.." secure client connections"; +end + +function def_env.c2s:show_tls(match_jid) + return self:show(match_jid, tls_info); +end + +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(build_reason(text, condition)); + end + end); + return true, "Total: "..count.." sessions closed"; +end + +function def_env.c2s:closeall(text, condition) + local count = 0; + --luacheck: ignore 212/jid + show_c2s(function (jid, session) + count = count + 1; + session:close(build_reason(text, condition)); + end); + return true, "Total: "..count.." sessions closed"; +end + + +def_env.s2s = {}; +function def_env.s2s:show(match_jid, annotate) + local print = self.session.print; + annotate = annotate or session_flags; + + local count_in, count_out = 0,0; + local s2s_list = { }; + + 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, + annotate(session, { "", direction, remotehost or "?" })}; + + if (not match_jid) or remotehost:match(match_jid) or localhost:match(match_jid) then + table.insert(s2s_list, sess_lines); + -- luacheck: ignore 421/print + 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.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 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 + 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)); + end + end + end + end + end + + -- Sort by local host, then remote host + table.sort(s2s_list, function(a,b) + if a.l == b.l then return _sort_hosts(a.r, b.r); end + return _sort_hosts(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 + +function def_env.s2s:show_tls(match_jid) + return self:show(match_jid, tls_info); +end + +local function print_subject(print, subject) + for _, entry in ipairs(subject) do + print( + (" %s: %q"):format( + entry.name or entry.oid, + entry.value:gsub("[\r\n%z%c]", " ") + ) + ); + end +end + +-- As much as it pains me to use the 0-based depths that OpenSSL does, +-- I think there's going to be more confusion among operators if we +-- break from that. +local function print_errors(print, errors) + for depth, t in pairs(errors) do + print( + (" %d: %s"):format( + depth-1, + table.concat(t, "\n| ") + ) + ); + end +end + +function def_env.s2s:showcert(domain) + local print = self.session.print; + 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; + conn = conn and conn:socket(); + if not conn.getpeerchain then + if conn.dohandshake then + error("This version of LuaSec does not support certificate viewing"); + end + else + local cert = conn:getpeercertificate(); + if cert then + local certs = conn:getpeerchain(); + local digest = cert:digest("sha1"); + if not cert_set[digest] then + local chain_valid, chain_errors = conn:getpeerverification(); + cert_set[digest] = { + { + from = session.from_host, + to = session.to_host, + direction = session.direction + }; + chain_valid = chain_valid; + chain_errors = chain_errors; + certs = certs; + }; + else + table.insert(cert_set[digest], { + from = session.from_host, + to = session.to_host, + direction = session.direction + }); + end + end + end + end + local domain_certs = array.collect(values(cert_set)); + -- Phew. We now have a array of unique certificates presented by domain. + local n_certs = #domain_certs; + + if n_certs == 0 then + return "No certificates found for "..domain; + end + + local function _capitalize_and_colon(byte) + return string.upper(byte)..":"; + end + local function pretty_fingerprint(hash) + return hash:gsub("..", _capitalize_and_colon):sub(1, -2); + end + + for cert_info in values(domain_certs) do + local certs = cert_info.certs; + local cert = certs[1]; + print("---") + print("Fingerprint (SHA1): "..pretty_fingerprint(cert:digest("sha1"))); + print(""); + local n_streams = #cert_info; + print("Currently used on "..n_streams.." stream"..(n_streams==1 and "" or "s")..":"); + for _, stream in ipairs(cert_info) do + if stream.direction == "incoming" then + print(" "..stream.to.." <- "..stream.from); + else + print(" "..stream.from.." -> "..stream.to); + end + end + print(""); + local chain_valid, errors = cert_info.chain_valid, cert_info.chain_errors; + local valid_identity = cert_verify_identity(domain, "xmpp-server", cert); + if chain_valid then + print("Trusted certificate: Yes"); + else + print("Trusted certificate: No"); + print_errors(print, errors); + end + print(""); + print("Issuer: "); + print_subject(print, cert:issuer()); + print(""); + print("Valid for "..domain..": "..(valid_identity and "Yes" or "No")); + print("Subject:"); + print_subject(print, cert:subject()); + end + print("---"); + return ("Showing "..n_certs.." certificate" + ..(n_certs==1 and "" or "s") + .." presented by "..domain.."."); +end + +function def_env.s2s:close(from, to, text, condition) + local print, count = self.session.print, 0; + local s2s_sessions = module:shared"/*/s2s/sessions"; + + local match_id; + if from and not to then + match_id, from = from, nil; + 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 + + for _, session in pairs(s2s_sessions) do + 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, 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, 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(build_reason(text, condition)); + count = count + 1; + 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 + +def_env.host = {}; def_env.hosts = def_env.host; + +function def_env.host:activate(hostname, config) + return hostmanager.activate(hostname, config); +end +function def_env.host:deactivate(hostname, reason) + return hostmanager.deactivate(hostname, reason); +end + +function def_env.host:list() + local print = self.session.print; + local i = 0; + local type; + for host, host_session in iterators.sorted_pairs(prosody.hosts, _sort_hosts) do + i = i + 1; + type = host_session.type; + if type == "local" then + print(host); + else + type = module:context(host):get_option_string("component_module", type); + if type ~= "component" then + type = type .. " component"; + end + print(("%s (%s)"):format(host, type)); + end + end + return true, i.." hosts"; +end + +def_env.port = {}; + +function def_env.port:list() + local print = self.session.print; + local services = portmanager.get_active_services().data; + local n_services, n_ports = 0, 0; + for service, interfaces in iterators.sorted_pairs(services) do + n_services = n_services + 1; + local ports_list = {}; + for interface, ports in pairs(interfaces) do + for port in pairs(ports) do + table.insert(ports_list, "["..interface.."]:"..port); + end + end + n_ports = n_ports + #ports_list; + print(service..": "..table.concat(ports_list, ", ")); + end + return true, n_services.." services listening on "..n_ports.." ports"; +end + +function def_env.port:close(close_port, close_interface) + close_port = assert(tonumber(close_port), "Invalid port number"); + local n_closed = 0; + local services = portmanager.get_active_services().data; + for service, interfaces in pairs(services) do -- luacheck: ignore 213 + for interface, ports in pairs(interfaces) do + if not close_interface or close_interface == interface then + if ports[close_port] then + self.session.print("Closing ["..interface.."]:"..close_port.."..."); + local ok, err = portmanager.close(interface, close_port) + if not ok then + self.session.print("Failed to close "..interface.." "..close_port..": "..err); + else + n_closed = n_closed + 1; + end + end + end + end + end + return true, "Closed "..n_closed.." ports"; +end + +def_env.muc = {}; + +local console_room_mt = { + __index = function (self, k) return self.room[k]; end; + __tostring = function (self) + return "MUC room <"..self.room.jid..">"; + end; +}; + +local function check_muc(jid) + local room_name, host = jid_split(jid); + if not prosody.hosts[host] then + return nil, "No such host: "..host; + elseif not prosody.hosts[host].modules.muc then + return nil, "Host '"..host.."' is not a MUC service"; + end + return room_name, host; +end + +function def_env.muc:create(room_jid, config) + local room_name, host = check_muc(room_jid); + if not room_name then + return room_name, host; + end + if not room_name then return nil, host end + if config ~= nil and type(config) ~= "table" then return nil, "Config must be a table"; end + if prosody.hosts[host].modules.muc.get_room_from_jid(room_jid) then return nil, "Room exists already" end + return prosody.hosts[host].modules.muc.create_room(room_jid, config); +end + +function def_env.muc:room(room_jid) + local room_name, host = check_muc(room_jid); + if not room_name then + return room_name, host; + end + local room_obj = prosody.hosts[host].modules.muc.get_room_from_jid(room_jid); + if not room_obj then + return nil, "No such room: "..room_jid; + end + return setmetatable({ room = room_obj }, console_room_mt); +end + +function def_env.muc:list(host) + local host_session = prosody.hosts[host]; + if not host_session or not host_session.modules.muc then + return nil, "Please supply the address of a local MUC component"; + end + local print = self.session.print; + local c = 0; + for room in host_session.modules.muc.each_room() do + print(room.jid); + c = c + 1; + end + return true, c.." rooms"; +end + +local um = require"core.usermanager"; + +def_env.user = {}; +function def_env.user:create(jid, password) + local username, host = jid_split(jid); + if not prosody.hosts[host] then + return nil, "No such host: "..host; + elseif um.user_exists(username, host) then + return nil, "User exists"; + end + local ok, err = um.create_user(username, password, host); + if ok then + return true, "User created"; + else + return nil, "Could not create user: "..err; + end +end + +function def_env.user:delete(jid) + local username, host = jid_split(jid); + if not prosody.hosts[host] then + return nil, "No such host: "..host; + elseif not um.user_exists(username, host) then + return nil, "No such user"; + end + local ok, err = um.delete_user(username, host); + if ok then + return true, "User deleted"; + else + return nil, "Could not delete user: "..err; + end +end + +function def_env.user:password(jid, password) + local username, host = jid_split(jid); + if not prosody.hosts[host] then + return nil, "No such host: "..host; + elseif not um.user_exists(username, host) then + return nil, "No such user"; + end + local ok, err = um.set_password(username, password, host, nil); + if ok then + return true, "User password changed"; + else + return nil, "Could not change password for user: "..err; + end +end + +function def_env.user:list(host, pat) + if not host then + return nil, "No host given"; + elseif not prosody.hosts[host] then + return nil, "No such host"; + end + local print = self.session.print; + local total, matches = 0, 0; + for user in um.users(host) do + if not pat or user:match(pat) then + print(user.."@"..host); + matches = matches + 1; + end + total = total + 1; + end + return true, "Showing "..(pat and (matches.." of ") or "all " )..total.." users"; +end + +def_env.xmpp = {}; + +local new_id = require "util.id".medium; +function def_env.xmpp:ping(localhost, remotehost, timeout) + localhost = select(2, jid_split(localhost)); + remotehost = select(2, jid_split(remotehost)); + if not localhost then + return nil, "Invalid sender hostname"; + elseif not prosody.hosts[localhost] then + return nil, "No such local host"; + end + if not remotehost then + return nil, "Invalid destination hostname"; + elseif prosody.hosts[remotehost] then + return nil, "Both hosts are local"; + end + local iq = st.iq{ from=localhost, to=remotehost, type="get", id=new_id()} + :tag("ping", {xmlns="urn:xmpp:ping"}); + local time_start = time.now(); + local ret, err = async.wait(module:context(localhost):send_iq(iq, nil, timeout)); + if ret then + return true, ("pong from %s in %gs"):format(ret.stanza.attr.from, time.now() - time_start); + else + return false, tostring(err); + end +end + +def_env.dns = {}; +local adns = require"net.adns"; + +local function get_resolver(session) + local resolver = session.dns_resolver; + if not resolver then + resolver = adns.resolver(); + session.dns_resolver = resolver; + end + return resolver; +end + +function def_env.dns:lookup(name, typ, class) + local resolver = get_resolver(self.session); + local ret, err = async.wait(resolver:lookup_promise(name, typ, class)); + if ret then + return true, ret; + elseif err then + return false, err; + end +end + +function def_env.dns:addnameserver(...) + local resolver = get_resolver(self.session); + resolver._resolver:addnameserver(...) + return true +end + +function def_env.dns:setnameserver(...) + local resolver = get_resolver(self.session); + resolver._resolver:setnameserver(...) + return true +end + +function def_env.dns:purge() + local resolver = get_resolver(self.session); + resolver._resolver:purge() + return true +end + +function def_env.dns:cache() + local resolver = get_resolver(self.session); + return true, "Cache:\n"..tostring(resolver._resolver.cache) +end + +def_env.http = {}; + +function def_env.http:list(hosts) + local print = self.session.print; + + for host in get_hosts_set(hosts) do + local http_apps = modulemanager.get_items("http-provider", host); + if #http_apps > 0 then + local http_host = module:context(host):get_option_string("http_host"); + print("HTTP endpoints on "..host..(http_host and (" (using "..http_host.."):") or ":")); + for _, provider in ipairs(http_apps) do + local url = module:context(host):http_url(provider.name, provider.default_path); + print("", url); + end + print(""); + end + end + + local default_host = module:get_option_string("http_default_host"); + if not default_host then + print("HTTP requests to unknown hosts will return 404 Not Found"); + else + print("HTTP requests to unknown hosts will be handled by "..default_host); + end + return true; +end + +def_env.debug = {}; + +function def_env.debug:logevents(host) + helpers.log_host_events(host); + return true; +end + +function def_env.debug:events(host, event) + local events_obj; + if host and host ~= "*" then + if host == "http" then + events_obj = require "net.http.server"._events; + elseif not prosody.hosts[host] then + return false, "Unknown host: "..host; + else + events_obj = prosody.hosts[host].events; + end + else + events_obj = prosody.events; + end + return true, helpers.show_events(events_obj, event); +end + +function def_env.debug:timers() + local print = self.session.print; + local add_task = require"util.timer".add_task; + local h, params = add_task.h, add_task.params; + if h then + print("-- util.timer"); + for i, id in ipairs(h.ids) do + if not params[id] then + print(os.date("%F %T", h.priorities[i]), h.items[id]); + elseif not params[id].callback then + print(os.date("%F %T", h.priorities[i]), h.items[id], unpack(params[id])); + else + print(os.date("%F %T", h.priorities[i]), params[id].callback, unpack(params[id])); + end + end + end + if server.event_base then + local count = 0; + for _, v in pairs(debug.getregistry()) do + if type(v) == "function" and v.callback and v.callback == add_task._on_timer then + count = count + 1; + end + end + print(count .. " libevent callbacks"); + end + if h then + local next_time = h:peek(); + if next_time then + return true, os.date("Next event at %F %T (in %%.6fs)", next_time):format(next_time - time.now()); + end + end + return true; +end + +-- COMPAT: debug:timers() was timer:info() for some time in trunk +def_env.timer = { info = def_env.debug.timers }; + +def_env.stats = {}; + +local function format_stat(type, value, ref_value) + ref_value = ref_value or value; + --do return tostring(value) end + if type == "duration" then + if ref_value < 0.001 then + return ("%g µs"):format(value*1000000); + elseif ref_value < 0.9 then + return ("%0.2f ms"):format(value*1000); + end + return ("%0.2f"):format(value); + elseif type == "size" then + if ref_value > 1048576 then + return ("%d MB"):format(value/1048576); + elseif ref_value > 1024 then + return ("%d KB"):format(value/1024); + end + return ("%d bytes"):format(value); + elseif type == "rate" then + if ref_value < 0.9 then + return ("%0.2f/min"):format(value*60); + end + return ("%0.2f/sec"):format(value); + end + return tostring(value); +end + +local stats_methods = {}; +function stats_methods:bounds(_lower, _upper) + for _, stat_info in ipairs(self) do + local data = stat_info[4]; + if data then + local lower = _lower or data.min; + local upper = _upper or data.max; + local new_data = { + min = lower; + max = upper; + samples = {}; + sample_count = 0; + count = data.count; + units = data.units; + }; + local sum = 0; + for _, v in ipairs(data.samples) do + if v > upper then + break; + elseif v>=lower then + table.insert(new_data.samples, v); + sum = sum + v; + end + end + new_data.sample_count = #new_data.samples; + stat_info[4] = new_data; + stat_info[3] = sum/new_data.sample_count; + end + end + return self; +end + +function stats_methods:trim(lower, upper) + upper = upper or (100-lower); + local statistics = require "util.statistics"; + for _, stat_info in ipairs(self) do + -- Strip outliers + local data = stat_info[4]; + if data then + local new_data = { + min = statistics.get_percentile(data, lower); + max = statistics.get_percentile(data, upper); + samples = {}; + sample_count = 0; + count = data.count; + units = data.units; + }; + local sum = 0; + for _, v in ipairs(data.samples) do + if v > new_data.max then + break; + elseif v>=new_data.min then + table.insert(new_data.samples, v); + sum = sum + v; + end + end + new_data.sample_count = #new_data.samples; + stat_info[4] = new_data; + stat_info[3] = sum/new_data.sample_count; + end + end + return self; +end + +function stats_methods:max(upper) + return self:bounds(nil, upper); +end + +function stats_methods:min(lower) + return self:bounds(lower, nil); +end + +function stats_methods:summary() + local statistics = require "util.statistics"; + for _, stat_info in ipairs(self) do + local type, value, data = stat_info[2], stat_info[3], stat_info[4]; + if data and data.samples then + table.insert(stat_info.output, string.format("Count: %d (%d captured)", + data.count, + data.sample_count + )); + table.insert(stat_info.output, string.format("Min: %s Mean: %s Max: %s", + format_stat(type, data.min), + format_stat(type, value), + format_stat(type, data.max) + )); + table.insert(stat_info.output, string.format("Q1: %s Median: %s Q3: %s", + format_stat(type, statistics.get_percentile(data, 25)), + format_stat(type, statistics.get_percentile(data, 50)), + format_stat(type, statistics.get_percentile(data, 75)) + )); + end + end + return self; +end + +function stats_methods:cfgraph() + for _, stat_info in ipairs(self) do + local name, type, value, data = unpack(stat_info, 1, 4); -- luacheck: ignore 211 + local function print(s) + table.insert(stat_info.output, s); + end + + if data and data.sample_count and data.sample_count > 0 then + local raw_histogram = require "util.statistics".get_histogram(data); + + local graph_width, graph_height = 50, 10; + local eighth_chars = " ▁▂▃▄▅▆▇█"; + + local range = data.max - data.min; + + if range > 0 then + local x_scaling = #raw_histogram/graph_width; + local histogram = {}; + for i = 1, graph_width do + histogram[i] = math.max(raw_histogram[i*x_scaling-1] or 0, raw_histogram[i*x_scaling] or 0); + end + + print(""); + print(("_"):rep(52)..format_stat(type, data.max)); + for row = graph_height, 1, -1 do + local row_chars = {}; + local min_eighths, max_eighths = 8, 0; + for i = 1, #histogram do + local char_eighths = math.ceil(math.max(math.min((graph_height/(data.max/histogram[i]))-(row-1), 1), 0)*8); + if char_eighths < min_eighths then + min_eighths = char_eighths; + end + if char_eighths > max_eighths then + max_eighths = char_eighths; + end + if char_eighths == 0 then + row_chars[i] = "-"; + else + local char = eighth_chars:sub(char_eighths*3+1, char_eighths*3+3); + row_chars[i] = char; + end + end + print(table.concat(row_chars).."|-"..format_stat(type, data.max/(graph_height/(row-0.5)))); + end + print(("\\ "):rep(11)); + local x_labels = {}; + for i = 1, 11 do + local s = ("%-4s"):format((i-1)*10); + if #s > 4 then + s = s:sub(1, 3).."…"; + end + x_labels[i] = s; + end + print(" "..table.concat(x_labels, " ")); + local units = "%"; + local margin = math.floor((graph_width-#units)/2); + print((" "):rep(margin)..units); + else + print("[range too small to graph]"); + end + print(""); + end + end + return self; +end + +function stats_methods:histogram() + for _, stat_info in ipairs(self) do + local name, type, value, data = unpack(stat_info, 1, 4); -- luacheck: ignore 211 + local function print(s) + table.insert(stat_info.output, s); + end + + if not data then + print("[no data]"); + return self; + elseif not data.sample_count then + print("[not a sampled metric type]"); + return self; + end + + local graph_width, graph_height = 50, 10; + local eighth_chars = " ▁▂▃▄▅▆▇█"; + + local range = data.max - data.min; + + if range > 0 then + local n_buckets = graph_width; + + local histogram = {}; + for i = 1, n_buckets do + histogram[i] = 0; + end + local max_bin_samples = 0; + for _, d in ipairs(data.samples) do + local bucket = math.floor(1+(n_buckets-1)/(range/(d-data.min))); + histogram[bucket] = histogram[bucket] + 1; + if histogram[bucket] > max_bin_samples then + max_bin_samples = histogram[bucket]; + end + end + + print(""); + print(("_"):rep(52)..max_bin_samples); + for row = graph_height, 1, -1 do + local row_chars = {}; + local min_eighths, max_eighths = 8, 0; + for i = 1, #histogram do + local char_eighths = math.ceil(math.max(math.min((graph_height/(max_bin_samples/histogram[i]))-(row-1), 1), 0)*8); + if char_eighths < min_eighths then + min_eighths = char_eighths; + end + if char_eighths > max_eighths then + max_eighths = char_eighths; + end + if char_eighths == 0 then + row_chars[i] = "-"; + else + local char = eighth_chars:sub(char_eighths*3+1, char_eighths*3+3); + row_chars[i] = char; + end + end + print(table.concat(row_chars).."|-"..math.ceil((max_bin_samples/graph_height)*(row-0.5))); + end + print(("\\ "):rep(11)); + local x_labels = {}; + for i = 1, 11 do + local s = ("%-4s"):format(format_stat(type, data.min+range*i/11, data.min):match("^%S+")); + if #s > 4 then + s = s:sub(1, 3).."…"; + end + x_labels[i] = s; + end + print(" "..table.concat(x_labels, " ")); + local units = format_stat(type, data.min):match("%s+(.+)$") or data.units or ""; + local margin = math.floor((graph_width-#units)/2); + print((" "):rep(margin)..units); + else + print("[range too small to graph]"); + end + print(""); + end + return self; +end + +local function stats_tostring(stats) + local print = stats.session.print; + for _, stat_info in ipairs(stats) do + if #stat_info.output > 0 then + print("\n#"..stat_info[1]); + print(""); + for _, v in ipairs(stat_info.output) do + print(v); + end + print(""); + else + print(("%-50s %s"):format(stat_info[1], format_stat(stat_info[2], stat_info[3]))); + end + end + return #stats.." statistics displayed"; +end + +local stats_mt = {__index = stats_methods, __tostring = stats_tostring } +local function new_stats_context(self) + return setmetatable({ session = self.session, stats = true }, stats_mt); +end + +function def_env.stats:show(filter) + -- luacheck: ignore 211/changed + local stats, changed, extra = require "core.statsmanager".get_stats(); + local available, displayed = 0, 0; + local displayed_stats = new_stats_context(self); + for name, value in iterators.sorted_pairs(stats) do + available = available + 1; + if not filter or name:match(filter) then + displayed = displayed + 1; + local type = name:match(":(%a+)$"); + table.insert(displayed_stats, { + name, type, value, extra[name]; + output = {}; + }); + end + end + return displayed_stats; +end + + + +------------- + +function printbanner(session) + local option = module:get_option_string("console_banner", "full"); + if option == "full" or option == "graphic" then + session.print [[ + ____ \ / _ + | _ \ _ __ ___ ___ _-_ __| |_ _ + | |_) | '__/ _ \/ __|/ _ \ / _` | | | | + | __/| | | (_) \__ \ |_| | (_| | |_| | + |_| |_| \___/|___/\___/ \__,_|\__, | + A study in simplicity |___/ + +]] + end + if option == "short" or option == "full" then + session.print("Welcome to the Prosody administration console. For a list of commands, type: help"); + session.print("You may find more help on using this console in our online documentation at "); + session.print("https://prosody.im/doc/console\n"); + end + if option ~= "short" and option ~= "full" and option ~= "graphic" then + session.print(option); + end +end -- cgit v1.2.3 From b0463f02908a21058f273aa971a1c06808a4cdd0 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 1 Jun 2020 15:43:47 +0100 Subject: mod_admin_telnet: Become a front for mod_admin_shell --- plugins/mod_admin_telnet.lua | 1623 +----------------------------------------- 1 file changed, 35 insertions(+), 1588 deletions(-) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index a082a851..ec8ff136 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -8,46 +8,37 @@ -- luacheck: ignore 212/self module:set_global(); - -local hostmanager = require "core.hostmanager"; -local modulemanager = require "core.modulemanager"; -local s2smanager = require "core.s2smanager"; -local portmanager = require "core.portmanager"; -local helpers = require "util.helpers"; -local server = require "net.server"; - -local _G = _G; - -local prosody = _G.prosody; +module:depends("admin_shell"); local console_listener = { default_port = 5582; default_mode = "*a"; interface = "127.0.0.1" }; -local unpack = table.unpack or unpack; -- luacheck: ignore 113 -local iterators = require "util.iterators"; -local keys, values = iterators.keys, iterators.values; -local jid_bare, jid_split, jid_join = import("util.jid", "bare", "prepped_split", "join"); -local set, array = require "util.set", require "util.array"; -local cert_verify_identity = require "util.x509".verify_identity; -local envload = require "util.envload".envload; -local envloadfile = require "util.envload".envloadfile; -local has_pposix, pposix = pcall(require, "util.pposix"); local async = require "util.async"; -local serialization = require "util.serialization"; -local serialize_config = serialization.new ({ fatal = false, unquoted = true}); -local time = require "util.time"; +local st = require "util.stanza"; -local commands = module:shared("commands") -local def_env = module:shared("env"); +local def_env = module:shared("admin_shell/env"); local default_env_mt = { __index = def_env }; -local function redirect_output(target, session) - local env = setmetatable({ print = session.print }, { __index = function (_, k) return rawget(target, k); end }); - env.dofile = function(name) - local f, err = envloadfile(name, env); - if not f then return f, err; end - return f(); - end; - return env; +local function printbanner(session) + local option = module:get_option_string("console_banner", "full"); + if option == "full" or option == "graphic" then + session.print [[ + ____ \ / _ + | _ \ _ __ ___ ___ _-_ __| |_ _ + | |_) | '__/ _ \/ __|/ _ \ / _` | | | | + | __/| | | (_) \__ \ |_| | (_| | |_| | + |_| |_| \___/|___/\___/ \__,_|\__, | + A study in simplicity |___/ + +]] + end + if option == "short" or option == "full" then + session.print("Welcome to the Prosody administration console. For a list of commands, type: help"); + session.print("You may find more help on using this console in our online documentation at "); + session.print("https://prosody.im/doc/console\n"); + end + if option ~= "short" and option ~= "full" and option ~= "graphic" then + session.print(option); + end end console = {}; @@ -73,7 +64,12 @@ end function console:new_session(conn) local w = function(s) conn:write(s:gsub("\n", "\r\n")); end; local session = { conn = conn; - send = function (t) w(tostring(t)); end; + send = function (t) + if st.is_stanza(t) and t.name == "repl-result" then + t = "| "..t:get_text().."\n"; + end + w(tostring(t)); + end; print = function (...) local t = {}; for i=1,select("#", ...) do @@ -104,59 +100,13 @@ function console:new_session(conn) end function console:process_line(session, line) - local useglobalenv; - - if line:match("^>") then - line = line:gsub("^>", ""); - useglobalenv = true; - elseif line == "\004" then - commands["bye"](session, line); - return; - else - local command = line:match("^%w+") or line:match("%p"); - if commands[command] then - commands[command](session, line); - return; - end - end - - 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 - -- luacheck: ignore 311/err - local chunk, err = envload("return "..line, chunkname, env); - if not chunk then - chunk, err = envload(line, chunkname, env); - if not chunk then - err = err:gsub("^%[string .-%]:%d+: ", ""); - err = err:gsub("^:%d+: ", ""); - err = err:gsub("''", "the end of the line"); - session.print("Sorry, I couldn't understand that... "..err); - return; - end - end - - local taskok, message = chunk(); - - if not message then - if type(taskok) ~= "string" and useglobalenv then - taskok = session.serialize(taskok); - end - session.print("Result: "..tostring(taskok)); - return; - elseif (not taskok) and message then - session.print("Command completed with a problem"); - session.print("Message: "..tostring(message)); + line = line:gsub("\r?\n$", ""); + if line == "bye" or line == "quit" or line == "exit" or line:byte() == 4 then + session.print("See you!"); + session:disconnect(); return; end - - session.print("OK: "..tostring(message)); + return module:fire_event("admin/repl-line", { origin = session, stanza = st.stanza("repl"):text(line) }); end local sessions = {}; @@ -218,1509 +168,6 @@ function console_listener.ondetach(conn) sessions[conn] = nil; end --- Console commands -- --- These are simple commands, not valid standalone in Lua - -function commands.bye(session) - session.print("See you! :)"); - session.closed = true; - session.disconnect(); -end -commands.quit, commands.exit = commands.bye, commands.bye; - -commands["!"] = function (session, data) - if data:match("^!!") and session.env._ then - session.print("!> "..session.env._); - return console_listener.onincoming(session.conn, session.env._); - end - local old, new = data:match("^!(.-[^\\])!(.-)!$"); - if old and new then - local ok, res = pcall(string.gsub, session.env._, old, new); - if not ok then - session.print(res) - return; - end - session.print("!> "..res); - return console_listener.onincoming(session.conn, res); - end - session.print("Sorry, not sure what you want"); -end - - -function commands.help(session, data) - local print = session.print; - local section = data:match("^help (%w+)"); - if not section then - print [[Commands are divided into multiple sections. For help on a particular section, ]] - print [[type: help SECTION (for example, 'help c2s'). Sections are: ]] - print [[]] - print [[c2s - Commands to manage local client-to-server sessions]] - print [[s2s - Commands to manage sessions between this server and others]] - print [[http - Commands to inspect HTTP services]] -- XXX plural but there is only one so far - print [[module - Commands to load/reload/unload modules/plugins]] - print [[host - Commands to activate, deactivate and list virtual hosts]] - print [[user - Commands to create and delete users, and change their passwords]] - 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 [[debug - Commands for debugging the server]] - print [[config - Reloading the configuration, etc.]] - print [[console - Help regarding the console itself]] - elseif section == "c2s" then - print [[c2s:show(jid) - Show all client sessions with the specified JID (or all if no JID given)]] - print [[c2s:show_insecure() - Show all unencrypted client connections]] - print [[c2s:show_secure() - Show all encrypted client connections]] - print [[c2s: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 - print [[s2s:show(domain) - Show all s2s connections for the given domain (or all if no domain given)]] - print [[s2s:show_tls(domain) - Show TLS cipher info for encrypted sessions]] - print [[s2s:close(from, to) - Close a connection from one domain to another]] - print [[s2s:closeall(host) - Close all the incoming/outgoing s2s sessions to specified host]] - elseif section == "http" then - print [[http:list(hosts) - Show HTTP endpoints]] - elseif section == "module" then - print [[module:load(module, host) - Load the specified module on the specified host (or all hosts if none given)]] - print [[module:reload(module, host) - The same, but unloads and loads the module (saving state if the module supports it)]] - print [[module:unload(module, host) - The same, but just unloads the module from memory]] - print [[module:list(host) - List the modules loaded on the specified host]] - elseif section == "host" then - print [[host:activate(hostname) - Activates the specified host]] - print [[host:deactivate(hostname) - Disconnects all clients on this host and deactivates]] - print [[host:list() - List the currently-activated hosts]] - elseif section == "user" then - print [[user:create(jid, password) - Create the specified user account]] - print [[user:password(jid, password) - Set the password for the specified user account]] - print [[user:delete(jid) - Permanently remove the specified user account]] - print [[user:list(hostname, pattern) - List users on the specified host, optionally filtering with a pattern]] - elseif section == "server" then - print [[server:version() - Show the server's version number]] - print [[server:uptime() - Show how long the server has been running]] - print [[server:memory() - Show details about the server's memory usage]] - print [[server:shutdown(reason) - Shut down the server, with an optional reason to be broadcast to all connections]] - elseif section == "port" then - print [[port:list() - Lists all network ports prosody currently listens on]] - print [[port:close(port, interface) - Close a port]] - elseif section == "dns" then - print [[dns:lookup(name, type, class) - Do a DNS lookup]] - print [[dns:addnameserver(nameserver) - Add a nameserver to the list]] - 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.]] - print [[config:get([host,] option) - Show the value of a config option.]] - elseif section == "stats" then -- luacheck: ignore 542 - -- TODO describe how stats:show() works - elseif section == "debug" then - print [[debug:logevents(host) - Enable logging of fired events on host]] - print [[debug:events(host, event) - Show registered event handlers]] - print [[debug:timers() - Show information about scheduled timers]] - elseif section == "console" then - print [[Hey! Welcome to Prosody's admin console.]] - print [[First thing, if you're ever wondering how to get out, simply type 'quit'.]] - print [[Secondly, note that we don't support the full telnet protocol yet (it's coming)]] - print [[so you may have trouble using the arrow keys, etc. depending on your system.]] - print [[]] - print [[For now we offer a couple of handy shortcuts:]] - print [[!! - Repeat the last command]] - print [[!old!new! - repeat the last command, but with 'old' replaced by 'new']] - print [[]] - print [[For those well-versed in Prosody's internals, or taking instruction from those who are,]] - print [[you can prefix a command with > to escape the console sandbox, and access everything in]] - print [[the running server. Great fun, but be careful not to break anything :)]] - end - print [[]] -end - --- Session environment -- --- Anything in def_env will be accessible within the session as a global variable - ---luacheck: ignore 212/self -local serialize_defaults = module:get_option("console_prettyprint_settings", { fatal = false, unquoted = true, maxdepth = 2}) - -def_env.output = {}; -function def_env.output:configure(opts) - if type(opts) ~= "table" then - opts = { preset = opts }; - end - if not opts.fallback then - -- XXX Error message passed to fallback is lost, does it matter? - opts.fallback = tostring; - end - for k,v in pairs(serialize_defaults) do - if opts[k] == nil then - opts[k] = v; - end - end - self.session.serialize = serialization.new(opts); -end - -def_env.server = {}; - -function def_env.server:insane_reload() - prosody.unlock_globals(); - dofile "prosody" - prosody = _G.prosody; - return true, "Server reloaded"; -end - -function def_env.server:version() - return true, tostring(prosody.version or "unknown"); -end - -function def_env.server:uptime() - local t = os.time()-prosody.start_time; - local seconds = t%60; - t = (t - seconds)/60; - local minutes = t%60; - t = (t - minutes)/60; - local hours = t%24; - t = (t - hours)/24; - local days = t; - return true, string.format("This server has been running for %d day%s, %d hour%s and %d minute%s (since %s)", - days, (days ~= 1 and "s") or "", hours, (hours ~= 1 and "s") or "", - minutes, (minutes ~= 1 and "s") or "", os.date("%c", prosody.start_time)); -end - -function def_env.server:shutdown(reason) - prosody.shutdown(reason); - return true, "Shutdown initiated"; -end - -local function human(kb) - local unit = "K"; - if kb > 1024 then - kb, unit = kb/1024, "M"; - end - return ("%0.2f%sB"):format(kb, unit); -end - -function def_env.server:memory() - if not has_pposix or not pposix.meminfo then - return true, "Lua is using "..human(collectgarbage("count")); - end - local mem, lua_mem = pposix.meminfo(), collectgarbage("count"); - local print = self.session.print; - print("Process: "..human((mem.allocated+mem.allocated_mmap)/1024)); - print(" Used: "..human(mem.used/1024).." ("..human(lua_mem).." by Lua)"); - print(" Free: "..human(mem.unused/1024).." ("..human(mem.returnable/1024).." returnable)"); - return true, "OK"; -end - -def_env.module = {}; - -local function get_hosts_set(hosts) - if type(hosts) == "table" then - if hosts[1] then - return set.new(hosts); - elseif hosts._items then - return hosts; - end - elseif type(hosts) == "string" then - return set.new { hosts }; - elseif hosts == nil then - return set.new(array.collect(keys(prosody.hosts))); - end -end - --- Hosts with a module or all virtualhosts if no module given --- matching modules_enabled in the global section -local function get_hosts_with_module(hosts, module) - local hosts_set = get_hosts_set(hosts) - / function (host) - if module then - -- Module given, filter in hosts with this module loaded - if modulemanager.is_loaded(host, module) then - return host; - else - return nil; - end - end - if not hosts then - -- No hosts given, filter in VirtualHosts - if prosody.hosts[host].type == "local" then - return host; - else - return nil - end - end; - -- No module given, but hosts are, don't filter at all - return host; - end; - if module and modulemanager.get_module("*", module) then - hosts_set:add("*"); - end - return hosts_set; -end - -function def_env.module:load(name, hosts, config) - hosts = get_hosts_with_module(hosts); - - -- Load the module for each host - local ok, err, count, mod = true, nil, 0; - for host in hosts do - if (not modulemanager.is_loaded(host, name)) then - mod, err = modulemanager.load(host, name, config); - if not mod then - ok = false; - if err == "global-module-already-loaded" then - if count > 0 then - ok, err, count = true, nil, 1; - end - break; - end - self.session.print(err or "Unknown error loading module"); - else - count = count + 1; - self.session.print("Loaded for "..mod.module.host); - end - end - end - - return ok, (ok and "Module loaded onto "..count.." host"..(count ~= 1 and "s" or "")) or ("Last error: "..tostring(err)); -end - -function def_env.module:unload(name, hosts) - hosts = get_hosts_with_module(hosts, name); - - -- Unload the module for each host - local ok, err, count = true, nil, 0; - for host in hosts do - if modulemanager.is_loaded(host, name) then - ok, err = modulemanager.unload(host, name); - if not ok then - ok = false; - self.session.print(err or "Unknown error unloading module"); - else - count = count + 1; - self.session.print("Unloaded from "..host); - end - end - end - return ok, (ok and "Module unloaded from "..count.." host"..(count ~= 1 and "s" or "")) or ("Last error: "..tostring(err)); -end - -local function _sort_hosts(a, b) - if a == "*" then return true - elseif b == "*" then return false - else return a:gsub("[^.]+", string.reverse):reverse() < b:gsub("[^.]+", string.reverse):reverse(); end -end - -function def_env.module:reload(name, hosts) - hosts = array.collect(get_hosts_with_module(hosts, name)):sort(_sort_hosts) - - -- Reload the module for each host - local ok, err, count = true, nil, 0; - for _, host in ipairs(hosts) do - if modulemanager.is_loaded(host, name) then - ok, err = modulemanager.reload(host, name); - if not ok then - ok = false; - self.session.print(err or "Unknown error reloading module"); - else - count = count + 1; - if ok == nil then - ok = true; - end - self.session.print("Reloaded on "..host); - end - end - end - return ok, (ok and "Module reloaded on "..count.." host"..(count ~= 1 and "s" or "")) or ("Last error: "..tostring(err)); -end - -function def_env.module:list(hosts) - hosts = array.collect(set.new({ not hosts and "*" or nil }) + get_hosts_set(hosts)):sort(_sort_hosts); - - local print = self.session.print; - for _, host in ipairs(hosts) do - print((host == "*" and "Global" or host)..":"); - local modules = array.collect(keys(modulemanager.get_modules(host) or {})):sort(); - if #modules == 0 then - if prosody.hosts[host] then - print(" No modules loaded"); - else - print(" Host not found"); - end - else - for _, name in ipairs(modules) do - local status, status_text = modulemanager.get_module(host, name).module:get_status(); - local status_summary = ""; - if status == "warn" or status == "error" then - status_summary = (" (%s: %s)"):format(status, status_text); - end - print((" %s%s"):format(name, status_summary)); - end - end - end -end - -def_env.config = {}; -function def_env.config:load(filename, format) - local config_load = require "core.configmanager".load; - local ok, err = config_load(filename, format); - if not ok then - return false, err or "Unknown error loading config"; - end - return true, "Config loaded"; -end - -function def_env.config:get(host, key) - if key == nil then - host, key = "*", host; - end - local config_get = require "core.configmanager".get - return true, serialize_config(config_get(host, key)); -end - -function def_env.config:reload() - local ok, err = prosody.reload_config(); - return ok, (ok and "Config reloaded (you may need to reload modules to take effect)") or tostring(err); -end - -local function common_info(session, line) - if session.id then - line[#line+1] = "["..session.id.."]" - else - line[#line+1] = "["..session.type..(tostring(session):match("%x*$")).."]" - end -end - -local function session_flags(session, line) - line = line or {}; - common_info(session, line); - if session.type == "c2s" then - local status, priority = "unavailable", tostring(session.priority or "-"); - if session.presence then - status = session.presence:get_child_text("show") or "available"; - end - line[#line+1] = status.."("..priority..")"; - end - if session.cert_identity_status == "valid" then - line[#line+1] = "(authenticated)"; - end - if session.dialback_key then - line[#line+1] = "(dialback)"; - end - if session.external_auth then - line[#line+1] = "(SASL)"; - end - if 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 - if session.remote then - line[#line+1] = "(remote)"; - end - if session.incoming and session.outgoing then - line[#line+1] = "(bidi)"; - elseif session.is_bidi or session.bidi_session then - line[#line+1] = "(bidi)"; - end - if session.bosh_version then - line[#line+1] = "(bosh)"; - end - if session.websocket_request then - line[#line+1] = "(websocket)"; - end - return table.concat(line, " "); -end - -local function tls_info(session, line) - line = line or {}; - common_info(session, line); - if session.secure then - local sock = session.conn and session.conn.socket and session.conn:socket(); - if sock then - local info = sock.info and sock:info(); - if info then - line[#line+1] = ("(%s with %s)"):format(info.protocol, info.cipher); - else - -- TLS session might not be ready yet - line[#line+1] = "(cipher info unavailable)"; - end - if sock.getsniname then - local name = sock:getsniname(); - if name then - line[#line+1] = ("(SNI:%q)"):format(name); - end - end - if sock.getalpn then - local proto = sock:getalpn(); - if proto then - line[#line+1] = ("(ALPN:%q)"):format(proto); - end - end - end - else - line[#line+1] = "(insecure)"; - end - return table.concat(line, " "); -end - -def_env.c2s = {}; - -local function get_jid(session) - if session.username then - return session.full_jid or jid_join(session.username, session.host, session.resource); - end - - local conn = session.conn; - local ip = session.ip or "?"; - local clientport = conn and conn:clientport() or "?"; - local serverip = conn and conn.server and conn:server():ip() or "?"; - local serverport = conn and conn:serverport() or "?" - return jid_join("["..ip.."]:"..clientport, session.host or "["..serverip.."]:"..serverport); -end - -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: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 ""); - end - return (a.username or "") > (b.username or ""); - end - return _sort_hosts(a.host or "", b.host or ""); - end):map(function (session) - callback(get_jid(session), session) - end); -end - -function def_env.c2s:count() - local c2s = get_c2s(); - return true, "Total: ".. #c2s .." clients"; -end - -function def_env.c2s:show(match_jid, annotate) - local print, count = self.session.print, 0; - annotate = annotate or session_flags; - local curr_host = false; - show_c2s(function (jid, session) - if curr_host ~= session.host then - curr_host = session.host; - print(curr_host or "(not connected to any host yet)"); - end - if (not match_jid) or jid:match(match_jid) then - count = count + 1; - print(annotate(session, { " ", jid })); - end - end); - return true, "Total: "..count.." clients"; -end - -function def_env.c2s:show_insecure(match_jid) - local print, count = self.session.print, 0; - show_c2s(function (jid, session) - if ((not match_jid) or jid:match(match_jid)) and not session.secure then - count = count + 1; - print(jid); - end - end); - return true, "Total: "..count.." insecure client connections"; -end - -function def_env.c2s:show_secure(match_jid) - local print, count = self.session.print, 0; - show_c2s(function (jid, session) - if ((not match_jid) or jid:match(match_jid)) and session.secure then - count = count + 1; - print(jid); - end - end); - return true, "Total: "..count.." secure client connections"; -end - -function def_env.c2s:show_tls(match_jid) - return self:show(match_jid, tls_info); -end - -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(build_reason(text, condition)); - end - end); - return true, "Total: "..count.." sessions closed"; -end - -function def_env.c2s:closeall(text, condition) - local count = 0; - --luacheck: ignore 212/jid - show_c2s(function (jid, session) - count = count + 1; - session:close(build_reason(text, condition)); - end); - return true, "Total: "..count.." sessions closed"; -end - - -def_env.s2s = {}; -function def_env.s2s:show(match_jid, annotate) - local print = self.session.print; - annotate = annotate or session_flags; - - local count_in, count_out = 0,0; - local s2s_list = { }; - - 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, - annotate(session, { "", direction, remotehost or "?" })}; - - if (not match_jid) or remotehost:match(match_jid) or localhost:match(match_jid) then - table.insert(s2s_list, sess_lines); - -- luacheck: ignore 421/print - 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.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 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 - 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)); - end - end - end - end - end - - -- Sort by local host, then remote host - table.sort(s2s_list, function(a,b) - if a.l == b.l then return _sort_hosts(a.r, b.r); end - return _sort_hosts(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 - -function def_env.s2s:show_tls(match_jid) - return self:show(match_jid, tls_info); -end - -local function print_subject(print, subject) - for _, entry in ipairs(subject) do - print( - (" %s: %q"):format( - entry.name or entry.oid, - entry.value:gsub("[\r\n%z%c]", " ") - ) - ); - end -end - --- As much as it pains me to use the 0-based depths that OpenSSL does, --- I think there's going to be more confusion among operators if we --- break from that. -local function print_errors(print, errors) - for depth, t in pairs(errors) do - print( - (" %d: %s"):format( - depth-1, - table.concat(t, "\n| ") - ) - ); - end -end - -function def_env.s2s:showcert(domain) - local print = self.session.print; - 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; - conn = conn and conn:socket(); - if not conn.getpeerchain then - if conn.dohandshake then - error("This version of LuaSec does not support certificate viewing"); - end - else - local cert = conn:getpeercertificate(); - if cert then - local certs = conn:getpeerchain(); - local digest = cert:digest("sha1"); - if not cert_set[digest] then - local chain_valid, chain_errors = conn:getpeerverification(); - cert_set[digest] = { - { - from = session.from_host, - to = session.to_host, - direction = session.direction - }; - chain_valid = chain_valid; - chain_errors = chain_errors; - certs = certs; - }; - else - table.insert(cert_set[digest], { - from = session.from_host, - to = session.to_host, - direction = session.direction - }); - end - end - end - end - local domain_certs = array.collect(values(cert_set)); - -- Phew. We now have a array of unique certificates presented by domain. - local n_certs = #domain_certs; - - if n_certs == 0 then - return "No certificates found for "..domain; - end - - local function _capitalize_and_colon(byte) - return string.upper(byte)..":"; - end - local function pretty_fingerprint(hash) - return hash:gsub("..", _capitalize_and_colon):sub(1, -2); - end - - for cert_info in values(domain_certs) do - local certs = cert_info.certs; - local cert = certs[1]; - print("---") - print("Fingerprint (SHA1): "..pretty_fingerprint(cert:digest("sha1"))); - print(""); - local n_streams = #cert_info; - print("Currently used on "..n_streams.." stream"..(n_streams==1 and "" or "s")..":"); - for _, stream in ipairs(cert_info) do - if stream.direction == "incoming" then - print(" "..stream.to.." <- "..stream.from); - else - print(" "..stream.from.." -> "..stream.to); - end - end - print(""); - local chain_valid, errors = cert_info.chain_valid, cert_info.chain_errors; - local valid_identity = cert_verify_identity(domain, "xmpp-server", cert); - if chain_valid then - print("Trusted certificate: Yes"); - else - print("Trusted certificate: No"); - print_errors(print, errors); - end - print(""); - print("Issuer: "); - print_subject(print, cert:issuer()); - print(""); - print("Valid for "..domain..": "..(valid_identity and "Yes" or "No")); - print("Subject:"); - print_subject(print, cert:subject()); - end - print("---"); - return ("Showing "..n_certs.." certificate" - ..(n_certs==1 and "" or "s") - .." presented by "..domain.."."); -end - -function def_env.s2s:close(from, to, text, condition) - local print, count = self.session.print, 0; - local s2s_sessions = module:shared"/*/s2s/sessions"; - - local match_id; - if from and not to then - match_id, from = from, nil; - 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 - - for _, session in pairs(s2s_sessions) do - 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, 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, 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(build_reason(text, condition)); - count = count + 1; - 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 - -def_env.host = {}; def_env.hosts = def_env.host; - -function def_env.host:activate(hostname, config) - return hostmanager.activate(hostname, config); -end -function def_env.host:deactivate(hostname, reason) - return hostmanager.deactivate(hostname, reason); -end - -function def_env.host:list() - local print = self.session.print; - local i = 0; - local type; - for host, host_session in iterators.sorted_pairs(prosody.hosts, _sort_hosts) do - i = i + 1; - type = host_session.type; - if type == "local" then - print(host); - else - type = module:context(host):get_option_string("component_module", type); - if type ~= "component" then - type = type .. " component"; - end - print(("%s (%s)"):format(host, type)); - end - end - return true, i.." hosts"; -end - -def_env.port = {}; - -function def_env.port:list() - local print = self.session.print; - local services = portmanager.get_active_services().data; - local n_services, n_ports = 0, 0; - for service, interfaces in iterators.sorted_pairs(services) do - n_services = n_services + 1; - local ports_list = {}; - for interface, ports in pairs(interfaces) do - for port in pairs(ports) do - table.insert(ports_list, "["..interface.."]:"..port); - end - end - n_ports = n_ports + #ports_list; - print(service..": "..table.concat(ports_list, ", ")); - end - return true, n_services.." services listening on "..n_ports.." ports"; -end - -function def_env.port:close(close_port, close_interface) - close_port = assert(tonumber(close_port), "Invalid port number"); - local n_closed = 0; - local services = portmanager.get_active_services().data; - for service, interfaces in pairs(services) do -- luacheck: ignore 213 - for interface, ports in pairs(interfaces) do - if not close_interface or close_interface == interface then - if ports[close_port] then - self.session.print("Closing ["..interface.."]:"..close_port.."..."); - local ok, err = portmanager.close(interface, close_port) - if not ok then - self.session.print("Failed to close "..interface.." "..close_port..": "..err); - else - n_closed = n_closed + 1; - end - end - end - end - end - return true, "Closed "..n_closed.." ports"; -end - -def_env.muc = {}; - -local console_room_mt = { - __index = function (self, k) return self.room[k]; end; - __tostring = function (self) - return "MUC room <"..self.room.jid..">"; - end; -}; - -local function check_muc(jid) - local room_name, host = jid_split(jid); - if not prosody.hosts[host] then - return nil, "No such host: "..host; - elseif not prosody.hosts[host].modules.muc then - return nil, "Host '"..host.."' is not a MUC service"; - end - return room_name, host; -end - -function def_env.muc:create(room_jid, config) - local room_name, host = check_muc(room_jid); - if not room_name then - return room_name, host; - end - if not room_name then return nil, host end - if config ~= nil and type(config) ~= "table" then return nil, "Config must be a table"; end - if prosody.hosts[host].modules.muc.get_room_from_jid(room_jid) then return nil, "Room exists already" end - return prosody.hosts[host].modules.muc.create_room(room_jid, config); -end - -function def_env.muc:room(room_jid) - local room_name, host = check_muc(room_jid); - if not room_name then - return room_name, host; - end - local room_obj = prosody.hosts[host].modules.muc.get_room_from_jid(room_jid); - if not room_obj then - return nil, "No such room: "..room_jid; - end - return setmetatable({ room = room_obj }, console_room_mt); -end - -function def_env.muc:list(host) - local host_session = prosody.hosts[host]; - if not host_session or not host_session.modules.muc then - return nil, "Please supply the address of a local MUC component"; - end - local print = self.session.print; - local c = 0; - for room in host_session.modules.muc.each_room() do - print(room.jid); - c = c + 1; - end - return true, c.." rooms"; -end - -local um = require"core.usermanager"; - -def_env.user = {}; -function def_env.user:create(jid, password) - local username, host = jid_split(jid); - if not prosody.hosts[host] then - return nil, "No such host: "..host; - elseif um.user_exists(username, host) then - return nil, "User exists"; - end - local ok, err = um.create_user(username, password, host); - if ok then - return true, "User created"; - else - return nil, "Could not create user: "..err; - end -end - -function def_env.user:delete(jid) - local username, host = jid_split(jid); - if not prosody.hosts[host] then - return nil, "No such host: "..host; - elseif not um.user_exists(username, host) then - return nil, "No such user"; - end - local ok, err = um.delete_user(username, host); - if ok then - return true, "User deleted"; - else - return nil, "Could not delete user: "..err; - end -end - -function def_env.user:password(jid, password) - local username, host = jid_split(jid); - if not prosody.hosts[host] then - return nil, "No such host: "..host; - elseif not um.user_exists(username, host) then - return nil, "No such user"; - end - local ok, err = um.set_password(username, password, host, nil); - if ok then - return true, "User password changed"; - else - return nil, "Could not change password for user: "..err; - end -end - -function def_env.user:list(host, pat) - if not host then - return nil, "No host given"; - elseif not prosody.hosts[host] then - return nil, "No such host"; - end - local print = self.session.print; - local total, matches = 0, 0; - for user in um.users(host) do - if not pat or user:match(pat) then - print(user.."@"..host); - matches = matches + 1; - end - total = total + 1; - end - return true, "Showing "..(pat and (matches.." of ") or "all " )..total.." users"; -end - -def_env.xmpp = {}; - -local st = require "util.stanza"; -local new_id = require "util.id".medium; -function def_env.xmpp:ping(localhost, remotehost, timeout) - localhost = select(2, jid_split(localhost)); - remotehost = select(2, jid_split(remotehost)); - if not localhost then - return nil, "Invalid sender hostname"; - elseif not prosody.hosts[localhost] then - return nil, "No such local host"; - end - if not remotehost then - return nil, "Invalid destination hostname"; - elseif prosody.hosts[remotehost] then - return nil, "Both hosts are local"; - end - local iq = st.iq{ from=localhost, to=remotehost, type="get", id=new_id()} - :tag("ping", {xmlns="urn:xmpp:ping"}); - local time_start = time.now(); - local ret, err = async.wait(module:context(localhost):send_iq(iq, nil, timeout)); - if ret then - return true, ("pong from %s in %gs"):format(ret.stanza.attr.from, time.now() - time_start); - else - return false, tostring(err); - end -end - -def_env.dns = {}; -local adns = require"net.adns"; - -local function get_resolver(session) - local resolver = session.dns_resolver; - if not resolver then - resolver = adns.resolver(); - session.dns_resolver = resolver; - end - return resolver; -end - -function def_env.dns:lookup(name, typ, class) - local resolver = get_resolver(self.session); - local ret, err = async.wait(resolver:lookup_promise(name, typ, class)); - if ret then - return true, ret; - elseif err then - return false, err; - end -end - -function def_env.dns:addnameserver(...) - local resolver = get_resolver(self.session); - resolver._resolver:addnameserver(...) - return true -end - -function def_env.dns:setnameserver(...) - local resolver = get_resolver(self.session); - resolver._resolver:setnameserver(...) - return true -end - -function def_env.dns:purge() - local resolver = get_resolver(self.session); - resolver._resolver:purge() - return true -end - -function def_env.dns:cache() - local resolver = get_resolver(self.session); - return true, "Cache:\n"..tostring(resolver._resolver.cache) -end - -def_env.http = {}; - -function def_env.http:list(hosts) - local print = self.session.print; - - for host in get_hosts_set(hosts) do - local http_apps = modulemanager.get_items("http-provider", host); - if #http_apps > 0 then - local http_host = module:context(host):get_option_string("http_host"); - print("HTTP endpoints on "..host..(http_host and (" (using "..http_host.."):") or ":")); - for _, provider in ipairs(http_apps) do - local url = module:context(host):http_url(provider.name, provider.default_path); - print("", url); - end - print(""); - end - end - - local default_host = module:get_option_string("http_default_host"); - if not default_host then - print("HTTP requests to unknown hosts will return 404 Not Found"); - else - print("HTTP requests to unknown hosts will be handled by "..default_host); - end - return true; -end - -def_env.debug = {}; - -function def_env.debug:logevents(host) - helpers.log_host_events(host); - return true; -end - -function def_env.debug:events(host, event) - local events_obj; - if host and host ~= "*" then - if host == "http" then - events_obj = require "net.http.server"._events; - elseif not prosody.hosts[host] then - return false, "Unknown host: "..host; - else - events_obj = prosody.hosts[host].events; - end - else - events_obj = prosody.events; - end - return true, helpers.show_events(events_obj, event); -end - -function def_env.debug:timers() - local print = self.session.print; - local add_task = require"util.timer".add_task; - local h, params = add_task.h, add_task.params; - if h then - print("-- util.timer"); - for i, id in ipairs(h.ids) do - if not params[id] then - print(os.date("%F %T", h.priorities[i]), h.items[id]); - elseif not params[id].callback then - print(os.date("%F %T", h.priorities[i]), h.items[id], unpack(params[id])); - else - print(os.date("%F %T", h.priorities[i]), params[id].callback, unpack(params[id])); - end - end - end - if server.event_base then - local count = 0; - for _, v in pairs(debug.getregistry()) do - if type(v) == "function" and v.callback and v.callback == add_task._on_timer then - count = count + 1; - end - end - print(count .. " libevent callbacks"); - end - if h then - local next_time = h:peek(); - if next_time then - return true, os.date("Next event at %F %T (in %%.6fs)", next_time):format(next_time - time.now()); - end - end - return true; -end - --- COMPAT: debug:timers() was timer:info() for some time in trunk -def_env.timer = { info = def_env.debug.timers }; - -module:hook("server-stopping", function(event) - for _, session in pairs(sessions) do - session.print("Shutting down: "..(event.reason or "unknown reason")); - end -end); - -def_env.stats = {}; - -local function format_stat(type, value, ref_value) - ref_value = ref_value or value; - --do return tostring(value) end - if type == "duration" then - if ref_value < 0.001 then - return ("%g µs"):format(value*1000000); - elseif ref_value < 0.9 then - return ("%0.2f ms"):format(value*1000); - end - return ("%0.2f"):format(value); - elseif type == "size" then - if ref_value > 1048576 then - return ("%d MB"):format(value/1048576); - elseif ref_value > 1024 then - return ("%d KB"):format(value/1024); - end - return ("%d bytes"):format(value); - elseif type == "rate" then - if ref_value < 0.9 then - return ("%0.2f/min"):format(value*60); - end - return ("%0.2f/sec"):format(value); - end - return tostring(value); -end - -local stats_methods = {}; -function stats_methods:bounds(_lower, _upper) - for _, stat_info in ipairs(self) do - local data = stat_info[4]; - if data then - local lower = _lower or data.min; - local upper = _upper or data.max; - local new_data = { - min = lower; - max = upper; - samples = {}; - sample_count = 0; - count = data.count; - units = data.units; - }; - local sum = 0; - for _, v in ipairs(data.samples) do - if v > upper then - break; - elseif v>=lower then - table.insert(new_data.samples, v); - sum = sum + v; - end - end - new_data.sample_count = #new_data.samples; - stat_info[4] = new_data; - stat_info[3] = sum/new_data.sample_count; - end - end - return self; -end - -function stats_methods:trim(lower, upper) - upper = upper or (100-lower); - local statistics = require "util.statistics"; - for _, stat_info in ipairs(self) do - -- Strip outliers - local data = stat_info[4]; - if data then - local new_data = { - min = statistics.get_percentile(data, lower); - max = statistics.get_percentile(data, upper); - samples = {}; - sample_count = 0; - count = data.count; - units = data.units; - }; - local sum = 0; - for _, v in ipairs(data.samples) do - if v > new_data.max then - break; - elseif v>=new_data.min then - table.insert(new_data.samples, v); - sum = sum + v; - end - end - new_data.sample_count = #new_data.samples; - stat_info[4] = new_data; - stat_info[3] = sum/new_data.sample_count; - end - end - return self; -end - -function stats_methods:max(upper) - return self:bounds(nil, upper); -end - -function stats_methods:min(lower) - return self:bounds(lower, nil); -end - -function stats_methods:summary() - local statistics = require "util.statistics"; - for _, stat_info in ipairs(self) do - local type, value, data = stat_info[2], stat_info[3], stat_info[4]; - if data and data.samples then - table.insert(stat_info.output, string.format("Count: %d (%d captured)", - data.count, - data.sample_count - )); - table.insert(stat_info.output, string.format("Min: %s Mean: %s Max: %s", - format_stat(type, data.min), - format_stat(type, value), - format_stat(type, data.max) - )); - table.insert(stat_info.output, string.format("Q1: %s Median: %s Q3: %s", - format_stat(type, statistics.get_percentile(data, 25)), - format_stat(type, statistics.get_percentile(data, 50)), - format_stat(type, statistics.get_percentile(data, 75)) - )); - end - end - return self; -end - -function stats_methods:cfgraph() - for _, stat_info in ipairs(self) do - local name, type, value, data = unpack(stat_info, 1, 4); -- luacheck: ignore 211 - local function print(s) - table.insert(stat_info.output, s); - end - - if data and data.sample_count and data.sample_count > 0 then - local raw_histogram = require "util.statistics".get_histogram(data); - - local graph_width, graph_height = 50, 10; - local eighth_chars = " ▁▂▃▄▅▆▇█"; - - local range = data.max - data.min; - - if range > 0 then - local x_scaling = #raw_histogram/graph_width; - local histogram = {}; - for i = 1, graph_width do - histogram[i] = math.max(raw_histogram[i*x_scaling-1] or 0, raw_histogram[i*x_scaling] or 0); - end - - print(""); - print(("_"):rep(52)..format_stat(type, data.max)); - for row = graph_height, 1, -1 do - local row_chars = {}; - local min_eighths, max_eighths = 8, 0; - for i = 1, #histogram do - local char_eighths = math.ceil(math.max(math.min((graph_height/(data.max/histogram[i]))-(row-1), 1), 0)*8); - if char_eighths < min_eighths then - min_eighths = char_eighths; - end - if char_eighths > max_eighths then - max_eighths = char_eighths; - end - if char_eighths == 0 then - row_chars[i] = "-"; - else - local char = eighth_chars:sub(char_eighths*3+1, char_eighths*3+3); - row_chars[i] = char; - end - end - print(table.concat(row_chars).."|-"..format_stat(type, data.max/(graph_height/(row-0.5)))); - end - print(("\\ "):rep(11)); - local x_labels = {}; - for i = 1, 11 do - local s = ("%-4s"):format((i-1)*10); - if #s > 4 then - s = s:sub(1, 3).."…"; - end - x_labels[i] = s; - end - print(" "..table.concat(x_labels, " ")); - local units = "%"; - local margin = math.floor((graph_width-#units)/2); - print((" "):rep(margin)..units); - else - print("[range too small to graph]"); - end - print(""); - end - end - return self; -end - -function stats_methods:histogram() - for _, stat_info in ipairs(self) do - local name, type, value, data = unpack(stat_info, 1, 4); -- luacheck: ignore 211 - local function print(s) - table.insert(stat_info.output, s); - end - - if not data then - print("[no data]"); - return self; - elseif not data.sample_count then - print("[not a sampled metric type]"); - return self; - end - - local graph_width, graph_height = 50, 10; - local eighth_chars = " ▁▂▃▄▅▆▇█"; - - local range = data.max - data.min; - - if range > 0 then - local n_buckets = graph_width; - - local histogram = {}; - for i = 1, n_buckets do - histogram[i] = 0; - end - local max_bin_samples = 0; - for _, d in ipairs(data.samples) do - local bucket = math.floor(1+(n_buckets-1)/(range/(d-data.min))); - histogram[bucket] = histogram[bucket] + 1; - if histogram[bucket] > max_bin_samples then - max_bin_samples = histogram[bucket]; - end - end - - print(""); - print(("_"):rep(52)..max_bin_samples); - for row = graph_height, 1, -1 do - local row_chars = {}; - local min_eighths, max_eighths = 8, 0; - for i = 1, #histogram do - local char_eighths = math.ceil(math.max(math.min((graph_height/(max_bin_samples/histogram[i]))-(row-1), 1), 0)*8); - if char_eighths < min_eighths then - min_eighths = char_eighths; - end - if char_eighths > max_eighths then - max_eighths = char_eighths; - end - if char_eighths == 0 then - row_chars[i] = "-"; - else - local char = eighth_chars:sub(char_eighths*3+1, char_eighths*3+3); - row_chars[i] = char; - end - end - print(table.concat(row_chars).."|-"..math.ceil((max_bin_samples/graph_height)*(row-0.5))); - end - print(("\\ "):rep(11)); - local x_labels = {}; - for i = 1, 11 do - local s = ("%-4s"):format(format_stat(type, data.min+range*i/11, data.min):match("^%S+")); - if #s > 4 then - s = s:sub(1, 3).."…"; - end - x_labels[i] = s; - end - print(" "..table.concat(x_labels, " ")); - local units = format_stat(type, data.min):match("%s+(.+)$") or data.units or ""; - local margin = math.floor((graph_width-#units)/2); - print((" "):rep(margin)..units); - else - print("[range too small to graph]"); - end - print(""); - end - return self; -end - -local function stats_tostring(stats) - local print = stats.session.print; - for _, stat_info in ipairs(stats) do - if #stat_info.output > 0 then - print("\n#"..stat_info[1]); - print(""); - for _, v in ipairs(stat_info.output) do - print(v); - end - print(""); - else - print(("%-50s %s"):format(stat_info[1], format_stat(stat_info[2], stat_info[3]))); - end - end - return #stats.." statistics displayed"; -end - -local stats_mt = {__index = stats_methods, __tostring = stats_tostring } -local function new_stats_context(self) - return setmetatable({ session = self.session, stats = true }, stats_mt); -end - -function def_env.stats:show(filter) - -- luacheck: ignore 211/changed - local stats, changed, extra = require "core.statsmanager".get_stats(); - local available, displayed = 0, 0; - local displayed_stats = new_stats_context(self); - for name, value in iterators.sorted_pairs(stats) do - available = available + 1; - if not filter or name:match(filter) then - displayed = displayed + 1; - local type = name:match(":(%a+)$"); - table.insert(displayed_stats, { - name, type, value, extra[name]; - output = {}; - }); - end - end - return displayed_stats; -end - - - -------------- - -function printbanner(session) - local option = module:get_option_string("console_banner", "full"); - if option == "full" or option == "graphic" then - session.print [[ - ____ \ / _ - | _ \ _ __ ___ ___ _-_ __| |_ _ - | |_) | '__/ _ \/ __|/ _ \ / _` | | | | - | __/| | | (_) \__ \ |_| | (_| | |_| | - |_| |_| \___/|___/\___/ \__,_|\__, | - A study in simplicity |___/ - -]] - end - if option == "short" or option == "full" then - session.print("Welcome to the Prosody administration console. For a list of commands, type: help"); - session.print("You may find more help on using this console in our online documentation at "); - session.print("https://prosody.im/doc/console\n"); - end - if option ~= "short" and option ~= "full" and option ~= "graphic" then - session.print(option); - end -end - module:provides("net", { name = "console"; listener = console_listener; -- cgit v1.2.3 From a355440c01d5ece456bda9c5a4f302ec82929dff Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 1 Jun 2020 15:44:44 +0100 Subject: prosodyctl, util.prosodyctl.shell: `prosodyctl shell` - a client to access the prosodyctl admin shell --- prosodyctl | 4 ++ util/prosodyctl/shell.lua | 125 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 129 insertions(+) create mode 100644 util/prosodyctl/shell.lua diff --git a/prosodyctl b/prosodyctl index 964285a0..43ed47c3 100755 --- a/prosodyctl +++ b/prosodyctl @@ -1356,6 +1356,10 @@ function commands.check(arg) return ok and 0 or 2; end +function commands.shell(arg) + require "util.prosodyctl.shell".start(arg); +end + --------------------- local async = require "util.async"; diff --git a/util/prosodyctl/shell.lua b/util/prosodyctl/shell.lua new file mode 100644 index 00000000..0b84eaca --- /dev/null +++ b/util/prosodyctl/shell.lua @@ -0,0 +1,125 @@ +local have_unix, unix = pcall(require, "socket.unix"); + +if not have_unix or type(unix) ~= "table" then + print("** LuaSocket unix socket support not available or incompatible, ensure your"); + print("** version is up to date."); + os.exit(1); +end + +local server = require "net.server"; +local st = require "util.stanza"; + +local have_readline, readline = pcall(require, "readline"); + +local adminstream = require "util.adminstream"; + +if have_readline then + readline.set_readline_name("prosody"); +end + +local function read_line() + if have_readline then + return readline.readline("prosody> "); + else + io.write("prosody> "); + return io.read("*line"); + end +end + +local function send_line(client, line) + client.send(st.stanza("repl-line"):text(line)); +end + +local function repl(client) + local line = read_line(); + if not line or line == "quit" or line == "exit" or line == "bye" then + if not line then + print(""); + end + os.exit(); + end + send_line(client, line); +end + +local function connection(socket_path, listeners) + local conn, sock; + + return { + connect = function () + if sock or conn then + return nil, "already connected"; + end + sock = unix.stream(); + sock:settimeout(0); + local ok, err = sock:connect(socket_path); + if not ok then + return nil, err; + end + conn = server.wrapclient(sock, nil, nil, listeners, "*a"); + return true; + end; + disconnect = function () + if conn then + conn:close(); + conn = nil; + end + if sock then + sock:close(); + sock = nil; + end + return true; + end; + }; +end + +local function printbanner() + print([[ + ____ \ / _ + | _ \ _ __ ___ ___ _-_ __| |_ _ + | |_) | '__/ _ \/ __|/ _ \ / _` | | | | + | __/| | | (_) \__ \ |_| | (_| | |_| | + |_| |_| \___/|___/\___/ \__,_|\__, | + A study in simplicity |___/ + +]]); + print("Welcome to the Prosody administration console. For a list of commands, type: help"); + print("You may find more help on using this console in our online documentation at "); + print("https://prosody.im/doc/console\n"); +end + +local function start(arg) --luacheck: ignore 212/arg + local client = adminstream.client(); + + client.events.add_handler("connected", function () + if not arg.quiet then + printbanner(); + end + repl(client); + end); + + client.events.add_handler("disconnected", function () + print("--- session closed ---"); + os.exit(); + end); + + client.events.add_handler("received", function (stanza) + if stanza.name == "repl-result" then + local result_prefix = stanza.attr.type == "error" and "!" or "|"; + print(result_prefix.." "..stanza:get_text()); + repl(client); + end + end); + + local conn = connection("data/prosody.sock", client.listeners); + local ok, err = conn:connect(); + if not ok then + print("** Unable to connect to server - is it running? Is mod_admin_shell enabled?"); + print("** Connection error: "..err); + os.exit(1); + end + server.loop(); +end + +return { + start = start; +}; -- cgit v1.2.3 From e703759258ebed21e3a0aa5e55a163b22fd9d91b Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 1 Jun 2020 16:14:06 +0100 Subject: mod_admin_shell, mod_admin_telnet, util.prosodyctl.shell: Separate output from final result Fixes the client pausing for input after output from commands. --- plugins/mod_admin_shell.lua | 32 +++++++++++++++++++------------- plugins/mod_admin_telnet.lua | 4 ++-- util/prosodyctl/shell.lua | 6 ++++-- 3 files changed, 25 insertions(+), 17 deletions(-) diff --git a/plugins/mod_admin_shell.lua b/plugins/mod_admin_shell.lua index d471e32f..b94ab07b 100644 --- a/plugins/mod_admin_shell.lua +++ b/plugins/mod_admin_shell.lua @@ -61,21 +61,21 @@ function runner_callbacks:error(err) self.data.print("Error: "..tostring(err)); end -local function send_repl_result(session, line) - return session.send(st.stanza("repl-result"):text(tostring(line))); +local function send_repl_output(session, line) + return session.send(st.stanza("repl-output"):text(tostring(line))); end function console:new_session(admin_session) local session = { send = function (t) - return send_repl_result(admin_session, t); + return send_repl_output(admin_session, t); end; print = function (...) local t = {}; for i=1,select("#", ...) do t[i] = tostring(select(i, ...)); end - return send_repl_result(admin_session, table.concat(t, "\t")); + return send_repl_output(admin_session, table.concat(t, "\t")); end; serialize = tostring; disconnect = function () admin_session:close(); end; @@ -107,8 +107,8 @@ local function handle_line(event) local line = event.stanza:get_text(); local useglobalenv; + local result = st.stanza("repl-result"); - module:log("debug", "HELLO: %s", line) if line:match("^>") then line = line:gsub("^>", ""); useglobalenv = true; @@ -116,6 +116,7 @@ local function handle_line(event) local command = line:match("^%w+") or line:match("%p"); if commands[command] then commands[command](session, line); + event.origin.send(result); return; end end @@ -124,6 +125,7 @@ local function handle_line(event) if not useglobalenv and commands[line:lower()] then commands[line:lower()](session, line); + event.origin.send(result); return; end @@ -137,29 +139,33 @@ local function handle_line(event) err = err:gsub("^%[string .-%]:%d+: ", ""); err = err:gsub("^:%d+: ", ""); err = err:gsub("''", "the end of the line"); - session.print("Sorry, I couldn't understand that... "..err); + result.attr.type = "error"; + result:text("Sorry, I couldn't understand that... "..err); + event.origin.send(result); return; end end local taskok, message = chunk(); + local result = st.stanza("repl-result"); + if not message then if type(taskok) ~= "string" and useglobalenv then taskok = session.serialize(taskok); end - session.print("Result: "..tostring(taskok)); - return; + result:text("Result: "..tostring(taskok)); elseif (not taskok) and message then - session.print("Command completed with a problem"); - session.print("Message: "..tostring(message)); - return; + result.attr.type = "error"; + result:text("Error: "..tostring(message)); + else + result:text("OK: "..tostring(message)); end - session.print("OK: "..tostring(message)); + event.origin.send(result); end -module:hook("admin/repl-line", function (event) +module:hook("admin/repl-input", function (event) local ok, err = pcall(handle_line, event); if not ok then event.origin.send(st.stanza("repl-result", { type = "error" }):text(err)); diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index ec8ff136..15220ec9 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -65,7 +65,7 @@ function console:new_session(conn) local w = function(s) conn:write(s:gsub("\n", "\r\n")); end; local session = { conn = conn; send = function (t) - if st.is_stanza(t) and t.name == "repl-result" then + if st.is_stanza(t) and (t.name == "repl-result" or t.name == "repl-output") then t = "| "..t:get_text().."\n"; end w(tostring(t)); @@ -106,7 +106,7 @@ function console:process_line(session, line) session:disconnect(); return; end - return module:fire_event("admin/repl-line", { origin = session, stanza = st.stanza("repl"):text(line) }); + return module:fire_event("admin/repl-input", { origin = session, stanza = st.stanza("repl-input"):text(line) }); end local sessions = {}; diff --git a/util/prosodyctl/shell.lua b/util/prosodyctl/shell.lua index 0b84eaca..3e70e8f1 100644 --- a/util/prosodyctl/shell.lua +++ b/util/prosodyctl/shell.lua @@ -27,7 +27,7 @@ local function read_line() end local function send_line(client, line) - client.send(st.stanza("repl-line"):text(line)); + client.send(st.stanza("repl-input"):text(line)); end local function repl(client) @@ -103,9 +103,11 @@ local function start(arg) --luacheck: ignore 212/arg end); client.events.add_handler("received", function (stanza) - if stanza.name == "repl-result" then + if stanza.name == "repl-output" or stanza.name == "repl-result" then local result_prefix = stanza.attr.type == "error" and "!" or "|"; print(result_prefix.." "..stanza:get_text()); + end + if stanza.name == "repl-result" then repl(client); end end); -- cgit v1.2.3 From f554caa3ac8cd7c7c8223d121f59947e9f0ded68 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 1 Jun 2020 16:14:44 +0100 Subject: mod_admin_shell: Remove extra newline at end of help text --- plugins/mod_admin_shell.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/mod_admin_shell.lua b/plugins/mod_admin_shell.lua index b94ab07b..a38bd000 100644 --- a/plugins/mod_admin_shell.lua +++ b/plugins/mod_admin_shell.lua @@ -263,7 +263,6 @@ function commands.help(session, data) print [[you can prefix a command with > to escape the console sandbox, and access everything in]] print [[the running server. Great fun, but be careful not to break anything :)]] end - print [[]] end -- Session environment -- -- cgit v1.2.3 From 9708aab9b3b62c87e038f8d33cf64c8d11d53d32 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 1 Jun 2020 17:19:08 +0200 Subject: net.server_epoll: Add way to start accepting clients on an arbitrary server socket This adds an escape hatch where things like UNIX sockets can be added. --- net/server_epoll.lua | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index ded42c4d..f4c14e13 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -719,10 +719,7 @@ function interface:onconnect() self:on("connect"); end -local function listen(addr, port, listeners, config) - local conn, err = socket.bind(addr, port, cfg.tcp_backlog); - if not conn then return conn, err; end - conn:settimeout(0); +local function wrapserver(conn, addr, port, listeners, config) local server = setmetatable({ conn = conn; created = realtime(); @@ -741,6 +738,13 @@ local function listen(addr, port, listeners, config) return server; end +local function listen(addr, port, listeners, config) + local conn, err = socket.bind(addr, port, cfg.tcp_backlog); + if not conn then return conn, err; end + conn:settimeout(0); + return wrapserver(conn, addr, port, listeners, config); +end + -- COMPAT local function addserver(addr, port, listeners, read_size, tls_ctx) return listen(addr, port, listeners, { @@ -897,6 +901,7 @@ return { closeall = closeall; setquitting = setquitting; wrapclient = wrapclient; + wrapserver = wrapserver; watchfd = watchfd; link = link; set_config = function (newconfig) -- cgit v1.2.3 From 5cdbe0edde662c434cb5c4df8c5153a1b324a4b0 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 1 Jun 2020 17:27:50 +0200 Subject: mod_admin_socket: Use wrapserver if available Why have a custom accept function when this is net.server's entire thing? --- plugins/mod_admin_socket.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/mod_admin_socket.lua b/plugins/mod_admin_socket.lua index c094aad2..2085c5b0 100644 --- a/plugins/mod_admin_socket.lua +++ b/plugins/mod_admin_socket.lua @@ -55,7 +55,11 @@ function module.load() os.remove(socket_path); assert(sock:bind(socket_path)); assert(sock:listen()); - conn = server.watchfd(sock:getfd(), accept_connection); + if server.wrapserver then + conn = server.wrapserver(sock, socket_path, 0, listeners); + else + conn = server.watchfd(sock:getfd(), accept_connection); + end end function module.unload() -- cgit v1.2.3 From 81457f2fff2c6700073c3df3af2565f250df3045 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 1 Jun 2020 16:38:16 +0100 Subject: mod_admin_shell: Remove old variable declaration [luacheck] --- plugins/mod_admin_shell.lua | 2 -- 1 file changed, 2 deletions(-) diff --git a/plugins/mod_admin_shell.lua b/plugins/mod_admin_shell.lua index a38bd000..a9ca797c 100644 --- a/plugins/mod_admin_shell.lua +++ b/plugins/mod_admin_shell.lua @@ -148,8 +148,6 @@ local function handle_line(event) local taskok, message = chunk(); - local result = st.stanza("repl-result"); - if not message then if type(taskok) ~= "string" and useglobalenv then taskok = session.serialize(taskok); -- cgit v1.2.3 From cd40d2a63038338c66c0d7115dfb87ab4b1c6ca6 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 1 Jun 2020 17:37:54 +0200 Subject: mod_vcard_legacy: Remove semi-broken support for multiple avatars Nobody does this. If someone wants to they should go use the PEP method directly instead. Additionally, this got in the way of doing multiple avatars The PEP Way, since it treated each 'data' as a distinct avatar with an optional corresponding 'metadata', which is not how it works. --- plugins/mod_vcard_legacy.lua | 62 ++++++++++++++++++++------------------------ 1 file changed, 28 insertions(+), 34 deletions(-) diff --git a/plugins/mod_vcard_legacy.lua b/plugins/mod_vcard_legacy.lua index a6ff47d0..3b175df4 100644 --- a/plugins/mod_vcard_legacy.lua +++ b/plugins/mod_vcard_legacy.lua @@ -126,22 +126,25 @@ module:hook("iq-get/bare/vcard-temp:vCard", function (event) end end - local meta_ok, avatar_meta = pep_service:get_items("urn:xmpp:avatar:metadata", stanza.attr.from); - local data_ok, avatar_data = pep_service:get_items("urn:xmpp:avatar:data", stanza.attr.from); - - if data_ok then - for _, hash in ipairs(avatar_data) do - local meta = meta_ok and avatar_meta[hash]; - local data = avatar_data[hash]; - local info = meta and meta.tags[1]:get_child("info"); + local ok, avatar_hash, meta = pep_service:get_last_item("urn:xmpp:avatar:metadata", true); + if ok and avatar_hash then + + local info = meta.tags[1]:get_child("info"); + if info then vcard_temp:tag("PHOTO"); - if info and info.attr.type then + + if info.attr.type then vcard_temp:text_tag("TYPE", info.attr.type); end - if data then - vcard_temp:text_tag("BINVAL", data.tags[1]:get_text()); - elseif info and info.attr.url then + + if info.attr.url then vcard_temp:text_tag("EXTVAL", info.attr.url); + elseif info.attr.id then + local data_ok, avatar_data = pep_service:get_items("urn:xmpp:avatar:data", stanza.attr.from, { info.attr.id }); + if data_ok and avatar_data and avatar_data[info.attr.id] then + local data = avatar_data[info.attr.id]; + vcard_temp:text_tag("BINVAL", data.tags[1]:get_text()); + end end vcard_temp:up(); end @@ -157,7 +160,7 @@ local node_defaults = { }; function vcard_to_pep(vcard_temp) - local avatars = {}; + local avatar = {}; local vcard4 = st.stanza("item", { xmlns = "http://jabber.org/protocol/pubsub", id = "current" }) :tag("vcard", { xmlns = 'urn:ietf:params:xml:ns:vcard-4.0' }); @@ -246,7 +249,9 @@ function vcard_to_pep(vcard_temp) local avatar_raw = base64_decode(avatar_payload); local avatar_hash = sha1(avatar_raw, true); - local avatar_meta = st.stanza("item", { id = avatar_hash, xmlns = "http://jabber.org/protocol/pubsub" }) + avatar.hash = avatar_hash; + + avatar.meta = st.stanza("item", { id = avatar_hash, xmlns = "http://jabber.org/protocol/pubsub" }) :tag("metadata", { xmlns="urn:xmpp:avatar:metadata" }) :tag("info", { bytes = tostring(#avatar_raw), @@ -254,40 +259,29 @@ function vcard_to_pep(vcard_temp) type = avatar_type, }); - local avatar_data = st.stanza("item", { id = avatar_hash, xmlns = "http://jabber.org/protocol/pubsub" }) + avatar.data = st.stanza("item", { id = avatar_hash, xmlns = "http://jabber.org/protocol/pubsub" }) :tag("data", { xmlns="urn:xmpp:avatar:data" }) :text(avatar_payload); - table.insert(avatars, { hash = avatar_hash, meta = avatar_meta, data = avatar_data }); end end end - return vcard4, avatars; + return vcard4, avatar; end -function save_to_pep(pep_service, actor, vcard4, avatars) - if avatars then +function save_to_pep(pep_service, actor, vcard4, avatar) + if avatar then if pep_service:purge("urn:xmpp:avatar:metadata", actor) then pep_service:purge("urn:xmpp:avatar:data", actor); end - local avatar_defaults = node_defaults; - if #avatars > 1 then - avatar_defaults = {}; - for k,v in pairs(node_defaults) do - avatar_defaults[k] = v; - end - avatar_defaults.max_items = #avatars; + local ok, err = pep_service:publish("urn:xmpp:avatar:data", actor, avatar.hash, avatar.data, node_defaults); + if ok then + ok, err = pep_service:publish("urn:xmpp:avatar:metadata", actor, avatar.hash, avatar.meta, node_defaults); end - for _, avatar in ipairs(avatars) do - local ok, err = pep_service:publish("urn:xmpp:avatar:data", actor, avatar.hash, avatar.data, avatar_defaults); - if ok then - ok, err = pep_service:publish("urn:xmpp:avatar:metadata", actor, avatar.hash, avatar.meta, avatar_defaults); - end - if not ok then - return ok, err; - end + if not ok then + return ok, err; end end -- cgit v1.2.3 From a78297bb6c6514d5360339a84f6acc1a94d5198f Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 1 Jun 2020 17:55:01 +0200 Subject: mod_vcard_legacy: Fix publishing vcard without avatar --- plugins/mod_vcard_legacy.lua | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/plugins/mod_vcard_legacy.lua b/plugins/mod_vcard_legacy.lua index 3b175df4..92b1d5e9 100644 --- a/plugins/mod_vcard_legacy.lua +++ b/plugins/mod_vcard_legacy.lua @@ -276,12 +276,14 @@ function save_to_pep(pep_service, actor, vcard4, avatar) pep_service:purge("urn:xmpp:avatar:data", actor); end - local ok, err = pep_service:publish("urn:xmpp:avatar:data", actor, avatar.hash, avatar.data, node_defaults); - if ok then - ok, err = pep_service:publish("urn:xmpp:avatar:metadata", actor, avatar.hash, avatar.meta, node_defaults); - end - if not ok then - return ok, err; + if avatar.data and avatar.meta then + local ok, err = assert(pep_service:publish("urn:xmpp:avatar:data", actor, avatar.hash, avatar.data, node_defaults)); + if ok then + ok, err = assert(pep_service:publish("urn:xmpp:avatar:metadata", actor, avatar.hash, avatar.meta, node_defaults)); + end + if not ok then + return ok, err; + end end end -- cgit v1.2.3 From aa8c162f111c2d0e2e5f9c20b2ea8e9588ed4e14 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 1 Jun 2020 23:17:20 +0200 Subject: mod_admin_socket: Use module API meant for file paths Makes it so that a relative path in the config becomes relative to the data directory. --- plugins/mod_admin_socket.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_admin_socket.lua b/plugins/mod_admin_socket.lua index 2085c5b0..b197adae 100644 --- a/plugins/mod_admin_socket.lua +++ b/plugins/mod_admin_socket.lua @@ -11,7 +11,7 @@ local server = require "net.server"; local adminstream = require "util.adminstream"; -local socket_path = module:get_option_string("admin_socket", prosody.paths.data.."/prosody.sock"); +local socket_path = module:get_option_path("admin_socket", "prosody.sock", "data"); local sessions = module:shared("sessions"); -- cgit v1.2.3 From aa24574b89d9a766d7789da3ecb09c600157ff95 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 1 Jun 2020 23:26:30 +0200 Subject: util.prosodyctl.shell: Join socket path with current data directory Don't hardcode socket path as it happens to be in a source checkout. Hold on, it should use the same config option as the module! --- util/prosodyctl/shell.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/util/prosodyctl/shell.lua b/util/prosodyctl/shell.lua index 3e70e8f1..6d5ea116 100644 --- a/util/prosodyctl/shell.lua +++ b/util/prosodyctl/shell.lua @@ -8,6 +8,7 @@ end local server = require "net.server"; local st = require "util.stanza"; +local path = require "util.paths"; local have_readline, readline = pcall(require, "readline"); @@ -112,7 +113,8 @@ local function start(arg) --luacheck: ignore 212/arg end end); - local conn = connection("data/prosody.sock", client.listeners); + local socket_path = path.join(prosody.paths.data, "prosody.sock"); + local conn = connection(socket_path, client.listeners); local ok, err = conn:connect(); if not ok then print("** Unable to connect to server - is it running? Is mod_admin_shell enabled?"); -- cgit v1.2.3 From 05f5f794bf9afcf412f5f5a9b3935f5fb048ba0d Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 1 Jun 2020 23:53:03 +0200 Subject: util.prosodyctl.shell: Use same config option as module for socket path So now if you set it to a custom value, both the client and the server should use it. --- util/prosodyctl/shell.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/util/prosodyctl/shell.lua b/util/prosodyctl/shell.lua index 6d5ea116..0e019152 100644 --- a/util/prosodyctl/shell.lua +++ b/util/prosodyctl/shell.lua @@ -6,6 +6,7 @@ if not have_unix or type(unix) ~= "table" then os.exit(1); end +local config = require "core.configmanager"; local server = require "net.server"; local st = require "util.stanza"; local path = require "util.paths"; @@ -113,7 +114,7 @@ local function start(arg) --luacheck: ignore 212/arg end end); - local socket_path = path.join(prosody.paths.data, "prosody.sock"); + local socket_path = path.resolve_relative_path(prosody.paths.data, config.get("*", "admin_socket") or "prosody.sock"); local conn = connection(socket_path, client.listeners); local ok, err = conn:connect(); if not ok then -- cgit v1.2.3 From f4436b0ad840c535dc5a8df513fb6e15401135d3 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 1 Jun 2020 23:57:50 +0200 Subject: util.prosodyctl.shell: Allow passing path to socket on command line E.g. `prosodyctl shell --socket /path/to/prosody.scok` --- util/prosodyctl/shell.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/prosodyctl/shell.lua b/util/prosodyctl/shell.lua index 0e019152..cbcea927 100644 --- a/util/prosodyctl/shell.lua +++ b/util/prosodyctl/shell.lua @@ -114,7 +114,7 @@ local function start(arg) --luacheck: ignore 212/arg end end); - local socket_path = path.resolve_relative_path(prosody.paths.data, config.get("*", "admin_socket") or "prosody.sock"); + local socket_path = path.resolve_relative_path(prosody.paths.data, arg.socket or config.get("*", "admin_socket") or "prosody.sock"); local conn = connection(socket_path, client.listeners); local ok, err = conn:connect(); if not ok then -- cgit v1.2.3 From e2b8325088e588ab4ccc00ce738dad223228114d Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Tue, 2 Jun 2020 08:00:37 +0100 Subject: util.human.io: New central place for UI helpers --- util/human/io.lua | 96 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 util/human/io.lua diff --git a/util/human/io.lua b/util/human/io.lua new file mode 100644 index 00000000..bfd1c00d --- /dev/null +++ b/util/human/io.lua @@ -0,0 +1,96 @@ +local function getchar(n) + local stty_ret = os.execute("stty raw -echo 2>/dev/null"); + local ok, char; + if stty_ret == true or stty_ret == 0 then + ok, char = pcall(io.read, n or 1); + os.execute("stty sane"); + else + ok, char = pcall(io.read, "*l"); + if ok then + char = char:sub(1, n or 1); + end + end + if ok then + return char; + end +end + +local function getline() + local ok, line = pcall(io.read, "*l"); + if ok then + return line; + end +end + +local function getpass() + local stty_ret, _, status_code = os.execute("stty -echo 2>/dev/null"); + if status_code then -- COMPAT w/ Lua 5.1 + stty_ret = status_code; + end + if stty_ret ~= 0 then + io.write("\027[08m"); -- ANSI 'hidden' text attribute + end + local ok, pass = pcall(io.read, "*l"); + if stty_ret == 0 then + os.execute("stty sane"); + else + io.write("\027[00m"); + end + io.write("\n"); + if ok then + return pass; + end +end + +local function show_yesno(prompt) + io.write(prompt, " "); + local choice = getchar():lower(); + io.write("\n"); + if not choice:match("%a") then + choice = prompt:match("%[.-(%U).-%]$"); + if not choice then return nil; end + end + return (choice == "y"); +end + +local function read_password() + local password; + while true do + io.write("Enter new password: "); + password = getpass(); + if not password then + print("No password - cancelled"); + return; + end + io.write("Retype new password: "); + if getpass() ~= password then + if not show_yesno [=[Passwords did not match, try again? [Y/n]]=] then + return; + end + else + break; + end + end + return password; +end + +local function show_prompt(prompt) + io.write(prompt, " "); + local line = getline(); + line = line and line:gsub("\n$",""); + return (line and #line > 0) and line or nil; +end + +local function printf(fmt, ...) + print(msg:format(...)); +end + +return { + getchar = getchar; + getline = getline; + getpass = getpass; + show_yesno = show_yesno; + read_password = read_password; + show_prompt = show_prompt; + printf = printf; +}; -- cgit v1.2.3 From 46022b09886badc89425865ae1ac02651995f8c8 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Tue, 2 Jun 2020 08:01:21 +0100 Subject: prosodyctl+util.prosodyctl.*: Start breaking up the ever-growing prosodyctl --- prosodyctl | 836 +--------------------------------------------- util/prosodyctl.lua | 109 +----- util/prosodyctl/cert.lua | 293 ++++++++++++++++ util/prosodyctl/check.lua | 530 +++++++++++++++++++++++++++++ util/prosodyctl/shell.lua | 2 +- 5 files changed, 851 insertions(+), 919 deletions(-) create mode 100644 util/prosodyctl/cert.lua create mode 100644 util/prosodyctl/check.lua diff --git a/prosodyctl b/prosodyctl index 43ed47c3..3b5291d6 100755 --- a/prosodyctl +++ b/prosodyctl @@ -49,20 +49,6 @@ startup.prosodyctl(); ----------- -local error_messages = setmetatable({ - ["invalid-username"] = "The given username is invalid in a Jabber ID"; - ["invalid-hostname"] = "The given hostname is invalid"; - ["no-password"] = "No password was supplied"; - ["no-such-user"] = "The given user does not exist on the server"; - ["no-such-host"] = "The given hostname does not exist in the config"; - ["unable-to-save-data"] = "Unable to store, perhaps you don't have permission?"; - ["no-pidfile"] = "There is no 'pidfile' option in the configuration file, see https://prosody.im/doc/prosodyctl#pidfile for help"; - ["invalid-pidfile"] = "The 'pidfile' option in the configuration file is not a string, see https://prosody.im/doc/prosodyctl#pidfile for help"; - ["no-posix"] = "The mod_posix module is not enabled in the Prosody config file, see https://prosody.im/doc/prosodyctl for more info"; - ["no-such-method"] = "This module has no commands"; - ["not-running"] = "Prosody is not running"; - }, { __index = function (_,k) return "Error: "..(tostring(k):gsub("%-", " "):gsub("^.", string.upper)); end }); - local configmanager = require "core.configmanager"; local modulemanager = require "core.modulemanager" local prosodyctl = require "util.prosodyctl" @@ -72,12 +58,13 @@ local lfs = dependencies.softreq "lfs"; ----------------------- +local human_io = require "util.human.io"; + local show_message, show_warning = prosodyctl.show_message, prosodyctl.show_warning; local show_usage = prosodyctl.show_usage; -local show_yesno = prosodyctl.show_yesno; -local show_prompt = prosodyctl.show_prompt; -local read_password = prosodyctl.read_password; +local read_password = human_io.read_password; local call_luarocks = prosodyctl.call_luarocks; +local error_messages = prosodyctl.error_messages; local jid_split = require "util.jid".prepped_split; @@ -552,814 +539,6 @@ function commands.unregister(arg) return 1; end -local openssl; - -local cert_commands = {}; - --- If a file already exists, ask if the user wants to use it or replace it --- Backups the old file if replaced -local function use_existing(filename) - local attrs = lfs.attributes(filename); - if attrs then - if show_yesno(filename .. " exists, do you want to replace it? [y/n]") then - local backup = filename..".bkp~"..os.date("%FT%T", attrs.change); - os.rename(filename, backup); - show_message(filename.." backed up to "..backup); - else - -- Use the existing file - return true; - end - end -end - -local have_pposix, pposix = pcall(require, "util.pposix"); -local cert_basedir = prosody.paths.data == "." and "./certs" or prosody.paths.data; -if have_pposix and pposix.getuid() == 0 then - -- FIXME should be enough to check if this directory is writable - local cert_dir = configmanager.get("*", "certificates") or "certs"; - cert_basedir = configmanager.resolve_relative_path(prosody.paths.config, cert_dir); -end - -function cert_commands.config(arg) - if #arg >= 1 and arg[1] ~= "--help" then - local conf_filename = cert_basedir .. "/" .. arg[1] .. ".cnf"; - if use_existing(conf_filename) then - return nil, conf_filename; - end - local distinguished_name; - if arg[#arg]:find("^/") then - distinguished_name = table.remove(arg); - end - local conf = openssl.config.new(); - conf:from_prosody(prosody.hosts, configmanager, arg); - if distinguished_name then - local dn = {}; - for k, v in distinguished_name:gmatch("/([^=/]+)=([^/]+)") do - table.insert(dn, k); - dn[k] = v; - end - conf.distinguished_name = dn; - else - show_message("Please provide details to include in the certificate config file."); - show_message("Leave the field empty to use the default value or '.' to exclude the field.") - for _, k in ipairs(openssl._DN_order) do - local v = conf.distinguished_name[k]; - if v then - local nv = nil; - if k == "commonName" then - v = arg[1] - elseif k == "emailAddress" then - v = "xmpp@" .. arg[1]; - elseif k == "countryName" then - local tld = arg[1]:match"%.([a-z]+)$"; - if tld and #tld == 2 and tld ~= "uk" then - v = tld:upper(); - end - end - nv = show_prompt(("%s (%s):"):format(k, nv or v)); - nv = (not nv or nv == "") and v or nv; - if nv:find"[\192-\252][\128-\191]+" then - conf.req.string_mask = "utf8only" - end - conf.distinguished_name[k] = nv ~= "." and nv or nil; - end - end - end - local conf_file, err = io.open(conf_filename, "w"); - if not conf_file then - show_warning("Could not open OpenSSL config file for writing"); - show_warning(err); - os.exit(1); - end - conf_file:write(conf:serialize()); - conf_file:close(); - print(""); - show_message("Config written to " .. conf_filename); - return nil, conf_filename; - else - show_usage("cert config HOSTNAME [HOSTNAME+]", "Builds a certificate config file covering the supplied hostname(s)") - end -end - -function cert_commands.key(arg) - if #arg >= 1 and arg[1] ~= "--help" then - local key_filename = cert_basedir .. "/" .. arg[1] .. ".key"; - if use_existing(key_filename) then - return nil, key_filename; - end - os.remove(key_filename); -- This file, if it exists is unlikely to have write permissions - local key_size = tonumber(arg[2] or show_prompt("Choose key size (2048):") or 2048); - local old_umask = pposix.umask("0377"); - if openssl.genrsa{out=key_filename, key_size} then - os.execute(("chmod 400 '%s'"):format(key_filename)); - show_message("Key written to ".. key_filename); - pposix.umask(old_umask); - return nil, key_filename; - end - show_message("There was a problem, see OpenSSL output"); - else - show_usage("cert key HOSTNAME ", "Generates a RSA key named HOSTNAME.key\n " - .."Prompts for a key size if none given") - end -end - -function cert_commands.request(arg) - if #arg >= 1 and arg[1] ~= "--help" then - local req_filename = cert_basedir .. "/" .. arg[1] .. ".req"; - if use_existing(req_filename) then - return nil, req_filename; - end - local _, key_filename = cert_commands.key({arg[1]}); - local _, conf_filename = cert_commands.config(arg); - if openssl.req{new=true, key=key_filename, utf8=true, sha256=true, config=conf_filename, out=req_filename} then - show_message("Certificate request written to ".. req_filename); - else - show_message("There was a problem, see OpenSSL output"); - end - else - show_usage("cert request HOSTNAME [HOSTNAME+]", "Generates a certificate request for the supplied hostname(s)") - end -end - -function cert_commands.generate(arg) - if #arg >= 1 and arg[1] ~= "--help" then - local cert_filename = cert_basedir .. "/" .. arg[1] .. ".crt"; - if use_existing(cert_filename) then - return nil, cert_filename; - end - local _, key_filename = cert_commands.key({arg[1]}); - local _, conf_filename = cert_commands.config(arg); - if key_filename and conf_filename and cert_filename - and openssl.req{new=true, x509=true, nodes=true, key=key_filename, - days=365, sha256=true, utf8=true, config=conf_filename, out=cert_filename} then - show_message("Certificate written to ".. cert_filename); - print(); - else - show_message("There was a problem, see OpenSSL output"); - end - else - show_usage("cert generate HOSTNAME [HOSTNAME+]", "Generates a self-signed certificate for the current hostname(s)") - end -end - -local function sh_esc(s) - return "'" .. s:gsub("'", "'\\''") .. "'"; -end - -local function copy(from, to, umask, owner, group) - local old_umask = umask and pposix.umask(umask); - local attrs = lfs.attributes(to); - if attrs then -- Move old file out of the way - local backup = to..".bkp~"..os.date("%FT%T", attrs.change); - os.rename(to, backup); - end - -- FIXME friendlier error handling, maybe move above backup back? - local input = assert(io.open(from)); - local output = assert(io.open(to, "w")); - local data = input:read(2^11); - while data and output:write(data) do - data = input:read(2^11); - end - assert(input:close()); - assert(output:close()); - if not prosody.installed then - -- FIXME this is possibly specific to GNU chown - os.execute(("chown -c --reference=%s %s"):format(sh_esc(cert_basedir), sh_esc(to))); - elseif owner and group then - local ok = os.execute(("chown %s:%s %s"):format(sh_esc(owner), sh_esc(group), sh_esc(to))); - assert(ok == true or ok == 0, "Failed to change ownership of "..to); - end - if old_umask then pposix.umask(old_umask); end - return true; -end - -function cert_commands.import(arg) - local hostnames = {}; - -- Move hostname arguments out of arg, the rest should be a list of paths - while arg[1] and prosody.hosts[ arg[1] ] do - table.insert(hostnames, table.remove(arg, 1)); - end - if hostnames[1] == nil then - local domains = os.getenv"RENEWED_DOMAINS"; -- Set if invoked via certbot - if domains then - for host in domains:gmatch("%S+") do - table.insert(hostnames, host); - end - else - for host in pairs(prosody.hosts) do - if host ~= "*" and configmanager.get(host, "enabled") ~= false then - table.insert(hostnames, host); - end - end - end - end - if not arg[1] or arg[1] == "--help" then -- Probably forgot the path - show_usage("cert import [HOSTNAME+] /path/to/certs [/other/paths/]+", - "Copies certificates to "..cert_basedir); - return 1; - end - local owner, group; - if pposix.getuid() == 0 then -- We need root to change ownership - owner = configmanager.get("*", "prosody_user") or "prosody"; - group = configmanager.get("*", "prosody_group") or owner; - end - local cm = require "core.certmanager"; - local imported = {}; - for _, host in ipairs(hostnames) do - for _, dir in ipairs(arg) do - local paths = cm.find_cert(dir, host); - if paths then - copy(paths.certificate, cert_basedir .. "/" .. host .. ".crt", nil, owner, group); - copy(paths.key, cert_basedir .. "/" .. host .. ".key", "0377", owner, group); - table.insert(imported, host); - else - -- TODO Say where we looked - show_warning("No certificate for host "..host.." found :("); - end - -- TODO Additional checks - -- Certificate names matches the hostname - -- Private key matches public key in certificate - end - end - if imported[1] then - show_message("Imported certificate and key for hosts "..table.concat(imported, ", ")); - local ok, err = prosodyctl.reload(); - if not ok and err ~= "not-running" then - show_message(error_messages[err]); - end - else - show_warning("No certificates imported :("); - return 1; - end -end - -function commands.cert(arg) - if #arg >= 1 and arg[1] ~= "--help" then - openssl = require "util.openssl"; - lfs = require "lfs"; - local cert_dir_attrs = lfs.attributes(cert_basedir); - if not cert_dir_attrs then - show_warning("The directory "..cert_basedir.." does not exist"); - return 1; -- TODO Should we create it? - end - local uid = pposix.getuid(); - if uid ~= 0 and uid ~= cert_dir_attrs.uid then - show_warning("The directory "..cert_basedir.." is not owned by the current user, won't be able to write files to it"); - return 1; - elseif not cert_dir_attrs.permissions then -- COMPAT with LuaFilesystem < 1.6.2 (hey CentOS!) - show_message("Unable to check permissions on "..cert_basedir.." (LuaFilesystem 1.6.2+ required)"); - show_message("Please confirm that Prosody (and only Prosody) can write to this directory)"); - elseif cert_dir_attrs.permissions:match("^%.w..%-..%-.$") then - show_warning("The directory "..cert_basedir.." not only writable by its owner"); - return 1; - end - local subcmd = table.remove(arg, 1); - if type(cert_commands[subcmd]) == "function" then - if subcmd ~= "import" then -- hostnames are optional for import - if not arg[1] then - show_message"You need to supply at least one hostname" - arg = { "--help" }; - end - if arg[1] ~= "--help" and not prosody.hosts[arg[1]] then - show_message(error_messages["no-such-host"]); - return 1; - end - end - return cert_commands[subcmd](arg); - elseif subcmd == "check" then - return commands.check({"certs"}); - end - end - show_usage("cert config|request|generate|key|import", "Helpers for generating X.509 certificates and keys.") - for _, cmd in pairs(cert_commands) do - print() - cmd{ "--help" } - end -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 set = require "util.set"; - local it = require "util.iterators"; - local ok = true; - local function disabled_hosts(host, conf) return host ~= "*" and conf.enabled ~= false; end - local function enabled_hosts() return it.filter(disabled_hosts, pairs(configmanager.getconfig())); end - if not (what == nil or what == "disabled" or what == "config" or what == "dns" or what == "certs") then - show_warning("Don't know how to check '%s'. Try one of 'config', 'dns', 'certs' or 'disabled'.", what); - return 1; - end - if not what or what == "disabled" then - local disabled_hosts_set = set.new(); - for host, host_options in it.filter("*", pairs(configmanager.getconfig())) do - if host_options.enabled == false then - disabled_hosts_set:add(host); - end - end - if not disabled_hosts_set:empty() then - local msg = "Checks will be skipped for these disabled hosts: %s"; - if what then msg = "These hosts are disabled: %s"; end - show_warning(msg, tostring(disabled_hosts_set)); - if what then return 0; end - print"" - end - end - if not what or what == "config" then - print("Checking config..."); - local deprecated = set.new({ - "bosh_ports", "disallow_s2s", "no_daemonize", "anonymous_login", "require_encryption", - "vcard_compatibility", "cross_domain_bosh", "cross_domain_websocket", "daemonize", - }); - local known_global_options = set.new({ - "pidfile", "log", "plugin_paths", "prosody_user", "prosody_group", "daemonize", - "umask", "prosodyctl_timeout", "use_ipv6", "use_libevent", "network_settings", - "network_backend", "http_default_host", - "statistics_interval", "statistics", "statistics_config", - }); - local config = configmanager.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 https://prosody.im/doc/configure#overview"); - end - if it.count(enabled_hosts()) == 0 then - ok = false; - print(""); - if it.count(it.filter("*", pairs(config))) == 0 then - print(" No hosts are defined, please add at least one VirtualHost section") - elseif config["*"]["enabled"] == false then - print(" No hosts are enabled. Remove enabled = false from the global section or put enabled = true under at least one VirtualHost section") - else - print(" All hosts are disabled. Remove enabled = false from at least one VirtualHost section") - end - end - if not config["*"].modules_enabled then - print(" No global modules_enabled is set?"); - local suggested_global_modules; - for host, options in enabled_hosts() do --luacheck: ignore 213/host - if not options.component_module and options.modules_enabled then - suggested_global_modules = set.intersection(suggested_global_modules or set.new(options.modules_enabled), set.new(options.modules_enabled)); - end - end - if suggested_global_modules and not suggested_global_modules:empty() then - print(" Consider moving these modules into modules_enabled in the global section:") - print(" "..tostring(suggested_global_modules / function (x) return ("%q"):format(x) end)); - end - print(); - end - - do -- Check for modules enabled both normally and as components - local modules = set.new(config["*"]["modules_enabled"]); - for host, options in enabled_hosts() do - local component_module = options.component_module; - if component_module and modules:contains(component_module) then - print((" mod_%s is enabled both in modules_enabled and as Component %q %q"):format(component_module, host, component_module)); - print(" This means the service is enabled on all VirtualHosts as well as the Component."); - print(" Are you sure this what you want? It may cause unexpected behaviour."); - end - end - end - - -- Check for global options under hosts - local global_options = set.new(it.to_array(it.keys(config["*"]))); - local deprecated_global_options = set.intersection(global_options, deprecated); - if not deprecated_global_options:empty() then - print(""); - print(" You have some deprecated options in the global section:"); - print(" "..tostring(deprecated_global_options)) - ok = false; - end - for host, options in it.filter(function (h) return h ~= "*" end, pairs(configmanager.getconfig())) 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$") and not name:match("^[cs]2s_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 https://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 - end - for host, options in enabled_hosts() do - local host_options = set.new(it.to_array(it.keys(options))); - 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: https://prosody.im/doc/dns"); - end - end - local all_modules = set.new(config["*"].modules_enabled); - local all_options = set.new(it.to_array(it.keys(config["*"]))); - for host in enabled_hosts() do - all_options:include(set.new(it.to_array(it.keys(config[host])))); - all_modules:include(set.new(config[host].modules_enabled)); - end - for mod in all_modules do - if mod:match("^mod_") then - print(""); - print(" Modules in modules_enabled should not have the 'mod_' prefix included."); - print(" Change '"..mod.."' to '"..mod:match("^mod_(.*)").."'."); - elseif mod:match("^auth_") then - print(""); - print(" Authentication modules should not be added to modules_enabled,"); - print(" but be specified in the 'authentication' option."); - print(" Remove '"..mod.."' from modules_enabled and instead add"); - print(" authentication = '"..mod:match("^auth_(.*)").."'"); - print(" For more information see https://prosody.im/doc/authentication"); - elseif mod:match("^storage_") then - print(""); - print(" storage modules should not be added to modules_enabled,"); - print(" but be specified in the 'storage' option."); - print(" Remove '"..mod.."' from modules_enabled and instead add"); - print(" storage = '"..mod:match("^storage_(.*)").."'"); - print(" For more information see https://prosody.im/doc/storage"); - end - end - if all_modules:contains("vcard") and all_modules:contains("vcard_legacy") then - print(""); - print(" Both mod_vcard_legacy and mod_vcard are enabled but they conflict"); - print(" with each other. Remove one."); - end - if all_modules:contains("pep") and all_modules:contains("pep_simple") then - print(""); - print(" Both mod_pep_simple and mod_pep are enabled but they conflict"); - print(" with each other. Remove one."); - end - for host, host_config in pairs(config) do --luacheck: ignore 213/host - if type(rawget(host_config, "storage")) == "string" and rawget(host_config, "default_storage") then - print(""); - print(" The 'default_storage' option is not needed if 'storage' is set to a string."); - break; - end - end - local require_encryption = set.intersection(all_options, set.new({ - "require_encryption", "c2s_require_encryption", "s2s_require_encryption" - })):empty(); - local ssl = dependencies.softreq"ssl"; - if not ssl then - if not require_encryption then - print(""); - print(" You require encryption but LuaSec is not available."); - print(" Connections will fail."); - ok = false; - end - elseif not ssl.loadcertificate then - if all_options:contains("s2s_secure_auth") then - print(""); - print(" You have set s2s_secure_auth but your version of LuaSec does "); - print(" not support certificate validation, so all s2s connections will"); - print(" fail."); - ok = false; - elseif all_options:contains("s2s_secure_domains") then - local secure_domains = set.new(); - for host in enabled_hosts() do - if config[host].s2s_secure_auth == true then - secure_domains:add("*"); - else - secure_domains:include(set.new(config[host].s2s_secure_domains)); - end - end - if not secure_domains:empty() then - print(""); - print(" You have set s2s_secure_domains but your version of LuaSec does "); - print(" not support certificate validation, so s2s connections to/from "); - print(" these domains will fail."); - ok = false; - end - end - elseif require_encryption and not all_modules:contains("tls") then - print(""); - print(" You require encryption but mod_tls is not enabled."); - print(" Connections will fail."); - ok = false; - end - - print("Done.\n"); - end - if not what or what == "dns" then - local dns = require "net.dns"; - local idna = require "util.encodings".idna; - local ip = require "util.ip"; - local c2s_ports = set.new(configmanager.get("*", "c2s_ports") or {5222}); - local s2s_ports = set.new(configmanager.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 - do - local res = dns.lookup(idna.to_ascii(fqdn), "A"); - if res then - for _, record in ipairs(res) do - external_addresses:add(record.a); - end - end - end - do - local res = dns.lookup(idna.to_ascii(fqdn), "AAAA"); - if res then - for _, record in ipairs(res) do - external_addresses:add(record.aaaa); - end - end - end - end - - local local_addresses = require"util.net".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 jid, host_options in enabled_hosts() do - local all_targets_ok, some_targets_ok = true, false; - local node, host = jid_split(jid); - - local modules, component_module = modulemanager.get_modules_for_host(host); - if component_module then - modules:add(component_module); - end - - local is_component = not not host_options.component_module; - print("Checking DNS for "..(is_component and "component" or "host").." "..jid.."..."); - if node then - print("Only the domain part ("..host..") is used in DNS.") - end - local target_hosts = set.new(); - if modules:contains("c2s") then - local res = dns.lookup("_xmpp-client._tcp."..idna.to_ascii(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_targets_ok = false; - else - target_hosts:add(host); - end - end - end - if modules:contains("s2s") then - local res = dns.lookup("_xmpp-server._tcp."..idna.to_ascii(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 - 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 - - if modules:contains("proxy65") then - local proxy65_target = configmanager.get(host, "proxy65_address") or host; - if type(proxy65_target) == "string" then - local A, AAAA = dns.lookup(idna.to_ascii(proxy65_target), "A"), dns.lookup(idna.to_ascii(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 - else - print(" proxy65_address for "..host.." should be set to a string, unable to perform DNS check"); - end - end - - for target_host in target_hosts do - local host_ok_v4, host_ok_v6; - do - local res = dns.lookup(idna.to_ascii(target_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(" "..target_host.." A record points to internal address, external connections might fail"); - else - print(" "..target_host.." A record points to unknown address "..record.a); - all_targets_ok = false; - end - end - end - end - do - local res = dns.lookup(idna.to_ascii(target_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(" "..target_host.." AAAA record points to internal address, external connections might fail"); - else - print(" "..target_host.." AAAA record points to unknown address "..record.aaaa); - all_targets_ok = false; - end - 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 "..target_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 "..target_host.." has AAAA records, but your version of LuaSocket does not support IPv6."); - print(" Please see https://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 https://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 create_context = require "core.certmanager".create_context; - local ssl = dependencies.softreq"ssl"; - -- local datetime_parse = require"util.datetime".parse_x509; - local load_cert = ssl and ssl.loadcertificate; - -- 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 - local function skip_bare_jid_hosts(host) - if jid_split(host) then - -- See issue #779 - return false; - end - return true; - end - for host in it.filter(skip_bare_jid_hosts, enabled_hosts()) do - print("Checking certificate for "..host); - -- First, let's find out what certificate this host uses. - local host_ssl_config = configmanager.rawget(host, "ssl") - or configmanager.rawget(host:match("%.(.*)"), "ssl"); - local global_ssl_config = configmanager.rawget("*", "ssl"); - local ok, err, ssl_config = create_context(host, "server", host_ssl_config, global_ssl_config); - if not ok then - print(" Error: "..err); - cert_ok = false - elseif not ssl_config.certificate then - print(" No 'certificate' found for "..host) - cert_ok = false - elseif not ssl_config.key then - print(" No 'key' found 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:close(); - if not cert:validat(os.time()) then - print(" Certificate has expired.") - cert_ok = false - elseif not cert:validat(os.time() + 86400) then - print(" Certificate expires within one day.") - cert_ok = false - elseif not cert:validat(os.time() + 86400*7) then - print(" Certificate expires within one week.") - elseif not cert:validat(os.time() + 86400*31) then - print(" Certificate expires within one month.") - end - if configmanager.get(host, "component_module") == nil - and not x509_verify_identity(host, "_xmpp-client", cert) then - print(" Not valid for client connections to "..host..".") - cert_ok = false - end - if (not (configmanager.get(host, "anonymous_login") - or configmanager.get(host, "authentication") == "anonymous")) - and not x509_verify_identity(host, "_xmpp-server", cert) then - print(" Not valid 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 https://prosody.im/doc/certificates"); - ok = false - 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 - -function commands.shell(arg) - require "util.prosodyctl.shell".start(arg); -end - --------------------- local async = require "util.async"; @@ -1408,6 +587,13 @@ local command_runner = async.runner(function () end end + if not commands[command] then + local ok, command_module = pcall(require, "util.prosodyctl."..command); + if ok and command_module[command] then + commands[command] = command_module[command]; + end + end + if not commands[command] then -- Show help for all commands function show_usage(usage, desc) print(" "..usage); diff --git a/util/prosodyctl.lua b/util/prosodyctl.lua index ea697ffc..cb86a5a1 100644 --- a/util/prosodyctl.lua +++ b/util/prosodyctl.lua @@ -15,7 +15,6 @@ local usermanager = require "core.usermanager"; local signal = require "util.signal"; local set = require "util.set"; local lfs = require "lfs"; -local pcall = pcall; local type = type; local nodeprep, nameprep = stringprep.nodeprep, stringprep.nameprep; @@ -27,10 +26,22 @@ local tonumber = tonumber; local _G = _G; local prosody = prosody; +local error_messages = setmetatable({ + ["invalid-username"] = "The given username is invalid in a Jabber ID"; + ["invalid-hostname"] = "The given hostname is invalid"; + ["no-password"] = "No password was supplied"; + ["no-such-user"] = "The given user does not exist on the server"; + ["no-such-host"] = "The given hostname does not exist in the config"; + ["unable-to-save-data"] = "Unable to store, perhaps you don't have permission?"; + ["no-pidfile"] = "There is no 'pidfile' option in the configuration file, see https://prosody.im/doc/prosodyctl#pidfile for help"; + ["invalid-pidfile"] = "The 'pidfile' option in the configuration file is not a string, see https://prosody.im/doc/prosodyctl#pidfile for help"; + ["no-posix"] = "The mod_posix module is not enabled in the Prosody config file, see https://prosody.im/doc/prosodyctl for more info"; + ["no-such-method"] = "This module has no commands"; + ["not-running"] = "Prosody is not running"; + }, { __index = function (_,k) return "Error: "..(tostring(k):gsub("%-", " "):gsub("^.", string.upper)); end }); + -- UI helpers -local function show_message(msg, ...) - print(msg:format(...)); -end +local show_message = require "util.human.io".printf; local function show_usage(usage, desc) print("Usage: ".._G.arg[0].." "..usage); @@ -49,89 +60,6 @@ local function show_module_configuration_help(mod_name) print(" "..mod_name..": https://modules.prosody.im/"..mod_name..".html") end -local function getchar(n) - local stty_ret = os.execute("stty raw -echo 2>/dev/null"); - local ok, char; - if stty_ret == true or stty_ret == 0 then - ok, char = pcall(io.read, n or 1); - os.execute("stty sane"); - else - ok, char = pcall(io.read, "*l"); - if ok then - char = char:sub(1, n or 1); - end - end - if ok then - return char; - end -end - -local function getline() - local ok, line = pcall(io.read, "*l"); - if ok then - return line; - end -end - -local function getpass() - local stty_ret, _, status_code = os.execute("stty -echo 2>/dev/null"); - if status_code then -- COMPAT w/ Lua 5.1 - stty_ret = status_code; - end - if stty_ret ~= 0 then - io.write("\027[08m"); -- ANSI 'hidden' text attribute - end - local ok, pass = pcall(io.read, "*l"); - if stty_ret == 0 then - os.execute("stty sane"); - else - io.write("\027[00m"); - end - io.write("\n"); - if ok then - return pass; - end -end - -local function show_yesno(prompt) - io.write(prompt, " "); - local choice = getchar():lower(); - io.write("\n"); - if not choice:match("%a") then - choice = prompt:match("%[.-(%U).-%]$"); - if not choice then return nil; end - end - return (choice == "y"); -end - -local function read_password() - local password; - while true do - io.write("Enter new password: "); - password = getpass(); - if not password then - show_message("No password - cancelled"); - return; - end - io.write("Retype new password: "); - if getpass() ~= password then - if not show_yesno [=[Passwords did not match, try again? [Y/n]]=] then - return; - end - else - break; - end - end - return password; -end - -local function show_prompt(prompt) - io.write(prompt, " "); - local line = getline(); - line = line and line:gsub("\n$",""); - return (line and #line > 0) and line or nil; -end - -- Server control local function adduser(params) local user, host, password = nodeprep(params.user, true), nameprep(params.host), params.password; @@ -318,12 +246,6 @@ return { show_warning = show_message; show_usage = show_usage; show_module_configuration_help = show_module_configuration_help; - getchar = getchar; - getline = getline; - getpass = getpass; - show_yesno = show_yesno; - read_password = read_password; - show_prompt = show_prompt; adduser = adduser; user_exists = user_exists; passwd = passwd; @@ -335,4 +257,5 @@ return { reload = reload; get_path_custom_plugins = get_path_custom_plugins; call_luarocks = call_luarocks; + error_messages = error_messages; }; diff --git a/util/prosodyctl/cert.lua b/util/prosodyctl/cert.lua new file mode 100644 index 00000000..29e26ed8 --- /dev/null +++ b/util/prosodyctl/cert.lua @@ -0,0 +1,293 @@ +local lfs = require "lfs"; + +local pctl = require "util.prosodyctl"; +local configmanager = require "core.configmanager"; + +local openssl; + +local cert_commands = {}; + +-- If a file already exists, ask if the user wants to use it or replace it +-- Backups the old file if replaced +local function use_existing(filename) + local attrs = lfs.attributes(filename); + if attrs then + if pctl.show_yesno(filename .. " exists, do you want to replace it? [y/n]") then + local backup = filename..".bkp~"..os.date("%FT%T", attrs.change); + os.rename(filename, backup); + pctl.show_message("%s backed up to %s", filename, backup); + else + -- Use the existing file + return true; + end + end +end + +local have_pposix, pposix = pcall(require, "util.pposix"); +local cert_basedir = prosody.paths.data == "." and "./certs" or prosody.paths.data; +if have_pposix and pposix.getuid() == 0 then + -- FIXME should be enough to check if this directory is writable + local cert_dir = configmanager.get("*", "certificates") or "certs"; + cert_basedir = configmanager.resolve_relative_path(prosody.paths.config, cert_dir); +end + +function cert_commands.config(arg) + if #arg >= 1 and arg[1] ~= "--help" then + local conf_filename = cert_basedir .. "/" .. arg[1] .. ".cnf"; + if use_existing(conf_filename) then + return nil, conf_filename; + end + local distinguished_name; + if arg[#arg]:find("^/") then + distinguished_name = table.remove(arg); + end + local conf = openssl.config.new(); + conf:from_prosody(prosody.hosts, configmanager, arg); + if distinguished_name then + local dn = {}; + for k, v in distinguished_name:gmatch("/([^=/]+)=([^/]+)") do + table.insert(dn, k); + dn[k] = v; + end + conf.distinguished_name = dn; + else + pctl.show_message("Please provide details to include in the certificate config file."); + pctl.show_message("Leave the field empty to use the default value or '.' to exclude the field.") + for _, k in ipairs(openssl._DN_order) do + local v = conf.distinguished_name[k]; + if v then + local nv = nil; + if k == "commonName" then + v = arg[1] + elseif k == "emailAddress" then + v = "xmpp@" .. arg[1]; + elseif k == "countryName" then + local tld = arg[1]:match"%.([a-z]+)$"; + if tld and #tld == 2 and tld ~= "uk" then + v = tld:upper(); + end + end + nv = pctl.show_prompt(("%s (%s):"):format(k, nv or v)); + nv = (not nv or nv == "") and v or nv; + if nv:find"[\192-\252][\128-\191]+" then + conf.req.string_mask = "utf8only" + end + conf.distinguished_name[k] = nv ~= "." and nv or nil; + end + end + end + local conf_file, err = io.open(conf_filename, "w"); + if not conf_file then + pctl.show_warning("Could not open OpenSSL config file for writing"); + pctl.show_warning(err); + os.exit(1); + end + conf_file:write(conf:serialize()); + conf_file:close(); + print(""); + pctl.show_message("Config written to %s", conf_filename); + return nil, conf_filename; + else + pctl.show_usage("cert config HOSTNAME [HOSTNAME+]", "Builds a certificate config file covering the supplied hostname(s)") + end +end + +function cert_commands.key(arg) + if #arg >= 1 and arg[1] ~= "--help" then + local key_filename = cert_basedir .. "/" .. arg[1] .. ".key"; + if use_existing(key_filename) then + return nil, key_filename; + end + os.remove(key_filename); -- This file, if it exists is unlikely to have write permissions + local key_size = tonumber(arg[2] or pctl.show_prompt("Choose key size (2048):") or 2048); + local old_umask = pposix.umask("0377"); + if openssl.genrsa{out=key_filename, key_size} then + os.execute(("chmod 400 '%s'"):format(key_filename)); + pctl.show_message("Key written to %s", key_filename); + pposix.umask(old_umask); + return nil, key_filename; + end + pctl.show_message("There was a problem, see OpenSSL output"); + else + pctl.show_usage("cert key HOSTNAME ", "Generates a RSA key named HOSTNAME.key\n " + .."Prompts for a key size if none given") + end +end + +function cert_commands.request(arg) + if #arg >= 1 and arg[1] ~= "--help" then + local req_filename = cert_basedir .. "/" .. arg[1] .. ".req"; + if use_existing(req_filename) then + return nil, req_filename; + end + local _, key_filename = cert_commands.key({arg[1]}); + local _, conf_filename = cert_commands.config(arg); + if openssl.req{new=true, key=key_filename, utf8=true, sha256=true, config=conf_filename, out=req_filename} then + pctl.show_message("Certificate request written to %s", req_filename); + else + pctl.show_message("There was a problem, see OpenSSL output"); + end + else + pctl.show_usage("cert request HOSTNAME [HOSTNAME+]", "Generates a certificate request for the supplied hostname(s)") + end +end + +function cert_commands.generate(arg) + if #arg >= 1 and arg[1] ~= "--help" then + local cert_filename = cert_basedir .. "/" .. arg[1] .. ".crt"; + if use_existing(cert_filename) then + return nil, cert_filename; + end + local _, key_filename = cert_commands.key({arg[1]}); + local _, conf_filename = cert_commands.config(arg); + if key_filename and conf_filename and cert_filename + and openssl.req{new=true, x509=true, nodes=true, key=key_filename, + days=365, sha256=true, utf8=true, config=conf_filename, out=cert_filename} then + pctl.show_message("Certificate written to %s", cert_filename); + print(); + else + pctl.show_message("There was a problem, see OpenSSL output"); + end + else + pctl.show_usage("cert generate HOSTNAME [HOSTNAME+]", "Generates a self-signed certificate for the current hostname(s)") + end +end + +local function sh_esc(s) + return "'" .. s:gsub("'", "'\\''") .. "'"; +end + +local function copy(from, to, umask, owner, group) + local old_umask = umask and pposix.umask(umask); + local attrs = lfs.attributes(to); + if attrs then -- Move old file out of the way + local backup = to..".bkp~"..os.date("%FT%T", attrs.change); + os.rename(to, backup); + end + -- FIXME friendlier error handling, maybe move above backup back? + local input = assert(io.open(from)); + local output = assert(io.open(to, "w")); + local data = input:read(2^11); + while data and output:write(data) do + data = input:read(2^11); + end + assert(input:close()); + assert(output:close()); + if not prosody.installed then + -- FIXME this is possibly specific to GNU chown + os.execute(("chown -c --reference=%s %s"):format(sh_esc(cert_basedir), sh_esc(to))); + elseif owner and group then + local ok = os.execute(("chown %s:%s %s"):format(sh_esc(owner), sh_esc(group), sh_esc(to))); + assert(ok == true or ok == 0, "Failed to change ownership of "..to); + end + if old_umask then pposix.umask(old_umask); end + return true; +end + +function cert_commands.import(arg) + local hostnames = {}; + -- Move hostname arguments out of arg, the rest should be a list of paths + while arg[1] and prosody.hosts[ arg[1] ] do + table.insert(hostnames, table.remove(arg, 1)); + end + if hostnames[1] == nil then + local domains = os.getenv"RENEWED_DOMAINS"; -- Set if invoked via certbot + if domains then + for host in domains:gmatch("%S+") do + table.insert(hostnames, host); + end + else + for host in pairs(prosody.hosts) do + if host ~= "*" and configmanager.get(host, "enabled") ~= false then + table.insert(hostnames, host); + end + end + end + end + if not arg[1] or arg[1] == "--help" then -- Probably forgot the path + pctl.show_usage("cert import [HOSTNAME+] /path/to/certs [/other/paths/]+", + "Copies certificates to "..cert_basedir); + return 1; + end + local owner, group; + if pposix.getuid() == 0 then -- We need root to change ownership + owner = configmanager.get("*", "prosody_user") or "prosody"; + group = configmanager.get("*", "prosody_group") or owner; + end + local cm = require "core.certmanager"; + local imported = {}; + for _, host in ipairs(hostnames) do + for _, dir in ipairs(arg) do + local paths = cm.find_cert(dir, host); + if paths then + copy(paths.certificate, cert_basedir .. "/" .. host .. ".crt", nil, owner, group); + copy(paths.key, cert_basedir .. "/" .. host .. ".key", "0377", owner, group); + table.insert(imported, host); + else + -- TODO Say where we looked + pctl.show_warning("No certificate for host "..host.." found :("); + end + -- TODO Additional checks + -- Certificate names matches the hostname + -- Private key matches public key in certificate + end + end + if imported[1] then + pctl.show_message("Imported certificate and key for hosts %s", table.concat(imported, ", ")); + local ok, err = pctl.reload(); + if not ok and err ~= "not-running" then + pctl.show_message(pctl.error_messages[err]); + end + else + pctl.show_warning("No certificates imported :("); + return 1; + end +end + +local function cert(arg) + if #arg >= 1 and arg[1] ~= "--help" then + openssl = require "util.openssl"; + lfs = require "lfs"; + local cert_dir_attrs = lfs.attributes(cert_basedir); + if not cert_dir_attrs then + pctl.show_warning("The directory "..cert_basedir.." does not exist"); + return 1; -- TODO Should we create it? + end + local uid = pposix.getuid(); + if uid ~= 0 and uid ~= cert_dir_attrs.uid then + pctl.show_warning("The directory "..cert_basedir.." is not owned by the current user, won't be able to write files to it"); + return 1; + elseif not cert_dir_attrs.permissions then -- COMPAT with LuaFilesystem < 1.6.2 (hey CentOS!) + pctl.show_message("Unable to check permissions on %s (LuaFilesystem 1.6.2+ required)", cert_basedir); + pctl.show_message("Please confirm that Prosody (and only Prosody) can write to this directory)"); + elseif cert_dir_attrs.permissions:match("^%.w..%-..%-.$") then + pctl.show_warning("The directory "..cert_basedir.." not only writable by its owner"); + return 1; + end + local subcmd = table.remove(arg, 1); + if type(cert_commands[subcmd]) == "function" then + if subcmd ~= "import" then -- hostnames are optional for import + if not arg[1] then + pctl.show_message"You need to supply at least one hostname" + arg = { "--help" }; + end + if arg[1] ~= "--help" and not prosody.hosts[arg[1]] then + pctl.show_message(pctl.error_messages["no-such-host"]); + return 1; + end + end + return cert_commands[subcmd](arg); + elseif subcmd == "check" then + return require "util.prosodyctl.check".check({"certs"}); + end + end + pctl.show_usage("cert config|request|generate|key|import", "Helpers for generating X.509 certificates and keys.") + for _, cmd in pairs(cert_commands) do + print() + cmd{ "--help" } + end +end + +return { + cert = cert; +}; diff --git a/util/prosodyctl/check.lua b/util/prosodyctl/check.lua new file mode 100644 index 00000000..d22d5f45 --- /dev/null +++ b/util/prosodyctl/check.lua @@ -0,0 +1,530 @@ +local configmanager = require "core.configmanager"; +local show_usage = require "util.prosodyctl".show_usage; +local show_warning = require "util.prosodyctl".show_warning; +local dependencies = require "util.dependencies"; +local socket = require "socket"; +local jid_split = require "util.jid".prepped_split; +local modulemanager = require "core.modulemanager"; + +local function 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 set = require "util.set"; + local it = require "util.iterators"; + local ok = true; + local function disabled_hosts(host, conf) return host ~= "*" and conf.enabled ~= false; end + local function enabled_hosts() return it.filter(disabled_hosts, pairs(configmanager.getconfig())); end + if not (what == nil or what == "disabled" or what == "config" or what == "dns" or what == "certs") then + show_warning("Don't know how to check '%s'. Try one of 'config', 'dns', 'certs' or 'disabled'.", what); + return 1; + end + if not what or what == "disabled" then + local disabled_hosts_set = set.new(); + for host, host_options in it.filter("*", pairs(configmanager.getconfig())) do + if host_options.enabled == false then + disabled_hosts_set:add(host); + end + end + if not disabled_hosts_set:empty() then + local msg = "Checks will be skipped for these disabled hosts: %s"; + if what then msg = "These hosts are disabled: %s"; end + show_warning(msg, tostring(disabled_hosts_set)); + if what then return 0; end + print"" + end + end + if not what or what == "config" then + print("Checking config..."); + local deprecated = set.new({ + "bosh_ports", "disallow_s2s", "no_daemonize", "anonymous_login", "require_encryption", + "vcard_compatibility", "cross_domain_bosh", "cross_domain_websocket", "daemonize", + }); + local known_global_options = set.new({ + "pidfile", "log", "plugin_paths", "prosody_user", "prosody_group", "daemonize", + "umask", "prosodyctl_timeout", "use_ipv6", "use_libevent", "network_settings", + "network_backend", "http_default_host", + "statistics_interval", "statistics", "statistics_config", + }); + local config = configmanager.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 https://prosody.im/doc/configure#overview"); + end + if it.count(enabled_hosts()) == 0 then + ok = false; + print(""); + if it.count(it.filter("*", pairs(config))) == 0 then + print(" No hosts are defined, please add at least one VirtualHost section") + elseif config["*"]["enabled"] == false then + print(" No hosts are enabled. Remove enabled = false from the global section or put enabled = true under at least one VirtualHost section") + else + print(" All hosts are disabled. Remove enabled = false from at least one VirtualHost section") + end + end + if not config["*"].modules_enabled then + print(" No global modules_enabled is set?"); + local suggested_global_modules; + for host, options in enabled_hosts() do --luacheck: ignore 213/host + if not options.component_module and options.modules_enabled then + suggested_global_modules = set.intersection(suggested_global_modules or set.new(options.modules_enabled), set.new(options.modules_enabled)); + end + end + if suggested_global_modules and not suggested_global_modules:empty() then + print(" Consider moving these modules into modules_enabled in the global section:") + print(" "..tostring(suggested_global_modules / function (x) return ("%q"):format(x) end)); + end + print(); + end + + do -- Check for modules enabled both normally and as components + local modules = set.new(config["*"]["modules_enabled"]); + for host, options in enabled_hosts() do + local component_module = options.component_module; + if component_module and modules:contains(component_module) then + print((" mod_%s is enabled both in modules_enabled and as Component %q %q"):format(component_module, host, component_module)); + print(" This means the service is enabled on all VirtualHosts as well as the Component."); + print(" Are you sure this what you want? It may cause unexpected behaviour."); + end + end + end + + -- Check for global options under hosts + local global_options = set.new(it.to_array(it.keys(config["*"]))); + local deprecated_global_options = set.intersection(global_options, deprecated); + if not deprecated_global_options:empty() then + print(""); + print(" You have some deprecated options in the global section:"); + print(" "..tostring(deprecated_global_options)) + ok = false; + end + for host, options in it.filter(function (h) return h ~= "*" end, pairs(configmanager.getconfig())) 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$") and not name:match("^[cs]2s_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 https://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 + end + for host, options in enabled_hosts() do + local host_options = set.new(it.to_array(it.keys(options))); + 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: https://prosody.im/doc/dns"); + end + end + local all_modules = set.new(config["*"].modules_enabled); + local all_options = set.new(it.to_array(it.keys(config["*"]))); + for host in enabled_hosts() do + all_options:include(set.new(it.to_array(it.keys(config[host])))); + all_modules:include(set.new(config[host].modules_enabled)); + end + for mod in all_modules do + if mod:match("^mod_") then + print(""); + print(" Modules in modules_enabled should not have the 'mod_' prefix included."); + print(" Change '"..mod.."' to '"..mod:match("^mod_(.*)").."'."); + elseif mod:match("^auth_") then + print(""); + print(" Authentication modules should not be added to modules_enabled,"); + print(" but be specified in the 'authentication' option."); + print(" Remove '"..mod.."' from modules_enabled and instead add"); + print(" authentication = '"..mod:match("^auth_(.*)").."'"); + print(" For more information see https://prosody.im/doc/authentication"); + elseif mod:match("^storage_") then + print(""); + print(" storage modules should not be added to modules_enabled,"); + print(" but be specified in the 'storage' option."); + print(" Remove '"..mod.."' from modules_enabled and instead add"); + print(" storage = '"..mod:match("^storage_(.*)").."'"); + print(" For more information see https://prosody.im/doc/storage"); + end + end + if all_modules:contains("vcard") and all_modules:contains("vcard_legacy") then + print(""); + print(" Both mod_vcard_legacy and mod_vcard are enabled but they conflict"); + print(" with each other. Remove one."); + end + if all_modules:contains("pep") and all_modules:contains("pep_simple") then + print(""); + print(" Both mod_pep_simple and mod_pep are enabled but they conflict"); + print(" with each other. Remove one."); + end + for host, host_config in pairs(config) do --luacheck: ignore 213/host + if type(rawget(host_config, "storage")) == "string" and rawget(host_config, "default_storage") then + print(""); + print(" The 'default_storage' option is not needed if 'storage' is set to a string."); + break; + end + end + local require_encryption = set.intersection(all_options, set.new({ + "require_encryption", "c2s_require_encryption", "s2s_require_encryption" + })):empty(); + local ssl = dependencies.softreq"ssl"; + if not ssl then + if not require_encryption then + print(""); + print(" You require encryption but LuaSec is not available."); + print(" Connections will fail."); + ok = false; + end + elseif not ssl.loadcertificate then + if all_options:contains("s2s_secure_auth") then + print(""); + print(" You have set s2s_secure_auth but your version of LuaSec does "); + print(" not support certificate validation, so all s2s connections will"); + print(" fail."); + ok = false; + elseif all_options:contains("s2s_secure_domains") then + local secure_domains = set.new(); + for host in enabled_hosts() do + if config[host].s2s_secure_auth == true then + secure_domains:add("*"); + else + secure_domains:include(set.new(config[host].s2s_secure_domains)); + end + end + if not secure_domains:empty() then + print(""); + print(" You have set s2s_secure_domains but your version of LuaSec does "); + print(" not support certificate validation, so s2s connections to/from "); + print(" these domains will fail."); + ok = false; + end + end + elseif require_encryption and not all_modules:contains("tls") then + print(""); + print(" You require encryption but mod_tls is not enabled."); + print(" Connections will fail."); + ok = false; + end + + print("Done.\n"); + end + if not what or what == "dns" then + local dns = require "net.dns"; + local idna = require "util.encodings".idna; + local ip = require "util.ip"; + local c2s_ports = set.new(configmanager.get("*", "c2s_ports") or {5222}); + local s2s_ports = set.new(configmanager.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 + do + local res = dns.lookup(idna.to_ascii(fqdn), "A"); + if res then + for _, record in ipairs(res) do + external_addresses:add(record.a); + end + end + end + do + local res = dns.lookup(idna.to_ascii(fqdn), "AAAA"); + if res then + for _, record in ipairs(res) do + external_addresses:add(record.aaaa); + end + end + end + end + + local local_addresses = require"util.net".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 jid, host_options in enabled_hosts() do + local all_targets_ok, some_targets_ok = true, false; + local node, host = jid_split(jid); + + local modules, component_module = modulemanager.get_modules_for_host(host); + if component_module then + modules:add(component_module); + end + + local is_component = not not host_options.component_module; + print("Checking DNS for "..(is_component and "component" or "host").." "..jid.."..."); + if node then + print("Only the domain part ("..host..") is used in DNS.") + end + local target_hosts = set.new(); + if modules:contains("c2s") then + local res = dns.lookup("_xmpp-client._tcp."..idna.to_ascii(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_targets_ok = false; + else + target_hosts:add(host); + end + end + end + if modules:contains("s2s") then + local res = dns.lookup("_xmpp-server._tcp."..idna.to_ascii(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 + 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 + + if modules:contains("proxy65") then + local proxy65_target = configmanager.get(host, "proxy65_address") or host; + if type(proxy65_target) == "string" then + local A, AAAA = dns.lookup(idna.to_ascii(proxy65_target), "A"), dns.lookup(idna.to_ascii(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 + else + print(" proxy65_address for "..host.." should be set to a string, unable to perform DNS check"); + end + end + + for target_host in target_hosts do + local host_ok_v4, host_ok_v6; + do + local res = dns.lookup(idna.to_ascii(target_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(" "..target_host.." A record points to internal address, external connections might fail"); + else + print(" "..target_host.." A record points to unknown address "..record.a); + all_targets_ok = false; + end + end + end + end + do + local res = dns.lookup(idna.to_ascii(target_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(" "..target_host.." AAAA record points to internal address, external connections might fail"); + else + print(" "..target_host.." AAAA record points to unknown address "..record.aaaa); + all_targets_ok = false; + end + 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 "..target_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 "..target_host.." has AAAA records, but your version of LuaSocket does not support IPv6."); + print(" Please see https://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 https://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 create_context = require "core.certmanager".create_context; + local ssl = dependencies.softreq"ssl"; + -- local datetime_parse = require"util.datetime".parse_x509; + local load_cert = ssl and ssl.loadcertificate; + -- 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 + local function skip_bare_jid_hosts(host) + if jid_split(host) then + -- See issue #779 + return false; + end + return true; + end + for host in it.filter(skip_bare_jid_hosts, enabled_hosts()) do + print("Checking certificate for "..host); + -- First, let's find out what certificate this host uses. + local host_ssl_config = configmanager.rawget(host, "ssl") + or configmanager.rawget(host:match("%.(.*)"), "ssl"); + local global_ssl_config = configmanager.rawget("*", "ssl"); + local ok, err, ssl_config = create_context(host, "server", host_ssl_config, global_ssl_config); + if not ok then + print(" Error: "..err); + cert_ok = false + elseif not ssl_config.certificate then + print(" No 'certificate' found for "..host) + cert_ok = false + elseif not ssl_config.key then + print(" No 'key' found 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:close(); + if not cert:validat(os.time()) then + print(" Certificate has expired.") + cert_ok = false + elseif not cert:validat(os.time() + 86400) then + print(" Certificate expires within one day.") + cert_ok = false + elseif not cert:validat(os.time() + 86400*7) then + print(" Certificate expires within one week.") + elseif not cert:validat(os.time() + 86400*31) then + print(" Certificate expires within one month.") + end + if configmanager.get(host, "component_module") == nil + and not x509_verify_identity(host, "_xmpp-client", cert) then + print(" Not valid for client connections to "..host..".") + cert_ok = false + end + if (not (configmanager.get(host, "anonymous_login") + or configmanager.get(host, "authentication") == "anonymous")) + and not x509_verify_identity(host, "_xmpp-server", cert) then + print(" Not valid 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 https://prosody.im/doc/certificates"); + ok = false + 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 + +return { + check = check; +}; diff --git a/util/prosodyctl/shell.lua b/util/prosodyctl/shell.lua index cbcea927..1d07e6ec 100644 --- a/util/prosodyctl/shell.lua +++ b/util/prosodyctl/shell.lua @@ -126,5 +126,5 @@ local function start(arg) --luacheck: ignore 212/arg end return { - start = start; + shell = start; }; -- cgit v1.2.3 From 01f780eef118521830a7e377f69f9b70acfc3896 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Tue, 2 Jun 2020 08:02:03 +0100 Subject: util.human.io: Fix variable name [luacheck] --- util/human/io.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/human/io.lua b/util/human/io.lua index bfd1c00d..4c84c4a4 100644 --- a/util/human/io.lua +++ b/util/human/io.lua @@ -82,7 +82,7 @@ local function show_prompt(prompt) end local function printf(fmt, ...) - print(msg:format(...)); + print(fmt:format(...)); end return { -- cgit v1.2.3 From 0fea1b599ea6d1c1f14d3566797ec148313d4ddb Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 2 Jun 2020 09:07:29 +0200 Subject: util.prosodyctl.shell: Correct check for --socket --- util/prosodyctl/shell.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/prosodyctl/shell.lua b/util/prosodyctl/shell.lua index 1d07e6ec..1d312d3f 100644 --- a/util/prosodyctl/shell.lua +++ b/util/prosodyctl/shell.lua @@ -114,7 +114,7 @@ local function start(arg) --luacheck: ignore 212/arg end end); - local socket_path = path.resolve_relative_path(prosody.paths.data, arg.socket or config.get("*", "admin_socket") or "prosody.sock"); + local socket_path = path.resolve_relative_path(prosody.paths.data, prosody.opts.socket or config.get("*", "admin_socket") or "prosody.sock"); local conn = connection(socket_path, client.listeners); local ok, err = conn:connect(); if not ok then -- cgit v1.2.3 From 425c9cb979bfaae53b4ccf2a19ad1ec5fddac34c Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 2 Jun 2020 09:19:07 +0200 Subject: util.prosodyctl.shell: Really fix --socket option Forgot it stops parsing --foo options at the first argument, so subsequent commands need to parse their own options like this. --- util/prosodyctl/shell.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/util/prosodyctl/shell.lua b/util/prosodyctl/shell.lua index 1d312d3f..8c8769e2 100644 --- a/util/prosodyctl/shell.lua +++ b/util/prosodyctl/shell.lua @@ -10,6 +10,7 @@ local config = require "core.configmanager"; local server = require "net.server"; local st = require "util.stanza"; local path = require "util.paths"; +local parse_args = require "util.argparse".parse; local have_readline, readline = pcall(require, "readline"); @@ -91,6 +92,7 @@ end local function start(arg) --luacheck: ignore 212/arg local client = adminstream.client(); + local opts = parse_args(arg); client.events.add_handler("connected", function () if not arg.quiet then @@ -114,7 +116,7 @@ local function start(arg) --luacheck: ignore 212/arg end end); - local socket_path = path.resolve_relative_path(prosody.paths.data, prosody.opts.socket or config.get("*", "admin_socket") or "prosody.sock"); + local socket_path = path.resolve_relative_path(prosody.paths.data, opts.socket or config.get("*", "admin_socket") or "prosody.sock"); local conn = connection(socket_path, client.listeners); local ok, err = conn:connect(); if not ok then -- cgit v1.2.3 From 78a8bfc31dbe40fb4e0d073b7eb86d7c78f6a183 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Tue, 2 Jun 2020 08:28:39 +0100 Subject: util.prosodyctl.shell, util.adminstream: Move connection logic into adminstream for easier reuse --- util/adminstream.lua | 39 ++++++++++++++++++++++++++++++++++++ util/prosodyctl/shell.lua | 50 ++++++++--------------------------------------- 2 files changed, 47 insertions(+), 42 deletions(-) diff --git a/util/adminstream.lua b/util/adminstream.lua index 186cb0e9..b1bc0e64 100644 --- a/util/adminstream.lua +++ b/util/adminstream.lua @@ -136,6 +136,44 @@ end --- Public methods +local function new_connection(socket_path, listeners) + local have_unix, unix = pcall(require, "socket.unix"); + if type(unix) ~= "table" then + have_unix = false; + end + local conn, sock; + + return { + connect = function () + if not have_unix then + return nil, "no unix socket support"; + end + if sock or conn then + return nil, "already connected"; + end + sock = unix.stream(); + sock:settimeout(0); + local ok, err = sock:connect(socket_path); + if not ok then + return nil, err; + end + conn = server.wrapclient(sock, nil, nil, listeners, "*a"); + return true; + end; + disconnect = function () + if conn then + conn:close(); + conn = nil; + end + if sock then + sock:close(); + sock = nil; + end + return true; + end; + }; +end + local function new_server(sessions, stanza_handler) local listeners = {}; @@ -280,6 +318,7 @@ local function new_client() end return { + connection = new_connection; server = new_server; client = new_client; }; diff --git a/util/prosodyctl/shell.lua b/util/prosodyctl/shell.lua index 8c8769e2..bbf0c83a 100644 --- a/util/prosodyctl/shell.lua +++ b/util/prosodyctl/shell.lua @@ -1,11 +1,3 @@ -local have_unix, unix = pcall(require, "socket.unix"); - -if not have_unix or type(unix) ~= "table" then - print("** LuaSocket unix socket support not available or incompatible, ensure your"); - print("** version is up to date."); - os.exit(1); -end - local config = require "core.configmanager"; local server = require "net.server"; local st = require "util.stanza"; @@ -44,37 +36,6 @@ local function repl(client) send_line(client, line); end -local function connection(socket_path, listeners) - local conn, sock; - - return { - connect = function () - if sock or conn then - return nil, "already connected"; - end - sock = unix.stream(); - sock:settimeout(0); - local ok, err = sock:connect(socket_path); - if not ok then - return nil, err; - end - conn = server.wrapclient(sock, nil, nil, listeners, "*a"); - return true; - end; - disconnect = function () - if conn then - conn:close(); - conn = nil; - end - if sock then - sock:close(); - sock = nil; - end - return true; - end; - }; -end - local function printbanner() print([[ ____ \ / _ @@ -117,11 +78,16 @@ local function start(arg) --luacheck: ignore 212/arg end); local socket_path = path.resolve_relative_path(prosody.paths.data, opts.socket or config.get("*", "admin_socket") or "prosody.sock"); - local conn = connection(socket_path, client.listeners); + local conn = adminstream.connection(socket_path, client.listeners); local ok, err = conn:connect(); if not ok then - print("** Unable to connect to server - is it running? Is mod_admin_shell enabled?"); - print("** Connection error: "..err); + if err == "no unix socket support" then + print("** LuaSocket unix socket support not available or incompatible, ensure your"); + print("** version is up to date."); + else + print("** Unable to connect to server - is it running? Is mod_admin_shell enabled?"); + print("** Connection error: "..err); + end os.exit(1); end server.loop(); -- cgit v1.2.3 From 9aebf2d7dbc1ba31e4def08374382099a57a693b Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Tue, 2 Jun 2020 08:41:05 +0100 Subject: util.adminstream: Import net.server [luacheck] --- util/adminstream.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/util/adminstream.lua b/util/adminstream.lua index b1bc0e64..4583b322 100644 --- a/util/adminstream.lua +++ b/util/adminstream.lua @@ -5,6 +5,7 @@ local gettime = require "util.time".now; local runner = require "util.async".runner; local add_task = require "util.timer".add_task; local events = require "util.events"; +local server = require "net.server"; local stream_close_timeout = 5; -- cgit v1.2.3 From 6264bd48c14ae1d1ba5f22d90abf3c9842279f9c Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 2 Jun 2020 09:35:07 +0200 Subject: util.prosodyctl.shell: Save readline history --- util/prosodyctl/shell.lua | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/util/prosodyctl/shell.lua b/util/prosodyctl/shell.lua index bbf0c83a..3e98540f 100644 --- a/util/prosodyctl/shell.lua +++ b/util/prosodyctl/shell.lua @@ -10,6 +10,10 @@ local adminstream = require "util.adminstream"; if have_readline then readline.set_readline_name("prosody"); + readline.set_options({ + histfile = path.join(prosody.paths.data, ".shell_history"); + ignoredups = true; + }); end local function read_line() @@ -31,6 +35,9 @@ local function repl(client) if not line then print(""); end + if have_readline then + readline.save_history(); + end os.exit(); end send_line(client, line); -- cgit v1.2.3 From 753836c876d5060cbc7e0c2029597abaae844eed Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 2 Jun 2020 16:40:23 +0200 Subject: mod_admin_shell: Fix error due to float passed to os.date in Lua 5.3 Thanks Martin --- plugins/mod_admin_shell.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/mod_admin_shell.lua b/plugins/mod_admin_shell.lua index a9ca797c..fd88f59c 100644 --- a/plugins/mod_admin_shell.lua +++ b/plugins/mod_admin_shell.lua @@ -1273,11 +1273,11 @@ function def_env.debug:timers() print("-- util.timer"); for i, id in ipairs(h.ids) do if not params[id] then - print(os.date("%F %T", h.priorities[i]), h.items[id]); + print(os.date("%F %T", math.floor(h.priorities[i])), h.items[id]); elseif not params[id].callback then - print(os.date("%F %T", h.priorities[i]), h.items[id], unpack(params[id])); + print(os.date("%F %T", math.floor(h.priorities[i])), h.items[id], unpack(params[id])); else - print(os.date("%F %T", h.priorities[i]), params[id].callback, unpack(params[id])); + print(os.date("%F %T", math.floor(h.priorities[i])), params[id].callback, unpack(params[id])); end end end @@ -1293,7 +1293,7 @@ function def_env.debug:timers() if h then local next_time = h:peek(); if next_time then - return true, os.date("Next event at %F %T (in %%.6fs)", next_time):format(next_time - time.now()); + return true, os.date("Next event at %F %T (in %%.6fs)", math.floor(next_time)):format(next_time - time.now()); end end return true; -- cgit v1.2.3 From 6b98a3f55143f5be0b22ff5ce83999363cd5e253 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 2 Jun 2020 19:43:50 +0200 Subject: mod_s2s: Move out of empty directory mod_s2s.lua had been all alone in there since the removal of s2sout.lib.lua in 756b8821007a --- GNUmakefile | 3 +- plugins/mod_s2s.lua | 810 ++++++++++++++++++++++++++++++++++++++++++++ plugins/mod_s2s/mod_s2s.lua | 810 -------------------------------------------- 3 files changed, 811 insertions(+), 812 deletions(-) create mode 100644 plugins/mod_s2s.lua delete mode 100644 plugins/mod_s2s/mod_s2s.lua diff --git a/GNUmakefile b/GNUmakefile index 15362f5c..950690e6 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -49,9 +49,8 @@ install: prosody.install prosodyctl.install prosody.cfg.lua.install util/encodin $(INSTALL_DATA) util/*.so $(SOURCE)/util $(MKDIR) $(SOURCE)/util/sasl $(INSTALL_DATA) util/sasl/*.lua $(SOURCE)/util/sasl - $(MKDIR) $(MODULES)/mod_s2s $(MODULES)/mod_pubsub $(MODULES)/adhoc $(MODULES)/muc $(MODULES)/mod_mam + $(MKDIR) $(MODULES)/mod_pubsub $(MODULES)/adhoc $(MODULES)/muc $(MODULES)/mod_mam $(INSTALL_DATA) plugins/*.lua $(MODULES) - $(INSTALL_DATA) plugins/mod_s2s/*.lua $(MODULES)/mod_s2s $(INSTALL_DATA) plugins/mod_pubsub/*.lua $(MODULES)/mod_pubsub $(INSTALL_DATA) plugins/adhoc/*.lua $(MODULES)/adhoc $(INSTALL_DATA) plugins/muc/*.lua $(MODULES)/muc diff --git a/plugins/mod_s2s.lua b/plugins/mod_s2s.lua new file mode 100644 index 00000000..0674f981 --- /dev/null +++ b/plugins/mod_s2s.lua @@ -0,0 +1,810 @@ +-- Prosody IM +-- Copyright (C) 2008-2010 Matthew Wild +-- Copyright (C) 2008-2010 Waqas Hussain +-- +-- This project is MIT/X11 licensed. Please see the +-- COPYING file in the source package for more information. +-- + +module:set_global(); + +local prosody = prosody; +local hosts = prosody.hosts; +local core_process_stanza = prosody.core_process_stanza; + +local tostring, type = tostring, type; +local t_insert = table.insert; +local traceback = debug.traceback; + +local add_task = require "util.timer".add_task; +local st = require "util.stanza"; +local initialize_filters = require "util.filters".initialize; +local nameprep = require "util.encodings".stringprep.nameprep; +local new_xmpp_stream = require "util.xmppstream".new; +local s2s_new_incoming = require "core.s2smanager".new_incoming; +local s2s_new_outgoing = require "core.s2smanager".new_outgoing; +local s2s_destroy_session = require "core.s2smanager".destroy_session; +local uuid_gen = require "util.uuid".generate; +local fire_global_event = prosody.events.fire_event; +local runner = require "util.async".runner; +local connect = require "net.connect".connect; +local service = require "net.resolvers.service"; +local errors = require "util.error"; +local set = require "util.set"; + +local connect_timeout = module:get_option_number("s2s_timeout", 90); +local stream_close_timeout = module:get_option_number("s2s_close_timeout", 5); +local opt_keepalives = module:get_option_boolean("s2s_tcp_keepalives", module:get_option_boolean("tcp_keepalives", true)); +local secure_auth = module:get_option_boolean("s2s_secure_auth", false); -- One day... +local secure_domains, insecure_domains = + module:get_option_set("s2s_secure_domains", {})._items, module:get_option_set("s2s_insecure_domains", {})._items; +local require_encryption = module:get_option_boolean("s2s_require_encryption", false); +local stanza_size_limit = module:get_option_number("s2s_stanza_size_limit"); -- TODO come up with a sensible default (util.xmppstream defaults to 10M) + +local measure_connections = module:measure("connections", "amount"); +local measure_ipv6 = module:measure("ipv6", "amount"); + +local sessions = module:shared("sessions"); + +local runner_callbacks = {}; + +local listener = {}; + +local log = module._log; + +local s2s_service_options = { + default_port = 5269; + use_ipv4 = module:get_option_boolean("use_ipv4", true); + use_ipv6 = module:get_option_boolean("use_ipv6", true); +}; + +module:hook("stats-update", function () + local count = 0; + local ipv6 = 0; + for _, session in pairs(sessions) do + count = count + 1; + if session.ip and session.ip:match(":") then + ipv6 = ipv6 + 1; + end + end + measure_connections(count); + measure_ipv6(ipv6); +end); + +--- Handle stanzas to remote domains + +local bouncy_stanzas = { message = true, presence = true, iq = true }; +local function bounce_sendq(session, reason) + local sendq = session.sendq; + if not sendq then return; end + session.log("info", "Sending error replies for %d queued stanzas because of failed outgoing connection to %s", #sendq, session.to_host); + local dummy = { + type = "s2sin"; + send = function () + (session.log or log)("error", "Replying to to an s2s error reply, please report this! Traceback: %s", traceback()); + end; + dummy = true; + close = function () + (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"; + local reason_text; + if session.had_stream then -- set when a stream is opened by the remote + error_type, condition = "wait", "remote-server-timeout"; + end + if errors.is_err(reason) then + error_type, condition, reason_text = reason.type, reason.condition, reason.text; + elseif type(reason) == "string" then + reason_text = reason; + 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 = error_type, by = session.from_host}) + :tag(condition, {xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas"}):up(); + if reason_text then + reply:tag("text", {xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas"}) + :text("Server-to-server connection failed: "..reason_text):up(); + end + core_process_stanza(dummy, reply); + end + sendq[i] = nil; + end + session.sendq = nil; +end + +-- Handles stanzas to existing s2s sessions +function route_to_existing_session(event) + local from_host, to_host, stanza = event.from_host, event.to_host, event.stanza; + if not hosts[from_host] then + log("warn", "Attempt to send stanza from %s - a host we don't serve", from_host); + return false; + end + if hosts[to_host] then + log("warn", "Attempt to route stanza to a remote %s - a host we do serve?!", from_host); + return false; + end + local host = hosts[from_host].s2sout[to_host]; + if not host then return end + + -- We have a connection to this host already + if host.type == "s2sout_unauthed" and (stanza.name ~= "db:verify" or not host.dialback_key) then + (host.log or log)("debug", "trying to send over unauthed s2sout to "..to_host); + + -- Queue stanza until we are able to send it + local queued_item = { + tostring(stanza), + stanza.attr.type ~= "error" and stanza.attr.type ~= "result" and st.reply(stanza); + }; + if host.sendq then + t_insert(host.sendq, queued_item); + else + -- luacheck: ignore 122 + host.sendq = { queued_item }; + end + host.log("debug", "stanza [%s] queued ", stanza.name); + return true; + elseif host.type == "local" or host.type == "component" then + log("error", "Trying to send a stanza to ourselves??") + log("error", "Traceback: %s", traceback()); + log("error", "Stanza: %s", stanza); + return false; + else + if host.sends2s(stanza) then + return true; + end + end +end + +-- Create a new outgoing session for a stanza +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", stanza.name); + connect(service.new(to_host, "xmpp-server", "tcp", s2s_service_options), listener, nil, { session = host_session }); + return true; +end + +local function keepalive(event) + return event.session.sends2s(' '); +end + +module:hook("s2s-read-timeout", keepalive, -1); + +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 https://prosody.im/doc/s2s#disabling"); + return nil, "This host has disallow_s2s set"; + end + 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); + module:hook_stanza("http://etherx.jabber.org/streams", "features", function (session, stanza) -- luacheck: ignore 212/stanza + if session.type == "s2sout" then + -- Stream is authenticated and we are seem to be done with feature negotiation, + -- so the stream is ready for stanzas. RFC 6120 Section 4.3 + mark_connected(session); + return true; + elseif require_encryption and not session.secure then + session.log("warn", "Encrypted server-to-server communication is required but was not offered by %s", session.to_host); + session:close({ + condition = "policy-violation", + text = "Encrypted server-to-server communication is required but was not offered", + }, nil, "Could not establish encrypted connection to remote server"); + return true; + elseif not session.dialback_verifying then + session.log("warn", "No SASL EXTERNAL offer and Dialback doesn't seem to be enabled, giving up"); + session:close({ + condition = "unsupported-feature", + text = "No viable authentication method offered", + }, nil, "No viable authentication method offered by remote server"); + return true; + end + end, -1); +end + +-- Stream is authorised, and ready for normal stanzas +function mark_connected(session) + + local sendq = session.sendq; + + local from, to = session.from_host, session.to_host; + + session.log("info", "%s s2s connection %s->%s complete", session.direction:gsub("^.", string.upper), from, to); + + local event_data = { session = session }; + if session.type == "s2sout" then + fire_global_event("s2sout-established", event_data); + hosts[from].events.fire_event("s2sout-established", event_data); + + if session.incoming then + session.send = function(stanza) + return hosts[from].events.fire_event("route/remote", { from_host = from, to_host = to, stanza = stanza }); + end; + end + + else + if session.outgoing and not hosts[to].s2sout[from] then + session.log("debug", "Setting up to handle route from %s to %s", to, from); + hosts[to].s2sout[from] = session; -- luacheck: ignore 122 + end + local host_session = hosts[to]; + session.send = function(stanza) + return host_session.events.fire_event("route/remote", { from_host = to, to_host = from, stanza = stanza }); + end; + + fire_global_event("s2sin-established", event_data); + hosts[to].events.fire_event("s2sin-established", event_data); + end + + if session.direction == "outgoing" then + if sendq then + session.log("debug", "sending %d queued stanzas across new outgoing connection to %s", #sendq, session.to_host); + local send = session.sends2s; + for i, data in ipairs(sendq) do + send(data[1]); + sendq[i] = nil; + end + session.sendq = nil; + end + end +end + +function make_authenticated(event) + local session, host = event.session, event.host; + if not session.secure then + if require_encryption or (secure_auth and not(insecure_domains[host])) or secure_domains[host] then + session:close({ + condition = "policy-violation", + text = "Encrypted server-to-server communication is required but was not " + ..((session.direction == "outgoing" and "offered") or "used") + }, nil, "Could not establish encrypted connection to remote server"); + end + end + if hosts[host] then + session:close({ condition = "undefined-condition", text = "Attempt to authenticate as a host we serve" }); + end + if session.type == "s2sout_unauthed" then + session.type = "s2sout"; + elseif session.type == "s2sin_unauthed" then + session.type = "s2sin"; + elseif session.type ~= "s2sin" and session.type ~= "s2sout" then + return false; + end + + if session.incoming and host then + if not session.hosts[host] then session.hosts[host] = {}; end + session.hosts[host].authed = true; + end + session.log("debug", "connection %s->%s is now authenticated for %s", session.from_host, session.to_host, host); + + if (session.type == "s2sout" and session.external_auth ~= "succeeded") or session.type == "s2sin" then + -- Stream either used dialback for authentication or is an incoming stream. + mark_connected(session); + end + + return true; +end + +--- Helper to check that a session peer's certificate is valid +function check_cert_status(session) + local host = session.direction == "outgoing" and session.to_host or session.from_host + local conn = session.conn:socket() + local cert + if conn.getpeercertificate then + cert = conn:getpeercertificate() + end + + return module:fire_event("s2s-check-certificate", { host = host, session = session, cert = cert }); +end + +--- XMPP stream event handlers + +local stream_callbacks = { default_ns = "jabber:server" }; + +function stream_callbacks.handlestanza(session, stanza) + stanza = session.filter("stanzas/in", stanza); + session.thread:run(stanza); +end + +local xmlns_xmpp_streams = "urn:ietf:params:xml:ns:xmpp-streams"; + +function stream_callbacks.streamopened(session, attr) + -- run _streamopened in async context + session.thread:run({ stream = "opened", attr = attr }); +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 + session.secure = true; + session.encrypted = true; + + local sock = session.conn:socket(); + if sock.info then + local info = sock:info(); + (session.log or log)("info", "Stream encrypted (%s with %s)", info.protocol, info.cipher); + session.compressed = info.compression; + else + (session.log or log)("info", "Stream encrypted"); + end + end + + if session.direction == "incoming" then + -- Send a reply stream header + + -- Validate to/from + local to, from = attr.to, attr.from; + if to then to = nameprep(attr.to); end + if from then from = nameprep(attr.from); end + if not to and attr.to then -- COMPAT: Some servers do not reliably set 'to' (especially on stream restarts) + session:close({ condition = "improper-addressing", text = "Invalid 'to' address" }); + return; + end + if not from and attr.from then -- COMPAT: Some servers do not reliably set 'from' (especially on stream restarts) + session:close({ condition = "improper-addressing", text = "Invalid 'from' address" }); + return; + end + + -- Set session.[from/to]_host if they have not been set already and if + -- this session isn't already authenticated + if session.type == "s2sin_unauthed" and from and not session.from_host then + session.from_host = from; + elseif from ~= session.from_host then + session:close({ condition = "improper-addressing", text = "New stream 'from' attribute does not match original" }); + return; + end + if session.type == "s2sin_unauthed" and to and not session.to_host then + session.to_host = to; + elseif to ~= session.to_host then + session:close({ condition = "improper-addressing", text = "New stream 'to' attribute does not match original" }); + return; + end + + -- For convenience we'll put the sanitised values into these variables + to, from = session.to_host, session.from_host; + + session.streamid = uuid_gen(); + (session.log or log)("debug", "Incoming s2s received %s", st.stanza("stream:stream", attr):top_tag()); + if to then + if not hosts[to] then + -- Attempting to connect to a host we don't serve + session:close({ + condition = "host-unknown"; + text = "This host does not serve "..to + }); + return; + elseif not hosts[to].modules.s2s then + -- Attempting to connect to a host that disallows s2s + session:close({ + condition = "policy-violation"; + text = "Server-to-server communication is disabled for this host"; + }); + return; + end + end + + if hosts[from] then + session:close({ condition = "undefined-condition", text = "Attempt to connect from a host we serve" }); + return; + end + + if session.secure and not session.cert_chain_status then + if check_cert_status(session) == false then + return; + end + end + + session:open_stream(session.to_host, session.from_host) + session.notopen = nil; + if session.version >= 1.0 then + local features = st.stanza("stream:features"); + + if to then + hosts[to].events.fire_event("s2s-stream-features", { origin = session, features = features }); + else + (session.log or log)("warn", "No 'to' on stream header from %s means we can't offer any features", from or session.ip or "unknown host"); + fire_global_event("s2s-stream-features-legacy", { origin = session, features = features }); + end + + if ( session.type == "s2sin" or session.type == "s2sout" ) or features.tags[1] then + log("debug", "Sending stream features: %s", features); + session.sends2s(features); + else + (session.log or log)("warn", "No stream features to offer, giving up"); + session:close({ condition = "undefined-condition", text = "No stream features to offer" }); + end + end + elseif session.direction == "outgoing" then + session.notopen = nil; + if not attr.id then + log("warn", "Stream response did not give us a stream id!"); + session:close({ condition = "undefined-condition", text = "Missing stream ID" }); + return; + end + session.streamid = attr.id; + + if session.secure and not session.cert_chain_status then + if check_cert_status(session) == false then + return; + end + end + + -- If server is pre-1.0, don't wait for features, just do dialback + if session.version < 1.0 then + if not session.dialback_verifying then + hosts[session.from_host].events.fire_event("s2sout-authenticate-legacy", { origin = session }); + else + mark_connected(session); + end + end + end +end + +function stream_callbacks._streamclosed(session) + (session.log or log)("debug", "Received "); + session:close(false); +end + +function stream_callbacks.streamclosed(session, attr) + -- run _streamclosed in async context + session.thread:run({ stream = "closed", attr = attr }); +end + +function stream_callbacks.error(session, error, data) + if error == "no-stream" then + session.log("debug", "Invalid opening stream header (%s)", (data:gsub("^([^\1]+)\1", "{%1}"))); + session:close("invalid-namespace"); + elseif error == "parse-error" then + session.log("debug", "Server-to-server XML parse error: %s", error); + session:close("not-well-formed"); + elseif error == "stream-error" then + local condition, text = "undefined-condition"; + for child in data:childtags(nil, xmlns_xmpp_streams) do + if child.name ~= "text" then + condition = child.name; + else + text = child:get_text(); + end + if condition ~= "undefined-condition" and text then + break; + end + end + text = condition .. (text and (" ("..text..")") or ""); + session.log("info", "Session closed by remote with error: %s", text); + session:close(nil, text); + end +end + +--- Session methods +local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'}; +-- reason: stream error to send to the remote server +-- remote_reason: stream error received from the remote server +-- bounce_reason: stanza error to pass to bounce_sendq because stream- and stanza errors are different +local function session_close(session, reason, remote_reason, bounce_reason) + local log = session.log or log; + if session.conn then + if session.notopen then + if session.direction == "incoming" then + session:open_stream(session.to_host, session.from_host); + else + session:open_stream(session.from_host, session.to_host); + end + end + if reason then -- nil == no err, initiated by us, false == initiated by remote + local stream_error; + if type(reason) == "string" then -- assume stream error + stream_error = st.stanza("stream:error"):tag(reason, {xmlns = 'urn:ietf:params:xml:ns:xmpp-streams' }); + elseif type(reason) == "table" and not st.is_stanza(reason) then + stream_error = st.stanza("stream:error"):tag(reason.condition or "undefined-condition", stream_xmlns_attr):up(); + if reason.text then + stream_error:tag("text", stream_xmlns_attr):text(reason.text):up(); + end + if reason.extra then + stream_error:add_child(reason.extra); + end + end + if st.is_stanza(stream_error) then + -- to and from are never unknown on outgoing connections + log("debug", "Disconnecting %s->%s[%s], is: %s", + session.from_host or "(unknown host)" or session.ip, session.to_host or "(unknown host)", session.type, reason); + session.sends2s(stream_error); + end + end + + session.sends2s(""); + function session.sends2s() return false; end + + -- luacheck: ignore 422/reason + -- FIXME reason should be managed in a place common to c2s, s2s, bosh, component etc + local reason = remote_reason or (reason and (reason.text or reason.condition)) or reason; + session.log("info", "%s s2s stream %s->%s closed: %s", session.direction:gsub("^.", string.upper), + session.from_host or "(unknown host)", session.to_host or "(unknown host)", reason or "stream closed"); + + -- Authenticated incoming stream may still be sending us stanzas, so wait for from remote + local conn = session.conn; + if reason == nil and not session.notopen and session.incoming then + add_task(stream_close_timeout, function () + if not session.destroyed then + session.log("warn", "Failed to receive a stream close response, closing connection anyway..."); + s2s_destroy_session(session, reason, bounce_reason); + conn:close(); + end + end); + else + s2s_destroy_session(session, reason, bounce_reason); + conn:close(); -- Close immediately, as this is an outgoing connection or is not authed + end + end +end + +function session_stream_attrs(session, from, to, attr) -- luacheck: ignore 212/session + if not from or (hosts[from] and hosts[from].modules.dialback) then + attr["xmlns:db"] = 'jabber:server:dialback'; + end + if not from then + attr.from = ''; + end + if not to then + attr.to = ''; + end +end + +-- Session initialization logic shared by incoming and outgoing +local function initialize_session(session) + local stream = new_xmpp_stream(session, stream_callbacks, stanza_size_limit); + + session.thread = runner(function (stanza) + if st.is_stanza(stanza) then + core_process_stanza(session, stanza); + elseif stanza.stream == "opened" then + stream_callbacks._streamopened(session, stanza.attr); + elseif stanza.stream == "closed" then + stream_callbacks._streamclosed(session, stanza.attr); + end + end, runner_callbacks, session); + + local log = session.log or log; + session.stream = stream; + + session.notopen = true; + + function session.reset_stream() + session.notopen = true; + session.streamid = nil; + session.stream:reset(); + end + + session.stream_attrs = session_stream_attrs; + + local filter = initialize_filters(session); + local conn = session.conn; + local w = conn.write; + + function session.sends2s(t) + log("debug", "Sending[%s]: %s", session.type, t.top_tag and t:top_tag() or t:match("^[^>]*>?")); + if t.name then + t = filter("stanzas/out", t); + end + if t then + t = filter("bytes/out", tostring(t)); + if t then + return w(conn, t); + end + end + end + + function session.data(data) + data = filter("bytes/in", data); + if data then + local ok, err = stream:feed(data); + if ok then return; end + log("debug", "Received invalid XML (%s) %d bytes: %q", err, #data, data:sub(1, 300)); + if err == "stanza-too-large" then + session:close({ condition = "policy-violation", text = "XML stanza is too big" }, nil, "Received invalid XML from remote server"); + else + session:close("not-well-formed", nil, "Received invalid XML from remote server"); + end + end + end + + session.close = session_close; + + local handlestanza = stream_callbacks.handlestanza; + function session.dispatch_stanza(session, stanza) -- luacheck: ignore 432/session + return handlestanza(session, stanza); + end + + module:fire_event("s2s-created", { session = session }); + + add_task(connect_timeout, function () + if session.type == "s2sin" or session.type == "s2sout" then + return; -- Ok, we're connected + elseif session.type == "s2s_destroyed" then + return; -- Session already destroyed + end + -- Not connected, need to close session and clean up + (session.log or log)("debug", "Destroying incomplete session %s->%s due to inactivity", + session.from_host or "(unknown)", session.to_host or "(unknown)"); + session:close("connection-timeout"); + end); +end + +function runner_callbacks:ready() + self.data.log("debug", "Runner %s ready (%s)", self.thread, coroutine.status(self.thread)); + self.data.conn:resume(); +end + +function runner_callbacks:waiting() + self.data.log("debug", "Runner %s waiting (%s)", self.thread, coroutine.status(self.thread)); + self.data.conn:pause(); +end + +function runner_callbacks:error(err) + (self.data.log or log)("error", "Traceback[s2s]: %s", err); +end + +function listener.onconnect(conn) + conn:setoption("keepalive", opt_keepalives); + local session = sessions[conn]; + if not session then -- New incoming connection + session = s2s_new_incoming(conn); + sessions[conn] = session; + session.log("debug", "Incoming s2s connection"); + initialize_session(session); + else -- Outgoing session connected + session:open_stream(session.from_host, session.to_host); + end + session.ip = conn:ip(); +end + +function listener.onincoming(conn, data) + local session = sessions[conn]; + if session then + session.data(data); + end +end + +function listener.onstatus(conn, status) + if status == "ssl-handshake-complete" then + local session = sessions[conn]; + if session and session.direction == "outgoing" then + session.log("debug", "Sending stream header..."); + session:open_stream(session.from_host, session.to_host); + end + end +end + +function listener.ondisconnect(conn, err) + local session = sessions[conn]; + if session then + sessions[conn] = nil; + (session.log or log)("debug", "s2s disconnected: %s->%s (%s)", session.from_host, session.to_host, err or "connection closed"); + if session.secure == false and err then + -- TODO util.error-ify this + err = "Error during negotiation of encrypted connection: "..err; + end + 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); + 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); + end +end + +function listener.onreadtimeout(conn) + local session = sessions[conn]; + if session then + local host = session.host or session.to_host; + return (hosts[host] or prosody).events.fire_event("s2s-read-timeout", { session = session }); + end +end + +function listener.register_outgoing(conn, session) + sessions[conn] = session; + initialize_session(session); +end + +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 + +-- Complete the sentence "Your certificate " with what's wrong +local function friendly_cert_error(session) --> string + if session.cert_chain_status == "invalid" then + if session.cert_chain_errors then + local cert_errors = set.new(session.cert_chain_errors[1]); + if cert_errors:contains("certificate has expired") then + return "has expired"; + elseif cert_errors:contains("self signed certificate") then + return "is self-signed"; + end + end + return "is not trusted"; -- for some other reason + elseif session.cert_identity_status == "invalid" then + return "is not valid for this name"; + end + -- this should normally be unreachable except if no s2s auth module was loaded + return "could not be validated"; +end + +function check_auth_policy(event) + local host, session = event.host, event.session; + local must_secure = secure_auth; + + if not must_secure and secure_domains[host] then + must_secure = true; + elseif must_secure and insecure_domains[host] then + must_secure = false; + end + + if must_secure and (session.cert_chain_status ~= "valid" or session.cert_identity_status ~= "valid") then + local reason = friendly_cert_error(session); + session.log("warn", "Forbidding insecure connection to/from %s because its certificate %s", host or session.ip or "(unknown host)", reason); + -- XEP-0178 recommends closing outgoing connections without warning + -- but does not give a rationale for this. + -- In practice most cases are configuration mistakes or forgotten + -- certificate renewals. We think it's better to let the other party + -- know about the problem so that they can fix it. + session:close({ condition = "not-authorized", text = "Your server's certificate "..reason }, + nil, "Remote server's certificate "..reason); + return false; + end +end + +module:hook("s2s-check-certificate", check_auth_policy, -1); + +module:hook("server-stopping", function(event) + local reason = event.reason; + for _, session in pairs(sessions) do + session:close{ condition = "system-shutdown", text = reason }; + end +end, -200); + + + +module:provides("net", { + name = "s2s"; + listener = listener; + default_port = 5269; + encryption = "starttls"; + ssl_config = { -- FIXME This is not used atm, see mod_tls + verify = { "peer", "client_once", }; + }; + multiplex = { + protocol = "xmpp-server"; + pattern = "^<.*:stream.*%sxmlns%s*=%s*(['\"])jabber:server%1.*>"; + }; +}); + diff --git a/plugins/mod_s2s/mod_s2s.lua b/plugins/mod_s2s/mod_s2s.lua deleted file mode 100644 index 0674f981..00000000 --- a/plugins/mod_s2s/mod_s2s.lua +++ /dev/null @@ -1,810 +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:set_global(); - -local prosody = prosody; -local hosts = prosody.hosts; -local core_process_stanza = prosody.core_process_stanza; - -local tostring, type = tostring, type; -local t_insert = table.insert; -local traceback = debug.traceback; - -local add_task = require "util.timer".add_task; -local st = require "util.stanza"; -local initialize_filters = require "util.filters".initialize; -local nameprep = require "util.encodings".stringprep.nameprep; -local new_xmpp_stream = require "util.xmppstream".new; -local s2s_new_incoming = require "core.s2smanager".new_incoming; -local s2s_new_outgoing = require "core.s2smanager".new_outgoing; -local s2s_destroy_session = require "core.s2smanager".destroy_session; -local uuid_gen = require "util.uuid".generate; -local fire_global_event = prosody.events.fire_event; -local runner = require "util.async".runner; -local connect = require "net.connect".connect; -local service = require "net.resolvers.service"; -local errors = require "util.error"; -local set = require "util.set"; - -local connect_timeout = module:get_option_number("s2s_timeout", 90); -local stream_close_timeout = module:get_option_number("s2s_close_timeout", 5); -local opt_keepalives = module:get_option_boolean("s2s_tcp_keepalives", module:get_option_boolean("tcp_keepalives", true)); -local secure_auth = module:get_option_boolean("s2s_secure_auth", false); -- One day... -local secure_domains, insecure_domains = - module:get_option_set("s2s_secure_domains", {})._items, module:get_option_set("s2s_insecure_domains", {})._items; -local require_encryption = module:get_option_boolean("s2s_require_encryption", false); -local stanza_size_limit = module:get_option_number("s2s_stanza_size_limit"); -- TODO come up with a sensible default (util.xmppstream defaults to 10M) - -local measure_connections = module:measure("connections", "amount"); -local measure_ipv6 = module:measure("ipv6", "amount"); - -local sessions = module:shared("sessions"); - -local runner_callbacks = {}; - -local listener = {}; - -local log = module._log; - -local s2s_service_options = { - default_port = 5269; - use_ipv4 = module:get_option_boolean("use_ipv4", true); - use_ipv6 = module:get_option_boolean("use_ipv6", true); -}; - -module:hook("stats-update", function () - local count = 0; - local ipv6 = 0; - for _, session in pairs(sessions) do - count = count + 1; - if session.ip and session.ip:match(":") then - ipv6 = ipv6 + 1; - end - end - measure_connections(count); - measure_ipv6(ipv6); -end); - ---- Handle stanzas to remote domains - -local bouncy_stanzas = { message = true, presence = true, iq = true }; -local function bounce_sendq(session, reason) - local sendq = session.sendq; - if not sendq then return; end - session.log("info", "Sending error replies for %d queued stanzas because of failed outgoing connection to %s", #sendq, session.to_host); - local dummy = { - type = "s2sin"; - send = function () - (session.log or log)("error", "Replying to to an s2s error reply, please report this! Traceback: %s", traceback()); - end; - dummy = true; - close = function () - (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"; - local reason_text; - if session.had_stream then -- set when a stream is opened by the remote - error_type, condition = "wait", "remote-server-timeout"; - end - if errors.is_err(reason) then - error_type, condition, reason_text = reason.type, reason.condition, reason.text; - elseif type(reason) == "string" then - reason_text = reason; - 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 = error_type, by = session.from_host}) - :tag(condition, {xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas"}):up(); - if reason_text then - reply:tag("text", {xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas"}) - :text("Server-to-server connection failed: "..reason_text):up(); - end - core_process_stanza(dummy, reply); - end - sendq[i] = nil; - end - session.sendq = nil; -end - --- Handles stanzas to existing s2s sessions -function route_to_existing_session(event) - local from_host, to_host, stanza = event.from_host, event.to_host, event.stanza; - if not hosts[from_host] then - log("warn", "Attempt to send stanza from %s - a host we don't serve", from_host); - return false; - end - if hosts[to_host] then - log("warn", "Attempt to route stanza to a remote %s - a host we do serve?!", from_host); - return false; - end - local host = hosts[from_host].s2sout[to_host]; - if not host then return end - - -- We have a connection to this host already - if host.type == "s2sout_unauthed" and (stanza.name ~= "db:verify" or not host.dialback_key) then - (host.log or log)("debug", "trying to send over unauthed s2sout to "..to_host); - - -- Queue stanza until we are able to send it - local queued_item = { - tostring(stanza), - stanza.attr.type ~= "error" and stanza.attr.type ~= "result" and st.reply(stanza); - }; - if host.sendq then - t_insert(host.sendq, queued_item); - else - -- luacheck: ignore 122 - host.sendq = { queued_item }; - end - host.log("debug", "stanza [%s] queued ", stanza.name); - return true; - elseif host.type == "local" or host.type == "component" then - log("error", "Trying to send a stanza to ourselves??") - log("error", "Traceback: %s", traceback()); - log("error", "Stanza: %s", stanza); - return false; - else - if host.sends2s(stanza) then - return true; - end - end -end - --- Create a new outgoing session for a stanza -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", stanza.name); - connect(service.new(to_host, "xmpp-server", "tcp", s2s_service_options), listener, nil, { session = host_session }); - return true; -end - -local function keepalive(event) - return event.session.sends2s(' '); -end - -module:hook("s2s-read-timeout", keepalive, -1); - -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 https://prosody.im/doc/s2s#disabling"); - return nil, "This host has disallow_s2s set"; - end - 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); - module:hook_stanza("http://etherx.jabber.org/streams", "features", function (session, stanza) -- luacheck: ignore 212/stanza - if session.type == "s2sout" then - -- Stream is authenticated and we are seem to be done with feature negotiation, - -- so the stream is ready for stanzas. RFC 6120 Section 4.3 - mark_connected(session); - return true; - elseif require_encryption and not session.secure then - session.log("warn", "Encrypted server-to-server communication is required but was not offered by %s", session.to_host); - session:close({ - condition = "policy-violation", - text = "Encrypted server-to-server communication is required but was not offered", - }, nil, "Could not establish encrypted connection to remote server"); - return true; - elseif not session.dialback_verifying then - session.log("warn", "No SASL EXTERNAL offer and Dialback doesn't seem to be enabled, giving up"); - session:close({ - condition = "unsupported-feature", - text = "No viable authentication method offered", - }, nil, "No viable authentication method offered by remote server"); - return true; - end - end, -1); -end - --- Stream is authorised, and ready for normal stanzas -function mark_connected(session) - - local sendq = session.sendq; - - local from, to = session.from_host, session.to_host; - - session.log("info", "%s s2s connection %s->%s complete", session.direction:gsub("^.", string.upper), from, to); - - local event_data = { session = session }; - if session.type == "s2sout" then - fire_global_event("s2sout-established", event_data); - hosts[from].events.fire_event("s2sout-established", event_data); - - if session.incoming then - session.send = function(stanza) - return hosts[from].events.fire_event("route/remote", { from_host = from, to_host = to, stanza = stanza }); - end; - end - - else - if session.outgoing and not hosts[to].s2sout[from] then - session.log("debug", "Setting up to handle route from %s to %s", to, from); - hosts[to].s2sout[from] = session; -- luacheck: ignore 122 - end - local host_session = hosts[to]; - session.send = function(stanza) - return host_session.events.fire_event("route/remote", { from_host = to, to_host = from, stanza = stanza }); - end; - - fire_global_event("s2sin-established", event_data); - hosts[to].events.fire_event("s2sin-established", event_data); - end - - if session.direction == "outgoing" then - if sendq then - session.log("debug", "sending %d queued stanzas across new outgoing connection to %s", #sendq, session.to_host); - local send = session.sends2s; - for i, data in ipairs(sendq) do - send(data[1]); - sendq[i] = nil; - end - session.sendq = nil; - end - end -end - -function make_authenticated(event) - local session, host = event.session, event.host; - if not session.secure then - if require_encryption or (secure_auth and not(insecure_domains[host])) or secure_domains[host] then - session:close({ - condition = "policy-violation", - text = "Encrypted server-to-server communication is required but was not " - ..((session.direction == "outgoing" and "offered") or "used") - }, nil, "Could not establish encrypted connection to remote server"); - end - end - if hosts[host] then - session:close({ condition = "undefined-condition", text = "Attempt to authenticate as a host we serve" }); - end - if session.type == "s2sout_unauthed" then - session.type = "s2sout"; - elseif session.type == "s2sin_unauthed" then - session.type = "s2sin"; - elseif session.type ~= "s2sin" and session.type ~= "s2sout" then - return false; - end - - if session.incoming and host then - if not session.hosts[host] then session.hosts[host] = {}; end - session.hosts[host].authed = true; - end - session.log("debug", "connection %s->%s is now authenticated for %s", session.from_host, session.to_host, host); - - if (session.type == "s2sout" and session.external_auth ~= "succeeded") or session.type == "s2sin" then - -- Stream either used dialback for authentication or is an incoming stream. - mark_connected(session); - end - - return true; -end - ---- Helper to check that a session peer's certificate is valid -function check_cert_status(session) - local host = session.direction == "outgoing" and session.to_host or session.from_host - local conn = session.conn:socket() - local cert - if conn.getpeercertificate then - cert = conn:getpeercertificate() - end - - return module:fire_event("s2s-check-certificate", { host = host, session = session, cert = cert }); -end - ---- XMPP stream event handlers - -local stream_callbacks = { default_ns = "jabber:server" }; - -function stream_callbacks.handlestanza(session, stanza) - stanza = session.filter("stanzas/in", stanza); - session.thread:run(stanza); -end - -local xmlns_xmpp_streams = "urn:ietf:params:xml:ns:xmpp-streams"; - -function stream_callbacks.streamopened(session, attr) - -- run _streamopened in async context - session.thread:run({ stream = "opened", attr = attr }); -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 - session.secure = true; - session.encrypted = true; - - local sock = session.conn:socket(); - if sock.info then - local info = sock:info(); - (session.log or log)("info", "Stream encrypted (%s with %s)", info.protocol, info.cipher); - session.compressed = info.compression; - else - (session.log or log)("info", "Stream encrypted"); - end - end - - if session.direction == "incoming" then - -- Send a reply stream header - - -- Validate to/from - local to, from = attr.to, attr.from; - if to then to = nameprep(attr.to); end - if from then from = nameprep(attr.from); end - if not to and attr.to then -- COMPAT: Some servers do not reliably set 'to' (especially on stream restarts) - session:close({ condition = "improper-addressing", text = "Invalid 'to' address" }); - return; - end - if not from and attr.from then -- COMPAT: Some servers do not reliably set 'from' (especially on stream restarts) - session:close({ condition = "improper-addressing", text = "Invalid 'from' address" }); - return; - end - - -- Set session.[from/to]_host if they have not been set already and if - -- this session isn't already authenticated - if session.type == "s2sin_unauthed" and from and not session.from_host then - session.from_host = from; - elseif from ~= session.from_host then - session:close({ condition = "improper-addressing", text = "New stream 'from' attribute does not match original" }); - return; - end - if session.type == "s2sin_unauthed" and to and not session.to_host then - session.to_host = to; - elseif to ~= session.to_host then - session:close({ condition = "improper-addressing", text = "New stream 'to' attribute does not match original" }); - return; - end - - -- For convenience we'll put the sanitised values into these variables - to, from = session.to_host, session.from_host; - - session.streamid = uuid_gen(); - (session.log or log)("debug", "Incoming s2s received %s", st.stanza("stream:stream", attr):top_tag()); - if to then - if not hosts[to] then - -- Attempting to connect to a host we don't serve - session:close({ - condition = "host-unknown"; - text = "This host does not serve "..to - }); - return; - elseif not hosts[to].modules.s2s then - -- Attempting to connect to a host that disallows s2s - session:close({ - condition = "policy-violation"; - text = "Server-to-server communication is disabled for this host"; - }); - return; - end - end - - if hosts[from] then - session:close({ condition = "undefined-condition", text = "Attempt to connect from a host we serve" }); - return; - end - - if session.secure and not session.cert_chain_status then - if check_cert_status(session) == false then - return; - end - end - - session:open_stream(session.to_host, session.from_host) - session.notopen = nil; - if session.version >= 1.0 then - local features = st.stanza("stream:features"); - - if to then - hosts[to].events.fire_event("s2s-stream-features", { origin = session, features = features }); - else - (session.log or log)("warn", "No 'to' on stream header from %s means we can't offer any features", from or session.ip or "unknown host"); - fire_global_event("s2s-stream-features-legacy", { origin = session, features = features }); - end - - if ( session.type == "s2sin" or session.type == "s2sout" ) or features.tags[1] then - log("debug", "Sending stream features: %s", features); - session.sends2s(features); - else - (session.log or log)("warn", "No stream features to offer, giving up"); - session:close({ condition = "undefined-condition", text = "No stream features to offer" }); - end - end - elseif session.direction == "outgoing" then - session.notopen = nil; - if not attr.id then - log("warn", "Stream response did not give us a stream id!"); - session:close({ condition = "undefined-condition", text = "Missing stream ID" }); - return; - end - session.streamid = attr.id; - - if session.secure and not session.cert_chain_status then - if check_cert_status(session) == false then - return; - end - end - - -- If server is pre-1.0, don't wait for features, just do dialback - if session.version < 1.0 then - if not session.dialback_verifying then - hosts[session.from_host].events.fire_event("s2sout-authenticate-legacy", { origin = session }); - else - mark_connected(session); - end - end - end -end - -function stream_callbacks._streamclosed(session) - (session.log or log)("debug", "Received "); - session:close(false); -end - -function stream_callbacks.streamclosed(session, attr) - -- run _streamclosed in async context - session.thread:run({ stream = "closed", attr = attr }); -end - -function stream_callbacks.error(session, error, data) - if error == "no-stream" then - session.log("debug", "Invalid opening stream header (%s)", (data:gsub("^([^\1]+)\1", "{%1}"))); - session:close("invalid-namespace"); - elseif error == "parse-error" then - session.log("debug", "Server-to-server XML parse error: %s", error); - session:close("not-well-formed"); - elseif error == "stream-error" then - local condition, text = "undefined-condition"; - for child in data:childtags(nil, xmlns_xmpp_streams) do - if child.name ~= "text" then - condition = child.name; - else - text = child:get_text(); - end - if condition ~= "undefined-condition" and text then - break; - end - end - text = condition .. (text and (" ("..text..")") or ""); - session.log("info", "Session closed by remote with error: %s", text); - session:close(nil, text); - end -end - ---- Session methods -local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'}; --- reason: stream error to send to the remote server --- remote_reason: stream error received from the remote server --- bounce_reason: stanza error to pass to bounce_sendq because stream- and stanza errors are different -local function session_close(session, reason, remote_reason, bounce_reason) - local log = session.log or log; - if session.conn then - if session.notopen then - if session.direction == "incoming" then - session:open_stream(session.to_host, session.from_host); - else - session:open_stream(session.from_host, session.to_host); - end - end - if reason then -- nil == no err, initiated by us, false == initiated by remote - local stream_error; - if type(reason) == "string" then -- assume stream error - stream_error = st.stanza("stream:error"):tag(reason, {xmlns = 'urn:ietf:params:xml:ns:xmpp-streams' }); - elseif type(reason) == "table" and not st.is_stanza(reason) then - stream_error = st.stanza("stream:error"):tag(reason.condition or "undefined-condition", stream_xmlns_attr):up(); - if reason.text then - stream_error:tag("text", stream_xmlns_attr):text(reason.text):up(); - end - if reason.extra then - stream_error:add_child(reason.extra); - end - end - if st.is_stanza(stream_error) then - -- to and from are never unknown on outgoing connections - log("debug", "Disconnecting %s->%s[%s], is: %s", - session.from_host or "(unknown host)" or session.ip, session.to_host or "(unknown host)", session.type, reason); - session.sends2s(stream_error); - end - end - - session.sends2s(""); - function session.sends2s() return false; end - - -- luacheck: ignore 422/reason - -- FIXME reason should be managed in a place common to c2s, s2s, bosh, component etc - local reason = remote_reason or (reason and (reason.text or reason.condition)) or reason; - session.log("info", "%s s2s stream %s->%s closed: %s", session.direction:gsub("^.", string.upper), - session.from_host or "(unknown host)", session.to_host or "(unknown host)", reason or "stream closed"); - - -- Authenticated incoming stream may still be sending us stanzas, so wait for from remote - local conn = session.conn; - if reason == nil and not session.notopen and session.incoming then - add_task(stream_close_timeout, function () - if not session.destroyed then - session.log("warn", "Failed to receive a stream close response, closing connection anyway..."); - s2s_destroy_session(session, reason, bounce_reason); - conn:close(); - end - end); - else - s2s_destroy_session(session, reason, bounce_reason); - conn:close(); -- Close immediately, as this is an outgoing connection or is not authed - end - end -end - -function session_stream_attrs(session, from, to, attr) -- luacheck: ignore 212/session - if not from or (hosts[from] and hosts[from].modules.dialback) then - attr["xmlns:db"] = 'jabber:server:dialback'; - end - if not from then - attr.from = ''; - end - if not to then - attr.to = ''; - end -end - --- Session initialization logic shared by incoming and outgoing -local function initialize_session(session) - local stream = new_xmpp_stream(session, stream_callbacks, stanza_size_limit); - - session.thread = runner(function (stanza) - if st.is_stanza(stanza) then - core_process_stanza(session, stanza); - elseif stanza.stream == "opened" then - stream_callbacks._streamopened(session, stanza.attr); - elseif stanza.stream == "closed" then - stream_callbacks._streamclosed(session, stanza.attr); - end - end, runner_callbacks, session); - - local log = session.log or log; - session.stream = stream; - - session.notopen = true; - - function session.reset_stream() - session.notopen = true; - session.streamid = nil; - session.stream:reset(); - end - - session.stream_attrs = session_stream_attrs; - - local filter = initialize_filters(session); - local conn = session.conn; - local w = conn.write; - - function session.sends2s(t) - log("debug", "Sending[%s]: %s", session.type, t.top_tag and t:top_tag() or t:match("^[^>]*>?")); - if t.name then - t = filter("stanzas/out", t); - end - if t then - t = filter("bytes/out", tostring(t)); - if t then - return w(conn, t); - end - end - end - - function session.data(data) - data = filter("bytes/in", data); - if data then - local ok, err = stream:feed(data); - if ok then return; end - log("debug", "Received invalid XML (%s) %d bytes: %q", err, #data, data:sub(1, 300)); - if err == "stanza-too-large" then - session:close({ condition = "policy-violation", text = "XML stanza is too big" }, nil, "Received invalid XML from remote server"); - else - session:close("not-well-formed", nil, "Received invalid XML from remote server"); - end - end - end - - session.close = session_close; - - local handlestanza = stream_callbacks.handlestanza; - function session.dispatch_stanza(session, stanza) -- luacheck: ignore 432/session - return handlestanza(session, stanza); - end - - module:fire_event("s2s-created", { session = session }); - - add_task(connect_timeout, function () - if session.type == "s2sin" or session.type == "s2sout" then - return; -- Ok, we're connected - elseif session.type == "s2s_destroyed" then - return; -- Session already destroyed - end - -- Not connected, need to close session and clean up - (session.log or log)("debug", "Destroying incomplete session %s->%s due to inactivity", - session.from_host or "(unknown)", session.to_host or "(unknown)"); - session:close("connection-timeout"); - end); -end - -function runner_callbacks:ready() - self.data.log("debug", "Runner %s ready (%s)", self.thread, coroutine.status(self.thread)); - self.data.conn:resume(); -end - -function runner_callbacks:waiting() - self.data.log("debug", "Runner %s waiting (%s)", self.thread, coroutine.status(self.thread)); - self.data.conn:pause(); -end - -function runner_callbacks:error(err) - (self.data.log or log)("error", "Traceback[s2s]: %s", err); -end - -function listener.onconnect(conn) - conn:setoption("keepalive", opt_keepalives); - local session = sessions[conn]; - if not session then -- New incoming connection - session = s2s_new_incoming(conn); - sessions[conn] = session; - session.log("debug", "Incoming s2s connection"); - initialize_session(session); - else -- Outgoing session connected - session:open_stream(session.from_host, session.to_host); - end - session.ip = conn:ip(); -end - -function listener.onincoming(conn, data) - local session = sessions[conn]; - if session then - session.data(data); - end -end - -function listener.onstatus(conn, status) - if status == "ssl-handshake-complete" then - local session = sessions[conn]; - if session and session.direction == "outgoing" then - session.log("debug", "Sending stream header..."); - session:open_stream(session.from_host, session.to_host); - end - end -end - -function listener.ondisconnect(conn, err) - local session = sessions[conn]; - if session then - sessions[conn] = nil; - (session.log or log)("debug", "s2s disconnected: %s->%s (%s)", session.from_host, session.to_host, err or "connection closed"); - if session.secure == false and err then - -- TODO util.error-ify this - err = "Error during negotiation of encrypted connection: "..err; - end - 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); - 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); - end -end - -function listener.onreadtimeout(conn) - local session = sessions[conn]; - if session then - local host = session.host or session.to_host; - return (hosts[host] or prosody).events.fire_event("s2s-read-timeout", { session = session }); - end -end - -function listener.register_outgoing(conn, session) - sessions[conn] = session; - initialize_session(session); -end - -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 - --- Complete the sentence "Your certificate " with what's wrong -local function friendly_cert_error(session) --> string - if session.cert_chain_status == "invalid" then - if session.cert_chain_errors then - local cert_errors = set.new(session.cert_chain_errors[1]); - if cert_errors:contains("certificate has expired") then - return "has expired"; - elseif cert_errors:contains("self signed certificate") then - return "is self-signed"; - end - end - return "is not trusted"; -- for some other reason - elseif session.cert_identity_status == "invalid" then - return "is not valid for this name"; - end - -- this should normally be unreachable except if no s2s auth module was loaded - return "could not be validated"; -end - -function check_auth_policy(event) - local host, session = event.host, event.session; - local must_secure = secure_auth; - - if not must_secure and secure_domains[host] then - must_secure = true; - elseif must_secure and insecure_domains[host] then - must_secure = false; - end - - if must_secure and (session.cert_chain_status ~= "valid" or session.cert_identity_status ~= "valid") then - local reason = friendly_cert_error(session); - session.log("warn", "Forbidding insecure connection to/from %s because its certificate %s", host or session.ip or "(unknown host)", reason); - -- XEP-0178 recommends closing outgoing connections without warning - -- but does not give a rationale for this. - -- In practice most cases are configuration mistakes or forgotten - -- certificate renewals. We think it's better to let the other party - -- know about the problem so that they can fix it. - session:close({ condition = "not-authorized", text = "Your server's certificate "..reason }, - nil, "Remote server's certificate "..reason); - return false; - end -end - -module:hook("s2s-check-certificate", check_auth_policy, -1); - -module:hook("server-stopping", function(event) - local reason = event.reason; - for _, session in pairs(sessions) do - session:close{ condition = "system-shutdown", text = reason }; - end -end, -200); - - - -module:provides("net", { - name = "s2s"; - listener = listener; - default_port = 5269; - encryption = "starttls"; - ssl_config = { -- FIXME This is not used atm, see mod_tls - verify = { "peer", "client_once", }; - }; - multiplex = { - protocol = "xmpp-server"; - pattern = "^<.*:stream.*%sxmlns%s*=%s*(['\"])jabber:server%1.*>"; - }; -}); - -- cgit v1.2.3 From 0a200a4ce312d7953f7a5d124e7debcdd3cae82c Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 3 Jun 2020 19:00:02 +0200 Subject: makefile: Remove installation of mod_s2s dir Already removed from the GNUmakeflie. --- makefile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/makefile b/makefile index 0b1c8788..18d24312 100644 --- a/makefile +++ b/makefile @@ -48,9 +48,8 @@ install: prosody.install prosodyctl.install prosody.cfg.lua.install util/encodin $(INSTALL_DATA) util/*.so $(SOURCE)/util $(MKDIR) $(SOURCE)/util/sasl $(INSTALL_DATA) util/sasl/*.lua $(SOURCE)/util/sasl - $(MKDIR) $(MODULES)/mod_s2s $(MODULES)/mod_pubsub $(MODULES)/adhoc $(MODULES)/muc $(MODULES)/mod_mam + $(MKDIR) $(MODULES)/mod_pubsub $(MODULES)/adhoc $(MODULES)/muc $(MODULES)/mod_mam $(INSTALL_DATA) plugins/*.lua $(MODULES) - $(INSTALL_DATA) plugins/mod_s2s/*.lua $(MODULES)/mod_s2s $(INSTALL_DATA) plugins/mod_pubsub/*.lua $(MODULES)/mod_pubsub $(INSTALL_DATA) plugins/adhoc/*.lua $(MODULES)/adhoc $(INSTALL_DATA) plugins/muc/*.lua $(MODULES)/muc -- cgit v1.2.3 From 5fc5fe64ee3bf260981a652f2abece5004899548 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 3 Jun 2020 19:01:46 +0200 Subject: GNUmakefile: Install util.human.* --- GNUmakefile | 2 ++ makefile | 2 ++ 2 files changed, 4 insertions(+) diff --git a/GNUmakefile b/GNUmakefile index 950690e6..00c73c54 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -49,6 +49,8 @@ install: prosody.install prosodyctl.install prosody.cfg.lua.install util/encodin $(INSTALL_DATA) util/*.so $(SOURCE)/util $(MKDIR) $(SOURCE)/util/sasl $(INSTALL_DATA) util/sasl/*.lua $(SOURCE)/util/sasl + $(MKDIR) $(SOURCE)/util/human + $(INSTALL_DATA) util/human/*.lua $(SOURCE)/util/human $(MKDIR) $(MODULES)/mod_pubsub $(MODULES)/adhoc $(MODULES)/muc $(MODULES)/mod_mam $(INSTALL_DATA) plugins/*.lua $(MODULES) $(INSTALL_DATA) plugins/mod_pubsub/*.lua $(MODULES)/mod_pubsub diff --git a/makefile b/makefile index 18d24312..a0e13f25 100644 --- a/makefile +++ b/makefile @@ -48,6 +48,8 @@ install: prosody.install prosodyctl.install prosody.cfg.lua.install util/encodin $(INSTALL_DATA) util/*.so $(SOURCE)/util $(MKDIR) $(SOURCE)/util/sasl $(INSTALL_DATA) util/sasl/*.lua $(SOURCE)/util/sasl + $(MKDIR) $(SOURCE)/util/human + $(INSTALL_DATA) util/human/*.lua $(SOURCE)/util/human $(MKDIR) $(MODULES)/mod_pubsub $(MODULES)/adhoc $(MODULES)/muc $(MODULES)/mod_mam $(INSTALL_DATA) plugins/*.lua $(MODULES) $(INSTALL_DATA) plugins/mod_pubsub/*.lua $(MODULES)/mod_pubsub -- cgit v1.2.3 From 1deae0e925edc5b83e1ca509be2d72bc1d686130 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 3 Jun 2020 19:10:42 +0200 Subject: GNUmakefile: Install the new util/prosodyctl/* too (thanks pascal.pascher) --- GNUmakefile | 2 ++ makefile | 2 ++ 2 files changed, 4 insertions(+) diff --git a/GNUmakefile b/GNUmakefile index 00c73c54..460f61ca 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -51,6 +51,8 @@ install: prosody.install prosodyctl.install prosody.cfg.lua.install util/encodin $(INSTALL_DATA) util/sasl/*.lua $(SOURCE)/util/sasl $(MKDIR) $(SOURCE)/util/human $(INSTALL_DATA) util/human/*.lua $(SOURCE)/util/human + $(MKDIR) $(SOURCE)/util/prosodyctl + $(INSTALL_DATA) util/prosodyctl/*.lua $(SOURCE)/util/prosodyctl $(MKDIR) $(MODULES)/mod_pubsub $(MODULES)/adhoc $(MODULES)/muc $(MODULES)/mod_mam $(INSTALL_DATA) plugins/*.lua $(MODULES) $(INSTALL_DATA) plugins/mod_pubsub/*.lua $(MODULES)/mod_pubsub diff --git a/makefile b/makefile index a0e13f25..c0aabb81 100644 --- a/makefile +++ b/makefile @@ -50,6 +50,8 @@ install: prosody.install prosodyctl.install prosody.cfg.lua.install util/encodin $(INSTALL_DATA) util/sasl/*.lua $(SOURCE)/util/sasl $(MKDIR) $(SOURCE)/util/human $(INSTALL_DATA) util/human/*.lua $(SOURCE)/util/human + $(MKDIR) $(SOURCE)/util/prosodyctl + $(INSTALL_DATA) util/prosodyctl/*.lua $(SOURCE)/util/prosodyctl $(MKDIR) $(MODULES)/mod_pubsub $(MODULES)/adhoc $(MODULES)/muc $(MODULES)/mod_mam $(INSTALL_DATA) plugins/*.lua $(MODULES) $(INSTALL_DATA) plugins/mod_pubsub/*.lua $(MODULES)/mod_pubsub -- cgit v1.2.3 From f10b1ff16550191f7b2be04073bec6fccfbf741b Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 4 Jan 2019 06:56:45 +0100 Subject: util.statistics: Unify API of methods to include a config table The primary goal here is to allow specifying an unit that each statistic is measured in. --- util/statistics.lua | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/util/statistics.lua b/util/statistics.lua index 0ec88e21..db608217 100644 --- a/util/statistics.lua +++ b/util/statistics.lua @@ -44,19 +44,23 @@ local function new_registry(config) local registry = {}; local methods; methods = { - amount = function (name, initial) - local v = initial or 0; - registry[name..":amount"] = function () return "amount", v; end + amount = function (name, conf) + local v = conf and conf.initial or 0; + registry[name..":amount"] = function () + return "amount", v, conf; + end return function (new_v) v = new_v; end end; - counter = function (name, initial) - local v = initial or 0; - registry[name..":amount"] = function () return "amount", v; end + counter = function (name, conf) + local v = conf and conf.initial or 0; + registry[name..":amount"] = function () + return "amount", v, conf; + end return function (delta) v = v + delta; end; end; - rate = function (name) + rate = function (name, conf) local since, n, total = time(), 0, 0; registry[name..":rate"] = function () total = total + n; @@ -65,6 +69,8 @@ local function new_registry(config) rate = n/(t-since); count = n; total = total; + units = conf and conf.units; + type = conf and conf.type; }; since, n = t, 0; return "rate", stats.rate, stats; @@ -73,15 +79,16 @@ local function new_registry(config) n = n + 1; end; end; - distribution = function (name, unit, type) - type = type or "distribution"; + distribution = function (name, conf) + local units = conf and conf.units; + local type = conf and conf.type or "distribution"; local events, last_event = {}, 0; local n_actual_events = 0; local since = time(); registry[name..":"..type] = function () local new_time = time(); - local stats = get_distribution_stats(events, n_actual_events, since, new_time, unit); + local stats = get_distribution_stats(events, n_actual_events, since, new_time, units); events, last_event = {}, 0; n_actual_events = 0; since = new_time; @@ -96,17 +103,19 @@ local function new_registry(config) end end; end; - sizes = function (name) - return methods.distribution(name, "bytes", "size"); + sizes = function (name, conf) + conf = conf or { units = "bytes", type = "size" } + return methods.distribution(name, conf); end; - times = function (name) + times = function (name, conf) + local units = conf and conf.units or "seconds"; local events, last_event = {}, 0; local n_actual_events = 0; local since = time(); registry[name..":duration"] = function () local new_time = time(); - local stats = get_distribution_stats(events, n_actual_events, since, new_time, "seconds"); + local stats = get_distribution_stats(events, n_actual_events, since, new_time, units); events, last_event = {}, 0; n_actual_events = 0; since = new_time; -- cgit v1.2.3 From 401c48015c334a4664f369824f61f0c1a30d536b Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 4 Jan 2019 07:00:44 +0100 Subject: core.statsmanager: Allow passing a config table trough measure --- core/statsmanager.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/statsmanager.lua b/core/statsmanager.lua index d3f1d4f5..2297c959 100644 --- a/core/statsmanager.lua +++ b/core/statsmanager.lua @@ -60,9 +60,9 @@ local changed_stats = {}; local stats_extra = {}; if stats then - function measure(type, name) + function measure(type, name, conf) local f = assert(stats[type], "unknown stat type: "..type); - return f(name); + return f(name, conf); end if stats_interval then -- cgit v1.2.3 From 1f80e42aa5522074c5fa09333307a1c3ce50dc58 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 4 Jan 2019 07:00:45 +0100 Subject: core.moduleapi: Allow passing a config table trough :measure --- core/moduleapi.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/moduleapi.lua b/core/moduleapi.lua index 021db4c8..1212db5a 100644 --- a/core/moduleapi.lua +++ b/core/moduleapi.lua @@ -508,9 +508,9 @@ function api:open_store(name, store_type) return require"core.storagemanager".open(self.host, name or self.name, store_type); end -function api:measure(name, stat_type) +function api:measure(name, stat_type, conf) local measure = require "core.statsmanager".measure; - return measure(stat_type, "/"..self.host.."/mod_"..self.name.."/"..name); + return measure(stat_type, "/"..self.host.."/mod_"..self.name.."/"..name, conf); end function api:measure_object_event(events_object, event_name, stat_name) -- cgit v1.2.3 From 994f59501b9eecf736792d256434ceb7d519adc7 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 4 Jan 2019 08:46:26 +0100 Subject: util.human.units: A library for formatting numbers with SI units --- spec/util_human_units_spec.lua | 15 +++++++++++ util/human/units.lua | 58 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 spec/util_human_units_spec.lua create mode 100644 util/human/units.lua diff --git a/spec/util_human_units_spec.lua b/spec/util_human_units_spec.lua new file mode 100644 index 00000000..4326cdd4 --- /dev/null +++ b/spec/util_human_units_spec.lua @@ -0,0 +1,15 @@ +local units = require "util.human.units"; + +describe("util.human.units", function () + describe("format", function () + it("formats numbers with SI units", function () + assert.equal("1 km", units.format(1000, "m")); + assert.equal("1 GJ", units.format(1000000000, "J")); + assert.equal("1 ms", units.format(1/1000, "s")); + assert.equal("10 ms", units.format(10/1000, "s")); + assert.equal("1 ns", units.format(1/1000000000, "s")); + assert.equal("1 KiB", units.format(1024, "B", 'b')); + assert.equal("1 MiB", units.format(1024*1024, "B", 'b')); + end); + end); +end); diff --git a/util/human/units.lua b/util/human/units.lua new file mode 100644 index 00000000..2c4662cd --- /dev/null +++ b/util/human/units.lua @@ -0,0 +1,58 @@ +local large = { + "k", 1000, + "M", 1000000, + "G", 1000000000, + "T", 1000000000000, + "P", 1000000000000000, + "E", 1000000000000000000, + "Z", 1000000000000000000000, + "Y", 1000000000000000000000000, +} +local small = { + "m", 0.001, + "μ", 0.000001, + "n", 0.000000001, + "p", 0.000000000001, + "f", 0.000000000000001, + "a", 0.000000000000000001, + "z", 0.000000000000000000001, + "y", 0.000000000000000000000001, +} + +local binary = { + "Ki", 2^10, + "Mi", 2^20, + "Gi", 2^30, + "Ti", 2^40, + "Pi", 2^50, + "Ei", 2^60, + "Zi", 2^70, + "Yi", 2^80, +} + +-- n: number, the number to format +-- unit: string, the base unit +-- b: optional enum 'b', thousands base +local function format(n, unit, b) --> string + local round = math.floor; + local prefixes = large; + local logbase = 1000; + local fmt = "%.3g %s%s"; + if n == 0 then + return fmt:format(n, "", unit); + end + if b == 'b' then + prefixes = binary; + logbase = 1024; + elseif n < 1 then + prefixes = small; + round = math.ceil; + end + local m = math.max(0, math.min(8, round(math.abs(math.log(math.abs(n), logbase))))); + local prefix, multiplier = table.unpack(prefixes, m * 2-1, m*2); + return fmt:format(n / (multiplier or 1), prefix or "", unit); +end + +return { + format = format; +}; -- cgit v1.2.3 From 509549f679bc6772bd36984d0f5ca056018d974d Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 3 Jun 2020 19:27:44 +0200 Subject: mod_admin_shell: Format stats with util.human.units --- plugins/mod_admin_shell.lua | 72 ++++++++++++++++++++++----------------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/plugins/mod_admin_shell.lua b/plugins/mod_admin_shell.lua index fd88f59c..afbd2922 100644 --- a/plugins/mod_admin_shell.lua +++ b/plugins/mod_admin_shell.lua @@ -36,6 +36,8 @@ local serialization = require "util.serialization"; local serialize_config = serialization.new ({ fatal = false, unquoted = true}); local time = require "util.time"; +local format_number = require "util.human.units".format; + local commands = module:shared("commands") local def_env = module:shared("env"); local default_env_mt = { __index = def_env }; @@ -319,11 +321,7 @@ function def_env.server:shutdown(reason) end local function human(kb) - local unit = "K"; - if kb > 1024 then - kb, unit = kb/1024, "M"; - end - return ("%0.2f%sB"):format(kb, unit); + return format_number(kb*1024, "B", "b"); end function def_env.server:memory() @@ -1304,30 +1302,32 @@ def_env.timer = { info = def_env.debug.timers }; def_env.stats = {}; -local function format_stat(type, value, ref_value) +local short_units = { + seconds = "s", + bytes = "B", +}; + +local function format_stat(type, unit, value, ref_value) ref_value = ref_value or value; --do return tostring(value) end - if type == "duration" then - if ref_value < 0.001 then - return ("%g µs"):format(value*1000000); - elseif ref_value < 0.9 then - return ("%0.2f ms"):format(value*1000); - end - return ("%0.2f"):format(value); - elseif type == "size" then - if ref_value > 1048576 then - return ("%d MB"):format(value/1048576); - elseif ref_value > 1024 then - return ("%d KB"):format(value/1024); - end - return ("%d bytes"):format(value); - elseif type == "rate" then - if ref_value < 0.9 then - return ("%0.2f/min"):format(value*60); + if not unit then + if type == "duration" then + unit = "seconds" + elseif type == "size" then + unit = "bytes"; + elseif type == "rate" then + unit = " events/sec" + if ref_value < 0.9 then + unit = " events/min" + value = value*60; + if ref_value < 0.6/60 then + unit = " events/h" + value = value*60; + end + end end - return ("%0.2f/sec"):format(value); end - return tostring(value); + return format_number(value, short_units[unit] or unit or "", unit == "bytes" and 'b' or nil); end local stats_methods = {}; @@ -1412,14 +1412,14 @@ function stats_methods:summary() data.sample_count )); table.insert(stat_info.output, string.format("Min: %s Mean: %s Max: %s", - format_stat(type, data.min), - format_stat(type, value), - format_stat(type, data.max) + format_stat(type, data.units, data.min), + format_stat(type, data.units, value), + format_stat(type, data.units, data.max) )); table.insert(stat_info.output, string.format("Q1: %s Median: %s Q3: %s", - format_stat(type, statistics.get_percentile(data, 25)), - format_stat(type, statistics.get_percentile(data, 50)), - format_stat(type, statistics.get_percentile(data, 75)) + format_stat(type, data.units, statistics.get_percentile(data, 25)), + format_stat(type, data.units, statistics.get_percentile(data, 50)), + format_stat(type, data.units, statistics.get_percentile(data, 75)) )); end end @@ -1449,7 +1449,7 @@ function stats_methods:cfgraph() end print(""); - print(("_"):rep(52)..format_stat(type, data.max)); + print(("_"):rep(52)..format_stat(type, data.units, data.max)); for row = graph_height, 1, -1 do local row_chars = {}; local min_eighths, max_eighths = 8, 0; @@ -1468,7 +1468,7 @@ function stats_methods:cfgraph() row_chars[i] = char; end end - print(table.concat(row_chars).."|-"..format_stat(type, data.max/(graph_height/(row-0.5)))); + print(table.concat(row_chars).."|-"..format_stat(type, data.units, data.max/(graph_height/(row-0.5)))); end print(("\\ "):rep(11)); local x_labels = {}; @@ -1553,14 +1553,14 @@ function stats_methods:histogram() print(("\\ "):rep(11)); local x_labels = {}; for i = 1, 11 do - local s = ("%-4s"):format(format_stat(type, data.min+range*i/11, data.min):match("^%S+")); + local s = ("%-4s"):format(format_stat(type, data.units, data.min+range*i/11, data.min):match("^%S+")); if #s > 4 then s = s:sub(1, 3).."…"; end x_labels[i] = s; end print(" "..table.concat(x_labels, " ")); - local units = format_stat(type, data.min):match("%s+(.+)$") or data.units or ""; + local units = format_stat(type, data.units, data.min):match("%s+(.+)$") or data.units or ""; local margin = math.floor((graph_width-#units)/2); print((" "):rep(margin)..units); else @@ -1582,7 +1582,7 @@ local function stats_tostring(stats) end print(""); else - print(("%-50s %s"):format(stat_info[1], format_stat(stat_info[2], stat_info[3]))); + print(("%-50s %s"):format(stat_info[1], format_stat(stat_info[2], (stat_info[4] or {}).unit, stat_info[3]))); end end return #stats.." statistics displayed"; -- cgit v1.2.3 From b95b0e602662861b8db5524d5e5971a927fb076f Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 3 Jun 2020 19:46:17 +0200 Subject: util.human.units: Handle location of unpack() in Lua 5.1 --- util/human/units.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/util/human/units.lua b/util/human/units.lua index 2c4662cd..91d6f0d5 100644 --- a/util/human/units.lua +++ b/util/human/units.lua @@ -1,3 +1,5 @@ +local unpack = table.unpack or unpack; --luacheck: ignore 113 + local large = { "k", 1000, "M", 1000000, @@ -49,7 +51,7 @@ local function format(n, unit, b) --> string round = math.ceil; end local m = math.max(0, math.min(8, round(math.abs(math.log(math.abs(n), logbase))))); - local prefix, multiplier = table.unpack(prefixes, m * 2-1, m*2); + local prefix, multiplier = unpack(prefixes, m * 2-1, m*2); return fmt:format(n / (multiplier or 1), prefix or "", unit); end -- cgit v1.2.3 From b73cad7f8e92862a77d03ecc3eb9e24bb389cc07 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 3 Jun 2020 20:16:00 +0200 Subject: util.human.units: Put math functions into locals Primarily because the next commit will deal with math.log behaving differently on Lua 5.1 and that's eaiser with locals. --- util/human/units.lua | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/util/human/units.lua b/util/human/units.lua index 91d6f0d5..471c82a0 100644 --- a/util/human/units.lua +++ b/util/human/units.lua @@ -1,3 +1,9 @@ +local math_abs = math.abs; +local math_ceil = math.ceil; +local math_floor = math.floor; +local math_log = math.log; +local math_max = math.max; +local math_min = math.min; local unpack = table.unpack or unpack; --luacheck: ignore 113 local large = { @@ -36,7 +42,7 @@ local binary = { -- unit: string, the base unit -- b: optional enum 'b', thousands base local function format(n, unit, b) --> string - local round = math.floor; + local round = math_floor; local prefixes = large; local logbase = 1000; local fmt = "%.3g %s%s"; @@ -48,9 +54,9 @@ local function format(n, unit, b) --> string logbase = 1024; elseif n < 1 then prefixes = small; - round = math.ceil; + round = math_ceil; end - local m = math.max(0, math.min(8, round(math.abs(math.log(math.abs(n), logbase))))); + local m = math_max(0, math_min(8, round(math_abs(math_log(math_abs(n), logbase))))); local prefix, multiplier = unpack(prefixes, m * 2-1, m*2); return fmt:format(n / (multiplier or 1), prefix or "", unit); end -- cgit v1.2.3 From b2ad87f966f17bacb36366f9e3bf21c21c31b6f7 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 3 Jun 2020 20:17:33 +0200 Subject: util.human.units: Handle lack of math.log(n, base) on Lua 5.1 --- util/human/units.lua | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/util/human/units.lua b/util/human/units.lua index 471c82a0..5a083783 100644 --- a/util/human/units.lua +++ b/util/human/units.lua @@ -6,6 +6,14 @@ local math_max = math.max; local math_min = math.min; local unpack = table.unpack or unpack; --luacheck: ignore 113 +if math_log(10, 10) ~= 1 then + -- Lua 5.1 COMPAT + local log10 = math.log10; + function math_log(n, base) + return log10(n) / log10(base); + end +end + local large = { "k", 1000, "M", 1000000, -- cgit v1.2.3 From baf9f5aeefeb2900af1d3026799ec382f4f69d5b Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Wed, 3 Jun 2020 22:21:17 +0100 Subject: util.human.io: Add padleft, padright and a table printing function --- util/human/io.lua | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/util/human/io.lua b/util/human/io.lua index 4c84c4a4..338509b1 100644 --- a/util/human/io.lua +++ b/util/human/io.lua @@ -85,6 +85,56 @@ local function printf(fmt, ...) print(fmt:format(...)); end +local function padright(s, width) + return s..string.rep(" ", width-#s); +end + +local function padleft(s, width) + return string.rep(" ", width-#s)..s; +end + +local function table(col_specs, max_width, padding) + max_width = max_width or 80; + padding = padding or 4; + + local widths = {}; + local total_width = max_width - padding; + local free_width = total_width; + -- Calculate width of fixed-size columns + for i = 1, #col_specs do + local width = col_specs[i].width or "0"; + if not(type(width) == "string" and width:sub(-1) == "%") then + local title = col_specs[i].title; + width = math.max(tonumber(width), title and (#title+1) or 0); + widths[i] = width; + free_width = free_width - width; + end + end + -- Calculate width of %-based columns + for i = 1, #col_specs do + if not widths[i] then + local pc_width = tonumber((col_specs[i].width:gsub("%%$", ""))); + widths[i] = math.floor(free_width*(pc_width/100)); + end + end + + return function (row, f) + for i, column in ipairs(col_specs) do + local width = widths[i]; + local v = tostring(row[column.key or i] or ""):sub(1, width); + if #v < width then + if column.align == "right" then + v = padleft(v, width-1).." "; + else + v = padright(v, width); + end + end + (f or io.stdout):write(v); + end + (f or io.stdout):write("\n"); + end; +end + return { getchar = getchar; getline = getline; @@ -93,4 +143,7 @@ return { read_password = read_password; show_prompt = show_prompt; printf = printf; + padleft = padleft; + padright = padright; + table = table; }; -- cgit v1.2.3 From 56c159d7c947f3bb71b33a4ff3e4f1fba8ebd30a Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Wed, 3 Jun 2020 22:26:48 +0100 Subject: util.adminstream: Fire event based on stanza name too for convenience --- util/adminstream.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/util/adminstream.lua b/util/adminstream.lua index 4583b322..70fa2b19 100644 --- a/util/adminstream.lua +++ b/util/adminstream.lua @@ -271,7 +271,9 @@ local function new_client() client.thread = runner(function (stanza) if st.is_stanza(stanza) then - client.events.fire_event("received", stanza); + if not client.events.fire_event("received", stanza) and not stanza.attr.xmlns then + client.events.fire_event("received/"..stanza.name, stanza); + end elseif stanza.stream == "opened" then stream_callbacks._streamopened(client, stanza.attr); client.events.fire_event("connected"); -- cgit v1.2.3 From 8467f4f9eaeeaf1c70c8ee958eb9eb9757c8fb64 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Wed, 3 Jun 2020 22:45:33 +0100 Subject: util.human.io: table: switch row function to simply returning prepared row string --- util/human/io.lua | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/util/human/io.lua b/util/human/io.lua index 338509b1..dfed3b09 100644 --- a/util/human/io.lua +++ b/util/human/io.lua @@ -93,7 +93,7 @@ local function padleft(s, width) return string.rep(" ", width-#s)..s; end -local function table(col_specs, max_width, padding) +local function new_table(col_specs, max_width, padding) max_width = max_width or 80; padding = padding or 4; @@ -118,7 +118,8 @@ local function table(col_specs, max_width, padding) end end - return function (row, f) + return function (row) + local output = {}; for i, column in ipairs(col_specs) do local width = widths[i]; local v = tostring(row[column.key or i] or ""):sub(1, width); @@ -129,9 +130,9 @@ local function table(col_specs, max_width, padding) v = padright(v, width); end end - (f or io.stdout):write(v); + table.insert(output, v); end - (f or io.stdout):write("\n"); + return table.concat(output); end; end @@ -145,5 +146,5 @@ return { printf = printf; padleft = padleft; padright = padright; - table = table; + table = new_table; }; -- cgit v1.2.3 From 7b7084ad68dd4c30998a4e5d79ac0e5d04fb08d6 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Wed, 3 Jun 2020 22:58:29 +0100 Subject: util.human.io: table: Return title row when no row data passed --- util/human/io.lua | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/util/human/io.lua b/util/human/io.lua index dfed3b09..8c328c14 100644 --- a/util/human/io.lua +++ b/util/human/io.lua @@ -1,3 +1,5 @@ +local array = require "util.array"; + local function getchar(n) local stty_ret = os.execute("stty raw -echo 2>/dev/null"); local ok, char; @@ -119,6 +121,9 @@ local function new_table(col_specs, max_width, padding) end return function (row) + if not row then + row = array.pluck(col_specs, "title"); + end local output = {}; for i, column in ipairs(col_specs) do local width = widths[i]; -- cgit v1.2.3 From b4ec6112e4bc4003d3257ccaded21a5c73599db1 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Thu, 4 Jun 2020 10:39:12 +0100 Subject: util.array: pluck: Support default value to avoid holes --- util/array.lua | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/util/array.lua b/util/array.lua index 32d2d6a5..6e5c8383 100644 --- a/util/array.lua +++ b/util/array.lua @@ -134,9 +134,13 @@ function array_base.unique(outa, ina) end); end -function array_base.pluck(outa, ina, key) +function array_base.pluck(outa, ina, key, default) for i = 1, #ina do - outa[i] = ina[i][key]; + local v = ina[i][key]; + if v == nil then + v = default; + end + outa[i] = v; end return outa; end -- cgit v1.2.3 From 19b7e38e1c80c4f9a8623877d36bbe4c5251c696 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Thu, 4 Jun 2020 10:39:55 +0100 Subject: util.human.io: table: Fix title printing when columns use named keys --- util/human/io.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/util/human/io.lua b/util/human/io.lua index 8c328c14..9e700e89 100644 --- a/util/human/io.lua +++ b/util/human/io.lua @@ -121,13 +121,14 @@ local function new_table(col_specs, max_width, padding) end return function (row) + local titles; if not row then - row = array.pluck(col_specs, "title"); + titles, row = true, array.pluck(col_specs, "title", ""); end local output = {}; for i, column in ipairs(col_specs) do local width = widths[i]; - local v = tostring(row[column.key or i] or ""):sub(1, width); + local v = tostring(row[not titles and column.key or i] or ""):sub(1, width); if #v < width then if column.align == "right" then v = padleft(v, width-1).." "; -- cgit v1.2.3 From 838f8ebd5bb26c2c96644af9e4f80ea7e150d976 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 29 May 2020 17:53:00 +0200 Subject: util.ringbuffer: Add some initial tests --- spec/util_ringbuffer_spec.lua | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 spec/util_ringbuffer_spec.lua diff --git a/spec/util_ringbuffer_spec.lua b/spec/util_ringbuffer_spec.lua new file mode 100644 index 00000000..5d63b24b --- /dev/null +++ b/spec/util_ringbuffer_spec.lua @@ -0,0 +1,17 @@ +local rb = require "util.ringbuffer"; +describe("util.ringbuffer", function () + describe("#new", function () + it("has a constructor", function () + assert.Function(rb.new); + end); + it("can be created", function () + assert.truthy(rb.new()); + end); + end); + describe(":write", function () + local b = rb.new(); + it("works", function () + assert.truthy(b:write("hi")); + end); + end); +end); -- cgit v1.2.3 From 0bb1474ce668f50c568e7751ac553cbf0acdba77 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 29 May 2020 18:11:42 +0200 Subject: util.ringbuffer: Prevent creation of zero-size buffer --- spec/util_ringbuffer_spec.lua | 5 +++++ util-src/ringbuffer.c | 1 + 2 files changed, 6 insertions(+) diff --git a/spec/util_ringbuffer_spec.lua b/spec/util_ringbuffer_spec.lua index 5d63b24b..ccb5493a 100644 --- a/spec/util_ringbuffer_spec.lua +++ b/spec/util_ringbuffer_spec.lua @@ -7,6 +7,11 @@ describe("util.ringbuffer", function () it("can be created", function () assert.truthy(rb.new()); end); + it("won't create an empty buffer", function () + assert.has_error(function () + rb.new(0); + end); + end); end); describe(":write", function () local b = rb.new(); diff --git a/util-src/ringbuffer.c b/util-src/ringbuffer.c index 3e17cdf5..f4a51cc9 100644 --- a/util-src/ringbuffer.c +++ b/util-src/ringbuffer.c @@ -198,6 +198,7 @@ static int rb_free(lua_State *L) { static int rb_new(lua_State *L) { size_t size = luaL_optinteger(L, 1, sysconf(_SC_PAGESIZE)); + luaL_argcheck(L, size > 0, 1, "positive integer expected"); ringbuffer *b = lua_newuserdata(L, sizeof(ringbuffer) + size); b->rpos = 0; -- cgit v1.2.3 From 4bab7af07d4707eda9246156e20610d95967679c Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 4 Jun 2020 16:11:08 +0200 Subject: util.ringbuffer: Prevent creation of buffer with negative size Previously this would have been (unsigned)-1 which is a large positive integer. --- spec/util_ringbuffer_spec.lua | 5 +++++ util-src/ringbuffer.c | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/spec/util_ringbuffer_spec.lua b/spec/util_ringbuffer_spec.lua index ccb5493a..9512bfd4 100644 --- a/spec/util_ringbuffer_spec.lua +++ b/spec/util_ringbuffer_spec.lua @@ -12,6 +12,11 @@ describe("util.ringbuffer", function () rb.new(0); end); end); + it("won't create a negatively sized buffer", function () + assert.has_error(function () + rb.new(-1); + end); + end); end); describe(":write", function () local b = rb.new(); diff --git a/util-src/ringbuffer.c b/util-src/ringbuffer.c index f4a51cc9..e15b66a6 100644 --- a/util-src/ringbuffer.c +++ b/util-src/ringbuffer.c @@ -197,7 +197,7 @@ static int rb_free(lua_State *L) { } static int rb_new(lua_State *L) { - size_t size = luaL_optinteger(L, 1, sysconf(_SC_PAGESIZE)); + lua_Integer size = luaL_optinteger(L, 1, sysconf(_SC_PAGESIZE)); luaL_argcheck(L, size > 0, 1, "positive integer expected"); ringbuffer *b = lua_newuserdata(L, sizeof(ringbuffer) + size); -- cgit v1.2.3 From a6c4ce73ef53b432b88950450bf479938807e0ae Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 4 Jun 2020 16:17:14 +0200 Subject: util.adminstream: Set a read timeout handler So that net.server doesn't close the connection on inactivity. --- util/adminstream.lua | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/util/adminstream.lua b/util/adminstream.lua index 70fa2b19..782a6b0f 100644 --- a/util/adminstream.lua +++ b/util/adminstream.lua @@ -247,6 +247,11 @@ local function new_server(sessions, stanza_handler) sessions[conn] = nil; end end + + function listeners.onreadtimeout(conn) + conn:send(" "); + end + return { listeners = listeners; }; @@ -315,6 +320,10 @@ local function new_client() client.conn = nil; end + function listeners.onreadtimeout(conn) + conn:send(" "); + end + client.listeners = listeners; return client; -- cgit v1.2.3 From 14a436817d5faaa1942abb6263a43ca3847f5c5c Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Thu, 4 Jun 2020 15:19:20 +0100 Subject: util.ringbuffer: Add :sub() and :byte() methods equivalent to the string methods --- spec/util_ringbuffer_spec.lua | 58 ++++++++++++++++++++++++ util-src/ringbuffer.c | 103 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 160 insertions(+), 1 deletion(-) diff --git a/spec/util_ringbuffer_spec.lua b/spec/util_ringbuffer_spec.lua index 9512bfd4..72656899 100644 --- a/spec/util_ringbuffer_spec.lua +++ b/spec/util_ringbuffer_spec.lua @@ -24,4 +24,62 @@ describe("util.ringbuffer", function () assert.truthy(b:write("hi")); end); end); + describe(":sub", function () + -- Helper function to compare buffer:sub() with string:sub() + local function test_sub(b, x, y) + local s = b:read(#b, true); + local string_result, buffer_result = s:sub(x, y), b:sub(x, y); + assert.equals(string_result, buffer_result, ("buffer:sub(%d, %s) does not match string:sub()"):format(x, y and ("%d"):format(y) or "nil")); + end + + it("works", function () + local b = rb.new(); + b:write("hello world"); + assert.equals("hello", b:sub(1, 5)); + end); + + it("supports optional end parameter", function () + local b = rb.new(); + b:write("hello world"); + assert.equals("hello world", b:sub(1)); + assert.equals("world", b:sub(-5)); + end); + + it("is equivalent to string:sub", function () + local b = rb.new(6); + b:write("foobar"); + b:read(3); + b:write("foo"); + for i = -13, 13 do + for j = -13, 13 do + test_sub(b, i, j); + end + end + end); + end); + + describe(":byte", function () + -- Helper function to compare buffer:byte() with string:byte() + local function test_byte(b, x, y) + local s = b:read(#b, true); + local string_result, buffer_result = {s:byte(x, y)}, {b:byte(x, y)}; + assert.same(string_result, buffer_result, ("buffer:byte(%d, %s) does not match string:byte()"):format(x, y and ("%d"):format(y) or "nil")); + end + + it("is equivalent to string:byte", function () + local b = rb.new(6); + b:write("foobar"); + b:read(3); + b:write("foo"); + test_byte(b, 1); + test_byte(b, 3); + test_byte(b, -1); + test_byte(b, -3); + for i = -13, 13 do + for j = -13, 13 do + test_byte(b, i, j); + end + end + end); + end); end); diff --git a/util-src/ringbuffer.c b/util-src/ringbuffer.c index e15b66a6..07e1096f 100644 --- a/util-src/ringbuffer.c +++ b/util-src/ringbuffer.c @@ -2,7 +2,6 @@ #include #include #include -#include #include #include @@ -15,6 +14,55 @@ typedef struct { char buffer[]; } ringbuffer; +/* Translate absolute idx to a wrapped index within the buffer, + based on current read position */ +static int wrap_pos(const ringbuffer *b, const long idx, long *pos) { + if(idx > (long)b->blen) { + return 0; + } + if(idx + (long)b->rpos > (long)b->alen) { + *pos = idx - (b->alen - b->rpos); + } else { + *pos = b->rpos + idx; + } + return 1; +} + +static int calc_splice_positions(const ringbuffer *b, long start, long end, long *out_start, long *out_end) { + if(start < 0) { + start = 1 + start + b->blen; + } + if(start <= 0) { + start = 1; + } + + if(end < 0) { + end = 1 + end + b->blen; + } + + if(end > (long)b->blen) { + end = b->blen; + } + if(start < 1) { + start = 1; + } + + if(start > end) { + return 0; + } + + start = start - 1; + + if(!wrap_pos(b, start, out_start)) { + return 0; + } + if(!wrap_pos(b, end, out_end)) { + return 0; + } + + return 1; +} + static void writechar(ringbuffer *b, char c) { b->blen++; b->buffer[(b->wpos++) % b->alen] = c; @@ -178,6 +226,55 @@ static int rb_tostring(lua_State *L) { return 1; } +static int rb_sub(lua_State *L) { + ringbuffer *b = luaL_checkudata(L, 1, "ringbuffer_mt"); + + long start = luaL_checkinteger(L, 2); + long end = luaL_optinteger(L, 3, -1); + + long wrapped_start, wrapped_end; + if(!calc_splice_positions(b, start, end, &wrapped_start, &wrapped_end)) { + lua_pushstring(L, ""); + } else if(wrapped_end <= wrapped_start) { + lua_pushlstring(L, &b->buffer[wrapped_start], b->alen - wrapped_start); + lua_pushlstring(L, b->buffer, wrapped_end); + lua_concat(L, 2); + } else { + lua_pushlstring(L, &b->buffer[wrapped_start], (wrapped_end - wrapped_start)); + } + + return 1; +} + +static int rb_byte(lua_State *L) { + ringbuffer *b = luaL_checkudata(L, 1, "ringbuffer_mt"); + + long start = luaL_optinteger(L, 2, 1); + long end = luaL_optinteger(L, 3, start); + + long i; + + long wrapped_start, wrapped_end; + if(calc_splice_positions(b, start, end, &wrapped_start, &wrapped_end)) { + if(wrapped_end <= wrapped_start) { + for(i = wrapped_start; i < (long)b->alen; i++) { + lua_pushinteger(L, b->buffer[i]); + } + for(i = 0; i < wrapped_end; i++) { + lua_pushinteger(L, b->buffer[i]); + } + return wrapped_end + (b->alen - wrapped_start); + } else { + for(i = wrapped_start; i < wrapped_end; i++) { + lua_pushinteger(L, b->buffer[i]); + } + return wrapped_end - wrapped_start; + } + } + + return 0; +} + static int rb_length(lua_State *L) { ringbuffer *b = luaL_checkudata(L, 1, "ringbuffer_mt"); lua_pushinteger(L, b->blen); @@ -239,6 +336,10 @@ int luaopen_util_ringbuffer(lua_State *L) { lua_setfield(L, -2, "size"); lua_pushcfunction(L, rb_length); lua_setfield(L, -2, "length"); + lua_pushcfunction(L, rb_sub); + lua_setfield(L, -2, "sub"); + lua_pushcfunction(L, rb_byte); + lua_setfield(L, -2, "byte"); lua_pushcfunction(L, rb_free); lua_setfield(L, -2, "free"); } -- cgit v1.2.3 From 4a087da58efef206a0ad39abf510723b8c77e239 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 4 Jun 2020 16:54:52 +0200 Subject: mod_admin_shell: Skip multiplier adjustment for rates --- plugins/mod_admin_shell.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/mod_admin_shell.lua b/plugins/mod_admin_shell.lua index afbd2922..f3672496 100644 --- a/plugins/mod_admin_shell.lua +++ b/plugins/mod_admin_shell.lua @@ -1318,13 +1318,14 @@ local function format_stat(type, unit, value, ref_value) elseif type == "rate" then unit = " events/sec" if ref_value < 0.9 then - unit = " events/min" + unit = "events/min" value = value*60; if ref_value < 0.6/60 then - unit = " events/h" + unit = "events/h" value = value*60; end end + return ("%.3g %s"):format(value, unit); end end return format_number(value, short_units[unit] or unit or "", unit == "bytes" and 'b' or nil); -- cgit v1.2.3 From 8c7811ec43bd254b110319f983043e5fa16dcabf Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 4 Jun 2020 16:56:28 +0200 Subject: util.human.units: Factor out function for getting multiplier --- util/human/units.lua | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/util/human/units.lua b/util/human/units.lua index 5a083783..af233e98 100644 --- a/util/human/units.lua +++ b/util/human/units.lua @@ -46,17 +46,10 @@ local binary = { "Yi", 2^80, } --- n: number, the number to format --- unit: string, the base unit --- b: optional enum 'b', thousands base -local function format(n, unit, b) --> string +local function adjusted_unit(n, b) local round = math_floor; local prefixes = large; local logbase = 1000; - local fmt = "%.3g %s%s"; - if n == 0 then - return fmt:format(n, "", unit); - end if b == 'b' then prefixes = binary; logbase = 1024; @@ -66,9 +59,22 @@ local function format(n, unit, b) --> string end local m = math_max(0, math_min(8, round(math_abs(math_log(math_abs(n), logbase))))); local prefix, multiplier = unpack(prefixes, m * 2-1, m*2); - return fmt:format(n / (multiplier or 1), prefix or "", unit); + return multiplier or 1, prefix; +end + +-- n: number, the number to format +-- unit: string, the base unit +-- b: optional enum 'b', thousands base +local function format(n, unit, b) --> string + local fmt = "%.3g %s%s"; + if n == 0 then + return fmt:format(n, "", unit); + end + local multiplier, prefix = adjusted_unit(n, b); + return fmt:format(n / multiplier, prefix or "", unit); end return { + adjust = adjusted_unit; format = format; }; -- cgit v1.2.3 From 1b30a8e979ed1962480e8f89f802211858896f84 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Thu, 4 Jun 2020 17:24:30 +0100 Subject: util.human.io: Remove padding option and use $COLUMNS as default width --- util/human/io.lua | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/util/human/io.lua b/util/human/io.lua index 9e700e89..2cea4f6b 100644 --- a/util/human/io.lua +++ b/util/human/io.lua @@ -95,12 +95,11 @@ local function padleft(s, width) return string.rep(" ", width-#s)..s; end -local function new_table(col_specs, max_width, padding) - max_width = max_width or 80; - padding = padding or 4; +local function new_table(col_specs, max_width) + max_width = max_width or tonumber(os.getenv("COLUMNS")) or 80; local widths = {}; - local total_width = max_width - padding; + local total_width = max_width; local free_width = total_width; -- Calculate width of fixed-size columns for i = 1, #col_specs do -- cgit v1.2.3 From 366a0994097c212ff8859af10c57da4576c454b0 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Thu, 4 Jun 2020 17:26:18 +0100 Subject: prosodyctl: Fix traceback when no command provided (thanks Zash) --- prosodyctl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prosodyctl b/prosodyctl index 3b5291d6..288d20b2 100755 --- a/prosodyctl +++ b/prosodyctl @@ -587,7 +587,7 @@ local command_runner = async.runner(function () end end - if not commands[command] then + if command and not commands[command] then local ok, command_module = pcall(require, "util.prosodyctl."..command); if ok and command_module[command] then commands[command] = command_module[command]; -- cgit v1.2.3 From bec20ff0b44be3bfc34fbb282b43e52cf178cd3d Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Thu, 4 Jun 2020 17:30:44 +0100 Subject: util.dependencies: Use util.human.io.table to replace custom layout code --- util/dependencies.lua | 47 ++++++++++++++++++++++------------------------- 1 file changed, 22 insertions(+), 25 deletions(-) diff --git a/util/dependencies.lua b/util/dependencies.lua index ede8c6ac..b53e385b 100644 --- a/util/dependencies.lua +++ b/util/dependencies.lua @@ -7,6 +7,7 @@ -- local function softreq(...) local ok, lib = pcall(require, ...); if ok then return lib; else return nil, lib; end end +local platform_table = require "util.human.io".table({ { width = 15, align = "right" }, { width = "100%" } }); -- Required to be able to find packages installed with luarocks if not softreq "luarocks.loader" then -- LuaRocks 2.x @@ -20,12 +21,8 @@ local function missingdep(name, sources, msg, err) -- luacheck: ignore err print("Prosody was unable to find "..tostring(name)); print("This package can be obtained in the following ways:"); print(""); - local longest_platform = 0; - for platform in pairs(sources) do - longest_platform = math.max(longest_platform, #platform); - end - for platform, source in pairs(sources) do - print("", platform..":"..(" "):rep(4+longest_platform-#platform)..source); + for _, row in ipairs(sources) do + print(platform_table(row)); end print(""); print(msg or (name.." is required for Prosody to run, so we will now exit.")); @@ -49,9 +46,9 @@ local function check_dependencies() if not lxp then missingdep("luaexpat", { - ["Debian/Ubuntu"] = "sudo apt-get install lua-expat"; - ["luarocks"] = "luarocks install luaexpat"; - ["Source"] = "http://matthewwild.co.uk/projects/luaexpat/"; + { "Debian/Ubuntu", "sudo apt-get install lua-expat" }; + { "luarocks", "luarocks install luaexpat" }; + { "Source", "http://matthewwild.co.uk/projects/luaexpat/" }; }, nil, err); fatal = true; end @@ -60,9 +57,9 @@ local function check_dependencies() if not socket then missingdep("luasocket", { - ["Debian/Ubuntu"] = "sudo apt-get install lua-socket"; - ["luarocks"] = "luarocks install luasocket"; - ["Source"] = "http://www.tecgraf.puc-rio.br/~diego/professional/luasocket/"; + { "Debian/Ubuntu", "sudo apt-get install lua-socket" }; + { "luarocks", "luarocks install luasocket" }; + { "Source", "http://www.tecgraf.puc-rio.br/~diego/professional/luasocket/" }; }, nil, err); fatal = true; elseif not socket.tcp4 then @@ -74,9 +71,9 @@ local function check_dependencies() local lfs, err = softreq "lfs" if not lfs then missingdep("luafilesystem", { - ["luarocks"] = "luarocks install luafilesystem"; - ["Debian/Ubuntu"] = "sudo apt-get install lua-filesystem"; - ["Source"] = "http://www.keplerproject.org/luafilesystem/"; + { "luarocks", "luarocks install luafilesystem" }; + { "Debian/Ubuntu", "sudo apt-get install lua-filesystem" }; + { "Source", "http://www.keplerproject.org/luafilesystem/" }; }, nil, err); fatal = true; end @@ -85,9 +82,9 @@ local function check_dependencies() if not ssl then missingdep("LuaSec", { - ["Debian/Ubuntu"] = "sudo apt-get install lua-sec"; - ["luarocks"] = "luarocks install luasec"; - ["Source"] = "https://github.com/brunoos/luasec"; + { "Debian/Ubuntu", "sudo apt-get install lua-sec" }; + { "luarocks", "luarocks install luasec" }; + { "Source", "https://github.com/brunoos/luasec" }; }, "SSL/TLS support will not be available", err); end @@ -95,9 +92,9 @@ local function check_dependencies() if not bit then missingdep("lua-bitops", { - ["Debian/Ubuntu"] = "sudo apt-get install lua-bitop"; - ["luarocks"] = "luarocks install luabitop"; - ["Source"] = "http://bitop.luajit.org/"; + { "Debian/Ubuntu", "sudo apt-get install lua-bitop" }; + { "luarocks", "luarocks install luabitop" }; + { "Source", "http://bitop.luajit.org/" }; }, "WebSocket support will not be available", err); end @@ -105,8 +102,8 @@ local function check_dependencies() if not encodings then if err:match("module '[^']*' not found") then missingdep("util.encodings", { - ["Windows"] = "Make sure you have encodings.dll from the Prosody distribution in util/"; - ["GNU/Linux"] = "Run './configure' and 'make' in the Prosody source directory to build util/encodings.so"; + { "Windows", "Make sure you have encodings.dll from the Prosody distribution in util/" }; + { "GNU/Linux", "Run './configure' and 'make' in the Prosody source directory to build util/encodings.so" }; }); else print "***********************************" @@ -123,8 +120,8 @@ local function check_dependencies() if not hashes then if err:match("module '[^']*' not found") then missingdep("util.hashes", { - ["Windows"] = "Make sure you have hashes.dll from the Prosody distribution in util/"; - ["GNU/Linux"] = "Run './configure' and 'make' in the Prosody source directory to build util/hashes.so"; + { "Windows", "Make sure you have hashes.dll from the Prosody distribution in util/" }; + { "GNU/Linux", "Run './configure' and 'make' in the Prosody source directory to build util/hashes.so" }; }); else print "***********************************" -- cgit v1.2.3 From ad3d04768583786fdbf595f901e832c040afa038 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 4 Jun 2020 18:31:50 +0200 Subject: util.human.io: Draw a separator between columns --- util/human/io.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/util/human/io.lua b/util/human/io.lua index 2cea4f6b..0f0c9155 100644 --- a/util/human/io.lua +++ b/util/human/io.lua @@ -97,9 +97,10 @@ end local function new_table(col_specs, max_width) max_width = max_width or tonumber(os.getenv("COLUMNS")) or 80; + local separator = " | "; local widths = {}; - local total_width = max_width; + local total_width = max_width - #separator * (#col_specs-1); local free_width = total_width; -- Calculate width of fixed-size columns for i = 1, #col_specs do @@ -137,7 +138,7 @@ local function new_table(col_specs, max_width) end table.insert(output, v); end - return table.concat(output); + return table.concat(output, separator); end; end -- cgit v1.2.3 From 435cd193730136205057c8cbed1c4095573303eb Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 4 Jun 2020 18:32:33 +0200 Subject: util.human.io: Replace overflow with ellipsis --- util/human/io.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/util/human/io.lua b/util/human/io.lua index 0f0c9155..389ed25a 100644 --- a/util/human/io.lua +++ b/util/human/io.lua @@ -128,13 +128,15 @@ local function new_table(col_specs, max_width) local output = {}; for i, column in ipairs(col_specs) do local width = widths[i]; - local v = tostring(row[not titles and column.key or i] or ""):sub(1, width); + local v = tostring(row[not titles and column.key or i] or ""); if #v < width then if column.align == "right" then v = padleft(v, width-1).." "; else v = padright(v, width); end + elseif #v > width then + v = v:sub(1, width-1) .. "\u{2026}"; end table.insert(output, v); end -- cgit v1.2.3 From 87777b26af1c1c63f90e1e0e8f9f3c18490322c1 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 4 Jun 2020 18:36:47 +0200 Subject: util.human.io: Use literal ellipsis instead of \u escape For compat with Lua 5.2 and before --- util/human/io.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/human/io.lua b/util/human/io.lua index 389ed25a..8d987355 100644 --- a/util/human/io.lua +++ b/util/human/io.lua @@ -136,7 +136,7 @@ local function new_table(col_specs, max_width) v = padright(v, width); end elseif #v > width then - v = v:sub(1, width-1) .. "\u{2026}"; + v = v:sub(1, width-1) .. "…"; end table.insert(output, v); end -- cgit v1.2.3 From 4e554bc4d156354ce835ae92fa0184504c77b61e Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 4 Jun 2020 18:40:37 +0200 Subject: util.human.io: Consider separator when calculating remaining width --- util/human/io.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/util/human/io.lua b/util/human/io.lua index 8d987355..76553fac 100644 --- a/util/human/io.lua +++ b/util/human/io.lua @@ -110,6 +110,9 @@ local function new_table(col_specs, max_width) width = math.max(tonumber(width), title and (#title+1) or 0); widths[i] = width; free_width = free_width - width; + if i > 1 then + free_width = free_width - #separator; + end end end -- Calculate width of %-based columns -- cgit v1.2.3 From 9b70ffb0ca54a2332459c90c1c32a9cc83b4076c Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 4 Jun 2020 21:32:28 +0200 Subject: util.human.io.table: Allow a map callaback per column This allows e.g. mapping booleans to "yes" or "no", specific number formatting or generating virtual columns. All while not mutating the underlying data or creating additional temporary tables. --- util/human/io.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/human/io.lua b/util/human/io.lua index 76553fac..7285c79f 100644 --- a/util/human/io.lua +++ b/util/human/io.lua @@ -131,7 +131,7 @@ local function new_table(col_specs, max_width) local output = {}; for i, column in ipairs(col_specs) do local width = widths[i]; - local v = tostring(row[not titles and column.key or i] or ""); + local v = (not titles and column.mapper or tostring)(row[not titles and column.key or i] or "", row); if #v < width then if column.align == "right" then v = padleft(v, width-1).." "; -- cgit v1.2.3 From c36203ba7ffe035f91f5065184cf741d3d32cce7 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 6 Jun 2020 00:49:48 +0200 Subject: doap: Mention XEP-0359 --- doc/doap.xml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/doc/doap.xml b/doc/doap.xml index df7b4a26..98931aa2 100644 --- a/doc/doap.xml +++ b/doc/doap.xml @@ -515,6 +515,15 @@ triggers buffer flush in mod_csi_simple since 0.11.6; recognised by mod_carbons and mod_mam since 0.12 + + + + 0.6.0 + complete + 0.10.0 + Used in context of XEP-0313 by mod_mam and mod_muc_mam + + -- cgit v1.2.3 From 4a0cb5a30643224dd6a9ebd91e53e257badaf7f9 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 6 Jun 2020 16:43:28 +0200 Subject: util.human.io: Fix right-alignment --- spec/scansion/prosody.cfg.lua | 6 ++++-- util/human/io.lua | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/spec/scansion/prosody.cfg.lua b/spec/scansion/prosody.cfg.lua index 4cf03de8..3fa36ab9 100644 --- a/spec/scansion/prosody.cfg.lua +++ b/spec/scansion/prosody.cfg.lua @@ -13,7 +13,9 @@ end admins = { "admin@localhost" } -use_libevent = true +network_backend = "epoll" +network_settings = { +} modules_enabled = { -- Generally required @@ -99,7 +101,7 @@ mam_smart_enable = true -- Logging configuration -- For advanced logging see https://prosody.im/doc/logging -log = "*console" +log = {debug = "*console" } pidfile = "prosody.pid" diff --git a/util/human/io.lua b/util/human/io.lua index 7285c79f..a38ab5dd 100644 --- a/util/human/io.lua +++ b/util/human/io.lua @@ -134,7 +134,7 @@ local function new_table(col_specs, max_width) local v = (not titles and column.mapper or tostring)(row[not titles and column.key or i] or "", row); if #v < width then if column.align == "right" then - v = padleft(v, width-1).." "; + v = padleft(v, width); else v = padright(v, width); end -- cgit v1.2.3 From ababc84794a77543eb1b84b5414d46f0eacb41f6 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 7 Jun 2020 00:18:14 +0200 Subject: mod_admin_shell: Fix display of units for some statistics --- plugins/mod_admin_shell.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_admin_shell.lua b/plugins/mod_admin_shell.lua index f3672496..0154cc65 100644 --- a/plugins/mod_admin_shell.lua +++ b/plugins/mod_admin_shell.lua @@ -1583,7 +1583,7 @@ local function stats_tostring(stats) end print(""); else - print(("%-50s %s"):format(stat_info[1], format_stat(stat_info[2], (stat_info[4] or {}).unit, stat_info[3]))); + print(("%-50s %s"):format(stat_info[1], format_stat(stat_info[2], (stat_info[4] or {}).units, stat_info[3]))); end end return #stats.." statistics displayed"; -- cgit v1.2.3 From fb5e6faad6782d9b034e7b19656c944529d5d538 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 7 Jun 2020 02:12:50 +0200 Subject: core.certmanager: Add TODO about LuaSec issue --- core/certmanager.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/certmanager.lua b/core/certmanager.lua index 91475467..023218fa 100644 --- a/core/certmanager.lua +++ b/core/certmanager.lua @@ -37,6 +37,9 @@ local config_path = prosody.paths.config or "."; local luasec_major, luasec_minor = ssl._VERSION:match("^(%d+)%.(%d+)"); local luasec_version = tonumber(luasec_major) * 100 + tonumber(luasec_minor); +-- TODO Use ssl.config instead of require here once we are sure that the fix +-- in LuaSec has been widely distributed +-- https://github.com/brunoos/luasec/issues/149 local luasec_has = softreq"ssl.config" or { algorithms = { ec = luasec_version >= 5; -- cgit v1.2.3 From b50db460865f3f0a1b1dbad90ee3bc5fbcec3a8f Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 7 Jun 2020 02:14:55 +0200 Subject: util.sslconfig: Process TLS 1.3-specific cipher list Same way as with other cipher list options --- util/sslconfig.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/util/sslconfig.lua b/util/sslconfig.lua index a5827a76..6074a1fb 100644 --- a/util/sslconfig.lua +++ b/util/sslconfig.lua @@ -67,6 +67,9 @@ end -- Curve list too finalisers.curveslist = finalisers.ciphers; +-- TLS 1.3 ciphers +finalisers.ciphersuites = finalisers.ciphers; + -- protocol = "x" should enable only that protocol -- protocol = "x+" should enable x and later versions -- cgit v1.2.3 From 625ec0a93f194fa152b0cf0b94f60afdee224df9 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 7 Jun 2020 02:25:56 +0200 Subject: util-src: Use the luaL_pushfail API added in Lua 5.4 to highlight all failure conditions Actually just an alias of pushnil, but it does make it more obvious where the failure conditions are, which is good for readability. --- util-src/encodings.c | 41 ++++++++++++++++++++++------------------- util-src/net.c | 13 ++++++++----- util-src/poll.c | 25 +++++++++++++++---------- util-src/pposix.c | 25 ++++++++++++++----------- util-src/ringbuffer.c | 8 ++++++-- util-src/windows.c | 7 +++++-- 6 files changed, 70 insertions(+), 49 deletions(-) diff --git a/util-src/encodings.c b/util-src/encodings.c index 996a6d4c..72264da8 100644 --- a/util-src/encodings.c +++ b/util-src/encodings.c @@ -24,6 +24,9 @@ #if (LUA_VERSION_NUM == 501) #define luaL_setfuncs(L, R, N) luaL_register(L, NULL, R) #endif +#if (LUA_VERSION_NUM < 504) +#define luaL_pushfail lua_pushnil +#endif /***************** BASE64 *****************/ @@ -247,7 +250,7 @@ static int Lutf8_length(lua_State *L) { size_t len; if(!check_utf8(L, 1, &len)) { - lua_pushnil(L); + luaL_pushfail(L); lua_pushliteral(L, "invalid utf8"); return 2; } @@ -286,7 +289,7 @@ static int icu_stringprep_prep(lua_State *L, const UStringPrepProfile *profile) input = luaL_checklstring(L, 1, &input_len); if(input_len >= 1024) { - lua_pushnil(L); + luaL_pushfail(L); return 1; } @@ -301,14 +304,14 @@ static int icu_stringprep_prep(lua_State *L, const UStringPrepProfile *profile) u_strFromUTF8(unprepped, 1024, &unprepped_len, input, input_len, &err); if(U_FAILURE(err)) { - lua_pushnil(L); + luaL_pushfail(L); return 1; } prepped_len = usprep_prepare(profile, unprepped, unprepped_len, prepped, 1024, flags, NULL, &err); if(U_FAILURE(err)) { - lua_pushnil(L); + luaL_pushfail(L); return 1; } else { u_strToUTF8(output, 1024, &output_len, prepped, prepped_len, &err); @@ -316,7 +319,7 @@ static int icu_stringprep_prep(lua_State *L, const UStringPrepProfile *profile) if(U_SUCCESS(err) && output_len < 1024) { lua_pushlstring(L, output, output_len); } else { - lua_pushnil(L); + luaL_pushfail(L); } return 1; @@ -414,7 +417,7 @@ static int stringprep_prep(lua_State *L, const Stringprep_profile *profile) { } if(s == NULL || len >= 1024 || len != strlen(s)) { - lua_pushnil(L); + luaL_pushfail(L); return 1; /* TODO return error message */ } @@ -425,7 +428,7 @@ static int stringprep_prep(lua_State *L, const Stringprep_profile *profile) { lua_pushstring(L, string); return 1; } else { - lua_pushnil(L); + luaL_pushfail(L); return 1; /* TODO return error message */ } } @@ -464,7 +467,7 @@ static int Lidna_to_ascii(lua_State *L) { /** idna.to_ascii(s) */ u_strFromUTF8(ustr, 1024, &ulen, s, len, &err); if(U_FAILURE(err)) { - lua_pushnil(L); + luaL_pushfail(L); return 1; } @@ -472,7 +475,7 @@ static int Lidna_to_ascii(lua_State *L) { /** idna.to_ascii(s) */ dest_len = uidna_nameToASCII(icu_idna2008, ustr, ulen, dest, 256, &info, &err); if(U_FAILURE(err) || info.errors) { - lua_pushnil(L); + luaL_pushfail(L); return 1; } else { u_strToUTF8(output, 1024, &output_len, dest, dest_len, &err); @@ -480,7 +483,7 @@ static int Lidna_to_ascii(lua_State *L) { /** idna.to_ascii(s) */ if(U_SUCCESS(err) && output_len < 1024) { lua_pushlstring(L, output, output_len); } else { - lua_pushnil(L); + luaL_pushfail(L); } return 1; @@ -499,7 +502,7 @@ static int Lidna_to_unicode(lua_State *L) { /** idna.to_unicode(s) */ u_strFromUTF8(ustr, 1024, &ulen, s, len, &err); if(U_FAILURE(err)) { - lua_pushnil(L); + luaL_pushfail(L); return 1; } @@ -507,7 +510,7 @@ static int Lidna_to_unicode(lua_State *L) { /** idna.to_unicode(s) */ dest_len = uidna_nameToUnicode(icu_idna2008, ustr, ulen, dest, 1024, &info, &err); if(U_FAILURE(err) || info.errors) { - lua_pushnil(L); + luaL_pushfail(L); return 1; } else { u_strToUTF8(output, 1024, &output_len, dest, dest_len, &err); @@ -515,7 +518,7 @@ static int Lidna_to_unicode(lua_State *L) { /** idna.to_unicode(s) */ if(U_SUCCESS(err) && output_len < 1024) { lua_pushlstring(L, output, output_len); } else { - lua_pushnil(L); + luaL_pushfail(L); } return 1; @@ -534,14 +537,14 @@ static int Lskeleton(lua_State *L) { u_strFromUTF8(ustr, 1024, &ulen, s, len, &err); if(U_FAILURE(err)) { - lua_pushnil(L); + luaL_pushfail(L); return 1; } dest_len = uspoof_getSkeleton(icu_spoofcheck, 0, ustr, ulen, dest, 1024, &err); if(U_FAILURE(err)) { - lua_pushnil(L); + luaL_pushfail(L); return 1; } @@ -552,7 +555,7 @@ static int Lskeleton(lua_State *L) { return 1; } - lua_pushnil(L); + luaL_pushfail(L); return 1; } @@ -569,7 +572,7 @@ static int Lidna_to_ascii(lua_State *L) { /** idna.to_ascii(s) */ int ret; if(s == NULL || len != strlen(s)) { - lua_pushnil(L); + luaL_pushfail(L); return 1; /* TODO return error message */ } @@ -580,7 +583,7 @@ static int Lidna_to_ascii(lua_State *L) { /** idna.to_ascii(s) */ idn_free(output); return 1; } else { - lua_pushnil(L); + luaL_pushfail(L); idn_free(output); return 1; /* TODO return error message */ } @@ -597,7 +600,7 @@ static int Lidna_to_unicode(lua_State *L) { /** idna.to_unicode(s) */ idn_free(output); return 1; } else { - lua_pushnil(L); + luaL_pushfail(L); idn_free(output); return 1; /* TODO return error message */ } diff --git a/util-src/net.c b/util-src/net.c index c3b07815..d786e885 100644 --- a/util-src/net.c +++ b/util-src/net.c @@ -33,6 +33,9 @@ #if (LUA_VERSION_NUM == 501) #define luaL_setfuncs(L, R, N) luaL_register(L, NULL, R) #endif +#if (LUA_VERSION_NUM < 504) +#define luaL_pushfail lua_pushnil +#endif /* Enumerate all locally configured IP addresses */ @@ -59,7 +62,7 @@ static int lc_local_addresses(lua_State *L) { #ifndef _WIN32 if(getifaddrs(&addr) < 0) { - lua_pushnil(L); + luaL_pushfail(L); lua_pushfstring(L, "getifaddrs failed (%d): %s", errno, strerror(errno)); return 2; @@ -141,14 +144,14 @@ static int lc_pton(lua_State *L) { case -1: errno_ = errno; - lua_pushnil(L); + luaL_pushfail(L); lua_pushstring(L, strerror(errno_)); lua_pushinteger(L, errno_); return 3; default: case 0: - lua_pushnil(L); + luaL_pushfail(L); lua_pushstring(L, strerror(EINVAL)); lua_pushinteger(L, EINVAL); return 3; @@ -170,7 +173,7 @@ static int lc_ntop(lua_State *L) { family = AF_INET; } else { - lua_pushnil(L); + luaL_pushfail(L); lua_pushstring(L, strerror(EAFNOSUPPORT)); lua_pushinteger(L, EAFNOSUPPORT); return 3; @@ -179,7 +182,7 @@ static int lc_ntop(lua_State *L) { if(!inet_ntop(family, ipaddr, buf, INET6_ADDRSTRLEN)) { errno_ = errno; - lua_pushnil(L); + luaL_pushfail(L); lua_pushstring(L, strerror(errno_)); lua_pushinteger(L, errno_); return 3; diff --git a/util-src/poll.c b/util-src/poll.c index 21cb9581..6c6a4e63 100644 --- a/util-src/poll.c +++ b/util-src/poll.c @@ -37,6 +37,9 @@ #if (LUA_VERSION_NUM == 501) #define luaL_setmetatable(L, tname) luaL_getmetatable(L, tname); lua_setmetatable(L, -2) #endif +#if (LUA_VERSION_NUM < 504) +#define luaL_pushfail lua_pushnil +#endif /* * Structure to keep state for each type of API @@ -67,7 +70,7 @@ static int Ladd(lua_State *L) { int wantwrite = lua_toboolean(L, 4); if(fd < 0) { - lua_pushnil(L); + luaL_pushfail(L); lua_pushstring(L, strerror(EBADF)); lua_pushinteger(L, EBADF); return 3; @@ -84,7 +87,7 @@ static int Ladd(lua_State *L) { if(ret < 0) { ret = errno; - lua_pushnil(L); + luaL_pushfail(L); lua_pushstring(L, strerror(ret)); lua_pushinteger(L, ret); return 3; @@ -96,14 +99,14 @@ static int Ladd(lua_State *L) { #else if(fd > FD_SETSIZE) { - lua_pushnil(L); + luaL_pushfail(L); lua_pushstring(L, strerror(EBADF)); lua_pushinteger(L, EBADF); return 3; } if(FD_ISSET(fd, &state->all)) { - lua_pushnil(L); + luaL_pushfail(L); lua_pushstring(L, strerror(EEXIST)); lua_pushinteger(L, EEXIST); return 3; @@ -160,7 +163,7 @@ static int Lset(lua_State *L) { } else { ret = errno; - lua_pushnil(L); + luaL_pushfail(L); lua_pushstring(L, strerror(ret)); lua_pushinteger(L, ret); return 3; @@ -169,7 +172,7 @@ static int Lset(lua_State *L) { #else if(!FD_ISSET(fd, &state->all)) { - lua_pushnil(L); + luaL_pushfail(L); lua_pushstring(L, strerror(ENOENT)); lua_pushinteger(L, ENOENT); return 3; @@ -218,7 +221,7 @@ static int Ldel(lua_State *L) { } else { ret = errno; - lua_pushnil(L); + luaL_pushfail(L); lua_pushstring(L, strerror(ret)); lua_pushinteger(L, ret); return 3; @@ -227,7 +230,7 @@ static int Ldel(lua_State *L) { #else if(!FD_ISSET(fd, &state->all)) { - lua_pushnil(L); + luaL_pushfail(L); lua_pushstring(L, strerror(ENOENT)); lua_pushinteger(L, ENOENT); return 3; @@ -314,18 +317,20 @@ static int Lwait(lua_State *L) { #endif if(ret == 0) { + /* Is this an error? */ lua_pushnil(L); lua_pushstring(L, "timeout"); return 2; } else if(ret < 0 && errno == EINTR) { + /* Is this an error? */ lua_pushnil(L); lua_pushstring(L, "signal"); return 2; } else if(ret < 0) { ret = errno; - lua_pushnil(L); + luaL_pushfail(L); lua_pushstring(L, strerror(ret)); lua_pushinteger(L, ret); return 3; @@ -399,7 +404,7 @@ static int Lnew(lua_State *L) { int epoll_fd = epoll_create1(EPOLL_CLOEXEC); if(epoll_fd <= 0) { - lua_pushnil(L); + luaL_pushfail(L); lua_pushstring(L, strerror(errno)); lua_pushinteger(L, errno); return 3; diff --git a/util-src/pposix.c b/util-src/pposix.c index 42b553dd..856905b0 100644 --- a/util-src/pposix.c +++ b/util-src/pposix.c @@ -64,6 +64,9 @@ #if (LUA_VERSION_NUM < 503) #define lua_isinteger(L, n) lua_isnumber(L, n) #endif +#if (LUA_VERSION_NUM < 504) +#define luaL_pushfail lua_pushnil +#endif #include #if defined(__linux__) @@ -413,7 +416,7 @@ static int lc_initgroups(lua_State *L) { struct passwd *p; if(!lua_isstring(L, 1)) { - lua_pushnil(L); + luaL_pushfail(L); lua_pushstring(L, "invalid-username"); return 2; } @@ -421,7 +424,7 @@ static int lc_initgroups(lua_State *L) { p = getpwnam(lua_tostring(L, 1)); if(!p) { - lua_pushnil(L); + luaL_pushfail(L); lua_pushstring(L, "no-such-user"); return 2; } @@ -440,7 +443,7 @@ static int lc_initgroups(lua_State *L) { break; default: - lua_pushnil(L); + luaL_pushfail(L); lua_pushstring(L, "invalid-gid"); return 2; } @@ -450,17 +453,17 @@ static int lc_initgroups(lua_State *L) { if(ret) { switch(errno) { case ENOMEM: - lua_pushnil(L); + luaL_pushfail(L); lua_pushstring(L, "no-memory"); break; case EPERM: - lua_pushnil(L); + luaL_pushfail(L); lua_pushstring(L, "permission-denied"); break; default: - lua_pushnil(L); + luaL_pushfail(L); lua_pushstring(L, "unknown-error"); } } else { @@ -672,7 +675,7 @@ static int lc_uname(lua_State *L) { struct utsname uname_info; if(uname(&uname_info) != 0) { - lua_pushnil(L); + luaL_pushfail(L); lua_pushstring(L, strerror(errno)); return 2; } @@ -702,7 +705,7 @@ static int lc_setenv(lua_State *L) { /* If the second argument is nil or nothing, unset the var */ if(lua_isnoneornil(L, 2)) { if(unsetenv(var) != 0) { - lua_pushnil(L); + luaL_pushfail(L); lua_pushstring(L, strerror(errno)); return 2; } @@ -714,7 +717,7 @@ static int lc_setenv(lua_State *L) { value = luaL_checkstring(L, 2); if(setenv(var, value, 1) != 0) { - lua_pushnil(L); + luaL_pushfail(L); lua_pushstring(L, strerror(errno)); return 2; } @@ -776,7 +779,7 @@ static int lc_atomic_append(lua_State *L) { case ENOSPC: /* No space left */ default: /* Other issues */ - lua_pushnil(L); + luaL_pushfail(L); lua_pushstring(L, strerror(err)); lua_pushinteger(L, err); return 3; @@ -803,7 +806,7 @@ static int lc_atomic_append(lua_State *L) { return luaL_error(L, "atomic_append() failed in ftruncate(): %s", strerror(errno)); } - lua_pushnil(L); + luaL_pushfail(L); lua_pushstring(L, strerror(err)); lua_pushinteger(L, err); return 3; diff --git a/util-src/ringbuffer.c b/util-src/ringbuffer.c index 07e1096f..007aa2ec 100644 --- a/util-src/ringbuffer.c +++ b/util-src/ringbuffer.c @@ -6,6 +6,10 @@ #include #include +#if (LUA_VERSION_NUM < 504) +#define luaL_pushfail lua_pushnil +#endif + typedef struct { size_t rpos; /* read position */ size_t wpos; /* write position */ @@ -152,7 +156,7 @@ static int rb_read(lua_State *L) { int peek = lua_toboolean(L, 3); if(r > b->blen) { - lua_pushnil(L); + luaL_pushfail(L); return 1; } @@ -204,7 +208,7 @@ static int rb_write(lua_State *L) { /* Does `l` bytes fit? */ if((l + b->blen) > b->alen) { - lua_pushnil(L); + luaL_pushfail(L); return 1; } diff --git a/util-src/windows.c b/util-src/windows.c index 89bec57b..57af79d5 100644 --- a/util-src/windows.c +++ b/util-src/windows.c @@ -22,6 +22,9 @@ #if (LUA_VERSION_NUM == 501) #define luaL_setfuncs(L, R, N) luaL_register(L, NULL, R) #endif +#if (LUA_VERSION_NUM < 504) +#define luaL_pushfail lua_pushnil +#endif static int Lget_nameservers(lua_State *L) { char stack_buffer[1024]; // stack allocated buffer @@ -45,14 +48,14 @@ static int Lget_nameservers(lua_State *L) { return 1; } else { - lua_pushnil(L); + luaL_pushfail(L); lua_pushfstring(L, "DnsQueryConfig returned %d", status); return 2; } } static int lerror(lua_State *L, char *string) { - lua_pushnil(L); + luaL_pushfail(L); lua_pushfstring(L, "%s: %d", string, GetLastError()); return 2; } -- cgit v1.2.3 From 9e0186b0ed9b0005807b5d383faf8626fdf36168 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 8 Jun 2020 14:01:02 +0100 Subject: util.promise: Add all_settled, which follows semantics of allSettled from ES2020 --- spec/util_promise_spec.lua | 54 ++++++++++++++++++++++++++++++++++++++++++++++ util/promise.lua | 22 +++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/spec/util_promise_spec.lua b/spec/util_promise_spec.lua index 0008c6a2..6fb5d6bd 100644 --- a/spec/util_promise_spec.lua +++ b/spec/util_promise_spec.lua @@ -353,6 +353,60 @@ describe("util.promise", function () assert.equal("fail", result); end); end); + describe("all_settled()", function () + it("works with fulfilled promises", function () + local p1, p2 = promise.resolve("yep"), promise.resolve("nope"); + local p = promise.all_settled({ p1, p2 }); + local result; + p:next(function (v) + result = v; + end); + assert.same({ + { status = "fulfilled", value = "yep" }; + { status = "fulfilled", value = "nope" }; + }, result); + end); + it("works with pending promises", function () + local r1, r2; + local p1, p2 = promise.new(function (resolve) r1 = resolve end), promise.new(function (resolve) r2 = resolve end); + local p = promise.all_settled({ p1, p2 }); + + local result; + local cb = spy.new(function (v) + result = v; + end); + p:next(cb); + assert.spy(cb).was_called(0); + r2("yep"); + assert.spy(cb).was_called(0); + r1("nope"); + assert.spy(cb).was_called(1); + assert.same({ + { status = "fulfilled", value = "nope" }; + { status = "fulfilled", value = "yep" }; + }, result); + end); + it("works when some promises reject", function () + local r1, r2; + local p1, p2 = promise.new(function (resolve) r1 = resolve end), promise.new(function (_, reject) r2 = reject end); + local p = promise.all_settled({ p1, p2 }); + + local result; + local cb = spy.new(function (v) + result = v; + end); + p:next(cb); + assert.spy(cb).was_called(0); + r2("this fails"); + assert.spy(cb).was_called(0); + r1("this succeeds"); + assert.spy(cb).was_called(1); + assert.same({ + { status = "fulfilled", value = "this succeeds" }; + { status = "rejected", reason = "this fails" }; + }, result); + end); + end); describe("catch()", function () it("works", function () local result; diff --git a/util/promise.lua b/util/promise.lua index 0b182b54..6ebb0dd6 100644 --- a/util/promise.lua +++ b/util/promise.lua @@ -104,6 +104,27 @@ local function all(promises) end); end +local function all_settled(promises) + return new(function (resolve) + local count, total, results = 0, #promises, {}; + for i = 1, total do + promises[i]:next(function (v) + results[i] = { status = "fulfilled", value = v }; + count = count + 1; + if count == total then + resolve(results); + end + end, function (e) + results[i] = { status = "rejected", reason = e }; + count = count + 1; + if count == total then + resolve(results); + end + end); + end + end); +end + local function race(promises) return new(function (resolve, reject) for i = 1, #promises do @@ -149,6 +170,7 @@ return { resolve = resolve; reject = reject; all = all; + all_settled = all_settled; race = race; try = try; is_promise = is_promise; -- cgit v1.2.3 From edd798dd98d083d81369e232348a23ebc8cc7b96 Mon Sep 17 00:00:00 2001 From: Boris Grozev Date: Wed, 10 Jun 2020 13:15:57 -0500 Subject: mod_http: Support CIDR for trusted proxies. --- plugins/mod_http.lua | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/plugins/mod_http.lua b/plugins/mod_http.lua index 3bacae61..cf63ecfb 100644 --- a/plugins/mod_http.lua +++ b/plugins/mod_http.lua @@ -18,6 +18,11 @@ local url_build = require "socket.url".build; local normalize_path = require "util.http".normalize_path; local set = require "util.set"; +local ip_util = require "util.ip"; +local new_ip = ip_util.new_ip; +local match_ip = ip_util.match; +local parse_cidr = ip_util.parse_cidr; + local server = require "net.http.server"; server.set_default_host(module:get_option_string("http_default_host")); @@ -204,6 +209,16 @@ module.add_host(module); -- set up handling on global context too local trusted_proxies = module:get_option_set("trusted_proxies", { "127.0.0.1", "::1" })._items; +local function is_trusted_proxy(ip) + local parsed_ip = new_ip(ip) + for trusted_proxy in trusted_proxies do + if match_ip(parsed_ip, parse_cidr(trusted_proxy)) then + return true; + end + end + return false +end + local function get_ip_from_request(request) local ip = request.conn:ip(); local forwarded_for = request.headers.x_forwarded_for; @@ -218,7 +233,7 @@ local function get_ip_from_request(request) -- Case d) If all IPs are in trusted proxies, something went obviously wrong and the logic never overwrites `ip`, leaving it at the original request IP. forwarded_for = forwarded_for..", "..ip; for forwarded_ip in forwarded_for:gmatch("[^%s,]+") do - if not trusted_proxies[forwarded_ip] then + if not is_trusted_proxy(forwarded_ip) then ip = forwarded_ip; end end -- cgit v1.2.3 From 9dfc424f2e747e58f5aae40abb73e9e9e3639990 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 11 Jun 2020 22:02:54 +0200 Subject: util.statsd: Update for API change See change d75d805c852f to util.statistics --- util/statsd.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/util/statsd.lua b/util/statsd.lua index 67481c36..8f6151c6 100644 --- a/util/statsd.lua +++ b/util/statsd.lua @@ -38,13 +38,13 @@ local function new(config) local methods; methods = { - amount = function (name, initial) - if initial then - send_gauge(name, initial); + amount = function (name, conf) + if conf and conf.initial then + send_gauge(name, conf.initial); end return function (new_v) send_gauge(name, new_v); end end; - counter = function (name, initial) --luacheck: ignore 212/initial + counter = function (name, conf) --luacheck: ignore 212/conf return function (delta) send_gauge(name, delta, true); end; @@ -54,7 +54,7 @@ local function new(config) send_counter(name, 1); end; end; - distribution = function (name, unit, type) --luacheck: ignore 212/unit 212/type + distribution = function (name, conf) --luacheck: ignore 212/conf return function (value) send_histogram_sample(name, value); end; -- cgit v1.2.3 From 371d05a0c6f7a9353155588d617a1efd468fd9d0 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Fri, 12 Jun 2020 16:54:38 +0100 Subject: mod_storage_sql: Fix incorrect results when fetching items before specific archive id Copy/paste error, introduced in deb68066c7aa --- plugins/mod_storage_sql.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_storage_sql.lua b/plugins/mod_storage_sql.lua index 30e38d49..67e7ad17 100644 --- a/plugins/mod_storage_sql.lua +++ b/plugins/mod_storage_sql.lua @@ -391,7 +391,7 @@ local function archive_where_id_range(query, args, where) end if query.before then local before_id = nil; - for row in engine:select(id_lookup_sql, query.after, args[1], args[2], args[3]) do + for row in engine:select(id_lookup_sql, query.before, args[1], args[2], args[3]) do before_id = row[1]; end if not before_id then -- cgit v1.2.3 From 0919bb30d392aabf24ff9e35792c2a9ee1666aa0 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Fri, 12 Jun 2020 16:55:35 +0100 Subject: mod_storage_internal, mod_storage_memory: Add support for query.before Previously returned all results. --- plugins/mod_storage_internal.lua | 8 ++++++-- plugins/mod_storage_memory.lua | 6 ++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/plugins/mod_storage_internal.lua b/plugins/mod_storage_internal.lua index 3998165b..28dc8921 100644 --- a/plugins/mod_storage_internal.lua +++ b/plugins/mod_storage_internal.lua @@ -135,7 +135,7 @@ function archive:find(username, query) return function () end; end local count = nil; - local i = 0; + local i, last_key = 0; if query then items = array(items); if query.key then @@ -178,6 +178,8 @@ function archive:find(username, query) return nil, "item-not-found"; end end + elseif query.before then + last_key = query.before; elseif query.after then local found = false; for j = 1, #items do @@ -198,7 +200,9 @@ function archive:find(username, query) return function () i = i + 1; local item = items[i]; - if not item then return; end + if not item or (last_key and item.key == last_key) then + return; + end local key = item.key or tostring(i); local when = item.when or datetime.parse(item.attr.stamp); local with = item.with; diff --git a/plugins/mod_storage_memory.lua b/plugins/mod_storage_memory.lua index 67598416..d71dc0f0 100644 --- a/plugins/mod_storage_memory.lua +++ b/plugins/mod_storage_memory.lua @@ -101,7 +101,7 @@ function archive_store:find(username, query) return function () end; end local count = nil; - local i = 0; + local i, last_key = 0; if query then items = array():append(items); if query.key then @@ -142,6 +142,8 @@ function archive_store:find(username, query) return nil, "item-not-found"; end end + elseif query.before then + last_key = query.before; elseif query.after then local found = false; for j = 1, #items do @@ -162,7 +164,7 @@ function archive_store:find(username, query) return function () i = i + 1; local item = items[i]; - if not item then return; end + if not item or (last_key and item.key == last_key) then return; end return item.key, item.value(), item.when, item.with; end, count; end -- cgit v1.2.3 From f2dbf366426578b2ba233168f8e09cbca0ceec59 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Fri, 12 Jun 2020 16:59:06 +0100 Subject: storage tests: Add tests for archive queries before/after specific ids Also increased the size of the test data for easier debugging with more complex tests. --- spec/core_storagemanager_spec.lua | 52 +++++++++++++++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 5 deletions(-) diff --git a/spec/core_storagemanager_spec.lua b/spec/core_storagemanager_spec.lua index a19edbab..d07dc4ec 100644 --- a/spec/core_storagemanager_spec.lua +++ b/spec/core_storagemanager_spec.lua @@ -215,12 +215,16 @@ describe("storagemanager", function () { nil, test_stanza, test_time+1, "contact2@example.com" }; { nil, test_stanza, test_time+2, "contact2@example.com" }; { nil, test_stanza, test_time-1, "contact2@example.com" }; + { nil, test_stanza, test_time-1, "contact3@example.com" }; + { nil, test_stanza, test_time+0, "contact3@example.com" }; + { nil, test_stanza, test_time+1, "contact3@example.com" }; }; it("can be added to", function () for _, data_item in ipairs(test_data) do - local ok = archive:append("user", unpack(data_item, 1, 4)); - assert.truthy(ok); + local id = archive:append("user", unpack(data_item, 1, 4)); + assert.truthy(id); + data_item[1] = id; end end); @@ -277,7 +281,7 @@ describe("storagemanager", function () assert.equal(2, #item.tags); assert(test_time >= when); end - assert.equal(2, count); + assert.equal(4, count); end); it("by time (start)", function () @@ -296,7 +300,7 @@ describe("storagemanager", function () assert.equal(2, #item.tags); assert(test_time <= when); end - assert.equal(#test_data -1, count); + assert.equal(#test_data - 2, count); end); it("by time (start+end)", function () @@ -317,7 +321,45 @@ describe("storagemanager", function () assert(when >= test_time, ("%d >= %d"):format(when, test_time)); assert(when <= test_time+1, ("%d <= %d"):format(when, test_time+1)); end - assert.equal(2, count); + assert.equal(4, count); + end); + + it("by id (after)", function () + -- luacheck: ignore 211/err + local data, err = archive:find("user", { + ["after"] = test_data[2][1]; + }); + assert.truthy(data); + local count = 0; + for id, item in data do + count = count + 1; + assert.truthy(id); + assert.equal(test_data[2+count][1], id); + assert(st.is_stanza(item)); + assert.equal("test", item.name); + assert.equal("urn:example:foo", item.attr.xmlns); + assert.equal(2, #item.tags); + end + assert.equal(5, count); + end); + + it("by id (before)", function () + -- luacheck: ignore 211/err + local data, err = archive:find("user", { + ["before"] = test_data[4][1]; + }); + assert.truthy(data); + local count = 0; + for id, item in data do + count = count + 1; + assert.truthy(id); + assert.equal(test_data[count][1], id); + assert(st.is_stanza(item)); + assert.equal("test", item.name); + assert.equal("urn:example:foo", item.attr.xmlns); + assert.equal(2, #item.tags); + end + assert.equal(3, count); end); end); -- cgit v1.2.3 From 9691969c448cd101a3ac55e9a833097fcb879065 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Sat, 13 Jun 2020 08:01:57 +0100 Subject: util.async: Rename wait -> wait_for (w/compat) Agreed this name is clearer and wait_for(thing) reads well in code. --- util/async.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/util/async.lua b/util/async.lua index d338071f..16dce200 100644 --- a/util/async.lua +++ b/util/async.lua @@ -246,7 +246,7 @@ local function ready() return pcall(checkthread); end -local function wait(promise) +local function wait_for(promise) local async_wait, async_done = waiter(); local ret, err = nil, nil; promise:next( @@ -266,5 +266,6 @@ return { waiter = waiter; guarder = guarder; runner = runner; - wait = wait; + wait = wait_for; -- COMPAT w/trunk pre-0.12 + wait_for = wait_for; }; -- cgit v1.2.3 From efcf7cb7d423525536b15bc4347ceb227556260e Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 13 Jun 2020 14:40:41 +0200 Subject: mod_admin_shell: Update for async.wait_for rename --- plugins/mod_admin_shell.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/mod_admin_shell.lua b/plugins/mod_admin_shell.lua index 0154cc65..8f966e6e 100644 --- a/plugins/mod_admin_shell.lua +++ b/plugins/mod_admin_shell.lua @@ -1160,7 +1160,7 @@ function def_env.xmpp:ping(localhost, remotehost, timeout) local iq = st.iq{ from=localhost, to=remotehost, type="get", id=new_id()} :tag("ping", {xmlns="urn:xmpp:ping"}); local time_start = time.now(); - local ret, err = async.wait(module:context(localhost):send_iq(iq, nil, timeout)); + local ret, err = async.wait_for(module:context(localhost):send_iq(iq, nil, timeout)); if ret then return true, ("pong from %s in %gs"):format(ret.stanza.attr.from, time.now() - time_start); else @@ -1182,7 +1182,7 @@ end function def_env.dns:lookup(name, typ, class) local resolver = get_resolver(self.session); - local ret, err = async.wait(resolver:lookup_promise(name, typ, class)); + local ret, err = async.wait_for(resolver:lookup_promise(name, typ, class)); if ret then return true, ret; elseif err then -- cgit v1.2.3 From 3c53df784751cef688a5ff41db26d4d11b7da2f7 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Sun, 14 Jun 2020 08:49:32 +0100 Subject: util.async: Call coroutine.close() on dead threads (Lua 5.4) --- util/async.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/util/async.lua b/util/async.lua index 16dce200..24378d8c 100644 --- a/util/async.lua +++ b/util/async.lua @@ -53,7 +53,7 @@ local function runner_continue(thread) return false; end call_watcher(runner, "error", debug.traceback(thread, err)); - runner.state, runner.thread = "ready", nil; + runner.state = "ready"; return runner:run(); elseif state == "ready" then -- If state is 'ready', it is our responsibility to update runner.state from 'waiting'. @@ -159,6 +159,10 @@ function runner_mt:run(input) local q, thread = self.queue, self.thread; if not thread or coroutine.status(thread) == "dead" then + --luacheck: ignore 143/coroutine + if coroutine.close then + coroutine.close(thread); + end self:log("debug", "creating new coroutine"); -- Create a new coroutine for this runner thread = runner_create_thread(self.func, self); -- cgit v1.2.3 From 020454b7a3afe41edcc365f593bffc97c827e962 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Sun, 14 Jun 2020 09:40:08 +0100 Subject: util.async: Don't attempt to close thread if not created yet --- util/async.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/async.lua b/util/async.lua index 24378d8c..341128d2 100644 --- a/util/async.lua +++ b/util/async.lua @@ -160,7 +160,7 @@ function runner_mt:run(input) local q, thread = self.queue, self.thread; if not thread or coroutine.status(thread) == "dead" then --luacheck: ignore 143/coroutine - if coroutine.close then + if thread and coroutine.close then coroutine.close(thread); end self:log("debug", "creating new coroutine"); -- cgit v1.2.3 From 9ea29301ad2334abdb9bffa85f12ff631692bdf3 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 14 Jun 2020 12:57:50 +0200 Subject: util.prosodyctl.check: Fix traceback by handling SRV '.' target to The IDNA to-ASCII operation returns nil in this case, which causes an error in net.dns --- util/prosodyctl/check.lua | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/util/prosodyctl/check.lua b/util/prosodyctl/check.lua index d22d5f45..ca5c51c9 100644 --- a/util/prosodyctl/check.lua +++ b/util/prosodyctl/check.lua @@ -299,6 +299,10 @@ local function check(arg) local res = dns.lookup("_xmpp-client._tcp."..idna.to_ascii(host)..".", "SRV"); if res then for _, record in ipairs(res) do + if record.srv.target == "." then -- TODO is this an error if mod_c2s is enabled? + print(" 'xmpp-client' service disabled by pointing to '.'"); -- FIXME Explain better what this is + break; + end 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); @@ -317,6 +321,10 @@ local function check(arg) local res = dns.lookup("_xmpp-server._tcp."..idna.to_ascii(host)..".", "SRV"); if res then for _, record in ipairs(res) do + if record.srv.target == "." then -- TODO Is this an error if mod_s2s is enabled? + print(" 'xmpp-server' service disabled by pointing to '.'"); -- FIXME Explain better what this is + break; + end 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); -- cgit v1.2.3 From 63ce191722822d441b3ece1302ca27f45b7f90a5 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 15 Jun 2020 14:16:10 +0100 Subject: util.gc: New module for configuring the Lua garbage collector --- util/gc.lua | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 util/gc.lua diff --git a/util/gc.lua b/util/gc.lua new file mode 100644 index 00000000..e02e85c4 --- /dev/null +++ b/util/gc.lua @@ -0,0 +1,50 @@ +local array = require "util.array"; +local set = require "util.set"; + +local known_options = { + incremental = set.new { "mode", "threshold", "speed", "step_size" }; + generational = set.new { "mode", "minor_threshold", "major_threshold" }; +}; + +if _VERSION ~= "5.4" then + known_options.generational = nil; + known_options.incremental:remove("step_size"); +end + +local function configure(user, defaults) + local mode = user.mode or defaults.mode or "incremental"; + if not known_options[mode] then + return nil, "GC mode not supported on ".._VERSION..": "..mode; + end + + for k, v in pairs(user) do + if not known_options[mode]:contains(k) then + return nil, "Unknown GC parameter: "..k; + elseif k ~= "mode" and type(v) ~= "number" then + return nil, "parameter '"..k.."' should be a number"; + end + end + + if mode == "incremental" then + if _VERSION == "Lua 5.4" then + collectgarbage(mode, + user.threshold or defaults.threshold, + user.speed or defaults.speed, + user.step_size or defaults.step_size + ); + else + collectgarbage("setpause", user.threshold or defaults.threshold); + collectgarbage("setstepmul", user.speed or defaults.speed); + end + elseif mode == "generational" then + collectgarbage(mode, + user.minor_threshold or defaults.minor_threshold, + user.major_threshold or defaults.major_threshold + ); + end + return true; +end + +return { + configure = configure; +}; -- cgit v1.2.3 From 758df8e14d695899481b5734afe7513672a5920d Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 15 Jun 2020 14:16:34 +0100 Subject: util.startup: Configure the GC on startup, using the config or built-in defaults --- util/startup.lua | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/util/startup.lua b/util/startup.lua index d45855f2..69633c47 100644 --- a/util/startup.lua +++ b/util/startup.lua @@ -14,6 +14,8 @@ local dependencies = require "util.dependencies"; local original_logging_config; +local default_gc_params = { mode = "incremental", threshold = 105, speed = 250 }; + local short_params = { D = "daemonize", F = "no-daemonize" }; local value_params = { config = true }; @@ -521,6 +523,19 @@ function startup.check_unwriteable() end end +function startup.init_gc() + -- Apply garbage collector settings from the config file + local gc = require "util.gc"; + local gc_settings = config.get("*", "gc") or { mode = default_gc_params.mode }; + + local ok, err = gc.configure(gc_settings, default_gc_params); + if not ok then + log("error", "Failed to apply GC configuration: %s", err); + return nil, err; + end + return true; +end + function startup.make_host(hostname) return { type = "local", @@ -551,6 +566,7 @@ function startup.prosodyctl() startup.read_config(); startup.force_console_logging(); startup.init_logging(); + startup.init_gc(); startup.setup_plugindir(); -- startup.setup_plugin_install_path(); startup.setup_datadir(); @@ -573,6 +589,7 @@ function startup.prosody() startup.init_global_state(); startup.read_config(); startup.init_logging(); + startup.init_gc(); startup.sanity_check(); startup.sandbox_require(); startup.set_function_metatable(); -- cgit v1.2.3 From 7c632da844ca208bc582465d99f2b1098afd1e7a Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 15 Jun 2020 14:23:47 +0100 Subject: util.gc: Linter fixes [luacheck] --- util/gc.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/util/gc.lua b/util/gc.lua index e02e85c4..b400af6b 100644 --- a/util/gc.lua +++ b/util/gc.lua @@ -1,4 +1,3 @@ -local array = require "util.array"; local set = require "util.set"; local known_options = { @@ -41,7 +40,7 @@ local function configure(user, defaults) user.minor_threshold or defaults.minor_threshold, user.major_threshold or defaults.major_threshold ); - end + end return true; end -- cgit v1.2.3 From 2b2f9903ae4f06f8da960379db80540922879653 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 17 Jun 2020 19:32:12 +0200 Subject: util.argparse: Move exiting and error to util.startup It's not so nice to have a library that exits the entire application from under you, so this and the error reporting belongs in util.startup. The argparse code was originally in util.startup but moved out in 1196f1e8d178 but the error handling should have stayed. --- util/argparse.lua | 7 ++----- util/startup.lua | 12 +++++++++++- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/util/argparse.lua b/util/argparse.lua index 928fc3eb..dde4fcc3 100644 --- a/util/argparse.lua +++ b/util/argparse.lua @@ -26,17 +26,14 @@ local function parse(arg, config) end if not param then - print("Unknown command-line option: "..tostring(param)); - print("Perhaps you meant to use prosodyctl instead?"); - os.exit(1); + return nil, "param-not-found", param; end local param_k, param_v; if value_params[param] then param_k, param_v = param, table.remove(arg, 1); if not param_v then - print("Expected a value to follow command-line option: "..raw_param); - os.exit(1); + return nil, "missing-value", raw_param; end else param_k, param_v = param:match("^([^=]+)=(.+)$"); diff --git a/util/startup.lua b/util/startup.lua index 69633c47..0e14395b 100644 --- a/util/startup.lua +++ b/util/startup.lua @@ -20,10 +20,20 @@ local short_params = { D = "daemonize", F = "no-daemonize" }; local value_params = { config = true }; function startup.parse_args() - prosody.opts = parse_args(arg, { + local opts, err, where = parse_args(arg, { short_params = short_params, value_params = value_params, }); + if not opts then + if err == "param-not-found" then + print("Unknown command-line option: "..tostring(where)); + print("Perhaps you meant to use prosodyctl instead?"); + elseif err == "missing-value" then + print("Expected a value to follow command-line option: "..where); + end + os.exit(1); + end + prosody.opts = opts; end function startup.read_config() -- cgit v1.2.3 From e2685cad2fbd4fbdecec1ceecab6ae3429a71070 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 17 Jun 2020 19:36:39 +0200 Subject: util.prosodyctl.shell: Handle argument parsing errors While almost identical to the handling in util.startup, this seems more appropriate. It would also simplify if shell-specific options need to be handled in the future. --- util/prosodyctl/shell.lua | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/util/prosodyctl/shell.lua b/util/prosodyctl/shell.lua index 3e98540f..d0a6881e 100644 --- a/util/prosodyctl/shell.lua +++ b/util/prosodyctl/shell.lua @@ -62,6 +62,15 @@ local function start(arg) --luacheck: ignore 212/arg local client = adminstream.client(); local opts = parse_args(arg); + if not opts then + if err == "param-not-found" then + print("Unknown command-line option: "..tostring(where)); + elseif err == "missing-value" then + print("Expected a value to follow command-line option: "..where); + end + os.exit(1); + end + client.events.add_handler("connected", function () if not arg.quiet then printbanner(); -- cgit v1.2.3 From 3077309ce7d96550a8e536e75bdf93a7ebdda096 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 17 Jun 2020 19:39:14 +0200 Subject: util.prosodyctl.shell: Collect extra return values Forgot in previous commit --- util/prosodyctl/shell.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/prosodyctl/shell.lua b/util/prosodyctl/shell.lua index d0a6881e..9ac982f7 100644 --- a/util/prosodyctl/shell.lua +++ b/util/prosodyctl/shell.lua @@ -60,7 +60,7 @@ end local function start(arg) --luacheck: ignore 212/arg local client = adminstream.client(); - local opts = parse_args(arg); + local opts, err, where = parse_args(arg); if not opts then if err == "param-not-found" then -- cgit v1.2.3 From f4805838390ce2f2127e70fac6f18d0ef7867c7d Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Thu, 18 Jun 2020 16:42:22 +0100 Subject: mod_register_ibr: Allow registration to reset an existing account password if permitted by a plugin --- plugins/mod_register_ibr.lua | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/plugins/mod_register_ibr.lua b/plugins/mod_register_ibr.lua index e79fc763..000ae740 100644 --- a/plugins/mod_register_ibr.lua +++ b/plugins/mod_register_ibr.lua @@ -9,10 +9,12 @@ local st = require "util.stanza"; local dataform_new = require "util.dataforms".new; -local usermanager_user_exists = require "core.usermanager".user_exists; -local usermanager_create_user = require "core.usermanager".create_user; -local usermanager_delete_user = require "core.usermanager".delete_user; +local usermanager_user_exists = require "core.usermanager".user_exists; +local usermanager_create_user = require "core.usermanager".create_user; +local usermanager_set_password = require "core.usermanager".create_user; +local usermanager_delete_user = require "core.usermanager".delete_user; local nodeprep = require "util.encodings".stringprep.nodeprep; +local util_error = require "util.error"; local additional_fields = module:get_option("additional_registration_fields", {}); local require_encryption = module:get_option_boolean("c2s_require_encryption", @@ -181,9 +183,20 @@ module:hook("stanza/iq/jabber:iq:register:query", function(event) end if usermanager_user_exists(username, host) then - log("debug", "Attempt to register with existing username"); - session.send(st.error_reply(stanza, "cancel", "conflict", "The requested username already exists.")); - return true; + if user.allow_reset == username then + local ok, err = util_error.coerce(usermanager_set_password(username, password, host)); + if ok then + session.send(st.reply(stanza)); -- reset ok! + else + session.log("error", "Unable to reset password for %s@%s: %s", username, host, err); + session.send(st.error_reply(stanza, err.type, err.condition, err.text)); + end + return true; + else + log("debug", "Attempt to register with existing username"); + session.send(st.error_reply(stanza, "cancel", "conflict", "The requested username already exists.")); + return true; + end end local created, err = usermanager_create_user(username, password, host); -- cgit v1.2.3 From 15227ecbc62d26707f37cbb82f5ee7c0f845442a Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 18 Jun 2020 17:54:28 +0200 Subject: util.adminstream: Prevent closure on read timeout --- util/adminstream.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/adminstream.lua b/util/adminstream.lua index 782a6b0f..5b592e76 100644 --- a/util/adminstream.lua +++ b/util/adminstream.lua @@ -249,7 +249,7 @@ local function new_server(sessions, stanza_handler) end function listeners.onreadtimeout(conn) - conn:send(" "); + return conn:send(" "); end return { -- cgit v1.2.3 From 44803031d0b89fad13326770f8f245e2b0c5906e Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 21 Jun 2020 19:06:37 +0200 Subject: MUC: Remove XEP-0091 Legacy Delayed Delivery from test Long time Obsolete XEP. The element is apparently not included when mod_muc_mam is enabled, and deleting this seems like the sanity-preserving approach. --- spec/scansion/muc_create_destroy.scs | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/scansion/muc_create_destroy.scs b/spec/scansion/muc_create_destroy.scs index 195200bb..789d4c41 100644 --- a/spec/scansion/muc_create_destroy.scs +++ b/spec/scansion/muc_create_destroy.scs @@ -90,7 +90,6 @@ Juliet receives: Where are thou my Juliet? - Juliet receives: -- cgit v1.2.3 From 93400f60fb40d8bf1b32977b76446d2e6fc78049 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 21 Jun 2020 19:09:47 +0200 Subject: scansion tests: Enable mod_muc_mam during tests (expect breakage) --- spec/scansion/prosody.cfg.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spec/scansion/prosody.cfg.lua b/spec/scansion/prosody.cfg.lua index 3fa36ab9..e75bb74f 100644 --- a/spec/scansion/prosody.cfg.lua +++ b/spec/scansion/prosody.cfg.lua @@ -111,6 +111,10 @@ hide_os_type = true -- absense tested for in version.scs Component "conference.localhost" "muc" storage = "memory" + modules_enabled = { + "muc_mam"; + } + Component "pubsub.localhost" "pubsub" storage = "memory" -- cgit v1.2.3 From 7b89ab9b86d3588717c0833a7b9b89c7513a459e Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 22 Jun 2020 01:42:18 +0200 Subject: net.connect: Remove TODO about use_ipv4/6 done in 3bfb20be844c --- net/connect.lua | 1 - net/resolvers/basic.lua | 1 - 2 files changed, 2 deletions(-) diff --git a/net/connect.lua b/net/connect.lua index 6d399dda..d52d3901 100644 --- a/net/connect.lua +++ b/net/connect.lua @@ -2,7 +2,6 @@ local server = require "net.server"; local log = require "util.logger".init("net.connect"); local new_id = require "util.id".short; --- TODO Respect use_ipv4, use_ipv6 -- TODO #1246 Happy Eyeballs -- FIXME RFC 6724 -- FIXME Error propagation from resolvers doesn't work diff --git a/net/resolvers/basic.lua b/net/resolvers/basic.lua index 75c23a58..84529bc5 100644 --- a/net/resolvers/basic.lua +++ b/net/resolvers/basic.lua @@ -7,7 +7,6 @@ local unpack = table.unpack or unpack; -- luacheck: ignore 113 local methods = {}; local resolver_mt = { __index = methods }; --- TODO Respect use_ipv4, use_ipv6 -- FIXME RFC 6724 -- FIXME #1428 Reuse DNS resolver object (from service resolver) -- FIXME #1429 Close DNS resolver object when done -- cgit v1.2.3 From 073a654eff55f79b67bd424529734538a1d7009e Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 22 Jun 2020 11:35:24 +0100 Subject: mod_register_ibr: Add event for successful password reset This is in addition to the existing event for password changes. This one includes additional details about the actor, and only triggers when the change is due to the account owner (presumably) resetting. As example use case is to invalidate one-time password reset tokens. --- plugins/mod_register_ibr.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/mod_register_ibr.lua b/plugins/mod_register_ibr.lua index 000ae740..83d284c8 100644 --- a/plugins/mod_register_ibr.lua +++ b/plugins/mod_register_ibr.lua @@ -186,6 +186,7 @@ module:hook("stanza/iq/jabber:iq:register:query", function(event) if user.allow_reset == username then local ok, err = util_error.coerce(usermanager_set_password(username, password, host)); if ok then + module:fire_event("user-password-reset", user); session.send(st.reply(stanza)); -- reset ok! else session.log("error", "Unable to reset password for %s@%s: %s", username, host, err); -- cgit v1.2.3 From dd0274b8c7499ceac91696cfcfb8e0b799bf0f52 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Tue, 23 Jun 2020 16:50:26 +0100 Subject: util.ringbuffer: Add test for :discard() --- spec/util_ringbuffer_spec.lua | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/spec/util_ringbuffer_spec.lua b/spec/util_ringbuffer_spec.lua index 72656899..9a50fd20 100644 --- a/spec/util_ringbuffer_spec.lua +++ b/spec/util_ringbuffer_spec.lua @@ -24,6 +24,17 @@ describe("util.ringbuffer", function () assert.truthy(b:write("hi")); end); end); + + describe(":discard", function () + local b = rb.new(); + it("works", function () + assert.truthy(b:write("hello world")); + assert.truthy(b:discard(6)); + assert.equal(5, #b); + assert.equal("world", b:read(5)); + end); + end); + describe(":sub", function () -- Helper function to compare buffer:sub() with string:sub() local function test_sub(b, x, y) -- cgit v1.2.3 From b9a670dace9252eaaf3de5871e5a1c6d355989fb Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Wed, 24 Jun 2020 12:34:20 +0100 Subject: util.ringbuffer: Ensure unsigned chars are always returned from :byte() --- spec/util_ringbuffer_spec.lua | 7 +++++++ util-src/ringbuffer.c | 6 +++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/spec/util_ringbuffer_spec.lua b/spec/util_ringbuffer_spec.lua index 9a50fd20..5657b3f6 100644 --- a/spec/util_ringbuffer_spec.lua +++ b/spec/util_ringbuffer_spec.lua @@ -92,5 +92,12 @@ describe("util.ringbuffer", function () end end end); + + it("works with characters > 127", function () + local b = rb.new(); + b:write(string.char(0, 140)); + local r = { b:byte(1, 2) }; + assert.same({ 0, 140 }, r); + end); end); end); diff --git a/util-src/ringbuffer.c b/util-src/ringbuffer.c index 007aa2ec..0f250c12 100644 --- a/util-src/ringbuffer.c +++ b/util-src/ringbuffer.c @@ -262,15 +262,15 @@ static int rb_byte(lua_State *L) { if(calc_splice_positions(b, start, end, &wrapped_start, &wrapped_end)) { if(wrapped_end <= wrapped_start) { for(i = wrapped_start; i < (long)b->alen; i++) { - lua_pushinteger(L, b->buffer[i]); + lua_pushinteger(L, (unsigned char)b->buffer[i]); } for(i = 0; i < wrapped_end; i++) { - lua_pushinteger(L, b->buffer[i]); + lua_pushinteger(L, (unsigned char)b->buffer[i]); } return wrapped_end + (b->alen - wrapped_start); } else { for(i = wrapped_start; i < wrapped_end; i++) { - lua_pushinteger(L, b->buffer[i]); + lua_pushinteger(L, (unsigned char)b->buffer[i]); } return wrapped_end - wrapped_start; } -- cgit v1.2.3 From f3add85e3c05049f24a498fd28783cff54f25b3b Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Wed, 24 Jun 2020 13:00:11 +0100 Subject: util.ringbuffer: Add some additional asserts to tests --- spec/util_ringbuffer_spec.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/util_ringbuffer_spec.lua b/spec/util_ringbuffer_spec.lua index 5657b3f6..3c052937 100644 --- a/spec/util_ringbuffer_spec.lua +++ b/spec/util_ringbuffer_spec.lua @@ -45,20 +45,20 @@ describe("util.ringbuffer", function () it("works", function () local b = rb.new(); - b:write("hello world"); + assert.truthy(b:write("hello world")); assert.equals("hello", b:sub(1, 5)); end); it("supports optional end parameter", function () local b = rb.new(); - b:write("hello world"); + assert.truthy(b:write("hello world")); assert.equals("hello world", b:sub(1)); assert.equals("world", b:sub(-5)); end); it("is equivalent to string:sub", function () local b = rb.new(6); - b:write("foobar"); + assert.truthy(b:write("foobar")); b:read(3); b:write("foo"); for i = -13, 13 do @@ -79,7 +79,7 @@ describe("util.ringbuffer", function () it("is equivalent to string:byte", function () local b = rb.new(6); - b:write("foobar"); + assert.truthy(b:write("foo"..string.char(0, 140).."obar")); b:read(3); b:write("foo"); test_byte(b, 1); -- cgit v1.2.3 From 5f4fcad1127da29e34759b5b54b6bbd3a42da5aa Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Thu, 25 Jun 2020 15:22:40 +0100 Subject: net.dns: Add some debug logging --- net/dns.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/net/dns.lua b/net/dns.lua index 193067e3..67eaa647 100644 --- a/net/dns.lua +++ b/net/dns.lua @@ -17,6 +17,8 @@ local have_timer, timer = pcall(require, "util.timer"); local new_ip = require "util.ip".new_ip; local have_util_net, util_net = pcall(require, "util.net"); +local log = require "util.logger".init("dns"); + local _, windows = pcall(require, "util.windows"); local is_windows = (_ and windows) or os.getenv("WINDIR"); @@ -877,6 +879,7 @@ function resolver:query(qname, qtype, qclass) -- - - - - - - - - - -- query timer.add_task(self.timeout, function () if get(self.wanted, qclass, qtype, qname, co) then if i < num_servers then + log("debug", "DNS request timeout %d/%d", i, num_servers) i = i + 1; self:servfail(conn); o.server = self.best_server; @@ -904,6 +907,7 @@ function resolver:servfail(sock, err) -- Find all requests to the down server, and retry on the next server self.time = socket.gettime(); + log("debug", "servfail %d (of %d)", num, #self.server); for id,queries in pairs(self.active) do for question,o in pairs(queries) do if o.server == num then -- This request was to the broken server -- cgit v1.2.3 From 3ff48b8386fdc9faf6e7f9d99a93858ec3a49728 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Thu, 25 Jun 2020 15:28:23 +0100 Subject: net.dns: Fix timeout retry logic On timeout the query would be resent twice - once within servfail(), and again inside the timeout callback. This commit moves all retry logic to servfail(). --- net/dns.lua | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/net/dns.lua b/net/dns.lua index 67eaa647..1dcb0479 100644 --- a/net/dns.lua +++ b/net/dns.lua @@ -856,6 +856,9 @@ function resolver:query(qname, qtype, qclass) -- - - - - - - - - - -- query server = self.best_server, delay = 1, retry = socket.gettime() + self.delays[1] + qclass = qclass; + qtype = qtype; + qname = qname; }; -- remember the query @@ -878,19 +881,14 @@ function resolver:query(qname, qtype, qclass) -- - - - - - - - - - -- query local i = 1; timer.add_task(self.timeout, function () if get(self.wanted, qclass, qtype, qname, co) then - if i < num_servers then log("debug", "DNS request timeout %d/%d", i, num_servers) i = i + 1; - self:servfail(conn); - o.server = self.best_server; - conn, err = self:getsocket(o.server); - if conn then - conn:send(o.packet); - return self.timeout; - end - end - -- Tried everything, failed - self:cancel(qclass, qtype, qname); + self:servfail(self.socket[o.server]); +-- end + end + -- Still outstanding? (i.e. retried) + if get(self.wanted, qclass, qtype, qname, co) then + return self.timeout; -- Then wait end end) end @@ -917,12 +915,19 @@ function resolver:servfail(sock, err) end o.retries = (o.retries or 0) + 1; - if o.retries >= #self.server then - --print('timeout'); - queries[question] = nil; - else + local retried; + if o.retries < #self.server then sock, err = self:getsocket(o.server); - if sock then sock:send(o.packet); end + if sock then + retried = true; + log("debug", "retry %d (immediate)", o.retries); + sock:send(o.packet); + end + end + if not retried then + log("debug", 'tried all servers, giving up'); + self:cancel(o.qclass, o.qtype, o.qname); + queries[question] = nil; end end end -- cgit v1.2.3 From d080fee3235102dd6a20cdfcd53f1ee080a1266b Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Thu, 25 Jun 2020 15:29:49 +0100 Subject: net.dns: Add jitter to spread queries and reduce failures due to congestion --- net/dns.lua | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/net/dns.lua b/net/dns.lua index 1dcb0479..6f5f28d4 100644 --- a/net/dns.lua +++ b/net/dns.lua @@ -72,6 +72,8 @@ local ztact = { -- public domain 20080404 lua@ztact.com local get, set = ztact.get, ztact.set; local default_timeout = 15; +local default_jitter = 1; +local default_retry_jitter = 2; -------------------------------------------------- module dns local _ENV = nil; @@ -668,6 +670,8 @@ end resolver.delays = { 1, 3 }; +resolver.jitter = have_timer and default_jitter or nil; +resolver.retry_jitter = have_timer and default_retry_jitter or nil; function resolver:addnameserver(address) -- - - - - - - - - - addnameserver self.server = self.server or {}; @@ -855,7 +859,7 @@ function resolver:query(qname, qtype, qclass) -- - - - - - - - - - -- query packet = header..question, server = self.best_server, delay = 1, - retry = socket.gettime() + self.delays[1] + retry = socket.gettime() + self.delays[1]; qclass = qclass; qtype = qtype; qname = qname; @@ -869,7 +873,13 @@ function resolver:query(qname, qtype, qclass) -- - - - - - - - - - -- query if not conn then return nil, err; end - conn:send (o.packet) + if self.jitter then + timer.add_task(math.random()*self.jitter, function () + conn:send(o.packet); + end); + else + conn:send(o.packet); + end -- remember which coroutine wants the answer if co then @@ -920,8 +930,16 @@ function resolver:servfail(sock, err) sock, err = self:getsocket(o.server); if sock then retried = true; + if self.retry_jitter then + local delay = self.delays[((o.retries-1)%#self.delays)+1] + (math.random()*self.retry_jitter); + log("debug", "retry %d in %0.2fs", o.retries, delay); + timer.add_task(delay, function () + sock:send(o.packet); + end); + else log("debug", "retry %d (immediate)", o.retries); sock:send(o.packet); + end end end if not retried then -- cgit v1.2.3 From 5e744740f305fdb82046c12c9f60cecc28226faf Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Thu, 25 Jun 2020 15:31:20 +0100 Subject: net.dns: Increase backoff delays Not entirely happy with the overall logic here. --- net/dns.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/dns.lua b/net/dns.lua index 6f5f28d4..ae3f947c 100644 --- a/net/dns.lua +++ b/net/dns.lua @@ -668,7 +668,7 @@ end -- socket layer -------------------------------------------------- socket layer -resolver.delays = { 1, 3 }; +resolver.delays = { 1, 2, 3, 5 }; resolver.jitter = have_timer and default_jitter or nil; resolver.retry_jitter = have_timer and default_retry_jitter or nil; -- cgit v1.2.3 From 6daae1f629f669872221186e7d2f9124fd66873f Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Thu, 25 Jun 2020 15:34:29 +0100 Subject: net.dns: Reduce default timeout to 5s Most healthy queries will return well within this time, and the new retry logic should help spread the cost of additional retries. --- net/dns.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/dns.lua b/net/dns.lua index ae3f947c..18cf51d6 100644 --- a/net/dns.lua +++ b/net/dns.lua @@ -71,7 +71,7 @@ local ztact = { -- public domain 20080404 lua@ztact.com }; local get, set = ztact.get, ztact.set; -local default_timeout = 15; +local default_timeout = 5; local default_jitter = 1; local default_retry_jitter = 2; -- cgit v1.2.3 From 558a365575c4ac2d601c19fcbd2bd6fd27078ac6 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Thu, 25 Jun 2020 15:45:13 +0100 Subject: util.ringbuffer: Fix accidentally committed test change (thanks buildbot) --- spec/util_ringbuffer_spec.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/util_ringbuffer_spec.lua b/spec/util_ringbuffer_spec.lua index 3c052937..633885a8 100644 --- a/spec/util_ringbuffer_spec.lua +++ b/spec/util_ringbuffer_spec.lua @@ -79,7 +79,7 @@ describe("util.ringbuffer", function () it("is equivalent to string:byte", function () local b = rb.new(6); - assert.truthy(b:write("foo"..string.char(0, 140).."obar")); + assert.truthy(b:write("foobar")); b:read(3); b:write("foo"); test_byte(b, 1); -- cgit v1.2.3 From 49dd598e71d066441f44a21f86a755c959bdcbf3 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 9 Mar 2019 21:16:27 +0100 Subject: util.dns: Library for decoding DNS records Imported from luaunbound-prosody 5f7c771138b1 --- util/dns.lua | 271 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 271 insertions(+) create mode 100644 util/dns.lua diff --git a/util/dns.lua b/util/dns.lua new file mode 100644 index 00000000..2a15fbc4 --- /dev/null +++ b/util/dns.lua @@ -0,0 +1,271 @@ +-- libunbound based net.adns replacement for Prosody IM +-- Copyright (C) 2012-2015 Kim Alvefur +-- Copyright (C) 2012 Waqas Hussain +-- +-- This file is MIT licensed. + +local setmetatable = setmetatable; +local table = table; +local t_concat = table.concat; +local t_insert = table.insert; +local s_byte = string.byte; +local s_char = string.char; +local s_format = string.format; +local s_gsub = string.gsub; +local s_sub = string.sub; +local s_match = string.match; +local s_gmatch = string.gmatch; + +local have_net, net_util = pcall(require, "util.net"); + +if have_net and not net_util.ntop then -- Added in Prosody 0.11 + have_net = false; +end + +local chartohex = {}; + +for c = 0, 255 do + chartohex[s_char(c)] = s_format("%02X", c); +end + +local function tohex(s) + return (s_gsub(s, ".", chartohex)); +end + +-- Converted from +-- http://www.iana.org/assignments/dns-parameters +-- 2015-05-19 + +local classes = { + IN = 1; "IN"; + nil; + CH = 3; "CH"; + HS = 4; "HS"; +}; + +local types = { +"A";"NS";"MD";"MF";"CNAME";"SOA";"MB";"MG";"MR";"NULL";"WKS";"PTR";"HINFO"; +"MINFO";"MX";"TXT";"RP";"AFSDB";"X25";"ISDN";"RT";"NSAP";"NSAP-PTR";"SIG"; +"KEY";"PX";"GPOS";"AAAA";"LOC";"NXT";"EID";"NIMLOC";"SRV";"ATMA";"NAPTR"; +"KX";"CERT";"A6";"DNAME";"SINK";"OPT";"APL";"DS";"SSHFP";"IPSECKEY";"RRSIG"; +"NSEC";"DNSKEY";"DHCID";"NSEC3";"NSEC3PARAM";"TLSA";[55]="HIP";[56]="NINFO"; +[57]="RKEY";[58]="TALINK";[59]="CDS";[60]="CDNSKEY";[61]="OPENPGPKEY"; +[62]="CSYNC";TLSA=52;NS=2;[249]="TKEY";[251]="IXFR";NSAP=22;UID=101;APL=42; +MG=8;NIMLOC=32;DHCID=49;TALINK=58;HINFO=13;MINFO=14;EID=31;DS=43;CSYNC=62; +RKEY=57;TKEY=249;NID=104;NAPTR=35;RT=21;LP=107;L32=105;KEY=25;MD=3;MX=15; +A6=38;KX=36;PX=26;CAA=257;WKS=11;TSIG=250;MAILA=254;CDS=59;SINK=40;LOC=29; +DLV=32769;[32769]="DLV";TA=32768;[32768]="TA";GID=102;IXFR=251;MAILB=253; +[256]="URI";[250]="TSIG";[252]="AXFR";NSEC=47;HIP=55;[254]="MAILA";[255]="*"; +NSEC3PARAM=51;["*"]=255;URI=256;[253]="MAILB";AXFR=252;SPF=99;NXT=30;AFSDB=18; +EUI48=108;NINFO=56;CDNSKEY=60;ISDN=20;L64=106;SRV=33;DNSKEY=48;X25=19;TXT=16; +RRSIG=46;OPENPGPKEY=61;DNAME=39;CNAME=5;EUI64=109;A=1;MR=9;IPSECKEY=45;OPT=41; +UNSPEC=103;["NSAP-PTR"]=23;[103]="UNSPEC";[257]="CAA";UINFO=100;[99]="SPF"; +MF=4;[101]="UID";[102]="GID";SOA=6;[104]="NID";[105]="L32";[106]="L64"; +[107]="LP";[108]="EUI48";[109]="EUI64";NSEC3=50;RP=17;PTR=12;[100]="UINFO"; +NULL=10;AAAA=28;MB=7;GPOS=27;SSHFP=44;CERT=37;SIG=24;ATMA=34 +}; + +local errors = { + NoError = "No Error"; [0] = "NoError"; + FormErr = "Format Error"; "FormErr"; + ServFail = "Server Failure"; "ServFail"; + NXDomain = "Non-Existent Domain"; "NXDomain"; + NotImp = "Not Implemented"; "NotImp"; + Refused = "Query Refused"; "Refused"; + YXDomain = "Name Exists when it should not"; "YXDomain"; + YXRRSet = "RR Set Exists when it should not"; "YXRRSet"; + NXRRSet = "RR Set that should exist does not"; "NXRRSet"; + NotAuth = "Server Not Authoritative for zone"; "NotAuth"; + NotZone = "Name not contained in zone"; "NotZone"; +}; + +-- Simplified versions of Waqas DNS parsers +-- Only the per RR parsers are needed and only feed a single RR + +local parsers = {}; + +-- No support for pointers, but libunbound appears to take care of that. +local function readDnsName(packet, pos) + if s_byte(packet, pos) == 0 then return "."; end + local pack_len, r, len = #packet, {}; + pos = pos or 1; + repeat + len = s_byte(packet, pos) or 0; + t_insert(r, s_sub(packet, pos + 1, pos + len)); + pos = pos + len + 1; + until len == 0 or pos >= pack_len; + return t_concat(r, "."), pos; +end + +-- These are just simple names. +parsers.CNAME = readDnsName; +parsers.NS = readDnsName +parsers.PTR = readDnsName; + +local soa_mt = { + __tostring = function(rr) + return s_format("%s %s %d %d %d %d %d", rr.mname, rr.rname, rr.serial, rr.refresh, rr.retry, rr.expire, rr.minimum); + end; +}; +function parsers.SOA(packet) + local mname, rname, offset; + + mname, offset = readDnsName(packet, 1); + rname, offset = readDnsName(packet, offset); + + -- Extract all the bytes of these fields in one call + local + s1, s2, s3, s4, -- serial + r1, r2, r3, r4, -- refresh + t1, t2, t3, t4, -- retry + e1, e2, e3, e4, -- expire + m1, m2, m3, m4 -- minimum + = s_byte(packet, offset, offset + 19); + + return setmetatable({ + mname = mname; + rname = rname; + serial = s1*0x1000000 + s2*0x10000 + s3*0x100 + s4; + refresh = r1*0x1000000 + r2*0x10000 + r3*0x100 + r4; + retry = t1*0x1000000 + t2*0x10000 + t3*0x100 + t4; + expire = e1*0x1000000 + e2*0x10000 + e3*0x100 + e4; + minimum = m1*0x1000000 + m2*0x10000 + m3*0x100 + m4; + }, soa_mt); +end + +function parsers.A(packet) + return s_format("%d.%d.%d.%d", s_byte(packet, 1, 4)); +end + +local aaaa = { nil, nil, nil, nil, nil, nil, nil, nil, }; +function parsers.AAAA(packet) + local hi, lo, ip, len, token; + for i = 1, 8 do + hi, lo = s_byte(packet, i * 2 - 1, i * 2); + aaaa[i] = s_format("%x", hi * 256 + lo); -- skips leading zeros + end + ip = t_concat(aaaa, ":", 1, 8); + len = (s_match(ip, "^0:[0:]+()") or 1) - 1; + for s in s_gmatch(ip, ":0:[0:]+") do + if len < #s then len, token = #s, s; end -- find longest sequence of zeros + end + return (s_gsub(ip, token or "^0:[0:]+", "::", 1)); +end + +if have_net then + parsers.A = net_util.ntop; + parsers.AAAA = net_util.ntop; +end + +local mx_mt = { + __tostring = function(rr) + return s_format("%d %s", rr.pref, rr.mx) + end +}; +function parsers.MX(packet) + local name = readDnsName(packet, 3); + local b1,b2 = s_byte(packet, 1, 2); + return setmetatable({ + pref = b1*256+b2; + mx = name; + }, mx_mt); +end + +local srv_mt = { + __tostring = function(rr) + return s_format("%d %d %d %s", rr.priority, rr.weight, rr.port, rr.target); + end +}; +function parsers.SRV(packet) + local name = readDnsName(packet, 7); + local b1, b2, b3, b4, b5, b6 = s_byte(packet, 1, 6); + return setmetatable({ + priority = b1*256+b2; + weight = b3*256+b4; + port = b5*256+b6; + target = name; + }, srv_mt); +end + +local txt_mt = { __tostring = t_concat }; +function parsers.TXT(packet) + local pack_len = #packet; + local r, pos, len = {}, 1; + repeat + len = s_byte(packet, pos) or 0; + t_insert(r, s_sub(packet, pos + 1, pos + len)); + pos = pos + len + 1; + until pos >= pack_len; + return setmetatable(r, txt_mt); +end + +parsers.SPF = parsers.TXT; + +-- Acronyms from RFC 7218 +local tlsa_usages = { + [0] = "PKIX-CA"; + [1] = "PKIX-EE"; + [2] = "DANE-TA"; + [3] = "DANE-EE"; + [255] = "PrivCert"; +}; +local tlsa_selectors = { + [0] = "Cert", + [1] = "SPKI", + [255] = "PrivSel", +}; +local tlsa_match_types = { + [0] = "Full", + [1] = "SHA2-256", + [2] = "SHA2-512", + [255] = "PrivMatch", +}; +local tlsa_mt = { + __tostring = function(rr) + return s_format("%s %s %s %s", + tlsa_usages[rr.use] or rr.use, + tlsa_selectors[rr.select] or rr.select, + tlsa_match_types[rr.match] or rr.match, + tohex(rr.data)); + end; + __index = { + getUsage = function(rr) return tlsa_usages[rr.use] end; + getSelector = function(rr) return tlsa_selectors[rr.select] end; + getMatchType = function(rr) return tlsa_match_types[rr.match] end; + } +}; +function parsers.TLSA(packet) + local use, select, match = s_byte(packet, 1,3); + return setmetatable({ + use = use; + select = select; + match = match; + data = s_sub(packet, 4); + }, tlsa_mt); +end + +local params = { + TLSA = { + use = tlsa_usages; + select = tlsa_selectors; + match = tlsa_match_types; + }; +}; + +local fallback_mt = { + __tostring = function(rr) + return s_format([[\# %d %s]], #rr.raw, tohex(rr.raw)); + end; +}; +local function fallback_parser(packet) + return setmetatable({ raw = packet },fallback_mt); +end +setmetatable(parsers, { __index = function() return fallback_parser end }); + +return { + parsers = parsers; + classes = classes; + types = types; + errors = errors; + params = params; +}; -- cgit v1.2.3 From b96535b6ae01148fe9969fffd5e602798aa1cdcc Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 9 Mar 2019 21:19:24 +0100 Subject: net.unbound: Async DNS resolver library based on libunbound via luaunbound --- net/unbound.lua | 245 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 245 insertions(+) create mode 100644 net/unbound.lua diff --git a/net/unbound.lua b/net/unbound.lua new file mode 100644 index 00000000..df26cd36 --- /dev/null +++ b/net/unbound.lua @@ -0,0 +1,245 @@ +-- libunbound based net.adns replacement for Prosody IM +-- Copyright (C) 2013-2015 Kim Alvefur +-- +-- This file is MIT licensed. +-- +-- luacheck: ignore prosody + +local setmetatable = setmetatable; +local tostring = tostring; +local t_concat = table.concat; +local s_format = string.format; +local s_lower = string.lower; +local s_upper = string.upper; +local noop = function() end; +local zero = function() return 0 end; +local truop = function() return true; end; + +local log = require "util.logger".init("unbound"); +local net_server = require "net.server"; +local libunbound = require"lunbound"; +local have_promise, promise = pcall(require, "util.promise"); + +local gettime = require"socket".gettime; +local dns_utils = require"util.dns"; +local classes, types, errors = dns_utils.classes, dns_utils.types, dns_utils.errors; +local parsers = dns_utils.parsers; + +local function add_defaults(conf) + if conf then + for option, default in pairs(libunbound.config) do + if conf[option] == nil then + conf[option] = default; + end + end + end + return conf; +end + +local unbound_config; +if prosody then + local config = require"core.configmanager"; + unbound_config = add_defaults(config.get("*", "unbound")); + prosody.events.add_handler("config-reloaded", function() + unbound_config = add_defaults(config.get("*", "unbound")); + end); +end +-- Note: libunbound will default to using root hints if resolvconf is unset + +local function connect_server(unbound, server) + if server.watchfd then + return server.watchfd(unbound, function () + unbound:process() + end); + elseif server.event and server.addevent then + local EV_READ = server.event.EV_READ; + local function event_callback() + unbound:process(); + return EV_READ; + end + return server.addevent(unbound:getfd(), EV_READ, event_callback) + elseif server.wrapclient then + local conn = { + getfd = function() + return unbound:getfd(); + end, + + send = zero, + receive = noop, + settimeout = noop, + close = truop, + } + + local function process() + unbound:process(); + end + local listener = { + onincoming = process, + + onconnect = noop, + ondisconnect = noop, + onreadtimeout = truop, + }; + return server.wrapclient(conn, "dns", 0, listener, "*a" ); + end +end + +local unbound = libunbound.new(unbound_config); + +local server_conn = connect_server(unbound, net_server); + +local answer_mt = { + __tostring = function(self) + if self._string then return self._string end + local h = s_format("Status: %s", errors[self.status]); + if self.secure then + h = h .. ", Secure"; + elseif self.bogus then + h = h .. s_format(", Bogus: %s", self.bogus); + end + local t = { h }; + for i = 1, #self do + t[i+1]=self.qname.."\t"..classes[self.qclass].."\t"..types[self.qtype].."\t"..tostring(self[i]); + end + local _string = t_concat(t, "\n"); + self._string = _string; + return _string; + end; +}; + +local waiting_queries = {}; + +local function prep_answer(a) + if not a then return end + local status = errors[a.rcode]; + local qclass = classes[a.qclass]; + local qtype = types[a.qtype]; + a.status, a.class, a.type = status, qclass, qtype; + + local t = s_lower(qtype); + local rr_mt = { __index = a, __tostring = function(self) return tostring(self[t]) end }; + local parser = parsers[qtype]; + for i = 1, #a do + if a.bogus then + -- Discard bogus data + a[i] = nil; + else + a[i] = setmetatable({[t] = parser(a[i])}, rr_mt); + end + end + return setmetatable(a, answer_mt); +end + +local function lookup(callback, qname, qtype, qclass) + qtype = qtype and s_upper(qtype) or "A"; + qclass = qclass and s_upper(qclass) or "IN"; + local ntype, nclass = types[qtype], classes[qclass]; + local startedat = gettime(); + local ret; + local function callback_wrapper(a, err) + local gotdataat = gettime(); + waiting_queries[ret] = nil; + if a then + prep_answer(a); + log("debug", "Results for %s %s %s: %s (%s, %f sec)", qname, qclass, qtype, a.rcode == 0 and (#a .. " items") or a.status, + a.secure and "Secure" or a.bogus or "Insecure", gotdataat - startedat); -- Insecure as in unsigned + else + log("error", "Results for %s %s %s: %s", qname, qclass, qtype, tostring(err)); + end + local ok, cerr = pcall(callback, a, err); + if not ok then log("error", "Error in callback: %s", cerr); end + end + log("debug", "Resolve %s %s %s", qname, qclass, qtype); + local err; + ret, err = unbound:resolve_async(callback_wrapper, qname, ntype, nclass); + if ret then + waiting_queries[ret] = callback; + else + log("warn", err); + end + return ret, err; +end + +local function lookup_sync(qname, qtype, qclass) + qtype = qtype and s_upper(qtype) or "A"; + qclass = qclass and s_upper(qclass) or "IN"; + local ntype, nclass = types[qtype], classes[qclass]; + local a, err = unbound:resolve(qname, ntype, nclass); + if not a then return a, err; end + return prep_answer(a); +end + +local function cancel(id) + local cb = waiting_queries[id]; + unbound:cancel(id); + if cb then + cb(nil, "canceled"); + waiting_queries[id] = nil; + end + return true; +end + +-- Reinitiate libunbound context, drops cache +local function purge() + for id in pairs(waiting_queries) do cancel(id); end + if server_conn then server_conn:close(); end + unbound = libunbound.new(unbound_config); + server_conn = connect_server(unbound, net_server); + return true; +end + +local function not_implemented() + error "not implemented"; +end +-- Public API +local _M = { + lookup = lookup; + cancel = cancel; + new_async_socket = not_implemented; + dns = { + lookup = lookup_sync; + cancel = cancel; + cache = noop; + socket_wrapper_set = noop; + settimeout = noop; + query = noop; + purge = purge; + random = noop; + peek = noop; + + types = types; + classes = classes; + }; +}; + +local lookup_promise; +if have_promise then + function lookup_promise(_, qname, qtype, qclass) + return promise.new(function (resolve, reject) + local function callback(answer, err) + if err then + return reject(err); + else + return resolve(answer); + end + end + local ret, err = lookup(callback, qname, qtype, qclass) + if not ret then reject(err); end + end); + end +end + +local wrapper = { + lookup = function (_, callback, qname, qtype, qclass) + return lookup(callback, qname, qtype, qclass) + end; + lookup_promise = lookup_promise; + _resolver = { + settimeout = function () end; + closeall = function () end; + }; +} + +function _M.resolver() return wrapper; end + +return _M; -- cgit v1.2.3 From 57253b7f87f70ed3e9711c931b03aa650b128d72 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 25 Jun 2020 17:26:21 +0200 Subject: util.dependencies: Add awareness of luaunbound --- util/dependencies.lua | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/util/dependencies.lua b/util/dependencies.lua index b53e385b..b76f7ab1 100644 --- a/util/dependencies.lua +++ b/util/dependencies.lua @@ -98,6 +98,14 @@ local function check_dependencies() }, "WebSocket support will not be available", err); end + local unbound, err = softreq"lunbound"; + if not unbound then + missingdep("lua-unbound", { + { "luarocks", "luarocks install luaunbound" }; + { "Source", "https://www.zash.se/luaunbound.html" }; + }, "Old DNS resolver library will be used", err); + end + local encodings, err = softreq "util.encodings" if not encodings then if err:match("module '[^']*' not found") then -- cgit v1.2.3 From 4a42f8042dc9c6289fc008c63ff2625fbdfc4808 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 25 Jun 2020 17:26:58 +0200 Subject: prosodyctl about: Report versions of luaunbound and libunbound --- prosodyctl | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/prosodyctl b/prosodyctl index 288d20b2..6b82e678 100755 --- a/prosodyctl +++ b/prosodyctl @@ -442,6 +442,7 @@ function commands.about(arg) local luaevent =dependencies.softreq"luaevent"; dependencies.softreq"ssl"; dependencies.softreq"DBI"; + local lunbound = dependencies.softreq"lunbound"; for name, module in pairs(package.loaded) do if type(module) == "table" and rawget(module, "_VERSION") and name ~= "_G" and not name:match("%.") then @@ -454,6 +455,12 @@ function commands.about(arg) if luaevent then module_versions["libevent"] = luaevent.core.libevent_version(); end + if lunbound then + if not module_versions["lunbound"] then + module_versions["lunbound"] = "<= 0.5"; + end + module_versions["libunbound"] = lunbound._LIBVER; + end local sorted_keys = array.collect(keys(module_versions)):sort(); for _, name in ipairs(sorted_keys) do print(name..":"..string.rep(" ", longest_name-#name), module_versions[name]); -- cgit v1.2.3 From c98a6d205e85ef1a580cd95301af9f3deddd3777 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 25 Jun 2020 17:41:07 +0200 Subject: util.dependencies: Prefer net.unbound over net.adns --- util/dependencies.lua | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/util/dependencies.lua b/util/dependencies.lua index b76f7ab1..56a04736 100644 --- a/util/dependencies.lua +++ b/util/dependencies.lua @@ -104,6 +104,11 @@ local function check_dependencies() { "luarocks", "luarocks install luaunbound" }; { "Source", "https://www.zash.se/luaunbound.html" }; }, "Old DNS resolver library will be used", err); + else + package.preload["net.adns"] = function () + local ub = require "net.unbound"; + return ub; + end end local encodings, err = softreq "util.encodings" -- cgit v1.2.3 From bde46d79cdd729cb2e7dbea290e4a05e18e5b0ad Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 25 Jun 2020 17:55:24 +0200 Subject: net.adns: Log a warning if loaded (because net.unbound wasn't) --- net/adns.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/adns.lua b/net/adns.lua index bf6c11ab..852f3f4f 100644 --- a/net/adns.lua +++ b/net/adns.lua @@ -12,6 +12,8 @@ local promise = require "util.promise"; local log = require "util.logger".init("adns"); +log("warn", "Old async DNS library used, lua-unbound missing?"); -- TODO write docs about luaunbound + local coroutine, pcall = coroutine, pcall; local setmetatable = setmetatable; -- cgit v1.2.3 From 9be5cb3fbdffc7c19ade04d635501cbea1be3af7 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 25 Jun 2020 17:56:48 +0200 Subject: net.unbound: Strip support for legacy net.server APIs These are not needed since the watchfd API is provided by all net.server backends. --- net/unbound.lua | 40 +++------------------------------------- 1 file changed, 3 insertions(+), 37 deletions(-) diff --git a/net/unbound.lua b/net/unbound.lua index df26cd36..dbf010ea 100644 --- a/net/unbound.lua +++ b/net/unbound.lua @@ -12,8 +12,6 @@ local s_format = string.format; local s_lower = string.lower; local s_upper = string.upper; local noop = function() end; -local zero = function() return 0 end; -local truop = function() return true; end; local log = require "util.logger".init("unbound"); local net_server = require "net.server"; @@ -47,41 +45,9 @@ end -- Note: libunbound will default to using root hints if resolvconf is unset local function connect_server(unbound, server) - if server.watchfd then - return server.watchfd(unbound, function () - unbound:process() - end); - elseif server.event and server.addevent then - local EV_READ = server.event.EV_READ; - local function event_callback() - unbound:process(); - return EV_READ; - end - return server.addevent(unbound:getfd(), EV_READ, event_callback) - elseif server.wrapclient then - local conn = { - getfd = function() - return unbound:getfd(); - end, - - send = zero, - receive = noop, - settimeout = noop, - close = truop, - } - - local function process() - unbound:process(); - end - local listener = { - onincoming = process, - - onconnect = noop, - ondisconnect = noop, - onreadtimeout = truop, - }; - return server.wrapclient(conn, "dns", 0, listener, "*a" ); - end + return server.watchfd(unbound, function () + unbound:process() + end); end local unbound = libunbound.new(unbound_config); -- cgit v1.2.3 From 02c3cc978d3320eead41d114bb8b814c98557756 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 25 Jun 2020 18:20:51 +0200 Subject: net.unbound: Remove compat for missing promises (pre-0.11) Code existed in a separate project before merged into Prosody, so util.promise was not always around. --- net/unbound.lua | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/net/unbound.lua b/net/unbound.lua index dbf010ea..49f220e1 100644 --- a/net/unbound.lua +++ b/net/unbound.lua @@ -16,7 +16,7 @@ local noop = function() end; local log = require "util.logger".init("unbound"); local net_server = require "net.server"; local libunbound = require"lunbound"; -local have_promise, promise = pcall(require, "util.promise"); +local promise = require"util.promise"; local gettime = require"socket".gettime; local dns_utils = require"util.dns"; @@ -178,21 +178,18 @@ local _M = { }; }; -local lookup_promise; -if have_promise then - function lookup_promise(_, qname, qtype, qclass) - return promise.new(function (resolve, reject) - local function callback(answer, err) - if err then - return reject(err); - else - return resolve(answer); - end +local function lookup_promise(_, qname, qtype, qclass) + return promise.new(function (resolve, reject) + local function callback(answer, err) + if err then + return reject(err); + else + return resolve(answer); end - local ret, err = lookup(callback, qname, qtype, qclass) - if not ret then reject(err); end - end); - end + end + local ret, err = lookup(callback, qname, qtype, qclass) + if not ret then reject(err); end + end); end local wrapper = { -- cgit v1.2.3 From 8646a54bea967ff425a7388d49ff7b61abadb977 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 25 Jun 2020 18:57:19 +0200 Subject: CHANGES: Add libunbound --- CHANGES | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES b/CHANGES index 0aa157a0..d1e50abb 100644 --- a/CHANGES +++ b/CHANGES @@ -16,6 +16,7 @@ TRUNK - ALPN support in mod\_net\_multiplex - `daemonize` option deprecated - SASL DIGEST-MD5 removed +- Switch to libunbound for DNS queries 0.11.0 ====== -- cgit v1.2.3 From 300a9a56c55f29e8ad422973f4674a858c5f30bc Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 25 Jun 2020 19:24:58 +0200 Subject: net.resolvers: Remove FIXMEs obsoleted by switch to libunbound --- net/resolvers/basic.lua | 2 -- net/resolvers/service.lua | 3 --- 2 files changed, 5 deletions(-) diff --git a/net/resolvers/basic.lua b/net/resolvers/basic.lua index 84529bc5..6458c638 100644 --- a/net/resolvers/basic.lua +++ b/net/resolvers/basic.lua @@ -8,8 +8,6 @@ local methods = {}; local resolver_mt = { __index = methods }; -- FIXME RFC 6724 --- FIXME #1428 Reuse DNS resolver object (from service resolver) --- FIXME #1429 Close DNS resolver object when done -- Find the next target to connect to, and -- pass it to cb() diff --git a/net/resolvers/service.lua b/net/resolvers/service.lua index f74338db..d74adf06 100644 --- a/net/resolvers/service.lua +++ b/net/resolvers/service.lua @@ -4,9 +4,6 @@ local inet_pton = require "util.net".pton; local idna_to_ascii = require "util.encodings".idna.to_ascii; local unpack = table.unpack or unpack; -- luacheck: ignore 113 --- FIXME #1428 Reuse DNS resolver object (pass to basic resorver) --- FIXME #1429 Close DNS resolver object when done - local methods = {}; local resolver_mt = { __index = methods }; -- cgit v1.2.3 From 61000837f8e6723cb2542d6c0ce6d0575d25ec80 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 25 Jun 2020 19:28:51 +0200 Subject: util.prosodyctl.check: Use net.unbound for DNS if available Improves performance somewhat by avoiding the rate limiting in net.dns --- util/prosodyctl/check.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/util/prosodyctl/check.lua b/util/prosodyctl/check.lua index ca5c51c9..86394a18 100644 --- a/util/prosodyctl/check.lua +++ b/util/prosodyctl/check.lua @@ -225,6 +225,9 @@ local function check(arg) end if not what or what == "dns" then local dns = require "net.dns"; + pcall(function () + dns = require"net.unbound".dns; + end) local idna = require "util.encodings".idna; local ip = require "util.ip"; local c2s_ports = set.new(configmanager.get("*", "c2s_ports") or {5222}); -- cgit v1.2.3 From 0f8937fc27f535fe33480e4db3f0ccb8b7af50ce Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 25 Jun 2020 20:45:06 +0200 Subject: util.dns: Update RR types from IANA registry --- util/dns.lua | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/util/dns.lua b/util/dns.lua index 2a15fbc4..282c4d42 100644 --- a/util/dns.lua +++ b/util/dns.lua @@ -34,7 +34,7 @@ end -- Converted from -- http://www.iana.org/assignments/dns-parameters --- 2015-05-19 +-- 2020-06-25 local classes = { IN = 1; "IN"; @@ -48,22 +48,28 @@ local types = { "MINFO";"MX";"TXT";"RP";"AFSDB";"X25";"ISDN";"RT";"NSAP";"NSAP-PTR";"SIG"; "KEY";"PX";"GPOS";"AAAA";"LOC";"NXT";"EID";"NIMLOC";"SRV";"ATMA";"NAPTR"; "KX";"CERT";"A6";"DNAME";"SINK";"OPT";"APL";"DS";"SSHFP";"IPSECKEY";"RRSIG"; -"NSEC";"DNSKEY";"DHCID";"NSEC3";"NSEC3PARAM";"TLSA";[55]="HIP";[56]="NINFO"; -[57]="RKEY";[58]="TALINK";[59]="CDS";[60]="CDNSKEY";[61]="OPENPGPKEY"; -[62]="CSYNC";TLSA=52;NS=2;[249]="TKEY";[251]="IXFR";NSAP=22;UID=101;APL=42; -MG=8;NIMLOC=32;DHCID=49;TALINK=58;HINFO=13;MINFO=14;EID=31;DS=43;CSYNC=62; -RKEY=57;TKEY=249;NID=104;NAPTR=35;RT=21;LP=107;L32=105;KEY=25;MD=3;MX=15; -A6=38;KX=36;PX=26;CAA=257;WKS=11;TSIG=250;MAILA=254;CDS=59;SINK=40;LOC=29; -DLV=32769;[32769]="DLV";TA=32768;[32768]="TA";GID=102;IXFR=251;MAILB=253; -[256]="URI";[250]="TSIG";[252]="AXFR";NSEC=47;HIP=55;[254]="MAILA";[255]="*"; -NSEC3PARAM=51;["*"]=255;URI=256;[253]="MAILB";AXFR=252;SPF=99;NXT=30;AFSDB=18; -EUI48=108;NINFO=56;CDNSKEY=60;ISDN=20;L64=106;SRV=33;DNSKEY=48;X25=19;TXT=16; -RRSIG=46;OPENPGPKEY=61;DNAME=39;CNAME=5;EUI64=109;A=1;MR=9;IPSECKEY=45;OPT=41; -UNSPEC=103;["NSAP-PTR"]=23;[103]="UNSPEC";[257]="CAA";UINFO=100;[99]="SPF"; -MF=4;[101]="UID";[102]="GID";SOA=6;[104]="NID";[105]="L32";[106]="L64"; -[107]="LP";[108]="EUI48";[109]="EUI64";NSEC3=50;RP=17;PTR=12;[100]="UINFO"; -NULL=10;AAAA=28;MB=7;GPOS=27;SSHFP=44;CERT=37;SIG=24;ATMA=34 -}; +"NSEC";"DNSKEY";"DHCID";"NSEC3";"NSEC3PARAM";"TLSA";"SMIMEA";[55]="HIP"; +[56]="NINFO";[57]="RKEY";[58]="TALINK";[59]="CDS";[60]="CDNSKEY";[61]="OPENPGPKEY"; +[62]="CSYNC";[63]="ZONEMD";[99]="SPF";[100]="UINFO";[101]="UID";[102]="GID"; +[103]="UNSPEC";[104]="NID";[105]="L32";[106]="L64";[107]="LP";[108]="EUI48"; +[109]="EUI64";["CSYNC"]=62;["TXT"]=16;["NAPTR"]=35;["A6"]=38;["RP"]=17; +["TALINK"]=58;["NXT"]=30;["MR"]=9;["UINFO"]=100;["X25"]=19;["TKEY"]=249; +["CERT"]=37;["SMIMEA"]=53;[252]="AXFR";[253]="MAILB";["CDS"]=59;[32769]="DLV"; +["RT"]=21;["WKS"]=11;[249]="TKEY";["LP"]=107;[250]="TSIG";["SSHFP"]=44;["DS"]=43; +["ISDN"]=20;["ATMA"]=34;["NS"]=2;[257]="CAA";["PX"]=26;["MX"]=15;["TSIG"]=250; +["EID"]=31;["TLSA"]=52;["GID"]=102;["KX"]=36;["SPF"]=99;["DOA"]=259;["GPOS"]=27; +["IPSECKEY"]=45;["NIMLOC"]=32;["RRSIG"]=46;["UID"]=101;["DNAME"]=39;["NSAP"]=22; +["DNSKEY"]=48;["SINK"]=40;["DHCID"]=49;[32768]="TA";["NSAP-PTR"]=23;["AAAA"]=28; +["PTR"]=12;["MINFO"]=14;["TA"]=32768;["EUI64"]=109;[260]="AMTRELAY"; +["AMTRELAY"]=260;["CDNSKEY"]=60;[259]="DOA";["LOC"]=29;[258]="AVC";["AVC"]=258; +["CAA"]=257;["MB"]=7;["*"]=255;[256]="URI";["URI"]=256;["SRV"]=33;["EUI48"]=108; +[255]="*";[254]="MAILA";["MAILA"]=254;["MAILB"]=253;["CNAME"]=5;[251]="IXFR"; +["APL"]=42;["OPENPGPKEY"]=61;["MD"]=3;["NINFO"]=56;["ZONEMD"]=63;["RKEY"]=57; +["L32"]=105;["NID"]=104;["HIP"]=55;["NSEC"]=47;["DLV"]=32769;["UNSPEC"]=103; +["NSEC3PARAM"]=51;["MF"]=4;["MG"]=8;["AFSDB"]=18;["A"]=1;["SIG"]=24;["NSEC3"]=50; +["HINFO"]=13;["IXFR"]=251;["NULL"]=10;["AXFR"]=252;["KEY"]=25;["OPT"]=41; +["SOA"]=6;["L64"]=106; +} local errors = { NoError = "No Error"; [0] = "NoError"; -- cgit v1.2.3 From fb8e10737dfe14aaaa5fbc0295044f1f57dc9ad0 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Fri, 26 Jun 2020 16:41:31 +0100 Subject: util.dbuffer: dynamic string buffer Similar to util.ringbuffer (and shares almost identical API). Differences: - size limit is optional and dynamic - does not allocate a fixed buffer of max_size bytes - focus on simply storing references to existing string objects where possible, avoiding unnecessary allocations - references are still stored in a ring buffer to enable use as a fast FIFO Optional second parameter to new() provides the number of ring buffer segments. On Lua 5.2 on my laptop, a segment is ~19 bytes. If the ring buffer fills up, the next write will compact all strings into a single item. --- spec/util_dbuffer_spec.lua | 95 +++++++++++++++++++++++++ util/dbuffer.lua | 171 +++++++++++++++++++++++++++++++++++++++++++++ util/queue.lua | 7 ++ 3 files changed, 273 insertions(+) create mode 100644 spec/util_dbuffer_spec.lua create mode 100644 util/dbuffer.lua diff --git a/spec/util_dbuffer_spec.lua b/spec/util_dbuffer_spec.lua new file mode 100644 index 00000000..854f3125 --- /dev/null +++ b/spec/util_dbuffer_spec.lua @@ -0,0 +1,95 @@ +local dbuffer = require "util.dbuffer"; +describe("util.dbuffer", function () + describe("#new", function () + it("has a constructor", function () + assert.Function(dbuffer.new); + end); + it("can be created", function () + assert.truthy(dbuffer.new()); + end); + it("won't create an empty buffer", function () + assert.falsy(dbuffer.new(0)); + end); + it("won't create a negatively sized buffer", function () + assert.falsy(dbuffer.new(-1)); + end); + end); + describe(":write", function () + local b = dbuffer.new(); + it("works", function () + assert.truthy(b:write("hi")); + end); + end); + + describe(":discard", function () + local b = dbuffer.new(); + it("works", function () + assert.truthy(b:write("hello world")); + assert.truthy(b:discard(6)); + assert.equal(5, #b); + assert.equal("world", b:read(5)); + end); + end); + + describe(":sub", function () + -- Helper function to compare buffer:sub() with string:sub() + local s = "hello world"; + local function test_sub(b, x, y) + local string_result, buffer_result = s:sub(x, y), b:sub(x, y); + assert.equals(string_result, buffer_result, ("buffer:sub(%d, %s) does not match string:sub()"):format(x, y and ("%d"):format(y) or "nil")); + end + + it("works", function () + local b = dbuffer.new(); + assert.truthy(b:write("hello world")); + assert.equals("hello", b:sub(1, 5)); + end); + + it("supports optional end parameter", function () + local b = dbuffer.new(); + assert.truthy(b:write("hello world")); + assert.equals("hello world", b:sub(1)); + assert.equals("world", b:sub(-5)); + end); + + it("is equivalent to string:sub", function () + local b = dbuffer.new(11); + assert.truthy(b:write(s)); + for i = -13, 13 do + for j = -13, 13 do + test_sub(b, i, j); + end + end + end); + end); + + describe(":byte", function () + -- Helper function to compare buffer:byte() with string:byte() + local s = "hello world" + local function test_byte(b, x, y) + local string_result, buffer_result = {s:byte(x, y)}, {b:byte(x, y)}; + assert.same(string_result, buffer_result, ("buffer:byte(%d, %s) does not match string:byte()"):format(x, y and ("%d"):format(y) or "nil")); + end + + it("is equivalent to string:byte", function () + local b = dbuffer.new(11); + assert.truthy(b:write(s)); + test_byte(b, 1); + test_byte(b, 3); + test_byte(b, -1); + test_byte(b, -3); + for i = -13, 13 do + for j = -13, 13 do + test_byte(b, i, j); + end + end + end); + + it("works with characters > 127", function () + local b = dbuffer.new(); + b:write(string.char(0, 140)); + local r = { b:byte(1, 2) }; + assert.same({ 0, 140 }, r); + end); + end); +end); diff --git a/util/dbuffer.lua b/util/dbuffer.lua new file mode 100644 index 00000000..c38a16f5 --- /dev/null +++ b/util/dbuffer.lua @@ -0,0 +1,171 @@ +local queue = require "util.queue"; + +local dbuffer_methods = {}; +local dynamic_buffer_mt = { __index = dbuffer_methods }; + +function dbuffer_methods:write(data) + if self.max_size and #data + self._length > self.max_size then + return nil; + end + local ok = self.items:push(data); + if not ok then + self:collapse(); + ok = self.items:push(data); + end + if not ok then + return nil; + end + self._length = self._length + #data; + return true; +end + +function dbuffer_methods:read_chunk(requested_bytes) + local chunk, consumed = self.items:peek(), self.front_consumed; + if not chunk then return; end + local chunk_length = #chunk; + local remaining_chunk_length = chunk_length - consumed; + if remaining_chunk_length <= requested_bytes then + self.front_consumed = 0; + self._length = self._length - remaining_chunk_length; + self.items:pop(); + assert(#chunk:sub(consumed + 1, -1) == remaining_chunk_length); + return chunk:sub(consumed + 1, -1), remaining_chunk_length; + end + local end_pos = consumed + requested_bytes; + self.front_consumed = end_pos; + self._length = self._length - requested_bytes; + assert(#chunk:sub(consumed + 1, end_pos) == requested_bytes); + return chunk:sub(consumed + 1, end_pos), requested_bytes; +end + +function dbuffer_methods:read(requested_bytes) + local chunks; + + if requested_bytes > self._length then + return nil; + end + + local chunk, read_bytes = self:read_chunk(requested_bytes); + if chunk then + requested_bytes = requested_bytes - read_bytes; + if requested_bytes == 0 then -- Already read everything we need + return chunk; + end + chunks = {}; + else + return nil; + end + + -- Need to keep reading more chunks + while chunk do + table.insert(chunks, chunk); + if requested_bytes > 0 then + chunk, read_bytes = self:read_chunk(requested_bytes); + requested_bytes = requested_bytes - read_bytes; + else + break; + end + end + + return table.concat(chunks); +end + +function dbuffer_methods:discard(requested_bytes) + if requested_bytes > self._length then + return nil; + end + + local chunk, read_bytes = self:read_chunk(requested_bytes); + if chunk then + requested_bytes = requested_bytes - read_bytes; + if requested_bytes == 0 then -- Already read everything we need + return true; + end + else + return nil; + end + + while chunk do + if requested_bytes > 0 then + chunk, read_bytes = self:read_chunk(requested_bytes); + requested_bytes = requested_bytes - read_bytes; + else + break; + end + end + return true; +end + +function dbuffer_methods:sub(i, j) + if j == nil then + j = -1; + end + if j < 0 then + j = self._length + (j+1); + end + if i < 0 then + i = self._length + (i+1); + end + if i < 1 then + i = 1; + end + if j > self._length then + j = self._length; + end + if i > j then + return ""; + end + + self:collapse(j); + + return self.items:peek():sub(i, j); +end + +function dbuffer_methods:byte(i, j) + i = i or 1; + j = j or i; + return string.byte(self:sub(i, j), 1, -1); +end + +function dbuffer_methods:length() + return self._length; +end +dynamic_buffer_mt.__len = dbuffer_methods.length; -- support # operator + +function dbuffer_methods:collapse(bytes) + bytes = bytes or self._length; + + local front_chunk = self.items:peek(); + + if #front_chunk - self.front_consumed >= bytes then + return; + end + + local front_chunks = { front_chunk:sub(self.front_consumed+1) }; + local front_bytes = #front_chunks[1]; + + while front_bytes < bytes do + self.items:pop(); + local chunk = self.items:peek(); + front_bytes = front_bytes + #chunk; + table.insert(front_chunks, chunk); + end + self.items:replace(table.concat(front_chunks)); + self.front_consumed = 0; +end + +local function new(max_size, max_chunks) + if max_size and max_size <= 0 then + return nil; + end + return setmetatable({ + front_consumed = 0; + _length = 0; + max_size = max_size; + items = queue.new(max_chunks or 32); + }, dynamic_buffer_mt); +end + +return { + new = new; +}; diff --git a/util/queue.lua b/util/queue.lua index 66ed098b..c94c62ae 100644 --- a/util/queue.lua +++ b/util/queue.lua @@ -51,6 +51,13 @@ local function new(size, allow_wrapping) end return t[tail]; end; + replace = function (self, data) + if items == 0 then + return self:push(data); + end + t[tail] = data; + return true; + end; items = function (self) return function (_, pos) if pos >= items then -- cgit v1.2.3 From 8c73ea0b2286fed2d1a91333c8d92dab247fd568 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 27 Jun 2020 14:25:57 +0200 Subject: util.dependencies: Tone down lua-unbound dependency for now At least until packages are available Wording from MattJ --- net/adns.lua | 3 ++- util/dependencies.lua | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/net/adns.lua b/net/adns.lua index 852f3f4f..8fe05653 100644 --- a/net/adns.lua +++ b/net/adns.lua @@ -12,7 +12,8 @@ local promise = require "util.promise"; local log = require "util.logger".init("adns"); -log("warn", "Old async DNS library used, lua-unbound missing?"); -- TODO write docs about luaunbound +log("debug", "Using legacy DNS API (missing lua-unbound?)"); -- TODO write docs about luaunbound +-- TODO Raise log level once packages are available local coroutine, pcall = coroutine, pcall; local setmetatable = setmetatable; diff --git a/util/dependencies.lua b/util/dependencies.lua index 56a04736..d2f87661 100644 --- a/util/dependencies.lua +++ b/util/dependencies.lua @@ -100,10 +100,12 @@ local function check_dependencies() local unbound, err = softreq"lunbound"; if not unbound then + --[[ TODO Re-enable once packages are available missingdep("lua-unbound", { { "luarocks", "luarocks install luaunbound" }; { "Source", "https://www.zash.se/luaunbound.html" }; }, "Old DNS resolver library will be used", err); + --]] else package.preload["net.adns"] = function () local ub = require "net.unbound"; -- cgit v1.2.3 From fb6356059181107d3d285088abe05dc20d4e8e8f Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 28 Jun 2020 02:15:25 +0200 Subject: util.dependencies: Quiet luacheck --- util/dependencies.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/util/dependencies.lua b/util/dependencies.lua index d2f87661..c117bfc2 100644 --- a/util/dependencies.lua +++ b/util/dependencies.lua @@ -98,8 +98,8 @@ local function check_dependencies() }, "WebSocket support will not be available", err); end - local unbound, err = softreq"lunbound"; - if not unbound then + local unbound, err = softreq"lunbound"; -- luacheck: ignore 211/err + if not unbound then -- luacheck: ignore 542 --[[ TODO Re-enable once packages are available missingdep("lua-unbound", { { "luarocks", "luarocks install luaunbound" }; -- cgit v1.2.3 From fb5059547f3171087410344fcf0bffcb8f5f1433 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Sun, 28 Jun 2020 12:02:10 +0100 Subject: net.dns: Disable jitter for default resolver (used by blocking dns.lookup() calls) This fixes 'prosodyctl check dns' being slow. --- net/dns.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/net/dns.lua b/net/dns.lua index 18cf51d6..17119152 100644 --- a/net/dns.lua +++ b/net/dns.lua @@ -1191,6 +1191,7 @@ end local _resolver = dns.resolver(); dns._resolver = _resolver; +_resolver.jitter, _resolver.retry_jitter = false, false; function dns.lookup(...) -- - - - - - - - - - - - - - - - - - - - - lookup return _resolver:lookup(...); -- cgit v1.2.3 From 8cc0b83179bde14826b91dd7e3c8ad986446923d Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Sun, 28 Jun 2020 12:26:04 +0100 Subject: util.dbuffer: Don't use # operator in tests, Lua 5.1 doesn't support __len --- spec/util_dbuffer_spec.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/util_dbuffer_spec.lua b/spec/util_dbuffer_spec.lua index 854f3125..1a0c2992 100644 --- a/spec/util_dbuffer_spec.lua +++ b/spec/util_dbuffer_spec.lua @@ -26,7 +26,7 @@ describe("util.dbuffer", function () it("works", function () assert.truthy(b:write("hello world")); assert.truthy(b:discard(6)); - assert.equal(5, #b); + assert.equal(5, b:length()); assert.equal("world", b:read(5)); end); end); -- cgit v1.2.3 From c2ba37df93033723e3dd10de2f8e8676bb68d27f Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 28 Jun 2020 14:58:32 +0200 Subject: util.human.io: Add brief test of table generation --- spec/util_human_io_spec.lua | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 spec/util_human_io_spec.lua diff --git a/spec/util_human_io_spec.lua b/spec/util_human_io_spec.lua new file mode 100644 index 00000000..ead117df --- /dev/null +++ b/spec/util_human_io_spec.lua @@ -0,0 +1,29 @@ +describe("util.human.io", function () + local human_io + setup(function () + human_io = require "util.human.io"; + end); + describe("table", function () + + it("alignment works", function () + local row = human_io.table({ + { + width = 3, + align = "right" + }, + { + width = 3, + }, + }); + + assert.equal(" 1 | . ", row({ 1, "." })); + assert.equal(" 10 | .. ", row({ 10, ".." })); + assert.equal("100 | ...", row({ 100, "..." })); + assert.equal("10… | ..…", row({ 1000, "...." })); + + end); + end); +end); + + + -- cgit v1.2.3 From e93be0e96ecfd43425b06f230d3b2efd9ff7fe5e Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 28 Jun 2020 15:58:47 +0200 Subject: doap: Expand on XEPs implemented by mod_time --- doc/doap.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/doc/doap.xml b/doc/doap.xml index 98931aa2..4937ccb7 100644 --- a/doc/doap.xml +++ b/doc/doap.xml @@ -179,6 +179,9 @@ + 1.1 + 0.1 + mod_time @@ -387,7 +390,9 @@ 2.0 + complete 0.1 + mod_time -- cgit v1.2.3 From 67cf276ba2e23710c9ab2abd7e2f7b377aab1070 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 29 Jun 2020 12:51:28 +0100 Subject: util.dbuffer: If no bytes parameter passed to read, return remainder of frontmost chunk --- spec/util_dbuffer_spec.lua | 15 +++++++++++++++ util/dbuffer.lua | 9 +++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/spec/util_dbuffer_spec.lua b/spec/util_dbuffer_spec.lua index 1a0c2992..c4fa22b2 100644 --- a/spec/util_dbuffer_spec.lua +++ b/spec/util_dbuffer_spec.lua @@ -21,6 +21,21 @@ describe("util.dbuffer", function () end); end); + describe(":read", function () + it("supports optional bytes parameter", function () + -- should return the frontmost chunk + local b = dbuffer.new(); + assert.truthy(b:write("hello")); + assert.truthy(b:write(" ")); + assert.truthy(b:write("world")); + assert.equal("h", b:read(1)); + + assert.equal("ello", b:read()); + assert.equal(" ", b:read()); + assert.equal("world", b:read()); + end); + end); + describe(":discard", function () local b = dbuffer.new(); it("works", function () diff --git a/util/dbuffer.lua b/util/dbuffer.lua index c38a16f5..63dca750 100644 --- a/util/dbuffer.lua +++ b/util/dbuffer.lua @@ -24,6 +24,9 @@ function dbuffer_methods:read_chunk(requested_bytes) if not chunk then return; end local chunk_length = #chunk; local remaining_chunk_length = chunk_length - consumed; + if not requested_bytes then + requested_bytes = remaining_chunk_length; + end if remaining_chunk_length <= requested_bytes then self.front_consumed = 0; self._length = self._length - remaining_chunk_length; @@ -41,12 +44,14 @@ end function dbuffer_methods:read(requested_bytes) local chunks; - if requested_bytes > self._length then + if requested_bytes and requested_bytes > self._length then return nil; end local chunk, read_bytes = self:read_chunk(requested_bytes); - if chunk then + if not requested_bytes then + return chunk; + elseif chunk then requested_bytes = requested_bytes - read_bytes; if requested_bytes == 0 then -- Already read everything we need return chunk; -- cgit v1.2.3 From 99b26ab45c54e677762b62cd0a1ca7bd4a6d7380 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 29 Jun 2020 16:37:58 +0200 Subject: util.timer: Defer to selected net.server if it implements this API E.g. net.server_epoll is very close and could easily be adapted to support this. --- util/timer.lua | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/util/timer.lua b/util/timer.lua index 4670e196..cbfc1992 100644 --- a/util/timer.lua +++ b/util/timer.lua @@ -16,6 +16,11 @@ local tostring = tostring; local xpcall = require "util.xpcall".xpcall; local math_max = math.max; +if server.timer then + -- The selected net.server implements this API, so defer to that + return server.timer; +end + local _ENV = nil; -- luacheck: std none -- cgit v1.2.3 From 927ef9f2f233b616d66e736dc19cd7e4d62dfcc7 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 29 Jun 2020 16:42:16 +0200 Subject: net.server_epoll: Make API-compatible with util.timer --- net/server_epoll.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index f4c14e13..fbe11401 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -99,9 +99,9 @@ local function reschedule(t, time) end -- Add relative timer -local function addtimer(timeout, f) +local function addtimer(timeout, f, param) local time = monotonic() + timeout; - local timer = { time, f, close = closetimer, reschedule = reschedule, id = nil }; + local timer = { time, f, param, close = closetimer, reschedule = reschedule, id = nil }; timer.id = timers:insert(timer, time); return timer; end @@ -121,7 +121,7 @@ local function runtimers(next_delay, min_wait) end local _, timer = timers:pop(); - local ok, ret = pcall(timer[2], now); + local ok, ret = pcall(timer[2], now, timer, timer[3]); if ok and type(ret) == "number" then local next_time = elapsed+ret; timer[1] = next_time; -- cgit v1.2.3 From ee10afcfabfce801deb7b07882674264f3892390 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 29 Jun 2020 17:13:05 +0200 Subject: net.server_epoll: Signal API-compatibilty with util.timer Reduces the overhead of having both util.timer and the timer handling here, since they are very similar and now API-compatible. --- net/server_epoll.lua | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index fbe11401..b7a11e8b 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -896,6 +896,12 @@ return { addserver = addserver; addclient = addclient; add_task = addtimer; + timer = { + -- API-compatible with util.timer + add_task = addtimer; + stop = closetimer; + reschedule = reschedule; + }; listen = listen; loop = loop; closeall = closeall; -- cgit v1.2.3 From 2b81c3b50f78236db6cf91d80eaee161de384dc9 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 29 Jun 2020 20:13:12 +0200 Subject: net.server_epoll: Remove unused time field from timer objects Unused since the move to util.indexedbheap in c8c3f2eba898 --- net/server_epoll.lua | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index b7a11e8b..f65be224 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -87,21 +87,19 @@ local timers = indexedbheap.create(); local function noop() end local function closetimer(t) - t[1] = 0; - t[2] = noop; + t[1] = noop; timers:remove(t.id); end local function reschedule(t, time) time = monotonic() + time; - t[1] = time; timers:reprioritize(t.id, time); end -- Add relative timer local function addtimer(timeout, f, param) local time = monotonic() + timeout; - local timer = { time, f, param, close = closetimer, reschedule = reschedule, id = nil }; + local timer = { f, param, close = closetimer, reschedule = reschedule, id = nil }; timer.id = timers:insert(timer, time); return timer; end @@ -121,10 +119,9 @@ local function runtimers(next_delay, min_wait) end local _, timer = timers:pop(); - local ok, ret = pcall(timer[2], now, timer, timer[3]); + local ok, ret = pcall(timer[1], now, timer, timer[2]); if ok and type(ret) == "number" then local next_time = elapsed+ret; - timer[1] = next_time; timers:insert(timer, next_time); end -- cgit v1.2.3 From a1ae266ff7913c4dbc50856ab191c15e92bd6e8a Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 29 Jun 2020 20:23:59 +0200 Subject: net.server_epoll: Optimize away table allocation for timer objects --- net/server_epoll.lua | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index f65be224..e4bf7708 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -86,22 +86,27 @@ local fds = createtable(10, 0); -- FD -> conn local timers = indexedbheap.create(); local function noop() end -local function closetimer(t) - t[1] = noop; - timers:remove(t.id); +local function closetimer(id) + timers:remove(id); end -local function reschedule(t, time) +local function reschedule(id, time) time = monotonic() + time; - timers:reprioritize(t.id, time); + timers:reprioritize(id, time); end -- Add relative timer local function addtimer(timeout, f, param) local time = monotonic() + timeout; - local timer = { f, param, close = closetimer, reschedule = reschedule, id = nil }; - timer.id = timers:insert(timer, time); - return timer; + if param ~= nil then + local timer_callback = f + function f(current_time, timer_id) + local t = timer_callback(current_time, timer_id, param) + return t; + end + end + local id = timers:insert(f, time); + return id; end -- Run callbacks of expired timers @@ -118,8 +123,8 @@ local function runtimers(next_delay, min_wait) break; end - local _, timer = timers:pop(); - local ok, ret = pcall(timer[1], now, timer, timer[2]); + local _, timer, id = timers:pop(); + local ok, ret = pcall(timer, now, id); if ok and type(ret) == "number" then local next_time = elapsed+ret; timers:insert(timer, next_time); @@ -259,14 +264,14 @@ end function interface:setreadtimeout(t) if t == false then if self._readtimeout then - self._readtimeout:close(); + closetimer(self._readtimeout); self._readtimeout = nil; end return end t = t or cfg.read_timeout; if self._readtimeout then - self._readtimeout:reschedule(t); + reschedule(self._readtimeout, t); else self._readtimeout = addtimer(t, function () if self:on("readtimeout") then @@ -285,14 +290,14 @@ end function interface:setwritetimeout(t) if t == false then if self._writetimeout then - self._writetimeout:close(); + closetimer(self._writetimeout); self._writetimeout = nil; end return end t = t or cfg.send_timeout; if self._writetimeout then - self._writetimeout:reschedule(t); + reschedule(self._writetimeout, t); else self._writetimeout = addtimer(t, function () self:noise("Write timeout"); @@ -663,7 +668,8 @@ end function interface:pausefor(t) self:noise("Pause for %fs", t); if self._pausefor then - self._pausefor:close(); + closetimer(self._pausefor); + self._pausefor = nil; end if t == false then return; end self:set(false); -- cgit v1.2.3 From 64614cb420301e20e2c385acc3fbebf3b254a443 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 29 Jun 2020 21:43:51 +0200 Subject: mod_admin_shell: Fix debug:timers to handle net.server native timers --- plugins/mod_admin_shell.lua | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/plugins/mod_admin_shell.lua b/plugins/mod_admin_shell.lua index 8f966e6e..89e4049f 100644 --- a/plugins/mod_admin_shell.lua +++ b/plugins/mod_admin_shell.lua @@ -1269,14 +1269,24 @@ function def_env.debug:timers() local h, params = add_task.h, add_task.params; if h then print("-- util.timer"); + elseif server.timer then + print("-- net.server.timer"); + h = server.timer.add_task.timers; + end + if h then for i, id in ipairs(h.ids) do - if not params[id] then - print(os.date("%F %T", math.floor(h.priorities[i])), h.items[id]); - elseif not params[id].callback then - print(os.date("%F %T", math.floor(h.priorities[i])), h.items[id], unpack(params[id])); - else - print(os.date("%F %T", math.floor(h.priorities[i])), params[id].callback, unpack(params[id])); + local t, cb = h.priorities[i], h.items[id]; + if not params then + local param = cb.param; + if param then + cb = param.callback; + else + cb = cb.timer_callback or cb; + end + elseif params[id] then + cb = params[id].callback or cb; end + print(os.date("%F %T", math.floor(t)), cb); end end if server.event_base then -- cgit v1.2.3 From 8ac1c4193c3a63b85e45c08b14cd63a29c5c2fc9 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 30 Jun 2020 02:31:29 +0200 Subject: net.server_epoll: Expose way to turn monotonic time into wall clock time --- net/server_epoll.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index e4bf7708..c5d5ec6c 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -904,6 +904,9 @@ return { add_task = addtimer; stop = closetimer; reschedule = reschedule; + to_absolute_time = function (t) + return t-monotonic()+realtime(); + end; }; listen = listen; loop = loop; -- cgit v1.2.3 From 7fb1dd361bf747dc02e2b25ae521ffc6670551f5 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 30 Jun 2020 02:31:57 +0200 Subject: mod_admin_shell: Handle server_epoll using monotonic time internally --- plugins/mod_admin_shell.lua | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/plugins/mod_admin_shell.lua b/plugins/mod_admin_shell.lua index 89e4049f..2db0cbb0 100644 --- a/plugins/mod_admin_shell.lua +++ b/plugins/mod_admin_shell.lua @@ -1267,11 +1267,18 @@ function def_env.debug:timers() local print = self.session.print; local add_task = require"util.timer".add_task; local h, params = add_task.h, add_task.params; + local function normalize_time(t) + return t; + end + local function format_time(t) + return os.date("%F %T", math.floor(normalize_time(t))); + end if h then print("-- util.timer"); elseif server.timer then print("-- net.server.timer"); h = server.timer.add_task.timers; + normalize_time = server.timer.to_absolute_time or normalize_time; end if h then for i, id in ipairs(h.ids) do @@ -1286,7 +1293,7 @@ function def_env.debug:timers() elseif params[id] then cb = params[id].callback or cb; end - print(os.date("%F %T", math.floor(t)), cb); + print(format_time(t), cb); end end if server.event_base then @@ -1301,7 +1308,7 @@ function def_env.debug:timers() if h then local next_time = h:peek(); if next_time then - return true, os.date("Next event at %F %T (in %%.6fs)", math.floor(next_time)):format(next_time - time.now()); + return true, ("Next event at %s (in %.6fs)"):format(format_time(next_time), normalize_time(next_time) - time.now()); end end return true; -- cgit v1.2.3 From 6baa04b056c6090a1b236eef246eed914c91da83 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 30 Jun 2020 17:33:48 +0200 Subject: net.server_epoll: Report errors in timers --- net/server_epoll.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index c5d5ec6c..64f79921 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -128,6 +128,8 @@ local function runtimers(next_delay, min_wait) if ok and type(ret) == "number" then local next_time = elapsed+ret; timers:insert(timer, next_time); + elseif not ok then + log("error", "Error in timer: %s", ret); end peek = timers:peek(); -- cgit v1.2.3 From 0a071df2d476cabe6153b250189ca6fa0e8a1762 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 30 Jun 2020 17:34:39 +0200 Subject: net.server_epoll: ... and include a traceback --- net/server_epoll.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index 64f79921..f102f806 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -124,7 +124,7 @@ local function runtimers(next_delay, min_wait) end local _, timer, id = timers:pop(); - local ok, ret = pcall(timer, now, id); + local ok, ret = xpcall(timer, traceback, now, id); if ok and type(ret) == "number" then local next_time = elapsed+ret; timers:insert(timer, next_time); -- cgit v1.2.3 From 7acd39cdbef144ecf3f32d4237de945c188fd5be Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 30 Jun 2020 17:35:07 +0200 Subject: net.server_epoll: Allow setting a custom error handler for listener This lets plugins handle errors in some custom way, should they wish to. --- net/server_epoll.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index f102f806..5ad20371 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -192,7 +192,8 @@ function interface:on(what, ...) self:noise("Missing listener 'on%s'", what); -- uncomment for development and debugging return; end - local ok, err = xpcall(listener, traceback, self, ...); + local onerror = self.listeners.onerror or traceback; + local ok, err = xpcall(listener, onerror, self, ...); if not ok then if cfg.fatal_errors then self:error("Closing due to error calling on%s: %s", what, err); -- cgit v1.2.3 From 8b9da402600ec5cea44968b05bfe0ccf92cf0999 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 30 Jun 2020 18:31:48 +0200 Subject: net.server_epoll: Add setting for turning off callback protections Might improve (CPU) performance at the risk of triggering top level errors. --- net/server_epoll.lua | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index 5ad20371..f767db78 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -74,6 +74,9 @@ local default_config = { __index = { -- Whether to kill connections in case of callback errors. fatal_errors = false; + -- Or disable protection (like server_select) for potential performance gains + protect_listeners = true; + -- Attempt writes instantly opportunistic_writes = false; }}; @@ -192,6 +195,9 @@ function interface:on(what, ...) self:noise("Missing listener 'on%s'", what); -- uncomment for development and debugging return; end + if not cfg.protect_listeners then + return listener(self, ...); + end local onerror = self.listeners.onerror or traceback; local ok, err = xpcall(listener, onerror, self, ...); if not ok then -- cgit v1.2.3 From 18525691a4b86ceefd7627abb8744c1f4d4fb2f0 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 2 Jul 2020 19:03:59 +0200 Subject: mod_storage_sql: Measure hits/misses on the item count cache A cache miss can be expensive so having numbers on how often this occurs may be valuable. --- plugins/mod_storage_sql.lua | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/plugins/mod_storage_sql.lua b/plugins/mod_storage_sql.lua index 67e7ad17..8fc92410 100644 --- a/plugins/mod_storage_sql.lua +++ b/plugins/mod_storage_sql.lua @@ -153,6 +153,9 @@ end local archive_item_limit = module:get_option_number("storage_archive_item_limit"); local archive_item_count_cache = cache.new(module:get_option("storage_archive_item_limit_cache_size", 1000)); +local item_count_cache_hit = module:measure("item_count_cache_hit", "rate"); +local item_count_cache_miss = module:measure("item_count_cache_miss", "rate") + -- luacheck: ignore 512 431/user 431/store 431/err local map_store = {}; map_store.__index = map_store; @@ -286,6 +289,7 @@ function archive_store:append(username, key, value, when, with) local cache_key = jid_join(username, host, store); local item_count = archive_item_count_cache:get(cache_key); if not item_count then + item_count_cache_miss(); local ok, ret = engine:transaction(function() local count_sql = [[ SELECT COUNT(*) FROM "prosodyarchive" @@ -303,6 +307,8 @@ function archive_store:append(username, key, value, when, with) return nil, "Failure while checking quota"; end archive_item_count_cache:set(cache_key, item_count); + else + item_count_cache_hit(); end if archive_item_limit then @@ -408,6 +414,7 @@ function archive_store:find(username, query) local user,store = username,self.store; local cache_key = jid_join(username, host, self.store); local total = archive_item_count_cache:get(cache_key); + (total and item_count_cache_hit or item_count_cache_miss)(); if total ~= nil and query.limit == 0 and query.start == nil and query.with == nil and query["end"] == nil and query.key == nil then return noop, total; end -- cgit v1.2.3 From 26f741b19be4a9b0d50df4b2e516560c8224a371 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 8 Jul 2020 20:11:49 +0200 Subject: net.cqueues: Switch to server.watchfd for main loop integration Why? Just look at all that code deleted! watchfd is the prefered way to poll things that expose FDs for this purpose, altho it was added after net.cqueues. --- net/cqueues.lua | 48 ++---------------------------------------------- 1 file changed, 2 insertions(+), 46 deletions(-) diff --git a/net/cqueues.lua b/net/cqueues.lua index 8c4c756f..84d59f15 100644 --- a/net/cqueues.lua +++ b/net/cqueues.lua @@ -16,55 +16,11 @@ local cq; if server.cq then -- server provides cqueues object cq = server.cq; -elseif server.get_backend() == "select" and server._addtimer then -- server_select +elseif server.watchfd then cq = cqueues.new(); - local function step() + server.watchfd(cq:pollfd(), function () assert(cq:loop(0)); - end - - -- Use wrapclient (as wrapconnection isn't exported) to get server_select to watch cq fd - local handler = server.wrapclient({ - getfd = function() return cq:pollfd(); end; - settimeout = function() end; -- Method just needs to exist - close = function() end; -- Need close method for 'closeall' - }, nil, nil, {}); - - -- Only need to listen for readable; cqueues handles everything under the hood - -- readbuffer is called when `select` notes an fd as readable - handler.readbuffer = step; - - -- Use server_select low lever timer facility, - -- this callback gets called *every* time there is a timeout in the main loop - server._addtimer(function(current_time) - -- This may end up in extra step()'s, but cqueues handles it for us. - step(); - return cq:timeout(); end); -elseif server.event and server.base then -- server_event - cq = cqueues.new(); - -- Only need to listen for readable; cqueues handles everything under the hood - local EV_READ = server.event.EV_READ; - -- Convert a cqueues timeout to an acceptable timeout for luaevent - local function luaevent_safe_timeout(cq) - local t = cq:timeout(); - -- if you give luaevent 0 or nil, it re-uses the previous timeout. - if t == 0 then - t = 0.000001; -- 1 microsecond is the smallest that works (goes into a `struct timeval`) - elseif t == nil then -- pick something big if we don't have one - t = 0x7FFFFFFF; -- largest 32bit int - end - return t - end - local event_handle; - event_handle = server.base:addevent(cq:pollfd(), EV_READ, function(e) - -- Need to reference event_handle or this callback will get collected - -- This creates a circular reference that can only be broken if event_handle is manually :close()'d - local _ = event_handle; - -- Run as many cqueues things as possible (with a timeout of 0) - -- If an error is thrown, it will break the libevent loop; but prosody resumes after logging a top level error - assert(cq:loop(0)); - return EV_READ, luaevent_safe_timeout(cq); - end, luaevent_safe_timeout(cq)); else error "NYI" end -- cgit v1.2.3 From 6a30c93ed3ca83c7505a1c7f026e692149a03240 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 8 Jul 2020 20:22:48 +0200 Subject: luacheck: Remove exception for net.cqueues since it is now clean --- .luacheckrc | 1 - 1 file changed, 1 deletion(-) diff --git a/.luacheckrc b/.luacheckrc index bb4568e6..cbfcef8d 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -135,7 +135,6 @@ if os.getenv("PROSODY_STRICT_LINT") ~= "1" then "fallbacks/bit.lua"; "fallbacks/lxp.lua"; - "net/cqueues.lua"; "net/dns.lua"; "net/server_select.lua"; -- cgit v1.2.3 From 14cdec45c594346fe87eb9a5f76e08f3924aba48 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 8 Jul 2020 21:39:10 +0200 Subject: util.indexedbheap: Add failing test case for #1572 This approximates what happens if you add a timer far in the future, then reschedule it to right now. --- spec/util_indexedbheap_spec.lua | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 spec/util_indexedbheap_spec.lua diff --git a/spec/util_indexedbheap_spec.lua b/spec/util_indexedbheap_spec.lua new file mode 100644 index 00000000..a76f94cb --- /dev/null +++ b/spec/util_indexedbheap_spec.lua @@ -0,0 +1,15 @@ +local ibh = require"util.indexedbheap"; +local h +setup(function () + h = ibh.create(); +end) +describe("util.indexedbheap", function () + pending("item can be moved from end to top", function () + h:insert("a", 1); + h:insert("b", 2); + h:insert("c", 3); + local id = h:insert("*", 10); + h:reprioritize(id, 0); + assert.same({ 0, "*", id }, { h:pop() }); + end) +end); -- cgit v1.2.3 From f2ae89296c1f2c4a2b20d259d40ab3fe14b4a87d Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 8 Jul 2020 22:01:19 +0200 Subject: net.cqueues: Fix resuming after timeouts net.cqueues previously relied on timers instead of fd events sometimes. Under net.server_select, it would have called cq:loop() on every iteration of the main loop, which was probably not optimal. --- net/cqueues.lua | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/net/cqueues.lua b/net/cqueues.lua index 84d59f15..65d2a019 100644 --- a/net/cqueues.lua +++ b/net/cqueues.lua @@ -9,6 +9,7 @@ local server = require "net.server"; local cqueues = require "cqueues"; +local timer = require "util.timer"; assert(cqueues.VERSION >= 20150113, "cqueues newer than 20150113 required") -- Create a single top level cqueue @@ -18,8 +19,21 @@ if server.cq then -- server provides cqueues object cq = server.cq; elseif server.watchfd then cq = cqueues.new(); + local timeout = timer.add_task(cq:timeout() or 0, function () + -- FIXME It should be enough to reschedule this timeout instead of replacing it, but this does not work. See https://issues.prosody.im/1572 + assert(cq:loop(0)); + return cq:timeout(); + end); server.watchfd(cq:pollfd(), function () assert(cq:loop(0)); + local t = cq:timeout(); + if t then + timer.stop(timeout); + timeout = timer.add_task(cq:timeout(), function () + assert(cq:loop(0)); + return cq:timeout(); + end); + end end); else error "NYI" -- cgit v1.2.3 From cd300d028561e1761fe13a8ad88bf5ff05327b76 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 8 Jul 2020 23:22:28 +0200 Subject: coding_style: Replace mention of git with hg We use Mercurial, not git! --- doc/coding_style.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/coding_style.md b/doc/coding_style.md index f9a10ece..2635fa2f 100644 --- a/doc/coding_style.md +++ b/doc/coding_style.md @@ -586,7 +586,7 @@ local a = 1; local long_identifier = 2; ``` -> **Rationale:** This produces extra diffs which add noise to `git blame`. +> **Rationale:** This produces extra diffs which add noise to `hg annotate`. * Alignment is occasionally useful when logical correspondence is to be highlighted: -- cgit v1.2.3 From b15a51597b6aa5a93b467c75d19ea7e0172561bf Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 9 Jul 2020 02:17:49 +0200 Subject: scansion/prosody.cfg: Fix typo --- spec/scansion/prosody.cfg.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/scansion/prosody.cfg.lua b/spec/scansion/prosody.cfg.lua index e75bb74f..c3f56108 100644 --- a/spec/scansion/prosody.cfg.lua +++ b/spec/scansion/prosody.cfg.lua @@ -107,7 +107,7 @@ pidfile = "prosody.pid" VirtualHost "localhost" -hide_os_type = true -- absense tested for in version.scs +hide_os_type = true -- absence tested for in version.scs Component "conference.localhost" "muc" storage = "memory" -- cgit v1.2.3 From 37566b1cc38d34e84673d1a1d3d979df6a2bade0 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 10 Jul 2020 03:13:27 +0200 Subject: prosodyctl about: Use library function for sorted listing of lua modules Code reuse and one less module to import is nice. --- prosodyctl | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/prosodyctl b/prosodyctl index 6b82e678..b5361d95 100755 --- a/prosodyctl +++ b/prosodyctl @@ -378,8 +378,7 @@ function commands.about(arg) end local pwd = "."; - local array = require "util.array"; - local keys = require "util.iterators".keys; + local sorted_pairs = require "util.iterators".sorted_pairs; local hg = require"util.mercurial"; local relpath = configmanager.resolve_relative_path; @@ -461,9 +460,8 @@ function commands.about(arg) end module_versions["libunbound"] = lunbound._LIBVER; end - local sorted_keys = array.collect(keys(module_versions)):sort(); - for _, name in ipairs(sorted_keys) do - print(name..":"..string.rep(" ", longest_name-#name), module_versions[name]); + for name, version in sorted_pairs(module_versions) do + print(name..":"..string.rep(" ", longest_name-#name), version); end print(""); end -- cgit v1.2.3 From 6e59b775022a6c385ce878898bf08a9def1f363c Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 10 Jul 2020 03:23:10 +0200 Subject: prosodyctl about: Split out libraries into a separate section Currently libevent and libunbound would show up under Lua modules but they are not, so a separate section seems more appropriate. --- prosodyctl | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/prosodyctl b/prosodyctl index b5361d95..d220355c 100755 --- a/prosodyctl +++ b/prosodyctl @@ -438,7 +438,7 @@ function commands.about(arg) print(""); print("# Lua module versions"); local module_versions, longest_name = {}, 8; - local luaevent =dependencies.softreq"luaevent"; + local library_versions = {}; dependencies.softreq"ssl"; dependencies.softreq"DBI"; local lunbound = dependencies.softreq"lunbound"; @@ -451,19 +451,24 @@ function commands.about(arg) module_versions[name] = module._VERSION; end end - if luaevent then - module_versions["libevent"] = luaevent.core.libevent_version(); - end if lunbound then if not module_versions["lunbound"] then module_versions["lunbound"] = "<= 0.5"; end - module_versions["libunbound"] = lunbound._LIBVER; + library_versions["libunbound"] = lunbound._LIBVER; end for name, version in sorted_pairs(module_versions) do print(name..":"..string.rep(" ", longest_name-#name), version); end print(""); + print("# library versions"); + if require "net.server".event_base then + library_versions["libevent"] = require"luaevent".core.libevent_version(); + end + for name, version in sorted_pairs(library_versions) do + print(name..":"..string.rep(" ", longest_name-#name), version); + end + print(""); end function commands.reload(arg) -- cgit v1.2.3 From c80ef421128f58dcfdce656c90a7d9daa918ae39 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 10 Jul 2020 03:29:02 +0200 Subject: prosodyctl about: Substitute better names for some Lua modules E.g. 'lxp' isn't that easy to guess that it's LuaExpat --- prosodyctl | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/prosodyctl b/prosodyctl index d220355c..27a44ec4 100755 --- a/prosodyctl +++ b/prosodyctl @@ -441,10 +441,19 @@ function commands.about(arg) local library_versions = {}; dependencies.softreq"ssl"; dependencies.softreq"DBI"; + local friendly_names = { + DBI = "LuaDBI"; + lfs = "LuaFileSystem"; + lunbound = "luaunbound"; + lxp = "LuaExpat"; + socket = "LuaSocket"; + ssl = "LuaSec"; + }; local lunbound = dependencies.softreq"lunbound"; for name, module in pairs(package.loaded) do if type(module) == "table" and rawget(module, "_VERSION") - and name ~= "_G" and not name:match("%.") then + and name ~= "_G" and not name:match("%.") then + name = friendly_names[name] or name; if #name > longest_name then longest_name = #name; end -- cgit v1.2.3 From 947c136c95d5767f83127ee62c30f13f14aea615 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 10 Jul 2020 03:29:06 +0200 Subject: prosodyctl about: Strip name from lua module _VERSION Some modules have _VERSION = "LuaModule x.y.z", so it is a bit weird to show the name twice. --- prosodyctl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/prosodyctl b/prosodyctl index 27a44ec4..fabfe04f 100755 --- a/prosodyctl +++ b/prosodyctl @@ -457,7 +457,11 @@ function commands.about(arg) if #name > longest_name then longest_name = #name; end - module_versions[name] = module._VERSION; + local mod_version = module._VERSION; + if tostring(mod_version):sub(1, #name+1) == name .. " " then + mod_version = mod_version:sub(#name+2); + end + module_versions[name] = mod_version; end end if lunbound then -- cgit v1.2.3 From f537afbb8e245beea12af9afa0de12744c6c595a Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 10 Jul 2020 03:33:37 +0200 Subject: prosodyctl about: Show longer name for luaunbound MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit luaunbound, lunbound, lua-unbound ... "kärt barn har många namn" --- prosodyctl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/prosodyctl b/prosodyctl index fabfe04f..dcd9e5a2 100755 --- a/prosodyctl +++ b/prosodyctl @@ -465,8 +465,8 @@ function commands.about(arg) end end if lunbound then - if not module_versions["lunbound"] then - module_versions["lunbound"] = "<= 0.5"; + if not module_versions["luaunbound"] then + module_versions["luaunbound"] = "0.5 (?)"; end library_versions["libunbound"] = lunbound._LIBVER; end -- cgit v1.2.3 From 9eb8214030a32056841d8cc537430e7dccab1c33 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 12 Jul 2020 23:28:04 +0200 Subject: configure: Pass compiler flag to enable ICU only when building util.encodings Passing it in CFLAGS applied to all modules, which was not needed. --- configure | 3 ++- util-src/GNUmakefile | 1 + util-src/makefile | 2 ++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/configure b/configure index 2b58efe5..abb5ffef 100755 --- a/configure +++ b/configure @@ -524,7 +524,7 @@ fi if [ "$IDN_LIBRARY" = "icu" ] then IDNA_LIBS="$ICU_FLAGS" - CFLAGS="$CFLAGS -DUSE_STRINGPREP_ICU" + IDNA_FLAGS="-DUSE_STRINGPREP_ICU" fi if [ "$IDN_LIBRARY" = "idn" ] then @@ -569,6 +569,7 @@ LUA_INCDIR=$LUA_INCDIR LUA_LIBDIR=$LUA_LIBDIR LUA_BINDIR=$LUA_BINDIR IDN_LIB=$IDN_LIB +IDNA_FLAGS=$IDNA_FLAGS IDNA_LIBS=$IDNA_LIBS OPENSSL_LIBS=$OPENSSL_LIBS CFLAGS=$CFLAGS diff --git a/util-src/GNUmakefile b/util-src/GNUmakefile index 054c9201..e9e06355 100644 --- a/util-src/GNUmakefile +++ b/util-src/GNUmakefile @@ -24,6 +24,7 @@ install: $(ALL) clean: rm -f $(ALL) $(patsubst %.so,%.o,$(ALL)) +encodings.o: CFLAGS+=$(IDNA_FLAGS) encodings.so: LDLIBS+=$(IDNA_LIBS) hashes.so: LDLIBS+=$(OPENSSL_LIBS) diff --git a/util-src/makefile b/util-src/makefile index 700633b4..2db415df 100644 --- a/util-src/makefile +++ b/util-src/makefile @@ -23,6 +23,8 @@ install: $(ALL) clean: rm -f $(ALL) $(patsubst %.so,%.o,$(ALL)) +encodings.o: encodings.c + $(CC) $(CFLAGS) $(IDNA_FLAGS) -c -o $@ $< encodings.so: encodings.o $(LD) $(LDFLAGS) -o $@ $< $(LDLIBS) $(IDNA_LIBS) -- cgit v1.2.3 From 1c0950bc362d76da12c33ea7e0f87545556c2a82 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 27 May 2020 19:47:52 +0200 Subject: mod_server_contact_info: Add status-addresses field XEP-0157 version 1.1.0 --- doc/doap.xml | 2 +- plugins/mod_server_contact_info.lua | 1 + spec/scansion/prosody.cfg.lua | 1 + spec/scansion/server_contact_info.scs | 3 +++ 4 files changed, 6 insertions(+), 1 deletion(-) diff --git a/doc/doap.xml b/doc/doap.xml index 4937ccb7..ef1437e7 100644 --- a/doc/doap.xml +++ b/doc/doap.xml @@ -284,7 +284,7 @@ - 1.0.1 + 1.1.0 0.10 diff --git a/plugins/mod_server_contact_info.lua b/plugins/mod_server_contact_info.lua index 0110ceaf..9c916ebc 100644 --- a/plugins/mod_server_contact_info.lua +++ b/plugins/mod_server_contact_info.lua @@ -16,6 +16,7 @@ local form_layout = require "util.dataforms".new({ { name = "feedback", var = "feedback-addresses", type = "list-multi" }, { name = "sales", var = "sales-addresses", type = "list-multi" }, { name = "security", var = "security-addresses", type = "list-multi" }, + { name = "status", var = "status-addresses", type = "list-multi" }, { name = "support", var = "support-addresses", type = "list-multi" }, }); diff --git a/spec/scansion/prosody.cfg.lua b/spec/scansion/prosody.cfg.lua index c3f56108..0bf68ddb 100644 --- a/spec/scansion/prosody.cfg.lua +++ b/spec/scansion/prosody.cfg.lua @@ -73,6 +73,7 @@ contact_info = { feedback = { "http://localhost/feedback.html", "mailto:feedback@localhost", "xmpp:feedback@localhost" }; sales = { "xmpp:sales@localhost" }; security = { "xmpp:security@localhost" }; + status = { "gopher://status.localhost" }; support = { "https://localhost/support.html", "xmpp:support@localhost" }; } diff --git a/spec/scansion/server_contact_info.scs b/spec/scansion/server_contact_info.scs index 15537e11..6efc7fd1 100644 --- a/spec/scansion/server_contact_info.scs +++ b/spec/scansion/server_contact_info.scs @@ -42,6 +42,9 @@ Romeo receives: xmpp:security@localhost + + gopher://status.localhost + https://localhost/support.html xmpp:support@localhost -- cgit v1.2.3 From d89a99eb4320d5b2d6067fde78be6c5706cac4ab Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 16 Jul 2020 10:26:36 +0200 Subject: mod_proxy65: Log invalid greetings escaped instead of as base64 Makes it easier to see human-readable parts and thus identifying the garbage. Also consistent with mod_c2s and others. --- plugins/mod_proxy65.lua | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/plugins/mod_proxy65.lua b/plugins/mod_proxy65.lua index 29c821e2..aacf6309 100644 --- a/plugins/mod_proxy65.lua +++ b/plugins/mod_proxy65.lua @@ -12,7 +12,6 @@ module:set_global(); local jid_compare, jid_prep = require "util.jid".compare, require "util.jid".prep; local st = require "util.stanza"; local sha1 = require "util.hashes".sha1; -local b64 = require "util.encodings".base64.encode; local server = require "net.server"; local portmanager = require "core.portmanager"; @@ -45,7 +44,7 @@ function listener.onincoming(conn, data) end -- else error, unexpected input conn:write("\5\255"); -- send (SOCKS version 5, no acceptable method) conn:close(); - module:log("debug", "Invalid SOCKS5 greeting received: '%s'", b64(data)); + module:log("debug", "Invalid SOCKS5 greeting received: %q", data); else -- connection request --local head = string.char( 0x05, 0x01, 0x00, 0x03, 40 ); -- ( VER=5=SOCKS5, CMD=1=CONNECT, RSV=0=RESERVED, ATYP=3=DOMAIMNAME, SHA-1 size ) if #data == 47 and data:sub(1,5) == "\5\1\0\3\40" and data:sub(-2) == "\0\0" then @@ -67,7 +66,7 @@ function listener.onincoming(conn, data) else -- error, unexpected input conn:write("\5\1\0\3\0\0\0"); -- VER, REP, RSV, ATYP, BND.ADDR (sha), BND.PORT (2 Byte) conn:close(); - module:log("debug", "Invalid SOCKS5 negotiation received: '%s'", b64(data)); + module:log("debug", "Invalid SOCKS5 negotiation received: %q", data); end end end -- cgit v1.2.3 From f1fcdfc2467753cc0f1ba3f48a64401395cba9af Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 17 Jul 2020 08:29:03 +0200 Subject: mod_proxy65: Limit dump of invalid data to 300 bytes (like mod_c2s) --- plugins/mod_proxy65.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/mod_proxy65.lua b/plugins/mod_proxy65.lua index aacf6309..bac36b55 100644 --- a/plugins/mod_proxy65.lua +++ b/plugins/mod_proxy65.lua @@ -44,7 +44,7 @@ function listener.onincoming(conn, data) end -- else error, unexpected input conn:write("\5\255"); -- send (SOCKS version 5, no acceptable method) conn:close(); - module:log("debug", "Invalid SOCKS5 greeting received: %q", data); + module:log("debug", "Invalid SOCKS5 greeting received: %q", data:sub(1, 300)); else -- connection request --local head = string.char( 0x05, 0x01, 0x00, 0x03, 40 ); -- ( VER=5=SOCKS5, CMD=1=CONNECT, RSV=0=RESERVED, ATYP=3=DOMAIMNAME, SHA-1 size ) if #data == 47 and data:sub(1,5) == "\5\1\0\3\40" and data:sub(-2) == "\0\0" then @@ -66,7 +66,7 @@ function listener.onincoming(conn, data) else -- error, unexpected input conn:write("\5\1\0\3\0\0\0"); -- VER, REP, RSV, ATYP, BND.ADDR (sha), BND.PORT (2 Byte) conn:close(); - module:log("debug", "Invalid SOCKS5 negotiation received: %q", data); + module:log("debug", "Invalid SOCKS5 negotiation received: %q", data:sub(1, 300)); end end end -- cgit v1.2.3 From 0314b7063c4487231a71e3c16b326aeb4bb75032 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Mon, 29 Jun 2020 21:03:13 +0200 Subject: mod_register: Add a dependency on mod_watchregistrations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Spammers are a big hassle, hopefully this will make admins aware of them sooner than when they’ve already spammed a bunch. --- plugins/mod_register.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/mod_register.lua b/plugins/mod_register.lua index 763e2fd9..bec3fb5a 100644 --- a/plugins/mod_register.lua +++ b/plugins/mod_register.lua @@ -11,6 +11,7 @@ local allow_registration = module:get_option_boolean("allow_registration", false if allow_registration then module:depends("register_ibr"); + module:depends("watchregistrations"); end module:depends("user_account_management"); -- cgit v1.2.3 From 7c00bae93a5034e1fdcae5ed2052439fe995a709 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 25 Jul 2020 17:26:11 +0200 Subject: net.server_epoll: Log debug message when a connection errors on read It's confusingly quiet otherwise, even with maximum verboseness. Thanks perflyst --- net/server_epoll.lua | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index f767db78..32b2f032 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -405,6 +405,11 @@ function interface:onreadable() self:onincoming(partial, err); end if err ~= "timeout" then + if err == "closed" then + self:debug("Connection closed by remote"); + else + self:debug("Read error, closing (%s)", err); + end self:on("disconnect", err); self:destroy() return; -- cgit v1.2.3 From 64aa6a2a0e704221821cb53ccd90e19fabfa475e Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 1 Aug 2020 18:14:09 +0200 Subject: net.http.parser: Switch to util.dbuffer for buffering incoming data This is primarily a step towards saving uploads directly to files, tho this should hopefully be more efficient than collapsing the entire buffer to a single string every now and then. --- net/http/parser.lua | 110 +++++++++++++++++++++++----------------------------- 1 file changed, 49 insertions(+), 61 deletions(-) diff --git a/net/http/parser.lua b/net/http/parser.lua index b328bdfe..84b1e005 100644 --- a/net/http/parser.lua +++ b/net/http/parser.lua @@ -1,8 +1,8 @@ local tonumber = tonumber; local assert = assert; -local t_insert, t_concat = table.insert, table.concat; local url_parse = require "socket.url".parse; local urldecode = require "util.http".urldecode; +local dbuffer = require "util.dbuffer"; local function preprocess_path(path) path = urldecode((path:gsub("//+", "/"))); @@ -28,10 +28,13 @@ local httpstream = {}; function httpstream.new(success_cb, error_cb, parser_type, options_cb) local client = true; if not parser_type or parser_type == "server" then client = false; else assert(parser_type == "client", "Invalid parser type"); end - local buf, buflen, buftable = {}, 0, true; local bodylimit = tonumber(options_cb and options_cb().body_size_limit) or 10*1024*1024; + -- https://stackoverflow.com/a/686243 + -- Indiviual headers can be up to 16k? What madness? + local headlimit = tonumber(options_cb and options_cb().head_size_limit) or 10*1024; local buflimit = tonumber(options_cb and options_cb().buffer_size_limit) or bodylimit * 2; - local chunked, chunk_size, chunk_start; + local buffer = dbuffer.new(buflimit); + local chunked; local state = nil; local packet; local len; @@ -41,33 +44,26 @@ function httpstream.new(success_cb, error_cb, parser_type, options_cb) feed = function(_, data) if error then return nil, "parse has failed"; end if not data then -- EOF - if buftable then buf, buftable = t_concat(buf), false; end if state and client and not len then -- reading client body until EOF - packet.body = buf; + buffer:collapse(); + packet.body = buffer:read_chunk() or ""; success_cb(packet); - elseif buf ~= "" then -- unexpected EOF + state = nil; + elseif buffer:length() ~= 0 then -- unexpected EOF error = true; return error_cb("unexpected-eof"); end return; end - if buftable then - t_insert(buf, data); - else - buf = { buf, data }; - buftable = true; - end - buflen = buflen + #data; - if buflen > buflimit then error = true; return error_cb("max-buffer-size-exceeded"); end - while buflen > 0 do + if not buffer:write(data) then error = true; return error_cb("max-buffer-size-exceeded"); end + while buffer:length() > 0 do if state == nil then -- read request - if buftable then buf, buftable = t_concat(buf), false; end - local index = buf:find("\r\n\r\n", nil, true); + local index = buffer:sub(1, headlimit):find("\r\n\r\n", nil, true); if not index then return; end -- not enough data -- FIXME was reason_phrase meant to be passed on somewhere? local method, path, httpversion, status_code, reason_phrase; -- luacheck: ignore reason_phrase local first_line; local headers = {}; - for line in buf:sub(1,index+1):gmatch("([^\r\n]+)\r\n") do -- parse request + for line in buffer:read(index+3):gmatch("([^\r\n]+)\r\n") do -- parse request if first_line then local key, val = line:match("^([^%s:]+): *(.*)$"); if not key then error = true; return error_cb("invalid-header-line"); end -- TODO handle multi-line and invalid headers @@ -101,7 +97,7 @@ function httpstream.new(success_cb, error_cb, parser_type, options_cb) code = status_code; httpversion = httpversion; headers = headers; - body = have_body and "" or nil; + body = false; -- COMPAT the properties below are deprecated responseversion = httpversion; responseheaders = headers; @@ -126,60 +122,52 @@ function httpstream.new(success_cb, error_cb, parser_type, options_cb) path = path; httpversion = httpversion; headers = headers; - body = nil; + body = false; + body_sink = nil; }; end - buf = buf:sub(index + 4); - buflen = #buf; + if chunked then + packet.body_buffer = dbuffer.new(buflimit); + end state = true; end if state then -- read body - if client then - if chunked then - if chunk_start and buflen - chunk_start - 2 < chunk_size then - return; - end -- not enough data - if buftable then buf, buftable = t_concat(buf), false; end - if not buf:find("\r\n", nil, true) then - return; - end -- not enough data - if not chunk_size then - chunk_size, chunk_start = buf:match("^(%x+)[^\r\n]*\r\n()"); - chunk_size = chunk_size and tonumber(chunk_size, 16); - if not chunk_size then error = true; return error_cb("invalid-chunk-size"); end - end - if chunk_size == 0 and buf:find("\r\n\r\n", chunk_start-2, true) then - state, chunk_size = nil, nil; - buf = buf:gsub("^.-\r\n\r\n", ""); -- This ensure extensions and trailers are stripped - success_cb(packet); - elseif buflen - chunk_start - 2 >= chunk_size then -- we have a chunk - packet.body = packet.body..buf:sub(chunk_start, chunk_start + (chunk_size-1)); - buf = buf:sub(chunk_start + chunk_size + 2); - buflen = buflen - (chunk_start + chunk_size + 2 - 1); - chunk_size, chunk_start = nil, nil; - else -- Partial chunk remaining - break; + if chunked then + local chunk_header = buffer:sub(1, 512); -- XXX How large do chunk headers grow? + local chunk_size, chunk_start = chunk_header:match("^(%x+)[^\r\n]*\r\n()"); + if not chunk_size then return; end + chunk_size = chunk_size and tonumber(chunk_size, 16); + if not chunk_size then error = true; return error_cb("invalid-chunk-size"); end + if chunk_size == 0 and chunk_header:find("\r\n\r\n", chunk_start-2, true) then + local body_buffer = packet.body_buffer; + if body_buffer then + packet.body_buffer = nil; + body_buffer:collapse(); + packet.body = body_buffer:read_chunk() or ""; end - elseif len and buflen >= len then - if buftable then buf, buftable = t_concat(buf), false; end - if packet.code == 101 then - packet.body, buf, buflen, buftable = buf, {}, 0, true; - else - packet.body, buf = buf:sub(1, len), buf:sub(len + 1); - buflen = #buf; - end - state = nil; success_cb(packet); - else + + buffer:collapse(); + local buf = buffer:read_chunk(); + buf = buf:gsub("^.-\r\n\r\n", ""); -- This ensure extensions and trailers are stripped + buffer:write(buf); + state, chunked = nil, nil; + success_cb(packet); + elseif buffer:length() - chunk_start - 2 >= chunk_size then -- we have a chunk + buffer:discard(chunk_start - 1); -- TODO verify that it's not off-by-one + packet.body_buffer:write(buffer:read(chunk_size)); + buffer:discard(2); -- CRLF + else -- Partial chunk remaining break; end - elseif buflen >= len then - if buftable then buf, buftable = t_concat(buf), false; end - packet.body, buf = buf:sub(1, len), buf:sub(len + 1); - buflen = #buf; + elseif buffer:length() >= len then + assert(not chunked) + packet.body = buffer:read(len) or ""; state = nil; success_cb(packet); else break; end + else + break; end end end; -- cgit v1.2.3 From 91d2ab91086d2aebcbc4d47a5bce05c6cd3abdcb Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 1 Aug 2020 18:41:23 +0200 Subject: net.http.parser: Allow specifying sink for large request bodies This enables uses such as saving uploaded files directly to a file on disk or streaming parsing of payloads. See #726 --- net/http/parser.lua | 26 ++++++++++++++++++++++---- plugins/mod_http.lua | 9 +++++++++ spec/net_http_parser_spec.lua | 8 +++++--- 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/net/http/parser.lua b/net/http/parser.lua index 84b1e005..b8396518 100644 --- a/net/http/parser.lua +++ b/net/http/parser.lua @@ -88,8 +88,6 @@ function httpstream.new(success_cb, error_cb, parser_type, options_cb) if not first_line then error = true; return error_cb("invalid-status-line"); end chunked = have_body and headers["transfer-encoding"] == "chunked"; len = tonumber(headers["content-length"]); -- TODO check for invalid len - if len and len > bodylimit then error = true; return error_cb("content-length-limit-exceeded"); end - -- TODO ask a callback whether to proceed in case of large requests or Expect: 100-continue if client then -- FIXME handle '100 Continue' response (by skipping it) if not have_body then len = 0; end @@ -126,9 +124,17 @@ function httpstream.new(success_cb, error_cb, parser_type, options_cb) body_sink = nil; }; end - if chunked then + if len and len > bodylimit then + -- Early notification, for redirection + success_cb(packet); + if not packet.body_sink then error = true; return error_cb("content-length-limit-exceeded"); end + end + if chunked and not packet.body_sink then + success_cb(packet); + if not packet.body_sink then packet.body_buffer = dbuffer.new(buflimit); end + end state = true; end if state then -- read body @@ -154,11 +160,23 @@ function httpstream.new(success_cb, error_cb, parser_type, options_cb) success_cb(packet); elseif buffer:length() - chunk_start - 2 >= chunk_size then -- we have a chunk buffer:discard(chunk_start - 1); -- TODO verify that it's not off-by-one - packet.body_buffer:write(buffer:read(chunk_size)); + (packet.body_sink or packet.body_buffer):write(buffer:read(chunk_size)); buffer:discard(2); -- CRLF else -- Partial chunk remaining break; end + elseif packet.body_sink then + local chunk = buffer:read_chunk(len); + while chunk and len > 0 do + if packet.body_sink:write(chunk) then + len = len - #chunk; + chunk = buffer:read_chunk(len); + else + error = true; + return error_cb("body-sink-write-failure"); + end + end + if len == 0 then state = nil; success_cb(packet); end elseif buffer:length() >= len then assert(not chunked) packet.body = buffer:read(len) or ""; diff --git a/plugins/mod_http.lua b/plugins/mod_http.lua index cf63ecfb..54c6089b 100644 --- a/plugins/mod_http.lua +++ b/plugins/mod_http.lua @@ -160,6 +160,15 @@ function module.add_host(module) elseif event_name:sub(-1, -1) == "/" then module:hook_object_event(server, event_name:sub(1, -2), redir_handler, -1); end + do + -- COMPAT Modules not compatible with streaming uploads behave as before. + local _handler = handler; + function handler(event) -- luacheck: ignore 432/event + if event.request.body ~= false then + return _handler(event); + end + end + end if not app_handlers[event_name] then app_handlers[event_name] = { main = handler; diff --git a/spec/net_http_parser_spec.lua b/spec/net_http_parser_spec.lua index 8310a451..4ac3cab9 100644 --- a/spec/net_http_parser_spec.lua +++ b/spec/net_http_parser_spec.lua @@ -3,7 +3,9 @@ local http_parser = require "net.http.parser"; local function test_stream(stream, expect) local success_cb = spy.new(function (packet) assert.is_table(packet); - assert.is_equal(expect.body, packet.body); + if packet.body ~= false then + assert.is_equal(expect.body, packet.body); + end end); stream = stream:gsub("\n", "\r\n"); @@ -79,7 +81,7 @@ o ]], { - body = "Hello", count = 1; + body = "Hello", count = 2; } ); end); @@ -108,7 +110,7 @@ o ]], { - body = "Hello", count = 2; + body = "Hello", count = 3; } ); end); -- cgit v1.2.3 From 933c04882934fdc8633c93e706c8b28c977f776e Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 1 Aug 2020 18:41:30 +0200 Subject: mod_http: Add way to signal that a module supports streaming uploads Fixes #726 API: module:provides("http", { streaming_uploads = true; route = { PUT = function (event) event.request.body_sink = io.tmpfile(); return true; end } }) --- plugins/mod_http.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/mod_http.lua b/plugins/mod_http.lua index 54c6089b..1248a06c 100644 --- a/plugins/mod_http.lua +++ b/plugins/mod_http.lua @@ -138,6 +138,8 @@ function module.add_host(module) return ""; end + local streaming = event.item.streaming_uploads; + for key, handler in pairs(event.item.route or {}) do local event_name = get_http_event(host, app_path, key); if event_name then @@ -160,7 +162,7 @@ function module.add_host(module) elseif event_name:sub(-1, -1) == "/" then module:hook_object_event(server, event_name:sub(1, -2), redir_handler, -1); end - do + if not streaming then -- COMPAT Modules not compatible with streaming uploads behave as before. local _handler = handler; function handler(event) -- luacheck: ignore 432/event -- cgit v1.2.3 From 8fae7acf319d7ff48a12f62e208ddd62665c81ba Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 2 Aug 2020 00:22:57 +0200 Subject: mod_net_multiplex: Read no more than the max buffer size setting Otherwise the '*a' read mode applies, which under certain circumstances can read infinite amounts of data into memory. --- plugins/mod_net_multiplex.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_net_multiplex.lua b/plugins/mod_net_multiplex.lua index 849b22ee..42a41709 100644 --- a/plugins/mod_net_multiplex.lua +++ b/plugins/mod_net_multiplex.lua @@ -41,7 +41,7 @@ end local buffers = {}; -local listener = { default_mode = "*a" }; +local listener = { default_mode = max_buffer_len }; function listener.onconnect(conn) local sock = conn:socket(); -- cgit v1.2.3 From 7acd3092ecba0fde91d7e56770c47d7bf98dfc30 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 2 Aug 2020 00:24:54 +0200 Subject: mod_net_multiplex: Set read size/mode to that of the target listener Otherwise it would use the configured buffer size, or previously '*a'. Using the read size set by the listener seems more sensible. --- plugins/mod_net_multiplex.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/mod_net_multiplex.lua b/plugins/mod_net_multiplex.lua index 42a41709..ddd58463 100644 --- a/plugins/mod_net_multiplex.lua +++ b/plugins/mod_net_multiplex.lua @@ -2,6 +2,7 @@ module:set_global(); local array = require "util.array"; local max_buffer_len = module:get_option_number("multiplex_buffer_size", 1024); +local default_mode = module:get_option_number("network_default_read_size", 4096); local portmanager = require "core.portmanager"; @@ -52,6 +53,7 @@ function listener.onconnect(conn) module:log("debug", "Routing incoming connection to %s based on ALPN %q", service.name, selected_proto); local next_listener = service.listener; conn:setlistener(next_listener); + conn:set_mode(next_listener.default_mode or default_mode); local onconnect = next_listener.onconnect; if onconnect then return onconnect(conn) end end @@ -67,6 +69,7 @@ function listener.onincoming(conn, data) module:log("debug", "Routing incoming connection to %s", service.name); local next_listener = service.listener; conn:setlistener(next_listener); + conn:set_mode(next_listener.default_mode or default_mode); local onconnect = next_listener.onconnect; if onconnect then onconnect(conn) end return next_listener.onincoming(conn, buf); -- cgit v1.2.3 From 9287d929c220bd88fff5458f143f24512800a63e Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Thu, 13 Aug 2020 17:01:05 +0100 Subject: net.http.errors: Add new module for converting net.http errors to util.error objects --- net/http/errors.lua | 115 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 net/http/errors.lua diff --git a/net/http/errors.lua b/net/http/errors.lua new file mode 100644 index 00000000..ae45d5a8 --- /dev/null +++ b/net/http/errors.lua @@ -0,0 +1,115 @@ +-- This module returns a table that is suitable for use as a util.error registry, +-- and a function to return a util.error object given callback 'code' and 'body' +-- parameters. + +local codes = require "net.http.codes"; +local util_error = require "util.error"; + +local error_templates = { + -- This code is used by us to report a client-side or connection error. + -- Instead of using the code, use the supplied body text to get one of + -- the more detailed errors below. + [0] = { + code = 0, type = "cancel", condition = "internal-server-error"; + text = "Connection or internal error"; + }; + + -- These are net.http built-in errors, they are returned in + -- the body parameter when code == 0 + ["cancelled"] = { + code = 0, type = "cancel", condition = "remote-server-timeout"; + text = "Request cancelled"; + }; + ["connection-closed"] = { + code = 0, type = "wait", condition = "remote-server-timeout"; + text = "Connection closed"; + }; + ["certificate-chain-invalid"] = { + code = 0, type = "cancel", condition = "remote-server-timeout"; + text = "Server certificate not trusted"; + }; + ["certificate-verify-failed"] = { + code = 0, type = "cancel", condition = "remote-server-timeout"; + text = "Server certificate invalid"; + }; + ["connection failed"] = { + code = 0, type = "cancel", condition = "remote-server-not-found"; + text = "Connection failed"; + }; + ["invalid-url"] = { + code = 0, type = "modify", condition = "bad-request"; + text = "Invalid URL"; + }; + + -- This doesn't attempt to map every single HTTP code (not all have sane mappings), + -- but all the common ones should be covered. XEP-0086 was used as reference for + -- most of these. + [400] = { type = "modify", condition = "bad-request" }; + [401] = { type = "auth", condition = "not-authorized" }; + [402] = { type = "auth", condition = "payment-required" }; + [403] = { type = "auth", condition = "forbidden" }; + [404] = { type = "cancel", condition = "item-not-found" }; + [405] = { type = "cancel", condition = "not-allowed" }; + [406] = { type = "modify", condition = "not-acceptable" }; + [407] = { type = "auth", condition = "registration-required" }; + [408] = { type = "wait", condition = "remote-server-timeout" }; + [409] = { type = "cancel", condition = "conflict" }; + [410] = { type = "cancel", condition = "gone" }; + [411] = { type = "modify", condition = "bad-request" }; + [412] = { type = "cancel", condition = "conflict" }; + [413] = { type = "modify", condition = "resource-constraint" }; + [414] = { type = "modify", condition = "resource-constraint" }; + [415] = { type = "cancel", condition = "feature-not-implemented" }; + [416] = { type = "modify", condition = "bad-request" }; + + [422] = { type = "modify", condition = "bad-request" }; + [423] = { type = "wait", condition = "resource-constraint" }; + + [429] = { type = "wait", condition = "resource-constraint" }; + [431] = { type = "modify", condition = "resource-constraint" }; + [451] = { type = "auth", condition = "forbidden" }; + + [500] = { type = "wait", condition = "internal-server-error" }; + [501] = { type = "cancel", condition = "feature-not-implemented" }; + [502] = { type = "wait", condition = "remote-server-timeout" }; + [503] = { type = "cancel", condition = "service-unavailable" }; + [504] = { type = "wait", condition = "remote-server-timeout" }; + [507] = { type = "wait", condition = "resource-constraint" }; + [511] = { type = "auth", condition = "not-authorized" }; +}; + +for k, v in pairs(codes) do + if error_templates[k] then + error_templates[k].code = k; + error_templates[k].text = v; + else + error_templates[k] = { type = "cancel", condition = "undefined-condition", text = v, code = k }; + end +end + +setmetatable(error_templates, { + __index = function(_, k) + if type(k) ~= "number" then + return nil; + end + return { + type = "cancel"; + condition = "undefined-condition"; + text = codes[k] or (k.." Unassigned"); + code = k; + }; + end +}); + +local function new(code, body, context) + if code == 0 then + return util_error.new(body, context, error_templates); + else + return util_error.new(code, context, error_templates); + end +end + +return { + registry = error_templates; + new = new; +}; -- cgit v1.2.3 From 1c0e4300ab36e37cb2ba83e5dca9f9247785bed6 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 16 Aug 2020 12:55:55 +0200 Subject: util.dataforms: Convert media element sizes to avoid error on Lua 5.3 The stanza API does not accept number values and threw an error due to the height and width attributes of the media element (XEP-0221). This part had no test coverage previously, explaining why it was not discovered until now. --- spec/util_dataforms_spec.lua | 15 +++++++++++++++ util/dataforms.lua | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/spec/util_dataforms_spec.lua b/spec/util_dataforms_spec.lua index 085128d1..ae6098be 100644 --- a/spec/util_dataforms_spec.lua +++ b/spec/util_dataforms_spec.lua @@ -106,6 +106,21 @@ describe("util.dataforms", function () name = "text-single-field", value = "text-single-value", }, + { + -- XEP-0221 + -- TODO Validate the XML produced by this. + type = "text-single", + label = "text-single-with-media-label", + name = "text-single-with-media-field", + media = { + height = 24, + width = 32, + { + type = "image/png", + uri = "data:", + }, + }, + }, }); xform = some_form:form(); end); diff --git a/util/dataforms.lua b/util/dataforms.lua index 052d6a55..449c1a58 100644 --- a/util/dataforms.lua +++ b/util/dataforms.lua @@ -136,7 +136,7 @@ function form_t.form(layout, data, formtype) local media = field.media; if media then - form:tag("media", { xmlns = "urn:xmpp:media-element", height = media.height, width = media.width }); + form:tag("media", { xmlns = "urn:xmpp:media-element", height = ("%g"):format(media.height), width = ("%g"):format(media.width) }); for _, val in ipairs(media) do form:tag("uri", { type = val.type }):text(val.uri):up() end -- cgit v1.2.3 From 8de93db692033a56ab3a516c9aadf6b27060e2a7 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 16 Aug 2020 20:30:02 +0200 Subject: util.dataforms: Add more XEP-0211 media element test coverage --- spec/util_dataforms_spec.lua | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/spec/util_dataforms_spec.lua b/spec/util_dataforms_spec.lua index ae6098be..0df9fb1d 100644 --- a/spec/util_dataforms_spec.lua +++ b/spec/util_dataforms_spec.lua @@ -438,5 +438,33 @@ describe("util.dataforms", function () assert.string(e.number); end); end); + describe("media element", function () + it("produced media element correctly", function () + local f; + for field in xform:childtags("field") do + if field.attr.var == "text-single-with-media-field" then + f = field; + break; + end + end + + assert.truthy(st.is_stanza(f)); + assert.equal("text-single-with-media-field", f.attr.var); + assert.equal("text-single", f.attr.type); + assert.equal("text-single-with-media-label", f.attr.label); + assert.equal(0, iter.count(f:childtags("value"))); + + local m = f:get_child("media", "urn:xmpp:media-element"); + assert.truthy(st.is_stanza(m)); + assert.equal("24", m.attr.height); + assert.equal("32", m.attr.width); + assert.equal(1, iter.count(m:childtags("uri"))); + + local u = m:get_child("uri"); + assert.truthy(st.is_stanza(u)); + assert.equal("image/png", u.attr.type); + assert.equal("data:", u:get_text()); + end); + end); end); -- cgit v1.2.3 From 9115d2a366b8c588a22041c3e35c97b8d8a37af2 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Thu, 20 Aug 2020 15:22:19 +0100 Subject: util.dbuffer: Fix traceback when :collapse() is called on empty buffer --- spec/util_dbuffer_spec.lua | 12 ++++++++++++ util/dbuffer.lua | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/spec/util_dbuffer_spec.lua b/spec/util_dbuffer_spec.lua index c4fa22b2..b7f80dc9 100644 --- a/spec/util_dbuffer_spec.lua +++ b/spec/util_dbuffer_spec.lua @@ -46,6 +46,13 @@ describe("util.dbuffer", function () end); end); + describe(":collapse()", function () + it("works on an empty buffer", function () + local b = dbuffer.new(); + b:collapse(); + end); + end); + describe(":sub", function () -- Helper function to compare buffer:sub() with string:sub() local s = "hello world"; @@ -106,5 +113,10 @@ describe("util.dbuffer", function () local r = { b:byte(1, 2) }; assert.same({ 0, 140 }, r); end); + + it("works on an empty buffer", function () + local b = dbuffer.new(); + assert.equal("", b:sub(1,1)); + end); end); end); diff --git a/util/dbuffer.lua b/util/dbuffer.lua index 63dca750..9ec40eb9 100644 --- a/util/dbuffer.lua +++ b/util/dbuffer.lua @@ -142,7 +142,7 @@ function dbuffer_methods:collapse(bytes) local front_chunk = self.items:peek(); - if #front_chunk - self.front_consumed >= bytes then + if not front_chunk or #front_chunk - self.front_consumed >= bytes then return; end -- cgit v1.2.3 From 031a8d8e6458cb2c5ba115a75cd4f404326f7acf Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 20 Aug 2020 16:43:27 +0200 Subject: net.http.parser: Fix indentation Probably due to a rebase/merge with a merge tool that ignores whitespace. Happens all the time to me :( --- net/http/parser.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/http/parser.lua b/net/http/parser.lua index b8396518..3470ffb1 100644 --- a/net/http/parser.lua +++ b/net/http/parser.lua @@ -132,8 +132,8 @@ function httpstream.new(success_cb, error_cb, parser_type, options_cb) if chunked and not packet.body_sink then success_cb(packet); if not packet.body_sink then - packet.body_buffer = dbuffer.new(buflimit); - end + packet.body_buffer = dbuffer.new(buflimit); + end end state = true; end -- cgit v1.2.3 From 076d8b698f664a8a47a38e16f54157cb702f43a8 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Fri, 21 Aug 2020 13:41:51 +0100 Subject: net.http.parser: Add failing test for (large?) chunk-encoded responses --- spec/inputs/http/httpstream-chunked-test.txt | 15 +++++++++++++++ spec/net_http_parser_spec.lua | 12 ++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 spec/inputs/http/httpstream-chunked-test.txt diff --git a/spec/inputs/http/httpstream-chunked-test.txt b/spec/inputs/http/httpstream-chunked-test.txt new file mode 100644 index 00000000..56efa067 --- /dev/null +++ b/spec/inputs/http/httpstream-chunked-test.txt @@ -0,0 +1,15 @@ +HTTP/1.1 200 OK +Cache-Control: max-age=0, must-revalidate, private +Content-Type: application/json +Date: Fri, 21 Aug 2020 12:18:51 GMT +Expires: Fri, 21 Aug 2020 12:18:51 GMT +Server: Apache/2.4.38 (Debian) +Set-Cookie: PHPSESSID=00000000000000000000000000; path=/; HttpOnly +Strict-Transport-Security: max-age=29030400 +X-Powered-By: PHP/7.4.7 +Transfer-Encoding: chunked + +2b4d +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +0 + diff --git a/spec/net_http_parser_spec.lua b/spec/net_http_parser_spec.lua index 4ac3cab9..6a35d717 100644 --- a/spec/net_http_parser_spec.lua +++ b/spec/net_http_parser_spec.lua @@ -1,4 +1,5 @@ local http_parser = require "net.http.parser"; +local sha1 = require "util.hashes".sha1; local function test_stream(stream, expect) local success_cb = spy.new(function (packet) @@ -115,4 +116,15 @@ o ); end); end); + + pending("should handle large chunked responses", function () + local data = io.open("spec/inputs/httpstream-chunked-test.txt", "rb"):read("*a"); + + -- Just a sanity check... text editors and things may mess with line endings, etc. + assert.equal("25930f021785ae14053a322c2dbc1897c3769720", sha1(data, true), "test data malformed"); + + test_stream(data, { + body = string.rep("~", 11085), count = 2; + }); + end); end); -- cgit v1.2.3 From 70b697de98ce0501cd5d139890c9c428acd71f14 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Fri, 21 Aug 2020 13:49:10 +0100 Subject: net.http.parser: Fix incorrect path in test --- spec/net_http_parser_spec.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/net_http_parser_spec.lua b/spec/net_http_parser_spec.lua index 6a35d717..1a15302d 100644 --- a/spec/net_http_parser_spec.lua +++ b/spec/net_http_parser_spec.lua @@ -118,7 +118,7 @@ o end); pending("should handle large chunked responses", function () - local data = io.open("spec/inputs/httpstream-chunked-test.txt", "rb"):read("*a"); + local data = io.open("spec/inputs/http/httpstream-chunked-test.txt", "rb"):read("*a"); -- Just a sanity check... text editors and things may mess with line endings, etc. assert.equal("25930f021785ae14053a322c2dbc1897c3769720", sha1(data, true), "test data malformed"); -- cgit v1.2.3 From 76dd86054ce12963190255a135cf7c16ba4c4615 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Fri, 21 Aug 2020 14:12:51 +0100 Subject: net.http.parser: Switch tests so that CRLF conversion of input data is optional --- spec/net_http_parser_spec.lua | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/spec/net_http_parser_spec.lua b/spec/net_http_parser_spec.lua index 1a15302d..1a86dbba 100644 --- a/spec/net_http_parser_spec.lua +++ b/spec/net_http_parser_spec.lua @@ -1,6 +1,10 @@ local http_parser = require "net.http.parser"; local sha1 = require "util.hashes".sha1; +local function CRLF(s) + return (s:gsub("\n", "\r\n")); +end + local function test_stream(stream, expect) local success_cb = spy.new(function (packet) assert.is_table(packet); @@ -9,7 +13,6 @@ local function test_stream(stream, expect) end end); - stream = stream:gsub("\n", "\r\n"); local parser = http_parser.new(success_cb, error, stream:sub(1,4) == "HTTP" and "client" or "server") for chunk in stream:gmatch("..?.?") do parser:feed(chunk); @@ -23,7 +26,7 @@ describe("net.http.parser", function() describe("parser", function() it("should handle requests with no content-length or body", function () test_stream( -[[ +CRLF[[ GET / HTTP/1.1 Host: example.com @@ -36,7 +39,7 @@ Host: example.com it("should handle responses with empty body", function () test_stream( -[[ +CRLF[[ HTTP/1.1 200 OK Content-Length: 0 @@ -50,7 +53,7 @@ Content-Length: 0 it("should handle simple responses", function () test_stream( -[[ +CRLF[[ HTTP/1.1 200 OK Content-Length: 7 @@ -65,7 +68,7 @@ Hello it("should handle chunked encoding in responses", function () test_stream( -[[ +CRLF[[ HTTP/1.1 200 OK Transfer-Encoding: chunked @@ -90,7 +93,7 @@ o it("should handle a stream of responses", function () test_stream( -[[ +CRLF[[ HTTP/1.1 200 OK Content-Length: 5 @@ -117,7 +120,7 @@ o end); end); - pending("should handle large chunked responses", function () + it("should handle large chunked responses", function () local data = io.open("spec/inputs/http/httpstream-chunked-test.txt", "rb"):read("*a"); -- Just a sanity check... text editors and things may mess with line endings, etc. -- cgit v1.2.3 From 54e37ffe8d4562adc974a61359a8bfef8e0dcd29 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Fri, 21 Aug 2020 14:14:29 +0100 Subject: net.http.parser: Allow configuration of the chunk size fed to the parser --- spec/net_http_parser_spec.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spec/net_http_parser_spec.lua b/spec/net_http_parser_spec.lua index 1a86dbba..f71cad20 100644 --- a/spec/net_http_parser_spec.lua +++ b/spec/net_http_parser_spec.lua @@ -1,6 +1,8 @@ local http_parser = require "net.http.parser"; local sha1 = require "util.hashes".sha1; +local parser_input_bytes = 3; + local function CRLF(s) return (s:gsub("\n", "\r\n")); end @@ -14,7 +16,7 @@ local function test_stream(stream, expect) end); local parser = http_parser.new(success_cb, error, stream:sub(1,4) == "HTTP" and "client" or "server") - for chunk in stream:gmatch("..?.?") do + for chunk in stream:gmatch("."..string.rep(".?", parser_input_bytes-1)) do parser:feed(chunk); end -- cgit v1.2.3 From 2f67c339a3a06a759fe504a2158113e826369e27 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 13 Apr 2020 02:46:03 +0200 Subject: mod_component: Reply with a different error when not connected The wait, service-unavailable is overloaded by XEP-0045 to mean that the room has reached the maximum number of users. See #1495. Bouncing errors for components is tricky since there is no way to tell that it comes from the server hosting the component, not from the other end of the component connection. --- plugins/mod_component.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_component.lua b/plugins/mod_component.lua index 9ffc496e..2189aceb 100644 --- a/plugins/mod_component.lua +++ b/plugins/mod_component.lua @@ -132,7 +132,7 @@ function module.add_host(module) end module:log("warn", "Component not connected, bouncing error for: %s", stanza:top_tag()); if stanza.attr.type ~= "error" and stanza.attr.type ~= "result" then - event.origin.send(st.error_reply(stanza, "wait", "service-unavailable", "Component unavailable", module.host)); + event.origin.send(st.error_reply(stanza, "wait", "remote-server-timeout", "Component unavailable", module.host)); end end return true; -- cgit v1.2.3 From aeaad491d454d15c215cfc4eeb587f85d6f23837 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 13 Apr 2020 02:49:19 +0200 Subject: mod_component: Return extended error condition when not connected This might be something to write a XEP about. --- plugins/mod_component.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/mod_component.lua b/plugins/mod_component.lua index 2189aceb..d06df71c 100644 --- a/plugins/mod_component.lua +++ b/plugins/mod_component.lua @@ -132,7 +132,8 @@ function module.add_host(module) end module:log("warn", "Component not connected, bouncing error for: %s", stanza:top_tag()); if stanza.attr.type ~= "error" and stanza.attr.type ~= "result" then - event.origin.send(st.error_reply(stanza, "wait", "remote-server-timeout", "Component unavailable", module.host)); + event.origin.send(st.error_reply(stanza, "wait", "remote-server-timeout", "Component unavailable", module.host) + :tag("not-connected", { xmlns = "xmpp:prosody.im/protocol/component" })); end end return true; -- cgit v1.2.3 From b289d05cfbde014799fcf66fec36895bee5be071 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 18 Jul 2020 15:36:25 +0200 Subject: mod_external_services: XEP-0215: External Service Discovery --- CHANGES | 1 + doc/doap.xml | 9 ++ plugins/mod_external_services.lua | 205 ++++++++++++++++++++++++++++++++++++++ spec/scansion/extdisco.scs | 57 +++++++++++ spec/scansion/prosody.cfg.lua | 12 +++ 5 files changed, 284 insertions(+) create mode 100644 plugins/mod_external_services.lua create mode 100644 spec/scansion/extdisco.scs diff --git a/CHANGES b/CHANGES index d1e50abb..080ee3d7 100644 --- a/CHANGES +++ b/CHANGES @@ -17,6 +17,7 @@ TRUNK - `daemonize` option deprecated - SASL DIGEST-MD5 removed - Switch to libunbound for DNS queries +- mod_external_services (XEP-0215) 0.11.0 ====== diff --git a/doc/doap.xml b/doc/doap.xml index ef1437e7..6024903f 100644 --- a/doc/doap.xml +++ b/doc/doap.xml @@ -414,6 +414,15 @@ required level + + + + 0.7 + complete + 0.12 + mod_external_services + + diff --git a/plugins/mod_external_services.lua b/plugins/mod_external_services.lua new file mode 100644 index 00000000..51d2d313 --- /dev/null +++ b/plugins/mod_external_services.lua @@ -0,0 +1,205 @@ + +local dt = require "util.datetime"; +local base64 = require "util.encodings".base64; +local hashes = require "util.hashes"; +local st = require "util.stanza"; +local jid = require "util.jid"; + +local default_host = module:get_option_string("external_service_host", module.host); +local default_port = module:get_option_number("external_service_port"); +local default_secret = module:get_option_string("external_service_secret"); +local default_ttl = module:get_option_number("external_service_ttl", 86400); + +local configured_services = module:get_option_array("external_services", {}); + +local access = module:get_option_set("external_service_access", {}); + +-- filter config into well-defined service records +local function prepare(item) + if type(item) ~= "table" then + module:log("error", "Service definition is not a table: %q", item); + return nil; + end + + local srv = { + type = nil; + transport = nil; + host = default_host; + port = default_port; + username = nil; + password = nil; + restricted = nil; + expires = nil; + }; + + if type(item.type) == "string" then + srv.type = item.type; + else + module:log("error", "Service missing mandatory 'type' field: %q", item); + return nil; + end + if type(item.transport) == "string" then + srv.transport = item.transport; + end + if type(item.host) == "string" then + srv.host = item.host; + end + if type(item.port) == "number" then + srv.port = item.port; + end + if type(item.username) == "string" then + srv.username = item.username; + end + if type(item.password) == "string" then + srv.password = item.password; + srv.restricted = true; + end + if item.restricted == true then + srv.restricted = true; + end + if type(item.expires) == "number" then + srv.expires = item.expires; + elseif type(item.ttl) == "number" then + srv.expires = os.time() + item.ttl; + end + if (item.secret == true and default_secret) or type(item.secret) == "string" then + local ttl = default_ttl; + if type(item.ttl) == "number" then + ttl = item.ttl; + end + local expires = os.time() + ttl; + local secret = item.secret; + if secret == true then + secret = default_secret; + end + local username; + if type(item.username) == "string" then + username = string.format("%d:%s", expires, item.username); + else + username = string.format("%d", expires); + end + srv.username = username; + srv.password = base64.encode(hashes.hmac_sha1(secret, srv.username)); + srv.restricted = true; + end + return srv; +end + +function module.load() + -- Trigger errors on startup + local services = configured_services / prepare; + if #services == 0 then + module:log("warn", "No services configured or all had errors"); + end +end + +local function handle_services(event) + local origin, stanza = event.origin, event.stanza; + local action = stanza.tags[1]; + + local user_bare = jid.bare(stanza.attr.from); + local user_host = jid.host(user_bare); + if not ((access:empty() and origin.type == "c2s") or access:contains(user_bare) or access:contains(user_host)) then + origin.send(st.error_reply(stanza, "auth", "forbidden")); + return true; + end + + local reply = st.reply(stanza):tag("services", { xmlns = action.attr.xmlns }); + local services = configured_services / prepare; + + local requested_type = action.attr.type; + if requested_type then + services:filter(function(item) + return item.type == requested_type; + end); + end + + module:fire_event("external_service/services", { + origin = origin; + stanza = stanza; + reply = reply; + requested_type = requested_type; + services = services; + }); + + for _, srv in ipairs(services) do + reply:tag("service", { + type = srv.type; + transport = srv.transport; + host = srv.host; + port = srv.port and string.format("%d", srv.port) or nil; + username = srv.username; + password = srv.password; + expires = srv.expires and dt.datetime(srv.expires) or nil; + restricted = srv.restricted and "1" or nil; + }):up(); + end + + origin.send(reply); + return true; +end + +local function handle_credentials(event) + local origin, stanza = event.origin, event.stanza; + local action = stanza.tags[1]; + + if origin.type ~= "c2s" then + origin.send(st.error_reply(stanza, "auth", "forbidden")); + return true; + end + + local reply = st.reply(stanza):tag("credentials", { xmlns = action.attr.xmlns }); + local services = configured_services / prepare; + services:filter(function (item) + return item.restricted; + end) + + local requested_credentials = {}; + for service in action:childtags("service") do + table.insert(requested_credentials, { + type = service.attr.type; + host = service.attr.host; + port = tonumber(service.attr.port); + }); + end + + module:fire_event("external_service/credentials", { + origin = origin; + stanza = stanza; + reply = reply; + requested_credentials = requested_credentials; + services = services; + }); + + for req_srv in action:childtags("service") do + for _, srv in ipairs(services) do + if srv.type == req_srv.attr.type and srv.host == req_srv.attr.host + and not req_srv.attr.port or srv.port == tonumber(req_srv.attr.port) then + reply:tag("service", { + type = srv.type; + transport = srv.transport; + host = srv.host; + port = srv.port and string.format("%d", srv.port) or nil; + username = srv.username; + password = srv.password; + expires = srv.expires and dt.datetime(srv.expires) or nil; + restricted = srv.restricted and "1" or nil; + }):up(); + end + end + end + + origin.send(reply); + return true; +end + +-- XEP-0215 v0.7 +module:add_feature("urn:xmpp:extdisco:2"); +module:hook("iq-get/host/urn:xmpp:extdisco:2:services", handle_services); +module:hook("iq-get/host/urn:xmpp:extdisco:2:credentials", handle_credentials); + +-- COMPAT XEP-0215 v0.6 +-- Those still on the old version gets to deal with undefined attributes until they upgrade. +module:add_feature("urn:xmpp:extdisco:1"); +module:hook("iq-get/host/urn:xmpp:extdisco:1:services", handle_services); +module:hook("iq-get/host/urn:xmpp:extdisco:1:credentials", handle_credentials); diff --git a/spec/scansion/extdisco.scs b/spec/scansion/extdisco.scs new file mode 100644 index 00000000..fd73c9da --- /dev/null +++ b/spec/scansion/extdisco.scs @@ -0,0 +1,57 @@ +# XEP-0215: External Service Discovery + +[Client] Romeo + password: password + jid: user@localhost/mFquWxSr + +----- + +Romeo connects + +Romeo sends: + + + + +Romeo receives: + + + + + + + + + + +Romeo sends: + + + + +Romeo receives: + + + + + + + +Romeo sends: + + + + + + +Romeo receives: + + + + + + + +Romeo disconnects + +# recording ended on 2020-07-18T16:47:57Z diff --git a/spec/scansion/prosody.cfg.lua b/spec/scansion/prosody.cfg.lua index 0bf68ddb..4e43d312 100644 --- a/spec/scansion/prosody.cfg.lua +++ b/spec/scansion/prosody.cfg.lua @@ -62,6 +62,7 @@ modules_enabled = { --"legacyauth"; -- Legacy authentication. Only used by some old clients and bots. --"proxy65"; -- Enables a file transfer proxy service which clients behind NAT can use "lastactivity"; + "external_services"; -- Useful for testing --"scansion_record"; -- Records things that happen in scansion test case format @@ -77,6 +78,17 @@ contact_info = { support = { "https://localhost/support.html", "xmpp:support@localhost" }; } +external_service_host = "default.example" +external_service_port = 9876 +external_service_secret = "" +external_services = { + {type = "stun"; transport = "udp"}; + {type = "turn"; transport = "udp"; secret = true}; + {type = "turn"; transport = "udp"; secret = "foo"}; + {type = "ftp"; transport = "tcp"; port = 2121; username = "john"; password = "password"}; + {type = "ftp"; transport = "tcp"; host = "ftp.example.com"; port = 21; username = "john"; password = "password"}; +} + modules_disabled = { "s2s"; } -- cgit v1.2.3 From 6dfae9bbfa4b60374a52482bdf60355acafd5a6a Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 25 Jul 2020 10:22:37 +0200 Subject: mod_external_services: Support adding services via items API --- plugins/mod_external_services.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/mod_external_services.lua b/plugins/mod_external_services.lua index 51d2d313..c4762e65 100644 --- a/plugins/mod_external_services.lua +++ b/plugins/mod_external_services.lua @@ -105,7 +105,8 @@ local function handle_services(event) end local reply = st.reply(stanza):tag("services", { xmlns = action.attr.xmlns }); - local services = configured_services / prepare; + local extras = module:get_host_items("external_service"); + local services = ( configured_services + extras ) / prepare; local requested_type = action.attr.type; if requested_type then @@ -149,7 +150,8 @@ local function handle_credentials(event) end local reply = st.reply(stanza):tag("credentials", { xmlns = action.attr.xmlns }); - local services = configured_services / prepare; + local extras = module:get_host_items("external_service"); + local services = ( configured_services + extras ) / prepare; services:filter(function (item) return item.restricted; end) -- cgit v1.2.3 From 0b65dea7c01b57dc036de2c62de3dd8e55c3f293 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 25 Jul 2020 12:09:19 +0200 Subject: mod_external_services: Prepare to allow more credential algorithms Not sure what algorithms might fit here. Separation makes some sense. This is also a preparation for having a callback. (See next commit) --- plugins/mod_external_services.lua | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/plugins/mod_external_services.lua b/plugins/mod_external_services.lua index c4762e65..e1c72387 100644 --- a/plugins/mod_external_services.lua +++ b/plugins/mod_external_services.lua @@ -14,6 +14,27 @@ local configured_services = module:get_option_array("external_services", {}); local access = module:get_option_set("external_service_access", {}); +-- https://tools.ietf.org/html/draft-uberti-behave-turn-rest-00 +local function behave_turn_rest_credentials(srv, item, secret) + local ttl = default_ttl; + if type(item.ttl) == "number" then + ttl = item.ttl; + end + local expires = srv.expires or os.time() + ttl; + local username; + if type(item.username) == "string" then + username = string.format("%d:%s", expires, item.username); + else + username = string.format("%d", expires); + end + srv.username = username; + srv.password = base64.encode(hashes.hmac_sha1(secret, srv.username)); +end + +local algorithms = { + turn = behave_turn_rest_credentials; +} + -- filter config into well-defined service records local function prepare(item) if type(item) ~= "table" then @@ -63,24 +84,15 @@ local function prepare(item) srv.expires = os.time() + item.ttl; end if (item.secret == true and default_secret) or type(item.secret) == "string" then - local ttl = default_ttl; - if type(item.ttl) == "number" then - ttl = item.ttl; - end - local expires = os.time() + ttl; + local secret_cb = algorithms[item.algorithm] or algorithms[srv.type]; local secret = item.secret; if secret == true then secret = default_secret; end - local username; - if type(item.username) == "string" then - username = string.format("%d:%s", expires, item.username); - else - username = string.format("%d", expires); + if secret_cb then + secret_cb(srv, item, secret); + srv.restricted = true; end - srv.username = username; - srv.password = base64.encode(hashes.hmac_sha1(secret, srv.username)); - srv.restricted = true; end return srv; end -- cgit v1.2.3 From 5bc6130e57fe2af3108ec538b83768100bdc177c Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 25 Jul 2020 12:22:03 +0200 Subject: mod_external_services: Allow specifying a credential generation callback This is especially targeted at services added via the items API. More involved credential generation should use the event hook. --- plugins/mod_external_services.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_external_services.lua b/plugins/mod_external_services.lua index e1c72387..7c18e326 100644 --- a/plugins/mod_external_services.lua +++ b/plugins/mod_external_services.lua @@ -84,7 +84,7 @@ local function prepare(item) srv.expires = os.time() + item.ttl; end if (item.secret == true and default_secret) or type(item.secret) == "string" then - local secret_cb = algorithms[item.algorithm] or algorithms[srv.type]; + local secret_cb = item.credentials_cb or algorithms[item.algorithm] or algorithms[srv.type]; local secret = item.secret; if secret == true then secret = default_secret; -- cgit v1.2.3 From fb6c098ed661d4bd14f1a0515e4ae6b110c76b19 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 17 Aug 2020 00:24:11 +0200 Subject: mod_external_services: Validate services added via events While writing developer documentation it became obvious that i was silly to have one item format for config and items API, and another format for the event API. Then there's the stanza format, but that's a common pattern. This change reduces the possible input formats to two and allows other modules the benefit of the processing and validation performed on items from the config. --- plugins/mod_external_services.lua | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/plugins/mod_external_services.lua b/plugins/mod_external_services.lua index 7c18e326..e18e7c3e 100644 --- a/plugins/mod_external_services.lua +++ b/plugins/mod_external_services.lua @@ -4,6 +4,7 @@ local base64 = require "util.encodings".base64; local hashes = require "util.hashes"; local st = require "util.stanza"; local jid = require "util.jid"; +local array = require "util.array"; local default_host = module:get_option_string("external_service_host", module.host); local default_port = module:get_option_number("external_service_port"); @@ -105,6 +106,14 @@ function module.load() end end +-- Ensure only valid items are added in events +local services_mt = { + __index = getmetatable(array()).__index; + __newindex = function (self, i, v) + rawset(self, i, assert(prepare(v), "Invalid service entry added")); + end; +} + local function handle_services(event) local origin, stanza = event.origin, event.stanza; local action = stanza.tags[1]; @@ -127,6 +136,8 @@ local function handle_services(event) end); end + setmetatable(services, services_mt); + module:fire_event("external_service/services", { origin = origin; stanza = stanza; @@ -177,6 +188,9 @@ local function handle_credentials(event) }); end + setmetatable(services, services_mt); + setmetatable(requested_credentials, services_mt); + module:fire_event("external_service/credentials", { origin = origin; stanza = stanza; -- cgit v1.2.3 From 460a55c86eb3ab164c54c7b49fe1b84d78520b85 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 22 Aug 2020 14:34:33 +0200 Subject: mod_admin_shell: Report CSI state in c2s:show() --- plugins/mod_admin_shell.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/mod_admin_shell.lua b/plugins/mod_admin_shell.lua index 2db0cbb0..3650e7f6 100644 --- a/plugins/mod_admin_shell.lua +++ b/plugins/mod_admin_shell.lua @@ -543,6 +543,9 @@ local function session_flags(session, line) if session.smacks then line[#line+1] = "(sm)"; end + if session.state then + line[#line+1] = string.format("(csi:%s)", session.state); + end if session.ip and session.ip:match(":") then line[#line+1] = "(IPv6)"; end -- cgit v1.2.3 From 6c5dd70664236b524f3b2524f3ed48cbc8f0bc42 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 22 Aug 2020 14:34:57 +0200 Subject: mod_admin_shell: Report CSI queue length from mod_csi_simple --- plugins/mod_admin_shell.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/mod_admin_shell.lua b/plugins/mod_admin_shell.lua index 3650e7f6..5b4c2a61 100644 --- a/plugins/mod_admin_shell.lua +++ b/plugins/mod_admin_shell.lua @@ -544,7 +544,11 @@ local function session_flags(session, line) line[#line+1] = "(sm)"; end if session.state then - line[#line+1] = string.format("(csi:%s)", session.state); + if type(session.csi_counter) == "number" then + line[#line+1] = string.format("(csi:%s queue #%d)", session.state, session.csi_counter); + else + line[#line+1] = string.format("(csi:%s)", session.state); + end end if session.ip and session.ip:match(":") then line[#line+1] = "(IPv6)"; -- cgit v1.2.3 From 33d00845e771884c54d6e1631646e60a2bb9c6ef Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 23 Aug 2020 22:19:29 +0200 Subject: net.server_select: Fix traceback (thanks eta) The `socket` here is unreferenced on disconnect. Calling :resume_writes after that causes an error when `addsocket()` tries to use it as a table index. --- net/server_select.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/server_select.lua b/net/server_select.lua index a2515d59..09c1c027 100644 --- a/net/server_select.lua +++ b/net/server_select.lua @@ -511,7 +511,7 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport end handler.resume_writes = function (self) nosend = false - if bufferlen > 0 then + if bufferlen > 0 and socket then _sendlistlen = addsocket(_sendlist, socket, _sendlistlen) end end -- cgit v1.2.3 From e21429d8e4533f765072f432b138a68092dab3fa Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 24 Aug 2020 17:07:37 +0200 Subject: util.dbuffer: Add failing test case involving :sub after :discard --- spec/util_dbuffer_spec.lua | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/spec/util_dbuffer_spec.lua b/spec/util_dbuffer_spec.lua index b7f80dc9..26fa7da6 100644 --- a/spec/util_dbuffer_spec.lua +++ b/spec/util_dbuffer_spec.lua @@ -67,6 +67,15 @@ describe("util.dbuffer", function () assert.equals("hello", b:sub(1, 5)); end); + pending("works after discard", function () + local b = dbuffer.new(256); + assert.truthy(b:write("foo")); + assert.truthy(b:write("bar")); + assert.equals("foobar", b:sub(1, 6)); + assert.truthy(b:discard(3)) -- until the space + assert.equals("bar", b:sub(1, 3)); + end); + it("supports optional end parameter", function () local b = dbuffer.new(); assert.truthy(b:write("hello world")); -- cgit v1.2.3 From 7fa4cc8de0ffb0a0914c1861987a19cf666febf6 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 24 Aug 2020 16:18:13 +0100 Subject: util.dbuffer: Fix :sub() not working with partially-consumed chunks (thanks Zash for test case) This also appears to fix some bugs with chunk-encoded streams in net.http.parser. --- spec/util_dbuffer_spec.lua | 2 +- util/dbuffer.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/util_dbuffer_spec.lua b/spec/util_dbuffer_spec.lua index 26fa7da6..554a7e54 100644 --- a/spec/util_dbuffer_spec.lua +++ b/spec/util_dbuffer_spec.lua @@ -67,7 +67,7 @@ describe("util.dbuffer", function () assert.equals("hello", b:sub(1, 5)); end); - pending("works after discard", function () + it("works after discard", function () local b = dbuffer.new(256); assert.truthy(b:write("foo")); assert.truthy(b:write("bar")); diff --git a/util/dbuffer.lua b/util/dbuffer.lua index 9ec40eb9..a50f3a64 100644 --- a/util/dbuffer.lua +++ b/util/dbuffer.lua @@ -123,7 +123,7 @@ function dbuffer_methods:sub(i, j) self:collapse(j); - return self.items:peek():sub(i, j); + return self.items:peek():sub(self.front_consumed+1):sub(i, j); end function dbuffer_methods:byte(i, j) -- cgit v1.2.3 From 21ecc6ca399493eb88e75f2979d64a779cd34f50 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 24 Aug 2020 17:28:48 +0200 Subject: util.dbuffer: Simplify test case An earlier theory involved the bug being related to collapsing multiple items, so it exercised that too. Also correct the comment, it referred to the space in "hello world" in an earlier version before the test string was changed to "foobar", which was what was tested in a REPL --- spec/util_dbuffer_spec.lua | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/spec/util_dbuffer_spec.lua b/spec/util_dbuffer_spec.lua index 554a7e54..af404042 100644 --- a/spec/util_dbuffer_spec.lua +++ b/spec/util_dbuffer_spec.lua @@ -69,10 +69,9 @@ describe("util.dbuffer", function () it("works after discard", function () local b = dbuffer.new(256); - assert.truthy(b:write("foo")); - assert.truthy(b:write("bar")); + assert.truthy(b:write("foobar")); assert.equals("foobar", b:sub(1, 6)); - assert.truthy(b:discard(3)) -- until the space + assert.truthy(b:discard(3)); -- consume "foo" assert.equals("bar", b:sub(1, 3)); end); -- cgit v1.2.3 From f25f4bb11a7e68ec997d360c71195e2941042f2a Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 24 Aug 2020 19:48:47 +0200 Subject: mod_posix: Remove ancient undocumented user switching MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User switching has been done by prosodyctl or init scripts for a very long time now, so this is not needed. Using this would not have worked with module reloading (e.g. to reload certificates) since ports are closed and re-bound, which would then not be allowed. Today there exists better ways to grant low ports, i.e. capabilities(7) Why do we have this? Remove it --- plugins/mod_posix.lua | 35 ++++------------------------------- 1 file changed, 4 insertions(+), 31 deletions(-) diff --git a/plugins/mod_posix.lua b/plugins/mod_posix.lua index 5177aaa5..0a658009 100644 --- a/plugins/mod_posix.lua +++ b/plugins/mod_posix.lua @@ -30,39 +30,12 @@ module:set_global(); -- we're a global module local umask = module:get_option_string("umask", "027"); pposix.umask(umask); --- Allow switching away from root, some people like strange ports. -module:hook("server-started", function () - local uid = module:get_option("setuid"); - local gid = module:get_option("setgid"); - if gid then - local success, msg = pposix.setgid(gid); - if success then - module:log("debug", "Changed group to %s successfully.", gid); - else - module:log("error", "Failed to change group to %s. Error: %s", gid, msg); - prosody.shutdown("Failed to change group to %s", gid); - end - end - if uid then - local success, msg = pposix.setuid(uid); - if success then - module:log("debug", "Changed user to %s successfully.", uid); - else - module:log("error", "Failed to change user to %s. Error: %s", uid, msg); - prosody.shutdown("Failed to change user to %s", uid); - end - end -end); - -- Don't even think about it! if not prosody.start_time then -- server-starting - local suid = module:get_option("setuid"); - if not suid or suid == 0 or suid == "root" then - if pposix.getuid() == 0 and not module:get_option_boolean("run_as_root") then - module:log("error", "Danger, Will Robinson! Prosody doesn't need to be run as root, so don't do it!"); - module:log("error", "For more information on running Prosody as root, see https://prosody.im/doc/root"); - prosody.shutdown("Refusing to run as root"); - end + if pposix.getuid() == 0 and not module:get_option_boolean("run_as_root") then + module:log("error", "Danger, Will Robinson! Prosody doesn't need to be run as root, so don't do it!"); + module:log("error", "For more information on running Prosody as root, see https://prosody.im/doc/root"); + prosody.shutdown("Refusing to run as root"); end end -- cgit v1.2.3 From 209660f21244ee06b5bfcd8f5addc830058d3104 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Tue, 25 Aug 2020 15:57:39 +0100 Subject: net.http: use new net.http.errors lib for creating error object --- net/http.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/http.lua b/net/http.lua index f1055840..2deeae14 100644 --- a/net/http.lua +++ b/net/http.lua @@ -13,7 +13,7 @@ local util_http = require "util.http"; local events = require "util.events"; local verify_identity = require"util.x509".verify_identity; local promise = require "util.promise"; -local errors = require "util.error"; +local http_errors = require "net.http.errors"; local basic_resolver = require "net.resolvers.basic"; local connect = require "net.connect".connect; @@ -291,7 +291,7 @@ local function new(options) return promise.new(function (resolve, reject) request(self, u, ex, function (body, code, a, b) if code == 0 then - reject(errors.new(body, { request = a })); + reject(http_errors.new(body, { request = a })); else resolve({ request = b, response = a }); end -- cgit v1.2.3 From c340e3ab373d32fd909e5902538031ed30d9585f Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Tue, 25 Aug 2020 15:59:04 +0100 Subject: net.http: http.request() promise now resolves with response (breaking change) Promise mode is not (widely?) used, changing this now while we can, as it improves usability of the API. The request is now available as response.request, if needed. --- net/http.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/http.lua b/net/http.lua index 2deeae14..c417ebec 100644 --- a/net/http.lua +++ b/net/http.lua @@ -293,7 +293,8 @@ local function new(options) if code == 0 then reject(http_errors.new(body, { request = a })); else - resolve({ request = b, response = a }); + a.request = b; + resolve(a); end end); end); -- cgit v1.2.3 From 75079963714b6633847f05d015e1adcfde11a8f6 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Fri, 28 Aug 2020 12:40:59 +0100 Subject: util.error: Allow optional tracebacks to be injected on errors This allows extra debug info to be provided for development purposes. --- util/error.lua | 15 +++++++++++++++ util/startup.lua | 6 ++++++ 2 files changed, 21 insertions(+) diff --git a/util/error.lua b/util/error.lua index ca960dd9..b83302fa 100644 --- a/util/error.lua +++ b/util/error.lua @@ -8,6 +8,14 @@ local function is_err(e) return getmetatable(e) == error_mt; end +local auto_inject_traceback = false; + +local function configure(opt) + if opt.auto_inject_traceback ~= nil then + auto_inject_traceback = opt.auto_inject_traceback; + end +end + -- Do we want any more well-known fields? -- Or could we just copy all fields from `e`? -- Sometimes you want variable details in the `text`, how to handle that? @@ -17,6 +25,12 @@ end local function new(e, context, registry) local template = (registry and registry[e]) or e or {}; + context = context or template.context or { _error_id = e }; + + if auto_inject_traceback then + context.traceback = debug.traceback("error stack", 2); + end + return setmetatable({ type = template.type or "cancel"; condition = template.condition or "undefined-condition"; @@ -57,4 +71,5 @@ return { coerce = coerce; is_err = is_err; from_stanza = from_stanza; + configure = configure; } diff --git a/util/startup.lua b/util/startup.lua index 01ca585b..40021981 100644 --- a/util/startup.lua +++ b/util/startup.lua @@ -546,6 +546,10 @@ function startup.init_gc() return true; end +function startup.init_errors() + require "util.error".configure(config.get("*", "error_library")); +end + function startup.make_host(hostname) return { type = "local", @@ -577,6 +581,7 @@ function startup.prosodyctl() startup.force_console_logging(); startup.init_logging(); startup.init_gc(); + startup.init_errors(); startup.setup_plugindir(); -- startup.setup_plugin_install_path(); startup.setup_datadir(); @@ -600,6 +605,7 @@ function startup.prosody() startup.read_config(); startup.init_logging(); startup.init_gc(); + startup.init_errors(); startup.sanity_check(); startup.sandbox_require(); startup.set_function_metatable(); -- cgit v1.2.3 From 5c04c5ce383eca1e9b544885c67a976251ca12db Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Fri, 28 Aug 2020 12:51:40 +0100 Subject: util.error: Add configuration for including traceback in tostring() --- util/error.lua | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/util/error.lua b/util/error.lua index b83302fa..ffdc4eec 100644 --- a/util/error.lua +++ b/util/error.lua @@ -1,6 +1,15 @@ + +-- Library configuration (see configure()) +local auto_inject_traceback = false; +local display_tracebacks = false; + + local error_mt = { __name = "error" }; function error_mt:__tostring() + if display_tracebacks and self.context.traceback then + return ("error<%s:%s:%s:%s>"):format(self.type, self.condition, self.text or "", self.context.traceback); + end return ("error<%s:%s:%s>"):format(self.type, self.condition, self.text or ""); end @@ -8,9 +17,10 @@ local function is_err(e) return getmetatable(e) == error_mt; end -local auto_inject_traceback = false; - local function configure(opt) + if opt.display_tracebacks ~= nil then + display_tracebacks = opt.display_tracebacks; + end if opt.auto_inject_traceback ~= nil then auto_inject_traceback = opt.auto_inject_traceback; end -- cgit v1.2.3 From fa462b850564ff370e466d679294795b43c41ada Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Fri, 28 Aug 2020 12:54:31 +0100 Subject: util.startup: Init util.error with defaults if none given --- util/startup.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/startup.lua b/util/startup.lua index 40021981..a8d8594c 100644 --- a/util/startup.lua +++ b/util/startup.lua @@ -547,7 +547,7 @@ function startup.init_gc() end function startup.init_errors() - require "util.error".configure(config.get("*", "error_library")); + require "util.error".configure(config.get("*", "error_library") or {}); end function startup.make_host(hostname) -- cgit v1.2.3 From df7cf5c388e5be03dfc5f57229b8f1ee7b355ded Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 28 Aug 2020 13:54:16 +0200 Subject: util.error: Add a 'source' parameter where origin module can be mentioned --- util/error.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/util/error.lua b/util/error.lua index ffdc4eec..600c9e5e 100644 --- a/util/error.lua +++ b/util/error.lua @@ -33,7 +33,7 @@ end -- Should the `type` be restricted to the stanza error types or free-form? -- What to set `type` to for stream errors or SASL errors? Those don't have a 'type' attr. -local function new(e, context, registry) +local function new(e, context, registry, source) local template = (registry and registry[e]) or e or {}; context = context or template.context or { _error_id = e }; @@ -48,6 +48,7 @@ local function new(e, context, registry) code = template.code; context = context or template.context or { _error_id = e }; + source = source; }, error_mt); end -- cgit v1.2.3 From a114e8cf0237266237412c98de3fbb2610e7a31b Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 28 Aug 2020 13:55:05 +0200 Subject: util.error: Add a wrapper for common parameters Lets you set up source and registry once per module --- util/error.lua | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/util/error.lua b/util/error.lua index 600c9e5e..c46f8790 100644 --- a/util/error.lua +++ b/util/error.lua @@ -52,6 +52,12 @@ local function new(e, context, registry, source) }, error_mt); end +local function init(source, registry) + return function (e, context) + return new(e, context, registry, source); + end +end + local function coerce(ok, err, ...) if ok or is_err(err) then return ok, err, ...; @@ -79,6 +85,7 @@ end return { new = new; + init = init; coerce = coerce; is_err = is_err; from_stanza = from_stanza; -- cgit v1.2.3 From 403f320d905ae32f2b1f7568c229675b71667473 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 28 Aug 2020 18:43:37 +0200 Subject: util.jid: Add test coverage for XEP-0106: JID Escaping functions --- spec/util_jid_spec.lua | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/spec/util_jid_spec.lua b/spec/util_jid_spec.lua index c075212f..17cadbee 100644 --- a/spec/util_jid_spec.lua +++ b/spec/util_jid_spec.lua @@ -75,6 +75,56 @@ describe("util.jid", function() end); end); + local jid_escaping_test_vectors = { + -- From https://xmpp.org/extensions/xep-0106.xml#examples sans @example.com + [[space cadet]], [[space\20cadet]], + [[call me "ishmael"]], [[call\20me\20\22ishmael\22]], + [[at&t guy]], [[at\26t\20guy]], + [[d'artagnan]], [[d\27artagnan]], + [[/.fanboy]], [[\2f.fanboy]], + [[::foo::]], [[\3a\3afoo\3a\3a]], + [[]], [[\3cfoo\3e]], + [[user@host]], [[user\40host]], + [[c:\net]], [[c\3a\net]], + [[c:\\net]], [[c\3a\\net]], + [[c:\cool stuff]], [[c\3a\cool\20stuff]], + [[c:\5commas]], [[c\3a\5c5commas]], + + -- Section 4.2 + [[\3and\2is\5cool]], [[\5c3and\2is\5c5cool]], + + -- From aioxmpp + [[\5c]], [[\5c5c]], + -- [[\5C]], [[\5C]], + [[\2plus\2is\4]], [[\2plus\2is\4]], + [[foo\bar]], [[foo\bar]], + [[foo\41r]], [[foo\41r]], + -- additional test vectors + [[call\20me]], [[call\5c20me]], + }; + + describe("#escape()", function () + it("should work", function () + for i = 1, #jid_escaping_test_vectors, 2 do + local original = jid_escaping_test_vectors[i]; + local escaped = jid_escaping_test_vectors[i+1]; + + assert.are.equal(escaped, jid.escape(original), ("Escapes '%s' -> '%s'"):format(original, escaped)); + end + end); + end) + + describe("#unescape()", function () + it("should work", function () + for i = 1, #jid_escaping_test_vectors, 2 do + local original = jid_escaping_test_vectors[i]; + local escaped = jid_escaping_test_vectors[i+1]; + + assert.are.equal(original, jid.unescape(escaped), ("Unescapes '%s' -> '%s'"):format(escaped, original)); + end + end); + end) + it("should work with nodes", function() local function test(_jid, expected_node) assert.are.equal(jid.node(_jid), expected_node, "Unexpected node for "..tostring(_jid)); -- cgit v1.2.3 From b37e985f4871fd2816c315d74869b08a5eb17b9d Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 28 Aug 2020 18:44:02 +0200 Subject: util.jid: Fix special escaping of '\' per XEP-0106 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From XEP-0106 §2. Requirements: > in certain circumstances, the escaping character itself ("\") might > also be escaped Later in §4.2 Address Transformation Algorithm it is stated that the backslash would only be escaped if it forms an escape sequence. Thus '\foo' is unaltered but '\20' must be escaped into '\5c20'. Thanks to lovetox and jonas’ for brining up. --- util/jid.lua | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/util/jid.lua b/util/jid.lua index 1ddf33d4..a1180534 100644 --- a/util/jid.lua +++ b/util/jid.lua @@ -22,7 +22,11 @@ local escapes = { ["@"] = "\\40"; ["\\"] = "\\5c"; }; local unescapes = {}; -for k,v in pairs(escapes) do unescapes[v] = k; end +local backslash_escapes = {}; +for k,v in pairs(escapes) do + unescapes[v] = k; + backslash_escapes[v] = v:gsub("\\", escapes) +end local _ENV = nil; -- luacheck: std none @@ -107,7 +111,7 @@ local function resource(jid) return (select(3, split(jid))); end -local function escape(s) return s and (s:gsub(".", escapes)); end +local function escape(s) return s and (s:gsub("\\%x%x", backslash_escapes):gsub("[\"&'/:<>@ ]", escapes)); end local function unescape(s) return s and (s:gsub("\\%x%x", unescapes)); end return { -- cgit v1.2.3 From 1cec1146460848a60dca8ebc85c97d7c45544c65 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 29 Aug 2020 18:51:13 +0200 Subject: MUC: Don't default room name to JID localpart (API breaking change) Behavior with turning empty name into localpart was originally introduced in 711eb5bf94b4 This has caused some problems for clients, making it difficult to differentiate between a room actually named like the localpart from a room without a name. Breaking: The function signature of the :get_name() method changes from always returning a string to optional string. --- plugins/muc/mod_muc.lua | 16 +++++++++++++--- plugins/muc/name.lib.lua | 4 +--- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index d911de08..e9fd1521 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -137,7 +137,12 @@ local room_items_cache = {}; local function room_save(room, forced, savestate) local node = jid_split(room.jid); local is_persistent = persistent.get(room); - room_items_cache[room.jid] = room:get_public() and room:get_name() or nil; + if room:get_public() then + room_items_cache[room.jid] = room:get_name() or ""; + else + room_items_cache[room.jid] = nil; + end + if is_persistent or savestate then persistent_rooms:set(nil, room.jid, true); local data, state = room:freeze(savestate); @@ -163,7 +168,11 @@ local rooms = cache.new(max_rooms or max_live_rooms, function (jid, room) end module:log("debug", "Evicting room %s", jid); room_eviction(); - room_items_cache[room.jid] = room:get_public() and room:get_name() or nil; + if room:get_public() then + room_items_cache[room.jid] = room:get_name() or ""; + else + room_items_cache[room.jid] = nil; + end local ok, err = room_save(room, nil, true); -- Force to disk if not ok then module:log("error", "Failed to swap inactive room %s to disk: %s", jid, err); @@ -337,13 +346,14 @@ module:hook("host-disco-items", function(event) module:log("debug", "host-disco-items called"); if next(room_items_cache) ~= nil then for jid, room_name in pairs(room_items_cache) do + if room_name == "" then room_name = nil; end reply:tag("item", { jid = jid, name = room_name }):up(); end else for room in all_rooms() do if not room:get_hidden() then local jid, room_name = room.jid, room:get_name(); - room_items_cache[jid] = room_name; + room_items_cache[jid] = room_name or ""; reply:tag("item", { jid = jid, name = room_name }):up(); end end diff --git a/plugins/muc/name.lib.lua b/plugins/muc/name.lib.lua index 37fe1259..5d73e74d 100644 --- a/plugins/muc/name.lib.lua +++ b/plugins/muc/name.lib.lua @@ -7,10 +7,8 @@ -- COPYING file in the source package for more information. -- -local jid_split = require "util.jid".split; - local function get_name(room) - return room._data.name or jid_split(room.jid); + return room._data.name; end local function set_name(room, name) -- cgit v1.2.3 From c909bca762b655dd798b9f0f5c937feced7aa655 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Thu, 3 Sep 2020 12:59:43 +0100 Subject: util.events: Add set_debug_hook() method Hook signature: ret = debug_hook(handler, event_name, event_data) --- spec/util_events_spec.lua | 38 ++++++++++++++++++++++++++++++++++++++ util/events.lua | 20 +++++++++++++++++++- 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/spec/util_events_spec.lua b/spec/util_events_spec.lua index fee60f8f..95303cce 100644 --- a/spec/util_events_spec.lua +++ b/spec/util_events_spec.lua @@ -208,5 +208,43 @@ describe("util.events", function () assert.spy(h).was_called(2); end); end); + + describe("debug hooks", function () + it("should get called", function () + local d = spy.new(function (handler, event_name, event_data) + return handler(event_data); + end); + + e.add_handler("myevent", h); + e.fire_event("myevent"); + + assert.spy(h).was_called(1); + assert.spy(d).was_called(0); + + assert.is_nil(e.set_debug_hook(d)); + + e.fire_event("myevent", { mydata = true }); + + assert.spy(h).was_called(2); + assert.spy(d).was_called(1); + assert.spy(d).was_called_with(h, "myevent", { mydata = true }); + + assert.equal(d, e.set_debug_hook(nil)); + + e.fire_event("myevent", { mydata = false }); + + assert.spy(h).was_called(3); + assert.spy(d).was_called(1); + end); + it("setting should return any existing debug hook", function () + local function f() end + local function g() end + assert.is_nil(e.set_debug_hook(f)); + assert.is_equal(f, e.set_debug_hook(g)); + assert.is_equal(g, e.set_debug_hook(f)); + assert.is_equal(f, e.set_debug_hook(nil)); + assert.is_nil(e.set_debug_hook(f)); + end); + end); end); end); diff --git a/util/events.lua b/util/events.lua index 0bf0ddcb..5205a457 100644 --- a/util/events.lua +++ b/util/events.lua @@ -26,6 +26,8 @@ local function new() local wrappers = {}; -- Event map: event_map[handler_function] = priority_number local event_map = {}; + -- Debug hook, if any + local active_debug_hook = nil; -- Called on-demand to build handlers entries local function _rebuild_index(self, event) local _handlers = event_map[event]; @@ -74,11 +76,16 @@ local function new() end; local function _fire_event(event_name, event_data) local h = handlers[event_name]; - if h then + if h and not active_debug_hook then for i=1,#h do local ret = h[i](event_data); if ret ~= nil then return ret; end end + elseif h and active_debug_hook then + for i=1,#h do + local ret = active_debug_hook(h[i], event_name, event_data); + if ret ~= nil then return ret; end + end end end; local function fire_event(event_name, event_data) @@ -140,6 +147,13 @@ local function new() end end end + + local function set_debug_hook(new_hook) + local old_hook = active_debug_hook; + active_debug_hook = new_hook; + return old_hook; + end + return { add_handler = add_handler; remove_handler = remove_handler; @@ -150,8 +164,12 @@ local function new() add_handler = add_wrapper; remove_handler = remove_wrapper; }; + add_wrapper = add_wrapper; remove_wrapper = remove_wrapper; + + set_debug_hook = set_debug_hook; + fire_event = fire_event; _handlers = handlers; _event_map = event_map; -- cgit v1.2.3 From a5775893f5aa7d107a08d2c8b797c492b605e630 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Thu, 3 Sep 2020 13:00:43 +0100 Subject: util.helpers: when logging events, log individual handler calls --- util/helpers.lua | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/util/helpers.lua b/util/helpers.lua index 02257ffa..139b62ec 100644 --- a/util/helpers.lua +++ b/util/helpers.lua @@ -23,12 +23,27 @@ local function log_events(events, name, logger) logger("debug", "%s firing event: %s", name, event); return f(event, ...); end + + local function event_handler_hook(handler, event_name, event_data) + logger("debug", "calling handler for %s: %s", event_name, handler); + local ok, ret = pcall(handler, event_data); + if not ok then + logger("error", "error in event handler %s: %s", handler, ret); + error(ret); + end + if ret ~= nil then + logger("debug", "event chain ended for %s by %s with result: %s", event_name, handler, ret); + end + return ret; + end + events.set_debug_hook(event_handler_hook); events[events.fire_event] = f; return events; end local function revert_log_events(events) events.fire_event, events[events.fire_event] = events[events.fire_event], nil; -- :)) + events.set_debug_hook(nil); end local function log_host_events(host) -- cgit v1.2.3 From e419f0b5f4fd61a6e0e2f96f497bb72ddddc1a98 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Thu, 3 Sep 2020 13:10:46 +0100 Subject: util.event: Add luacheck annotation to unused parameter in tests --- spec/util_events_spec.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/util_events_spec.lua b/spec/util_events_spec.lua index 95303cce..fcfa6e53 100644 --- a/spec/util_events_spec.lua +++ b/spec/util_events_spec.lua @@ -211,7 +211,7 @@ describe("util.events", function () describe("debug hooks", function () it("should get called", function () - local d = spy.new(function (handler, event_name, event_data) + local d = spy.new(function (handler, event_name, event_data) --luacheck: ignore 212/event_name return handler(event_data); end); -- cgit v1.2.3 From 33e7e5ef2d5e97b45cd9bcb56ce5ec6adc3a66c9 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 8 Sep 2020 22:50:43 +0200 Subject: mod_posix: Daemonize later Daemonizing later means we can use that as a "successful startup" signal and problems can be reported via exit code. --- plugins/mod_posix.lua | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/plugins/mod_posix.lua b/plugins/mod_posix.lua index 0a658009..d3ff1f7c 100644 --- a/plugins/mod_posix.lua +++ b/plugins/mod_posix.lua @@ -117,9 +117,7 @@ if daemonize then write_pidfile(); end end - if not prosody.start_time then -- server-starting - daemonize_server(); - end + module:hook("server-started", daemonize_server) else -- Not going to daemonize, so write the pid of this process write_pidfile(); -- cgit v1.2.3 From 0ca2e88384f6ec512879cf4cfbe2627b984762d5 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Wed, 9 Sep 2020 17:10:33 +0100 Subject: util.interpolation: Add '~' as the opposite of '&' (render sub-block if falsy) One more magic character consumed! --- spec/util_interpolation_spec.lua | 12 +++++++++++- util/interpolation.lua | 3 +++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/spec/util_interpolation_spec.lua b/spec/util_interpolation_spec.lua index 76000d94..98ed3b74 100644 --- a/spec/util_interpolation_spec.lua +++ b/spec/util_interpolation_spec.lua @@ -32,7 +32,15 @@ local template_map = [[ local expect_map = [[ FOO: bar ]] - +local template_not = [[ +{thing~Thing is nil}{thing&Thing is not nil} +]] +local expect_not_true = [[ +Thing is not nil +]] +local expect_not_nil = [[ +Thing is nil +]] describe("util.interpolation", function () it("renders", function () local render = require "util.interpolation".new("%b{}", string.upper, { sort = function (t) table.sort(t) return t end }); @@ -43,5 +51,7 @@ describe("util.interpolation", function () assert.equal(expect_func_pipe, render(template_func_pipe, { foo = { "c", "a", "d", "b", } })); -- assert.equal("", render(template_func_pipe, { foo = nil })); -- FIXME assert.equal(expect_map, render(template_map, { foo = { foo = "bar" } })); + assert.equal(expect_not_true, render(template_not, { thing = true })); + assert.equal(expect_not_nil, render(template_not, { thing = nil })); end); end); diff --git a/util/interpolation.lua b/util/interpolation.lua index e0ccf47b..808a45f2 100644 --- a/util/interpolation.lua +++ b/util/interpolation.lua @@ -64,6 +64,9 @@ local function new_render(pat, escape, funcs) elseif opt == '&' then if not value then return ""; end return render(s_sub(block, e), values); + elseif opt == '~' then + if value then return ""; end + return render(s_sub(block, e), values); elseif opt == '?' and not value then return render(s_sub(block, e), values); elseif value ~= nil then -- cgit v1.2.3 From 686adb2d713f0a1b5f8ec27221fcf00f6ece886f Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Wed, 9 Sep 2020 17:12:00 +0100 Subject: util.interpolation: Add test for ~ when value is false (not just nil) --- spec/util_interpolation_spec.lua | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/spec/util_interpolation_spec.lua b/spec/util_interpolation_spec.lua index 98ed3b74..614d1f54 100644 --- a/spec/util_interpolation_spec.lua +++ b/spec/util_interpolation_spec.lua @@ -33,13 +33,16 @@ local expect_map = [[ FOO: bar ]] local template_not = [[ -{thing~Thing is nil}{thing&Thing is not nil} +{thing~Thing is falsy}{thing&Thing is truthy} ]] local expect_not_true = [[ -Thing is not nil +Thing is truthy ]] local expect_not_nil = [[ -Thing is nil +Thing is falsy +]] +local expect_not_false = [[ +Thing is falsy ]] describe("util.interpolation", function () it("renders", function () @@ -53,5 +56,6 @@ describe("util.interpolation", function () assert.equal(expect_map, render(template_map, { foo = { foo = "bar" } })); assert.equal(expect_not_true, render(template_not, { thing = true })); assert.equal(expect_not_nil, render(template_not, { thing = nil })); + assert.equal(expect_not_false, render(template_not, { thing = false })); end); end); -- cgit v1.2.3 From 29f2e5906fab2bc82b04041a1e29399d044a662f Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Fri, 11 Sep 2020 12:37:07 +0100 Subject: mod_http: Silence warnings when running under prosodyctl --- plugins/mod_http.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/mod_http.lua b/plugins/mod_http.lua index 1248a06c..84e2d18f 100644 --- a/plugins/mod_http.lua +++ b/plugins/mod_http.lua @@ -93,7 +93,9 @@ function moduleapi.http_url(module, app_name, default_path) return url_build(url); end end - module:log("warn", "No http ports enabled, can't generate an external URL"); + if prosody.process_type == "prosody" then + module:log("warn", "No http ports enabled, can't generate an external URL"); + end return "http://disabled.invalid/"; end @@ -190,7 +192,7 @@ function module.add_host(module) local services = portmanager.get_active_services(); if services:get("https") or services:get("http") then module:log("info", "Serving '%s' at %s", app_name, module:http_url(app_name, app_path)); - else + elseif prosody.process_type == "prosody" then module:log("warn", "Not listening on any ports, '%s' will be unreachable", app_name); end end -- cgit v1.2.3 From 1f159505104015652a3571756aaa71112938c4e9 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 16 Sep 2020 18:16:08 +0200 Subject: doap: Add XEP-0307 Support first added in 2919f3b985fc and later moved into its own module in 3a1c39b31497 --- doc/doap.xml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/doc/doap.xml b/doc/doap.xml index 6024903f..7e9ef815 100644 --- a/doc/doap.xml +++ b/doc/doap.xml @@ -489,6 +489,15 @@ Core Server + + + + 0.1 + complete + 0.6 + Moved into mod_muc_unique in 0.11 + + -- cgit v1.2.3 From bc20052a9b53c466db12e7d35cf6c073c0ee5a3e Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 21 Nov 2019 18:56:43 +0100 Subject: util.dataforms: Add support for validating (integer) ranges --- spec/util_dataforms_spec.lua | 9 +++++++++ util/dataforms.lua | 14 +++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/spec/util_dataforms_spec.lua b/spec/util_dataforms_spec.lua index 0df9fb1d..d2d1264a 100644 --- a/spec/util_dataforms_spec.lua +++ b/spec/util_dataforms_spec.lua @@ -423,6 +423,8 @@ describe("util.dataforms", function () name = "number", type = "text-single", datatype = "xs:integer", + range_min = -10, + range_max = 10, }, }; @@ -437,6 +439,13 @@ describe("util.dataforms", function () assert.table(e); assert.string(e.number); end); + + it("works", function () + local d,e = f:data(f:form({number = 100})); + assert.not_equal(100, d.number); + assert.table(e); + assert.string(e.number); + end); end); describe("media element", function () it("produced media element correctly", function () diff --git a/util/dataforms.lua b/util/dataforms.lua index 449c1a58..efb13ac9 100644 --- a/util/dataforms.lua +++ b/util/dataforms.lua @@ -10,6 +10,7 @@ local setmetatable = setmetatable; local ipairs = ipairs; local type, next = type, next; local tonumber = tonumber; +local tostring = tostring; local t_concat = table.concat; local st = require "util.stanza"; local jid_prep = require "util.jid".prep; @@ -54,6 +55,12 @@ function form_t.form(layout, data, formtype) if formtype == "form" and field.datatype then form:tag("validate", { xmlns = xmlns_validate, datatype = field.datatype }); + if field.range_min or field.range_max then + form:tag("range", { + min = field.range_min and tostring(field.range_min), + max = field.range_max and tostring(field.range_max), + }):up(); + end -- assumed form:up(); end @@ -290,13 +297,18 @@ field_readers["hidden"] = end data_validators["xs:integer"] = - function (data) + function (data, field) local n = tonumber(data); if not n then return false, "not a number"; elseif n % 1 ~= 0 then return false, "not an integer"; end + if field.range_max and n > field.range_max then + return false, "out of bounds"; + elseif field.range_min and n < field.range_min then + return false, "out of bounds"; + end return true, n; end -- cgit v1.2.3 From 646bd1da3df6ca12ea02e036ed2af9b8e9eb173d Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Fri, 25 Sep 2020 12:18:18 +0100 Subject: util.error: Add unique 'instance_id' to error objects --- util/error.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/util/error.lua b/util/error.lua index c46f8790..2c96bc03 100644 --- a/util/error.lua +++ b/util/error.lua @@ -1,3 +1,4 @@ +local id = require "util.id"; -- Library configuration (see configure()) local auto_inject_traceback = false; @@ -42,6 +43,7 @@ local function new(e, context, registry, source) end return setmetatable({ + instance_id = id.short(); type = template.type or "cancel"; condition = template.condition or "undefined-condition"; text = template.text; -- cgit v1.2.3 From 73e87f3901e29b91662717b6cddb49b6f5d27858 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Fri, 25 Sep 2020 12:19:30 +0100 Subject: util.error: Simplify error creation - remove ability to set context from templates, and remove default context --- util/error.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/error.lua b/util/error.lua index 2c96bc03..ade1bb3b 100644 --- a/util/error.lua +++ b/util/error.lua @@ -36,7 +36,7 @@ end local function new(e, context, registry, source) local template = (registry and registry[e]) or e or {}; - context = context or template.context or { _error_id = e }; + context = context or {}; if auto_inject_traceback then context.traceback = debug.traceback("error stack", 2); -- cgit v1.2.3 From 9c709d29fd4e37aeb41a9959889347e8d579273b Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Fri, 25 Sep 2020 12:27:45 +0100 Subject: util.error: Minor tweaks to error creation code to prepare for future changes --- util/error.lua | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/util/error.lua b/util/error.lua index ade1bb3b..4079a876 100644 --- a/util/error.lua +++ b/util/error.lua @@ -42,16 +42,19 @@ local function new(e, context, registry, source) context.traceback = debug.traceback("error stack", 2); end - return setmetatable({ + local error_instance = setmetatable({ instance_id = id.short(); + type = template.type or "cancel"; condition = template.condition or "undefined-condition"; text = template.text; code = template.code; - context = context or template.context or { _error_id = e }; + context = context; source = source; }, error_mt); + + return error_instance; end local function init(source, registry) -- cgit v1.2.3 From 891a03885a2b183425f7318a89e8d91319b55681 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Fri, 25 Sep 2020 12:32:43 +0100 Subject: util.error: Have init() return an object to allow API extensibility via additional methods --- util/error.lua | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/util/error.lua b/util/error.lua index 4079a876..00192273 100644 --- a/util/error.lua +++ b/util/error.lua @@ -58,9 +58,11 @@ local function new(e, context, registry, source) end local function init(source, registry) - return function (e, context) - return new(e, context, registry, source); - end + return { + new = function (e, context) + return new(e, context, registry, source); + end; + }; end local function coerce(ok, err, ...) -- cgit v1.2.3 From 2595c39a8a79ea65a100709877548efeb0c1e061 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Fri, 25 Sep 2020 12:38:58 +0100 Subject: util.error: Switch coerce() to use new() and change 'native' to context field 'wrapped_error' --- util/error.lua | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/util/error.lua b/util/error.lua index 00192273..976bc355 100644 --- a/util/error.lua +++ b/util/error.lua @@ -70,12 +70,10 @@ local function coerce(ok, err, ...) return ok, err, ...; end - local new_err = setmetatable({ - native = err; + local new_err = new({ + type = "cancel", condition = "undefined-condition" + }, { wrapped_error = err }); - type = "cancel"; - condition = "undefined-condition"; - }, error_mt); return ok, new_err, ...; end -- cgit v1.2.3 From a34633771db29e751058f93b1abbfd6801283364 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Fri, 25 Sep 2020 16:39:22 +0100 Subject: util.error: Simplify error creation flow --- util/error.lua | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/util/error.lua b/util/error.lua index 976bc355..cfb35350 100644 --- a/util/error.lua +++ b/util/error.lua @@ -35,7 +35,19 @@ end -- What to set `type` to for stream errors or SASL errors? Those don't have a 'type' attr. local function new(e, context, registry, source) - local template = (registry and registry[e]) or e or {}; + local template = registry and registry[e]; + if not template then + if type(e) == "table" then + template = { + code = e.code; + type = e.type; + condition = e.condition; + text = e.text; + }; + else + template = {}; + end + end context = context or {}; if auto_inject_traceback then -- cgit v1.2.3 From ed5841e42b9c6b2ae31ba5f9a5767e67e2fb71d4 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 26 Sep 2020 17:16:34 +0200 Subject: util.error: Add well-known field 'extra' A place for various extra fields and edge cases of the stanza error data model, e.g. the URI field of --- spec/util_error_spec.lua | 8 ++++++++ util/error.lua | 2 ++ 2 files changed, 10 insertions(+) diff --git a/spec/util_error_spec.lua b/spec/util_error_spec.lua index ca053285..136f8b12 100644 --- a/spec/util_error_spec.lua +++ b/spec/util_error_spec.lua @@ -66,5 +66,13 @@ describe("util.error", function () end); end); + describe("extra", function () + it("keeps some extra fields", function () + local err = errors.new({condition="gone",text="Sorry mate, it's all gone",extra={uri="file:///dev/null"}}); + assert.is_table(err.extra); + assert.equal("file:///dev/null", err.extra.uri); + end); + end) + end); diff --git a/util/error.lua b/util/error.lua index cfb35350..e0b1f9a6 100644 --- a/util/error.lua +++ b/util/error.lua @@ -43,6 +43,7 @@ local function new(e, context, registry, source) type = e.type; condition = e.condition; text = e.text; + extra = e.extra; }; else template = {}; @@ -61,6 +62,7 @@ local function new(e, context, registry, source) condition = template.condition or "undefined-condition"; text = template.text; code = template.code; + extra = template.extra; context = context; source = source; -- cgit v1.2.3 From d9e80064b5e57d3055a4d0e93f732e14c4c11a11 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 26 Sep 2020 17:18:17 +0200 Subject: util.stanza: Reorder code to prepare for extracting 'by' from util.error --- util/stanza.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/util/stanza.lua b/util/stanza.lua index a8a417ab..7d45d444 100644 --- a/util/stanza.lua +++ b/util/stanza.lua @@ -455,12 +455,12 @@ local function error_reply(orig, error_type, condition, error_message, error_by) end local t = reply(orig); t.attr.type = "error"; - if t.attr.from == error_by then - error_by = nil; - end if type(error_type) == "table" then -- an util.error or similar object error_type, condition, error_message = error_type.type, error_type.condition, error_type.text; end + if t.attr.from == error_by then + error_by = nil; + end t:tag("error", {type = error_type, by = error_by}) --COMPAT: Some day xmlns:stanzas goes here :tag(condition, xmpp_stanzas_attr):up(); if error_message then t:text_tag("text", error_message, xmpp_stanzas_attr); end -- cgit v1.2.3 From 2f0b85ce29a6b4eaa210a60a5d04610f0401379d Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 26 Sep 2020 17:26:31 +0200 Subject: util.stanza: Support getting 'by' from util.error object --- spec/util_stanza_spec.lua | 3 ++- util/stanza.lua | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/spec/util_stanza_spec.lua b/spec/util_stanza_spec.lua index efe3e47e..ec392611 100644 --- a/spec/util_stanza_spec.lua +++ b/spec/util_stanza_spec.lua @@ -234,7 +234,7 @@ describe("util.stanza", function() it("should accept util.error objects", function () local s = st.message({ to = "touser", from = "fromuser", id = "123", type = "chat" }, "Hello"); - local e = errors.new({ type = "modify", condition = "not-acceptable", text = "Bork bork bork" }); + local e = errors.new({ type = "modify", condition = "not-acceptable", text = "Bork bork bork", extra = { by = "this.test" } }); local r = st.error_reply(s, e); assert.are.equal(r.name, s.name); @@ -246,6 +246,7 @@ describe("util.stanza", function() assert.are.equal(r.tags[1].attr.type, e.type); assert.are.equal(r.tags[1].tags[1].name, e.condition); assert.are.equal(r.tags[1].tags[2]:get_text(), e.text); + assert.are.equal("this.test", r.tags[1].attr.by); end); end); diff --git a/util/stanza.lua b/util/stanza.lua index 7d45d444..3c415f20 100644 --- a/util/stanza.lua +++ b/util/stanza.lua @@ -456,6 +456,9 @@ local function error_reply(orig, error_type, condition, error_message, error_by) local t = reply(orig); t.attr.type = "error"; if type(error_type) == "table" then -- an util.error or similar object + if type(error_type.extra) == "table" then + if type(error_type.extra.by) == "string" then error_by = error_type.extra.by; end + end error_type, condition, error_message = error_type.type, error_type.condition, error_type.text; end if t.attr.from == error_by then -- cgit v1.2.3 From 3f9988468d11d6e3b94fec0ec1ed4271712bed14 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 26 Sep 2020 17:30:47 +0200 Subject: util.stanza: Support inclusion of URI from util.error object --- spec/util_stanza_spec.lua | 5 +++++ util/stanza.lua | 10 ++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/spec/util_stanza_spec.lua b/spec/util_stanza_spec.lua index ec392611..c4c1d174 100644 --- a/spec/util_stanza_spec.lua +++ b/spec/util_stanza_spec.lua @@ -247,6 +247,11 @@ describe("util.stanza", function() assert.are.equal(r.tags[1].tags[1].name, e.condition); assert.are.equal(r.tags[1].tags[2]:get_text(), e.text); assert.are.equal("this.test", r.tags[1].attr.by); + + local gone = errors.new({ condition = "gone", extra = { uri = "file:///dev/null" } }) + local gonner = st.error_reply(s, gone); + assert.are.equal("gone", gonner.tags[1].tags[1].name); + assert.are.equal("file:///dev/null", gonner.tags[1].tags[1][1]); end); end); diff --git a/util/stanza.lua b/util/stanza.lua index 3c415f20..2c936a40 100644 --- a/util/stanza.lua +++ b/util/stanza.lua @@ -455,9 +455,11 @@ local function error_reply(orig, error_type, condition, error_message, error_by) end local t = reply(orig); t.attr.type = "error"; + local extra; if type(error_type) == "table" then -- an util.error or similar object if type(error_type.extra) == "table" then - if type(error_type.extra.by) == "string" then error_by = error_type.extra.by; end + extra = error_type.extra; + if type(extra.by) == "string" then error_by = extra.by; end end error_type, condition, error_message = error_type.type, error_type.condition, error_type.text; end @@ -465,7 +467,11 @@ local function error_reply(orig, error_type, condition, error_message, error_by) error_by = nil; end t:tag("error", {type = error_type, by = error_by}) --COMPAT: Some day xmlns:stanzas goes here - :tag(condition, xmpp_stanzas_attr):up(); + :tag(condition, xmpp_stanzas_attr); + if extra and condition == "gone" and type(extra.uri) == "string" then + t:text(extra.uri); + end + t:up(); if error_message then t:text_tag("text", error_message, xmpp_stanzas_attr); end return t; -- stanza ready for adding app-specific errors end -- cgit v1.2.3 From 8c0efc9e55d0980e2ce5872da325724aeaec5dba Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 26 Sep 2020 18:07:33 +0200 Subject: util.stanza: Get 'by' from context instead Zash> should go where? extra.by? context? source? Zash> In Prosody this would usually be module.host or a bare user/room JID MattJ> Zash: context MattJ> context.by, basically the opposite of context.actor --- spec/util_stanza_spec.lua | 2 +- util/stanza.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/util_stanza_spec.lua b/spec/util_stanza_spec.lua index c4c1d174..cc32bba9 100644 --- a/spec/util_stanza_spec.lua +++ b/spec/util_stanza_spec.lua @@ -234,7 +234,7 @@ describe("util.stanza", function() it("should accept util.error objects", function () local s = st.message({ to = "touser", from = "fromuser", id = "123", type = "chat" }, "Hello"); - local e = errors.new({ type = "modify", condition = "not-acceptable", text = "Bork bork bork", extra = { by = "this.test" } }); + local e = errors.new({ type = "modify", condition = "not-acceptable", text = "Bork bork bork" }, { by = "this.test" }); local r = st.error_reply(s, e); assert.are.equal(r.name, s.name); diff --git a/util/stanza.lua b/util/stanza.lua index 2c936a40..3d0d5002 100644 --- a/util/stanza.lua +++ b/util/stanza.lua @@ -459,8 +459,8 @@ local function error_reply(orig, error_type, condition, error_message, error_by) if type(error_type) == "table" then -- an util.error or similar object if type(error_type.extra) == "table" then extra = error_type.extra; - if type(extra.by) == "string" then error_by = extra.by; end end + if type(error_type.context) == "table" and type(error_type.context.by) == "string" then error_by = error_type.context.by; end error_type, condition, error_message = error_type.type, error_type.condition, error_type.text; end if t.attr.from == error_by then -- cgit v1.2.3 From 1c53f533b09008be2ed26e3ffccc31c2609cc566 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 26 Sep 2020 18:09:10 +0200 Subject: util.stanza: Support Application-Specific Conditions in util.error --- spec/util_stanza_spec.lua | 14 +++++++++++++- util/stanza.lua | 5 +++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/spec/util_stanza_spec.lua b/spec/util_stanza_spec.lua index cc32bba9..ba90eb84 100644 --- a/spec/util_stanza_spec.lua +++ b/spec/util_stanza_spec.lua @@ -252,8 +252,20 @@ describe("util.stanza", function() local gonner = st.error_reply(s, gone); assert.are.equal("gone", gonner.tags[1].tags[1].name); assert.are.equal("file:///dev/null", gonner.tags[1].tags[1][1]); - end); + local e = errors.new({ condition = "internal-server-error", text = "Namespaced thing happened", + extra = {namespace="xmpp:example.test", condition="this-happened"} }) + local r = st.error_reply(s, e); + assert.are.equal("xmpp:example.test", r.tags[1].tags[3].attr.xmlns); + assert.are.equal("this-happened", r.tags[1].tags[3].name); + + local e2 = errors.new({ condition = "internal-server-error", text = "Namespaced thing happened", + extra = {tag=st.stanza("that-happened", { xmlns = "xmpp:example.test", ["another-attribute"] = "here" })} }) + local r2 = st.error_reply(s, e2); + assert.are.equal("xmpp:example.test", r2.tags[1].tags[3].attr.xmlns); + assert.are.equal("that-happened", r2.tags[1].tags[3].name); + assert.are.equal("here", r2.tags[1].tags[3].attr["another-attribute"]); + end); end); describe("should reject #invalid", function () diff --git a/util/stanza.lua b/util/stanza.lua index 3d0d5002..9bcf3747 100644 --- a/util/stanza.lua +++ b/util/stanza.lua @@ -473,6 +473,11 @@ local function error_reply(orig, error_type, condition, error_message, error_by) end t:up(); if error_message then t:text_tag("text", error_message, xmpp_stanzas_attr); end + if extra and is_stanza(extra.tag) then + t:add_child(extra.tag); + elseif extra and extra.namespace and extra.condition then + t:tag(extra.condition, { xmlns = extra.namespace }):up(); + end return t; -- stanza ready for adding app-specific errors end -- cgit v1.2.3 From a7c3fc20a079422cf597a5a20e88c6e58054de39 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 26 Sep 2020 19:00:17 +0200 Subject: spec.stanza spec: Split up util.error related tests --- spec/util_stanza_spec.lua | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/spec/util_stanza_spec.lua b/spec/util_stanza_spec.lua index ba90eb84..a363086c 100644 --- a/spec/util_stanza_spec.lua +++ b/spec/util_stanza_spec.lua @@ -232,6 +232,7 @@ describe("util.stanza", function() end, "got stanza of type error"); end); + describe("util.error integration", function () it("should accept util.error objects", function () local s = st.message({ to = "touser", from = "fromuser", id = "123", type = "chat" }, "Hello"); local e = errors.new({ type = "modify", condition = "not-acceptable", text = "Bork bork bork" }, { by = "this.test" }); @@ -247,12 +248,18 @@ describe("util.stanza", function() assert.are.equal(r.tags[1].tags[1].name, e.condition); assert.are.equal(r.tags[1].tags[2]:get_text(), e.text); assert.are.equal("this.test", r.tags[1].attr.by); + end); + it("should accept util.error objects with an URI", function () + local s = st.message({ to = "touser", from = "fromuser", id = "123", type = "chat" }, "Hello"); local gone = errors.new({ condition = "gone", extra = { uri = "file:///dev/null" } }) local gonner = st.error_reply(s, gone); assert.are.equal("gone", gonner.tags[1].tags[1].name); assert.are.equal("file:///dev/null", gonner.tags[1].tags[1][1]); + end); + it("should accept util.error objects with application specific error", function () + local s = st.message({ to = "touser", from = "fromuser", id = "123", type = "chat" }, "Hello"); local e = errors.new({ condition = "internal-server-error", text = "Namespaced thing happened", extra = {namespace="xmpp:example.test", condition="this-happened"} }) local r = st.error_reply(s, e); @@ -266,6 +273,7 @@ describe("util.stanza", function() assert.are.equal("that-happened", r2.tags[1].tags[3].name); assert.are.equal("here", r2.tags[1].tags[3].attr["another-attribute"]); end); + end); end); describe("should reject #invalid", function () -- cgit v1.2.3 From 815ce25d10e49f4efa7b402c8d5b03a0f63474f1 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 26 Sep 2020 18:12:18 +0200 Subject: util.stanza: Extract Application-Specific Condition from errors API change --- spec/util_stanza_spec.lua | 13 +++++++++++++ util/stanza.lua | 14 ++++++++------ 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/spec/util_stanza_spec.lua b/spec/util_stanza_spec.lua index a363086c..535f783b 100644 --- a/spec/util_stanza_spec.lua +++ b/spec/util_stanza_spec.lua @@ -276,6 +276,19 @@ describe("util.stanza", function() end); end); + describe("#get_error()", function () + describe("basics", function () + local s = st.message(); + local e = st.error_reply(s, "cancel", "not-acceptable", "UNACCEPTABLE!!!! ONE MILLION YEARS DUNGEON!") + :tag("dungeon", { xmlns = "urn:uuid:c9026187-5b05-4e70-b265-c3b6338a7d0f", period="1000000years"}); + local typ, cond, text, extra = e:get_error(); + assert.equal("cancel", typ); + assert.equal("not-acceptable", cond); + assert.equal("UNACCEPTABLE!!!! ONE MILLION YEARS DUNGEON!", text); + assert.not_nil(extra) + end) + end) + describe("should reject #invalid", function () local invalid_names = { ["empty string"] = "", ["characters"] = "<>"; diff --git a/util/stanza.lua b/util/stanza.lua index 9bcf3747..94815346 100644 --- a/util/stanza.lua +++ b/util/stanza.lua @@ -349,11 +349,11 @@ function stanza_mt.get_text(t) end function stanza_mt.get_error(stanza) - local error_type, condition, text; + local error_type, condition, text, extra_tag; local error_tag = stanza:get_child("error"); if not error_tag then - return nil, nil, nil; + return nil, nil, nil, nil; end error_type = error_tag.attr.type; @@ -364,12 +364,14 @@ function stanza_mt.get_error(stanza) elseif not condition then condition = child.name; end - if condition and text then - break; - end + else + extra_tag = child; + end + if condition and text and extra_tag then + break; end end - return error_type, condition or "undefined-condition", text; + return error_type, condition or "undefined-condition", text, extra_tag; end local function preserialize(stanza) -- cgit v1.2.3 From 0354452a9a537d141e61d09b553e1ceadf97cf42 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 26 Sep 2020 18:13:27 +0200 Subject: util.error: Extract error originator from stanza errors --- spec/util_error_spec.lua | 3 ++- util/error.lua | 7 ++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/spec/util_error_spec.lua b/spec/util_error_spec.lua index 136f8b12..9cb1c57b 100644 --- a/spec/util_error_spec.lua +++ b/spec/util_error_spec.lua @@ -48,12 +48,13 @@ describe("util.error", 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 e = st.error_reply(m, "modify", "bad-request", nil, "error.example"); 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); + assert.equal("error.example", err.context.by); end); end); diff --git a/util/error.lua b/util/error.lua index e0b1f9a6..c2b7d149 100644 --- a/util/error.lua +++ b/util/error.lua @@ -93,12 +93,17 @@ end local function from_stanza(stanza, context) local error_type, condition, text = stanza:get_error(); + local error_tag = stanza:get_child("error"); + context = context or {}; + context.stanza = stanza; + context.by = error_tag.attr.by; return setmetatable({ type = error_type or "cancel"; condition = condition or "undefined-condition"; text = text; - context = context or { stanza = stanza }; + context = context; + }, error_mt); end -- cgit v1.2.3 From 8627f2e4d560a5fa4a2f9acf638f6535b9df8f26 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 26 Sep 2020 18:14:10 +0200 Subject: util.error: Default error originator to stanza sender The @by attribute is primarily useful for errors caused by intermediate entities. --- util/error.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/util/error.lua b/util/error.lua index c2b7d149..0fe6cfa2 100644 --- a/util/error.lua +++ b/util/error.lua @@ -96,7 +96,8 @@ local function from_stanza(stanza, context) local error_tag = stanza:get_child("error"); context = context or {}; context.stanza = stanza; - context.by = error_tag.attr.by; + context.by = error_tag.attr.by or stanza.attr.from; + return setmetatable({ type = error_type or "cancel"; condition = condition or "undefined-condition"; -- cgit v1.2.3 From 3c7cbd0e2cbf471addcc332f2a8ba5cd635ae845 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 26 Sep 2020 18:15:27 +0200 Subject: util.error: Add special case handling of with an URI --- util/error.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/util/error.lua b/util/error.lua index 0fe6cfa2..c0b87d86 100644 --- a/util/error.lua +++ b/util/error.lua @@ -102,6 +102,9 @@ local function from_stanza(stanza, context) type = error_type or "cancel"; condition = condition or "undefined-condition"; text = text; + extra = condition == "gone" and { + uri = error_tag:get_child_text("gone", "urn:ietf:params:xml:ns:xmpp-stanzas"); + } or nil; context = context; -- cgit v1.2.3 From a51d5912465d8129ab062572d1f2e2bdddf967e6 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 26 Sep 2020 18:15:49 +0200 Subject: util.error: Collect Application-Specific Conditions from stanza errors --- spec/util_error_spec.lua | 3 ++- util/error.lua | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/spec/util_error_spec.lua b/spec/util_error_spec.lua index 9cb1c57b..bb85d303 100644 --- a/spec/util_error_spec.lua +++ b/spec/util_error_spec.lua @@ -48,13 +48,14 @@ describe("util.error", function () it("works", function () local st = require "util.stanza"; local m = st.message({ type = "chat" }); - local e = st.error_reply(m, "modify", "bad-request", nil, "error.example"); + local e = st.error_reply(m, "modify", "bad-request", nil, "error.example"):tag("extra", { xmlns = "xmpp:example.test" }); 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); assert.equal("error.example", err.context.by); + assert.not_nil(err.extra.tag); end); end); diff --git a/util/error.lua b/util/error.lua index c0b87d86..92567248 100644 --- a/util/error.lua +++ b/util/error.lua @@ -92,7 +92,7 @@ local function coerce(ok, err, ...) end local function from_stanza(stanza, context) - local error_type, condition, text = stanza:get_error(); + local error_type, condition, text, extra_tag = stanza:get_error(); local error_tag = stanza:get_child("error"); context = context or {}; context.stanza = stanza; @@ -102,8 +102,9 @@ local function from_stanza(stanza, context) type = error_type or "cancel"; condition = condition or "undefined-condition"; text = text; - extra = condition == "gone" and { + extra = (extra_tag or condition == "gone") and { uri = error_tag:get_child_text("gone", "urn:ietf:params:xml:ns:xmpp-stanzas"); + tag = extra_tag; } or nil; context = context; -- cgit v1.2.3 From 487f96c2f1700d17e5ed7623183a26a5345a3537 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 26 Sep 2020 23:17:53 +0200 Subject: util.serialization: Let freeze metamethod return a literal string Enables custom serialization, such as creating a datatype that serializes into a variable reference. --- util/serialization.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/util/serialization.lua b/util/serialization.lua index d70e92ba..d310a3e8 100644 --- a/util/serialization.lua +++ b/util/serialization.lua @@ -150,6 +150,10 @@ local function new(opt) if type(fr) == "function" then t = fr(t); + if type(t) == "string" then + o[l], l = t, l + 1; + return l; + end if type(tag) == "string" then o[l], l = tag, l + 1; end -- cgit v1.2.3 From 60a6ee0d30769c9066fae19b7db7dbf92ea777b7 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 27 Sep 2020 00:17:48 +0200 Subject: util.error: Pass converted stanza errors throguh new() In order to benefit from common processing --- util/error.lua | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/util/error.lua b/util/error.lua index 92567248..fd6deb80 100644 --- a/util/error.lua +++ b/util/error.lua @@ -91,14 +91,14 @@ local function coerce(ok, err, ...) return ok, new_err, ...; end -local function from_stanza(stanza, context) +local function from_stanza(stanza, context, source) local error_type, condition, text, extra_tag = stanza:get_error(); local error_tag = stanza:get_child("error"); context = context or {}; context.stanza = stanza; context.by = error_tag.attr.by or stanza.attr.from; - return setmetatable({ + return new({ type = error_type or "cancel"; condition = condition or "undefined-condition"; text = text; @@ -106,10 +106,7 @@ local function from_stanza(stanza, context) uri = error_tag:get_child_text("gone", "urn:ietf:params:xml:ns:xmpp-stanzas"); tag = extra_tag; } or nil; - - context = context; - - }, error_mt); + }, context, nil, source); end return { -- cgit v1.2.3 From 04be6c2e839894756eaf4a3aa53a823508f14b12 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 28 Sep 2020 01:55:35 +0200 Subject: util.error: Turns out wasn't alone, there's also --- util/error.lua | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/util/error.lua b/util/error.lua index fd6deb80..44eb59a2 100644 --- a/util/error.lua +++ b/util/error.lua @@ -98,12 +98,17 @@ local function from_stanza(stanza, context, source) context.stanza = stanza; context.by = error_tag.attr.by or stanza.attr.from; + local uri; + if condition == "gone" or condition == "redirect" then + uri = error_tag:get_child_text(condition, "urn:ietf:params:xml:ns:xmpp-stanzas"); + end + return new({ type = error_type or "cancel"; condition = condition or "undefined-condition"; text = text; - extra = (extra_tag or condition == "gone") and { - uri = error_tag:get_child_text("gone", "urn:ietf:params:xml:ns:xmpp-stanzas"); + extra = (extra_tag or uri) and { + uri = uri; tag = extra_tag; } or nil; }, context, nil, source); -- cgit v1.2.3 From 9229c7a57188168e992145e4f3be6fd381ffe90c Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 28 Sep 2020 16:21:41 +0100 Subject: net.http.server: Default to HTTP result code 500 when promise is rejected --- net/http/server.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/net/http/server.lua b/net/http/server.lua index f563d330..0070367a 100644 --- a/net/http/server.lua +++ b/net/http/server.lua @@ -197,6 +197,7 @@ local function handle_result(request, response, result) result:next(function (ret) handle_result(request, response, ret); end, function (err) + response.status_code = 500; handle_result(request, response, err or 500); end); return true; -- cgit v1.2.3 From dc446463ab10d466f5c67cab4530156a85d4dd9f Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 28 Sep 2020 18:36:00 +0200 Subject: util.error: Cover registry initialization in test --- spec/util_error_spec.lua | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/spec/util_error_spec.lua b/spec/util_error_spec.lua index bb85d303..6314d9ff 100644 --- a/spec/util_error_spec.lua +++ b/spec/util_error_spec.lua @@ -76,5 +76,25 @@ describe("util.error", function () end); end) + describe("init", function() + it("basics works", function() + local reg = errors.init("test", { + broke = {type = "cancel"; condition = "internal-server-error"; text = "It broke :("}; + nope = {type = "auth"; condition = "not-authorized"; text = "Can't let you do that Dave"}; + }); + + local broke = reg.new("broke"); + assert.equal("cancel", broke.type); + assert.equal("internal-server-error", broke.condition); + assert.equal("It broke :(", broke.text); + assert.equal("test", broke.source); + + local nope = reg.new("nope"); + assert.equal("auth", nope.type); + assert.equal("not-authorized", nope.condition); + assert.equal("Can't let you do that Dave", nope.text); + end); + end); + end); -- cgit v1.2.3 From d101532cfcafd983a09ba5bbc990b5fe26c75d83 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 28 Sep 2020 19:26:48 +0200 Subject: util.error: Expose source and registry as fields on the registry object For access, e.g. to identify and compare errors later --- util/error.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/util/error.lua b/util/error.lua index 44eb59a2..47d4e7b6 100644 --- a/util/error.lua +++ b/util/error.lua @@ -73,6 +73,8 @@ end local function init(source, registry) return { + source = source; + registry = registry; new = function (e, context) return new(e, context, registry, source); end; -- cgit v1.2.3 From 9a5debbb9fbbfb19a5112b2f8cfda3ba8f936dfb Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 28 Sep 2020 19:32:54 +0200 Subject: CHANGES: Add util.error --- CHANGES | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES b/CHANGES index 080ee3d7..619dec03 100644 --- a/CHANGES +++ b/CHANGES @@ -18,6 +18,7 @@ TRUNK - SASL DIGEST-MD5 removed - Switch to libunbound for DNS queries - mod_external_services (XEP-0215) +- util.error for encapsulating errors 0.11.0 ====== -- cgit v1.2.3 From 5da983d8bdc574f9358a16eb08d700dde3ca7026 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 28 Sep 2020 18:39:51 +0200 Subject: util.error: Add a "compact mode" for registries Inspired by the older registry in pubsub.lib.lua --- spec/util_error_spec.lua | 21 +++++++++++++++++++++ util/error.lua | 11 +++++++---- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/spec/util_error_spec.lua b/spec/util_error_spec.lua index 6314d9ff..399b5998 100644 --- a/spec/util_error_spec.lua +++ b/spec/util_error_spec.lua @@ -94,6 +94,27 @@ describe("util.error", function () assert.equal("not-authorized", nope.condition); assert.equal("Can't let you do that Dave", nope.text); end); + + it("compact mode works", function() + local reg = errors.init("test", { + namespace = "spec"; + broke = {"cancel"; "internal-server-error"; "It broke :("}; + nope = {"auth"; "not-authorized"; "Can't let you do that Dave"; "sorry-dave"}; + }); + + local broke = reg.new("broke"); + assert.equal("cancel", broke.type); + assert.equal("internal-server-error", broke.condition); + assert.equal("It broke :(", broke.text); + assert.is_nil(broke.extra); + + local nope = reg.new("nope"); + assert.equal("auth", nope.type); + assert.equal("not-authorized", nope.condition); + assert.equal("Can't let you do that Dave", nope.text); + assert.equal("spec", nope.extra.namespace); + assert.equal("sorry-dave", nope.extra.condition); + end); end); end); diff --git a/util/error.lua b/util/error.lua index 47d4e7b6..2e4118ec 100644 --- a/util/error.lua +++ b/util/error.lua @@ -58,11 +58,14 @@ local function new(e, context, registry, source) local error_instance = setmetatable({ instance_id = id.short(); - type = template.type or "cancel"; - condition = template.condition or "undefined-condition"; - text = template.text; + type = template.type or template[1] or "cancel"; + condition = template.condition or template[2] or "undefined-condition"; + text = template.text or template[3]; code = template.code; - extra = template.extra; + extra = template.extra or (registry and registry.namespace and template[4] and { + namespace = registry.namespace; + condition = template[4] + }); context = context; source = source; -- cgit v1.2.3 From 9dbdb91c471ea765936a15af02b62e4cbf7ba7a2 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 28 Sep 2020 22:13:04 +0200 Subject: util.error: Expand compact registries into normal form internally Also the exposed form on the table returned from init() --- spec/util_error_spec.lua | 32 ++++++++++++++++++++++++++++++++ util/error.lua | 42 ++++++++++++++++++++++++++++++++++-------- 2 files changed, 66 insertions(+), 8 deletions(-) diff --git a/spec/util_error_spec.lua b/spec/util_error_spec.lua index 399b5998..8b995895 100644 --- a/spec/util_error_spec.lua +++ b/spec/util_error_spec.lua @@ -115,6 +115,38 @@ describe("util.error", function () assert.equal("spec", nope.extra.namespace); assert.equal("sorry-dave", nope.extra.condition); end); + + it("registry looks the same regardless of syntax", function() + local normal = errors.init("test", { + broke = {type = "cancel"; condition = "internal-server-error"; text = "It broke :("}; + nope = { + type = "auth"; + condition = "not-authorized"; + text = "Can't let you do that Dave"; + extra = {namespace = "spec"; condition = "sorry-dave"}; + }; + }); + local compact1 = errors.init("test", { + namespace = "spec"; + broke = {"cancel"; "internal-server-error"; "It broke :("}; + nope = {"auth"; "not-authorized"; "Can't let you do that Dave"; "sorry-dave"}; + }); + local compact2 = errors.init("test", "spec", { + broke = {"cancel"; "internal-server-error"; "It broke :("}; + nope = {"auth"; "not-authorized"; "Can't let you do that Dave"; "sorry-dave"}; + }); + local compact3 = errors.init("test", { + broke = {"cancel"; "internal-server-error"; "It broke :("}; + nope = {"auth"; "not-authorized"; "Can't let you do that Dave"}; + }); + assert.same(normal.registry, compact1.registry); + assert.same(normal.registry, compact2.registry); + + assert.same({ + broke = {type = "cancel"; condition = "internal-server-error"; text = "It broke :("}; + nope = {type = "auth"; condition = "not-authorized"; text = "Can't let you do that Dave"}; + }, compact3.registry); + end); end); end); diff --git a/util/error.lua b/util/error.lua index 2e4118ec..5bd0f80e 100644 --- a/util/error.lua +++ b/util/error.lua @@ -58,14 +58,11 @@ local function new(e, context, registry, source) local error_instance = setmetatable({ instance_id = id.short(); - type = template.type or template[1] or "cancel"; - condition = template.condition or template[2] or "undefined-condition"; - text = template.text or template[3]; + type = template.type or "cancel"; + condition = template.condition or "undefined-condition"; + text = template.text; code = template.code; - extra = template.extra or (registry and registry.namespace and template[4] and { - namespace = registry.namespace; - condition = template[4] - }); + extra = template.extra; context = context; source = source; @@ -74,7 +71,36 @@ local function new(e, context, registry, source) return error_instance; end -local function init(source, registry) +-- compact --> normal form +local function expand_registry(namespace, registry) + local mapped = {} + for err,template in pairs(registry) do + local e = { + type = template[1]; + condition = template[2]; + text = template[3]; + }; + if namespace and template[4] then + e.extra = { namespace = namespace, condition = template[4] }; + end + mapped[err] = e; + end + return mapped; +end + +local function init(source, namespace, registry) + if type(namespace) == "table" then + -- registry can be given as second argument if namespace is either not used + registry, namespace = namespace, nil; + if type(registry.namespace) == "string" then + -- error templates are always type table, so this can't be one + namespace, registry.namespace = registry.namespace, nil; + end + end + local _, protoerr = next(registry, nil); + if protoerr and type(next(protoerr)) == "number" then + registry = expand_registry(namespace, registry); + end return { source = source; registry = registry; -- cgit v1.2.3 From 33070035bf3e92548e69418e357bec5ca55f6266 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 28 Sep 2020 23:48:02 +0200 Subject: util.error: Drop registry initialization with namespace as key Enough complexity with compact vs normal and with/without namespace --- spec/util_error_spec.lua | 15 ++++----------- util/error.lua | 4 ---- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/spec/util_error_spec.lua b/spec/util_error_spec.lua index 8b995895..34edd313 100644 --- a/spec/util_error_spec.lua +++ b/spec/util_error_spec.lua @@ -96,8 +96,7 @@ describe("util.error", function () end); it("compact mode works", function() - local reg = errors.init("test", { - namespace = "spec"; + local reg = errors.init("test", "spec", { broke = {"cancel"; "internal-server-error"; "It broke :("}; nope = {"auth"; "not-authorized"; "Can't let you do that Dave"; "sorry-dave"}; }); @@ -126,26 +125,20 @@ describe("util.error", function () extra = {namespace = "spec"; condition = "sorry-dave"}; }; }); - local compact1 = errors.init("test", { - namespace = "spec"; - broke = {"cancel"; "internal-server-error"; "It broke :("}; - nope = {"auth"; "not-authorized"; "Can't let you do that Dave"; "sorry-dave"}; - }); - local compact2 = errors.init("test", "spec", { + local compact1 = errors.init("test", "spec", { broke = {"cancel"; "internal-server-error"; "It broke :("}; nope = {"auth"; "not-authorized"; "Can't let you do that Dave"; "sorry-dave"}; }); - local compact3 = errors.init("test", { + local compact2 = errors.init("test", { broke = {"cancel"; "internal-server-error"; "It broke :("}; nope = {"auth"; "not-authorized"; "Can't let you do that Dave"}; }); assert.same(normal.registry, compact1.registry); - assert.same(normal.registry, compact2.registry); assert.same({ broke = {type = "cancel"; condition = "internal-server-error"; text = "It broke :("}; nope = {type = "auth"; condition = "not-authorized"; text = "Can't let you do that Dave"}; - }, compact3.registry); + }, compact2.registry); end); end); diff --git a/util/error.lua b/util/error.lua index 5bd0f80e..132389f7 100644 --- a/util/error.lua +++ b/util/error.lua @@ -92,10 +92,6 @@ local function init(source, namespace, registry) if type(namespace) == "table" then -- registry can be given as second argument if namespace is either not used registry, namespace = namespace, nil; - if type(registry.namespace) == "string" then - -- error templates are always type table, so this can't be one - namespace, registry.namespace = registry.namespace, nil; - end end local _, protoerr = next(registry, nil); if protoerr and type(next(protoerr)) == "number" then -- cgit v1.2.3 From 5fd3895de56b1189e2baac2a17a02a301785e625 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 2 Oct 2020 16:35:05 +0200 Subject: make: Add way to run individual tests --- GNUmakefile | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/GNUmakefile b/GNUmakefile index 460f61ca..1846d72d 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -75,6 +75,9 @@ clean: test: $(BUSTED) --lua=$(RUNWITH) +test-%: + $(BUSTED) --lua=$(RUNWITH) -r $* + integration-test: all $(MKDIR) data $(RUNWITH) prosodyctl --config ./spec/scansion/prosody.cfg.lua start @@ -82,6 +85,13 @@ integration-test: all $(RUNWITH) prosodyctl --config ./spec/scansion/prosody.cfg.lua stop \ exit $$R +integration-test-%: all + $(MKDIR) data + $(RUNWITH) prosodyctl --config ./spec/scansion/prosody.cfg.lua start + $(SCANSION) ./spec/scansion/$*.scs; R=$$? \ + $(RUNWITH) prosodyctl --config ./spec/scansion/prosody.cfg.lua stop \ + exit $$R + coverage: -rm -- luacov.* $(BUSTED) --lua=$(RUNWITH) -c -- cgit v1.2.3 From be5199db29773bad53139598f2dfc8a4d65fbe1a Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Fri, 2 Oct 2020 16:44:30 +0100 Subject: mod_auth_anonymous: Add config option to allow/disallow storage writes --- plugins/mod_auth_anonymous.lua | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/plugins/mod_auth_anonymous.lua b/plugins/mod_auth_anonymous.lua index 1f2bceb3..90646e71 100644 --- a/plugins/mod_auth_anonymous.lua +++ b/plugins/mod_auth_anonymous.lua @@ -11,6 +11,8 @@ local new_sasl = require "util.sasl".new; local datamanager = require "util.datamanager"; local hosts = prosody.hosts; +local allow_storage = module:get_option_boolean("allow_anonymous_storage", false); + -- define auth provider local provider = {}; @@ -62,10 +64,14 @@ if not module:get_option_boolean("allow_anonymous_s2s", false) then end function module.load() - datamanager.add_callback(dm_callback); + if not allow_storage then + datamanager.add_callback(dm_callback); + end end function module.unload() - datamanager.remove_callback(dm_callback); + if not allow_storage then + datamanager.remove_callback(dm_callback); + end end module:provides("auth", provider); -- cgit v1.2.3 From 7eb15b0b3fd49a58656e510866349c9725cd27cc Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 3 Oct 2020 15:09:12 +0200 Subject: mod_bosh: Count connection attempts non-VirtualHost as "bad host" (stats) --- plugins/mod_bosh.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/mod_bosh.lua b/plugins/mod_bosh.lua index c5f5e044..6393a73e 100644 --- a/plugins/mod_bosh.lua +++ b/plugins/mod_bosh.lua @@ -293,6 +293,7 @@ function stream_callbacks.streamopened(context, attr) if not prosody.hosts[to_host] then log("debug", "BOSH client tried to connect to non-existant 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" }); response:send(tostring(close_reply)); @@ -301,6 +302,7 @@ function stream_callbacks.streamopened(context, attr) if prosody.hosts[to_host].type ~= "local" then log("debug", "BOSH client tried to connect to %s host: %s", prosody.hosts[to_host].type, attr.to); + report_bad_host(); local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", ["xmlns:stream"] = xmlns_streams, condition = "improper-addressing" }); response:send(tostring(close_reply)); -- cgit v1.2.3 From f875b6f99c458a0e1d6a6e784761ed255f369b22 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 3 Oct 2020 16:22:56 +0200 Subject: util.xml: Fix float formatting of line and columns in error (on Lua 5.3+) --- util/xml.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/xml.lua b/util/xml.lua index dac3f6fe..f826b6bf 100644 --- a/util/xml.lua +++ b/util/xml.lua @@ -71,7 +71,7 @@ local parse_xml = (function() if ok then return stanza.tags[1]; else - return ok, err.." (line "..line..", col "..col..")"; + return ok, ("%s (line %d, col %d))"):format(err, line, col); end end; end)(); -- cgit v1.2.3 From b0616dd198c00f823dd982a80781787d98166cbe Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 5 Oct 2020 20:56:03 +0200 Subject: util.pluginloader: Extract Lua version once It's not going to change while the module is loaded. --- util/pluginloader.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/pluginloader.lua b/util/pluginloader.lua index af0428c4..1cebe436 100644 --- a/util/pluginloader.lua +++ b/util/pluginloader.lua @@ -8,6 +8,7 @@ -- luacheck: ignore 113/CFG_PLUGINDIR local dir_sep, path_sep = package.config:match("^(%S+)%s(%S+)"); +local lua_version = _VERSION:match(" (.+)$"); local plugin_dir = {}; for path in (CFG_PLUGINDIR or "./plugins/"):gsub("[/\\]", dir_sep):gmatch("[^"..path_sep.."]+") do path = path..dir_sep; -- add path separator to path end @@ -36,7 +37,6 @@ end local function load_resource(plugin, resource) resource = resource or "mod_"..plugin..".lua"; - local lua_version = _VERSION:match(" (.+)$"); local names = { "mod_"..plugin..dir_sep..plugin..dir_sep..resource; -- mod_hello/hello/mod_hello.lua "mod_"..plugin..dir_sep..resource; -- mod_hello/mod_hello.lua -- cgit v1.2.3 From 19549a6976ecd52f9ab080bf02f7aee357441087 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 5 Oct 2020 20:56:53 +0200 Subject: util.pluginloader: Look for top level mod_something.lua in luarocks-style tree --- util/pluginloader.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/util/pluginloader.lua b/util/pluginloader.lua index 1cebe436..5af5084d 100644 --- a/util/pluginloader.lua +++ b/util/pluginloader.lua @@ -42,6 +42,7 @@ local function load_resource(plugin, resource) "mod_"..plugin..dir_sep..resource; -- mod_hello/mod_hello.lua plugin..dir_sep..resource; -- hello/mod_hello.lua resource; -- mod_hello.lua + "share"..dir_sep.."lua"..dir_sep..lua_version..dir_sep..resource; "share"..dir_sep.."lua"..dir_sep..lua_version..dir_sep.."mod_"..plugin..dir_sep..resource; }; -- cgit v1.2.3 From 0113f59de04e2ac926e392e6295936e854534883 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 5 Oct 2020 20:58:37 +0200 Subject: util.pluginloader: Look for module libs in mod_plugin/lib.lua Luarocks can't be told to install something as foo.lib.lua AFAIK, so instead let's try mod_bar/foo.lua --- util/pluginloader.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/util/pluginloader.lua b/util/pluginloader.lua index 5af5084d..8e37ffc4 100644 --- a/util/pluginloader.lua +++ b/util/pluginloader.lua @@ -60,6 +60,9 @@ end local function load_code_ext(plugin, resource, extension, env) local content, err = load_resource(plugin, resource.."."..extension); + if not content and extension == "lib.lua" then + content, err = load_resource(plugin, resource..".lua"); + end if not content then content, err = load_resource(resource, resource.."."..extension); if not content then -- cgit v1.2.3 From 3c0445e1b22bd0b6ab0fa160f2ad3250da48014c Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 5 Oct 2020 21:13:23 +0200 Subject: util.prosodyctl: Construct luarocks command line with templates More flexible and safer wrt escaping --- util/prosodyctl.lua | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/util/prosodyctl.lua b/util/prosodyctl.lua index cb86a5a1..6378856a 100644 --- a/util/prosodyctl.lua +++ b/util/prosodyctl.lua @@ -12,6 +12,7 @@ local encodings = require "util.encodings"; local stringprep = encodings.stringprep; local storagemanager = require "core.storagemanager"; local usermanager = require "core.usermanager"; +local interpolation = require "util.interpolation"; local signal = require "util.signal"; local set = require "util.set"; local lfs = require "lfs"; @@ -224,6 +225,8 @@ local function get_path_custom_plugins(plugin_paths) end end +local render_cli = interpolation.new("%b{}", function (s) return "'"..s:gsub("'","'\\''").."'" end) + local function call_luarocks(mod, operation) local dir = get_path_custom_plugins(prosody.paths.plugins); if operation == "install" then @@ -232,9 +235,11 @@ local function call_luarocks(mod, operation) show_message("Removing %s from %s", mod, dir); end if operation == "list" then - os.execute("luarocks list --tree='"..dir.."'") + os.execute(render_cli("luarocks list --tree={dir}", {dir = dir})); else - os.execute("luarocks --tree='"..dir.."' --server='http://localhost/' "..operation.." "..mod); + os.execute(render_cli("luarocks {op} --tree={dir} {server&--server={server}} {mod}", { + dir = dir; op = operation; mod = mod; server = "http://localhost/"; + })); end if operation == "install" then show_module_configuration_help(mod); -- cgit v1.2.3 From b74859e35c353452264b0f44704049932ae0e37a Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 5 Oct 2020 21:17:29 +0200 Subject: util.prosodyctl: Flip argument order "verb subject" feels better than "subject verb", especially since the subject (module) is optional. --- prosodyctl | 6 +++--- util/prosodyctl.lua | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/prosodyctl b/prosodyctl index dcd9e5a2..928d2bec 100755 --- a/prosodyctl +++ b/prosodyctl @@ -78,7 +78,7 @@ function commands.install(arg) show_usage([[install]], [[Installs a prosody/luarocks plugin]]); return 1; end - call_luarocks(arg[1], "install") + call_luarocks("install", arg[1]); end function commands.remove(arg) @@ -86,7 +86,7 @@ function commands.remove(arg) show_usage([[remove]], [[Removes a module installed in the working directory's plugins folder]]); return 1; end - call_luarocks(arg[1], "remove") + call_luarocks("remove", arg[1]) end function commands.list(arg) @@ -94,7 +94,7 @@ function commands.list(arg) show_usage([[list]], [[Shows installed rocks]]); return 1; end - call_luarocks(arg[1], "list") + call_luarocks("list", arg[1]) end function commands.adduser(arg) diff --git a/util/prosodyctl.lua b/util/prosodyctl.lua index 6378856a..28347455 100644 --- a/util/prosodyctl.lua +++ b/util/prosodyctl.lua @@ -227,7 +227,7 @@ end local render_cli = interpolation.new("%b{}", function (s) return "'"..s:gsub("'","'\\''").."'" end) -local function call_luarocks(mod, operation) +local function call_luarocks(operation, mod) local dir = get_path_custom_plugins(prosody.paths.plugins); if operation == "install" then show_message("Installing %s at %s", mod, dir); -- cgit v1.2.3 From 28aadca557b90b2a3a981aecd4626b577c002346 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 5 Oct 2020 21:24:07 +0200 Subject: util.prosodyctl: Move hardcoded luarocks server into prosodyctl To be replaced with config option in future commit --- prosodyctl | 2 +- util/prosodyctl.lua | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/prosodyctl b/prosodyctl index 928d2bec..ca631047 100755 --- a/prosodyctl +++ b/prosodyctl @@ -78,7 +78,7 @@ function commands.install(arg) show_usage([[install]], [[Installs a prosody/luarocks plugin]]); return 1; end - call_luarocks("install", arg[1]); + call_luarocks("install", arg[1], "http://localhost/"); end function commands.remove(arg) diff --git a/util/prosodyctl.lua b/util/prosodyctl.lua index 28347455..b80ce46b 100644 --- a/util/prosodyctl.lua +++ b/util/prosodyctl.lua @@ -227,7 +227,7 @@ end local render_cli = interpolation.new("%b{}", function (s) return "'"..s:gsub("'","'\\''").."'" end) -local function call_luarocks(operation, mod) +local function call_luarocks(operation, mod, server) local dir = get_path_custom_plugins(prosody.paths.plugins); if operation == "install" then show_message("Installing %s at %s", mod, dir); @@ -238,7 +238,7 @@ local function call_luarocks(operation, mod) os.execute(render_cli("luarocks list --tree={dir}", {dir = dir})); else os.execute(render_cli("luarocks {op} --tree={dir} {server&--server={server}} {mod}", { - dir = dir; op = operation; mod = mod; server = "http://localhost/"; + dir = dir; op = operation; mod = mod; server = server; })); end if operation == "install" then -- cgit v1.2.3 From 42eec522a3a327185ee358ac77cf894e93c221e8 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 5 Oct 2020 21:24:14 +0200 Subject: util.prosodyctl: Get Luarocks server from config --- prosodyctl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/prosodyctl b/prosodyctl index ca631047..c74a04d1 100755 --- a/prosodyctl +++ b/prosodyctl @@ -78,7 +78,8 @@ function commands.install(arg) show_usage([[install]], [[Installs a prosody/luarocks plugin]]); return 1; end - call_luarocks("install", arg[1], "http://localhost/"); + -- TODO finalize config option name + call_luarocks("install", arg[1], configmanager.get("*", "plugin_server") or "http://localhost/"); end function commands.remove(arg) -- cgit v1.2.3 From ef24d8cd72873a4cb0e222bd8374551925da3eb9 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 5 Oct 2020 21:23:39 +0200 Subject: util.prosodyctl: Simplify luarocks invocation --- util/prosodyctl.lua | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/util/prosodyctl.lua b/util/prosodyctl.lua index b80ce46b..80052163 100644 --- a/util/prosodyctl.lua +++ b/util/prosodyctl.lua @@ -234,13 +234,9 @@ local function call_luarocks(operation, mod, server) elseif operation == "remove" then show_message("Removing %s from %s", mod, dir); end - if operation == "list" then - os.execute(render_cli("luarocks list --tree={dir}", {dir = dir})); - else - os.execute(render_cli("luarocks {op} --tree={dir} {server&--server={server}} {mod}", { - dir = dir; op = operation; mod = mod; server = server; - })); - end + os.execute(render_cli("luarocks {op} --tree={dir} {server&--server={server}} {mod?}", { + dir = dir; op = operation; mod = mod; server = server; + })); if operation == "install" then show_module_configuration_help(mod); end -- cgit v1.2.3 From e9c3e1dedcb316b94764ecf0827f2c86998ade04 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 5 Oct 2020 21:43:47 +0200 Subject: util.startup: Put 'installer_plugin_path' under data directory by default Fixes issue where it ends up creating this in $PWD, which might be ~prosody, ~you or /, depending on how it's invoked. --- util/startup.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/startup.lua b/util/startup.lua index a8d8594c..339a11dc 100644 --- a/util/startup.lua +++ b/util/startup.lua @@ -260,7 +260,7 @@ function startup.setup_plugindir() end function startup.setup_plugin_install_path() - local installer_plugin_path = config.get("*", "installer_plugin_path") or "custom_plugins"; + local installer_plugin_path = config.get("*", "installer_plugin_path") or CFG_DATADIR.."/custom_plugins"; local path_sep = package.config:sub(3,3); -- TODO Figure out what this should be relative to, because CWD could be anywhere installer_plugin_path = config.resolve_relative_path(require "lfs".currentdir(), installer_plugin_path); -- cgit v1.2.3 From defba34a6c15adb6dbb2fcef71fb5b2852f1e493 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 5 Oct 2020 21:45:02 +0200 Subject: util.startup: Re-enable installer path setup --- util/startup.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/util/startup.lua b/util/startup.lua index 339a11dc..fefedcbf 100644 --- a/util/startup.lua +++ b/util/startup.lua @@ -583,7 +583,7 @@ function startup.prosodyctl() startup.init_gc(); startup.init_errors(); startup.setup_plugindir(); - -- startup.setup_plugin_install_path(); + startup.setup_plugin_install_path(); startup.setup_datadir(); startup.chdir(); startup.read_version(); @@ -612,7 +612,7 @@ function startup.prosody() startup.check_dependencies(); startup.load_libraries(); startup.setup_plugindir(); - -- startup.setup_plugin_install_path(); + startup.setup_plugin_install_path(); startup.setup_datadir(); startup.chdir(); startup.add_global_prosody_functions(); -- cgit v1.2.3 From 4b4f32104fa6bf53f1c1ad22296d7be212601891 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 5 Oct 2020 21:54:17 +0200 Subject: util.startup: Fix startup failure if CFG_DATADIR is unset As is normal when running from source --- util/startup.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/startup.lua b/util/startup.lua index fefedcbf..b7d9f6fe 100644 --- a/util/startup.lua +++ b/util/startup.lua @@ -260,7 +260,7 @@ function startup.setup_plugindir() end function startup.setup_plugin_install_path() - local installer_plugin_path = config.get("*", "installer_plugin_path") or CFG_DATADIR.."/custom_plugins"; + local installer_plugin_path = config.get("*", "installer_plugin_path") or (CFG_DATADIR or "data").."/custom_plugins"; local path_sep = package.config:sub(3,3); -- TODO Figure out what this should be relative to, because CWD could be anywhere installer_plugin_path = config.resolve_relative_path(require "lfs".currentdir(), installer_plugin_path); -- cgit v1.2.3 From f7f0a20abffc3535b02ffe06235964929cc87091 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 7 Oct 2020 15:37:15 +0200 Subject: util.startup: Save the path used by the installer to prosody.paths Makes it easier for other parts of the code to use this for things, such as determining whether a certain module is from this path or from elsewhere. --- util/startup.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/util/startup.lua b/util/startup.lua index b7d9f6fe..f0b9b2c0 100644 --- a/util/startup.lua +++ b/util/startup.lua @@ -269,6 +269,7 @@ function startup.setup_plugin_install_path() require"util.paths".complement_lua_path(installer_plugin_path); -- luacheck: ignore 111 CFG_PLUGINDIR = installer_plugin_path..path_sep..(CFG_PLUGINDIR or "plugins"); + prosody.paths.installer = installer_plugin_path; prosody.paths.plugins = CFG_PLUGINDIR; end -- cgit v1.2.3 From 915cebae961534f07c3c70bc5ab7b3ec5b790aba Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 7 Oct 2020 15:51:37 +0200 Subject: core.modulemanager: Locate resources of LuaRocks-installed modules Extra non-code files included with a `copy_directories` directive in a LuaRocks manifest will be copied into a per-module and per-version directory under /lib/luarocks/ and all this is there to dig that out so it can be used in e.g. moduleapi :load_resource(). --- core/modulemanager.lua | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/core/modulemanager.lua b/core/modulemanager.lua index e23f1e55..ea5a1324 100644 --- a/core/modulemanager.lua +++ b/core/modulemanager.lua @@ -10,6 +10,7 @@ local logger = require "util.logger"; local log = logger.init("modulemanager"); local config = require "core.configmanager"; local pluginloader = require "util.pluginloader"; +local envload = require "util.envload"; local set = require "util.set"; local new_multitable = require "util.multitable".new; @@ -22,6 +23,7 @@ local xpcall = require "util.xpcall".xpcall; local debug_traceback = debug.traceback; local setmetatable, rawget = setmetatable, rawget; local ipairs, pairs, type, t_insert = ipairs, pairs, type, table.insert; +local lua_version = _VERSION:match("5%.%d$"); local autoload_modules = { prosody.platform, @@ -196,6 +198,35 @@ local function do_load_module(host, module_name, state) api_instance.path = err; + local custom_plugins = prosody.paths.installer; + if err:sub(1, #custom_plugins+1) == custom_plugins.."/" then + -- Stage 1: Make it work (you are here) + -- Stage 2: Make it less hacky (TODO) + local manifest = {}; + local luarocks_path = custom_plugins.."/lib/luarocks/rocks-"..lua_version; + local manifest_filename = luarocks_path.."/manifest"; + local load_manifest, err = envload.envloadfile(manifest_filename, manifest); + if not load_manifest then + log("error", "Could not load manifest of installed plugins: %s", err, load_manifest); + else + local ok, err = xpcall(load_manifest, debug_traceback); + if not ok then + log("error", "Could not load manifest of installed plugins: %s", err); + elseif type(manifest.modules) ~= "table" then + log("debug", "Expected 'table' but manifest.modules = %q", manifest.modules); + log("error", "Can't look up resource path for mod_%s because '%s' does not appear to be a LuaRocks manifest", module_name, manifest_filename); + else + local versions = manifest.modules["mod_"..module_name]; + if type(versions) == "table" and versions[1] then + -- Not going to deal with multiple installed versions + api_instance.resource_path = luarocks_path.."/"..versions[1]; + else + log("debug", "mod_%s does not appear in the installation manifest", module_name); + end + end + end + end + modulemap[host][module_name] = pluginenv; local ok, err = xpcall(mod, debug_traceback); if ok then -- cgit v1.2.3 From 58be93c18484c9e12f276f5bb0f8d49e3d833b47 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 7 Oct 2020 16:10:51 +0200 Subject: core.moduleapi: Use resource path for :load_resource() --- core/moduleapi.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/moduleapi.lua b/core/moduleapi.lua index 1212db5a..0f43478c 100644 --- a/core/moduleapi.lua +++ b/core/moduleapi.lua @@ -500,7 +500,7 @@ function api:get_directory() end function api:load_resource(path, mode) - path = resolve_relative_path(self:get_directory(), path); + path = resolve_relative_path(self.resource_path or self:get_directory(), path); return io.open(path, mode); end -- cgit v1.2.3 From 387a03e1d3fd622dde78b2e4e66abb109f7c40cd Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 7 Oct 2020 22:54:12 +0200 Subject: core.modulemanager: Add compat for LuaRocks 2.x --- core/modulemanager.lua | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/core/modulemanager.lua b/core/modulemanager.lua index ea5a1324..b89c82d7 100644 --- a/core/modulemanager.lua +++ b/core/modulemanager.lua @@ -206,6 +206,13 @@ local function do_load_module(host, module_name, state) local luarocks_path = custom_plugins.."/lib/luarocks/rocks-"..lua_version; local manifest_filename = luarocks_path.."/manifest"; local load_manifest, err = envload.envloadfile(manifest_filename, manifest); + if not load_manifest then + -- COMPAT Luarocks 2.x + log("debug", "Could not load LuaRocks 3.x manifest, trying 2.x", err); + luarocks_path = custom_plugins.."/lib/luarocks/rocks-"..lua_version; + manifest_filename = luarocks_path.."/manifest"; + load_manifest, err = envload.envloadfile(manifest_filename, manifest); + end if not load_manifest then log("error", "Could not load manifest of installed plugins: %s", err, load_manifest); else -- cgit v1.2.3 From d53dcee89074430a831a5d8e46c17de61a5aa065 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 9 Oct 2020 16:37:15 +0200 Subject: core.moduleapi: Return resource path from module:get_directory() (API BC) :get_directory has so far returned the base directory of the current module source code. This has worked well so far to load resources which tend to be included in the same directory, but with the plugin installer using LuaRocks, extra resources (e.g. templates and other assets) these are saved in a completely different directory. In be73df6765b9 core.modulemanager gained some code for finding that directory and saving it in module.resource_path but now the question is how this should be reflected in the API. A survey of community modules suggest the vast majority use the :get_directory method for locating templates and other assets, rather than the code (which would use module:require instead). Therefore this commit changes :get_directory to return the resource_path when available. This should work for most modules. --- core/moduleapi.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/moduleapi.lua b/core/moduleapi.lua index 0f43478c..82002737 100644 --- a/core/moduleapi.lua +++ b/core/moduleapi.lua @@ -496,11 +496,11 @@ end local path_sep = package.config:sub(1,1); function api:get_directory() - return self.path and (self.path:gsub("%"..path_sep.."[^"..path_sep.."]*$", "")) or nil; + return self.resource_path or self.path and (self.path:gsub("%"..path_sep.."[^"..path_sep.."]*$", "")) or nil; end function api:load_resource(path, mode) - path = resolve_relative_path(self.resource_path or self:get_directory(), path); + path = resolve_relative_path(self:get_directory(), path); return io.open(path, mode); end -- cgit v1.2.3 From 9302a14d803625e06d7a93a9b7d498a1219c383d Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 9 Oct 2020 17:34:04 +0200 Subject: util.startup: Retrieve less data for function string representation debug.getinfo(f) collects more info than what is needed here. --- util/startup.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/startup.lua b/util/startup.lua index f0b9b2c0..af126e8c 100644 --- a/util/startup.lua +++ b/util/startup.lua @@ -197,7 +197,7 @@ function startup.set_function_metatable() end end function mt.__tostring(f) - local info = debug.getinfo(f); + local info = debug.getinfo(f, "S"); return ("function(%s:%d)"):format(info.short_src:match("[^\\/]*$"), info.linedefined); end debug.setmetatable(function() end, mt); -- cgit v1.2.3 From c5933e9ed7902f748e561d92b741d09468ba8067 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 9 Oct 2020 17:41:10 +0200 Subject: util.startup: Include arguments in function string representation Improves usability of the console when digging around the internals. No specific rationale for the function(args) format, it looked best of the variants I tried. --- util/startup.lua | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/util/startup.lua b/util/startup.lua index af126e8c..358c491f 100644 --- a/util/startup.lua +++ b/util/startup.lua @@ -197,8 +197,14 @@ function startup.set_function_metatable() end end function mt.__tostring(f) - local info = debug.getinfo(f, "S"); - return ("function(%s:%d)"):format(info.short_src:match("[^\\/]*$"), info.linedefined); + local info = debug.getinfo(f, "Su"); + for i = 1, info.nparams do + info[i] = debug.getlocal(f, i); + end + if info.isvararg then + info[info.nparams+1] = "..."; + end + return ("function<%s:%d>(%s)"):format(info.short_src:match("[^\\/]*$"), info.linedefined, table.concat(info, ", ")); end debug.setmetatable(function() end, mt); end -- cgit v1.2.3 From bb3948c85772f99ab69876412e904b373841835d Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 11 Oct 2020 14:27:28 +0200 Subject: core.modulemanager: Fix error if installer path missing Happens if run outside prosody. Noticed because because the storage tests fail. --- core/modulemanager.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/modulemanager.lua b/core/modulemanager.lua index b89c82d7..3f4233e6 100644 --- a/core/modulemanager.lua +++ b/core/modulemanager.lua @@ -199,7 +199,7 @@ local function do_load_module(host, module_name, state) api_instance.path = err; local custom_plugins = prosody.paths.installer; - if err:sub(1, #custom_plugins+1) == custom_plugins.."/" then + if custom_plugins and err:sub(1, #custom_plugins+1) == custom_plugins.."/" then -- Stage 1: Make it work (you are here) -- Stage 2: Make it less hacky (TODO) local manifest = {}; -- cgit v1.2.3 From c07d15cf71f962a64aa93cdf29d5fcf592a7548e Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Sun, 11 Oct 2020 20:25:32 +0100 Subject: util.startup: Handle missing nparams field from debug info (not present in 5.1) --- util/startup.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/util/startup.lua b/util/startup.lua index 358c491f..27420c57 100644 --- a/util/startup.lua +++ b/util/startup.lua @@ -198,11 +198,12 @@ function startup.set_function_metatable() end function mt.__tostring(f) local info = debug.getinfo(f, "Su"); - for i = 1, info.nparams do + local n_params = info.nparams or 0; + for i = 1, n_params do info[i] = debug.getlocal(f, i); end if info.isvararg then - info[info.nparams+1] = "..."; + info[n_params+1] = "..."; end return ("function<%s:%d>(%s)"):format(info.short_src:match("[^\\/]*$"), info.linedefined, table.concat(info, ", ")); end -- cgit v1.2.3 From 09e8795afb2f3d218be88fbe51819c01d3f7ed13 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 12 Oct 2020 18:13:20 +0200 Subject: mod_http_errors: Use a class on extra data section This CSS selector makes it awkward to add more items. --- plugins/mod_http_errors.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/mod_http_errors.lua b/plugins/mod_http_errors.lua index e151a68e..1abdc57e 100644 --- a/plugins/mod_http_errors.lua +++ b/plugins/mod_http_errors.lua @@ -41,7 +41,7 @@ p { font-size : x-large } -p+p { +p.extra { font-size : large; font-family : courier } @@ -50,7 +50,7 @@ p+p {

{title}

{message}

-

{extra?}

+

{extra?}

]]; -- cgit v1.2.3 From 6518e5f41b9ebf9a4be4e9e8a4872fad7ff88f3f Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 12 Oct 2020 18:16:18 +0200 Subject: mod_http_errors: Dark theme! --- plugins/mod_http_errors.lua | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/plugins/mod_http_errors.lua b/plugins/mod_http_errors.lua index 1abdc57e..727b2516 100644 --- a/plugins/mod_http_errors.lua +++ b/plugins/mod_http_errors.lua @@ -45,6 +45,13 @@ p.extra { font-size : large; font-family : courier } + +@media(prefers-color-scheme: dark) { + body { + background-color: #161616; + color: #eee + } +} -- cgit v1.2.3 From b4af560c9711221a4da3da41c8cef1d302b63beb Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 12 Oct 2020 18:27:42 +0200 Subject: mod_http_errors: Remove 'extra' element when empty --- plugins/mod_http_errors.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_http_errors.lua b/plugins/mod_http_errors.lua index 727b2516..a2138d88 100644 --- a/plugins/mod_http_errors.lua +++ b/plugins/mod_http_errors.lua @@ -57,7 +57,7 @@ p.extra {

{title}

{message}

-

{extra?}

+{extra&

{extra?}

} ]]; -- cgit v1.2.3 From 58e33e399553ccc457de266c36102308f63bfe02 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Thu, 15 Oct 2020 13:43:03 +0100 Subject: util.error: Pass through existing error objects passed to new() --- util/error.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/util/error.lua b/util/error.lua index 132389f7..85265e47 100644 --- a/util/error.lua +++ b/util/error.lua @@ -35,6 +35,7 @@ end -- What to set `type` to for stream errors or SASL errors? Those don't have a 'type' attr. local function new(e, context, registry, source) + if is_err(e) then return e; end local template = registry and registry[e]; if not template then if type(e) == "table" then -- cgit v1.2.3 From 203b48b127ebfc16ec092b7dfba1836b3a70a8fd Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 7 Sep 2019 13:38:02 +0200 Subject: util.sasl.scram: Use util.strbitop for XOR step --- util/sasl/scram.lua | 30 +----------------------------- 1 file changed, 1 insertion(+), 29 deletions(-) diff --git a/util/sasl/scram.lua b/util/sasl/scram.lua index 009a01ce..865f8cf7 100644 --- a/util/sasl/scram.lua +++ b/util/sasl/scram.lua @@ -19,9 +19,7 @@ local generate_uuid = require "util.uuid".generate; local saslprep = require "util.encodings".stringprep.saslprep; local nodeprep = require "util.encodings".stringprep.nodeprep; local log = require "util.logger".init("sasl"); -local t_concat = table.concat; -local char = string.char; -local byte = string.byte; +local binaryXOR = require "util.strbitop".sxor; local _ENV = nil; -- luacheck: std none @@ -45,32 +43,6 @@ Supported Channel Binding Backends local default_i = 4096 -local xor_map = { - 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,1,0,3,2,5,4,7,6,9,8,11,10, - 13,12,15,14,2,3,0,1,6,7,4,5,10,11,8,9,14,15,12,13,3,2,1,0,7,6,5, - 4,11,10,9,8,15,14,13,12,4,5,6,7,0,1,2,3,12,13,14,15,8,9,10,11,5, - 4,7,6,1,0,3,2,13,12,15,14,9,8,11,10,6,7,4,5,2,3,0,1,14,15,12,13, - 10,11,8,9,7,6,5,4,3,2,1,0,15,14,13,12,11,10,9,8,8,9,10,11,12,13, - 14,15,0,1,2,3,4,5,6,7,9,8,11,10,13,12,15,14,1,0,3,2,5,4,7,6,10, - 11,8,9,14,15,12,13,2,3,0,1,6,7,4,5,11,10,9,8,15,14,13,12,3,2,1, - 0,7,6,5,4,12,13,14,15,8,9,10,11,4,5,6,7,0,1,2,3,13,12,15,14,9,8, - 11,10,5,4,7,6,1,0,3,2,14,15,12,13,10,11,8,9,6,7,4,5,2,3,0,1,15, - 14,13,12,11,10,9,8,7,6,5,4,3,2,1,0, -}; - -local result = {}; -local function binaryXOR( a, b ) - for i=1, #a do - local x, y = byte(a, i), byte(b, i); - local lowx, lowy = x % 16, y % 16; - local hix, hiy = (x - lowx) / 16, (y - lowy) / 16; - local lowr, hir = xor_map[lowx * 16 + lowy + 1], xor_map[hix * 16 + hiy + 1]; - local r = hir * 16 + lowr; - result[i] = char(r) - end - return t_concat(result); -end - local function validate_username(username, _nodeprep) -- check for forbidden char sequences for eq in username:gmatch("=(.?.?)") do -- cgit v1.2.3 From 8f059290a719e2dd5aba81477749863c0c663350 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Fri, 16 Oct 2020 14:01:25 +0100 Subject: mod_posix: Hook and fire events on SIGUSR1/2 --- plugins/mod_posix.lua | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/plugins/mod_posix.lua b/plugins/mod_posix.lua index d3ff1f7c..03177530 100644 --- a/plugins/mod_posix.lua +++ b/plugins/mod_posix.lua @@ -147,5 +147,20 @@ if have_signal then prosody.shutdown("Received SIGINT"); prosody.lock_globals(); end); + + signal.signal("SIGUSR1", function () + module:log("info", "Received SIGUSR1"); + module:fire_event("signal/SIGUSR1"); + end); + + signal.signal("SIGUSR2", function () + module:log("info", "Received SIGUSR2"); + module:fire_event("signal/SIGUSR2"); + end); end); end + +-- For other modules to reference +features = { + signal_events = true; +}; -- cgit v1.2.3 From 6b306e33131a800f7a6c914f3ca6d65a81371019 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 17 Oct 2020 14:19:41 +0200 Subject: MUC: Remove XEP-0091: Legacy Delayed Delivery Why do we still include this? Deprecated in 2007, obsoleted in 2009. Removes redundant timestamp that nobody should be looking at since many years and a redundant copy of the room JID. --- doc/doap.xml | 4 ++++ plugins/muc/history.lib.lua | 3 --- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/doc/doap.xml b/doc/doap.xml index 7e9ef815..0b507236 100644 --- a/doc/doap.xml +++ b/doc/doap.xml @@ -187,6 +187,10 @@ + 1.4 + 0.1 + 0.12 + Gone from offline messages in 0.10.0, gone from MUC in 0.12 diff --git a/plugins/muc/history.lib.lua b/plugins/muc/history.lib.lua index 1b7167af..fdf65365 100644 --- a/plugins/muc/history.lib.lua +++ b/plugins/muc/history.lib.lua @@ -182,9 +182,6 @@ module:hook("muc-add-history", function(event) stanza:tag("delay", { -- XEP-0203 xmlns = "urn:xmpp:delay", from = room.jid, stamp = stamp }):up(); - stanza:tag("x", { -- XEP-0091 (deprecated) - xmlns = "jabber:x:delay", from = room.jid, stamp = datetime.legacy() - }):up(); local entry = { stanza = stanza, timestamp = ts }; table.insert(history, entry); while #history > get_historylength(room) do table.remove(history, 1) end -- cgit v1.2.3 From fce45aff34dea1959b0640d9a02374c6bb5897de Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 17 Oct 2020 19:00:16 +0200 Subject: util.paths: Add some tests --- spec/util_paths_spec.lua | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 spec/util_paths_spec.lua diff --git a/spec/util_paths_spec.lua b/spec/util_paths_spec.lua new file mode 100644 index 00000000..243b35fb --- /dev/null +++ b/spec/util_paths_spec.lua @@ -0,0 +1,39 @@ +local sep = package.config:match("(.)\n"); +describe("util.paths", function () + local paths = require "util.paths"; + describe("#join()", function () + it("returns single component as-is", function () + assert.equal("foo", paths.join("foo")); + end); + it("joins paths", function () + assert.equal("foo"..sep.."bar", paths.join("foo", "bar")) + end); + it("joins longer paths", function () + assert.equal("foo"..sep.."bar"..sep.."baz", paths.join("foo", "bar", "baz")) + end); + it("joins even longer paths", function () + assert.equal("foo"..sep.."bar"..sep.."baz"..sep.."moo", paths.join("foo", "bar", "baz", "moo")) + end); + end) + + describe("#glob_to_pattern()", function () + it("works", function () + assert.equal("^thing.%..*$", paths.glob_to_pattern("thing?.*")) + end); + + end) + describe("#resolve_relative_path()", function () + it("returns absolute paths as-is", function () + if sep == "/" then + assert.equal("/tmp/path", paths.resolve_relative_path("/run", "/tmp/path")); + elseif sep == "\\" then + assert.equal("C:\\Program Files", paths.resolve_relative_path("A:\\", "C:\\Program Files")); + end + end); + it("resolves relative paths", function () + if sep == "/" then + assert.equal("/run/path", paths.resolve_relative_path("/run", "path")); + end + end); + end) +end) -- cgit v1.2.3 From c812a980682ce678045ee8031b8b0da9237458f0 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 17 Oct 2020 19:24:44 +0200 Subject: util_paths_spec: Trim trailing white space And add spacing between describe() blocks --- spec/util_paths_spec.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/util_paths_spec.lua b/spec/util_paths_spec.lua index 243b35fb..2e8a0c08 100644 --- a/spec/util_paths_spec.lua +++ b/spec/util_paths_spec.lua @@ -20,8 +20,8 @@ describe("util.paths", function () it("works", function () assert.equal("^thing.%..*$", paths.glob_to_pattern("thing?.*")) end); - end) + describe("#resolve_relative_path()", function () it("returns absolute paths as-is", function () if sep == "/" then -- cgit v1.2.3 From 5b33f834fa2159a74cf0f80922f67b5e1f18a4d7 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 11 Oct 2020 23:04:13 +0200 Subject: util.paths: Optimize path joining with few arguments A casual search suggests that the majority of paths.join() calls involve only two arguments. This saves the creation of a table for up to 3 arguments. Looks like 3x faster for 3 arguments or less, 5% slower when it uses the array to concatenate. --- util/paths.lua | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/util/paths.lua b/util/paths.lua index 036f315a..b75c35e5 100644 --- a/util/paths.lua +++ b/util/paths.lua @@ -37,8 +37,18 @@ function path_util.glob_to_pattern(glob) end).."$"; end -function path_util.join(...) - return t_concat({...}, path_sep); +function path_util.join(a, b, c, ...) -- (... : string) --> string + -- Optimization: Avoid creating table for most uses + if b then + if c then + if ... then + return t_concat({a,b,c,...}, path_sep); + end + return a..path_sep..b..path_sep..c; + end + return a..path_sep..b; + end + return a; end function path_util.complement_lua_path(installer_plugin_path) -- cgit v1.2.3 From bc402b6409639f130f2ef7f13c9e240194de859c Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Wed, 21 Oct 2020 10:34:16 +0100 Subject: net.http.parser: Expose 'partial', 'chunked' and 'body_length' on packets --- net/http/parser.lua | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/net/http/parser.lua b/net/http/parser.lua index 3470ffb1..96f17fdb 100644 --- a/net/http/parser.lua +++ b/net/http/parser.lua @@ -47,6 +47,7 @@ function httpstream.new(success_cb, error_cb, parser_type, options_cb) if state and client and not len then -- reading client body until EOF buffer:collapse(); packet.body = buffer:read_chunk() or ""; + packet.partial = nil; success_cb(packet); state = nil; elseif buffer:length() ~= 0 then -- unexpected EOF @@ -96,6 +97,9 @@ function httpstream.new(success_cb, error_cb, parser_type, options_cb) httpversion = httpversion; headers = headers; body = false; + body_length = len; + chunked = chunked; + partial = true; -- COMPAT the properties below are deprecated responseversion = httpversion; responseheaders = headers; @@ -122,6 +126,8 @@ function httpstream.new(success_cb, error_cb, parser_type, options_cb) headers = headers; body = false; body_sink = nil; + chunked = chunked; + partial = true; }; end if len and len > bodylimit then @@ -157,6 +163,7 @@ function httpstream.new(success_cb, error_cb, parser_type, options_cb) buf = buf:gsub("^.-\r\n\r\n", ""); -- This ensure extensions and trailers are stripped buffer:write(buf); state, chunked = nil, nil; + packet.partial = nil; success_cb(packet); elseif buffer:length() - chunk_start - 2 >= chunk_size then -- we have a chunk buffer:discard(chunk_start - 1); -- TODO verify that it's not off-by-one @@ -176,11 +183,17 @@ function httpstream.new(success_cb, error_cb, parser_type, options_cb) return error_cb("body-sink-write-failure"); end end - if len == 0 then state = nil; success_cb(packet); end + if len == 0 then + state = nil; + packet.partial = nil; + success_cb(packet); + end elseif buffer:length() >= len then assert(not chunked) packet.body = buffer:read(len) or ""; - state = nil; success_cb(packet); + state = nil; + packet.partial = nil; + success_cb(packet); else break; end -- cgit v1.2.3 From b50d4b2ef0a476f282e5413e65e8b7ed17f1dd4b Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Wed, 21 Oct 2020 10:40:11 +0100 Subject: net.http: Add support for streaming chunked/large responses --- net/http.lua | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/net/http.lua b/net/http.lua index 31157afa..bb247d18 100644 --- a/net/http.lua +++ b/net/http.lua @@ -83,7 +83,24 @@ local function request_reader(request, data, err) return; end + local finalize_sink; local function success_cb(r) + if r.partial then + -- Request should be streamed + log("debug", "Request '%s': partial response (%s%s)", + request.id, + r.chunked and "chunked, " or "", + r.body_length and ("%d bytes"):format(r.body_length) or "unknown length" + ); + if request.streaming_handler then + log("debug", "Request '%s': Streaming via handler"); + r.body_sink, finalize_sink = request.streaming_handler(r); + end + return; + elseif finalize_sink then + log("debug", "Request '%s': Finalizing response stream"); + finalize_sink(r); + end if request.callback then request.callback(r.body, r.code, r, request); request.callback = nil; @@ -256,6 +273,7 @@ local function request(self, u, ex, callback) end req.insecure = ex.insecure; req.suppress_errors = ex.suppress_errors; + req.streaming_handler = ex.streaming_handler; end log("debug", "Making %s %s request '%s' to %s", req.scheme:upper(), method or "GET", req.id, (ex and ex.suppress_url and host_header) or u); -- cgit v1.2.3 From 3133ff234af7809ee5271ab995fcd68786858c56 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 25 Oct 2020 15:20:14 +0100 Subject: mod_pubsub: Don't set store as metatable 'archive' is not a metatable here, so this has no effect. Remove since apparently nothing depends on this. --- plugins/mod_pubsub/pubsub.lib.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_pubsub/pubsub.lib.lua b/plugins/mod_pubsub/pubsub.lib.lua index 0938dbbc..6a2c6ad9 100644 --- a/plugins/mod_pubsub/pubsub.lib.lua +++ b/plugins/mod_pubsub/pubsub.lib.lua @@ -872,7 +872,7 @@ local function archive_itemstore(archive, config, user, node) return item.attr.id, item; end end - return setmetatable(get_set, archive); + return get_set; end _M.archive_itemstore = archive_itemstore; -- cgit v1.2.3 From c34989f57eb0403f6c3849c3ebc4fe86c128e09a Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 25 Oct 2020 15:21:34 +0100 Subject: mod_pubsub: Clarify kind of item store created Planning to make this configurable, so good to distinguish it from future backends. --- plugins/mod_pubsub/pubsub.lib.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_pubsub/pubsub.lib.lua b/plugins/mod_pubsub/pubsub.lib.lua index 6a2c6ad9..c84208a2 100644 --- a/plugins/mod_pubsub/pubsub.lib.lua +++ b/plugins/mod_pubsub/pubsub.lib.lua @@ -796,7 +796,7 @@ local function create_encapsulating_item(id, payload) end local function archive_itemstore(archive, config, user, node) - module:log("debug", "Creation of itemstore for node %s with config %s", node, config); + module:log("debug", "Creation of archive itemstore for node %s with config %q", node, config); local get_set = {}; local max_items = config["max_items"]; function get_set:items() -- luacheck: ignore 212/self -- cgit v1.2.3 From 7af694f5104fcf0adf05b5bfebcb81d5b5e44009 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 25 Oct 2020 15:23:36 +0100 Subject: mod_pubsub: Comment on itemstore type --- plugins/mod_pubsub/mod_pubsub.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_pubsub/mod_pubsub.lua b/plugins/mod_pubsub/mod_pubsub.lua index 4f901ba4..9b89f3e0 100644 --- a/plugins/mod_pubsub/mod_pubsub.lua +++ b/plugins/mod_pubsub/mod_pubsub.lua @@ -42,7 +42,7 @@ end local node_store = module:open_store(module.name.."_nodes"); -local function create_simple_itemstore(node_config, node_name) +local function create_simple_itemstore(node_config, node_name) --> util.cache like object local driver = storagemanager.get_driver(module.host, "pubsub_data"); local archive = driver:open("pubsub_"..node_name, "archive"); return lib_pubsub.archive_itemstore(archive, node_config, nil, node_name); -- cgit v1.2.3 From 4cb9c49d9522b827cca3ff9906b4ca2348c988b1 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 28 Oct 2020 22:42:43 +0100 Subject: tools/tb2err: Formats Lua traceback in errors.err format Manually opening to the files and line numbers from a Lua traceback is tedious. This tool converts tracebacks into a format that many compilers and such tools use, which is also compatible with Vim (and possibly other editors). Thus if someone sends you a pastebin link with a traceback, a command like the following gets you right to the relevant lines: curl paste.example/abc123.txt | tb2err > errors.err; vim -q --- tools/tb2err | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100755 tools/tb2err diff --git a/tools/tb2err b/tools/tb2err new file mode 100755 index 00000000..7b676813 --- /dev/null +++ b/tools/tb2err @@ -0,0 +1,21 @@ +#!/usr/bin/env lua-any +-- Lua-Versions: 5.3 5.2 5.1 +-- traceback to errors.err for vim -q +local path_sep = package.config:sub(1,1); +for line in io.lines() do + local src, err = line:match("%s*(%S+)(:%d+: .*)") + if src then + src = src:gsub("\\", path_sep); + local cut = src:match("/()core/") + or src:match("/()net/") + or src:match("/()util/") + or src:match("/()modules/") + or src:match("/()plugins/") + or src:match("/()prosody[ctl]*$") + if cut then + src = src:sub(cut); + end + src = src:gsub("^modules/", "plugins/") + io.write(src, err, "\n"); + end +end -- cgit v1.2.3 From 43ec58831c05914393b9931503d1f32f0ba72549 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 28 Oct 2020 22:48:31 +0100 Subject: tools/cfgdump: Reads Prosody config file and pretty-prints it back out Useful for comparing what you think you have in your config with what Prosody sees, e.g. wrt (lack of) significance of indentation, order of options vs scope etc. (global options do not go at the end!) Could probably be turned into a prosodyctl command, especially if it learns to redact secrets and passwords. --- tools/cfgdump.lua | 120 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100755 tools/cfgdump.lua diff --git a/tools/cfgdump.lua b/tools/cfgdump.lua new file mode 100755 index 00000000..b7ca0716 --- /dev/null +++ b/tools/cfgdump.lua @@ -0,0 +1,120 @@ +#!/usr/bin/env lua + +-- cfgdump.lua prosody.cfg.lua [[host] option] + +local s_format, print = string.format, print; +local printf = function(fmt, ...) return print(s_format(fmt, ...)); end +local serialization = require"util.serialization"; +local serialize = serialization.new and serialization.new({ unquoted = true }) or serialization.serialize; +local configmanager = require"core.configmanager"; +local startup = require "util.startup"; + +startup.set_function_metatable(); +local config_filename, onlyhost, onlyoption = ...; + +local ok, _, err = configmanager.load(config_filename or "./prosody.cfg.lua", "lua"); +assert(ok, err); + +if onlyhost then + if not onlyoption then + onlyhost, onlyoption = "*", onlyhost; + end + if onlyhost ~= "*" then + local component_module = configmanager.get(onlyhost, "component_module"); + + if component_module == "component" then + printf("Component %q", onlyhost); + elseif component_module then + printf("Component %q %q", onlyhost, component_module); + else + printf("VirtualHost %q", onlyhost); + end + end + printf("%s = %s", onlyoption or "?", serialize(configmanager.get(onlyhost, onlyoption))); + return; +end + +local config = configmanager.getconfig(); + + +for host, hostcfg in pairs(config) do + local fixed = {}; + for option, value in pairs(hostcfg) do + fixed[option] = value; + if option:match("ports?$") or option:match("interfaces?$") then + if option:match("s$") then + if type(value) ~= "table" then + fixed[option] = { value }; + end + else + if type(value) == "table" and #value > 1 then + fixed[option] = nil; + fixed[option.."s"] = value; + end + end + end + end + config[host] = fixed; +end + +local globals = config["*"]; config["*"] = nil; + +local function printsection(section) + local out, n = {}, 1; + for k,v in pairs(section) do + out[n], n = s_format("%s = %s", k, serialize(v)), n + 1; + end + table.sort(out); + print(table.concat(out, "\n")); +end + +print("-------------- Prosody Exported Configuration File -------------"); +print(); +print("------------------------ Global section ------------------------"); +print(); +printsection(globals); +print(); + +local has_components = nil; + +print("------------------------ Virtual hosts -------------------------"); + +for host, hostcfg in pairs(config) do + setmetatable(hostcfg, nil); + hostcfg.defined = nil; + + if hostcfg.component_module == nil then + print(); + printf("VirtualHost %q", host); + printsection(hostcfg); + else + has_components = true + end +end + +print(); + +if has_components then +print("------------------------- Components ---------------------------"); + + for host, hostcfg in pairs(config) do + local component_module = hostcfg.component_module; + hostcfg.component_module = nil; + + if component_module then + print(); + if component_module == "component" then + printf("Component %q", host); + else + printf("Component %q %q", host, component_module); + hostcfg.component_module = nil; + hostcfg.load_global_modules = nil; + end + printsection(hostcfg); + end + end +end + +print() +print("------------------------- End of File --------------------------"); + -- cgit v1.2.3 From 4d9e30aff34d5c37c4a43e7ed6af8ed4c8b621ca Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 28 Oct 2020 23:05:15 +0100 Subject: tools/linedebug: Print each line of source executed The extremely verbose debug output. Nice for getting some idea which code runs most often, or where it pauses for no reason etc. --- tools/linedebug.lua | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 tools/linedebug.lua diff --git a/tools/linedebug.lua b/tools/linedebug.lua new file mode 100644 index 00000000..96214cc8 --- /dev/null +++ b/tools/linedebug.lua @@ -0,0 +1,18 @@ +local data = {} +local getinfo = debug.getinfo; +local function linehook(ev, li) + local S = getinfo(2, "S"); + if S and S.source and S.source:match"^@" then + local file = S.source:sub(2); + local lines = data[file]; + if not lines then + lines = {}; + data[file] = lines; + for line in io.lines(file) do + lines[#lines+1] = line; + end + end + io.stderr:write(ev, " ", file, " ", li, " ", lines[li], "\n"); + end +end +debug.sethook(linehook, "l"); -- cgit v1.2.3 From 53e6579aaba5b9d1ec4e75c3d323f6e52d23bf80 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 28 Oct 2020 23:15:52 +0100 Subject: tools/form2table: Convert XEP-0004 dataform from XML to util.dataforms Lua format Used this to generate code for a number of PubSub forms IIRC --- tools/form2table.lua | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 tools/form2table.lua diff --git a/tools/form2table.lua b/tools/form2table.lua new file mode 100644 index 00000000..49a6972b --- /dev/null +++ b/tools/form2table.lua @@ -0,0 +1,48 @@ +-- Read an XML dataform and spit out a serialized Lua table of it + +local function from_stanza(stanza) + local layout = { + title = stanza:get_child_text("title"); + instructions = stanza:get_child_text("instructions"); + }; + for tag in stanza:childtags("field") do + local field = { + name = tag.attr.var; + type = tag.attr.type; + label = tag.attr.label; + desc = tag:get_child_text("desc"); + required = tag:get_child("required") and true or nil; + value = tag:get_child_text("value"); + options = nil; + }; + + if field.type == "list-single" or field.type == "list-multi" then + local options = {}; + for option in tag:childtags("option") do + options[#options+1] = { label = option.attr.label, value = option:get_child_text("value") }; + end + field.options = options; + end + + if field.type == "jid-multi" or field.type == "list-multi" or field.type == "text-multi" then + local values = {}; + for value in tag:childtags("value") do + values[#values+1] = value:get_text(); + end + if field.type == "text-multi" then + values = table.concat(values, "\n"); + end + field.value = values; + end + + if field.type == "boolean" then + field.value = field.value == "true" or field.value == "1"; + end + + layout[#layout+1] = field; + + end + return layout; +end + +print("dataforms.new " .. require "util.serialization".serialize(from_stanza(require "util.xml".parse(io.read("*a"))), { unquoted = true })) -- cgit v1.2.3 From 7312a610d4a9c1ca879feb7d271b98d33541ca3a Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Fri, 30 Oct 2020 13:53:24 +0000 Subject: tools.modtrace: Library for tracing/debugging Lua module and method calls --- tools/modtrace.lua | 152 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 tools/modtrace.lua diff --git a/tools/modtrace.lua b/tools/modtrace.lua new file mode 100644 index 00000000..6360fb61 --- /dev/null +++ b/tools/modtrace.lua @@ -0,0 +1,152 @@ +-- Trace module calls and method calls on created objects +-- +-- Very rough and for debugging purposes only. It makes many +-- assumptions and there are many ways it could fail. +-- +-- Example use: +-- +-- local dbuffer = require "tools.modtrace".trace("util.dbuffer"); +-- + +local t_pack = require "util.table".pack; +local serialize = require "util.serialization".serialize; +local unpack = table.unpack or unpack; --luacheck: ignore 113 +local set = require "util.set"; + +local function stringify_value(v) + if type(v) == "string" and #v > 20 then + return (""):format(#v); + elseif type(v) == "function" then + return tostring(v); + end + return serialize(v, "debug"); +end + +local function stringify_params(...) + local n = select("#", ...); + local r = {}; + for i = 1, n do + table.insert(r, stringify_value((select(i, ...)))); + end + return table.concat(r, ", "); +end + +local function stringify_result(ret) + local r = {}; + for i = 1, ret.n do + table.insert(r, stringify_value(ret[i])); + end + return table.concat(r, ", "); +end + +local function stringify_call(method_name, ...) + return ("%s(%s)"):format(method_name, stringify_params(...)); +end + +local function wrap_method(original_obj, original_method, method_name) + method_name = ("<%s>:%s"):format(getmetatable(original_obj).__name or "object", method_name); + return function (new_obj_self, ...) + local opts = new_obj_self._modtrace_opts; + local f = opts.output or io.stderr; + f:write(stringify_call(method_name, ...)); + local ret = t_pack(original_method(original_obj, ...)); + if ret.n > 0 then + f:write(" = ", stringify_result(ret), "\n"); + else + f:write("\n"); + end + return unpack(ret, 1, ret.n); + end; +end + +local function wrap_function(original_function, function_name, opts) + local f = opts.output or io.stderr; + return function (...) + f:write(stringify_call(function_name, ...)); + local ret = t_pack(original_function(...)); + if ret.n > 0 then + f:write(" = ", stringify_result(ret), "\n"); + else + f:write("\n"); + end + return unpack(ret, 1, ret.n); + end; +end + +local function wrap_metamethod(name, method) + if name == "__index" then + return function (new_obj, k) + local original_method; + if type(method) == "table" then + original_method = new_obj._modtrace_original_obj[k]; + else + original_method = method(new_obj._modtrace_original_obj, k); + end + if original_method == nil then + return nil; + end + return wrap_method(new_obj._modtrace_original_obj, original_method, k); + end; + end + return function (new_obj, ...) + return method(new_obj._modtrace_original_obj, ...); + end; +end + +local function wrap_mt(original_mt) + local new_mt = {}; + for k, v in pairs(original_mt) do + new_mt[k] = wrap_metamethod(k, v); + end + return new_mt; +end + +local function wrap_obj(original_obj, opts) + local new_mt = wrap_mt(getmetatable(original_obj)); + return setmetatable({_modtrace_original_obj = original_obj, _modtrace_opts = opts}, new_mt); +end + +local function wrap_new(original_new, function_name, opts) + local f = opts.output or io.stderr; + return function (...) + f:write(stringify_call(function_name, ...)); + local ret = t_pack(original_new(...)); + local obj = ret[1]; + + if ret.n == 1 and type(ret[1]) == "table" then + f:write(" = <", getmetatable(ret[1]).__name or "object", ">", "\n"); + elseif ret.n > 0 then + f:write(" = ", stringify_result(ret), "\n"); + else + f:write("\n"); + end + + if obj then + ret[1] = wrap_obj(obj, opts); + end + return unpack(ret, 1, ret.n); + end; +end + +local function trace(module, opts) + if type(module) == "string" then + module = require(module); + end + opts = opts or {}; + local new_methods = set.new(opts.new_methods or {"new"}); + local fake_module = setmetatable({}, { + __index = function (_, k) + if new_methods:contains(k) then + return wrap_new(module[k], k, opts); + else + return wrap_function(module[k], k, opts); + end + end; + }); + return fake_module; +end + +return { + wrap = trace; + trace = trace; +} -- cgit v1.2.3 From f80e643a0e903c51bec88a6462e60aa96da11506 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Fri, 30 Oct 2020 13:53:39 +0000 Subject: util.dbuffer: Add __name to metatable --- util/dbuffer.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/dbuffer.lua b/util/dbuffer.lua index 640c1449..54e12266 100644 --- a/util/dbuffer.lua +++ b/util/dbuffer.lua @@ -2,7 +2,7 @@ local queue = require "util.queue"; local s_byte, s_sub = string.byte, string.sub; local dbuffer_methods = {}; -local dynamic_buffer_mt = { __index = dbuffer_methods }; +local dynamic_buffer_mt = { __name = "dbuffer", __index = dbuffer_methods }; function dbuffer_methods:write(data) if self.max_size and #data + self._length > self.max_size then -- cgit v1.2.3 From 1ab96f2289a63f6d1fa55b5d131ebf3296206bc2 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Fri, 30 Oct 2020 14:04:40 +0000 Subject: tools.modtrace: Pass config to serialize() --- tools/modtrace.lua | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tools/modtrace.lua b/tools/modtrace.lua index 6360fb61..45fa9f6a 100644 --- a/tools/modtrace.lua +++ b/tools/modtrace.lua @@ -13,13 +13,20 @@ local serialize = require "util.serialization".serialize; local unpack = table.unpack or unpack; --luacheck: ignore 113 local set = require "util.set"; +local serialize_cfg = { + preset = "oneline"; + freeze = true; + fatal = false; + fallback = function (v) return "<"..tostring(v)..">" end; +}; + local function stringify_value(v) if type(v) == "string" and #v > 20 then return (""):format(#v); elseif type(v) == "function" then return tostring(v); end - return serialize(v, "debug"); + return serialize(v, serialize_cfg); end local function stringify_params(...) -- cgit v1.2.3 From 48521ba1538f797f5bef64f5fe5f3a9fb6e68f7f Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Fri, 30 Oct 2020 14:05:07 +0000 Subject: util.cache: Add __name to metatable --- util/cache.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/cache.lua b/util/cache.lua index a5fd5e6d..cd1b4544 100644 --- a/util/cache.lua +++ b/util/cache.lua @@ -28,7 +28,7 @@ local function _insert(list, m) end local cache_methods = {}; -local cache_mt = { __index = cache_methods }; +local cache_mt = { __name = "cache", __index = cache_methods }; function cache_methods:set(k, v) local m = self._data[k]; -- cgit v1.2.3