From 20e3f127a9c79f8ed9a9fcc3ffea0172eda4d606 Mon Sep 17 00:00:00 2001
From: dzwdz
Date: Sat, 13 Aug 2022 22:07:15 +0200
Subject: user: a primitive login utility

---
 initrd/Users/dzwdz/private/secrets.txt |  2 +
 initrd/Users/dzwdz/public/stuff.txt    |  2 +
 initrd/bin/sh/mkuser                   |  7 +++
 src/user/app/init/init.c               | 16 ++++--
 src/user/app/login/login.c             | 95 ++++++++++++++++++++++++++++++++++
 5 files changed, 118 insertions(+), 4 deletions(-)
 create mode 100644 initrd/Users/dzwdz/private/secrets.txt
 create mode 100644 initrd/Users/dzwdz/public/stuff.txt
 create mode 100644 initrd/bin/sh/mkuser
 create mode 100644 src/user/app/login/login.c

diff --git a/initrd/Users/dzwdz/private/secrets.txt b/initrd/Users/dzwdz/private/secrets.txt
new file mode 100644
index 0000000..7f1bcbc
--- /dev/null
+++ b/initrd/Users/dzwdz/private/secrets.txt
@@ -0,0 +1,2 @@
+I enjoyed Twilight.
+
diff --git a/initrd/Users/dzwdz/public/stuff.txt b/initrd/Users/dzwdz/public/stuff.txt
new file mode 100644
index 0000000..b864fc2
--- /dev/null
+++ b/initrd/Users/dzwdz/public/stuff.txt
@@ -0,0 +1,2 @@
+A publicly readable file.
+
diff --git a/initrd/bin/sh/mkuser b/initrd/bin/sh/mkuser
new file mode 100644
index 0000000..a1544c6
--- /dev/null
+++ b/initrd/bin/sh/mkuser
@@ -0,0 +1,7 @@
+#!/bin/shell
+touch /Users/user/
+cd /Users/user/
+touch private/
+echo secrets! > private/secret.txt
+touch public/
+echo no secrets here > public/hi.txt
\ No newline at end of file
diff --git a/src/user/app/init/init.c b/src/user/app/init/init.c
index d9dabc6..49cc085 100644
--- a/src/user/app/init/init.c
+++ b/src/user/app/init/init.c
@@ -47,6 +47,18 @@ int main(void) {
 			NULL
 		});
 	}
+	MOUNT_AT("/Users/") {
+		MOUNT_AT("/tmp/") {
+			const char *argv[] = {"/bin/tmpfs", NULL};
+			execv(argv[0], (void*)argv);
+		}
+		// TODO a simple union isn't enough here
+		fs_union((const char*[]){
+			"/tmp/",
+			"/init/Users/",
+			NULL
+		});
+	}
 	MOUNT_AT("/tmp/") {
 		const char *argv[] = {"/bin/tmpfs", NULL};
 		execv(argv[0], (void*)argv);
@@ -55,10 +67,6 @@ int main(void) {
 		const char *argv[] = {"/bin/vterm", NULL};
 		execv(argv[0], (void*)argv);
 	}
-	MOUNT_AT("/union/") {
-		const char *list[] = {"/tmp/", "/init/", NULL};
-		fs_union(list);
-	}
 
 	if (_syscall_pipe(killswitch_pipe, 0) < 0) {
 		printf("couldn't create the killswitch pipe, quitting...\n");
diff --git a/src/user/app/login/login.c b/src/user/app/login/login.c
new file mode 100644
index 0000000..c1fab3f
--- /dev/null
+++ b/src/user/app/login/login.c
@@ -0,0 +1,95 @@
+#include <camellia/flags.h>
+#include <camellia/syscalls.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <user/lib/fs/misc.h>
+
+static const char *shell = "/bin/shell";
+
+static void cutspace(char *s) {
+	for (; *s; s++) {
+		if (isspace(*s)) {
+			*s = '\0';
+			break;
+		}
+	}
+}
+
+static bool accesscheck(const char *path) {
+	const char *prefix = "/Users/";
+	if (strlen(path) < strlen(prefix) || memcmp(path, prefix, strlen(prefix)))
+		return true; /* not an user dir - access allowed */
+	path += strlen(prefix);
+
+	/* skip username */
+	path = strchr(path, '/');
+	if (!path) return true;
+	path++;
+
+	/* inside an user dir */
+	const char *private = "private/";
+	return strlen(path) < strlen(private) || memcmp(path, private, strlen(private));
+}
+
+static void drv(const char *prefix) {
+	struct fs_wait_response res;
+	size_t prefixlen = strlen(prefix);
+	char buf[128];
+	while (!_syscall_fs_wait(buf, sizeof buf, &res)) {
+		switch (res.op) {
+			handle_t h;
+			case VFSOP_OPEN:
+				if (res.len == sizeof buf) {
+					_syscall_fs_respond(NULL, -1, 0);
+					break;
+				}
+				buf[res.len] = '\0';
+
+				if (res.len >= prefixlen && !memcmp(prefix, buf, prefixlen)) {
+					h = _syscall_open(buf, res.len, res.flags);
+				} else if (accesscheck(buf)) {
+					h = _syscall_open(buf, res.len, res.flags | OPEN_RO);
+				} else {
+					h = -EACCES;
+				}
+				_syscall_fs_respond(NULL, h, FSR_DELEGATE);
+				break;
+
+			default:
+				_syscall_fs_respond(NULL, -1, 0);
+				break;
+		}
+	}
+}
+
+static void trylogin(const char *user) {
+	if (strcmp(user, "root")) {
+		char buf[128];
+		snprintf(buf, sizeof buf, "/Users/%s/", user);
+		if (chdir(buf) < 0) {
+			printf("no such user: %s\n", user);
+			return;
+		}
+		MOUNT_AT("/") { drv(buf); }
+	}
+
+	execv(shell, NULL);
+	fprintf(stderr, "login: couldn't launch %s\n", shell);
+	exit(1);
+}
+
+int main(void) {
+	char user[64];
+	printf("\nCamellia\n");
+	for (;;) {
+		printf("login: ");
+		fgets(user, sizeof user, stdin);
+		if (ferror(stdin)) return -1;
+
+		cutspace(user);
+		if (user[0]) trylogin(user);
+	}
+}
-- 
cgit v1.2.3