aboutsummaryrefslogtreecommitdiff
path: root/advtrains_luaautomation/environment.lua
diff options
context:
space:
mode:
Diffstat (limited to 'advtrains_luaautomation/environment.lua')
-rw-r--r--advtrains_luaautomation/environment.lua275
1 files changed, 275 insertions, 0 deletions
diff --git a/advtrains_luaautomation/environment.lua b/advtrains_luaautomation/environment.lua
new file mode 100644
index 0000000..0b25e87
--- /dev/null
+++ b/advtrains_luaautomation/environment.lua
@@ -0,0 +1,275 @@
+-------------
+-- lua sandboxed environment
+
+-- function to cross out functions and userdata.
+-- modified from dump()
+function atlatc.remove_invalid_data(o, nested)
+ if o==nil then return nil end
+ local valid_dt={["nil"]=true, boolean=true, number=true, string=true}
+ if type(o) ~= "table" then
+ --check valid data type
+ if not valid_dt[type(o)] then
+ return nil
+ end
+ return o
+ end
+ -- Contains table -> true/nil of currently nested tables
+ nested = nested or {}
+ if nested[o] then
+ return nil
+ end
+ nested[o] = true
+ for k, v in pairs(o) do
+ v = atlatc.remove_invalid_data(v, nested)
+ end
+ nested[o] = nil
+ return o
+end
+
+
+local env_proto={
+ load = function(self, envname, data)
+ self.name=envname
+ self.sdata=data.sdata and atlatc.remove_invalid_data(data.sdata) or {}
+ self.fdata={}
+ self.init_code=data.init_code or ""
+ self.step_code=data.step_code or ""
+ end,
+ save = function(self)
+ -- throw any function values out of the sdata table
+ self.sdata = atlatc.remove_invalid_data(self.sdata)
+ return {sdata = self.sdata, init_code=self.init_code, step_code=self.step_code}
+ end,
+}
+
+--Environment
+--Code modified from mesecons_luacontroller (credit goes to Jeija and mesecons contributors)
+
+local safe_globals = {
+ "assert", "error", "ipairs", "next", "pairs", "select",
+ "tonumber", "tostring", "type", "unpack", "_VERSION"
+}
+
+--print is actually minetest.chat_send_all()
+--using advtrains.print_concat_table because it's cool
+local function safe_print(t, ...)
+ local str=advtrains.print_concat_table({t, ...})
+ minetest.log("action", "[atlatc] "..str)
+ minetest.chat_send_all(str)
+end
+
+local function safe_date()
+ return(os.date("*t",os.time()))
+end
+
+-- string.rep(str, n) with a high value for n can be used to DoS
+-- the server. Therefore, limit max. length of generated string.
+local function safe_string_rep(str, n)
+ if #str * n > 2000 then
+ debug.sethook() -- Clear hook
+ error("string.rep: string length overflow", 2)
+ end
+
+ return string.rep(str, n)
+end
+
+-- string.find with a pattern can be used to DoS the server.
+-- Therefore, limit string.find to patternless matching.
+-- Note: Disabled security since there are enough security leaks and this would be unneccessary anyway to DoS the server
+local function safe_string_find(...)
+ --if (select(4, ...)) ~= true then
+ -- debug.sethook() -- Clear hook
+ -- error("string.find: 'plain' (fourth parameter) must always be true for security reasons.")
+ --end
+
+ return string.find(...)
+end
+
+local mp=minetest.get_modpath("advtrains_luaautomation")
+local p_api_getstate, p_api_setstate = dofile(mp.."/passive.lua")
+
+local static_env = {
+ --core LUA functions
+ print = safe_print,
+ string = {
+ byte = string.byte,
+ char = string.char,
+ format = string.format,
+ len = string.len,
+ lower = string.lower,
+ upper = string.upper,
+ rep = safe_string_rep,
+ reverse = string.reverse,
+ sub = string.sub,
+ find = safe_string_find,
+ },
+ math = {
+ abs = math.abs,
+ acos = math.acos,
+ asin = math.asin,
+ atan = math.atan,
+ atan2 = math.atan2,
+ ceil = math.ceil,
+ cos = math.cos,
+ cosh = math.cosh,
+ deg = math.deg,
+ exp = math.exp,
+ floor = math.floor,
+ fmod = math.fmod,
+ frexp = math.frexp,
+ huge = math.huge,
+ ldexp = math.ldexp,
+ log = math.log,
+ log10 = math.log10,
+ max = math.max,
+ min = math.min,
+ modf = math.modf,
+ pi = math.pi,
+ pow = math.pow,
+ rad = math.rad,
+ random = math.random,
+ sin = math.sin,
+ sinh = math.sinh,
+ sqrt = math.sqrt,
+ tan = math.tan,
+ tanh = math.tanh,
+ },
+ table = {
+ concat = table.concat,
+ insert = table.insert,
+ maxn = table.maxn,
+ remove = table.remove,
+ sort = table.sort,
+ },
+ os = {
+ clock = os.clock,
+ difftime = os.difftime,
+ time = os.time,
+ date = safe_date,
+ },
+ POS = function(x,y,z) return {x=x, y=y, z=z} end,
+ getstate = p_api_getstate,
+ setstate = p_api_setstate,
+ --interrupts are handled per node, position unknown.
+ --however external interrupts can be set here.
+ interrupt_pos = function(pos, imesg)
+ if not type(pos)=="table" or not pos.x or not pos.y or not pos.z then
+ debug.sethook()
+ error("Invalid position supplied to interrupt_pos")
+ end
+ atlatc.interrupt.add(0, pos, {type="ext_int", ext_int=true, message=imesg})
+ end,
+}
+
+for _, name in pairs(safe_globals) do
+ static_env[name] = _G[name]
+end
+
+
+--The environment all code calls get is a table that has set static_env as metatable.
+--In general, every variable is local to a single code chunk, but kept persistent over code re-runs. Data is also saved, but functions and userdata and circular references are removed
+--Init code and step code's environments are not saved
+-- S - Table that can contain any save data global to the environment. Will be saved statically. Can't contain functions or userdata or circular references.
+-- F - Table global to the environment, can contain volatile data that is deleted when server quits.
+-- The init code should populate this table with functions and other definitions.
+
+local proxy_env={}
+--proxy_env gets a new metatable in every run, but is the shared environment of all functions ever defined.
+
+-- returns: true, fenv if successful; nil, error if error
+function env_proto:execute_code(localenv, code, evtdata, customfct)
+ local metatbl ={
+ __index = function(t, i)
+ if i=="S" then
+ return self.sdata
+ elseif i=="F" then
+ return self.fdata
+ elseif i=="event" then
+ return evtdata
+ elseif customfct and customfct[i] then
+ return customfct[i]
+ elseif localenv and localenv[i] then
+ return localenv[i]
+ end
+ return static_env[i]
+ end,
+ __newindex = function(t, i, v)
+ if i=="S" or i=="F" or i=="event" or (customfct and customfct[i]) or static_env[i] then
+ debug.sethook()
+ error("Trying to overwrite environment contents")
+ end
+ localenv[i]=v
+ end,
+ }
+ setmetatable(proxy_env, metatbl)
+ local fun, err=loadstring(code)
+ if not fun then
+ return false, err
+ end
+
+ setfenv(fun, proxy_env)
+ local succ, data = pcall(fun)
+ if succ then
+ data=localenv
+ end
+ return succ, data
+end
+
+function env_proto:run_initcode()
+ if self.init_code and self.init_code~="" then
+ local old_fdata=self.fdata
+ self.fdata = {}
+ atprint("[atlatc]Running initialization code for environment '"..self.name.."'")
+ local succ, err = self:execute_code({}, self.init_code, {type="init", init=true})
+ if not succ then
+ atwarn("[atlatc]Executing InitCode for '"..self.name.."' failed:"..err)
+ self.init_err=err
+ if old_fdata then
+ self.fdata=old_fdata
+ atwarn("[atlatc]The 'F' table has been restored to the previous state.")
+ end
+ end
+ end
+end
+function env_proto:run_stepcode()
+ if self.step_code and self.step_code~="" then
+ local succ, err = self:execute_code({}, self.step_code, nil, {})
+ if not succ then
+ --TODO
+ end
+ end
+end
+
+--- class interface
+
+function atlatc.env_new(name)
+ local newenv={
+ name=name,
+ init_code="",
+ step_code="",
+ sdata={}
+ }
+ setmetatable(newenv, {__index=env_proto})
+ return newenv
+end
+function atlatc.env_load(name, data)
+ local newenv={}
+ setmetatable(newenv, {__index=env_proto})
+ newenv:load(name, data)
+ return newenv
+end
+
+function atlatc.run_initcode()
+ for envname, env in pairs(atlatc.envs) do
+ env:run_initcode()
+ end
+end
+function atlatc.run_stepcode()
+ for envname, env in pairs(atlatc.envs) do
+ env:run_stepcode()
+ end
+end
+
+
+
+