197 lines
5.9 KiB
Lua
197 lines
5.9 KiB
Lua
-- LUALOCALS < ---------------------------------------------------------
|
|
local assert, error, ipairs, minetest, os, pairs, pcall, require,
|
|
string, tostring
|
|
= assert, error, ipairs, minetest, os, pairs, pcall, require,
|
|
string, tostring
|
|
local os_remove, string_char, string_gsub, string_lower, string_match
|
|
= os.remove, string.char, string.gsub, string.lower, string.match
|
|
-- LUALOCALS > ---------------------------------------------------------
|
|
|
|
local modname = minetest.get_current_modname()
|
|
|
|
------------------------------------------------------------------------
|
|
-- VIRTUAL PLAYER SETUP
|
|
|
|
-- Name for the virtual "player" used for the console.
|
|
local CONSOLE = "CONSOLE"
|
|
|
|
-- Override privileges for the "console player", granting
|
|
-- them every privilege, including "cheats". We assume that
|
|
-- there are no "anti-privileges" registered that would
|
|
-- actually limit access.
|
|
do
|
|
local old_checkprivs = minetest.get_player_privs
|
|
function minetest.get_player_privs(who, ...)
|
|
if who == CONSOLE then
|
|
local p = {}
|
|
for k in pairs(minetest.registered_privileges) do
|
|
if k ~= "shout" then p[k] = true end
|
|
end
|
|
return p
|
|
else
|
|
return old_checkprivs(who, ...)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Disallow any player from actually connecting with the
|
|
-- "console" player name, which would grant them the corresponding
|
|
-- special privileges.
|
|
minetest.register_on_prejoinplayer(function(name)
|
|
if string_lower(name) == string_lower(CONSOLE) then
|
|
return "Player name " .. CONSOLE .. " is reserved."
|
|
end
|
|
end)
|
|
|
|
-- Hook to send messages to a client socket for immediate
|
|
-- command responses.
|
|
local conmsg
|
|
|
|
-- Strip all non-plaintext from text.
|
|
local stripall
|
|
do
|
|
-- Lua pattern string to strip color codes from chat text.
|
|
local stripcolor = minetest.get_color_escape_sequence('#ffffff')
|
|
stripcolor = string_gsub(stripcolor, "%W", "%%%1")
|
|
stripcolor = string_gsub(stripcolor, "ffffff", "%%x+")
|
|
|
|
-- Lua pattern to strip translation sequences.
|
|
local striptrans = minetest.get_translator("12345")("67890")
|
|
striptrans = string_gsub(striptrans, "%W", "%%%1")
|
|
striptrans = string_gsub(striptrans, "12345", "%%S-")
|
|
striptrans = string_gsub(striptrans, "67890", "(%.-)")
|
|
|
|
-- Strip remaining escape chars.
|
|
local stripesc = "%" .. string_char(27) .. "%S"
|
|
|
|
function stripall(s)
|
|
s = string_gsub(s, stripcolor, "")
|
|
s = string_gsub(s, striptrans, "%1")
|
|
s = string_gsub(s, stripesc, "")
|
|
return s
|
|
end
|
|
end
|
|
|
|
-- Intercept messages sent to the "console" player and send them
|
|
-- to the actual console instead.
|
|
do
|
|
local old_chatsend = minetest.chat_send_player
|
|
function minetest.chat_send_player(who, text, ...)
|
|
if who == CONSOLE then
|
|
text = stripall(text)
|
|
if conmsg then conmsg(text) end
|
|
return minetest.log("action", "to " .. CONSOLE .. ": " .. text)
|
|
else
|
|
return old_chatsend(who, text, ...)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Intercept broadcast messages and send them to the console
|
|
-- user, if in response to a command.
|
|
do
|
|
local old_sendall = minetest.chat_send_all
|
|
function minetest.chat_send_all(text, ...)
|
|
if conmsg then conmsg(stripall(text)) end
|
|
return old_sendall(text, ...)
|
|
end
|
|
end
|
|
|
|
------------------------------------------------------------------------
|
|
-- CONSOLE CLIENT SOCKETS
|
|
|
|
-- Keep track of multiple connected clients.
|
|
local clients = {}
|
|
|
|
-- Create a listening unix-domain socket inside the world dir.
|
|
-- All sockets and connections will be non-blocking, by setting
|
|
-- timeout to zero, so we don't block the game engine.
|
|
local master = assert(require("socket.unix")())
|
|
assert(master:settimeout(0))
|
|
local sockpath = minetest.get_worldpath() .. "/" .. modname .. ".sock"
|
|
os_remove(sockpath)
|
|
assert(master:bind(sockpath))
|
|
assert(master:listen())
|
|
|
|
-- Helper function to log console debugging information.
|
|
local function clientlog(client, str)
|
|
minetest.log("action", modname .. "[" .. client.id .. "]: " .. str)
|
|
end
|
|
|
|
-- Attempt to accept a new client connection.
|
|
local function accept()
|
|
local sock, err = master:accept()
|
|
if sock then
|
|
-- Make the new client non-blocking too.
|
|
assert(sock:settimeout(0))
|
|
|
|
-- Try to determine an identifier for the connection.
|
|
local id = string_match(tostring(sock), "0x%x+")
|
|
or tostring(sock)
|
|
|
|
-- Register new connection.
|
|
local c = {id = id, sock = sock}
|
|
clients[id] = c
|
|
|
|
clientlog(c, "connected")
|
|
c.sock:send("connected as " .. id .. "\n> ")
|
|
elseif err ~= "timeout" then
|
|
minetest.log("warning", CONSOLE .. " accept(): " .. err)
|
|
end
|
|
end
|
|
|
|
-- Execute actual console commands.
|
|
local function concmd(client, line)
|
|
-- Special "exit" command to disconnect, e.g. when
|
|
-- unable to send an EOF or interrupt.
|
|
if line == "/exit" then
|
|
clients[client.id] = nil
|
|
return client.sock:close()
|
|
end
|
|
|
|
-- Try to run registered chat commands, and return a
|
|
-- failure if not found.
|
|
for _, v in ipairs(minetest.registered_on_chat_messages) do
|
|
local ok, err = pcall(function() return v(CONSOLE, line) end)
|
|
if ok and err then return end
|
|
if not ok then
|
|
return minetest.chat_send_player(CONSOLE, err)
|
|
end
|
|
end
|
|
minetest.chat_send_player(CONSOLE, "unrecognized command")
|
|
end
|
|
|
|
-- Attempt to receive an input line from the console client, if
|
|
-- one is ready (buffered non-blocking IO)
|
|
local function receive(client)
|
|
local line, err = client.sock:receive("*l")
|
|
if line ~= nil then
|
|
-- Prepend the slash. We assume that all input is to
|
|
-- be commands rather than accidentally leaking chat.
|
|
while line:sub(1, 1) == "/" do
|
|
line = line:sub(2)
|
|
end
|
|
line = "/" .. line
|
|
clientlog(client, "command: " .. line)
|
|
|
|
-- Hook console messages and send to client, too.
|
|
conmsg = function(x)
|
|
client.sock:send(x .. "\n")
|
|
end
|
|
local ok, err2 = pcall(function() concmd(client, line) end)
|
|
conmsg = nil
|
|
if not ok then return error(err2) end
|
|
client.sock:send("> ")
|
|
elseif err ~= "timeout" then
|
|
clientlog(client, err)
|
|
clients[client.id] = nil
|
|
end
|
|
end
|
|
|
|
-- On every server cycle, check for new connections, and
|
|
-- process commands from existing ones.
|
|
minetest.register_globalstep(function()
|
|
accept()
|
|
for _, client in pairs(clients) do receive(client) end
|
|
end)
|