Rewrite moonlock from Python to Rust (v0.4.0)
Complete rewrite of the Wayland lockscreen from Python/PyGObject to Rust/gtk4-rs for memory safety in security-critical PAM code and consistency with the moonset/moongreet Rust ecosystem. Modules: main, lockscreen, auth (PAM FFI), fingerprint (fprintd D-Bus), config, i18n, users, power. 37 unit tests. Security: PAM conversation callback with Zeroizing password, panic hook that never unlocks, root check, ext-session-lock-v1 compositor policy, absolute loginctl path, avatar symlink rejection.
This commit is contained in:
+164
@@ -0,0 +1,164 @@
|
||||
// ABOUTME: Entry point for Moonlock — secure Wayland lockscreen.
|
||||
// ABOUTME: Sets up GTK Application, ext-session-lock-v1, Panic-Hook, and multi-monitor windows.
|
||||
|
||||
mod auth;
|
||||
mod config;
|
||||
mod fingerprint;
|
||||
mod i18n;
|
||||
mod lockscreen;
|
||||
mod power;
|
||||
mod users;
|
||||
|
||||
use gdk4 as gdk;
|
||||
use gtk4::prelude::*;
|
||||
use gtk4::{self as gtk, gio};
|
||||
use gtk4_session_lock;
|
||||
use std::path::PathBuf;
|
||||
use std::rc::Rc;
|
||||
|
||||
fn load_css(display: &gdk::Display) {
|
||||
let css_provider = gtk::CssProvider::new();
|
||||
css_provider.load_from_resource("/dev/moonarch/moonlock/style.css");
|
||||
gtk::style_context_add_provider_for_display(
|
||||
display,
|
||||
&css_provider,
|
||||
gtk::STYLE_PROVIDER_PRIORITY_APPLICATION,
|
||||
);
|
||||
}
|
||||
|
||||
fn activate(app: >k::Application) {
|
||||
let display = match gdk::Display::default() {
|
||||
Some(d) => d,
|
||||
None => {
|
||||
log::error!("No display available — cannot start lockscreen UI");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
load_css(&display);
|
||||
|
||||
let config = config::load_config(None);
|
||||
let bg_path = config::resolve_background_path(&config);
|
||||
|
||||
if gtk4_session_lock::is_supported() {
|
||||
activate_with_session_lock(app, &display, &bg_path, &config);
|
||||
} else {
|
||||
log::warn!("ext-session-lock-v1 not supported — running in development mode");
|
||||
activate_without_lock(app, &bg_path, &config);
|
||||
}
|
||||
}
|
||||
|
||||
fn activate_with_session_lock(
|
||||
app: >k::Application,
|
||||
display: &gdk::Display,
|
||||
bg_path: &PathBuf,
|
||||
config: &config::Config,
|
||||
) {
|
||||
let lock = gtk4_session_lock::Instance::new();
|
||||
lock.lock();
|
||||
|
||||
let monitors = display.monitors();
|
||||
|
||||
// Shared unlock callback — unlocks session and quits
|
||||
let lock_clone = lock.clone();
|
||||
let app_clone = app.clone();
|
||||
let unlock_callback: Rc<dyn Fn()> = Rc::new(move || {
|
||||
lock_clone.unlock();
|
||||
app_clone.quit();
|
||||
});
|
||||
|
||||
let mut created_any = false;
|
||||
for i in 0..monitors.n_items() {
|
||||
if let Some(monitor) = monitors
|
||||
.item(i)
|
||||
.and_then(|obj| obj.downcast::<gdk::Monitor>().ok())
|
||||
{
|
||||
let window = lockscreen::create_lockscreen_window(
|
||||
bg_path,
|
||||
config,
|
||||
app,
|
||||
unlock_callback.clone(),
|
||||
);
|
||||
lock.assign_window_to_monitor(&window, &monitor);
|
||||
window.present();
|
||||
created_any = true;
|
||||
}
|
||||
}
|
||||
|
||||
if !created_any {
|
||||
log::error!("No lockscreen windows created — screen stays locked (compositor policy)");
|
||||
}
|
||||
}
|
||||
|
||||
fn activate_without_lock(
|
||||
app: >k::Application,
|
||||
bg_path: &PathBuf,
|
||||
config: &config::Config,
|
||||
) {
|
||||
let app_clone = app.clone();
|
||||
let unlock_callback: Rc<dyn Fn()> = Rc::new(move || {
|
||||
app_clone.quit();
|
||||
});
|
||||
|
||||
let window = lockscreen::create_lockscreen_window(
|
||||
bg_path,
|
||||
config,
|
||||
app,
|
||||
unlock_callback,
|
||||
);
|
||||
window.set_default_size(800, 600);
|
||||
window.present();
|
||||
}
|
||||
|
||||
fn setup_logging() {
|
||||
let mut builder = env_logger::Builder::from_default_env();
|
||||
builder.filter_level(log::LevelFilter::Info);
|
||||
|
||||
let log_dir = PathBuf::from("/var/cache/moonlock");
|
||||
if log_dir.is_dir() {
|
||||
let log_file = log_dir.join("moonlock.log");
|
||||
if let Ok(file) = std::fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(&log_file)
|
||||
{
|
||||
builder.target(env_logger::Target::Pipe(Box::new(file)));
|
||||
}
|
||||
}
|
||||
|
||||
builder.init();
|
||||
}
|
||||
|
||||
fn install_panic_hook() {
|
||||
// Install a panic hook BEFORE starting the app.
|
||||
// On panic, we log but NEVER unlock. The compositor's ext-session-lock-v1
|
||||
// policy keeps the screen locked when the client crashes.
|
||||
let default_hook = std::panic::take_hook();
|
||||
std::panic::set_hook(Box::new(move |info| {
|
||||
log::error!("PANIC — screen stays locked (compositor policy): {info}");
|
||||
default_hook(info);
|
||||
}));
|
||||
}
|
||||
|
||||
fn main() {
|
||||
setup_logging();
|
||||
|
||||
// Root check — moonlock should not run as root
|
||||
if nix::unistd::getuid().is_root() {
|
||||
log::error!("Moonlock should not run as root");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
install_panic_hook();
|
||||
log::info!("Moonlock starting");
|
||||
|
||||
// Register compiled GResources
|
||||
gio::resources_register_include!("moonlock.gresource").expect("Failed to register resources");
|
||||
|
||||
let app = gtk::Application::builder()
|
||||
.application_id("dev.moonarch.moonlock")
|
||||
.build();
|
||||
|
||||
app.connect_activate(activate);
|
||||
app.run();
|
||||
}
|
||||
Reference in New Issue
Block a user