diff --git a/main.py b/main.py index 0c78223..efea672 100644 --- a/main.py +++ b/main.py @@ -1,58 +1,155 @@ -""" -This is a sort of OS built in Python, not bootable, but creates a custom directory structure and path definition. -This is the "kernel", it hosts all features and runs all programs. -For safety reasons the kernel is isolated, which means that with every shell instance, -A new kernel instance will follow it. Same goes for every program, it will need to call upon a brand-new kernel instance. -Version: 0.1.1-nps3 -""" -import os -import runpy - -class PyJail: - """ - The "kernel" for PyNVOS - """ - def __init__(self): - self.rootpath = "" - self.rootpath = self.fs() - - def run_program(self, path_to_bin): - """ - Runs a specified program. - """ - path_to_bin = self.fs(path_to_bin) - # print(path_to_bin) - # print(str(self.rootpath) + str(path_to_bin)) - if path_to_bin == 3 or path_to_bin == 2: - print("An error has occurred launching the program.") - else: - runpy.run_path(path_to_bin) - - def fs(self, check_path=None) -> str: - """ - Keeps track of the jailed filesystem and makes sure any calls to any - file get done in the jailed filesystem - """ - if check_path is not None: - if os.path.exists(f"{self.rootpath}{check_path}"): - if check_path.startswith("."): - check_path = check_path.lstrip(".") - return os.getcwd() + check_path if "vfs" in os.getcwd() else 2 - return self.rootpath + check_path - elif self.rootpath in check_path: - print("ERR: Cannot parse rootpath, expected vfspath") - return 3 - else: - # Path is not in the jailed fs, so we say it doesn't exist. - print("ERR: File/directory doesn't exist in vfspath") - return 2 - else: - rootpath = os.getcwd() + "/vfs" - return rootpath - - @staticmethod - def is_posix_compatible() -> bool: - """ - Returns if the kernel is POSIX compatible. - """ - return False +""" +This is the PyJail, a jailing tool for running Python apps in a sandboxed environment. +Version: 0.2.1-next1 +""" +import os +import time +import runpy + + +class PyJail: + """ + The jail manager, handles all system calls and such. + """ + def __init__(self, debug=False): + self.rootpath = "" + self.rootpath = self.fs() + self._debug = debug + with open(self.fs("/proc/klog"), "w") as f: + # Always use jailmgr.msg() from this point onwards. + f.write(f"[{time.time}] [jailmgr.__init__()] [INFO] START LOG") + f.close() + with open(self.fs("/proc/kproc"), "w") as f: + f.write("proc: jailmgr(1)") + self._program_counter = 2 + f.close() + self._resolve_symlinks = False if os.path.isdir(self.fs("/bin")) else True + + def run_program(self, path_to_bin): + """ + Runs a specified program. + """ + path_to_bin = self.fs(path_to_bin) + # print(path_to_bin) + # print(str(self.rootpath) + str(path_to_bin)) + if path_to_bin == 3 or path_to_bin == 2: + self.msg("jailmgr.run_program()", "An error has occurred launching the program.", True, + "WARNING") + else: + with open(self.fs("/proc/kproc"), "a+") as f: + f.write(f"proc: {path_to_bin}({self._program_counter})") + self._program_counter += 1 + runpy.run_path(path_to_bin) + + def msg(self, caller: str, message: str, emit: bool = False, log_level: str = "INFO"): + """ + The custom message parser, can parse messages and alert apps of said messages. + Replaces print statements. + Args: + caller: The program that called the logger + message: Is the message to parse. + emit: If the message needs to be passed to apps. + log_level: The loglevel, either DEBUG, INFO, WARNING, ERROR, CRITICAL + """ + if self._debug is True: + emit = True + accepted_log_levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] + if log_level.upper() not in accepted_log_levels: + self.msg(f"jailmgr.msg()",f"Not accepted loglevel!! {log_level}", False, "ERROR") + with open(self.fs("/proc/klog"), "a+") as f: + f.write(f"[{time.time}] [{caller}] [{log_level}] {message}") + if emit is True: + print(message) + return 0 + + def fs(self, check_path=None, resolve_symlinks=True): + """ + Keeps track of the jailed filesystem and makes sure any calls to any + file get done in the jailed filesystem + """ + if check_path is not None: + if os.path.exists(f"{self.rootpath}{check_path}"): + if self._resolve_symlinks is True or resolve_symlinks is True: + # This exists to ease /bin and other symlinks that are always present. + # It allows the system to drastically speed up resolving symlinks. + symlinkable_dirs = ["/bin", "/sbin", "/lib", "/lib64"] + for directory in symlinkable_dirs: + if check_path.startswith(directory): + check_path_usr_merge = f"/usr{check_path}" + return self.rootpath + check_path_usr_merge + # Well, if it doesn't start with any of them, we need to check each and every directory. + check_path_split = check_path.split("/", -1) + prepend_path = "" + for i, path in enumerate(check_path_split): + prepend_path = f"{prepend_path}/{path}" if not path.endswith("/") else f"{prepend_path}/{path}/" + check_path_split[i] = f"{prepend_path}" + check_path = "" + for i, path in enumerate(check_path_split): + if os.path.isdir(path) and i != (len(check_path_split) - 1): + # Directory is not a symlink, we can just ignore and move on. + pass + elif os.path.isdir(path) and i == (len(check_path_split) - 1): + # The last thing to access was a directory. We can safely return the full path now. + return path + elif not os.path.isdir(path) and i != (len(check_path_split) - 1): + # This was not the last thing we needed to access, so we assume it's a symlink. + # One problem is that we can't be sure, so we make sure it is a symlink. + with open(self.fs(path, False)) as f: + is_symlink = f.read() + f.close() + if is_symlink.startswith("symlnk"): + # This is a symlink! + # Symlinks always contain the full literal path that they need to access, so we can + # take that and do the same trick to split it and add the next things to it. + # raise NotImplementedError() + is_symlink_split = is_symlink.split(" ", 1) + symlink_dest = is_symlink_split[1] + symlink_dest = f"{symlink_dest}/{path}" + return symlink_dest + else: + # This is either not a symlink or an improperly configured one. + self.msg("jailmgr.fs()", "reached non-symlink file not at end of list", + False, "ERROR") + self.msg("jailmgr.fs()", "What was assumed to be a directory isn't a" + " directory nor a symlink! This might be because of a " + "typo or misconfigured symlink. The directory in question: " + f"{path}", True, "WARNING") + return 2 + + return self.rootpath + check_path + if check_path.startswith("."): + check_path = check_path.lstrip(".") + return os.getcwd() + check_path if "vfs" in os.getcwd() else 2 + elif self.rootpath in check_path: + self.msg(f"jailmgr.fs()", "Cannot parse rootpath, expected vfspath", log_level="ERROR") + return 3 + else: + # Path is not in the jailed fs, so we say it doesn't exist. + self.msg(f"jailmgr.fs()", "File/directory doesn't exist in vfspath", log_level="ERROR") + return 2 + else: + rootpath = os.getcwd() + "/vfs" + self.msg("jailmgr.fs()", message=rootpath, log_level="INFO") + return rootpath + + def kver(self): + """ + Returns the kernel version + """ + return "0.2.1-next1" + + def netsock(self, ip, port, mode, msg): + """ + An easy interface to network sockets, built right into the jailmanager + + Args: + ip: The IP of the server to access. + port: The port to access the server on + mode: Either UDP, TCP or PKG (HTTP) + msg: The message to send the server + + Returns: + Whatever the server returns. + """ + raise NotImplementedError("TODO: Netsock will be implemented once 0.3.0 comes around!") + diff --git a/sh.py b/sh.py index 3c344a8..f6d2a3d 100644 --- a/sh.py +++ b/sh.py @@ -1,44 +1,60 @@ """ The shell for PyNVOS -Version: 0.1.0-main1 +Version: 0.2.0.0399 """ import importlib import os import cmd +import shutil # from ..sys.krnl import Kernel print(__name__) class shell(cmd.Cmd): - intro = "Shell started, PyNVOS 0.1.1-main1" - prompt = "shell-0.1.0$ " + jail_mgr = importlib.import_module(".jail_mgr", "vfs.sys") + jailmgr = jail_mgr.PyJail() + kver = jailmgr.kver() + intro = f"Shell started, PyNVOS {kver}" + prompt = "shell-0.2$ " file = None - krnl = importlib.import_module(".jail_mgr", "vfs.sys") - kernel = krnl.Kernel() - print(str(kernel) + " " + str(type(kernel))) def do_cd(self, args): """Changes directory""" - args = shell.kernel.fs(args) + args = shell.jailmgr.fs(args) os.chdir(args) def do_exec(self, args): """Allows you to execute a file""" # Apps in /bin should be allowed to launch without first adding /bin/ or ./, just the name of the executable # So for ledit it should be just 'ledit' and not /bin/ledit.py or ./ledit.py - bins_in_bin = os.listdir(shell.kernel.fs("/bin")) + bins_in_bin = os.listdir(self.jailmgr.fs("/bin")) apps_strip = [] for apps in bins_in_bin: if apps.endswith(".py"): apps_strip.append(apps.strip(".py")) if args in apps_strip: - shell.kernel.run_program(f"/bin/{args}.py") + shell.jailmgr.run_program(f"/bin/{args}.py") else: - shell.kernel.run_program(args) + shell.jailmgr.run_program(args) def do_ls(self, none): """Lists the content of a directory""" os.listdir(os.getcwd()) + def do_pkg(self, pkg): + """Downloads packages over the internet.""" + self.jailmgr.netsock(f"https://pkg.novacow.ch/repo/{kver}/meta/{pkg}.pmd", 443, "PKG", "") + shutil.copy(self.jailmgr.fs(f"/usr/netsock/cache/{pkg}.pmd"), self.jailmgr.fs(f"/usr/pkg/metacache/")) + with open(self.jailmgr.fs(f"/usr/pkgs/metacache/{pkg}.pmd"), "r") as f: + package_meta = f.read() + f.close() + print(package_meta) + y_n_confirmation = input("Do you want to install this package? [y/N] ") + if y_n_confirmation.lower() != "y": + print("Aborted.") + return + self.jailmgr.netsock(f"https://pkg.novacow.ch/repo/{kver}/main/static/binary/{pkg}.py", 443, "PKG", "") + shutil.copy(self.jailmgr.fs(f"/usr/netsock/cache/{pkg}.py"), self.jailmgr.fs(f"/usr/bin/")) + def postloop(self): pass @@ -46,6 +62,6 @@ class shell(cmd.Cmd): if __name__ == '': shell().cmdloop() if __name__ == '__main__': - print("The shell can't be ran as a standalone program and must be ran in conjunction with the kernel.") + print("The shell can't be ran as a standalone program and must be ran in conjunction with the jail manager.") input("Press Enter to continue...") - exit(-1) \ No newline at end of file + exit(-1)