view plugins/mod_admin_socket.lua @ 14217:7eb0e47418ab 13.0

util.human.io: Add simple version comparison This is a small helper function that compares version string (it can be passed to table.sort() as a comparator). Full version sorts (e.g. semver, and natural/alphanumeric sort in general) are more complex, and I would like to eventually support those. But this is a stable branch and we don't need it for anything just yet. Regarding justification for the stable branch: The function is being committed with extensive tests, and isn't currently used anywhere. The plan is to use it in an upcoming update of how TLS profiles are configured, but the inputs in that case will be fixed (meaning that the risk of breakage is lower than if we were running it against arbitrary data).
author Matthew Wild <mwild1@gmail.com>
date Fri, 12 Jun 2026 11:38:39 +0100
parents 34da8cc10b82
children
line wrap: on
line source

module:set_global();

local have_unix, unix = pcall(require, "socket.unix");

if have_unix and type(unix) == "function" then
	-- COMPAT #1717
	-- Before the introduction of datagram support, only the stream socket
	-- constructor was exported instead of a module table. Due to the lack of a
	-- proper release of LuaSocket, distros have settled on shipping either the
	-- last RC tag or some commit since then.
	-- Here we accommodate both variants.
	unix = { stream = unix };
end
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 "prosody.net.server";

local adminstream = require "prosody.util.adminstream";
local st = require "prosody.util.stanza";

local socket_path = module:get_option_path("admin_socket", "prosody.sock", "data");

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);
	local ret = module:fire_event(event_name, event_data);
	if ret == nil then
		session.send(st.stanza("repl-result", { type = "error" }):text("No module handled this query. Is mod_admin_shell enabled?"));
	end
	return ret;
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 admin_server = adminstream.server(sessions, fire_admin_event);
local listeners = admin_server.listeners;

module:hook_object_event(admin_server.events, "disconnected", function (event)
	return module:fire_event("admin-disconnected", event);
end);

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);
	local ok, err = sock:bind(socket_path);
	if not ok then
		module:log_status("error", "Unable to bind admin socket %s: %s", socket_path, err);
		return;
	end
	local ok, err = sock:listen();
	if not ok then
		module:log_status("error", "Unable to listen on admin socket %s: %s", socket_path, err);
		return;
	end
	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()
	if conn then
		conn:close();
	end
	if sock then
		sock:close();
	end
	os.remove(socket_path);
end