From 3391b3c8909285808e4c0760a991049b384ecf4a 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 ef4bb01f0da61e8512c0c6f9dffb2c1822f42c05 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 b593a8c54f2c37aefae0503b130242589e42dc79 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 d67b08d9c48ac3bef4a495b7cd700b956c7e7c61 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 193eebddaf36f0df296b70d858c97d66d5ad20bb 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 e21ccabfac08dfb94fda4236a3b7bc842dd54b80 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 e1846ef9a35e2e91a3c844ef489be780811aa1b0 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 031dc2e2a0450b25e16bbc534b29522e0ee52793 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 81a8dca01e7df9f7dbf7478ccac4ada001a88b6d 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 9270a17d2089c16c1cb37040f55638867b5adfbe 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 48162c5fca869aa8adefc5dc7e8a58bf1ce40c20 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 1682a8437de2afe9e01d5e4daaa87f56d7af7f6b 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 c309d32629c0b0f103f32dda5506528e2e1f468b 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 83ab43ca8ff3d3e93042f76b91194f2ec3c6e36f 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 6188d89e486917bf5b4e1c6cafae1deeb7f0d132 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 16ddfbbe443acfbb5657b77978b7e774faf34753 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 2e3e5db959672f2175223d670d106f1202e95f49 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 452611bf58da7b159ba69d3a36fa9624915eac57 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 5dff3b198343a7804811126efa30bedaad092d93 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 5adc46bde533b5ce92029eb33b2ddc2899dbd737 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 c219636c741e61d57ef8315480e6a596e6e4ff7a 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 38b328ad1be357a028a818eaad7c6c3541ac6759 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 e984432ca4a85a2e9abef99b667731d8541ed662 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 aa3cfe1ea0241eeb60d09ad11bd2727d28887a7d 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 f6cb6c1fed62059341d1b10aa9bb8b9720833fde 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 3e2ef7ac97fbb6b9c544f7f7f6937e395ef58a8d 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 4d02538a3dd73f8f7f7c21d21aa3ffafecfd7a0c 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 6c6c7bd07f3bcd44d8bb8294180510b4f29a0fef 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 f82561a4992773fc76c214ead3e442e6ba5f1af0 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 256b2aff1bc66f653fbbaa3ea35586db5e61141f 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 27083633e2ef371a834b30c654fefb6fe665bc8a 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 7d5ceb5754d44db6255a5f5325a737682c0a3178 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 9ca48298d7444c3fa207487419e06c76716cb795 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 755f1d8050c373af7abf578ac30067f1d141c93f 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 08cb0002476600be61921a3a2a15bc9626e052e5 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 d67408b4d87c4556371c15082097ac00f4edcb6c 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 e22dada0b27f0793bd88b4882fc73fcdc7936b6a 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 6dcf53f7b284f265ce3f2215e33c6f0c28be5ce9 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 a8b096516b2ef1791bc348e18baa7e7f5399de8f 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 c0b8a6ef63746675fbe1441bceed47f6fb2bee88 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 109ee6261982614ea367de4b2716b0ea6f375168 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 a575159cb0172220dd3657ed8a3eff5ca22d9532 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 05886ceece8c11a31ca9f414332bb9fa99c15473 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 4a60144a3f67a83ccab4f1599548f292e20dc24b 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 15113f0bb16515caf35d100d4aedf4894bdbda6b 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 743c5bd552deeb935adcf94057cd015e10a70745 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 abbf8a6b78264e44bb7d634dbf1bcb66649e0248 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 a454069cfa2a593d304d0fb49997b93eee66bef9 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 6e6914b3cab39ff86f39869879a284e97c1c11df 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 77a51af33dbafc0f301bce2f3b09122645f4e406 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 9aee66a6c5ee1754afd603b14c7109c2b13db542 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 91e23f83983af91b65287c209caa54f9027a6487 Mon Sep 17 00:00:00 2001 From: Maxime ?pep? Buquet 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 ed48077ce4b5ac8310270edb93c7d68a88cf0602 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 4cbf600434638bcb53c8defb767a076135ab8f24 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 36d31b94bf54bac423fdeac12c4978d22ca1b6f8 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 b4e3b4ea25135148113b1fce6207b28889a4eb7e 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 6c573bacf091c7fe4e15591833639003b2519a3b 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 d39cf4f42e2ec7afff198437a7ccb82f8b7664ab 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 f4b4239e3763ac2f27f1f7d0244b5b2c7d666821 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 0a11d53f81d33de026024e624f062cf73f6fd006 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 321a68c41995de92e3002c48c7df8571343dfd54 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 b250ff0d70b614a5ee0025fc68f175428bfca4b5 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 5806d67e82705701d6b778d4b6399724a035c528 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 07ab17120c03070c138137dd30d3fb601716a8bc 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 b1b7f0261f054bf83a955e073dbdeb9631518097 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 01878444937fe8b7ea149ef35cd76453517bf798 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 ba5a1d71a0b8b34e04d1684b2037306d6131d526 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 f8bc92174749c64b3804b8ad46f47dfceb107312 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 da5ec59b36188e325ed6dc9fa6ebf8187d492b33 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 3f9505a0d5ce144beb79748bc783e06862693129 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 708dc874279d00235cb729b61f7438e753bd8008 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 26e3090de67f9fc5152c764122b7a62d3d9534ac 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 93c5ebb743337c82103ea94166065106a9f5b641 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 38462e35420ca9e93af018210be940353b32d508 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 7799a8a7bf9f29c6d34bdcd3ded42fc6387c0fe5 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 ad3e5fd42bd1dddd478fffd412a842e98c735e9a 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 ac2d2297b62ba2973c49d7d5bee3927cef64ea97 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 a8d08ff3ba20bd2a64d8f59930019d460488c6e5 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 e4087a66e42e86ea4aae8d6cba1bc7dbb66ecb87 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 76f85eb776b39feeb37c8dd276fcd9eaf6c722c7 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 1271c21c89d544ad3d34526f9317c952092c0658 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 ce8379079e7e0fae9b292a7a8ce6ddd91a4f6e46 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 f902e414f63ae70f74c414a57c0c3cfe26ba693d 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 9e01c3ff642670c8b10a20686f7a6e100ad08fe9 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 fa09b8372d173408df3cb468d9e1b5098c783f31 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 d9e14b0e994443bb64eaae05b080a44929b8d03d 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 60213520c2a27bf0472a0ef5a2cf16640134a1a8 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 17be1c595df0e62ff28d9e115b83ace74c800db0 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 f61b36e2d32c4e5ea7ed7eb3d800bcc7ef5afee2 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 12cea7094d339f68a661d00f096e8db9a51417f5 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 d9677a50b51b8e40ef6cac11052f7a2477dff51e 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 caecb38b5b01262452ac26e15aece8a9c1870518 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 1a3edc0ab921f131c7304e92d7402ef88ff465ef 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 08d0a26c8c614aeafbaa990f55b3d3372d88dbb5 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 4a1dec0392e8a0a076c91ce6ff47f938ee9b2641 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 fe9e33d8c22b774d5a1f4cbc0b1145f54201c096 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 7ae9fe492d2e616d94b77c280a2f5e0f2c744075 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 0c19a99587bd6f0f590498dd1639c52af662c9ee 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 2d229c341839f408fd64dd02be2524bb7f121eda 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 0cc513679d55d2f1b67cbf4b5c133f86eaeeaaca 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 2cbbc9fe2bb407230e89c1903f8470ba6ff7ecb0 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 26542811eafd9c708a130272d7b7de77b92712de 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 4d5f1da475da390dab6e8835f8159d868fe8c5b3 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 13ce41d3f969bc039e3e1fcedcc566da86ba9028 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 22195430f16dcf56cb4728c00254d89964617255 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 814eebb672ee88a87a7c3effe5c437efd34f5f3e 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 88568a7af092d21061020932dd3ed7cfb0113c15 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 ffd845f929ab51d182ae10765b5b9730d235f63b 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 976c452a40c2711c5afc14097762515efa216023 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 053a17c00a2808a9da7bea08e87e74b34a54e0c2 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 e5603e10c58e543ac1e4bf8223ea07fb648ba58e 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 b80261ccbf3fbb5e0f07ccfe8b7419b48ee9bcbf 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 eb245f3dd6fb501214fabe1536a64bb76cef9c48 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 344b370b5e6367761a32da79587ad02a15604845 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 4b80adc9641fca202ab7e2488722280932b44ad2 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 9ee67a16d1441e74435d156af7b0c19bb8d28d4e 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 ddf2d725a6968d38b5819c2ef881a1479b5972a6 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 3d5cb716b4f2dd544f83ce7bd1fee55f3a1ef43f 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 37ba8188057989a530aa5a5e138b181d1ad855a1 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 7f1a7179dde0dc2569c4100b307a55c6c2a71fd2 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 ac2aeb46621d033813e7ca0730302dfff2a6d4a6 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 7e568a68dc06ca5fda2786cdb16b86d7715ee309 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 dda988a3891717bc1ef5bc5fd3c6b9172ee00887 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 700f9dceb670bb7dacddbbb5800e6ba12927b409 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 b47d67c80fe77a3414c8f0ef1fa92cec96696e54 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 a08b6d8226ab4f2fe5358f664990e245db01b9a6 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 2bc176f1d072da9404436a40581745033d1e41b6 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 4b022060a27216380565302aa0fc3ca805a532eb 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 39291da9614b3a9f01afa877d319b11d5b942087 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 82a10071d06089c724860a2cbd64bc0e8b6b38e8 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 ae2df5b05cac3568c34f61c173391c3097f90817 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 ab78dfb2abc1cfa7c3119d0bad619da9e155c535 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 84be765efd340d27079e3524acf20df5bfb11f1e 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 b4f63e714100773e131d69792239875583595c15 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 8dbde146973dbdf875e1a5fc5a455218426f454c 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 a335b6de27f7da7df46ec7a4091b6d1ea8ba2fba 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 8999b2a97c8d5e0d9dcf10d7692f928287d6bc5e 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 dda9c3ad7236b58675d66b7eb23bfb969f9c90d0 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 958b92cc3a56ad4cb4fe8d1549bf611df6d633e6 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 d684b288de7ac00a0e12795cc3fb8f02bfed1217 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 75db23afc67254286b7d0bb94c8e5945f32c8068 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 8189ab0bcdceea2c6ee2df4c1fb522242d8e1533 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 9fb58cf11f3de8f167a940c221d9a7b7a0c2da56 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 143c6bb57368837d9860a3f8a3678304689d5d46 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 67b08fccf7d370c1df426890057d2775edd49797 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 d4db66791dd4136374610889c68789ec3b7a186c 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 6a11648a077700aa5efedd3e300f370c8a95f665 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 eb63c91d91f503946fe9aa731d975f0b289e6065 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 798be44f563b8764dad50e893f4b25268b42e5a8 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 c77d416cfffd6604cc4d9a633f9f7941f6fd5f10 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 8aef0281ab608e1b1d421673f924ff91d439097d 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 351783fd1d132fea8098aa37084258afe81afccf 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 8c6c1d0fd0fa2f907b0821954b7aa03a5fe2618d 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 db917661a6946e25b53b38b79ba7c8f4f01e50db 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 d1ad7d498828b48d2a2477319083227b91b5441d 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 92595546031e1f8a6222e9e164f8cca45e949bdf 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 7e9722a7362c9e7ffd6cca6c756ee61acc52ff93 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 669da78e01c61d83912210877b2c5514360fe7e9 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 79bebb21f83365e33208644f1c0060752f4fc532 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 e21f31c7c282aa5fba3c37e6d1e829edb3b78a6b 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 2a073294ae4a6eccc10ffcef2a2146bfb9abe1ae 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 36900805270064f089c7e3ae9b2e3be6b7a63df0 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 a763f1f8235b4cc9633847203d3a282871ebfca4 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 1a68e66b842a4af6fe19e99f2e09044a540b404e 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 49b2878157c4ed51205580848642058e53acdb59 Mon Sep 17 00:00:00 2001 From: Maxime ?pep? Buquet 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 6d02c463f7436544bfce141a7f36bfda417d497f Mon Sep 17 00:00:00 2001 From: Maxime ?pep? Buquet 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 fc06f69a4f3b22c97cdca1114cca191aa5e259ea 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 268e487245b73baee4f8d8add6d12707d278f371 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 2080433daf5a956d0456c6ce24ce1ef9183a023e 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 bd52e2269b0fff09fc8aea6c6807a9f08727bb31 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 c1a1511e2662fdd1749c30cfc1cabc3d4b89f082 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 aea959880cf8ae4d81c41d4e6ef224cc978ef278 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 4a2f05236136f1a2e1a74e120bb6131c628a278a 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 eda65b96f853b9210c8a7e650eef5578a1ce34bf 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 ff3904d881514abc83443b02e10cd621873bed64 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 3277b108c4f1b772e265d270c52ca2e5c9956879 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 7ef5adb448ad36074123a924dc80607e087ed200 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 ee4a464a26db437f1b187c4a6cd83a53492010fc 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 e31a4cdd89be95fd6b97617491ea8e4a9822af66 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 00f3e4db9c96b7939c1cf87c514a40f58ae7fde0 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 439a1e0ebb1045d72b0a3c106dc1d122783387e2 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 461c69ff4eca9672bac066cb7f14fdc98bdae32b 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 ea52a16673127e4428efb59ab50d660a48b6f7e1 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 8cb20bea8cbbcb0f8e2557738135511b617e79cf 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 eefe3deafa99c7d227a4e77c179dde796824cb23 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 8ad3c66121ddc50c102061c9365411545d660625 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 0c520e8a39f380940ad216a228eb0624d85e02bd 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 dd0258ff779f4ccc4da11a19c344d054ba7f8430 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 ee003734474622524bcf3eea479e64a6c91f2802 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 3179518b6ac887fef63d5ffa83b2020691809299 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 65f01dec0dc42ee9e1997f2dcddeb6375cdc92c8 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 e9eca8cd5521f2bfc8f9285b606eb84de5a467e4 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 08ec4600470e32bbc4bb0afdfce9012f6a8cb5e9 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 8e20f176e04a261129c9d49705b72f9460999145 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 0c7ea2e51a59a69ab23abc505bbcd9b6ba3722bc 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 f7b4b8ed7a50a034299e0e24ac9003757c01d78d 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 60f37d33c9d31e8978c52a782aff00d7d572ebb1 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 e986a0e13786adbf60c42d352a019fd62b903c34 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 cc28a5eb1232228097d229ea21a187f4ebf190cc 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 2abb3c16201ea848989d5c8bfcef67507faacf5c 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 00f1e6982dcbc263f8b58e11283b31078e048a01 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 19de299874a63dd0f5143844f638b879d571b17f 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 a23b9fd0ea3b3404861fd56db9d99d9b7e32f3fd 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 d22524dbc05c76ac357f5e6dd5383efe8cd2e036 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 1c244327eccfa235260ea2a27173da8633ad956c 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 ad925d5dad90ae6462302e74325b74c88ff1fceb 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 0f802ffe183a94cc9407cdf92bb9eaa9c213e602 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 fc818b419ebf2ef4991020744e0a2cb15ccf201a 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 ada8fc186d60e1d3250e6fe844cbcc853eee64bd 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 79b43ce34ca3914d01b24b1afb50a0d184bb292f 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 1b3c29d26553854f4cf577a3bda0c12d61db8ae2 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 c0e30a99fc9b49847577598541328ebe1d7eaaba 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 4db92c1c120b6ed9f91bb2e1167b7159f2337129 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 f25e2bc13b7485b762bc3ff71b98da96ade2df54 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 568fc99fea90769ea7ddac77cbf1028ca3c332d2 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 9163493840c796d31feabdf3d3d379442e3a7020 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 f5dd9bf4c61dcbc768490cabb25bf58ed0111eaf 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 8041f3f5490a8f4893316437ab01415c516dcaa7 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 8e2e187ff819b98ebae24f2a5ebd9994a2d41c2f 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 1d11df9a67d5f650d1ff4404ba7d6668df99875e 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 3752c51a3a2e27a1d5231e9acb27deb864024bb0 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 593ab1bb69e15586b9da9d9fbd1f9e5fd8878b13 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 eecd66b9941cc7e07b9a019fad141996b6b1a4a6 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 3f3280d0c0bf789ecace5e3024ab24a9dc7fd339 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 2a2cdcecdf15dc13f939cf9b83959e9929a94eb4 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 701d64ed8507cf2becd68a4285b66d340f0d9e5a 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 d73c6a8327c79775989303c823bfa9be22e26116 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 265d5cbec07c6297c811b72cf5416acb446869da 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 65ca6b82048f1b260291ea9d920d3b117a2603b4 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 cb8a085c3bcbec83ad6b65631f8189e406bca042 Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 83524f2d6a28d82db56233a5cf3660bb726fdb7a 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 150569cb8975a73f044c57684a004b4247005093 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 5252a355d02d298f4ab6449abdbdbd761f51bc88 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 e60915744d6dcb90bbe500aee78d3820b7f89b82 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 d8a52f2c8a9b1ca96d9f9c08024e5ac55476e89b 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 88345b017257091ca0ae216599d2b8a43ea9681c 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 d64c3ffda48fa9b263f29e8f30a86ef3b3edb04b 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 d2907f3c5d523dedb3daacfaa398efaa7987bdfd 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 6d0fe1858d95548d49de6cfabcea91dc9c90a241 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 e663152ccca82750f06781817370f036f5bd517d 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 5e6f65812dff9f1f3e5178dc9e5738c963c25c6a 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 c964e26cc7236b85e623cf6e2eec001d4208c9ff 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 dae2e97303535d5f3c42e763a1a809ef340c2eb0 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 bf89be01bb4e16a11c72a7e1f6e8939b76aed47a 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 2f208d6021b741382ae25708d4cbbeba3f28eb00 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 355ee5ab12cb9938c969abe970dbcf247f262758 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 f16711edf5cb9b0456a457ca55d42ad6d8500019 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 4e2269a9c3afc930eeaf094d116efb0e88c2c73a 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 aa07a1cbe1d2454f8cc5d5d0570c1b167c53ea70 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 7e9e7b764e8c141aed692ee140edab4589a462ae 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 f218adca6982d00fa3995d2f97ed7a1945f09954 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 2d46bfed68cb755d1facdd4605c958814181e1b7 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 49f055f1b45dbfb4b4f11b14c9953b94df752a4e 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 9b6e98a731cf360aa60c13df062c74b58a368b37 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 0efe801b5d1e0b45cb16d8fdddaa3620d6317073 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 a9f304b6bdf3c3ef34cf1db64cd0634888e6f9a7 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 92571bfb04cc6cdf0bdd5d634b4f3be35f4d1117 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 5bd804db729d2f20a9f82bdd954b71a2b2ba4298 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 7c80b5b23e5a307ff4c62d49d0d98fd92e9908df 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 166033b48a434e08d404f45dcdd1a07a4a7fc2f1 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 216f7ce87bf9754010a73e1d0fc06d439e3c5a8d 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 24ace735d084fa5c57ae05f0bb38b2fa9440ca28 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 f30175295c5d0d843faeed59d99c3a9d67462504 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 5652bd1b40edb6a20eb85c0b7ab809ed59aab395 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 a2e98728c048d8457d9947017e611cfc57b1baa3 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 0ecfe1d6f19acc2f17e93b42cf86f9c6ed019229 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 e5864b338fb4edc4061c317136e45bfe21808cd8 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 7e6c27dd6bfbc5f0f1069036b28828805bf9d300 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 94fd1eada65f65524901ef6f5466febacf7ae2dd 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 97b80c860af1dd2913f457243f64276a8136b165 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 1f194ad556aa754c72be6aeabbdb93cd12ce57a0 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 3358bdc9bfb967295ef8b28715845567c03d7898 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 41efd38807871a198da442721cef6f1b953ebf51 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 ad89e20a7c9647b8db78c3c7e3cec625dcd16f4c 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 a0c37efd1f7ef8b4e55ff8849076c2db2d442450 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 151dccede107452e57fea55fc34a9a17012589a6 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 814e34400076376423770d65a1e7b262511df834 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 f1296c621d3577dc82019c6835596be2cd04ff08 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 680d10732be09705c1fdb2891dd72de260b97c08 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 8dba7528d5d580e965a4a44bd590cf5259564446 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 e57ef38b90335796e0d6f571c55fccbe33a3fe17 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 73fab2dc0d581b9744b611d97650cbe09ac6aaca 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 2b8caf8dac9239ef94ec9f6d3d4424ce9b79d294 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 90b6787b47dfdbc825d8d821a717e8f2bd3b3f03 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 1ea8e965ca4d59333bcf473fc164da73adaca8ac 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 fac7829083cfd11f1d35798a15b21c784322da16 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 80c57be10f96cca0c3c441aa0af27b5090ef1b8a 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 `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 2134cff478362f60eeb43c0f30d0297ea9100ef9 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 d572adfb0940f1d0bc6e6438af3f2b83749da998 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 b6ba263f15f7a5eaf6e2df2980d7c9d3b2cc9528 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 3d8f6c593b170aeb941fb1af8120f4acd3f6c640 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 2b7a0e891a84b1af9b4b3387da4ce76811532c10 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 701fd99947b8d897a60c74b1c2f4ab39fc620876 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 1054f2438b2f56790a7d5dcdcb3f841d460d27e7 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 00887cf07d565353152332f0adb854659e237fbf 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 54d0329e4c0809920c954a45e448263d3a669a2b 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 21d4a58469853ebffca787cfc76e9b7d8b40d9b4 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 41c9fe2b76ee5bd90eeb948c7bb75bf94906b102 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 30ef8e7ac46587732ab469e9775229e8d78e1845 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 cdbb6348f6536cb270544c933f3e45ccf66ef585 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 da0d3e296ca8fef7882cbf571039b153525a1185 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 5bf3c84d07ff55408262ab5644752f6f0e28415e 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 690c6a4eb240ede63cf7a31007c25df29fa123f2 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 b3741ec9ef19f4cc5dfb0bbc5832d632339dadcc 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 200664ab8a16a7f5a4d4a2de878c9f94cbeb4a8f 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 c39b08a25d18e6ca268a3c75f52992255d7b3e50 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 017e7f74389c0e20a1baaae682fd85b825975592 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 696da1f571e723216a1e6b74d793eed4fe98dbd8 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 7112f319c35e19bbc75579c102f2bc37b4d911bb 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 015d0616141b8ff16abf47b6e45d2c486f102e07 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 b61b308c569dda7ca5e214466503e9cc7ea4a7e7 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 e81589a45be00cb02e8577d3eff16a358a3f9080 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 fa03622e56e1520f4e0f87558d5e375f581fcad0 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 a49e94f20f276be733be3eab4f0ae8a57d28b1e2 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 617d39783136dc8405dcdeded238ec42a6dc8468 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 5c927a37de1ce01b20970e5245959c1c41a85b3f 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 cf5c64cd690c92ae7705fe6e7acce2ed81610ec2 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 59b4972c34a6887caf3ecb8b96d088fef831d9e9 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 7e32666c2b78f5bebe46bcf336f66a3e7d2eff36 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 ffd7ca3f8daaa3d0137012759f7cfe52a835ea89 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 43f98c242d4a212a8de0d8965733c206109c4d23 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 faf1282ee2dfacfba3a947edd9142b93ca1f4162 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 fa0d394f9b0ab4f503558d9c944fe4052d5eb324 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 2dbd1528bb118e77463db85403d535d307ec050d 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 8799bf12c702335a32d1d220269090f653230b0d 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 a52c045f4a8b7e11eac9b6ba4cf72e0a920594e5 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 411d4c02a1315eb778e2ee5613723b74b6cfd00d 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 be249b1a12e22c3276598555bedf7beb8386f550 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 9991f8892518b2d48cf27b3667a2fe33d25dcd67 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 4c7de2595bc229710b3985f96bea96a25c053300 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 c3c7aaae0f69ab4b2dfcef94f6224f46613fb191 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 9bca9eb263362d4f6c4f4e4a790b33676c509e03 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 cd4878033fd3f3b6b7dd67d66f8f02423ee11b94 Mon Sep 17 00:00:00 2001 From: Maxime ?pep? Buquet 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 9a5a3fd0f10693e6cba2779095398614eb94e68c 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 80cb8096a36c026c412d26e2a55cc744d05f6da6 Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 46dec34795a537f761d1720c00133275ad966558 Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 9f5b1c9f72049598a62926a8ecaa48f40d2810f4 Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 84441e9815f0e9cd322671f893d12ef18230f45c Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 027554ebcca06ab7fcf923c03089f82658adf990 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 6c5d0e50e7d5d890c30308aad97ecbd4e65c5fcb 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 4e7d17bfff9ff33d5c0b58cae1941aac45090b70 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 c91bee45450498e9858b10b1f1ecdeabe4fc8d92 Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 47b408b4bdf206a1c2967e1d8272f945e816fc9c 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 06188cd48624caf6a3ff64a8e6a111d893afd2da Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 554f3210e10ed9fad88f5e37f859fe7396fe984f Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 7673a68043b3e8682a82003780c2112fbf28321e Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 ab88117d108ace278c66dc17eed17c30b992d0ce Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 4af3c3997c55fc0e1afc6cac160a7a0919b7e954 Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 3a8b7b6f9fda5d1494be6835b8a47ddbdd2b2292 Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 f101e7330874a67f4bf5401914fcbced634da052 Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 8ed0ec1790dad15eb619c201e65a603980db6f26 Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 e5971f6e72429dfb5f0df4f52cd23b60a286da4d Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 9235343665d689cdd0c47ab5bc775864c3b006fb Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 c8848c38c99500ed686924689b96ae7d9e477922 Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 4c4eb9d656b576a221126ac7a805e17c303fc8cb Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 f4d9999bfa8a620508833160483acd0fb9b413bd Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 dba11eabd8f6dd8b60eada5e7d22b7ad0e344b19 Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 cb669478c458fec600dd7765d295d5aeeb3f4b95 Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 1b5d7c04d9f3bc95d9984fb837313d061bcfaeee Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 20c564dc12d100914c6aac0b8184e9d54f376f3f Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 94a15879bd6823d7b9ab5413b4bb6e04814909b9 Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 d3cff9d605ee5fead85e0f7a864ba4e288d991af Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 7ce0a7906de49266babf5b0e3de2eccb011f1662 Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 f55bf2aace5ee3389366f0c5ef800d0ae197a8e3 Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 cf6a8d7e5aa887e7c0a51e95c37dc96ac2ea4d76 Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 1352b68bc283b1621e04e3bbfd28fdebaf9f4cc7 Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 6c44ec5dbbc2b699c0e29e6528ce9f7c8765623f Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 86016bed9b3d17207b4e29b6b3fe3c700cf16abb Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 9aeb87e816f6be135344f68c5a4205cc3d4d252d Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 aa7360c3691a2779254eca84871959cb271b13cc Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 cb27e192f3fbadda2652dfa8ebd0ec38bdaf1631 Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 66e6cc02ae6d27ae948445ec652b31df210479d8 Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 715eb6db6c880250978dbaa18942ebdcbf7e74c7 Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 f89d5e01b0ddb4cde58073440da999d61f46a4d7 Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 3e3aea359b0801d444c0a07f63cbd4543cd3cdca Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 7724cc5c1ba5823c3fd023ac60d1aeec402d2a71 Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 c2f5b9d51fbee1e9f5c0ffb163e327f965321745 Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 efc2a81c59c5b465429a99175d63effdbd8ad12e Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 8a516777e10421846c59c3fce74d7899fcbdeed2 Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 2288a5a3561d8678ab4a9a4cb53771e4784224df Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 ea1cfdc042d163fdb8eee7b92388ffdb146fb67c Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 030787755a2d68fe2e72f28740af730c6f7450d9 Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 5fa62c1c2a1d7ac5d17526cfdf1d1d6faa8288b2 Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 f98d9a47d95c36380545c180ed32e9b0b14f9138 Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 6244336ad342df68da979c8a5a06497dc055cee2 Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 9cf38b7ed5b58856d9dd2298834feaa7501cfa6e Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 a0aa32c752425c9abcd0e565722818ea73ddaf90 Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 ec4a8200cd9374b17d823ecf947b705d94b67952 Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 b29dab1c1c6185c310c6346910aa7e0c8ed72596 Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 aa6a7d9238f2288a2fbca2e19f3ab855d0d43d95 Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 899ab776e55395a68e94462376f3bd7b852b8d66 Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 894d1f2645764fc000d51bd5a45033aa100506ed Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 1a3637b9ce9396b849e27d92cd6bda654493d817 Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 a9dae0f388d61f07408f19eb556e54be473ca2ad Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 457ca610cc9f40a1535ee97a1351713664d27660 Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 ebb62190fb2ec32435be0074f84532d248f463f3 Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 62e70b82a16a91f7964a52b2fe15b8c8ffbafa4c Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 71920e97cca96b3db7fc3c0c17d133ae8b15e74e Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 03d787c0aca4c2267a1535cfc7bbb25372a5d349 Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 4c33b461d6d7600f3d7a133c6a1f5c96931bf339 Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 46f373b49bdec7e65ed48e54dc92dabc11d9ec17 Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 73068551843b9e600a903ef6fc554afad76b5b65 Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 e956f52972ff4ae03afe866037d8559a77817032 Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 a114b5700c4dafe05ab522b3261689fb114bfd0a Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 ee77705d60cd0a61d6fac8d1c03e6814da42b928 Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 1cb49b2aa393153c36fb9edeb682c6fd1078e8fc Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 f93f3a10cdb1ed0db62dac40096df10cd0a0e0dc Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 9cf24d5024f39cc54a39d220879d4c62d4a4b34c Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 8cfebaa8318c0b6233941be5dafa5940fafa9360 Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 dcbfef2fcf7a397f230b2e13985d2f410fddf177 Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 d317295e55436b65519f05f48d76abc989354283 Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 fdbe6f4afe9d2f6942451dc2d03df132a0988d75 Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 33ae171c3d547dfbd2e03d2383c39902fc911aed Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 a55f24139e8413ddf71280615253ab62b29e7b51 Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 84da5cd3d7c5c1fb2c74cff6044569f0bc03fec4 Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 5df8222affc9ee205232020be4b00856dc51d0b0 Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 6a485386c060dcce9565ca0f2431ce6af15c88c7 Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 88f90d12ddb346a3a15602b01dbe394d98d4fadc Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 7a596299b4c3fc79424130f2beb2cffe1a8caf02 Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 930c50094c3d5f7d8ef01e9c3bcd93509f3c6693 Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 c96869a9b30536712703439a5a5ece36a26ef8df Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 5191643f99bb0742720248de134049f4acbb1412 Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 a81aea73315e5ace1ddd0e3158947146e2414a10 Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 2e5522674ec344d9f240f656ed907ad7801185ee Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 8a6819c5643736a0fc7d4c45f496ad958796d36e Mon Sep 17 00:00:00 2001 From: Jo?o Duarte 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 91a0bce2b12a098408d7a67f77b5c42e9421e00b 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 790a10f47da55d1b6b5eab489dd082eaca942b2a 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 bd3c389d48a14a2509b46a4830938c2c83505713 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 0bf3161a3f25b84af6f78f2e40da17d853761240 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 59cbfb4e8c8ea4fcab7d443a7db9a897c04a2362 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 77c357d325f1df0b755affca72daa4e1583554fc 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 00f6257046199935e7ebe526c698cfdc7ecc2687 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 f93754de4a3bc93dc7fd563eb570b358042ad693 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 24670cf607bf3eed29ca79dd4e6b7397773db91b 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 5a382ce091fd1edf8d29d4531ec7a6bd957fc4e5 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 efd7a8c22603525106125df3e35a5cf64e76ec22 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 81bcce711729db0d8b2983f8cec3d7a7bb7e22d4 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 7cd3955aa03761deb3cb5eeb56e3f22402b0f3c5 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 48e395179d9f64d90f8e36d44b3d6fe1f857c264 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 47625cfc952c23e1102caec097a6803b9e1f29c5 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 60733a4006c9dab2e1df60b4ff75d6844b926c4e 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 6d1272823e56321129de933588947dab1180aa6b 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 0d63689659c45ff52f5d05dbe7358f1fc54eee85 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 9fd74d5f053c8081017e7109e46f606fa1ad84a5 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 9a6332e037265977a9ba8075ede746af0ab32961 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 61dd9fbc74ceb1399473a82d0397e9ad2326703b 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 a56dab0d87d19c507f66bf4b8284aa000b81d334 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 4e4e344d730c546af62462d7af892ba7ad353407 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 0f10c2e800d6c4edc2c24c49432b589bd5d4ecdd 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 d2ab204f6892090a300cdeaea12fd51d22ff6368 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 e0334f431b28a3a773e077d567b84594b11dd925 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 aedca363c093007542941749578caf1badf1674e 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 246c0d1c6e67ed432ddd62e15c1420d73b879ca4 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 e20cc123daf6cb7aa911d3826eb61ab62dcaa9d9 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 b2c215307e175582368a579cca3c37402c77d307 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 4f1f69ca558e9bddf44187a1b74d9e7a279c72ab 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 db40d65ac3025f33b558fadacd68d8dd9e867391 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 976d6ec54b064a9cc0d8b34cc229f4260e17f2bc 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 72c7702ca51a74195b27b73defb112b2b4d67708 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 7191f9b4f69abb50b4207fa9cae16880bb900eb9 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 852c0e837ddf989a5a9f6989b6cbbca74902ba06 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 445057889fe5f3abf431eb7a5695baca77a79031 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 f1dfdac89b396d5c390d64550123a5a1fc54b554 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 951906f5d37932dd1149419d7ad50ac085f47111 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 b83267011047adaa367b6b65d5268ec74456294b 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 9ea6b422a3568aefec377a30c321096fb5604be9 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 1219da203e46b757ca1fb824b264d3ab726df056 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 0817627105dcb6724fcae29488440fc25cb502d9 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 e575b5de199840dfe17247e851ba2ee511565f68 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 d7e75816c8bb8151ad78fc4f870ce61a34925a66 Mon Sep 17 00:00:00 2001 From: Maxime ?pep? Buquet 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 fb8c824b0b403bc2e1f565adc1278734778a7f18 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 5888ebf21b7bc446ced654c57e08708a0446ba2a 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 c579d03884d10aaac7e12497c8d740278fd02fab 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 6b404c1f20cf13a6efca1ece6ba673139615f3ca 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 145567df0e0dd5b22ea73baf605bb97e31262ad2 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 10cb79700fa64bceb43844d7512709a0be59b963 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 ac208e0520994fcab9761057d3c9dded5a439d2d 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 51657ec9ff0fbd652bf1e84f16cd9e0816fa692b 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 9cd4728bfc6626372b1606b92fa984c2119b778a 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 743efb513499a0d2b3577c51f972d4b7a6ae9353 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 bbfc926e6ebc2de781d0f65a31d41960220a2660 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 5bc702719e48c8b44d31f0ee65197357397d6f35 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 f0907daf21057fa3b78d3e2d5dec379407de8c0c 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 . 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 8369b96e16a57ce3b80caf13f5eeb536747e177f 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 5ee42bd47c64aab40311f06c9a70f6a689536655 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 2a4bd1629c9ff0bd2bfce459c09440386061f695 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 f869dc1956e95d6c1f7e8c514177608b1a136ae6 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 a0574119d1e1a2a6ec3d759b2a358b634282280a 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 06c7fc85beca2329da24aa901bb623ea28d9d0f1 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 4ab02db7350394798bc924f8eeb341677d3504aa 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 31ede02c28c4b837a6b9b4bb1e6dcafe51efb217 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 8791fa6eca2b7e44f12e9e716900235c46e664ce 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 21cbe37ca6791b24be7098c5b1ea02171f2579cc 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 cf99479744b47139f6329680d17e01faafb1524b 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 5ea3c0e75847b74ac1aa616d20a5f5886b0e768a 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 dc32ede2ae2e074f6246822f9a05a2ab65685757 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 1a0b7e772f647db324618682695957773f415759 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 24b178651d59bb841090eb3d83f907172c98e0f4 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 469bfe7d6b3753aac14c9a576968391c94668e35 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 cbbcbf6fda93217a647d58ecd6b8b08968338e59 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 7850aa7c109befaeeeaeafe200fe237bb0d57a54 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 490ddd34b7f214a6ee1e35cfc6f5a366940aec31 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 430f8a05a8a258e1007dcd00bd51fd86b615572d 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 37d4260349ab89d91e8a9b35186b3cbf8e77fc61 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 120baded86a77140148563c67db785f2e4e4b6cd 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 a183273abe029f83f6d7104426ee1eec8779d387 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 70f9952d374d1882c1116b48be66930b1d1d75ff 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 85a40d85b6ab3f7675ad9816cee402534afe29a9 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 af596f7e291caa5db92c484ddfa256ec2efa75c1 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 621a28541bb2390e67e7336b13fa3f749d2a5ec9 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 1a397a36222936903b71375d37365f046f586256 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 f684ae8a7c8034942f6373729c4ed39aff600dbe 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 7590aa04d61ecb6d8bb5912b3fb2e7a317cc8826 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 aa602d57688607475e3ba37d73df7f8742ca5cdc 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 72380e22ade1ad646b411933d9311bcc96fed5d4 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 f7a939d9a77537c10c260285cc3b5238a60deace 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 fe0ae623c79dea7d567e7328b287396deed7d429 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 4bce859570bae198340e6a3d4f1e504d9eb0a15c 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 7659dc8a0d58f9cbdd4b85ca40f36179fd3374e7 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 575a2b30949dfdab25d0621b7f4de82652b21b79 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 fdc6bdae111cbcb647687be1cb1c4c977b9196da 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 45fd61ee955a60316d4ed1474971cf6e24e67e29 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 03eafd7093a75be522c4f67597ec40ca1a584f46 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 8703cb40b855b875bcf6ad1ec9ab73a846274168 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 a0a2b4278c9039c7d7e4919b248d98d6ce8b9eab 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 420d792b3769086b92a5b0d1e949a5de926b3a97 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 3a80fa84005eb38cdfb90125ac667d7fda591bdc 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 165db3098de67c7cca33ecbe0e41a9dafc61d056 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 9ae0da672746701c2be437e4cd1716d306ce14a8 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 8902c6ae9e7d10ec62227abb7a3b9bbf2a77f9c8 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 67e6ee1d8365ade2138454d668e7bbbe169290d2 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 ec277db6ab17f16c5cb7b8a38b16d32db111868a 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 cabc1a2443672e086fd6e68ca8ca09f972450c82 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 5e1bd07ad0b604c7cda48f73eead8a60a8be6871 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 eed81413651e464163d2a46c5b1cf585cf1d3816 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 d3b17f865ba1d3b7f5b983f44c4c3f7cc5524c3b 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 354ad54edb6f77c49678e77b6b077d3c7cd5e1c0 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 29d64641c9362fd7a9bc83f5f68dc46a647aebb3 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 d105e33dd52253617720739913cc6fb15bdbb15e 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 96b4d467b9173a8dd3f758ab692f108c28b2f523 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 2177adab441cbe4ea69beb6cb277808a4a13c33a 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 6ebae609caf83908288ac8c920d3c55a78f99214 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 7dbc2e2ac3ae5d56c7b3f7f3b6713e9cb2558ee0 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 e066e530df8af2d8070d2e049e8f3ebf6a8e48e8 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 18ba9560d9511e5bf011c6868a5e77c4c860ed22 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 e06049d395967208a698e475a3c3b95d8654d6c6 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 88560a588b9ec3b38bf8deb7d0eff1745c122152 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 d2639a69153d6c00f39faf48471f7f9bbc34d8a2 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 5015900f911d7c237f5fa04c00e7e821f5575748 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 c313d534b114a35d62bab1021c306d37fa496688 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 37a97e23ac9636177c989f90c97d2708da21ae0a 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 790d4a630edbeb17e0185d10543931afd2912f93 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 047261f9af0594fba81f8f5e3f22e2b58de6da40 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 9ba9680078871914ecf096ed74d996c71ae67494 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 3496abfdacdb40cd401952b7512c1221e771280e 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 8f4840c484393aa84cad6a5d47e6b8bcc2579266 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 571afbf292e67ca92636a6b491ea88fdc1be653d 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 988243a601855d2f91ea6f9d1057b25a14c62275 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 ef60c12bef4c6c461e05355f2fe036aac140ab33 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 39a09669859afad63161cd5f0e42762404733b80 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 95f7ce5e3d4c81517210149170663bf3dadec024 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 e4fe222fb68c3d73fa9045e9d5357a87bef8ded9 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 d8226abd24c7c37e48ccb7fefba816084dbec860 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 3ba24f4366692a0bb2c8df19a034ac37c731428e 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 fdc397e2edee77a3e7e72dc257dc8a0c192ac771 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 f700edb4f0a23ca0327629936da4a25ac034a550 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 57273eb1488cebf258176e16c43b4ced4c9f6469 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 8216017ab5628bd82ac8226c568e492da45ef61a 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 0817fc87bbce5084cdd67276869680128191a916 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 99ae8431de84d208455d6d432140217de3e9ea52 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 b480791da95848d3cd78c71d2ff7c98946bddfca 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 534f64b5c8c2fa62405055ef0098d54dce0de275 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 20226a9804f6ce4e44f1413df1ac54e1f0ebc34c 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 00cf4fcf1414a5b633725f053c3d799b619cb2ed 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 cffb8bea9a82b514aec4ec3b479e055298c3c4bb 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 fc5957ca423511b31830e8a610e218a6364a78e9 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 625b06a9cbe5c52be80b2ab36a2859694a3f10c7 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 34c79b915eb8d623d60b4fe001c893e9a2f1360e 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 5123cae2ff7ce9c0abfb575615779803ae914dde 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 1d46ca36e68e0f686aaafec0890cdaf91279cdb4 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 bf12db8122dd4e36b2e386a91da583253f114101 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 2a10ceae09f3bc7e0978e3a3d57943dccc232e9f 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 ad623682976153a33031aa9c78db936fcea0a491 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 724cc7f24ae27a969305e100cf57926a768fc9da 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 23b03a53ef4085fb704a2fe50e564711b3c3fee8 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 2cd169874e8404f3a4c87855558a7bc7c7709c14 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 c53b66920be43a25f5b5a4c65144bf011407dbf9 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 9ea8b29e99e2f8a037dd04384cac0959f47c9c05 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 028bf41728f26ba7c969395556a6a04c05f5889d 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 39310388b98043fdf6795c126ef1c225c92d32f3 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 86779fa1955cb7c4307ec542cb3a657bb1e2c470 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 b10435f306638c4d99a33a398c62003f27628c0e 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 bcaf97314929c5e06eb526917a6047002be481d8 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 36edc39c5bf5f9d525cec144d12011252c501ab8 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 f8cd8190d57f3a04420c3ecfb0c85e6a1ee02620 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 f6c25cbfee77020698f43865a9e33e74ca71a048 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 e6f328b33cfd381832e89c2ef2b316aefbc03f83 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 c61d243834244ad2f6a274c454eb088936c96d95 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 1153c39ab12715159d897b4389319bcabc5a0d33 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 70a81b5ffdb84e5dc082fee557dc9e3af237bc27 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 39075a9a7e7b980ae42a00e05e077bd228646b4f 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 b3da3a365de86ff541165ccae17531aa72c23e1e 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 c20412ca92ab0506713dcca86b5f28c8fbf93cc7 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 90ac1b271193cfa1fff79ccf199c228b0ac8c156 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 6c4ecdcff0cfe13a522c2019282c6e5e73f13600 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 64e3af4deb3e7fa939275b7defa35e21df9fd683 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 8982f029f8f307915a5373ce9513373070882bec 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 335c5b6e66d7736f209ba75e792e76569dc58d2d 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 178dbdaa18abd86deff518c85c348b3a7e2050a3 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 80a58e551bf3dad52a01401d72d76207b916ebfb 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 3731922cb2e7a9e1bcbcc33a93c91e49baa04c05 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 2e216150e8938ad5df6e5df751db3a48bfb96ced 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 b1d048f1177a4f6b2971510dd83184c6db8bbb38 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 4a9bb8c321af1127e1ff7caa8f05cc97a60c4566 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 5047e42a4dd52576dfd1f509d9ba0f6ef5f3343a 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 5ce52878ab9bbcad78beba75bb0d2fcb5cfb3175 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 0e3cb510adbc4d6f09360e6929c0146b7a5b4378 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 33e92de38717fce322d4c2a6ddd8b5c7be0b1c46 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 1421714628a9f35f20e4154e031f49c70fd29672 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 f1816201f19c73650056e681ade9a3196d9d39c8 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 378673b456a6b0dbf228324d6f030f3cb7ddfebc 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 b9875e5501fe7ab32465e4677a9f35b064be5099 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 7fdb06225efa628956ee38d944bd45d811bac358 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 7954f16d5913ce2383e220c040e36b153ac522e3 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 cb1ec1605974c11ab431aa2537804956f1ecd579 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 1396c094b678d3e8f91ef6ccf2ccb7cf330541c5 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 97b1139a7cb61512f244133b6de8913f40baa382 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 caa5b2ca77854859d943304b5d0f8ebf812fcb18 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 5a377944856a243ed151cc99380fa7513025717d 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 d7d2b03003d4ab9d5842aeba1ecc2fd0f6d66066 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 7801141c088739b6bdf17847910af00b190a8fdb 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 a98badef8ed87aac870ed5e95e0fdc74df340231 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 3dea196a532763136bda9ebae0c3195e8862081f 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 838f903396f4ecd76e00659d1561849b1c30cc67 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 07e3b931813528b905c85fbffa97c4f86cea54f3 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 4560212e9be2eacfc22d8effa6b4e80d04bb5212 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 ebfbcab1a91dce7877d48a606ca4d6b01366d625 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 2de1ed7ca22551ea896afe67283dba4e6aabee0b 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 36483b12ca405c2fab734a2989a63841f99312fb 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 83f9904a4873ee7507a86e446bba1b713f7500c4 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 2d7eada577d9288f749a7831b67e049af3cbee1a 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 af05495fa2591bb9e8f0d41402873cae6497bbaa 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 b1338d27ac7f4ed2c5277f760568ecefb83dfb32 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 e0a077e53b7daa04975397bbd3fdc49b73ef0f50 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 349ac37e3c11f3fa28dde84f9946620c6235930a 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 a577a981eea607479d8271a1cfe24fc2407289ca 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 4a4e62688b9186a75e50da4673ae38c432a29be1 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 d1420d641f150a5dab0fae46288b9e1ed0b9e05e 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 5aee95c57c0ae9d92b681f644bf841641cef93af 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 07645554cc8c98328ae4cae5cc2490197c6ce3f0 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 64944464564d65cc215cac3d234a9458414826a9 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 78a340ccf79daca7cc45ffc79a9bc614db5048c9 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 7f02081af251e0be1ce6006cfe858a79384a286d 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 376d6bf4f3129ea71f9102166ef07a2a59452c8f 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 ae49a4500f5f0d9bb9006da73c784d94881cf9f4 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 a5d6f3717ad968b68b5cdbf4e9ca14d191a933f7 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 190a2a2cab743324b12bebba4efd0c30884d21bd 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 af6462aa3ab0d69b2a90d3c61c73bce7d08e3367 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Sun, 1 Dec 2019 23:34:45 +0100 Subject: util.encodings: Don?t export unneeded symbols 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 52ab88e51e556956b5f9236344481570ef9527f5 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 83a7665a0e5e1b2bee53d7331f012cdfa323f191 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 947c10c689ea46a5ceff8c577bffb88b8371064a 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 129b463d8141be35e4f364d88dc2cdcce1d1f0be 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 ee33732c5fd2bfbf5a700a5aebaf5ba8d75b4110 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 02fcdb63ef83c533cc4aa3559401e7c24e125f2d 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 9c4ff0b7127ca04f826689fd400bd4dc323922d8 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 01aea4e5d2256d359690431214e54e4bdf1e2672 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 0ec577b5dfed46586ff329e531bcc18a4cd5ec00 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 65316d3d5aae5b4554e273c75474dfd704e95d7e 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 ab7c35c8e5fc5f85a60beeaebe44aecad6f82027 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 516309caeca1b0c74be8d4da5c2494a24587daa4 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 d4c21f9ee5157bfd22de4d01cd97ca8ed2286380 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 b7a0a21035b1cf38a1330cba02e1fac2916efa96 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 007f277b1716c5052f3bbf2fba120973440a9f70 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 104eff8b7c85497a8352d9ce3b234a149fa74486 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 8455413a6602a3d86b38eaf13a102836238cc3b0 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 e4837f6d92578afcde7114ecab7c9922d7035f11 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 eb854bf2fe859d7812548cb7a3fe9562827fc59d 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 32a0363453f088e3afbaee722da065bafad6f565 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 8401285913baab3730236e06f48ecb079a5fa16d 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 8669c1bbd464f984a656eb423b5e97de466aeaca 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 1ac8dabcf5cd07b82745e00f91b161e03bf120a4 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 317cc2862df483f2234b41a13214aae22458020f 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 ee3a103480d2af2dabf83174cd64e6917df5972d 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 821603db8aaadd6a068a619feb3ac5e7c2c6e099 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 a34238b2a26a3f4033fd732bc63f0e2d831a14a6 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 65c7619a6d4b1c28cbb694ce2afcbee70d7cc9f6 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 5f7845641e8664aa63f31b1eb38049e8f6f396cb 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 9eea55bee824ffe812459eb7a3cf7ab26462f3dd 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 b3ff5eec9c901dcb005c1c1636b3533a1a278c09 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 9d389440a337d18a730621ec15e0770406ecb1c0 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 a72969c9c9bf83baba1989470edab803b08160ad 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 d1b1a7fa33855e7d46aa2361eeb2851518f4448e 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 3a14bfec85e586c156b60bdb2907acca3815bed9 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 2de609cb9b976f9b2fb59c4680d86abd1e0ff091 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 d30c43acf0696207085ef1f374a832782ddac810 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 646e0247d39cb8f4c64c60a7dc424a5f1f6e9150 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 842657970f1a69585ca8727e6a0c4828f9ee503b 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 66675a416af8ef45b26355debe2c4b7cb15deed7 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 da8be04d4f0ce33d17a40e9f65cce0ef45dbe086 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 996e2640d0989ea962d2c0b2cad8898375cf9eed 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 802fa58a2981bb53bad384092c8dbb11b6d3a98c 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 392f6fec79929be9bd303543649872baaafda756 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 d36536327142ecfe77b00fa929f383bf932270ae 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 7da0e322ed6969444703a976ae01fabdadfb2794 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 be507b93838619f24a0b5cc89463513bc2238932 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 e93c2bc0ece20334952feaa90124895959dc61cc 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 5c0fcb510634d09bbd1b583c92d3414c8c0b3026 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 79df4b5cc14a57ff8d9b50b5eb101c2c994aa692 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 b4fa48b3c737faeab14b5a16a076085181a779c1 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 c29efd2a381697c1e2deb51087cee2c9b3dd18fd 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 bc442f7e678140010d2bb872367e1103fe6e3ffe 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 8b1b7204f585763df1d7612f6af618fd293fe775 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 f244d20c58d152a20e45701a58ec56ac9f3e89f7 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 b3acb8633819fc55b12c737a0b1adc532e4dc323 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 7ee9d3b7af9862e08fa94841f19afaf2c92f0681 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 e9cb34f310d4998077882c3ffd3d22bef4532d7c 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 d96ac3886720ffb683b153c529f6f192c2cc34da 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 4da419b6282e9e57aebabf5db0e3f62731657530 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 eb54e65e689ba596a562f34fa5ee3e24a1eb5ec9 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 6c9a8d13a0a0e7465a969da62f932c4071b3cb3c 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 3433599c776d3ed1febe568aaf6c2faa70f39803 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 ae314b7e5107f700d41d0faff61af0ffbeb278a0 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 7f50de114c80d8ea3723381fd36b819ce3bf3aa7 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 5aeaa9dc903d109bd2b71eb826f52d5e3ab7d54b 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 502b99f2de61dd3b901f3704ba8338a80b73cdcb 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 45b0ace82a72a06e5d558001e5352b6b9a2d5c41 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 64ccf28f856ee689571855af9594d4f419ef9511 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 0e1961a7c505cade31afd6a18c81c82c76493d6e 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 f35ffff6ae2bbee4d18ae932d2df177d289ba633 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 83f3a630e4668abb4d856c7d609f68fa47675e50 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 cf87960bda697b3727b5b62ab8c983a48b8c5314 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 dec1bce0debb65759b94a73783b86354538c6f59 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 e09ce77c8d099e9cbb308990e68ad44859c5e62a 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 021483a5cf67080a064f4ed47615e3f70f671ce0 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 1cfa9cb2f5565b2ff8196068b5a433d889b38cf0 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 8f217cae73c46075eac6947ec6d71a56d555f790 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 103c09e0c42be4c4fe3440955e4eaad3f5d058c5 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 a24af5103282cfb37705574d4bb5ddb393986b02 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 ab5b83d85a2db08555866a2de559919208d29946 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 7a17e5b8cad5c3cbb3737bb685bff079c1106778 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 abafbf8d459cc8cbeee59f94df952d25ea9ffe3f 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 d30f224c1cde9833d449ba98bea1d1526901d6e6 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 f17b76ce487a90598c67f1e43e451d45f3e3a946 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 949e78e2e18325729f7a60c688da853e89b43b8a 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 ee39bc87efa74ad806e1478b7f6a149c7c337c09 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 08d280a145bba8f7f13c99878dd38070b1bf5bab 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 da1edea68ef8fc9eb3b5483f1ce97e9586936ba6 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 21e4b6cb9f41b478583c71a810399efa7e3f5267 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 f4013af558b8e4489f66cf05279329c64289b2ef 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 6aca72acd8752ba17d1f5e1b3740c8fed22e81da 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 4d0edf5ebdfe6f8dfd815526145e23b212449505 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 dc65ddc4a43daabb97ee1f2af7dedac7c5406688 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 23ded0507b5f4fa41a1ca46e84cc676840eb6ed9 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 4fe8de2434041a7105fe0a10c3f1b408f864cba2 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 6ab4b844637e4a558acca967b601cdbb909d5f99 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 5d9270502bcf1309d9718cb55873292296eebf70 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 1b8229790ea4d2dd66fae2662406089a94060bbc 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 f507f3bb8b78a112f60aa4c429f488c3543cd10e 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 0e57190857be0c6d97b17321d62a7b18242b1288 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 8cc9b4d6bd3586d50ebd53508ef8b4b5bbe26e77 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 7a7f98f364cea699b2c12b86db6c8dff0ffe18f1 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 ecb2a069af5f1f96583b7b1c0ec88e2bf30d3980 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 3e689c6c2d594a79c44fc2bc25ed13c3899237dc 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 6d67164438abe237f394d88fbd4d38204aec78e8 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 c139f46ec0357c09c8fb7cd458ec16c53d8c751c 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 56290868a1893dc36aa486b770fea42d50b4bcbc 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 26e85f57988bac46d6842e4564485ab021248373 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 64bf88696a48abdf65c7ffd64d2e57f8e524b4ec 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 10715ccb2868f0c902ce6813afaa523ef570ad94 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 458f098b1cf36aaf8aa69940eb35474573743847 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 15b310c4e2cca25059b1ac274e29a96a957400ba 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 e4fad868df95526bb0637457559fca9ef80426a6 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 67382277df7bbd9b286c1cdd477b15b2cfb5455d 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 43a5cfe0bc34a0dee2cfe255a0f0f909204b45b7 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 61789d01d9b260ee0201fe27f6fb71fc7507971e 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 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 cce028c97a467f2d96f40ed147bf9975cec19249 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 9066d99481cf340ea7ff31db125e1bed3c4882fa 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 5fadc67e4ffad49d7823d193597a21b32543c6c7 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 84c6ebd007ae88cd4149590f98121b39cc3dfcf1 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 dae152e58f50aa0fc7940514bb363068207fcad1 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 6839acc859d00f7cb4dfd91507fa8a0ab9e11355 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 6e9828ec0333eae14e05bea9e441b69a9398ebbb 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 e4c3f5d3adef809c3e9c53ec8acb663bc88ac633 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 8cd0bc491f3c4fca739384c6b107f57e8ddc76ef 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 99e46313aa83db68f265aa4e5c07bbca4f1a60ed 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 2327c841256b8ccd83c9ef35b7a1151b46366b34 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 9a4fe983315802ff15adea3d481718a0e4180be0 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 71d08e8cab4b9b981c8655927495000aa81c5b4f 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 62e66fe940cd820119b90ad40fba347894ff7d52 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 5fe826e31796743c7ca1042a2f5301880189d3e1 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 1b136bfe7d3670267f62af9d77a8ae860066b608 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 ac7d71241f20e2a598dacf7f163e9757e59bbdc3 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 6dc942c5cecda6b09caf062289a0b9dbfeeee3e0 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 d7348a322667f5c60d0de1e425dd4cf13c203766 Mon Sep 17 00:00:00 2001 From: Maxime ?pep? Buquet 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 cd774239f73bb1eedb9a7acb872a83dbb6db122c 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 1c68c0e36cb05b880857246cd0fc851384ab6520 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 7137e467a0a251a18dbade4d32b4222dae475231 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 514311f1eb120c63d8495ee97555033a147269e3 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 9a55257c45e8a0f6bb9403cfa39f19e34cf07434 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 d9782e12a3a1915a0f5ee6508194dca204530b7f 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 62fda5c1f4ea55ead3701bd7054b0ea62f32a9ba 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 d56fa1f1c1a325bb86084ab71fdd3a77d6db3a61 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 d669797472629e4876d295a67f76ac045ddb6d30 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 e15b284dc80c208bdec60a2bb2ad8939822418fc 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 335ce1bf2ad08c54bc0ad41a535c0a7f8b60b4b1 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 f2ac7bdb8053c3cc984eabcd077583e0ae8c0da2 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 ed68050d3304f55cf5916ac228e28f8432968f3e 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 656fe85b7aeddd0650e627e16c1eccc275cb12f1 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 2692479e43cc1fd47c1bbed3612afe8c459dde51 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 ece417a7c93bf8a977c452cf78559a9338e8aa04 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 28b69c5a62a7fcd01f79a7f48f3bc820b7c7c829 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 b8237a43b484db897558f18570b74ee240d30e14 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 c5d7245be50a0c172bfa7f7bf352b177f9e8e28a 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 8d12bd04f7877e15bea58c660cf3d195c826f401 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 9fcef682cb2abf517a2a396e1b9a33b519b9a71b 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 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 aa899ea68f6ec56df5ccd1500592aa2b9e87852d 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 0063c2f4c188d3aad8e0f74e17fd13a802ce4389 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 1f8e8154a527d83c72d50d459bff26c2ddba6c65 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 b9c78541d727cfef9d016ff6c471bb46830280e2 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 3c3e1f02003c27f1c383d03443c89432b9a01a1d 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 ad142872d43a94c419cffe4f8dbc37a19cd34d33 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 d4c500cb06a9f7669cd33f86c8ea1753c74fe514 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 c2744b2c1122b04149fe4af8f008ccf4f1f3cb34 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 0bdf52fd9026c4f1925377f4f79012b401ccf562 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 a68413e51c2918db3bce9f3bcb3aa8fdd608ef73 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 e3e10773c1d2c5014c26c78ea079e3b4e7b3753b 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 da9dc387a4e619d0cd9f04dda65e161683459339 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 0d1dfac4ea5f17c3efbd338067f38c06c4c08fef 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 b4bd0fc355b30083da874b2be0be5ee1e783b908 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 4120763d20fb8f2f19f7b9c2c789f34dc71c59ae 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 69efeba4ec4ae3e85c942761e824c23d40410891 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 d341deca9e5855e0bf94ff02fd64515a80bd63dc 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 61dfe526dc6faa3e1121961f7729d4cd074b5408 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 662a8fda881ecb08c017a0241c145dcd37f3ee0a 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 7040fbb812f524845c9164732ec4f0957e0318d0 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 24cf05c4ae1f6fa1c125c220a7b6f3c438db9073 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 3e737edb975e4baef8a6aec50fbd6e4cf8dda340 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 87b22c2165d441e68491fa46a9be6b272f16f98d 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 d2231200edaa9ad8b8b9d5374cd55b8b6b4e87a9 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 2b1538e30e869b3624139427a895317c6fea995c 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 ee80be9fa402a8c8656a56a2028101890c97a1d2 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 325833753210cd7bd87f997096237e53624f9726 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 378bb147c81634128bf0918f4ede3aa48cac8b45 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 9549657e06f1d565bdc11808eb10da8707b91c40 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 5c12379ed9273e2be201476763ef51af96b43fff 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 baef0f02e863f093af83003598949c94e9a82218 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 1056f4bb820479c00c4123f5fea818f45c47f98c 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 3dcfe11e54b8c4f936b701f9e57843fce39500f9 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 230ceb086948cd12c258ca78cfbf8a345904fd8e 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 ef0b90ff2393da021d6ac0f101aaba533a4825d9 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 62b575639d000dcf172caa1cfd089ba12674c6dc 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 ead815ece336974e216141f5549c4ea8083b0a63 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 bb2a3804d1db20f02ae445991a3d92a596431c41 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 4dbf52830e170867320e83f742d69803651871cb 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 de0caa52012ef89b63a090c6a06e5288efd3ff84 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 91e76bc97e5dfad413507e93f9a66023a368c3ff 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 d146b0d68af64e2dc0ba49e29301b2433dae02bf 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 95906a0ef4699b745fd5dfbef3729c8041b0e2b4 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 f4ef7e458730d5913fe6760892f713657b8be007 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 9644332df92d2c2d589744377cc685a405213143 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 7c3e03b3bbb8494882738f83459266579df97c72 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 462dd30f7bd88421d884ff0ab3cb83f0887fc115 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 7de53c6cd6514dc92b5dc4148a63504628e3b5fa 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 d0b6d2010c6badde88a1493ea67fc19d94ccff4c 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 bd9c89039785925ef833c2081150adaf9acbdb94 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 e39789a5fff23500a7ff393ea67d7bf80175d9be 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 40788f772440e610aa50936e330f5a067ae6fd58 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 77efec64cd4ea8e5ac11492e355610d56f1a7cf8 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 ed34a5f4764715ddd4362f40d856ece4ec1f17f2 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 7e72aa8bc59a93d88e3b87cbe3ddb61933b0b215 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 fcb1c5ba2c0c6d75f0ce758d7a01105e92634aba 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 785473050ad6d7cb841b1b53d49b985fdd8025dd 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 ba0fe2779ae0826e8cb5a3f31e319d7f30e9f8e5 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 4fe295898ae6c43553914ab14ae3148852e22aa8 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 99d495680bbc684dbca519f94d069c1dd92e223c 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 f5761f46f2c4ee391d883b7b5c70de04065e514b 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 3f78f7638d37ea987beea1f06789582a34ef71d5 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 2853b3ac5db9ecefacbfd575924c8ba663b7e7c2 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 42cbd7910fbc6d242aa4cae1ca7c161d87a30d11 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 db10f4fe1707d3b2610a5067a739108ad037adb7 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 5694b402f2109b2cec6a40195467e77de7e51536 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 dfbdd09b1ec53446aedc4c4e4e5d096bc6a0181c 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 5223386e0e8891163c040b7d4f1447678c3effd7 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 95f4c6a7496cdf4033985ca6dc539ee3e08874a2 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 edbf00f460c104b5b99730abd8bb05288c72d810 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 66f554d5aaeb8278b9a626b6201a1d3cc51d2919 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 3ac134a9e3310902ea09adbe726ae73916fd2af8 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 ed209d1f70a8e8502c3724dfd3318e9178f71e38 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 5efc9d8b9c3e7e8e154968917f37fda02e6555ee 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 7612c4703106d404d6cfd23a27b5a7c5e932f2cf 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 0c677709af08ba2c1c86648ccc8c1dcb8f759e27 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 a06578f4fb1429a7f57e8ee3dc366fb5d89fef39 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 b044b8b0a13da161f885a9409ce543be43344bda 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 5d387ccb3a43a2d027b2eba081c471486266fece 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 4500aabbae173b93a9725de59acbd4f1b56696bb 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 9e60a1be7cc7882c59595198a8ef3b07de3f8d4f 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 d88904312b8f7a2582ce1eb0ee9204fdc2912038 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 ebf52208e4105478eebcac3440ceaf58f6630cff 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 405ba6df12d248331d74dfb8ec93bc1321b9056a 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 39aa6b37284d7c45a38001e6076e536b67ca044c 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 173c10f2328323de9f88e53bfdd8bed36f597105 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 85dbafb31a5f2cf83846edbbbeefbf0e3bb52108 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 0de207dbbd153fe2d254c2b33a3417cd3d4a3ace 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 5593dc204fa3637aadc1e97bef7b17d4dc3c696b 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 b9684574dbfa0ef1f256c8bacd58579907223adc 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 593c693e220d3f6e5a2eb622200352cc7d4976cb 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 0f201fd4b4229836ec7824112ba3962e907707a0 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 1150c8ab78f1b261ca39a08550389ac101fc3247 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 d0581ffa473383a593d47919a527af3c235efd21 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 a27006ab2af6ad8935ccb9fcd33cb3493254ff8e 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 126834ffca7f96ac14010b589af648e978fc1fd7 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 6c4f5f47fc233e1e8e97250be1eb44a3ee312688 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 a3e494189587109b074ce5cabb906613dc188514 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 9bd6d1a47a26f62a663a0910d7ec82f402e8dbb2 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 03b29a2a10fd8aa4712ad904b8b8999df250d9c3 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 dfee86b33890adb9f82afcb04298495ec26c8eb3 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 35c44c74ab5125515107c5e54bbdeae7ec2e10bc 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 d182923d72aac40aaa95bee9e3dac7b3c63d6fc8 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 6f60cb0639b8cdcaaf0f160f7c7f65ee567cf95d 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 efcd07cf8b1aa3bdd1906e11b36c3a5a1b8f47bf 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 926003db59ab42490ca4f787c5755a68ca439ed2 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 de96139b6c5eabdb6ce053f38cae5397cf999df2 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 931adcf40c7d5afdd193cddcd565750fd384eff1 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 3e50ef4da605e3203830a56bb7302ec80aa865d4 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 aba0d80e94e48aba23f0c1de4137489fc7485bdb 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 d51e32b8d35443278d56a106e350f6f12458cd1f 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 05e276504d45f73d08da05ef46fd75511a123b42 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 c3058505713cfbad76812f7797d52cc32e626fea 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 b9bb7cca20ac53685a13fd096a74abd1ae5eb3d3 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 7b3b4efb797c438065ee52aea2ac67a66f5e22cf 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 c9498a4d00f68a4cbb282de99124a4d0922f30e3 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 1e270c57c7d024b9fd313c7e9b66ead56a23d775 Mon Sep 17 00:00:00 2001 From: Jonas Sch?fer 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 21af26d1a109f4e596d69df52aecc1b5755bbf94 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 35836fec719c62b65ba73039552180c3085e0e80 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 9c0f2a5d5a4a52c8e9cd7c84654243f87a9451ad 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 9aac893a7a1ae0bc0ed2b67b278149bae0316b18 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 4a5187553b785695bde16f6df587471ca10a46a8 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 0c1ed35de130c079f26a97ca56e8baf8662cbd7b 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 c79e73ffbe417e73c9d40d15337fb74e505b8939 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 1ee999ce17640604b48513d23cb5da233c16807a 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 8f50f4acfab2bdc8bb38fd684918734eee07c191 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 1bf7e0fcf72bfbc5b1544d08dff1d520284fce7d 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 a353d19e23d440036324c9ecb8058fbaf7a11b9c 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 66d8ec5545aa0e8c2831a583ccbe07a02db13d0f 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 26d3ffdd4a4a8752609ffdb6f1670e0c603db42c 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 319ba8b1e0bf6bfb40613a39df31d8806b2ced10 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 30d735e74848a034b75168b5675c1c3080b1921e 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 f9176ca0e930cd142df047178f9e585b56275ddf 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 cdeaa5882177f00e6eb3262c7428486874faab28 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 4027f638127cb54cd93675bdc2945a28e9a2adfd 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 7eadd412eb46fd12eb2fcc81064f205c49beca83 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 ac8a1c76f416d474451807a9231c9bdd9d95e2e8 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 3a00266c140b1d829d1cc3ce11dbff2c706d7c0b 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 b2aa9773f9a8ee1445ca33c8e61ebfd46b4db2d3 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 d30363f5ef370125946505973da9b7f97d6ecec3 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 c2ec680ca72326ac47dca077a2a68157e05393a3 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 fef43e354399548ed548e992d18f17515d7bd087 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 360dac00c284c855dd251e709a419dac62d6a214 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 431e3bcc54c79dc521786cbce5a16116d5b25eb8 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 dc9ce16d648c4cb2c953f871aaad84474bf380fb 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 225aaa81ca334414c7736050384897578796b9f3 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 cf230c2b431ba23d19a08b69cff08948531dfc29 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 c3764ecf3ee523b4fb88805b17e84fc1cfed22a8 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 23cef5c6c53809c5be09581f1bfdab10f5af6bb8 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 5c3b43f01416ba31fa3f81b87c82e046e165a8e8 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 50b12ee71606356d944691772859bf629fa1f9fb 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 c382e09470c10a66dc58a40e7367f28c5073f71f 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 5a58b068e1a87a47e1e668f3f7be69b9d7aab3bb 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 e3bb9434f10d99486340476f7b4082d0c54834be 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 e2670273d4c7388365428dbdf6f74e16ceba90cc 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 df375c133cbb6ce822629d4826354158d9ad5d71 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 eb1fedd983fc83e13d6f798fb34324d9218b988e 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 7a662dd66d0210a3a4290f6c0d2be3c8130c3495 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 bc56346dde1199160f7c6591a9477cf31fd8c675 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 291523df2861a29a83065b0a07d63d4a2fdac8e9 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 30b218c7fe878f81e43fbae1a035ecde14f935b9 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 7c630e91836aba13141dee803d07739a1a2d76b7 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 3a3d0bad1c2dfe2437488c1dfa12ca98f9a1b5b5 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 bed3607b09feff29c48bc543a5ff5d93deffda5f 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 715aaf3e80a330a4c74f495f5659faa4b7c5c540 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 59b96c51b0037eeaf02da285cb66b6910e5efe49 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 43c83b93420116f1ed88981268bbe8e7919dd27e 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 7c813b45d875f59e43a357736ac6b3716b112ed8 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 f9afa34c93a18fad6fd667e176cd5d1fe6c359c0 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 6950744de9817d0d29897d72605656807853588d 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 506a747629dcbeb61be9735a86994d70c1379e40 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 4c70417684d3617a83aacc8cae16078e354ad87b 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 73efa2f2f174f699ddb4a56320dc6fbb681e5be1 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 05bfe971bf9839b1b2319b60dac65ef299ef07ef 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 88833e9c01a27fa19f01ed73009e7c82575f3b11 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 2155ef819e0e9cd1d43ea52c6f4f76ff921a3ff7 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 f86aaa431f2135df07ea543060fb9457b5654639 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 15b298c191115c9acdba05d9bd2cf8d3d64f0468 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 3a849a580d3fabdfb22c7da60710d3ec8ec75e42 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 5bc24e6161f41e1428fd1cc3af25fda2d80781f7 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 24c071f9a17c2b5252ba2854b0f958669f822bd1 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 6bcf492c34189308db20ff64e344f823e341aa77 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 d2f09cb37f8cadb2a6de205b93a7e9c80d29c5d2 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 7dacb1176e6c042ea407b9973dfe9a7ecd32bda0 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 f5a1f9a5559a25a5d7d47991d1619f2a1d617077 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 72f890e6914a50ff3efd9e51395453fcf1a8857c 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 90ae8d8e1d3fc0ba58c24501b0a39b2c999233a0 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 cf640c8ded8615a2721208df1274b1eeee5a2b42 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 3405f0dfe777ba876d50e75611826eae32f0d315 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 e57ef01055be671f49f57808a0e3f2469f83ab3b 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 733df3d5d71cadace023cf5a2b0ef13ecb83650f 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 c7f339117d74a9a57268c44d0b20354e674df0c5 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 fdf2f0e9f4775809f72c6c283038f2e48a50e081 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 68146065e1208146138f3d51115f5a6b6172d27d 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 694bdc972b3ec75320e14a26a2b34d7e3d498c8c 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 c96212e146b390739d411e5f7ba87429468a8462 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 6a126c350f01160c7e4b03109b5e3a3f67d5d5bb 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 ec6a129d4d810bf701549d5147665b0f2aa4d902 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 6b9051637f99f8c03be6864871be169fbfe5c537 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 2861bb873a547ef5c1655fa3fa72c98a2af7411f 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 cdfa305a67507d6e7f36a943c2bb599359a54059 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 e40ea08629de93e021547cba05c36eaf55945c01 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 b56771c8d1ac59f875211c8a56d721fbeaf6d66e 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 257c1c47b2bde0a71247a38d0661ba752bc72696 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 f7167c53dd46baa8dcc69cb8e7c404e70e28b367 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 4e5cc92ac3f293b658838e0faa55645b705e3b48 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 d8ec3f98bef461a1e04a1c208aef4e18d537f9f9 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 5dc0e3901776eb546f69536bd0ca80a8715a84df 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 1c8c4a4b60ed1118629d1004ec1e6639ed1108b6 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 2a5e57cf2775989d97c797e18bf5eef511a8353a 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 72c9c5dae88617d7d08f3b3122a06a3239ba419b 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 bcdd51e2d3cea6b2d43ce82f36ec0a77f6ccee0f 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 902ff03af0e7118696f07e07c410df3d3157020a 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 84673d1d4ede9f69e6210438a198456fe010366c 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 e2a48d60bba1c3444d2e6d8186dc7f90d2b0e416 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 07bb3974e2ae0558fb45712494ebddc9cae9675d 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 512f97b32f8de0878f9a97a385f7105cd7fce5b7 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 dff6f428be4187be94f3c48de426473d4c32952b 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 e7b78ffb178d0246619988278d273360d5299ff1 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 16ed6fec76c8664c312ac730be47875561f071db 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 99a1b1f69f7868918c64a166e4ac548e3a331d61 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 ac1cff82ae22826868ba6817879bdb01da5d393d 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 7c12d9f38cdc342655af505e18ab98b90411e0a5 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 5c1b8d64ebbf57478b41994e13517afe63dadb11 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 3cbe9b03daa9fddabc3c46655a1ca105a7c326c5 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 11e21424bbd2e83b3e675277f4c07373925c85df 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 cb6df1369856f800fa0a572e8561f72ab0e5b05a 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 966a8bd7943dcdf23ae7d9339cd7a3ab74137269 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 3b7348fb55baa9c30baf76cef28395b9c8ccc160 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 639e6eb3e0d1d1370fa41f67cebccf4adeb0701f 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 740bb5ee8dd557eb16c473a12605c52d49a65166 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 d3fa685cd3ecd274fbaf98b47fab830d84111cd6 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 ca749721bfc69cdf20f0d217992853cf43e1f318 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 1773caa0a30634d92328d4a893c5a2837dbac966 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 0d7e7dd525bcf115a32f1a9465e3420c00239019 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 932870e8cc35a7db32985ed322bb06db26869374 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 70ab78d9b13e1bc8f2b35ed718091b1c05ce206c 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 8ad24d50cb6283dcc9f89b2418b9b5f518af14b1 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 310ca3f76c608944c62031c0e9cf15ddaeae3f7c 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 99974464b36197231849c886ffa8f936c6c71c89 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 c1033eae3e20a5d6e836c306eabc0632369276db 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 cebf5ee0e3a462c3d5b80d213ed9b0f0bc15c09a 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 d38b9625a3bdbcc22ced600cb763581e89681902 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 e39e20f7f7f1a8554032f060b5dcbc6554e10804 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 d8efd81254826f38e3ff47287eb2833cd8121a5d 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 871fe5d529ef1c5948baa4616a3c83c4ae013f28 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 0c848042bf5dc17287d8393ac058c5a634339dc0 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 57f0dea301e0d690770c155f50cf0746ebc693cc 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 55eb4ad93fe0cf0103efdfe5c9e77004c00ecd15 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 5bafaaa5d711a6e7995f23e931c7b26f6f1b28c3 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 dc5f00e62f93566f9f74c6985855c2fdf9ecfd11 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 f05d6e2222f58941834d3f2203e66352cea363e1 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 0bd28a3c0f5a48d874bb603b0e7fe7c58a0c7b1b 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 d8d6efa3ab002ee94341de79d0946f218f6fb61e 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 cca6ca589f9ac470e704e979b252839a3b121869 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 7d8b2969cc5d61df047c7a6a1e9eb115cec40480 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 72129eca1ca71a0e534ec889a7dec14a50885870 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 6276ba3a85c226cfbaa12020f9224cc2d28744d5 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 c248dbebd67a205debbce46027a3259b90c91966 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 dd63e1dfcaae4bcb38253ebdb4480264a7e904b4 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 b42441f0769f12ec57925ca1e4bd49779a5be409 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 a916b3360781c948c8e96462b4eaf0ab50040e8b 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 27ad6f88c55f968b551e31e7c3051a29c58a75fc 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 63b691caec0d4df348d1b884d745a22b17b5d72a 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 1b0079854e9cbb15fe5763c2e110b14e46d3efd8 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 a4914feb84f2a13c0884f9e3f89cf8e636d87980 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 f96ca297e3c83226a56b09337cc37528a2320c8a 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 e85395014d43753c221c16f22a372e405628faf0 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 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 57c5c4ddbef78aab3aa06a308eb4f629aecdb974 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 9cd5484770d4b4576e2eaa6997962839bbc76760 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 c64ae5fb65c3020fe832b7a972c6155a71b03433 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 5b9261dab6f415d771cede866dd32b23612edf42 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 1ed0b6ce1271c9b67042860b6fca0dad96ddf21c 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 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 2d4006b1c87861ed8f2442c214ad9d8c96f7b771 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 55a5af04f19344a497dcf17e3a762e9b7da72d29 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 eefe8a76880872993d0f65f600830b91caabbe23 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 30cf0db09c12d17be72ad55fe8820e9fd7cf7e68 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 aa584a52866a38894a80def3da90990cc2f48134 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 a725ff6218040cf93b85fb634e6833f7e0e4fe52 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 120c93c789426cb5fc130f209798232afda5360f 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 5b16babafbc5f17f8534b71727ee6c0aa2198948 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 d0835ef1cf1eb4bff19a9444c510ffa5574259c6 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 41f1cb95d3d18a0203edf3bb2f75458d42d56629 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 bacba33b8ab5940d8cea08e383fb42db995e1cd0 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 974d6a467954a69bae4ba9e73ae6c8c7dee12965 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 8fa236e66f6da9664a2083b47d89eb712327f857 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 efd66980fa02230674e775f7bdef8b4177f57dbb 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 348becb5694f1ecc71b8be11db0a1f6092c90891 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 2bee026da736b4dba4a032bc769a5b487f71242b 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 923994105c47cf4d8a9df88d4afb5e7a1d0354eb 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 117b53efe5cf5a9b2c2f2da3abb0bcda0cc9aa05 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 dfe8d1428412e32ab7f275adc1a6f458952e8596 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 5c4fb96e17c1d0ab96f565344799b0fb85a5e35b 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 ba1834dd49c1a42e64f1b72a60a3570c8279afad 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 651d90a762573d8f2583d34d261e2ecab3bc7183 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 fcfc09d01b44dfc731d04ac87a2fd3bb010a5658 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 b6e1f9feb9e17f0c430683318b475aa806058955 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 2238804d7aece8e7a64e03f822b5106f932ea683 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 7c21a741466febdb5a9731531f89398c4994f27b 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 526412d6b80bb24d43ccf537f02fa1d9450f7317 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 d8b0ab9cd04e6198e52f8b0182e3cef09eb82a12 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 14826181f338168d41d3e85bce80f5132a9532ba 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 80616c004c1952471126f68e57ee61a0e740055b 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 f8c7c8fd53c24a8b9e6e487c12ce4da8673cb1a0 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 7f4e1073cd28ad524ee7d1b4d414460da0641aaf 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 5134e640373428d5e73172b110a42b3d191471fd 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 2a9cdf2538d3fc11685f65e2d137f5e10e511f8d 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 7e563605c92e6b7ee609c33b622a1ca86b6956c4 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 3533f8e1b2e2f12ff1a49fdf2da91a9f50e9bf6c 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 f7cdeb54fdc288dd548f0ab2020a2440c0332f97 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 ace2ee1b3b34a9e122367abe945298c4a7e6865f 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 52f18d29556770aec795c40f7032a013d125946a 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 86d220234afd850254f3303d793709db27f3b58e 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 5392d207d4b1a0c07245ebdc0ba749edfe5762bb 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 de7565e382abe025e4987a4b77c78da794b57226 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 97fc84beed9a3187537b9c8f3d68c7ea34fa82ac 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 c00bd4515da975d5db8004b2b92809e04aeaec32 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 86acb2276c938f80b66799284ced24589576d6c0 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 e6866e4df85c88862923347b818ef417b4dd36c5 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 f8a0c8422e2d50fa17499bcba298a9be6ca25dd4 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 7cafc6927809105e4e295e35c909d1e01e8b51cb 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 b55922a55a4d53d1106c6c2d2da6705a23abe63a 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 93500d02d77f1541cbe952bf3b89f2e73f2ad031 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 4f6f98131e29535594b8e53f97d2f9a8914ad05d 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 7411b0a73e941b409a59d288db347efa31fc9542 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 bcd629fbc17aab71e0ab7ce04a931626ff5b9df0 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 3e3ce993e6b3573f96ebbadae405ba753c4a5c8f 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 cfb933ba988b70d98c8694be2beb7c346e93f947 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 05a3d574dce37d28621bfd35703b2a2283cfd1bd 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 27273548aec77ac761a23426a31a089283506750 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 10f82d332163c8a3572ee507e32611d56e930ca4 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 40800ea93bb1dca78c3548de58a09f424f5eef30 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 a2ec5e4e52adc58ef83d5e20f268f2a1005b13ac 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 bdee3ae38c52eb1237dd05bc4237cf15689dba69 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 6674d10a2294a7f767fcdd0fbffe01060d522b32 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 aba41217c99b08a2c64b2177a33c03fa73d0f29e 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 8f053aedc2be00ba51477dca694d0ff6eb928185 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 049c2437d825dca3f97cc441803986e69d04aed5 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 534435a9d00e7389067ec6654a9afc3dc5c187ed 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 b93e90ebb3df8770e783318443baa3bafd9652d2 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 76fbdfbfddc32302884ef33949a82fca17e98479 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 5407d52f84ccb1fc6dd1b04deb659d412ebb6aa8 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 d73381f60e54b118517e385f7b2eb3d3e86412e5 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 6ff7dfc843b14e3850c8390eaaf0a0eb5ba24d5d 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 8d1e8e9ad951787a257ef93dce4a02799280f62a 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 9d5d74ea0658d5d6ce7ea4360167c58eaa083a1f 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 e04db26e8fa4d0af71d79b6dbe2192917d38d379 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 315d9ef8d9f43f0cc7929f98a6769f0567048e72 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 02b56f2304d65945480ecef156c5cf5c60b5ca9f 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