X-Git-Url: https://git.enpas.org/?a=blobdiff_plain;f=plugins%2Fmod_admin_telnet.lua;h=e4b5a045ccbab2308b758c4f1f87556c4326929a;hb=d8eb8704b5dd84f198ba64c49ee9aec98326356b;hp=17e5babc0ecc24e95b217365efa08d07f8d3f716;hpb=998710d454c1faa3c37aa83959b97dda6ee00387;p=prosody.git diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 17e5babc..e4b5a045 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -8,30 +8,38 @@ module:set_global(); +local hostmanager = require "core.hostmanager"; +local modulemanager = require "core.modulemanager"; +local s2smanager = require "core.s2smanager"; +local portmanager = require "core.portmanager"; + local _G = _G; local prosody = _G.prosody; local hosts = prosody.hosts; +local incoming_s2s = prosody.incoming_s2s; local console_listener = { default_port = 5582; default_mode = "*a"; interface = "127.0.0.1" }; 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 = import("util.jid", "bare", "prepped_split"); 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 commands = module:shared("commands") local def_env = module:shared("env"); local default_env_mt = { __index = def_env }; +local core_post_stanza = prosody.core_post_stanza; local function redirect_output(_G, session) local env = setmetatable({ print = session.print }, { __index = function (t, k) return rawget(_G, k); end }); env.dofile = function(name) - local f, err = loadfile(name); + local f, err = envloadfile(name, env); if not f then return f, err; end - return setfenv(f, env)(); + return f(); end; return env; end @@ -63,6 +71,64 @@ function console:new_session(conn) return session; 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; + + local chunkname = "=console"; + local env = (useglobalenv and redirect_output(_G, session)) or session.env or nil + 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 ranok, taskok, message = pcall(chunk); + + if not (ranok or message or useglobalenv) and commands[line:lower()] then + commands[line:lower()](session, line); + return; + end + + if not ranok then + session.print("Fatal error while running command, it did not complete"); + session.print("Error: "..taskok); + return; + end + + if not message then + session.print("Result: "..tostring(taskok)); + return; + elseif (not taskok) and message then + session.print("Command completed with a problem"); + session.print("Message: "..tostring(message)); + return; + end + + session.print("OK: "..tostring(message)); +end + local sessions = {}; function console_listener.onconnect(conn) @@ -82,67 +148,8 @@ function console_listener.onincoming(conn, data) end for line in data:gmatch("[^\n]*[\n\004]") do - -- Handle data (loop allows us to break to add \0 after response) - repeat - local useglobalenv; - - if line:match("^>") then - line = line:gsub("^>", ""); - useglobalenv = true; - elseif line == "\004" then - commands["bye"](session, line); - break; - else - local command = line:lower(); - command = line:match("^%w+") or line:match("%p"); - if commands[command] then - commands[command](session, line); - break; - end - end - - session.env._ = line; - - local chunkname = "=console"; - local chunk, err = loadstring("return "..line, chunkname); - if not chunk then - chunk, err = loadstring(line, chunkname); - 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); - break; - end - end - - setfenv(chunk, (useglobalenv and redirect_output(_G, session)) or session.env or nil); - - local ranok, taskok, message = pcall(chunk); - - if not (ranok or message or useglobalenv) and commands[line:lower()] then - commands[line:lower()](session, line); - break; - end - - if not ranok then - session.print("Fatal error while running command, it did not complete"); - session.print("Error: "..taskok); - break; - end - - if not message then - session.print("Result: "..tostring(taskok)); - break; - elseif (not taskok) and message then - session.print("Command completed with a problem"); - session.print("Message: "..tostring(message)); - break; - end - - session.print("OK: "..tostring(message)); - until true - + if session.closed then return end + console:process_line(session, line); session.send(string.char(0)); end session.partial_data = data:match("[^\n]+$"); @@ -156,11 +163,16 @@ 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 function commands.bye(session) session.print("See you! :)"); + session.closed = true; session.disconnect(); end commands.quit, commands.exit = commands.bye, commands.bye; @@ -197,6 +209,8 @@ function commands.help(session, data) 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 [[config - Reloading the configuration, etc.]] print [[console - Help regarding the console itself]] elseif section == "c2s" then @@ -220,11 +234,22 @@ function commands.help(session, data) 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, password) - Permanently remove 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 == "config" then print [[config:reload() - Reload the server configuration. Modules may need to be reloaded for changes to take effect.]] elseif section == "console" then @@ -279,6 +304,26 @@ function def_env.server: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 pposix.meminfo then + return true, "Lua is using "..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, module) @@ -513,7 +558,7 @@ function def_env.c2s:show_secure(match_jid) end function def_env.c2s:close(match_jid) - local print, count = self.session.print, 0; + local count = 0; show_c2s(function (jid, session) if jid == match_jid or jid_bare(jid) == match_jid then count = count + 1; @@ -631,7 +676,7 @@ end -- 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 ipairs(errors) do + for depth, t in pairs(errors) do print( (" %d: %s"):format( depth-1, @@ -642,6 +687,7 @@ local function print_errors(print, errors) end function def_env.s2s:showcert(domain) + local ser = require "util.serialization".serialize; local print = self.session.print; local domain_sessions = set.new(array.collect(keys(incoming_s2s))) /function(session) return session.from_host == domain and session or nil; end; @@ -660,9 +706,9 @@ function def_env.s2s:showcert(domain) error("This version of LuaSec does not support certificate viewing"); end else - local certs = conn:getpeerchain(); - local cert = certs[1]; + 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(); @@ -868,7 +914,7 @@ function def_env.port:close(close_port, close_interface) 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.." "..port..": "..err); + self.session.print("Failed to close "..interface.." "..close_port..": "..err); else n_closed = n_closed + 1; end @@ -888,13 +934,31 @@ local console_room_mt = { end; }; -function def_env.muc:room(room_jid) - local room_name, host = jid_split(room_jid); +local function check_muc(jid) + local room_name, host = jid_split(jid); if not hosts[host] then return nil, "No such host: "..host; elseif not 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) + local room, host = check_muc(room_jid); + if not room_name then + return room_name, host; + end + if not room 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; @@ -907,6 +971,11 @@ local um = require"core.usermanager"; def_env.user = {}; function def_env.user:create(jid, password) local username, host = jid_split(jid); + if not hosts[host] then + 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"; @@ -917,6 +986,11 @@ end function def_env.user:delete(jid) local username, host = jid_split(jid); + if not 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"; @@ -925,22 +999,93 @@ function def_env.user:delete(jid) end end -function def_env.user:passwd(jid, password) +function def_env.user:password(jid, password) local username, host = jid_split(jid); + if not 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); if ok then - return true, "User created"; + 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 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"; +function def_env.xmpp:ping(localhost, remotehost) + if hosts[localhost] then + core_post_stanza(hosts[localhost], + st.iq{ from=localhost, to=remotehost, type="get", id="ping" } + :tag("ping", {xmlns="urn:xmpp:ping"})); + return true, "Sent ping"; + else + return nil, "No such host"; + end +end + +def_env.dns = {}; +local adns = require"net.adns"; +local dns = require"net.dns"; + +function def_env.dns:lookup(name, typ, class) + local ret = "Query sent"; + local print = self.session.print; + local function handler(...) + ret = "Got response"; + print(...); + end + adns.lookup(handler, name, typ, class); + return true, ret; +end + +function def_env.dns:addnameserver(...) + dns._resolver:addnameserver(...) + return true +end + +function def_env.dns:setnameserver(...) + dns._resolver:setnameserver(...) + return true +end + +function def_env.dns:purge() + dns.purge() + return true +end + +function def_env.dns:cache() + return true, "Cache:\n"..tostring(dns.cache()) +end + ------------- function printbanner(session) - local option = config.get("*", "core", "console_banner"); -if option == nil or option == "full" or option == "graphic" then -session.print [[ + local option = module:get_option("console_banner"); + if option == nil or option == "full" or option == "graphic" then + session.print [[ ____ \ / _ | _ \ _ __ ___ ___ _-_ __| |_ _ | |_) | '__/ _ \/ __|/ _ \ / _` | | | | @@ -949,23 +1094,22 @@ session.print [[ A study in simplicity |___/ ]] -end -if option == nil or 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 - setfenv(option, redirect_output(_G, session)); - pcall(option, session); end -end + if option == nil or 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 + end end -module:add_item("net-provider", { +module:provides("net", { name = "console"; listener = console_listener; default_port = 5582;