Files
dysm-rs/src/auth.rs

589 lines
16 KiB
Rust

//! 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<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 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<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
///
/// # Returns
///
/// - `Result<(), Box<dyn std::error::Error>>`
///
/// # Errors
///
/// Any error raised during session opening
///
fn run(&self, command: &str) -> 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 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<dyn std::error::Error>>`
///
/// # Errors
/// Any error
///
fn drop_and_run(&self, command: &str) -> 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 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<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) -> Result<(), Box<dyn std::error::Error>> {
self.drop_and_run(command)
}
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) -> Result<(), Box<dyn std::error::Error>> {
self.pam_set_credentials(PAM_ESTABLISH_CRED)?
.pam_open_session()?
.drop_and_run(command)
}
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));
}
}
}
}