Compare commits
7 Commits
c060924eeb
...
a256c54ecf
| Author | SHA1 | Date | |
|---|---|---|---|
| a256c54ecf | |||
| 0cca54c151 | |||
| 3db633a726 | |||
| f48dc565a0 | |||
| 28480080ce | |||
| 803b9d9d77 | |||
| cff1ccc265 |
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
/target
|
||||||
|
.cargo
|
||||||
28
Cargo.toml
Normal file
28
Cargo.toml
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
[package]
|
||||||
|
name = "dysm-rs"
|
||||||
|
version = "0.1.1"
|
||||||
|
edition = "2021"
|
||||||
|
authors = ["Dany LE"]
|
||||||
|
description = "Diya Session Manager"
|
||||||
|
repository = "https://git.iohub.dev/diya/dysm-rs.git"
|
||||||
|
license = "MIT"
|
||||||
|
readme = "README.md"
|
||||||
|
keywords = ["session manager", "PAM"]
|
||||||
|
categories = ["daemon", "service"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
clap = "4"
|
||||||
|
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"
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
opt-level = 3
|
||||||
|
# 's' for size
|
||||||
|
lto = true
|
||||||
|
# panic = 'abort'
|
||||||
|
codegen-units = 1
|
||||||
45
references/daemon.conf
Normal file
45
references/daemon.conf
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
# 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
|
||||||
|
# if not specified, default to diya
|
||||||
|
pam_service = "diya"
|
||||||
|
|
||||||
|
# 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
|
||||||
|
# this configuration is mandatory
|
||||||
|
default_session_command = "/usr/bin/diyac -x /usr/bin/diya-login-shell"
|
||||||
|
|
||||||
|
# default session user
|
||||||
|
# The user that owns the default session, root by default
|
||||||
|
# if this setting is not set
|
||||||
|
default_session_user = "xdg"
|
||||||
|
|
||||||
|
# if false keep only one session at a time
|
||||||
|
# open a new session will close any previously opened session
|
||||||
|
# default true (if not specified), optional
|
||||||
|
enable_multiple_session = false
|
||||||
|
|
||||||
|
# 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
|
||||||
|
# this configuration is mandatory
|
||||||
|
user_session_command = "/usr/bin/diyac -x /usr/bin/diya-shell"
|
||||||
|
|
||||||
|
# setting environment variables
|
||||||
|
# when opening a session, optional
|
||||||
|
[session.envars]
|
||||||
|
# format key = string value
|
||||||
|
# example
|
||||||
|
DBUS_SESSION_BUS_ADDRESS = "unix:path=/tmp/dbus-1"
|
||||||
|
DBUS_SESSION_BUS_PID = "3598"
|
||||||
|
|
||||||
29
references/diya-dbus.conf
Normal file
29
references/diya-dbus.conf
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<!DOCTYPE busconfig PUBLIC
|
||||||
|
"-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
|
||||||
|
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
|
||||||
|
<busconfig>
|
||||||
|
|
||||||
|
<!-- Only root can own the service -->
|
||||||
|
<policy user="root">
|
||||||
|
<allow own="dev.iohub.diya.SessionManager"/>
|
||||||
|
</policy>
|
||||||
|
|
||||||
|
<!--deny all access from another user-->
|
||||||
|
<policy context="default">
|
||||||
|
<deny send_destination="dev.iohub.diya.SessionManager"/>
|
||||||
|
<deny receive_sender="dev.iohub.diya.SessionManager"/>
|
||||||
|
</policy>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
<policy group="xdg">
|
||||||
|
<allow send_destination="dev.iohub.diya.SessionManager"/>
|
||||||
|
<allow receive_sender="dev.iohub.diya.SessionManager"/>
|
||||||
|
</policy>
|
||||||
|
-->
|
||||||
|
|
||||||
|
<policy user="root">
|
||||||
|
<allow send_destination="dev.iohub.diya.SessionManager"/>
|
||||||
|
<allow receive_sender="dev.iohub.diya.SessionManager"/>
|
||||||
|
</policy>
|
||||||
|
|
||||||
|
</busconfig>
|
||||||
10
resources/introspection.xml
Normal file
10
resources/introspection.xml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<node name = "/dev/iohub/diya">
|
||||||
|
<interface name='dev.iohub.diya.SessionManager'>
|
||||||
|
<method name='login'>
|
||||||
|
<!--annotation name='org.gtk.GDBus.Annotation' value='OnMethod' /-->
|
||||||
|
<arg type='s' name='user' direction='in' />
|
||||||
|
<arg type='s' name='password' direction='in' />
|
||||||
|
<arg type='b' name='result' direction='out' />
|
||||||
|
</method>
|
||||||
|
</interface>
|
||||||
|
</node>
|
||||||
611
src/auth.rs
Normal file
611
src/auth.rs
Normal file
@@ -0,0 +1,611 @@
|
|||||||
|
//! 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::collections::HashMap;
|
||||||
|
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 setup
|
||||||
|
///
|
||||||
|
/// # 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 for 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<pam_response> = 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::<pam_response>()) 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 be implemented on any session object (PamSession, AnonymousSession)
|
||||||
|
/// that wants to export public APIs for user authentication and session opening
|
||||||
|
///
|
||||||
|
pub trait Session {
|
||||||
|
/// Perform authentication for user check
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// - `&self` - Object that implements this trait
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// - `Result<(), Box<dyn std::error::Error>>`
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Any error during authentication phrase
|
||||||
|
///
|
||||||
|
fn authenticate(&self) -> Result<(), Box<dyn std::error::Error>>;
|
||||||
|
/// 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
|
||||||
|
/// - `envars` (`&HashMap<String, String>`) - environment variable for the new session
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// - `Result<(), Box<dyn std::error::Error>>`
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Any error raised during session opening
|
||||||
|
///
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
command: &str,
|
||||||
|
envars: &HashMap<String, String>,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>>;
|
||||||
|
|
||||||
|
/// End the currently running session
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// - `&self` - Object that implement this trait
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// - `Result<(), Box<dyn std::error::Error>>`
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Any error raised
|
||||||
|
fn end(&self) -> Result<(), Box<dyn std::error::Error>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Trait that provide privilege dropping for new user session
|
||||||
|
///
|
||||||
|
/// This trait shall be implemented 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
|
||||||
|
/// - `envars` (`&HashMap<String, String>`) - environment variable for the new session
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// - `Result<(), Box<dyn std::error::Error>>`
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// Any error
|
||||||
|
///
|
||||||
|
fn drop_and_run(
|
||||||
|
&self,
|
||||||
|
command: &str,
|
||||||
|
envars: &HashMap<String, String>,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
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 mut envs_c_string = vec![CString::new(format!("USER={}", self.username()))?];
|
||||||
|
for (k, v) in envars {
|
||||||
|
envs_c_string.push(CString::new(format!("{}={}", k, v))?);
|
||||||
|
}
|
||||||
|
let envs: Vec<&CStr> = envs_c_string.iter().map(|e| e.as_c_str()).collect();
|
||||||
|
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<Self, Box<dyn std::error::Error>>`
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// any error
|
||||||
|
pub fn new(
|
||||||
|
service: &str,
|
||||||
|
username: &str,
|
||||||
|
password: &str,
|
||||||
|
) -> Result<Self, Box<dyn std::error::Error>> {
|
||||||
|
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<dyn std::error::Error>>`
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// any error
|
||||||
|
fn pam_set_item(&self, flag: i32, value: &str) -> Result<&Self, Box<dyn std::error::Error>> {
|
||||||
|
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<dyn std::error::Error>>`
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// any error
|
||||||
|
fn pam_authenticate(&self) -> Result<&Self, Box<dyn std::error::Error>> {
|
||||||
|
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<dyn std::error::Error>>`
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// any error
|
||||||
|
fn pam_check_account(&self) -> Result<&Self, Box<dyn std::error::Error>> {
|
||||||
|
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<dyn std::error::Error>>`
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// any error
|
||||||
|
fn pam_set_credentials(&self, flag: i32) -> Result<&Self, Box<dyn std::error::Error>> {
|
||||||
|
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<dyn std::error::Error>>`
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// any error
|
||||||
|
fn pam_open_session(&self) -> Result<&Self, Box<dyn std::error::Error>> {
|
||||||
|
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<dyn std::error::Error>>`
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// any error
|
||||||
|
fn pam_close_session(&self) -> Result<&Self, Box<dyn std::error::Error>> {
|
||||||
|
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<dyn std::error::Error>> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
command: &str,
|
||||||
|
envars: &HashMap<String, String>,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
self.drop_and_run(command, envars)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Session for PamSession {
|
||||||
|
fn authenticate(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
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,
|
||||||
|
envars: &HashMap<String, String>,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
self.pam_set_credentials(PAM_ESTABLISH_CRED)?
|
||||||
|
.pam_open_session()?
|
||||||
|
.drop_and_run(command, envars)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
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<PamAppData>
|
||||||
|
if !self.appdata.is_null() {
|
||||||
|
drop(Box::from_raw(self.appdata));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
177
src/configuration.rs
Normal file
177
src/configuration.rs
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
//! # 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::INFO;
|
||||||
|
use core::fmt;
|
||||||
|
use std::{collections::HashMap, 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 default sessions (optional).
|
||||||
|
default_session_command: Option<String>,
|
||||||
|
/// 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,
|
||||||
|
/// flag to enable multiple session
|
||||||
|
enable_multiple_session: bool,
|
||||||
|
/// Session environment variables
|
||||||
|
session_envars: HashMap<String, 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<dyn std::error::Error>)` if there was an error reading the file or
|
||||||
|
/// parsing the TOML content.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use dysm_rs::configuration::Configuration;
|
||||||
|
/// use std::path::PathBuf;
|
||||||
|
///
|
||||||
|
/// let _ = Configuration::from(&PathBuf::from("references/daemon.conf")).unwrap();
|
||||||
|
/// ```
|
||||||
|
pub fn from(file: &PathBuf) -> Result<Self, Box<dyn std::error::Error>> {
|
||||||
|
INFO!("Loading configuration from: {}", file.display());
|
||||||
|
let content = std::fs::read_to_string(file)?;
|
||||||
|
let content = content.parse::<Value>()?;
|
||||||
|
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(),
|
||||||
|
default_session_command: table
|
||||||
|
.get("default_session_command")
|
||||||
|
.and_then(Value::as_str)
|
||||||
|
.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(),
|
||||||
|
user_session_command: table
|
||||||
|
.get("user_session_command")
|
||||||
|
.and_then(Value::as_str)
|
||||||
|
.ok_or("Missing user session command")?
|
||||||
|
.to_string(),
|
||||||
|
enable_multiple_session: table
|
||||||
|
.get("enable_multiple_session")
|
||||||
|
.and_then(Value::as_bool)
|
||||||
|
.unwrap_or(true),
|
||||||
|
session_envars: table
|
||||||
|
.get("session")
|
||||||
|
.and_then(Value::as_table)
|
||||||
|
.and_then(|table| table.get("envars"))
|
||||||
|
.and_then(Value::as_table)
|
||||||
|
.and_then(|table| {
|
||||||
|
Some(
|
||||||
|
table
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|(k, v)| {
|
||||||
|
v.as_str()
|
||||||
|
.and_then(|value| Some((k.to_string(), value.to_string())))
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.unwrap_or(HashMap::new()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 default session command.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// * 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 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 default_session_user(&self) -> &str {
|
||||||
|
&self.default_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
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Is multiple session enabled
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// * bool value
|
||||||
|
pub fn enable_multiple_session(&self) -> bool {
|
||||||
|
self.enable_multiple_session
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Session default environment variables
|
||||||
|
///
|
||||||
|
/// # Returs
|
||||||
|
/// HashMap<String, String>
|
||||||
|
pub fn session_envars(&self) -> &HashMap<String, String> {
|
||||||
|
&self.session_envars
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
" - Default Session Command: {}",
|
||||||
|
self.default_session_command
|
||||||
|
.as_ref()
|
||||||
|
.map(|v| v.as_str())
|
||||||
|
.unwrap_or("None")
|
||||||
|
)?;
|
||||||
|
writeln!(f, " - Default Session User: {}", self.default_session_user)?;
|
||||||
|
writeln!(f, " - User Session Command: {}", self.user_session_command)?;
|
||||||
|
writeln!(
|
||||||
|
f,
|
||||||
|
" - Enable multiple session: {}",
|
||||||
|
self.enable_multiple_session
|
||||||
|
)?;
|
||||||
|
writeln!(f, " - Session environment variables:")?;
|
||||||
|
for (k, v) in &self.session_envars {
|
||||||
|
writeln!(f, " + {} = '{}'", k, v)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
76
src/lib.rs
Normal file
76
src/lib.rs
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
//! # 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 auth;
|
||||||
|
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";
|
||||||
|
|
||||||
|
/// 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;
|
||||||
36
src/main.rs
Normal file
36
src/main.rs
Normal file
@@ -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 <dany@iohub.dev>")
|
||||||
|
.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::<String>("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) {}
|
||||||
|
}
|
||||||
539
src/session.rs
Normal file
539
src/session.rs
Normal file
@@ -0,0 +1,539 @@
|
|||||||
|
//! Session management for the display manager.
|
||||||
|
//!
|
||||||
|
//! This module defines the `SessionManager` struct, which is responsible for
|
||||||
|
//! 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::{CRITICAL, DEBUG, INFO, VERSION, WARNING};
|
||||||
|
use gio::glib::variant::ToVariant;
|
||||||
|
use gio::glib::Variant;
|
||||||
|
use gio::{self, DBusConnection, OwnerId, RegistrationId};
|
||||||
|
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";
|
||||||
|
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! {
|
||||||
|
/// A global instance of the `SessionManager` wrapped in a `Mutex` for thread safety.
|
||||||
|
/// This allows the session manager to be accessed and modified from different parts of the application
|
||||||
|
/// while ensuring that only one instance is active at a time.
|
||||||
|
static ref G_SESSION_MANAGER: Arc<Mutex<SessionManagerState>> = Arc::new(Mutex::<SessionManagerState>::new(SessionManagerState::End));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// SessionManagerState
|
||||||
|
///
|
||||||
|
/// An enum representing the state of the session manager. It can be in a running state with an active `SessionManager`,
|
||||||
|
/// or it can be in an end or reload state.
|
||||||
|
pub enum SessionManagerState {
|
||||||
|
/// The session manager is currently running and managing user sessions.
|
||||||
|
Running(SessionManager),
|
||||||
|
/// The session manager has been stopped and is no longer managing sessions.
|
||||||
|
End,
|
||||||
|
/// The session manager is in the process of reloading its configuration or restarting.
|
||||||
|
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,
|
||||||
|
/// The D-Bus owner ID for the session manager's D-Bus name.
|
||||||
|
bus_owner_id: Option<OwnerId>,
|
||||||
|
/// A list of signal handler IDs for any signals the session manager has connected to.
|
||||||
|
signals: Vec<glib::SourceId>,
|
||||||
|
/// 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<Pid>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Public interface for SessionManager
|
||||||
|
///
|
||||||
|
pub trait SessionManagerInterface {
|
||||||
|
/// Run the session manager with an input config file
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// - `&self` - Object that implements the Trait
|
||||||
|
/// - `config` (`&PathBuf`) - Config file.
|
||||||
|
/// - `is_dbus_session` (`bool`) - run the session manager using session DBUS or system DBUS
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// - `bool` - if True service shall be restarted after finished
|
||||||
|
///
|
||||||
|
fn run(&self, config: &PathBuf, is_dbus_session: bool) -> bool;
|
||||||
|
|
||||||
|
/// Quit the current session manager service
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// - `&self` - Object that implements the Trait
|
||||||
|
/// - `reload` (`bool`) - if true the service shall be restarted after finished
|
||||||
|
///
|
||||||
|
fn quit(&self, reload: bool);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Trait that defines callbacks for SessionManager
|
||||||
|
///
|
||||||
|
trait SessionManagerCallback {
|
||||||
|
/// Handler for DBUS name acquired event
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// - `&self` - Object that implements the Trait.
|
||||||
|
/// - `connection` (`DBusConnection`) - DBUS connection.
|
||||||
|
/// - `name` (`&str`) - DBUS name.
|
||||||
|
///
|
||||||
|
fn on_name_acquired(&self, connection: DBusConnection, name: &str);
|
||||||
|
|
||||||
|
/// Handler for DBUS bus acquired event
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// - `&self` - Object that implements this Trait
|
||||||
|
/// - `connection` (`DBusConnection`) - DBUS connection
|
||||||
|
/// - `name` (`&str`) - DBUS name
|
||||||
|
///
|
||||||
|
fn on_bus_acquired(&self, connection: DBusConnection, name: &str);
|
||||||
|
|
||||||
|
/// Hander for DBUS name lost event
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// - `&self` - Object that implements this Trait
|
||||||
|
/// - `connection` (`Option<DBusConnection>`) - DBUS connection.
|
||||||
|
/// - `name` (`&str`) - DBUS name.
|
||||||
|
///
|
||||||
|
fn on_name_lost(&self, connection: Option<DBusConnection>, name: &str);
|
||||||
|
|
||||||
|
/// Handler for DBUS method call event
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// - `&self` (`undefined`) - Object that implements this Trait.
|
||||||
|
/// - `connection` (`DBusConnection`) - DBUS connection.
|
||||||
|
/// - `sender` (`Option<&str>`) - Sender name.
|
||||||
|
/// - `object_path` (`&str`) - DBUS Object path.
|
||||||
|
/// - `interface_name` (`Option<&str>`) - DBUS interface name.
|
||||||
|
/// - `method_name` (`&str`) - DBUS method name.
|
||||||
|
/// - `parameters` (`gio::glib::Variant`) - DBUS method parameters.
|
||||||
|
/// - `invocation` (`gio::DBusMethodInvocation`) - DBUS method invocation object.
|
||||||
|
///
|
||||||
|
fn on_method_call(
|
||||||
|
&self,
|
||||||
|
connection: DBusConnection,
|
||||||
|
sender: Option<&str>,
|
||||||
|
object_path: &str,
|
||||||
|
interface_name: Option<&str>,
|
||||||
|
method_name: &str,
|
||||||
|
parameters: Variant,
|
||||||
|
invocation: gio::DBusMethodInvocation,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Hander for service processes monitor
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// - `&self` - Object that implements this Trait
|
||||||
|
///
|
||||||
|
fn on_processes_monitor(&self);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SessionManagerInterface for Arc<Mutex<SessionManagerState>> {
|
||||||
|
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(),
|
||||||
|
bus_registration: None,
|
||||||
|
pids: 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();
|
||||||
|
match self.lock().as_deref().unwrap() {
|
||||||
|
SessionManagerState::Reload => true,
|
||||||
|
_ => 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<Mutex<SessionManagerState>> {
|
||||||
|
fn on_name_acquired(&self, _connection: DBusConnection, name: &str) {
|
||||||
|
DEBUG!("D-Bus name acquired: {}", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_bus_acquired(&self, connection: DBusConnection, name: &str) {
|
||||||
|
DEBUG!("D-BUS acquired for name {}", name);
|
||||||
|
let introspection = match gio::DBusNodeInfo::for_xml(G_DBUS_INTROSPECTION) {
|
||||||
|
Ok(info) => info,
|
||||||
|
Err(err) => {
|
||||||
|
CRITICAL!("Failed to parse D-Bus introspection data: {}", err);
|
||||||
|
self.quit(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let interface = &introspection.interfaces()[0];
|
||||||
|
|
||||||
|
let registration_id = match 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: Variant,
|
||||||
|
invocation: gio::DBusMethodInvocation| {
|
||||||
|
SessionManager::instance().on_method_call(
|
||||||
|
connection,
|
||||||
|
sender,
|
||||||
|
object_path,
|
||||||
|
interface_name,
|
||||||
|
method_name,
|
||||||
|
parameters,
|
||||||
|
invocation,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
{
|
||||||
|
Err(error) => {
|
||||||
|
CRITICAL!(
|
||||||
|
"Failed to register D-Bus object at path {}: {}",
|
||||||
|
G_DBUS_SERVER_PATH,
|
||||||
|
error
|
||||||
|
);
|
||||||
|
self.quit(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Ok(resource) => resource,
|
||||||
|
};
|
||||||
|
if let SessionManagerState::Running(session) = self.lock().as_deref_mut().unwrap() {
|
||||||
|
session.bus_registration = Some((connection, registration_id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn on_name_lost(&self, _connection: Option<DBusConnection>, name: &str) {
|
||||||
|
CRITICAL!("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: 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
|
||||||
|
);
|
||||||
|
WARNING!("{}", msg);
|
||||||
|
invocation.return_dbus_error(G_DBUS_SERVER_ERROR_NAME, &msg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
DEBUG!(
|
||||||
|
"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);
|
||||||
|
WARNING!("{}", msg);
|
||||||
|
invocation.return_dbus_error(G_DBUS_SERVER_ERROR_NAME, &msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_processes_monitor(&self) {
|
||||||
|
if let SessionManagerState::Running(session) = self.lock().as_deref_mut().unwrap() {
|
||||||
|
session.monitor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SessionManager {
|
||||||
|
/// Get a threadsafe singleton instance of the SessionManager
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// - `Arc<Mutex<SessionManagerState>>` - Smart pointer to SessionManager
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use dysm_rs::SessionManager;
|
||||||
|
///
|
||||||
|
/// let instance = SessionManager::instance();
|
||||||
|
/// ```
|
||||||
|
pub fn instance() -> Arc<Mutex<SessionManagerState>> {
|
||||||
|
G_SESSION_MANAGER.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Setup the session manager before running
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// - `&mut self` (`&mut SessionManager`) - Mutable reference to SessionManager.
|
||||||
|
/// - `is_dbus_session` (`bool`) - if true setup service using DBUS session, otherwise SBUS system
|
||||||
|
///
|
||||||
|
fn setup(&mut self, is_dbus_session: bool) {
|
||||||
|
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
|
||||||
|
} 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 || {
|
||||||
|
WARNING!("Received SIGHUP, reloading service...");
|
||||||
|
SessionManager::instance().quit(true);
|
||||||
|
glib::ControlFlow::Continue
|
||||||
|
}),
|
||||||
|
_ => glib::unix_signal_add_local(signal, move || {
|
||||||
|
WARNING!("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
|
||||||
|
// DEBUG!( "Monitoring child processes...");
|
||||||
|
SessionManager::instance().on_processes_monitor();
|
||||||
|
glib::ControlFlow::Continue
|
||||||
|
});
|
||||||
|
self.signals.push(processes_monitor_id);
|
||||||
|
// Implementation for running the session manager
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Process DBUS login method cal
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// - `&self` (`&mut SessionManager`) - Mutable reference to current SessionManager.
|
||||||
|
/// - `parameters` (`&gio::glib::Variant`) - DBUS method parameters.
|
||||||
|
/// - `invocation` (`gio::DBusMethodInvocation`) - DBUS method invocation object.
|
||||||
|
///
|
||||||
|
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(()) => {
|
||||||
|
DEBUG!("Authenticate success");
|
||||||
|
invocation.return_value(Some(&Variant::tuple_from_iter([true.to_variant()])));
|
||||||
|
if !self.config.enable_multiple_session() {
|
||||||
|
self.drop_all_sessions();
|
||||||
|
}
|
||||||
|
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<Pid, Box<dyn std::error::Error>>` - The Pid of new opened process
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Any error when execute the command
|
||||||
|
///
|
||||||
|
fn open_session(
|
||||||
|
&self,
|
||||||
|
command: &str,
|
||||||
|
session: &impl Session,
|
||||||
|
) -> Result<Pid, Box<dyn std::error::Error>> {
|
||||||
|
match unsafe { fork() }? {
|
||||||
|
ForkResult::Parent { child, .. } => {
|
||||||
|
DEBUG!("Opened session at PID={}", child);
|
||||||
|
Ok(child)
|
||||||
|
}
|
||||||
|
ForkResult::Child => {
|
||||||
|
session
|
||||||
|
.run(command, self.config.session_envars())
|
||||||
|
.expect("This should not happend");
|
||||||
|
panic!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Monitor session processes
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// - `&self` (`&mut SessionManager`) - Mutable reference to current SessionManager.
|
||||||
|
///
|
||||||
|
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, None);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Drop all opened session (including the default one)
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// - `&mut self` (`SessionManager`) - SessionManager
|
||||||
|
///
|
||||||
|
fn drop_all_sessions(&mut self) {
|
||||||
|
DEBUG!("Dropping all running sessions");
|
||||||
|
// kill all running session
|
||||||
|
for pid in self.pids.drain(..) {
|
||||||
|
let _ = kill(pid, Signal::SIGKILL);
|
||||||
|
let _ = waitpid(pid, None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for SessionManager {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
INFO!("Shutting down SessionManager");
|
||||||
|
// unregistered dbus object
|
||||||
|
if let Some((connection, registration_id)) = self.bus_registration.take() {
|
||||||
|
DEBUG!("Unregistering D-Bus object");
|
||||||
|
connection
|
||||||
|
.unregister_object(registration_id)
|
||||||
|
.unwrap_or_else(|e| {
|
||||||
|
CRITICAL!("Unable to unregister object from D-Bus connection: {}", e)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
self.drop_all_sessions();
|
||||||
|
DEBUG!("Removing signal handlers");
|
||||||
|
for signal_id in self.signals.drain(..) {
|
||||||
|
signal_id.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
35
src/tests.rs
Normal file
35
src/tests.rs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
//! 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.default_session_command(),
|
||||||
|
Some("/usr/bin/diyac -x /usr/bin/diya-login-shell")
|
||||||
|
);
|
||||||
|
assert_eq!(config.default_session_user(), "xdg");
|
||||||
|
assert_eq!(
|
||||||
|
config.user_session_command(),
|
||||||
|
"/usr/bin/diyac -x /usr/bin/diya-shell"
|
||||||
|
);
|
||||||
|
assert_eq!(config.enable_multiple_session(), false);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
config
|
||||||
|
.session_envars()
|
||||||
|
.get("DBUS_SESSION_BUS_ADDRESS")
|
||||||
|
.unwrap(),
|
||||||
|
"unix:path=/tmp/dbus-1"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
config.session_envars().get("DBUS_SESSION_BUS_PID").unwrap(),
|
||||||
|
"3598"
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user