/** * @file loginui.cpp * @author apio (cloudapio.eu) * @brief Graphical login prompt. * * @copyright Copyright (c) 2024, the Luna authors. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include enum Stage { UsernameInput, PasswordInput, }; static constexpr ui::Color BACKGROUND_COLOR = ui::Color::from_rgb(89, 89, 89); Result hash_password(StringView& view) { SHA256 sha; sha.append((const u8*)view.chars(), view.length()); auto digest = TRY(sha.digest()); return digest.to_string(); } Result luna_main(int argc, char** argv) { os::ArgumentParser parser; parser.add_description("Login prompt for a graphical UI session."); parser.add_system_program_info("loginui"_sv); parser.parse(argc, argv); if (geteuid() != 0) { os::eprintln("error: %s can only be started as root.", argv[0]); return 1; } TRY(os::Security::pledge("stdio rpath wpath unix proc exec id", nullptr)); setsid(); bool success = os::IPC::Notifier::run_and_wait( [&] { StringView wind_command[] = { "/usr/bin/wind" }; os::Process::spawn(wind_command[0], Slice(wind_command, 1)); }, 1000); if (!success) { os::eprintln("loginui: failed to start wind, timed out"); return 1; } auto config = TRY(os::ConfigFile::open("/etc/loginui.conf")); if (config->read_boolean_or("Autologin", false)) { StringView username = config->read_string_or("AutologinUser", ""); if (!username.is_empty()) { auto flag = RefString::format("--user=%s"_sv, username.chars()).release_value(); StringView startui_command[] = { "/usr/bin/startui", flag.view() }; os::Process::exec(startui_command[0], Slice(startui_command, 2)); unreachable(); } } ui::App app; TRY(app.init()); auto* window = TRY(ui::Window::create(ui::Rect { 300, 300, 400, 300 })); app.set_main_window(window); window->set_title("Log in"); window->set_background(BACKGROUND_COLOR); ui::VerticalLayout main_layout; window->set_main_widget(main_layout); ui::Label label("Username:"); main_layout.add_widget(label); ui::InputField input(ui::Font::default_font()); main_layout.add_widget(input); ui::Label error(""); error.set_font(ui::Font::default_bold_font()); error.set_color(ui::RED); main_layout.add_widget(error); Stage stage = Stage::UsernameInput; struct passwd* pw; input.on_submit([&](StringView data) { error.set_text(""); if (stage == Stage::UsernameInput) { struct passwd* entry = getpwnam(data.chars()); if (!entry) { error.set_text("User not found."); input.clear(); return; } pw = entry; stage = Stage::PasswordInput; label.set_text("Password:"); RefString title = RefString::format("Log in: %s"_sv, data.chars()).release_value(); window->set_title(title.view()); input.clear(); return; } else { const char* passwd = pw->pw_passwd; // If the user's password entry is 'x', read their password from the shadow file instead. if (!strcmp(pw->pw_passwd, "x")) { struct spwd* sp = getspnam(pw->pw_name); if (!sp) { error.set_text("User not found in shadow file."); input.clear(); return; } endspent(); passwd = sp->sp_pwdp; } if (!strcmp(passwd, "!")) { error.set_text("User's password is disabled."); input.clear(); return; } auto result = hash_password(data).release_value(); if (strcmp(result.chars(), passwd)) { error.set_text("Incorrect password."); input.clear(); return; } auto flag = RefString::format("--user=%s"_sv, pw->pw_name).release_value(); StringView startui_command[] = { "/usr/bin/startui", flag.view() }; os::Process::exec(startui_command[0], Slice(startui_command, 2)); unreachable(); } }); return app.run(); }