commit 94d1973c7c040aabb4f814b57ba3c6e33ccd6973 Author: Dany LE Date: Tue Mar 24 20:29:11 2026 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3e0001d --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +.cargo \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..6b46fa5 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "dysm-rs" +version = "0.1.0" +edition = "2021" + +[dependencies] +clap = "4" +gio = "0.22.2" +glib = "0.21.5" +lazy_static = "1.5.0" +libc = "0.2.183" +toml = "0.8" diff --git a/references/daemon.conf b/references/daemon.conf new file mode 100644 index 0000000..b057fe0 --- /dev/null +++ b/references/daemon.conf @@ -0,0 +1,28 @@ +# this file is the configuration file for the SessionMaganer daemon +# daemon +# By default, the daemon will look for this file in the following locations +# /etc/diya/daemon.conf +# or it can be specified using the -c option +# example: diya-session-manager -c /path/to/daemon.conf + +# PAM service +# the pam service used for authentication +pam_service = "diya" + +# Login session command +# The command to run to start a login session +# this command will handle the user input and send user +# credentials to the daemon via Dbus message +login_session_command = "/usr/bin/diyac -x /usr/bin/diya-login-shell" + +# login session user +# The user that owns the login session, root by default +# if this setting is not set +login_session_user = "xdg" + +# User session command +# The command to run to start a user session after the +# login session is successful +# the logged in user will own this session + +user_session_command = "/usr/bin/diyac -x /usr/bin/diya-shell" \ No newline at end of file diff --git a/resources/introspection.xml b/resources/introspection.xml new file mode 100644 index 0000000..977b9be --- /dev/null +++ b/resources/introspection.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/configuration.rs b/src/configuration.rs new file mode 100644 index 0000000..2d91a54 --- /dev/null +++ b/src/configuration.rs @@ -0,0 +1,121 @@ +//! # Configuration Module +//! +//! This module defines the `Configuration` struct, which is responsible for +//! loading and managing the application's configuration settings from a TOML file. +//! The configuration includes parameters such as the PAM service name, +//! commands for login and user sessions, and the user to run login sessions as. +//! The `Configuration` struct provides methods to access these settings and implements +//! the `Display` trait for easy debugging and logging. +use crate::APPLICATION_NAME; +use core::fmt; +use glib::g_info; +use std::path::PathBuf; +use toml::Value; + +/// Configuration +/// +/// Represents the application's configuration settings loaded from a TOML file. +pub struct Configuration { + /// The name of the PAM service to use (default: "diya"). + pam_service: String, + /// The command to execute for login sessions (required). + login_session_command: String, + /// The user to run login sessions as (default: "root"). + login_session_user: String, // optional (default root) + /// The command to execute for user sessions (required). + user_session_command: String, +} + +impl Configuration { + /// Load configuration from a TOML file. + /// + /// # Arguments + //// + /// * `file` - A `PathBuf` pointing to the configuration file. + /// + /// # Returns + /// * `Ok(Configuration)` if the configuration was successfully loaded and parsed. + /// * `Err(Box)` if there was an error reading the file or + /// parsing the TOML content. + pub fn from(file: &PathBuf) -> Result> { + g_info!( + APPLICATION_NAME, + "Loading configuration from: {}", + file.display() + ); + let content = std::fs::read_to_string(file)?; + let content = content.parse::()?; + let table = content.as_table().ok_or("Invalid configuration format")?; + + Ok(Configuration { + pam_service: table + .get("pam_service") + .and_then(Value::as_str) + .unwrap_or("diya") + .to_string(), + login_session_command: table + .get("login_session_command") + .and_then(Value::as_str) + .ok_or("Missing login session command")? + .to_string(), + login_session_user: table + .get("login_session_user") + .and_then(Value::as_str) + .unwrap_or("root") + .to_string(), + user_session_command: table + .get("user_session_command") + .and_then(Value::as_str) + .ok_or("Missing user session command")? + .to_string(), + }) + } + + /// Get the PAM service name. + /// + /// # Returns + /// * A string slice representing the PAM service name. + /// Defaults to "diya" if not specified in the configuration. + pub fn pam_service(&self) -> &str { + &self.pam_service + } + + /// Get the login session command. + /// + /// # Returns + /// * A string slice representing the login session command. + pub fn login_session_command(&self) -> &str { + &self.login_session_command + } + /// Get the login session user. + /// + /// # Returns + /// * A string slice representing the user to run login sessions as. + /// * Defaults to "root" if not specified in the configuration. + pub fn login_session_user(&self) -> &str { + &self.login_session_user + } + + /// Get the user session command. + /// + /// # Returns + /// * A string slice representing the user session command. + pub fn user_session_command(&self) -> &str { + &self.user_session_command + } +} + +impl fmt::Display for Configuration { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!(f, "Configuration:")?; + writeln!(f, " - PAM Service: {}", self.pam_service)?; + writeln!( + f, + " - Login Session Command: {}", + self.login_session_command + )?; + writeln!(f, " - Login Session User: {}", self.login_session_user)?; + writeln!(f, " - User Session Command: {}", self.user_session_command)?; + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..c4dd1d1 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,27 @@ +//! # dysm-rs main library +//! +//! +//! +#![warn( + trivial_casts, + trivial_numeric_casts, + unused_extern_crates, + unused_qualifications, + unused_results, + missing_docs, + clippy::pedantic, + clippy::missing_docs_in_private_items +)] + +pub mod configuration; +pub mod session; +pub use session::SessionManager; +pub use session::SessionManagerInterface; + +/// The version of the dysm-rs library, derived from the Cargo package version. +pub const VERSION: &str = env!("CARGO_PKG_VERSION"); +/// The application name for logging and identification purposes. +pub const APPLICATION_NAME: &str = "dysm-rs"; + +#[cfg(test)] +pub mod tests; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..5d000bb --- /dev/null +++ b/src/main.rs @@ -0,0 +1,36 @@ +use clap::{Arg, Command}; +use dysm_rs::{SessionManager, SessionManagerInterface}; +use std::path::PathBuf; + +const DEFAULT_CONFIG_PATH: &str = "/etc/diya/daemon.conf"; + +fn main() { + let matches = Command::new("dysm") + .version(env!("CARGO_PKG_VERSION")) + .author("Dany LE ") + .about("Diya Session Manager") + .arg( + Arg::new("config") + .short('c') + .long("config") + .num_args(1) + .value_name("FILE") + .value_hint(clap::ValueHint::FilePath) + .help("Path to configuration file"), + ) + .arg( + Arg::new("session") + .short('s') + .long("session") + .help("Using session DEBUS instead of system") + .action(clap::ArgAction::SetTrue), + ) + .get_matches(); + let default_config_path = PathBuf::from(DEFAULT_CONFIG_PATH); + let config_file = matches + .get_one::("config") + .map(PathBuf::from) + .unwrap_or(default_config_path); + let is_dbus_session = matches.get_flag("session"); + while let true = SessionManager::instance().run(&config_file, is_dbus_session) {} +} diff --git a/src/session.rs b/src/session.rs new file mode 100644 index 0000000..eb033ac --- /dev/null +++ b/src/session.rs @@ -0,0 +1,272 @@ +//! Session management for the display manager. +//! +//! This module defines the `SessionManager` struct, which is responsible for +//! managing user sessions in the display manager. It includes functionality +//! for loading configuration, starting and stopping sessions, and +//! handling session-related events. +use crate::configuration::Configuration; +use crate::{APPLICATION_NAME, VERSION}; +use gio::{self, DBusConnection, OwnerId, ResourceLookupFlags}; +use glib::{g_debug, g_info, g_warning}; +use lazy_static::*; +use std::path::PathBuf; +use std::sync::{Arc, Mutex}; +use std::time::Duration; + +static G_DBUS_INTROSPECTION: &str = include_str!("../resources/introspection.xml"); +const G_DBUS_SERVER_NAME: &str = "dev.iohub.diya.SessionManager"; +const G_DBUS_SERVER_PATH: &str = "/dev/iohub/diya/SessionManager"; +const G_DBUS_SERVER_ERROR_NAME: &str = "dev.iohub.diya.SessionManager.Error"; +const G_DBUS_METHOD_LOGIN: &str = "login"; +const G_PROCESSES_MON_TO: u64 = 500; // Interval for monitoring child processes in milliseconds + +lazy_static! { + /// static global mutex variable that stores the current state of the session manager process, which can be either Running, End, or Reload. + /// This variable is used to control the behavior of the session manager based on signals received (e.g., SIGHUP for reload, SIGINT/SIGTERM for shutdown). + static ref G_SESSION_MANAGER: Arc> = Arc::new(Mutex::::new(SessionManagerState::End)); +} + +pub enum SessionManagerState { + Running(SessionManager), + End, + Reload, +} + +/// SessionManager +/// +/// The `SessionManager` struct is responsible for managing user sessions in the display manager. +pub struct SessionManager { + /// the configuration for the session manager + config: Configuration, + /// The main loop for handling session events. + main_loop: glib::MainLoop, + // introspection: gio::DBusNodeInfo, + bus_owner_id: Option, + signals: Vec, +} + +pub trait SessionManagerInterface { + fn run(&self, config: &PathBuf, is_dbus_session: bool) -> bool; + fn quit(&self, reload: bool); +} + +trait SessionManagerCallback { + fn on_name_acquired(&self, connection: DBusConnection, name: &str); + fn on_bus_acquired(&self, connection: DBusConnection, name: &str); + fn on_name_lost(&self, connection: Option, name: &str); + fn on_method_call( + &self, + connection: DBusConnection, + sender: Option<&str>, + object_path: &str, + interface_name: Option<&str>, + method_name: &str, + parameters: gio::glib::Variant, + invocation: gio::DBusMethodInvocation, + ); + fn on_process_monitor(&self); +} + +impl SessionManagerInterface for Arc> { + fn run(&self, config: &PathBuf, is_dbus_session: bool) -> bool { + self.quit(false); + *self.lock().as_deref_mut().unwrap() = SessionManagerState::Running(SessionManager { + config: Configuration::from(&config).expect("Failed to load configuration"), + main_loop: glib::MainLoop::new(None, false), + bus_owner_id: None, + signals: Vec::new(), + }); + let evtloop = match self.lock().as_deref_mut().unwrap() { + SessionManagerState::Running(session) => { + session.setup(is_dbus_session); + session.main_loop.clone() + } + _ => panic!("No SessionManager created"), + }; + evtloop.run(); + if let SessionManagerState::Reload = self.lock().as_deref().unwrap() { + true + } else { + false + } + } + + fn quit(&self, reload: bool) { + if let SessionManagerState::Running(session) = self.lock().as_deref().unwrap() { + session.main_loop.quit(); + } + *self.lock().as_deref_mut().unwrap() = if reload { + SessionManagerState::Reload + } else { + SessionManagerState::End + }; + } +} + +impl SessionManagerCallback for Arc> { + fn on_name_acquired(&self, _connection: DBusConnection, name: &str) { + g_debug!(APPLICATION_NAME, "D-Bus name acquired: {}", name); + } + + fn on_bus_acquired(&self, connection: DBusConnection, name: &str) { + g_debug!(APPLICATION_NAME, "D-BUS acquired for name {}", name); + let introspection = gio::DBusNodeInfo::for_xml(G_DBUS_INTROSPECTION).unwrap(); + let interface = &introspection.interfaces()[0]; + let _ = connection + .register_object(G_DBUS_SERVER_PATH, interface) + .method_call( + |connection: DBusConnection, + sender: Option<&str>, + object_path: &str, + interface_name: Option<&str>, + method_name: &str, + parameters: gio::glib::Variant, + invocation: gio::DBusMethodInvocation| { + SessionManager::instance().on_method_call( + connection, + sender, + object_path, + interface_name, + method_name, + parameters, + invocation, + ); + }, + ) + .build() + .expect("Failed to register D-Bus object"); + } + + fn on_name_lost(&self, _connection: Option, name: &str) { + g_warning!(APPLICATION_NAME, "D-Bus name lost: {}", name); + if let SessionManagerState::Running(session) = self.lock().as_deref_mut().unwrap() { + session.bus_owner_id = None; + } + self.quit(false); + } + + fn on_method_call( + &self, + connection: DBusConnection, + sender: Option<&str>, + object_path: &str, + interface_name: Option<&str>, + method_name: &str, + parameters: gio::glib::Variant, + invocation: gio::DBusMethodInvocation, + ) { + if let SessionManagerState::Running(session) = self.lock().as_deref_mut().unwrap() { + if !matches!(interface_name, Some(G_DBUS_SERVER_NAME)) { + let msg = format!( + "Interface name mismatch: got {}, but expected {}", + interface_name.unwrap(), + G_DBUS_SERVER_NAME + ); + g_warning!(APPLICATION_NAME, "{}", msg); + invocation.return_dbus_error(G_DBUS_SERVER_ERROR_NAME, &msg); + return; + } + g_debug!( + APPLICATION_NAME, + "Received method call: {}, interface: {}", + method_name, + interface_name.unwrap() + ); + match method_name { + G_DBUS_METHOD_LOGIN => session.login(¶meters, invocation), + _ => { + let msg = format!("Unsupported method {}", method_name); + g_warning!(APPLICATION_NAME, "{}", msg); + invocation.return_dbus_error(G_DBUS_SERVER_ERROR_NAME, &msg); + } + } + } + } + + fn on_process_monitor(&self) {} +} + +impl SessionManager { + pub fn instance() -> Arc> { + G_SESSION_MANAGER.clone() + } + /// Set up the session manager. + /// + /// This method starts the main loop and begins handling session events. + /// It will block until the main loop is quit, at which point it will return. + fn setup(&mut self, is_dbus_session: bool) { + g_info!( + APPLICATION_NAME, + "Starting SessionManager version {}", + VERSION + ); + g_debug!(APPLICATION_NAME, "{}", self.config); + g_debug!(APPLICATION_NAME, "Use session DBUS: {}", is_dbus_session); + self.bus_owner_id = Some(gio::bus_own_name( + if is_dbus_session { + gio::BusType::Session + } else { + gio::BusType::System + }, + G_DBUS_SERVER_NAME, + gio::BusNameOwnerFlags::NONE, + move |connection, name| { + SessionManager::instance().on_bus_acquired(connection, name); + }, + |connection, name| { + SessionManager::instance().on_name_acquired(connection, name); + }, + move |connection, name| { + SessionManager::instance().on_name_lost(connection, name); + }, + )); + + for signal in vec![libc::SIGINT, libc::SIGTERM, libc::SIGHUP] { + let signal_id = match signal { + libc::SIGHUP => glib::unix_signal_add_local(signal, move || { + g_warning!(APPLICATION_NAME, "Received SIGHUP, reloading service..."); + SessionManager::instance().quit(true); + glib::ControlFlow::Continue + }), + _ => glib::unix_signal_add_local(signal, move || { + g_info!( + APPLICATION_NAME, + "Received signal {}, shutting down...", + signal + ); + SessionManager::instance().quit(false); + glib::ControlFlow::Continue + }), + }; + self.signals.push(signal_id); + } + let processes_monitor_id = + glib::timeout_add_local(Duration::from_millis(G_PROCESSES_MON_TO), move || { + // Monitor child processes here + // For example, you could check if any child processes have exited and handle that accordingly + // g_debug!(APPLICATION_NAME, "Monitoring child processes..."); + SessionManager::instance().on_process_monitor(); + glib::ControlFlow::Continue + }); + self.signals.push(processes_monitor_id); + // Implementation for running the session manager + } + + fn login(&self, parameters: &gio::glib::Variant, invocation: gio::DBusMethodInvocation) { + g_debug!(APPLICATION_NAME, "Logging called"); + invocation.return_dbus_error(G_DBUS_SERVER_ERROR_NAME, "Unimplemented"); + } +} + +impl Drop for SessionManager { + fn drop(&mut self) { + g_info!(APPLICATION_NAME, "Shutting down SessionManager"); + if let Some(bus_owner_id) = self.bus_owner_id.take() { + g_debug!(APPLICATION_NAME, "Releasing acquired D-Bus name"); + gio::bus_unown_name(bus_owner_id); + } + for signal_id in self.signals.drain(..) { + signal_id.remove(); + } + } +} diff --git a/src/tests.rs b/src/tests.rs new file mode 100644 index 0000000..243fb78 --- /dev/null +++ b/src/tests.rs @@ -0,0 +1,22 @@ +//! Tests for the dysm-rs library. +//! +//! This module contains tests for the `dysm-rs` library + +use super::configuration::*; + +#[test] +fn test_configuration_loading() { + let config_path = std::path::PathBuf::from("references/daemon.conf"); + let config = Configuration::from(&config_path).expect("Failed to load configuration"); + + assert_eq!(config.pam_service(), "diya"); + assert_eq!( + config.login_session_command(), + "/usr/bin/diyac -x /usr/bin/diya-login-shell" + ); + assert_eq!(config.login_session_user(), "xdg"); + assert_eq!( + config.user_session_command(), + "/usr/bin/diyac -x /usr/bin/diya-shell" + ); +}