diff options
Diffstat (limited to 'advtrains_luaautomation/environment.lua')
-rw-r--r-- | advtrains_luaautomation/environment.lua | 372 |
1 files changed, 372 insertions, 0 deletions
diff --git a/advtrains_luaautomation/environment.lua b/advtrains_luaautomation/environment.lua new file mode 100644 index 0000000..63aa68d --- /dev/null +++ b/advtrains_luaautomation/environment.lua @@ -0,0 +1,372 @@ +------------- +-- 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.subscribers=data.subscribers 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, subscribers=self.subscribers} + 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" +} + +local function safe_date(f, t) + if not f then + -- fall back to old behavior + return(os.date("*t",os.time())) + else + --pass parameters + return os.date(f,t) + end +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 static_env = { + --core LUA functions + 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 = advtrains.getstate, + setstate = advtrains.setstate, + is_passive = advtrains.is_passive, + --interrupts are handled per node, position unknown. (same goes for digilines) + --however external interrupts can be set here. + interrupt_pos = function(parpos, imesg) + local pos=atlatc.pcnaming.resolve_pos(parpos) + atlatc.interrupt.add(0, pos, {type="ext_int", ext_int=true, message=imesg}) + end, + -- sends an atc command to train regardless of where it is in the world + atc_send_to_train = function(train_id, command) + assertt(command, "string") + local train = advtrains.trains[train_id] + if train then + advtrains.atc.train_set_command(train, command, true) + return true + else + return false + end + end, +} + +-- If interlocking is present, enable route setting functions +if advtrains.interlocking then + local function gen_checks(signal, route_name, noroutesearch) + assertt(route_name, "string") + local pos = atlatc.pcnaming.resolve_pos(signal) + local sigd = advtrains.interlocking.db.get_sigd_for_signal(pos) + if not sigd then + error("There's no signal at "..minetest.pos_to_string(pos)) + end + local tcbs = advtrains.interlocking.db.get_tcbs(sigd) + if not tcbs then + error("Inconsistent configuration, no tcbs for signal at "..minetest.pos_to_string(pos)) + end + + local routeid, route + if not noroutesearch then + for routeidt, routet in ipairs(tcbs.routes) do + if routet.name == route_name then + routeid = routeidt + route = routet + break + end + end + if not route then + error("No route called "..route_name.." at "..minetest.pos_to_string(pos)) + end + end + return pos, sigd, tcbs, routeid, route + end + + + static_env.can_set_route = function(signal, route_name) + local pos, sigd, tcbs, routeid, route = gen_checks(signal, route_name) + -- if route is already set on signal, return whether it's committed + if tcbs.routeset == routeid then + return tcbs.route_committed + end + -- actually try setting route (parameter 'true' designates try-run + local ok = advtrains.interlocking.route.set_route(sigd, route, true) + return ok + end + static_env.set_route = function(signal, route_name) + local pos, sigd, tcbs, routeid, route = gen_checks(signal, route_name) + return advtrains.interlocking.route.update_route(sigd, tcbs, routeid) + end + static_env.cancel_route = function(signal) + local pos, sigd, tcbs, routeid, route = gen_checks(signal, "", true) + return advtrains.interlocking.route.update_route(sigd, tcbs, nil, true) + end + static_env.get_aspect = function(signal) + local pos = atlatc.pcnaming.resolve_pos(signal) + return advtrains.interlocking.signal_get_aspect(pos) + end + static_env.set_aspect = function(signal, asp) + local pos = atlatc.pcnaming.resolve_pos(signal) + return advtrains.interlocking.signal_set_aspect(pos) + end +end + +-- Lines-specific: +if advtrains.lines then + local atlrwt = advtrains.lines.rwt + static_env.rwt = { + now = atlrwt.now, + new = atlrwt.new, + copy = atlrwt.copy, + to_table = atlrwt.to_table, + to_secs = atlrwt.to_secs, + to_string = atlrwt.to_string, + add = atlrwt.add, + diff = atlrwt.diff, + sub = atlrwt.sub, + adj_diff = atlrwt.adj_diff, + adjust_cycle = atlrwt.adjust_cycle, + adjust = atlrwt.adjust, + to_string = atlrwt.to_string, + get_time_until = atlrwt.get_time_until, + next_rpt = atlrwt.next_rpt, + last_rpt = atlrwt.last_rpt, + time_from_last_rpt = atlrwt.time_from_last_rpt, + time_to_next_rpt = atlrwt.time_to_next_rpt, + } +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) + -- create us a print function specific for this environment + if not self.safe_print_func then + local myenv = self + self.safe_print_func = function(...) + myenv:log("info", ...) + end + end + + 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] + elseif i=="print" then + return self.safe_print_func + 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 + self:log("error", "Executing InitCode for '"..self.name.."' failed:"..err) + self.init_err=err + if old_fdata then + self.fdata=old_fdata + self:log("warning", "The 'F' table has been restored to the previous state.") + end + end + end +end + +-- log to environment subscribers. severity can be "error", "warning" or "info" (used by internal print) +function env_proto:log(severity, ...) + local text=advtrains.print_concat_table({"[atlatc "..self.name.." "..severity.."]", ...}) + minetest.log("action", text) + for _, pname in ipairs(self.subscribers) do + minetest.chat_send_player(pname, text) + end +end + +-- env.subscribers table may be directly altered by callers. + + +--- class interface + +function atlatc.env_new(name) + local newenv={ + name=name, + init_code="", + sdata={}, + subscribers={}, + } + 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 + + + + |