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.lua238
1 files changed, 202 insertions, 36 deletions
diff --git a/plugins/mod_admin_shell.lua b/plugins/mod_admin_shell.lua
index fe231dca..b88b5316 100644
--- a/plugins/mod_admin_shell.lua
+++ b/plugins/mod_admin_shell.lua
@@ -19,6 +19,7 @@ local it = require "prosody.util.iterators";
local server = require "prosody.net.server";
local schema = require "prosody.util.jsonschema";
local st = require "prosody.util.stanza";
+local parse_args = require "prosody.util.argparse".parse;
local _G = _G;
@@ -255,6 +256,83 @@ function console:new_session(admin_session)
return session;
end
+local function process_cmd_line(session, arg_line)
+ local chunk = load("return "..arg_line, "=shell", "t", {});
+ local ok, args = pcall(chunk);
+ if not ok then return nil, args; end
+
+ local section_name, command = args[1], args[2];
+
+ local section_mt = getmetatable(def_env[section_name]);
+ local section_help = section_mt and section_mt.help;
+ local command_help = section_help and section_help.commands[command];
+
+ if not command_help then
+ if commands[section_name] then
+ commands[section_name](session, table.concat(args, " "));
+ return;
+ end
+ if section_help then
+ return nil, "Command not found or necessary module not loaded. Try 'help "..section_name.." for a list of available commands.";
+ end
+ return nil, "Command not found. Is the necessary module loaded?";
+ end
+
+ local fmt = { "%s"; ":%s("; ")" };
+
+ if command_help.flags then
+ local flags, flags_err, flags_err_extra = parse_args(args, command_help.flags);
+ if not flags then
+ if flags_err == "missing-value" then
+ return nil, "Expected value after "..flags_err_extra;
+ elseif flags_err == "param-not-found" then
+ return nil, "Unknown parameter: "..flags_err_extra;
+ end
+ return nil, flags_err;
+ end
+
+ table.remove(flags, 2);
+ table.remove(flags, 1);
+
+ local n_fixed_args = #command_help.args;
+
+ local arg_str = {};
+ for i = 1, n_fixed_args do
+ if flags[i] ~= nil then
+ table.insert(arg_str, ("%q"):format(flags[i]));
+ else
+ table.insert(arg_str, "nil");
+ end
+ end
+
+ table.insert(arg_str, "flags");
+
+ for i = n_fixed_args + 1, #flags do
+ if flags[i] ~= nil then
+ table.insert(arg_str, ("%q"):format(flags[i]));
+ else
+ table.insert(arg_str, "nil");
+ end
+ end
+
+ table.insert(fmt, 3, "%s");
+
+ return "local flags = ...; return "..string.format(table.concat(fmt), section_name, command, table.concat(arg_str, ", ")), flags;
+ end
+
+ for i = 3, #args do
+ if args[i]:sub(1, 1) == ":" then
+ table.insert(fmt, i, ")%s(");
+ elseif i > 3 and fmt[i - 1]:match("%%q$") then
+ table.insert(fmt, i, ", %q");
+ else
+ table.insert(fmt, i, "%q");
+ end
+ end
+
+ return "return "..string.format(table.concat(fmt), table.unpack(args));
+end
+
local function handle_line(event)
local session = event.origin.shell_session;
if not session then
@@ -295,23 +373,6 @@ local function handle_line(event)
session.globalenv = redirect_output(_G, session);
end
- local chunkname = "=console";
- local env = (useglobalenv and session.globalenv) 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("'<eof>'", "the end of the line");
- result.attr.type = "error";
- result:text("Sorry, I couldn't understand that... "..err);
- event.origin.send(result);
- return;
- end
- end
-
local function send_result(taskok, message)
if not message then
if type(taskok) ~= "string" and useglobalenv then
@@ -328,7 +389,49 @@ local function handle_line(event)
event.origin.send(result);
end
- local taskok, message = chunk();
+ local taskok, message;
+ local env = (useglobalenv and session.globalenv) or session.env or nil;
+ local flags;
+
+ local source;
+ if line:match("^{") then
+ -- Input is a serialized array of strings, typically from
+ -- a command-line invocation of 'prosodyctl shell something'
+ source, flags = process_cmd_line(session, line);
+ if not source then
+ if flags then -- err
+ send_result(false, flags);
+ else -- no err, but nothing more to do
+ -- This happens if it was a "simple" command
+ event.origin.send(result);
+ end
+ return;
+ end
+ end
+
+ local chunkname = "=console";
+ -- luacheck: ignore 311/err
+ local chunk, err = envload(source or ("return "..line), chunkname, env);
+ if not chunk then
+ if not source then
+ chunk, err = envload(line, chunkname, env);
+ end
+ if not chunk then
+ err = err:gsub("^%[string .-%]:%d+: ", "");
+ err = err:gsub("^:%d+: ", "");
+ err = err:gsub("'<eof>'", "the end of the line");
+ result.attr.type = "error";
+ result:text("Sorry, I couldn't understand that... "..err);
+ event.origin.send(result);
+ return;
+ end
+ end
+
+ if not source then
+ session.repl = true;
+ end
+
+ taskok, message = chunk(flags);
if promise.is_promise(taskok) then
taskok:next(function (resolved_message)
@@ -349,7 +452,7 @@ module:hook("admin/repl-input", function (event)
return true;
end);
-local function describe_command(s)
+local function describe_command(s, hidden)
local section, name, args, desc = s:match("^([%w_]+):([%w_]+)%(([^)]*)%) %- (.+)$");
if not section then
error("Failed to parse command description: "..s);
@@ -360,9 +463,14 @@ local function describe_command(s)
args = array.collect(args:gmatch("[%w_]+")):map(function (arg_name)
return { name = arg_name };
end);
+ hidden = hidden;
};
end
+local function hidden_command(s)
+ return describe_command(s, true);
+end
+
-- Console commands --
-- These are simple commands, not valid standalone in Lua
@@ -455,10 +563,46 @@ def_env.help = setmetatable({}, {
end
for command, command_help in it.sorted_pairs(section_help.commands or {}) do
- c = c + 1;
- local args = array.pluck(command_help.args, "name"):concat(", ");
- local desc = command_help.desc or command_help.module and ("Provided by mod_"..command_help.module) or "";
- print(("%s:%s(%s) - %s"):format(section_name, command, args, desc));
+ if not command_help.hidden then
+ c = c + 1;
+ local desc = command_help.desc or command_help.module and ("Provided by mod_"..command_help.module) or "";
+ if self.session.repl then
+ local args = array.pluck(command_help.args, "name"):concat(", ");
+ print(("%s:%s(%s) - %s"):format(section_name, command, args, desc));
+ else
+ local args = array.pluck(command_help.args, "name"):concat("> <");
+ if args ~= "" then
+ args = "<"..args..">";
+ end
+ print(("%s %s %s"):format(section_name, command, args));
+ print((" %s"):format(desc));
+ if command_help.flags then
+ local flags = command_help.flags;
+ print("");
+ print((" Flags:"));
+
+ if flags.kv_params then
+ for name in it.sorted_pairs(flags.kv_params) do
+ print(" --"..name:gsub("_", "-"));
+ end
+ end
+
+ if flags.value_params then
+ for name in it.sorted_pairs(flags.value_params) do
+ print(" --"..name:gsub("_", "-").." <"..name..">");
+ end
+ end
+
+ if flags.array_params then
+ for name in it.sorted_pairs(flags.array_params) do
+ print(" --"..name:gsub("_", "-").." <"..name..">, ...");
+ end
+ end
+
+ end
+ print("");
+ end
+ end
end
elseif help_topics[section_name] then
local topic = help_topics[section_name];
@@ -1800,9 +1944,8 @@ function def_env.user:password(jid, password)
end);
end
-describe_command [[user:roles(jid, host) - Show current roles for an user]]
+describe_command [[user:role(jid, host) - Show primary role for a user]]
function def_env.user:role(jid, host)
- local print = self.session.print;
local username, userhost = jid_split(jid);
if host == nil then host = userhost; end
if not prosody.hosts[host] then
@@ -1814,22 +1957,27 @@ function def_env.user:role(jid, host)
local primary_role = um.get_user_role(username, host);
local secondary_roles = um.get_user_secondary_roles(username, host);
+ local primary_role_desc = primary_role and primary_role.name or "<none>";
+
print(primary_role and primary_role.name or "<none>");
- local count = primary_role and 1 or 0;
+ local n_secondary = 0;
for role_name in pairs(secondary_roles or {}) do
- count = count + 1;
+ n_secondary = n_secondary + 1;
print(role_name.." (secondary)");
end
- return true, count == 1 and "1 role" or count.." roles";
+ if n_secondary > 0 then
+ return true, primary_role_desc.." (primary)";
+ end
+ return true, primary_role_desc;
end
def_env.user.roles = def_env.user.role;
-describe_command [[user:setrole(jid, host, role) - Set primary role of a user (see 'help roles')]]
--- user:setrole("someone@example.com", "example.com", "prosody:admin")
--- user:setrole("someone@example.com", "prosody:admin")
-function def_env.user:setrole(jid, host, new_role)
+describe_command [[user:set_role(jid, host, role) - Set primary role of a user (see 'help roles')]]
+-- user:set_role("someone@example.com", "example.com", "prosody:admin")
+-- user:set_role("someone@example.com", "prosody:admin")
+function def_env.user:set_role(jid, host, new_role)
local username, userhost = jid_split(jid);
if new_role == nil then host, new_role = userhost, host; end
if not prosody.hosts[host] then
@@ -1844,7 +1992,7 @@ function def_env.user:setrole(jid, host, new_role)
end
end
-describe_command [[user:addrole(jid, host, role) - Add a secondary role to a user]]
+hidden_command [[user:addrole(jid, host, role) - Add a secondary role to a user]]
function def_env.user:addrole(jid, host, new_role)
local username, userhost = jid_split(jid);
if new_role == nil then host, new_role = userhost, host; end
@@ -1855,10 +2003,14 @@ function def_env.user:addrole(jid, host, new_role)
elseif userhost ~= host then
return nil, "Can't add roles outside users own host"
end
- return um.add_user_secondary_role(username, host, new_role);
+ local role, err = um.add_user_secondary_role(username, host, new_role);
+ if not role then
+ return nil, err;
+ end
+ return true, "Role added";
end
-describe_command [[user:delrole(jid, host, role) - Remove a secondary role from a user]]
+hidden_command [[user:delrole(jid, host, role) - Remove a secondary role from a user]]
function def_env.user:delrole(jid, host, role_name)
local username, userhost = jid_split(jid);
if role_name == nil then host, role_name = userhost, host; end
@@ -1869,7 +2021,11 @@ function def_env.user:delrole(jid, host, role_name)
elseif userhost ~= host then
return nil, "Can't remove roles outside users own host"
end
- return um.remove_user_secondary_role(username, host, role_name);
+ local ok, err = um.remove_user_secondary_role(username, host, role_name);
+ if not ok then
+ return nil, err;
+ end
+ return true, "Role removed";
end
describe_command [[user:list(hostname, pattern) - List users on the specified host, optionally filtering with a pattern]]
@@ -2622,10 +2778,20 @@ local function new_item_handlers(command_host)
section_mt.help = section_help;
end
+ if command.flags then
+ if command.flags.stop_on_positional == nil then
+ command.flags.stop_on_positional = false;
+ end
+ if command.flags.strict == nil then
+ command.flags.strict = true;
+ end
+ end
+
section_help.commands[command.name] = {
desc = command.desc;
full = command.help;
args = array(command.args);
+ flags = command.flags;
module = command._provided_by;
};