async def run_dm(self, loop, instance_dir, major, minor): """Executor proc to host and run the .dmb file provided.""" dd_path = os.path.join( self.get_work_dir(), f"byond{major}.{minor}\\byond\\bin\\dreamdaemon.exe") if not os.path.isfile(dd_path): raise BotError("dreadaemon.exe not found.", "run_dm") dmb_path = os.path.join(instance_dir, "eval.dmb") if not os.path.isfile(dmb_path): raise BotError(".dmb under evaluation not found.", "run_dm") p_args = [dd_path, dmb_path] + [ "-invisible", "-ultrasafe", "-logself", "-log", "output.log", "-once", "-close", "-quiet" ] process = await asyncio.create_subprocess_exec( *p_args, loop=loop, stderr=asyncio.subprocess.DEVNULL, stdout=asyncio.subprocess.DEVNULL) try: await asyncio.wait_for(process.wait(), timeout=60.0, loop=loop) except TimeoutError: raise BotError("DreamDaemon timed out.", "run_dm")
def validate_compile(self, instance_dir): """Checks wether or not the compiled end result is safe to run.""" dmb_found = False for fname in os.listdir(instance_dir): if fname.endswith(".rsc"): raise BotError("Resource file detected. Execution aborted.", "validate_compile") elif fname.endswith(".dmb"): dmb_found = True if not dmb_found: raise BotError("Compilation failed and no .dmb was generated.", "validate_compile")
def validate_byond_build(byond_str): """ A little shit of a failed command argument. Return a tuple containing (major, minor) build information if the argument string matches the defined format of: v:{major}.{minor} {rest of code here}. Returns None if such a tuple can't be generated. """ if not byond_str.startswith("v:"): return None chunks = byond_str.split(" ") if not len(chunks) > 1: return None chunks = chunks[0].split(".") # This is triggered alyways forever. So. Return null if format doesn't match. if len(chunks) != 2: return None try: major = int(chunks[0][2:]) minor = int(chunks[1]) except ValueError: raise BotError("Error processing BYOND version request.", "validate_byond_build") return major, minor
def validate_dm(self, code): """Validates the code given for potential exploits.""" for expr in self._safety_expressions: if expr.search(code): raise BotError("Disallowed/dangerous code found. Aborting.", "validate_dm")
def process_args(self, code): """ Generates an array of code segments to be placed into the compiled DM code. Returned dictionary must have three keys: "pre_proc", "proc", and "to_out". If those pieces do not exist, they are to be set as None. As to avoid key errors further down the call stack. """ res = self._arg_expression.match(code) if not res or not res.groupdict(): raise BotError("No valid code sent.", "process_args") code_segs = {"pre_proc": None, "proc": None, "to_out": None} res_dict = res.groupdict() for key in code_segs: if key in res_dict: code_segs[key] = res_dict[key] if (code_segs["pre_proc"] and not code_segs["pre_proc"].endswith(";") and not code_segs["pre_proc"].endswith("}")): code_segs["pre_proc"] += ";" if (code_segs["proc"] and not code_segs["proc"].endswith(";") and not code_segs["proc"].endswith(";")): code_segs["proc"] += ";" if code_segs["to_out"]: code_segs["to_out"] = code_segs["to_out"].split(";") return code_segs
async def setup_byond(self, major=DEFAULT_MAJOR, minor=DEFAULT_MINOR): """Downloads and unzips the provided BYOND version.""" path = self.get_work_dir() byond_path = os.path.join(path, f"byond{major}.{minor}") url = f"http://www.byond.com/download/build/{major}/{major}.{minor}_byond.zip" async with aiohttp.ClientSession() as session: async with session.get(url) as resp: try: data = await resp.read() except Exception: raise BotError("Unable to download the BYOND zip file.", "init_byond") if resp.status != 200: raise BotError( "Unable to download the specified BYOND version.", "init_byond") with ZipFile(BytesIO(data)) as z: z.extractall(byond_path)
def get_repo(self): """Gets the appropriate github API object.""" conf = self.bot.Config() if not conf.github["api_token"]: raise BotError("Github API token not provided.", "get_repo") try: git = github.Github(conf.github["api_token"]) except github.GithubException: raise BotError("Unable to login to Github.", "get_repo") try: org = git.get_organization(conf.github["wiki_org"]) except github.GithubException: raise BotError("Unable to fetch the organization.", "get_repo") try: repo = org.get_repo(conf.github["wiki_repo"]) except github.GithubException: raise BotError("Unable to acquire the repository.", "get_repo") return repo
async def compile_dm(self, loop, instance_dir, major, minor): """Executor proc to compile the .dme file provided.""" dm_path = os.path.join(self.get_work_dir(), f"byond{major}.{minor}\\byond\\bin\\dm.exe") if not os.path.isfile(dm_path): raise BotError("dm.exe not found.", "compile_dm") dme_path = os.path.join(instance_dir, "eval.dme") if not os.path.isfile(dme_path): raise BotError(".dme under evaluation not found.", "compile_dm") process = await asyncio.create_subprocess_exec( *[dm_path, dme_path], loop=loop, stderr=asyncio.subprocess.DEVNULL, stdout=asyncio.subprocess.DEVNULL) try: await asyncio.wait_for(process.wait(), timeout=60.0, loop=loop) except TimeoutError: raise BotError("Compiler timed out.", "compile_dm") if process.returncode != 0: raise BotError("Error compiling or running DM.", "compile_dm")
async def run_executor(self, proc, p_args): """A helper for running Windows subprocesses in a separate thread.""" thread = WindowsProcessThread(proc, p_args) thread.start() cycles = 0 while cycles < 60: if not thread.is_alive(): break cycles += 1 await asyncio.sleep(1) error = thread.errored error_msg = thread.error_msg thread.join() if error: raise BotError(error_msg, "run_executor")