All checks were successful
Build and test / build (push) Successful in 1m34s
Unsalted SHA256 passwords are still a long way from being secure, but at least we're not storing plaintext anymore.
187 lines
4.8 KiB
C++
187 lines
4.8 KiB
C++
/**
|
|
* @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;
|
|
}
|
|
|
|
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();
|
|
}
|