aboutsummaryrefslogtreecommitdiffstats
path: root/plugins/mod_admin_shell.lua
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/mod_admin_shell.lua')
-rw-r--r--plugins/mod_admin_shell.lua119
1 files changed, 90 insertions, 29 deletions
diff --git a/plugins/mod_admin_shell.lua b/plugins/mod_admin_shell.lua
index 0b8d3c43..974ed8d9 100644
--- a/plugins/mod_admin_shell.lua
+++ b/plugins/mod_admin_shell.lua
@@ -25,6 +25,8 @@ local _G = _G;
local prosody = _G.prosody;
local unpack = table.unpack;
+local cache = require "prosody.util.cache";
+local new_short_id = require "prosody.util.id".short;
local iterators = require "prosody.util.iterators";
local keys, values = iterators.keys, iterators.values;
local jid_bare, jid_split, jid_join, jid_resource, jid_compare = import("prosody.util.jid", "bare", "prepped_split", "join", "resource", "compare");
@@ -170,6 +172,47 @@ local function send_repl_output(session, line, attr)
return session.send(st.stanza("repl-output", attr):text(tostring(line)));
end
+local function request_repl_input(session, input_type)
+ if input_type ~= "password" then
+ return promise.reject("internal error - unsupported input type "..tostring(input_type));
+ end
+ local pending_inputs = session.pending_inputs;
+ if not pending_inputs then
+ pending_inputs = cache.new(5, function (input_id, input_promise) --luacheck: ignore 212/input_id
+ input_promise.reject();
+ end);
+ session.pending_inputs = pending_inputs;
+ end
+
+ local input_id = new_short_id();
+ local p = promise.new(function (resolve, reject)
+ pending_inputs:set(input_id, { resolve = resolve, reject = reject });
+ end):finally(function ()
+ pending_inputs:set(input_id, nil);
+ end);
+ session.send(st.stanza("repl-request-input", { type = input_type, id = input_id }));
+ return p;
+end
+
+module:hook("admin-disconnected", function (event)
+ local pending_inputs = event.session.pending_inputs;
+ if not pending_inputs then return; end
+ for input_promise in pending_inputs:values() do
+ input_promise.reject();
+ end
+end);
+
+module:hook("admin/repl-requested-input", function (event)
+ local input_id = event.stanza.attr.id;
+ local input_promise = event.origin.pending_inputs:get(input_id);
+ if not input_promise then
+ event.origin.send(st.stanza("repl-result", { type = "error" }):text("Internal error - unexpected input"));
+ return true;
+ end
+ input_promise.resolve(event.stanza:get_text());
+ return true;
+end);
+
function console:new_session(admin_session)
local session = {
send = function (t)
@@ -185,6 +228,9 @@ function console:new_session(admin_session)
write = function (t)
return send_repl_output(admin_session, t, { eol = "0" });
end;
+ request_input = function (input_type)
+ return request_repl_input(admin_session, input_type);
+ end;
serialize = tostring;
disconnect = function () admin_session:close(); end;
is_connected = function ()
@@ -266,25 +312,33 @@ local function handle_line(event)
end
end
- local taskok, message = chunk();
+ local function send_result(taskok, message)
+ if not message then
+ if type(taskok) ~= "string" and useglobalenv then
+ taskok = session.serialize(taskok);
+ end
+ result:text("Result: "..tostring(taskok));
+ elseif (not taskok) and message then
+ result.attr.type = "error";
+ result:text("Error: "..tostring(message));
+ else
+ result:text("OK: "..tostring(message));
+ end
- if promise.is_promise(taskok) then
- taskok, message = async.wait_for(taskok);
+ event.origin.send(result);
end
- if not message then
- if type(taskok) ~= "string" and useglobalenv then
- taskok = session.serialize(taskok);
- end
- result:text("Result: "..tostring(taskok));
- elseif (not taskok) and message then
- result.attr.type = "error";
- result:text("Error: "..tostring(message));
+ local taskok, message = chunk();
+
+ if promise.is_promise(taskok) then
+ taskok:next(function (resolved_message)
+ send_result(true, resolved_message);
+ end, function (rejected_message)
+ send_result(nil, rejected_message);
+ end);
else
- result:text("OK: "..tostring(message));
+ send_result(taskok, message);
end
-
- event.origin.send(result);
end
module:hook("admin/repl-input", function (event)
@@ -1670,12 +1724,13 @@ function def_env.user:create(jid, password, role)
role = module:get_option_string("default_provisioned_role", "prosody:member");
end
- local ok, err = um.create_user_with_role(username, password, host, role);
- if not ok then
- return nil, "Could not create user: "..err;
- end
-
- return true, ("Created %s with role '%s'"):format(jid, role);
+ return promise.resolve(password or self.session.request_input("password")):next(function (password_)
+ local ok, err = um.create_user_with_role(username, password_, host, role);
+ if not ok then
+ return promise.reject("Could not create user: "..err);
+ end
+ return ("Created %s with role '%s'"):format(jid, role);
+ end);
end
describe_command [[user:disable(jid) - Disable the specified user account, preventing login]]
@@ -1734,12 +1789,15 @@ function def_env.user:password(jid, password)
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
+
+ return promise.resolve(password or self.session.request_input("password")):next(function (password_)
+ local ok, err = um.set_password(username, password_, host, nil);
+ if ok then
+ return "User password changed";
+ else
+ return promise.reject("Could not change password for user: "..err);
+ end
+ end);
end
describe_command [[user:roles(jid, host) - Show current roles for an user]]
@@ -2431,12 +2489,15 @@ describe_command [[stats:show(pattern) - Show internal statistics, optionally fi
-- Undocumented currently, you can append :histogram() or :cfgraph() to stats:show() for rendered graphs.
function def_env.stats:show(name_filter)
local statsman = require "prosody.core.statsmanager"
+ local metric_registry = statsman.get_metric_registry();
+ if not metric_registry then
+ return nil, [[Statistics disabled. Try `statistics = "internal"` in the global section of the config file and restart.]];
+ end
local collect = statsman.collect
if collect then
-- force collection if in manual mode
collect()
end
- local metric_registry = statsman.get_metric_registry();
local displayed_stats = new_stats_context(self);
for family_name, metric_family in iterators.sorted_pairs(metric_registry:get_metric_families()) do
if not name_filter or family_name:match(name_filter) then
@@ -2481,7 +2542,7 @@ local host_commands = {};
local function new_item_handlers(command_host)
local function on_command_added(event)
local command = event.item;
- local mod_name = command._provided_by and ("mod_"..command._provided_by) or "<unknown module>";
+ local mod_name = event.source and ("mod_"..event.source.name) or "<unknown module>";
if not schema.validate(command_metadata_schema, command) or type(command.handler) ~= "function" then
module:log("warn", "Ignoring command added by %s: missing or invalid data", mod_name);
return;
@@ -2568,7 +2629,7 @@ local function new_item_handlers(command_host)
module = command._provided_by;
};
- module:log("debug", "Shell command added by mod_%s: %s:%s()", mod_name, command.section, command.name);
+ module:log("debug", "Shell command added by %s: %s:%s()", mod_name, command.section, command.name);
end
local function on_command_removed(event)