Luna/gui/loginui.cpp

187 lines
4.8 KiB
C++
Raw Normal View History

/**
* @file loginui.cpp
* @author apio (cloudapio.eu)
* @brief Graphical login prompt.
*
* @copyright Copyright (c) 2024, the Luna authors.
*
*/
#include <luna/RefString.h>
#include <luna/SHA.h>
#include <os/ArgumentParser.h>
#include <os/Config.h>
#include <os/File.h>
#include <os/FileSystem.h>
#include <os/IPC.h>
#include <os/Process.h>
#include <os/Security.h>
#include <pwd.h>
#include <shadow.h>
#include <sys/stat.h>
#include <ui/App.h>
#include <ui/Button.h>
#include <ui/InputField.h>
#include <ui/Label.h>
#include <ui/Layout.h>
#include <unistd.h>
enum Stage
{
UsernameInput,
PasswordInput,
};
static constexpr ui::Color BACKGROUND_COLOR = ui::Color::from_rgb(89, 89, 89);
Result<String> hash_password(StringView& view)
{
SHA256 sha;
sha.append((const u8*)view.chars(), view.length());
auto digest = TRY(sha.digest());
return digest.to_string();
}
Result<int> 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;
}
2024-12-14 12:18:59 +01:00
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<StringView>(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<StringView>(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<StringView>(startui_command, 2));
unreachable();
}
});
return app.run();
}