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"