diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt index f2a98141..b8334e71 100644 --- a/apps/CMakeLists.txt +++ b/apps/CMakeLists.txt @@ -49,3 +49,4 @@ luna_app(2048.cpp 2048) target_link_libraries(2048 PUBLIC ui) luna_app(clock.cpp clock) target_link_libraries(clock PUBLIC ui) +luna_app(startui.cpp startui) diff --git a/apps/startui.cpp b/apps/startui.cpp new file mode 100644 index 00000000..776af9ca --- /dev/null +++ b/apps/startui.cpp @@ -0,0 +1,132 @@ +/** + * @file startui.cpp + * @author apio (cloudapio.eu) + * @brief Servic to start and manage a UI session. + * + * @copyright Copyright (c) 2024, the Luna authors. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Timeout should be provided in milliseconds. +Result wait_until_file_created(StringView path, int timeout) +{ + struct stat st; + while (stat(path.chars(), &st) < 0) + { + if (timeout <= 0) return err(ETIMEDOUT); + usleep(100000); // FIXME: Implement something like inotify or use signals to avoid polling. + timeout -= 100; + } + return {}; +} + +Result> read_supplementary_groups(const char* name) +{ + Vector extra_groups; + + setgrent(); + group* grp; + while ((grp = getgrent())) + { + for (char** user = grp->gr_mem; *user; user++) + { + if (!strcmp(*user, name)) + { + TRY(extra_groups.try_append(grp->gr_gid)); + break; + } + } + } + endgrent(); + + return extra_groups; +} + +Result spawn_process_as_user(Slice arguments, uid_t user, gid_t group, + Slice supplementary_groups) +{ + pid_t child = TRY(os::Process::fork()); + if (child == 0) + { + if (setgroups(static_cast(supplementary_groups.size()), supplementary_groups.data()) < 0) + return err(errno); + setgid(group); + setuid(user); + TRY(os::Process::exec(arguments[0], arguments, false)); + } + return {}; +} + +Result luna_main(int argc, char** argv) +{ + if (geteuid() != 0) + { + os::eprintln("error: %s can only be started as root.", argv[0]); + return 1; + } + + StringView username; + + os::ArgumentParser parser; + parser.add_description("Service to start and manage a UI session."); + parser.add_system_program_info("startui"_sv); + parser.add_value_argument(username, 'u', "user", "the user to start the UI session as"); + parser.parse(argc, argv); + + if (username.is_empty()) + { + os::eprintln("error: startui needs a --username parameter to run."); + parser.short_usage(argv[0]); + return 1; + } + + struct passwd* pw = getpwnam(username.chars()); + if (!pw) + { + os::eprintln("error: user %s not found.", username.chars()); + return 1; + } + + auto groups = TRY(read_supplementary_groups(username.chars())); + + setsid(); + + // First of all, start the display server. + StringView wind_command[] = { "/usr/bin/wind" }; + TRY(os::Process::spawn(wind_command[0], Slice(wind_command, 1))); + + TRY(wait_until_file_created("/tmp/wind.sock", 10000)); + + clearenv(); + chdir(pw->pw_dir); + setenv("PATH", "/usr/bin:/usr/local/bin", 1); + setenv("USER", pw->pw_name, 1); + setenv("HOME", pw->pw_dir, 1); + setenv("SHELL", pw->pw_shell, 1); + + // Next, start the required UI components. + StringView taskbar_command[] = { "/usr/bin/taskbar" }; + TRY(spawn_process_as_user(Slice(taskbar_command, 1), pw->pw_uid, pw->pw_gid, groups.slice())); + + // Finally, start init in user mode to manage all configured optional services. + StringView init_command[] = { "/usr/bin/init", "--user" }; + TRY(spawn_process_as_user(Slice(init_command, 2), pw->pw_uid, pw->pw_gid, groups.slice())); + + // If any process exits, it's either the display server, one of the required UI components, or init --user. We care + // about all of these, so let's just crash the entire UI session if they exit. In the future we might restart some + // of them and continue, but for now we'll just exit on all of them. + os::Process::wait(os::Process::ANY_CHILD, nullptr); + + return 0; +} diff --git a/base/etc/init/99-login b/base/etc/init/99-login index bb7e7ac9..180fbf11 100644 --- a/base/etc/init/99-login +++ b/base/etc/init/99-login @@ -1,6 +1,6 @@ Name=login Description=Start the display server. -Command=/usr/bin/wind --user=selene +Command=/usr/bin/startui --user=selene StandardOutput=/dev/uart0 StandardError=/dev/uart0 Restart=true diff --git a/base/etc/user/00-taskbar b/base/etc/user/00-taskbar deleted file mode 100644 index 398940b9..00000000 --- a/base/etc/user/00-taskbar +++ /dev/null @@ -1,5 +0,0 @@ -Name=taskbar -Description=Start the taskbar. -Command=/usr/bin/taskbar -WorkingDirectory=/home/selene -Restart=true diff --git a/base/etc/user/01-terminal b/base/etc/user/00-terminal similarity index 70% rename from base/etc/user/01-terminal rename to base/etc/user/00-terminal index c73e5fe1..2e93a0e6 100644 --- a/base/etc/user/01-terminal +++ b/base/etc/user/00-terminal @@ -1,4 +1,3 @@ Name=terminal Description=Start the terminal. -WorkingDirectory=/home/selene Command=/usr/bin/terminal diff --git a/wind/main.cpp b/wind/main.cpp index 01309cf7..f5289011 100644 --- a/wind/main.cpp +++ b/wind/main.cpp @@ -56,31 +56,6 @@ static void debug(const Vector>& clients) os::println("--- wind: END DEBUG OUTPUT ---"); } -Result set_supplementary_groups(const char* name) -{ - Vector extra_groups; - - setgrent(); - group* grp; - while ((grp = getgrent())) - { - for (char** user = grp->gr_mem; *user; user++) - { - if (!strcmp(*user, name)) - { - os::println("Adding supplementary group: %d", grp->gr_gid); - TRY(extra_groups.try_append(grp->gr_gid)); - break; - } - } - } - endgrent(); - - if (setgroups(static_cast(extra_groups.size()), extra_groups.data()) < 0) return err(errno); - - return {}; -} - Result luna_main(int argc, char** argv) { srand((unsigned)time(NULL)); @@ -88,13 +63,11 @@ Result luna_main(int argc, char** argv) TRY(os::Security::pledge("stdio rpath wpath cpath unix proc exec tty id", NULL)); StringView socket_path = "/tmp/wind.sock"; - StringView user; os::ArgumentParser parser; parser.add_description("The display server for Luna's graphical user interface."_sv); parser.add_system_program_info("wind"_sv); parser.add_value_argument(socket_path, 's', "socket"_sv, "the path for the local IPC socket"_sv); - parser.add_value_argument(user, 'u', "user"_sv, "the user to run as"_sv); parser.parse(argc, argv); if (geteuid() != 0) @@ -117,6 +90,10 @@ Result luna_main(int argc, char** argv) Mouse mouse_pointer { screen.canvas() }; + // Opened all necessary files as root, drop privileges now. + setgid(WIND_GROUP_ID); + setuid(WIND_USER_ID); + int fd = open("/dev/null", O_RDONLY); if (fd >= 0) { @@ -124,45 +101,11 @@ Result luna_main(int argc, char** argv) close(fd); } - setegid(WIND_GROUP_ID); - seteuid(WIND_USER_ID); - - if (setsid() < 0) perror("setsid"); - - mode_t mask = umask(0002); + umask(0002); auto server = TRY(os::LocalServer::create(socket_path, false)); TRY(server->listen(20)); - umask(mask); - - seteuid(0); - - clearenv(); - - pid_t child = TRY(os::Process::fork()); - if (!child) - { - if (!user.is_empty()) - { - auto* pwd = getpwnam(user.chars()); - if (pwd) - { - TRY(set_supplementary_groups(user.chars())); - setgid(pwd->pw_gid); - setuid(pwd->pw_uid); - } - } - - StringView args[] = { "/usr/bin/init"_sv, "--user"_sv }; - TRY(os::Process::exec("/usr/bin/init"_sv, Slice { args, 2 }, false)); - } - - umask(0002); - - setgid(WIND_GROUP_ID); - setuid(WIND_USER_ID); - ui::Color background = ui::Color::from_rgb(0x10, 0x10, 0x10); Vector> clients;