Comparison

plugins/mod_admin_shell.lua @ 13349:d5d4e386c6fb

mod_admin_shell: Refactor help to data structures for extensibility This makes it easier for commands added by other modules to add to the help output, for example.
author Matthew Wild <mwild1@gmail.com>
date Wed, 29 Nov 2023 17:18:17 +0000
parent 13338:470ab6b55e93
child 13350:0573401b0f9f
comparison
equal deleted inserted replaced
13348:47ede3d51833 13349:d5d4e386c6fb
13 local hostmanager = require "prosody.core.hostmanager"; 13 local hostmanager = require "prosody.core.hostmanager";
14 local modulemanager = require "prosody.core.modulemanager"; 14 local modulemanager = require "prosody.core.modulemanager";
15 local s2smanager = require "prosody.core.s2smanager"; 15 local s2smanager = require "prosody.core.s2smanager";
16 local portmanager = require "prosody.core.portmanager"; 16 local portmanager = require "prosody.core.portmanager";
17 local helpers = require "prosody.util.helpers"; 17 local helpers = require "prosody.util.helpers";
18 local it = require "prosody.util.iterators";
18 local server = require "prosody.net.server"; 19 local server = require "prosody.net.server";
19 local st = require "prosody.util.stanza"; 20 local st = require "prosody.util.stanza";
20 21
21 local _G = _G; 22 local _G = _G;
22 23
61 62
62 local commands = module:shared("commands") 63 local commands = module:shared("commands")
63 local def_env = module:shared("env"); 64 local def_env = module:shared("env");
64 local default_env_mt = { __index = def_env }; 65 local default_env_mt = { __index = def_env };
65 66
67 local function new_section(section_desc)
68 return setmetatable({}, {
69 help = {
70 desc = section_desc;
71 commands = {};
72 };
73 });
74 end
75
76 local help_topics = {};
77 local function help_topic(name)
78 return function (desc)
79 return function (content)
80 help_topics[name] = {
81 desc = desc;
82 content = content;
83 };
84 end;
85 end
86 end
87
88 -- Seed with default sections and their description text
89 help_topic "console" "Help regarding the console itself" [[
90 Hey! Welcome to Prosody's admin console.
91 First thing, if you're ever wondering how to get out, simply type 'quit'.
92 Secondly, note that we don't support the full telnet protocol yet (it's coming)
93 so you may have trouble using the arrow keys, etc. depending on your system.
94
95 For now we offer a couple of handy shortcuts:
96 !! - Repeat the last command
97 !old!new! - repeat the last command, but with 'old' replaced by 'new'
98
99 For those well-versed in Prosody's internals, or taking instruction from those who are,
100 you can prefix a command with > to escape the console sandbox, and access everything in
101 the running server. Great fun, but be careful not to break anything :)
102 ]];
103
104 local available_columns; --forward declaration so it is reachable from the help
105
106 help_topic "columns" "Information about customizing session listings" (function (self, print)
107 print [[The columns shown by c2s:show() and s2s:show() can be customizied via the]]
108 print [['columns' argument as described here.]]
109 print [[]]
110 print [[Columns can be specified either as "id jid ipv" or as {"id", "jid", "ipv"}.]]
111 print [[Available columns are:]]
112 local meta_columns = {
113 { title = "ID"; width = 5 };
114 { title = "Column Title"; width = 12 };
115 { title = "Description"; width = 12 };
116 };
117 -- auto-adjust widths
118 for column, spec in pairs(available_columns) do
119 meta_columns[1].width = math.max(meta_columns[1].width or 0, #column);
120 meta_columns[2].width = math.max(meta_columns[2].width or 0, #(spec.title or ""));
121 meta_columns[3].width = math.max(meta_columns[3].width or 0, #(spec.description or ""));
122 end
123 local row = format_table(meta_columns, self.session.width)
124 print(row());
125 for column, spec in iterators.sorted_pairs(available_columns) do
126 print(row({ column, spec.title, spec.description }));
127 end
128 print [[]]
129 print [[Most fields on the internal session structures can also be used as columns]]
130 -- Also, you can pass a table column specification directly, with mapper callback and all
131 end);
132
133 help_topic "roles" "Show information about user roles" [[
134 Roles may grant access or restrict users from certain operations.
135
136 Built-in roles are:
137 prosody:guest - Guest/anonymous user
138 prosody:registered - Registered user
139 prosody:member - Provisioned user
140 prosody:admin - Host administrator
141 prosody:operator - Server administrator
142
143 Roles can be assigned using the user management commands (see 'help user').
144 ]];
145
146
66 local function redirect_output(target, session) 147 local function redirect_output(target, session)
67 local env = setmetatable({ print = session.print }, { __index = function (_, k) return rawget(target, k); end }); 148 local env = setmetatable({ print = session.print }, { __index = function (_, k) return rawget(target, k); end });
68 env.dofile = function(name) 149 env.dofile = function(name)
69 local f, err = envloadfile(name, env); 150 local f, err = envloadfile(name, env);
70 if not f then return f, err; end 151 if not f then return f, err; end
141 local line = event.stanza:get_text(); 222 local line = event.stanza:get_text();
142 local useglobalenv; 223 local useglobalenv;
143 224
144 local result = st.stanza("repl-result"); 225 local result = st.stanza("repl-result");
145 226
227 module:log("warn", "Processing line: %q", line);
228
146 if line:match("^>") then 229 if line:match("^>") then
147 line = line:gsub("^>", ""); 230 line = line:gsub("^>", "");
148 useglobalenv = true; 231 useglobalenv = true;
149 else 232 else
150 local command = line:match("^%w+") or line:match("%p"); 233 local command = line:match("^(%w+) ") or line:match("^%w+$") or line:match("%p");
151 if commands[command] then 234 if commands[command] then
152 commands[command](session, line); 235 commands[command](session, line);
153 event.origin.send(result); 236 event.origin.send(result);
154 return; 237 return;
155 end 238 end
211 event.origin.send(st.stanza("repl-result", { type = "error" }):text(err)); 294 event.origin.send(st.stanza("repl-result", { type = "error" }):text(err));
212 end 295 end
213 return true; 296 return true;
214 end); 297 end);
215 298
299 local function describe_command(s)
300 local section, name, args, desc = s:match("^([%w_]+):([%w_]+)%(([^)]*)%) %- (.+)$");
301 if not section then
302 error("Failed to parse command description: "..s);
303 end
304 local command_help = getmetatable(def_env[section]).help.commands;
305 command_help[name] = {
306 desc = desc;
307 args = array.collect(args:gmatch("[%w_]+")):map(function (name)
308 return { name = name };
309 end);
310 };
311 end
312
216 -- Console commands -- 313 -- Console commands --
217 -- These are simple commands, not valid standalone in Lua 314 -- These are simple commands, not valid standalone in Lua
218 315
219 local available_columns; --forward declaration so it is reachable from the help 316 -- Help about individual topics is handled by def_env.help
220
221 function commands.help(session, data) 317 function commands.help(session, data)
222 local print = session.print; 318 local print = session.print;
223 local section = data:match("^help (%w+)"); 319 local section = data:match("^help (%w+)");
224 if not section then 320 if not section then
225 print [[Commands are divided into multiple sections. For help on a particular section, ]] 321 print [[Commands are divided into multiple sections. For help on a particular section, ]]
226 print [[type: help SECTION (for example, 'help c2s'). Sections are: ]] 322 print [[type: help SECTION (for example, 'help c2s'). Sections are: ]]
227 print [[]] 323 print [[]]
228 local row = format_table({ { title = "Section", width = 7 }, { title = "Description", width = "100%" } }, session.width) 324 local row = format_table({ { title = "Section", width = 7 }, { title = "Description", width = "100%" } }, session.width)
229 print(row()) 325 print(row())
230 print(row { "c2s"; "Commands to manage local client-to-server sessions" }) 326 for section_name, section in it.sorted_pairs(def_env) do
231 print(row { "s2s"; "Commands to manage sessions between this server and others" }) 327 local section_mt = getmetatable(section);
232 print(row { "http"; "Commands to inspect HTTP services" }) -- XXX plural but there is only one so far 328 local section_help = section_mt and section_mt.help;
233 print(row { "module"; "Commands to load/reload/unload modules/plugins" }) 329 print(row { section_name; section_help and section_help.desc or "" });
234 print(row { "host"; "Commands to activate, deactivate and list virtual hosts" }) 330 end
235 print(row { "user"; "Commands to create and delete users, and change their passwords" }) 331 else
236 print(row { "roles"; "Show information about user roles" }) 332 return def_env.help[section]({ session = session });
237 print(row { "muc"; "Commands to create, list and manage chat rooms" }) 333 end
238 print(row { "stats"; "Commands to show internal statistics" }) 334
239 print(row { "server"; "Uptime, version, shutting down, etc." }) 335 print("");
240 print(row { "port"; "Commands to manage ports the server is listening on" }) 336
241 print(row { "dns"; "Commands to manage and inspect the internal DNS resolver" }) 337 print [[In addition to info about commands, the following general topics are available:]]
242 print(row { "xmpp"; "Commands for sending XMPP stanzas" }) 338
243 print(row { "debug"; "Commands for debugging the server" }) 339 print("");
244 print(row { "watch"; "Commands for watching live logs from the server" }) 340 for topic_name, topic in it.sorted_pairs(help_topics) do
245 print(row { "config"; "Reloading the configuration, etc." }) 341 print(topic_name .. " - "..topic.desc);
246 print(row { "columns"; "Information about customizing session listings" })
247 print(row { "console"; "Help regarding the console itself" })
248 elseif section == "c2s" then
249 print [[c2s:show(jid, columns) - Show all client sessions with the specified JID (or all if no JID given)]]
250 print [[c2s:show_tls(jid) - Show TLS cipher info for encrypted sessions]]
251 print [[c2s:count() - Count sessions without listing them]]
252 print [[c2s:close(jid) - Close all sessions for the specified JID]]
253 print [[c2s:closeall() - Close all active c2s connections ]]
254 elseif section == "s2s" then
255 print [[s2s:show(domain, columns) - Show all s2s connections for the given domain (or all if no domain given)]]
256 print [[s2s:show_tls(domain) - Show TLS cipher info for encrypted sessions]]
257 print [[s2s:close(from, to) - Close a connection from one domain to another]]
258 print [[s2s:closeall(host) - Close all the incoming/outgoing s2s sessions to specified host]]
259 elseif section == "http" then
260 print [[http:list(hosts) - Show HTTP endpoints]]
261 elseif section == "module" then
262 print [[module:info(module, host) - Show information about a loaded module]]
263 print [[module:load(module, host) - Load the specified module on the specified host (or all hosts if none given)]]
264 print [[module:reload(module, host) - The same, but unloads and loads the module (saving state if the module supports it)]]
265 print [[module:unload(module, host) - The same, but just unloads the module from memory]]
266 print [[module:list(host) - List the modules loaded on the specified host]]
267 elseif section == "host" then
268 print [[host:activate(hostname) - Activates the specified host]]
269 print [[host:deactivate(hostname) - Disconnects all clients on this host and deactivates]]
270 print [[host:list() - List the currently-activated hosts]]
271 elseif section == "user" then
272 print [[user:create(jid, password, role) - Create the specified user account]]
273 print [[user:password(jid, password) - Set the password for the specified user account]]
274 print [[user:roles(jid, host) - Show current roles for an user]]
275 print [[user:setrole(jid, host, role) - Set primary role of a user (see 'help roles')]]
276 print [[user:addrole(jid, host, role) - Add a secondary role to a user]]
277 print [[user:delrole(jid, host, role) - Remove a secondary role from a user]]
278 print [[user:disable(jid) - Disable the specified user account, preventing login]]
279 print [[user:enable(jid) - Enable the specified user account, restoring login access]]
280 print [[user:delete(jid) - Permanently remove the specified user account]]
281 print [[user:list(hostname, pattern) - List users on the specified host, optionally filtering with a pattern]]
282 elseif section == "roles" then
283 print [[Roles may grant access or restrict users from certain operations]]
284 print [[Built-in roles are:]]
285 print [[ prosody:guest - Guest/anonymous user]]
286 print [[ prosody:registered - Registered user]]
287 print [[ prosody:member - Provisioned user]]
288 print [[ prosody:admin - Host administrator]]
289 print [[ prosody:operator - Server administrator]]
290 print [[]]
291 print [[Roles can be assigned using the user management commands (see 'help user').]]
292 elseif section == "muc" then
293 -- TODO `muc:room():foo()` commands
294 print [[muc:create(roomjid, { config }) - Create the specified MUC room with the given config]]
295 print [[muc:list(host) - List rooms on the specified MUC component]]
296 print [[muc:room(roomjid) - Reference the specified MUC room to access MUC API methods]]
297 print [[muc:occupants(roomjid, filter) - List room occupants, optionally filtered on substring or role]]
298 print [[muc:affiliations(roomjid, filter) - List affiliated members of the room, optionally filtered on substring or affiliation]]
299 elseif section == "server" then
300 print [[server:version() - Show the server's version number]]
301 print [[server:uptime() - Show how long the server has been running]]
302 print [[server:memory() - Show details about the server's memory usage]]
303 print [[server:shutdown(reason) - Shut down the server, with an optional reason to be broadcast to all connections]]
304 elseif section == "port" then
305 print [[port:list() - Lists all network ports prosody currently listens on]]
306 print [[port:close(port, interface) - Close a port]]
307 elseif section == "dns" then
308 print [[dns:lookup(name, type, class) - Do a DNS lookup]]
309 print [[dns:addnameserver(nameserver) - Add a nameserver to the list]]
310 print [[dns:setnameserver(nameserver) - Replace the list of name servers with the supplied one]]
311 print [[dns:purge() - Clear the DNS cache]]
312 print [[dns:cache() - Show cached records]]
313 elseif section == "xmpp" then
314 print [[xmpp:ping(localhost, remotehost) -- Sends a ping to a remote XMPP server and reports the response]]
315 elseif section == "config" then
316 print [[config:reload() - Reload the server configuration. Modules may need to be reloaded for changes to take effect.]]
317 print [[config:get([host,] option) - Show the value of a config option.]]
318 print [[config:set([host,] option, value) - Update the value of a config option without writing to the config file.]]
319 elseif section == "stats" then -- luacheck: ignore 542
320 print [[stats:show(pattern) - Show internal statistics, optionally filtering by name with a pattern]]
321 print [[stats:show():cfgraph() - Show a cumulative frequency graph]]
322 print [[stats:show():histogram() - Show a histogram of selected metric]]
323 elseif section == "debug" then
324 print [[debug:async() - Show information about pending asynchronous tasks]]
325 print [[debug:logevents(host) - Enable logging of fired events on host]]
326 print [[debug:events(host, event) - Show registered event handlers]]
327 print [[debug:timers() - Show information about scheduled timers]]
328 elseif section == "watch" then
329 print [[watch:log() - Follow debug logs]]
330 print [[watch:stanzas(target, filter) - Watch live stanzas matching the specified target and filter]]
331 elseif section == "console" then
332 print [[Hey! Welcome to Prosody's admin console.]]
333 print [[First thing, if you're ever wondering how to get out, simply type 'quit'.]]
334 print [[Secondly, note that we don't support the full telnet protocol yet (it's coming)]]
335 print [[so you may have trouble using the arrow keys, etc. depending on your system.]]
336 print [[]]
337 print [[For now we offer a couple of handy shortcuts:]]
338 print [[!! - Repeat the last command]]
339 print [[!old!new! - repeat the last command, but with 'old' replaced by 'new']]
340 print [[]]
341 print [[For those well-versed in Prosody's internals, or taking instruction from those who are,]]
342 print [[you can prefix a command with > to escape the console sandbox, and access everything in]]
343 print [[the running server. Great fun, but be careful not to break anything :)]]
344 elseif section == "columns" then
345 print [[The columns shown by c2s:show() and s2s:show() can be customizied via the]]
346 print [['columns' argument as described here.]]
347 print [[]]
348 print [[Columns can be specified either as "id jid ipv" or as {"id", "jid", "ipv"}.]]
349 print [[Available columns are:]]
350 local meta_columns = {
351 { title = "ID"; width = 5 };
352 { title = "Column Title"; width = 12 };
353 { title = "Description"; width = 12 };
354 };
355 -- auto-adjust widths
356 for column, spec in pairs(available_columns) do
357 meta_columns[1].width = math.max(meta_columns[1].width or 0, #column);
358 meta_columns[2].width = math.max(meta_columns[2].width or 0, #(spec.title or ""));
359 meta_columns[3].width = math.max(meta_columns[3].width or 0, #(spec.description or ""));
360 end
361 local row = format_table(meta_columns, session.width)
362 print(row());
363 for column, spec in iterators.sorted_pairs(available_columns) do
364 print(row({ column, spec.title, spec.description }));
365 end
366 print [[]]
367 print [[Most fields on the internal session structures can also be used as columns]]
368 -- Also, you can pass a table column specification directly, with mapper callback and all
369 end 342 end
370 end 343 end
371 344
372 -- Session environment -- 345 -- Session environment --
373 -- Anything in def_env will be accessible within the session as a global variable 346 -- Anything in def_env will be accessible within the session as a global variable
377 preset = "pretty"; 350 preset = "pretty";
378 maxdepth = 2; 351 maxdepth = 2;
379 table_iterator = "pairs"; 352 table_iterator = "pairs";
380 }) 353 })
381 354
382 def_env.output = {}; 355 def_env.output = new_section("Configure admin console output");
383 function def_env.output:configure(opts) 356 function def_env.output:configure(opts)
384 if type(opts) ~= "table" then 357 if type(opts) ~= "table" then
385 opts = { preset = opts }; 358 opts = { preset = opts };
386 end 359 end
387 if not opts.fallback then 360 if not opts.fallback then
399 opts.table_iterator = nil; -- rawpairs is the default 372 opts.table_iterator = nil; -- rawpairs is the default
400 end 373 end
401 self.session.serialize = serialization.new(opts); 374 self.session.serialize = serialization.new(opts);
402 end 375 end
403 376
404 def_env.server = {}; 377 def_env.help = setmetatable({}, {
378 help = {
379 desc = "Show this help about available commands";
380 commands = {};
381 };
382 __index = function (_, section_name)
383 return function (self)
384 local print = self.session.print;
385 local section_mt = getmetatable(def_env[section_name]);
386 local section_help = section_mt and section_mt.help;
387
388 local c = 0;
389
390 if section_help then
391 print("Help: "..section_name);
392 if section_help.desc then
393 print(section_help.desc);
394 end
395 print(("-"):rep(#(section_help.desc or section_name)));
396 print("");
397
398 if section_help.content then
399 print(section_help.content);
400 print("");
401 end
402
403 for command, command_help in it.sorted_pairs(section_help.commands or {}) do
404 c = c + 1;
405 local args = command_help.args:pluck("name"):concat(", ");
406 local desc = command_help.desc or command_help.module and ("Provided by mod_"..command_help.module) or "";
407 print(("%s:%s(%s) - %s"):format(section_name, command, args, desc));
408 end
409 elseif help_topics[section_name] then
410 local topic = help_topics[section_name];
411 if type(topic.content) == "function" then
412 topic.content(self, print);
413 else
414 print(topic.content);
415 end
416 print("");
417 return true, "Showing help topic '"..section_name.."'";
418 else
419 print("Unknown topic: "..section_name);
420 end
421 print("");
422 return true, ("%d command(s) listed"):format(c);
423 end;
424 end;
425 });
426
427 def_env.server = new_section("Uptime, version, shutting down, etc.");
405 428
406 function def_env.server:insane_reload() 429 function def_env.server:insane_reload()
407 prosody.unlock_globals(); 430 prosody.unlock_globals();
408 dofile "prosody" 431 dofile "prosody"
409 prosody = _G.prosody; 432 prosody = _G.prosody;
410 return true, "Server reloaded"; 433 return true, "Server reloaded";
411 end 434 end
412 435
436 describe_command [[server:version() - Show the server's version number]]
413 function def_env.server:version() 437 function def_env.server:version()
414 return true, tostring(prosody.version or "unknown"); 438 return true, tostring(prosody.version or "unknown");
415 end 439 end
416 440
441 describe_command [[server:uptime() - Show how long the server has been running]]
417 function def_env.server:uptime() 442 function def_env.server:uptime()
418 local t = os.time()-prosody.start_time; 443 local t = os.time()-prosody.start_time;
419 local seconds = t%60; 444 local seconds = t%60;
420 t = (t - seconds)/60; 445 t = (t - seconds)/60;
421 local minutes = t%60; 446 local minutes = t%60;
426 return true, string.format("This server has been running for %d day%s, %d hour%s and %d minute%s (since %s)", 451 return true, string.format("This server has been running for %d day%s, %d hour%s and %d minute%s (since %s)",
427 days, (days ~= 1 and "s") or "", hours, (hours ~= 1 and "s") or "", 452 days, (days ~= 1 and "s") or "", hours, (hours ~= 1 and "s") or "",
428 minutes, (minutes ~= 1 and "s") or "", os.date("%c", prosody.start_time)); 453 minutes, (minutes ~= 1 and "s") or "", os.date("%c", prosody.start_time));
429 end 454 end
430 455
456 describe_command [[server:shutdown(reason) - Shut down the server, with an optional reason to be broadcast to all connections]]
431 function def_env.server:shutdown(reason, code) 457 function def_env.server:shutdown(reason, code)
432 prosody.shutdown(reason, code); 458 prosody.shutdown(reason, code);
433 return true, "Shutdown initiated"; 459 return true, "Shutdown initiated";
434 end 460 end
435 461
436 local function human(kb) 462 local function human(kb)
437 return format_number(kb*1024, "B", "b"); 463 return format_number(kb*1024, "B", "b");
438 end 464 end
439 465
466 describe_command [[server:memory() - Show details about the server's memory usage]]
440 function def_env.server:memory() 467 function def_env.server:memory()
441 if not has_pposix or not pposix.meminfo then 468 if not has_pposix or not pposix.meminfo then
442 return true, "Lua is using "..human(collectgarbage("count")); 469 return true, "Lua is using "..human(collectgarbage("count"));
443 end 470 end
444 local mem, lua_mem = pposix.meminfo(), collectgarbage("count"); 471 local mem, lua_mem = pposix.meminfo(), collectgarbage("count");
447 print(" Used: "..human(mem.used/1024).." ("..human(lua_mem).." by Lua)"); 474 print(" Used: "..human(mem.used/1024).." ("..human(lua_mem).." by Lua)");
448 print(" Free: "..human(mem.unused/1024).." ("..human(mem.returnable/1024).." returnable)"); 475 print(" Free: "..human(mem.unused/1024).." ("..human(mem.returnable/1024).." returnable)");
449 return true, "OK"; 476 return true, "OK";
450 end 477 end
451 478
452 def_env.module = {}; 479 def_env.module = new_section("Commands to load/reload/unload modules/plugins");
453 480
454 local function get_hosts_set(hosts) 481 local function get_hosts_set(hosts)
455 if type(hosts) == "table" then 482 if type(hosts) == "table" then
456 if hosts[1] then 483 if hosts[1] then
457 return set.new(hosts); 484 return set.new(hosts);
493 hosts_set:add("*"); 520 hosts_set:add("*");
494 end 521 end
495 return hosts_set; 522 return hosts_set;
496 end 523 end
497 524
525 describe_command [[module:info(module, host) - Show information about a loaded module]]
498 function def_env.module:info(name, hosts) 526 function def_env.module:info(name, hosts)
499 if not name then 527 if not name then
500 return nil, "module name expected"; 528 return nil, "module name expected";
501 end 529 end
502 local print = self.session.print; 530 local print = self.session.print;
597 end 625 end
598 end 626 end
599 return true; 627 return true;
600 end 628 end
601 629
630 describe_command [[module:load(module, host) - Load the specified module on the specified host (or all hosts if none given)]]
602 function def_env.module:load(name, hosts) 631 function def_env.module:load(name, hosts)
603 hosts = get_hosts_with_module(hosts); 632 hosts = get_hosts_with_module(hosts);
604 633
605 -- Load the module for each host 634 -- Load the module for each host
606 local ok, err, count, mod = true, nil, 0; 635 local ok, err, count, mod = true, nil, 0;
630 end 659 end
631 660
632 return ok, (ok and "Module loaded onto "..count.." host"..(count ~= 1 and "s" or "")) or ("Last error: "..tostring(err)); 661 return ok, (ok and "Module loaded onto "..count.." host"..(count ~= 1 and "s" or "")) or ("Last error: "..tostring(err));
633 end 662 end
634 663
664 describe_command [[module:unload(module, host) - The same, but just unloads the module from memory]]
635 function def_env.module:unload(name, hosts) 665 function def_env.module:unload(name, hosts)
636 hosts = get_hosts_with_module(hosts, name); 666 hosts = get_hosts_with_module(hosts, name);
637 667
638 -- Unload the module for each host 668 -- Unload the module for each host
639 local ok, err, count = true, nil, 0; 669 local ok, err, count = true, nil, 0;
662 if a == "*" then return true 692 if a == "*" then return true
663 elseif b == "*" then return false 693 elseif b == "*" then return false
664 else return a:gsub("[^.]+", string.reverse):reverse() < b:gsub("[^.]+", string.reverse):reverse(); end 694 else return a:gsub("[^.]+", string.reverse):reverse() < b:gsub("[^.]+", string.reverse):reverse(); end
665 end 695 end
666 696
697 describe_command [[module:reload(module, host) - The same, but unloads and loads the module (saving state if the module supports it)]]
667 function def_env.module:reload(name, hosts) 698 function def_env.module:reload(name, hosts)
668 hosts = array.collect(get_hosts_with_module(hosts, name)):sort(_sort_hosts) 699 hosts = array.collect(get_hosts_with_module(hosts, name)):sort(_sort_hosts)
669 700
670 -- Reload the module for each host 701 -- Reload the module for each host
671 local ok, err, count = true, nil, 0; 702 local ok, err, count = true, nil, 0;
685 end 716 end
686 end 717 end
687 return ok, (ok and "Module reloaded on "..count.." host"..(count ~= 1 and "s" or "")) or ("Last error: "..tostring(err)); 718 return ok, (ok and "Module reloaded on "..count.." host"..(count ~= 1 and "s" or "")) or ("Last error: "..tostring(err));
688 end 719 end
689 720
721 describe_command [[module:list(host) - List the modules loaded on the specified host]]
690 function def_env.module:list(hosts) 722 function def_env.module:list(hosts)
691 hosts = array.collect(set.new({ not hosts and "*" or nil }) + get_hosts_set(hosts)):sort(_sort_hosts); 723 hosts = array.collect(set.new({ not hosts and "*" or nil }) + get_hosts_set(hosts)):sort(_sort_hosts);
692 724
693 local print = self.session.print; 725 local print = self.session.print;
694 for _, host in ipairs(hosts) do 726 for _, host in ipairs(hosts) do
711 end 743 end
712 end 744 end
713 end 745 end
714 end 746 end
715 747
716 def_env.config = {}; 748 def_env.config = new_section("Reloading the configuration, etc.");
749
717 function def_env.config:load(filename, format) 750 function def_env.config:load(filename, format)
718 local config_load = require "prosody.core.configmanager".load; 751 local config_load = require "prosody.core.configmanager".load;
719 local ok, err = config_load(filename, format); 752 local ok, err = config_load(filename, format);
720 if not ok then 753 if not ok then
721 return false, err or "Unknown error loading config"; 754 return false, err or "Unknown error loading config";
722 end 755 end
723 return true, "Config loaded"; 756 return true, "Config loaded";
724 end 757 end
725 758
759 describe_command [[config:get([host,] option) - Show the value of a config option.]]
726 function def_env.config:get(host, key) 760 function def_env.config:get(host, key)
727 if key == nil then 761 if key == nil then
728 host, key = "*", host; 762 host, key = "*", host;
729 end 763 end
730 local config_get = require "prosody.core.configmanager".get 764 local config_get = require "prosody.core.configmanager".get
731 return true, serialize_config(config_get(host, key)); 765 return true, serialize_config(config_get(host, key));
732 end 766 end
733 767
768 describe_command [[config:set([host,] option, value) - Update the value of a config option without writing to the config file.]]
734 function def_env.config:set(host, key, value) 769 function def_env.config:set(host, key, value)
735 if host ~= "*" and not prosody.hosts[host] then 770 if host ~= "*" and not prosody.hosts[host] then
736 host, key, value = "*", host, key; 771 host, key, value = "*", host, key;
737 end 772 end
738 return require "prosody.core.configmanager".set(host, key, value); 773 return require "prosody.core.configmanager".set(host, key, value);
739 end 774 end
740 775
776 describe_command [[config:reload() - Reload the server configuration. Modules may need to be reloaded for changes to take effect.]]
741 function def_env.config:reload() 777 function def_env.config:reload()
742 local ok, err = prosody.reload_config(); 778 local ok, err = prosody.reload_config();
743 return ok, (ok and "Config reloaded (you may need to reload modules to take effect)") or tostring(err); 779 return ok, (ok and "Config reloaded (you may need to reload modules to take effect)") or tostring(err);
744 end 780 end
745 781
746 def_env.c2s = {}; 782 def_env.c2s = new_section("Commands to manage local client-to-server sessions");
747 783
748 local function get_jid(session) 784 local function get_jid(session)
749 if session.username then 785 if session.username then
750 return session.full_jid or jid_join(session.username, session.host, session.resource); 786 return session.full_jid or jid_join(session.username, session.host, session.resource);
751 end 787 end
778 get_c2s():sort(_sort_by_jid):map(function (session) 814 get_c2s():sort(_sort_by_jid):map(function (session)
779 callback(get_jid(session), session) 815 callback(get_jid(session), session)
780 end); 816 end);
781 end 817 end
782 818
819 describe_command [[c2s:count() - Count sessions without listing them]]
783 function def_env.c2s:count() 820 function def_env.c2s:count()
784 local c2s = get_c2s(); 821 local c2s = get_c2s();
785 return true, "Total: ".. #c2s .." clients"; 822 return true, "Total: ".. #c2s .." clients";
786 end 823 end
787 824
1042 end 1079 end
1043 1080
1044 return columns; 1081 return columns;
1045 end 1082 end
1046 1083
1084 describe_command [[c2s:show(jid, columns) - Show all client sessions with the specified JID (or all if no JID given)]]
1047 function def_env.c2s:show(match_jid, colspec) 1085 function def_env.c2s:show(match_jid, colspec)
1048 local print = self.session.print; 1086 local print = self.session.print;
1049 local columns = get_colspec(colspec, { "id"; "jid"; "role"; "ipv"; "status"; "secure"; "smacks"; "csi" }); 1087 local columns = get_colspec(colspec, { "id"; "jid"; "role"; "ipv"; "status"; "secure"; "smacks"; "csi" });
1050 local row = format_table(columns, self.session.width); 1088 local row = format_table(columns, self.session.width);
1051 1089
1082 return true, ("%d out of %d c2s sessions shown"):format(shown_count, total_count); 1120 return true, ("%d out of %d c2s sessions shown"):format(shown_count, total_count);
1083 end 1121 end
1084 return true, ("%d c2s sessions shown"):format(total_count); 1122 return true, ("%d c2s sessions shown"):format(total_count);
1085 end 1123 end
1086 1124
1125 describe_command [[c2s:show_tls(jid) - Show TLS cipher info for encrypted sessions]]
1087 function def_env.c2s:show_tls(match_jid) 1126 function def_env.c2s:show_tls(match_jid)
1088 return self:show(match_jid, { "jid"; "id"; "secure"; "encryption" }); 1127 return self:show(match_jid, { "jid"; "id"; "secure"; "encryption" });
1089 end 1128 end
1090 1129
1091 local function build_reason(text, condition) 1130 local function build_reason(text, condition)
1095 condition = condition or "undefined-condition", 1134 condition = condition or "undefined-condition",
1096 }; 1135 };
1097 end 1136 end
1098 end 1137 end
1099 1138
1139 describe_command [[c2s:close(jid) - Close all sessions for the specified JID]]
1100 function def_env.c2s:close(match_jid, text, condition) 1140 function def_env.c2s:close(match_jid, text, condition)
1101 local count = 0; 1141 local count = 0;
1102 show_c2s(function (jid, session) 1142 show_c2s(function (jid, session)
1103 if jid == match_jid or jid_bare(jid) == match_jid then 1143 if jid == match_jid or jid_bare(jid) == match_jid then
1104 count = count + 1; 1144 count = count + 1;
1106 end 1146 end
1107 end); 1147 end);
1108 return true, "Total: "..count.." sessions closed"; 1148 return true, "Total: "..count.." sessions closed";
1109 end 1149 end
1110 1150
1151 describe_command [[c2s:closeall() - Close all active c2s connections ]]
1111 function def_env.c2s:closeall(text, condition) 1152 function def_env.c2s:closeall(text, condition)
1112 local count = 0; 1153 local count = 0;
1113 --luacheck: ignore 212/jid 1154 --luacheck: ignore 212/jid
1114 show_c2s(function (jid, session) 1155 show_c2s(function (jid, session)
1115 count = count + 1; 1156 count = count + 1;
1117 end); 1158 end);
1118 return true, "Total: "..count.." sessions closed"; 1159 return true, "Total: "..count.." sessions closed";
1119 end 1160 end
1120 1161
1121 1162
1122 def_env.s2s = {}; 1163 def_env.s2s = new_section("Commands to manage sessions between this server and others");
1164
1123 local function _sort_s2s(a, b) 1165 local function _sort_s2s(a, b)
1124 local a_local, a_remote = get_s2s_hosts(a); 1166 local a_local, a_remote = get_s2s_hosts(a);
1125 local b_local, b_remote = get_s2s_hosts(b); 1167 local b_local, b_remote = get_s2s_hosts(b);
1126 if (a_local or "") == (b_local or "") then return _sort_hosts(a_remote or "", b_remote or ""); end 1168 if (a_local or "") == (b_local or "") then return _sort_hosts(a_remote or "", b_remote or ""); end
1127 return _sort_hosts(a_local or "", b_local or ""); 1169 return _sort_hosts(a_local or "", b_local or "");
1142 return match_wildcard(match_jid, host) or match_wildcard(match_jid, remote); 1184 return match_wildcard(match_jid, host) or match_wildcard(match_jid, remote);
1143 end 1185 end
1144 return false; 1186 return false;
1145 end 1187 end
1146 1188
1189 describe_command [[s2s:show(domain, columns) - Show all s2s connections for the given domain (or all if no domain given)]]
1147 function def_env.s2s:show(match_jid, colspec) 1190 function def_env.s2s:show(match_jid, colspec)
1148 local print = self.session.print; 1191 local print = self.session.print;
1149 local columns = get_colspec(colspec, { "id"; "host"; "dir"; "remote"; "ipv"; "secure"; "s2s_sasl"; "dialback" }); 1192 local columns = get_colspec(colspec, { "id"; "host"; "dir"; "remote"; "ipv"; "secure"; "s2s_sasl"; "dialback" });
1150 local row = format_table(columns, self.session.width); 1193 local row = format_table(columns, self.session.width);
1151 1194
1182 return true, ("%d out of %d s2s connections shown"):format(shown_count, total_count); 1225 return true, ("%d out of %d s2s connections shown"):format(shown_count, total_count);
1183 end 1226 end
1184 return true, ("%d s2s connections shown"):format(total_count); 1227 return true, ("%d s2s connections shown"):format(total_count);
1185 end 1228 end
1186 1229
1230 describe_command [[s2s:show_tls(domain) - Show TLS cipher info for encrypted sessions]]
1187 function def_env.s2s:show_tls(match_jid) 1231 function def_env.s2s:show_tls(match_jid)
1188 return self:show(match_jid, { "id"; "host"; "dir"; "remote"; "secure"; "encryption"; "cert" }); 1232 return self:show(match_jid, { "id"; "host"; "dir"; "remote"; "secure"; "encryption"; "cert" });
1189 end 1233 end
1190 1234
1191 local function print_subject(print, subject) 1235 local function print_subject(print, subject)
1304 return ("Showing "..n_certs.." certificate" 1348 return ("Showing "..n_certs.." certificate"
1305 ..(n_certs==1 and "" or "s") 1349 ..(n_certs==1 and "" or "s")
1306 .." presented by "..domain.."."); 1350 .." presented by "..domain..".");
1307 end 1351 end
1308 1352
1353 describe_command [[s2s:close(from, to) - Close a connection from one domain to another]]
1309 function def_env.s2s:close(from, to, text, condition) 1354 function def_env.s2s:close(from, to, text, condition)
1310 local print, count = self.session.print, 0; 1355 local print, count = self.session.print, 0;
1311 local s2s_sessions = module:shared"/*/s2s/sessions"; 1356 local s2s_sessions = module:shared"/*/s2s/sessions";
1312 1357
1313 local match_id; 1358 local match_id;
1328 end 1373 end
1329 end 1374 end
1330 return true, "Closed "..count.." s2s session"..((count == 1 and "") or "s"); 1375 return true, "Closed "..count.." s2s session"..((count == 1 and "") or "s");
1331 end 1376 end
1332 1377
1378 describe_command [[s2s:closeall(host) - Close all the incoming/outgoing s2s sessions to specified host]]
1333 function def_env.s2s:closeall(host, text, condition) 1379 function def_env.s2s:closeall(host, text, condition)
1334 local count = 0; 1380 local count = 0;
1335 local s2s_sessions = module:shared"/*/s2s/sessions"; 1381 local s2s_sessions = module:shared"/*/s2s/sessions";
1336 for _,session in pairs(s2s_sessions) do 1382 for _,session in pairs(s2s_sessions) do
1337 if not host or host == "*" or match_s2s_jid(session, host) then 1383 if not host or host == "*" or match_s2s_jid(session, host) then
1341 end 1387 end
1342 if count == 0 then return false, "No sessions to close."; 1388 if count == 0 then return false, "No sessions to close.";
1343 else return true, "Closed "..count.." s2s session"..((count == 1 and "") or "s"); end 1389 else return true, "Closed "..count.." s2s session"..((count == 1 and "") or "s"); end
1344 end 1390 end
1345 1391
1346 def_env.host = {}; def_env.hosts = def_env.host; 1392 def_env.host = new_section("Commands to activate, deactivate and list virtual hosts");
1347 1393
1394 describe_command [[host:activate(hostname) - Activates the specified host]]
1348 function def_env.host:activate(hostname, config) 1395 function def_env.host:activate(hostname, config)
1349 return hostmanager.activate(hostname, config); 1396 return hostmanager.activate(hostname, config);
1350 end 1397 end
1398
1399 describe_command [[host:deactivate(hostname) - Disconnects all clients on this host and deactivates]]
1351 function def_env.host:deactivate(hostname, reason) 1400 function def_env.host:deactivate(hostname, reason)
1352 return hostmanager.deactivate(hostname, reason); 1401 return hostmanager.deactivate(hostname, reason);
1353 end 1402 end
1354 1403
1404 describe_command [[host:list() - List the currently-activated hosts]]
1355 function def_env.host:list() 1405 function def_env.host:list()
1356 local print = self.session.print; 1406 local print = self.session.print;
1357 local i = 0; 1407 local i = 0;
1358 local host_type; 1408 local host_type;
1359 for host, host_session in iterators.sorted_pairs(prosody.hosts, _sort_hosts) do 1409 for host, host_session in iterators.sorted_pairs(prosody.hosts, _sort_hosts) do
1370 end 1420 end
1371 end 1421 end
1372 return true, i.." hosts"; 1422 return true, i.." hosts";
1373 end 1423 end
1374 1424
1375 def_env.port = {}; 1425 def_env.port = new_section("Commands to manage ports the server is listening on");
1376 1426
1427 describe_command [[port:list() - Lists all network ports prosody currently listens on]]
1377 function def_env.port:list() 1428 function def_env.port:list()
1378 local print = self.session.print; 1429 local print = self.session.print;
1379 local services = portmanager.get_active_services().data; 1430 local services = portmanager.get_active_services().data;
1380 local n_services, n_ports = 0, 0; 1431 local n_services, n_ports = 0, 0;
1381 for service, interfaces in iterators.sorted_pairs(services) do 1432 for service, interfaces in iterators.sorted_pairs(services) do
1390 print(service..": "..table.concat(ports_list, ", ")); 1441 print(service..": "..table.concat(ports_list, ", "));
1391 end 1442 end
1392 return true, n_services.." services listening on "..n_ports.." ports"; 1443 return true, n_services.." services listening on "..n_ports.." ports";
1393 end 1444 end
1394 1445
1446 describe_command [[port:close(port, interface) - Close a port]]
1395 function def_env.port:close(close_port, close_interface) 1447 function def_env.port:close(close_port, close_interface)
1396 close_port = assert(tonumber(close_port), "Invalid port number"); 1448 close_port = assert(tonumber(close_port), "Invalid port number");
1397 local n_closed = 0; 1449 local n_closed = 0;
1398 local services = portmanager.get_active_services().data; 1450 local services = portmanager.get_active_services().data;
1399 for service, interfaces in pairs(services) do -- luacheck: ignore 213 1451 for service, interfaces in pairs(services) do -- luacheck: ignore 213
1412 end 1464 end
1413 end 1465 end
1414 return true, "Closed "..n_closed.." ports"; 1466 return true, "Closed "..n_closed.." ports";
1415 end 1467 end
1416 1468
1417 def_env.muc = {}; 1469 def_env.muc = new_section("Commands to create, list and manage chat rooms");
1418 1470
1419 local console_room_mt = { 1471 local console_room_mt = {
1420 __index = function (self, k) return self.room[k]; end; 1472 __index = function (self, k) return self.room[k]; end;
1421 __tostring = function (self) 1473 __tostring = function (self)
1422 return "MUC room <"..self.room.jid..">"; 1474 return "MUC room <"..self.room.jid..">";
1445 return room_obj; 1497 return room_obj;
1446 end 1498 end
1447 1499
1448 local muc_util = module:require"muc/util"; 1500 local muc_util = module:require"muc/util";
1449 1501
1502 describe_command [[muc:create(roomjid, { config }) - Create the specified MUC room with the given config]]
1450 function def_env.muc:create(room_jid, config) 1503 function def_env.muc:create(room_jid, config)
1451 local room_name, host = check_muc(room_jid); 1504 local room_name, host = check_muc(room_jid);
1452 if not room_name then 1505 if not room_name then
1453 return room_name, host; 1506 return room_name, host;
1454 end 1507 end
1456 if config ~= nil and type(config) ~= "table" then return nil, "Config must be a table"; end 1509 if config ~= nil and type(config) ~= "table" then return nil, "Config must be a table"; end
1457 if prosody.hosts[host].modules.muc.get_room_from_jid(room_jid) then return nil, "Room exists already" end 1510 if prosody.hosts[host].modules.muc.get_room_from_jid(room_jid) then return nil, "Room exists already" end
1458 return prosody.hosts[host].modules.muc.create_room(room_jid, config); 1511 return prosody.hosts[host].modules.muc.create_room(room_jid, config);
1459 end 1512 end
1460 1513
1514 describe_command [[muc:room(roomjid) - Reference the specified MUC room to access MUC API methods]]
1461 function def_env.muc:room(room_jid) 1515 function def_env.muc:room(room_jid)
1462 local room_obj, err = get_muc(room_jid); 1516 local room_obj, err = get_muc(room_jid);
1463 if not room_obj then 1517 if not room_obj then
1464 return room_obj, err; 1518 return room_obj, err;
1465 end 1519 end
1466 return setmetatable({ room = room_obj }, console_room_mt); 1520 return setmetatable({ room = room_obj }, console_room_mt);
1467 end 1521 end
1468 1522
1523 describe_command [[muc:list(host) - List rooms on the specified MUC component]]
1469 function def_env.muc:list(host) 1524 function def_env.muc:list(host)
1470 local host_session = prosody.hosts[host]; 1525 local host_session = prosody.hosts[host];
1471 if not host_session or not host_session.modules.muc then 1526 if not host_session or not host_session.modules.muc then
1472 return nil, "Please supply the address of a local MUC component"; 1527 return nil, "Please supply the address of a local MUC component";
1473 end 1528 end
1478 c = c + 1; 1533 c = c + 1;
1479 end 1534 end
1480 return true, c.." rooms"; 1535 return true, c.." rooms";
1481 end 1536 end
1482 1537
1538 describe_command [[muc:occupants(roomjid, filter) - List room occupants, optionally filtered on substring or role]]
1483 function def_env.muc:occupants(room_jid, filter) 1539 function def_env.muc:occupants(room_jid, filter)
1484 local room_obj, err = get_muc(room_jid); 1540 local room_obj, err = get_muc(room_jid);
1485 if not room_obj then 1541 if not room_obj then
1486 return room_obj, err; 1542 return room_obj, err;
1487 end 1543 end
1522 else 1578 else
1523 return true, ("%d out of %d occupant%s listed"):format(displayed, total, total ~= 1 and "s" or "") 1579 return true, ("%d out of %d occupant%s listed"):format(displayed, total, total ~= 1 and "s" or "")
1524 end 1580 end
1525 end 1581 end
1526 1582
1583 describe_command [[muc:affiliations(roomjid, filter) - List affiliated members of the room, optionally filtered on substring or affiliation]]
1527 function def_env.muc:affiliations(room_jid, filter) 1584 function def_env.muc:affiliations(room_jid, filter)
1528 local room_obj, err = get_muc(room_jid); 1585 local room_obj, err = get_muc(room_jid);
1529 if not room_obj then 1586 if not room_obj then
1530 return room_obj, err; 1587 return room_obj, err;
1531 end 1588 end
1574 end 1631 end
1575 end 1632 end
1576 1633
1577 local um = require"prosody.core.usermanager"; 1634 local um = require"prosody.core.usermanager";
1578 1635
1579 def_env.user = {}; 1636 def_env.user = new_section("Commands to create and delete users, and change their passwords");
1637
1638 describe_command [[user:create(jid, password, role) - Create the specified user account]]
1580 function def_env.user:create(jid, password, role) 1639 function def_env.user:create(jid, password, role)
1581 local username, host = jid_split(jid); 1640 local username, host = jid_split(jid);
1582 if not prosody.hosts[host] then 1641 if not prosody.hosts[host] then
1583 return nil, "No such host: "..host; 1642 return nil, "No such host: "..host;
1584 elseif um.user_exists(username, host) then 1643 elseif um.user_exists(username, host) then
1595 end 1654 end
1596 1655
1597 return true, ("Created %s with role '%s'"):format(jid, role); 1656 return true, ("Created %s with role '%s'"):format(jid, role);
1598 end 1657 end
1599 1658
1659 describe_command [[user:disable(jid) - Disable the specified user account, preventing login]]
1600 function def_env.user:disable(jid) 1660 function def_env.user:disable(jid)
1601 local username, host = jid_split(jid); 1661 local username, host = jid_split(jid);
1602 if not prosody.hosts[host] then 1662 if not prosody.hosts[host] then
1603 return nil, "No such host: "..host; 1663 return nil, "No such host: "..host;
1604 elseif not um.user_exists(username, host) then 1664 elseif not um.user_exists(username, host) then
1610 else 1670 else
1611 return nil, "Could not disable user: "..err; 1671 return nil, "Could not disable user: "..err;
1612 end 1672 end
1613 end 1673 end
1614 1674
1675 describe_command [[user:enable(jid) - Enable the specified user account, restoring login access]]
1615 function def_env.user:enable(jid) 1676 function def_env.user:enable(jid)
1616 local username, host = jid_split(jid); 1677 local username, host = jid_split(jid);
1617 if not prosody.hosts[host] then 1678 if not prosody.hosts[host] then
1618 return nil, "No such host: "..host; 1679 return nil, "No such host: "..host;
1619 elseif not um.user_exists(username, host) then 1680 elseif not um.user_exists(username, host) then
1625 else 1686 else
1626 return nil, "Could not enable user: "..err; 1687 return nil, "Could not enable user: "..err;
1627 end 1688 end
1628 end 1689 end
1629 1690
1691 describe_command [[user:delete(jid) - Permanently remove the specified user account]]
1630 function def_env.user:delete(jid) 1692 function def_env.user:delete(jid)
1631 local username, host = jid_split(jid); 1693 local username, host = jid_split(jid);
1632 if not prosody.hosts[host] then 1694 if not prosody.hosts[host] then
1633 return nil, "No such host: "..host; 1695 return nil, "No such host: "..host;
1634 elseif not um.user_exists(username, host) then 1696 elseif not um.user_exists(username, host) then
1640 else 1702 else
1641 return nil, "Could not delete user: "..err; 1703 return nil, "Could not delete user: "..err;
1642 end 1704 end
1643 end 1705 end
1644 1706
1707 describe_command [[user:password(jid, password) - Set the password for the specified user account]]
1645 function def_env.user:password(jid, password) 1708 function def_env.user:password(jid, password)
1646 local username, host = jid_split(jid); 1709 local username, host = jid_split(jid);
1647 if not prosody.hosts[host] then 1710 if not prosody.hosts[host] then
1648 return nil, "No such host: "..host; 1711 return nil, "No such host: "..host;
1649 elseif not um.user_exists(username, host) then 1712 elseif not um.user_exists(username, host) then
1655 else 1718 else
1656 return nil, "Could not change password for user: "..err; 1719 return nil, "Could not change password for user: "..err;
1657 end 1720 end
1658 end 1721 end
1659 1722
1723 describe_command [[user:roles(jid, host) - Show current roles for an user]]
1660 function def_env.user:role(jid, host) 1724 function def_env.user:role(jid, host)
1661 local print = self.session.print; 1725 local print = self.session.print;
1662 local username, userhost = jid_split(jid); 1726 local username, userhost = jid_split(jid);
1663 if host == nil then host = userhost; end 1727 if host == nil then host = userhost; end
1664 if not prosody.hosts[host] then 1728 if not prosody.hosts[host] then
1680 1744
1681 return true, count == 1 and "1 role" or count.." roles"; 1745 return true, count == 1 and "1 role" or count.." roles";
1682 end 1746 end
1683 def_env.user.roles = def_env.user.role; 1747 def_env.user.roles = def_env.user.role;
1684 1748
1749 describe_command [[user:setrole(jid, host, role) - Set primary role of a user (see 'help roles')]]
1685 -- user:setrole("someone@example.com", "example.com", "prosody:admin") 1750 -- user:setrole("someone@example.com", "example.com", "prosody:admin")
1686 -- user:setrole("someone@example.com", "prosody:admin") 1751 -- user:setrole("someone@example.com", "prosody:admin")
1687 function def_env.user:setrole(jid, host, new_role) 1752 function def_env.user:setrole(jid, host, new_role)
1688 local username, userhost = jid_split(jid); 1753 local username, userhost = jid_split(jid);
1689 if new_role == nil then host, new_role = userhost, host; end 1754 if new_role == nil then host, new_role = userhost, host; end
1693 return nil, "No such user"; 1758 return nil, "No such user";
1694 end 1759 end
1695 return um.set_user_role(username, host, new_role); 1760 return um.set_user_role(username, host, new_role);
1696 end 1761 end
1697 1762
1763 describe_command [[user:addrole(jid, host, role) - Add a secondary role to a user]]
1698 function def_env.user:addrole(jid, host, new_role) 1764 function def_env.user:addrole(jid, host, new_role)
1699 local username, userhost = jid_split(jid); 1765 local username, userhost = jid_split(jid);
1700 if new_role == nil then host, new_role = userhost, host; end 1766 if new_role == nil then host, new_role = userhost, host; end
1701 if not prosody.hosts[host] then 1767 if not prosody.hosts[host] then
1702 return nil, "No such host: "..host; 1768 return nil, "No such host: "..host;
1704 return nil, "No such user"; 1770 return nil, "No such user";
1705 end 1771 end
1706 return um.add_user_secondary_role(username, host, new_role); 1772 return um.add_user_secondary_role(username, host, new_role);
1707 end 1773 end
1708 1774
1775 describe_command [[user:delrole(jid, host, role) - Remove a secondary role from a user]]
1709 function def_env.user:delrole(jid, host, role_name) 1776 function def_env.user:delrole(jid, host, role_name)
1710 local username, userhost = jid_split(jid); 1777 local username, userhost = jid_split(jid);
1711 if role_name == nil then host, role_name = userhost, host; end 1778 if role_name == nil then host, role_name = userhost, host; end
1712 if not prosody.hosts[host] then 1779 if not prosody.hosts[host] then
1713 return nil, "No such host: "..host; 1780 return nil, "No such host: "..host;
1715 return nil, "No such user"; 1782 return nil, "No such user";
1716 end 1783 end
1717 return um.remove_user_secondary_role(username, host, role_name); 1784 return um.remove_user_secondary_role(username, host, role_name);
1718 end 1785 end
1719 1786
1787 describe_command [[user:list(hostname, pattern) - List users on the specified host, optionally filtering with a pattern]]
1720 -- TODO switch to table view, include roles 1788 -- TODO switch to table view, include roles
1721 function def_env.user:list(host, pat) 1789 function def_env.user:list(host, pat)
1722 if not host then 1790 if not host then
1723 return nil, "No host given"; 1791 return nil, "No host given";
1724 elseif not prosody.hosts[host] then 1792 elseif not prosody.hosts[host] then
1734 total = total + 1; 1802 total = total + 1;
1735 end 1803 end
1736 return true, "Showing "..(pat and (matches.." of ") or "all " )..total.." users"; 1804 return true, "Showing "..(pat and (matches.." of ") or "all " )..total.." users";
1737 end 1805 end
1738 1806
1739 def_env.xmpp = {}; 1807 def_env.xmpp = new_section("Commands for sending XMPP stanzas");;
1740 1808
1809 describe_command [[xmpp:ping(localhost, remotehost) - Sends a ping to a remote XMPP server and reports the response]]
1741 local new_id = require "prosody.util.id".medium; 1810 local new_id = require "prosody.util.id".medium;
1742 function def_env.xmpp:ping(localhost, remotehost, timeout) 1811 function def_env.xmpp:ping(localhost, remotehost, timeout)
1743 localhost = select(2, jid_split(localhost)); 1812 localhost = select(2, jid_split(localhost));
1744 remotehost = select(2, jid_split(remotehost)); 1813 remotehost = select(2, jid_split(remotehost));
1745 if not localhost then 1814 if not localhost then
1787 end):next(function(pong) 1856 end):next(function(pong)
1788 return ("pong from %s on %s in %gs"):format(pong.stanza.attr.from, pong.origin.id, time.now() - time_start); 1857 return ("pong from %s on %s in %gs"):format(pong.stanza.attr.from, pong.origin.id, time.now() - time_start);
1789 end); 1858 end);
1790 end 1859 end
1791 1860
1792 def_env.dns = {}; 1861 def_env.dns = new_section("Commands to manage and inspect the internal DNS resolver");
1793 local adns = require"prosody.net.adns"; 1862 local adns = require"prosody.net.adns";
1794 1863
1795 local function get_resolver(session) 1864 local function get_resolver(session)
1796 local resolver = session.dns_resolver; 1865 local resolver = session.dns_resolver;
1797 if not resolver then 1866 if not resolver then
1799 session.dns_resolver = resolver; 1868 session.dns_resolver = resolver;
1800 end 1869 end
1801 return resolver; 1870 return resolver;
1802 end 1871 end
1803 1872
1873 describe_command [[dns:lookup(name, type, class) - Do a DNS lookup]]
1804 function def_env.dns:lookup(name, typ, class) 1874 function def_env.dns:lookup(name, typ, class)
1805 local resolver = get_resolver(self.session); 1875 local resolver = get_resolver(self.session);
1806 return resolver:lookup_promise(name, typ, class) 1876 return resolver:lookup_promise(name, typ, class)
1807 end 1877 end
1808 1878
1879 describe_command [[dns:addnameserver(nameserver) - Add a nameserver to the list]]
1809 function def_env.dns:addnameserver(...) 1880 function def_env.dns:addnameserver(...)
1810 local resolver = get_resolver(self.session); 1881 local resolver = get_resolver(self.session);
1811 resolver._resolver:addnameserver(...) 1882 resolver._resolver:addnameserver(...)
1812 return true 1883 return true
1813 end 1884 end
1814 1885
1886 describe_command [[dns:setnameserver(nameserver) - Replace the list of name servers with the supplied one]]
1815 function def_env.dns:setnameserver(...) 1887 function def_env.dns:setnameserver(...)
1816 local resolver = get_resolver(self.session); 1888 local resolver = get_resolver(self.session);
1817 resolver._resolver:setnameserver(...) 1889 resolver._resolver:setnameserver(...)
1818 return true 1890 return true
1819 end 1891 end
1820 1892
1893 describe_command [[dns:purge() - Clear the DNS cache]]
1821 function def_env.dns:purge() 1894 function def_env.dns:purge()
1822 local resolver = get_resolver(self.session); 1895 local resolver = get_resolver(self.session);
1823 resolver._resolver:purge() 1896 resolver._resolver:purge()
1824 return true 1897 return true
1825 end 1898 end
1826 1899
1900 describe_command [[dns:cache() - Show cached records]]
1827 function def_env.dns:cache() 1901 function def_env.dns:cache()
1828 local resolver = get_resolver(self.session); 1902 local resolver = get_resolver(self.session);
1829 return true, "Cache:\n"..tostring(resolver._resolver.cache) 1903 return true, "Cache:\n"..tostring(resolver._resolver.cache)
1830 end 1904 end
1831 1905
1832 def_env.http = {}; 1906 def_env.http = new_section("Commands to inspect HTTP services");
1833 1907
1908 describe_command [[http:list(hosts) - Show HTTP endpoints]]
1834 function def_env.http:list(hosts) 1909 function def_env.http:list(hosts)
1835 local print = self.session.print; 1910 local print = self.session.print;
1836 hosts = array.collect(set.new({ not hosts and "*" or nil }) + get_hosts_set(hosts)):sort(_sort_hosts); 1911 hosts = array.collect(set.new({ not hosts and "*" or nil }) + get_hosts_set(hosts)):sort(_sort_hosts);
1837 local output_simple = format_table({ 1912 local output_simple = format_table({
1838 { title = "Module"; width = "1p" }; 1913 { title = "Module"; width = "1p" };
1873 print("HTTP requests to unknown hosts will be handled by "..default_host); 1948 print("HTTP requests to unknown hosts will be handled by "..default_host);
1874 end 1949 end
1875 return true; 1950 return true;
1876 end 1951 end
1877 1952
1878 def_env.watch = {}; 1953 def_env.watch = new_section("Commands for watching live logs from the server");
1879 1954
1955 describe_command [[watch:log() - Follow debug logs]]
1880 function def_env.watch:log() 1956 function def_env.watch:log()
1881 local writing = false; 1957 local writing = false;
1882 local sink = logger.add_simple_sink(function (source, level, message) 1958 local sink = logger.add_simple_sink(function (source, level, message)
1883 if writing then return; end 1959 if writing then return; end
1884 writing = true; 1960 writing = true;
1892 if not logger.remove_sink(sink) then 1968 if not logger.remove_sink(sink) then
1893 module:log("warn", "Unable to remove watch:log() sink"); 1969 module:log("warn", "Unable to remove watch:log() sink");
1894 end 1970 end
1895 end 1971 end
1896 1972
1973 describe_command [[watch:stanzas(target, filter) - Watch live stanzas matching the specified target and filter]]
1897 local stanza_watchers = module:require("mod_debug_stanzas/watcher"); 1974 local stanza_watchers = module:require("mod_debug_stanzas/watcher");
1898 function def_env.watch:stanzas(target_spec, filter_spec) 1975 function def_env.watch:stanzas(target_spec, filter_spec)
1899 local function handler(event_type, stanza, session) 1976 local function handler(event_type, stanza, session)
1900 if stanza then 1977 if stanza then
1901 if event_type == "sent" then 1978 if event_type == "sent" then
1927 end 2004 end
1928 2005
1929 stanza_watchers.remove(handler); 2006 stanza_watchers.remove(handler);
1930 end 2007 end
1931 2008
1932 def_env.debug = {}; 2009 def_env.debug = new_section("Commands for debugging the server");
1933 2010
2011 describe_command [[debug:logevents(host) - Enable logging of fired events on host]]
1934 function def_env.debug:logevents(host) 2012 function def_env.debug:logevents(host)
1935 if host == "*" then 2013 if host == "*" then
1936 helpers.log_events(prosody.events); 2014 helpers.log_events(prosody.events);
1937 elseif host == "http" then 2015 elseif host == "http" then
1938 helpers.log_events(require "prosody.net.http.server"._events); 2016 helpers.log_events(require "prosody.net.http.server"._events);
1941 helpers.log_host_events(host); 2019 helpers.log_host_events(host);
1942 end 2020 end
1943 return true; 2021 return true;
1944 end 2022 end
1945 2023
2024 describe_command [[debug:events(host, event) - Show registered event handlers]]
1946 function def_env.debug:events(host, event) 2025 function def_env.debug:events(host, event)
1947 local events_obj; 2026 local events_obj;
1948 if host and host ~= "*" then 2027 if host and host ~= "*" then
1949 if host == "http" then 2028 if host == "http" then
1950 events_obj = require "prosody.net.http.server"._events; 2029 events_obj = require "prosody.net.http.server"._events;
1957 events_obj = prosody.events; 2036 events_obj = prosody.events;
1958 end 2037 end
1959 return true, helpers.show_events(events_obj, event); 2038 return true, helpers.show_events(events_obj, event);
1960 end 2039 end
1961 2040
2041 describe_command [[debug:timers() - Show information about scheduled timers]]
1962 function def_env.debug:timers() 2042 function def_env.debug:timers()
1963 local print = self.session.print; 2043 local print = self.session.print;
1964 local add_task = require"prosody.util.timer".add_task; 2044 local add_task = require"prosody.util.timer".add_task;
1965 local h, params = add_task.h, add_task.params; 2045 local h, params = add_task.h, add_task.params;
1966 local function normalize_time(t) 2046 local function normalize_time(t)
2013 end 2093 end
2014 end 2094 end
2015 return true; 2095 return true;
2016 end 2096 end
2017 2097
2098 describe_command [[debug:async() - Show information about pending asynchronous tasks]]
2018 function def_env.debug:async(runner_id) 2099 function def_env.debug:async(runner_id)
2019 local print = self.session.print; 2100 local print = self.session.print;
2020 local time_now = time.now(); 2101 local time_now = time.now();
2021 2102
2022 if runner_id then 2103 if runner_id then
2078 end 2159 end
2079 2160
2080 -- COMPAT: debug:timers() was timer:info() for some time in trunk 2161 -- COMPAT: debug:timers() was timer:info() for some time in trunk
2081 def_env.timer = { info = def_env.debug.timers }; 2162 def_env.timer = { info = def_env.debug.timers };
2082 2163
2083 def_env.stats = {}; 2164 def_env.stats = new_section("Commands to show internal statistics");
2084 2165
2085 local short_units = { 2166 local short_units = {
2086 seconds = "s", 2167 seconds = "s",
2087 bytes = "B", 2168 bytes = "B",
2088 }; 2169 };
2317 -- Otherwise we get strange stuff like average cpu usage decreasing until 2398 -- Otherwise we get strange stuff like average cpu usage decreasing until
2318 -- the next sample and so on. 2399 -- the next sample and so on.
2319 return setmetatable({ session = self.session, stats = true, now = time.now() }, stats_mt); 2400 return setmetatable({ session = self.session, stats = true, now = time.now() }, stats_mt);
2320 end 2401 end
2321 2402
2403 describe_command [[stats:show(pattern) - Show internal statistics, optionally filtering by name with a pattern. Append :cfgraph() or :histogram() for graphs]]
2322 function def_env.stats:show(name_filter) 2404 function def_env.stats:show(name_filter)
2323 local statsman = require "prosody.core.statsmanager" 2405 local statsman = require "prosody.core.statsmanager"
2324 local collect = statsman.collect 2406 local collect = statsman.collect
2325 if collect then 2407 if collect then
2326 -- force collection if in manual mode 2408 -- force collection if in manual mode