Changeset

12362:0fd58f54d653

Merge config-updates+check-turn from timber
author Matthew Wild <mwild1@gmail.com>
date Fri, 04 Mar 2022 16:33:41 +0000
parents 12320:f0be98bab9dd (diff) 12361:713f8ee9e1b4 (current diff)
children 12363:0576d7d625a0
files CHANGES GNUmakefile core/certmanager.lua plugins/mod_s2s.lua util/prosodyctl/check.lua
diffstat 13 files changed, 351 insertions(+), 52 deletions(-) [+]
line wrap: on
line diff
--- a/CHANGES	Fri Mar 04 16:13:05 2022 +0000
+++ b/CHANGES	Fri Mar 04 16:33:41 2022 +0000
@@ -58,6 +58,7 @@
 -   MUC: support for XEP-0421 occupant identifiers
 -   `prosodyctl check connectivity` via observe.jabber.network
 -   libunbound for DNS queries
+-   The POSIX poll() API used by server_epoll on \*nix other than Linux
 
 ## Changes
 
--- a/GNUmakefile	Fri Mar 04 16:13:05 2022 +0000
+++ b/GNUmakefile	Fri Mar 04 16:33:41 2022 +0000
@@ -31,23 +31,36 @@
 	-$(MAKE) -C certs localhost.crt example.com.crt
 endif
 
-CMODULES=util/encodings.so util/encodings.so util/pposix.so util/signal.so util/struct.so
+install-etc: prosody.cfg.lua.install
+	$(MKDIR) $(CONFIG)
+	$(MKDIR) $(CONFIG)/certs
+	$(INSTALL_DATA) certs/* $(CONFIG)/certs
+	test -f $(CONFIG)/prosody.cfg.lua || $(INSTALL_DATA) prosody.cfg.lua.install $(CONFIG)/prosody.cfg.lua
 
-install: prosody.install prosodyctl.install prosody.cfg.lua.install $(CMODULES)
-	$(MKDIR) $(BIN) $(CONFIG) $(MODULES) $(SOURCE)
-	$(MKDIR_PRIVATE) $(DATA)
-	$(MKDIR) $(MAN)/man1
-	$(MKDIR) $(CONFIG)/certs
-	$(MKDIR) $(SOURCE)/core $(SOURCE)/net $(SOURCE)/util
+install-bin: prosody.install prosodyctl.install
+	$(MKDIR) $(BIN)
 	$(INSTALL_EXEC) ./prosody.install $(BIN)/prosody
 	$(INSTALL_EXEC) ./prosodyctl.install $(BIN)/prosodyctl
+
+install-core:
+	$(MKDIR) $(SOURCE)
+	$(MKDIR) $(SOURCE)/core
 	$(INSTALL_DATA) core/*.lua $(SOURCE)/core
+
+install-net:
+	$(MKDIR) $(SOURCE)
+	$(MKDIR) $(SOURCE)/net
 	$(INSTALL_DATA) net/*.lua $(SOURCE)/net
 	$(MKDIR) $(SOURCE)/net/http $(SOURCE)/net/resolvers $(SOURCE)/net/websocket
 	$(INSTALL_DATA) net/http/*.lua $(SOURCE)/net/http
 	$(INSTALL_DATA) net/resolvers/*.lua $(SOURCE)/net/resolvers
 	$(INSTALL_DATA) net/websocket/*.lua $(SOURCE)/net/websocket
+
+install-util: util/encodings.so util/encodings.so util/pposix.so util/signal.so util/struct.so
+	$(MKDIR) $(SOURCE)
+	$(MKDIR) $(SOURCE)/util
 	$(INSTALL_DATA) util/*.lua $(SOURCE)/util
+	$(MAKE) install -C util-src
 	$(INSTALL_DATA) util/*.so $(SOURCE)/util
 	$(MKDIR) $(SOURCE)/util/sasl
 	$(INSTALL_DATA) util/sasl/*.lua $(SOURCE)/util/sasl
@@ -55,17 +68,27 @@
 	$(INSTALL_DATA) util/human/*.lua $(SOURCE)/util/human
 	$(MKDIR) $(SOURCE)/util/prosodyctl
 	$(INSTALL_DATA) util/prosodyctl/*.lua $(SOURCE)/util/prosodyctl
+
+install-plugins:
+	$(MKDIR) $(MODULES)
 	$(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
 	$(INSTALL_DATA) plugins/adhoc/*.lua $(MODULES)/adhoc
 	$(INSTALL_DATA) plugins/muc/*.lua $(MODULES)/muc
 	$(INSTALL_DATA) plugins/mod_mam/*.lua $(MODULES)/mod_mam
-	$(INSTALL_DATA) certs/* $(CONFIG)/certs
+
+install-man:
+	$(MKDIR) $(MAN)/man1
 	$(INSTALL_DATA) man/prosodyctl.man $(MAN)/man1/prosodyctl.1
-	test -f $(CONFIG)/prosody.cfg.lua || $(INSTALL_DATA) prosody.cfg.lua.install $(CONFIG)/prosody.cfg.lua
+
+install-meta:
 	-test -f prosody.version && $(INSTALL_DATA) prosody.version $(SOURCE)/prosody.version
-	$(MAKE) install -C util-src
+
+install-data:
+	$(MKDIR_PRIVATE) $(DATA)
+
+install: install-util install-net install-core install-plugins install-bin install-etc install-man install-meta install-data
 
 clean:
 	rm -f prosody.install
--- a/core/certmanager.lua	Fri Mar 04 16:13:05 2022 +0000
+++ b/core/certmanager.lua	Fri Mar 04 16:33:41 2022 +0000
@@ -120,7 +120,7 @@
 			if f then
 				-- TODO look for chained certificates
 				local firstline = f:read();
-				if firstline == "-----BEGIN CERTIFICATE-----" then
+				if firstline == "-----BEGIN CERTIFICATE-----" and lfs.attributes(find_matching_key(full), "mode") == "file" then
 					f:seek("set")
 					local cert = ssl.loadcertificate(f:read("*a"))
 					-- TODO if more than one cert is found for a name, the most recently
--- a/core/portmanager.lua	Fri Mar 04 16:13:05 2022 +0000
+++ b/core/portmanager.lua	Fri Mar 04 16:33:41 2022 +0000
@@ -216,7 +216,9 @@
 end
 
 function get_service_at(interface, port)
-	local data = active_services:search(nil, interface, port)[1][1];
+	local data = active_services:search(nil, interface, port);
+	if not data or not data[1] or not data[1][1] then return nil, "not-found"; end
+	data = data[1][1];
 	return data.service, data.server;
 end
 
--- a/makefile	Fri Mar 04 16:13:05 2022 +0000
+++ b/makefile	Fri Mar 04 16:33:41 2022 +0000
@@ -30,21 +30,39 @@
 	$(MAKE) -C certs localhost.crt example.com.crt
 .endif
 
-install: prosody.install prosodyctl.install prosody.cfg.lua.install util/encodings.so util/encodings.so util/pposix.so util/signal.so
-	$(MKDIR) $(BIN) $(CONFIG) $(MODULES) $(SOURCE)
-	$(MKDIR_PRIVATE) $(DATA)
-	$(MKDIR) $(MAN)/man1
+install-etc: prosody.cfg.lua.install
+	$(MKDIR) $(CONFIG)
 	$(MKDIR) $(CONFIG)/certs
-	$(MKDIR) $(SOURCE)/core $(SOURCE)/net $(SOURCE)/util
+	test -f $(CONFIG)/prosody.cfg.lua || $(INSTALL_DATA) prosody.cfg.lua.install $(CONFIG)/prosody.cfg.lua
+.if $(EXCERTS) == "yes"
+	$(INSTALL_DATA) certs/localhost.crt certs/localhost.key $(CONFIG)/certs
+	$(INSTALL_DATA) certs/example.com.crt certs/example.com.key $(CONFIG)/certs
+.endif
+
+install-bin: prosody.install prosodyctl.install
+	$(MKDIR) $(BIN)
 	$(INSTALL_EXEC) ./prosody.install $(BIN)/prosody
 	$(INSTALL_EXEC) ./prosodyctl.install $(BIN)/prosodyctl
+
+install-core:
+	$(MKDIR) $(SOURCE)
+	$(MKDIR) $(SOURCE)/core
 	$(INSTALL_DATA) core/*.lua $(SOURCE)/core
+
+install-net:
+	$(MKDIR) $(SOURCE)
+	$(MKDIR) $(SOURCE)/net
 	$(INSTALL_DATA) net/*.lua $(SOURCE)/net
 	$(MKDIR) $(SOURCE)/net/http $(SOURCE)/net/resolvers $(SOURCE)/net/websocket
 	$(INSTALL_DATA) net/http/*.lua $(SOURCE)/net/http
 	$(INSTALL_DATA) net/resolvers/*.lua $(SOURCE)/net/resolvers
 	$(INSTALL_DATA) net/websocket/*.lua $(SOURCE)/net/websocket
+
+install-util: util/encodings.so util/encodings.so util/pposix.so util/signal.so
+	$(MKDIR) $(SOURCE)
+	$(MKDIR) $(SOURCE)/util
 	$(INSTALL_DATA) util/*.lua $(SOURCE)/util
+	$(MAKE) install -C util-src
 	$(INSTALL_DATA) util/*.so $(SOURCE)/util
 	$(MKDIR) $(SOURCE)/util/sasl
 	$(INSTALL_DATA) util/sasl/*.lua $(SOURCE)/util/sasl
@@ -52,20 +70,27 @@
 	$(INSTALL_DATA) util/human/*.lua $(SOURCE)/util/human
 	$(MKDIR) $(SOURCE)/util/prosodyctl
 	$(INSTALL_DATA) util/prosodyctl/*.lua $(SOURCE)/util/prosodyctl
+
+install-plugins:
+	$(MKDIR) $(MODULES)
 	$(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
 	$(INSTALL_DATA) plugins/adhoc/*.lua $(MODULES)/adhoc
 	$(INSTALL_DATA) plugins/muc/*.lua $(MODULES)/muc
 	$(INSTALL_DATA) plugins/mod_mam/*.lua $(MODULES)/mod_mam
-.if $(EXCERTS) == "yes"
-	$(INSTALL_DATA) certs/localhost.crt certs/localhost.key $(CONFIG)/certs
-	$(INSTALL_DATA) certs/example.com.crt certs/example.com.key $(CONFIG)/certs
-.endif
+
+install-man:
+	$(MKDIR) $(MAN)/man1
 	$(INSTALL_DATA) man/prosodyctl.man $(MAN)/man1/prosodyctl.1
-	test -f $(CONFIG)/prosody.cfg.lua || $(INSTALL_DATA) prosody.cfg.lua.install $(CONFIG)/prosody.cfg.lua
+
+install-meta:
 	-test -f prosody.version && $(INSTALL_DATA) prosody.version $(SOURCE)/prosody.version
-	$(MAKE) install -C util-src
+
+install-data:
+	$(MKDIR_PRIVATE) $(DATA)
+
+install: install-util install-net install-core install-plugins install-bin install-etc install-man install-meta install-data
 
 clean:
 	rm -f prosody.install
--- a/plugins/mod_c2s.lua	Fri Mar 04 16:13:05 2022 +0000
+++ b/plugins/mod_c2s.lua	Fri Mar 04 16:33:41 2022 +0000
@@ -433,7 +433,7 @@
 end, -80);
 
 module:hook("server-stopping", function(event)
-	local wait, done = async.waiter();
+	local wait, done = async.waiter(1, true);
 	module:hook("c2s-closed", function ()
 		if next(sessions) == nil then done(); end
 	end)
@@ -446,7 +446,7 @@
 
 	-- Wait for them to close properly if they haven't already
 	if next(sessions) ~= nil then
-		add_task(stream_close_timeout+1, done);
+		add_task(stream_close_timeout+1, function () done() end);
 		module:log("info", "Waiting for sessions to close");
 		wait();
 	end
--- a/plugins/mod_carbons.lua	Fri Mar 04 16:13:05 2022 +0000
+++ b/plugins/mod_carbons.lua	Fri Mar 04 16:33:41 2022 +0000
@@ -69,6 +69,12 @@
 	return false, "default";
 end
 
+module:hook("carbons-should-copy", function (event)
+	local should, why = should_copy(event.stanza);
+	event.reason = why;
+	return should;
+end, -1)
+
 local function message_handler(event, c2s)
 	local origin, stanza = event.origin, event.stanza;
 	local orig_type = stanza.attr.type or "normal";
@@ -101,7 +107,9 @@
 		return -- No use in sending carbons to an offline user
 	end
 
-	local should, why = should_copy(stanza, c2s, bare_jid);
+	local event_payload = { stanza = stanza; session = origin };
+	local should = module:fire_event("carbons-should-copy", event_payload);
+	local why = event_payload.reason;
 
 	if not should then
 		module:log("debug", "Not copying stanza: %s (%s)", stanza:top_tag(), why);
--- a/plugins/mod_mam/mod_mam.lua	Fri Mar 04 16:13:05 2022 +0000
+++ b/plugins/mod_mam/mod_mam.lua	Fri Mar 04 16:33:41 2022 +0000
@@ -54,7 +54,7 @@
 local use_total = module:get_option_boolean("mam_include_total", true);
 
 function schedule_cleanup()
-	-- replaced by non-noop later if cleanup is enabled
+	-- replaced later if cleanup is enabled
 end
 
 -- Handle prefs.
@@ -311,7 +311,7 @@
 	return stanza;
 end
 
-local function should_store(stanza, c2s) --> boolean, reason: string
+local function should_store(stanza) --> 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
@@ -320,7 +320,7 @@
 		-- Headline messages are ephemeral by definition
 		return false, "headline";
 	end
-	if st_type == "error" and not c2s then
+	if st_type == "error" then
 		-- Errors not sent sent from a local client
 		-- Why would a client send an error anyway?
 		if jid_resource(stanza.attr.to) then
@@ -380,6 +380,12 @@
 	return false, "default";
 end
 
+module:hook("archive-should-store", function (event)
+	local should, why = should_store(event.stanza);
+	event.reason = why;
+	return should;
+end, -1)
+
 -- Handle messages
 local function message_handler(event, c2s)
 	local origin, stanza = event.origin, event.stanza;
@@ -396,9 +402,12 @@
 	-- Filter out <stanza-id> that claim to be from us
 	event.stanza = strip_stanza_id(stanza, store_user);
 
-	local should, why = should_store(stanza, c2s);
+	local event_payload = { stanza = stanza; session = origin };
+	local should = module:fire_event("archive-should-store", event_payload);
+	local why = event_payload.reason;
+
 	if not should then
-		log("debug", "Not archiving stanza: %s (%s)", stanza:top_tag(), why);
+		log("debug", "Not archiving stanza: %s (%s)", stanza:top_tag(), event_payload.reason);
 		return;
 	end
 
--- a/plugins/mod_s2s.lua	Fri Mar 04 16:13:05 2022 +0000
+++ b/plugins/mod_s2s.lua	Fri Mar 04 16:33:41 2022 +0000
@@ -973,7 +973,7 @@
 		end
 	end
 
-	local wait, done = async.waiter();
+	local wait, done = async.waiter(1, true);
 	module:hook("s2s-closed", function ()
 		if next(sessions) == nil then done(); end
 	end, 1)
@@ -987,7 +987,7 @@
 	-- Wait for them to close properly if they haven't already
 	if next(sessions) ~= nil then
 		module:log("info", "Waiting for sessions to close");
-		add_task(stream_close_timeout + 1, done);
+		add_task(stream_close_timeout + 1, function () done() end);
 		wait();
 	end
 
--- a/teal-src/util/poll.d.tl	Fri Mar 04 16:13:05 2022 +0000
+++ b/teal-src/util/poll.d.tl	Fri Mar 04 16:33:41 2022 +0000
@@ -17,8 +17,15 @@
 
 local record lib
 	new : function () : state
+	EEXIST : integer
+	EMFILE : integer
 	ENOENT : integer
-	EEXIST : integer
+	enum api_backend
+		"epoll"
+		"poll"
+		"select"
+	end
+	api : api_backend
 end
 
 return lib
--- a/util-src/poll.c	Fri Mar 04 16:13:05 2022 +0000
+++ b/util-src/poll.c	Fri Mar 04 16:33:41 2022 +0000
@@ -1,7 +1,7 @@
 
 /*
  * Lua polling library
- * Copyright (C) 2017-2018 Kim Alvefur
+ * Copyright (C) 2017-2022 Kim Alvefur
  *
  * This project is MIT licensed. Please see the
  * COPYING file in the source package for more information.
@@ -12,8 +12,15 @@
 #include <string.h>
 #include <errno.h>
 
-#ifdef __linux__
+#if defined(__linux__)
 #define USE_EPOLL
+#define POLL_BACKEND "epoll"
+#elif defined(__unix__)
+#define USE_POLL
+#define POLL_BACKEND "poll"
+#else
+#define USE_SELECT
+#define POLL_BACKEND "select"
 #endif
 
 #ifdef USE_EPOLL
@@ -21,18 +28,21 @@
 #ifndef MAX_EVENTS
 #define MAX_EVENTS 64
 #endif
-#else
+#endif
+#ifdef USE_POLL
+#include <poll.h>
+#ifndef MAX_EVENTS
+#define MAX_EVENTS 10000
+#endif
+#endif
+#ifdef USE_SELECT
 #include <sys/select.h>
 #endif
 
 #include <lualib.h>
 #include <lauxlib.h>
 
-#ifdef USE_EPOLL
-#define STATE_MT "util.poll<epoll>"
-#else
-#define STATE_MT "util.poll<select>"
-#endif
+#define STATE_MT "util.poll<" POLL_BACKEND ">"
 
 #if (LUA_VERSION_NUM == 501)
 #define luaL_setmetatable(L, tname) luaL_getmetatable(L, tname); lua_setmetatable(L, -2)
@@ -49,7 +59,12 @@
 #ifdef USE_EPOLL
 	int epoll_fd;
 	struct epoll_event events[MAX_EVENTS];
-#else
+#endif
+#ifdef USE_POLL
+	nfds_t count;
+	struct pollfd events[MAX_EVENTS];
+#endif
+#ifdef USE_SELECT
 	fd_set wantread;
 	fd_set wantwrite;
 	fd_set readable;
@@ -96,7 +111,34 @@
 	lua_pushboolean(L, 1);
 	return 1;
 
-#else
+#endif
+#ifdef USE_POLL
+
+	for(nfds_t i = 0; i < state->count; i++) {
+		if(state->events[i].fd == fd) {
+			luaL_pushfail(L);
+			lua_pushstring(L, strerror(EEXIST));
+			lua_pushinteger(L, EEXIST);
+			return 3;
+		}
+	}
+
+	if(state->count >= MAX_EVENTS) {
+		luaL_pushfail(L);
+		lua_pushstring(L, strerror(EMFILE));
+		lua_pushinteger(L, EMFILE);
+		return 3;
+	}
+
+	state->events[state->count].fd = fd;
+	state->events[state->count].events = (wantread ? POLLIN : 0) | (wantwrite ? POLLOUT : 0);
+	state->events[state->count].revents = 0;
+	state->count++;
+
+	lua_pushboolean(L, 1);
+	return 1;
+#endif
+#ifdef USE_SELECT
 
 	if(fd > FD_SETSIZE) {
 		luaL_pushfail(L);
@@ -169,7 +211,29 @@
 		return 3;
 	}
 
-#else
+#endif
+#ifdef USE_POLL
+	int wantread = lua_toboolean(L, 3);
+	int wantwrite = lua_toboolean(L, 4);
+
+	for(nfds_t i = 0; i < state->count; i++) {
+		struct pollfd *event =  &state->events[i];
+
+		if(event->fd == fd) {
+			event->events = (wantread ? POLLIN : 0) | (wantwrite ? POLLOUT : 0);
+			lua_pushboolean(L, 1);
+			return 1;
+		} else if(event->fd == -1) {
+			break;
+		}
+	}
+
+	luaL_pushfail(L);
+	lua_pushstring(L, strerror(ENOENT));
+	lua_pushinteger(L, ENOENT);
+	return 3;
+#endif
+#ifdef USE_SELECT
 
 	if(!FD_ISSET(fd, &state->all)) {
 		luaL_pushfail(L);
@@ -227,7 +291,42 @@
 		return 3;
 	}
 
-#else
+#endif
+#ifdef USE_POLL
+
+	if(state->count == 0) {
+		luaL_pushfail(L);
+		lua_pushstring(L, strerror(ENOENT));
+		lua_pushinteger(L, ENOENT);
+		return 3;
+	}
+
+	/*
+	 * Move the last item on top of the removed one
+	 */
+	struct pollfd *last = &state->events[state->count - 1];
+
+	for(nfds_t i = 0; i < state->count; i++) {
+		struct pollfd *event = &state->events[i];
+
+		if(event->fd == fd) {
+			event->fd = last->fd;
+			event->events = last->events;
+			event->revents = last->revents;
+			last->fd = -1;
+			state->count--;
+
+			lua_pushboolean(L, 1);
+			return 1;
+		}
+	}
+
+	luaL_pushfail(L);
+	lua_pushstring(L, strerror(ENOENT));
+	lua_pushinteger(L, ENOENT);
+	return 3;
+#endif
+#ifdef USE_SELECT
 
 	if(!FD_ISSET(fd, &state->all)) {
 		luaL_pushfail(L);
@@ -264,7 +363,24 @@
 		return 3;
 	}
 
-#else
+#endif
+#ifdef USE_POLL
+
+	for(int i = state->processed - 1; i >= 0; i--) {
+		struct pollfd *event = &state->events[i];
+
+		if(event->fd != -1 && event->revents != 0) {
+			lua_pushinteger(L, event->fd);
+			lua_pushboolean(L, event->revents & (POLLIN | POLLHUP | POLLERR));
+			lua_pushboolean(L, event->revents & POLLOUT);
+			event->revents = 0;
+			state->processed = i;
+			return 3;
+		}
+	}
+
+#endif
+#ifdef USE_SELECT
 
 	for(int fd = state->processed + 1; fd < FD_SETSIZE; fd++) {
 		if(FD_ISSET(fd, &state->readable) || FD_ISSET(fd, &state->writable) || FD_ISSET(fd, &state->err)) {
@@ -300,7 +416,11 @@
 
 #ifdef USE_EPOLL
 	ret = epoll_wait(state->epoll_fd, state->events, MAX_EVENTS, timeout * 1000);
-#else
+#endif
+#ifdef USE_POLL
+	ret = poll(state->events, state->count, timeout * 1000);
+#endif
+#ifdef USE_SELECT
 	/*
 	 * select(2) mutates the fd_sets passed to it so in order to not
 	 * have to recreate it manually every time a copy is made.
@@ -341,7 +461,11 @@
 	 */
 #ifdef USE_EPOLL
 	state->processed = ret;
-#else
+#endif
+#ifdef USE_POLL
+	state->processed = state->count;
+#endif
+#ifdef USE_SELECT
 	state->processed = -1;
 #endif
 	return Lpushevent(L, state);
@@ -411,7 +535,19 @@
 	}
 
 	state->epoll_fd = epoll_fd;
-#else
+#endif
+#ifdef USE_POLL
+	state->processed = -1;
+	state->count = 0;
+
+	for(nfds_t i = 0; i < MAX_EVENTS; i++) {
+		state->events[i].fd = -1;
+		state->events[i].events = 0;
+		state->events[i].revents = 0;
+	}
+
+#endif
+#ifdef USE_SELECT
 	FD_ZERO(&state->wantread);
 	FD_ZERO(&state->wantwrite);
 	FD_ZERO(&state->readable);
@@ -473,8 +609,12 @@
 		lua_setfield(L, -2, #named_error);
 
 		push_errno(EEXIST);
+		push_errno(EMFILE);
 		push_errno(ENOENT);
 
+		lua_pushliteral(L, POLL_BACKEND);
+		lua_setfield(L, -2, "api");
+
 	}
 	return 1;
 }
--- a/util/async.lua	Fri Mar 04 16:13:05 2022 +0000
+++ b/util/async.lua	Fri Mar 04 16:33:41 2022 +0000
@@ -73,7 +73,7 @@
 	return true;
 end
 
-local function waiter(num)
+local function waiter(num, allow_many)
 	local thread = checkthread();
 	num = num or 1;
 	local waiting;
@@ -85,7 +85,7 @@
 		num = num - 1;
 		if num == 0 and waiting then
 			runner_continue(thread);
-		elseif num < 0 then
+		elseif not allow_many and num < 0 then
 			error("done() called too many times");
 		end
 	end;
--- a/util/prosodyctl/check.lua	Fri Mar 04 16:13:05 2022 +0000
+++ b/util/prosodyctl/check.lua	Fri Mar 04 16:33:41 2022 +0000
@@ -505,6 +505,69 @@
 			ok = false;
 		end
 
+		do
+			local global_modules = set.new(config["*"].modules_enabled);
+			local registration_enabled_hosts = {};
+			for host in enabled_hosts() do
+				local host_modules = set.new(config[host].modules_enabled) + global_modules;
+				local allow_registration = config[host].allow_registration;
+				local mod_register = host_modules:contains("register");
+				local mod_register_ibr = host_modules:contains("register_ibr");
+				local mod_invites_register = host_modules:contains("invites_register");
+				local registration_invite_only = config[host].registration_invite_only;
+				local is_vhost = not config[host].component_module;
+				if is_vhost and (mod_register_ibr or (mod_register and allow_registration))
+				   and not (mod_invites_register and registration_invite_only) then
+					table.insert(registration_enabled_hosts, host);
+				end
+			end
+			if #registration_enabled_hosts > 0 then
+				table.sort(registration_enabled_hosts);
+				print("");
+				print("    Public registration is enabled on:");
+				print("        "..table.concat(registration_enabled_hosts, ", "));
+				print("");
+				print("        If this is intentional, review our guidelines on running a public server");
+				print("        at https://prosody.im/doc/public_servers - otherwise, consider switching to");
+				print("        invite-based registration, which is more secure.");
+			end
+		end
+
+		do
+			local orphan_components = {};
+			local referenced_components = set.new();
+			local enabled_hosts_set = set.new();
+			for host, host_options in it.filter("*", pairs(configmanager.getconfig())) do
+				if host_options.enabled ~= false then
+					enabled_hosts_set:add(host);
+					for _, disco_item in ipairs(host_options.disco_items or {}) do
+						referenced_components:add(disco_item[1]);
+					end
+				end
+			end
+			for host, host_config in enabled_hosts() do
+				local is_component = not not host_config.component_module;
+				if is_component then
+					local parent_domain = host:match("^[^.]+%.(.+)$");
+					local is_orphan = not (enabled_hosts_set:contains(parent_domain) or referenced_components:contains(host));
+					if is_orphan then
+						table.insert(orphan_components, host);
+					end
+				end
+			end
+			if #orphan_components > 0 then
+				table.sort(orphan_components);
+				print("");
+				print("    Your configuration contains the following unreferenced components:\n");
+				print("        "..table.concat(orphan_components, "\n        "));
+				print("");
+				print("    Clients may not be able to discover these services because they are not linked to");
+				print("    any VirtualHost. They are automatically linked if they are direct subdomains of a");
+				print("    VirtualHost. Alternatively, you can explicitly link them using the disco_items option.");
+				print("    For more information see https://prosody.im/doc/modules/mod_disco#items");
+			end
+		end
+
 		print("Done.\n");
 	end
 	if not what or what == "dns" then
@@ -585,6 +648,11 @@
 			end
 		end
 
+		-- Allow admin to specify additional (e.g. undiscoverable) IP addresses in the config
+		for _, address in ipairs(configmanager.get("*", "external_addresses") or {}) do
+			external_addresses:add(address);
+		end
+
 		if external_addresses:empty() then
 			print("");
 			print("   Failed to determine the external addresses of this server. Checks may be inaccurate.");
@@ -599,6 +667,8 @@
 			return (n:gsub("%.$", ""));
 		end
 
+		local unknown_addresses = set.new();
+
 		for jid, host_options in enabled_hosts() do
 			local all_targets_ok, some_targets_ok = true, false;
 			local node, host = jid_split(jid);
@@ -781,6 +851,7 @@
 								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);
+								unknown_addresses:add(record.a);
 								all_targets_ok = false;
 							end
 						end
@@ -799,6 +870,7 @@
 								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);
+								unknown_addresses:add(record.aaaa);
 								all_targets_ok = false;
 							end
 						end
@@ -844,6 +916,18 @@
 			print("");
 		end
 		if not problem_hosts:empty() then
+			if not unknown_addresses:empty() then
+				print("");
+				print("Some of your DNS records point to unknown IP addresses. This may be expected if your server");
+				print("is behind a NAT or proxy. The unrecognized addresses were:");
+				print("");
+				print("    Unrecognized: "..tostring(unknown_addresses));
+				print("");
+				print("The addresses we found on this system are:");
+				print("");
+				print("    Internal: "..tostring(internal_addresses));
+				print("    External: "..tostring(external_addresses));
+			end
 			print("");
 			print("For more information about DNS configuration please see https://prosody.im/doc/dns");
 			print("");