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.lua225
1 files changed, 163 insertions, 62 deletions
diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua
index 1cbe27a4..f298ad2f 100644
--- a/plugins/mod_admin_telnet.lua
+++ b/plugins/mod_admin_telnet.lua
@@ -22,6 +22,7 @@ local prosody = _G.prosody;
local console_listener = { default_port = 5582; default_mode = "*a"; interface = "127.0.0.1" };
+local unpack = table.unpack or unpack; -- luacheck: ignore 113
local iterators = require "util.iterators";
local keys, values = iterators.keys, iterators.values;
local jid_bare, jid_split, jid_join = import("util.jid", "bare", "prepped_split", "join");
@@ -30,6 +31,9 @@ local cert_verify_identity = require "util.x509".verify_identity;
local envload = require "util.envload".envload;
local envloadfile = require "util.envload".envloadfile;
local has_pposix, pposix = pcall(require, "util.pposix");
+local async = require "util.async";
+local serialize = require "util.serialization".new({ fatal = false, unquoted = true});
+local time = require "util.time";
local commands = module:shared("commands")
local def_env = module:shared("env");
@@ -47,6 +51,24 @@ end
console = {};
+local runner_callbacks = {};
+
+function runner_callbacks:ready()
+ self.data.conn:resume();
+end
+
+function runner_callbacks:waiting()
+ self.data.conn:pause();
+end
+
+function runner_callbacks:error(err)
+ module:log("error", "Traceback[telnet]: %s", err);
+
+ self.data.print("Fatal error while running command, it did not complete");
+ self.data.print("Error: "..tostring(err));
+end
+
+
function console:new_session(conn)
local w = function(s) conn:write(s:gsub("\n", "\r\n")); end;
local session = { conn = conn;
@@ -62,6 +84,11 @@ function console:new_session(conn)
};
session.env = setmetatable({}, default_env_mt);
+ session.thread = async.runner(function (line)
+ console:process_line(session, line);
+ session.send(string.char(0));
+ end, runner_callbacks, session);
+
-- Load up environment with helper objects
for name, t in pairs(def_env) do
if type(t) == "table" then
@@ -91,6 +118,11 @@ function console:process_line(session, line)
session.env._ = line;
+ if not useglobalenv and commands[line:lower()] then
+ commands[line:lower()](session, line);
+ return;
+ end
+
local chunkname = "=console";
local env = (useglobalenv and redirect_output(_G, session)) or session.env or nil
local chunk, err = envload("return "..line, chunkname, env);
@@ -105,18 +137,7 @@ function console:process_line(session, line)
end
end
- local ranok, taskok, message = pcall(chunk);
-
- if not (ranok or message or useglobalenv) and commands[line:lower()] then
- commands[line:lower()](session, line);
- return;
- end
-
- if not ranok then
- session.print("Fatal error while running command, it did not complete");
- session.print("Error: "..taskok);
- return;
- end
+ local taskok, message = chunk();
if not message then
session.print("Result: "..tostring(taskok));
@@ -150,8 +171,7 @@ function console_listener.onincoming(conn, data)
for line in data:gmatch("[^\n]*[\n\004]") do
if session.closed then return end
- console:process_line(session, line);
- session.send(string.char(0));
+ session.thread:run(line);
end
session.partial_data = data:match("[^\n]+$");
end
@@ -220,6 +240,7 @@ function commands.help(session, data)
print [[server - Uptime, version, shutting down, etc.]]
print [[port - Commands to manage ports the server is listening on]]
print [[dns - Commands to manage and inspect the internal DNS resolver]]
+ print [[xmpp - Commands for sending XMPP stanzas]]
print [[config - Reloading the configuration, etc.]]
print [[console - Help regarding the console itself]]
elseif section == "c2s" then
@@ -227,7 +248,9 @@ function commands.help(session, data)
print [[c2s:show_insecure() - Show all unencrypted client connections]]
print [[c2s:show_secure() - Show all encrypted client connections]]
print [[c2s:show_tls() - Show TLS cipher info for encrypted sessions]]
+ print [[c2s:count() - Count sessions without listing them]]
print [[c2s:close(jid) - Close all sessions for the specified JID]]
+ print [[c2s:closeall() - Close all active c2s connections ]]
elseif section == "s2s" then
print [[s2s:show(domain) - Show all s2s connections for the given domain (or all if no domain given)]]
print [[s2s:show_tls(domain) - Show TLS cipher info for encrypted sessions]]
@@ -261,8 +284,11 @@ function commands.help(session, data)
print [[dns:setnameserver(nameserver) - Replace the list of name servers with the supplied one]]
print [[dns:purge() - Clear the DNS cache]]
print [[dns:cache() - Show cached records]]
+ elseif section == "xmpp" then
+ print [[xmpp:ping(localhost, remotehost) -- Sends a ping to a remote XMPP server and reports the response]]
elseif section == "config" then
print [[config:reload() - Reload the server configuration. Modules may need to be reloaded for changes to take effect.]]
+ print [[config:get([host,] option) - Show the value of a config option.]]
elseif section == "console" then
print [[Hey! Welcome to Prosody's admin console.]]
print [[First thing, if you're ever wondering how to get out, simply type 'quit'.]]
@@ -339,7 +365,7 @@ end
def_env.module = {};
-local function get_hosts_set(hosts, module)
+local function get_hosts_set(hosts)
if type(hosts) == "table" then
if hosts[1] then
return set.new(hosts);
@@ -349,17 +375,23 @@ local function get_hosts_set(hosts, module)
elseif type(hosts) == "string" then
return set.new { hosts };
elseif hosts == nil then
- local hosts_set = set.new(array.collect(keys(prosody.hosts)))
- / function (host) return (prosody.hosts[host].type == "local" or module and modulemanager.is_loaded(host, module)) and host or nil; end;
- if module and modulemanager.get_module("*", module) then
- hosts_set:add("*");
- end
- return hosts_set;
+ return set.new(array.collect(keys(prosody.hosts)));
end
end
+-- Hosts with a module or all virtualhosts if no module given
+-- matching modules_enabled in the global section
+local function get_hosts_with_module(hosts, module)
+ local hosts_set = get_hosts_set(hosts)
+ / function (host) return (prosody.hosts[host].type == "local" or module and modulemanager.is_loaded(host, module)) and host or nil; end;
+ if module and modulemanager.get_module("*", module) then
+ hosts_set:add("*");
+ end
+ return hosts_set;
+end
+
function def_env.module:load(name, hosts, config)
- hosts = get_hosts_set(hosts);
+ hosts = get_hosts_with_module(hosts);
-- Load the module for each host
local ok, err, count, mod = true, nil, 0;
@@ -386,7 +418,7 @@ function def_env.module:load(name, hosts, config)
end
function def_env.module:unload(name, hosts)
- hosts = get_hosts_set(hosts, name);
+ hosts = get_hosts_with_module(hosts, name);
-- Unload the module for each host
local ok, err, count = true, nil, 0;
@@ -408,11 +440,11 @@ end
local function _sort_hosts(a, b)
if a == "*" then return true
elseif b == "*" then return false
- else return a < b; end
+ else return a:gsub("[^.]+", string.reverse):reverse() < b:gsub("[^.]+", string.reverse):reverse(); end
end
function def_env.module:reload(name, hosts)
- hosts = array.collect(get_hosts_set(hosts, name)):sort(_sort_hosts)
+ hosts = array.collect(get_hosts_with_module(hosts, name)):sort(_sort_hosts)
-- Reload the module for each host
local ok, err, count = true, nil, 0;
@@ -435,16 +467,7 @@ function def_env.module:reload(name, hosts)
end
function def_env.module:list(hosts)
- if hosts == nil then
- hosts = array.collect(keys(prosody.hosts));
- table.insert(hosts, 1, "*");
- end
- if type(hosts) == "string" then
- hosts = { hosts };
- end
- if type(hosts) ~= "table" then
- return false, "Please supply a host or a list of hosts you would like to see";
- end
+ hosts = array.collect(set.new({ not hosts and "*" or nil }) + get_hosts_set(hosts)):sort(_sort_hosts);
local print = self.session.print;
for _, host in ipairs(hosts) do
@@ -458,7 +481,12 @@ function def_env.module:list(hosts)
end
else
for _, name in ipairs(modules) do
- print(" "..name);
+ local status, status_text = modulemanager.get_module(host, name).module:get_status();
+ local status_summary = "";
+ if status == "warn" or status == "error" then
+ status_summary = (" (%s: %s)"):format(status, status_text);
+ end
+ print((" %s%s"):format(name, status_summary));
end
end
end
@@ -474,9 +502,12 @@ function def_env.config:load(filename, format)
return true, "Config loaded";
end
-function def_env.config:get(host, section, key)
+function def_env.config:get(host, key)
+ if key == nil then
+ host, key = "*", host;
+ end
local config_get = require "core.configmanager".get
- return true, tostring(config_get(host, section, key));
+ return true, serialize(config_get(host, key));
end
function def_env.config:reload()
@@ -505,6 +536,12 @@ local function session_flags(session, line)
if session.cert_identity_status == "valid" then
line[#line+1] = "(authenticated)";
end
+ if session.dialback_key then
+ line[#line+1] = "(dialback)";
+ end
+ if session.external_auth then
+ line[#line+1] = "(SASL)";
+ end
if session.secure then
line[#line+1] = "(encrypted)";
end
@@ -520,6 +557,17 @@ local function session_flags(session, line)
if session.remote then
line[#line+1] = "(remote)";
end
+ if session.incoming and session.outgoing then
+ line[#line+1] = "(bidi)";
+ elseif session.is_bidi or session.bidi_session then
+ line[#line+1] = "(bidi)";
+ end
+ if session.bosh_version then
+ line[#line+1] = "(bosh)";
+ end
+ if session.websocket_request then
+ line[#line+1] = "(websocket)";
+ end
return table.concat(line, " ");
end
@@ -534,6 +582,18 @@ local function tls_info(session, line)
else
line[#line+1] = "(cipher info unavailable)";
end
+ if sock.getsniname then
+ local name = sock:getsniname();
+ if name then
+ line[#line+1] = ("(SNI:%q)"):format(name);
+ end
+ end
+ if sock.getalpn then
+ local proto = sock:getalpn();
+ if proto then
+ line[#line+1] = ("(ALPN:%q)"):format(proto);
+ end
+ end
else
line[#line+1] = "(insecure)";
end
@@ -555,23 +615,31 @@ local function get_jid(session)
return jid_join("["..ip.."]:"..clientport, session.host or "["..serverip.."]:"..serverport);
end
+local function get_c2s()
+ local c2s = array.collect(values(prosody.full_sessions));
+ c2s:append(array.collect(values(module:shared"/*/c2s/sessions")));
+ c2s:append(array.collect(values(module:shared"/*/bosh/sessions")));
+ c2s:unique();
+ return c2s;
+end
+
local function show_c2s(callback)
- local c2s = array.collect(values(module:shared"/*/c2s/sessions"));
- c2s:sort(function(a, b)
+ get_c2s():sort(function(a, b)
if a.host == b.host then
if a.username == b.username then
return (a.resource or "") > (b.resource or "");
end
return (a.username or "") > (b.username or "");
end
- return (a.host or "") > (b.host or "");
+ return _sort_hosts(a.host or "", b.host or "");
end):map(function (session)
callback(get_jid(session), session)
end);
end
function def_env.c2s:count()
- return true, "Total: ".. iterators.count(values(module:shared"/*/c2s/sessions")) .." clients";
+ local c2s = get_c2s();
+ return true, "Total: ".. #c2s .." clients";
end
function def_env.c2s:show(match_jid, annotate)
@@ -617,17 +685,36 @@ function def_env.c2s:show_tls(match_jid)
return self:show(match_jid, tls_info);
end
-function def_env.c2s:close(match_jid)
+local function build_reason(text, condition)
+ if text or condition then
+ return {
+ text = text,
+ condition = condition or "undefined-condition",
+ };
+ end
+end
+
+function def_env.c2s:close(match_jid, text, condition)
local count = 0;
show_c2s(function (jid, session)
if jid == match_jid or jid_bare(jid) == match_jid then
count = count + 1;
- session:close();
+ session:close(build_reason(text, condition));
end
end);
return true, "Total: "..count.." sessions closed";
end
+function def_env.c2s:closeall(text, condition)
+ local count = 0;
+ --luacheck: ignore 212/jid
+ show_c2s(function (jid, session)
+ count = count + 1;
+ session:close(build_reason(text, condition));
+ end);
+ return true, "Total: "..count.." sessions closed";
+end
+
def_env.s2s = {};
function def_env.s2s:show(match_jid, annotate)
@@ -695,8 +782,8 @@ function def_env.s2s:show(match_jid, annotate)
-- Sort by local host, then remote host
table.sort(s2s_list, function(a,b)
- if a.l == b.l then return a.r < b.r; end
- return a.l < b.l;
+ if a.l == b.l then return _sort_hosts(a.r, b.r); end
+ return _sort_hosts(a.l, b.l);
end);
local lasthost;
for _, sess_lines in ipairs(s2s_list) do
@@ -828,7 +915,7 @@ function def_env.s2s:showcert(domain)
.." presented by "..domain..".");
end
-function def_env.s2s:close(from, to)
+function def_env.s2s:close(from, to, text, condition)
local print, count = self.session.print, 0;
local s2s_sessions = module:shared"/*/s2s/sessions";
@@ -842,23 +929,23 @@ function def_env.s2s:close(from, to)
end
for _, session in pairs(s2s_sessions) do
- local id = session.type..tostring(session):match("[a-f0-9]+$");
+ local id = session.id or (session.type..tostring(session):match("[a-f0-9]+$"));
if (match_id and match_id == id)
or (session.from_host == from and session.to_host == to) then
print(("Closing connection from %s to %s [%s]"):format(session.from_host, session.to_host, id));
- (session.close or s2smanager.destroy_session)(session);
+ (session.close or s2smanager.destroy_session)(session, build_reason(text, condition));
count = count + 1 ;
end
end
return true, "Closed "..count.." s2s session"..((count == 1 and "") or "s");
end
-function def_env.s2s:closeall(host)
+function def_env.s2s:closeall(host, text, condition)
local count = 0;
local s2s_sessions = module:shared"/*/s2s/sessions";
for _,session in pairs(s2s_sessions) do
if not host or session.from_host == host or session.to_host == host then
- session:close();
+ session:close(build_reason(text, condition));
count = count + 1;
end
end
@@ -879,7 +966,7 @@ function def_env.host:list()
local print = self.session.print;
local i = 0;
local type;
- for host, host_session in iterators.sorted_pairs(prosody.hosts) do
+ for host, host_session in iterators.sorted_pairs(prosody.hosts, _sort_hosts) do
i = i + 1;
type = host_session.type;
if type == "local" then
@@ -1062,13 +1149,28 @@ end
def_env.xmpp = {};
local st = require "util.stanza";
-function def_env.xmpp:ping(localhost, remotehost)
- if prosody.hosts[localhost] then
- module:send(st.iq{ from=localhost, to=remotehost, type="get", id="ping" }
- :tag("ping", {xmlns="urn:xmpp:ping"}), prosody.hosts[localhost]);
- return true, "Sent ping";
+local new_id = require "util.id".medium;
+function def_env.xmpp:ping(localhost, remotehost, timeout)
+ localhost = select(2, jid_split(localhost));
+ remotehost = select(2, jid_split(remotehost));
+ if not localhost then
+ return nil, "Invalid sender hostname";
+ elseif not prosody.hosts[localhost] then
+ return nil, "No such local host";
+ end
+ if not remotehost then
+ return nil, "Invalid destination hostname";
+ elseif prosody.hosts[remotehost] then
+ return nil, "Both hosts are local";
+ end
+ local iq = st.iq{ from=localhost, to=remotehost, type="get", id=new_id()}
+ :tag("ping", {xmlns="urn:xmpp:ping"});
+ local time_start = time.now();
+ local ret, err = async.wait(module:context(localhost):send_iq(iq, nil, timeout));
+ if ret then
+ return true, ("pong from %s in %gs"):format(ret.stanza.attr.from, time.now() - time_start);
else
- return nil, "No such host";
+ return false, tostring(err);
end
end
@@ -1157,7 +1259,6 @@ function def_env.debug:events(host, 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;
@@ -1185,7 +1286,7 @@ function def_env.debug:timers()
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());
+ return true, os.date("Next event at %F %T (in %%.6fs)", next_time):format(next_time - time.now());
end
end
return true;
@@ -1207,7 +1308,7 @@ local function format_stat(type, value, ref_value)
--do return tostring(value) end
if type == "duration" then
if ref_value < 0.001 then
- return ("%d µs"):format(value*1000000);
+ return ("%g µs"):format(value*1000000);
elseif ref_value < 0.9 then
return ("%0.2f ms"):format(value*1000);
end
@@ -1495,7 +1596,7 @@ 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
+ for name, value in iterators.sorted_pairs(stats) do
available = available + 1;
if not filter or name:match(filter) then
displayed = displayed + 1;