aboutsummaryrefslogtreecommitdiffstats
path: root/plugins/mod_admin_telnet.lua
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/mod_admin_telnet.lua')
-rw-r--r--plugins/mod_admin_telnet.lua438
1 files changed, 396 insertions, 42 deletions
diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua
index 5e8d8534..1cbe27a4 100644
--- a/plugins/mod_admin_telnet.lua
+++ b/plugins/mod_admin_telnet.lua
@@ -5,6 +5,7 @@
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
+-- luacheck: ignore 212/self
module:set_global();
@@ -13,11 +14,11 @@ local modulemanager = require "core.modulemanager";
local s2smanager = require "core.s2smanager";
local portmanager = require "core.portmanager";
local helpers = require "util.helpers";
+local server = require "net.server";
local _G = _G;
local prosody = _G.prosody;
-local hosts = prosody.hosts;
local console_listener = { default_port = 5582; default_mode = "*a"; interface = "127.0.0.1" };
@@ -34,8 +35,8 @@ local commands = module:shared("commands")
local def_env = module:shared("env");
local default_env_mt = { __index = def_env };
-local function redirect_output(_G, session)
- local env = setmetatable({ print = session.print }, { __index = function (t, k) return rawget(_G, k); end });
+local function redirect_output(target, session)
+ local env = setmetatable({ print = session.print }, { __index = function (_, k) return rawget(target, k); end });
env.dofile = function(name)
local f, err = envloadfile(name, env);
if not f then return f, err; end
@@ -163,7 +164,7 @@ function console_listener.onreadtimeout(conn)
end
end
-function console_listener.ondisconnect(conn, err)
+function console_listener.ondisconnect(conn, err) -- luacheck: ignore 212/err
local session = sessions[conn];
if session then
session.disconnect();
@@ -361,7 +362,7 @@ function def_env.module:load(name, hosts, config)
hosts = get_hosts_set(hosts);
-- Load the module for each host
- local ok, err, count, mod = true, nil, 0, nil;
+ local ok, err, count, mod = true, nil, 0;
for host in hosts do
if (not modulemanager.is_loaded(host, name)) then
mod, err = modulemanager.load(host, name, config);
@@ -404,12 +405,14 @@ function def_env.module:unload(name, hosts)
return ok, (ok and "Module unloaded from "..count.." host"..(count ~= 1 and "s" or "")) or ("Last error: "..tostring(err));
end
+local function _sort_hosts(a, b)
+ if a == "*" then return true
+ elseif b == "*" then return false
+ else return a < b; end
+end
+
function def_env.module:reload(name, hosts)
- hosts = array.collect(get_hosts_set(hosts, name)):sort(function (a, b)
- if a == "*" then return true
- elseif b == "*" then return false
- else return a < b; end
- end);
+ hosts = array.collect(get_hosts_set(hosts, name)):sort(_sort_hosts)
-- Reload the module for each host
local ok, err, count = true, nil, 0;
@@ -567,7 +570,7 @@ local function show_c2s(callback)
end);
end
-function def_env.c2s:count(match_jid)
+function def_env.c2s:count()
return true, "Total: ".. iterators.count(values(module:shared"/*/c2s/sessions")) .." clients";
end
@@ -651,6 +654,7 @@ function def_env.s2s:show(match_jid, annotate)
if (not match_jid) or remotehost:match(match_jid) or localhost:match(match_jid) then
table.insert(s2s_list, sess_lines);
+ -- luacheck: ignore 421/print
local print = function (s) table.insert(sess_lines, " "..s); end
if session.sendq then
print("There are "..#session.sendq.." queued outgoing stanzas for this connection");
@@ -830,7 +834,7 @@ function def_env.s2s:close(from, to)
local match_id;
if from and not to then
- match_id, from = from;
+ match_id, from = from, nil;
elseif not to then
return false, "Syntax: s2s:close('from', 'to') - Closes all s2s sessions from 'from' to 'to'";
elseif from == to then
@@ -875,9 +879,9 @@ function def_env.host:list()
local print = self.session.print;
local i = 0;
local type;
- for host in values(array.collect(keys(prosody.hosts)):sort()) do
+ for host, host_session in iterators.sorted_pairs(prosody.hosts) do
i = i + 1;
- type = hosts[host].type;
+ type = host_session.type;
if type == "local" then
print(host);
else
@@ -896,14 +900,11 @@ def_env.port = {};
function def_env.port:list()
local print = self.session.print;
local services = portmanager.get_active_services().data;
- local ordered_services, n_ports = {}, 0;
- for service, interfaces in pairs(services) do
- table.insert(ordered_services, service);
- end
- table.sort(ordered_services);
- for _, service in ipairs(ordered_services) do
+ local n_services, n_ports = 0, 0;
+ for service, interfaces in iterators.sorted_pairs(services) do
+ n_services = n_services + 1;
local ports_list = {};
- for interface, ports in pairs(services[service]) do
+ for interface, ports in pairs(interfaces) do
for port in pairs(ports) do
table.insert(ports_list, "["..interface.."]:"..port);
end
@@ -911,14 +912,14 @@ function def_env.port:list()
n_ports = n_ports + #ports_list;
print(service..": "..table.concat(ports_list, ", "));
end
- return true, #ordered_services.." services listening on "..n_ports.." ports";
+ return true, n_services.." services listening on "..n_ports.." ports";
end
function def_env.port:close(close_port, close_interface)
close_port = assert(tonumber(close_port), "Invalid port number");
local n_closed = 0;
local services = portmanager.get_active_services().data;
- for service, interfaces in pairs(services) do
+ for service, interfaces in pairs(services) do -- luacheck: ignore 213
for interface, ports in pairs(interfaces) do
if not close_interface or close_interface == interface then
if ports[close_port] then
@@ -947,22 +948,23 @@ local console_room_mt = {
local function check_muc(jid)
local room_name, host = jid_split(jid);
- if not hosts[host] then
+ if not prosody.hosts[host] then
return nil, "No such host: "..host;
- elseif not hosts[host].modules.muc then
+ elseif not prosody.hosts[host].modules.muc then
return nil, "Host '"..host.."' is not a MUC service";
end
return room_name, host;
end
-function def_env.muc:create(room_jid)
+function def_env.muc:create(room_jid, config)
local room_name, host = check_muc(room_jid);
if not room_name then
return room_name, host;
end
if not room_name then return nil, host end
- if hosts[host].modules.muc.rooms[room_jid] then return nil, "Room exists already" end
- return hosts[host].modules.muc.create_room(room_jid);
+ if config ~= nil and type(config) ~= "table" then return nil, "Config must be a table"; end
+ if prosody.hosts[host].modules.muc.get_room_from_jid(room_jid) then return nil, "Room exists already" end
+ return prosody.hosts[host].modules.muc.create_room(room_jid, config);
end
function def_env.muc:room(room_jid)
@@ -970,7 +972,7 @@ function def_env.muc:room(room_jid)
if not room_name then
return room_name, host;
end
- local room_obj = hosts[host].modules.muc.rooms[room_jid];
+ local room_obj = prosody.hosts[host].modules.muc.get_room_from_jid(room_jid);
if not room_obj then
return nil, "No such room: "..room_jid;
end
@@ -978,14 +980,14 @@ function def_env.muc:room(room_jid)
end
function def_env.muc:list(host)
- local host_session = hosts[host];
+ local host_session = prosody.hosts[host];
if not host_session or not host_session.modules.muc then
return nil, "Please supply the address of a local MUC component";
end
local print = self.session.print;
local c = 0;
- for name in keys(host_session.modules.muc.rooms) do
- print(name);
+ for room in host_session.modules.muc.each_room() do
+ print(room.jid);
c = c + 1;
end
return true, c.." rooms";
@@ -996,7 +998,7 @@ local um = require"core.usermanager";
def_env.user = {};
function def_env.user:create(jid, password)
local username, host = jid_split(jid);
- if not hosts[host] then
+ if not prosody.hosts[host] then
return nil, "No such host: "..host;
elseif um.user_exists(username, host) then
return nil, "User exists";
@@ -1011,7 +1013,7 @@ end
function def_env.user:delete(jid)
local username, host = jid_split(jid);
- if not hosts[host] then
+ if not prosody.hosts[host] then
return nil, "No such host: "..host;
elseif not um.user_exists(username, host) then
return nil, "No such user";
@@ -1026,7 +1028,7 @@ end
function def_env.user:password(jid, password)
local username, host = jid_split(jid);
- if not hosts[host] then
+ if not prosody.hosts[host] then
return nil, "No such host: "..host;
elseif not um.user_exists(username, host) then
return nil, "No such user";
@@ -1042,7 +1044,7 @@ end
function def_env.user:list(host, pat)
if not host then
return nil, "No host given";
- elseif not hosts[host] then
+ elseif not prosody.hosts[host] then
return nil, "No such host";
end
local print = self.session.print;
@@ -1061,9 +1063,9 @@ def_env.xmpp = {};
local st = require "util.stanza";
function def_env.xmpp:ping(localhost, remotehost)
- if hosts[localhost] then
+ if prosody.hosts[localhost] then
module:send(st.iq{ from=localhost, to=remotehost, type="get", id="ping" }
- :tag("ping", {xmlns="urn:xmpp:ping"}), hosts[localhost]);
+ :tag("ping", {xmlns="urn:xmpp:ping"}), prosody.hosts[localhost]);
return true, "Sent ping";
else
return nil, "No such host";
@@ -1141,22 +1143,374 @@ end
function def_env.debug:events(host, event)
local events_obj;
if host and host ~= "*" then
- if not hosts[host] then
+ if host == "http" then
+ events_obj = require "net.http.server"._events;
+ elseif not prosody.hosts[host] then
return false, "Unknown host: "..host;
+ else
+ events_obj = prosody.hosts[host].events;
end
- events_obj = hosts[host].events;
else
events_obj = prosody.events;
end
return true, helpers.show_events(events_obj, event);
end
+function def_env.debug:timers()
+ local socket = require "socket";
+ local print = self.session.print;
+ local add_task = require"util.timer".add_task;
+ local h, params = add_task.h, add_task.params;
+ if h then
+ print("-- util.timer");
+ for i, id in ipairs(h.ids) do
+ if not params[id] then
+ print(os.date("%F %T", h.priorities[i]), h.items[id]);
+ elseif not params[id].callback then
+ print(os.date("%F %T", h.priorities[i]), h.items[id], unpack(params[id]));
+ else
+ print(os.date("%F %T", h.priorities[i]), params[id].callback, unpack(params[id]));
+ end
+ end
+ end
+ if server.event_base then
+ local count = 0;
+ for _, v in pairs(debug.getregistry()) do
+ if type(v) == "function" and v.callback and v.callback == add_task._on_timer then
+ count = count + 1;
+ end
+ end
+ print(count .. " libevent callbacks");
+ end
+ if h then
+ local next_time = h:peek();
+ if next_time then
+ return true, os.date("Next event at %F %T (in %%.6fs)", next_time):format(next_time - socket.gettime());
+ end
+ end
+ return true;
+end
+
+-- COMPAT: debug:timers() was timer:info() for some time in trunk
+def_env.timer = { info = def_env.debug.timers };
+
module:hook("server-stopping", function(event)
- for conn, session in pairs(sessions) do
+ for _, session in pairs(sessions) do
session.print("Shutting down: "..(event.reason or "unknown reason"));
end
end);
+def_env.stats = {};
+
+local function format_stat(type, value, ref_value)
+ ref_value = ref_value or value;
+ --do return tostring(value) end
+ if type == "duration" then
+ if ref_value < 0.001 then
+ return ("%d µs"):format(value*1000000);
+ elseif ref_value < 0.9 then
+ return ("%0.2f ms"):format(value*1000);
+ end
+ return ("%0.2f"):format(value);
+ elseif type == "size" then
+ if ref_value > 1048576 then
+ return ("%d MB"):format(value/1048576);
+ elseif ref_value > 1024 then
+ return ("%d KB"):format(value/1024);
+ end
+ return ("%d bytes"):format(value);
+ elseif type == "rate" then
+ if ref_value < 0.9 then
+ return ("%0.2f/min"):format(value*60);
+ end
+ return ("%0.2f/sec"):format(value);
+ end
+ return tostring(value);
+end
+
+local stats_methods = {};
+function stats_methods:bounds(_lower, _upper)
+ for _, stat_info in ipairs(self) do
+ local data = stat_info[4];
+ if data then
+ local lower = _lower or data.min;
+ local upper = _upper or data.max;
+ local new_data = {
+ min = lower;
+ max = upper;
+ samples = {};
+ sample_count = 0;
+ count = data.count;
+ units = data.units;
+ };
+ local sum = 0;
+ for _, v in ipairs(data.samples) do
+ if v > upper then
+ break;
+ elseif v>=lower then
+ table.insert(new_data.samples, v);
+ sum = sum + v;
+ end
+ end
+ new_data.sample_count = #new_data.samples;
+ stat_info[4] = new_data;
+ stat_info[3] = sum/new_data.sample_count;
+ end
+ end
+ return self;
+end
+
+function stats_methods:trim(lower, upper)
+ upper = upper or (100-lower);
+ local statistics = require "util.statistics";
+ for _, stat_info in ipairs(self) do
+ -- Strip outliers
+ local data = stat_info[4];
+ if data then
+ local new_data = {
+ min = statistics.get_percentile(data, lower);
+ max = statistics.get_percentile(data, upper);
+ samples = {};
+ sample_count = 0;
+ count = data.count;
+ units = data.units;
+ };
+ local sum = 0;
+ for _, v in ipairs(data.samples) do
+ if v > new_data.max then
+ break;
+ elseif v>=new_data.min then
+ table.insert(new_data.samples, v);
+ sum = sum + v;
+ end
+ end
+ new_data.sample_count = #new_data.samples;
+ stat_info[4] = new_data;
+ stat_info[3] = sum/new_data.sample_count;
+ end
+ end
+ return self;
+end
+
+function stats_methods:max(upper)
+ return self:bounds(nil, upper);
+end
+
+function stats_methods:min(lower)
+ return self:bounds(lower, nil);
+end
+
+function stats_methods:summary()
+ local statistics = require "util.statistics";
+ for _, stat_info in ipairs(self) do
+ local type, value, data = stat_info[2], stat_info[3], stat_info[4];
+ if data and data.samples then
+ table.insert(stat_info.output, string.format("Count: %d (%d captured)",
+ data.count,
+ data.sample_count
+ ));
+ table.insert(stat_info.output, string.format("Min: %s Mean: %s Max: %s",
+ format_stat(type, data.min),
+ format_stat(type, value),
+ format_stat(type, data.max)
+ ));
+ table.insert(stat_info.output, string.format("Q1: %s Median: %s Q3: %s",
+ format_stat(type, statistics.get_percentile(data, 25)),
+ format_stat(type, statistics.get_percentile(data, 50)),
+ format_stat(type, statistics.get_percentile(data, 75))
+ ));
+ end
+ end
+ return self;
+end
+
+function stats_methods:cfgraph()
+ for _, stat_info in ipairs(self) do
+ local name, type, value, data = unpack(stat_info, 1, 4);
+ local function print(s)
+ table.insert(stat_info.output, s);
+ end
+
+ if data and data.sample_count and data.sample_count > 0 then
+ local raw_histogram = require "util.statistics".get_histogram(data);
+
+ local graph_width, graph_height = 50, 10;
+ local eighth_chars = " ▁▂▃▄▅▆▇█";
+
+ local range = data.max - data.min;
+
+ if range > 0 then
+ local x_scaling = #raw_histogram/graph_width;
+ local histogram = {};
+ for i = 1, graph_width do
+ histogram[i] = math.max(raw_histogram[i*x_scaling-1] or 0, raw_histogram[i*x_scaling] or 0);
+ end
+
+ print("");
+ print(("_"):rep(52)..format_stat(type, data.max));
+ for row = graph_height, 1, -1 do
+ local row_chars = {};
+ local min_eighths, max_eighths = 8, 0;
+ for i = 1, #histogram do
+ local char_eighths = math.ceil(math.max(math.min((graph_height/(data.max/histogram[i]))-(row-1), 1), 0)*8);
+ if char_eighths < min_eighths then
+ min_eighths = char_eighths;
+ end
+ if char_eighths > max_eighths then
+ max_eighths = char_eighths;
+ end
+ if char_eighths == 0 then
+ row_chars[i] = "-";
+ else
+ local char = eighth_chars:sub(char_eighths*3+1, char_eighths*3+3);
+ row_chars[i] = char;
+ end
+ end
+ print(table.concat(row_chars).."|-"..format_stat(type, data.max/(graph_height/(row-0.5))));
+ end
+ print(("\\ "):rep(11));
+ local x_labels = {};
+ for i = 1, 11 do
+ local s = ("%-4s"):format((i-1)*10);
+ if #s > 4 then
+ s = s:sub(1, 3).."…";
+ end
+ x_labels[i] = s;
+ end
+ print(" "..table.concat(x_labels, " "));
+ local units = "%";
+ local margin = math.floor((graph_width-#units)/2);
+ print((" "):rep(margin)..units);
+ else
+ print("[range too small to graph]");
+ end
+ print("");
+ end
+ end
+ return self;
+end
+
+function stats_methods:histogram()
+ for _, stat_info in ipairs(self) do
+ local name, type, value, data = unpack(stat_info, 1, 4);
+ local function print(s)
+ table.insert(stat_info.output, s);
+ end
+
+ if not data then
+ print("[no data]");
+ return self;
+ elseif not data.sample_count then
+ print("[not a sampled metric type]");
+ return self;
+ end
+
+ local graph_width, graph_height = 50, 10;
+ local eighth_chars = " ▁▂▃▄▅▆▇█";
+
+ local range = data.max - data.min;
+
+ if range > 0 then
+ local n_buckets = graph_width;
+
+ local histogram = {};
+ for i = 1, n_buckets do
+ histogram[i] = 0;
+ end
+ local max_bin_samples = 0;
+ for _, d in ipairs(data.samples) do
+ local bucket = math.floor(1+(n_buckets-1)/(range/(d-data.min)));
+ histogram[bucket] = histogram[bucket] + 1;
+ if histogram[bucket] > max_bin_samples then
+ max_bin_samples = histogram[bucket];
+ end
+ end
+
+ print("");
+ print(("_"):rep(52)..max_bin_samples);
+ for row = graph_height, 1, -1 do
+ local row_chars = {};
+ local min_eighths, max_eighths = 8, 0;
+ for i = 1, #histogram do
+ local char_eighths = math.ceil(math.max(math.min((graph_height/(max_bin_samples/histogram[i]))-(row-1), 1), 0)*8);
+ if char_eighths < min_eighths then
+ min_eighths = char_eighths;
+ end
+ if char_eighths > max_eighths then
+ max_eighths = char_eighths;
+ end
+ if char_eighths == 0 then
+ row_chars[i] = "-";
+ else
+ local char = eighth_chars:sub(char_eighths*3+1, char_eighths*3+3);
+ row_chars[i] = char;
+ end
+ end
+ print(table.concat(row_chars).."|-"..math.ceil((max_bin_samples/graph_height)*(row-0.5)));
+ end
+ print(("\\ "):rep(11));
+ local x_labels = {};
+ for i = 1, 11 do
+ local s = ("%-4s"):format(format_stat(type, data.min+range*i/11, data.min):match("^%S+"));
+ if #s > 4 then
+ s = s:sub(1, 3).."…";
+ end
+ x_labels[i] = s;
+ end
+ print(" "..table.concat(x_labels, " "));
+ local units = format_stat(type, data.min):match("%s+(.+)$") or data.units or "";
+ local margin = math.floor((graph_width-#units)/2);
+ print((" "):rep(margin)..units);
+ else
+ print("[range too small to graph]");
+ end
+ print("");
+ end
+ return self;
+end
+
+local function stats_tostring(stats)
+ local print = stats.session.print;
+ for _, stat_info in ipairs(stats) do
+ if #stat_info.output > 0 then
+ print("\n#"..stat_info[1]);
+ print("");
+ for _, v in ipairs(stat_info.output) do
+ print(v);
+ end
+ print("");
+ else
+ print(("%-50s %s"):format(stat_info[1], format_stat(stat_info[2], stat_info[3])));
+ end
+ end
+ return #stats.." statistics displayed";
+end
+
+local stats_mt = {__index = stats_methods, __tostring = stats_tostring }
+local function new_stats_context(self)
+ return setmetatable({ session = self.session, stats = true }, stats_mt);
+end
+
+function def_env.stats:show(filter)
+ local stats, changed, extra = require "core.statsmanager".get_stats();
+ local available, displayed = 0, 0;
+ local displayed_stats = new_stats_context(self);
+ for name, value in pairs(stats) do
+ available = available + 1;
+ if not filter or name:match(filter) then
+ displayed = displayed + 1;
+ local type = name:match(":(%a+)$");
+ table.insert(displayed_stats, {
+ name, type, value, extra[name];
+ output = {};
+ });
+ end
+ end
+ return displayed_stats;
+end
+
+
+
-------------
function printbanner(session)
@@ -1175,7 +1529,7 @@ function printbanner(session)
if option == "short" or option == "full" then
session.print("Welcome to the Prosody administration console. For a list of commands, type: help");
session.print("You may find more help on using this console in our online documentation at ");
- session.print("http://prosody.im/doc/console\n");
+ session.print("https://prosody.im/doc/console\n");
end
if option ~= "short" and option ~= "full" and option ~= "graphic" then
session.print(option);