diff options
Diffstat (limited to 'advtrains/advtrains_luaautomation/environment.lua')
-rw-r--r-- | advtrains/advtrains_luaautomation/environment.lua | 288 |
1 files changed, 288 insertions, 0 deletions
diff --git a/advtrains/advtrains_luaautomation/environment.lua b/advtrains/advtrains_luaautomation/environment.lua new file mode 100644 index 0000000..8dfdd39 --- /dev/null +++ b/advtrains/advtrains_luaautomation/environment.lua @@ -0,0 +1,288 @@ +------------- +-- 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 +function atlatc.replace_function_envs(o, fenv, 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 type(o)=="function" then + setfenv(o, fenv) + 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.replace_function_envs(v, fenv, 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. + +-- returns: true, fenv if successful; nil, error if error +function env_proto:execute_code(fenv, 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] + 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 + rawset(t,i,v) + end, + } + setmetatable(fenv, metatbl) + local fun, err=loadstring(code) + if not fun then + return false, err + end + --set function environment for all functions residing in F, so they get the right variables. Else it's a huge mess... + atlatc.replace_function_envs(self.fdata, fenv) + + setfenv(fun, fenv) + local succ, data = pcall(fun) + if succ then + data=fenv + end + return succ, data +end + +function env_proto:run_initcode() + if self.init_code and self.init_code~="" then + 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 + self.init_err=err + 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 + + + + |