From 2b289f34f929a69424a22bb0de3b668a58ba80cd Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 8 Dec 2018 17:09:55 +0100 Subject: various: Don't rely on _G.unpack existing --- plugins/mod_admin_telnet.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 1cbe27a4..8a3508db 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"); -- cgit v1.2.3 From 738a1171dc1415544b2289591578670333250d9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20=E2=80=9Cpep=E2=80=9D=20Buquet?= Date: Tue, 18 Dec 2018 20:23:33 +0000 Subject: admin_telnet: show when bidi is used on s2s --- plugins/mod_admin_telnet.lua | 3 +++ 1 file changed, 3 insertions(+) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 8a3508db..63136d63 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -521,6 +521,9 @@ local function session_flags(session, line) if session.remote then line[#line+1] = "(remote)"; end + if session.is_bidi then + line[#line+1] = "(bidi)"; + end return table.concat(line, " "); end -- cgit v1.2.3 From 9a412b02e9ab54e2201986bae39e5c7c1d664d3d Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 28 Dec 2018 20:56:01 +0100 Subject: mod_admin_telnet: Invert host existence check Simplifies and reduces indentation --- plugins/mod_admin_telnet.lua | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 63136d63..5ba88b84 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -1067,13 +1067,12 @@ 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"; - else + if not prosody.hosts[localhost] then return nil, "No such host"; end + 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"; end def_env.dns = {}; -- cgit v1.2.3 From 851f33034886b3d25d698497139cb51bf40ed506 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 27 Dec 2018 02:53:34 +0100 Subject: mod_admin_telnet: Enable async processing using util.async --- plugins/mod_admin_telnet.lua | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 5ba88b84..bb97a09b 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -31,6 +31,7 @@ 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 commands = module:shared("commands") local def_env = module:shared("env"); @@ -48,6 +49,21 @@ 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); +end + + function console:new_session(conn) local w = function(s) conn:write(s:gsub("\n", "\r\n")); end; local session = { conn = conn; @@ -63,6 +79,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 @@ -151,8 +172,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 -- cgit v1.2.3 From f1f0c276bc41aa4290f06a7b308671d88ee54050 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 28 Dec 2018 20:59:10 +0100 Subject: mod_admin_telnet: Make xmpp:ping command wait and report the reply --- plugins/mod_admin_telnet.lua | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index bb97a09b..ee6a4176 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -1086,13 +1086,28 @@ end def_env.xmpp = {}; local st = require "util.stanza"; -function def_env.xmpp:ping(localhost, remotehost) +local new_id = require "util.id".medium; +function def_env.xmpp:ping(localhost, remotehost, timeout) if not prosody.hosts[localhost] then return nil, "No such host"; end - 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 iq = st.iq{ from=localhost, to=remotehost, type="get", id=new_id()} + :tag("ping", {xmlns="urn:xmpp:ping"}); + local ret, err; + local wait, done = async.waiter(); + module:context(localhost):send_iq(iq, nil, timeout) + :next(function (ret_) ret = ret_; end, + function (err_) err = err_; end) + :finally(done); + wait(); + if ret then + return true, "pong from " .. ret.stanza.attr.from; + elseif type(err) == "table" and st.is_stanza(err.stanza) then + local t, cond, text = err.stanza:get_error(); + return false, text or cond or t; + else + return false, tostring(err); + end end def_env.dns = {}; -- cgit v1.2.3 From 5eb327274aa1ab27ec45b49e419943c264bd237d Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 29 Dec 2018 03:21:13 +0100 Subject: mod_admin_telnet: Validate hostnames in xmpp:ping command Attempt to ping some invalid hostnames cause weird behavior --- plugins/mod_admin_telnet.lua | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index ee6a4176..f3731c8a 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -1088,8 +1088,17 @@ def_env.xmpp = {}; local st = require "util.stanza"; local new_id = require "util.id".medium; function def_env.xmpp:ping(localhost, remotehost, timeout) - if not prosody.hosts[localhost] then - return nil, "No such host"; + 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"}); -- cgit v1.2.3 From f102941562aa2228e1949261c91045ecbf71c18d Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 30 Dec 2018 16:03:15 +0100 Subject: core.moduleapi: Use util.error for :send_iq errors --- plugins/mod_admin_telnet.lua | 3 --- 1 file changed, 3 deletions(-) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index f3731c8a..5bb9361e 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -1111,9 +1111,6 @@ function def_env.xmpp:ping(localhost, remotehost, timeout) wait(); if ret then return true, "pong from " .. ret.stanza.attr.from; - elseif type(err) == "table" and st.is_stanza(err.stanza) then - local t, cond, text = err.stanza:get_error(); - return false, text or cond or t; else return false, tostring(err); end -- cgit v1.2.3 From 11b2a79872902ae26905c006a2171aaecbcb4300 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 4 Jan 2019 13:38:30 +0100 Subject: mod_admin_telnet: Remove the long gone 'section' argument in the undocumented config:get command --- plugins/mod_admin_telnet.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 5bb9361e..4c049b95 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -495,9 +495,9 @@ 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) local config_get = require "core.configmanager".get - return true, tostring(config_get(host, section, key)); + return true, tostring(config_get(host, key)); end function def_env.config:reload() -- cgit v1.2.3 From 5fb717bbcec4af6aee2bc709f97fbea7b88f3fe6 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 4 Jan 2019 13:39:13 +0100 Subject: mod_admin_telnet: config:get: Assume the global section if only one argument is given --- plugins/mod_admin_telnet.lua | 3 +++ 1 file changed, 3 insertions(+) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 4c049b95..c6b67b95 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -496,6 +496,9 @@ function def_env.config:load(filename, format) end 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, key)); end -- cgit v1.2.3 From d020a0b57782846653d6145d388143df5b616c64 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 4 Jan 2019 13:41:39 +0100 Subject: mod_admin_telnet: Serialize config values (table: 0x123abc isn't useful) --- plugins/mod_admin_telnet.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index c6b67b95..cd9f8078 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -32,6 +32,7 @@ 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 commands = module:shared("commands") local def_env = module:shared("env"); @@ -500,7 +501,7 @@ function def_env.config:get(host, key) host, key = "*", host; end local config_get = require "core.configmanager".get - return true, tostring(config_get(host, key)); + return true, serialize(config_get(host, key)); end function def_env.config:reload() -- cgit v1.2.3 From 51c4d0a0e4d0b1d83bdcfc779bcc9e83be4f3d08 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 4 Jan 2019 15:13:52 +0100 Subject: mod_admin_telnet: Sort stats by name --- plugins/mod_admin_telnet.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index cd9f8078..5ce504f8 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -1543,7 +1543,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; -- cgit v1.2.3 From b2c3b2f740d777f1e04df40494f2be0637f946a6 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 16 Jan 2019 14:20:16 +0100 Subject: mod_admin_telnet: sttas:show: Use format option that allows float numbers string.format("%d", 0.5) causes an error on Lua 5.3 --- plugins/mod_admin_telnet.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 5ce504f8..7fae8983 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -1255,7 +1255,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 -- cgit v1.2.3 From ab545f19a339c76afe6d24912a79e05ee5f4d94c Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Tue, 19 Mar 2019 09:05:37 +0000 Subject: mod_admin_telnet: Show module status in module:list() --- plugins/mod_admin_telnet.lua | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 7fae8983..34cc4dc6 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -480,7 +480,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 -- cgit v1.2.3 From 196ac28ab5b002063f2b25dc60bd809c707b8ab2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Duarte?= Date: Wed, 17 Apr 2019 10:11:22 -0700 Subject: mod_admin_telnet: Adds c2s:closeall() (Fixes #1315) --- plugins/mod_admin_telnet.lua | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 34cc4dc6..c66630ca 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -250,6 +250,7 @@ function commands.help(session, data) print [[c2s:show_secure() - Show all encrypted client connections]] print [[c2s:show_tls() - Show TLS cipher info for encrypted sessions]] 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]] @@ -661,6 +662,16 @@ function def_env.c2s:close(match_jid) return true, "Total: "..count.." sessions closed"; end +function def_env.c2s:closeall() + local count = 0; + --luacheck: ignore 212/jid + show_c2s(function (jid, session) + count = count + 1; + session:close(); + end); + return true, "Total: "..count.." sessions closed"; +end + def_env.s2s = {}; function def_env.s2s:show(match_jid, annotate) -- cgit v1.2.3 From bbc0bd0b8aa6cca380e0e4b81a9ebc6051224b20 Mon Sep 17 00:00:00 2001 From: Arc Riley Date: Thu, 2 May 2019 16:33:14 -0700 Subject: mod_admin_telnet: include BOSH connections in c2s session commands (#998) --- plugins/mod_admin_telnet.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index c66630ca..5ee25771 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -591,6 +591,7 @@ end local function show_c2s(callback) local c2s = array.collect(values(module:shared"/*/c2s/sessions")); + c2s:append(values(module:shared"/*/bosh/sessions")); c2s:sort(function(a, b) if a.host == b.host then if a.username == b.username then -- cgit v1.2.3 From ed8b36a84b47e6ab21347d6cf9c616a69e0b3f7d Mon Sep 17 00:00:00 2001 From: Arc Riley Date: Thu, 2 May 2019 17:28:49 -0700 Subject: mod_admin_telnet: added "(bosh)" and "(websocket)" connection flags (#998) --- plugins/mod_admin_telnet.lua | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 5ee25771..248d6b07 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -554,6 +554,12 @@ local function session_flags(session, line) if session.is_bidi 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 -- cgit v1.2.3 From a16b70c96df75d562734f84e5dbda7bae2d41eed Mon Sep 17 00:00:00 2001 From: Arc Riley Date: Thu, 2 May 2019 17:44:21 -0700 Subject: mod_admin_telnet: include BOSH connections in c2s:count (#998) --- plugins/mod_admin_telnet.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 248d6b07..0fbd3ff9 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -612,7 +612,9 @@ local function show_c2s(callback) end function def_env.c2s:count() - return true, "Total: ".. iterators.count(values(module:shared"/*/c2s/sessions")) .." clients"; + local c2s_count = iterators.count(values(module:shared"/*/c2s/sessions")) + local bosh_count = iterators.count(values(module:shared"/*/bosh/sessions")) + return true, "Total: ".. c2s_count + bosh_count .." clients"; end function def_env.c2s:show(match_jid, annotate) -- cgit v1.2.3 From 2bb05d010d9b237a088bd9b4c997451407191d3f Mon Sep 17 00:00:00 2001 From: Michel Le Bihan Date: Mon, 3 Jun 2019 20:51:15 +0200 Subject: mod_admin_telnet: Collect array from Bosh connections when appending to connection list Fixes #1356 --- plugins/mod_admin_telnet.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 0fbd3ff9..fa03840b 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -597,7 +597,7 @@ end local function show_c2s(callback) local c2s = array.collect(values(module:shared"/*/c2s/sessions")); - c2s:append(values(module:shared"/*/bosh/sessions")); + c2s:append(array.collect(values(module:shared"/*/bosh/sessions"))); c2s:sort(function(a, b) if a.host == b.host then if a.username == b.username then -- cgit v1.2.3 From f9f7ac859acc839ac4b7ff95de811717ab2afd7e Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 10 May 2019 01:28:09 +0200 Subject: mod_admin_telnet: Check for simple commands before executing in sandbox This makes fixing yield over pcall boundry issue easier since it would have jumped to the thread error handler instead of proceeding to checking for simple commands. --- plugins/mod_admin_telnet.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index fa03840b..50753f04 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -114,6 +114,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); @@ -130,11 +135,6 @@ function console:process_line(session, line) 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); -- cgit v1.2.3 From a7e58a0c50dd7bf8d5321b50467707e93effc994 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 10 May 2019 01:29:26 +0200 Subject: mod_admin_telnet: Move error handling to thread callback (fixes #1391) Avoids yielding over pcall boundry, fixes xmpp:ping() command on Lua 5.1 --- plugins/mod_admin_telnet.lua | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 50753f04..55ed89b8 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -62,6 +62,9 @@ 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 @@ -133,13 +136,7 @@ function console:process_line(session, line) end end - local ranok, taskok, message = pcall(chunk); - - 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)); -- cgit v1.2.3 From 3effd36ff0bb97de406768b838cf51c9d7c0af41 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 26 Jul 2019 20:25:15 +0200 Subject: mod_admin_telnet: Include both c2s connections and sessions in c2s:show() This way both incomplete connections and hibernating c2s sessions are shown. --- plugins/mod_admin_telnet.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 55ed89b8..af0ac9e7 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -593,8 +593,10 @@ local function get_jid(session) end local function show_c2s(callback) - local c2s = array.collect(values(module:shared"/*/c2s/sessions")); + 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(); c2s:sort(function(a, b) if a.host == b.host then if a.username == b.username then -- cgit v1.2.3 From 65b25e80b00cd99a78a3d8ee9b5a916795e53774 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 26 Jul 2019 21:05:13 +0200 Subject: mod_admin_telnet: Factor out function for collecting all c2s sessions for easier reuse --- plugins/mod_admin_telnet.lua | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index af0ac9e7..4e34455c 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -592,12 +592,16 @@ local function get_jid(session) return jid_join("["..ip.."]:"..clientport, session.host or "["..serverip.."]:"..serverport); end -local function show_c2s(callback) +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(); - c2s:sort(function(a, b) + return c2s; +end + +local function show_c2s(callback) + 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 ""); -- cgit v1.2.3 From 54e39ab881929efad96e45c115e7d857b42f456b Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 26 Jul 2019 21:06:47 +0200 Subject: mod_admin_telnet: Make c2s:count() consistent with c2s:show() Both now operate on the same complete set of c2s sessions --- plugins/mod_admin_telnet.lua | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 4e34455c..a7ba41b8 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -615,9 +615,8 @@ local function show_c2s(callback) end function def_env.c2s:count() - local c2s_count = iterators.count(values(module:shared"/*/c2s/sessions")) - local bosh_count = iterators.count(values(module:shared"/*/bosh/sessions")) - return true, "Total: ".. c2s_count + bosh_count .." clients"; + local c2s = get_c2s(); + return true, "Total: ".. #c2s .." clients"; end function def_env.c2s:show(match_jid, annotate) -- cgit v1.2.3 From 1ede2571be5c8c7fa75c1be64b9951b5898915e8 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 26 Jul 2019 21:10:42 +0200 Subject: mod_admin_telnet: Add c2s:count() to help --- plugins/mod_admin_telnet.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index a7ba41b8..40160765 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -246,6 +246,7 @@ 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 -- cgit v1.2.3 From a8db3548e41edddef094c82ec1b8029834a5c47f Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 26 Jul 2019 21:13:17 +0200 Subject: mod_admin_telnet: Add xmpp:ping to help --- plugins/mod_admin_telnet.lua | 3 +++ 1 file changed, 3 insertions(+) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 40160765..7f324999 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -239,6 +239,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 @@ -282,6 +283,8 @@ 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.]] elseif section == "console" then -- cgit v1.2.3 From 49b3e1479cef50bc69f72b5e32ff115c7975c062 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 28 Jul 2019 01:39:47 +0200 Subject: mod_admin_telnet: Allow specifying a reason when closing sessions (#1400) --- plugins/mod_admin_telnet.lua | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 7f324999..df702b91 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -666,23 +666,32 @@ 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() +function def_env.c2s:closeall(text, condition) local count = 0; --luacheck: ignore 212/jid show_c2s(function (jid, session) count = count + 1; - session:close(); + session:close(build_reason(text, condition)); end); return true, "Total: "..count.." sessions closed"; end @@ -887,7 +896,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"; @@ -905,19 +914,19 @@ function def_env.s2s:close(from, to) 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 -- cgit v1.2.3 From 9e57e0279736a1ec952d4915b02735b14ddb6d19 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 28 Jul 2019 01:43:10 +0200 Subject: mod_admin_telnet: Use already generated session id Don't need to construct it from components again --- plugins/mod_admin_telnet.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index df702b91..b6cdfe82 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -910,7 +910,7 @@ function def_env.s2s:close(from, to, text, condition) 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)); -- cgit v1.2.3 From 60fddf5c7c7a2272f0235cc5d702f7a89c0e9568 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 7 Sep 2019 15:53:05 +0200 Subject: mod_admin_telnet: Identify bidi-capable s2sout sessions (fixes #1403) --- plugins/mod_admin_telnet.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index b6cdfe82..c184924c 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -552,7 +552,7 @@ local function session_flags(session, line) if session.remote then line[#line+1] = "(remote)"; end - if session.is_bidi then + if session.is_bidi or session.bidi_session then line[#line+1] = "(bidi)"; end if session.bosh_version then -- cgit v1.2.3 From 2a9da5b8f04ce7b13d9db194fa01a36ec4b6ac91 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 8 Sep 2019 18:51:15 +0200 Subject: mod_admin_telnet: Identify native bidi sessions --- plugins/mod_admin_telnet.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index c184924c..5c08b8d1 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -552,7 +552,9 @@ local function session_flags(session, line) if session.remote then line[#line+1] = "(remote)"; end - if session.is_bidi or session.bidi_session then + 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 -- cgit v1.2.3 From f304a306dd05b8f99ba5ef26d301ea33731d2007 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 29 Sep 2019 18:44:58 +0200 Subject: mod_admin_telnet: Use new compact function for waiting on promises --- plugins/mod_admin_telnet.lua | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 5c08b8d1..24230257 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -1148,13 +1148,7 @@ function def_env.xmpp:ping(localhost, remotehost, timeout) end local iq = st.iq{ from=localhost, to=remotehost, type="get", id=new_id()} :tag("ping", {xmlns="urn:xmpp:ping"}); - local ret, err; - local wait, done = async.waiter(); - module:context(localhost):send_iq(iq, nil, timeout) - :next(function (ret_) ret = ret_; end, - function (err_) err = err_; end) - :finally(done); - wait(); + local ret, err = async.wait(module:context(localhost):send_iq(iq, nil, timeout)); if ret then return true, "pong from " .. ret.stanza.attr.from; else -- cgit v1.2.3 From 7b43531fa9779ba0ec001e87cbe5004cc8e303f1 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 6 Oct 2019 19:35:35 +0200 Subject: mod_admin_telnet: xmpp:ping: Log ping time --- plugins/mod_admin_telnet.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 24230257..afb4fb4b 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -33,6 +33,7 @@ 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"); @@ -1148,9 +1149,10 @@ function def_env.xmpp:ping(localhost, remotehost, timeout) 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 " .. ret.stanza.attr.from; + return true, ("pong from %s in %gs"):format(ret.stanza.attr.from, time.now() - time_start); else return false, tostring(err); end -- cgit v1.2.3 From 1a78e0a7ac93b3d0b4251a865eb994bcd3e5eaba Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 2 Nov 2019 16:02:37 +0100 Subject: mod_admin_telnet: Show s2s authentication method (probably) used --- plugins/mod_admin_telnet.lua | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index afb4fb4b..2bbd367b 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -538,6 +538,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 -- cgit v1.2.3 From e130b377970996b9eb3d36c74db121835ed0a57e Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 20 Nov 2019 21:31:46 +0100 Subject: mod_admin_telnet: Show SNI name in show_tls() if available --- plugins/mod_admin_telnet.lua | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 2bbd367b..cef79d25 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -584,6 +584,12 @@ 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 else line[#line+1] = "(insecure)"; end -- cgit v1.2.3 From 593c04436f5052d8ed201dc6a2f29e52ff5ab622 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 21 Nov 2019 00:16:20 +0100 Subject: mod_admin_telnet: Display ALPN in show_tls() if supported and available --- plugins/mod_admin_telnet.lua | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index cef79d25..f3ab9597 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -590,6 +590,12 @@ local function tls_info(session, line) 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 -- cgit v1.2.3 From 3d63c139e666e1a89f5caa0eb96afc20ac43fc2a Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 30 Nov 2019 21:56:21 +0100 Subject: mod_admin_telnet: Sort hosts Groups by domain in DNS hierarchy order or something. Why not split on '.' you ask? Well becasue that's not what I typed here. Also "[^.]" is longer than "%P". --- plugins/mod_admin_telnet.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index f3ab9597..8427f811 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -964,11 +964,15 @@ function def_env.host:deactivate(hostname, reason) return hostmanager.deactivate(hostname, reason); end +local function compare_hosts(a, b) + return a:gsub("%P", string.reverse):reverse() < b:gsub("%P", string.reverse):reverse(); +end + 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, compare_hosts) do i = i + 1; type = host_session.type; if type == "local" then -- cgit v1.2.3 From 655294f93e53175928cf6523b82952134e268495 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 8 Dec 2019 13:38:48 +0100 Subject: mod_admin_telnet: Avoid using LuaSocket for timestamps Using util.time will make it easier to move away from LuaSocket if we ever wanted to do that. --- plugins/mod_admin_telnet.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 8427f811..60f92cda 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -1265,7 +1265,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; @@ -1293,7 +1292,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; -- cgit v1.2.3 From d7570eee7e9f2ff351a58ee541395522d5cf7385 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 15 Dec 2019 20:43:02 +0100 Subject: mod_admin_telnet: Fix host sorting Reversing each %P is a noop --- plugins/mod_admin_telnet.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 60f92cda..940c3533 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -965,7 +965,7 @@ function def_env.host:deactivate(hostname, reason) end local function compare_hosts(a, b) - return a:gsub("%P", string.reverse):reverse() < b:gsub("%P", string.reverse):reverse(); + return a:gsub("%P+", string.reverse):reverse() < b:gsub("%P+", string.reverse):reverse(); end function def_env.host:list() -- cgit v1.2.3 From d0cd5469d272364bede91196edbb783e3fe1f769 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 15 Dec 2019 20:44:10 +0100 Subject: mod_admin_telnet: Sort by complete labels Might as well. --- plugins/mod_admin_telnet.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 940c3533..6dc7c5c3 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -965,7 +965,7 @@ function def_env.host:deactivate(hostname, reason) end local function compare_hosts(a, b) - return a:gsub("%P+", string.reverse):reverse() < b:gsub("%P+", string.reverse):reverse(); + return a:gsub("[^.]+", string.reverse):reverse() < b:gsub("[^.]+", string.reverse):reverse(); end function def_env.host:list() -- cgit v1.2.3 From d146d6b8acb261c4a2e1f6d721fdd44243805fd3 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 15 Dec 2019 21:42:42 +0100 Subject: mod_admin_telnet: Merge hostname comparison functions Missed that there existed one already when writing the one for host:list --- plugins/mod_admin_telnet.lua | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 6dc7c5c3..6ed36258 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -433,7 +433,7 @@ 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) @@ -964,15 +964,11 @@ function def_env.host:deactivate(hostname, reason) return hostmanager.deactivate(hostname, reason); end -local function compare_hosts(a, b) - return a:gsub("[^.]+", string.reverse):reverse() < b:gsub("[^.]+", string.reverse):reverse(); -end - 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, compare_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 -- cgit v1.2.3 From 7b64b46af18c1ff2816547f6f6f0f046fdabf88f Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 15 Dec 2019 22:07:24 +0100 Subject: mod_admin_telnet: Refactor internal function for listing hosts Splits out a function that doesn't deal with modules for reuse elsewhere --- plugins/mod_admin_telnet.lua | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 6ed36258..fffab4cf 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -364,7 +364,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); @@ -374,17 +374,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; @@ -411,7 +417,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; @@ -437,7 +443,7 @@ local function _sort_hosts(a, b) 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; -- cgit v1.2.3 From 567e4183b5e2ba4fe92ef0ef86b34327f036e9f1 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 15 Dec 2019 22:08:20 +0100 Subject: mod_admin_telnet: Sort hosts in module:list --- plugins/mod_admin_telnet.lua | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index fffab4cf..fbaa80d0 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -466,16 +466,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 -- cgit v1.2.3 From 13622b141e299ba3df60b82153084f34bdcb8e6f Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 15 Dec 2019 21:44:58 +0100 Subject: mod_admin_telnet: Use existing host comparison when comparing JIDs --- plugins/mod_admin_telnet.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index fbaa80d0..a454f568 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -630,7 +630,7 @@ local function show_c2s(callback) 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); -- cgit v1.2.3 From fac877feaa6c394469614b4604f1723b6bb3b681 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 15 Dec 2019 22:15:52 +0100 Subject: mod_admin_telnet: Use common sort function in s2s:show --- plugins/mod_admin_telnet.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index a454f568..9468be62 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -781,8 +781,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 -- cgit v1.2.3 From 6183e7a303d4fed16b368b55dcd0fddc34b20739 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 22 Dec 2019 20:10:20 +0100 Subject: mod_admin_telnet: Include config:get() in help text --- plugins/mod_admin_telnet.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 9468be62..f298ad2f 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -288,6 +288,7 @@ function commands.help(session, data) 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'.]] -- cgit v1.2.3 From d7df11baf30a0308a8ed1c40b365051a19c74f92 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 23 Dec 2019 21:38:19 +0100 Subject: mod_admin_telnet: Silence luacheck warnings --- plugins/mod_admin_telnet.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index f298ad2f..7df63da9 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -125,6 +125,7 @@ function console:process_line(session, line) local chunkname = "=console"; local env = (useglobalenv and redirect_output(_G, session)) or session.env or nil + -- luacheck: ignore 311/err local chunk, err = envload("return "..line, chunkname, env); if not chunk then chunk, err = envload(line, chunkname, env); @@ -1427,7 +1428,7 @@ end function stats_methods:cfgraph() for _, stat_info in ipairs(self) do - local name, type, value, data = unpack(stat_info, 1, 4); + local name, type, value, data = unpack(stat_info, 1, 4); -- luacheck: ignore 211 local function print(s) table.insert(stat_info.output, s); end @@ -1493,7 +1494,7 @@ end function stats_methods:histogram() for _, stat_info in ipairs(self) do - local name, type, value, data = unpack(stat_info, 1, 4); + local name, type, value, data = unpack(stat_info, 1, 4); -- luacheck: ignore 211 local function print(s) table.insert(stat_info.output, s); end @@ -1593,6 +1594,7 @@ local function new_stats_context(self) end function def_env.stats:show(filter) + -- luacheck: ignore 211/changed local stats, changed, extra = require "core.statsmanager".get_stats(); local available, displayed = 0, 0; local displayed_stats = new_stats_context(self); -- cgit v1.2.3 From b37a36b1be0a8a91e851bcffe1f3e84c9f0f6151 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 24 Jan 2020 23:29:14 +0100 Subject: mod_admin_telnet: Use promise based DNS resolving Mostly done for testing this new API --- plugins/mod_admin_telnet.lua | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 5ffd40e0..fba10faf 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -1189,14 +1189,12 @@ end function def_env.dns:lookup(name, typ, class) local resolver = get_resolver(self.session); - local ret = "Query sent"; - local print = self.session.print; - local function handler(...) - ret = "Got response"; - print(...); + local ret, err = async.wait(resolver:lookup_promise(name, typ, class)); + if ret then + return true, ret; + elseif err then + return false, err; end - resolver:lookup(handler, name, typ, class); - return true, ret; end function def_env.dns:addnameserver(...) -- cgit v1.2.3 From 3827fd9716252e9b74bef848fd39879dd2699950 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 5 Feb 2020 23:29:39 +0100 Subject: mod_admin_telnet: Avoid indexing missing socket (thanks tmolitor) if `sock` was nil it would still proceed with SNI and ALPN checks --- plugins/mod_admin_telnet.lua | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index fba10faf..2485e698 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -580,20 +580,20 @@ local function tls_info(session, line) if sock and sock.info then local info = sock:info(); line[#line+1] = ("(%s with %s)"):format(info.protocol, info.cipher); - 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); + if sock.getsniname then + local name = sock:getsniname(); + if name then + line[#line+1] = ("(SNI:%q)"):format(name); + end end - end - if sock.getalpn then - local proto = sock:getalpn(); - if proto then - line[#line+1] = ("(ALPN:%q)"):format(proto); + if sock.getalpn then + local proto = sock:getalpn(); + if proto then + line[#line+1] = ("(ALPN:%q)"):format(proto); + end end + else + line[#line+1] = "(cipher info unavailable)"; end else line[#line+1] = "(insecure)"; -- cgit v1.2.3 From 23cd82ba5fd0a00d85fb7d6a708723419fdc0cb4 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 22 Feb 2020 18:23:38 +0100 Subject: mod_admin_telnet: Reflow hosts filter for readability --- plugins/mod_admin_telnet.lua | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 2485e698..730053fe 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -384,7 +384,12 @@ end -- 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; + / function (host) + if prosody.hosts[host].type == "local" or module and modulemanager.is_loaded(host, module) then + return host; + end; + return nil; + end; if module and modulemanager.get_module("*", module) then hosts_set:add("*"); end -- cgit v1.2.3 From 3947003b7e86a084cc725b5c68d7b4e8724e3312 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 22 Feb 2020 18:32:50 +0100 Subject: mod_admin_telnet: Fix host selection filter, fixes loading on components get_hosts_with_module(a component, mod) would still filter out components since they don't have type="component" instead of "local" Introduced in 4d3549e64489 --- plugins/mod_admin_telnet.lua | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 730053fe..2688209b 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -385,10 +385,24 @@ end local function get_hosts_with_module(hosts, module) local hosts_set = get_hosts_set(hosts) / function (host) - if prosody.hosts[host].type == "local" or module and modulemanager.is_loaded(host, module) then - return host; + if module then + -- Module given, filter in hosts with this module loaded + if modulemanager.is_loaded(host, module) then + return host; + else + return nil; + end + end + if not hosts then + -- No hosts given, filter in VirtualHosts + if prosody.hosts[host].type == "local" then + return host; + else + return nil + end end; - return nil; + -- No module given, but hosts are, don't filter at all + return host; end; if module and modulemanager.get_module("*", module) then hosts_set:add("*"); -- cgit v1.2.3 From 1969b96da192e7d8e3233883bdea256942c73a7b Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 24 Feb 2020 18:38:09 +0100 Subject: mod_admin_telnet: Allow passing list of hosts to http:list() Lets you select what hosts to list http services on. In particular, this enables listing global http services, which was not possible before. --- plugins/mod_admin_telnet.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 2688209b..4dc32e47 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -1241,10 +1241,10 @@ end def_env.http = {}; -function def_env.http:list() +function def_env.http:list(hosts) local print = self.session.print; - for host in pairs(prosody.hosts) do + for host in get_hosts_set(hosts) do local http_apps = modulemanager.get_items("http-provider", host); if #http_apps > 0 then local http_host = module:context(host):get_option_string("http_host"); -- cgit v1.2.3 From edce14b4a40664a6f10239d7f2ef50421f6a93af Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 29 Apr 2020 22:23:05 +0200 Subject: mod_admin_telnet: Pretty-print values returned from commands This makes it much nicer to inspect Prosody internals. Existing textual status messages from commands are not serialized to preserve existing behavior. Explicit serialization of configuration is kept in order to make it clear that returned strings are serialized strings that would look like what's actually in the config file. The default maxdepth of 2 seems ought to be an okay default, balanced between showing enough structure to continue exploring and DoS-ing your terminal. Thanks to Ge0rG for the motivation to finally do this. --- plugins/mod_admin_telnet.lua | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 2efd424c..5407b737 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -32,7 +32,8 @@ 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 serialization = require "util.serialization"; +local serialize_config = serialization.new ({ fatal = false, unquoted = true}); local time = require "util.time"; local commands = module:shared("commands") @@ -80,6 +81,7 @@ function console:new_session(conn) end w("| "..table.concat(t, "\t").."\n"); end; + serialize = serialization.new({ fatal = false, unquoted = true, maxdepth = 2}); disconnect = function () conn:close(); end; }; session.env = setmetatable({}, default_env_mt); @@ -141,7 +143,10 @@ function console:process_line(session, line) local taskok, message = chunk(); if not message then - session.print("Result: "..tostring(taskok)); + if type(taskok) ~= "string" then + taskok = session.serialize(taskok); + end + session.print("Result: "..taskok); return; elseif (not taskok) and message then session.print("Command completed with a problem"); @@ -149,7 +154,11 @@ function console:process_line(session, line) return; end - session.print("OK: "..tostring(message)); + if type(message) ~= "string" then + message = session.serialize(message); + end + + session.print("OK: "..message); end local sessions = {}; @@ -527,7 +536,7 @@ function def_env.config:get(host, key) host, key = "*", host; end local config_get = require "core.configmanager".get - return true, serialize(config_get(host, key)); + return true, serialize_config(config_get(host, key)); end function def_env.config:reload() -- cgit v1.2.3 From de35ba33a1347a42004b039bce2fc09a284e9dd6 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 29 Apr 2020 22:48:36 +0200 Subject: mod_admin_telnet: Document (in the internal help) debug commands --- plugins/mod_admin_telnet.lua | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 5407b737..71a9420b 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -251,6 +251,7 @@ function commands.help(session, data) 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 [[debug - Commands for debugging the server]] print [[config - Reloading the configuration, etc.]] print [[console - Help regarding the console itself]] elseif section == "c2s" then @@ -299,6 +300,10 @@ function commands.help(session, data) 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 == "debug" then + print [[debug:logevents(host) - Enable logging of fired events on host]] + print [[debug:events(host, event) - Show registered event handlers]] + print [[debug:timers() - Show information about scheduled timers]] 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'.]] -- cgit v1.2.3 From 3da0521744c13023ebb07bd261154277d103ab25 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 29 Apr 2020 22:56:35 +0200 Subject: mod_admin_telnet: Document HTTP command in internal help --- plugins/mod_admin_telnet.lua | 3 +++ 1 file changed, 3 insertions(+) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 71a9420b..96b5a97b 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -244,6 +244,7 @@ function commands.help(session, data) print [[]] print [[c2s - Commands to manage local client-to-server sessions]] print [[s2s - Commands to manage sessions between this server and others]] + print [[http - Commands to inspect HTTP services]] -- XXX plural but there is only one so far print [[module - Commands to load/reload/unload modules/plugins]] print [[host - Commands to activate, deactivate and list virtual hosts]] print [[user - Commands to create and delete users, and change their passwords]] @@ -267,6 +268,8 @@ function commands.help(session, data) print [[s2s:show_tls(domain) - Show TLS cipher info for encrypted sessions]] print [[s2s:close(from, to) - Close a connection from one domain to another]] print [[s2s:closeall(host) - Close all the incoming/outgoing s2s sessions to specified host]] + elseif section == "http" then + print [[http:list(hosts) - Show HTTP endpoints]] elseif section == "module" then print [[module:load(module, host) - Load the specified module on the specified host (or all hosts if none given)]] print [[module:reload(module, host) - The same, but unloads and loads the module (saving state if the module supports it)]] -- cgit v1.2.3 From c38264dd58ffae438f630380fed2996f4f515afe Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 29 Apr 2020 22:59:01 +0200 Subject: mod_admin_telnet: Add a TODO for someone to find in the future --- plugins/mod_admin_telnet.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 96b5a97b..a27f6d5c 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -303,6 +303,7 @@ function commands.help(session, data) 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 == "stats" then -- TODO describe how this works elseif section == "debug" then print [[debug:logevents(host) - Enable logging of fired events on host]] print [[debug:events(host, event) - Show registered event handlers]] -- cgit v1.2.3 From 222299d18c5524cc3ee344e6c24f13dfb4a876dd Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 29 Apr 2020 23:15:01 +0200 Subject: mod_admin_telnet: Add a command to configure pretty-printing settings Sometimes you wanna adjust the maxdepth or something. --- plugins/mod_admin_telnet.lua | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index a27f6d5c..d25a576a 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -330,6 +330,11 @@ end --luacheck: ignore 212/self +def_env.output = {}; +function def_env.output:configure(opts) + self.session.serialize = serialization.new(opts); +end + def_env.server = {}; function def_env.server:insane_reload() -- cgit v1.2.3 From 7369538232b41b98872b5bf45759fc19d6293727 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 29 Apr 2020 23:28:21 +0200 Subject: mod_admin_telnet: Silence luacheck --- plugins/mod_admin_telnet.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index d25a576a..dcc19687 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -303,7 +303,8 @@ function commands.help(session, data) 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 == "stats" then -- TODO describe how this works + elseif section == "stats" then -- luacheck: ignore 542 + -- TODO describe how stats:show() works elseif section == "debug" then print [[debug:logevents(host) - Enable logging of fired events on host]] print [[debug:events(host, event) - Show registered event handlers]] -- cgit v1.2.3 From bfdff4488f72dd5e3595968b2fdb14d054c3977c Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 2 May 2020 20:32:43 +0200 Subject: mod_admin_telnet: Allow configuring pretty printing defaults Mostly just to have the defaults merged so you can e.g. output:configure({maxdepth=1}) --- plugins/mod_admin_telnet.lua | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index dcc19687..93a4587f 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -330,9 +330,18 @@ end -- Anything in def_env will be accessible within the session as a global variable --luacheck: ignore 212/self +local serialize_defaults = module:get_option("console_prettyprint_settings", { fatal = false, unquoted = true, maxdepth = 2}) def_env.output = {}; function def_env.output:configure(opts) + if type(opts) ~= "table" then + opts = { preset = opts }; + end + for k,v in pairs(serialize_defaults) do + if opts[k] == nil then + opts[k] = v; + end + end self.session.serialize = serialization.new(opts); end -- cgit v1.2.3 From f1c4d468e2134147b6aec12910837d05b332b8d6 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 2 May 2020 20:37:49 +0200 Subject: mod_admin_telnet: Reuse existing pretty printing setup Didn't do the configurable defaults thing here because I was going to do this, so that there's only one spot where it's done. --- plugins/mod_admin_telnet.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 93a4587f..792f4f78 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -81,7 +81,7 @@ function console:new_session(conn) end w("| "..table.concat(t, "\t").."\n"); end; - serialize = serialization.new({ fatal = false, unquoted = true, maxdepth = 2}); + serialize = tostring; disconnect = function () conn:close(); end; }; session.env = setmetatable({}, default_env_mt); @@ -98,6 +98,8 @@ function console:new_session(conn) end end + session.env.output:configure(); + return session; end -- cgit v1.2.3 From 95b5facf3bc6ec7b346ea6dff07522fc9aceda4e Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 2 May 2020 20:39:33 +0200 Subject: mod_admin_telnet: Don't pretty-print the normal console stuff Typing e.g. `c2s` would dump out a bunch of stuff that would probably just confuse users. Now you only get pretty-printing when poking around in the internals with `>`. --- plugins/mod_admin_telnet.lua | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 792f4f78..a5657d09 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -145,10 +145,10 @@ function console:process_line(session, line) local taskok, message = chunk(); if not message then - if type(taskok) ~= "string" then + if type(taskok) ~= "string" and useglobalenv then taskok = session.serialize(taskok); end - session.print("Result: "..taskok); + session.print("Result: "..tostring(taskok)); return; elseif (not taskok) and message then session.print("Command completed with a problem"); @@ -156,11 +156,7 @@ function console:process_line(session, line) return; end - if type(message) ~= "string" then - message = session.serialize(message); - end - - session.print("OK: "..message); + session.print("OK: "..tostring(message)); end local sessions = {}; -- cgit v1.2.3 From 783f5430a57d1f543f0eddbc3d6b6a3185be8008 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 2 May 2020 20:41:35 +0200 Subject: mod_admin_telnet: Use tostring as fallback in pretty printing This has some nice effects such as functions, VirtualHosts and other things being printed using their `__tostring` metamethod. --- plugins/mod_admin_telnet.lua | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index a5657d09..3014517c 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -335,6 +335,10 @@ function def_env.output:configure(opts) if type(opts) ~= "table" then opts = { preset = opts }; end + if not opts.fallback then + -- XXX Error message passed to fallback is lost, does it matter? + opts.fallback = tostring; + end for k,v in pairs(serialize_defaults) do if opts[k] == nil then opts[k] = v; -- cgit v1.2.3 From 29f51d7e6dd71d1a702410cf24ea59edd7e5afcd Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 16 May 2020 20:46:12 +0200 Subject: mod_admin_telnet: Update existing sessions on reload This removes the need to disconnect and reconnect to the telnet console for changes to take effect. --- plugins/mod_admin_telnet.lua | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 3014517c..a082a851 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -161,6 +161,20 @@ end local sessions = {}; +function module.save() + return { sessions = sessions } +end + +function module.restore(data) + if data.sessions then + for conn in pairs(data.sessions) do + conn:setlistener(console_listener); + local session = console:new_session(conn); + sessions[conn] = session; + end + end +end + function console_listener.onconnect(conn) -- Handle new connection local session = console:new_session(conn); -- cgit v1.2.3 From b0463f02908a21058f273aa971a1c06808a4cdd0 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 1 Jun 2020 15:43:47 +0100 Subject: mod_admin_telnet: Become a front for mod_admin_shell --- plugins/mod_admin_telnet.lua | 1623 +----------------------------------------- 1 file changed, 35 insertions(+), 1588 deletions(-) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index a082a851..ec8ff136 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -8,46 +8,37 @@ -- luacheck: ignore 212/self module:set_global(); - -local hostmanager = require "core.hostmanager"; -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; +module:depends("admin_shell"); 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"); -local set, array = require "util.set", require "util.array"; -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 serialization = require "util.serialization"; -local serialize_config = serialization.new ({ fatal = false, unquoted = true}); -local time = require "util.time"; +local st = require "util.stanza"; -local commands = module:shared("commands") -local def_env = module:shared("env"); +local def_env = module:shared("admin_shell/env"); local default_env_mt = { __index = def_env }; -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 - return f(); - end; - return env; +local function printbanner(session) + local option = module:get_option_string("console_banner", "full"); + if option == "full" or option == "graphic" then + session.print [[ + ____ \ / _ + | _ \ _ __ ___ ___ _-_ __| |_ _ + | |_) | '__/ _ \/ __|/ _ \ / _` | | | | + | __/| | | (_) \__ \ |_| | (_| | |_| | + |_| |_| \___/|___/\___/ \__,_|\__, | + A study in simplicity |___/ + +]] + end + 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("https://prosody.im/doc/console\n"); + end + if option ~= "short" and option ~= "full" and option ~= "graphic" then + session.print(option); + end end console = {}; @@ -73,7 +64,12 @@ end function console:new_session(conn) local w = function(s) conn:write(s:gsub("\n", "\r\n")); end; local session = { conn = conn; - send = function (t) w(tostring(t)); end; + send = function (t) + if st.is_stanza(t) and t.name == "repl-result" then + t = "| "..t:get_text().."\n"; + end + w(tostring(t)); + end; print = function (...) local t = {}; for i=1,select("#", ...) do @@ -104,59 +100,13 @@ function console:new_session(conn) end function console:process_line(session, line) - local useglobalenv; - - if line:match("^>") then - line = line:gsub("^>", ""); - useglobalenv = true; - elseif line == "\004" then - commands["bye"](session, line); - return; - else - local command = line:match("^%w+") or line:match("%p"); - if commands[command] then - commands[command](session, line); - return; - end - end - - 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 - -- luacheck: ignore 311/err - local chunk, err = envload("return "..line, chunkname, env); - if not chunk then - chunk, err = envload(line, chunkname, env); - if not chunk then - err = err:gsub("^%[string .-%]:%d+: ", ""); - err = err:gsub("^:%d+: ", ""); - err = err:gsub("''", "the end of the line"); - session.print("Sorry, I couldn't understand that... "..err); - return; - end - end - - local taskok, message = chunk(); - - if not message then - if type(taskok) ~= "string" and useglobalenv then - taskok = session.serialize(taskok); - end - session.print("Result: "..tostring(taskok)); - return; - elseif (not taskok) and message then - session.print("Command completed with a problem"); - session.print("Message: "..tostring(message)); + line = line:gsub("\r?\n$", ""); + if line == "bye" or line == "quit" or line == "exit" or line:byte() == 4 then + session.print("See you!"); + session:disconnect(); return; end - - session.print("OK: "..tostring(message)); + return module:fire_event("admin/repl-line", { origin = session, stanza = st.stanza("repl"):text(line) }); end local sessions = {}; @@ -218,1509 +168,6 @@ function console_listener.ondetach(conn) sessions[conn] = nil; end --- Console commands -- --- These are simple commands, not valid standalone in Lua - -function commands.bye(session) - session.print("See you! :)"); - session.closed = true; - session.disconnect(); -end -commands.quit, commands.exit = commands.bye, commands.bye; - -commands["!"] = function (session, data) - if data:match("^!!") and session.env._ then - session.print("!> "..session.env._); - return console_listener.onincoming(session.conn, session.env._); - end - local old, new = data:match("^!(.-[^\\])!(.-)!$"); - if old and new then - local ok, res = pcall(string.gsub, session.env._, old, new); - if not ok then - session.print(res) - return; - end - session.print("!> "..res); - return console_listener.onincoming(session.conn, res); - end - session.print("Sorry, not sure what you want"); -end - - -function commands.help(session, data) - local print = session.print; - local section = data:match("^help (%w+)"); - if not section then - print [[Commands are divided into multiple sections. For help on a particular section, ]] - print [[type: help SECTION (for example, 'help c2s'). Sections are: ]] - print [[]] - print [[c2s - Commands to manage local client-to-server sessions]] - print [[s2s - Commands to manage sessions between this server and others]] - print [[http - Commands to inspect HTTP services]] -- XXX plural but there is only one so far - print [[module - Commands to load/reload/unload modules/plugins]] - print [[host - Commands to activate, deactivate and list virtual hosts]] - print [[user - Commands to create and delete users, and change their passwords]] - 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 [[debug - Commands for debugging the server]] - print [[config - Reloading the configuration, etc.]] - print [[console - Help regarding the console itself]] - elseif section == "c2s" then - print [[c2s:show(jid) - Show all client sessions with the specified JID (or all if no JID given)]] - 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]] - print [[s2s:close(from, to) - Close a connection from one domain to another]] - print [[s2s:closeall(host) - Close all the incoming/outgoing s2s sessions to specified host]] - elseif section == "http" then - print [[http:list(hosts) - Show HTTP endpoints]] - elseif section == "module" then - print [[module:load(module, host) - Load the specified module on the specified host (or all hosts if none given)]] - print [[module:reload(module, host) - The same, but unloads and loads the module (saving state if the module supports it)]] - print [[module:unload(module, host) - The same, but just unloads the module from memory]] - print [[module:list(host) - List the modules loaded on the specified host]] - elseif section == "host" then - print [[host:activate(hostname) - Activates the specified host]] - print [[host:deactivate(hostname) - Disconnects all clients on this host and deactivates]] - print [[host:list() - List the currently-activated hosts]] - elseif section == "user" then - print [[user:create(jid, password) - Create the specified user account]] - print [[user:password(jid, password) - Set the password for the specified user account]] - print [[user:delete(jid) - Permanently remove the specified user account]] - print [[user:list(hostname, pattern) - List users on the specified host, optionally filtering with a pattern]] - elseif section == "server" then - print [[server:version() - Show the server's version number]] - print [[server:uptime() - Show how long the server has been running]] - print [[server:memory() - Show details about the server's memory usage]] - print [[server:shutdown(reason) - Shut down the server, with an optional reason to be broadcast to all connections]] - elseif section == "port" then - print [[port:list() - Lists all network ports prosody currently listens on]] - print [[port:close(port, interface) - Close a port]] - elseif section == "dns" then - print [[dns:lookup(name, type, class) - Do a DNS lookup]] - print [[dns:addnameserver(nameserver) - Add a nameserver to the list]] - 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 == "stats" then -- luacheck: ignore 542 - -- TODO describe how stats:show() works - elseif section == "debug" then - print [[debug:logevents(host) - Enable logging of fired events on host]] - print [[debug:events(host, event) - Show registered event handlers]] - print [[debug:timers() - Show information about scheduled timers]] - 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'.]] - print [[Secondly, note that we don't support the full telnet protocol yet (it's coming)]] - print [[so you may have trouble using the arrow keys, etc. depending on your system.]] - print [[]] - print [[For now we offer a couple of handy shortcuts:]] - print [[!! - Repeat the last command]] - print [[!old!new! - repeat the last command, but with 'old' replaced by 'new']] - print [[]] - print [[For those well-versed in Prosody's internals, or taking instruction from those who are,]] - print [[you can prefix a command with > to escape the console sandbox, and access everything in]] - print [[the running server. Great fun, but be careful not to break anything :)]] - end - print [[]] -end - --- Session environment -- --- Anything in def_env will be accessible within the session as a global variable - ---luacheck: ignore 212/self -local serialize_defaults = module:get_option("console_prettyprint_settings", { fatal = false, unquoted = true, maxdepth = 2}) - -def_env.output = {}; -function def_env.output:configure(opts) - if type(opts) ~= "table" then - opts = { preset = opts }; - end - if not opts.fallback then - -- XXX Error message passed to fallback is lost, does it matter? - opts.fallback = tostring; - end - for k,v in pairs(serialize_defaults) do - if opts[k] == nil then - opts[k] = v; - end - end - self.session.serialize = serialization.new(opts); -end - -def_env.server = {}; - -function def_env.server:insane_reload() - prosody.unlock_globals(); - dofile "prosody" - prosody = _G.prosody; - return true, "Server reloaded"; -end - -function def_env.server:version() - return true, tostring(prosody.version or "unknown"); -end - -function def_env.server:uptime() - local t = os.time()-prosody.start_time; - local seconds = t%60; - t = (t - seconds)/60; - local minutes = t%60; - t = (t - minutes)/60; - local hours = t%24; - t = (t - hours)/24; - local days = t; - return true, string.format("This server has been running for %d day%s, %d hour%s and %d minute%s (since %s)", - days, (days ~= 1 and "s") or "", hours, (hours ~= 1 and "s") or "", - minutes, (minutes ~= 1 and "s") or "", os.date("%c", prosody.start_time)); -end - -function def_env.server:shutdown(reason) - prosody.shutdown(reason); - return true, "Shutdown initiated"; -end - -local function human(kb) - local unit = "K"; - if kb > 1024 then - kb, unit = kb/1024, "M"; - end - return ("%0.2f%sB"):format(kb, unit); -end - -function def_env.server:memory() - if not has_pposix or not pposix.meminfo then - return true, "Lua is using "..human(collectgarbage("count")); - end - local mem, lua_mem = pposix.meminfo(), collectgarbage("count"); - local print = self.session.print; - print("Process: "..human((mem.allocated+mem.allocated_mmap)/1024)); - print(" Used: "..human(mem.used/1024).." ("..human(lua_mem).." by Lua)"); - print(" Free: "..human(mem.unused/1024).." ("..human(mem.returnable/1024).." returnable)"); - return true, "OK"; -end - -def_env.module = {}; - -local function get_hosts_set(hosts) - if type(hosts) == "table" then - if hosts[1] then - return set.new(hosts); - elseif hosts._items then - return hosts; - end - elseif type(hosts) == "string" then - return set.new { hosts }; - elseif hosts == nil then - 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) - if module then - -- Module given, filter in hosts with this module loaded - if modulemanager.is_loaded(host, module) then - return host; - else - return nil; - end - end - if not hosts then - -- No hosts given, filter in VirtualHosts - if prosody.hosts[host].type == "local" then - return host; - else - return nil - end - end; - -- No module given, but hosts are, don't filter at all - return host; - 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_with_module(hosts); - - -- Load the module for each host - 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); - if not mod then - ok = false; - if err == "global-module-already-loaded" then - if count > 0 then - ok, err, count = true, nil, 1; - end - break; - end - self.session.print(err or "Unknown error loading module"); - else - count = count + 1; - self.session.print("Loaded for "..mod.module.host); - end - end - end - - return ok, (ok and "Module loaded onto "..count.." host"..(count ~= 1 and "s" or "")) or ("Last error: "..tostring(err)); -end - -function def_env.module:unload(name, hosts) - hosts = get_hosts_with_module(hosts, name); - - -- Unload the module for each host - local ok, err, count = true, nil, 0; - for host in hosts do - if modulemanager.is_loaded(host, name) then - ok, err = modulemanager.unload(host, name); - if not ok then - ok = false; - self.session.print(err or "Unknown error unloading module"); - else - count = count + 1; - self.session.print("Unloaded from "..host); - end - end - end - 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:gsub("[^.]+", string.reverse):reverse() < b:gsub("[^.]+", string.reverse):reverse(); end -end - -function def_env.module:reload(name, 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; - for _, host in ipairs(hosts) do - if modulemanager.is_loaded(host, name) then - ok, err = modulemanager.reload(host, name); - if not ok then - ok = false; - self.session.print(err or "Unknown error reloading module"); - else - count = count + 1; - if ok == nil then - ok = true; - end - self.session.print("Reloaded on "..host); - end - end - end - return ok, (ok and "Module reloaded on "..count.." host"..(count ~= 1 and "s" or "")) or ("Last error: "..tostring(err)); -end - -function def_env.module:list(hosts) - 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 - print((host == "*" and "Global" or host)..":"); - local modules = array.collect(keys(modulemanager.get_modules(host) or {})):sort(); - if #modules == 0 then - if prosody.hosts[host] then - print(" No modules loaded"); - else - print(" Host not found"); - end - else - for _, name in ipairs(modules) do - 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 -end - -def_env.config = {}; -function def_env.config:load(filename, format) - local config_load = require "core.configmanager".load; - local ok, err = config_load(filename, format); - if not ok then - return false, err or "Unknown error loading config"; - end - return true, "Config loaded"; -end - -function def_env.config:get(host, key) - if key == nil then - host, key = "*", host; - end - local config_get = require "core.configmanager".get - return true, serialize_config(config_get(host, key)); -end - -function def_env.config:reload() - local ok, err = prosody.reload_config(); - return ok, (ok and "Config reloaded (you may need to reload modules to take effect)") or tostring(err); -end - -local function common_info(session, line) - if session.id then - line[#line+1] = "["..session.id.."]" - else - line[#line+1] = "["..session.type..(tostring(session):match("%x*$")).."]" - end -end - -local function session_flags(session, line) - line = line or {}; - common_info(session, line); - if session.type == "c2s" then - local status, priority = "unavailable", tostring(session.priority or "-"); - if session.presence then - status = session.presence:get_child_text("show") or "available"; - end - line[#line+1] = status.."("..priority..")"; - end - 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 - if session.compressed then - line[#line+1] = "(compressed)"; - end - if session.smacks then - line[#line+1] = "(sm)"; - end - if session.ip and session.ip:match(":") then - line[#line+1] = "(IPv6)"; - end - 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 - -local function tls_info(session, line) - line = line or {}; - common_info(session, line); - if session.secure then - local sock = session.conn and session.conn.socket and session.conn:socket(); - if sock then - local info = sock.info and sock:info(); - if info then - line[#line+1] = ("(%s with %s)"):format(info.protocol, info.cipher); - else - -- TLS session might not be ready yet - 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 - end - else - line[#line+1] = "(insecure)"; - end - return table.concat(line, " "); -end - -def_env.c2s = {}; - -local function get_jid(session) - if session.username then - return session.full_jid or jid_join(session.username, session.host, session.resource); - end - - local conn = session.conn; - local ip = session.ip or "?"; - local clientport = conn and conn:clientport() or "?"; - local serverip = conn and conn.server and conn:server():ip() or "?"; - local serverport = conn and conn:serverport() or "?" - 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) - 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 _sort_hosts(a.host or "", b.host or ""); - end):map(function (session) - callback(get_jid(session), session) - end); -end - -function def_env.c2s:count() - local c2s = get_c2s(); - return true, "Total: ".. #c2s .." clients"; -end - -function def_env.c2s:show(match_jid, annotate) - local print, count = self.session.print, 0; - annotate = annotate or session_flags; - local curr_host = false; - show_c2s(function (jid, session) - if curr_host ~= session.host then - curr_host = session.host; - print(curr_host or "(not connected to any host yet)"); - end - if (not match_jid) or jid:match(match_jid) then - count = count + 1; - print(annotate(session, { " ", jid })); - end - end); - return true, "Total: "..count.." clients"; -end - -function def_env.c2s:show_insecure(match_jid) - local print, count = self.session.print, 0; - show_c2s(function (jid, session) - if ((not match_jid) or jid:match(match_jid)) and not session.secure then - count = count + 1; - print(jid); - end - end); - return true, "Total: "..count.." insecure client connections"; -end - -function def_env.c2s:show_secure(match_jid) - local print, count = self.session.print, 0; - show_c2s(function (jid, session) - if ((not match_jid) or jid:match(match_jid)) and session.secure then - count = count + 1; - print(jid); - end - end); - return true, "Total: "..count.." secure client connections"; -end - -function def_env.c2s:show_tls(match_jid) - return self:show(match_jid, tls_info); -end - -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(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) - local print = self.session.print; - annotate = annotate or session_flags; - - local count_in, count_out = 0,0; - local s2s_list = { }; - - local s2s_sessions = module:shared"/*/s2s/sessions"; - for _, session in pairs(s2s_sessions) do - local remotehost, localhost, direction; - if session.direction == "outgoing" then - direction = "->"; - count_out = count_out + 1; - remotehost, localhost = session.to_host or "?", session.from_host or "?"; - else - direction = "<-"; - count_in = count_in + 1; - remotehost, localhost = session.from_host or "?", session.to_host or "?"; - end - local sess_lines = { l = localhost, r = remotehost, - annotate(session, { "", direction, remotehost or "?" })}; - - 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"); - end - if session.type == "s2sout_unauthed" then - if session.connecting then - print("Connection not yet established"); - if not session.srv_hosts then - if not session.conn then - print("We do not yet have a DNS answer for this host's SRV records"); - else - print("This host has no SRV records, using A record instead"); - end - elseif session.srv_choice then - print("We are on SRV record "..session.srv_choice.." of "..#session.srv_hosts); - local srv_choice = session.srv_hosts[session.srv_choice]; - print("Using "..(srv_choice.target or ".")..":"..(srv_choice.port or 5269)); - end - elseif session.notopen then - print("The has not yet been opened"); - elseif not session.dialback_key then - print("Dialback has not been initiated yet"); - elseif session.dialback_key then - print("Dialback has been requested, but no result received"); - end - end - if session.type == "s2sin_unauthed" then - print("Connection not yet authenticated"); - elseif session.type == "s2sin" then - for name in pairs(session.hosts) do - if name ~= session.from_host then - print("also hosts "..tostring(name)); - end - end - end - end - end - - -- Sort by local host, then remote host - table.sort(s2s_list, function(a,b) - 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 - if sess_lines.l ~= lasthost then print(sess_lines.l); lasthost=sess_lines.l end - for _, line in ipairs(sess_lines) do print(line); end - end - return true, "Total: "..count_out.." outgoing, "..count_in.." incoming connections"; -end - -function def_env.s2s:show_tls(match_jid) - return self:show(match_jid, tls_info); -end - -local function print_subject(print, subject) - for _, entry in ipairs(subject) do - print( - (" %s: %q"):format( - entry.name or entry.oid, - entry.value:gsub("[\r\n%z%c]", " ") - ) - ); - end -end - --- As much as it pains me to use the 0-based depths that OpenSSL does, --- I think there's going to be more confusion among operators if we --- break from that. -local function print_errors(print, errors) - for depth, t in pairs(errors) do - print( - (" %d: %s"):format( - depth-1, - table.concat(t, "\n| ") - ) - ); - end -end - -function def_env.s2s:showcert(domain) - local print = self.session.print; - local s2s_sessions = module:shared"/*/s2s/sessions"; - local domain_sessions = set.new(array.collect(values(s2s_sessions))) - /function(session) return (session.to_host == domain or session.from_host == domain) and session or nil; end; - local cert_set = {}; - for session in domain_sessions do - local conn = session.conn; - conn = conn and conn:socket(); - if not conn.getpeerchain then - if conn.dohandshake then - error("This version of LuaSec does not support certificate viewing"); - end - else - local cert = conn:getpeercertificate(); - if cert then - local certs = conn:getpeerchain(); - local digest = cert:digest("sha1"); - if not cert_set[digest] then - local chain_valid, chain_errors = conn:getpeerverification(); - cert_set[digest] = { - { - from = session.from_host, - to = session.to_host, - direction = session.direction - }; - chain_valid = chain_valid; - chain_errors = chain_errors; - certs = certs; - }; - else - table.insert(cert_set[digest], { - from = session.from_host, - to = session.to_host, - direction = session.direction - }); - end - end - end - end - local domain_certs = array.collect(values(cert_set)); - -- Phew. We now have a array of unique certificates presented by domain. - local n_certs = #domain_certs; - - if n_certs == 0 then - return "No certificates found for "..domain; - end - - local function _capitalize_and_colon(byte) - return string.upper(byte)..":"; - end - local function pretty_fingerprint(hash) - return hash:gsub("..", _capitalize_and_colon):sub(1, -2); - end - - for cert_info in values(domain_certs) do - local certs = cert_info.certs; - local cert = certs[1]; - print("---") - print("Fingerprint (SHA1): "..pretty_fingerprint(cert:digest("sha1"))); - print(""); - local n_streams = #cert_info; - print("Currently used on "..n_streams.." stream"..(n_streams==1 and "" or "s")..":"); - for _, stream in ipairs(cert_info) do - if stream.direction == "incoming" then - print(" "..stream.to.." <- "..stream.from); - else - print(" "..stream.from.." -> "..stream.to); - end - end - print(""); - local chain_valid, errors = cert_info.chain_valid, cert_info.chain_errors; - local valid_identity = cert_verify_identity(domain, "xmpp-server", cert); - if chain_valid then - print("Trusted certificate: Yes"); - else - print("Trusted certificate: No"); - print_errors(print, errors); - end - print(""); - print("Issuer: "); - print_subject(print, cert:issuer()); - print(""); - print("Valid for "..domain..": "..(valid_identity and "Yes" or "No")); - print("Subject:"); - print_subject(print, cert:subject()); - end - print("---"); - return ("Showing "..n_certs.." certificate" - ..(n_certs==1 and "" or "s") - .." presented by "..domain.."."); -end - -function def_env.s2s:close(from, to, text, condition) - local print, count = self.session.print, 0; - local s2s_sessions = module:shared"/*/s2s/sessions"; - - local match_id; - if from and not to then - 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 - return false, "Both from and to are the same... you can't do that :)"; - end - - for _, session in pairs(s2s_sessions) do - 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, 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, 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(build_reason(text, condition)); - count = count + 1; - end - end - if count == 0 then return false, "No sessions to close."; - else return true, "Closed "..count.." s2s session"..((count == 1 and "") or "s"); end -end - -def_env.host = {}; def_env.hosts = def_env.host; - -function def_env.host:activate(hostname, config) - return hostmanager.activate(hostname, config); -end -function def_env.host:deactivate(hostname, reason) - return hostmanager.deactivate(hostname, reason); -end - -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, _sort_hosts) do - i = i + 1; - type = host_session.type; - if type == "local" then - print(host); - else - type = module:context(host):get_option_string("component_module", type); - if type ~= "component" then - type = type .. " component"; - end - print(("%s (%s)"):format(host, type)); - end - end - return true, i.." hosts"; -end - -def_env.port = {}; - -function def_env.port:list() - local print = self.session.print; - local services = portmanager.get_active_services().data; - 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(interfaces) do - for port in pairs(ports) do - table.insert(ports_list, "["..interface.."]:"..port); - end - end - n_ports = n_ports + #ports_list; - print(service..": "..table.concat(ports_list, ", ")); - end - 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 -- luacheck: ignore 213 - for interface, ports in pairs(interfaces) do - if not close_interface or close_interface == interface then - if ports[close_port] then - self.session.print("Closing ["..interface.."]:"..close_port.."..."); - local ok, err = portmanager.close(interface, close_port) - if not ok then - self.session.print("Failed to close "..interface.." "..close_port..": "..err); - else - n_closed = n_closed + 1; - end - end - end - end - end - return true, "Closed "..n_closed.." ports"; -end - -def_env.muc = {}; - -local console_room_mt = { - __index = function (self, k) return self.room[k]; end; - __tostring = function (self) - return "MUC room <"..self.room.jid..">"; - end; -}; - -local function check_muc(jid) - local room_name, host = jid_split(jid); - if not prosody.hosts[host] then - return nil, "No such host: "..host; - 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, 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 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) - local room_name, host = check_muc(room_jid); - if not room_name then - return room_name, host; - end - 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 - return setmetatable({ room = room_obj }, console_room_mt); -end - -function def_env.muc:list(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 room in host_session.modules.muc.each_room() do - print(room.jid); - c = c + 1; - end - return true, c.." rooms"; -end - -local um = require"core.usermanager"; - -def_env.user = {}; -function def_env.user:create(jid, password) - local username, host = jid_split(jid); - if not prosody.hosts[host] then - return nil, "No such host: "..host; - elseif um.user_exists(username, host) then - return nil, "User exists"; - end - local ok, err = um.create_user(username, password, host); - if ok then - return true, "User created"; - else - return nil, "Could not create user: "..err; - end -end - -function def_env.user:delete(jid) - local username, host = jid_split(jid); - 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"; - end - local ok, err = um.delete_user(username, host); - if ok then - return true, "User deleted"; - else - return nil, "Could not delete user: "..err; - end -end - -function def_env.user:password(jid, password) - local username, host = jid_split(jid); - 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"; - end - local ok, err = um.set_password(username, password, host, nil); - if ok then - return true, "User password changed"; - else - return nil, "Could not change password for user: "..err; - end -end - -function def_env.user:list(host, pat) - if not host then - return nil, "No host given"; - elseif not prosody.hosts[host] then - return nil, "No such host"; - end - local print = self.session.print; - local total, matches = 0, 0; - for user in um.users(host) do - if not pat or user:match(pat) then - print(user.."@"..host); - matches = matches + 1; - end - total = total + 1; - end - return true, "Showing "..(pat and (matches.." of ") or "all " )..total.." users"; -end - -def_env.xmpp = {}; - -local st = require "util.stanza"; -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 false, tostring(err); - end -end - -def_env.dns = {}; -local adns = require"net.adns"; - -local function get_resolver(session) - local resolver = session.dns_resolver; - if not resolver then - resolver = adns.resolver(); - session.dns_resolver = resolver; - end - return resolver; -end - -function def_env.dns:lookup(name, typ, class) - local resolver = get_resolver(self.session); - local ret, err = async.wait(resolver:lookup_promise(name, typ, class)); - if ret then - return true, ret; - elseif err then - return false, err; - end -end - -function def_env.dns:addnameserver(...) - local resolver = get_resolver(self.session); - resolver._resolver:addnameserver(...) - return true -end - -function def_env.dns:setnameserver(...) - local resolver = get_resolver(self.session); - resolver._resolver:setnameserver(...) - return true -end - -function def_env.dns:purge() - local resolver = get_resolver(self.session); - resolver._resolver:purge() - return true -end - -function def_env.dns:cache() - local resolver = get_resolver(self.session); - return true, "Cache:\n"..tostring(resolver._resolver.cache) -end - -def_env.http = {}; - -function def_env.http:list(hosts) - local print = self.session.print; - - for host in get_hosts_set(hosts) do - local http_apps = modulemanager.get_items("http-provider", host); - if #http_apps > 0 then - local http_host = module:context(host):get_option_string("http_host"); - print("HTTP endpoints on "..host..(http_host and (" (using "..http_host.."):") or ":")); - for _, provider in ipairs(http_apps) do - local url = module:context(host):http_url(provider.name, provider.default_path); - print("", url); - end - print(""); - end - end - - local default_host = module:get_option_string("http_default_host"); - if not default_host then - print("HTTP requests to unknown hosts will return 404 Not Found"); - else - print("HTTP requests to unknown hosts will be handled by "..default_host); - end - return true; -end - -def_env.debug = {}; - -function def_env.debug:logevents(host) - helpers.log_host_events(host); - return true; -end - -function def_env.debug:events(host, event) - local events_obj; - if host and 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 - else - events_obj = prosody.events; - end - return true, helpers.show_events(events_obj, event); -end - -function def_env.debug:timers() - 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 - time.now()); - 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 _, 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 ("%g µ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); -- luacheck: ignore 211 - 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); -- luacheck: ignore 211 - 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) - -- luacheck: ignore 211/changed - 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 iterators.sorted_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) - local option = module:get_option_string("console_banner", "full"); - if option == "full" or option == "graphic" then - session.print [[ - ____ \ / _ - | _ \ _ __ ___ ___ _-_ __| |_ _ - | |_) | '__/ _ \/ __|/ _ \ / _` | | | | - | __/| | | (_) \__ \ |_| | (_| | |_| | - |_| |_| \___/|___/\___/ \__,_|\__, | - A study in simplicity |___/ - -]] - end - 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("https://prosody.im/doc/console\n"); - end - if option ~= "short" and option ~= "full" and option ~= "graphic" then - session.print(option); - end -end - module:provides("net", { name = "console"; listener = console_listener; -- cgit v1.2.3 From e703759258ebed21e3a0aa5e55a163b22fd9d91b Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 1 Jun 2020 16:14:06 +0100 Subject: mod_admin_shell, mod_admin_telnet, util.prosodyctl.shell: Separate output from final result Fixes the client pausing for input after output from commands. --- plugins/mod_admin_telnet.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'plugins/mod_admin_telnet.lua') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index ec8ff136..15220ec9 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -65,7 +65,7 @@ function console:new_session(conn) local w = function(s) conn:write(s:gsub("\n", "\r\n")); end; local session = { conn = conn; send = function (t) - if st.is_stanza(t) and t.name == "repl-result" then + if st.is_stanza(t) and (t.name == "repl-result" or t.name == "repl-output") then t = "| "..t:get_text().."\n"; end w(tostring(t)); @@ -106,7 +106,7 @@ function console:process_line(session, line) session:disconnect(); return; end - return module:fire_event("admin/repl-line", { origin = session, stanza = st.stanza("repl"):text(line) }); + return module:fire_event("admin/repl-input", { origin = session, stanza = st.stanza("repl-input"):text(line) }); end local sessions = {}; -- cgit v1.2.3