• There is NO official Otland's Discord server and NO official Otland's server list. The Otland's Staff does not manage any Discord server or server list. Moderators or administrator of any Discord server or server lists have NO connection to the Otland's Staff. Do not get scammed!

[TFS 1.2] Luascript channel

forgot to test that on mc
Yeah, for longer scripts it's better.
Your feature might be useful on dedicated servers to quickly execute something without using putty/winscp.

I have to warn everyone: luascript channel(in both scripts) is dangerous itself - If someone knows lua and linux well and have access to that channel, he may get access to server system/hack your ots easily. This channel with os.execute() is basically like a terminal, this is why that channel should be available for trusted admins only.
Seriously, if examples below are possible, then imagine what else.
Code:
os.execute("color 70")
os.execute("tasklist")

to block os.execute() use this:
Code:
if message:match('os.execute') then
return false
end

However, reading config.lua is still possible and it's harder to block because for example: ("c" .. "onfig" .. "." .. "lua") will get around filter like this on os.execute. Currently I don't have any solution for this so just be careful and don't allow random people to use that channel.
 
Last edited:
It would be best to only use it for development and with people you really trust.
Maybe it would be possible to create a kind of safe environment in which the script will be executed and which does not support certain functions (os.execute, getConfigValue, etc..)

Checking the string does not really help.
e.g.
Code:
local t = {'c', 'o', 'n', 'f', 'i', 'g', '.', 'l', 'u', 'a'}
local file = io.open(table.concat(t))
 
I used in 1.1
now in 1.2 it doesnt work

do you use in 1.2?
I'm not sure. Currently I use this version for testing (works on tfs version from 29 mar 2015):
Code:
<channel id="9" name="Lua" script="execute.lua" />
Code:
function canJoin(player)
	return player:getAccountType() >= ACCOUNT_TYPE_GAMEMASTER
end

function onSpeak(player, type, message)
	if not (player:getAccountType() >= ACCOUNT_TYPE_GOD) then
		return false
	end

	local pos = Position(player:getPosition())
	local tile = Tile(pos)

	_G["cid"] = player:getId()
	_G["player"] = player
	_G["creature"] = Creature(player:getId())
	_G["pos"] = pos
	_G["tile"] = tile
	_G["container"] = player:getSlotItem(CONST_SLOT_BACKPACK)
	_G["group"] = player:getGroup()
	_G["vocation"] = player:getVocation()
	_G["town"] = player:getTown()
	_G["house"] = tile:getHouse()
	_G["party"] = player:getParty()
	
	local res, err = loadstring(message)
	if res then
		local ret, err = pcall(res)
		if not ret then
			player:sendChannelMessage(player:getName(), "Lua Script Error: " .. err .. ".", TALKTYPE_CHANNEL_O, 9)
			return false
		end
	else
		player:sendChannelMessage(player:getName(), "Lua Script Error: " .. err .. ".", TALKTYPE_CHANNEL_O, 9)
		return false
	end
	player:sendChannelMessage("", player:getName() .. ">>  " .. message, TALKTYPE_CHANNEL_Y, 9)
	return false
end
 
Last edited:
Why not sandbox it? You can describe which functions you can use.
Ex: i set trusted functions to be print and Game.broadcastMessage
lua-sandbox.png


Code:
local env = {print = print, Game = {broadcastMessage = Game.broadcastMessage}}

local function run(untrusted_code)
   local untrusted_function, message = load(untrusted_code, nil, 't', env)
   if not untrusted_function then return nil, message end
   return pcall(untrusted_function)
end

function canJoin(player)
   return player:getAccountType() >= ACCOUNT_TYPE_GAMEMASTER
end

function onSpeak(player, type, message)
   if not (player:getAccountType() >= ACCOUNT_TYPE_GOD) then
      return false
   end

   local pos = Position(player:getPosition())
   local tile = Tile(pos)

   _G["cid"] = player:getId()
   _G["player"] = player
   _G["creature"] = Creature(player:getId())
   _G["pos"] = pos
   _G["tile"] = tile
   _G["container"] = player:getSlotItem(CONST_SLOT_BACKPACK)
   _G["group"] = player:getGroup()
   _G["vocation"] = player:getVocation()
   _G["town"] = player:getTown()
   _G["house"] = tile:getHouse()
   _G["party"] = player:getParty()
  
   local ret, err = run(message)
   if not ret then
      player:sendChannelMessage(player:getName(), "Lua Script Error: " .. err .. ".", TALKTYPE_CHANNEL_O, 9)
      return false
   end
   player:sendChannelMessage("", player:getName() .. ">>  " .. message, TALKTYPE_CHANNEL_Y, 9)

   return false
end
 
@LaloHao
I didn't found a way to "sandbox" it. What happens when you try to bypass it?
 
doesn't seem to work on clear 1.2, here is current version I use:
Code:
function onSpeak(player, type, message)
   if player:getAccountType() < 5 then
     return false
   end
  
   local pos = Position(player:getPosition())
   local tile = Tile(pos)

   _G["cid"] = player:getId()
   _G["player"] = player
   _G["creature"] = Creature(player:getId())
   _G["pos"] = pos
   _G["tile"] = tile
   _G["container"] = player:getSlotItem(CONST_SLOT_BACKPACK)
   _G["group"] = player:getGroup()
   _G["vocation"] = player:getVocation()
   _G["town"] = player:getTown()
   _G["house"] = tile:getHouse()
   _G["party"] = player:getParty()
  
   local res, err = loadstring(message)
   if res then
     local ret, err = pcall(res)
     if not ret then
       player:sendChannelMessage(player:getName(), "Lua Script Error: " .. err .. ".", TALKTYPE_CHANNEL_O, 9)
       return false
     end
   else
     player:sendChannelMessage(player:getName(), "Lua Script Error: " .. err .. ".", TALKTYPE_CHANNEL_O, 9)
     return false
   end
   player:sendChannelMessage("", player:getName() .. ">>  " .. message, TALKTYPE_CHANNEL_Y, 9)
   return false
end
 
I'm also using TFS stock 1.2
If you copy-paste the code it should work
PHP:
local env = {print = print, Game = {broadcastMessage = Game.broadcastMessage}}

local function run(untrusted_code)
   local untrusted_function, message = load(untrusted_code, nil, 't', env)
   if not untrusted_function then return nil, message end
   return pcall(untrusted_function)
end

function canJoin(player)
   return player:getAccountType() >= ACCOUNT_TYPE_GAMEMASTER
end

function onSpeak(player, type, message)
   if not (player:getAccountType() >= ACCOUNT_TYPE_GOD) then
      return false
   end

   local pos = Position(player:getPosition())
   local tile = Tile(pos)

   _G["cid"] = player:getId()
   _G["player"] = player
   _G["creature"] = Creature(player:getId())
   _G["pos"] = pos
   _G["tile"] = tile
   _G["container"] = player:getSlotItem(CONST_SLOT_BACKPACK)
   _G["group"] = player:getGroup()
   _G["vocation"] = player:getVocation()
   _G["town"] = player:getTown()
   _G["house"] = tile:getHouse()
   _G["party"] = player:getParty()

   local ret, err = run(message)
   if not ret then
      player:sendChannelMessage(player:getName(), "Lua Script Error: " .. err .. ".", TALKTYPE_CHANNEL_O, 9)
      return false
   end
   player:sendChannelMessage("", player:getName() .. ">>  " .. message, TALKTYPE_CHANNEL_Y, 9)

   return false
end

Capturadepantalla_2015-10-24_19-55-17.png


It already has the sandbox function for "print" and Game.broadcast and denies everything else you want to execute
 
You shouldn't have items and positions hard coded in the environment because they aren't updated, they will be the same values from the first time the player used the console and accessing them could crash the server (e.g container:getName() after throwing your backpack in the water or trash). I would suggest removing all of them (the player userdata is fine, that is, unless it is being used on an addEvent, as always), because now that the environment is sandboxed, you can simply store them in a variable yourself when using the console.
 
You shouldn't have items and positions hard coded in the environment because they aren't updated, they will be the same values from the first time the player used the console and accessing them could crash the server (e.g container:getName() after throwing your backpack in the water or trash). I would suggest removing all of them (the player userdata is fine, that is, unless it is being used on an addEvent, as always), because now that the environment is sandboxed, you can simply store them in a variable yourself when using the console.
I see.
Game.createMonster("dragon", pos) executed quickly 3 times in a row crashed the server
 
Well after some time, and since you didn't notify that lua 5.2 (and later) have solutions to this using environment variable, which you can easily update upvalue.
 
If people are still wanting to use this but reviving an error about "setfenc" you will need to add this function. (Error is due to using lua version 5.2+)
Code:
local function setfenv(fn, env)
  local i = 1
  while true do
    local name = debug.getupvalue(fn, i)
    if name == "_ENV" then
      debug.upvaluejoin(fn, i, (function()
        return env
      end), 1)
      break
    elseif not name then
      break
    end

    i = i + 1
  end

  return fn
end


Found another error that i cant seem to work out.

When in tibia in general you cannt reference links properly..

For example this
Code:
broadcastMessage(test.test)

will Produce this
Code:
broadcastMessage?test.test)
 
Last edited:
Im thinking about giving the players the ability to execute a small sandbox that allows them to play their character, like a programmers tibia war but who knows if anyone would play that, anyway i made a small shell script that sends selected text to OTClient, as a tool that might someday get to that point


PHP:
#!/bin/sh

n="OTClient"
c="/l" #i converted luascript channel to a command
i=`xclip -o` #gets selection
echo "$c $i" > /tmp/lasttibiacommand #format "/l YourLuaCode" to a file
w=`xdotool search --name $n` #find otclient id window
xdotool type --window $w --file /tmp/lasttibiacommand #type
xdotool search --name $n key Return
 
Back
Top