comparison plugins/mod_admin_telnet.lua @ 10411:db2a06b9ff98

Merge 0.11->trunk
author Kim Alvefur <zash@zash.se>
date Sat, 16 Nov 2019 16:52:31 +0100
parents 496248e48a1d
children e7d3fa49495f
comparison
equal deleted inserted replaced
10410:659b577f280c 10411:db2a06b9ff98
20 20
21 local prosody = _G.prosody; 21 local prosody = _G.prosody;
22 22
23 local console_listener = { default_port = 5582; default_mode = "*a"; interface = "127.0.0.1" }; 23 local console_listener = { default_port = 5582; default_mode = "*a"; interface = "127.0.0.1" };
24 24
25 local unpack = table.unpack or unpack; -- luacheck: ignore 113
25 local iterators = require "util.iterators"; 26 local iterators = require "util.iterators";
26 local keys, values = iterators.keys, iterators.values; 27 local keys, values = iterators.keys, iterators.values;
27 local jid_bare, jid_split, jid_join = import("util.jid", "bare", "prepped_split", "join"); 28 local jid_bare, jid_split, jid_join = import("util.jid", "bare", "prepped_split", "join");
28 local set, array = require "util.set", require "util.array"; 29 local set, array = require "util.set", require "util.array";
29 local cert_verify_identity = require "util.x509".verify_identity; 30 local cert_verify_identity = require "util.x509".verify_identity;
30 local envload = require "util.envload".envload; 31 local envload = require "util.envload".envload;
31 local envloadfile = require "util.envload".envloadfile; 32 local envloadfile = require "util.envload".envloadfile;
32 local has_pposix, pposix = pcall(require, "util.pposix"); 33 local has_pposix, pposix = pcall(require, "util.pposix");
34 local async = require "util.async";
35 local serialize = require "util.serialization".new({ fatal = false, unquoted = true});
36 local time = require "util.time";
33 37
34 local commands = module:shared("commands") 38 local commands = module:shared("commands")
35 local def_env = module:shared("env"); 39 local def_env = module:shared("env");
36 local default_env_mt = { __index = def_env }; 40 local default_env_mt = { __index = def_env };
37 41
44 end; 48 end;
45 return env; 49 return env;
46 end 50 end
47 51
48 console = {}; 52 console = {};
53
54 local runner_callbacks = {};
55
56 function runner_callbacks:ready()
57 self.data.conn:resume();
58 end
59
60 function runner_callbacks:waiting()
61 self.data.conn:pause();
62 end
63
64 function runner_callbacks:error(err)
65 module:log("error", "Traceback[telnet]: %s", err);
66
67 self.data.print("Fatal error while running command, it did not complete");
68 self.data.print("Error: "..tostring(err));
69 end
70
49 71
50 function console:new_session(conn) 72 function console:new_session(conn)
51 local w = function(s) conn:write(s:gsub("\n", "\r\n")); end; 73 local w = function(s) conn:write(s:gsub("\n", "\r\n")); end;
52 local session = { conn = conn; 74 local session = { conn = conn;
53 send = function (t) w(tostring(t)); end; 75 send = function (t) w(tostring(t)); end;
60 end; 82 end;
61 disconnect = function () conn:close(); end; 83 disconnect = function () conn:close(); end;
62 }; 84 };
63 session.env = setmetatable({}, default_env_mt); 85 session.env = setmetatable({}, default_env_mt);
64 86
87 session.thread = async.runner(function (line)
88 console:process_line(session, line);
89 session.send(string.char(0));
90 end, runner_callbacks, session);
91
65 -- Load up environment with helper objects 92 -- Load up environment with helper objects
66 for name, t in pairs(def_env) do 93 for name, t in pairs(def_env) do
67 if type(t) == "table" then 94 if type(t) == "table" then
68 session.env[name] = setmetatable({ session = session }, { __index = t }); 95 session.env[name] = setmetatable({ session = session }, { __index = t });
69 end 96 end
89 end 116 end
90 end 117 end
91 118
92 session.env._ = line; 119 session.env._ = line;
93 120
121 if not useglobalenv and commands[line:lower()] then
122 commands[line:lower()](session, line);
123 return;
124 end
125
94 local chunkname = "=console"; 126 local chunkname = "=console";
95 local env = (useglobalenv and redirect_output(_G, session)) or session.env or nil 127 local env = (useglobalenv and redirect_output(_G, session)) or session.env or nil
96 local chunk, err = envload("return "..line, chunkname, env); 128 local chunk, err = envload("return "..line, chunkname, env);
97 if not chunk then 129 if not chunk then
98 chunk, err = envload(line, chunkname, env); 130 chunk, err = envload(line, chunkname, env);
103 session.print("Sorry, I couldn't understand that... "..err); 135 session.print("Sorry, I couldn't understand that... "..err);
104 return; 136 return;
105 end 137 end
106 end 138 end
107 139
108 local ranok, taskok, message = pcall(chunk); 140 local taskok, message = chunk();
109
110 if not (ranok or message or useglobalenv) and commands[line:lower()] then
111 commands[line:lower()](session, line);
112 return;
113 end
114
115 if not ranok then
116 session.print("Fatal error while running command, it did not complete");
117 session.print("Error: "..taskok);
118 return;
119 end
120 141
121 if not message then 142 if not message then
122 session.print("Result: "..tostring(taskok)); 143 session.print("Result: "..tostring(taskok));
123 return; 144 return;
124 elseif (not taskok) and message then 145 elseif (not taskok) and message then
148 data = partial..data; 169 data = partial..data;
149 end 170 end
150 171
151 for line in data:gmatch("[^\n]*[\n\004]") do 172 for line in data:gmatch("[^\n]*[\n\004]") do
152 if session.closed then return end 173 if session.closed then return end
153 console:process_line(session, line); 174 session.thread:run(line);
154 session.send(string.char(0));
155 end 175 end
156 session.partial_data = data:match("[^\n]+$"); 176 session.partial_data = data:match("[^\n]+$");
157 end 177 end
158 178
159 function console_listener.onreadtimeout(conn) 179 function console_listener.onreadtimeout(conn)
218 print [[host - Commands to activate, deactivate and list virtual hosts]] 238 print [[host - Commands to activate, deactivate and list virtual hosts]]
219 print [[user - Commands to create and delete users, and change their passwords]] 239 print [[user - Commands to create and delete users, and change their passwords]]
220 print [[server - Uptime, version, shutting down, etc.]] 240 print [[server - Uptime, version, shutting down, etc.]]
221 print [[port - Commands to manage ports the server is listening on]] 241 print [[port - Commands to manage ports the server is listening on]]
222 print [[dns - Commands to manage and inspect the internal DNS resolver]] 242 print [[dns - Commands to manage and inspect the internal DNS resolver]]
243 print [[xmpp - Commands for sending XMPP stanzas]]
223 print [[config - Reloading the configuration, etc.]] 244 print [[config - Reloading the configuration, etc.]]
224 print [[console - Help regarding the console itself]] 245 print [[console - Help regarding the console itself]]
225 elseif section == "c2s" then 246 elseif section == "c2s" then
226 print [[c2s:show(jid) - Show all client sessions with the specified JID (or all if no JID given)]] 247 print [[c2s:show(jid) - Show all client sessions with the specified JID (or all if no JID given)]]
227 print [[c2s:show_insecure() - Show all unencrypted client connections]] 248 print [[c2s:show_insecure() - Show all unencrypted client connections]]
228 print [[c2s:show_secure() - Show all encrypted client connections]] 249 print [[c2s:show_secure() - Show all encrypted client connections]]
229 print [[c2s:show_tls() - Show TLS cipher info for encrypted sessions]] 250 print [[c2s:show_tls() - Show TLS cipher info for encrypted sessions]]
251 print [[c2s:count() - Count sessions without listing them]]
230 print [[c2s:close(jid) - Close all sessions for the specified JID]] 252 print [[c2s:close(jid) - Close all sessions for the specified JID]]
253 print [[c2s:closeall() - Close all active c2s connections ]]
231 elseif section == "s2s" then 254 elseif section == "s2s" then
232 print [[s2s:show(domain) - Show all s2s connections for the given domain (or all if no domain given)]] 255 print [[s2s:show(domain) - Show all s2s connections for the given domain (or all if no domain given)]]
233 print [[s2s:show_tls(domain) - Show TLS cipher info for encrypted sessions]] 256 print [[s2s:show_tls(domain) - Show TLS cipher info for encrypted sessions]]
234 print [[s2s:close(from, to) - Close a connection from one domain to another]] 257 print [[s2s:close(from, to) - Close a connection from one domain to another]]
235 print [[s2s:closeall(host) - Close all the incoming/outgoing s2s sessions to specified host]] 258 print [[s2s:closeall(host) - Close all the incoming/outgoing s2s sessions to specified host]]
259 print [[dns:lookup(name, type, class) - Do a DNS lookup]] 282 print [[dns:lookup(name, type, class) - Do a DNS lookup]]
260 print [[dns:addnameserver(nameserver) - Add a nameserver to the list]] 283 print [[dns:addnameserver(nameserver) - Add a nameserver to the list]]
261 print [[dns:setnameserver(nameserver) - Replace the list of name servers with the supplied one]] 284 print [[dns:setnameserver(nameserver) - Replace the list of name servers with the supplied one]]
262 print [[dns:purge() - Clear the DNS cache]] 285 print [[dns:purge() - Clear the DNS cache]]
263 print [[dns:cache() - Show cached records]] 286 print [[dns:cache() - Show cached records]]
287 elseif section == "xmpp" then
288 print [[xmpp:ping(localhost, remotehost) -- Sends a ping to a remote XMPP server and reports the response]]
264 elseif section == "config" then 289 elseif section == "config" then
265 print [[config:reload() - Reload the server configuration. Modules may need to be reloaded for changes to take effect.]] 290 print [[config:reload() - Reload the server configuration. Modules may need to be reloaded for changes to take effect.]]
266 elseif section == "console" then 291 elseif section == "console" then
267 print [[Hey! Welcome to Prosody's admin console.]] 292 print [[Hey! Welcome to Prosody's admin console.]]
268 print [[First thing, if you're ever wondering how to get out, simply type 'quit'.]] 293 print [[First thing, if you're ever wondering how to get out, simply type 'quit'.]]
456 else 481 else
457 print(" Host not found"); 482 print(" Host not found");
458 end 483 end
459 else 484 else
460 for _, name in ipairs(modules) do 485 for _, name in ipairs(modules) do
461 print(" "..name); 486 local status, status_text = modulemanager.get_module(host, name).module:get_status();
487 local status_summary = "";
488 if status == "warn" or status == "error" then
489 status_summary = (" (%s: %s)"):format(status, status_text);
490 end
491 print((" %s%s"):format(name, status_summary));
462 end 492 end
463 end 493 end
464 end 494 end
465 end 495 end
466 496
472 return false, err or "Unknown error loading config"; 502 return false, err or "Unknown error loading config";
473 end 503 end
474 return true, "Config loaded"; 504 return true, "Config loaded";
475 end 505 end
476 506
477 function def_env.config:get(host, section, key) 507 function def_env.config:get(host, key)
508 if key == nil then
509 host, key = "*", host;
510 end
478 local config_get = require "core.configmanager".get 511 local config_get = require "core.configmanager".get
479 return true, tostring(config_get(host, section, key)); 512 return true, serialize(config_get(host, key));
480 end 513 end
481 514
482 function def_env.config:reload() 515 function def_env.config:reload()
483 local ok, err = prosody.reload_config(); 516 local ok, err = prosody.reload_config();
484 return ok, (ok and "Config reloaded (you may need to reload modules to take effect)") or tostring(err); 517 return ok, (ok and "Config reloaded (you may need to reload modules to take effect)") or tostring(err);
503 line[#line+1] = status.."("..priority..")"; 536 line[#line+1] = status.."("..priority..")";
504 end 537 end
505 if session.cert_identity_status == "valid" then 538 if session.cert_identity_status == "valid" then
506 line[#line+1] = "(authenticated)"; 539 line[#line+1] = "(authenticated)";
507 end 540 end
541 if session.dialback_key then
542 line[#line+1] = "(dialback)";
543 end
544 if session.external_auth then
545 line[#line+1] = "(SASL)";
546 end
508 if session.secure then 547 if session.secure then
509 line[#line+1] = "(encrypted)"; 548 line[#line+1] = "(encrypted)";
510 end 549 end
511 if session.compressed then 550 if session.compressed then
512 line[#line+1] = "(compressed)"; 551 line[#line+1] = "(compressed)";
517 if session.ip and session.ip:match(":") then 556 if session.ip and session.ip:match(":") then
518 line[#line+1] = "(IPv6)"; 557 line[#line+1] = "(IPv6)";
519 end 558 end
520 if session.remote then 559 if session.remote then
521 line[#line+1] = "(remote)"; 560 line[#line+1] = "(remote)";
561 end
562 if session.incoming and session.outgoing then
563 line[#line+1] = "(bidi)";
564 elseif session.is_bidi or session.bidi_session then
565 line[#line+1] = "(bidi)";
566 end
567 if session.bosh_version then
568 line[#line+1] = "(bosh)";
569 end
570 if session.websocket_request then
571 line[#line+1] = "(websocket)";
522 end 572 end
523 return table.concat(line, " "); 573 return table.concat(line, " ");
524 end 574 end
525 575
526 local function tls_info(session, line) 576 local function tls_info(session, line)
553 local serverip = conn and conn.server and conn:server():ip() or "?"; 603 local serverip = conn and conn.server and conn:server():ip() or "?";
554 local serverport = conn and conn:serverport() or "?" 604 local serverport = conn and conn:serverport() or "?"
555 return jid_join("["..ip.."]:"..clientport, session.host or "["..serverip.."]:"..serverport); 605 return jid_join("["..ip.."]:"..clientport, session.host or "["..serverip.."]:"..serverport);
556 end 606 end
557 607
608 local function get_c2s()
609 local c2s = array.collect(values(prosody.full_sessions));
610 c2s:append(array.collect(values(module:shared"/*/c2s/sessions")));
611 c2s:append(array.collect(values(module:shared"/*/bosh/sessions")));
612 c2s:unique();
613 return c2s;
614 end
615
558 local function show_c2s(callback) 616 local function show_c2s(callback)
559 local c2s = array.collect(values(module:shared"/*/c2s/sessions")); 617 get_c2s():sort(function(a, b)
560 c2s:sort(function(a, b)
561 if a.host == b.host then 618 if a.host == b.host then
562 if a.username == b.username then 619 if a.username == b.username then
563 return (a.resource or "") > (b.resource or ""); 620 return (a.resource or "") > (b.resource or "");
564 end 621 end
565 return (a.username or "") > (b.username or ""); 622 return (a.username or "") > (b.username or "");
569 callback(get_jid(session), session) 626 callback(get_jid(session), session)
570 end); 627 end);
571 end 628 end
572 629
573 function def_env.c2s:count() 630 function def_env.c2s:count()
574 return true, "Total: ".. iterators.count(values(module:shared"/*/c2s/sessions")) .." clients"; 631 local c2s = get_c2s();
632 return true, "Total: ".. #c2s .." clients";
575 end 633 end
576 634
577 function def_env.c2s:show(match_jid, annotate) 635 function def_env.c2s:show(match_jid, annotate)
578 local print, count = self.session.print, 0; 636 local print, count = self.session.print, 0;
579 annotate = annotate or session_flags; 637 annotate = annotate or session_flags;
615 673
616 function def_env.c2s:show_tls(match_jid) 674 function def_env.c2s:show_tls(match_jid)
617 return self:show(match_jid, tls_info); 675 return self:show(match_jid, tls_info);
618 end 676 end
619 677
620 function def_env.c2s:close(match_jid) 678 local function build_reason(text, condition)
679 if text or condition then
680 return {
681 text = text,
682 condition = condition or "undefined-condition",
683 };
684 end
685 end
686
687 function def_env.c2s:close(match_jid, text, condition)
621 local count = 0; 688 local count = 0;
622 show_c2s(function (jid, session) 689 show_c2s(function (jid, session)
623 if jid == match_jid or jid_bare(jid) == match_jid then 690 if jid == match_jid or jid_bare(jid) == match_jid then
624 count = count + 1; 691 count = count + 1;
625 session:close(); 692 session:close(build_reason(text, condition));
626 end 693 end
694 end);
695 return true, "Total: "..count.." sessions closed";
696 end
697
698 function def_env.c2s:closeall(text, condition)
699 local count = 0;
700 --luacheck: ignore 212/jid
701 show_c2s(function (jid, session)
702 count = count + 1;
703 session:close(build_reason(text, condition));
627 end); 704 end);
628 return true, "Total: "..count.." sessions closed"; 705 return true, "Total: "..count.." sessions closed";
629 end 706 end
630 707
631 708
826 return ("Showing "..n_certs.." certificate" 903 return ("Showing "..n_certs.." certificate"
827 ..(n_certs==1 and "" or "s") 904 ..(n_certs==1 and "" or "s")
828 .." presented by "..domain.."."); 905 .." presented by "..domain..".");
829 end 906 end
830 907
831 function def_env.s2s:close(from, to) 908 function def_env.s2s:close(from, to, text, condition)
832 local print, count = self.session.print, 0; 909 local print, count = self.session.print, 0;
833 local s2s_sessions = module:shared"/*/s2s/sessions"; 910 local s2s_sessions = module:shared"/*/s2s/sessions";
834 911
835 local match_id; 912 local match_id;
836 if from and not to then 913 if from and not to then
840 elseif from == to then 917 elseif from == to then
841 return false, "Both from and to are the same... you can't do that :)"; 918 return false, "Both from and to are the same... you can't do that :)";
842 end 919 end
843 920
844 for _, session in pairs(s2s_sessions) do 921 for _, session in pairs(s2s_sessions) do
845 local id = session.type..tostring(session):match("[a-f0-9]+$"); 922 local id = session.id or (session.type..tostring(session):match("[a-f0-9]+$"));
846 if (match_id and match_id == id) 923 if (match_id and match_id == id)
847 or (session.from_host == from and session.to_host == to) then 924 or (session.from_host == from and session.to_host == to) then
848 print(("Closing connection from %s to %s [%s]"):format(session.from_host, session.to_host, id)); 925 print(("Closing connection from %s to %s [%s]"):format(session.from_host, session.to_host, id));
849 (session.close or s2smanager.destroy_session)(session); 926 (session.close or s2smanager.destroy_session)(session, build_reason(text, condition));
850 count = count + 1 ; 927 count = count + 1 ;
851 end 928 end
852 end 929 end
853 return true, "Closed "..count.." s2s session"..((count == 1 and "") or "s"); 930 return true, "Closed "..count.." s2s session"..((count == 1 and "") or "s");
854 end 931 end
855 932
856 function def_env.s2s:closeall(host) 933 function def_env.s2s:closeall(host, text, condition)
857 local count = 0; 934 local count = 0;
858 local s2s_sessions = module:shared"/*/s2s/sessions"; 935 local s2s_sessions = module:shared"/*/s2s/sessions";
859 for _,session in pairs(s2s_sessions) do 936 for _,session in pairs(s2s_sessions) do
860 if not host or session.from_host == host or session.to_host == host then 937 if not host or session.from_host == host or session.to_host == host then
861 session:close(); 938 session:close(build_reason(text, condition));
862 count = count + 1; 939 count = count + 1;
863 end 940 end
864 end 941 end
865 if count == 0 then return false, "No sessions to close."; 942 if count == 0 then return false, "No sessions to close.";
866 else return true, "Closed "..count.." s2s session"..((count == 1 and "") or "s"); end 943 else return true, "Closed "..count.." s2s session"..((count == 1 and "") or "s"); end
1060 end 1137 end
1061 1138
1062 def_env.xmpp = {}; 1139 def_env.xmpp = {};
1063 1140
1064 local st = require "util.stanza"; 1141 local st = require "util.stanza";
1065 function def_env.xmpp:ping(localhost, remotehost) 1142 local new_id = require "util.id".medium;
1066 if prosody.hosts[localhost] then 1143 function def_env.xmpp:ping(localhost, remotehost, timeout)
1067 module:send(st.iq{ from=localhost, to=remotehost, type="get", id="ping" } 1144 localhost = select(2, jid_split(localhost));
1068 :tag("ping", {xmlns="urn:xmpp:ping"}), prosody.hosts[localhost]); 1145 remotehost = select(2, jid_split(remotehost));
1069 return true, "Sent ping"; 1146 if not localhost then
1147 return nil, "Invalid sender hostname";
1148 elseif not prosody.hosts[localhost] then
1149 return nil, "No such local host";
1150 end
1151 if not remotehost then
1152 return nil, "Invalid destination hostname";
1153 elseif prosody.hosts[remotehost] then
1154 return nil, "Both hosts are local";
1155 end
1156 local iq = st.iq{ from=localhost, to=remotehost, type="get", id=new_id()}
1157 :tag("ping", {xmlns="urn:xmpp:ping"});
1158 local time_start = time.now();
1159 local ret, err = async.wait(module:context(localhost):send_iq(iq, nil, timeout));
1160 if ret then
1161 return true, ("pong from %s in %gs"):format(ret.stanza.attr.from, time.now() - time_start);
1070 else 1162 else
1071 return nil, "No such host"; 1163 return false, tostring(err);
1072 end 1164 end
1073 end 1165 end
1074 1166
1075 def_env.dns = {}; 1167 def_env.dns = {};
1076 local adns = require"net.adns"; 1168 local adns = require"net.adns";
1205 local function format_stat(type, value, ref_value) 1297 local function format_stat(type, value, ref_value)
1206 ref_value = ref_value or value; 1298 ref_value = ref_value or value;
1207 --do return tostring(value) end 1299 --do return tostring(value) end
1208 if type == "duration" then 1300 if type == "duration" then
1209 if ref_value < 0.001 then 1301 if ref_value < 0.001 then
1210 return ("%d µs"):format(value*1000000); 1302 return ("%g µs"):format(value*1000000);
1211 elseif ref_value < 0.9 then 1303 elseif ref_value < 0.9 then
1212 return ("%0.2f ms"):format(value*1000); 1304 return ("%0.2f ms"):format(value*1000);
1213 end 1305 end
1214 return ("%0.2f"):format(value); 1306 return ("%0.2f"):format(value);
1215 elseif type == "size" then 1307 elseif type == "size" then
1493 1585
1494 function def_env.stats:show(filter) 1586 function def_env.stats:show(filter)
1495 local stats, changed, extra = require "core.statsmanager".get_stats(); 1587 local stats, changed, extra = require "core.statsmanager".get_stats();
1496 local available, displayed = 0, 0; 1588 local available, displayed = 0, 0;
1497 local displayed_stats = new_stats_context(self); 1589 local displayed_stats = new_stats_context(self);
1498 for name, value in pairs(stats) do 1590 for name, value in iterators.sorted_pairs(stats) do
1499 available = available + 1; 1591 available = available + 1;
1500 if not filter or name:match(filter) then 1592 if not filter or name:match(filter) then
1501 displayed = displayed + 1; 1593 displayed = displayed + 1;
1502 local type = name:match(":(%a+)$"); 1594 local type = name:match(":(%a+)$");
1503 table.insert(displayed_stats, { 1595 table.insert(displayed_stats, {