From 28480080ce0ed2f8f53ba07dec9cecdc105636fd Mon Sep 17 00:00:00 2001 From: Dany LE Date: Wed, 25 Mar 2026 21:50:50 +0100 Subject: [PATCH] add the completed port of diys-session-manager --- Cargo.toml | 6 +- references/daemon.conf | 15 +- references/diya-dbus.conf | 29 ++ src/auth.rs | 588 ++++++++++++++++++++++++++++++++++++++ src/configuration.rs | 51 ++-- src/lib.rs | 49 ++++ src/session.rs | 193 +++++++++---- src/tests.rs | 6 +- 8 files changed, 845 insertions(+), 92 deletions(-) create mode 100644 references/diya-dbus.conf create mode 100644 src/auth.rs diff --git a/Cargo.toml b/Cargo.toml index 6b46fa5..f8f5ed2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,8 +5,10 @@ edition = "2021" [dependencies] clap = "4" -gio = "0.22.2" -glib = "0.21.5" +gio = "0.21" +glib = { version="0.21"} lazy_static = "1.5.0" libc = "0.2.183" +libpam-sys = "0.2.0" +nix = {version ="0.31.2", features = ["user", "process", "signal"]} toml = "0.8" diff --git a/references/daemon.conf b/references/daemon.conf index b057fe0..21b54ad 100644 --- a/references/daemon.conf +++ b/references/daemon.conf @@ -9,16 +9,17 @@ # 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 +# Default session command +# The command to run to start a default session when there are'nt +# any session ruining. Often, this default session will be a login +# session that handles 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" +default_session_command = "/usr/bin/diyac -x /usr/bin/diya-login-shell" -# login session user -# The user that owns the login session, root by default +# default session user +# The user that owns the default session, root by default # if this setting is not set -login_session_user = "xdg" +default_session_user = "xdg" # User session command # The command to run to start a user session after the diff --git a/references/diya-dbus.conf b/references/diya-dbus.conf new file mode 100644 index 0000000..a118ce7 --- /dev/null +++ b/references/diya-dbus.conf @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/auth.rs b/src/auth.rs new file mode 100644 index 0000000..2f82323 --- /dev/null +++ b/src/auth.rs @@ -0,0 +1,588 @@ +//! Authentication module +//! +//! This module is used by the session manager to authenticate a user +//! and open a new user session if success +use crate::CRITICAL; +use crate::DEBUG; +use crate::ERR; +use crate::INFO; +use crate::WARNING; +use libpam_sys::pam_acct_mgmt; +use libpam_sys::pam_authenticate; +use libpam_sys::pam_close_session; +use libpam_sys::pam_conv; +use libpam_sys::pam_end; +use libpam_sys::pam_handle; +use libpam_sys::pam_message; +use libpam_sys::pam_open_session; +use libpam_sys::pam_response; +use libpam_sys::pam_set_item; +use libpam_sys::pam_setcred; +use libpam_sys::pam_start; +use libpam_sys::pam_strerror; +use libpam_sys::PAM_BUF_ERR; +use libpam_sys::PAM_DELETE_CRED; +use libpam_sys::PAM_ERROR_MSG; +use libpam_sys::PAM_ESTABLISH_CRED; +use libpam_sys::PAM_PROMPT_ECHO_OFF; +use libpam_sys::PAM_PROMPT_ECHO_ON; +use libpam_sys::PAM_SUCCESS; +use libpam_sys::PAM_TEXT_INFO; +use libpam_sys::PAM_TTY; +use libpam_sys::PAM_USER; +use libpam_sys::PAM_XDISPLAY; +use nix::unistd::execvpe; +use nix::unistd::setgroups; +use nix::unistd::User; +use nix::unistd::{getgrouplist, initgroups, setgid, setuid}; +use std::ffi::{CStr, CString}; +use std::os::raw::c_void; +use std::ptr; + +/// PAM based session +/// +/// Session that use PAM for user authentication and session initializing +/// +/// # Fields +/// +/// - `pamh` (`*mut pam_handle`) - low level PAM handle +/// - `appdata` (`*mut PamAppData`) - Application data +/// +pub struct PamSession { + pamh: *mut pam_handle, + appdata: *mut PamAppData, +} + +/// Anonymous session +/// +/// This type of session will ignore any authentication check +/// and open immediately a user session based on input user +/// +/// # Fields +/// +/// - `username` (`String`) - User of the session +/// +pub struct AnonymousSession { + username: String, +} + +/// Private application data form PamSession +/// +/// # Fields +/// +/// - `username` (`String`) - user name +/// - `password` (`String`) - password that will be remove after authentication +/// +#[derive(Clone)] +struct PamAppData { + pub username: String, + pub password: String, +} + +unsafe impl Send for PamSession {} +unsafe impl Sync for PamSession {} + +/// PAM conversation handle +/// +/// # Arguments +/// +/// - `num_msg` (`i32`) - number of messages +/// - `msg` (`*const *const pam_message`) - list of messages +/// - `resp` (`*mut *mut pam_response`) - list of response +/// - `appdata_ptr` (`*mut c_void`) - user data +/// +/// # Returns +/// +/// - `i32` - status code +/// +/// # Safety +/// +/// - **This function is `unsafe` because:** +/// - Low level type conversion +/// - Memory allocation +unsafe extern "C" fn pam_conv_handler( + num_msg: i32, + msg: *const *const pam_message, + resp: *mut *mut pam_response, + appdata_ptr: *mut c_void, +) -> i32 { + unsafe { + let appdata = &*(appdata_ptr as *mut PamAppData); + DEBUG!("pam_conv_handler: called with data {}", appdata.username,); + let mut replies: Vec = Vec::with_capacity(num_msg as usize); + + for i in 0..num_msg { + let m = *msg.add(i as usize); + let response = match (*m).msg_style { + PAM_PROMPT_ECHO_OFF => { + DEBUG!("PAM password request"); + let c_pwd = CString::new(appdata.password.clone()).unwrap(); + pam_response { + resp: c_pwd.into_raw(), + resp_retcode: 0, + } + } + PAM_PROMPT_ECHO_ON => { + let c_user = CString::new(appdata.username.clone()).unwrap(); + pam_response { + resp: c_user.into_raw(), + resp_retcode: 0, + } + } + PAM_TEXT_INFO => { + INFO!( + "PAM_TEXT_INFO: {}", + CStr::from_ptr((*m).msg).to_string_lossy().into_owned() + ); + pam_response { + resp: ptr::null_mut(), + resp_retcode: 0, + } + } + PAM_ERROR_MSG => { + CRITICAL!( + "PAM_ERROR_MSG: {}", + CStr::from_ptr((*m).msg).to_string_lossy().into_owned() + ); + pam_response { + resp: ptr::null_mut(), + resp_retcode: 0, + } + } + _ => pam_response { + resp: ptr::null_mut(), + resp_retcode: 0, + }, + }; + + replies.push(response); + } + let arr = libc::calloc(replies.len(), size_of::()) as *mut pam_response; + if arr.is_null() { + return PAM_BUF_ERR; + } + for (i, r) in replies.into_iter().enumerate() { + *arr.add(i) = r; + } + *resp = arr; + PAM_SUCCESS + } +} + +/// Public trait for all session object +/// +/// This interface shall implemented for any session object (PamSession, AnonymousSession) +/// that want to export public API for user authentication and session opening +/// +pub trait Session { + /// Perform authentication for user check + /// + /// # Arguments + /// + /// - `&self` - Object that implements this trait + /// + /// # Returns + /// + /// - `Result<(), Box>` + /// + /// # Errors + /// + /// Any error during authentication phrase + /// + fn authenticate(&self) -> Result<(), Box>; + /// Open a new user session after a successful authentication + /// + /// # Arguments + /// + /// - `&self` - Object that implement this trait + /// - `command` (`&str`) - Command to executed in a new user session + /// + /// # Returns + /// + /// - `Result<(), Box>` + /// + /// # Errors + /// + /// Any error raised during session opening + /// + fn run(&self, command: &str) -> Result<(), Box>; + + /// End the currently running session + /// + /// # Arguments + /// + /// - `&self` - Object that implement this trait + /// + /// # Returns + /// + /// - `Result<(), Box>` + /// + /// # Errors + /// + /// Any error raised + fn end(&self) -> Result<(), Box>; +} + +/// Trait that provide privilege dropping for new user session +/// +/// This trait shall be implement on any Session object +/// +trait DropPrivilege { + /// Drop the process privilege to current user + /// + /// # Arguments + /// + /// - `&self` - Object that define this trait + /// - `command` (`&str`) - Command that will be executed un the user session after privilege dropping + /// + /// # Returns + /// + /// - `Result<(), Box>` + /// + /// # Errors + /// Any error + /// + fn drop_and_run(&self, command: &str) -> Result<(), Box> { + DEBUG!("Run command: {} as user: {}", command, self.username()); + let user = + User::from_name(self.username())?.ok_or(ERR!("Unknown user {}", self.username()))?; + let username_c = CString::new(self.username())?; + let username_c_str = username_c.as_c_str(); + initgroups(username_c_str, user.gid)?; + let groups = getgrouplist(username_c_str, user.gid)?; + setgid(user.gid)?; + setgroups(&groups)?; + setuid(user.uid)?; + let shell = CString::new("/bin/sh")?; + let arg0 = CString::new("sh")?; + let arg_c = CString::new("-c")?; + let arg_cmd = CString::new(command)?; + let args = vec![arg0.as_c_str(), arg_c.as_c_str(), arg_cmd.as_c_str()]; + let env_user = CString::new(format!("USER={}", self.username()))?; + let envs = vec![env_user.as_c_str()]; + execvpe(shell.as_c_str(), &args, &envs)?; + Ok(()) + } + + /// Getter to get the session username + /// + /// # Arguments + /// + /// - `&self` - Object that implements this trait + /// + /// # Returns + /// + /// - `&str` - username + /// + fn username(&self) -> &str; +} + +impl PamSession { + /// Create a new PamSession object + /// + /// # Arguments + /// + /// - `service` (`&str`) - PAM service + /// - `username` (`&str`) - session user name + /// - `password` (`&str`) - session user password + /// + /// # Returns + /// + /// - `Result>` + /// + /// # Errors + /// + /// any error + pub fn new( + service: &str, + username: &str, + password: &str, + ) -> Result> { + let mut pamh: *mut pam_handle = ptr::null_mut(); + + let c_service = CString::new(service)?; + let c_user = CString::new(username)?; + + // allocate user/pass for conv function + let boxed_data = Box::new(PamAppData { + username: username.to_string(), + password: password.to_string(), + }); + + let appdata_ptr = Box::into_raw(boxed_data); + + let mut conv = pam_conv { + conv: pam_conv_handler, + appdata_ptr: appdata_ptr as *mut _, + }; + DEBUG!( + "Open PAM session with service: {} for user {}", + service, + username + ); + let code = unsafe { pam_start(c_service.as_ptr(), c_user.as_ptr(), &mut conv, &mut pamh) }; + if code != PAM_SUCCESS { + unsafe { + drop(Box::from_raw(appdata_ptr)); + } + Err(Self::pam_err(pamh, code))?; + } + + Ok(Self { + pamh, + appdata: appdata_ptr, + }) + } + + /// Utility method that convert low level PAM error to std::io::Error + /// + /// # Arguments + /// + /// - `pamh` (`*mut pam_handle`) - PAM handle + /// - `code` (`i32`) - PAM error code + /// + /// # Returns + /// + /// - `std::io::Error` + fn pam_err(pamh: *mut pam_handle, code: i32) -> std::io::Error { + unsafe { + let msg = CStr::from_ptr(pam_strerror(pamh, code)); + std::io::Error::new( + std::io::ErrorKind::Other, + msg.to_string_lossy().into_owned(), + ) + } + } + + /// Wrapper for low-level pam_set_item + /// + /// # Arguments + /// + /// - `&self` (`PamSession`) - PamSession object + /// - `flag` (`i32`) - PAM flag + /// - `value` (`&str`) - value to set + /// + /// # Returns + /// + /// - `Result<&Self, Box>` + /// + /// # Errors + /// any error + fn pam_set_item(&self, flag: i32, value: &str) -> Result<&Self, Box> { + let data = CString::new(value)?; + let ret = unsafe { pam_set_item(self.pamh, flag, data.as_ptr() as *const c_void) }; + if ret != PAM_SUCCESS { + return Err(Self::pam_err(self.pamh, ret))?; + } + Ok(self) + } + + /// Wrapper for low level pam_authenticate + /// + /// # Arguments + /// + /// - `&self` (`PamSession`) - PamSession object + /// + /// # Returns + /// + /// - `Result<&Self, Box>` + /// + /// # Errors + /// any error + fn pam_authenticate(&self) -> Result<&Self, Box> { + let ret = unsafe { pam_authenticate(self.pamh, 0) }; + // remove password from memory + unsafe { + let appdata = &mut (*self.appdata); + appdata.password = "".to_string(); + } + if ret != PAM_SUCCESS { + return Err(Self::pam_err(self.pamh, ret))?; + } + Ok(self) + } + + /// Wrapper for low level pam_acct_mgmt + /// + /// # Arguments + /// + /// - `&self` (`PamSession`) - PamSession object + /// + /// # Returns + /// + /// - `Result<&Self, Box>` + /// + /// # Errors + /// any error + fn pam_check_account(&self) -> Result<&Self, Box> { + let ret = unsafe { pam_acct_mgmt(self.pamh, 0) }; + if ret != PAM_SUCCESS { + return Err(Self::pam_err(self.pamh, ret))?; + } + Ok(self) + } + + /// Wrapper for low level pam_setcred + /// + /// # Arguments + /// + /// - `&self` (`PamSession`) - PamSession object + /// - `flag` (`i32`) - PAM flag + /// + /// # Returns + /// + /// - `Result<&Self, Box>` + /// + /// # Errors + /// + /// any error + fn pam_set_credentials(&self, flag: i32) -> Result<&Self, Box> { + let ret = unsafe { pam_setcred(self.pamh, flag) }; + if ret != PAM_SUCCESS { + return Err(Self::pam_err(self.pamh, ret))?; + } + Ok(self) + } + + /// Wrapper for low level pam_open_session + /// + /// # Arguments + /// + /// - `&self` (`PamSession`) - PamSession object + /// + /// # Returns + /// + /// - `Result<&Self, Box>` + /// + /// # Errors + /// + /// any error + fn pam_open_session(&self) -> Result<&Self, Box> { + let ret = unsafe { pam_open_session(self.pamh, 0) }; + if ret != PAM_SUCCESS { + return Err(Self::pam_err(self.pamh, ret))?; + } + Ok(self) + } + + /// Wrapper for low level pam_close_session + /// + /// # Arguments + /// + /// - `&self` (`PamSession`) - PamSession object + /// + /// # Returns + /// + /// - `Result<&Self, Box>` + /// + /// # Errors + /// + /// any error + fn pam_close_session(&self) -> Result<&Self, Box> { + let ret = unsafe { pam_close_session(self.pamh, 0) }; + if ret != PAM_SUCCESS { + return Err(Self::pam_err(self.pamh, ret))?; + } + Ok(self) + } + + /// Wrapper for low level pam_end + /// + /// # Arguments + /// + /// - `&self` (`PamSession`) - PamSession object + /// + fn pam_end(&self) { + unsafe { + let _ = pam_end(self.pamh, PAM_SUCCESS); + } + } +} + +impl DropPrivilege for PamSession { + fn username(&self) -> &str { + let appdata = unsafe { &*(self.appdata as *mut PamAppData) }; + &appdata.username + } +} + +impl Session for AnonymousSession { + fn authenticate(&self) -> Result<(), Box> { + Ok(()) + } + + fn run(&self, command: &str) -> Result<(), Box> { + self.drop_and_run(command) + } + + fn end(&self) -> Result<(), Box> { + Ok(()) + } +} + +impl Session for PamSession { + fn authenticate(&self) -> Result<(), Box> { + if let Ok(var) = std::env::var("DISPLAY") { + let _ = self.pam_set_item(PAM_XDISPLAY, &var)?; + } + if let Ok(var) = std::env::var("TTY") { + let _ = self.pam_set_item(PAM_TTY, &var)?; + } + let _ = self + .pam_set_item(PAM_USER, self.username())? + .pam_authenticate()? + .pam_check_account()?; + Ok(()) + } + + fn run(&self, command: &str) -> Result<(), Box> { + self.pam_set_credentials(PAM_ESTABLISH_CRED)? + .pam_open_session()? + .drop_and_run(command) + } + + fn end(&self) -> Result<(), Box> { + let _ = self.pam_close_session(); + let _ = self.pam_set_credentials(PAM_DELETE_CRED); + Ok(self.pam_end()) + } +} + +impl AnonymousSession { + /// Create new AnonymousSession + /// + /// # Arguments + /// + /// - `username` (`&str`) - username + /// + /// # Returns + /// + /// - `Self` + /// + pub fn new(username: &str) -> Self { + AnonymousSession { + username: username.to_string(), + } + } +} + +impl DropPrivilege for AnonymousSession { + fn username(&self) -> &str { + &self.username + } +} + +impl Drop for PamSession { + fn drop(&mut self) { + DEBUG!("Drop PAM session for user {}", self.username()); + if let Err(error) = self.end() { + WARNING!("Unable to properly close PAM session: {}", error); + } + unsafe { + // free the Box + if !self.appdata.is_null() { + drop(Box::from_raw(self.appdata)); + } + } + } +} diff --git a/src/configuration.rs b/src/configuration.rs index 188ce77..ceadc37 100644 --- a/src/configuration.rs +++ b/src/configuration.rs @@ -6,9 +6,8 @@ //! 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 crate::INFO; use core::fmt; -use glib::g_info; use std::path::PathBuf; use toml::Value; @@ -18,10 +17,10 @@ use toml::Value; 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 default sessions (optional). + default_session_command: Option, + /// The user to run default sessions as (default: "root"). + default_session_user: String, // optional (default root) /// The command to execute for user sessions (required). user_session_command: String, } @@ -47,11 +46,7 @@ impl Configuration { /// let _ = Configuration::from(&PathBuf::from("references/daemon.conf")).unwrap(); /// ``` pub fn from(file: &PathBuf) -> Result> { - g_info!( - APPLICATION_NAME, - "Loading configuration from: {}", - file.display() - ); + INFO!("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")?; @@ -62,13 +57,12 @@ impl Configuration { .and_then(Value::as_str) .unwrap_or("diya") .to_string(), - login_session_command: table - .get("login_session_command") + default_session_command: table + .get("default_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(|str| Some(str.to_string())), + default_session_user: table + .get("default_session_user") .and_then(Value::as_str) .unwrap_or("root") .to_string(), @@ -89,20 +83,20 @@ impl Configuration { &self.pam_service } - /// Get the login session command. + /// Get the default session command. /// /// # Returns - /// * A string slice representing the login session command. - pub fn login_session_command(&self) -> &str { - &self.login_session_command + /// * A option representing the default session command. + pub fn default_session_command(&self) -> Option<&str> { + self.default_session_command.as_ref().map(|x| x.as_str()) } - /// Get the login session user. + /// Get the default 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 + pub fn default_session_user(&self) -> &str { + &self.default_session_user } /// Get the user session command. @@ -120,10 +114,13 @@ impl fmt::Display for Configuration { writeln!(f, " - PAM Service: {}", self.pam_service)?; writeln!( f, - " - Login Session Command: {}", - self.login_session_command + " - Default Session Command: {}", + self.default_session_command + .as_ref() + .map(|v| v.as_str()) + .unwrap_or("None") )?; - writeln!(f, " - Login Session User: {}", self.login_session_user)?; + writeln!(f, " - Default Session User: {}", self.default_session_user)?; writeln!(f, " - User Session Command: {}", self.user_session_command)?; Ok(()) } diff --git a/src/lib.rs b/src/lib.rs index c4dd1d1..ef20835 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,7 @@ clippy::missing_docs_in_private_items )] +pub mod auth; pub mod configuration; pub mod session; pub use session::SessionManager; @@ -23,5 +24,53 @@ pub const VERSION: &str = env!("CARGO_PKG_VERSION"); /// The application name for logging and identification purposes. pub const APPLICATION_NAME: &str = "dysm-rs"; +/// Macro for error log helper +/// +#[macro_export] +macro_rules! INFO { + ($($args:tt)*) => ({ + glib::g_info!(crate::APPLICATION_NAME, "[{}:{}]: {}", file!(), line!(), format_args!($($args)*)); + }) +} + +/// Macro for debug log helper +/// +#[macro_export] +macro_rules! DEBUG { + ($($args:tt)*) => ({ + glib::g_debug!(crate::APPLICATION_NAME, "[{}:{}]: {}", file!(), line!(), format_args!($($args)*)); + }) +} + +/// Macro for warning log helper +/// +#[macro_export] +macro_rules! WARNING { + ($($args:tt)*) => ({ + glib::g_warning!(crate::APPLICATION_NAME, "[{}:{}]: {}", file!(), line!(), format_args!($($args)*)); + }) +} + +/// Macro for critical log helper +/// +#[macro_export] +macro_rules! CRITICAL { + ($($args:tt)*) => ({ + glib::g_critical!(crate::APPLICATION_NAME, "[{}:{}]: {}", file!(), line!(), format_args!($($args)*)); + }) +} + +/// Return an Error Result object from error string +/// +#[macro_export] +macro_rules! ERR { + ($($args:tt)*) => { + std::io::Error::new( + std::io::ErrorKind::Other, + format!("({}:{}): {}", file!(), line!(), format_args!($($args)*)), + ) + }; +} + #[cfg(test)] pub mod tests; diff --git a/src/session.rs b/src/session.rs index fcc15e7..6c6f418 100644 --- a/src/session.rs +++ b/src/session.rs @@ -4,15 +4,19 @@ //! managing user sessions. It includes functionality //! for loading configuration, starting and stopping sessions, and //! handling session-related events. +use crate::auth::{AnonymousSession, PamSession, Session}; use crate::configuration::Configuration; -use crate::{APPLICATION_NAME, VERSION}; +use crate::{CRITICAL, DEBUG, INFO, VERSION, WARNING}; +use gio::glib::variant::ToVariant; +use gio::glib::Variant; use gio::{self, DBusConnection, OwnerId, RegistrationId}; -use glib::{g_critical, g_debug, g_info, g_warning}; use lazy_static::*; +use nix::sys::signal::{kill, Signal}; +use nix::sys::wait::{waitpid, WaitPidFlag, WaitStatus}; +use nix::unistd::{fork, ForkResult, Pid}; 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"; @@ -54,6 +58,8 @@ pub struct SessionManager { signals: Vec, /// The D-Bus connection and registration ID for the session manager's D-Bus object. bus_registration: Option<(DBusConnection, RegistrationId)>, + /// All session PIDs + pids: Vec, } /// Public interface for SessionManager @@ -136,7 +142,7 @@ trait SessionManagerCallback { object_path: &str, interface_name: Option<&str>, method_name: &str, - parameters: gio::glib::Variant, + parameters: Variant, invocation: gio::DBusMethodInvocation, ); @@ -158,6 +164,7 @@ impl SessionManagerInterface for Arc> { bus_owner_id: None, signals: Vec::new(), bus_registration: None, + pids: Vec::new(), }); let evtloop = match self.lock().as_deref_mut().unwrap() { SessionManagerState::Running(session) => { @@ -187,19 +194,15 @@ impl SessionManagerInterface for Arc> { impl SessionManagerCallback for Arc> { fn on_name_acquired(&self, _connection: DBusConnection, name: &str) { - g_debug!(APPLICATION_NAME, "D-Bus name acquired: {}", name); + DEBUG!("D-Bus name acquired: {}", name); } fn on_bus_acquired(&self, connection: DBusConnection, name: &str) { - g_debug!(APPLICATION_NAME, "D-BUS acquired for name {}", name); + DEBUG!("D-BUS acquired for name {}", name); let introspection = match gio::DBusNodeInfo::for_xml(G_DBUS_INTROSPECTION) { Ok(info) => info, Err(err) => { - g_critical!( - APPLICATION_NAME, - "Failed to parse D-Bus introspection data: {}", - err - ); + CRITICAL!("Failed to parse D-Bus introspection data: {}", err); self.quit(false); return; } @@ -214,7 +217,7 @@ impl SessionManagerCallback for Arc> { object_path: &str, interface_name: Option<&str>, method_name: &str, - parameters: gio::glib::Variant, + parameters: Variant, invocation: gio::DBusMethodInvocation| { SessionManager::instance().on_method_call( connection, @@ -230,8 +233,7 @@ impl SessionManagerCallback for Arc> { .build() { Err(error) => { - g_critical!( - APPLICATION_NAME, + CRITICAL!( "Failed to register D-Bus object at path {}: {}", G_DBUS_SERVER_PATH, error @@ -246,7 +248,7 @@ impl SessionManagerCallback for Arc> { } } fn on_name_lost(&self, _connection: Option, name: &str) { - g_critical!(APPLICATION_NAME, "D-Bus name lost: {}", name); + CRITICAL!("D-Bus name lost: {}", name); if let SessionManagerState::Running(session) = self.lock().as_deref_mut().unwrap() { session.bus_owner_id = None; } @@ -260,7 +262,7 @@ impl SessionManagerCallback for Arc> { _object_path: &str, interface_name: Option<&str>, method_name: &str, - parameters: gio::glib::Variant, + parameters: Variant, invocation: gio::DBusMethodInvocation, ) { if let SessionManagerState::Running(session) = self.lock().as_deref_mut().unwrap() { @@ -270,12 +272,11 @@ impl SessionManagerCallback for Arc> { interface_name.unwrap(), G_DBUS_SERVER_NAME ); - g_warning!(APPLICATION_NAME, "{}", msg); + WARNING!("{}", msg); invocation.return_dbus_error(G_DBUS_SERVER_ERROR_NAME, &msg); return; } - g_debug!( - APPLICATION_NAME, + DEBUG!( "Received method call: {}, interface: {}", method_name, interface_name.unwrap() @@ -284,7 +285,7 @@ impl SessionManagerCallback for Arc> { G_DBUS_METHOD_LOGIN => session.login(¶meters, invocation), _ => { let msg = format!("Unsupported method {}", method_name); - g_warning!(APPLICATION_NAME, "{}", msg); + WARNING!("{}", msg); invocation.return_dbus_error(G_DBUS_SERVER_ERROR_NAME, &msg); } } @@ -324,13 +325,9 @@ impl SessionManager { /// - `is_dbus_session` (`bool`) - if true setup service using DBUS session, otherwise SBUS system /// 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); + INFO!("Starting SessionManager version {}", VERSION); + DEBUG!("{}", self.config); + DEBUG!("Use session DBUS: {}", is_dbus_session); self.bus_owner_id = Some(gio::bus_own_name( if is_dbus_session { gio::BusType::Session @@ -353,16 +350,12 @@ impl SessionManager { 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..."); + WARNING!("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 - ); + WARNING!("Received signal {}, shutting down...", signal); SessionManager::instance().quit(false); glib::ControlFlow::Continue }), @@ -373,7 +366,7 @@ impl SessionManager { 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..."); + // DEBUG!( "Monitoring child processes..."); SessionManager::instance().on_processes_monitor(); glib::ControlFlow::Continue }); @@ -389,9 +382,69 @@ impl SessionManager { /// - `parameters` (`&gio::glib::Variant`) - DBUS method parameters. /// - `invocation` (`gio::DBusMethodInvocation`) - DBUS method invocation object. /// - 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"); + fn login(&mut self, parameters: &Variant, invocation: gio::DBusMethodInvocation) { + let (username, password) = match parameters.get::<(String, String)>() { + Some(data) => data, + None => { + WARNING!("DBUS login call, invalid input arguments"); + invocation.return_dbus_error(G_DBUS_SERVER_ERROR_NAME, "Invalid arguments"); + return; + } + }; + DEBUG!("Login request for user: {}", username); + let session = match PamSession::new(self.config.pam_service(), &username, &password) { + Ok(session) => session, + Err(error) => { + CRITICAL!("Unable to create PAM user session: {}", error); + invocation.return_dbus_error(G_DBUS_SERVER_NAME, "Unable to open login session"); + return; + } + }; + match session.authenticate() { + Ok(()) => { + invocation.return_value(Some(&Variant::tuple_from_iter([true.to_variant()]))); + match self.open_session(self.config.user_session_command(), &session) { + Ok(pid) => self.pids.push(pid), + Err(error) => CRITICAL!("Failed to run user session: {}", error), + } + } + Err(error) => { + WARNING!("Authentication failed: {}", error); + invocation.return_value(Some(&Variant::tuple_from_iter([false.to_variant()]))); + } + } + } + + /// Run a command as a user + /// + /// # Arguments + /// + /// - `command` (`&str`) - Command to execute + /// - `username` (`&str`) - User that own the process + /// + /// # Returns + /// + /// - `Result>` - The Pid of new opened process + /// + /// # Errors + /// + /// Any error when execute the command + /// + fn open_session( + &self, + command: &str, + session: &impl Session, + ) -> Result> { + match unsafe { fork() }? { + ForkResult::Parent { child, .. } => { + DEBUG!("Opened session at PID={}", child); + Ok(child) + } + ForkResult::Child => { + session.run(command).expect("This should not happend"); + panic!(); + } + } } /// Monitor session processes @@ -400,33 +453,67 @@ impl SessionManager { /// /// - `&self` (`&mut SessionManager`) - Mutable reference to current SessionManager. /// - fn monitor(&self) { - // Here you would implement the logic to monitor child processes. - // For example, you could check if any child processes have exited and handle that accordingly. - g_debug!(APPLICATION_NAME, "Monitoring child processes..."); + fn monitor(&mut self) { + // DEBUG!("Monitoring running session"); + if let (true, Some(default_command)) = + (self.pids.is_empty(), self.config.default_session_command()) + { + DEBUG!("No session is running, starting default session"); + let default_session_user = self.config.default_session_user(); + match self.open_session( + default_command, + &AnonymousSession::new(default_session_user), + ) { + Ok(pid) => self.pids.push(pid), + Err(err) => { + CRITICAL!("Unable to open default session: {}", err); + return; + } + } + } + self.pids + .retain(|pid| match waitpid(*pid, Some(WaitPidFlag::WNOHANG)) { + Ok(WaitStatus::Exited(pid, status)) => { + INFO!("Session PID={} exited with status {}", pid, status); + false + } + Ok(WaitStatus::Signaled(pid, signal, _)) => { + WARNING!("Session PID={} exited by a signal {}", pid, signal); + false + } + Ok(_) => true, + Err(error) => { + CRITICAL!("Unable to get session PID={} status: {}", pid, error); + let _ = kill(*pid, Signal::SIGKILL); + let _ = waitpid(*pid, Some(WaitPidFlag::WNOHANG)); + false + } + }); } } 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); - } + INFO!("Shutting down SessionManager"); + // unregistered dbus object if let Some((connection, registration_id)) = self.bus_registration.take() { - g_debug!(APPLICATION_NAME, "Unregistering D-Bus object"); + DEBUG!("Unregistering D-Bus object"); connection .unregister_object(registration_id) .unwrap_or_else(|e| { - g_critical!( - APPLICATION_NAME, - "Unable to unregister object from D-Bus connection: {}", - e - ) + CRITICAL!("Unable to unregister object from D-Bus connection: {}", e) }); } - g_debug!(APPLICATION_NAME, "Removing signal handlers"); + // unown dbus name + if let Some(bus_owner_id) = self.bus_owner_id.take() { + DEBUG!("Releasing acquired D-Bus name"); + gio::bus_unown_name(bus_owner_id); + } + // kill all running session + for pid in self.pids.drain(..) { + let _ = kill(pid, Signal::SIGINT); + } + DEBUG!("Removing signal handlers"); for signal_id in self.signals.drain(..) { signal_id.remove(); } diff --git a/src/tests.rs b/src/tests.rs index 243fb78..668d5bc 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -11,10 +11,10 @@ fn test_configuration_loading() { assert_eq!(config.pam_service(), "diya"); assert_eq!( - config.login_session_command(), - "/usr/bin/diyac -x /usr/bin/diya-login-shell" + config.default_session_command(), + Some("/usr/bin/diyac -x /usr/bin/diya-login-shell") ); - assert_eq!(config.login_session_user(), "xdg"); + assert_eq!(config.default_session_user(), "xdg"); assert_eq!( config.user_session_command(), "/usr/bin/diyac -x /usr/bin/diya-shell"