mod_admin_telnet: rename variable to make it defined (room -> room_name)
[prosody.git] / plugins / mod_admin_telnet.lua
index e13d27c24a9ac2c8019e26ad6956aa8854c63a47..b2a013247c7026fa60dbdf6b46e215df9aa6fd97 100644 (file)
@@ -22,12 +22,12 @@ local console_listener = { default_port = 5582; default_mode = "*a"; interface =
 
 local iterators = require "util.iterators";
 local keys, values = iterators.keys, iterators.values;
-local jid = require "util.jid";
-local jid_bare, jid_split = jid.bare, jid.split;
+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 commands = module:shared("commands")
 local def_env = module:shared("env");
@@ -155,6 +155,14 @@ function console_listener.onincoming(conn, data)
        session.partial_data = data:match("[^\n]+$");
 end
 
+function console_listener.onreadtimeout(conn)
+       local session = sessions[conn];
+       if session then
+               session.send("\0");
+               return true;
+       end
+end
+
 function console_listener.ondisconnect(conn, err)
        local session = sessions[conn];
        if session then
@@ -163,6 +171,10 @@ function console_listener.ondisconnect(conn, err)
        end
 end
 
+function console_listener.ondetach(conn)
+       sessions[conn] = nil;
+end
+
 -- Console commands --
 -- These are simple commands, not valid standalone in Lua
 
@@ -213,9 +225,11 @@ function commands.help(session, data)
                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:close(jid) - Close all sessions for the specified JID]]
        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 == "module" then
@@ -268,6 +282,8 @@ end
 -- Session environment --
 -- Anything in def_env will be accessible within the session as a global variable
 
+--luacheck: ignore 212/self
+
 def_env.server = {};
 
 function def_env.server:insane_reload()
@@ -309,7 +325,7 @@ local function human(kb)
 end
 
 function def_env.server:memory()
-       if not pposix.meminfo then
+       if not has_pposix or not pposix.meminfo then
                return true, "Lua is using "..collectgarbage("count");
        end
        local mem, lua_mem = pposix.meminfo(), collectgarbage("count");
@@ -332,10 +348,9 @@ local function get_hosts_set(hosts, module)
        elseif type(hosts) == "string" then
                return set.new { hosts };
        elseif hosts == nil then
-               local mm = require "modulemanager";
                local hosts_set = set.new(array.collect(keys(prosody.hosts)))
-                       / function (host) return (prosody.hosts[host].type == "local" or module and mm.is_loaded(host, module)) and host or nil; end;
-               if module and mm.get_module("*", module) then
+                       / 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;
@@ -343,15 +358,13 @@ local function get_hosts_set(hosts, module)
 end
 
 function def_env.module:load(name, hosts, config)
-       local mm = require "modulemanager";
-
        hosts = get_hosts_set(hosts);
 
        -- Load the module for each host
        local ok, err, count, mod = true, nil, 0, nil;
        for host in hosts do
-               if (not mm.is_loaded(host, name)) then
-                       mod, err = mm.load(host, name, config);
+               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
@@ -372,15 +385,13 @@ function def_env.module:load(name, hosts, config)
 end
 
 function def_env.module:unload(name, hosts)
-       local mm = require "modulemanager";
-
        hosts = get_hosts_set(hosts, name);
 
        -- Unload the module for each host
        local ok, err, count = true, nil, 0;
        for host in hosts do
-               if mm.is_loaded(host, name) then
-                       ok, err = mm.unload(host, name);
+               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");
@@ -394,8 +405,6 @@ function def_env.module:unload(name, hosts)
 end
 
 function def_env.module:reload(name, hosts)
-       local mm = require "modulemanager";
-
        hosts = array.collect(get_hosts_set(hosts, name)):sort(function (a, b)
                if a == "*" then return true
                elseif b == "*" then return false
@@ -405,8 +414,8 @@ function def_env.module:reload(name, hosts)
        -- Reload the module for each host
        local ok, err, count = true, nil, 0;
        for _, host in ipairs(hosts) do
-               if mm.is_loaded(host, name) then
-                       ok, err = mm.reload(host, name);
+               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");
@@ -472,22 +481,28 @@ function def_env.config:reload()
        return ok, (ok and "Config reloaded (you may need to reload modules to take effect)") or tostring(err);
 end
 
-def_env.hosts = {};
-function def_env.hosts:list()
-       for host, host_session in pairs(hosts) do
-               self.session.print(host);
+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
-       return true, "Done";
-end
-
-function def_env.hosts:add(name)
 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] = "(secure)";
-       elseif session.secure then
+               line[#line+1] = "(authenticated)";
+       end
+       if session.secure then
                line[#line+1] = "(encrypted)";
        end
        if session.compressed then
@@ -499,47 +514,75 @@ local function session_flags(session, line)
        if session.ip and session.ip:match(":") then
                line[#line+1] = "(IPv6)";
        end
+       if session.remote then
+               line[#line+1] = "(remote)";
+       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 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
+       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 show_c2s(callback)
-       for hostname, host in pairs(hosts) do
-               for username, user in pairs(host.sessions or {}) do
-                       for resource, session in pairs(user.sessions or {}) do
-                               local jid = username.."@"..hostname.."/"..resource;
-                               callback(jid, session);
+       local c2s = array.collect(values(module:shared"/*/c2s/sessions"));
+       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
-       end
+               return (a.host or "") > (b.host or "");
+       end):map(function (session)
+               callback(get_jid(session), session)
+       end);
 end
 
 function def_env.c2s:count(match_jid)
-       local count = 0;
-       show_c2s(function (jid, session)
-               if (not match_jid) or jid:match(match_jid) then
-                       count = count + 1;
-               end
-       end);
-       return true, "Total: "..count.." clients";
+       return true, "Total: "..  iterators.count(values(module:shared"/*/c2s/sessions")) .." clients";
 end
 
-function def_env.c2s:show(match_jid)
+function def_env.c2s:show(match_jid, annotate)
        local print, count = self.session.print, 0;
-       local curr_host;
+       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);
+                       print(curr_host or "(not connected to any host yet)");
                end
                if (not match_jid) or jid:match(match_jid) then
                        count = count + 1;
-                       local status, priority = "unavailable", tostring(session.priority or "-");
-                       if session.presence then
-                               status = session.presence:get_child_text("show") or "available";
-                       end
-                       print(session_flags(session, { "   "..jid.." - "..status.."("..priority..")" }));
+                       print(annotate(session, { "  ", jid }));
                end
        end);
        return true, "Total: "..count.." clients";
@@ -567,6 +610,10 @@ function def_env.c2s:show_secure(match_jid)
        return true, "Total: "..count.." secure client connections";
 end
 
+function def_env.c2s:show_tls(match_jid)
+       return self:show(match_jid, tls_info);
+end
+
 function def_env.c2s:close(match_jid)
        local count = 0;
        show_c2s(function (jid, session)
@@ -580,8 +627,9 @@ end
 
 
 def_env.s2s = {};
-function def_env.s2s:show(match_jid)
+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 = { };
@@ -599,8 +647,7 @@ function def_env.s2s:show(match_jid)
                        remotehost, localhost = session.from_host or "?", session.to_host or "?";
                end
                local sess_lines = { l = localhost, r = remotehost,
-                       session_flags(session, { "", direction, remotehost or "?",
-                               "["..session.type..tostring(session):match("[a-f0-9]*$").."]" })};
+                       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);
@@ -655,6 +702,10 @@ function def_env.s2s:show(match_jid)
        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(
@@ -795,19 +846,19 @@ function def_env.s2s:close(from, to)
                        (session.close or s2smanager.destroy_session)(session);
                        count = count + 1 ;
                end
-                       end
+       end
        return true, "Closed "..count.." s2s session"..((count == 1 and "") or "s");
 end
 
 function def_env.s2s:closeall(host)
-        local count = 0;
+       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();
-                                count = count + 1;
-                        end
-                end
+                       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
@@ -824,9 +875,19 @@ end
 function def_env.host:list()
        local print = self.session.print;
        local i = 0;
+       local type;
        for host in values(array.collect(keys(prosody.hosts)):sort()) do
                i = i + 1;
-               print(host);
+               type = hosts[host].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
@@ -896,14 +957,20 @@ local function check_muc(jid)
 end
 
 function def_env.muc:create(room_jid)
-       local room, host = check_muc(room_jid);
-       if not room then return nil, host end
+       local room_name, host = check_muc(room_jid);
+       if not room_name then
+               return room_name, host;
+       end
+       if not room_name then return nil, host end
        if hosts[host].modules.muc.rooms[room_jid] then return nil, "Room exists already" end
        return hosts[host].modules.muc.create_room(room_jid);
 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 = hosts[host].modules.muc.rooms[room_jid];
        if not room_obj then
                return nil, "No such room: "..room_jid;
@@ -911,6 +978,20 @@ function def_env.muc:room(room_jid)
        return setmetatable({ room = room_obj }, console_room_mt);
 end
 
+function def_env.muc:list(host)
+       local host_session = hosts[host];
+       if not host_session or not host_session.modules.muc then
+               return nil, "Please supply the address of a local MUC component";
+       end
+       local print = self.session.print;
+       local c = 0;
+       for name in keys(host_session.modules.muc.rooms) do
+               print(name);
+               c = c + 1;
+       end
+       return true, c.." rooms";
+end
+
 local um = require"core.usermanager";
 
 def_env.user = {};
@@ -1007,12 +1088,12 @@ function def_env.dns:lookup(name, typ, class)
 end
 
 function def_env.dns:addnameserver(...)
-       dns.addnameserver(...)
+       dns._resolver:addnameserver(...)
        return true
 end
 
 function def_env.dns:setnameserver(...)
-       dns.setnameserver(...)
+       dns._resolver:setnameserver(...)
        return true
 end
 
@@ -1025,11 +1106,38 @@ function def_env.dns:cache()
        return true, "Cache:\n"..tostring(dns.cache())
 end
 
+def_env.http = {};
+
+function def_env.http:list()
+       local print = self.session.print;
+
+       for host in pairs(prosody.hosts) do
+               local http_apps = modulemanager.get_items("http-provider", host);
+               if #http_apps > 0 then
+                       local http_host = module:context(host):get_option("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);
+                               print("", url);
+                       end
+                       print("");
+               end
+       end
+
+       local default_host = module:get_option("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
+
 -------------
 
 function printbanner(session)
-       local option = module:get_option("console_banner");
-       if option == nil or option == "full" or option == "graphic" then
+       local option = module:get_option_string("console_banner", "full");
+       if option == "full" or option == "graphic" then
                session.print [[
                    ____                \   /     _
                     |  _ \ _ __ ___  ___  _-_   __| |_   _
@@ -1040,17 +1148,13 @@ function printbanner(session)
 
 ]]
        end
-       if option == nil or option == "short" or option == "full" then
+       if option == "short" or option == "full" then
        session.print("Welcome to the Prosody administration console. For a list of commands, type: help");
        session.print("You may find more help on using this console in our online documentation at ");
        session.print("http://prosody.im/doc/console\n");
        end
-       if option and option ~= "short" and option ~= "full" and option ~= "graphic" then
-               if type(option) == "string" then
-                       session.print(option)
-               elseif type(option) == "function" then
-                       module:log("warn", "Using functions as value for the console_banner option is no longer supported");
-               end
+       if option ~= "short" and option ~= "full" and option ~= "graphic" then
+               session.print(option);
        end
 end