diff --git a/install.py b/install.py new file mode 100644 index 0000000..5230dab --- /dev/null +++ b/install.py @@ -0,0 +1,87 @@ +""" +Install script for the Python jailer. +Version: 1.0.0-main1 +""" +import os +import shutil +with open("./kverinfo.txt", "r") as f: + kver = f.read() + f.close() +with open("./verinfo.txt", "r") as f: + ver = f.read() + f.close() +print("Welcome to the PyJail install wizard.") +print(f"You are about to install version {ver} with manager {kver}") +y_n_question = input("Do you want to install PyJail? [Y/n] ") +if y_n_question.lower() == "n": + print("Installation cancelled!") + input("Press to exit ") + exit(-1) +print("Preparing installation...") +os.mkdir(os.getcwd() + "/vfs") +shutil.move("./main.py", "./vfs/main.py") +shutil.move("./runner.py", "./main.py") +shutil.move("./sh.py", "./vfs/sh.py") +shutil.move("./ledit.py", "./vfs/ledit.py") +os.chdir(os.getcwd() + "/vfs") +print("Gathering info...") +usrname = input("Please enter your username: [usr1] ") +if usrname == "": + usrname = "usr1" +setup_posix = input("Do you wish to setup a mostly-POSIX compliant environment? [y/N] ") +if setup_posix.lower() == "y": + print("Creating directories...") + with open("./bin", "a+") as f: + f.write("symlnk /usr/bin/") + f.close() + with open("./sbin", "a+") as f: + f.write("symlnk /usr/sbin/") + f.close() + with open("./lib", "a+") as f: + f.write("symlnk /usr/lib/") + f.close() + with open("./lib64", "a+") as f: + f.write("symlnk /usr/lib64/") + f.close() + with open("./usr/bin/sh", "a+") as f: + f.write("symlnk /usr/bin/shell.py") + f.close() + os.mkdir("./sys") + os.mkdir("./usr") + os.mkdir("./proc") + os.mkdir(f"./home/{usrname}") + os.mkdir("./usr/bin/") + os.mkdir("./usr/sbin/") + os.mkdir("./usr/lib/") + os.mkdir("./usr/lib64/") + print("Copying files...") + shutil.move("./main.py", "./sys/jail_mgr.py") + shutil.move("./sh.py", "./bin/shell.py") + shutil.move("./ledit.py", "./bin/ledit.py") + print("Creating system configuration files...") + with open("./sys/usr.conf", "a+") as f: + f.write(usrname) + f.close() + with open("./sys/procinfo", "a+") as f: + f.write("proc: vfs(/proc/)\nmgr: vfs(/proc/kcore)") + f.close() + with open("./proc/kcore", "a+") as f: + f.write("/sys/jail_mgr.py") + f.close() +else: + print("Creating directories...") + os.mkdir("./bin") + os.mkdir("./sys") + os.mkdir("./usr") + os.mkdir("./proc") + os.mkdir(f"./home/{usrname}") + print("Copying files...") + shutil.move("./main.py", "./sys/jail_mgr.py") + shutil.move("./sh.py", "./bin/shell.py") + shutil.move("./ledit.py", "./bin/ledit.py") + print("Creating system configuration files...") + with open("./sys/usr.conf", "a+") as f: + f.write(usrname) + f.close() +print("Install completed! Run ./main.py to start the kernel!") +input("Press to exit! ") diff --git a/kverinfo.txt b/kverinfo.txt new file mode 100644 index 0000000..610655b --- /dev/null +++ b/kverinfo.txt @@ -0,0 +1 @@ +0.2.0-main1 diff --git a/ledit.py b/ledit.py new file mode 100644 index 0000000..48835ac --- /dev/null +++ b/ledit.py @@ -0,0 +1,17 @@ +""" +LEdit, a single line text editor made in Python +Version: 0.0.2-alpha1 +Made as an example program for PyNVOS +""" +class ledit: + def __init__(self, instance): + self._kernel = instance + def prgm(self): + path_to_file = input("Enter location of file to edit/create: ") + path_to_file = self._kernel.fs(path_to_file) + with open(path_to_file, "a+") as f: + text_to_write = input("Line: ") + f.write(text_to_write + "\n") + f.close() + exit() +if __name__ == "": diff --git a/main.py b/main.py new file mode 100644 index 0000000..1a56783 --- /dev/null +++ b/main.py @@ -0,0 +1,140 @@ +""" +This is the PyJail, a jailing tool for running Python apps in a sandboxed environment. +Version: 0.2.0-main1 +""" +import os +import time +import runpy + + +class PyJail: + """ + The + """ + 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 + + @staticmethod + def kver(): + """ + Returns the jail manager version + """ + return "0.2.0-main1" diff --git a/runner.py b/runner.py new file mode 100644 index 0000000..0ce4b87 --- /dev/null +++ b/runner.py @@ -0,0 +1,7 @@ +""" +Allows the PyNVOS "kernel" to "boot" +Version: 1.0.0 +""" +from vfs.sys.jail_mgr import PyJail +krnl = PyJail() +krnl.run_program("/bin/shell.py") diff --git a/sh.py b/sh.py new file mode 100644 index 0000000..3c344a8 --- /dev/null +++ b/sh.py @@ -0,0 +1,51 @@ +""" +The shell for PyNVOS +Version: 0.1.0-main1 +""" +import importlib +import os +import cmd + +# from ..sys.krnl import Kernel +print(__name__) +class shell(cmd.Cmd): + intro = "Shell started, PyNVOS 0.1.1-main1" + prompt = "shell-0.1.0$ " + 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) + 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")) + 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") + else: + shell.kernel.run_program(args) + + def do_ls(self, none): + """Lists the content of a directory""" + os.listdir(os.getcwd()) + + def postloop(self): + pass + + +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.") + input("Press Enter to continue...") + exit(-1) \ No newline at end of file diff --git a/verinfo.txt b/verinfo.txt new file mode 100644 index 0000000..1f9d3a0 --- /dev/null +++ b/verinfo.txt @@ -0,0 +1 @@ +0.2 build 0036